Browser MV3: Default store values to session storage (#8844)
* Introduce browser large object storage location. This location is encrypted and serialized to disk in order to allow for storage of uncountable things like vault items that take a significant amount of time to prepare, but are not guaranteed to fit within session storage. however, limit the need to write to disk is a big benefit, so _most_ things are written to storage.session instead, where things specifically flagged as large will be moved to disk-backed memory * Store derived values in large object store for browser * Fix AbstractMemoryStorageService implementation
This commit is contained in:
parent
f829cdd8a7
commit
b5362ca1ce
|
@ -111,7 +111,6 @@ 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 { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service";
|
import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service";
|
||||||
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
||||||
|
@ -226,6 +225,7 @@ import { BackgroundPlatformUtilsService } from "../platform/services/platform-ut
|
||||||
import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
|
import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service";
|
||||||
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
|
import { BackgroundDerivedStateProvider } from "../platform/state/background-derived-state.provider";
|
||||||
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
||||||
|
import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider";
|
||||||
import { ForegroundMemoryStorageService } from "../platform/storage/foreground-memory-storage.service";
|
import { ForegroundMemoryStorageService } from "../platform/storage/foreground-memory-storage.service";
|
||||||
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
|
import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging";
|
||||||
import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
|
import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service";
|
||||||
|
@ -246,6 +246,8 @@ export default class MainBackground {
|
||||||
secureStorageService: AbstractStorageService;
|
secureStorageService: AbstractStorageService;
|
||||||
memoryStorageService: AbstractMemoryStorageService;
|
memoryStorageService: AbstractMemoryStorageService;
|
||||||
memoryStorageForStateProviders: AbstractMemoryStorageService & ObservableStorageService;
|
memoryStorageForStateProviders: AbstractMemoryStorageService & ObservableStorageService;
|
||||||
|
largeObjectMemoryStorageForStateProviders: AbstractMemoryStorageService &
|
||||||
|
ObservableStorageService;
|
||||||
i18nService: I18nServiceAbstraction;
|
i18nService: I18nServiceAbstraction;
|
||||||
platformUtilsService: PlatformUtilsServiceAbstraction;
|
platformUtilsService: PlatformUtilsServiceAbstraction;
|
||||||
logService: LogServiceAbstraction;
|
logService: LogServiceAbstraction;
|
||||||
|
@ -424,12 +426,16 @@ export default class MainBackground {
|
||||||
? mv3MemoryStorageCreator("stateService")
|
? mv3MemoryStorageCreator("stateService")
|
||||||
: new MemoryStorageService();
|
: new MemoryStorageService();
|
||||||
this.memoryStorageForStateProviders = BrowserApi.isManifestVersion(3)
|
this.memoryStorageForStateProviders = BrowserApi.isManifestVersion(3)
|
||||||
? mv3MemoryStorageCreator("stateProviders")
|
? new BrowserMemoryStorageService() // mv3 stores to storage.session
|
||||||
: new BackgroundMemoryStorageService();
|
: new BackgroundMemoryStorageService(); // mv2 stores to memory
|
||||||
|
this.largeObjectMemoryStorageForStateProviders = BrowserApi.isManifestVersion(3)
|
||||||
|
? mv3MemoryStorageCreator("stateProviders") // mv3 stores to local-backed session storage
|
||||||
|
: this.memoryStorageForStateProviders; // mv2 stores to the same location
|
||||||
|
|
||||||
const storageServiceProvider = new StorageServiceProvider(
|
const storageServiceProvider = new BrowserStorageServiceProvider(
|
||||||
this.storageService,
|
this.storageService,
|
||||||
this.memoryStorageForStateProviders,
|
this.memoryStorageForStateProviders,
|
||||||
|
this.largeObjectMemoryStorageForStateProviders,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
this.globalStateProvider = new DefaultGlobalStateProvider(storageServiceProvider);
|
||||||
|
@ -466,9 +472,7 @@ export default class MainBackground {
|
||||||
this.accountService,
|
this.accountService,
|
||||||
this.singleUserStateProvider,
|
this.singleUserStateProvider,
|
||||||
);
|
);
|
||||||
this.derivedStateProvider = new BackgroundDerivedStateProvider(
|
this.derivedStateProvider = new BackgroundDerivedStateProvider(storageServiceProvider);
|
||||||
this.memoryStorageForStateProviders,
|
|
||||||
);
|
|
||||||
this.stateProvider = new DefaultStateProvider(
|
this.stateProvider = new DefaultStateProvider(
|
||||||
this.activeUserStateProvider,
|
this.activeUserStateProvider,
|
||||||
this.singleUserStateProvider,
|
this.singleUserStateProvider,
|
||||||
|
|
|
@ -4,14 +4,14 @@ import { BackgroundDerivedStateProvider } from "../../state/background-derived-s
|
||||||
|
|
||||||
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
import { CachedServices, FactoryOptions, factory } from "./factory-options";
|
||||||
import {
|
import {
|
||||||
MemoryStorageServiceInitOptions,
|
StorageServiceProviderInitOptions,
|
||||||
observableMemoryStorageServiceFactory,
|
storageServiceProviderFactory,
|
||||||
} from "./storage-service.factory";
|
} from "./storage-service-provider.factory";
|
||||||
|
|
||||||
type DerivedStateProviderFactoryOptions = FactoryOptions;
|
type DerivedStateProviderFactoryOptions = FactoryOptions;
|
||||||
|
|
||||||
export type DerivedStateProviderInitOptions = DerivedStateProviderFactoryOptions &
|
export type DerivedStateProviderInitOptions = DerivedStateProviderFactoryOptions &
|
||||||
MemoryStorageServiceInitOptions;
|
StorageServiceProviderInitOptions;
|
||||||
|
|
||||||
export async function derivedStateProviderFactory(
|
export async function derivedStateProviderFactory(
|
||||||
cache: { derivedStateProvider?: DerivedStateProvider } & CachedServices,
|
cache: { derivedStateProvider?: DerivedStateProvider } & CachedServices,
|
||||||
|
@ -22,6 +22,6 @@ export async function derivedStateProviderFactory(
|
||||||
"derivedStateProvider",
|
"derivedStateProvider",
|
||||||
opts,
|
opts,
|
||||||
async () =>
|
async () =>
|
||||||
new BackgroundDerivedStateProvider(await observableMemoryStorageServiceFactory(cache, opts)),
|
new BackgroundDerivedStateProvider(await storageServiceProviderFactory(cache, opts)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
|
import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
|
||||||
import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service";
|
import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service";
|
||||||
|
|
||||||
export default class BrowserMemoryStorageService extends AbstractChromeStorageService {
|
export default class BrowserMemoryStorageService
|
||||||
|
extends AbstractChromeStorageService
|
||||||
|
implements AbstractMemoryStorageService
|
||||||
|
{
|
||||||
constructor() {
|
constructor() {
|
||||||
super(chrome.storage.session);
|
super(chrome.storage.session);
|
||||||
}
|
}
|
||||||
|
type = "MemoryStorageService" as const;
|
||||||
|
getBypassCache<T>(key: string): Promise<T> {
|
||||||
|
return this.get(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AbstractStorageService,
|
||||||
|
ObservableStorageService,
|
||||||
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state";
|
import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state";
|
||||||
// eslint-disable-next-line import/no-restricted-paths -- extending this class for this client
|
// eslint-disable-next-line import/no-restricted-paths -- extending this class for this client
|
||||||
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
|
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
|
||||||
|
@ -12,11 +16,14 @@ export class BackgroundDerivedStateProvider extends DefaultDerivedStateProvider
|
||||||
parentState$: Observable<TFrom>,
|
parentState$: Observable<TFrom>,
|
||||||
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
||||||
dependencies: TDeps,
|
dependencies: TDeps,
|
||||||
|
storageLocation: [string, AbstractStorageService & ObservableStorageService],
|
||||||
): DerivedState<TTo> {
|
): DerivedState<TTo> {
|
||||||
|
const [cacheKey, storageService] = storageLocation;
|
||||||
return new BackgroundDerivedState(
|
return new BackgroundDerivedState(
|
||||||
parentState$,
|
parentState$,
|
||||||
deriveDefinition,
|
deriveDefinition,
|
||||||
this.memoryStorage,
|
storageService,
|
||||||
|
cacheKey,
|
||||||
dependencies,
|
dependencies,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,10 @@ export class BackgroundDerivedState<
|
||||||
parentState$: Observable<TFrom>,
|
parentState$: Observable<TFrom>,
|
||||||
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
||||||
memoryStorage: AbstractStorageService & ObservableStorageService,
|
memoryStorage: AbstractStorageService & ObservableStorageService,
|
||||||
|
portName: string,
|
||||||
dependencies: TDeps,
|
dependencies: TDeps,
|
||||||
) {
|
) {
|
||||||
super(parentState$, deriveDefinition, memoryStorage, dependencies);
|
super(parentState$, deriveDefinition, memoryStorage, dependencies);
|
||||||
const portName = deriveDefinition.buildCacheKey();
|
|
||||||
|
|
||||||
// listen for foreground derived states to connect
|
// listen for foreground derived states to connect
|
||||||
BrowserApi.addListener(chrome.runtime.onConnect, (port) => {
|
BrowserApi.addListener(chrome.runtime.onConnect, (port) => {
|
||||||
|
|
|
@ -38,14 +38,21 @@ describe("foreground background derived state interactions", () => {
|
||||||
let memoryStorage: FakeStorageService;
|
let memoryStorage: FakeStorageService;
|
||||||
const initialParent = "2020-01-01";
|
const initialParent = "2020-01-01";
|
||||||
const ngZone = mock<NgZone>();
|
const ngZone = mock<NgZone>();
|
||||||
|
const portName = "testPort";
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockPorts();
|
mockPorts();
|
||||||
parentState$ = new Subject<string>();
|
parentState$ = new Subject<string>();
|
||||||
memoryStorage = new FakeStorageService();
|
memoryStorage = new FakeStorageService();
|
||||||
|
|
||||||
background = new BackgroundDerivedState(parentState$, deriveDefinition, memoryStorage, {});
|
background = new BackgroundDerivedState(
|
||||||
foreground = new ForegroundDerivedState(deriveDefinition, memoryStorage, ngZone);
|
parentState$,
|
||||||
|
deriveDefinition,
|
||||||
|
memoryStorage,
|
||||||
|
portName,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
foreground = new ForegroundDerivedState(deriveDefinition, memoryStorage, portName, ngZone);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -65,7 +72,12 @@ describe("foreground background derived state interactions", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should initialize a late-connected foreground", async () => {
|
it("should initialize a late-connected foreground", async () => {
|
||||||
const newForeground = new ForegroundDerivedState(deriveDefinition, memoryStorage, ngZone);
|
const newForeground = new ForegroundDerivedState(
|
||||||
|
deriveDefinition,
|
||||||
|
memoryStorage,
|
||||||
|
portName,
|
||||||
|
ngZone,
|
||||||
|
);
|
||||||
const backgroundEmissions = trackEmissions(background.state$);
|
const backgroundEmissions = trackEmissions(background.state$);
|
||||||
parentState$.next(initialParent);
|
parentState$.next(initialParent);
|
||||||
await awaitAsync();
|
await awaitAsync();
|
||||||
|
@ -82,8 +94,6 @@ describe("foreground background derived state interactions", () => {
|
||||||
const dateString = "2020-12-12";
|
const dateString = "2020-12-12";
|
||||||
const emissions = trackEmissions(background.state$);
|
const emissions = trackEmissions(background.state$);
|
||||||
|
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
await foreground.forceValue(new Date(dateString));
|
await foreground.forceValue(new Date(dateString));
|
||||||
await awaitAsync();
|
await awaitAsync();
|
||||||
|
|
||||||
|
@ -99,9 +109,7 @@ describe("foreground background derived state interactions", () => {
|
||||||
|
|
||||||
expect(foreground["port"]).toBeDefined();
|
expect(foreground["port"]).toBeDefined();
|
||||||
const newDate = new Date();
|
const newDate = new Date();
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await foreground.forceValue(newDate);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
foreground.forceValue(newDate);
|
|
||||||
await awaitAsync();
|
await awaitAsync();
|
||||||
|
|
||||||
expect(connectMock.mock.calls.length).toBe(initialConnectCalls);
|
expect(connectMock.mock.calls.length).toBe(initialConnectCalls);
|
||||||
|
@ -114,9 +122,7 @@ describe("foreground background derived state interactions", () => {
|
||||||
|
|
||||||
expect(foreground["port"]).toBeUndefined();
|
expect(foreground["port"]).toBeUndefined();
|
||||||
const newDate = new Date();
|
const newDate = new Date();
|
||||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
await foreground.forceValue(newDate);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
foreground.forceValue(newDate);
|
|
||||||
await awaitAsync();
|
await awaitAsync();
|
||||||
|
|
||||||
expect(connectMock.mock.calls.length).toBe(initialConnectCalls + 1);
|
expect(connectMock.mock.calls.length).toBe(initialConnectCalls + 1);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state";
|
import { DeriveDefinition, DerivedState } from "@bitwarden/common/platform/state";
|
||||||
// eslint-disable-next-line import/no-restricted-paths -- extending this class for this client
|
// eslint-disable-next-line import/no-restricted-paths -- extending this class for this client
|
||||||
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
|
import { DefaultDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/default-derived-state.provider";
|
||||||
|
@ -14,16 +15,18 @@ import { ForegroundDerivedState } from "./foreground-derived-state";
|
||||||
|
|
||||||
export class ForegroundDerivedStateProvider extends DefaultDerivedStateProvider {
|
export class ForegroundDerivedStateProvider extends DefaultDerivedStateProvider {
|
||||||
constructor(
|
constructor(
|
||||||
memoryStorage: AbstractStorageService & ObservableStorageService,
|
storageServiceProvider: StorageServiceProvider,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
) {
|
) {
|
||||||
super(memoryStorage);
|
super(storageServiceProvider);
|
||||||
}
|
}
|
||||||
override buildDerivedState<TFrom, TTo, TDeps extends DerivedStateDependencies>(
|
override buildDerivedState<TFrom, TTo, TDeps extends DerivedStateDependencies>(
|
||||||
_parentState$: Observable<TFrom>,
|
_parentState$: Observable<TFrom>,
|
||||||
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
||||||
_dependencies: TDeps,
|
_dependencies: TDeps,
|
||||||
|
storageLocation: [string, AbstractStorageService & ObservableStorageService],
|
||||||
): DerivedState<TTo> {
|
): DerivedState<TTo> {
|
||||||
return new ForegroundDerivedState(deriveDefinition, this.memoryStorage, this.ngZone);
|
const [cacheKey, storageService] = storageLocation;
|
||||||
|
return new ForegroundDerivedState(deriveDefinition, storageService, cacheKey, this.ngZone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,13 +33,14 @@ jest.mock("../browser/run-inside-angular.operator", () => {
|
||||||
describe("ForegroundDerivedState", () => {
|
describe("ForegroundDerivedState", () => {
|
||||||
let sut: ForegroundDerivedState<Date>;
|
let sut: ForegroundDerivedState<Date>;
|
||||||
let memoryStorage: FakeStorageService;
|
let memoryStorage: FakeStorageService;
|
||||||
|
const portName = "testPort";
|
||||||
const ngZone = mock<NgZone>();
|
const ngZone = mock<NgZone>();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
memoryStorage = new FakeStorageService();
|
memoryStorage = new FakeStorageService();
|
||||||
memoryStorage.internalUpdateValuesRequireDeserialization(true);
|
memoryStorage.internalUpdateValuesRequireDeserialization(true);
|
||||||
mockPorts();
|
mockPorts();
|
||||||
sut = new ForegroundDerivedState(deriveDefinition, memoryStorage, ngZone);
|
sut = new ForegroundDerivedState(deriveDefinition, memoryStorage, portName, ngZone);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|
|
@ -35,6 +35,7 @@ export class ForegroundDerivedState<TTo> implements DerivedState<TTo> {
|
||||||
constructor(
|
constructor(
|
||||||
private deriveDefinition: DeriveDefinition<unknown, TTo, DerivedStateDependencies>,
|
private deriveDefinition: DeriveDefinition<unknown, TTo, DerivedStateDependencies>,
|
||||||
private memoryStorage: AbstractStorageService & ObservableStorageService,
|
private memoryStorage: AbstractStorageService & ObservableStorageService,
|
||||||
|
private portName: string,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
) {
|
) {
|
||||||
this.storageKey = deriveDefinition.storageKey;
|
this.storageKey = deriveDefinition.storageKey;
|
||||||
|
@ -88,7 +89,7 @@ export class ForegroundDerivedState<TTo> implements DerivedState<TTo> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.port = chrome.runtime.connect({ name: this.deriveDefinition.buildCacheKey() });
|
this.port = chrome.runtime.connect({ name: this.portName });
|
||||||
|
|
||||||
this.backgroundResponses$ = fromChromeEvent(this.port.onMessage).pipe(
|
this.backgroundResponses$ = fromChromeEvent(this.port.onMessage).pipe(
|
||||||
map(([message]) => message as DerivedStateMessage),
|
map(([message]) => message as DerivedStateMessage),
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import {
|
||||||
|
AbstractStorageService,
|
||||||
|
ObservableStorageService,
|
||||||
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
import {
|
||||||
|
PossibleLocation,
|
||||||
|
StorageServiceProvider,
|
||||||
|
} from "@bitwarden/common/platform/services/storage-service.provider";
|
||||||
|
// eslint-disable-next-line import/no-restricted-paths
|
||||||
|
import { ClientLocations } from "@bitwarden/common/platform/state/state-definition";
|
||||||
|
|
||||||
|
export class BrowserStorageServiceProvider extends StorageServiceProvider {
|
||||||
|
constructor(
|
||||||
|
diskStorageService: AbstractStorageService & ObservableStorageService,
|
||||||
|
limitedMemoryStorageService: AbstractStorageService & ObservableStorageService,
|
||||||
|
private largeObjectMemoryStorageService: AbstractStorageService & ObservableStorageService,
|
||||||
|
) {
|
||||||
|
super(diskStorageService, limitedMemoryStorageService);
|
||||||
|
}
|
||||||
|
|
||||||
|
override get(
|
||||||
|
defaultLocation: PossibleLocation,
|
||||||
|
overrides: Partial<ClientLocations>,
|
||||||
|
): [location: PossibleLocation, service: AbstractStorageService & ObservableStorageService] {
|
||||||
|
const location = overrides["browser"] ?? defaultLocation;
|
||||||
|
switch (location) {
|
||||||
|
case "memory-large-object":
|
||||||
|
return ["memory-large-object", this.largeObjectMemoryStorageService];
|
||||||
|
default:
|
||||||
|
// Pass in computed location to super because they could have
|
||||||
|
// override default "disk" with web "memory".
|
||||||
|
return super.get(location, overrides);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,6 +71,7 @@ import { GlobalState } from "@bitwarden/common/platform/models/domain/global-sta
|
||||||
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
|
||||||
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
|
import { ContainerService } from "@bitwarden/common/platform/services/container.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 { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
|
||||||
import {
|
import {
|
||||||
DerivedStateProvider,
|
DerivedStateProvider,
|
||||||
|
@ -108,6 +109,7 @@ import { DefaultBrowserStateService } from "../../platform/services/default-brow
|
||||||
import I18nService from "../../platform/services/i18n.service";
|
import I18nService from "../../platform/services/i18n.service";
|
||||||
import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service";
|
import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service";
|
||||||
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
|
import { ForegroundDerivedStateProvider } from "../../platform/state/foreground-derived-state.provider";
|
||||||
|
import { BrowserStorageServiceProvider } from "../../platform/storage/browser-storage-service.provider";
|
||||||
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
|
import { ForegroundMemoryStorageService } from "../../platform/storage/foreground-memory-storage.service";
|
||||||
import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging";
|
import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging";
|
||||||
import { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service";
|
import { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service";
|
||||||
|
@ -120,6 +122,10 @@ import { InitService } from "./init.service";
|
||||||
import { PopupCloseWarningService } from "./popup-close-warning.service";
|
import { PopupCloseWarningService } from "./popup-close-warning.service";
|
||||||
import { PopupSearchService } from "./popup-search.service";
|
import { PopupSearchService } from "./popup-search.service";
|
||||||
|
|
||||||
|
const OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE = new SafeInjectionToken<
|
||||||
|
AbstractStorageService & ObservableStorageService
|
||||||
|
>("OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE");
|
||||||
|
|
||||||
const needsBackgroundInit = BrowserPopupUtils.backgroundInitializationRequired();
|
const needsBackgroundInit = BrowserPopupUtils.backgroundInitializationRequired();
|
||||||
const isPrivateMode = BrowserPopupUtils.inPrivateMode();
|
const isPrivateMode = BrowserPopupUtils.inPrivateMode();
|
||||||
const mainBackground: MainBackground = needsBackgroundInit
|
const mainBackground: MainBackground = needsBackgroundInit
|
||||||
|
@ -380,6 +386,21 @@ const safeProviders: SafeProvider[] = [
|
||||||
},
|
},
|
||||||
deps: [],
|
deps: [],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE,
|
||||||
|
useFactory: (
|
||||||
|
regularMemoryStorageService: AbstractMemoryStorageService & ObservableStorageService,
|
||||||
|
) => {
|
||||||
|
if (BrowserApi.isManifestVersion(2)) {
|
||||||
|
return regularMemoryStorageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBgService<AbstractStorageService & ObservableStorageService>(
|
||||||
|
"largeObjectMemoryStorageForStateProviders",
|
||||||
|
)();
|
||||||
|
},
|
||||||
|
deps: [OBSERVABLE_MEMORY_STORAGE],
|
||||||
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: OBSERVABLE_DISK_STORAGE,
|
provide: OBSERVABLE_DISK_STORAGE,
|
||||||
useExisting: AbstractStorageService,
|
useExisting: AbstractStorageService,
|
||||||
|
@ -466,7 +487,7 @@ const safeProviders: SafeProvider[] = [
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: DerivedStateProvider,
|
provide: DerivedStateProvider,
|
||||||
useClass: ForegroundDerivedStateProvider,
|
useClass: ForegroundDerivedStateProvider,
|
||||||
deps: [OBSERVABLE_MEMORY_STORAGE, NgZone],
|
deps: [StorageServiceProvider, NgZone],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: AutofillSettingsServiceAbstraction,
|
provide: AutofillSettingsServiceAbstraction,
|
||||||
|
@ -542,6 +563,15 @@ const safeProviders: SafeProvider[] = [
|
||||||
},
|
},
|
||||||
deps: [],
|
deps: [],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: StorageServiceProvider,
|
||||||
|
useClass: BrowserStorageServiceProvider,
|
||||||
|
deps: [
|
||||||
|
OBSERVABLE_DISK_STORAGE,
|
||||||
|
OBSERVABLE_MEMORY_STORAGE,
|
||||||
|
OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE,
|
||||||
|
],
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -309,9 +309,7 @@ export class Main {
|
||||||
this.singleUserStateProvider,
|
this.singleUserStateProvider,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.derivedStateProvider = new DefaultDerivedStateProvider(
|
this.derivedStateProvider = new DefaultDerivedStateProvider(storageServiceProvider);
|
||||||
this.memoryStorageForStateProviders,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.stateProvider = new DefaultStateProvider(
|
this.stateProvider = new DefaultStateProvider(
|
||||||
this.activeUserStateProvider,
|
this.activeUserStateProvider,
|
||||||
|
|
|
@ -157,7 +157,7 @@ export class Main {
|
||||||
activeUserStateProvider,
|
activeUserStateProvider,
|
||||||
singleUserStateProvider,
|
singleUserStateProvider,
|
||||||
globalStateProvider,
|
globalStateProvider,
|
||||||
new DefaultDerivedStateProvider(this.memoryStorageForStateProviders),
|
new DefaultDerivedStateProvider(storageServiceProvider),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.environmentService = new DefaultEnvironmentService(stateProvider, accountService);
|
this.environmentService = new DefaultEnvironmentService(stateProvider, accountService);
|
||||||
|
|
|
@ -1047,7 +1047,7 @@ const safeProviders: SafeProvider[] = [
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: DerivedStateProvider,
|
provide: DerivedStateProvider,
|
||||||
useClass: DefaultDerivedStateProvider,
|
useClass: DefaultDerivedStateProvider,
|
||||||
deps: [OBSERVABLE_MEMORY_STORAGE],
|
deps: [StorageServiceProvider],
|
||||||
}),
|
}),
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: StateProvider,
|
provide: StateProvider,
|
||||||
|
|
|
@ -249,11 +249,11 @@ export class FakeDerivedStateProvider implements DerivedStateProvider {
|
||||||
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
||||||
dependencies: TDeps,
|
dependencies: TDeps,
|
||||||
): DerivedState<TTo> {
|
): DerivedState<TTo> {
|
||||||
let result = this.states.get(deriveDefinition.buildCacheKey()) as DerivedState<TTo>;
|
let result = this.states.get(deriveDefinition.buildCacheKey("memory")) as DerivedState<TTo>;
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = new FakeDerivedState(parentState$, deriveDefinition, dependencies);
|
result = new FakeDerivedState(parentState$, deriveDefinition, dependencies);
|
||||||
this.states.set(deriveDefinition.buildCacheKey(), result);
|
this.states.set(deriveDefinition.buildCacheKey("memory"), result);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,8 +171,8 @@ export class DeriveDefinition<TFrom, TTo, TDeps extends DerivedStateDependencies
|
||||||
return this.options.clearOnCleanup ?? true;
|
return this.options.clearOnCleanup ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCacheKey(): string {
|
buildCacheKey(location: string): string {
|
||||||
return `derived_${this.stateDefinition.name}_${this.uniqueDerivationName}`;
|
return `derived_${location}_${this.stateDefinition.name}_${this.uniqueDerivationName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "../../abstractions/storage.service";
|
} from "../../abstractions/storage.service";
|
||||||
|
import { StorageServiceProvider } from "../../services/storage-service.provider";
|
||||||
import { DeriveDefinition } from "../derive-definition";
|
import { DeriveDefinition } from "../derive-definition";
|
||||||
import { DerivedState } from "../derived-state";
|
import { DerivedState } from "../derived-state";
|
||||||
import { DerivedStateProvider } from "../derived-state.provider";
|
import { DerivedStateProvider } from "../derived-state.provider";
|
||||||
|
@ -14,14 +15,18 @@ import { DefaultDerivedState } from "./default-derived-state";
|
||||||
export class DefaultDerivedStateProvider implements DerivedStateProvider {
|
export class DefaultDerivedStateProvider implements DerivedStateProvider {
|
||||||
private cache: Record<string, DerivedState<unknown>> = {};
|
private cache: Record<string, DerivedState<unknown>> = {};
|
||||||
|
|
||||||
constructor(protected memoryStorage: AbstractStorageService & ObservableStorageService) {}
|
constructor(protected storageServiceProvider: StorageServiceProvider) {}
|
||||||
|
|
||||||
get<TFrom, TTo, TDeps extends DerivedStateDependencies>(
|
get<TFrom, TTo, TDeps extends DerivedStateDependencies>(
|
||||||
parentState$: Observable<TFrom>,
|
parentState$: Observable<TFrom>,
|
||||||
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
||||||
dependencies: TDeps,
|
dependencies: TDeps,
|
||||||
): DerivedState<TTo> {
|
): DerivedState<TTo> {
|
||||||
const cacheKey = deriveDefinition.buildCacheKey();
|
// TODO: we probably want to support optional normal memory storage for browser
|
||||||
|
const [location, storageService] = this.storageServiceProvider.get("memory", {
|
||||||
|
browser: "memory-large-object",
|
||||||
|
});
|
||||||
|
const cacheKey = deriveDefinition.buildCacheKey(location);
|
||||||
const existingDerivedState = this.cache[cacheKey];
|
const existingDerivedState = this.cache[cacheKey];
|
||||||
if (existingDerivedState != null) {
|
if (existingDerivedState != 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
|
||||||
|
@ -29,7 +34,10 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider {
|
||||||
return existingDerivedState as DefaultDerivedState<TFrom, TTo, TDeps>;
|
return existingDerivedState as DefaultDerivedState<TFrom, TTo, TDeps>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newDerivedState = this.buildDerivedState(parentState$, deriveDefinition, dependencies);
|
const newDerivedState = this.buildDerivedState(parentState$, deriveDefinition, dependencies, [
|
||||||
|
location,
|
||||||
|
storageService,
|
||||||
|
]);
|
||||||
this.cache[cacheKey] = newDerivedState;
|
this.cache[cacheKey] = newDerivedState;
|
||||||
return newDerivedState;
|
return newDerivedState;
|
||||||
}
|
}
|
||||||
|
@ -38,11 +46,12 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider {
|
||||||
parentState$: Observable<TFrom>,
|
parentState$: Observable<TFrom>,
|
||||||
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
||||||
dependencies: TDeps,
|
dependencies: TDeps,
|
||||||
|
storageLocation: [string, AbstractStorageService & ObservableStorageService],
|
||||||
): DerivedState<TTo> {
|
): DerivedState<TTo> {
|
||||||
return new DefaultDerivedState<TFrom, TTo, TDeps>(
|
return new DefaultDerivedState<TFrom, TTo, TDeps>(
|
||||||
parentState$,
|
parentState$,
|
||||||
deriveDefinition,
|
deriveDefinition,
|
||||||
this.memoryStorage,
|
storageLocation[1],
|
||||||
dependencies,
|
dependencies,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,12 +72,12 @@ describe("DefaultDerivedState", () => {
|
||||||
parentState$.next(dateString);
|
parentState$.next(dateString);
|
||||||
await awaitAsync();
|
await awaitAsync();
|
||||||
|
|
||||||
expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual(
|
expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual(
|
||||||
derivedValue(new Date(dateString)),
|
derivedValue(new Date(dateString)),
|
||||||
);
|
);
|
||||||
const calls = memoryStorage.mock.save.mock.calls;
|
const calls = memoryStorage.mock.save.mock.calls;
|
||||||
expect(calls.length).toBe(1);
|
expect(calls.length).toBe(1);
|
||||||
expect(calls[0][0]).toBe(deriveDefinition.buildCacheKey());
|
expect(calls[0][0]).toBe(deriveDefinition.storageKey);
|
||||||
expect(calls[0][1]).toEqual(derivedValue(new Date(dateString)));
|
expect(calls[0][1]).toEqual(derivedValue(new Date(dateString)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ describe("DefaultDerivedState", () => {
|
||||||
|
|
||||||
it("should store the forced value", async () => {
|
it("should store the forced value", async () => {
|
||||||
await sut.forceValue(forced);
|
await sut.forceValue(forced);
|
||||||
expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual(
|
expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual(
|
||||||
derivedValue(forced),
|
derivedValue(forced),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -109,7 +109,7 @@ describe("DefaultDerivedState", () => {
|
||||||
|
|
||||||
it("should store the forced value", async () => {
|
it("should store the forced value", async () => {
|
||||||
await sut.forceValue(forced);
|
await sut.forceValue(forced);
|
||||||
expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual(
|
expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual(
|
||||||
derivedValue(forced),
|
derivedValue(forced),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -153,7 +153,7 @@ describe("DefaultDerivedState", () => {
|
||||||
parentState$.next(newDate);
|
parentState$.next(newDate);
|
||||||
await awaitAsync();
|
await awaitAsync();
|
||||||
|
|
||||||
expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual(
|
expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual(
|
||||||
derivedValue(new Date(newDate)),
|
derivedValue(new Date(newDate)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ describe("DefaultDerivedState", () => {
|
||||||
// Wait for cleanup
|
// Wait for cleanup
|
||||||
await awaitAsync(cleanupDelayMs * 2);
|
await awaitAsync(cleanupDelayMs * 2);
|
||||||
|
|
||||||
expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toBeUndefined();
|
expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not clear state after cleanup if clearOnCleanup is false", async () => {
|
it("should not clear state after cleanup if clearOnCleanup is false", async () => {
|
||||||
|
@ -171,7 +171,7 @@ describe("DefaultDerivedState", () => {
|
||||||
parentState$.next(newDate);
|
parentState$.next(newDate);
|
||||||
await awaitAsync();
|
await awaitAsync();
|
||||||
|
|
||||||
expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual(
|
expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual(
|
||||||
derivedValue(new Date(newDate)),
|
derivedValue(new Date(newDate)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ describe("DefaultDerivedState", () => {
|
||||||
// Wait for cleanup
|
// Wait for cleanup
|
||||||
await awaitAsync(cleanupDelayMs * 2);
|
await awaitAsync(cleanupDelayMs * 2);
|
||||||
|
|
||||||
expect(memoryStorage.internalStore[deriveDefinition.buildCacheKey()]).toEqual(
|
expect(memoryStorage.internalStore[deriveDefinition.storageKey]).toEqual(
|
||||||
derivedValue(new Date(newDate)),
|
derivedValue(new Date(newDate)),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,8 +24,10 @@ export type ClientLocations = {
|
||||||
web: StorageLocation | "disk-local";
|
web: StorageLocation | "disk-local";
|
||||||
/**
|
/**
|
||||||
* Overriding storage location for browser clients.
|
* Overriding storage location for browser clients.
|
||||||
|
*
|
||||||
|
* "memory-large-object" is used to store non-countable objects in memory. This exists due to limited persistent memory available to browser extensions.
|
||||||
*/
|
*/
|
||||||
//browser: StorageLocation;
|
browser: StorageLocation | "memory-large-object";
|
||||||
/**
|
/**
|
||||||
* Overriding storage location for desktop clients.
|
* Overriding storage location for desktop clients.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -116,7 +116,9 @@ export const EVENT_COLLECTION_DISK = new StateDefinition("eventCollection", "dis
|
||||||
export const SEND_DISK = new StateDefinition("encryptedSend", "disk", {
|
export const SEND_DISK = new StateDefinition("encryptedSend", "disk", {
|
||||||
web: "memory",
|
web: "memory",
|
||||||
});
|
});
|
||||||
export const SEND_MEMORY = new StateDefinition("decryptedSend", "memory");
|
export const SEND_MEMORY = new StateDefinition("decryptedSend", "memory", {
|
||||||
|
browser: "memory-large-object",
|
||||||
|
});
|
||||||
|
|
||||||
// Vault
|
// Vault
|
||||||
|
|
||||||
|
@ -133,10 +135,16 @@ export const VAULT_ONBOARDING = new StateDefinition("vaultOnboarding", "disk", {
|
||||||
export const VAULT_SETTINGS_DISK = new StateDefinition("vaultSettings", "disk", {
|
export const VAULT_SETTINGS_DISK = new StateDefinition("vaultSettings", "disk", {
|
||||||
web: "disk-local",
|
web: "disk-local",
|
||||||
});
|
});
|
||||||
export const VAULT_BROWSER_MEMORY = new StateDefinition("vaultBrowser", "memory");
|
export const VAULT_BROWSER_MEMORY = new StateDefinition("vaultBrowser", "memory", {
|
||||||
export const VAULT_SEARCH_MEMORY = new StateDefinition("vaultSearch", "memory");
|
browser: "memory-large-object",
|
||||||
|
});
|
||||||
|
export const VAULT_SEARCH_MEMORY = new StateDefinition("vaultSearch", "memory", {
|
||||||
|
browser: "memory-large-object",
|
||||||
|
});
|
||||||
export const CIPHERS_DISK = new StateDefinition("ciphers", "disk", { web: "memory" });
|
export const CIPHERS_DISK = new StateDefinition("ciphers", "disk", { web: "memory" });
|
||||||
export const CIPHERS_DISK_LOCAL = new StateDefinition("ciphersLocal", "disk", {
|
export const CIPHERS_DISK_LOCAL = new StateDefinition("ciphersLocal", "disk", {
|
||||||
web: "disk-local",
|
web: "disk-local",
|
||||||
});
|
});
|
||||||
export const CIPHERS_MEMORY = new StateDefinition("ciphersMemory", "memory");
|
export const CIPHERS_MEMORY = new StateDefinition("ciphersMemory", "memory", {
|
||||||
|
browser: "memory-large-object",
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue