Files
standardnotes-app-web/packages/services/src/Domain/SharedVaults/SharedVaultService.ts
2023-10-02 19:15:58 +05:30

226 lines
8.9 KiB
TypeScript

import { DiscardItemsLocally } from './../UseCase/DiscardItemsLocally'
import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData'
import { ClientDisplayableError } from '@standardnotes/responses'
import {
DecryptedItemInterface,
PayloadEmitSource,
TrustedContactInterface,
SharedVaultListingInterface,
VaultListingInterface,
KeySystemRootKeyStorageMode,
EmojiString,
IconType,
} from '@standardnotes/models'
import { SharedVaultServiceInterface } from './SharedVaultServiceInterface'
import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent'
import { AbstractService } from '../Service/AbstractService'
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { SessionsClientInterface } from '../Session/SessionsClientInterface'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { SyncEvent } from '../Event/SyncEvent'
import { SessionEvent } from '../Session/SessionEvent'
import { InternalEventInterface } from '../Internal/InternalEventInterface'
import { NotificationServiceEvent, NotificationServiceEventPayload } from '../UserEvent/NotificationServiceEvent'
import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault'
import { DeleteSharedVault } from './UseCase/DeleteSharedVault'
import { ShareContactWithVault } from './UseCase/ShareContactWithVault'
import { CreateSharedVault } from './UseCase/CreateSharedVault'
import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault'
import { GetVault } from '../Vault/UseCase/GetVault'
import { ContentType, NotificationType, Uuid } from '@standardnotes/domain-core'
import { HandleKeyPairChange } from '../Contacts/UseCase/HandleKeyPairChange'
import { FindContact } from '../Contacts/UseCase/FindContact'
import { GetOwnedSharedVaults } from './UseCase/GetOwnedSharedVaults'
import { SyncLocalVaultsWithRemoteSharedVaults } from './UseCase/SyncLocalVaultsWithRemoteSharedVaults'
import { VaultUserServiceInterface } from '../VaultUser/VaultUserServiceInterface'
export class SharedVaultService
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload>
implements SharedVaultServiceInterface, InternalEventHandlerInterface
{
constructor(
private items: ItemManagerInterface,
private session: SessionsClientInterface,
private vaultUsers: VaultUserServiceInterface,
private _syncLocalVaultsWithRemoteSharedVaults: SyncLocalVaultsWithRemoteSharedVaults,
private _getVault: GetVault,
private _getOwnedSharedVaults: GetOwnedSharedVaults,
private _createSharedVault: CreateSharedVault,
private _handleKeyPairChange: HandleKeyPairChange,
private _findContact: FindContact,
private _deleteThirdPartyVault: DeleteThirdPartyVault,
private _shareContactWithVault: ShareContactWithVault,
private _convertToSharedVault: ConvertToSharedVault,
private _deleteSharedVault: DeleteSharedVault,
private _discardItemsLocally: DiscardItemsLocally,
eventBus: InternalEventBusInterface,
) {
super(eventBus)
this.eventDisposers.push(
items.addObserver<TrustedContactInterface>(ContentType.TYPES.TrustedContact, async ({ changed, source }) => {
if (source === PayloadEmitSource.LocalChanged && changed.length > 0) {
void this.handleTrustedContactsChange(changed)
}
}),
)
}
override deinit(): void {
super.deinit()
;(this.items as unknown) = undefined
;(this.session as unknown) = undefined
;(this._syncLocalVaultsWithRemoteSharedVaults as unknown) = undefined
;(this._getVault as unknown) = undefined
;(this._createSharedVault as unknown) = undefined
;(this._handleKeyPairChange as unknown) = undefined
;(this._findContact as unknown) = undefined
;(this._deleteThirdPartyVault as unknown) = undefined
;(this._shareContactWithVault as unknown) = undefined
;(this._convertToSharedVault as unknown) = undefined
;(this._deleteSharedVault as unknown) = undefined
}
async handleEvent(event: InternalEventInterface): Promise<void> {
switch (event.type) {
case SessionEvent.UserKeyPairChanged: {
const eventData = event.payload as UserKeyPairChangedEventData
void this._handleKeyPairChange.execute({
newKeys: eventData.current,
previousKeys: eventData.previous,
})
break
}
case NotificationServiceEvent.NotificationReceived:
await this.handleNotification(event.payload as NotificationServiceEventPayload)
break
case SyncEvent.ReceivedRemoteSharedVaults:
void this.notifyEvent(SharedVaultServiceEvent.SharedVaultStatusChanged)
break
}
}
private async handleNotification(event: NotificationServiceEventPayload): Promise<void> {
switch (event.eventPayload.props.type.value) {
case NotificationType.TYPES.SelfRemovedFromSharedVault: {
const vault = this._getVault.execute<SharedVaultListingInterface>({
sharedVaultUuid: event.eventPayload.props.primaryIdentifier.value,
})
if (!vault.isFailed()) {
await this._deleteThirdPartyVault.execute(vault.getValue())
}
break
}
case NotificationType.TYPES.UserRemovedFromSharedVault: {
const vaultOrError = this._getVault.execute<SharedVaultListingInterface>({
sharedVaultUuid: event.eventPayload.props.primaryIdentifier.value,
})
if (!vaultOrError.isFailed()) {
const vault = vaultOrError.getValue()
this.vaultUsers
.invalidateVaultUsersCache(event.eventPayload.props.primaryIdentifier.value)
.catch(console.error)
await this._syncLocalVaultsWithRemoteSharedVaults.execute([vault])
void this.notifyEvent(SharedVaultServiceEvent.SharedVaultStatusChanged)
}
break
}
case NotificationType.TYPES.SharedVaultItemRemoved: {
const item = this.items.findItem((event.eventPayload.props.secondaryIdentifier as Uuid).value)
if (item) {
void this._discardItemsLocally.execute([item])
}
break
}
case NotificationType.TYPES.SharedVaultFileRemoved:
case NotificationType.TYPES.SharedVaultFileUploaded:
case NotificationType.TYPES.UserDesignatedAsSurvivor: {
const vaultOrError = this._getVault.execute<SharedVaultListingInterface>({
sharedVaultUuid: event.eventPayload.props.primaryIdentifier.value,
})
if (!vaultOrError.isFailed()) {
await this._syncLocalVaultsWithRemoteSharedVaults.execute([vaultOrError.getValue()])
void this.notifyEvent(SharedVaultServiceEvent.SharedVaultStatusChanged)
}
break
}
}
}
async createSharedVault(dto: {
name: string
description?: string
iconString: IconType | EmojiString
userInputtedPassword: string | undefined
storagePreference?: KeySystemRootKeyStorageMode
}): Promise<VaultListingInterface | ClientDisplayableError> {
return this._createSharedVault.execute({
vaultName: dto.name,
vaultDescription: dto.description,
vaultIcon: dto.iconString,
userInputtedPassword: dto.userInputtedPassword,
storagePreference: dto.storagePreference ?? KeySystemRootKeyStorageMode.Synced,
})
}
async convertVaultToSharedVault(
vault: VaultListingInterface,
): Promise<SharedVaultListingInterface | ClientDisplayableError> {
return this._convertToSharedVault.execute({ vault })
}
private async handleTrustedContactsChange(contacts: TrustedContactInterface[]): Promise<void> {
for (const contact of contacts) {
if (contact.isMe) {
continue
}
await this.shareContactWithVaults(contact)
}
}
public async deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
return this._deleteSharedVault.execute({ sharedVault })
}
async shareContactWithVaults(contact: TrustedContactInterface): Promise<void> {
if (contact.isMe) {
throw new Error('Cannot share self contact')
}
const ownedVaults = this._getOwnedSharedVaults.execute().getValue()
for (const vault of ownedVaults) {
await this._shareContactWithVault.execute({
sharedVault: vault,
contactToShare: contact,
})
}
}
getItemLastEditedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined {
if (!item.last_edited_by_uuid) {
return undefined
}
const contact = this._findContact.execute({ userUuid: item.last_edited_by_uuid })
return contact.isFailed() ? undefined : contact.getValue()
}
getItemSharedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined {
if (!item.user_uuid || item.user_uuid === this.session.getSureUser().uuid) {
return undefined
}
const contact = this._findContact.execute({ userUuid: item.user_uuid })
return contact.isFailed() ? undefined : contact.getValue()
}
}