feat: iap (#1996)
This commit is contained in:
@@ -287,6 +287,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
return this.listedService
|
||||
}
|
||||
|
||||
public get alerts(): ExternalServices.AlertService {
|
||||
return this.alertService
|
||||
}
|
||||
|
||||
public computePrivateUsername(username: string): Promise<string | undefined> {
|
||||
return ComputePrivateUsername(this.options.crypto, username)
|
||||
}
|
||||
@@ -367,8 +371,12 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
|
||||
await this.handleStage(ExternalServices.ApplicationStage.StorageDecrypted_09)
|
||||
|
||||
this.apiService.loadHost()
|
||||
const host = this.apiService.loadHost()
|
||||
|
||||
this.httpService.setHost(host)
|
||||
|
||||
this.webSocketsService.loadWebSocketUrl()
|
||||
|
||||
await this.sessionManager.initializeFromDisk()
|
||||
|
||||
this.settingsService.initializeFromDisk()
|
||||
@@ -594,6 +602,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
|
||||
public async setCustomHost(host: string): Promise<void> {
|
||||
await this.setHost(host)
|
||||
|
||||
this.webSocketsService.setWebSocketUrl(undefined)
|
||||
}
|
||||
|
||||
@@ -1072,7 +1081,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.createProtocolService()
|
||||
this.diskStorageService.provideEncryptionProvider(this.protocolService)
|
||||
this.createChallengeService()
|
||||
this.createHttpManager()
|
||||
this.createLegacyHttpManager()
|
||||
this.createApiService()
|
||||
this.createHttpService()
|
||||
this.createUserServer()
|
||||
@@ -1238,6 +1247,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
void this.notifyEvent(ApplicationEvent.FeaturesUpdated)
|
||||
break
|
||||
}
|
||||
case ExternalServices.FeaturesEvent.DidPurchaseSubscription: {
|
||||
void this.notifyEvent(ApplicationEvent.DidPurchaseSubscription)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
Utils.assertUnreachable(event)
|
||||
}
|
||||
@@ -1385,7 +1398,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.services.push(this.componentManagerService)
|
||||
}
|
||||
|
||||
private createHttpManager() {
|
||||
private createLegacyHttpManager() {
|
||||
this.deprecatedHttpService = new InternalServices.SNHttpService(
|
||||
this.environment,
|
||||
this.options.appVersion,
|
||||
@@ -1399,7 +1412,6 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
this.environment,
|
||||
this.options.appVersion,
|
||||
SnjsVersion,
|
||||
this.options.defaultHost,
|
||||
this.apiService.processMetaObject.bind(this.apiService),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export class SNApiService
|
||||
this.invalidSessionObserver = observer
|
||||
}
|
||||
|
||||
public loadHost(): void {
|
||||
public loadHost(): string {
|
||||
const storedValue = this.storageService.getValue<string | undefined>(StorageKey.ServerHost)
|
||||
this.host =
|
||||
storedValue ||
|
||||
@@ -120,6 +120,8 @@ export class SNApiService
|
||||
_default_sync_server?: string
|
||||
}
|
||||
)._default_sync_server as string)
|
||||
|
||||
return this.host
|
||||
}
|
||||
|
||||
public async setHost(host: string): Promise<void> {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { PureCryptoInterface } from '@standardnotes/sncrypto-common'
|
||||
import { convertTimestampToMilliseconds } from '@standardnotes/utils'
|
||||
import {
|
||||
AlertService,
|
||||
FeaturesEvent,
|
||||
FeatureStatus,
|
||||
InternalEventBusInterface,
|
||||
StorageKey,
|
||||
@@ -203,6 +204,47 @@ describe('featuresService', () => {
|
||||
})
|
||||
|
||||
describe('updateRoles()', () => {
|
||||
it('setRoles should notify event if roles changed', async () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
|
||||
const mock = (featuresService['notifyEvent'] = jest.fn())
|
||||
|
||||
const newRoles = [...roles, RoleName.PlusUser]
|
||||
await featuresService.setRoles(newRoles)
|
||||
|
||||
expect(mock.mock.calls[0][0]).toEqual(FeaturesEvent.UserRolesChanged)
|
||||
})
|
||||
|
||||
it('should notify of subscription purchase', async () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
|
||||
const spy = jest.spyOn(featuresService, 'notifyEvent' as never)
|
||||
|
||||
const newRoles = [...roles, RoleName.ProUser]
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
|
||||
expect(spy.mock.calls[2][0]).toEqual(FeaturesEvent.DidPurchaseSubscription)
|
||||
})
|
||||
|
||||
it('should not notify of subscription purchase on initial roles load after sign in', async () => {
|
||||
storageService.getValue = jest.fn().mockReturnValue(roles)
|
||||
const featuresService = createService()
|
||||
featuresService.initializeFromDisk()
|
||||
featuresService['roles'] = []
|
||||
|
||||
const spy = jest.spyOn(featuresService, 'notifyEvent' as never)
|
||||
|
||||
const newRoles = [...roles, RoleName.ProUser]
|
||||
await featuresService.updateRolesAndFetchFeatures('123', newRoles)
|
||||
|
||||
const triggeredEvents = spy.mock.calls.map((call) => call[0])
|
||||
expect(triggeredEvents).not.toContain(FeaturesEvent.DidPurchaseSubscription)
|
||||
})
|
||||
|
||||
it('saves new roles to storage and fetches features if a role has been added', async () => {
|
||||
const newRoles = [...roles, RoleName.PlusUser]
|
||||
|
||||
@@ -631,7 +673,7 @@ describe('featuresService', () => {
|
||||
await featuresService.updateRolesAndFetchFeatures('123', [RoleName.CoreUser, RoleName.PlusUser])
|
||||
|
||||
sessionManager.isSignedIntoFirstPartyServer = jest.fn().mockReturnValue(false)
|
||||
featuresService.hasOnlineSubscription = jest.fn().mockReturnValue(false)
|
||||
featuresService.rolesIncludePaidSubscription = jest.fn().mockReturnValue(false)
|
||||
featuresService['completedSuccessfulFeaturesRetrieval'] = true
|
||||
|
||||
expect(featuresService.getFeatureStatus(FeatureIdentifier.MidnightTheme)).toBe(FeatureStatus.NoUserSubscription)
|
||||
|
||||
@@ -154,7 +154,7 @@ export class SNFeaturesService
|
||||
if (stage === ApplicationStage.FullSyncCompleted_13) {
|
||||
void this.addDarkTheme()
|
||||
|
||||
if (!this.hasOnlineSubscription()) {
|
||||
if (!this.rolesIncludePaidSubscription()) {
|
||||
const offlineRepo = this.getOfflineRepo()
|
||||
if (offlineRepo) {
|
||||
void this.downloadOfflineFeatures(offlineRepo)
|
||||
@@ -355,8 +355,12 @@ export class SNFeaturesService
|
||||
}
|
||||
|
||||
public async updateRolesAndFetchFeatures(userUuid: UuidString, roles: RoleName[]): Promise<void> {
|
||||
const previousRoles = this.roles
|
||||
|
||||
const userRolesChanged = this.haveRolesChanged(roles)
|
||||
|
||||
const isInitialLoadRolesChange = previousRoles.length === 0 && userRolesChanged
|
||||
|
||||
if (!userRolesChanged && !this.needsInitialFeaturesUpdate) {
|
||||
return
|
||||
}
|
||||
@@ -375,13 +379,23 @@ export class SNFeaturesService
|
||||
await this.didDownloadFeatures(features)
|
||||
}
|
||||
}
|
||||
|
||||
if (userRolesChanged && !isInitialLoadRolesChange) {
|
||||
if (this.rolesIncludePaidSubscription()) {
|
||||
await this.notifyEvent(FeaturesEvent.DidPurchaseSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async setRoles(roles: RoleName[]): Promise<void> {
|
||||
async setRoles(roles: RoleName[]): Promise<void> {
|
||||
const rolesChanged = !arraysEqual(this.roles, roles)
|
||||
|
||||
this.roles = roles
|
||||
if (!arraysEqual(this.roles, roles)) {
|
||||
|
||||
if (rolesChanged) {
|
||||
void this.notifyEvent(FeaturesEvent.UserRolesChanged)
|
||||
}
|
||||
|
||||
this.storageService.setValue(StorageKey.UserRoles, this.roles)
|
||||
}
|
||||
|
||||
@@ -434,14 +448,13 @@ export class SNFeaturesService
|
||||
return this.features.find((feature) => feature.identifier === featureId)
|
||||
}
|
||||
|
||||
hasOnlineSubscription(): boolean {
|
||||
const roles = this.roles
|
||||
rolesIncludePaidSubscription(): boolean {
|
||||
const unpaidRoles = [RoleName.CoreUser]
|
||||
return roles.some((role) => !unpaidRoles.includes(role))
|
||||
return this.roles.some((role) => !unpaidRoles.includes(role))
|
||||
}
|
||||
|
||||
public hasPaidOnlineOrOfflineSubscription(): boolean {
|
||||
return this.hasOnlineSubscription() || this.hasOfflineRepo()
|
||||
return this.rolesIncludePaidSubscription() || this.hasOfflineRepo()
|
||||
}
|
||||
|
||||
public rolesBySorting(roles: RoleName[]): RoleName[] {
|
||||
|
||||
Reference in New Issue
Block a user