import { isClientDisplayableError } from '@standardnotes/responses' import { DecryptedItemInterface, FileItem, KeySystemIdentifier, KeySystemRootKeyStorageMode, VaultListingInterface, VaultListingMutator, isNote, } from '@standardnotes/models' import { VaultServiceInterface } from './VaultServiceInterface' import { ChangeVaultKeyOptionsDTO } from './UseCase/ChangeVaultKeyOptionsDTO' import { VaultServiceEvent, VaultServiceEventPayload } from './VaultServiceEvent' import { CreateVault } from './UseCase/CreateVault' import { AbstractService } from '../Service/AbstractService' import { SyncServiceInterface } from '../Sync/SyncServiceInterface' import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { RemoveItemFromVault } from './UseCase/RemoveItemFromVault' import { DeleteVault } from './UseCase/DeleteVault' import { MoveItemsToVault } from './UseCase/MoveItemsToVault' import { RotateVaultKey } from './UseCase/RotateVaultKey' import { GetVault } from './UseCase/GetVault' import { ChangeVaultKeyOptions } from './UseCase/ChangeVaultKeyOptions' import { MutatorClientInterface } from '../Mutator/MutatorClientInterface' import { AlertService } from '../Alert/AlertService' import { GetVaults } from './UseCase/GetVaults' import { VaultLockServiceInterface } from '../VaultLock/VaultLockServiceInterface' import { Result } from '@standardnotes/domain-core' export class VaultService extends AbstractService implements VaultServiceInterface { constructor( private sync: SyncServiceInterface, private items: ItemManagerInterface, private mutator: MutatorClientInterface, private vaultLocks: VaultLockServiceInterface, private alerts: AlertService, private _getVault: GetVault, private _getVaults: GetVaults, private _changeVaultKeyOptions: ChangeVaultKeyOptions, private _moveItemsToVault: MoveItemsToVault, private _createVault: CreateVault, private _removeItemFromVault: RemoveItemFromVault, private _deleteVault: DeleteVault, private _rotateVaultKey: RotateVaultKey, eventBus: InternalEventBusInterface, ) { super(eventBus) } override deinit(): void { super.deinit() ;(this.sync as unknown) = undefined ;(this.items as unknown) = undefined ;(this.mutator as unknown) = undefined ;(this.vaultLocks as unknown) = undefined ;(this.alerts as unknown) = undefined ;(this._getVault as unknown) = undefined ;(this._getVaults as unknown) = undefined ;(this._changeVaultKeyOptions as unknown) = undefined ;(this._moveItemsToVault as unknown) = undefined ;(this._createVault as unknown) = undefined ;(this._removeItemFromVault as unknown) = undefined ;(this._deleteVault as unknown) = undefined ;(this._rotateVaultKey as unknown) = undefined } getVaults(): VaultListingInterface[] { return this._getVaults.execute().getValue() } public getVault(dto: { keySystemIdentifier: KeySystemIdentifier }): VaultListingInterface | undefined { const result = this._getVault.execute(dto) if (result.isFailed()) { return undefined } return result.getValue() } public getSureVault(dto: { keySystemIdentifier: KeySystemIdentifier }): VaultListingInterface { const vault = this.getVault(dto) if (!vault) { throw new Error('Vault not found') } return vault } async createRandomizedVault(dto: { name: string; description?: string }): Promise { return this.createVaultWithParameters({ name: dto.name, description: dto.description, userInputtedPassword: undefined, storagePreference: KeySystemRootKeyStorageMode.Synced, }) } async createUserInputtedPasswordVault(dto: { name: string description?: string userInputtedPassword: string storagePreference: KeySystemRootKeyStorageMode }): Promise { return this.createVaultWithParameters(dto) } private async createVaultWithParameters(dto: { name: string description?: string userInputtedPassword: string | undefined storagePreference: KeySystemRootKeyStorageMode }): Promise { const result = await this._createVault.execute({ vaultName: dto.name, vaultDescription: dto.description, userInputtedPassword: dto.userInputtedPassword, storagePreference: dto.storagePreference, }) return result } async moveItemToVault( vault: VaultListingInterface, item: DecryptedItemInterface, ): Promise { if (this.vaultLocks.isVaultLocked(vault)) { throw new Error('Attempting to add item to locked vault') } let linkedFiles: FileItem[] = [] if (isNote(item)) { linkedFiles = this.items.getNoteLinkedFiles(item) if (linkedFiles.length > 0) { const confirmed = await this.alerts.confirmV2({ title: 'Linked files will be moved to vault', text: `This note has ${linkedFiles.length} linked files. They will also be moved to the vault. Do you want to continue?`, }) if (!confirmed) { return undefined } } } await this._moveItemsToVault.execute({ vault, items: [item, ...linkedFiles] }) return this.items.findSureItem(item.uuid) } async removeItemFromVault(item: DecryptedItemInterface): Promise { const vault = this.getItemVault(item) if (!vault) { throw new Error('Cannot find vault to remove item from') } if (this.vaultLocks.isVaultLocked(vault)) { throw new Error('Attempting to remove item from locked vault') } await this._removeItemFromVault.execute({ item }) return this.items.findSureItem(item.uuid) } async deleteVault(vault: VaultListingInterface): Promise { if (vault.isSharedVaultListing()) { throw new Error('Shared vault must be deleted through SharedVaultService') } const error = await this._deleteVault.execute(vault) if (isClientDisplayableError(error)) { return false } await this.sync.sync() return true } async changeVaultNameAndDescription( vault: VaultListingInterface, params: { name: string; description?: string }, ): Promise { const updatedVault = await this.mutator.changeItem(vault, (mutator) => { mutator.name = params.name mutator.description = params.description }) await this.sync.sync() return updatedVault } async rotateVaultRootKey(vault: VaultListingInterface, vaultPassword?: string): Promise { if (this.vaultLocks.isVaultLocked(vault)) { throw new Error('Cannot rotate root key of locked vault') } await this._rotateVaultKey.execute({ vault, sharedVaultUuid: vault.isSharedVaultListing() ? vault.sharing.sharedVaultUuid : undefined, userInputtedPassword: vaultPassword, }) await this.notifyEventSync(VaultServiceEvent.VaultRootKeyRotated, { vault }) await this.sync.sync() } isItemInVault(item: DecryptedItemInterface): boolean { return item.key_system_identifier !== undefined } getItemVault(item: DecryptedItemInterface): VaultListingInterface | undefined { const latestItem = this.items.findItem(item.uuid) if (!latestItem) { throw new Error('Cannot find latest version of item to get vault for') } if (!latestItem.key_system_identifier) { return undefined } return this.getVault({ keySystemIdentifier: latestItem.key_system_identifier }) } async changeVaultOptions(dto: ChangeVaultKeyOptionsDTO): Promise> { if (this.vaultLocks.isVaultLocked(dto.vault)) { throw new Error('Attempting to change vault options on a locked vault') } const result = await this._changeVaultKeyOptions.execute(dto) if (dto.newPasswordType) { await this.notifyEventSync(VaultServiceEvent.VaultRootKeyRotated, { vault: dto.vault }) } return result } }