mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 21:33:27 +00:00
[PM-6404] Fully Integrate clearOn Events (#8134)
* Add New KeyDefinitionOption * Add New Services * Add WebStorageServiceProvider Tests * Update Error Message * Add `UserKeyDefinition` * Fix Deserialization Helpers * Fix KeyDefinition * Add `UserKeyDefinition` * Fix Deserialization Helpers * Fix KeyDefinition * Move `ClearEvent` * Cleanup * Fix Imports * Integrate onClear Events * Remove Accidental Addition * Fix Test * Add VaultTimeoutService Tests * Only Register When Current State is Null * Address Feedback
This commit is contained in:
@@ -93,6 +93,7 @@ import { KeyGenerationService } from "@bitwarden/common/platform/services/key-ge
|
|||||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
import { SystemService } from "@bitwarden/common/platform/services/system.service";
|
||||||
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
||||||
import {
|
import {
|
||||||
@@ -100,6 +101,7 @@ import {
|
|||||||
DerivedStateProvider,
|
DerivedStateProvider,
|
||||||
GlobalStateProvider,
|
GlobalStateProvider,
|
||||||
SingleUserStateProvider,
|
SingleUserStateProvider,
|
||||||
|
StateEventRunnerService,
|
||||||
StateProvider,
|
StateProvider,
|
||||||
} from "@bitwarden/common/platform/state";
|
} from "@bitwarden/common/platform/state";
|
||||||
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
|
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
|
||||||
@@ -107,6 +109,7 @@ import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state
|
|||||||
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
||||||
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
||||||
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
||||||
|
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
||||||
/* eslint-enable import/no-restricted-paths */
|
/* eslint-enable import/no-restricted-paths */
|
||||||
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
|
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
|
||||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||||
@@ -299,6 +302,7 @@ export default class MainBackground {
|
|||||||
organizationVaultExportService: OrganizationVaultExportServiceAbstraction;
|
organizationVaultExportService: OrganizationVaultExportServiceAbstraction;
|
||||||
vaultSettingsService: VaultSettingsServiceAbstraction;
|
vaultSettingsService: VaultSettingsServiceAbstraction;
|
||||||
biometricStateService: BiometricStateService;
|
biometricStateService: BiometricStateService;
|
||||||
|
stateEventRunnerService: StateEventRunnerService;
|
||||||
ssoLoginService: SsoLoginServiceAbstraction;
|
ssoLoginService: SsoLoginServiceAbstraction;
|
||||||
|
|
||||||
// Passed to the popup for Safari to workaround issues with theming, downloading, etc.
|
// Passed to the popup for Safari to workaround issues with theming, downloading, etc.
|
||||||
@@ -366,10 +370,24 @@ export default class MainBackground {
|
|||||||
this.keyGenerationService,
|
this.keyGenerationService,
|
||||||
)
|
)
|
||||||
: new BackgroundMemoryStorageService();
|
: new BackgroundMemoryStorageService();
|
||||||
this.globalStateProvider = new DefaultGlobalStateProvider(
|
|
||||||
this.memoryStorageForStateProviders,
|
const storageServiceProvider = new StorageServiceProvider(
|
||||||
this.storageService as BrowserLocalStorageService,
|
this.storageService as BrowserLocalStorageService,
|
||||||
|
this.memoryStorageForStateProviders,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
||||||
|
|
||||||
|
const stateEventRegistrarService = new StateEventRegistrarService(
|
||||||
|
this.globalStateProvider,
|
||||||
|
storageServiceProvider,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.stateEventRunnerService = new StateEventRunnerService(
|
||||||
|
this.globalStateProvider,
|
||||||
|
storageServiceProvider,
|
||||||
|
);
|
||||||
|
|
||||||
this.encryptService = flagEnabled("multithreadDecryption")
|
this.encryptService = flagEnabled("multithreadDecryption")
|
||||||
? new MultithreadEncryptServiceImplementation(
|
? new MultithreadEncryptServiceImplementation(
|
||||||
this.cryptoFunctionService,
|
this.cryptoFunctionService,
|
||||||
@@ -379,8 +397,8 @@ export default class MainBackground {
|
|||||||
: new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, true);
|
: new EncryptServiceImplementation(this.cryptoFunctionService, this.logService, true);
|
||||||
|
|
||||||
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||||
this.memoryStorageForStateProviders,
|
storageServiceProvider,
|
||||||
this.storageService as BrowserLocalStorageService,
|
stateEventRegistrarService,
|
||||||
);
|
);
|
||||||
this.accountService = new AccountServiceImplementation(
|
this.accountService = new AccountServiceImplementation(
|
||||||
this.messagingService,
|
this.messagingService,
|
||||||
@@ -389,8 +407,8 @@ export default class MainBackground {
|
|||||||
);
|
);
|
||||||
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
|
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
|
||||||
this.accountService,
|
this.accountService,
|
||||||
this.memoryStorageForStateProviders,
|
storageServiceProvider,
|
||||||
this.storageService as BrowserLocalStorageService,
|
stateEventRegistrarService,
|
||||||
);
|
);
|
||||||
this.derivedStateProvider = new BackgroundDerivedStateProvider(
|
this.derivedStateProvider = new BackgroundDerivedStateProvider(
|
||||||
this.memoryStorageForStateProviders,
|
this.memoryStorageForStateProviders,
|
||||||
@@ -666,6 +684,7 @@ export default class MainBackground {
|
|||||||
this.stateService,
|
this.stateService,
|
||||||
this.authService,
|
this.authService,
|
||||||
this.vaultTimeoutSettingsService,
|
this.vaultTimeoutSettingsService,
|
||||||
|
this.stateEventRunnerService,
|
||||||
lockedCallback,
|
lockedCallback,
|
||||||
logoutCallback,
|
logoutCallback,
|
||||||
);
|
);
|
||||||
@@ -1113,6 +1132,8 @@ export default class MainBackground {
|
|||||||
this.searchService.clearIndex();
|
this.searchService.clearIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.stateEventRunnerService.handleEvent("logout", currentUserId as UserId);
|
||||||
|
|
||||||
if (newActiveUser != null) {
|
if (newActiveUser != null) {
|
||||||
// we have a new active user, do not continue tearing down application
|
// we have a new active user, do not continue tearing down application
|
||||||
await this.switchAccount(newActiveUser as UserId);
|
await this.switchAccount(newActiveUser as UserId);
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ import {
|
|||||||
platformUtilsServiceFactory,
|
platformUtilsServiceFactory,
|
||||||
PlatformUtilsServiceInitOptions,
|
PlatformUtilsServiceInitOptions,
|
||||||
} from "../../platform/background/service-factories/platform-utils-service.factory";
|
} from "../../platform/background/service-factories/platform-utils-service.factory";
|
||||||
|
import {
|
||||||
|
stateEventRunnerServiceFactory,
|
||||||
|
StateEventRunnerServiceInitOptions,
|
||||||
|
} from "../../platform/background/service-factories/state-event-runner-service.factory";
|
||||||
import {
|
import {
|
||||||
StateServiceInitOptions,
|
StateServiceInitOptions,
|
||||||
stateServiceFactory,
|
stateServiceFactory,
|
||||||
@@ -62,7 +66,8 @@ export type VaultTimeoutServiceInitOptions = VaultTimeoutServiceFactoryOptions &
|
|||||||
SearchServiceInitOptions &
|
SearchServiceInitOptions &
|
||||||
StateServiceInitOptions &
|
StateServiceInitOptions &
|
||||||
AuthServiceInitOptions &
|
AuthServiceInitOptions &
|
||||||
VaultTimeoutSettingsServiceInitOptions;
|
VaultTimeoutSettingsServiceInitOptions &
|
||||||
|
StateEventRunnerServiceInitOptions;
|
||||||
|
|
||||||
export function vaultTimeoutServiceFactory(
|
export function vaultTimeoutServiceFactory(
|
||||||
cache: { vaultTimeoutService?: AbstractVaultTimeoutService } & CachedServices,
|
cache: { vaultTimeoutService?: AbstractVaultTimeoutService } & CachedServices,
|
||||||
@@ -84,6 +89,7 @@ export function vaultTimeoutServiceFactory(
|
|||||||
await stateServiceFactory(cache, opts),
|
await stateServiceFactory(cache, opts),
|
||||||
await authServiceFactory(cache, opts),
|
await authServiceFactory(cache, opts),
|
||||||
await vaultTimeoutSettingsServiceFactory(cache, opts),
|
await vaultTimeoutSettingsServiceFactory(cache, opts),
|
||||||
|
await stateEventRunnerServiceFactory(cache, opts),
|
||||||
opts.vaultTimeoutServiceOptions.lockedCallback,
|
opts.vaultTimeoutServiceOptions.lockedCallback,
|
||||||
opts.vaultTimeoutServiceOptions.loggedOutCallback,
|
opts.vaultTimeoutServiceOptions.loggedOutCallback,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,18 +9,20 @@ import {
|
|||||||
|
|
||||||
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
||||||
import {
|
import {
|
||||||
DiskStorageServiceInitOptions,
|
StateEventRegistrarServiceInitOptions,
|
||||||
MemoryStorageServiceInitOptions,
|
stateEventRegistrarServiceFactory,
|
||||||
observableDiskStorageServiceFactory,
|
} from "./state-event-registrar-service.factory";
|
||||||
observableMemoryStorageServiceFactory,
|
import {
|
||||||
} from "./storage-service.factory";
|
StorageServiceProviderInitOptions,
|
||||||
|
storageServiceProviderFactory,
|
||||||
|
} from "./storage-service-provider.factory";
|
||||||
|
|
||||||
type ActiveUserStateProviderFactory = FactoryOptions;
|
type ActiveUserStateProviderFactory = FactoryOptions;
|
||||||
|
|
||||||
export type ActiveUserStateProviderInitOptions = ActiveUserStateProviderFactory &
|
export type ActiveUserStateProviderInitOptions = ActiveUserStateProviderFactory &
|
||||||
AccountServiceInitOptions &
|
AccountServiceInitOptions &
|
||||||
MemoryStorageServiceInitOptions &
|
StorageServiceProviderInitOptions &
|
||||||
DiskStorageServiceInitOptions;
|
StateEventRegistrarServiceInitOptions;
|
||||||
|
|
||||||
export async function activeUserStateProviderFactory(
|
export async function activeUserStateProviderFactory(
|
||||||
cache: { activeUserStateProvider?: ActiveUserStateProvider } & CachedServices,
|
cache: { activeUserStateProvider?: ActiveUserStateProvider } & CachedServices,
|
||||||
@@ -33,8 +35,8 @@ export async function activeUserStateProviderFactory(
|
|||||||
async () =>
|
async () =>
|
||||||
new DefaultActiveUserStateProvider(
|
new DefaultActiveUserStateProvider(
|
||||||
await accountServiceFactory(cache, opts),
|
await accountServiceFactory(cache, opts),
|
||||||
await observableMemoryStorageServiceFactory(cache, opts),
|
await storageServiceProviderFactory(cache, opts),
|
||||||
await observableDiskStorageServiceFactory(cache, opts),
|
await stateEventRegistrarServiceFactory(cache, opts),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,14 @@ import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/imp
|
|||||||
|
|
||||||
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
||||||
import {
|
import {
|
||||||
DiskStorageServiceInitOptions,
|
StorageServiceProviderInitOptions,
|
||||||
MemoryStorageServiceInitOptions,
|
storageServiceProviderFactory,
|
||||||
observableDiskStorageServiceFactory,
|
} from "./storage-service-provider.factory";
|
||||||
observableMemoryStorageServiceFactory,
|
|
||||||
} from "./storage-service.factory";
|
|
||||||
|
|
||||||
type GlobalStateProviderFactoryOptions = FactoryOptions;
|
type GlobalStateProviderFactoryOptions = FactoryOptions;
|
||||||
|
|
||||||
export type GlobalStateProviderInitOptions = GlobalStateProviderFactoryOptions &
|
export type GlobalStateProviderInitOptions = GlobalStateProviderFactoryOptions &
|
||||||
MemoryStorageServiceInitOptions &
|
StorageServiceProviderInitOptions;
|
||||||
DiskStorageServiceInitOptions;
|
|
||||||
|
|
||||||
export async function globalStateProviderFactory(
|
export async function globalStateProviderFactory(
|
||||||
cache: { globalStateProvider?: GlobalStateProvider } & CachedServices,
|
cache: { globalStateProvider?: GlobalStateProvider } & CachedServices,
|
||||||
@@ -24,10 +21,6 @@ export async function globalStateProviderFactory(
|
|||||||
cache,
|
cache,
|
||||||
"globalStateProvider",
|
"globalStateProvider",
|
||||||
opts,
|
opts,
|
||||||
async () =>
|
async () => new DefaultGlobalStateProvider(await storageServiceProviderFactory(cache, opts)),
|
||||||
new DefaultGlobalStateProvider(
|
|
||||||
await observableMemoryStorageServiceFactory(cache, opts),
|
|
||||||
await observableDiskStorageServiceFactory(cache, opts),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,19 @@ import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state
|
|||||||
|
|
||||||
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
||||||
import {
|
import {
|
||||||
DiskStorageServiceInitOptions,
|
StateEventRegistrarServiceInitOptions,
|
||||||
MemoryStorageServiceInitOptions,
|
stateEventRegistrarServiceFactory,
|
||||||
observableDiskStorageServiceFactory,
|
} from "./state-event-registrar-service.factory";
|
||||||
observableMemoryStorageServiceFactory,
|
import {
|
||||||
} from "./storage-service.factory";
|
StorageServiceProviderInitOptions,
|
||||||
|
storageServiceProviderFactory,
|
||||||
|
} from "./storage-service-provider.factory";
|
||||||
|
|
||||||
type SingleUserStateProviderFactoryOptions = FactoryOptions;
|
type SingleUserStateProviderFactoryOptions = FactoryOptions;
|
||||||
|
|
||||||
export type SingleUserStateProviderInitOptions = SingleUserStateProviderFactoryOptions &
|
export type SingleUserStateProviderInitOptions = SingleUserStateProviderFactoryOptions &
|
||||||
MemoryStorageServiceInitOptions &
|
StorageServiceProviderInitOptions &
|
||||||
DiskStorageServiceInitOptions;
|
StateEventRegistrarServiceInitOptions;
|
||||||
|
|
||||||
export async function singleUserStateProviderFactory(
|
export async function singleUserStateProviderFactory(
|
||||||
cache: { singleUserStateProvider?: SingleUserStateProvider } & CachedServices,
|
cache: { singleUserStateProvider?: SingleUserStateProvider } & CachedServices,
|
||||||
@@ -26,8 +28,8 @@ export async function singleUserStateProviderFactory(
|
|||||||
opts,
|
opts,
|
||||||
async () =>
|
async () =>
|
||||||
new DefaultSingleUserStateProvider(
|
new DefaultSingleUserStateProvider(
|
||||||
await observableMemoryStorageServiceFactory(cache, opts),
|
await storageServiceProviderFactory(cache, opts),
|
||||||
await observableDiskStorageServiceFactory(cache, opts),
|
await stateEventRegistrarServiceFactory(cache, opts),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { StateEventRunnerService } from "@bitwarden/common/platform/state";
|
||||||
|
|
||||||
|
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
||||||
|
import {
|
||||||
|
GlobalStateProviderInitOptions,
|
||||||
|
globalStateProviderFactory,
|
||||||
|
} from "./global-state-provider.factory";
|
||||||
|
import {
|
||||||
|
StorageServiceProviderInitOptions,
|
||||||
|
storageServiceProviderFactory,
|
||||||
|
} from "./storage-service-provider.factory";
|
||||||
|
|
||||||
|
type StateEventRunnerServiceFactoryOptions = FactoryOptions;
|
||||||
|
|
||||||
|
export type StateEventRunnerServiceInitOptions = StateEventRunnerServiceFactoryOptions &
|
||||||
|
GlobalStateProviderInitOptions &
|
||||||
|
StorageServiceProviderInitOptions;
|
||||||
|
|
||||||
|
export function stateEventRunnerServiceFactory(
|
||||||
|
cache: { stateEventRunnerService?: StateEventRunnerService } & CachedServices,
|
||||||
|
opts: StateEventRunnerServiceInitOptions,
|
||||||
|
): Promise<StateEventRunnerService> {
|
||||||
|
return factory(
|
||||||
|
cache,
|
||||||
|
"stateEventRunnerService",
|
||||||
|
opts,
|
||||||
|
async () =>
|
||||||
|
new StateEventRunnerService(
|
||||||
|
await globalStateProviderFactory(cache, opts),
|
||||||
|
await storageServiceProviderFactory(cache, opts),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -60,11 +60,13 @@ import { MigrationBuilderService } from "@bitwarden/common/platform/services/mig
|
|||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-messaging.service";
|
import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-messaging.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/services/state.service";
|
import { StateService } from "@bitwarden/common/platform/services/state.service";
|
||||||
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
import {
|
import {
|
||||||
ActiveUserStateProvider,
|
ActiveUserStateProvider,
|
||||||
DerivedStateProvider,
|
DerivedStateProvider,
|
||||||
GlobalStateProvider,
|
GlobalStateProvider,
|
||||||
SingleUserStateProvider,
|
SingleUserStateProvider,
|
||||||
|
StateEventRunnerService,
|
||||||
StateProvider,
|
StateProvider,
|
||||||
} from "@bitwarden/common/platform/state";
|
} from "@bitwarden/common/platform/state";
|
||||||
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
|
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally these should not be accessed */
|
||||||
@@ -73,6 +75,7 @@ import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/im
|
|||||||
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
||||||
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
||||||
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
||||||
|
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
||||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||||
/* eslint-enable import/no-restricted-paths */
|
/* eslint-enable import/no-restricted-paths */
|
||||||
import { AuditService } from "@bitwarden/common/services/audit.service";
|
import { AuditService } from "@bitwarden/common/services/audit.service";
|
||||||
@@ -208,6 +211,7 @@ export class Main {
|
|||||||
derivedStateProvider: DerivedStateProvider;
|
derivedStateProvider: DerivedStateProvider;
|
||||||
stateProvider: StateProvider;
|
stateProvider: StateProvider;
|
||||||
loginStrategyService: LoginStrategyServiceAbstraction;
|
loginStrategyService: LoginStrategyServiceAbstraction;
|
||||||
|
stateEventRunnerService: StateEventRunnerService;
|
||||||
biometricStateService: BiometricStateService;
|
biometricStateService: BiometricStateService;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -249,14 +253,26 @@ export class Main {
|
|||||||
this.memoryStorageService = new MemoryStorageService();
|
this.memoryStorageService = new MemoryStorageService();
|
||||||
this.memoryStorageForStateProviders = new MemoryStorageServiceForStateProviders();
|
this.memoryStorageForStateProviders = new MemoryStorageServiceForStateProviders();
|
||||||
|
|
||||||
this.globalStateProvider = new DefaultGlobalStateProvider(
|
const storageServiceProvider = new StorageServiceProvider(
|
||||||
this.memoryStorageForStateProviders,
|
|
||||||
this.storageService,
|
this.storageService,
|
||||||
|
this.memoryStorageForStateProviders,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
||||||
|
|
||||||
|
const stateEventRegistrarService = new StateEventRegistrarService(
|
||||||
|
this.globalStateProvider,
|
||||||
|
storageServiceProvider,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.stateEventRunnerService = new StateEventRunnerService(
|
||||||
|
this.globalStateProvider,
|
||||||
|
storageServiceProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
this.singleUserStateProvider = new DefaultSingleUserStateProvider(
|
||||||
this.memoryStorageForStateProviders,
|
storageServiceProvider,
|
||||||
this.storageService,
|
stateEventRegistrarService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.messagingService = new NoopMessagingService();
|
this.messagingService = new NoopMessagingService();
|
||||||
@@ -269,8 +285,8 @@ export class Main {
|
|||||||
|
|
||||||
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
|
this.activeUserStateProvider = new DefaultActiveUserStateProvider(
|
||||||
this.accountService,
|
this.accountService,
|
||||||
this.memoryStorageForStateProviders,
|
storageServiceProvider,
|
||||||
this.storageService,
|
stateEventRegistrarService,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.derivedStateProvider = new DefaultDerivedStateProvider(
|
this.derivedStateProvider = new DefaultDerivedStateProvider(
|
||||||
@@ -530,6 +546,7 @@ export class Main {
|
|||||||
this.stateService,
|
this.stateService,
|
||||||
this.authService,
|
this.authService,
|
||||||
this.vaultTimeoutSettingsService,
|
this.vaultTimeoutSettingsService,
|
||||||
|
this.stateEventRunnerService,
|
||||||
lockedCallback,
|
lockedCallback,
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
@@ -639,6 +656,9 @@ export class Main {
|
|||||||
this.policyService.clear(userId),
|
this.policyService.clear(userId),
|
||||||
this.passwordGenerationService.clear(),
|
this.passwordGenerationService.clear(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
await this.stateEventRunnerService.handleEvent("logout", userId as UserId);
|
||||||
|
|
||||||
await this.stateService.clean();
|
await this.stateService.clean();
|
||||||
process.env.BW_SESSION = null;
|
process.env.BW_SESSION = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
import { SystemService } from "@bitwarden/common/platform/abstractions/system.service";
|
||||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||||
|
import { StateEventRunnerService } from "@bitwarden/common/platform/state";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -149,6 +150,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private configService: ConfigServiceAbstraction,
|
private configService: ConfigServiceAbstraction,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private biometricStateService: BiometricStateService,
|
private biometricStateService: BiometricStateService,
|
||||||
|
private stateEventRunnerService: StateEventRunnerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -219,13 +221,13 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
const currentUser = await this.stateService.getUserId();
|
const currentUser = await this.stateService.getUserId();
|
||||||
const accounts = await firstValueFrom(this.stateService.accounts$);
|
const accounts = await firstValueFrom(this.stateService.accounts$);
|
||||||
await this.vaultTimeoutService.lock(currentUser);
|
await this.vaultTimeoutService.lock(currentUser);
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
for (const account of Object.keys(accounts)) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
if (account === currentUser) {
|
||||||
Promise.all(
|
continue;
|
||||||
Object.keys(accounts)
|
}
|
||||||
.filter((u) => u !== currentUser)
|
|
||||||
.map((u) => this.vaultTimeoutService.lock(u)),
|
await this.vaultTimeoutService.lock(account);
|
||||||
);
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "locked":
|
case "locked":
|
||||||
@@ -583,6 +585,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
await this.keyConnectorService.clear();
|
await this.keyConnectorService.clear();
|
||||||
await this.biometricStateService.logout(userBeingLoggedOut as UserId);
|
await this.biometricStateService.logout(userBeingLoggedOut as UserId);
|
||||||
|
|
||||||
|
await this.stateEventRunnerService.handleEvent("logout", userBeingLoggedOut as UserId);
|
||||||
|
|
||||||
preLogoutActiveUserId = this.activeUserId;
|
preLogoutActiveUserId = this.activeUserId;
|
||||||
await this.stateService.clean({ userId: userBeingLoggedOut });
|
await this.stateService.clean({ userId: userBeingLoggedOut });
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -13,11 +13,13 @@ import { MigrationBuilderService } from "@bitwarden/common/platform/services/mig
|
|||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-messaging.service";
|
import { NoopMessagingService } from "@bitwarden/common/platform/services/noop-messaging.service";
|
||||||
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed */
|
/* eslint-disable import/no-restricted-paths -- We need the implementation to inject, but generally this should not be accessed */
|
||||||
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
|
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
|
||||||
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
|
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
|
||||||
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
||||||
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
||||||
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
||||||
|
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
||||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||||
/* eslint-enable import/no-restricted-paths */
|
/* eslint-enable import/no-restricted-paths */
|
||||||
|
|
||||||
@@ -104,10 +106,11 @@ export class Main {
|
|||||||
this.storageService = new ElectronStorageService(app.getPath("userData"), storageDefaults);
|
this.storageService = new ElectronStorageService(app.getPath("userData"), storageDefaults);
|
||||||
this.memoryStorageService = new MemoryStorageService();
|
this.memoryStorageService = new MemoryStorageService();
|
||||||
this.memoryStorageForStateProviders = new MemoryStorageServiceForStateProviders();
|
this.memoryStorageForStateProviders = new MemoryStorageServiceForStateProviders();
|
||||||
const globalStateProvider = new DefaultGlobalStateProvider(
|
const storageServiceProvider = new StorageServiceProvider(
|
||||||
this.memoryStorageForStateProviders,
|
|
||||||
this.storageService,
|
this.storageService,
|
||||||
|
this.memoryStorageForStateProviders,
|
||||||
);
|
);
|
||||||
|
const globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
||||||
|
|
||||||
const accountService = new AccountServiceImplementation(
|
const accountService = new AccountServiceImplementation(
|
||||||
new NoopMessagingService(),
|
new NoopMessagingService(),
|
||||||
@@ -115,13 +118,18 @@ export class Main {
|
|||||||
globalStateProvider,
|
globalStateProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const stateEventRegistrarService = new StateEventRegistrarService(
|
||||||
|
globalStateProvider,
|
||||||
|
storageServiceProvider,
|
||||||
|
);
|
||||||
|
|
||||||
const stateProvider = new DefaultStateProvider(
|
const stateProvider = new DefaultStateProvider(
|
||||||
new DefaultActiveUserStateProvider(
|
new DefaultActiveUserStateProvider(
|
||||||
accountService,
|
accountService,
|
||||||
this.memoryStorageForStateProviders,
|
storageServiceProvider,
|
||||||
this.storageService,
|
stateEventRegistrarService,
|
||||||
),
|
),
|
||||||
new DefaultSingleUserStateProvider(this.memoryStorageForStateProviders, this.storageService),
|
new DefaultSingleUserStateProvider(storageServiceProvider, stateEventRegistrarService),
|
||||||
globalStateProvider,
|
globalStateProvider,
|
||||||
new DefaultDerivedStateProvider(this.memoryStorageForStateProviders),
|
new DefaultDerivedStateProvider(this.memoryStorageForStateProviders),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
|
||||||
|
import { StateEventRunnerService } from "@bitwarden/common/platform/state";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
import { UserId } from "@bitwarden/common/types/guid";
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
@@ -89,6 +90,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
private configService: ConfigServiceAbstraction,
|
private configService: ConfigServiceAbstraction,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private biometricStateService: BiometricStateService,
|
private biometricStateService: BiometricStateService,
|
||||||
|
private stateEventRunnerService: StateEventRunnerService,
|
||||||
private paymentMethodWarningService: PaymentMethodWarningService,
|
private paymentMethodWarningService: PaymentMethodWarningService,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
) {}
|
) {}
|
||||||
@@ -284,6 +286,8 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
this.paymentMethodWarningService.clear(),
|
this.paymentMethodWarningService.clear(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
await this.stateEventRunnerService.handleEvent("logout", userId as UserId);
|
||||||
|
|
||||||
this.searchService.clearIndex();
|
this.searchService.clearIndex();
|
||||||
this.authService.logOut(async () => {
|
this.authService.logOut(async () => {
|
||||||
if (expired) {
|
if (expired) {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
} from "@bitwarden/angular/services/injection-tokens";
|
} from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||||
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
|
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
|
||||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { LoginService } from "@bitwarden/common/auth/services/login.service";
|
import { LoginService } from "@bitwarden/common/auth/services/login.service";
|
||||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||||
@@ -28,21 +27,16 @@ import { StateFactory } from "@bitwarden/common/platform/factories/state-factory
|
|||||||
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
|
||||||
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import {
|
/* eslint-disable import/no-restricted-paths -- Implementation for memory storage */
|
||||||
ActiveUserStateProvider,
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
GlobalStateProvider,
|
|
||||||
SingleUserStateProvider,
|
|
||||||
} from "@bitwarden/common/platform/state";
|
|
||||||
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
|
|
||||||
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service";
|
||||||
|
/* eslint-enable import/no-restricted-paths -- Implementation for memory storage */
|
||||||
|
|
||||||
import { PolicyListService } from "../admin-console/core/policy-list.service";
|
import { PolicyListService } from "../admin-console/core/policy-list.service";
|
||||||
import { HtmlStorageService } from "../core/html-storage.service";
|
import { HtmlStorageService } from "../core/html-storage.service";
|
||||||
import { I18nService } from "../core/i18n.service";
|
import { I18nService } from "../core/i18n.service";
|
||||||
import { WebActiveUserStateProvider } from "../platform/web-active-user-state.provider";
|
|
||||||
import { WebGlobalStateProvider } from "../platform/web-global-state.provider";
|
|
||||||
import { WebMigrationRunner } from "../platform/web-migration-runner";
|
import { WebMigrationRunner } from "../platform/web-migration-runner";
|
||||||
import { WebSingleUserStateProvider } from "../platform/web-single-user-state.provider";
|
import { WebStorageServiceProvider } from "../platform/web-storage-service.provider";
|
||||||
import { WindowStorageService } from "../platform/window-storage.service";
|
import { WindowStorageService } from "../platform/window-storage.service";
|
||||||
import { CollectionAdminService } from "../vault/core/collection-admin.service";
|
import { CollectionAdminService } from "../vault/core/collection-admin.service";
|
||||||
|
|
||||||
@@ -124,24 +118,9 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service";
|
|||||||
useFactory: () => new WindowStorageService(window.localStorage),
|
useFactory: () => new WindowStorageService(window.localStorage),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SingleUserStateProvider,
|
provide: StorageServiceProvider,
|
||||||
useClass: WebSingleUserStateProvider,
|
useClass: WebStorageServiceProvider,
|
||||||
deps: [OBSERVABLE_MEMORY_STORAGE, OBSERVABLE_DISK_STORAGE, OBSERVABLE_DISK_LOCAL_STORAGE],
|
deps: [OBSERVABLE_DISK_STORAGE, OBSERVABLE_MEMORY_STORAGE, OBSERVABLE_DISK_LOCAL_STORAGE],
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: ActiveUserStateProvider,
|
|
||||||
useClass: WebActiveUserStateProvider,
|
|
||||||
deps: [
|
|
||||||
AccountService,
|
|
||||||
OBSERVABLE_MEMORY_STORAGE,
|
|
||||||
OBSERVABLE_DISK_STORAGE,
|
|
||||||
OBSERVABLE_DISK_LOCAL_STORAGE,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: GlobalStateProvider,
|
|
||||||
useClass: WebGlobalStateProvider,
|
|
||||||
deps: [OBSERVABLE_MEMORY_STORAGE, OBSERVABLE_DISK_STORAGE, OBSERVABLE_DISK_LOCAL_STORAGE],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: MigrationRunner,
|
provide: MigrationRunner,
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
|
||||||
import {
|
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
|
||||||
ObservableStorageService,
|
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
|
||||||
import { UserKeyDefinition } from "@bitwarden/common/platform/state";
|
|
||||||
/* eslint-disable import/no-restricted-paths -- Needed to extend class & in platform owned code */
|
|
||||||
import { DefaultActiveUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-active-user-state.provider";
|
|
||||||
import { StateDefinition } from "@bitwarden/common/platform/state/state-definition";
|
|
||||||
/* eslint-enable import/no-restricted-paths */
|
|
||||||
|
|
||||||
export class WebActiveUserStateProvider extends DefaultActiveUserStateProvider {
|
|
||||||
constructor(
|
|
||||||
accountService: AccountService,
|
|
||||||
memoryStorage: AbstractMemoryStorageService & ObservableStorageService,
|
|
||||||
sessionStorage: AbstractStorageService & ObservableStorageService,
|
|
||||||
private readonly diskLocalStorage: AbstractStorageService & ObservableStorageService,
|
|
||||||
) {
|
|
||||||
super(accountService, memoryStorage, sessionStorage);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override getLocationString(keyDefinition: UserKeyDefinition<unknown>): string {
|
|
||||||
return (
|
|
||||||
keyDefinition.stateDefinition.storageLocationOverrides["web"] ??
|
|
||||||
keyDefinition.stateDefinition.defaultStorageLocation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override getLocation(
|
|
||||||
stateDefinition: StateDefinition,
|
|
||||||
): AbstractStorageService & ObservableStorageService {
|
|
||||||
const location =
|
|
||||||
stateDefinition.storageLocationOverrides["web"] ?? stateDefinition.defaultStorageLocation;
|
|
||||||
switch (location) {
|
|
||||||
case "disk":
|
|
||||||
return this.diskStorage;
|
|
||||||
case "memory":
|
|
||||||
return this.memoryStorage;
|
|
||||||
case "disk-local":
|
|
||||||
return this.diskLocalStorage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import {
|
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
|
||||||
ObservableStorageService,
|
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
|
||||||
import { KeyDefinition } from "@bitwarden/common/platform/state";
|
|
||||||
/* eslint-disable import/no-restricted-paths -- Needed to extend class & in platform owned code*/
|
|
||||||
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
|
||||||
import { StateDefinition } from "@bitwarden/common/platform/state/state-definition";
|
|
||||||
/* eslint-enable import/no-restricted-paths */
|
|
||||||
|
|
||||||
export class WebGlobalStateProvider extends DefaultGlobalStateProvider {
|
|
||||||
constructor(
|
|
||||||
memoryStorage: AbstractMemoryStorageService & ObservableStorageService,
|
|
||||||
sessionStorage: AbstractStorageService & ObservableStorageService,
|
|
||||||
private readonly diskLocalStorage: AbstractStorageService & ObservableStorageService,
|
|
||||||
) {
|
|
||||||
super(memoryStorage, sessionStorage);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getLocationString(keyDefinition: KeyDefinition<unknown>): string {
|
|
||||||
return (
|
|
||||||
keyDefinition.stateDefinition.storageLocationOverrides["web"] ??
|
|
||||||
keyDefinition.stateDefinition.defaultStorageLocation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override getLocation(
|
|
||||||
stateDefinition: StateDefinition,
|
|
||||||
): AbstractStorageService & ObservableStorageService {
|
|
||||||
const location =
|
|
||||||
stateDefinition.storageLocationOverrides["web"] ?? stateDefinition.defaultStorageLocation;
|
|
||||||
switch (location) {
|
|
||||||
case "disk":
|
|
||||||
return this.diskStorage;
|
|
||||||
case "memory":
|
|
||||||
return this.memoryStorage;
|
|
||||||
case "disk-local":
|
|
||||||
return this.diskLocalStorage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import {
|
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
|
||||||
ObservableStorageService,
|
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
|
||||||
import { UserKeyDefinition } from "@bitwarden/common/platform/state";
|
|
||||||
/* eslint-disable import/no-restricted-paths -- Needed to extend service & and in platform owned file */
|
|
||||||
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
|
||||||
import { StateDefinition } from "@bitwarden/common/platform/state/state-definition";
|
|
||||||
/* eslint-enable import/no-restricted-paths */
|
|
||||||
|
|
||||||
export class WebSingleUserStateProvider extends DefaultSingleUserStateProvider {
|
|
||||||
constructor(
|
|
||||||
memoryStorageService: AbstractMemoryStorageService & ObservableStorageService,
|
|
||||||
sessionStorageService: AbstractStorageService & ObservableStorageService,
|
|
||||||
private readonly diskLocalStorageService: AbstractStorageService & ObservableStorageService,
|
|
||||||
) {
|
|
||||||
super(memoryStorageService, sessionStorageService);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override getLocationString(keyDefinition: UserKeyDefinition<unknown>): string {
|
|
||||||
return (
|
|
||||||
keyDefinition.stateDefinition.storageLocationOverrides["web"] ??
|
|
||||||
keyDefinition.stateDefinition.defaultStorageLocation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override getLocation(
|
|
||||||
stateDefinition: StateDefinition,
|
|
||||||
): AbstractStorageService & ObservableStorageService {
|
|
||||||
const location =
|
|
||||||
stateDefinition.storageLocationOverrides["web"] ?? stateDefinition.defaultStorageLocation;
|
|
||||||
|
|
||||||
switch (location) {
|
|
||||||
case "disk":
|
|
||||||
return this.diskStorage;
|
|
||||||
case "memory":
|
|
||||||
return this.memoryStorage;
|
|
||||||
case "disk-local":
|
|
||||||
return this.diskLocalStorageService;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -135,6 +135,7 @@ import { MigrationBuilderService } from "@bitwarden/common/platform/services/mig
|
|||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
import { NoopNotificationsService } from "@bitwarden/common/platform/services/noop-notifications.service";
|
import { NoopNotificationsService } from "@bitwarden/common/platform/services/noop-notifications.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/services/state.service";
|
import { StateService } from "@bitwarden/common/platform/services/state.service";
|
||||||
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
import { ValidationService } from "@bitwarden/common/platform/services/validation.service";
|
import { ValidationService } from "@bitwarden/common/platform/services/validation.service";
|
||||||
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
||||||
import {
|
import {
|
||||||
@@ -150,6 +151,8 @@ import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/im
|
|||||||
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
import { DefaultGlobalStateProvider } from "@bitwarden/common/platform/state/implementations/default-global-state.provider";
|
||||||
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
import { DefaultSingleUserStateProvider } from "@bitwarden/common/platform/state/implementations/default-single-user-state.provider";
|
||||||
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
import { DefaultStateProvider } from "@bitwarden/common/platform/state/implementations/default-state.provider";
|
||||||
|
import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service";
|
||||||
|
import { StateEventRunnerService } from "@bitwarden/common/platform/state/state-event-runner.service";
|
||||||
/* eslint-enable import/no-restricted-paths */
|
/* eslint-enable import/no-restricted-paths */
|
||||||
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
|
import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-update.service";
|
||||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||||
@@ -551,6 +554,7 @@ import { ModalService } from "./modal.service";
|
|||||||
StateServiceAbstraction,
|
StateServiceAbstraction,
|
||||||
AuthServiceAbstraction,
|
AuthServiceAbstraction,
|
||||||
VaultTimeoutSettingsServiceAbstraction,
|
VaultTimeoutSettingsServiceAbstraction,
|
||||||
|
StateEventRunnerService,
|
||||||
LOCKED_CALLBACK,
|
LOCKED_CALLBACK,
|
||||||
LOGOUT_CALLBACK,
|
LOGOUT_CALLBACK,
|
||||||
],
|
],
|
||||||
@@ -890,20 +894,35 @@ import { ModalService } from "./modal.service";
|
|||||||
LogService,
|
LogService,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: StorageServiceProvider,
|
||||||
|
useClass: StorageServiceProvider,
|
||||||
|
deps: [OBSERVABLE_DISK_STORAGE, OBSERVABLE_MEMORY_STORAGE],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: StateEventRegistrarService,
|
||||||
|
useClass: StateEventRegistrarService,
|
||||||
|
deps: [GlobalStateProvider, StorageServiceProvider],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: StateEventRunnerService,
|
||||||
|
useClass: StateEventRunnerService,
|
||||||
|
deps: [GlobalStateProvider, StorageServiceProvider],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: GlobalStateProvider,
|
provide: GlobalStateProvider,
|
||||||
useClass: DefaultGlobalStateProvider,
|
useClass: DefaultGlobalStateProvider,
|
||||||
deps: [OBSERVABLE_MEMORY_STORAGE, OBSERVABLE_DISK_STORAGE],
|
deps: [StorageServiceProvider],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: ActiveUserStateProvider,
|
provide: ActiveUserStateProvider,
|
||||||
useClass: DefaultActiveUserStateProvider,
|
useClass: DefaultActiveUserStateProvider,
|
||||||
deps: [AccountServiceAbstraction, OBSERVABLE_MEMORY_STORAGE, OBSERVABLE_DISK_STORAGE],
|
deps: [AccountServiceAbstraction, StorageServiceProvider, StateEventRegistrarService],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SingleUserStateProvider,
|
provide: SingleUserStateProvider,
|
||||||
useClass: DefaultSingleUserStateProvider,
|
useClass: DefaultSingleUserStateProvider,
|
||||||
deps: [OBSERVABLE_MEMORY_STORAGE, OBSERVABLE_DISK_STORAGE],
|
deps: [StorageServiceProvider, StateEventRegistrarService],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: DerivedStateProvider,
|
provide: DerivedStateProvider,
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export class FakeGlobalState<T> implements GlobalState<T> {
|
|||||||
this.nextMock(newState);
|
this.nextMock(newState);
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tracks update values resolved by `FakeState.update` */
|
/** Tracks update values resolved by `FakeState.update` */
|
||||||
nextMock = jest.fn<void, [T]>();
|
nextMock = jest.fn<void, [T]>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { mock } from "jest-mock-extended";
|
||||||
import { firstValueFrom, timeout } from "rxjs";
|
import { firstValueFrom, timeout } from "rxjs";
|
||||||
|
|
||||||
import { awaitAsync } from "../../../spec";
|
import { awaitAsync } from "../../../spec";
|
||||||
@@ -14,9 +15,11 @@ import { DefaultDerivedStateProvider } from "../state/implementations/default-de
|
|||||||
import { DefaultGlobalStateProvider } from "../state/implementations/default-global-state.provider";
|
import { DefaultGlobalStateProvider } from "../state/implementations/default-global-state.provider";
|
||||||
import { DefaultSingleUserStateProvider } from "../state/implementations/default-single-user-state.provider";
|
import { DefaultSingleUserStateProvider } from "../state/implementations/default-single-user-state.provider";
|
||||||
import { DefaultStateProvider } from "../state/implementations/default-state.provider";
|
import { DefaultStateProvider } from "../state/implementations/default-state.provider";
|
||||||
/* eslint-disable import/no-restricted-paths */
|
import { StateEventRegistrarService } from "../state/state-event-registrar.service";
|
||||||
|
/* eslint-enable import/no-restricted-paths */
|
||||||
|
|
||||||
import { EnvironmentService } from "./environment.service";
|
import { EnvironmentService } from "./environment.service";
|
||||||
|
import { StorageServiceProvider } from "./storage-service.provider";
|
||||||
|
|
||||||
// There are a few main states EnvironmentService could be in when first used
|
// There are a few main states EnvironmentService could be in when first used
|
||||||
// 1. Not initialized, no active user. Hopefully not to likely but possible
|
// 1. Not initialized, no active user. Hopefully not to likely but possible
|
||||||
@@ -26,6 +29,8 @@ import { EnvironmentService } from "./environment.service";
|
|||||||
describe("EnvironmentService", () => {
|
describe("EnvironmentService", () => {
|
||||||
let diskStorageService: FakeStorageService;
|
let diskStorageService: FakeStorageService;
|
||||||
let memoryStorageService: FakeStorageService;
|
let memoryStorageService: FakeStorageService;
|
||||||
|
let storageServiceProvider: StorageServiceProvider;
|
||||||
|
const stateEventRegistrarService = mock<StateEventRegistrarService>();
|
||||||
let accountService: FakeAccountService;
|
let accountService: FakeAccountService;
|
||||||
let stateProvider: StateProvider;
|
let stateProvider: StateProvider;
|
||||||
|
|
||||||
@@ -37,16 +42,17 @@ describe("EnvironmentService", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
diskStorageService = new FakeStorageService();
|
diskStorageService = new FakeStorageService();
|
||||||
memoryStorageService = new FakeStorageService();
|
memoryStorageService = new FakeStorageService();
|
||||||
|
storageServiceProvider = new StorageServiceProvider(diskStorageService, memoryStorageService);
|
||||||
|
|
||||||
accountService = mockAccountServiceWith(undefined);
|
accountService = mockAccountServiceWith(undefined);
|
||||||
stateProvider = new DefaultStateProvider(
|
stateProvider = new DefaultStateProvider(
|
||||||
new DefaultActiveUserStateProvider(
|
new DefaultActiveUserStateProvider(
|
||||||
accountService,
|
accountService,
|
||||||
memoryStorageService as any,
|
storageServiceProvider,
|
||||||
diskStorageService,
|
stateEventRegistrarService,
|
||||||
),
|
),
|
||||||
new DefaultSingleUserStateProvider(memoryStorageService as any, diskStorageService),
|
new DefaultSingleUserStateProvider(storageServiceProvider, stateEventRegistrarService),
|
||||||
new DefaultGlobalStateProvider(memoryStorageService as any, diskStorageService),
|
new DefaultGlobalStateProvider(storageServiceProvider),
|
||||||
new DefaultDerivedStateProvider(memoryStorageService),
|
new DefaultDerivedStateProvider(memoryStorageService),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,14 @@ import { mock } from "jest-mock-extended";
|
|||||||
import { mockAccountServiceWith, trackEmissions } from "../../../../spec";
|
import { mockAccountServiceWith, trackEmissions } from "../../../../spec";
|
||||||
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import {
|
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
||||||
AbstractMemoryStorageService,
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
AbstractStorageService,
|
|
||||||
ObservableStorageService,
|
|
||||||
} from "../../abstractions/storage.service";
|
|
||||||
|
|
||||||
import { DefaultActiveUserStateProvider } from "./default-active-user-state.provider";
|
import { DefaultActiveUserStateProvider } from "./default-active-user-state.provider";
|
||||||
|
|
||||||
describe("DefaultActiveUserStateProvider", () => {
|
describe("DefaultActiveUserStateProvider", () => {
|
||||||
const memoryStorage = mock<AbstractMemoryStorageService & ObservableStorageService>();
|
const storageServiceProvider = mock<StorageServiceProvider>();
|
||||||
const diskStorage = mock<AbstractStorageService & ObservableStorageService>();
|
const stateEventRegistrarService = mock<StateEventRegistrarService>();
|
||||||
const userId = "userId" as UserId;
|
const userId = "userId" as UserId;
|
||||||
const accountInfo = {
|
const accountInfo = {
|
||||||
id: userId,
|
id: userId,
|
||||||
@@ -25,7 +22,11 @@ describe("DefaultActiveUserStateProvider", () => {
|
|||||||
let sut: DefaultActiveUserStateProvider;
|
let sut: DefaultActiveUserStateProvider;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sut = new DefaultActiveUserStateProvider(accountService, memoryStorage, diskStorage);
|
sut = new DefaultActiveUserStateProvider(
|
||||||
|
accountService,
|
||||||
|
storageServiceProvider,
|
||||||
|
stateEventRegistrarService,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -2,13 +2,9 @@ import { Observable, map } from "rxjs";
|
|||||||
|
|
||||||
import { AccountService } from "../../../auth/abstractions/account.service";
|
import { AccountService } from "../../../auth/abstractions/account.service";
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import {
|
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
|
||||||
ObservableStorageService,
|
|
||||||
} from "../../abstractions/storage.service";
|
|
||||||
import { KeyDefinition } from "../key-definition";
|
import { KeyDefinition } from "../key-definition";
|
||||||
import { StateDefinition } from "../state-definition";
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
import { UserKeyDefinition, isUserKeyDefinition } from "../user-key-definition";
|
import { UserKeyDefinition, isUserKeyDefinition } from "../user-key-definition";
|
||||||
import { ActiveUserState } from "../user-state";
|
import { ActiveUserState } from "../user-state";
|
||||||
import { ActiveUserStateProvider } from "../user-state.provider";
|
import { ActiveUserStateProvider } from "../user-state.provider";
|
||||||
@@ -21,9 +17,9 @@ export class DefaultActiveUserStateProvider implements ActiveUserStateProvider {
|
|||||||
activeUserId$: Observable<UserId | undefined>;
|
activeUserId$: Observable<UserId | undefined>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly accountService: AccountService,
|
private readonly accountService: AccountService,
|
||||||
protected readonly memoryStorage: AbstractMemoryStorageService & ObservableStorageService,
|
private readonly storageServiceProvider: StorageServiceProvider,
|
||||||
protected readonly diskStorage: AbstractStorageService & ObservableStorageService,
|
private readonly stateEventRegistrarService: StateEventRegistrarService,
|
||||||
) {
|
) {
|
||||||
this.activeUserId$ = this.accountService.activeAccount$.pipe(map((account) => account?.id));
|
this.activeUserId$ = this.accountService.activeAccount$.pipe(map((account) => account?.id));
|
||||||
}
|
}
|
||||||
@@ -32,7 +28,11 @@ export class DefaultActiveUserStateProvider implements ActiveUserStateProvider {
|
|||||||
if (!isUserKeyDefinition(keyDefinition)) {
|
if (!isUserKeyDefinition(keyDefinition)) {
|
||||||
keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition);
|
keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition);
|
||||||
}
|
}
|
||||||
const cacheKey = this.buildCacheKey(keyDefinition);
|
const [location, storageService] = this.storageServiceProvider.get(
|
||||||
|
keyDefinition.stateDefinition.defaultStorageLocation,
|
||||||
|
keyDefinition.stateDefinition.storageLocationOverrides,
|
||||||
|
);
|
||||||
|
const cacheKey = this.buildCacheKey(location, keyDefinition);
|
||||||
const existingUserState = this.cache[cacheKey];
|
const existingUserState = this.cache[cacheKey];
|
||||||
if (existingUserState != null) {
|
if (existingUserState != null) {
|
||||||
// I have to cast out of the unknown generic but this should be safe if rules
|
// I have to cast out of the unknown generic but this should be safe if rules
|
||||||
@@ -40,36 +40,17 @@ export class DefaultActiveUserStateProvider implements ActiveUserStateProvider {
|
|||||||
return existingUserState as ActiveUserState<T>;
|
return existingUserState as ActiveUserState<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUserState = this.buildActiveUserState(keyDefinition);
|
const newUserState = new DefaultActiveUserState<T>(
|
||||||
|
keyDefinition,
|
||||||
|
this.accountService,
|
||||||
|
storageService,
|
||||||
|
this.stateEventRegistrarService,
|
||||||
|
);
|
||||||
this.cache[cacheKey] = newUserState;
|
this.cache[cacheKey] = newUserState;
|
||||||
return newUserState;
|
return newUserState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildCacheKey(keyDefinition: UserKeyDefinition<unknown>) {
|
private buildCacheKey(location: string, keyDefinition: UserKeyDefinition<unknown>) {
|
||||||
return `${this.getLocationString(keyDefinition)}_${keyDefinition.fullName}`;
|
return `${location}_${keyDefinition.fullName}`;
|
||||||
}
|
|
||||||
|
|
||||||
protected buildActiveUserState<T>(keyDefinition: UserKeyDefinition<T>): ActiveUserState<T> {
|
|
||||||
return new DefaultActiveUserState<T>(
|
|
||||||
keyDefinition,
|
|
||||||
this.accountService,
|
|
||||||
this.getLocation(keyDefinition.stateDefinition),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getLocationString(keyDefinition: UserKeyDefinition<unknown>): string {
|
|
||||||
return keyDefinition.stateDefinition.defaultStorageLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getLocation(stateDefinition: StateDefinition) {
|
|
||||||
// The default implementations don't support the client overrides
|
|
||||||
// it is up to the client to extend this class and add that support
|
|
||||||
const location = stateDefinition.defaultStorageLocation;
|
|
||||||
switch (location) {
|
|
||||||
case "disk":
|
|
||||||
return this.diskStorage;
|
|
||||||
case "memory":
|
|
||||||
return this.memoryStorage;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { AccountInfo, AccountService } from "../../../auth/abstractions/account.
|
|||||||
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
import { AuthenticationStatus } from "../../../auth/enums/authentication-status";
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import { StateDefinition } from "../state-definition";
|
import { StateDefinition } from "../state-definition";
|
||||||
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
import { UserKeyDefinition } from "../user-key-definition";
|
import { UserKeyDefinition } from "../user-key-definition";
|
||||||
|
|
||||||
import { DefaultActiveUserState } from "./default-active-user-state";
|
import { DefaultActiveUserState } from "./default-active-user-state";
|
||||||
@@ -42,6 +43,7 @@ const testKeyDefinition = new UserKeyDefinition<TestState>(testStateDefinition,
|
|||||||
describe("DefaultActiveUserState", () => {
|
describe("DefaultActiveUserState", () => {
|
||||||
const accountService = mock<AccountService>();
|
const accountService = mock<AccountService>();
|
||||||
let diskStorageService: FakeStorageService;
|
let diskStorageService: FakeStorageService;
|
||||||
|
const stateEventRegistrarService = mock<StateEventRegistrarService>();
|
||||||
let activeAccountSubject: BehaviorSubject<{ id: UserId } & AccountInfo>;
|
let activeAccountSubject: BehaviorSubject<{ id: UserId } & AccountInfo>;
|
||||||
let userState: DefaultActiveUserState<TestState>;
|
let userState: DefaultActiveUserState<TestState>;
|
||||||
|
|
||||||
@@ -50,7 +52,12 @@ describe("DefaultActiveUserState", () => {
|
|||||||
accountService.activeAccount$ = activeAccountSubject;
|
accountService.activeAccount$ = activeAccountSubject;
|
||||||
|
|
||||||
diskStorageService = new FakeStorageService();
|
diskStorageService = new FakeStorageService();
|
||||||
userState = new DefaultActiveUserState(testKeyDefinition, accountService, diskStorageService);
|
userState = new DefaultActiveUserState(
|
||||||
|
testKeyDefinition,
|
||||||
|
accountService,
|
||||||
|
diskStorageService,
|
||||||
|
stateEventRegistrarService,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeUserId = (id: string) => {
|
const makeUserId = (id: string) => {
|
||||||
@@ -391,6 +398,48 @@ describe("DefaultActiveUserState", () => {
|
|||||||
"No active user at this time.",
|
"No active user at this time.",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([null, undefined])(
|
||||||
|
"should register user key definition when state transitions from null-ish (%s) to non-null",
|
||||||
|
async (startingValue: TestState | null) => {
|
||||||
|
diskStorageService.internalUpdateStore({
|
||||||
|
"user_00000000-0000-1000-a000-000000000001_fake_fake": startingValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
await userState.update(() => ({ array: ["one"], date: new Date() }));
|
||||||
|
|
||||||
|
expect(stateEventRegistrarService.registerEvents).toHaveBeenCalledWith(testKeyDefinition);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("should not register user key definition when state has preexisting value", async () => {
|
||||||
|
diskStorageService.internalUpdateStore({
|
||||||
|
"user_00000000-0000-1000-a000-000000000001_fake_fake": {
|
||||||
|
date: new Date(2019, 1),
|
||||||
|
array: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await userState.update(() => ({ array: ["one"], date: new Date() }));
|
||||||
|
|
||||||
|
expect(stateEventRegistrarService.registerEvents).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([null, undefined])(
|
||||||
|
"should not register user key definition when setting value to null-ish (%s) value",
|
||||||
|
async (updatedValue: TestState | null) => {
|
||||||
|
diskStorageService.internalUpdateStore({
|
||||||
|
"user_00000000-0000-1000-a000-000000000001_fake_fake": {
|
||||||
|
date: new Date(2019, 1),
|
||||||
|
array: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await userState.update(() => updatedValue);
|
||||||
|
|
||||||
|
expect(stateEventRegistrarService.registerEvents).not.toHaveBeenCalled();
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("update races", () => {
|
describe("update races", () => {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "../../abstractions/storage.service";
|
} from "../../abstractions/storage.service";
|
||||||
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
|
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
|
||||||
import { UserKeyDefinition } from "../user-key-definition";
|
import { UserKeyDefinition } from "../user-key-definition";
|
||||||
import { ActiveUserState, CombinedState, activeMarker } from "../user-state";
|
import { ActiveUserState, CombinedState, activeMarker } from "../user-state";
|
||||||
@@ -42,6 +43,7 @@ export class DefaultActiveUserState<T> implements ActiveUserState<T> {
|
|||||||
protected keyDefinition: UserKeyDefinition<T>,
|
protected keyDefinition: UserKeyDefinition<T>,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private chosenStorageLocation: AbstractStorageService & ObservableStorageService,
|
private chosenStorageLocation: AbstractStorageService & ObservableStorageService,
|
||||||
|
private stateEventRegistrarService: StateEventRegistrarService,
|
||||||
) {
|
) {
|
||||||
this.activeUserId$ = this.accountService.activeAccount$.pipe(
|
this.activeUserId$ = this.accountService.activeAccount$.pipe(
|
||||||
// We only care about the UserId but we do want to know about no user as well.
|
// We only care about the UserId but we do want to know about no user as well.
|
||||||
@@ -150,6 +152,11 @@ export class DefaultActiveUserState<T> implements ActiveUserState<T> {
|
|||||||
|
|
||||||
const newState = configureState(currentState, combinedDependencies);
|
const newState = configureState(currentState, combinedDependencies);
|
||||||
await this.saveToStorage(key, newState);
|
await this.saveToStorage(key, newState);
|
||||||
|
if (newState != null && currentState == null) {
|
||||||
|
// Only register this state as something clearable on the first time it saves something
|
||||||
|
// worth deleting. This is helpful in making sure there is less of a race to adding events.
|
||||||
|
await this.stateEventRegistrarService.registerEvents(this.keyDefinition);
|
||||||
|
}
|
||||||
return [userId, newState];
|
return [userId, newState];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,21 @@
|
|||||||
import {
|
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
|
||||||
ObservableStorageService,
|
|
||||||
} from "../../abstractions/storage.service";
|
|
||||||
import { GlobalState } from "../global-state";
|
import { GlobalState } from "../global-state";
|
||||||
import { GlobalStateProvider } from "../global-state.provider";
|
import { GlobalStateProvider } from "../global-state.provider";
|
||||||
import { KeyDefinition } from "../key-definition";
|
import { KeyDefinition } from "../key-definition";
|
||||||
import { StateDefinition } from "../state-definition";
|
|
||||||
|
|
||||||
import { DefaultGlobalState } from "./default-global-state";
|
import { DefaultGlobalState } from "./default-global-state";
|
||||||
|
|
||||||
export class DefaultGlobalStateProvider implements GlobalStateProvider {
|
export class DefaultGlobalStateProvider implements GlobalStateProvider {
|
||||||
private globalStateCache: Record<string, GlobalState<unknown>> = {};
|
private globalStateCache: Record<string, GlobalState<unknown>> = {};
|
||||||
|
|
||||||
constructor(
|
constructor(private storageServiceProvider: StorageServiceProvider) {}
|
||||||
protected readonly memoryStorage: AbstractMemoryStorageService & ObservableStorageService,
|
|
||||||
protected readonly diskStorage: AbstractStorageService & ObservableStorageService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
get<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> {
|
get<T>(keyDefinition: KeyDefinition<T>): GlobalState<T> {
|
||||||
const cacheKey = this.buildCacheKey(keyDefinition);
|
const [location, storageService] = this.storageServiceProvider.get(
|
||||||
|
keyDefinition.stateDefinition.defaultStorageLocation,
|
||||||
|
keyDefinition.stateDefinition.storageLocationOverrides,
|
||||||
|
);
|
||||||
|
const cacheKey = this.buildCacheKey(location, keyDefinition);
|
||||||
const existingGlobalState = this.globalStateCache[cacheKey];
|
const existingGlobalState = this.globalStateCache[cacheKey];
|
||||||
if (existingGlobalState != null) {
|
if (existingGlobalState != null) {
|
||||||
// The cast into the actual generic is safe because of rules around key definitions
|
// The cast into the actual generic is safe because of rules around key definitions
|
||||||
@@ -27,30 +23,13 @@ export class DefaultGlobalStateProvider implements GlobalStateProvider {
|
|||||||
return existingGlobalState as DefaultGlobalState<T>;
|
return existingGlobalState as DefaultGlobalState<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newGlobalState = new DefaultGlobalState<T>(
|
const newGlobalState = new DefaultGlobalState<T>(keyDefinition, storageService);
|
||||||
keyDefinition,
|
|
||||||
this.getLocation(keyDefinition.stateDefinition),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.globalStateCache[cacheKey] = newGlobalState;
|
this.globalStateCache[cacheKey] = newGlobalState;
|
||||||
return newGlobalState;
|
return newGlobalState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildCacheKey(keyDefinition: KeyDefinition<unknown>) {
|
private buildCacheKey(location: string, keyDefinition: KeyDefinition<unknown>) {
|
||||||
return `${this.getLocationString(keyDefinition)}_${keyDefinition.fullName}`;
|
return `${location}_${keyDefinition.fullName}`;
|
||||||
}
|
|
||||||
|
|
||||||
protected getLocationString(keyDefinition: KeyDefinition<unknown>): string {
|
|
||||||
return keyDefinition.stateDefinition.defaultStorageLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getLocation(stateDefinition: StateDefinition) {
|
|
||||||
const location = stateDefinition.defaultStorageLocation;
|
|
||||||
switch (location) {
|
|
||||||
case "disk":
|
|
||||||
return this.diskStorage;
|
|
||||||
case "memory":
|
|
||||||
return this.memoryStorage;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import {
|
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
|
||||||
ObservableStorageService,
|
|
||||||
} from "../../abstractions/storage.service";
|
|
||||||
import { KeyDefinition } from "../key-definition";
|
import { KeyDefinition } from "../key-definition";
|
||||||
import { StateDefinition } from "../state-definition";
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
import { UserKeyDefinition, isUserKeyDefinition } from "../user-key-definition";
|
import { UserKeyDefinition, isUserKeyDefinition } from "../user-key-definition";
|
||||||
import { SingleUserState } from "../user-state";
|
import { SingleUserState } from "../user-state";
|
||||||
import { SingleUserStateProvider } from "../user-state.provider";
|
import { SingleUserStateProvider } from "../user-state.provider";
|
||||||
@@ -16,8 +12,8 @@ export class DefaultSingleUserStateProvider implements SingleUserStateProvider {
|
|||||||
private cache: Record<string, SingleUserState<unknown>> = {};
|
private cache: Record<string, SingleUserState<unknown>> = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly memoryStorage: AbstractMemoryStorageService & ObservableStorageService,
|
private readonly storageServiceProvider: StorageServiceProvider,
|
||||||
protected readonly diskStorage: AbstractStorageService & ObservableStorageService,
|
private readonly stateEventRegistrarService: StateEventRegistrarService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get<T>(
|
get<T>(
|
||||||
@@ -27,7 +23,11 @@ export class DefaultSingleUserStateProvider implements SingleUserStateProvider {
|
|||||||
if (!isUserKeyDefinition(keyDefinition)) {
|
if (!isUserKeyDefinition(keyDefinition)) {
|
||||||
keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition);
|
keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition);
|
||||||
}
|
}
|
||||||
const cacheKey = this.buildCacheKey(userId, keyDefinition);
|
const [location, storageService] = this.storageServiceProvider.get(
|
||||||
|
keyDefinition.stateDefinition.defaultStorageLocation,
|
||||||
|
keyDefinition.stateDefinition.storageLocationOverrides,
|
||||||
|
);
|
||||||
|
const cacheKey = this.buildCacheKey(location, userId, keyDefinition);
|
||||||
const existingUserState = this.cache[cacheKey];
|
const existingUserState = this.cache[cacheKey];
|
||||||
if (existingUserState != null) {
|
if (existingUserState != null) {
|
||||||
// I have to cast out of the unknown generic but this should be safe if rules
|
// I have to cast out of the unknown generic but this should be safe if rules
|
||||||
@@ -35,38 +35,21 @@ export class DefaultSingleUserStateProvider implements SingleUserStateProvider {
|
|||||||
return existingUserState as SingleUserState<T>;
|
return existingUserState as SingleUserState<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUserState = this.buildSingleUserState(userId, keyDefinition);
|
const newUserState = new DefaultSingleUserState<T>(
|
||||||
|
userId,
|
||||||
|
keyDefinition,
|
||||||
|
storageService,
|
||||||
|
this.stateEventRegistrarService,
|
||||||
|
);
|
||||||
this.cache[cacheKey] = newUserState;
|
this.cache[cacheKey] = newUserState;
|
||||||
return newUserState;
|
return newUserState;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildCacheKey(userId: UserId, keyDefinition: UserKeyDefinition<unknown>) {
|
private buildCacheKey(
|
||||||
return `${this.getLocationString(keyDefinition)}_${keyDefinition.fullName}_${userId}`;
|
location: string,
|
||||||
}
|
|
||||||
|
|
||||||
protected buildSingleUserState<T>(
|
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
keyDefinition: UserKeyDefinition<T>,
|
keyDefinition: UserKeyDefinition<unknown>,
|
||||||
): SingleUserState<T> {
|
) {
|
||||||
return new DefaultSingleUserState<T>(
|
return `${location}_${keyDefinition.fullName}_${userId}`;
|
||||||
userId,
|
|
||||||
keyDefinition,
|
|
||||||
this.getLocation(keyDefinition.stateDefinition),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getLocationString(keyDefinition: UserKeyDefinition<unknown>): string {
|
|
||||||
return keyDefinition.stateDefinition.defaultStorageLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getLocation(stateDefinition: StateDefinition) {
|
|
||||||
// The default implementations don't support the client overrides
|
|
||||||
// it is up to the client to extend this class and add that support
|
|
||||||
switch (stateDefinition.defaultStorageLocation) {
|
|
||||||
case "disk":
|
|
||||||
return this.diskStorage;
|
|
||||||
case "memory":
|
|
||||||
return this.memoryStorage;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* @jest-environment ../shared/test.environment.ts
|
* @jest-environment ../shared/test.environment.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { mock } from "jest-mock-extended";
|
||||||
import { firstValueFrom, of } from "rxjs";
|
import { firstValueFrom, of } from "rxjs";
|
||||||
import { Jsonify } from "type-fest";
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
|||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
import { Utils } from "../../misc/utils";
|
import { Utils } from "../../misc/utils";
|
||||||
import { StateDefinition } from "../state-definition";
|
import { StateDefinition } from "../state-definition";
|
||||||
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
import { UserKeyDefinition } from "../user-key-definition";
|
import { UserKeyDefinition } from "../user-key-definition";
|
||||||
|
|
||||||
import { DefaultSingleUserState } from "./default-single-user-state";
|
import { DefaultSingleUserState } from "./default-single-user-state";
|
||||||
@@ -42,11 +44,17 @@ const userKey = testKeyDefinition.buildKey(userId);
|
|||||||
describe("DefaultSingleUserState", () => {
|
describe("DefaultSingleUserState", () => {
|
||||||
let diskStorageService: FakeStorageService;
|
let diskStorageService: FakeStorageService;
|
||||||
let userState: DefaultSingleUserState<TestState>;
|
let userState: DefaultSingleUserState<TestState>;
|
||||||
|
const stateEventRegistrarService = mock<StateEventRegistrarService>();
|
||||||
const newData = { date: new Date() };
|
const newData = { date: new Date() };
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
diskStorageService = new FakeStorageService();
|
diskStorageService = new FakeStorageService();
|
||||||
userState = new DefaultSingleUserState(userId, testKeyDefinition, diskStorageService);
|
userState = new DefaultSingleUserState(
|
||||||
|
userId,
|
||||||
|
testKeyDefinition,
|
||||||
|
diskStorageService,
|
||||||
|
stateEventRegistrarService,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -255,6 +263,49 @@ describe("DefaultSingleUserState", () => {
|
|||||||
expect(emissions).toHaveLength(2);
|
expect(emissions).toHaveLength(2);
|
||||||
expect(emissions).toEqual(expect.arrayContaining([initialState, newState]));
|
expect(emissions).toEqual(expect.arrayContaining([initialState, newState]));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([null, undefined])(
|
||||||
|
"should register user key definition when state transitions from null-ish (%s) to non-null",
|
||||||
|
async (startingValue: TestState | null) => {
|
||||||
|
const initialState: Record<string, TestState> = {};
|
||||||
|
initialState[userKey] = startingValue;
|
||||||
|
|
||||||
|
diskStorageService.internalUpdateStore(initialState);
|
||||||
|
|
||||||
|
await userState.update(() => ({ array: ["one"], date: new Date() }));
|
||||||
|
|
||||||
|
expect(stateEventRegistrarService.registerEvents).toHaveBeenCalledWith(testKeyDefinition);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("should not register user key definition when state has preexisting value", async () => {
|
||||||
|
const initialState: Record<string, TestState> = {};
|
||||||
|
initialState[userKey] = {
|
||||||
|
date: new Date(2019, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
diskStorageService.internalUpdateStore(initialState);
|
||||||
|
|
||||||
|
await userState.update(() => ({ array: ["one"], date: new Date() }));
|
||||||
|
|
||||||
|
expect(stateEventRegistrarService.registerEvents).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([null, undefined])(
|
||||||
|
"should not register user key definition when setting value to null-ish (%s) value",
|
||||||
|
async (updatedValue: TestState | null) => {
|
||||||
|
const initialState: Record<string, TestState> = {};
|
||||||
|
initialState[userKey] = {
|
||||||
|
date: new Date(2019, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
diskStorageService.internalUpdateStore(initialState);
|
||||||
|
|
||||||
|
await userState.update(() => updatedValue);
|
||||||
|
|
||||||
|
expect(stateEventRegistrarService.registerEvents).not.toHaveBeenCalled();
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("update races", () => {
|
describe("update races", () => {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "../../abstractions/storage.service";
|
} from "../../abstractions/storage.service";
|
||||||
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
|
import { StateUpdateOptions, populateOptionsWithDefault } from "../state-update-options";
|
||||||
import { UserKeyDefinition } from "../user-key-definition";
|
import { UserKeyDefinition } from "../user-key-definition";
|
||||||
import { CombinedState, SingleUserState } from "../user-state";
|
import { CombinedState, SingleUserState } from "../user-state";
|
||||||
@@ -35,6 +36,7 @@ export class DefaultSingleUserState<T> implements SingleUserState<T> {
|
|||||||
readonly userId: UserId,
|
readonly userId: UserId,
|
||||||
private keyDefinition: UserKeyDefinition<T>,
|
private keyDefinition: UserKeyDefinition<T>,
|
||||||
private chosenLocation: AbstractStorageService & ObservableStorageService,
|
private chosenLocation: AbstractStorageService & ObservableStorageService,
|
||||||
|
private stateEventRegistrarService: StateEventRegistrarService,
|
||||||
) {
|
) {
|
||||||
this.storageKey = this.keyDefinition.buildKey(this.userId);
|
this.storageKey = this.keyDefinition.buildKey(this.userId);
|
||||||
const initialStorageGet$ = defer(() => {
|
const initialStorageGet$ = defer(() => {
|
||||||
@@ -100,6 +102,11 @@ export class DefaultSingleUserState<T> implements SingleUserState<T> {
|
|||||||
|
|
||||||
const newState = configureState(currentState, combinedDependencies);
|
const newState = configureState(currentState, combinedDependencies);
|
||||||
await this.chosenLocation.save(this.storageKey, newState);
|
await this.chosenLocation.save(this.storageKey, newState);
|
||||||
|
if (newState != null && currentState == null) {
|
||||||
|
// Only register this state as something clearable on the first time it saves something
|
||||||
|
// worth deleting. This is helpful in making sure there is less of a race to adding events.
|
||||||
|
await this.stateEventRegistrarService.registerEvents(this.keyDefinition);
|
||||||
|
}
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
import { mockAccountServiceWith } from "../../../../spec/fake-account-service";
|
import { mockAccountServiceWith } from "../../../../spec/fake-account-service";
|
||||||
import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
import { FakeStorageService } from "../../../../spec/fake-storage.service";
|
||||||
import { UserId } from "../../../types/guid";
|
import { UserId } from "../../../types/guid";
|
||||||
|
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
||||||
import { KeyDefinition } from "../key-definition";
|
import { KeyDefinition } from "../key-definition";
|
||||||
import { StateDefinition } from "../state-definition";
|
import { StateDefinition } from "../state-definition";
|
||||||
|
import { StateEventRegistrarService } from "../state-event-registrar.service";
|
||||||
|
|
||||||
import { DefaultActiveUserState } from "./default-active-user-state";
|
import { DefaultActiveUserState } from "./default-active-user-state";
|
||||||
import { DefaultActiveUserStateProvider } from "./default-active-user-state.provider";
|
import { DefaultActiveUserStateProvider } from "./default-active-user-state.provider";
|
||||||
@@ -12,6 +16,9 @@ import { DefaultSingleUserState } from "./default-single-user-state";
|
|||||||
import { DefaultSingleUserStateProvider } from "./default-single-user-state.provider";
|
import { DefaultSingleUserStateProvider } from "./default-single-user-state.provider";
|
||||||
|
|
||||||
describe("Specific State Providers", () => {
|
describe("Specific State Providers", () => {
|
||||||
|
const storageServiceProvider = mock<StorageServiceProvider>();
|
||||||
|
const stateEventRegistrarService = mock<StateEventRegistrarService>();
|
||||||
|
|
||||||
let singleSut: DefaultSingleUserStateProvider;
|
let singleSut: DefaultSingleUserStateProvider;
|
||||||
let activeSut: DefaultActiveUserStateProvider;
|
let activeSut: DefaultActiveUserStateProvider;
|
||||||
let globalSut: DefaultGlobalStateProvider;
|
let globalSut: DefaultGlobalStateProvider;
|
||||||
@@ -19,19 +26,20 @@ describe("Specific State Providers", () => {
|
|||||||
const fakeUser1 = "00000000-0000-1000-a000-000000000001" as UserId;
|
const fakeUser1 = "00000000-0000-1000-a000-000000000001" as UserId;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
storageServiceProvider.get.mockImplementation((location) => {
|
||||||
|
return [location, new FakeStorageService()];
|
||||||
|
});
|
||||||
|
|
||||||
singleSut = new DefaultSingleUserStateProvider(
|
singleSut = new DefaultSingleUserStateProvider(
|
||||||
new FakeStorageService() as any,
|
storageServiceProvider,
|
||||||
new FakeStorageService(),
|
stateEventRegistrarService,
|
||||||
);
|
);
|
||||||
activeSut = new DefaultActiveUserStateProvider(
|
activeSut = new DefaultActiveUserStateProvider(
|
||||||
mockAccountServiceWith(null),
|
mockAccountServiceWith(null),
|
||||||
new FakeStorageService() as any,
|
storageServiceProvider,
|
||||||
new FakeStorageService(),
|
stateEventRegistrarService,
|
||||||
);
|
|
||||||
globalSut = new DefaultGlobalStateProvider(
|
|
||||||
new FakeStorageService() as any,
|
|
||||||
new FakeStorageService(),
|
|
||||||
);
|
);
|
||||||
|
globalSut = new DefaultGlobalStateProvider(storageServiceProvider);
|
||||||
});
|
});
|
||||||
|
|
||||||
const fakeDiskStateDefinition = new StateDefinition("fake", "disk");
|
const fakeDiskStateDefinition = new StateDefinition("fake", "disk");
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { MessagingService } from "../../platform/abstractions/messaging.service"
|
|||||||
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
import { StateService } from "../../platform/abstractions/state.service";
|
||||||
import { Account } from "../../platform/models/domain/account";
|
import { Account } from "../../platform/models/domain/account";
|
||||||
|
import { StateEventRunnerService } from "../../platform/state";
|
||||||
import { CipherService } from "../../vault/abstractions/cipher.service";
|
import { CipherService } from "../../vault/abstractions/cipher.service";
|
||||||
import { CollectionService } from "../../vault/abstractions/collection.service";
|
import { CollectionService } from "../../vault/abstractions/collection.service";
|
||||||
import { FolderService } from "../../vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "../../vault/abstractions/folder/folder.service.abstraction";
|
||||||
@@ -28,6 +29,7 @@ describe("VaultTimeoutService", () => {
|
|||||||
let stateService: MockProxy<StateService>;
|
let stateService: MockProxy<StateService>;
|
||||||
let authService: MockProxy<AuthService>;
|
let authService: MockProxy<AuthService>;
|
||||||
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
let vaultTimeoutSettingsService: MockProxy<VaultTimeoutSettingsService>;
|
||||||
|
let stateEventRunnerService: MockProxy<StateEventRunnerService>;
|
||||||
let lockedCallback: jest.Mock<Promise<void>, [userId: string]>;
|
let lockedCallback: jest.Mock<Promise<void>, [userId: string]>;
|
||||||
let loggedOutCallback: jest.Mock<Promise<void>, [expired: boolean, userId?: string]>;
|
let loggedOutCallback: jest.Mock<Promise<void>, [expired: boolean, userId?: string]>;
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ describe("VaultTimeoutService", () => {
|
|||||||
stateService = mock();
|
stateService = mock();
|
||||||
authService = mock();
|
authService = mock();
|
||||||
vaultTimeoutSettingsService = mock();
|
vaultTimeoutSettingsService = mock();
|
||||||
|
stateEventRunnerService = mock();
|
||||||
|
|
||||||
lockedCallback = jest.fn();
|
lockedCallback = jest.fn();
|
||||||
loggedOutCallback = jest.fn();
|
loggedOutCallback = jest.fn();
|
||||||
@@ -73,6 +76,7 @@ describe("VaultTimeoutService", () => {
|
|||||||
stateService,
|
stateService,
|
||||||
authService,
|
authService,
|
||||||
vaultTimeoutSettingsService,
|
vaultTimeoutSettingsService,
|
||||||
|
stateEventRunnerService,
|
||||||
lockedCallback,
|
lockedCallback,
|
||||||
loggedOutCallback,
|
loggedOutCallback,
|
||||||
);
|
);
|
||||||
@@ -103,7 +107,8 @@ describe("VaultTimeoutService", () => {
|
|||||||
return Promise.resolve(accounts[userId]?.authStatus);
|
return Promise.resolve(accounts[userId]?.authStatus);
|
||||||
});
|
});
|
||||||
stateService.getIsAuthenticated.mockImplementation((options) => {
|
stateService.getIsAuthenticated.mockImplementation((options) => {
|
||||||
return Promise.resolve(accounts[options.userId]?.isAuthenticated);
|
// Just like actual state service, if no userId is given fallback to active userId
|
||||||
|
return Promise.resolve(accounts[options.userId ?? globalSetups?.userId]?.isAuthenticated);
|
||||||
});
|
});
|
||||||
|
|
||||||
vaultTimeoutSettingsService.getVaultTimeout.mockImplementation((userId) => {
|
vaultTimeoutSettingsService.getVaultTimeout.mockImplementation((userId) => {
|
||||||
@@ -337,4 +342,80 @@ describe("VaultTimeoutService", () => {
|
|||||||
expectNoAction("1");
|
expectNoAction("1");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("lock", () => {
|
||||||
|
const setupLock = () => {
|
||||||
|
setupAccounts(
|
||||||
|
{
|
||||||
|
user1: {
|
||||||
|
authStatus: AuthenticationStatus.Unlocked,
|
||||||
|
isAuthenticated: true,
|
||||||
|
},
|
||||||
|
user2: {
|
||||||
|
authStatus: AuthenticationStatus.Unlocked,
|
||||||
|
isAuthenticated: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
userId: "user1",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should call state event runner with currently active user if no user passed into lock", async () => {
|
||||||
|
setupLock();
|
||||||
|
|
||||||
|
await vaultTimeoutService.lock();
|
||||||
|
|
||||||
|
expect(stateEventRunnerService.handleEvent).toHaveBeenCalledWith("lock", "user1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call messaging service locked message if no user passed into lock", async () => {
|
||||||
|
setupLock();
|
||||||
|
|
||||||
|
await vaultTimeoutService.lock();
|
||||||
|
|
||||||
|
// Currently these pass `undefined` (or what they were given) as the userId back
|
||||||
|
// but we could change this to give the user that was locked (active) to these methods
|
||||||
|
// so they don't have to get it their own way, but that is a behavioral change that needs
|
||||||
|
// to be tested.
|
||||||
|
expect(messagingService.send).toHaveBeenCalledWith("locked", { userId: undefined });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call locked callback if no user passed into lock", async () => {
|
||||||
|
setupLock();
|
||||||
|
|
||||||
|
await vaultTimeoutService.lock();
|
||||||
|
|
||||||
|
// Currently these pass `undefined` (or what they were given) as the userId back
|
||||||
|
// but we could change this to give the user that was locked (active) to these methods
|
||||||
|
// so they don't have to get it their own way, but that is a behavioral change that needs
|
||||||
|
// to be tested.
|
||||||
|
expect(lockedCallback).toHaveBeenCalledWith(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call state event runner with user passed into lock", async () => {
|
||||||
|
setupLock();
|
||||||
|
|
||||||
|
await vaultTimeoutService.lock("user2");
|
||||||
|
|
||||||
|
expect(stateEventRunnerService.handleEvent).toHaveBeenCalledWith("lock", "user2");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call messaging service locked message with user passed into lock", async () => {
|
||||||
|
setupLock();
|
||||||
|
|
||||||
|
await vaultTimeoutService.lock("user2");
|
||||||
|
|
||||||
|
expect(messagingService.send).toHaveBeenCalledWith("locked", { userId: "user2" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call locked callback with user passed into lock", async () => {
|
||||||
|
setupLock();
|
||||||
|
|
||||||
|
await vaultTimeoutService.lock("user2");
|
||||||
|
|
||||||
|
expect(lockedCallback).toHaveBeenCalledWith("user2");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { CryptoService } from "../../platform/abstractions/crypto.service";
|
|||||||
import { MessagingService } from "../../platform/abstractions/messaging.service";
|
import { MessagingService } from "../../platform/abstractions/messaging.service";
|
||||||
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "../../platform/abstractions/state.service";
|
import { StateService } from "../../platform/abstractions/state.service";
|
||||||
|
import { StateEventRunnerService } from "../../platform/state";
|
||||||
|
import { UserId } from "../../types/guid";
|
||||||
import { CipherService } from "../../vault/abstractions/cipher.service";
|
import { CipherService } from "../../vault/abstractions/cipher.service";
|
||||||
import { CollectionService } from "../../vault/abstractions/collection.service";
|
import { CollectionService } from "../../vault/abstractions/collection.service";
|
||||||
import { FolderService } from "../../vault/abstractions/folder/folder.service.abstraction";
|
import { FolderService } from "../../vault/abstractions/folder/folder.service.abstraction";
|
||||||
@@ -29,6 +31,7 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||||
|
private stateEventRunnerService: StateEventRunnerService,
|
||||||
private lockedCallback: (userId?: string) => Promise<void> = null,
|
private lockedCallback: (userId?: string) => Promise<void> = null,
|
||||||
private loggedOutCallback: (expired: boolean, userId?: string) => Promise<void> = null,
|
private loggedOutCallback: (expired: boolean, userId?: string) => Promise<void> = null,
|
||||||
) {}
|
) {}
|
||||||
@@ -81,7 +84,9 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
await this.logOut(userId);
|
await this.logOut(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userId == null || userId === (await this.stateService.getUserId())) {
|
const currentUserId = await this.stateService.getUserId();
|
||||||
|
|
||||||
|
if (userId == null || userId === currentUserId) {
|
||||||
this.searchService.clearIndex();
|
this.searchService.clearIndex();
|
||||||
await this.folderService.clearCache();
|
await this.folderService.clearCache();
|
||||||
await this.collectionService.clearActiveUserCache();
|
await this.collectionService.clearActiveUserCache();
|
||||||
@@ -98,6 +103,11 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
|
|
||||||
await this.cipherService.clearCache(userId);
|
await this.cipherService.clearCache(userId);
|
||||||
|
|
||||||
|
await this.stateEventRunnerService.handleEvent("lock", (userId ?? currentUserId) as UserId);
|
||||||
|
|
||||||
|
// FIXME: We should send the userId of the user that was locked, in the case of this method being passed
|
||||||
|
// undefined then it should give back the currentUserId. Better yet, this method shouldn't take
|
||||||
|
// an undefined userId at all. All receivers need to be checked for how they handle getting undefined.
|
||||||
this.messagingService.send("locked", { userId: userId });
|
this.messagingService.send("locked", { userId: userId });
|
||||||
|
|
||||||
if (this.lockedCallback != null) {
|
if (this.lockedCallback != null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user