import { BehaviorSubject, concatMap } from "rxjs"; import { Jsonify, JsonValue } from "type-fest"; import { LogService } from "../abstractions/log.service"; import { StateService as StateServiceAbstraction } from "../abstractions/state.service"; import { StateMigrationService } from "../abstractions/stateMigration.service"; import { AbstractMemoryStorageService, AbstractStorageService, } from "../abstractions/storage.service"; import { CollectionData } from "../admin-console/models/data/collection.data"; import { EncryptedOrganizationKeyData } from "../admin-console/models/data/encrypted-organization-key.data"; import { OrganizationData } from "../admin-console/models/data/organization.data"; import { PolicyData } from "../admin-console/models/data/policy.data"; import { ProviderData } from "../admin-console/models/data/provider.data"; import { Policy } from "../admin-console/models/domain/policy"; import { CollectionView } from "../admin-console/models/view/collection.view"; import { EnvironmentUrls } from "../auth/models/domain/environment-urls"; import { ForceResetPasswordReason } from "../auth/models/domain/force-reset-password-reason"; import { KdfConfig } from "../auth/models/domain/kdf-config"; import { BiometricKey } from "../auth/types/biometric-key"; import { HtmlStorageLocation, KdfType, StorageLocation, ThemeType, UriMatchType } from "../enums"; import { VaultTimeoutAction } from "../enums/vault-timeout-action.enum"; import { StateFactory } from "../factories/stateFactory"; import { Utils } from "../misc/utils"; import { EventData } from "../models/data/event.data"; import { ServerConfigData } from "../models/data/server-config.data"; import { Account, AccountData, AccountSettings, AccountSettingsSettings, } from "../models/domain/account"; import { EncString } from "../models/domain/enc-string"; import { GlobalState } from "../models/domain/global-state"; import { State } from "../models/domain/state"; import { StorageOptions } from "../models/domain/storage-options"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { WindowState } from "../models/domain/window-state"; import { GeneratedPasswordHistory } from "../tools/generator/password"; import { SendData } from "../tools/send/models/data/send.data"; import { SendView } from "../tools/send/models/view/send.view"; import { CipherData } from "../vault/models/data/cipher.data"; import { FolderData } from "../vault/models/data/folder.data"; import { LocalData } from "../vault/models/data/local.data"; import { CipherView } from "../vault/models/view/cipher.view"; import { AddEditCipherInfo } from "../vault/types/add-edit-cipher-info"; const keys = { state: "state", global: "global", authenticatedAccounts: "authenticatedAccounts", activeUserId: "activeUserId", tempAccountSettings: "tempAccountSettings", // used to hold account specific settings (i.e clear clipboard) between initial migration and first account authentication accountActivity: "accountActivity", }; const partialKeys = { autoKey: "_masterkey_auto", biometricKey: "_masterkey_biometric", masterKey: "_masterkey", }; const DDG_SHARED_KEY = "DuckDuckGoSharedKey"; export class StateService< TGlobalState extends GlobalState = GlobalState, TAccount extends Account = Account > implements StateServiceAbstraction { protected accountsSubject = new BehaviorSubject<{ [userId: string]: TAccount }>({}); accounts$ = this.accountsSubject.asObservable(); protected activeAccountSubject = new BehaviorSubject(null); activeAccount$ = this.activeAccountSubject.asObservable(); protected activeAccountUnlockedSubject = new BehaviorSubject(false); activeAccountUnlocked$ = this.activeAccountUnlockedSubject.asObservable(); private hasBeenInited = false; private isRecoveredSession = false; protected accountDiskCache = new BehaviorSubject>({}); // default account serializer, must be overridden by child class protected accountDeserializer = Account.fromJSON as (json: Jsonify) => TAccount; constructor( protected storageService: AbstractStorageService, protected secureStorageService: AbstractStorageService, protected memoryStorageService: AbstractMemoryStorageService, protected logService: LogService, protected stateMigrationService: StateMigrationService, protected stateFactory: StateFactory, protected useAccountCache: boolean = true ) { // If the account gets changed, verify the new account is unlocked this.activeAccountSubject .pipe( concatMap(async (userId) => { if (userId == null && this.activeAccountUnlockedSubject.getValue() == false) { return; } else if (userId == null) { this.activeAccountUnlockedSubject.next(false); } // FIXME: This should be refactored into AuthService or a similar service, // as checking for the existance of the crypto key is a low level // implementation detail. this.activeAccountUnlockedSubject.next((await this.getCryptoMasterKey()) != null); }) ) .subscribe(); } async init(): Promise { if (this.hasBeenInited) { return; } if (await this.stateMigrationService.needsMigration()) { await this.stateMigrationService.migrate(); } await this.state().then(async (state) => { if (state == null) { await this.setState(new State(this.createGlobals())); } else { this.isRecoveredSession = true; } }); await this.initAccountState(); this.hasBeenInited = true; } async initAccountState() { if (this.isRecoveredSession) { return; } await this.updateState(async (state) => { state.authenticatedAccounts = (await this.storageService.get(keys.authenticatedAccounts)) ?? []; for (const i in state.authenticatedAccounts) { if (i != null) { await this.syncAccountFromDisk(state.authenticatedAccounts[i]); } } const storedActiveUser = await this.storageService.get(keys.activeUserId); if (storedActiveUser != null) { state.activeUserId = storedActiveUser; } await this.pushAccounts(); this.activeAccountSubject.next(state.activeUserId); return state; }); } async syncAccountFromDisk(userId: string) { if (userId == null) { return; } await this.updateState(async (state) => { if (state.accounts == null) { state.accounts = {}; } state.accounts[userId] = this.createAccount(); const diskAccount = await this.getAccountFromDisk({ userId: userId }); state.accounts[userId].profile = diskAccount.profile; return state; }); } async addAccount(account: TAccount) { account = await this.setAccountEnvironmentUrls(account); await this.updateState(async (state) => { state.authenticatedAccounts.push(account.profile.userId); await this.storageService.save(keys.authenticatedAccounts, state.authenticatedAccounts); state.accounts[account.profile.userId] = account; return state; }); await this.scaffoldNewAccountStorage(account); await this.setLastActive(new Date().getTime(), { userId: account.profile.userId }); await this.setActiveUser(account.profile.userId); this.activeAccountSubject.next(account.profile.userId); } async setActiveUser(userId: string): Promise { this.clearDecryptedDataForActiveUser(); await this.updateState(async (state) => { state.activeUserId = userId; await this.storageService.save(keys.activeUserId, userId); this.activeAccountSubject.next(state.activeUserId); return state; }); await this.pushAccounts(); } async clean(options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultInMemoryOptions()); await this.deAuthenticateAccount(options.userId); if (options.userId === (await this.state())?.activeUserId) { await this.dynamicallySetActiveUser(); } await this.removeAccountFromDisk(options?.userId); this.removeAccountFromMemory(options?.userId); await this.pushAccounts(); } async getAccessToken(options?: StorageOptions): Promise { options = await this.getTimeoutBasedStorageOptions(options); return (await this.getAccount(options))?.tokens?.accessToken; } async setAccessToken(value: string, options?: StorageOptions): Promise { options = await this.getTimeoutBasedStorageOptions(options); const account = await this.getAccount(options); account.tokens.accessToken = value; await this.saveAccount(account, options); } async getAddEditCipherInfo(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.data?.addEditCipherInfo; } async setAddEditCipherInfo(value: AddEditCipherInfo, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.data.addEditCipherInfo = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getAlwaysShowDock(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.alwaysShowDock ?? false ); } async setAlwaysShowDock(value: boolean, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.alwaysShowDock = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getApiKeyClientId(options?: StorageOptions): Promise { options = await this.getTimeoutBasedStorageOptions(options); return (await this.getAccount(options))?.profile?.apiKeyClientId; } async setApiKeyClientId(value: string, options?: StorageOptions): Promise { options = await this.getTimeoutBasedStorageOptions(options); const account = await this.getAccount(options); account.profile.apiKeyClientId = value; await this.saveAccount(account, options); } async getApiKeyClientSecret(options?: StorageOptions): Promise { options = await this.getTimeoutBasedStorageOptions(options); return (await this.getAccount(options))?.keys?.apiKeyClientSecret; } async setApiKeyClientSecret(value: string, options?: StorageOptions): Promise { options = await this.getTimeoutBasedStorageOptions(options); const account = await this.getAccount(options); account.keys.apiKeyClientSecret = value; await this.saveAccount(account, options); } async getAutoConfirmFingerPrints(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.autoConfirmFingerPrints ?? false ); } async setAutoConfirmFingerprints(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.autoConfirmFingerPrints = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getAutoFillOnPageLoadDefault(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.autoFillOnPageLoadDefault ?? true ); } async setAutoFillOnPageLoadDefault(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.autoFillOnPageLoadDefault = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getBiometricAwaitingAcceptance(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.biometricAwaitingAcceptance ?? false ); } async setBiometricAwaitingAcceptance(value: boolean, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.biometricAwaitingAcceptance = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getBiometricFingerprintValidated(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.biometricFingerprintValidated ?? false ); } async setBiometricFingerprintValidated(value: boolean, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.biometricFingerprintValidated = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getBiometricText(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.biometricText; } async setBiometricText(value: string, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.biometricText = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getBiometricUnlock(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.biometricUnlock ?? false ); } async setBiometricUnlock(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.biometricUnlock = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getCanAccessPremium(options?: StorageOptions): Promise { if (!(await this.getIsAuthenticated(options))) { return false; } return ( (await this.getHasPremiumPersonally(options)) || (await this.getHasPremiumFromOrganization(options)) ); } async getHasPremiumPersonally(options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); return account?.profile?.hasPremiumPersonally; } async setHasPremiumPersonally(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.profile.hasPremiumPersonally = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getHasPremiumFromOrganization(options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); if (account.profile?.hasPremiumFromOrganization) { return true; } // TODO: older server versions won't send the hasPremiumFromOrganization flag, so we're keeping the old logic // for backwards compatibility. It can be removed after everyone has upgraded. const organizations = await this.getOrganizations(options); if (organizations == null) { return false; } for (const id of Object.keys(organizations)) { const o = organizations[id]; if (o.enabled && o.usersGetPremium && !o.isProviderUser) { return true; } } return false; } async setHasPremiumFromOrganization(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.profile.hasPremiumFromOrganization = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getClearClipboard(options?: StorageOptions): Promise { return ( ( await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ) )?.settings?.clearClipboard ?? null ); } async setClearClipboard(value: number, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.clearClipboard = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getCollapsedGroupings(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.collapsedGroupings; } async setCollapsedGroupings(value: string[], options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.collapsedGroupings = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getConvertAccountToKeyConnector(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.profile?.convertAccountToKeyConnector; } async setConvertAccountToKeyConnector(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.profile.convertAccountToKeyConnector = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getCryptoMasterKey(options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); return account?.keys?.cryptoMasterKey; } async setCryptoMasterKey(value: SymmetricCryptoKey, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.keys.cryptoMasterKey = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); if (options.userId == this.activeAccountSubject.getValue()) { const nextValue = value != null; // Avoid emitting if we are already unlocked if (this.activeAccountUnlockedSubject.getValue() != nextValue) { this.activeAccountUnlockedSubject.next(nextValue); } } } async getCryptoMasterKeyAuto(options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "auto" }), await this.defaultSecureStorageOptions() ); if (options?.userId == null) { return null; } return await this.secureStorageService.get( `${options.userId}${partialKeys.autoKey}`, options ); } async setCryptoMasterKeyAuto(value: string, options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "auto" }), await this.defaultSecureStorageOptions() ); if (options?.userId == null) { return; } await this.saveSecureStorageKey(partialKeys.autoKey, value, options); } async getCryptoMasterKeyB64(options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); if (options?.userId == null) { return null; } return await this.secureStorageService.get( `${options?.userId}${partialKeys.masterKey}`, options ); } async setCryptoMasterKeyB64(value: string, options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); if (options?.userId == null) { return; } await this.saveSecureStorageKey(partialKeys.masterKey, value, options); } async getCryptoMasterKeyBiometric(options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "biometric" }), await this.defaultSecureStorageOptions() ); if (options?.userId == null) { return null; } return await this.secureStorageService.get( `${options.userId}${partialKeys.biometricKey}`, options ); } async hasCryptoMasterKeyBiometric(options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "biometric" }), await this.defaultSecureStorageOptions() ); if (options?.userId == null) { return false; } return await this.secureStorageService.has( `${options.userId}${partialKeys.biometricKey}`, options ); } async setCryptoMasterKeyBiometric(value: BiometricKey, options?: StorageOptions): Promise { options = this.reconcileOptions( this.reconcileOptions(options, { keySuffix: "biometric" }), await this.defaultSecureStorageOptions() ); if (options?.userId == null) { return; } await this.saveSecureStorageKey(partialKeys.biometricKey, value, options); } @withPrototypeForArrayMembers(CipherView, CipherView.fromJSON) async getDecryptedCiphers(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.data?.ciphers?.decrypted; } async setDecryptedCiphers(value: CipherView[], options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.data.ciphers.decrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } @withPrototypeForArrayMembers(CollectionView) async getDecryptedCollections(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.data?.collections?.decrypted; } async setDecryptedCollections(value: CollectionView[], options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.data.collections.decrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getDecryptedCryptoSymmetricKey(options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); return account?.keys?.cryptoSymmetricKey?.decrypted; } async setDecryptedCryptoSymmetricKey( value: SymmetricCryptoKey, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.keys.cryptoSymmetricKey.decrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getDecryptedOrganizationKeys( options?: StorageOptions ): Promise> { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); return Utils.recordToMap(account?.keys?.organizationKeys?.decrypted); } async setDecryptedOrganizationKeys( value: Map, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.keys.organizationKeys.decrypted = Utils.mapToRecord(value); await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } @withPrototypeForArrayMembers(GeneratedPasswordHistory) async getDecryptedPasswordGenerationHistory( options?: StorageOptions ): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.data?.passwordGenerationHistory?.decrypted; } async setDecryptedPasswordGenerationHistory( value: GeneratedPasswordHistory[], options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.data.passwordGenerationHistory.decrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getDecryptedPinProtected(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.settings?.pinProtected?.decrypted; } async setDecryptedPinProtected(value: EncString, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.settings.pinProtected.decrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } @withPrototypeForArrayMembers(Policy) async getDecryptedPolicies(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.data?.policies?.decrypted; } async setDecryptedPolicies(value: Policy[], options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.data.policies.decrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getDecryptedPrivateKey(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.keys?.privateKey.decrypted; } async setDecryptedPrivateKey(value: ArrayBuffer, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.keys.privateKey.decrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getDecryptedProviderKeys( options?: StorageOptions ): Promise> { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); return Utils.recordToMap(account?.keys?.providerKeys?.decrypted); } async setDecryptedProviderKeys( value: Map, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.keys.providerKeys.decrypted = Utils.mapToRecord(value); await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } @withPrototypeForArrayMembers(SendView) async getDecryptedSends(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.data?.sends?.decrypted; } async setDecryptedSends(value: SendView[], options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.data.sends.decrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getDefaultUriMatch(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.settings?.defaultUriMatch; } async setDefaultUriMatch(value: UriMatchType, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.defaultUriMatch = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getDisableAddLoginNotification(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.disableAddLoginNotification ?? false ); } async setDisableAddLoginNotification(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.disableAddLoginNotification = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getDisableAutoBiometricsPrompt(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.disableAutoBiometricsPrompt ?? false ); } async setDisableAutoBiometricsPrompt(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.disableAutoBiometricsPrompt = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getDisableAutoTotpCopy(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.disableAutoTotpCopy ?? false ); } async setDisableAutoTotpCopy(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.disableAutoTotpCopy = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getDisableBadgeCounter(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.disableBadgeCounter ?? false ); } async setDisableBadgeCounter(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.disableBadgeCounter = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getDisableChangedPasswordNotification(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.disableChangedPasswordNotification ?? false ); } async setDisableChangedPasswordNotification( value: boolean, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.disableChangedPasswordNotification = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getDisableContextMenuItem(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.disableContextMenuItem ?? false ); } async setDisableContextMenuItem(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.disableContextMenuItem = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getDisableFavicon(options?: StorageOptions): Promise { return ( ( await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ) )?.disableFavicon ?? false ); } async setDisableFavicon(value: boolean, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); globals.disableFavicon = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getDisableGa(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.disableGa ?? false ); } async setDisableGa(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.disableGa = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getDismissedAutofillCallout(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.dismissedAutoFillOnPageLoadCallout ?? false ); } async setDismissedAutofillCallout(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.dismissedAutoFillOnPageLoadCallout = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getDontShowCardsCurrentTab(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.dontShowCardsCurrentTab ?? false ); } async setDontShowCardsCurrentTab(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.dontShowCardsCurrentTab = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getDontShowIdentitiesCurrentTab(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.dontShowIdentitiesCurrentTab ?? false ); } async setDontShowIdentitiesCurrentTab(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.dontShowIdentitiesCurrentTab = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getDuckDuckGoSharedKey(options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); if (options?.userId == null) { return null; } return await this.secureStorageService.get(DDG_SHARED_KEY, options); } async setDuckDuckGoSharedKey(value: string, options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultSecureStorageOptions()); if (options?.userId == null) { return; } value == null ? await this.secureStorageService.remove(DDG_SHARED_KEY, options) : await this.secureStorageService.save(DDG_SHARED_KEY, value, options); } async getEmail(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.profile?.email; } async setEmail(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.profile.email = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getEmailVerified(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.profile.emailVerified ?? false ); } async setEmailVerified(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.profile.emailVerified = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEnableAlwaysOnTop(options?: StorageOptions): Promise { const accountPreference = ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.settings?.enableAlwaysOnTop; const globalPreference = ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.enableAlwaysOnTop; return accountPreference ?? globalPreference ?? false; } async setEnableAlwaysOnTop(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.enableAlwaysOnTop = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.enableAlwaysOnTop = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEnableAutoFillOnPageLoad(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.enableAutoFillOnPageLoad ?? false ); } async setEnableAutoFillOnPageLoad(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.enableAutoFillOnPageLoad = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEnableBrowserIntegration(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.enableBrowserIntegration ?? false ); } async setEnableBrowserIntegration(value: boolean, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.enableBrowserIntegration = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEnableBrowserIntegrationFingerprint(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.enableBrowserIntegrationFingerprint ?? false ); } async setEnableBrowserIntegrationFingerprint( value: boolean, options?: StorageOptions ): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.enableBrowserIntegrationFingerprint = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEnableCloseToTray(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.enableCloseToTray ?? false ); } async setEnableCloseToTray(value: boolean, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.enableCloseToTray = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEnableDuckDuckGoBrowserIntegration(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.enableDuckDuckGoBrowserIntegration ?? false ); } async setEnableDuckDuckGoBrowserIntegration( value: boolean, options?: StorageOptions ): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.enableDuckDuckGoBrowserIntegration = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEnableFullWidth(options?: StorageOptions): Promise { return ( ( await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ) )?.settings?.enableFullWidth ?? false ); } async setEnableFullWidth(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.enableFullWidth = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getEnableMinimizeToTray(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.enableMinimizeToTray ?? false ); } async setEnableMinimizeToTray(value: boolean, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.enableMinimizeToTray = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEnableStartToTray(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.enableStartToTray ?? false ); } async setEnableStartToTray(value: boolean, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.enableStartToTray = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEnableTray(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.enableTray ?? false ); } async setEnableTray(value: boolean, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.enableTray = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } @withPrototypeForObjectValues(CipherData) async getEncryptedCiphers(options?: StorageOptions): Promise<{ [id: string]: CipherData }> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) )?.data?.ciphers?.encrypted; } async setEncryptedCiphers( value: { [id: string]: CipherData }, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); account.data.ciphers.encrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); } @withPrototypeForObjectValues(CollectionData) async getEncryptedCollections( options?: StorageOptions ): Promise<{ [id: string]: CollectionData }> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) )?.data?.collections?.encrypted; } async setEncryptedCollections( value: { [id: string]: CollectionData }, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); account.data.collections.encrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); } async getEncryptedCryptoSymmetricKey(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.keys.cryptoSymmetricKey.encrypted; } async setEncryptedCryptoSymmetricKey(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.keys.cryptoSymmetricKey.encrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } @withPrototypeForObjectValues(FolderData) async getEncryptedFolders(options?: StorageOptions): Promise<{ [id: string]: FolderData }> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) )?.data?.folders?.encrypted; } async setEncryptedFolders( value: { [id: string]: FolderData }, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); account.data.folders.encrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); } async getEncryptedOrganizationKeys( options?: StorageOptions ): Promise<{ [orgId: string]: EncryptedOrganizationKeyData }> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.keys?.organizationKeys.encrypted; } async setEncryptedOrganizationKeys( value: { [orgId: string]: EncryptedOrganizationKeyData }, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.keys.organizationKeys.encrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } @withPrototypeForArrayMembers(GeneratedPasswordHistory) async getEncryptedPasswordGenerationHistory( options?: StorageOptions ): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.data?.passwordGenerationHistory?.encrypted; } async setEncryptedPasswordGenerationHistory( value: GeneratedPasswordHistory[], options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.data.passwordGenerationHistory.encrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEncryptedPinProtected(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.settings?.pinProtected?.encrypted; } async setEncryptedPinProtected(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.pinProtected.encrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } @withPrototypeForObjectValues(PolicyData) async getEncryptedPolicies(options?: StorageOptions): Promise<{ [id: string]: PolicyData }> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.data?.policies?.encrypted; } async setEncryptedPolicies( value: { [id: string]: PolicyData }, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.data.policies.encrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEncryptedPrivateKey(options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); return account?.keys?.privateKey?.encrypted; } async setEncryptedPrivateKey(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.keys.privateKey.encrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEncryptedProviderKeys(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.keys?.providerKeys?.encrypted; } async setEncryptedProviderKeys(value: any, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.keys.providerKeys.encrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } @withPrototypeForObjectValues(SendData) async getEncryptedSends(options?: StorageOptions): Promise<{ [id: string]: SendData }> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) )?.data?.sends.encrypted; } async setEncryptedSends( value: { [id: string]: SendData }, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); account.data.sends.encrypted = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); } async getEntityId(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.profile?.entityId; } async setEntityId(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.profile.entityId = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getEntityType(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.profile?.entityType; } async setEntityType(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.profile.entityType = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getEnvironmentUrls(options?: StorageOptions): Promise { if ((await this.state())?.activeUserId == null) { return await this.getGlobalEnvironmentUrls(options); } options = this.reconcileOptions(options, await this.defaultOnDiskOptions()); return (await this.getAccount(options))?.settings?.environmentUrls ?? new EnvironmentUrls(); } async setEnvironmentUrls(value: EnvironmentUrls, options?: StorageOptions): Promise { // Global values are set on each change and the current global settings are passed to any newly authed accounts. // This is to allow setting environement values before an account is active, while still allowing individual accounts to have their own environments. const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.environmentUrls = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEquivalentDomains(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.settings?.equivalentDomains; } async setEquivalentDomains(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.equivalentDomains = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } @withPrototypeForArrayMembers(EventData) async getEventCollection(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.data?.eventCollection; } async setEventCollection(value: EventData[], options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.data.eventCollection = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getEverBeenUnlocked(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))) ?.profile?.everBeenUnlocked ?? false ); } async setEverBeenUnlocked(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.profile.everBeenUnlocked = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getForcePasswordResetReason(options?: StorageOptions): Promise { return ( ( await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ) )?.profile?.forcePasswordResetReason ?? ForceResetPasswordReason.None ); } async setForcePasswordResetReason( value: ForceResetPasswordReason, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); account.profile.forcePasswordResetReason = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); } async getInstalledVersion(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.installedVersion; } async setInstalledVersion(value: string, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.installedVersion = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getIsAuthenticated(options?: StorageOptions): Promise { return (await this.getAccessToken(options)) != null && (await this.getUserId(options)) != null; } async getKdfConfig(options?: StorageOptions): Promise { const iterations = ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.profile?.kdfIterations; const memory = ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.profile?.kdfMemory; const parallelism = ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.profile?.kdfParallelism; return new KdfConfig(iterations, memory, parallelism); } async setKdfConfig(config: KdfConfig, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.profile.kdfIterations = config.iterations; account.profile.kdfMemory = config.memory; account.profile.kdfParallelism = config.parallelism; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getKdfType(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.profile?.kdfType; } async setKdfType(value: KdfType, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.profile.kdfType = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getKeyHash(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.profile?.keyHash; } async setKeyHash(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.profile.keyHash = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getLastActive(options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultOnDiskOptions()); const accountActivity = await this.storageService.get<{ [userId: string]: number }>( keys.accountActivity, options ); if (accountActivity == null || Object.keys(accountActivity).length < 1) { return null; } return accountActivity[options.userId]; } async setLastActive(value: number, options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultOnDiskOptions()); if (options.userId == null) { return; } const accountActivity = (await this.storageService.get<{ [userId: string]: number }>( keys.accountActivity, options )) ?? {}; accountActivity[options.userId] = value; await this.storageService.save(keys.accountActivity, accountActivity, options); } async getLastSync(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) )?.profile?.lastSync; } async setLastSync(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); account.profile.lastSync = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); } async getLocalData(options?: StorageOptions): Promise<{ [cipherId: string]: LocalData }> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.data?.localData; } async setLocalData( value: { [cipherId: string]: LocalData }, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.data.localData = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getLocale(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.locale; } async setLocale(value: string, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); globals.locale = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getMainWindowSize(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.mainWindowSize; } async setMainWindowSize(value: number, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); globals.mainWindowSize = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getMinimizeOnCopyToClipboard(options?: StorageOptions): Promise { return ( (await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.settings?.minimizeOnCopyToClipboard ?? false ); } async setMinimizeOnCopyToClipboard(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.minimizeOnCopyToClipboard = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getNeverDomains(options?: StorageOptions): Promise<{ [id: string]: any }> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.settings?.neverDomains; } async setNeverDomains(value: { [id: string]: any }, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.neverDomains = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getNoAutoPromptBiometricsText(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.noAutoPromptBiometricsText; } async setNoAutoPromptBiometricsText(value: string, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.noAutoPromptBiometricsText = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getOpenAtLogin(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) ?.openAtLogin ?? false ); } async setOpenAtLogin(value: boolean, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.openAtLogin = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getOrganizationInvitation(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.organizationInvitation; } async setOrganizationInvitation(value: any, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); globals.organizationInvitation = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } /** * @deprecated Do not call this directly, use OrganizationService */ async getOrganizations(options?: StorageOptions): Promise<{ [id: string]: OrganizationData }> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.data?.organizations; } /** * @deprecated Do not call this directly, use OrganizationService */ async setOrganizations( value: { [id: string]: OrganizationData }, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.data.organizations = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getPasswordGenerationOptions(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.passwordGenerationOptions; } async setPasswordGenerationOptions(value: any, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.passwordGenerationOptions = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getUsernameGenerationOptions(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.usernameGenerationOptions; } async setUsernameGenerationOptions(value: any, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.usernameGenerationOptions = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getGeneratorOptions(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.generatorOptions; } async setGeneratorOptions(value: any, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.generatorOptions = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getProtectedPin(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.settings?.protectedPin; } async setProtectedPin(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.settings.protectedPin = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } @withPrototypeForObjectValues(ProviderData) async getProviders(options?: StorageOptions): Promise<{ [id: string]: ProviderData }> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.data?.providers; } async setProviders( value: { [id: string]: ProviderData }, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.data.providers = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getPublicKey(options?: StorageOptions): Promise { const keys = ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.keys; return keys?.publicKey; } async setPublicKey(value: ArrayBuffer, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.keys.publicKey = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getRefreshToken(options?: StorageOptions): Promise { options = await this.getTimeoutBasedStorageOptions(options); return (await this.getAccount(options))?.tokens?.refreshToken; } async setRefreshToken(value: string, options?: StorageOptions): Promise { options = await this.getTimeoutBasedStorageOptions(options); const account = await this.getAccount(options); account.tokens.refreshToken = value; await this.saveAccount(account, options); } async getRememberedEmail(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.rememberedEmail; } async setRememberedEmail(value: string, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); globals.rememberedEmail = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getSecurityStamp(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) )?.tokens?.securityStamp; } async setSecurityStamp(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); account.tokens.securityStamp = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultInMemoryOptions()) ); } async getSettings(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions())) )?.settings?.settings; } async setSettings(value: AccountSettingsSettings, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); account.settings.settings = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskMemoryOptions()) ); } async getSsoCodeVerifier(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.ssoCodeVerifier; } async setSsoCodeVerifier(value: string, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.ssoCodeVerifier = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getSsoOrgIdentifier(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.ssoOrganizationIdentifier; } async setSsoOrganizationIdentifier(value: string, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); globals.ssoOrganizationIdentifier = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getSsoState(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.ssoState; } async setSsoState(value: string, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.ssoState = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getTheme(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.theme; } async setTheme(value: ThemeType, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); globals.theme = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getTwoFactorToken(options?: StorageOptions): Promise { return ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.twoFactorToken; } async setTwoFactorToken(value: string, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); globals.twoFactorToken = value; await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getUserId(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.profile?.userId; } async getUsesKeyConnector(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())) )?.profile?.usesKeyConnector; } async setUsesKeyConnector(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); account.profile.usesKeyConnector = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async getVaultTimeout(options?: StorageOptions): Promise { const accountVaultTimeout = ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.vaultTimeout; return accountVaultTimeout; } async setVaultTimeout(value: number, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.vaultTimeout = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getVaultTimeoutAction(options?: StorageOptions): Promise { const accountVaultTimeoutAction = ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.vaultTimeoutAction; const globalVaultTimeoutAction = ( await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.vaultTimeoutAction; return accountVaultTimeoutAction ?? globalVaultTimeoutAction; } async setVaultTimeoutAction(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.vaultTimeoutAction = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getApproveLoginRequests(options?: StorageOptions): Promise { const approveLoginRequests = ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.approveLoginRequests; return approveLoginRequests; } async setApproveLoginRequests(value: boolean, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.approveLoginRequests = value; await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getStateVersion(): Promise { return (await this.getGlobals(await this.defaultOnDiskLocalOptions())).stateVersion ?? 1; } async setStateVersion(value: number): Promise { const globals = await this.getGlobals(await this.defaultOnDiskOptions()); globals.stateVersion = value; await this.saveGlobals(globals, await this.defaultOnDiskOptions()); } async getWindow(): Promise { const globals = await this.getGlobals(await this.defaultOnDiskOptions()); return globals?.window != null && Object.keys(globals.window).length > 0 ? globals.window : new WindowState(); } async setWindow(value: WindowState, options?: StorageOptions): Promise { const globals = await this.getGlobals( this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); globals.window = value; return await this.saveGlobals( globals, this.reconcileOptions(options, await this.defaultOnDiskOptions()) ); } async setServerConfig(value: ServerConfigData, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.serverConfig = value; return await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getServerConfig(options: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.serverConfig; } async getAvatarColor(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.avatarColor; } async setAvatarColor(value: string, options?: StorageOptions): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.avatarColor = value; return await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getActivateAutoFillOnPageLoadFromPolicy(options?: StorageOptions): Promise { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.activateAutoFillOnPageLoadFromPolicy; } async setActivateAutoFillOnPageLoadFromPolicy( value: boolean, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.activateAutoFillOnPageLoadFromPolicy = value; return await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } async getSMOnboardingTasks( options?: StorageOptions ): Promise>> { return ( await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())) )?.settings?.smOnboardingTasks; } async setSMOnboardingTasks( value: Record>, options?: StorageOptions ): Promise { const account = await this.getAccount( this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); account.settings.smOnboardingTasks = value; return await this.saveAccount( account, this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()) ); } protected async getGlobals(options: StorageOptions): Promise { let globals: TGlobalState; if (this.useMemory(options.storageLocation)) { globals = await this.getGlobalsFromMemory(); } if (this.useDisk && globals == null) { globals = await this.getGlobalsFromDisk(options); } return globals ?? this.createGlobals(); } protected async saveGlobals(globals: TGlobalState, options: StorageOptions) { return this.useMemory(options.storageLocation) ? this.saveGlobalsToMemory(globals) : await this.saveGlobalsToDisk(globals, options); } protected async getGlobalsFromMemory(): Promise { return (await this.state()).globals; } protected async getGlobalsFromDisk(options: StorageOptions): Promise { return await this.storageService.get(keys.global, options); } protected async saveGlobalsToMemory(globals: TGlobalState): Promise { await this.updateState(async (state) => { state.globals = globals; return state; }); } protected async saveGlobalsToDisk(globals: TGlobalState, options: StorageOptions): Promise { if (options.useSecureStorage) { await this.secureStorageService.save(keys.global, globals, options); } else { await this.storageService.save(keys.global, globals, options); } } protected async getAccount(options: StorageOptions): Promise { try { let account: TAccount; if (this.useMemory(options.storageLocation)) { account = await this.getAccountFromMemory(options); } if (this.useDisk(options.storageLocation) && account == null) { account = await this.getAccountFromDisk(options); } return account; } catch (e) { this.logService.error(e); } } protected async getAccountFromMemory(options: StorageOptions): Promise { return await this.state().then(async (state) => { if (state.accounts == null) { return null; } return state.accounts[await this.getUserIdFromMemory(options)]; }); } protected async getUserIdFromMemory(options: StorageOptions): Promise { return await this.state().then((state) => { return options?.userId != null ? state.accounts[options.userId]?.profile?.userId : state.activeUserId; }); } protected async getAccountFromDisk(options: StorageOptions): Promise { if (options?.userId == null && (await this.state())?.activeUserId == null) { return null; } if (this.useAccountCache) { const cachedAccount = this.accountDiskCache.value[options.userId]; if (cachedAccount != null) { return cachedAccount; } } const account = options?.useSecureStorage ? (await this.secureStorageService.get(options.userId, options)) ?? (await this.storageService.get( options.userId, this.reconcileOptions(options, { htmlStorageLocation: HtmlStorageLocation.Local }) )) : await this.storageService.get(options.userId, options); this.setDiskCache(options.userId, account); return account; } protected useMemory(storageLocation: StorageLocation) { return storageLocation === StorageLocation.Memory || storageLocation === StorageLocation.Both; } protected useDisk(storageLocation: StorageLocation) { return storageLocation === StorageLocation.Disk || storageLocation === StorageLocation.Both; } protected async saveAccount( account: TAccount, options: StorageOptions = { storageLocation: StorageLocation.Both, useSecureStorage: false, } ) { return this.useMemory(options.storageLocation) ? await this.saveAccountToMemory(account) : await this.saveAccountToDisk(account, options); } protected async saveAccountToDisk(account: TAccount, options: StorageOptions): Promise { const storageLocation = options.useSecureStorage ? this.secureStorageService : this.storageService; await storageLocation.save(`${options.userId}`, account, options); this.deleteDiskCache(options.userId); } protected async saveAccountToMemory(account: TAccount): Promise { if (this.getAccountFromMemory({ userId: account.profile.userId }) !== null) { await this.updateState((state) => { return new Promise((resolve) => { state.accounts[account.profile.userId] = account; resolve(state); }); }); } await this.pushAccounts(); } protected async scaffoldNewAccountStorage(account: TAccount): Promise { // We don't want to manipulate the referenced in memory account const deepClone = JSON.parse(JSON.stringify(account)); await this.scaffoldNewAccountLocalStorage(deepClone); await this.scaffoldNewAccountSessionStorage(deepClone); await this.scaffoldNewAccountMemoryStorage(deepClone); } // TODO: There is a tech debt item for splitting up these methods - only Web uses multiple storage locations in its storageService. // For now these methods exist with some redundancy to facilitate this special web requirement. protected async scaffoldNewAccountLocalStorage(account: TAccount): Promise { const storedAccount = await this.getAccount( this.reconcileOptions( { userId: account.profile.userId }, await this.defaultOnDiskLocalOptions() ) ); // EnvironmentUrls are set before authenticating and should override whatever is stored from any previous session const environmentUrls = account.settings.environmentUrls; if (storedAccount?.settings != null) { account.settings = storedAccount.settings; } else if (await this.storageService.has(keys.tempAccountSettings)) { account.settings = await this.storageService.get(keys.tempAccountSettings); await this.storageService.remove(keys.tempAccountSettings); } account.settings.environmentUrls = environmentUrls; if ( account.settings.vaultTimeoutAction === VaultTimeoutAction.LogOut && account.settings.vaultTimeout != null ) { account.tokens.accessToken = null; account.tokens.refreshToken = null; account.profile.apiKeyClientId = null; account.keys.apiKeyClientSecret = null; } await this.saveAccount( account, this.reconcileOptions( { userId: account.profile.userId }, await this.defaultOnDiskLocalOptions() ) ); } protected async scaffoldNewAccountMemoryStorage(account: TAccount): Promise { const storedAccount = await this.getAccount( this.reconcileOptions( { userId: account.profile.userId }, await this.defaultOnDiskMemoryOptions() ) ); if (storedAccount?.settings != null) { storedAccount.settings.environmentUrls = account.settings.environmentUrls; account.settings = storedAccount.settings; } await this.storageService.save( account.profile.userId, account, await this.defaultOnDiskMemoryOptions() ); await this.saveAccount( account, this.reconcileOptions( { userId: account.profile.userId }, await this.defaultOnDiskMemoryOptions() ) ); } protected async scaffoldNewAccountSessionStorage(account: TAccount): Promise { const storedAccount = await this.getAccount( this.reconcileOptions({ userId: account.profile.userId }, await this.defaultOnDiskOptions()) ); if (storedAccount?.settings != null) { storedAccount.settings.environmentUrls = account.settings.environmentUrls; account.settings = storedAccount.settings; } await this.storageService.save( account.profile.userId, account, await this.defaultOnDiskMemoryOptions() ); await this.saveAccount( account, this.reconcileOptions({ userId: account.profile.userId }, await this.defaultOnDiskOptions()) ); } // protected async pushAccounts(): Promise { await this.pruneInMemoryAccounts(); await this.state().then((state) => { if (state.accounts == null || Object.keys(state.accounts).length < 1) { this.accountsSubject.next({}); return; } this.accountsSubject.next(state.accounts); }); } protected reconcileOptions( requestedOptions: StorageOptions, defaultOptions: StorageOptions ): StorageOptions { if (requestedOptions == null) { return defaultOptions; } requestedOptions.userId = requestedOptions?.userId ?? defaultOptions.userId; requestedOptions.storageLocation = requestedOptions?.storageLocation ?? defaultOptions.storageLocation; requestedOptions.useSecureStorage = requestedOptions?.useSecureStorage ?? defaultOptions.useSecureStorage; requestedOptions.htmlStorageLocation = requestedOptions?.htmlStorageLocation ?? defaultOptions.htmlStorageLocation; requestedOptions.keySuffix = requestedOptions?.keySuffix ?? defaultOptions.keySuffix; return requestedOptions; } protected async defaultInMemoryOptions(): Promise { return { storageLocation: StorageLocation.Memory, userId: (await this.state()).activeUserId, }; } protected async defaultOnDiskOptions(): Promise { return { storageLocation: StorageLocation.Disk, htmlStorageLocation: HtmlStorageLocation.Session, userId: (await this.state())?.activeUserId ?? (await this.getActiveUserIdFromStorage()), useSecureStorage: false, }; } protected async defaultOnDiskLocalOptions(): Promise { return { storageLocation: StorageLocation.Disk, htmlStorageLocation: HtmlStorageLocation.Local, userId: (await this.state())?.activeUserId ?? (await this.getActiveUserIdFromStorage()), useSecureStorage: false, }; } protected async defaultOnDiskMemoryOptions(): Promise { return { storageLocation: StorageLocation.Disk, htmlStorageLocation: HtmlStorageLocation.Memory, userId: (await this.state())?.activeUserId ?? (await this.getUserId()), useSecureStorage: false, }; } protected async defaultSecureStorageOptions(): Promise { return { storageLocation: StorageLocation.Disk, useSecureStorage: true, userId: (await this.state())?.activeUserId ?? (await this.getActiveUserIdFromStorage()), }; } protected async getActiveUserIdFromStorage(): Promise { return await this.storageService.get(keys.activeUserId); } protected async removeAccountFromLocalStorage(userId: string = null): Promise { userId = userId ?? (await this.state())?.activeUserId; const storedAccount = await this.getAccount( this.reconcileOptions({ userId: userId }, await this.defaultOnDiskLocalOptions()) ); await this.saveAccount( this.resetAccount(storedAccount), this.reconcileOptions({ userId: userId }, await this.defaultOnDiskLocalOptions()) ); } protected async removeAccountFromSessionStorage(userId: string = null): Promise { userId = userId ?? (await this.state())?.activeUserId; const storedAccount = await this.getAccount( this.reconcileOptions({ userId: userId }, await this.defaultOnDiskOptions()) ); await this.saveAccount( this.resetAccount(storedAccount), this.reconcileOptions({ userId: userId }, await this.defaultOnDiskOptions()) ); } protected async removeAccountFromSecureStorage(userId: string = null): Promise { userId = userId ?? (await this.state())?.activeUserId; await this.setCryptoMasterKeyAuto(null, { userId: userId }); await this.setCryptoMasterKeyBiometric(null, { userId: userId }); await this.setCryptoMasterKeyB64(null, { userId: userId }); } protected async removeAccountFromMemory(userId: string = null): Promise { await this.updateState(async (state) => { userId = userId ?? state.activeUserId; delete state.accounts[userId]; this.deleteDiskCache(userId); return state; }); } protected async pruneInMemoryAccounts() { // We preserve settings for logged out accounts, but we don't want to consider them when thinking about active account state for (const userId in (await this.state())?.accounts) { if (!(await this.getIsAuthenticated({ userId: userId }))) { await this.removeAccountFromMemory(userId); } } } // settings persist even on reset, and are not effected by this method protected resetAccount(account: TAccount) { const persistentAccountInformation = { settings: account.settings }; return Object.assign(this.createAccount(), persistentAccountInformation); } protected async setAccountEnvironmentUrls(account: TAccount): Promise { account.settings.environmentUrls = await this.getGlobalEnvironmentUrls(); return account; } protected async getGlobalEnvironmentUrls(options?: StorageOptions): Promise { options = this.reconcileOptions(options, await this.defaultOnDiskOptions()); return (await this.getGlobals(options)).environmentUrls ?? new EnvironmentUrls(); } protected async clearDecryptedDataForActiveUser(): Promise { await this.updateState(async (state) => { const userId = state?.activeUserId; if (userId != null && state?.accounts[userId]?.data != null) { state.accounts[userId].data = new AccountData(); } return state; }); } protected createAccount(init: Partial = null): TAccount { return this.stateFactory.createAccount(init); } protected createGlobals(init: Partial = null): TGlobalState { return this.stateFactory.createGlobal(init); } protected async deAuthenticateAccount(userId: string): Promise { await this.setAccessToken(null, { userId: userId }); await this.setLastActive(null, { userId: userId }); await this.updateState(async (state) => { state.authenticatedAccounts = state.authenticatedAccounts.filter((id) => id !== userId); await this.storageService.save(keys.authenticatedAccounts, state.authenticatedAccounts); return state; }); } protected async removeAccountFromDisk(userId: string) { await this.removeAccountFromSessionStorage(userId); await this.removeAccountFromLocalStorage(userId); await this.removeAccountFromSecureStorage(userId); } protected async dynamicallySetActiveUser() { const accounts = (await this.state())?.accounts; if (accounts == null || Object.keys(accounts).length < 1) { await this.setActiveUser(null); return; } for (const userId in accounts) { if (userId == null) { continue; } if (await this.getIsAuthenticated({ userId: userId })) { await this.setActiveUser(userId); break; } await this.setActiveUser(null); } } private async getTimeoutBasedStorageOptions(options?: StorageOptions): Promise { const timeoutAction = await this.getVaultTimeoutAction({ userId: options?.userId }); const timeout = await this.getVaultTimeout({ userId: options?.userId }); const defaultOptions = timeoutAction === VaultTimeoutAction.LogOut && timeout != null ? await this.defaultInMemoryOptions() : await this.defaultOnDiskOptions(); return this.reconcileOptions(options, defaultOptions); } private async saveSecureStorageKey( key: string, value: T, options?: StorageOptions ) { return value == null ? await this.secureStorageService.remove(`${options.userId}${key}`, options) : await this.secureStorageService.save(`${options.userId}${key}`, value, options); } protected async state(): Promise> { const state = await this.memoryStorageService.get>(keys.state, { deserializer: (s) => State.fromJSON(s, this.accountDeserializer), }); return state; } private async setState(state: State): Promise { await this.memoryStorageService.save(keys.state, state); } protected async updateState( stateUpdater: (state: State) => Promise> ) { await this.state().then(async (state) => { const updatedState = await stateUpdater(state); if (updatedState == null) { throw new Error("Attempted to update state to null value"); } await this.setState(updatedState); }); } private setDiskCache(key: string, value: TAccount, options?: StorageOptions) { if (this.useAccountCache) { this.accountDiskCache.value[key] = value; this.accountDiskCache.next(this.accountDiskCache.value); } } private deleteDiskCache(key: string) { if (this.useAccountCache) { delete this.accountDiskCache.value[key]; this.accountDiskCache.next(this.accountDiskCache.value); } } } function withPrototypeForArrayMembers( memberConstructor: new (...args: any[]) => T, memberConverter: (input: any) => T = (i) => i ): ( target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor ) => { value: (...args: any[]) => Promise } { return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { const originalMethod = descriptor.value; return { value: function (...args: any[]) { const originalResult: Promise = originalMethod.apply(this, args); if (!Utils.isPromise(originalResult)) { throw new Error( `Error applying prototype to stored value -- result is not a promise for method ${String( propertyKey )}` ); } return originalResult.then((result) => { if (result == null) { return null; } else if (!(result instanceof Array)) { throw new Error( `Attempted to retrieve non array type from state as an array for method ${String( propertyKey )}` ); } else { return result.map((r) => { return r == null || r.constructor.name === memberConstructor.prototype.constructor.name ? r : memberConverter( Object.create(memberConstructor.prototype, Object.getOwnPropertyDescriptors(r)) ); }); } }); }, }; }; } function withPrototypeForObjectValues( valuesConstructor: new (...args: any[]) => T, valuesConverter: (input: any) => T = (i) => i ): ( target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor ) => { value: (...args: any[]) => Promise<{ [key: string]: T }> } { return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { const originalMethod = descriptor.value; return { value: function (...args: any[]) { const originalResult: Promise<{ [key: string]: T }> = originalMethod.apply(this, args); if (!Utils.isPromise(originalResult)) { throw new Error( `Error applying prototype to stored value -- result is not a promise for method ${String( propertyKey )}` ); } return originalResult.then((result) => { if (result == null) { return null; } else { for (const [key, val] of Object.entries(result)) { result[key] = val == null || val.constructor.name === valuesConstructor.prototype.constructor.name ? valuesConverter(val) : valuesConverter( Object.create( valuesConstructor.prototype, Object.getOwnPropertyDescriptors(val) ) ); } return result as { [key: string]: T }; } }); }, }; }; }