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 { SubscriptionManagerInterface } from './SubscriptionManagerInterface' import { AppleIAPReceipt } from './AppleIAPReceipt' import { AvailableSubscriptions, getErrorFromErrorResponse, isErrorResponse } from '@standardnotes/responses' import { Subscription } from '@standardnotes/security' import { SubscriptionManagerEvent } from './SubscriptionManagerEvent' export class SubscriptionManager extends AbstractService implements SubscriptionManagerInterface, InternalEventHandlerInterface { private onlineSubscription?: Subscription private availableSubscriptions?: AvailableSubscriptions | undefined 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 { 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 { 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 }> { try { const result = await this.subscriptionApiService.acceptInvite(inviteUuid) if (isErrorResponse(result)) { return { success: false, message: getErrorFromErrorResponse(result).message } } return result.data } catch (error) { return { success: false, message: 'Could not accept invitation.' } } } async listSubscriptionInvitations(): Promise { try { const result = await this.subscriptionApiService.listInvites() if (isErrorResponse(result)) { return [] } return result.data.invitations ?? [] } catch (error) { return [] } } async inviteToSubscription(inviteeEmail: string): Promise { try { const result = await this.subscriptionApiService.invite(inviteeEmail) if (isErrorResponse(result)) { return false } return result.data.success === true } catch (error) { return false } } async cancelInvitation(inviteUuid: string): Promise { try { const result = await this.subscriptionApiService.cancelInvite(inviteUuid) if (isErrorResponse(result)) { return false } return result.data.success === true } catch (error) { return false } } private async fetchOnlineSubscription(): Promise { 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 { 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, ): Promise<{ success: true } | { success: false; message: string }> { try { const result = await this.subscriptionApiService.confirmAppleIAP({ ...params, subscription_token: subscriptionToken, }) if (isErrorResponse(result)) { return { success: false, message: getErrorFromErrorResponse(result).message } } return result.data } catch (error) { return { success: false, message: 'Could not confirm IAP.' } } } }