mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 14:23:32 +00:00
Use Memory Storage directly in Session Sync (#4423)
* Use Memory Storage directly in Session Sync * Update apps/browser/src/decorators/session-sync-observable/browser-session.decorator.spec.ts Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> * Fix up test Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
This commit is contained in:
@@ -28,7 +28,10 @@ import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common
|
|||||||
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService as SearchServiceAbstraction } from "@bitwarden/common/abstractions/search.service";
|
||||||
import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstractions/send.service";
|
import { SendService as SendServiceAbstraction } from "@bitwarden/common/abstractions/send.service";
|
||||||
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
|
import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common/abstractions/settings.service";
|
||||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
import {
|
||||||
|
AbstractMemoryStorageService,
|
||||||
|
AbstractStorageService,
|
||||||
|
} from "@bitwarden/common/abstractions/storage.service";
|
||||||
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
||||||
import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/abstractions/sync/syncNotifier.service.abstraction";
|
import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/abstractions/sync/syncNotifier.service.abstraction";
|
||||||
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abstractions/system.service";
|
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abstractions/system.service";
|
||||||
@@ -123,7 +126,7 @@ export default class MainBackground {
|
|||||||
messagingService: MessagingServiceAbstraction;
|
messagingService: MessagingServiceAbstraction;
|
||||||
storageService: AbstractStorageService;
|
storageService: AbstractStorageService;
|
||||||
secureStorageService: AbstractStorageService;
|
secureStorageService: AbstractStorageService;
|
||||||
memoryStorageService: AbstractStorageService;
|
memoryStorageService: AbstractMemoryStorageService;
|
||||||
i18nService: I18nServiceAbstraction;
|
i18nService: I18nServiceAbstraction;
|
||||||
platformUtilsService: PlatformUtilsServiceAbstraction;
|
platformUtilsService: PlatformUtilsServiceAbstraction;
|
||||||
logService: LogServiceAbstraction;
|
logService: LogServiceAbstraction;
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
import {
|
||||||
|
AbstractMemoryStorageService,
|
||||||
|
AbstractStorageService,
|
||||||
|
} from "@bitwarden/common/abstractions/storage.service";
|
||||||
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
||||||
|
|
||||||
import { BrowserApi } from "../../browser/browserApi";
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
@@ -35,9 +38,9 @@ export function secureStorageServiceFactory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function memoryStorageServiceFactory(
|
export function memoryStorageServiceFactory(
|
||||||
cache: { memoryStorageService?: AbstractStorageService } & CachedServices,
|
cache: { memoryStorageService?: AbstractMemoryStorageService } & CachedServices,
|
||||||
opts: MemoryStorageServiceInitOptions
|
opts: MemoryStorageServiceInitOptions
|
||||||
): Promise<AbstractStorageService> {
|
): Promise<AbstractMemoryStorageService> {
|
||||||
return factory(cache, "memoryStorageService", opts, async () => {
|
return factory(cache, "memoryStorageService", opts, async () => {
|
||||||
if (BrowserApi.manifestVersion === 3) {
|
if (BrowserApi.manifestVersion === 3) {
|
||||||
return new LocalBackedSessionStorageService(
|
return new LocalBackedSessionStorageService(
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import { BrowserStateService } from "../services/abstractions/browser-state.service";
|
|
||||||
|
|
||||||
const clearClipboardStorageKey = "clearClipboardTime";
|
|
||||||
export const getClearClipboardTime = async (stateService: BrowserStateService) => {
|
|
||||||
return await stateService.getFromSessionMemory<number>(clearClipboardStorageKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setClearClipboardTime = async (stateService: BrowserStateService, time: number) => {
|
|
||||||
await stateService.setInSessionMemory(clearClipboardStorageKey, time);
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
|
|
||||||
|
import { AbstractMemoryStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||||
|
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
||||||
|
|
||||||
import { BrowserStateService } from "../../services/browser-state.service";
|
import { BrowserStateService } from "../../services/browser-state.service";
|
||||||
|
|
||||||
import { browserSession } from "./browser-session.decorator";
|
import { browserSession } from "./browser-session.decorator";
|
||||||
@@ -11,18 +14,24 @@ import { sessionSync } from "./session-sync.decorator";
|
|||||||
jest.mock("./session-syncer");
|
jest.mock("./session-syncer");
|
||||||
|
|
||||||
describe("browserSession decorator", () => {
|
describe("browserSession decorator", () => {
|
||||||
it("should throw if StateService is not a constructor argument", () => {
|
it("should throw if neither StateService nor MemoryStorageService is a constructor argument", () => {
|
||||||
@browserSession
|
@browserSession
|
||||||
class TestClass {}
|
class TestClass {}
|
||||||
expect(() => {
|
expect(() => {
|
||||||
new TestClass();
|
new TestClass();
|
||||||
}).toThrowError(
|
}).toThrowError(
|
||||||
"Cannot decorate TestClass with browserSession, Browser's StateService must be injected"
|
"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", () => {
|
it("should create if StateService is a constructor argument", () => {
|
||||||
const stateService = Object.create(BrowserStateService.prototype, {});
|
const stateService = Object.create(BrowserStateService.prototype, {
|
||||||
|
memoryStorageService: {
|
||||||
|
value: Object.create(MemoryStorageService.prototype, {
|
||||||
|
type: { value: MemoryStorageService.TYPE },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@browserSession
|
@browserSession
|
||||||
class TestClass {
|
class TestClass {
|
||||||
@@ -32,15 +41,28 @@ describe("browserSession decorator", () => {
|
|||||||
expect(new TestClass(stateService)).toBeDefined();
|
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", () => {
|
describe("interaction with @sessionSync decorator", () => {
|
||||||
let stateService: BrowserStateService;
|
let memoryStorageService: MemoryStorageService;
|
||||||
|
|
||||||
@browserSession
|
@browserSession
|
||||||
class TestClass {
|
class TestClass {
|
||||||
@sessionSync({ initializer: (s: string) => s })
|
@sessionSync({ initializer: (s: string) => s })
|
||||||
private behaviorSubject = new BehaviorSubject("");
|
private behaviorSubject = new BehaviorSubject("");
|
||||||
|
|
||||||
constructor(private stateService: BrowserStateService) {}
|
constructor(private memoryStorageService: MemoryStorageService) {}
|
||||||
|
|
||||||
fromJSON(json: any) {
|
fromJSON(json: any) {
|
||||||
this.behaviorSubject.next(json);
|
this.behaviorSubject.next(json);
|
||||||
@@ -48,16 +70,18 @@ describe("browserSession decorator", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
stateService = Object.create(BrowserStateService.prototype, {}) as BrowserStateService;
|
memoryStorageService = Object.create(MemoryStorageService.prototype, {
|
||||||
|
type: { value: MemoryStorageService.TYPE },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create a session syncer", () => {
|
it("should create a session syncer", () => {
|
||||||
const testClass = new TestClass(stateService) as any as SessionStorable;
|
const testClass = new TestClass(memoryStorageService) as any as SessionStorable;
|
||||||
expect(testClass.__sessionSyncers.length).toEqual(1);
|
expect(testClass.__sessionSyncers.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should initialize the session syncer", () => {
|
it("should initialize the session syncer", () => {
|
||||||
const testClass = new TestClass(stateService) as any as SessionStorable;
|
const testClass = new TestClass(memoryStorageService) as any as SessionStorable;
|
||||||
expect(testClass.__sessionSyncers[0].init).toHaveBeenCalled();
|
expect(testClass.__sessionSyncers[0].init).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Constructor } from "type-fest";
|
import { Constructor } from "type-fest";
|
||||||
|
|
||||||
import { BrowserStateService } from "../../services/browser-state.service";
|
import { AbstractMemoryStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||||
|
|
||||||
import { SessionStorable } from "./session-storable";
|
import { SessionStorable } from "./session-storable";
|
||||||
import { SessionSyncer } from "./session-syncer";
|
import { SessionSyncer } from "./session-syncer";
|
||||||
@@ -22,32 +22,51 @@ export function browserSession<TCtor extends Constructor<any>>(constructor: TCto
|
|||||||
super(...args);
|
super(...args);
|
||||||
|
|
||||||
// Require state service to be injected
|
// Require state service to be injected
|
||||||
const stateService: BrowserStateService = [this as any]
|
const storageService: AbstractMemoryStorageService = this.findStorageService(
|
||||||
.concat(args)
|
[this as any].concat(args)
|
||||||
.find(
|
);
|
||||||
(arg) =>
|
|
||||||
typeof arg.setInSessionMemory === "function" &&
|
|
||||||
typeof arg.getFromSessionMemory === "function"
|
|
||||||
);
|
|
||||||
if (!stateService) {
|
|
||||||
throw new Error(
|
|
||||||
`Cannot decorate ${constructor.name} with browserSession, Browser's StateService must be injected`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.__syncedItemMetadata == null || !(this.__syncedItemMetadata instanceof Array)) {
|
if (this.__syncedItemMetadata == null || !(this.__syncedItemMetadata instanceof Array)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.__sessionSyncers = this.__syncedItemMetadata.map((metadata) =>
|
this.__sessionSyncers = this.__syncedItemMetadata.map((metadata) =>
|
||||||
this.buildSyncer(metadata, stateService)
|
this.buildSyncer(metadata, storageService)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildSyncer(metadata: SyncedItemMetadata, stateService: BrowserStateService) {
|
buildSyncer(metadata: SyncedItemMetadata, storageSerice: AbstractMemoryStorageService) {
|
||||||
const syncer = new SessionSyncer((this as any)[metadata.propertyKey], stateService, metadata);
|
const syncer = new SessionSyncer(
|
||||||
|
(this as any)[metadata.propertyKey],
|
||||||
|
storageSerice,
|
||||||
|
metadata
|
||||||
|
);
|
||||||
syncer.init();
|
syncer.init();
|
||||||
return syncer;
|
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,9 +1,10 @@
|
|||||||
import { awaitAsync, awaitAsync as flushAsyncObservables } from "@bitwarden/angular/../test-utils";
|
import { awaitAsync } from "@bitwarden/angular/../test-utils";
|
||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
import { BehaviorSubject, ReplaySubject } from "rxjs";
|
import { BehaviorSubject, ReplaySubject } from "rxjs";
|
||||||
|
|
||||||
|
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
||||||
|
|
||||||
import { BrowserApi } from "../../browser/browserApi";
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
import { BrowserStateService } from "../../services/abstractions/browser-state.service";
|
|
||||||
|
|
||||||
import { SessionSyncer } from "./session-syncer";
|
import { SessionSyncer } from "./session-syncer";
|
||||||
import { SyncedItemMetadata } from "./sync-item-metadata";
|
import { SyncedItemMetadata } from "./sync-item-metadata";
|
||||||
@@ -17,7 +18,7 @@ describe("session syncer", () => {
|
|||||||
initializer: (s: string) => s,
|
initializer: (s: string) => s,
|
||||||
initializeAs: "object",
|
initializeAs: "object",
|
||||||
};
|
};
|
||||||
let stateService: MockProxy<BrowserStateService>;
|
let storageService: MockProxy<MemoryStorageService>;
|
||||||
let sut: SessionSyncer;
|
let sut: SessionSyncer;
|
||||||
let behaviorSubject: BehaviorSubject<string>;
|
let behaviorSubject: BehaviorSubject<string>;
|
||||||
|
|
||||||
@@ -29,9 +30,9 @@ describe("session syncer", () => {
|
|||||||
manifest_version: 3,
|
manifest_version: 3,
|
||||||
});
|
});
|
||||||
|
|
||||||
stateService = mock<BrowserStateService>();
|
storageService = mock();
|
||||||
stateService.hasInSessionMemory.mockResolvedValue(false);
|
storageService.has.mockResolvedValue(false);
|
||||||
sut = new SessionSyncer(behaviorSubject, stateService, metaData);
|
sut = new SessionSyncer(behaviorSubject, storageService, metaData);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -43,13 +44,13 @@ describe("session syncer", () => {
|
|||||||
describe("constructor", () => {
|
describe("constructor", () => {
|
||||||
it("should throw if subject is not an instance of Subject", () => {
|
it("should throw if subject is not an instance of Subject", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
new SessionSyncer({} as any, stateService, null);
|
new SessionSyncer({} as any, storageService, null);
|
||||||
}).toThrowError("subject must inherit from Subject");
|
}).toThrowError("subject must inherit from Subject");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should create if either ctor or initializer is provided", () => {
|
it("should create if either ctor or initializer is provided", () => {
|
||||||
expect(
|
expect(
|
||||||
new SessionSyncer(behaviorSubject, stateService, {
|
new SessionSyncer(behaviorSubject, storageService, {
|
||||||
propertyKey,
|
propertyKey,
|
||||||
sessionKey,
|
sessionKey,
|
||||||
ctor: String,
|
ctor: String,
|
||||||
@@ -57,7 +58,7 @@ describe("session syncer", () => {
|
|||||||
})
|
})
|
||||||
).toBeDefined();
|
).toBeDefined();
|
||||||
expect(
|
expect(
|
||||||
new SessionSyncer(behaviorSubject, stateService, {
|
new SessionSyncer(behaviorSubject, storageService, {
|
||||||
propertyKey,
|
propertyKey,
|
||||||
sessionKey,
|
sessionKey,
|
||||||
initializer: (s: any) => s,
|
initializer: (s: any) => s,
|
||||||
@@ -67,7 +68,7 @@ describe("session syncer", () => {
|
|||||||
});
|
});
|
||||||
it("should throw if neither ctor or initializer is provided", () => {
|
it("should throw if neither ctor or initializer is provided", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
new SessionSyncer(behaviorSubject, stateService, {
|
new SessionSyncer(behaviorSubject, storageService, {
|
||||||
propertyKey,
|
propertyKey,
|
||||||
sessionKey,
|
sessionKey,
|
||||||
initializeAs: "object",
|
initializeAs: "object",
|
||||||
@@ -82,7 +83,7 @@ describe("session syncer", () => {
|
|||||||
replaySubject.next("1");
|
replaySubject.next("1");
|
||||||
replaySubject.next("2");
|
replaySubject.next("2");
|
||||||
replaySubject.next("3");
|
replaySubject.next("3");
|
||||||
sut = new SessionSyncer(replaySubject, stateService, metaData);
|
sut = new SessionSyncer(replaySubject, storageService, metaData);
|
||||||
// block observing the subject
|
// block observing the subject
|
||||||
jest.spyOn(sut as any, "observe").mockImplementation();
|
jest.spyOn(sut as any, "observe").mockImplementation();
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ describe("session syncer", () => {
|
|||||||
|
|
||||||
it("should ignore BehaviorSubject's initial value", () => {
|
it("should ignore BehaviorSubject's initial value", () => {
|
||||||
const behaviorSubject = new BehaviorSubject<string>("initial");
|
const behaviorSubject = new BehaviorSubject<string>("initial");
|
||||||
sut = new SessionSyncer(behaviorSubject, stateService, metaData);
|
sut = new SessionSyncer(behaviorSubject, storageService, metaData);
|
||||||
// block observing the subject
|
// block observing the subject
|
||||||
jest.spyOn(sut as any, "observe").mockImplementation();
|
jest.spyOn(sut as any, "observe").mockImplementation();
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ describe("session syncer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should grab an initial value from storage if it exists", async () => {
|
it("should grab an initial value from storage if it exists", async () => {
|
||||||
stateService.hasInSessionMemory.mockResolvedValue(true);
|
storageService.has.mockResolvedValue(true);
|
||||||
//Block a call to update
|
//Block a call to update
|
||||||
const updateSpy = jest.spyOn(sut as any, "update").mockImplementation();
|
const updateSpy = jest.spyOn(sut as any, "update").mockImplementation();
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ describe("session syncer", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not grab an initial value from storage if it does not exist", async () => {
|
it("should not grab an initial value from storage if it does not exist", async () => {
|
||||||
stateService.hasInSessionMemory.mockResolvedValue(false);
|
storageService.has.mockResolvedValue(false);
|
||||||
//Block a call to update
|
//Block a call to update
|
||||||
const updateSpy = jest.spyOn(sut as any, "update").mockImplementation();
|
const updateSpy = jest.spyOn(sut as any, "update").mockImplementation();
|
||||||
|
|
||||||
@@ -139,8 +140,8 @@ describe("session syncer", () => {
|
|||||||
it("should update the session memory", async () => {
|
it("should update the session memory", async () => {
|
||||||
// await finishing of fire-and-forget operation
|
// await finishing of fire-and-forget operation
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
expect(stateService.setInSessionMemory).toHaveBeenCalledTimes(1);
|
expect(storageService.save).toHaveBeenCalledTimes(1);
|
||||||
expect(stateService.setInSessionMemory).toHaveBeenCalledWith(sessionKey, "test");
|
expect(storageService.save).toHaveBeenCalledWith(sessionKey, "test");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update sessionSyncers in other contexts", async () => {
|
it("should update sessionSyncers in other contexts", async () => {
|
||||||
@@ -170,27 +171,29 @@ describe("session syncer", () => {
|
|||||||
it("should ignore messages with the wrong command", async () => {
|
it("should ignore messages with the wrong command", async () => {
|
||||||
await sut.updateFromMessage({ command: "wrong_command", id: sut.id });
|
await sut.updateFromMessage({ command: "wrong_command", id: sut.id });
|
||||||
|
|
||||||
expect(stateService.getFromSessionMemory).not.toHaveBeenCalled();
|
expect(storageService.getBypassCache).not.toHaveBeenCalled();
|
||||||
expect(nextSpy).not.toHaveBeenCalled();
|
expect(nextSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should ignore messages from itself", async () => {
|
it("should ignore messages from itself", async () => {
|
||||||
await sut.updateFromMessage({ command: `${sessionKey}_update`, id: sut.id });
|
await sut.updateFromMessage({ command: `${sessionKey}_update`, id: sut.id });
|
||||||
|
|
||||||
expect(stateService.getFromSessionMemory).not.toHaveBeenCalled();
|
expect(storageService.getBypassCache).not.toHaveBeenCalled();
|
||||||
expect(nextSpy).not.toHaveBeenCalled();
|
expect(nextSpy).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update from message on emit from another instance", async () => {
|
it("should update from message on emit from another instance", async () => {
|
||||||
const builder = jest.fn();
|
const builder = jest.fn();
|
||||||
jest.spyOn(SyncedItemMetadata, "builder").mockReturnValue(builder);
|
jest.spyOn(SyncedItemMetadata, "builder").mockReturnValue(builder);
|
||||||
stateService.getFromSessionMemory.mockResolvedValue("test");
|
storageService.getBypassCache.mockResolvedValue("test");
|
||||||
|
|
||||||
await sut.updateFromMessage({ command: `${sessionKey}_update`, id: "different_id" });
|
await sut.updateFromMessage({ command: `${sessionKey}_update`, id: "different_id" });
|
||||||
await flushAsyncObservables();
|
await awaitAsync();
|
||||||
|
|
||||||
expect(stateService.getFromSessionMemory).toHaveBeenCalledTimes(1);
|
expect(storageService.getBypassCache).toHaveBeenCalledTimes(1);
|
||||||
expect(stateService.getFromSessionMemory).toHaveBeenCalledWith(sessionKey, builder);
|
expect(storageService.getBypassCache).toHaveBeenCalledWith(sessionKey, {
|
||||||
|
deserializer: builder,
|
||||||
|
});
|
||||||
|
|
||||||
expect(nextSpy).toHaveBeenCalledTimes(1);
|
expect(nextSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(nextSpy).toHaveBeenCalledWith("test");
|
expect(nextSpy).toHaveBeenCalledWith("test");
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { BehaviorSubject, concatMap, ReplaySubject, Subject, Subscription } from "rxjs";
|
import { BehaviorSubject, concatMap, ReplaySubject, Subject, Subscription } from "rxjs";
|
||||||
|
|
||||||
|
import { AbstractMemoryStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||||
import { Utils } from "@bitwarden/common/misc/utils";
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
|
|
||||||
import { BrowserApi } from "../../browser/browserApi";
|
import { BrowserApi } from "../../browser/browserApi";
|
||||||
import { BrowserStateService } from "../../services/abstractions/browser-state.service";
|
|
||||||
|
|
||||||
import { SyncedItemMetadata } from "./sync-item-metadata";
|
import { SyncedItemMetadata } from "./sync-item-metadata";
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ export class SessionSyncer {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private subject: Subject<any>,
|
private subject: Subject<any>,
|
||||||
private stateService: BrowserStateService,
|
private memoryStorageService: AbstractMemoryStorageService,
|
||||||
private metaData: SyncedItemMetadata
|
private metaData: SyncedItemMetadata
|
||||||
) {
|
) {
|
||||||
if (!(subject instanceof Subject)) {
|
if (!(subject instanceof Subject)) {
|
||||||
@@ -43,7 +43,7 @@ export class SessionSyncer {
|
|||||||
|
|
||||||
this.observe();
|
this.observe();
|
||||||
// must be synchronous
|
// must be synchronous
|
||||||
this.stateService.hasInSessionMemory(this.metaData.sessionKey).then((hasInSessionMemory) => {
|
this.memoryStorageService.has(this.metaData.sessionKey).then((hasInSessionMemory) => {
|
||||||
if (hasInSessionMemory) {
|
if (hasInSessionMemory) {
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
@@ -86,13 +86,15 @@ export class SessionSyncer {
|
|||||||
|
|
||||||
async update() {
|
async update() {
|
||||||
const builder = SyncedItemMetadata.builder(this.metaData);
|
const builder = SyncedItemMetadata.builder(this.metaData);
|
||||||
const value = await this.stateService.getFromSessionMemory(this.metaData.sessionKey, builder);
|
const value = await this.memoryStorageService.getBypassCache(this.metaData.sessionKey, {
|
||||||
|
deserializer: builder,
|
||||||
|
});
|
||||||
this.ignoreNUpdates = 1;
|
this.ignoreNUpdates = 1;
|
||||||
this.subject.next(value);
|
this.subject.next(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateSession(value: any) {
|
private async updateSession(value: any) {
|
||||||
await this.stateService.setInSessionMemory(this.metaData.sessionKey, value);
|
await this.memoryStorageService.save(this.metaData.sessionKey, value);
|
||||||
await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id });
|
await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,10 @@ import { SendService } from "@bitwarden/common/abstractions/send.service";
|
|||||||
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
import { SettingsService } from "@bitwarden/common/abstractions/settings.service";
|
||||||
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
|
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
|
||||||
import { StateMigrationService } from "@bitwarden/common/abstractions/stateMigration.service";
|
import { StateMigrationService } from "@bitwarden/common/abstractions/stateMigration.service";
|
||||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
import {
|
||||||
|
AbstractMemoryStorageService,
|
||||||
|
AbstractStorageService,
|
||||||
|
} from "@bitwarden/common/abstractions/storage.service";
|
||||||
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
||||||
import { TokenService } from "@bitwarden/common/abstractions/token.service";
|
import { TokenService } from "@bitwarden/common/abstractions/token.service";
|
||||||
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
|
import { TotpService } from "@bitwarden/common/abstractions/totp.service";
|
||||||
@@ -329,7 +332,7 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||||||
useFactory: (
|
useFactory: (
|
||||||
storageService: AbstractStorageService,
|
storageService: AbstractStorageService,
|
||||||
secureStorageService: AbstractStorageService,
|
secureStorageService: AbstractStorageService,
|
||||||
memoryStorageService: AbstractStorageService,
|
memoryStorageService: AbstractMemoryStorageService,
|
||||||
logService: LogServiceAbstraction,
|
logService: LogServiceAbstraction,
|
||||||
stateMigrationService: StateMigrationService
|
stateMigrationService: StateMigrationService
|
||||||
) => {
|
) => {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { Jsonify } from "type-fest";
|
|
||||||
|
|
||||||
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
|
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
|
||||||
import { StorageOptions } from "@bitwarden/common/models/domain/storage-options";
|
import { StorageOptions } from "@bitwarden/common/models/domain/storage-options";
|
||||||
|
|
||||||
@@ -9,9 +7,6 @@ import { BrowserGroupingsComponentState } from "../../models/browserGroupingsCom
|
|||||||
import { BrowserSendComponentState } from "../../models/browserSendComponentState";
|
import { BrowserSendComponentState } from "../../models/browserSendComponentState";
|
||||||
|
|
||||||
export abstract class BrowserStateService extends BaseStateServiceAbstraction<Account> {
|
export abstract class BrowserStateService extends BaseStateServiceAbstraction<Account> {
|
||||||
abstract hasInSessionMemory(key: string): Promise<boolean>;
|
|
||||||
abstract getFromSessionMemory<T>(key: string, deserializer?: (obj: Jsonify<T>) => T): Promise<T>;
|
|
||||||
abstract setInSessionMemory(key: string, value: any): Promise<void>;
|
|
||||||
getBrowserGroupingComponentState: (
|
getBrowserGroupingComponentState: (
|
||||||
options?: StorageOptions
|
options?: StorageOptions
|
||||||
) => Promise<BrowserGroupingsComponentState>;
|
) => Promise<BrowserGroupingsComponentState>;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service";
|
|
||||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||||
import {
|
import {
|
||||||
MemoryStorageServiceInterface,
|
AbstractMemoryStorageService,
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
} from "@bitwarden/common/abstractions/storage.service";
|
} from "@bitwarden/common/abstractions/storage.service";
|
||||||
import { SendType } from "@bitwarden/common/enums/sendType";
|
import { SendType } from "@bitwarden/common/enums/sendType";
|
||||||
@@ -18,9 +17,10 @@ import { BrowserComponentState } from "../models/browserComponentState";
|
|||||||
import { BrowserGroupingsComponentState } from "../models/browserGroupingsComponentState";
|
import { BrowserGroupingsComponentState } from "../models/browserGroupingsComponentState";
|
||||||
import { BrowserSendComponentState } from "../models/browserSendComponentState";
|
import { BrowserSendComponentState } from "../models/browserSendComponentState";
|
||||||
|
|
||||||
import { AbstractKeyGenerationService } from "./abstractions/abstractKeyGeneration.service";
|
|
||||||
import { BrowserStateService } from "./browser-state.service";
|
import { BrowserStateService } from "./browser-state.service";
|
||||||
import { LocalBackedSessionStorageService } from "./localBackedSessionStorage.service";
|
|
||||||
|
// disable session syncing to just test class
|
||||||
|
jest.mock("../decorators/session-sync-observable/");
|
||||||
|
|
||||||
describe("Browser State Service", () => {
|
describe("Browser State Service", () => {
|
||||||
let secureStorageService: MockProxy<AbstractStorageService>;
|
let secureStorageService: MockProxy<AbstractStorageService>;
|
||||||
@@ -50,41 +50,8 @@ describe("Browser State Service", () => {
|
|||||||
state.activeUserId = userId;
|
state.activeUserId = userId;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("direct memory storage access", () => {
|
|
||||||
let memoryStorageService: LocalBackedSessionStorageService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// We need `AbstractCachedStorageService` in the prototype chain to correctly test cache bypass.
|
|
||||||
memoryStorageService = new LocalBackedSessionStorageService(
|
|
||||||
mock<EncryptService>(),
|
|
||||||
mock<AbstractKeyGenerationService>()
|
|
||||||
);
|
|
||||||
|
|
||||||
sut = new BrowserStateService(
|
|
||||||
diskStorageService,
|
|
||||||
secureStorageService,
|
|
||||||
memoryStorageService,
|
|
||||||
logService,
|
|
||||||
stateMigrationService,
|
|
||||||
stateFactory,
|
|
||||||
useAccountCache
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should bypass cache if possible", async () => {
|
|
||||||
const spyBypass = jest
|
|
||||||
.spyOn(memoryStorageService, "getBypassCache")
|
|
||||||
.mockResolvedValue("value");
|
|
||||||
const spyGet = jest.spyOn(memoryStorageService, "get");
|
|
||||||
const result = await sut.getFromSessionMemory("key");
|
|
||||||
expect(spyBypass).toHaveBeenCalled();
|
|
||||||
expect(spyGet).not.toHaveBeenCalled();
|
|
||||||
expect(result).toBe("value");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("state methods", () => {
|
describe("state methods", () => {
|
||||||
let memoryStorageService: MockProxy<AbstractStorageService & MemoryStorageServiceInterface>;
|
let memoryStorageService: MockProxy<AbstractMemoryStorageService>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
memoryStorageService = mock();
|
memoryStorageService = mock();
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
import { Jsonify } from "type-fest";
|
|
||||||
|
|
||||||
import { AbstractCachedStorageService } from "@bitwarden/common/abstractions/storage.service";
|
|
||||||
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
|
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
|
||||||
import { StorageOptions } from "@bitwarden/common/models/domain/storage-options";
|
import { StorageOptions } from "@bitwarden/common/models/domain/storage-options";
|
||||||
import { StateService as BaseStateService } from "@bitwarden/common/services/state.service";
|
import { StateService as BaseStateService } from "@bitwarden/common/services/state.service";
|
||||||
@@ -36,20 +34,6 @@ export class BrowserStateService
|
|||||||
|
|
||||||
protected accountDeserializer = Account.fromJSON;
|
protected accountDeserializer = Account.fromJSON;
|
||||||
|
|
||||||
async hasInSessionMemory(key: string): Promise<boolean> {
|
|
||||||
return await this.memoryStorageService.has(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFromSessionMemory<T>(key: string, deserializer?: (obj: Jsonify<T>) => T): Promise<T> {
|
|
||||||
return this.memoryStorageService instanceof AbstractCachedStorageService
|
|
||||||
? await this.memoryStorageService.getBypassCache<T>(key, { deserializer: deserializer })
|
|
||||||
: await this.memoryStorageService.get<T>(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setInSessionMemory(key: string, value: any): Promise<void> {
|
|
||||||
await this.memoryStorageService.save(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
async addAccount(account: Account) {
|
async addAccount(account: Account) {
|
||||||
// Apply browser overrides to default account values
|
// Apply browser overrides to default account values
|
||||||
account = new Account(account);
|
account = new Account(account);
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { Jsonify } from "type-fest";
|
import { Jsonify } from "type-fest";
|
||||||
|
|
||||||
import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/abstractions/encrypt.service";
|
||||||
import {
|
import { AbstractMemoryStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||||
AbstractCachedStorageService,
|
|
||||||
MemoryStorageServiceInterface,
|
|
||||||
} from "@bitwarden/common/abstractions/storage.service";
|
|
||||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||||
import { MemoryStorageOptions } from "@bitwarden/common/models/domain/storage-options";
|
import { MemoryStorageOptions } from "@bitwarden/common/models/domain/storage-options";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||||
@@ -21,10 +18,7 @@ const keys = {
|
|||||||
sessionKey: "session",
|
sessionKey: "session",
|
||||||
};
|
};
|
||||||
|
|
||||||
export class LocalBackedSessionStorageService
|
export class LocalBackedSessionStorageService extends AbstractMemoryStorageService {
|
||||||
extends AbstractCachedStorageService
|
|
||||||
implements MemoryStorageServiceInterface
|
|
||||||
{
|
|
||||||
private cache = new Map<string, unknown>();
|
private cache = new Map<string, unknown>();
|
||||||
private localStorage = new BrowserLocalStorageService();
|
private localStorage = new BrowserLocalStorageService();
|
||||||
private sessionStorage = new BrowserMemoryStorageService();
|
private sessionStorage = new BrowserMemoryStorageService();
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import {
|
|||||||
} from "@bitwarden/angular/services/injection-tokens";
|
} from "@bitwarden/angular/services/injection-tokens";
|
||||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||||
import { StateMigrationService } from "@bitwarden/common/abstractions/stateMigration.service";
|
import { StateMigrationService } from "@bitwarden/common/abstractions/stateMigration.service";
|
||||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
import {
|
||||||
|
AbstractMemoryStorageService,
|
||||||
|
AbstractStorageService,
|
||||||
|
} from "@bitwarden/common/abstractions/storage.service";
|
||||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||||
import { CipherData } from "@bitwarden/common/models/data/cipher.data";
|
import { CipherData } from "@bitwarden/common/models/data/cipher.data";
|
||||||
import { CollectionData } from "@bitwarden/common/models/data/collection.data";
|
import { CollectionData } from "@bitwarden/common/models/data/collection.data";
|
||||||
@@ -25,7 +28,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: AbstractStorageService,
|
@Inject(MEMORY_STORAGE) memoryStorageService: AbstractMemoryStorageService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
stateMigrationService: StateMigrationService,
|
stateMigrationService: StateMigrationService,
|
||||||
@Inject(STATE_FACTORY) stateFactory: StateFactory<GlobalState, Account>,
|
@Inject(STATE_FACTORY) stateFactory: StateFactory<GlobalState, Account>,
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { InjectionToken } from "@angular/core";
|
import { InjectionToken } from "@angular/core";
|
||||||
|
|
||||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
import {
|
||||||
|
AbstractMemoryStorageService,
|
||||||
|
AbstractStorageService,
|
||||||
|
} from "@bitwarden/common/abstractions/storage.service";
|
||||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||||
|
|
||||||
export const WINDOW = new InjectionToken<Window>("WINDOW");
|
export const WINDOW = new InjectionToken<Window>("WINDOW");
|
||||||
export const MEMORY_STORAGE = new InjectionToken<AbstractStorageService>("MEMORY_STORAGE");
|
export const MEMORY_STORAGE = new InjectionToken<AbstractMemoryStorageService>("MEMORY_STORAGE");
|
||||||
export const SECURE_STORAGE = new InjectionToken<AbstractStorageService>("SECURE_STORAGE");
|
export const SECURE_STORAGE = new InjectionToken<AbstractStorageService>("SECURE_STORAGE");
|
||||||
export const STATE_FACTORY = new InjectionToken<StateFactory>("STATE_FACTORY");
|
export const STATE_FACTORY = new InjectionToken<StateFactory>("STATE_FACTORY");
|
||||||
export const STATE_SERVICE_USE_CACHE = new InjectionToken<boolean>("STATE_SERVICE_USE_CACHE");
|
export const STATE_SERVICE_USE_CACHE = new InjectionToken<boolean>("STATE_SERVICE_USE_CACHE");
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ export abstract class AbstractStorageService {
|
|||||||
abstract remove(key: string, options?: StorageOptions): Promise<void>;
|
abstract remove(key: string, options?: StorageOptions): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class AbstractCachedStorageService extends AbstractStorageService {
|
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>;
|
abstract getBypassCache<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MemoryStorageServiceInterface {
|
|
||||||
get<T>(key: string, options?: MemoryStorageOptions<T>): Promise<T>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import {
|
import { AbstractMemoryStorageService } from "../abstractions/storage.service";
|
||||||
AbstractStorageService,
|
|
||||||
MemoryStorageServiceInterface,
|
|
||||||
} from "../abstractions/storage.service";
|
|
||||||
|
|
||||||
export class MemoryStorageService
|
export class MemoryStorageService extends AbstractMemoryStorageService {
|
||||||
extends AbstractStorageService
|
|
||||||
implements MemoryStorageServiceInterface
|
|
||||||
{
|
|
||||||
private store = new Map<string, any>();
|
private store = new Map<string, any>();
|
||||||
|
|
||||||
get<T>(key: string): Promise<T> {
|
get<T>(key: string): Promise<T> {
|
||||||
@@ -33,4 +27,8 @@ export class MemoryStorageService
|
|||||||
this.store.delete(key);
|
this.store.delete(key);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBypassCache<T>(key: string): Promise<T> {
|
||||||
|
return this.get<T>(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { LogService } from "../abstractions/log.service";
|
|||||||
import { StateService as StateServiceAbstraction } from "../abstractions/state.service";
|
import { StateService as StateServiceAbstraction } from "../abstractions/state.service";
|
||||||
import { StateMigrationService } from "../abstractions/stateMigration.service";
|
import { StateMigrationService } from "../abstractions/stateMigration.service";
|
||||||
import {
|
import {
|
||||||
MemoryStorageServiceInterface,
|
AbstractMemoryStorageService,
|
||||||
AbstractStorageService,
|
AbstractStorageService,
|
||||||
} from "../abstractions/storage.service";
|
} from "../abstractions/storage.service";
|
||||||
import { HtmlStorageLocation } from "../enums/htmlStorageLocation";
|
import { HtmlStorageLocation } from "../enums/htmlStorageLocation";
|
||||||
@@ -87,7 +87,7 @@ export class StateService<
|
|||||||
constructor(
|
constructor(
|
||||||
protected storageService: AbstractStorageService,
|
protected storageService: AbstractStorageService,
|
||||||
protected secureStorageService: AbstractStorageService,
|
protected secureStorageService: AbstractStorageService,
|
||||||
protected memoryStorageService: AbstractStorageService & MemoryStorageServiceInterface,
|
protected memoryStorageService: AbstractMemoryStorageService,
|
||||||
protected logService: LogService,
|
protected logService: LogService,
|
||||||
protected stateMigrationService: StateMigrationService,
|
protected stateMigrationService: StateMigrationService,
|
||||||
protected stateFactory: StateFactory<TGlobalState, TAccount>,
|
protected stateFactory: StateFactory<TGlobalState, TAccount>,
|
||||||
|
|||||||
Reference in New Issue
Block a user