[PM-7917] Remove session sync (#9024)
* Remove session sync and MemoryStorageService * Fix merge
This commit is contained in:
parent
c241aba025
commit
de0852431a
|
@ -84,7 +84,6 @@ import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwar
|
||||||
import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
@ -246,10 +245,9 @@ export default class MainBackground {
|
||||||
messagingService: MessageSender;
|
messagingService: MessageSender;
|
||||||
storageService: BrowserLocalStorageService;
|
storageService: BrowserLocalStorageService;
|
||||||
secureStorageService: AbstractStorageService;
|
secureStorageService: AbstractStorageService;
|
||||||
memoryStorageService: AbstractMemoryStorageService;
|
memoryStorageService: AbstractStorageService;
|
||||||
memoryStorageForStateProviders: AbstractMemoryStorageService & ObservableStorageService;
|
memoryStorageForStateProviders: AbstractStorageService & ObservableStorageService;
|
||||||
largeObjectMemoryStorageForStateProviders: AbstractMemoryStorageService &
|
largeObjectMemoryStorageForStateProviders: AbstractStorageService & ObservableStorageService;
|
||||||
ObservableStorageService;
|
|
||||||
i18nService: I18nServiceAbstraction;
|
i18nService: I18nServiceAbstraction;
|
||||||
platformUtilsService: PlatformUtilsServiceAbstraction;
|
platformUtilsService: PlatformUtilsServiceAbstraction;
|
||||||
logService: LogServiceAbstraction;
|
logService: LogServiceAbstraction;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
@ -66,9 +65,9 @@ export function sessionStorageServiceFactory(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function memoryStorageServiceFactory(
|
export function memoryStorageServiceFactory(
|
||||||
cache: { memoryStorageService?: AbstractMemoryStorageService } & CachedServices,
|
cache: { memoryStorageService?: AbstractStorageService } & CachedServices,
|
||||||
opts: MemoryStorageServiceInitOptions,
|
opts: MemoryStorageServiceInitOptions,
|
||||||
): Promise<AbstractMemoryStorageService> {
|
): Promise<AbstractStorageService> {
|
||||||
return factory(cache, "memoryStorageService", opts, async () => {
|
return factory(cache, "memoryStorageService", opts, async () => {
|
||||||
if (BrowserApi.isManifestVersion(3)) {
|
if (BrowserApi.isManifestVersion(3)) {
|
||||||
return new LocalBackedSessionStorageService(
|
return new LocalBackedSessionStorageService(
|
||||||
|
@ -97,10 +96,10 @@ export function memoryStorageServiceFactory(
|
||||||
|
|
||||||
export function observableMemoryStorageServiceFactory(
|
export function observableMemoryStorageServiceFactory(
|
||||||
cache: {
|
cache: {
|
||||||
memoryStorageService?: AbstractMemoryStorageService & ObservableStorageService;
|
memoryStorageService?: AbstractStorageService & ObservableStorageService;
|
||||||
} & CachedServices,
|
} & CachedServices,
|
||||||
opts: MemoryStorageServiceInitOptions,
|
opts: MemoryStorageServiceInitOptions,
|
||||||
): Promise<AbstractMemoryStorageService & ObservableStorageService> {
|
): Promise<AbstractStorageService & ObservableStorageService> {
|
||||||
return factory(cache, "memoryStorageService", opts, async () => {
|
return factory(cache, "memoryStorageService", opts, async () => {
|
||||||
return new BackgroundMemoryStorageService();
|
return new BackgroundMemoryStorageService();
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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<TCtor extends Constructor<any>>(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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
export { browserSession } from "./browser-session.decorator";
|
|
||||||
export { sessionSync } from "./session-sync.decorator";
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { SessionSyncer } from "./session-syncer";
|
|
||||||
import { SyncedItemMetadata } from "./sync-item-metadata";
|
|
||||||
|
|
||||||
export interface SessionStorable {
|
|
||||||
__syncedItemMetadata: SyncedItemMetadata[];
|
|
||||||
__sessionSyncers: SessionSyncer[];
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,54 +0,0 @@
|
||||||
import { Jsonify } from "type-fest";
|
|
||||||
|
|
||||||
import { SessionStorable } from "./session-storable";
|
|
||||||
import { InitializeOptions } from "./sync-item-metadata";
|
|
||||||
|
|
||||||
class BuildOptions<T, TJson = Jsonify<T>> {
|
|
||||||
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<T>(buildOptions: BuildOptions<T>) {
|
|
||||||
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",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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<MemoryStorageService>;
|
|
||||||
let sut: SessionSyncer;
|
|
||||||
let behaviorSubject: BehaviorSubject<string>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
behaviorSubject = new BehaviorSubject<string>("");
|
|
||||||
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<string>(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<string>("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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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<any>,
|
|
||||||
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`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<any, any> = {};
|
|
||||||
for (const key in keyValuePair) {
|
|
||||||
record[key] = itemBuilder(keyValuePair[key]);
|
|
||||||
}
|
|
||||||
return record;
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return (keyValuePair: any) => itemBuilder(keyValuePair);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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" });
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,16 +1,7 @@
|
||||||
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
|
export default class BrowserMemoryStorageService extends AbstractChromeStorageService {
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,7 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import {
|
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||||
import { State } from "@bitwarden/common/platform/models/domain/state";
|
import { State } from "@bitwarden/common/platform/models/domain/state";
|
||||||
|
@ -56,7 +53,7 @@ describe("Browser State Service", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("state methods", () => {
|
describe("state methods", () => {
|
||||||
let memoryStorageService: MockProxy<AbstractMemoryStorageService>;
|
let memoryStorageService: MockProxy<AbstractStorageService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
memoryStorageService = mock();
|
memoryStorageService = mock();
|
||||||
|
|
|
@ -2,10 +2,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import {
|
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
AbstractStorageService,
|
|
||||||
AbstractMemoryStorageService,
|
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||||
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
||||||
|
@ -25,7 +22,7 @@ export class DefaultBrowserStateService
|
||||||
constructor(
|
constructor(
|
||||||
storageService: AbstractStorageService,
|
storageService: AbstractStorageService,
|
||||||
secureStorageService: AbstractStorageService,
|
secureStorageService: AbstractStorageService,
|
||||||
memoryStorageService: AbstractMemoryStorageService,
|
memoryStorageService: AbstractStorageService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
stateFactory: StateFactory<GlobalState, Account>,
|
stateFactory: StateFactory<GlobalState, Account>,
|
||||||
accountService: AccountService,
|
accountService: AccountService,
|
||||||
|
|
|
@ -59,24 +59,12 @@ describe("LocalBackedSessionStorage", () => {
|
||||||
await sut.get("test");
|
await sut.get("test");
|
||||||
expect(sut["cache"]["test"]).toEqual("decrypted");
|
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 () => {
|
it("returns a decrypted value when one is stored in local storage", async () => {
|
||||||
const encrypted = makeEncString("encrypted");
|
const encrypted = makeEncString("encrypted");
|
||||||
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||||
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
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(encryptService.decryptToUtf8).toHaveBeenCalledWith(encrypted, sessionKey);
|
||||||
expect(result).toEqual("decrypted");
|
expect(result).toEqual("decrypted");
|
||||||
});
|
});
|
||||||
|
@ -85,19 +73,9 @@ describe("LocalBackedSessionStorage", () => {
|
||||||
const encrypted = makeEncString("encrypted");
|
const encrypted = makeEncString("encrypted");
|
||||||
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
localStorage.internalStore["session_test"] = encrypted.encryptedString;
|
||||||
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted"));
|
||||||
await sut.getBypassCache("test");
|
await sut.get("test");
|
||||||
expect(sut["cache"]["test"]).toEqual("decrypted");
|
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", () => {
|
describe("has", () => {
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
import { Jsonify } from "type-fest";
|
|
||||||
|
|
||||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
StorageUpdate,
|
StorageUpdate,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
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 { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
import { BrowserApi } from "../browser/browser-api";
|
import { BrowserApi } from "../browser/browser-api";
|
||||||
|
@ -20,7 +18,7 @@ import { MemoryStoragePortMessage } from "../storage/port-messages";
|
||||||
import { portName } from "../storage/port-name";
|
import { portName } from "../storage/port-name";
|
||||||
|
|
||||||
export class LocalBackedSessionStorageService
|
export class LocalBackedSessionStorageService
|
||||||
extends AbstractMemoryStorageService
|
extends AbstractStorageService
|
||||||
implements ObservableStorageService
|
implements ObservableStorageService
|
||||||
{
|
{
|
||||||
private ports: Set<chrome.runtime.Port> = new Set([]);
|
private ports: Set<chrome.runtime.Port> = new Set([]);
|
||||||
|
@ -65,20 +63,12 @@ export class LocalBackedSessionStorageService
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async get<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T> {
|
async get<T>(key: string, options?: StorageOptions): Promise<T> {
|
||||||
if (this.cache[key] !== undefined) {
|
if (this.cache[key] !== undefined) {
|
||||||
return this.cache[key] as T;
|
return this.cache[key] as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.getBypassCache(key, options);
|
const value = await this.getLocalSessionValue(await this.sessionKey.get(), key);
|
||||||
}
|
|
||||||
|
|
||||||
async getBypassCache<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T> {
|
|
||||||
let value = await this.getLocalSessionValue(await this.sessionKey.get(), key);
|
|
||||||
|
|
||||||
if (options?.deserializer != null) {
|
|
||||||
value = options.deserializer(value as Jsonify<T>);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cache[key] = value;
|
this.cache[key] = value;
|
||||||
return value as T;
|
return value as T;
|
||||||
|
@ -159,7 +149,6 @@ export class LocalBackedSessionStorageService
|
||||||
|
|
||||||
switch (message.action) {
|
switch (message.action) {
|
||||||
case "get":
|
case "get":
|
||||||
case "getBypassCache":
|
|
||||||
case "has": {
|
case "has": {
|
||||||
result = await this[message.action](message.key);
|
result = await this[message.action](message.key);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -51,7 +51,6 @@ export class BackgroundMemoryStorageService extends MemoryStorageService {
|
||||||
|
|
||||||
switch (message.action) {
|
switch (message.action) {
|
||||||
case "get":
|
case "get":
|
||||||
case "getBypassCache":
|
|
||||||
case "has": {
|
case "has": {
|
||||||
result = await this[message.action](message.key);
|
result = await this[message.action](message.key);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Observable, Subject, filter, firstValueFrom, map } from "rxjs";
|
import { Observable, Subject, filter, firstValueFrom, map } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
AbstractStorageService,
|
||||||
StorageUpdate,
|
StorageUpdate,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
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 { MemoryStoragePortMessage } from "./port-messages";
|
||||||
import { portName } from "./port-name";
|
import { portName } from "./port-name";
|
||||||
|
|
||||||
export class ForegroundMemoryStorageService extends AbstractMemoryStorageService {
|
export class ForegroundMemoryStorageService extends AbstractStorageService {
|
||||||
private _port: chrome.runtime.Port;
|
private _port: chrome.runtime.Port;
|
||||||
private _backgroundResponses$: Observable<MemoryStoragePortMessage>;
|
private _backgroundResponses$: Observable<MemoryStoragePortMessage>;
|
||||||
private updatesSubject = new Subject<StorageUpdate>();
|
private updatesSubject = new Subject<StorageUpdate>();
|
||||||
|
@ -59,9 +59,6 @@ export class ForegroundMemoryStorageService extends AbstractMemoryStorageService
|
||||||
async get<T>(key: string): Promise<T> {
|
async get<T>(key: string): Promise<T> {
|
||||||
return await this.delegateToBackground<T>("get", key);
|
return await this.delegateToBackground<T>("get", key);
|
||||||
}
|
}
|
||||||
async getBypassCache<T>(key: string): Promise<T> {
|
|
||||||
return await this.delegateToBackground<T>("getBypassCache", key);
|
|
||||||
}
|
|
||||||
async has(key: string): Promise<boolean> {
|
async has(key: string): Promise<boolean> {
|
||||||
return await this.delegateToBackground<boolean>("has", key);
|
return await this.delegateToBackground<boolean>("has", key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,9 @@ describe("foreground background memory storage interaction", () => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each(["has", "get", "getBypassCache"])(
|
test.each(["has", "get"])(
|
||||||
"background should respond with the correct value for %s",
|
"background should respond with the correct value for %s",
|
||||||
async (action: "get" | "has" | "getBypassCache") => {
|
async (action: "get" | "has") => {
|
||||||
const key = "key";
|
const key = "key";
|
||||||
const value = "value";
|
const value = "value";
|
||||||
background[action] = jest.fn().mockResolvedValue(value);
|
background[action] = jest.fn().mockResolvedValue(value);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
AbstractStorageService,
|
||||||
StorageUpdate,
|
StorageUpdate,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ type MemoryStoragePortMessage = {
|
||||||
data: string | string[] | StorageUpdate;
|
data: string | string[] | StorageUpdate;
|
||||||
originator: "foreground" | "background";
|
originator: "foreground" | "background";
|
||||||
action?:
|
action?:
|
||||||
| keyof Pick<AbstractMemoryStorageService, "get" | "getBypassCache" | "has" | "save" | "remove">
|
| keyof Pick<AbstractStorageService, "get" | "has" | "save" | "remove">
|
||||||
| "subject_update"
|
| "subject_update"
|
||||||
| "initialization";
|
| "initialization";
|
||||||
};
|
};
|
||||||
|
|
|
@ -59,7 +59,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
@ -411,7 +410,7 @@ const safeProviders: SafeProvider[] = [
|
||||||
safeProvider({
|
safeProvider({
|
||||||
provide: OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE,
|
provide: OBSERVABLE_LARGE_OBJECT_MEMORY_STORAGE,
|
||||||
useFactory: (
|
useFactory: (
|
||||||
regularMemoryStorageService: AbstractMemoryStorageService & ObservableStorageService,
|
regularMemoryStorageService: AbstractStorageService & ObservableStorageService,
|
||||||
) => {
|
) => {
|
||||||
if (BrowserApi.isManifestVersion(2)) {
|
if (BrowserApi.isManifestVersion(2)) {
|
||||||
return regularMemoryStorageService;
|
return regularMemoryStorageService;
|
||||||
|
@ -439,7 +438,7 @@ const safeProviders: SafeProvider[] = [
|
||||||
useFactory: (
|
useFactory: (
|
||||||
storageService: AbstractStorageService,
|
storageService: AbstractStorageService,
|
||||||
secureStorageService: AbstractStorageService,
|
secureStorageService: AbstractStorageService,
|
||||||
memoryStorageService: AbstractMemoryStorageService,
|
memoryStorageService: AbstractStorageService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
accountService: AccountServiceAbstraction,
|
accountService: AccountServiceAbstraction,
|
||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
|
|
|
@ -9,10 +9,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import {
|
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
|
||||||
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
|
||||||
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
||||||
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
|
||||||
|
@ -26,7 +23,7 @@ export class StateService extends BaseStateService<GlobalState, Account> {
|
||||||
constructor(
|
constructor(
|
||||||
storageService: AbstractStorageService,
|
storageService: AbstractStorageService,
|
||||||
@Inject(SECURE_STORAGE) secureStorageService: AbstractStorageService,
|
@Inject(SECURE_STORAGE) secureStorageService: AbstractStorageService,
|
||||||
@Inject(MEMORY_STORAGE) memoryStorageService: AbstractMemoryStorageService,
|
@Inject(MEMORY_STORAGE) memoryStorageService: AbstractStorageService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
@Inject(STATE_FACTORY) stateFactory: StateFactory<GlobalState, Account>,
|
@Inject(STATE_FACTORY) stateFactory: StateFactory<GlobalState, Account>,
|
||||||
accountService: AccountService,
|
accountService: AccountService,
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { Observable, Subject } from "rxjs";
|
||||||
|
|
||||||
import { ClientType } from "@bitwarden/common/enums";
|
import { ClientType } from "@bitwarden/common/enums";
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||||
|
@ -24,7 +23,7 @@ export class SafeInjectionToken<T> extends InjectionToken<T> {
|
||||||
|
|
||||||
export const WINDOW = new SafeInjectionToken<Window>("WINDOW");
|
export const WINDOW = new SafeInjectionToken<Window>("WINDOW");
|
||||||
export const OBSERVABLE_MEMORY_STORAGE = new SafeInjectionToken<
|
export const OBSERVABLE_MEMORY_STORAGE = new SafeInjectionToken<
|
||||||
AbstractMemoryStorageService & ObservableStorageService
|
AbstractStorageService & ObservableStorageService
|
||||||
>("OBSERVABLE_MEMORY_STORAGE");
|
>("OBSERVABLE_MEMORY_STORAGE");
|
||||||
export const OBSERVABLE_DISK_STORAGE = new SafeInjectionToken<
|
export const OBSERVABLE_DISK_STORAGE = new SafeInjectionToken<
|
||||||
AbstractStorageService & ObservableStorageService
|
AbstractStorageService & ObservableStorageService
|
||||||
|
@ -32,9 +31,7 @@ export const OBSERVABLE_DISK_STORAGE = new SafeInjectionToken<
|
||||||
export const OBSERVABLE_DISK_LOCAL_STORAGE = new SafeInjectionToken<
|
export const OBSERVABLE_DISK_LOCAL_STORAGE = new SafeInjectionToken<
|
||||||
AbstractStorageService & ObservableStorageService
|
AbstractStorageService & ObservableStorageService
|
||||||
>("OBSERVABLE_DISK_LOCAL_STORAGE");
|
>("OBSERVABLE_DISK_LOCAL_STORAGE");
|
||||||
export const MEMORY_STORAGE = new SafeInjectionToken<AbstractMemoryStorageService>(
|
export const MEMORY_STORAGE = new SafeInjectionToken<AbstractStorageService>("MEMORY_STORAGE");
|
||||||
"MEMORY_STORAGE",
|
|
||||||
);
|
|
||||||
export const SECURE_STORAGE = new SafeInjectionToken<AbstractStorageService>("SECURE_STORAGE");
|
export const SECURE_STORAGE = new SafeInjectionToken<AbstractStorageService>("SECURE_STORAGE");
|
||||||
export const STATE_FACTORY = new SafeInjectionToken<StateFactory>("STATE_FACTORY");
|
export const STATE_FACTORY = new SafeInjectionToken<StateFactory>("STATE_FACTORY");
|
||||||
export const LOGOUT_CALLBACK = new SafeInjectionToken<
|
export const LOGOUT_CALLBACK = new SafeInjectionToken<
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Observable } from "rxjs";
|
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 StorageUpdateType = "save" | "remove";
|
||||||
export type StorageUpdate = {
|
export type StorageUpdate = {
|
||||||
|
@ -24,12 +24,3 @@ export abstract class AbstractStorageService {
|
||||||
abstract save<T>(key: string, obj: T, options?: StorageOptions): Promise<void>;
|
abstract save<T>(key: string, obj: T, options?: StorageOptions): Promise<void>;
|
||||||
abstract remove(key: string, options?: StorageOptions): Promise<void>;
|
abstract remove(key: string, options?: StorageOptions): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
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<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T>;
|
|
||||||
abstract getBypassCache<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T>;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { Jsonify } from "type-fest";
|
|
||||||
|
|
||||||
import { HtmlStorageLocation, StorageLocation } from "../../enums";
|
import { HtmlStorageLocation, StorageLocation } from "../../enums";
|
||||||
|
|
||||||
export type StorageOptions = {
|
export type StorageOptions = {
|
||||||
|
@ -9,5 +7,3 @@ export type StorageOptions = {
|
||||||
htmlStorageLocation?: HtmlStorageLocation;
|
htmlStorageLocation?: HtmlStorageLocation;
|
||||||
keySuffix?: string;
|
keySuffix?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MemoryStorageOptions<T> = StorageOptions & { deserializer?: (obj: Jsonify<T>) => T };
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Subject } from "rxjs";
|
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<string, unknown>();
|
protected store = new Map<string, unknown>();
|
||||||
private updatesSubject = new Subject<StorageUpdate>();
|
private updatesSubject = new Subject<StorageUpdate>();
|
||||||
|
|
||||||
|
@ -42,8 +42,4 @@ export class MemoryStorageService extends AbstractMemoryStorageService {
|
||||||
this.updatesSubject.next({ key, updateType: "remove" });
|
this.updatesSubject.next({ key, updateType: "remove" });
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBypassCache<T>(key: string): Promise<T> {
|
|
||||||
return this.get<T>(key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,7 @@ import {
|
||||||
InitOptions,
|
InitOptions,
|
||||||
StateService as StateServiceAbstraction,
|
StateService as StateServiceAbstraction,
|
||||||
} from "../abstractions/state.service";
|
} from "../abstractions/state.service";
|
||||||
import {
|
import { AbstractStorageService } from "../abstractions/storage.service";
|
||||||
AbstractMemoryStorageService,
|
|
||||||
AbstractStorageService,
|
|
||||||
} from "../abstractions/storage.service";
|
|
||||||
import { HtmlStorageLocation, StorageLocation } from "../enums";
|
import { HtmlStorageLocation, StorageLocation } from "../enums";
|
||||||
import { StateFactory } from "../factories/state-factory";
|
import { StateFactory } from "../factories/state-factory";
|
||||||
import { Utils } from "../misc/utils";
|
import { Utils } from "../misc/utils";
|
||||||
|
@ -61,7 +58,7 @@ export class StateService<
|
||||||
constructor(
|
constructor(
|
||||||
protected storageService: AbstractStorageService,
|
protected storageService: AbstractStorageService,
|
||||||
protected secureStorageService: AbstractStorageService,
|
protected secureStorageService: AbstractStorageService,
|
||||||
protected memoryStorageService: AbstractMemoryStorageService,
|
protected memoryStorageService: AbstractStorageService,
|
||||||
protected logService: LogService,
|
protected logService: LogService,
|
||||||
protected stateFactory: StateFactory<TGlobalState, TAccount>,
|
protected stateFactory: StateFactory<TGlobalState, TAccount>,
|
||||||
protected accountService: AccountService,
|
protected accountService: AccountService,
|
||||||
|
@ -1111,9 +1108,10 @@ export class StateService<
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async state(): Promise<State<TGlobalState, TAccount>> {
|
protected async state(): Promise<State<TGlobalState, TAccount>> {
|
||||||
const state = await this.memoryStorageService.get<State<TGlobalState, TAccount>>(keys.state, {
|
let state = await this.memoryStorageService.get<State<TGlobalState, TAccount>>(keys.state);
|
||||||
deserializer: (s) => State.fromJSON(s, this.accountDeserializer),
|
if (this.memoryStorageService.valuesRequireDeserialization) {
|
||||||
});
|
state = State.fromJSON(state, this.accountDeserializer);
|
||||||
|
}
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { Subject } from "rxjs";
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AbstractMemoryStorageService,
|
AbstractStorageService,
|
||||||
ObservableStorageService,
|
ObservableStorageService,
|
||||||
StorageUpdate,
|
StorageUpdate,
|
||||||
} from "../../abstractions/storage.service";
|
} from "../../abstractions/storage.service";
|
||||||
|
|
||||||
export class MemoryStorageService
|
export class MemoryStorageService
|
||||||
extends AbstractMemoryStorageService
|
extends AbstractStorageService
|
||||||
implements ObservableStorageService
|
implements ObservableStorageService
|
||||||
{
|
{
|
||||||
protected store: Record<string, string> = {};
|
protected store: Record<string, string> = {};
|
||||||
|
@ -49,8 +49,4 @@ export class MemoryStorageService
|
||||||
this.updatesSubject.next({ key, updateType: "remove" });
|
this.updatesSubject.next({ key, updateType: "remove" });
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBypassCache<T>(key: string): Promise<T> {
|
|
||||||
return this.get<T>(key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue