Files
standardnotes-app-web/packages/ui-services/src/Vaults/VaultDisplayService.ts

217 lines
7.1 KiB
TypeScript

import {
ApplicationEvent,
ApplicationStage,
ApplicationStageChangedEventPayload,
Challenge,
ChallengePrompt,
ChallengeReason,
ChallengeStrings,
ChallengeValidation,
InternalEventBusInterface,
InternalEventHandlerInterface,
InternalEventInterface,
StorageKey,
VaultServiceEvent,
} from '@standardnotes/services'
import { VaultDisplayOptions, VaultDisplayOptionsPersistable, VaultListingInterface } from '@standardnotes/models'
import { VaultDisplayServiceEvent } from './VaultDisplayServiceEvent'
import { AbstractUIServicee } from '../Abstract/AbstractUIService'
import { WebApplicationInterface } from '../WebApplication/WebApplicationInterface'
import { VaultDisplayServiceInterface } from './VaultDisplayServiceInterface'
import { action, makeObservable, observable } from 'mobx'
export class VaultDisplayService
extends AbstractUIServicee<VaultDisplayServiceEvent>
implements VaultDisplayServiceInterface, InternalEventHandlerInterface
{
options: VaultDisplayOptions
public exclusivelyShownVault: VaultListingInterface | undefined = undefined
constructor(application: WebApplicationInterface, internalEventBus: InternalEventBusInterface) {
super(application, internalEventBus)
this.options = new VaultDisplayOptions({ exclude: [], locked: [] })
internalEventBus.addEventHandler(this, VaultServiceEvent.VaultLocked)
internalEventBus.addEventHandler(this, VaultServiceEvent.VaultUnlocked)
internalEventBus.addEventHandler(this, ApplicationEvent.ApplicationStageChanged)
makeObservable(this, {
options: observable,
isVaultExplicitelyExcluded: observable,
isVaultExclusivelyShown: observable,
exclusivelyShownVault: observable,
hideVault: action,
unhideVault: action,
showOnlyVault: action,
})
}
async handleEvent(event: InternalEventInterface): Promise<void> {
if (event.type === VaultServiceEvent.VaultLocked || event.type === VaultServiceEvent.VaultUnlocked) {
this.handleVaultLockingStatusChanged()
} else if (event.type === ApplicationEvent.ApplicationStageChanged) {
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
if (stage === ApplicationStage.StorageDecrypted_09) {
void this.loadVaultSelectionOptionsFromDisk()
}
}
}
private handleVaultLockingStatusChanged(): void {
const lockedVaults = this.application.vaults.getLockedvaults()
const options = this.options.newOptionsByIntakingLockedVaults(lockedVaults)
this.setVaultSelectionOptions(options)
}
public getOptions(): VaultDisplayOptions {
return this.options
}
isVaultExplicitelyExcluded = (vault: VaultListingInterface): boolean => {
return this.options.isVaultExplicitelyExcluded(vault) ?? false
}
isVaultDisabledOrLocked(vault: VaultListingInterface): boolean {
return this.options.isVaultDisabledOrLocked(vault)
}
isVaultExclusivelyShown = (vault: VaultListingInterface): boolean => {
return this.options.isVaultExclusivelyShown(vault)
}
isInExclusiveDisplayMode(): boolean {
return this.options.isInExclusiveDisplayMode()
}
changeToMultipleVaultDisplayMode(): void {
const vaults = this.application.vaults.getVaults()
const lockedVaults = this.application.vaults.getLockedvaults()
const newOptions = new VaultDisplayOptions({
exclude: vaults
.map((vault) => vault.systemIdentifier)
.filter((identifier) => identifier !== this.exclusivelyShownVault?.systemIdentifier),
locked: lockedVaults.map((vault) => vault.systemIdentifier),
})
this.setVaultSelectionOptions(newOptions)
}
hideVault = (vault: VaultListingInterface) => {
const lockedVaults = this.application.vaults.getLockedvaults()
const newOptions = this.options.newOptionsByExcludingVault(vault, lockedVaults)
this.setVaultSelectionOptions(newOptions)
}
unhideVault = async (vault: VaultListingInterface) => {
if (this.application.vaults.isVaultLocked(vault)) {
const unlocked = await this.unlockVault(vault)
if (!unlocked) {
return
}
}
const lockedVaults = this.application.vaults.getLockedvaults()
const newOptions = this.options.newOptionsByUnexcludingVault(vault, lockedVaults)
this.setVaultSelectionOptions(newOptions)
}
showOnlyVault = async (vault: VaultListingInterface) => {
if (this.application.vaults.isVaultLocked(vault)) {
const unlocked = await this.unlockVault(vault)
if (!unlocked) {
return
}
}
const newOptions = new VaultDisplayOptions({ exclusive: vault.systemIdentifier })
this.setVaultSelectionOptions(newOptions)
}
async unlockVault(vault: VaultListingInterface): Promise<boolean> {
if (!this.application.vaults.isVaultLocked(vault)) {
throw new Error('Attempting to unlock a vault that is not locked.')
}
const challenge = new Challenge(
[new ChallengePrompt(ChallengeValidation.None, undefined, 'Password')],
ChallengeReason.Custom,
true,
ChallengeStrings.UnlockVault(vault.name),
ChallengeStrings.EnterVaultPassword,
)
return new Promise((resolve) => {
this.application.challenges.addChallengeObserver(challenge, {
onCancel() {
resolve(false)
},
onNonvalidatedSubmit: async (challengeResponse) => {
const value = challengeResponse.getDefaultValue()
if (!value) {
this.application.challenges.completeChallenge(challenge)
resolve(false)
return
}
const password = value.value as string
const unlocked = await this.application.vaults.unlockNonPersistentVault(vault, password)
if (!unlocked) {
this.application.challenges.setValidationStatusForChallenge(challenge, value, false)
resolve(false)
return
}
this.application.challenges.completeChallenge(challenge)
resolve(true)
},
})
void this.application.challenges.promptForChallengeResponse(challenge)
})
}
private setVaultSelectionOptions = (options: VaultDisplayOptions) => {
this.options = options
if (this.isInExclusiveDisplayMode()) {
this.exclusivelyShownVault = this.application.vaults.getVault({
keySystemIdentifier: this.options.getExclusivelyShownVault(),
})
} else {
this.exclusivelyShownVault = undefined
}
this.application.items.setVaultDisplayOptions(options)
void this.notifyEvent(VaultDisplayServiceEvent.VaultDisplayOptionsChanged, options)
if (this.application.isLaunched()) {
this.application.setValue(StorageKey.VaultSelectionOptions, options.getPersistableValue())
}
}
private loadVaultSelectionOptionsFromDisk = (): void => {
const raw = this.application.getValue<VaultDisplayOptionsPersistable>(StorageKey.VaultSelectionOptions)
if (!raw) {
return
}
const options = VaultDisplayOptions.FromPersistableValue(raw)
this.options = options
void this.notifyEvent(VaultDisplayServiceEvent.VaultDisplayOptionsChanged, options)
}
override deinit(): void {
;(this.options as unknown) = undefined
super.deinit()
}
}