internal: incomplete vault systems behind feature flag (#2340)
This commit is contained in:
216
packages/ui-services/src/Vaults/VaultDisplayService.ts
Normal file
216
packages/ui-services/src/Vaults/VaultDisplayService.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum VaultDisplayServiceEvent {
|
||||
VaultDisplayOptionsChanged = 'VaultDisplayOptionsChanged',
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { VaultDisplayOptions, VaultListingInterface } from '@standardnotes/models'
|
||||
import { AbstractUIServiceInterface } from '../Abstract/AbstractUIServiceInterface'
|
||||
|
||||
export interface VaultDisplayServiceInterface extends AbstractUIServiceInterface {
|
||||
exclusivelyShownVault?: VaultListingInterface
|
||||
|
||||
getOptions(): VaultDisplayOptions
|
||||
|
||||
isVaultDisabledOrLocked(vault: VaultListingInterface): boolean
|
||||
isVaultExplicitelyExcluded: (vault: VaultListingInterface) => boolean
|
||||
isVaultExclusivelyShown: (vault: VaultListingInterface) => boolean
|
||||
isInExclusiveDisplayMode(): boolean
|
||||
|
||||
changeToMultipleVaultDisplayMode(): void
|
||||
|
||||
hideVault: (vault: VaultListingInterface) => void
|
||||
unhideVault: (vault: VaultListingInterface) => void
|
||||
showOnlyVault: (vault: VaultListingInterface) => void
|
||||
unlockVault(vault: VaultListingInterface): Promise<boolean>
|
||||
}
|
||||
Reference in New Issue
Block a user