1386 lines
44 KiB
TypeScript
1386 lines
44 KiB
TypeScript
import { SNMfaService } from './../Services/Mfa/MfaService'
|
|
import { KeyRecoveryService } from './../Services/KeyRecovery/KeyRecoveryService'
|
|
import { WebSocketsService } from './../Services/Api/WebsocketsService'
|
|
import { MigrationService } from './../Services/Migration/MigrationService'
|
|
import { LegacyApiService } from './../Services/Api/ApiService'
|
|
import { FeaturesService } from '@Lib/Services/Features/FeaturesService'
|
|
import { SNPreferencesService } from './../Services/Preferences/PreferencesService'
|
|
import { SNProtectionService } from './../Services/Protection/ProtectionService'
|
|
import { SessionManager } from './../Services/Session/SessionManager'
|
|
import { HttpService, HttpServiceInterface, UserRegistrationResponseBody } from '@standardnotes/api'
|
|
import { ApplicationIdentifier, compareVersions, ProtocolVersion, KeyParamsOrigination } from '@standardnotes/common'
|
|
import {
|
|
DeinitCallback,
|
|
SessionEvent,
|
|
SyncEvent,
|
|
ApplicationStage,
|
|
FeaturesEvent,
|
|
SyncMode,
|
|
SyncSource,
|
|
ApplicationStageChangedEventPayload,
|
|
StorageValueModes,
|
|
ChallengeObserver,
|
|
SyncOptions,
|
|
ImportDataReturnType,
|
|
ImportDataUseCase,
|
|
StoragePersistencePolicies,
|
|
HomeServerServiceInterface,
|
|
ApiServiceEvent,
|
|
IntegrityEvent,
|
|
DeviceInterface,
|
|
SubscriptionManagerInterface,
|
|
FeaturesClientInterface,
|
|
ItemManagerInterface,
|
|
SyncServiceInterface,
|
|
UserClientInterface,
|
|
MutatorClientInterface,
|
|
StatusServiceInterface,
|
|
AlertService,
|
|
StorageServiceInterface,
|
|
ChallengeServiceInterface,
|
|
AsymmetricMessageServiceInterface,
|
|
VaultServiceInterface,
|
|
ContactServiceInterface,
|
|
SharedVaultServiceInterface,
|
|
PreferenceServiceInterface,
|
|
InternalEventBusInterface,
|
|
ApplicationEvent,
|
|
ApplicationEventCallback,
|
|
ChallengeValidation,
|
|
ComponentManagerInterface,
|
|
ChallengeValue,
|
|
StorageKey,
|
|
ChallengeReason,
|
|
DeinitMode,
|
|
DeinitSource,
|
|
AppGroupManagedApplication,
|
|
ApplicationInterface,
|
|
EncryptionService,
|
|
EncryptionServiceEvent,
|
|
ChallengePrompt,
|
|
Challenge,
|
|
ErrorAlertStrings,
|
|
SessionsClientInterface,
|
|
ProtectionsClientInterface,
|
|
UserService,
|
|
ProtocolUpgradeStrings,
|
|
CredentialsChangeFunctionResponse,
|
|
SessionStrings,
|
|
AccountEvent,
|
|
PayloadManagerInterface,
|
|
HistoryServiceInterface,
|
|
InternalEventPublishStrategy,
|
|
EncryptionProviderInterface,
|
|
VaultUserServiceInterface,
|
|
VaultInviteServiceInterface,
|
|
UserEventServiceEvent,
|
|
VaultServiceEvent,
|
|
VaultLockServiceInterface,
|
|
} from '@standardnotes/services'
|
|
import {
|
|
PayloadEmitSource,
|
|
SNNote,
|
|
PrefKey,
|
|
PrefValue,
|
|
DecryptedItemMutator,
|
|
BackupFile,
|
|
DecryptedItemInterface,
|
|
EncryptedItemInterface,
|
|
Environment,
|
|
ItemStream,
|
|
Platform,
|
|
MutationType,
|
|
} from '@standardnotes/models'
|
|
import {
|
|
HttpResponse,
|
|
SessionListResponse,
|
|
User,
|
|
SignInResponse,
|
|
ClientDisplayableError,
|
|
SessionListEntry,
|
|
} from '@standardnotes/responses'
|
|
import {
|
|
SyncService,
|
|
ProtectionEvent,
|
|
SettingsService,
|
|
ActionsService,
|
|
ChallengeResponse,
|
|
ListedClientInterface,
|
|
DiskStorageService,
|
|
} from '../Services'
|
|
import {
|
|
nonSecureRandomIdentifier,
|
|
assertUnreachable,
|
|
removeFromArray,
|
|
isNullOrUndefined,
|
|
sleep,
|
|
UuidGenerator,
|
|
useBoolean,
|
|
} from '@standardnotes/utils'
|
|
import { UuidString, ApplicationEventPayload } from '../Types'
|
|
import { applicationEventForSyncEvent } from '@Lib/Application/Event'
|
|
import { BackupServiceInterface, FilesClientInterface } from '@standardnotes/files'
|
|
import { ComputePrivateUsername } from '@standardnotes/encryption'
|
|
import { SNLog } from '../Log'
|
|
import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions'
|
|
import { ApplicationOptionsDefaults } from './Options/Defaults'
|
|
import { SignInWithRecoveryCodes } from '@Lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes'
|
|
import { UseCaseContainerInterface } from '@Lib/Domain/UseCase/UseCaseContainerInterface'
|
|
import { GetRecoveryCodes } from '@Lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes'
|
|
import { AddAuthenticator } from '@Lib/Domain/UseCase/AddAuthenticator/AddAuthenticator'
|
|
import { ListAuthenticators } from '@Lib/Domain/UseCase/ListAuthenticators/ListAuthenticators'
|
|
import { DeleteAuthenticator } from '@Lib/Domain/UseCase/DeleteAuthenticator/DeleteAuthenticator'
|
|
import { ListRevisions } from '@Lib/Domain/UseCase/ListRevisions/ListRevisions'
|
|
import { GetRevision } from '@Lib/Domain/UseCase/GetRevision/GetRevision'
|
|
import { DeleteRevision } from '@Lib/Domain/UseCase/DeleteRevision/DeleteRevision'
|
|
import { GetAuthenticatorAuthenticationResponse } from '@Lib/Domain/UseCase/GetAuthenticatorAuthenticationResponse/GetAuthenticatorAuthenticationResponse'
|
|
import { GetAuthenticatorAuthenticationOptions } from '@Lib/Domain/UseCase/GetAuthenticatorAuthenticationOptions/GetAuthenticatorAuthenticationOptions'
|
|
import { Dependencies } from './Dependencies/Dependencies'
|
|
import { TYPES } from './Dependencies/Types'
|
|
import { canBlockDeinit } from './Dependencies/isDeinitable'
|
|
|
|
/** How often to automatically sync, in milliseconds */
|
|
const DEFAULT_AUTO_SYNC_INTERVAL = 30_000
|
|
|
|
type LaunchCallback = {
|
|
receiveChallenge: (challenge: Challenge) => void
|
|
}
|
|
|
|
type ApplicationObserver = {
|
|
singleEvent?: ApplicationEvent
|
|
callback: ApplicationEventCallback
|
|
}
|
|
|
|
type ObserverRemover = () => void
|
|
|
|
export class SNApplication implements ApplicationInterface, AppGroupManagedApplication, UseCaseContainerInterface {
|
|
onDeinit!: DeinitCallback
|
|
|
|
/**
|
|
* A runtime based identifier for each dynamic instantiation of the application instance.
|
|
* This differs from the persistent application.identifier which persists in storage
|
|
* across instantiations.
|
|
*/
|
|
public readonly ephemeralIdentifier = nonSecureRandomIdentifier()
|
|
|
|
private eventHandlers: ApplicationObserver[] = []
|
|
|
|
private streamRemovers: ObserverRemover[] = []
|
|
private serviceObservers: ObserverRemover[] = []
|
|
private managedSubscribers: ObserverRemover[] = []
|
|
private autoSyncInterval!: ReturnType<typeof setInterval>
|
|
|
|
/** True if the result of deviceInterface.openDatabase yields a new database being created */
|
|
private createdNewDatabase = false
|
|
/** True if the application has started (but not necessarily launched) */
|
|
private started = false
|
|
/** True if the application has launched */
|
|
private launched = false
|
|
/** Whether the application has been destroyed via .deinit() */
|
|
public dealloced = false
|
|
private isBiometricsSoftLockEngaged = false
|
|
private revokingSession = false
|
|
private handledFullSyncStage = false
|
|
|
|
public readonly environment: Environment
|
|
public readonly platform: Platform
|
|
|
|
public readonly identifier: ApplicationIdentifier
|
|
public readonly options: FullyResolvedApplicationOptions
|
|
|
|
private dependencies: Dependencies
|
|
|
|
constructor(options: ApplicationConstructorOptions) {
|
|
const allOptions: FullyResolvedApplicationOptions = {
|
|
...ApplicationOptionsDefaults,
|
|
...options,
|
|
}
|
|
|
|
if (!SNLog.onLog) {
|
|
throw Error('SNLog.onLog must be set.')
|
|
}
|
|
if (!SNLog.onError) {
|
|
throw Error('SNLog.onError must be set.')
|
|
}
|
|
|
|
const requiredOptions: (keyof FullyResolvedApplicationOptions)[] = [
|
|
'deviceInterface',
|
|
'environment',
|
|
'platform',
|
|
'crypto',
|
|
'alertService',
|
|
'identifier',
|
|
'defaultHost',
|
|
'appVersion',
|
|
]
|
|
|
|
for (const optionName of requiredOptions) {
|
|
if (!allOptions[optionName]) {
|
|
throw Error(`${optionName} must be supplied when creating an application.`)
|
|
}
|
|
}
|
|
|
|
this.environment = options.environment
|
|
this.platform = options.platform
|
|
|
|
this.identifier = options.identifier
|
|
this.options = Object.freeze(allOptions)
|
|
|
|
this.dependencies = new Dependencies(this.options)
|
|
|
|
this.registerServiceObservers()
|
|
this.defineInternalEventHandlers()
|
|
this.createBackgroundDependencies()
|
|
}
|
|
|
|
private registerServiceObservers() {
|
|
const encryptionService = this.dependencies.get<EncryptionService>(TYPES.EncryptionService)
|
|
this.serviceObservers.push(
|
|
encryptionService.addEventObserver(async (event) => {
|
|
if (event === EncryptionServiceEvent.RootKeyStatusChanged) {
|
|
await this.notifyEvent(ApplicationEvent.KeyStatusChanged)
|
|
}
|
|
}),
|
|
)
|
|
|
|
this.dependencies.get<DiskStorageService>(TYPES.DiskStorageService).provideEncryptionProvider(encryptionService)
|
|
|
|
const apiService = this.dependencies.get<LegacyApiService>(TYPES.LegacyApiService)
|
|
this.dependencies
|
|
.get<HttpService>(TYPES.HttpService)
|
|
.setCallbacks(apiService.processMetaObject.bind(apiService), apiService.setSession.bind(apiService))
|
|
|
|
this.serviceObservers.push(
|
|
this.dependencies.get<SessionManager>(TYPES.SessionManager).addEventObserver(async (event) => {
|
|
switch (event) {
|
|
case SessionEvent.Restored: {
|
|
void (async () => {
|
|
await this.sync.sync({ sourceDescription: 'Session restored pre key creation' })
|
|
if (encryptionService.needsNewRootKeyBasedItemsKey()) {
|
|
void encryptionService.createNewDefaultItemsKey().then(() => {
|
|
void this.sync.sync({ sourceDescription: 'Session restored post key creation' })
|
|
})
|
|
}
|
|
})()
|
|
break
|
|
}
|
|
case SessionEvent.Revoked: {
|
|
await this.handleRevokedSession()
|
|
break
|
|
}
|
|
case SessionEvent.UserKeyPairChanged:
|
|
break
|
|
default: {
|
|
assertUnreachable(event)
|
|
}
|
|
}
|
|
}),
|
|
)
|
|
|
|
const syncEventCallback = async (eventName: SyncEvent) => {
|
|
const appEvent = applicationEventForSyncEvent(eventName)
|
|
if (appEvent) {
|
|
await encryptionService.onSyncEvent(eventName)
|
|
|
|
await this.notifyEvent(appEvent)
|
|
|
|
if (appEvent === ApplicationEvent.CompletedFullSync) {
|
|
if (!this.handledFullSyncStage) {
|
|
this.handledFullSyncStage = true
|
|
await this.handleStage(ApplicationStage.FullSyncCompleted_13)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const syncService = this.dependencies.get<SyncService>(TYPES.SyncService)
|
|
const uninstall = syncService.addEventObserver(syncEventCallback)
|
|
this.serviceObservers.push(uninstall)
|
|
|
|
const protectionService = this.dependencies.get<SNProtectionService>(TYPES.ProtectionService)
|
|
this.serviceObservers.push(
|
|
protectionService.addEventObserver((event) => {
|
|
if (event === ProtectionEvent.UnprotectedSessionBegan) {
|
|
void this.notifyEvent(ApplicationEvent.UnprotectedSessionBegan)
|
|
} else if (event === ProtectionEvent.UnprotectedSessionExpired) {
|
|
void this.notifyEvent(ApplicationEvent.UnprotectedSessionExpired)
|
|
}
|
|
}),
|
|
)
|
|
|
|
const userService = this.dependencies.get<UserService>(TYPES.UserService)
|
|
this.serviceObservers.push(
|
|
userService.addEventObserver(async (event, data) => {
|
|
switch (event) {
|
|
case AccountEvent.SignedInOrRegistered: {
|
|
void this.notifyEvent(ApplicationEvent.SignedIn)
|
|
break
|
|
}
|
|
case AccountEvent.SignedOut: {
|
|
await this.notifyEvent(ApplicationEvent.SignedOut)
|
|
await this.prepareForDeinit()
|
|
this.deinit(this.getDeinitMode(), data?.payload.source || DeinitSource.SignOut)
|
|
break
|
|
}
|
|
default: {
|
|
assertUnreachable(event)
|
|
}
|
|
}
|
|
}),
|
|
)
|
|
|
|
const preferencesService = this.dependencies.get<SNPreferencesService>(TYPES.PreferencesService)
|
|
this.serviceObservers.push(
|
|
preferencesService.addEventObserver(() => {
|
|
void this.notifyEvent(ApplicationEvent.PreferencesChanged)
|
|
}),
|
|
)
|
|
|
|
const featuresService = this.dependencies.get<FeaturesService>(TYPES.FeaturesService)
|
|
this.serviceObservers.push(
|
|
featuresService.addEventObserver((event) => {
|
|
switch (event) {
|
|
case FeaturesEvent.UserRolesChanged: {
|
|
void this.notifyEvent(ApplicationEvent.UserRolesChanged)
|
|
break
|
|
}
|
|
case FeaturesEvent.FeaturesAvailabilityChanged: {
|
|
void this.notifyEvent(ApplicationEvent.FeaturesAvailabilityChanged)
|
|
break
|
|
}
|
|
case FeaturesEvent.DidPurchaseSubscription: {
|
|
void this.notifyEvent(ApplicationEvent.DidPurchaseSubscription)
|
|
break
|
|
}
|
|
default: {
|
|
assertUnreachable(event)
|
|
}
|
|
}
|
|
}),
|
|
)
|
|
}
|
|
|
|
public computePrivateUsername(username: string): Promise<string | undefined> {
|
|
return ComputePrivateUsername(this.options.crypto, username)
|
|
}
|
|
|
|
/**
|
|
* The first thing consumers should call when starting their app.
|
|
* This function will load all services in their correct order.
|
|
*/
|
|
async prepareForLaunch(callback: LaunchCallback): Promise<void> {
|
|
if (this.launched) {
|
|
throw new Error('Attempting to prelaunch already launched application')
|
|
}
|
|
|
|
await this.options.crypto.initialize()
|
|
|
|
this.setLaunchCallback(callback)
|
|
|
|
const databaseResult = await this.device.openDatabase(this.identifier).catch((error) => {
|
|
void this.notifyEvent(ApplicationEvent.LocalDatabaseReadError, error)
|
|
return undefined
|
|
})
|
|
|
|
this.createdNewDatabase = useBoolean(databaseResult?.isNewDatabase, false)
|
|
|
|
await this.migrations.initialize()
|
|
|
|
await this.notifyEvent(ApplicationEvent.MigrationsLoaded)
|
|
await this.handleStage(ApplicationStage.PreparingForLaunch_0)
|
|
|
|
await this.storage.initializeFromDisk()
|
|
await this.notifyEvent(ApplicationEvent.StorageReady)
|
|
|
|
await this.encryption.initialize()
|
|
|
|
await this.handleStage(ApplicationStage.ReadyForLaunch_05)
|
|
|
|
this.started = true
|
|
await this.notifyEvent(ApplicationEvent.Started)
|
|
}
|
|
|
|
private setLaunchCallback(callback: LaunchCallback) {
|
|
this.challenges.sendChallenge = callback.receiveChallenge
|
|
}
|
|
|
|
/**
|
|
* Handles device authentication, unlocks application, and
|
|
* issues a callback if a device activation requires user input
|
|
* (i.e local passcode or fingerprint).
|
|
* @param awaitDatabaseLoad
|
|
* Option to await database load before marking the app as ready.
|
|
*/
|
|
public async launch(awaitDatabaseLoad = false): Promise<void> {
|
|
if (this.launched) {
|
|
throw new Error('Attempting to launch already launched application')
|
|
}
|
|
|
|
this.launched = false
|
|
|
|
const launchChallenge = this.getLaunchChallenge()
|
|
if (launchChallenge) {
|
|
const response = await this.challenges.promptForChallengeResponse(launchChallenge)
|
|
if (!response) {
|
|
throw Error('Launch challenge was cancelled.')
|
|
}
|
|
await this.handleLaunchChallengeResponse(response)
|
|
}
|
|
|
|
if (this.storage.isStorageWrapped()) {
|
|
try {
|
|
await this.storage.decryptStorage()
|
|
} catch (_error) {
|
|
void this.alerts.alert(ErrorAlertStrings.StorageDecryptErrorBody, ErrorAlertStrings.StorageDecryptErrorTitle)
|
|
}
|
|
}
|
|
|
|
await this.handleStage(ApplicationStage.StorageDecrypted_09)
|
|
|
|
const host = this.legacyApi.loadHost()
|
|
|
|
this.http.setHost(host)
|
|
|
|
this.sockets.loadWebSocketUrl()
|
|
|
|
this.settings.initializeFromDisk()
|
|
|
|
this.launched = true
|
|
await this.notifyEvent(ApplicationEvent.Launched)
|
|
await this.handleStage(ApplicationStage.Launched_10)
|
|
|
|
await this.handleStage(ApplicationStage.LoadingDatabase_11)
|
|
if (this.createdNewDatabase) {
|
|
await this.sync.onNewDatabaseCreated()
|
|
}
|
|
/**
|
|
* We don't want to await this, as we want to begin allowing the app to function
|
|
* before local data has been loaded fully.
|
|
*/
|
|
const loadPromise = this.sync
|
|
.loadDatabasePayloads()
|
|
.then(async () => {
|
|
if (this.dealloced) {
|
|
throw 'Application has been destroyed.'
|
|
}
|
|
await this.handleStage(ApplicationStage.LoadedDatabase_12)
|
|
this.beginAutoSyncTimer()
|
|
await this.sync.sync({
|
|
mode: SyncMode.DownloadFirst,
|
|
source: SyncSource.External,
|
|
sourceDescription: 'Application Launch',
|
|
})
|
|
})
|
|
.catch((error) => {
|
|
void this.notifyEvent(ApplicationEvent.LocalDatabaseReadError, error)
|
|
throw error
|
|
})
|
|
if (awaitDatabaseLoad) {
|
|
await loadPromise
|
|
}
|
|
}
|
|
|
|
public onStart(): void {
|
|
// optional override
|
|
}
|
|
|
|
public onLaunch(): void {
|
|
// optional override
|
|
}
|
|
|
|
public getLaunchChallenge(): Challenge | undefined {
|
|
return this.protections.createLaunchChallenge()
|
|
}
|
|
|
|
private async handleLaunchChallengeResponse(response: ChallengeResponse) {
|
|
if (response.challenge.hasPromptForValidationType(ChallengeValidation.LocalPasscode)) {
|
|
let wrappingKey = response.artifacts?.wrappingKey
|
|
if (!wrappingKey) {
|
|
const value = response.getValueForType(ChallengeValidation.LocalPasscode)
|
|
wrappingKey = await this.encryption.computeWrappingKey(value.value as string)
|
|
}
|
|
await this.encryption.unwrapRootKey(wrappingKey)
|
|
}
|
|
}
|
|
|
|
private beginAutoSyncTimer() {
|
|
this.autoSyncInterval = setInterval(() => {
|
|
this.sync.log('Syncing from autosync')
|
|
void this.sync.sync({ sourceDescription: 'Auto Sync' })
|
|
}, DEFAULT_AUTO_SYNC_INTERVAL)
|
|
}
|
|
|
|
private async handleStage(stage: ApplicationStage) {
|
|
await this.events.publishSync(
|
|
{
|
|
type: ApplicationEvent.ApplicationStageChanged,
|
|
payload: { stage } as ApplicationStageChangedEventPayload,
|
|
},
|
|
InternalEventPublishStrategy.SEQUENCE,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* @param singleEvent Whether to only listen for a particular event.
|
|
*/
|
|
public addEventObserver(callback: ApplicationEventCallback, singleEvent?: ApplicationEvent): () => void {
|
|
const observer = { callback, singleEvent }
|
|
this.eventHandlers.push(observer)
|
|
return () => {
|
|
removeFromArray(this.eventHandlers, observer)
|
|
}
|
|
}
|
|
|
|
public addSingleEventObserver(event: ApplicationEvent, callback: ApplicationEventCallback): () => void {
|
|
const filteredCallback = async (firedEvent: ApplicationEvent) => {
|
|
if (firedEvent === event) {
|
|
void callback(event)
|
|
}
|
|
}
|
|
return this.addEventObserver(filteredCallback, event)
|
|
}
|
|
|
|
private async notifyEvent(event: ApplicationEvent, data?: ApplicationEventPayload) {
|
|
if (event === ApplicationEvent.Started) {
|
|
this.onStart()
|
|
} else if (event === ApplicationEvent.Launched) {
|
|
this.onLaunch()
|
|
}
|
|
|
|
for (const observer of this.eventHandlers.slice()) {
|
|
if ((observer.singleEvent && observer.singleEvent === event) || !observer.singleEvent) {
|
|
await observer.callback(event, data || {})
|
|
}
|
|
}
|
|
|
|
this.events.publish({
|
|
type: event,
|
|
payload: data,
|
|
})
|
|
|
|
void this.migrations.handleApplicationEvent(event)
|
|
}
|
|
|
|
/**
|
|
* Whether the local database has completed loading local items.
|
|
*/
|
|
public isDatabaseLoaded(): boolean {
|
|
return this.sync.isDatabaseLoaded()
|
|
}
|
|
|
|
public getSessions(): Promise<HttpResponse<SessionListEntry[]>> {
|
|
return this.sessions.getSessionsList()
|
|
}
|
|
|
|
public async revokeSession(sessionId: UuidString): Promise<HttpResponse<SessionListResponse> | undefined> {
|
|
if (await this.protections.authorizeSessionRevoking()) {
|
|
return this.sessions.revokeSession(sessionId)
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
/**
|
|
* Revokes all sessions except the current one.
|
|
*/
|
|
public async revokeAllOtherSessions(): Promise<void> {
|
|
return this.sessions.revokeAllOtherSessions()
|
|
}
|
|
|
|
public userCanManageSessions(): boolean {
|
|
const userVersion = this.getUserVersion()
|
|
if (isNullOrUndefined(userVersion)) {
|
|
return false
|
|
}
|
|
return compareVersions(userVersion, ProtocolVersion.V004) >= 0
|
|
}
|
|
|
|
/**
|
|
* Begin streaming items to display in the UI. The stream callback will be called
|
|
* immediately with the present items that match the constraint, and over time whenever
|
|
* items matching the constraint are added, changed, or deleted.
|
|
*/
|
|
public streamItems<I extends DecryptedItemInterface = DecryptedItemInterface>(
|
|
contentType: string | string[],
|
|
stream: ItemStream<I>,
|
|
): () => void {
|
|
const removeItemManagerObserver = this.items.addObserver<I>(
|
|
contentType,
|
|
({ changed, inserted, removed, source }) => {
|
|
stream({ changed, inserted, removed, source })
|
|
},
|
|
)
|
|
|
|
const matches = this.items.getItems<I>(contentType)
|
|
stream({
|
|
inserted: matches,
|
|
changed: [],
|
|
removed: [],
|
|
source: PayloadEmitSource.InitialObserverRegistrationPush,
|
|
})
|
|
|
|
this.streamRemovers.push(removeItemManagerObserver)
|
|
|
|
return () => {
|
|
removeItemManagerObserver()
|
|
|
|
removeFromArray(this.streamRemovers, removeItemManagerObserver)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the server's URL
|
|
*/
|
|
public async setHost(host: string): Promise<void> {
|
|
this.http.setHost(host)
|
|
|
|
await this.legacyApi.setHost(host)
|
|
}
|
|
|
|
public getHost(): string {
|
|
return this.legacyApi.getHost()
|
|
}
|
|
|
|
public async setCustomHost(host: string): Promise<void> {
|
|
await this.setHost(host)
|
|
|
|
this.sockets.setWebSocketUrl(undefined)
|
|
}
|
|
|
|
public getUser(): User | undefined {
|
|
if (!this.launched) {
|
|
throw Error('Attempting to access user before application unlocked')
|
|
}
|
|
return this.sessions.getUser()
|
|
}
|
|
|
|
public getUserPasswordCreationDate(): Date | undefined {
|
|
return this.encryption.getPasswordCreatedDate()
|
|
}
|
|
|
|
public getProtocolEncryptionDisplayName(): Promise<string | undefined> {
|
|
return this.encryption.getEncryptionDisplayName()
|
|
}
|
|
|
|
public getUserVersion(): ProtocolVersion | undefined {
|
|
return this.encryption.getUserVersion()
|
|
}
|
|
|
|
/**
|
|
* Returns true if there is an upgrade available for the account or passcode
|
|
*/
|
|
public protocolUpgradeAvailable(): Promise<boolean> {
|
|
return this.encryption.upgradeAvailable()
|
|
}
|
|
|
|
/**
|
|
* Returns true if there is an encryption source available
|
|
*/
|
|
public isEncryptionAvailable(): boolean {
|
|
return this.hasAccount() || this.hasPasscode()
|
|
}
|
|
|
|
public async upgradeProtocolVersion(): Promise<{
|
|
success?: true
|
|
canceled?: true
|
|
error?: {
|
|
message: string
|
|
}
|
|
}> {
|
|
const result = await this.user.performProtocolUpgrade()
|
|
if (result.success) {
|
|
if (this.hasAccount()) {
|
|
void this.alerts.alert(ProtocolUpgradeStrings.SuccessAccount)
|
|
} else {
|
|
void this.alerts.alert(ProtocolUpgradeStrings.SuccessPasscodeOnly)
|
|
}
|
|
} else if (result.error) {
|
|
void this.alerts.alert(ProtocolUpgradeStrings.Fail)
|
|
}
|
|
return result
|
|
}
|
|
|
|
public noAccount(): boolean {
|
|
return !this.hasAccount()
|
|
}
|
|
|
|
public hasAccount(): boolean {
|
|
return this.encryption.hasAccount()
|
|
}
|
|
|
|
/**
|
|
* @returns true if the user has a source of protection available, such as a
|
|
* passcode, password, or biometrics.
|
|
*/
|
|
public hasProtectionSources(): boolean {
|
|
return this.protections.hasProtectionSources()
|
|
}
|
|
|
|
public hasUnprotectedAccessSession(): boolean {
|
|
return this.protections.hasUnprotectedAccessSession()
|
|
}
|
|
|
|
/**
|
|
* When a user specifies a non-zero remember duration on a protection
|
|
* challenge, a session will be started during which protections are disabled.
|
|
*/
|
|
public getProtectionSessionExpiryDate(): Date {
|
|
return this.protections.getSessionExpiryDate()
|
|
}
|
|
|
|
public clearProtectionSession(): Promise<void> {
|
|
return this.protections.clearSession()
|
|
}
|
|
|
|
public async authorizeProtectedActionForNotes(notes: SNNote[], challengeReason: ChallengeReason): Promise<SNNote[]> {
|
|
return await this.protections.authorizeProtectedActionForItems(notes, challengeReason)
|
|
}
|
|
|
|
/**
|
|
* @returns whether note access has been granted or not
|
|
*/
|
|
public authorizeNoteAccess(note: SNNote): Promise<boolean> {
|
|
return this.protections.authorizeItemAccess(note)
|
|
}
|
|
|
|
public authorizeAutolockIntervalChange(): Promise<boolean> {
|
|
return this.protections.authorizeAutolockIntervalChange()
|
|
}
|
|
|
|
public authorizeSearchingProtectedNotesText(): Promise<boolean> {
|
|
return this.protections.authorizeSearchingProtectedNotesText()
|
|
}
|
|
|
|
public async createEncryptedBackupFileForAutomatedDesktopBackups(): Promise<BackupFile | undefined> {
|
|
return this.encryption.createEncryptedBackupFile()
|
|
}
|
|
|
|
public async createEncryptedBackupFile(): Promise<BackupFile | undefined> {
|
|
if (!(await this.protections.authorizeBackupCreation())) {
|
|
return
|
|
}
|
|
|
|
return this.encryption.createEncryptedBackupFile()
|
|
}
|
|
|
|
public async createDecryptedBackupFile(): Promise<BackupFile | undefined> {
|
|
if (!(await this.protections.authorizeBackupCreation())) {
|
|
return
|
|
}
|
|
|
|
return this.encryption.createDecryptedBackupFile()
|
|
}
|
|
|
|
public isEphemeralSession(): boolean {
|
|
return this.storage.isEphemeralSession()
|
|
}
|
|
|
|
public setValue(key: string, value: unknown, mode?: StorageValueModes): void {
|
|
return this.storage.setValue(key, value, mode)
|
|
}
|
|
|
|
public getValue<T>(key: string, mode?: StorageValueModes): T {
|
|
return this.storage.getValue<T>(key, mode)
|
|
}
|
|
|
|
public async removeValue(key: string, mode?: StorageValueModes): Promise<void> {
|
|
return this.storage.removeValue(key, mode)
|
|
}
|
|
|
|
public getPreference<K extends PrefKey>(key: K): PrefValue[K] | undefined
|
|
public getPreference<K extends PrefKey>(key: K, defaultValue: PrefValue[K]): PrefValue[K]
|
|
public getPreference<K extends PrefKey>(key: K, defaultValue?: PrefValue[K]): PrefValue[K] | undefined {
|
|
return this.preferences.getValue(key, defaultValue)
|
|
}
|
|
|
|
public async setPreference<K extends PrefKey>(key: K, value: PrefValue[K]): Promise<void> {
|
|
return this.preferences.setValue(key, value)
|
|
}
|
|
|
|
/**
|
|
* Gives services a chance to complete any sensitive operations before yielding
|
|
* @param maxWait The maximum number of milliseconds to wait for services
|
|
* to finish tasks. 0 means no limit.
|
|
*/
|
|
private async prepareForDeinit(maxWait = 0): Promise<void> {
|
|
const deps = this.dependencies.getAll().filter(canBlockDeinit)
|
|
const promise = Promise.all(deps.map((service) => service.blockDeinit()))
|
|
if (maxWait === 0) {
|
|
await promise
|
|
} else {
|
|
/** Await up to maxWait. If not resolved by then, return. */
|
|
await Promise.race([promise, sleep(maxWait)])
|
|
}
|
|
}
|
|
|
|
public addChallengeObserver(challenge: Challenge, observer: ChallengeObserver): () => void {
|
|
return this.challenges.addChallengeObserver(challenge, observer)
|
|
}
|
|
|
|
public submitValuesForChallenge(challenge: Challenge, values: ChallengeValue[]): Promise<void> {
|
|
return this.challenges.submitValuesForChallenge(challenge, values)
|
|
}
|
|
|
|
public cancelChallenge(challenge: Challenge): void {
|
|
this.challenges.cancelChallenge(challenge)
|
|
}
|
|
|
|
public setOnDeinit(onDeinit: DeinitCallback): void {
|
|
this.onDeinit = onDeinit
|
|
}
|
|
|
|
/**
|
|
* Destroys the application instance.
|
|
*/
|
|
public deinit(mode: DeinitMode, source: DeinitSource): void {
|
|
this.dealloced = true
|
|
|
|
clearInterval(this.autoSyncInterval)
|
|
;(this.autoSyncInterval as unknown) = undefined
|
|
|
|
for (const uninstallObserver of this.serviceObservers) {
|
|
uninstallObserver()
|
|
}
|
|
|
|
for (const uninstallSubscriber of this.managedSubscribers) {
|
|
uninstallSubscriber()
|
|
}
|
|
|
|
this.options.crypto.deinit()
|
|
;(this.options as unknown) = undefined
|
|
|
|
this.createdNewDatabase = false
|
|
|
|
this.serviceObservers.length = 0
|
|
this.managedSubscribers.length = 0
|
|
this.streamRemovers.length = 0
|
|
|
|
this.started = false
|
|
|
|
this.dependencies.deinit()
|
|
|
|
this.onDeinit?.(this, mode, source)
|
|
;(this.onDeinit as unknown) = undefined
|
|
}
|
|
|
|
/**
|
|
* @param mergeLocal Whether to merge existing offline data into account. If false,
|
|
* any pre-existing data will be fully deleted upon success.
|
|
*/
|
|
public async register(
|
|
email: string,
|
|
password: string,
|
|
ephemeral = false,
|
|
mergeLocal = true,
|
|
): Promise<UserRegistrationResponseBody> {
|
|
return this.user.register(email, password, ephemeral, mergeLocal)
|
|
}
|
|
|
|
/**
|
|
* @param mergeLocal Whether to merge existing offline data into account.
|
|
* If false, any pre-existing data will be fully deleted upon success.
|
|
*/
|
|
public async signIn(
|
|
email: string,
|
|
password: string,
|
|
strict = false,
|
|
ephemeral = false,
|
|
mergeLocal = true,
|
|
awaitSync = false,
|
|
): Promise<HttpResponse<SignInResponse>> {
|
|
return this.user.signIn(email, password, strict, ephemeral, mergeLocal, awaitSync)
|
|
}
|
|
|
|
public async changeEmail(
|
|
newEmail: string,
|
|
currentPassword: string,
|
|
passcode?: string,
|
|
origination = KeyParamsOrigination.EmailChange,
|
|
): Promise<CredentialsChangeFunctionResponse> {
|
|
return this.user.changeCredentials({
|
|
currentPassword,
|
|
newEmail,
|
|
passcode,
|
|
origination,
|
|
validateNewPasswordStrength: false,
|
|
})
|
|
}
|
|
|
|
public async changePassword(
|
|
currentPassword: string,
|
|
newPassword: string,
|
|
passcode?: string,
|
|
origination = KeyParamsOrigination.PasswordChange,
|
|
validateNewPasswordStrength = true,
|
|
): Promise<CredentialsChangeFunctionResponse> {
|
|
return this.user.changeCredentials({
|
|
currentPassword,
|
|
newPassword,
|
|
passcode,
|
|
origination,
|
|
validateNewPasswordStrength,
|
|
})
|
|
}
|
|
|
|
public async changeAndSaveItem<M extends DecryptedItemMutator = DecryptedItemMutator>(
|
|
itemToLookupUuidFor: DecryptedItemInterface,
|
|
mutate: (mutator: M) => void,
|
|
updateTimestamps = true,
|
|
emitSource?: PayloadEmitSource,
|
|
syncOptions?: SyncOptions,
|
|
): Promise<DecryptedItemInterface | undefined> {
|
|
await this.mutator.changeItems(
|
|
[itemToLookupUuidFor],
|
|
mutate,
|
|
updateTimestamps ? MutationType.UpdateUserTimestamps : MutationType.NoUpdateUserTimestamps,
|
|
emitSource,
|
|
)
|
|
await this.sync.sync(syncOptions)
|
|
return this.items.findItem(itemToLookupUuidFor.uuid)
|
|
}
|
|
|
|
public async changeAndSaveItems<M extends DecryptedItemMutator = DecryptedItemMutator>(
|
|
itemsToLookupUuidsFor: DecryptedItemInterface[],
|
|
mutate: (mutator: M) => void,
|
|
updateTimestamps = true,
|
|
emitSource?: PayloadEmitSource,
|
|
syncOptions?: SyncOptions,
|
|
): Promise<void> {
|
|
await this.mutator.changeItems(
|
|
itemsToLookupUuidsFor,
|
|
mutate,
|
|
updateTimestamps ? MutationType.UpdateUserTimestamps : MutationType.NoUpdateUserTimestamps,
|
|
emitSource,
|
|
)
|
|
await this.sync.sync(syncOptions)
|
|
}
|
|
|
|
public async importData(data: BackupFile, awaitSync = false): Promise<ImportDataReturnType> {
|
|
const usecase = this.dependencies.get<ImportDataUseCase>(TYPES.ImportDataUseCase)
|
|
return usecase.execute(data, awaitSync)
|
|
}
|
|
|
|
private async handleRevokedSession(): Promise<void> {
|
|
/**
|
|
* Because multiple API requests can come back at the same time
|
|
* indicating revoked session we only want to do this once.
|
|
*/
|
|
if (this.revokingSession) {
|
|
return
|
|
}
|
|
this.revokingSession = true
|
|
/** Keep a reference to the soon-to-be-cleared alertService */
|
|
const alertService = this.alerts
|
|
await this.user.signOut(true)
|
|
void alertService.alert(SessionStrings.CurrentSessionRevoked)
|
|
}
|
|
|
|
public async validateAccountPassword(password: string): Promise<boolean> {
|
|
const { valid } = await this.encryption.validateAccountPassword(password)
|
|
return valid
|
|
}
|
|
|
|
public isStarted(): boolean {
|
|
return this.started
|
|
}
|
|
|
|
public isLaunched(): boolean {
|
|
return this.launched
|
|
}
|
|
|
|
public hasPasscode(): boolean {
|
|
return this.encryption.hasPasscode()
|
|
}
|
|
|
|
async isLocked(): Promise<boolean> {
|
|
if (!this.started) {
|
|
return Promise.resolve(true)
|
|
}
|
|
const isPasscodeLocked = await this.challenges.isPasscodeLocked()
|
|
return isPasscodeLocked || this.isBiometricsSoftLockEngaged
|
|
}
|
|
|
|
public async lock(): Promise<void> {
|
|
/** Because locking is a critical operation, we want to try to do it safely,
|
|
* but only up to a certain limit. */
|
|
const MaximumWaitTime = 500
|
|
await this.prepareForDeinit(MaximumWaitTime)
|
|
return this.deinit(this.getDeinitMode(), DeinitSource.Lock)
|
|
}
|
|
|
|
public softLockBiometrics(): void {
|
|
const challenge = new Challenge(
|
|
[new ChallengePrompt(ChallengeValidation.Biometric)],
|
|
ChallengeReason.ApplicationUnlock,
|
|
false,
|
|
)
|
|
|
|
void this.challenges.promptForChallengeResponse(challenge)
|
|
|
|
this.isBiometricsSoftLockEngaged = true
|
|
void this.notifyEvent(ApplicationEvent.BiometricsSoftLockEngaged)
|
|
|
|
this.addChallengeObserver(challenge, {
|
|
onComplete: () => {
|
|
this.isBiometricsSoftLockEngaged = false
|
|
void this.notifyEvent(ApplicationEvent.BiometricsSoftLockDisengaged)
|
|
},
|
|
})
|
|
}
|
|
|
|
isNativeMobileWeb() {
|
|
return this.environment === Environment.Mobile
|
|
}
|
|
|
|
getDeinitMode(): DeinitMode {
|
|
const value = this.getValue(StorageKey.DeinitMode)
|
|
if (value === 'hard') {
|
|
return DeinitMode.Hard
|
|
}
|
|
|
|
return DeinitMode.Soft
|
|
}
|
|
|
|
public addPasscode(passcode: string): Promise<boolean> {
|
|
return this.user.addPasscode(passcode)
|
|
}
|
|
|
|
/**
|
|
* @returns whether the passcode was successfuly removed
|
|
*/
|
|
public async removePasscode(): Promise<boolean> {
|
|
return this.user.removePasscode()
|
|
}
|
|
|
|
public async changePasscode(
|
|
newPasscode: string,
|
|
origination = KeyParamsOrigination.PasscodeChange,
|
|
): Promise<boolean> {
|
|
return this.user.changePasscode(newPasscode, origination)
|
|
}
|
|
|
|
public enableEphemeralPersistencePolicy(): Promise<void> {
|
|
return this.storage.setPersistencePolicy(StoragePersistencePolicies.Ephemeral)
|
|
}
|
|
|
|
public hasPendingMigrations(): Promise<boolean> {
|
|
return this.migrations.hasPendingMigrations()
|
|
}
|
|
|
|
public generateUuid(): string {
|
|
return UuidGenerator.GenerateUuid()
|
|
}
|
|
|
|
public presentKeyRecoveryWizard(): void {
|
|
const service = this.dependencies.get<KeyRecoveryService>(TYPES.KeyRecoveryService)
|
|
return service.presentKeyRecoveryWizard()
|
|
}
|
|
|
|
public canAttemptDecryptionOfItem(item: EncryptedItemInterface): ClientDisplayableError | true {
|
|
const service = this.dependencies.get<KeyRecoveryService>(TYPES.KeyRecoveryService)
|
|
return service.canAttemptDecryptionOfItem(item)
|
|
}
|
|
|
|
public async isMfaActivated(): Promise<boolean> {
|
|
return this.mfa.isMfaActivated()
|
|
}
|
|
|
|
public async generateMfaSecret(): Promise<string> {
|
|
return this.mfa.generateMfaSecret()
|
|
}
|
|
|
|
public async getOtpToken(secret: string): Promise<string> {
|
|
return this.mfa.getOtpToken(secret)
|
|
}
|
|
|
|
public async enableMfa(secret: string, otpToken: string): Promise<void> {
|
|
return this.mfa.enableMfa(secret, otpToken)
|
|
}
|
|
|
|
public async disableMfa(): Promise<void> {
|
|
if (await this.protections.authorizeMfaDisable()) {
|
|
return this.mfa.disableMfa()
|
|
}
|
|
}
|
|
|
|
public getNewSubscriptionToken(): Promise<string | undefined> {
|
|
return this.legacyApi.getNewSubscriptionToken()
|
|
}
|
|
|
|
public isThirdPartyHostUsed(): boolean {
|
|
return this.legacyApi.isThirdPartyHostUsed()
|
|
}
|
|
|
|
async isUsingHomeServer(): Promise<boolean> {
|
|
const homeServerService = this.dependencies.get<HomeServerServiceInterface>(TYPES.HomeServerService)
|
|
|
|
if (!homeServerService) {
|
|
return false
|
|
}
|
|
|
|
return this.getHost() === (await homeServerService.getHomeServerUrl())
|
|
}
|
|
|
|
private createBackgroundDependencies() {
|
|
this.dependencies.get(TYPES.UserEventService)
|
|
this.dependencies.get(TYPES.KeyRecoveryService)
|
|
}
|
|
|
|
private defineInternalEventHandlers(): void {
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.FeaturesService), ApiServiceEvent.MetaReceived)
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.IntegrityService), SyncEvent.SyncRequestsIntegrityCheck)
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.SyncService), IntegrityEvent.IntegrityCheckCompleted)
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.UserService), AccountEvent.SignedInOrRegistered)
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.SessionManager), ApiServiceEvent.SessionRefreshed)
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.SubscriptionManager), SessionEvent.Restored)
|
|
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SyncEvent.ReceivedSharedVaultInvites)
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.VaultInviteService), SessionEvent.UserKeyPairChanged)
|
|
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SessionEvent.UserKeyPairChanged)
|
|
this.events.addEventHandler(
|
|
this.dependencies.get(TYPES.SharedVaultService),
|
|
UserEventServiceEvent.UserEventReceived,
|
|
)
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), VaultServiceEvent.VaultRootKeyRotated)
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SyncEvent.ReceivedRemoteSharedVaults)
|
|
|
|
this.events.addEventHandler(
|
|
this.dependencies.get(TYPES.AsymmetricMessageService),
|
|
SyncEvent.ReceivedAsymmetricMessages,
|
|
)
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.AsymmetricMessageService), SessionEvent.UserKeyPairChanged)
|
|
|
|
if (this.dependencies.get(TYPES.FilesBackupService)) {
|
|
this.events.addEventHandler(
|
|
this.dependencies.get(TYPES.FilesBackupService),
|
|
ApplicationEvent.ApplicationStageChanged,
|
|
)
|
|
}
|
|
if (this.dependencies.get(TYPES.HomeServerService)) {
|
|
this.events.addEventHandler(
|
|
this.dependencies.get(TYPES.HomeServerService),
|
|
ApplicationEvent.ApplicationStageChanged,
|
|
)
|
|
}
|
|
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.SessionManager), ApplicationEvent.ApplicationStageChanged)
|
|
this.events.addEventHandler(
|
|
this.dependencies.get(TYPES.SelfContactManager),
|
|
ApplicationEvent.ApplicationStageChanged,
|
|
)
|
|
this.events.addEventHandler(
|
|
this.dependencies.get(TYPES.KeySystemKeyManager),
|
|
ApplicationEvent.ApplicationStageChanged,
|
|
)
|
|
this.events.addEventHandler(
|
|
this.dependencies.get(TYPES.SubscriptionManager),
|
|
ApplicationEvent.ApplicationStageChanged,
|
|
)
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.FeaturesService), ApplicationEvent.ApplicationStageChanged)
|
|
this.events.addEventHandler(
|
|
this.dependencies.get(TYPES.KeyRecoveryService),
|
|
ApplicationEvent.ApplicationStageChanged,
|
|
)
|
|
this.events.addEventHandler(this.dependencies.get(TYPES.MigrationService), ApplicationEvent.ApplicationStageChanged)
|
|
this.events.addEventHandler(
|
|
this.dependencies.get(TYPES.PreferencesService),
|
|
ApplicationEvent.ApplicationStageChanged,
|
|
)
|
|
this.events.addEventHandler(
|
|
this.dependencies.get(TYPES.ProtectionService),
|
|
ApplicationEvent.ApplicationStageChanged,
|
|
)
|
|
this.events.addEventHandler(
|
|
this.dependencies.get(TYPES.DiskStorageService),
|
|
ApplicationEvent.ApplicationStageChanged,
|
|
)
|
|
}
|
|
|
|
get device(): DeviceInterface {
|
|
return this.dependencies.get<DeviceInterface>(TYPES.DeviceInterface)
|
|
}
|
|
|
|
get subscriptions(): SubscriptionManagerInterface {
|
|
return this.dependencies.get<SubscriptionManagerInterface>(TYPES.SubscriptionManager)
|
|
}
|
|
|
|
get signInWithRecoveryCodes(): SignInWithRecoveryCodes {
|
|
return this.dependencies.get<SignInWithRecoveryCodes>(TYPES.SignInWithRecoveryCodes)
|
|
}
|
|
|
|
get getRecoveryCodes(): GetRecoveryCodes {
|
|
return this.dependencies.get<GetRecoveryCodes>(TYPES.GetRecoveryCodes)
|
|
}
|
|
|
|
get addAuthenticator(): AddAuthenticator {
|
|
return this.dependencies.get<AddAuthenticator>(TYPES.AddAuthenticator)
|
|
}
|
|
|
|
get listAuthenticators(): ListAuthenticators {
|
|
return this.dependencies.get<ListAuthenticators>(TYPES.ListAuthenticators)
|
|
}
|
|
|
|
get deleteAuthenticator(): DeleteAuthenticator {
|
|
return this.dependencies.get<DeleteAuthenticator>(TYPES.DeleteAuthenticator)
|
|
}
|
|
|
|
get getAuthenticatorAuthenticationOptions(): GetAuthenticatorAuthenticationOptions {
|
|
return this.dependencies.get<GetAuthenticatorAuthenticationOptions>(TYPES.GetAuthenticatorAuthenticationOptions)
|
|
}
|
|
|
|
get getAuthenticatorAuthenticationResponse(): GetAuthenticatorAuthenticationResponse {
|
|
return this.dependencies.get<GetAuthenticatorAuthenticationResponse>(TYPES.GetAuthenticatorAuthenticationResponse)
|
|
}
|
|
|
|
get listRevisions(): ListRevisions {
|
|
return this.dependencies.get<ListRevisions>(TYPES.ListRevisions)
|
|
}
|
|
|
|
get getRevision(): GetRevision {
|
|
return this.dependencies.get<GetRevision>(TYPES.GetRevision)
|
|
}
|
|
|
|
get deleteRevision(): DeleteRevision {
|
|
return this.dependencies.get<DeleteRevision>(TYPES.DeleteRevision)
|
|
}
|
|
|
|
public get files(): FilesClientInterface {
|
|
return this.dependencies.get<FilesClientInterface>(TYPES.FileService)
|
|
}
|
|
|
|
public get features(): FeaturesClientInterface {
|
|
return this.dependencies.get<FeaturesClientInterface>(TYPES.FeaturesService)
|
|
}
|
|
|
|
public get items(): ItemManagerInterface {
|
|
return this.dependencies.get<ItemManagerInterface>(TYPES.ItemManager)
|
|
}
|
|
|
|
public get payloads(): PayloadManagerInterface {
|
|
return this.dependencies.get<PayloadManagerInterface>(TYPES.PayloadManager)
|
|
}
|
|
|
|
public get protections(): ProtectionsClientInterface {
|
|
return this.dependencies.get<ProtectionsClientInterface>(TYPES.ProtectionService)
|
|
}
|
|
|
|
public get sync(): SyncServiceInterface {
|
|
return this.dependencies.get<SyncServiceInterface>(TYPES.SyncService)
|
|
}
|
|
|
|
public get user(): UserClientInterface {
|
|
return this.dependencies.get<UserClientInterface>(TYPES.UserService)
|
|
}
|
|
|
|
public get settings(): SettingsService {
|
|
return this.dependencies.get<SettingsService>(TYPES.SettingsService)
|
|
}
|
|
|
|
public get mutator(): MutatorClientInterface {
|
|
return this.dependencies.get<MutatorClientInterface>(TYPES.MutatorService)
|
|
}
|
|
|
|
public get sessions(): SessionsClientInterface {
|
|
return this.dependencies.get<SessionsClientInterface>(TYPES.SessionManager)
|
|
}
|
|
|
|
public get status(): StatusServiceInterface {
|
|
return this.dependencies.get<StatusServiceInterface>(TYPES.StatusService)
|
|
}
|
|
|
|
public get fileBackups(): BackupServiceInterface | undefined {
|
|
return this.dependencies.get<BackupServiceInterface | undefined>(TYPES.FilesBackupService)
|
|
}
|
|
|
|
public get componentManager(): ComponentManagerInterface {
|
|
return this.dependencies.get<ComponentManagerInterface>(TYPES.ComponentManager)
|
|
}
|
|
|
|
public get listed(): ListedClientInterface {
|
|
return this.dependencies.get<ListedClientInterface>(TYPES.ListedService)
|
|
}
|
|
|
|
public get alerts(): AlertService {
|
|
return this.dependencies.get<AlertService>(TYPES.AlertService)
|
|
}
|
|
|
|
public get storage(): StorageServiceInterface {
|
|
return this.dependencies.get<StorageServiceInterface>(TYPES.DiskStorageService)
|
|
}
|
|
|
|
public get actions(): ActionsService {
|
|
return this.dependencies.get<ActionsService>(TYPES.ActionsService)
|
|
}
|
|
|
|
public get challenges(): ChallengeServiceInterface {
|
|
return this.dependencies.get<ChallengeServiceInterface>(TYPES.ChallengeService)
|
|
}
|
|
|
|
public get asymmetric(): AsymmetricMessageServiceInterface {
|
|
return this.dependencies.get<AsymmetricMessageServiceInterface>(TYPES.AsymmetricMessageService)
|
|
}
|
|
|
|
get homeServer(): HomeServerServiceInterface | undefined {
|
|
return this.dependencies.get<HomeServerServiceInterface | undefined>(TYPES.HomeServerService)
|
|
}
|
|
|
|
public get preferences(): PreferenceServiceInterface {
|
|
return this.dependencies.get<PreferenceServiceInterface>(TYPES.PreferencesService)
|
|
}
|
|
|
|
public get history(): HistoryServiceInterface {
|
|
return this.dependencies.get<HistoryServiceInterface>(TYPES.HistoryManager)
|
|
}
|
|
|
|
public get encryption(): EncryptionProviderInterface {
|
|
return this.dependencies.get<EncryptionProviderInterface>(TYPES.EncryptionService)
|
|
}
|
|
|
|
public get events(): InternalEventBusInterface {
|
|
return this.dependencies.get<InternalEventBusInterface>(TYPES.InternalEventBus)
|
|
}
|
|
|
|
public get vaults(): VaultServiceInterface {
|
|
return this.dependencies.get<VaultServiceInterface>(TYPES.VaultService)
|
|
}
|
|
|
|
public get vaultLocks(): VaultLockServiceInterface {
|
|
return this.dependencies.get<VaultLockServiceInterface>(TYPES.VaultLockService)
|
|
}
|
|
|
|
public get vaultUsers(): VaultUserServiceInterface {
|
|
return this.dependencies.get<VaultUserServiceInterface>(TYPES.VaultUserService)
|
|
}
|
|
|
|
public get vaultInvites(): VaultInviteServiceInterface {
|
|
return this.dependencies.get<VaultInviteServiceInterface>(TYPES.VaultInviteService)
|
|
}
|
|
|
|
public get contacts(): ContactServiceInterface {
|
|
return this.dependencies.get<ContactServiceInterface>(TYPES.ContactService)
|
|
}
|
|
|
|
public get sharedVaults(): SharedVaultServiceInterface {
|
|
return this.dependencies.get<SharedVaultServiceInterface>(TYPES.SharedVaultService)
|
|
}
|
|
|
|
private get migrations(): MigrationService {
|
|
return this.dependencies.get<MigrationService>(TYPES.MigrationService)
|
|
}
|
|
|
|
private get legacyApi(): LegacyApiService {
|
|
return this.dependencies.get<LegacyApiService>(TYPES.LegacyApiService)
|
|
}
|
|
|
|
private get http(): HttpServiceInterface {
|
|
return this.dependencies.get<HttpServiceInterface>(TYPES.HttpService)
|
|
}
|
|
|
|
private get sockets(): WebSocketsService {
|
|
return this.dependencies.get<WebSocketsService>(TYPES.WebSocketsService)
|
|
}
|
|
|
|
private get mfa(): SNMfaService {
|
|
return this.dependencies.get<SNMfaService>(TYPES.MfaService)
|
|
}
|
|
}
|