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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user