From de0852431adf467e828da17b0d8038804c1c30dc Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Tue, 7 May 2024 13:25:49 -0400 Subject: [PATCH] [PM-7917] Remove session sync (#9024) * Remove session sync and MemoryStorageService * Fix merge --- .../browser/src/background/main.background.ts | 8 +- .../storage-service.factory.ts | 9 +- .../browser-session.decorator.spec.ts | 88 ----- .../browser-session.decorator.ts | 75 ----- .../session-sync-observable/index.ts | 2 - .../session-storable.ts | 7 - .../session-sync.decorator.spec.ts | 57 ---- .../session-sync.decorator.ts | 54 ---- .../session-syncer.spec.ts | 301 ------------------ .../session-sync-observable/session-syncer.ts | 125 -------- .../sync-item-metadata.ts | 25 -- .../synced-item-metadata.spec.ts | 42 --- .../browser-memory-storage.service.ts | 11 +- .../services/browser-state.service.spec.ts | 7 +- .../services/default-browser-state.service.ts | 7 +- ...cal-backed-session-storage.service.spec.ts | 26 +- .../local-backed-session-storage.service.ts | 19 +- .../background-memory-storage.service.ts | 1 - .../foreground-memory-storage.service.ts | 7 +- ...emory-storage-service-interactions.spec.ts | 4 +- .../src/platform/storage/port-messages.d.ts | 4 +- .../src/popup/services/services.module.ts | 5 +- apps/web/src/app/core/state/state.service.ts | 7 +- libs/angular/src/services/injection-tokens.ts | 7 +- .../platform/abstractions/storage.service.ts | 11 +- .../platform/models/domain/storage-options.ts | 4 - .../services/memory-storage.service.ts | 8 +- .../src/platform/services/state.service.ts | 14 +- .../state/storage/memory-storage.service.ts | 8 +- 29 files changed, 41 insertions(+), 902 deletions(-) delete mode 100644 apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.spec.ts delete mode 100644 apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.ts delete mode 100644 apps/browser/src/platform/decorators/session-sync-observable/index.ts delete mode 100644 apps/browser/src/platform/decorators/session-sync-observable/session-storable.ts delete mode 100644 apps/browser/src/platform/decorators/session-sync-observable/session-sync.decorator.spec.ts delete mode 100644 apps/browser/src/platform/decorators/session-sync-observable/session-sync.decorator.ts delete mode 100644 apps/browser/src/platform/decorators/session-sync-observable/session-syncer.spec.ts delete mode 100644 apps/browser/src/platform/decorators/session-sync-observable/session-syncer.ts delete mode 100644 apps/browser/src/platform/decorators/session-sync-observable/sync-item-metadata.ts delete mode 100644 apps/browser/src/platform/decorators/session-sync-observable/synced-item-metadata.spec.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 5cd4113bae..713dfe801c 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -84,7 +84,6 @@ import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwar import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { - AbstractMemoryStorageService, AbstractStorageService, ObservableStorageService, } from "@bitwarden/common/platform/abstractions/storage.service"; @@ -246,10 +245,9 @@ export default class MainBackground { messagingService: MessageSender; storageService: BrowserLocalStorageService; secureStorageService: AbstractStorageService; - memoryStorageService: AbstractMemoryStorageService; - memoryStorageForStateProviders: AbstractMemoryStorageService & ObservableStorageService; - largeObjectMemoryStorageForStateProviders: AbstractMemoryStorageService & - ObservableStorageService; + memoryStorageService: AbstractStorageService; + memoryStorageForStateProviders: AbstractStorageService & ObservableStorageService; + largeObjectMemoryStorageForStateProviders: AbstractStorageService & ObservableStorageService; i18nService: I18nServiceAbstraction; platformUtilsService: PlatformUtilsServiceAbstraction; logService: LogServiceAbstraction; diff --git a/apps/browser/src/platform/background/service-factories/storage-service.factory.ts b/apps/browser/src/platform/background/service-factories/storage-service.factory.ts index e63e39944d..764842d751 100644 --- a/apps/browser/src/platform/background/service-factories/storage-service.factory.ts +++ b/apps/browser/src/platform/background/service-factories/storage-service.factory.ts @@ -1,5 +1,4 @@ import { - AbstractMemoryStorageService, AbstractStorageService, ObservableStorageService, } from "@bitwarden/common/platform/abstractions/storage.service"; @@ -66,9 +65,9 @@ export function sessionStorageServiceFactory( } export function memoryStorageServiceFactory( - cache: { memoryStorageService?: AbstractMemoryStorageService } & CachedServices, + cache: { memoryStorageService?: AbstractStorageService } & CachedServices, opts: MemoryStorageServiceInitOptions, -): Promise { +): Promise { return factory(cache, "memoryStorageService", opts, async () => { if (BrowserApi.isManifestVersion(3)) { return new LocalBackedSessionStorageService( @@ -97,10 +96,10 @@ export function memoryStorageServiceFactory( export function observableMemoryStorageServiceFactory( cache: { - memoryStorageService?: AbstractMemoryStorageService & ObservableStorageService; + memoryStorageService?: AbstractStorageService & ObservableStorageService; } & CachedServices, opts: MemoryStorageServiceInitOptions, -): Promise { +): Promise { return factory(cache, "memoryStorageService", opts, async () => { return new BackgroundMemoryStorageService(); }); diff --git a/apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.spec.ts b/apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.spec.ts deleted file mode 100644 index 2092f6992b..0000000000 --- a/apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.spec.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { BehaviorSubject } from "rxjs"; - -import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; -import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; - -import { DefaultBrowserStateService } from "../../services/default-browser-state.service"; - -import { browserSession } from "./browser-session.decorator"; -import { SessionStorable } from "./session-storable"; -import { sessionSync } from "./session-sync.decorator"; - -// browserSession initializes SessionSyncers for each sessionSync decorated property -// We don't want to test SessionSyncers, so we'll mock them -jest.mock("./session-syncer"); - -describe("browserSession decorator", () => { - it("should throw if neither StateService nor MemoryStorageService is a constructor argument", () => { - @browserSession - class TestClass {} - expect(() => { - new TestClass(); - }).toThrowError( - "Cannot decorate TestClass with browserSession, Browser's AbstractMemoryStorageService must be accessible through the observed classes parameters", - ); - }); - - it("should create if StateService is a constructor argument", () => { - const stateService = Object.create(DefaultBrowserStateService.prototype, { - memoryStorageService: { - value: Object.create(MemoryStorageService.prototype, { - type: { value: MemoryStorageService.TYPE }, - }), - }, - }); - - @browserSession - class TestClass { - constructor(private stateService: DefaultBrowserStateService) {} - } - - expect(new TestClass(stateService)).toBeDefined(); - }); - - it("should create if MemoryStorageService is a constructor argument", () => { - const memoryStorageService = Object.create(MemoryStorageService.prototype, { - type: { value: MemoryStorageService.TYPE }, - }); - - @browserSession - class TestClass { - constructor(private memoryStorageService: AbstractMemoryStorageService) {} - } - - expect(new TestClass(memoryStorageService)).toBeDefined(); - }); - - describe("interaction with @sessionSync decorator", () => { - let memoryStorageService: MemoryStorageService; - - @browserSession - class TestClass { - @sessionSync({ initializer: (s: string) => s }) - private behaviorSubject = new BehaviorSubject(""); - - constructor(private memoryStorageService: MemoryStorageService) {} - - fromJSON(json: any) { - this.behaviorSubject.next(json); - } - } - - beforeEach(() => { - memoryStorageService = Object.create(MemoryStorageService.prototype, { - type: { value: MemoryStorageService.TYPE }, - }); - }); - - it("should create a session syncer", () => { - const testClass = new TestClass(memoryStorageService) as any as SessionStorable; - expect(testClass.__sessionSyncers.length).toEqual(1); - }); - - it("should initialize the session syncer", () => { - const testClass = new TestClass(memoryStorageService) as any as SessionStorable; - expect(testClass.__sessionSyncers[0].init).toHaveBeenCalled(); - }); - }); -}); diff --git a/apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.ts b/apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.ts deleted file mode 100644 index 8cf84ef153..0000000000 --- a/apps/browser/src/platform/decorators/session-sync-observable/browser-session.decorator.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Constructor } from "type-fest"; - -import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; - -import { SessionStorable } from "./session-storable"; -import { SessionSyncer } from "./session-syncer"; -import { SyncedItemMetadata } from "./sync-item-metadata"; - -/** - * Mark the class as syncing state across the browser session. This decorator finds rxjs BehaviorSubject properties - * marked with @sessionSync and syncs these values across the browser session. - * - * @param constructor - * @returns A new constructor that extends the original one to add session syncing. - */ -export function browserSession>(constructor: TCtor) { - return class extends constructor implements SessionStorable { - __syncedItemMetadata: SyncedItemMetadata[]; - __sessionSyncers: SessionSyncer[]; - - constructor(...args: any[]) { - super(...args); - - // Require state service to be injected - const storageService: AbstractMemoryStorageService = this.findStorageService( - [this as any].concat(args), - ); - - if (this.__syncedItemMetadata == null || !(this.__syncedItemMetadata instanceof Array)) { - return; - } - - this.__sessionSyncers = this.__syncedItemMetadata.map((metadata) => - this.buildSyncer(metadata, storageService), - ); - } - - buildSyncer(metadata: SyncedItemMetadata, storageSerice: AbstractMemoryStorageService) { - const syncer = new SessionSyncer( - (this as any)[metadata.propertyKey], - storageSerice, - metadata, - ); - // 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 - syncer.init(); - return syncer; - } - - findStorageService(args: any[]): AbstractMemoryStorageService { - const storageService = args.find(this.isMemoryStorageService); - - if (storageService) { - return storageService; - } - - const stateService = args.find( - (arg) => - arg?.memoryStorageService != null && - this.isMemoryStorageService(arg.memoryStorageService), - ); - if (stateService) { - return stateService.memoryStorageService; - } - - throw new Error( - `Cannot decorate ${constructor.name} with browserSession, Browser's AbstractMemoryStorageService must be accessible through the observed classes parameters`, - ); - } - - isMemoryStorageService(arg: any): arg is AbstractMemoryStorageService { - return arg.type != null && arg.type === AbstractMemoryStorageService.TYPE; - } - }; -} diff --git a/apps/browser/src/platform/decorators/session-sync-observable/index.ts b/apps/browser/src/platform/decorators/session-sync-observable/index.ts deleted file mode 100644 index c0c547192e..0000000000 --- a/apps/browser/src/platform/decorators/session-sync-observable/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { browserSession } from "./browser-session.decorator"; -export { sessionSync } from "./session-sync.decorator"; diff --git a/apps/browser/src/platform/decorators/session-sync-observable/session-storable.ts b/apps/browser/src/platform/decorators/session-sync-observable/session-storable.ts deleted file mode 100644 index f5838b86ef..0000000000 --- a/apps/browser/src/platform/decorators/session-sync-observable/session-storable.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SessionSyncer } from "./session-syncer"; -import { SyncedItemMetadata } from "./sync-item-metadata"; - -export interface SessionStorable { - __syncedItemMetadata: SyncedItemMetadata[]; - __sessionSyncers: SessionSyncer[]; -} diff --git a/apps/browser/src/platform/decorators/session-sync-observable/session-sync.decorator.spec.ts b/apps/browser/src/platform/decorators/session-sync-observable/session-sync.decorator.spec.ts deleted file mode 100644 index 7a6e726608..0000000000 --- a/apps/browser/src/platform/decorators/session-sync-observable/session-sync.decorator.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { BehaviorSubject } from "rxjs"; - -import { sessionSync } from "./session-sync.decorator"; - -describe("sessionSync decorator", () => { - const initializer = (s: string) => "test"; - class TestClass { - @sessionSync({ initializer: initializer }) - private testProperty = new BehaviorSubject(""); - @sessionSync({ initializer: initializer, initializeAs: "array" }) - private secondTestProperty = new BehaviorSubject(""); - - complete() { - this.testProperty.complete(); - this.secondTestProperty.complete(); - } - } - - it("should add __syncedItemKeys to prototype", () => { - const testClass = new TestClass(); - expect((testClass as any).__syncedItemMetadata).toEqual([ - expect.objectContaining({ - propertyKey: "testProperty", - sessionKey: "testProperty_0", - initializer: initializer, - }), - expect.objectContaining({ - propertyKey: "secondTestProperty", - sessionKey: "secondTestProperty_1", - initializer: initializer, - initializeAs: "array", - }), - ]); - testClass.complete(); - }); - - class TestClass2 { - @sessionSync({ initializer: initializer }) - private testProperty = new BehaviorSubject(""); - - complete() { - this.testProperty.complete(); - } - } - - it("should maintain sessionKey index count for other test classes", () => { - const testClass = new TestClass2(); - expect((testClass as any).__syncedItemMetadata).toEqual([ - expect.objectContaining({ - propertyKey: "testProperty", - sessionKey: "testProperty_2", - initializer: initializer, - }), - ]); - testClass.complete(); - }); -}); diff --git a/apps/browser/src/platform/decorators/session-sync-observable/session-sync.decorator.ts b/apps/browser/src/platform/decorators/session-sync-observable/session-sync.decorator.ts deleted file mode 100644 index e439cea45a..0000000000 --- a/apps/browser/src/platform/decorators/session-sync-observable/session-sync.decorator.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Jsonify } from "type-fest"; - -import { SessionStorable } from "./session-storable"; -import { InitializeOptions } from "./sync-item-metadata"; - -class BuildOptions> { - initializer?: (keyValuePair: TJson) => T; - initializeAs?: InitializeOptions; -} - -// Used to ensure uniqueness for each synced observable -let index = 0; - -/** - * A decorator used to indicate the BehaviorSubject should be synced for this browser session across all contexts. - * - * >**Note** This decorator does nothing if the enclosing class is not decorated with @browserSession. - * - * >**Note** The Behavior subject must be initialized with a default or in the constructor of the class. If it is not, an error will be thrown. - * - * >**!!Warning!!** If the property is overwritten at any time, the new value will not be synced across the browser session. - * - * @param buildOptions - * Builders for the value, requires either a constructor (ctor) for your BehaviorSubject type or an - * initializer function that takes a key value pair representation of the BehaviorSubject data - * and returns your instantiated BehaviorSubject value. `initializeAs can optionally be used to indicate - * the provided initializer function should be used to build an array of values. For example, - * ```ts - * \@sessionSync({ initializer: Foo.fromJSON, initializeAs: 'array' }) - * ``` - * is equivalent to - * ``` - * \@sessionSync({ initializer: (obj: any[]) => obj.map((f) => Foo.fromJSON }) - * ``` - * - * @returns decorator function - */ -export function sessionSync(buildOptions: BuildOptions) { - return (prototype: unknown, propertyKey: string) => { - // Force prototype into SessionStorable and implement it. - const p = prototype as SessionStorable; - - if (p.__syncedItemMetadata == null) { - p.__syncedItemMetadata = []; - } - - p.__syncedItemMetadata.push({ - propertyKey, - sessionKey: `${propertyKey}_${index++}`, - initializer: buildOptions.initializer, - initializeAs: buildOptions.initializeAs ?? "object", - }); - }; -} diff --git a/apps/browser/src/platform/decorators/session-sync-observable/session-syncer.spec.ts b/apps/browser/src/platform/decorators/session-sync-observable/session-syncer.spec.ts deleted file mode 100644 index 18f0ceac60..0000000000 --- a/apps/browser/src/platform/decorators/session-sync-observable/session-syncer.spec.ts +++ /dev/null @@ -1,301 +0,0 @@ -import { awaitAsync } from "@bitwarden/common/../spec/utils"; -import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject, ReplaySubject } from "rxjs"; - -import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; - -import { BrowserApi } from "../../browser/browser-api"; - -import { SessionSyncer } from "./session-syncer"; -import { SyncedItemMetadata } from "./sync-item-metadata"; - -describe("session syncer", () => { - const propertyKey = "behaviorSubject"; - const sessionKey = "Test__" + propertyKey; - const metaData: SyncedItemMetadata = { - propertyKey, - sessionKey, - initializer: (s: string) => s, - initializeAs: "object", - }; - let storageService: MockProxy; - let sut: SessionSyncer; - let behaviorSubject: BehaviorSubject; - - beforeEach(() => { - behaviorSubject = new BehaviorSubject(""); - jest.spyOn(chrome.runtime, "getManifest").mockReturnValue({ - name: "bitwarden-test", - version: "0.0.0", - manifest_version: 3, - }); - - storageService = mock(); - storageService.has.mockResolvedValue(false); - sut = new SessionSyncer(behaviorSubject, storageService, metaData); - }); - - afterEach(() => { - jest.resetAllMocks(); - - behaviorSubject.complete(); - }); - - describe("constructor", () => { - it("should throw if subject is not an instance of Subject", () => { - expect(() => { - new SessionSyncer({} as any, storageService, null); - }).toThrowError("subject must inherit from Subject"); - }); - - it("should create if either ctor or initializer is provided", () => { - expect( - new SessionSyncer(behaviorSubject, storageService, { - propertyKey, - sessionKey, - initializeAs: "object", - initializer: () => null, - }), - ).toBeDefined(); - expect( - new SessionSyncer(behaviorSubject, storageService, { - propertyKey, - sessionKey, - initializer: (s: any) => s, - initializeAs: "object", - }), - ).toBeDefined(); - }); - it("should throw if neither ctor or initializer is provided", () => { - expect(() => { - new SessionSyncer(behaviorSubject, storageService, { - propertyKey, - sessionKey, - initializeAs: "object", - initializer: null, - }); - }).toThrowError("initializer must be provided"); - }); - }); - - describe("init", () => { - it("should ignore all updates currently in a ReplaySubject's buffer", () => { - const replaySubject = new ReplaySubject(Infinity); - replaySubject.next("1"); - replaySubject.next("2"); - replaySubject.next("3"); - sut = new SessionSyncer(replaySubject, storageService, metaData); - // block observing the subject - jest.spyOn(sut as any, "observe").mockImplementation(); - - // 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 - sut.init(); - - expect(sut["ignoreNUpdates"]).toBe(3); - }); - - it("should ignore BehaviorSubject's initial value", () => { - const behaviorSubject = new BehaviorSubject("initial"); - sut = new SessionSyncer(behaviorSubject, storageService, metaData); - // block observing the subject - jest.spyOn(sut as any, "observe").mockImplementation(); - - // 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 - sut.init(); - - expect(sut["ignoreNUpdates"]).toBe(1); - }); - - it("should grab an initial value from storage if it exists", async () => { - storageService.has.mockResolvedValue(true); - //Block a call to update - const updateSpy = jest.spyOn(sut as any, "updateFromMemory").mockImplementation(); - - // 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 - sut.init(); - await awaitAsync(); - - expect(updateSpy).toHaveBeenCalledWith(); - }); - - it("should not grab an initial value from storage if it does not exist", async () => { - storageService.has.mockResolvedValue(false); - //Block a call to update - const updateSpy = jest.spyOn(sut as any, "update").mockImplementation(); - - // 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 - sut.init(); - await awaitAsync(); - - expect(updateSpy).not.toHaveBeenCalled(); - }); - }); - - describe("a value is emitted on the observable", () => { - let sendMessageSpy: jest.SpyInstance; - const value = "test"; - const serializedValue = JSON.stringify(value); - - beforeEach(() => { - sendMessageSpy = jest.spyOn(BrowserApi, "sendMessage"); - - // 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 - sut.init(); - - behaviorSubject.next(value); - }); - - it("should update sessionSyncers in other contexts", async () => { - // await finishing of fire-and-forget operation - await new Promise((resolve) => setTimeout(resolve, 100)); - - expect(sendMessageSpy).toHaveBeenCalledTimes(1); - expect(sendMessageSpy).toHaveBeenCalledWith(`${sessionKey}_update`, { - id: sut.id, - serializedValue, - }); - }); - }); - - describe("A message is received", () => { - let nextSpy: jest.SpyInstance; - let sendMessageSpy: jest.SpyInstance; - - beforeEach(() => { - nextSpy = jest.spyOn(behaviorSubject, "next"); - sendMessageSpy = jest.spyOn(BrowserApi, "sendMessage"); - - // 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 - sut.init(); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it("should ignore messages with the wrong command", async () => { - await sut.updateFromMessage({ command: "wrong_command", id: sut.id }); - - expect(storageService.getBypassCache).not.toHaveBeenCalled(); - expect(nextSpy).not.toHaveBeenCalled(); - }); - - it("should ignore messages from itself", async () => { - await sut.updateFromMessage({ command: `${sessionKey}_update`, id: sut.id }); - - expect(storageService.getBypassCache).not.toHaveBeenCalled(); - expect(nextSpy).not.toHaveBeenCalled(); - }); - - it("should update from message on emit from another instance", async () => { - const builder = jest.fn(); - jest.spyOn(SyncedItemMetadata, "builder").mockReturnValue(builder); - const value = "test"; - const serializedValue = JSON.stringify(value); - builder.mockReturnValue(value); - - // Expect no circular messaging - await awaitAsync(); - expect(sendMessageSpy).toHaveBeenCalledTimes(0); - - await sut.updateFromMessage({ - command: `${sessionKey}_update`, - id: "different_id", - serializedValue, - }); - await awaitAsync(); - - expect(storageService.getBypassCache).toHaveBeenCalledTimes(0); - - expect(nextSpy).toHaveBeenCalledTimes(1); - expect(nextSpy).toHaveBeenCalledWith(value); - expect(behaviorSubject.value).toBe(value); - - // Expect no circular messaging - expect(sendMessageSpy).toHaveBeenCalledTimes(0); - }); - }); - - describe("memory storage", () => { - const value = "test"; - const serializedValue = JSON.stringify(value); - let saveSpy: jest.SpyInstance; - const builder = jest.fn().mockReturnValue(value); - const manifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get"); - const isBackgroundPageSpy = jest.spyOn(BrowserApi, "isBackgroundPage"); - - beforeEach(async () => { - jest.spyOn(SyncedItemMetadata, "builder").mockReturnValue(builder); - saveSpy = jest.spyOn(storageService, "save"); - - // 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 - sut.init(); - await awaitAsync(); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it("should always store on observed next for manifest version 3", async () => { - manifestVersionSpy.mockReturnValue(3); - isBackgroundPageSpy.mockReturnValueOnce(true).mockReturnValueOnce(false); - behaviorSubject.next(value); - await awaitAsync(); - behaviorSubject.next(value); - await awaitAsync(); - - expect(saveSpy).toHaveBeenCalledTimes(2); - }); - - it("should not store on message receive for manifest version 3", async () => { - manifestVersionSpy.mockReturnValue(3); - isBackgroundPageSpy.mockReturnValueOnce(true).mockReturnValueOnce(false); - await sut.updateFromMessage({ - command: `${sessionKey}_update`, - id: "different_id", - serializedValue, - }); - await awaitAsync(); - - expect(saveSpy).toHaveBeenCalledTimes(0); - }); - - it("should store on message receive for manifest version 2 for background page only", async () => { - manifestVersionSpy.mockReturnValue(2); - isBackgroundPageSpy.mockReturnValueOnce(true).mockReturnValueOnce(false); - await sut.updateFromMessage({ - command: `${sessionKey}_update`, - id: "different_id", - serializedValue, - }); - await awaitAsync(); - await sut.updateFromMessage({ - command: `${sessionKey}_update`, - id: "different_id", - serializedValue, - }); - await awaitAsync(); - - expect(saveSpy).toHaveBeenCalledTimes(1); - }); - - it("should store on observed next for manifest version 2 for background page only", async () => { - manifestVersionSpy.mockReturnValue(2); - isBackgroundPageSpy.mockReturnValueOnce(true).mockReturnValueOnce(false); - behaviorSubject.next(value); - await awaitAsync(); - behaviorSubject.next(value); - await awaitAsync(); - - expect(saveSpy).toHaveBeenCalledTimes(1); - }); - }); -}); diff --git a/apps/browser/src/platform/decorators/session-sync-observable/session-syncer.ts b/apps/browser/src/platform/decorators/session-sync-observable/session-syncer.ts deleted file mode 100644 index 6561d5074c..0000000000 --- a/apps/browser/src/platform/decorators/session-sync-observable/session-syncer.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { BehaviorSubject, concatMap, ReplaySubject, skip, Subject, Subscription } from "rxjs"; - -import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - -import { BrowserApi } from "../../browser/browser-api"; - -import { SyncedItemMetadata } from "./sync-item-metadata"; - -export class SessionSyncer { - subscription: Subscription; - id = Utils.newGuid(); - - // ignore initial values - private ignoreNUpdates = 0; - - constructor( - private subject: Subject, - private memoryStorageService: AbstractMemoryStorageService, - private metaData: SyncedItemMetadata, - ) { - if (!(subject instanceof Subject)) { - throw new Error("subject must inherit from Subject"); - } - - if (metaData.initializer == null) { - throw new Error("initializer must be provided"); - } - } - - async init() { - switch (this.subject.constructor) { - case ReplaySubject: - // ignore all updates currently in the buffer - this.ignoreNUpdates = (this.subject as any)._buffer.length; - break; - case BehaviorSubject: - this.ignoreNUpdates = 1; - break; - default: - break; - } - - await this.observe(); - // must be synchronous - const hasInSessionMemory = await this.memoryStorageService.has(this.metaData.sessionKey); - if (hasInSessionMemory) { - await this.updateFromMemory(); - } - - this.listenForUpdates(); - } - - private async observe() { - const stream = this.subject.pipe(skip(this.ignoreNUpdates)); - this.ignoreNUpdates = 0; - - // This may be a memory leak. - // There is no good time to unsubscribe from this observable. Hopefully Manifest V3 clears memory from temporary - // contexts. If so, this is handled by destruction of the context. - this.subscription = stream - .pipe( - concatMap(async (next) => { - if (this.ignoreNUpdates > 0) { - this.ignoreNUpdates -= 1; - return; - } - await this.updateSession(next); - }), - ) - .subscribe(); - } - - private listenForUpdates() { - // This is an unawaited promise, but it will be executed asynchronously in the background. - BrowserApi.messageListener(this.updateMessageCommand, (message) => { - // 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 - this.updateFromMessage(message); - }); - } - - async updateFromMessage(message: any) { - if (message.command != this.updateMessageCommand || message.id === this.id) { - return; - } - await this.update(message.serializedValue); - } - - async updateFromMemory() { - const value = await this.memoryStorageService.getBypassCache(this.metaData.sessionKey); - await this.update(value); - } - - async update(serializedValue: any) { - if (!serializedValue) { - return; - } - - const unBuiltValue = JSON.parse(serializedValue); - if (!BrowserApi.isManifestVersion(3) && BrowserApi.isBackgroundPage(self)) { - await this.memoryStorageService.save(this.metaData.sessionKey, serializedValue); - } - const builder = SyncedItemMetadata.builder(this.metaData); - const value = builder(unBuiltValue); - this.ignoreNUpdates = 1; - this.subject.next(value); - } - - private async updateSession(value: any) { - if (!value) { - return; - } - - const serializedValue = JSON.stringify(value); - if (BrowserApi.isManifestVersion(3) || BrowserApi.isBackgroundPage(self)) { - await this.memoryStorageService.save(this.metaData.sessionKey, serializedValue); - } - await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id, serializedValue }); - } - - private get updateMessageCommand() { - return `${this.metaData.sessionKey}_update`; - } -} diff --git a/apps/browser/src/platform/decorators/session-sync-observable/sync-item-metadata.ts b/apps/browser/src/platform/decorators/session-sync-observable/sync-item-metadata.ts deleted file mode 100644 index fe2b393923..0000000000 --- a/apps/browser/src/platform/decorators/session-sync-observable/sync-item-metadata.ts +++ /dev/null @@ -1,25 +0,0 @@ -export type InitializeOptions = "array" | "record" | "object"; - -export class SyncedItemMetadata { - propertyKey: string; - sessionKey: string; - initializer: (keyValuePair: any) => any; - initializeAs: InitializeOptions; - - static builder(metadata: SyncedItemMetadata): (o: any) => any { - const itemBuilder = metadata.initializer; - if (metadata.initializeAs === "array") { - return (keyValuePair: any) => keyValuePair.map((o: any) => itemBuilder(o)); - } else if (metadata.initializeAs === "record") { - return (keyValuePair: any) => { - const record: Record = {}; - for (const key in keyValuePair) { - record[key] = itemBuilder(keyValuePair[key]); - } - return record; - }; - } else { - return (keyValuePair: any) => itemBuilder(keyValuePair); - } - } -} diff --git a/apps/browser/src/platform/decorators/session-sync-observable/synced-item-metadata.spec.ts b/apps/browser/src/platform/decorators/session-sync-observable/synced-item-metadata.spec.ts deleted file mode 100644 index 61eb63eaac..0000000000 --- a/apps/browser/src/platform/decorators/session-sync-observable/synced-item-metadata.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { SyncedItemMetadata } from "./sync-item-metadata"; - -describe("builder", () => { - const propertyKey = "propertyKey"; - const key = "key"; - const initializer = (s: any) => "used initializer"; - - it("should use initializer", () => { - const metadata: SyncedItemMetadata = { - propertyKey, - sessionKey: key, - initializer, - initializeAs: "object", - }; - const builder = SyncedItemMetadata.builder(metadata); - expect(builder({})).toBe("used initializer"); - }); - - it("should honor initialize as array", () => { - const metadata: SyncedItemMetadata = { - propertyKey, - sessionKey: key, - initializer: initializer, - initializeAs: "array", - }; - const builder = SyncedItemMetadata.builder(metadata); - expect(builder([{}])).toBeInstanceOf(Array); - expect(builder([{}])[0]).toBe("used initializer"); - }); - - it("should honor initialize as record", () => { - const metadata: SyncedItemMetadata = { - propertyKey, - sessionKey: key, - initializer: initializer, - initializeAs: "record", - }; - const builder = SyncedItemMetadata.builder(metadata); - expect(builder({ key: "" })).toBeInstanceOf(Object); - expect(builder({ key: "" })).toStrictEqual({ key: "used initializer" }); - }); -}); diff --git a/apps/browser/src/platform/services/browser-memory-storage.service.ts b/apps/browser/src/platform/services/browser-memory-storage.service.ts index b067dc5a12..f824a1df0d 100644 --- a/apps/browser/src/platform/services/browser-memory-storage.service.ts +++ b/apps/browser/src/platform/services/browser-memory-storage.service.ts @@ -1,16 +1,7 @@ -import { AbstractMemoryStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; - import AbstractChromeStorageService from "./abstractions/abstract-chrome-storage-api.service"; -export default class BrowserMemoryStorageService - extends AbstractChromeStorageService - implements AbstractMemoryStorageService -{ +export default class BrowserMemoryStorageService extends AbstractChromeStorageService { constructor() { super(chrome.storage.session); } - type = "MemoryStorageService" as const; - getBypassCache(key: string): Promise { - return this.get(key); - } } diff --git a/apps/browser/src/platform/services/browser-state.service.spec.ts b/apps/browser/src/platform/services/browser-state.service.spec.ts index a0a52ff622..9077305f44 100644 --- a/apps/browser/src/platform/services/browser-state.service.spec.ts +++ b/apps/browser/src/platform/services/browser-state.service.spec.ts @@ -3,10 +3,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { - AbstractMemoryStorageService, - AbstractStorageService, -} from "@bitwarden/common/platform/abstractions/storage.service"; +import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { State } from "@bitwarden/common/platform/models/domain/state"; @@ -56,7 +53,7 @@ describe("Browser State Service", () => { }); describe("state methods", () => { - let memoryStorageService: MockProxy; + let memoryStorageService: MockProxy; beforeEach(() => { memoryStorageService = mock(); diff --git a/apps/browser/src/platform/services/default-browser-state.service.ts b/apps/browser/src/platform/services/default-browser-state.service.ts index d7bc45bcc3..92da28efa2 100644 --- a/apps/browser/src/platform/services/default-browser-state.service.ts +++ b/apps/browser/src/platform/services/default-browser-state.service.ts @@ -2,10 +2,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { - AbstractStorageService, - AbstractMemoryStorageService, -} from "@bitwarden/common/platform/abstractions/storage.service"; +import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; @@ -25,7 +22,7 @@ export class DefaultBrowserStateService constructor( storageService: AbstractStorageService, secureStorageService: AbstractStorageService, - memoryStorageService: AbstractMemoryStorageService, + memoryStorageService: AbstractStorageService, logService: LogService, stateFactory: StateFactory, accountService: AccountService, diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts index 7114bda06e..8d43c8f2fe 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts @@ -59,24 +59,12 @@ describe("LocalBackedSessionStorage", () => { await sut.get("test"); expect(sut["cache"]["test"]).toEqual("decrypted"); }); - }); - - describe("getBypassCache", () => { - it("ignores cached values", async () => { - sut["cache"]["test"] = "cached"; - const encrypted = makeEncString("encrypted"); - localStorage.internalStore["session_test"] = encrypted.encryptedString; - encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); - const result = await sut.getBypassCache("test"); - expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encrypted, sessionKey); - expect(result).toEqual("decrypted"); - }); it("returns a decrypted value when one is stored in local storage", async () => { const encrypted = makeEncString("encrypted"); localStorage.internalStore["session_test"] = encrypted.encryptedString; encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); - const result = await sut.getBypassCache("test"); + const result = await sut.get("test"); expect(encryptService.decryptToUtf8).toHaveBeenCalledWith(encrypted, sessionKey); expect(result).toEqual("decrypted"); }); @@ -85,19 +73,9 @@ describe("LocalBackedSessionStorage", () => { const encrypted = makeEncString("encrypted"); localStorage.internalStore["session_test"] = encrypted.encryptedString; encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); - await sut.getBypassCache("test"); + await sut.get("test"); expect(sut["cache"]["test"]).toEqual("decrypted"); }); - - it("deserializes when a deserializer is provided", async () => { - const encrypted = makeEncString("encrypted"); - localStorage.internalStore["session_test"] = encrypted.encryptedString; - encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); - const deserializer = jest.fn().mockReturnValue("deserialized"); - const result = await sut.getBypassCache("test", { deserializer }); - expect(deserializer).toHaveBeenCalledWith("decrypted"); - expect(result).toEqual("deserialized"); - }); }); describe("has", () => { diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.ts index c29b9c69dc..2c14ac2833 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.ts @@ -1,18 +1,16 @@ import { Subject } from "rxjs"; -import { Jsonify } from "type-fest"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { - AbstractMemoryStorageService, AbstractStorageService, ObservableStorageService, StorageUpdate, } from "@bitwarden/common/platform/abstractions/storage.service"; import { Lazy } from "@bitwarden/common/platform/misc/lazy"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { MemoryStorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; +import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { BrowserApi } from "../browser/browser-api"; @@ -20,7 +18,7 @@ import { MemoryStoragePortMessage } from "../storage/port-messages"; import { portName } from "../storage/port-name"; export class LocalBackedSessionStorageService - extends AbstractMemoryStorageService + extends AbstractStorageService implements ObservableStorageService { private ports: Set = new Set([]); @@ -65,20 +63,12 @@ export class LocalBackedSessionStorageService }); } - async get(key: string, options?: MemoryStorageOptions): Promise { + async get(key: string, options?: StorageOptions): Promise { if (this.cache[key] !== undefined) { return this.cache[key] as T; } - return await this.getBypassCache(key, options); - } - - async getBypassCache(key: string, options?: MemoryStorageOptions): Promise { - let value = await this.getLocalSessionValue(await this.sessionKey.get(), key); - - if (options?.deserializer != null) { - value = options.deserializer(value as Jsonify); - } + const value = await this.getLocalSessionValue(await this.sessionKey.get(), key); this.cache[key] = value; return value as T; @@ -159,7 +149,6 @@ export class LocalBackedSessionStorageService switch (message.action) { case "get": - case "getBypassCache": case "has": { result = await this[message.action](message.key); break; diff --git a/apps/browser/src/platform/storage/background-memory-storage.service.ts b/apps/browser/src/platform/storage/background-memory-storage.service.ts index 9203d2aacb..a1d333affa 100644 --- a/apps/browser/src/platform/storage/background-memory-storage.service.ts +++ b/apps/browser/src/platform/storage/background-memory-storage.service.ts @@ -51,7 +51,6 @@ export class BackgroundMemoryStorageService extends MemoryStorageService { switch (message.action) { case "get": - case "getBypassCache": case "has": { result = await this[message.action](message.key); break; diff --git a/apps/browser/src/platform/storage/foreground-memory-storage.service.ts b/apps/browser/src/platform/storage/foreground-memory-storage.service.ts index b3ac8de55e..bd6a52c82f 100644 --- a/apps/browser/src/platform/storage/foreground-memory-storage.service.ts +++ b/apps/browser/src/platform/storage/foreground-memory-storage.service.ts @@ -1,7 +1,7 @@ import { Observable, Subject, filter, firstValueFrom, map } from "rxjs"; import { - AbstractMemoryStorageService, + AbstractStorageService, StorageUpdate, } from "@bitwarden/common/platform/abstractions/storage.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -11,7 +11,7 @@ import { fromChromeEvent } from "../browser/from-chrome-event"; import { MemoryStoragePortMessage } from "./port-messages"; import { portName } from "./port-name"; -export class ForegroundMemoryStorageService extends AbstractMemoryStorageService { +export class ForegroundMemoryStorageService extends AbstractStorageService { private _port: chrome.runtime.Port; private _backgroundResponses$: Observable; private updatesSubject = new Subject(); @@ -59,9 +59,6 @@ export class ForegroundMemoryStorageService extends AbstractMemoryStorageService async get(key: string): Promise { return await this.delegateToBackground("get", key); } - async getBypassCache(key: string): Promise { - return await this.delegateToBackground("getBypassCache", key); - } async has(key: string): Promise { return await this.delegateToBackground("has", key); } diff --git a/apps/browser/src/platform/storage/memory-storage-service-interactions.spec.ts b/apps/browser/src/platform/storage/memory-storage-service-interactions.spec.ts index 43ffb6a065..c462f24269 100644 --- a/apps/browser/src/platform/storage/memory-storage-service-interactions.spec.ts +++ b/apps/browser/src/platform/storage/memory-storage-service-interactions.spec.ts @@ -25,9 +25,9 @@ describe("foreground background memory storage interaction", () => { jest.resetAllMocks(); }); - test.each(["has", "get", "getBypassCache"])( + test.each(["has", "get"])( "background should respond with the correct value for %s", - async (action: "get" | "has" | "getBypassCache") => { + async (action: "get" | "has") => { const key = "key"; const value = "value"; background[action] = jest.fn().mockResolvedValue(value); diff --git a/apps/browser/src/platform/storage/port-messages.d.ts b/apps/browser/src/platform/storage/port-messages.d.ts index a64a9b2ef7..60817c98a4 100644 --- a/apps/browser/src/platform/storage/port-messages.d.ts +++ b/apps/browser/src/platform/storage/port-messages.d.ts @@ -1,5 +1,5 @@ import { - AbstractMemoryStorageService, + AbstractStorageService, StorageUpdate, } from "@bitwarden/common/platform/abstractions/storage.service"; @@ -14,7 +14,7 @@ type MemoryStoragePortMessage = { data: string | string[] | StorageUpdate; originator: "foreground" | "background"; action?: - | keyof Pick + | keyof Pick | "subject_update" | "initialization"; }; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index ee08ed84b7..7dc79fa01e 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -59,7 +59,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { - AbstractMemoryStorageService, AbstractStorageService, ObservableStorageService, } from "@bitwarden/common/platform/abstractions/storage.service"; @@ -411,7 +410,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE, useFactory: ( - regularMemoryStorageService: AbstractMemoryStorageService & ObservableStorageService, + regularMemoryStorageService: AbstractStorageService & ObservableStorageService, ) => { if (BrowserApi.isManifestVersion(2)) { return regularMemoryStorageService; @@ -439,7 +438,7 @@ const safeProviders: SafeProvider[] = [ useFactory: ( storageService: AbstractStorageService, secureStorageService: AbstractStorageService, - memoryStorageService: AbstractMemoryStorageService, + memoryStorageService: AbstractStorageService, logService: LogService, accountService: AccountServiceAbstraction, environmentService: EnvironmentService, diff --git a/apps/web/src/app/core/state/state.service.ts b/apps/web/src/app/core/state/state.service.ts index 185509e150..de47a69555 100644 --- a/apps/web/src/app/core/state/state.service.ts +++ b/apps/web/src/app/core/state/state.service.ts @@ -9,10 +9,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { - AbstractMemoryStorageService, - AbstractStorageService, -} from "@bitwarden/common/platform/abstractions/storage.service"; +import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; @@ -26,7 +23,7 @@ export class StateService extends BaseStateService { constructor( storageService: AbstractStorageService, @Inject(SECURE_STORAGE) secureStorageService: AbstractStorageService, - @Inject(MEMORY_STORAGE) memoryStorageService: AbstractMemoryStorageService, + @Inject(MEMORY_STORAGE) memoryStorageService: AbstractStorageService, logService: LogService, @Inject(STATE_FACTORY) stateFactory: StateFactory, accountService: AccountService, diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index b7989e7f32..c58931ce55 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -3,7 +3,6 @@ import { Observable, Subject } from "rxjs"; import { ClientType } from "@bitwarden/common/enums"; import { - AbstractMemoryStorageService, AbstractStorageService, ObservableStorageService, } from "@bitwarden/common/platform/abstractions/storage.service"; @@ -24,7 +23,7 @@ export class SafeInjectionToken extends InjectionToken { export const WINDOW = new SafeInjectionToken("WINDOW"); export const OBSERVABLE_MEMORY_STORAGE = new SafeInjectionToken< - AbstractMemoryStorageService & ObservableStorageService + AbstractStorageService & ObservableStorageService >("OBSERVABLE_MEMORY_STORAGE"); export const OBSERVABLE_DISK_STORAGE = new SafeInjectionToken< AbstractStorageService & ObservableStorageService @@ -32,9 +31,7 @@ export const OBSERVABLE_DISK_STORAGE = new SafeInjectionToken< export const OBSERVABLE_DISK_LOCAL_STORAGE = new SafeInjectionToken< AbstractStorageService & ObservableStorageService >("OBSERVABLE_DISK_LOCAL_STORAGE"); -export const MEMORY_STORAGE = new SafeInjectionToken( - "MEMORY_STORAGE", -); +export const MEMORY_STORAGE = new SafeInjectionToken("MEMORY_STORAGE"); export const SECURE_STORAGE = new SafeInjectionToken("SECURE_STORAGE"); export const STATE_FACTORY = new SafeInjectionToken("STATE_FACTORY"); export const LOGOUT_CALLBACK = new SafeInjectionToken< diff --git a/libs/common/src/platform/abstractions/storage.service.ts b/libs/common/src/platform/abstractions/storage.service.ts index f380420c39..390d71ae2a 100644 --- a/libs/common/src/platform/abstractions/storage.service.ts +++ b/libs/common/src/platform/abstractions/storage.service.ts @@ -1,6 +1,6 @@ import { Observable } from "rxjs"; -import { MemoryStorageOptions, StorageOptions } from "../models/domain/storage-options"; +import { StorageOptions } from "../models/domain/storage-options"; export type StorageUpdateType = "save" | "remove"; export type StorageUpdate = { @@ -24,12 +24,3 @@ export abstract class AbstractStorageService { abstract save(key: string, obj: T, options?: StorageOptions): Promise; abstract remove(key: string, options?: StorageOptions): Promise; } - -export abstract class AbstractMemoryStorageService extends AbstractStorageService { - // Used to identify the service in the session sync decorator framework - static readonly TYPE = "MemoryStorageService"; - readonly type = AbstractMemoryStorageService.TYPE; - - abstract get(key: string, options?: MemoryStorageOptions): Promise; - abstract getBypassCache(key: string, options?: MemoryStorageOptions): Promise; -} diff --git a/libs/common/src/platform/models/domain/storage-options.ts b/libs/common/src/platform/models/domain/storage-options.ts index 6ed430ac50..e27628b850 100644 --- a/libs/common/src/platform/models/domain/storage-options.ts +++ b/libs/common/src/platform/models/domain/storage-options.ts @@ -1,5 +1,3 @@ -import { Jsonify } from "type-fest"; - import { HtmlStorageLocation, StorageLocation } from "../../enums"; export type StorageOptions = { @@ -9,5 +7,3 @@ export type StorageOptions = { htmlStorageLocation?: HtmlStorageLocation; keySuffix?: string; }; - -export type MemoryStorageOptions = StorageOptions & { deserializer?: (obj: Jsonify) => T }; diff --git a/libs/common/src/platform/services/memory-storage.service.ts b/libs/common/src/platform/services/memory-storage.service.ts index 9cecee7538..d5debf46cc 100644 --- a/libs/common/src/platform/services/memory-storage.service.ts +++ b/libs/common/src/platform/services/memory-storage.service.ts @@ -1,8 +1,8 @@ import { Subject } from "rxjs"; -import { AbstractMemoryStorageService, StorageUpdate } from "../abstractions/storage.service"; +import { AbstractStorageService, StorageUpdate } from "../abstractions/storage.service"; -export class MemoryStorageService extends AbstractMemoryStorageService { +export class MemoryStorageService extends AbstractStorageService { protected store = new Map(); private updatesSubject = new Subject(); @@ -42,8 +42,4 @@ export class MemoryStorageService extends AbstractMemoryStorageService { this.updatesSubject.next({ key, updateType: "remove" }); return Promise.resolve(); } - - getBypassCache(key: string): Promise { - return this.get(key); - } } diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 156a871a2d..aa245f8688 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -14,10 +14,7 @@ import { InitOptions, StateService as StateServiceAbstraction, } from "../abstractions/state.service"; -import { - AbstractMemoryStorageService, - AbstractStorageService, -} from "../abstractions/storage.service"; +import { AbstractStorageService } from "../abstractions/storage.service"; import { HtmlStorageLocation, StorageLocation } from "../enums"; import { StateFactory } from "../factories/state-factory"; import { Utils } from "../misc/utils"; @@ -61,7 +58,7 @@ export class StateService< constructor( protected storageService: AbstractStorageService, protected secureStorageService: AbstractStorageService, - protected memoryStorageService: AbstractMemoryStorageService, + protected memoryStorageService: AbstractStorageService, protected logService: LogService, protected stateFactory: StateFactory, protected accountService: AccountService, @@ -1111,9 +1108,10 @@ export class StateService< } protected async state(): Promise> { - const state = await this.memoryStorageService.get>(keys.state, { - deserializer: (s) => State.fromJSON(s, this.accountDeserializer), - }); + let state = await this.memoryStorageService.get>(keys.state); + if (this.memoryStorageService.valuesRequireDeserialization) { + state = State.fromJSON(state, this.accountDeserializer); + } return state; } diff --git a/libs/common/src/platform/state/storage/memory-storage.service.ts b/libs/common/src/platform/state/storage/memory-storage.service.ts index 36116f5e4e..ab45c101f9 100644 --- a/libs/common/src/platform/state/storage/memory-storage.service.ts +++ b/libs/common/src/platform/state/storage/memory-storage.service.ts @@ -1,13 +1,13 @@ import { Subject } from "rxjs"; import { - AbstractMemoryStorageService, + AbstractStorageService, ObservableStorageService, StorageUpdate, } from "../../abstractions/storage.service"; export class MemoryStorageService - extends AbstractMemoryStorageService + extends AbstractStorageService implements ObservableStorageService { protected store: Record = {}; @@ -49,8 +49,4 @@ export class MemoryStorageService this.updatesSubject.next({ key, updateType: "remove" }); return Promise.resolve(); } - - getBypassCache(key: string): Promise { - return this.get(key); - } }