refactor: native feature management (#2350)
This commit is contained in:
6
packages/services/src/Domain/Api/ApiServiceEvent.ts
Normal file
6
packages/services/src/Domain/Api/ApiServiceEvent.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
export enum ApiServiceEvent {
|
||||
MetaReceived = 'MetaReceived',
|
||||
SessionRefreshed = 'SessionRefreshed',
|
||||
}
|
||||
5
packages/services/src/Domain/Api/ApiServiceEventData.ts
Normal file
5
packages/services/src/Domain/Api/ApiServiceEventData.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
import { SessionRefreshedData } from './SessionRefreshedData'
|
||||
import { MetaReceivedData } from './MetaReceivedData'
|
||||
|
||||
export type ApiServiceEventData = Either<MetaReceivedData, SessionRefreshedData>
|
||||
@@ -1,26 +1,17 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
import { FilesApiInterface } from '@standardnotes/files'
|
||||
import { Session } from '@standardnotes/domain-core'
|
||||
import { Role } from '@standardnotes/security'
|
||||
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { ApiServiceEvent } from './ApiServiceEvent'
|
||||
import { ApiServiceEventData } from './ApiServiceEventData'
|
||||
import { SNFeatureRepo } from '@standardnotes/models'
|
||||
import { ClientDisplayableError, HttpResponse } from '@standardnotes/responses'
|
||||
import { AnyFeatureDescription } from '@standardnotes/features'
|
||||
|
||||
/* istanbul ignore file */
|
||||
export interface ApiServiceInterface extends AbstractService<ApiServiceEvent, ApiServiceEventData>, FilesApiInterface {
|
||||
isThirdPartyHostUsed(): boolean
|
||||
|
||||
export enum ApiServiceEvent {
|
||||
MetaReceived = 'MetaReceived',
|
||||
SessionRefreshed = 'SessionRefreshed',
|
||||
downloadOfflineFeaturesFromRepo(
|
||||
repo: SNFeatureRepo,
|
||||
): Promise<{ features: AnyFeatureDescription[]; roles: string[] } | ClientDisplayableError>
|
||||
|
||||
downloadFeatureUrl(url: string): Promise<HttpResponse>
|
||||
}
|
||||
|
||||
export type MetaReceivedData = {
|
||||
userUuid: string
|
||||
userRoles: Role[]
|
||||
}
|
||||
|
||||
export type SessionRefreshedData = {
|
||||
session: Session
|
||||
}
|
||||
|
||||
export type ApiServiceEventData = Either<MetaReceivedData, SessionRefreshedData>
|
||||
|
||||
export interface ApiServiceInterface extends AbstractService<ApiServiceEvent, ApiServiceEventData>, FilesApiInterface {}
|
||||
|
||||
6
packages/services/src/Domain/Api/MetaReceivedData.ts
Normal file
6
packages/services/src/Domain/Api/MetaReceivedData.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Role } from '@standardnotes/security'
|
||||
|
||||
export type MetaReceivedData = {
|
||||
userUuid: string
|
||||
userRoles: Role[]
|
||||
}
|
||||
5
packages/services/src/Domain/Api/SessionRefreshedData.ts
Normal file
5
packages/services/src/Domain/Api/SessionRefreshedData.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Session } from '@standardnotes/domain-core'
|
||||
|
||||
export type SessionRefreshedData = {
|
||||
session: Session
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PreferenceServiceInterface } from './../Preferences/PreferenceServiceInterface'
|
||||
import { AsymmetricMessageServiceInterface } from './../AsymmetricMessage/AsymmetricMessageServiceInterface'
|
||||
import { SyncOptions } from './../Sync/SyncOptions'
|
||||
import { ImportDataReturnType } from './../Mutator/ImportDataUseCase'
|
||||
@@ -21,7 +22,7 @@ import { ComponentManagerInterface } from '../Component/ComponentManagerInterfac
|
||||
import { ApplicationEvent } from '../Event/ApplicationEvent'
|
||||
import { ApplicationEventCallback } from '../Event/ApplicationEventCallback'
|
||||
import { FeaturesClientInterface } from '../Feature/FeaturesClientInterface'
|
||||
import { SubscriptionClientInterface } from '../Subscription/SubscriptionClientInterface'
|
||||
import { SubscriptionManagerInterface } from '../Subscription/SubscriptionManagerInterface'
|
||||
import { DeviceInterface } from '../Device/DeviceInterface'
|
||||
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { MutatorClientInterface } from '../Mutator/MutatorClientInterface'
|
||||
@@ -66,6 +67,7 @@ export interface ApplicationInterface {
|
||||
setCustomHost(host: string): Promise<void>
|
||||
isThirdPartyHostUsed(): boolean
|
||||
isUsingHomeServer(): Promise<boolean>
|
||||
getNewSubscriptionToken(): Promise<string | undefined>
|
||||
|
||||
importData(data: BackupFile, awaitSync?: boolean): Promise<ImportDataReturnType>
|
||||
/**
|
||||
@@ -96,7 +98,7 @@ export interface ApplicationInterface {
|
||||
get mutator(): MutatorClientInterface
|
||||
get user(): UserClientInterface
|
||||
get files(): FilesClientInterface
|
||||
get subscriptions(): SubscriptionClientInterface
|
||||
get subscriptions(): SubscriptionManagerInterface
|
||||
get fileBackups(): BackupServiceInterface | undefined
|
||||
get sessions(): SessionsClientInterface
|
||||
get homeServer(): HomeServerServiceInterface | undefined
|
||||
@@ -104,6 +106,7 @@ export interface ApplicationInterface {
|
||||
get challenges(): ChallengeServiceInterface
|
||||
get alerts(): AlertService
|
||||
get asymmetric(): AsymmetricMessageServiceInterface
|
||||
get preferences(): PreferenceServiceInterface
|
||||
|
||||
readonly identifier: ApplicationIdentifier
|
||||
readonly platform: Platform
|
||||
|
||||
@@ -1,26 +1,47 @@
|
||||
import { ComponentArea, FeatureIdentifier } from '@standardnotes/features'
|
||||
import { ActionObserver, PermissionDialog, SNComponent, SNNote } from '@standardnotes/models'
|
||||
import { ComponentViewerItem } from './ComponentViewerItem'
|
||||
import {
|
||||
ComponentArea,
|
||||
ComponentFeatureDescription,
|
||||
EditorFeatureDescription,
|
||||
IframeComponentFeatureDescription,
|
||||
ThemeFeatureDescription,
|
||||
} from '@standardnotes/features'
|
||||
import {
|
||||
ActionObserver,
|
||||
ComponentInterface,
|
||||
ComponentOrNativeFeature,
|
||||
PermissionDialog,
|
||||
SNNote,
|
||||
} from '@standardnotes/models'
|
||||
|
||||
import { DesktopManagerInterface } from '../Device/DesktopManagerInterface'
|
||||
import { ComponentViewerInterface } from './ComponentViewerInterface'
|
||||
|
||||
export interface ComponentManagerInterface {
|
||||
urlForComponent(component: SNComponent): string | undefined
|
||||
urlForComponent(uiFeature: ComponentOrNativeFeature<ComponentFeatureDescription>): string | undefined
|
||||
setDesktopManager(desktopManager: DesktopManagerInterface): void
|
||||
componentsForArea(area: ComponentArea): SNComponent[]
|
||||
editorForNote(note: SNNote): SNComponent | undefined
|
||||
doesEditorChangeRequireAlert(from: SNComponent | undefined, to: SNComponent | undefined): boolean
|
||||
thirdPartyComponentsForArea(area: ComponentArea): ComponentInterface[]
|
||||
editorForNote(note: SNNote): ComponentOrNativeFeature<EditorFeatureDescription | IframeComponentFeatureDescription>
|
||||
doesEditorChangeRequireAlert(
|
||||
from: ComponentOrNativeFeature<IframeComponentFeatureDescription | EditorFeatureDescription> | undefined,
|
||||
to: ComponentOrNativeFeature<IframeComponentFeatureDescription | EditorFeatureDescription> | undefined,
|
||||
): boolean
|
||||
showEditorChangeAlert(): Promise<boolean>
|
||||
destroyComponentViewer(viewer: ComponentViewerInterface): void
|
||||
createComponentViewer(
|
||||
component: SNComponent,
|
||||
contextItem?: string,
|
||||
uiFeature: ComponentOrNativeFeature<IframeComponentFeatureDescription>,
|
||||
item: ComponentViewerItem,
|
||||
actionObserver?: ActionObserver,
|
||||
urlOverride?: string,
|
||||
): ComponentViewerInterface
|
||||
presentPermissionsDialog(_dialog: PermissionDialog): void
|
||||
legacyGetDefaultEditor(): SNComponent | undefined
|
||||
componentWithIdentifier(identifier: FeatureIdentifier | string): SNComponent | undefined
|
||||
toggleTheme(uuid: string): Promise<void>
|
||||
toggleComponent(uuid: string): Promise<void>
|
||||
legacyGetDefaultEditor(): ComponentInterface | undefined
|
||||
|
||||
isThemeActive(theme: ComponentOrNativeFeature<ThemeFeatureDescription>): boolean
|
||||
toggleTheme(theme: ComponentOrNativeFeature<ThemeFeatureDescription>): Promise<void>
|
||||
getActiveThemes(): ComponentOrNativeFeature<ThemeFeatureDescription>[]
|
||||
getActiveThemesIdentifiers(): string[]
|
||||
|
||||
isComponentActive(component: ComponentInterface): boolean
|
||||
toggleComponent(component: ComponentInterface): Promise<void>
|
||||
}
|
||||
|
||||
@@ -2,20 +2,22 @@ import {
|
||||
ActionObserver,
|
||||
ComponentEventObserver,
|
||||
ComponentMessage,
|
||||
DecryptedItemInterface,
|
||||
SNComponent,
|
||||
ComponentOrNativeFeature,
|
||||
} from '@standardnotes/models'
|
||||
import { FeatureStatus } from '../Feature/FeatureStatus'
|
||||
import { ComponentViewerError } from './ComponentViewerError'
|
||||
import { IframeComponentFeatureDescription } from '@standardnotes/features'
|
||||
|
||||
export interface ComponentViewerInterface {
|
||||
readonly component: SNComponent
|
||||
readonly url?: string
|
||||
identifier: string
|
||||
lockReadonly: boolean
|
||||
sessionKey?: string
|
||||
overrideContextItem?: DecryptedItemInterface
|
||||
get componentUuid(): string
|
||||
readonly identifier: string
|
||||
readonly lockReadonly: boolean
|
||||
readonly sessionKey?: string
|
||||
|
||||
get url(): string
|
||||
get componentUniqueIdentifier(): string
|
||||
|
||||
getComponentOrFeatureItem(): ComponentOrNativeFeature<IframeComponentFeatureDescription>
|
||||
|
||||
destroy(): void
|
||||
setReadonly(readonly: boolean): void
|
||||
getFeatureStatus(): FeatureStatus
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { DecryptedItemInterface } from '@standardnotes/models'
|
||||
|
||||
export type ComponentViewerItem = { uuid: string } | { readonlyItem: DecryptedItemInterface }
|
||||
|
||||
export function isComponentViewerItemReadonlyItem(
|
||||
item: ComponentViewerItem,
|
||||
): item is { readonlyItem: DecryptedItemInterface } {
|
||||
return 'readonlyItem' in item
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SNComponent } from '@standardnotes/models'
|
||||
import { ComponentInterface } from '@standardnotes/models'
|
||||
|
||||
export interface DesktopManagerInterface {
|
||||
syncComponentsInstallation(components: SNComponent[]): void
|
||||
registerUpdateObserver(callback: (component: SNComponent) => void): () => void
|
||||
syncComponentsInstallation(components: ComponentInterface[]): void
|
||||
registerUpdateObserver(callback: (component: ComponentInterface) => void): () => void
|
||||
getExtServerHost(): string
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface MobileDeviceInterface extends DeviceInterface {
|
||||
authenticateWithBiometrics(): Promise<boolean>
|
||||
hideMobileInterfaceFromScreenshots(): void
|
||||
stopHidingMobileInterfaceFromScreenshots(): void
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
consoleLog(...args: any[]): void
|
||||
handleThemeSchemeChange(isDark: boolean, bgColor: string): void
|
||||
shareBase64AsFile(base64: string, filename: string): Promise<void>
|
||||
|
||||
@@ -1,79 +1,61 @@
|
||||
import { ApplicationStage } from './../Application/ApplicationStage'
|
||||
export enum ApplicationEvent {
|
||||
SignedIn = 'signed-in',
|
||||
SignedOut = 'signed-out',
|
||||
|
||||
SignedIn = 'Application:SignedIn',
|
||||
SignedOut = 'Application:SignedOut',
|
||||
/** When a full, potentially multi-page sync completes */
|
||||
CompletedFullSync = 'completed-full-sync',
|
||||
|
||||
FailedSync = 'failed-sync',
|
||||
HighLatencySync = 'high-latency-sync',
|
||||
EnteredOutOfSync = 'entered-out-of-sync',
|
||||
ExitedOutOfSync = 'exited-out-of-sync',
|
||||
|
||||
ApplicationStageChanged = 'application-stage-changed',
|
||||
|
||||
CompletedFullSync = 'Application:CompletedFullSync',
|
||||
FailedSync = 'Application:FailedSync',
|
||||
HighLatencySync = 'Application:HighLatencySync',
|
||||
EnteredOutOfSync = 'Application:EnteredOutOfSync',
|
||||
ExitedOutOfSync = 'Application:ExitedOutOfSync',
|
||||
ApplicationStageChanged = 'Application:ApplicationStageChanged',
|
||||
/**
|
||||
* The application has finished its prepareForLaunch state and is now ready for unlock
|
||||
* Called when the application has initialized and is ready for launch, but before
|
||||
* the application has been unlocked, if applicable. Use this to do pre-launch
|
||||
* configuration, but do not attempt to access user data like notes or tags.
|
||||
*/
|
||||
Started = 'started',
|
||||
|
||||
Started = 'Application:Started',
|
||||
/**
|
||||
* The applicaiton is fully unlocked and ready for i/o
|
||||
* Called when the application has been fully decrypted and unlocked. Use this to
|
||||
* to begin streaming data like notes and tags.
|
||||
*/
|
||||
Launched = 'launched',
|
||||
|
||||
LocalDataLoaded = 'local-data-loaded',
|
||||
|
||||
Launched = 'Application:Launched',
|
||||
LocalDataLoaded = 'Application:LocalDataLoaded',
|
||||
/**
|
||||
* When the root key or root key wrapper changes. Includes events like account state
|
||||
* changes (registering, signing in, changing pw, logging out) and passcode state
|
||||
* changes (adding, removing, changing).
|
||||
*/
|
||||
KeyStatusChanged = 'key-status-changed',
|
||||
|
||||
MajorDataChange = 'major-data-change',
|
||||
CompletedRestart = 'completed-restart',
|
||||
LocalDataIncrementalLoad = 'local-data-incremental-load',
|
||||
SyncStatusChanged = 'sync-status-changed',
|
||||
WillSync = 'will-sync',
|
||||
InvalidSyncSession = 'invalid-sync-session',
|
||||
LocalDatabaseReadError = 'local-database-read-error',
|
||||
LocalDatabaseWriteError = 'local-database-write-error',
|
||||
|
||||
KeyStatusChanged = 'Application:KeyStatusChanged',
|
||||
MajorDataChange = 'Application:MajorDataChange',
|
||||
CompletedRestart = 'Application:CompletedRestart',
|
||||
LocalDataIncrementalLoad = 'Application:LocalDataIncrementalLoad',
|
||||
SyncStatusChanged = 'Application:SyncStatusChanged',
|
||||
WillSync = 'Application:WillSync',
|
||||
InvalidSyncSession = 'Application:InvalidSyncSession',
|
||||
LocalDatabaseReadError = 'Application:LocalDatabaseReadError',
|
||||
LocalDatabaseWriteError = 'Application:LocalDatabaseWriteError',
|
||||
/**
|
||||
* When a single roundtrip completes with sync, in a potentially multi-page sync request.
|
||||
* If just a single roundtrip, this event will be triggered, along with CompletedFullSync
|
||||
*/
|
||||
CompletedIncrementalSync = 'completed-incremental-sync',
|
||||
|
||||
CompletedIncrementalSync = 'Application:CompletedIncrementalSync',
|
||||
/**
|
||||
* The application has loaded all pending migrations (but not run any, except for the base one),
|
||||
* and consumers may now call hasPendingMigrations
|
||||
*/
|
||||
MigrationsLoaded = 'migrations-loaded',
|
||||
|
||||
MigrationsLoaded = 'Application:MigrationsLoaded',
|
||||
/** When StorageService is ready (but NOT yet decrypted) to start servicing read/write requests */
|
||||
StorageReady = 'storage-ready',
|
||||
|
||||
PreferencesChanged = 'preferences-changed',
|
||||
UnprotectedSessionBegan = 'unprotected-session-began',
|
||||
UserRolesChanged = 'user-roles-changed',
|
||||
FeaturesUpdated = 'features-updated',
|
||||
UnprotectedSessionExpired = 'unprotected-session-expired',
|
||||
|
||||
StorageReady = 'Application:StorageReady',
|
||||
PreferencesChanged = 'Application:PreferencesChanged',
|
||||
UnprotectedSessionBegan = 'Application:UnprotectedSessionBegan',
|
||||
UserRolesChanged = 'Application:UserRolesChanged',
|
||||
FeaturesAvailabilityChanged = 'Application:FeaturesAvailabilityChanged',
|
||||
UnprotectedSessionExpired = 'Application:UnprotectedSessionExpired',
|
||||
/** Called when the app first launches and after first sync request made after sign in */
|
||||
CompletedInitialSync = 'completed-initial-sync',
|
||||
BiometricsSoftLockEngaged = 'biometrics-soft-lock-engaged',
|
||||
BiometricsSoftLockDisengaged = 'biometrics-soft-lock-disengaged',
|
||||
DidPurchaseSubscription = 'did-purchase-subscription',
|
||||
}
|
||||
|
||||
export type ApplicationStageChangedEventPayload = {
|
||||
stage: ApplicationStage
|
||||
CompletedInitialSync = 'Application:CompletedInitialSync',
|
||||
BiometricsSoftLockEngaged = 'Application:BiometricsSoftLockEngaged',
|
||||
BiometricsSoftLockDisengaged = 'Application:BiometricsSoftLockDisengaged',
|
||||
DidPurchaseSubscription = 'Application:DidPurchaseSubscription',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ApplicationStage } from './../Application/ApplicationStage'
|
||||
|
||||
export type ApplicationStageChangedEventPayload = {
|
||||
stage: ApplicationStage
|
||||
}
|
||||
@@ -1,37 +1,27 @@
|
||||
import { FeatureIdentifier } from '@standardnotes/features'
|
||||
import { SNComponent } from '@standardnotes/models'
|
||||
import { ComponentInterface } from '@standardnotes/models'
|
||||
|
||||
import { FeatureStatus } from './FeatureStatus'
|
||||
import { SetOfflineFeaturesFunctionResponse } from './SetOfflineFeaturesFunctionResponse'
|
||||
|
||||
export interface FeaturesClientInterface {
|
||||
downloadExternalFeature(urlOrCode: string): Promise<SNComponent | undefined>
|
||||
|
||||
getFeatureStatus(featureId: FeatureIdentifier): FeatureStatus
|
||||
|
||||
hasFirstPartySubscription(): boolean
|
||||
|
||||
hasMinimumRole(role: string): boolean
|
||||
|
||||
hasFirstPartyOfflineSubscription(): boolean
|
||||
setOfflineFeaturesCode(code: string): Promise<SetOfflineFeaturesFunctionResponse>
|
||||
|
||||
hasOfflineRepo(): boolean
|
||||
|
||||
deleteOfflineFeatureRepo(): Promise<void>
|
||||
|
||||
isThirdPartyFeature(identifier: string): boolean
|
||||
|
||||
toggleExperimentalFeature(identifier: FeatureIdentifier): void
|
||||
|
||||
getExperimentalFeatures(): FeatureIdentifier[]
|
||||
|
||||
getEnabledExperimentalFeatures(): FeatureIdentifier[]
|
||||
|
||||
enableExperimentalFeature(identifier: FeatureIdentifier): void
|
||||
|
||||
disableExperimentalFeature(identifier: FeatureIdentifier): void
|
||||
|
||||
isExperimentalFeatureEnabled(identifier: FeatureIdentifier): boolean
|
||||
|
||||
isExperimentalFeature(identifier: FeatureIdentifier): boolean
|
||||
|
||||
downloadRemoteThirdPartyFeature(urlOrCode: string): Promise<ComponentInterface | undefined>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export enum FeaturesEvent {
|
||||
UserRolesChanged = 'UserRolesChanged',
|
||||
FeaturesUpdated = 'FeaturesUpdated',
|
||||
FeaturesAvailabilityChanged = 'Features:FeaturesAvailabilityChanged',
|
||||
DidPurchaseSubscription = 'DidPurchaseSubscription',
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
|
||||
export type SetOfflineFeaturesFunctionResponse = ClientDisplayableError | undefined
|
||||
export type SetOfflineFeaturesFunctionResponse = ClientDisplayableError | void
|
||||
|
||||
@@ -15,13 +15,13 @@ import {
|
||||
SNNote,
|
||||
SmartView,
|
||||
TagItemCountChangeObserver,
|
||||
SNComponent,
|
||||
SNTheme,
|
||||
DecryptedPayloadInterface,
|
||||
DecryptedTransferPayload,
|
||||
FileItem,
|
||||
VaultDisplayOptions,
|
||||
NotesAndFilesDisplayControllerOptions,
|
||||
ThemeInterface,
|
||||
ComponentInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
|
||||
@@ -92,9 +92,15 @@ export interface ItemManagerInterface extends AbstractService {
|
||||
itemToLookupUuidFor: DecryptedItemInterface,
|
||||
contentType?: string,
|
||||
): I[]
|
||||
|
||||
findItem<T extends DecryptedItemInterface = DecryptedItemInterface>(uuid: string): T | undefined
|
||||
findItems<T extends DecryptedItemInterface>(uuids: string[]): T[]
|
||||
findSureItem<T extends DecryptedItemInterface = DecryptedItemInterface>(uuid: string): T
|
||||
/**
|
||||
* If item is not found, an `undefined` element will be inserted into the array.
|
||||
*/
|
||||
findItemsIncludingBlanks<T extends DecryptedItemInterface>(uuids: string[]): (T | undefined)[]
|
||||
|
||||
get trashedItems(): SNNote[]
|
||||
itemsBelongingToKeySystem(systemIdentifier: KeySystemIdentifier): DecryptedItemInterface[]
|
||||
hasTagsNeedingFoldersMigration(): boolean
|
||||
@@ -111,8 +117,8 @@ export interface ItemManagerInterface extends AbstractService {
|
||||
getTagParent(itemToLookupUuidFor: SNTag): SNTag | undefined
|
||||
isValidTagParent(parentTagToLookUpUuidFor: SNTag, childToLookUpUuidFor: SNTag): boolean
|
||||
isSmartViewTitle(title: string): boolean
|
||||
getDisplayableComponents(): (SNComponent | SNTheme)[]
|
||||
createItemFromPayload(payload: DecryptedPayloadInterface): DecryptedItemInterface
|
||||
getDisplayableComponents(): (ComponentInterface | ThemeInterface)[]
|
||||
createItemFromPayload<T extends DecryptedItemInterface>(payload: DecryptedPayloadInterface): T
|
||||
createPayloadFromObject(object: DecryptedTransferPayload): DecryptedPayloadInterface
|
||||
getDisplayableFiles(): FileItem[]
|
||||
setVaultDisplayOptions(options: VaultDisplayOptions): void
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
ComponentInterface,
|
||||
ComponentMutator,
|
||||
DecryptedItemInterface,
|
||||
DecryptedItemMutator,
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
PayloadEmitSource,
|
||||
PredicateInterface,
|
||||
SmartView,
|
||||
SNComponent,
|
||||
SNFeatureRepo,
|
||||
SNNote,
|
||||
SNTag,
|
||||
@@ -72,12 +72,12 @@ export interface MutatorClientInterface {
|
||||
): Promise<ItemsKeyInterface>
|
||||
|
||||
changeComponent(
|
||||
itemToLookupUuidFor: SNComponent,
|
||||
itemToLookupUuidFor: ComponentInterface,
|
||||
mutate: (mutator: ComponentMutator) => void,
|
||||
mutationType?: MutationType,
|
||||
emitSource?: PayloadEmitSource,
|
||||
payloadSourceKey?: string,
|
||||
): Promise<SNComponent>
|
||||
): Promise<ComponentInterface>
|
||||
|
||||
changeFeatureRepo(
|
||||
itemToLookupUuidFor: SNFeatureRepo,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { PrefKey, PrefValue } from '@standardnotes/models'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
|
||||
/* istanbul ignore file */
|
||||
|
||||
export enum PreferencesServiceEvent {
|
||||
PreferencesChanged = 'PreferencesChanged',
|
||||
}
|
||||
@@ -11,5 +9,8 @@ export interface PreferenceServiceInterface extends AbstractService<PreferencesS
|
||||
getValue<K extends PrefKey>(key: K, defaultValue: PrefValue[K] | undefined): PrefValue[K] | undefined
|
||||
getValue<K extends PrefKey>(key: K, defaultValue: PrefValue[K]): PrefValue[K]
|
||||
getValue<K extends PrefKey>(key: K, defaultValue?: PrefValue[K]): PrefValue[K] | undefined
|
||||
|
||||
setValue<K extends PrefKey>(key: K, value: PrefValue[K]): Promise<void>
|
||||
/** Set value without triggering sync or event notifications */
|
||||
setValueDetached<K extends PrefKey>(key: K, value: PrefValue[K]): Promise<void>
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface SessionsClientInterface {
|
||||
isSignedIn(): boolean
|
||||
get userUuid(): string
|
||||
getSureUser(): User
|
||||
isSignedIntoFirstPartyServer(): boolean
|
||||
|
||||
isCurrentSessionReadOnly(): boolean | undefined
|
||||
register(email: string, password: string, ephemeral: boolean): Promise<UserRegistrationResponseBody>
|
||||
|
||||
@@ -36,7 +36,6 @@ export enum StorageKey {
|
||||
WebSocketUrl = 'webSocket_url',
|
||||
UserRoles = 'user_roles',
|
||||
OfflineUserRoles = 'offline_user_roles',
|
||||
UserFeatures = 'user_features',
|
||||
ExperimentalFeatures = 'experimental_features',
|
||||
DeinitMode = 'deinit_mode',
|
||||
CodeVerifier = 'code_verifier',
|
||||
@@ -50,6 +49,7 @@ export enum StorageKey {
|
||||
FileBackupsEnabled = 'file_backups_enabled',
|
||||
FileBackupsLocation = 'file_backups_location',
|
||||
VaultSelectionOptions = 'vault_selection_options',
|
||||
Subscription = 'subscription',
|
||||
}
|
||||
|
||||
export enum NonwrappedStorageKey {
|
||||
|
||||
@@ -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 }>
|
||||
}
|
||||
4
packages/services/src/Domain/User/AccountEvent.ts
Normal file
4
packages/services/src/Domain/User/AccountEvent.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum AccountEvent {
|
||||
SignedInOrRegistered = 'SignedInOrRegistered',
|
||||
SignedOut = 'SignedOut',
|
||||
}
|
||||
7
packages/services/src/Domain/User/AccountEventData.ts
Normal file
7
packages/services/src/Domain/User/AccountEventData.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Either } from '@standardnotes/common'
|
||||
import { SignedInOrRegisteredEventPayload } from './SignedInOrRegisteredEventPayload'
|
||||
import { SignedOutEventPayload } from './SignedOutEventPayload'
|
||||
|
||||
export interface AccountEventData {
|
||||
payload: Either<SignedInOrRegisteredEventPayload, SignedOutEventPayload>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { HttpError } from '@standardnotes/responses'
|
||||
|
||||
export type CredentialsChangeFunctionResponse = { error?: HttpError }
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface SignedInOrRegisteredEventPayload {
|
||||
ephemeral: boolean
|
||||
mergeLocal: boolean
|
||||
awaitSync: boolean
|
||||
checkIntegrity: boolean
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { DeinitSource } from '../Application/DeinitSource'
|
||||
|
||||
export interface SignedOutEventPayload {
|
||||
source: DeinitSource
|
||||
}
|
||||
@@ -1,33 +1,14 @@
|
||||
import { Base64String } from '@standardnotes/sncrypto-common'
|
||||
import { Either, UserRequestType } from '@standardnotes/common'
|
||||
import { UserRequestType } from '@standardnotes/common'
|
||||
import { DeinitSource } from '../Application/DeinitSource'
|
||||
import { UserRegistrationResponseBody } from '@standardnotes/api'
|
||||
import { HttpError, HttpResponse, SignInResponse } from '@standardnotes/responses'
|
||||
import { HttpResponse, SignInResponse } from '@standardnotes/responses'
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
|
||||
export type CredentialsChangeFunctionResponse = { error?: HttpError }
|
||||
|
||||
export enum AccountEvent {
|
||||
SignedInOrRegistered = 'SignedInOrRegistered',
|
||||
SignedOut = 'SignedOut',
|
||||
}
|
||||
|
||||
export interface SignedInOrRegisteredEventPayload {
|
||||
ephemeral: boolean
|
||||
mergeLocal: boolean
|
||||
awaitSync: boolean
|
||||
checkIntegrity: boolean
|
||||
}
|
||||
|
||||
export interface SignedOutEventPayload {
|
||||
source: DeinitSource
|
||||
}
|
||||
|
||||
export interface AccountEventData {
|
||||
payload: Either<SignedInOrRegisteredEventPayload, SignedOutEventPayload>
|
||||
}
|
||||
import { AccountEventData } from './AccountEventData'
|
||||
import { AccountEvent } from './AccountEvent'
|
||||
|
||||
export interface UserClientInterface extends AbstractService<AccountEvent, AccountEventData> {
|
||||
getUserUuid(): string
|
||||
isSignedIn(): boolean
|
||||
register(
|
||||
email: string,
|
||||
|
||||
@@ -10,13 +10,6 @@ import {
|
||||
import { KeyParamsOrigination, UserRequestType } from '@standardnotes/common'
|
||||
import { UuidGenerator } from '@standardnotes/utils'
|
||||
import { UserApiServiceInterface, UserRegistrationResponseBody } from '@standardnotes/api'
|
||||
import {
|
||||
AccountEventData,
|
||||
AccountEvent,
|
||||
SignedInOrRegisteredEventPayload,
|
||||
CredentialsChangeFunctionResponse,
|
||||
} from '@standardnotes/services'
|
||||
|
||||
import * as Messages from '../Strings/Messages'
|
||||
import { InfoStrings } from '../Strings/InfoStrings'
|
||||
import { SyncServiceInterface } from '../Sync/SyncServiceInterface'
|
||||
@@ -39,6 +32,10 @@ import { SessionsClientInterface } from '../Session/SessionsClientInterface'
|
||||
import { ProtectionsClientInterface } from '../Protection/ProtectionClientInterface'
|
||||
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
|
||||
import { InternalEventInterface } from '../Internal/InternalEventInterface'
|
||||
import { AccountEventData } from './AccountEventData'
|
||||
import { AccountEvent } from './AccountEvent'
|
||||
import { SignedInOrRegisteredEventPayload } from './SignedInOrRegisteredEventPayload'
|
||||
import { CredentialsChangeFunctionResponse } from './CredentialsChangeFunctionResponse'
|
||||
|
||||
export class UserService
|
||||
extends AbstractService<AccountEvent, AccountEventData>
|
||||
@@ -115,6 +112,10 @@ export class UserService
|
||||
;(this.userApiService as unknown) = undefined
|
||||
}
|
||||
|
||||
getUserUuid(): string {
|
||||
return this.sessionManager.userUuid
|
||||
}
|
||||
|
||||
isSignedIn(): boolean {
|
||||
return this.sessionManager.isSignedIn()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
export * from './Alert/AlertService'
|
||||
|
||||
export * from './Api/ApiServiceInterface'
|
||||
export * from './Api/ApiServiceEventData'
|
||||
export * from './Api/ApiServiceEvent'
|
||||
export * from './Api/MetaReceivedData'
|
||||
export * from './Api/SessionRefreshedData'
|
||||
|
||||
export * from './Application/AppGroupManagedApplication'
|
||||
export * from './Application/ApplicationInterface'
|
||||
@@ -24,6 +29,7 @@ export * from './Challenge'
|
||||
export * from './Component/ComponentManagerInterface'
|
||||
export * from './Component/ComponentViewerError'
|
||||
export * from './Component/ComponentViewerInterface'
|
||||
export * from './Component/ComponentViewerItem'
|
||||
|
||||
export * from './Contacts/ContactServiceInterface'
|
||||
export * from './Contacts/ContactService'
|
||||
@@ -69,6 +75,7 @@ export * from './Event/EventObserver'
|
||||
export * from './Event/SyncEvent'
|
||||
export * from './Event/SyncEventReceiver'
|
||||
export * from './Event/WebAppEvent'
|
||||
export * from './Event/ApplicationStageChangedEventPayload'
|
||||
|
||||
export * from './Feature/FeaturesClientInterface'
|
||||
export * from './Feature/FeaturesEvent'
|
||||
@@ -140,8 +147,9 @@ export * from './Strings/Messages'
|
||||
|
||||
export * from './Subscription/AppleIAPProductId'
|
||||
export * from './Subscription/AppleIAPReceipt'
|
||||
export * from './Subscription/SubscriptionClientInterface'
|
||||
export * from './Subscription/SubscriptionManagerInterface'
|
||||
export * from './Subscription/SubscriptionManager'
|
||||
export * from './Subscription/SubscriptionManagerEvent'
|
||||
|
||||
export * from './Sync/SyncMode'
|
||||
export * from './Sync/SyncOptions'
|
||||
@@ -152,6 +160,11 @@ export * from './Sync/SyncSource'
|
||||
export * from './User/UserClientInterface'
|
||||
export * from './User/UserClientInterface'
|
||||
export * from './User/UserService'
|
||||
export * from './User/AccountEvent'
|
||||
export * from './User/AccountEventData'
|
||||
export * from './User/CredentialsChangeFunctionResponse'
|
||||
export * from './User/SignedInOrRegisteredEventPayload'
|
||||
export * from './User/SignedOutEventPayload'
|
||||
|
||||
export * from './UserEvent/UserEventService'
|
||||
export * from './UserEvent/UserEventServiceEvent'
|
||||
|
||||
Reference in New Issue
Block a user