1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

Split session key and synced item property key (#3317)

This commit is contained in:
Matt Gibson
2022-08-16 09:59:50 -06:00
committed by GitHub
parent 16c41b823b
commit 7c3facec80
7 changed files with 31 additions and 21 deletions

View File

@@ -39,7 +39,7 @@ export function browserSession<TCtor extends Constructor<any>>(constructor: TCto
} }
buildSyncer(metadata: SyncedItemMetadata, stateService: StateService) { buildSyncer(metadata: SyncedItemMetadata, stateService: StateService) {
const syncer = new SessionSyncer((this as any)[metadata.key], stateService, metadata); const syncer = new SessionSyncer((this as any)[metadata.propertyKey], stateService, metadata);
syncer.init(); syncer.init();
return syncer; return syncer;
} }

View File

@@ -14,7 +14,8 @@ describe("sessionSync decorator", () => {
const testClass = new TestClass(); const testClass = new TestClass();
expect((testClass as any).__syncedItemMetadata).toEqual([ expect((testClass as any).__syncedItemMetadata).toEqual([
expect.objectContaining({ expect.objectContaining({
key: "TestClass_testProperty", propertyKey: "testProperty",
sessionKey: "TestClass_testProperty",
ctor: ctor, ctor: ctor,
initializer: initializer, initializer: initializer,
}), }),

View File

@@ -42,7 +42,8 @@ export function sessionSync<T>(buildOptions: BuildOptions<T>) {
} }
p.__syncedItemMetadata.push({ p.__syncedItemMetadata.push({
key: `${prototype.constructor.name}_${propertyKey}`, propertyKey,
sessionKey: `${prototype.constructor.name}_${propertyKey}`,
ctor: buildOptions.ctor, ctor: buildOptions.ctor,
initializer: buildOptions.initializer, initializer: buildOptions.initializer,
initializeAsArray: buildOptions.initializeAsArray, initializeAsArray: buildOptions.initializeAsArray,

View File

@@ -7,8 +7,9 @@ import { StateService } from "../../services/abstractions/state.service";
import { SessionSyncer } from "./session-syncer"; import { SessionSyncer } from "./session-syncer";
describe("session syncer", () => { describe("session syncer", () => {
const key = "Test__behaviorSubject"; const propertyKey = "behaviorSubject";
const metaData = { key, initializer: (s: string) => s }; const sessionKey = "Test__" + propertyKey;
const metaData = { propertyKey, sessionKey, initializer: (s: string) => s };
let stateService: MockProxy<StateService>; let stateService: MockProxy<StateService>;
let sut: SessionSyncer; let sut: SessionSyncer;
let behaviorSubject: BehaviorSubject<string>; let behaviorSubject: BehaviorSubject<string>;
@@ -40,18 +41,19 @@ describe("session syncer", () => {
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, { key: key, ctor: String }) new SessionSyncer(behaviorSubject, stateService, { propertyKey, sessionKey, ctor: String })
).toBeDefined(); ).toBeDefined();
expect( expect(
new SessionSyncer(behaviorSubject, stateService, { new SessionSyncer(behaviorSubject, stateService, {
key: key, propertyKey,
sessionKey,
initializer: (s: any) => s, initializer: (s: any) => s,
}) })
).toBeDefined(); ).toBeDefined();
}); });
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, { key: key }); new SessionSyncer(behaviorSubject, stateService, { propertyKey, sessionKey });
}).toThrowError("ctor or initializer must be provided"); }).toThrowError("ctor or initializer must be provided");
}); });
}); });
@@ -96,7 +98,7 @@ describe("session syncer", () => {
// 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(stateService.setInSessionMemory).toHaveBeenCalledTimes(1);
expect(stateService.setInSessionMemory).toHaveBeenCalledWith(key, "test"); expect(stateService.setInSessionMemory).toHaveBeenCalledWith(sessionKey, "test");
}); });
it("should update sessionSyncers in other contexts", async () => { it("should update sessionSyncers in other contexts", async () => {
@@ -104,7 +106,7 @@ describe("session syncer", () => {
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100));
expect(sendMessageSpy).toHaveBeenCalledTimes(1); expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(sendMessageSpy).toHaveBeenCalledWith(`${key}_update`, { id: sut.id }); expect(sendMessageSpy).toHaveBeenCalledWith(`${sessionKey}_update`, { id: sut.id });
}); });
}); });
@@ -131,7 +133,7 @@ describe("session syncer", () => {
}); });
it("should ignore messages from itself", async () => { it("should ignore messages from itself", async () => {
await sut.updateFromMessage({ command: `${key}_update`, id: sut.id }); await sut.updateFromMessage({ command: `${sessionKey}_update`, id: sut.id });
expect(stateService.getFromSessionMemory).not.toHaveBeenCalled(); expect(stateService.getFromSessionMemory).not.toHaveBeenCalled();
expect(nextSpy).not.toHaveBeenCalled(); expect(nextSpy).not.toHaveBeenCalled();
@@ -140,10 +142,10 @@ describe("session syncer", () => {
it("should update from message on emit from another instance", async () => { it("should update from message on emit from another instance", async () => {
stateService.getFromSessionMemory.mockResolvedValue("test"); stateService.getFromSessionMemory.mockResolvedValue("test");
await sut.updateFromMessage({ command: `${key}_update`, id: "different_id" }); await sut.updateFromMessage({ command: `${sessionKey}_update`, id: "different_id" });
expect(stateService.getFromSessionMemory).toHaveBeenCalledTimes(1); expect(stateService.getFromSessionMemory).toHaveBeenCalledTimes(1);
expect(stateService.getFromSessionMemory).toHaveBeenCalledWith(key); expect(stateService.getFromSessionMemory).toHaveBeenCalledWith(sessionKey);
expect(nextSpy).toHaveBeenCalledTimes(1); expect(nextSpy).toHaveBeenCalledTimes(1);
expect(nextSpy).toHaveBeenCalledWith("test"); expect(nextSpy).toHaveBeenCalledWith("test");

View File

@@ -62,18 +62,18 @@ export class SessionSyncer {
if (message.command != this.updateMessageCommand || message.id === this.id) { if (message.command != this.updateMessageCommand || message.id === this.id) {
return; return;
} }
const keyValuePair = await this.stateService.getFromSessionMemory(this.metaData.key); const keyValuePair = await this.stateService.getFromSessionMemory(this.metaData.sessionKey);
const value = SyncedItemMetadata.buildFromKeyValuePair(keyValuePair, this.metaData); const value = SyncedItemMetadata.buildFromKeyValuePair(keyValuePair, this.metaData);
this.ignoreNextUpdate = true; this.ignoreNextUpdate = true;
this.behaviorSubject.next(value); this.behaviorSubject.next(value);
} }
private async updateSession(value: any) { private async updateSession(value: any) {
await this.stateService.setInSessionMemory(this.metaData.key, value); await this.stateService.setInSessionMemory(this.metaData.sessionKey, value);
await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id }); await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id });
} }
private get updateMessageCommand() { private get updateMessageCommand() {
return `${this.metaData.key}_update`; return `${this.metaData.sessionKey}_update`;
} }
} }

View File

@@ -1,5 +1,6 @@
export class SyncedItemMetadata { export class SyncedItemMetadata {
key: string; propertyKey: string;
sessionKey: string;
ctor?: new () => any; ctor?: new () => any;
initializer?: (keyValuePair: any) => any; initializer?: (keyValuePair: any) => any;
initializeAsArray?: boolean; initializeAsArray?: boolean;

View File

@@ -1,6 +1,7 @@
import { SyncedItemMetadata } from "./sync-item-metadata"; import { SyncedItemMetadata } from "./sync-item-metadata";
describe("build from key value pair", () => { describe("build from key value pair", () => {
const propertyKey = "propertyKey";
const key = "key"; const key = "key";
const initializer = (s: any) => "used initializer"; const initializer = (s: any) => "used initializer";
class TestClass {} class TestClass {}
@@ -10,7 +11,8 @@ describe("build from key value pair", () => {
const actual = SyncedItemMetadata.buildFromKeyValuePair( const actual = SyncedItemMetadata.buildFromKeyValuePair(
{}, {},
{ {
key: "key", propertyKey,
sessionKey: "key",
initializer: initializer, initializer: initializer,
} }
); );
@@ -21,7 +23,8 @@ describe("build from key value pair", () => {
it("should call ctor if provided", () => { it("should call ctor if provided", () => {
const expected = { provided: "value" }; const expected = { provided: "value" };
const actual = SyncedItemMetadata.buildFromKeyValuePair(expected, { const actual = SyncedItemMetadata.buildFromKeyValuePair(expected, {
key: key, propertyKey,
sessionKey: key,
ctor: ctor, ctor: ctor,
}); });
@@ -33,7 +36,8 @@ describe("build from key value pair", () => {
const actual = SyncedItemMetadata.buildFromKeyValuePair( const actual = SyncedItemMetadata.buildFromKeyValuePair(
{}, {},
{ {
key: key, propertyKey,
sessionKey: key,
initializer: initializer, initializer: initializer,
ctor: ctor, ctor: ctor,
} }
@@ -44,7 +48,8 @@ describe("build from key value pair", () => {
it("should honor initialize as array", () => { it("should honor initialize as array", () => {
const actual = SyncedItemMetadata.buildFromKeyValuePair([1, 2], { const actual = SyncedItemMetadata.buildFromKeyValuePair([1, 2], {
key: key, propertyKey,
sessionKey: key,
initializer: initializer, initializer: initializer,
initializeAsArray: true, initializeAsArray: true,
}); });