refactor: native feature management (#2350)
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
import { Invitation } from '@standardnotes/models'
|
||||
import { AppleIAPReceipt } from './AppleIAPReceipt'
|
||||
|
||||
export interface SubscriptionClientInterface {
|
||||
listSubscriptionInvitations(): Promise<Invitation[]>
|
||||
inviteToSubscription(inviteeEmail: string): Promise<boolean>
|
||||
cancelInvitation(inviteUuid: string): Promise<boolean>
|
||||
acceptInvitation(inviteUuid: string): Promise<{ success: true } | { success: false; message: string }>
|
||||
confirmAppleIAP(
|
||||
receipt: AppleIAPReceipt,
|
||||
subscriptionToken: string,
|
||||
): Promise<{ success: true } | { success: false; message: string }>
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { StorageServiceInterface } from './../Storage/StorageServiceInterface'
|
||||
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
|
||||
import { SubscriptionApiServiceInterface } from '@standardnotes/api'
|
||||
import { Invitation } from '@standardnotes/models'
|
||||
import { InternalEventBusInterface } from '..'
|
||||
@@ -6,8 +8,10 @@ import { SubscriptionManager } from './SubscriptionManager'
|
||||
describe('SubscriptionManager', () => {
|
||||
let subscriptionApiService: SubscriptionApiServiceInterface
|
||||
let internalEventBus: InternalEventBusInterface
|
||||
let sessions: SessionsClientInterface
|
||||
let storage: StorageServiceInterface
|
||||
|
||||
const createManager = () => new SubscriptionManager(subscriptionApiService, internalEventBus)
|
||||
const createManager = () => new SubscriptionManager(subscriptionApiService, sessions, storage, internalEventBus)
|
||||
|
||||
beforeEach(() => {
|
||||
subscriptionApiService = {} as jest.Mocked<SubscriptionApiServiceInterface>
|
||||
@@ -16,7 +20,12 @@ describe('SubscriptionManager', () => {
|
||||
subscriptionApiService.invite = jest.fn()
|
||||
subscriptionApiService.listInvites = jest.fn()
|
||||
|
||||
sessions = {} as jest.Mocked<SessionsClientInterface>
|
||||
|
||||
storage = {} as jest.Mocked<StorageServiceInterface>
|
||||
|
||||
internalEventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
internalEventBus.addEventHandler = jest.fn()
|
||||
})
|
||||
|
||||
it('should invite user by email to a shared subscription', async () => {
|
||||
|
||||
@@ -1,17 +1,117 @@
|
||||
import { StorageKey } from './../Storage/StorageKeys'
|
||||
import { ApplicationStage } from './../Application/ApplicationStage'
|
||||
import { StorageServiceInterface } from './../Storage/StorageServiceInterface'
|
||||
import { InternalEventInterface } from './../Internal/InternalEventInterface'
|
||||
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
|
||||
import { SubscriptionName } from '@standardnotes/common'
|
||||
import { convertTimestampToMilliseconds } from '@standardnotes/utils'
|
||||
import { ApplicationEvent } from './../Event/ApplicationEvent'
|
||||
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
|
||||
import { Invitation } from '@standardnotes/models'
|
||||
import { SubscriptionApiServiceInterface } from '@standardnotes/api'
|
||||
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { SubscriptionClientInterface } from './SubscriptionClientInterface'
|
||||
import { SubscriptionManagerInterface } from './SubscriptionManagerInterface'
|
||||
import { AppleIAPReceipt } from './AppleIAPReceipt'
|
||||
import { getErrorFromErrorResponse, isErrorResponse } from '@standardnotes/responses'
|
||||
import { AvailableSubscriptions, getErrorFromErrorResponse, isErrorResponse } from '@standardnotes/responses'
|
||||
import { Subscription } from '@standardnotes/security'
|
||||
import { SubscriptionManagerEvent } from './SubscriptionManagerEvent'
|
||||
|
||||
export class SubscriptionManager
|
||||
extends AbstractService<SubscriptionManagerEvent>
|
||||
implements SubscriptionManagerInterface, InternalEventHandlerInterface
|
||||
{
|
||||
private onlineSubscription?: Subscription
|
||||
private availableSubscriptions?: AvailableSubscriptions | undefined
|
||||
|
||||
export class SubscriptionManager extends AbstractService implements SubscriptionClientInterface {
|
||||
constructor(
|
||||
private subscriptionApiService: SubscriptionApiServiceInterface,
|
||||
private sessions: SessionsClientInterface,
|
||||
private storage: StorageServiceInterface,
|
||||
protected override internalEventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(internalEventBus)
|
||||
|
||||
internalEventBus.addEventHandler(this, ApplicationEvent.UserRolesChanged)
|
||||
internalEventBus.addEventHandler(this, ApplicationEvent.Launched)
|
||||
internalEventBus.addEventHandler(this, ApplicationEvent.SignedIn)
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
switch (event.type) {
|
||||
case ApplicationEvent.Launched: {
|
||||
void this.fetchOnlineSubscription()
|
||||
void this.fetchAvailableSubscriptions()
|
||||
break
|
||||
}
|
||||
|
||||
case ApplicationEvent.UserRolesChanged:
|
||||
case ApplicationEvent.SignedIn:
|
||||
void this.fetchOnlineSubscription()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public override async handleApplicationStage(stage: ApplicationStage): Promise<void> {
|
||||
if (stage === ApplicationStage.StorageDecrypted_09) {
|
||||
this.onlineSubscription = this.storage.getValue(StorageKey.Subscription)
|
||||
void this.notifyEvent(SubscriptionManagerEvent.DidFetchSubscription)
|
||||
}
|
||||
}
|
||||
|
||||
hasOnlineSubscription(): boolean {
|
||||
return this.onlineSubscription != undefined
|
||||
}
|
||||
|
||||
getOnlineSubscription(): Subscription | undefined {
|
||||
return this.onlineSubscription
|
||||
}
|
||||
|
||||
getAvailableSubscriptions(): AvailableSubscriptions | undefined {
|
||||
return this.availableSubscriptions
|
||||
}
|
||||
|
||||
get userSubscriptionName(): string {
|
||||
if (!this.onlineSubscription) {
|
||||
throw new Error('Attempting to get subscription name without a subscription.')
|
||||
}
|
||||
|
||||
if (
|
||||
this.availableSubscriptions &&
|
||||
this.availableSubscriptions[this.onlineSubscription.planName as SubscriptionName]
|
||||
) {
|
||||
return this.availableSubscriptions[this.onlineSubscription.planName as SubscriptionName].name
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
get userSubscriptionExpirationDate(): Date | undefined {
|
||||
if (!this.onlineSubscription) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return new Date(convertTimestampToMilliseconds(this.onlineSubscription.endsAt))
|
||||
}
|
||||
|
||||
get isUserSubscriptionExpired(): boolean {
|
||||
if (!this.onlineSubscription) {
|
||||
throw new Error('Attempting to check subscription expiration without a subscription.')
|
||||
}
|
||||
|
||||
if (!this.userSubscriptionExpirationDate) {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.userSubscriptionExpirationDate.getTime() < new Date().getTime()
|
||||
}
|
||||
|
||||
get isUserSubscriptionCanceled(): boolean {
|
||||
if (!this.onlineSubscription) {
|
||||
throw new Error('Attempting to check subscription expiration without a subscription.')
|
||||
}
|
||||
|
||||
return this.onlineSubscription.cancelled
|
||||
}
|
||||
|
||||
async acceptInvitation(inviteUuid: string): Promise<{ success: true } | { success: false; message: string }> {
|
||||
@@ -70,6 +170,48 @@ export class SubscriptionManager extends AbstractService implements Subscription
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchOnlineSubscription(): Promise<void> {
|
||||
if (!this.sessions.isSignedIn()) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.subscriptionApiService.getUserSubscription({ userUuid: this.sessions.userUuid })
|
||||
|
||||
if (isErrorResponse(result)) {
|
||||
return
|
||||
}
|
||||
|
||||
const subscription = result.data.subscription
|
||||
|
||||
this.handleReceivedOnlineSubscriptionFromServer(subscription)
|
||||
} catch (error) {
|
||||
void error
|
||||
}
|
||||
}
|
||||
|
||||
private handleReceivedOnlineSubscriptionFromServer(subscription: Subscription | undefined): void {
|
||||
this.onlineSubscription = subscription
|
||||
|
||||
this.storage.setValue(StorageKey.Subscription, subscription)
|
||||
|
||||
void this.notifyEvent(SubscriptionManagerEvent.DidFetchSubscription)
|
||||
}
|
||||
|
||||
private async fetchAvailableSubscriptions(): Promise<void> {
|
||||
try {
|
||||
const response = await this.subscriptionApiService.getAvailableSubscriptions()
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.availableSubscriptions = response.data
|
||||
} catch (error) {
|
||||
void error
|
||||
}
|
||||
}
|
||||
|
||||
async confirmAppleIAP(
|
||||
params: AppleIAPReceipt,
|
||||
subscriptionToken: string,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum SubscriptionManagerEvent {
|
||||
DidFetchSubscription = 'Subscription:DidFetchSubscription',
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ApplicationServiceInterface } from './../Service/ApplicationServiceInterface'
|
||||
import { Invitation } from '@standardnotes/models'
|
||||
import { AppleIAPReceipt } from './AppleIAPReceipt'
|
||||
import { AvailableSubscriptions } from '@standardnotes/responses'
|
||||
import { Subscription } from '@standardnotes/security'
|
||||
import { SubscriptionManagerEvent } from './SubscriptionManagerEvent'
|
||||
|
||||
export interface SubscriptionManagerInterface extends ApplicationServiceInterface<SubscriptionManagerEvent, unknown> {
|
||||
getOnlineSubscription(): Subscription | undefined
|
||||
getAvailableSubscriptions(): AvailableSubscriptions | undefined
|
||||
hasOnlineSubscription(): boolean
|
||||
|
||||
get userSubscriptionName(): string
|
||||
get userSubscriptionExpirationDate(): Date | undefined
|
||||
get isUserSubscriptionExpired(): boolean
|
||||
get isUserSubscriptionCanceled(): boolean
|
||||
|
||||
listSubscriptionInvitations(): Promise<Invitation[]>
|
||||
inviteToSubscription(inviteeEmail: string): Promise<boolean>
|
||||
cancelInvitation(inviteUuid: string): Promise<boolean>
|
||||
acceptInvitation(inviteUuid: string): Promise<{ success: true } | { success: false; message: string }>
|
||||
confirmAppleIAP(
|
||||
receipt: AppleIAPReceipt,
|
||||
subscriptionToken: string,
|
||||
): Promise<{ success: true } | { success: false; message: string }>
|
||||
}
|
||||
Reference in New Issue
Block a user