Ps/sync only when changed (#4245)

* Only sync if synced value changes

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>

* Catch error if no one is listening

This occurs if the background tries to update any visualizers, but none
are open.

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: Justin Baur <justindbaur@users.noreply.github.com>

* Allow provided deserializer to handle not found

* Debounce synced items

* Remove object-hash

* Revert "Only sync if synced value changes"

This reverts commit 024fe226d9.

* Test debounce

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
Co-authored-by: Justin Baur <justindbaur@users.noreply.github.com>
Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com>
This commit is contained in:
Matt Gibson 2022-12-15 18:20:46 -05:00 committed by GitHub
parent 8a1230b959
commit 161ff3de28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 41 additions and 11 deletions

View File

@ -32,6 +32,7 @@ describe("session syncer", () => {
stateService = mock<BrowserStateService>();
stateService.hasInSessionMemory.mockResolvedValue(false);
sut = new SessionSyncer(behaviorSubject, stateService, metaData);
jest.spyOn(sut as any, "debounceMs", "get").mockReturnValue(0);
});
afterEach(() => {
@ -88,7 +89,7 @@ describe("session syncer", () => {
sut.init();
expect(sut["ignoreNUpdates"]).toBe(3);
expect(sut["ignoreNUpdates"]).toBe(1);
});
it("should ignore BehaviorSubject's initial value", () => {
@ -128,28 +129,41 @@ describe("session syncer", () => {
describe("a value is emitted on the observable", () => {
let sendMessageSpy: jest.SpyInstance;
beforeEach(() => {
beforeEach(async () => {
sendMessageSpy = jest.spyOn(BrowserApi, "sendMessage");
sut.init();
// allow initial value to be set
await awaitAsync();
behaviorSubject.next("test");
});
it("should update the session memory", async () => {
// await finishing of fire-and-forget operation
await new Promise((resolve) => setTimeout(resolve, 100));
await awaitAsync();
expect(stateService.setInSessionMemory).toHaveBeenCalledTimes(1);
expect(stateService.setInSessionMemory).toHaveBeenCalledWith(sessionKey, "test");
});
it("should update sessionSyncers in other contexts", async () => {
// await finishing of fire-and-forget operation
await new Promise((resolve) => setTimeout(resolve, 100));
await awaitAsync();
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
expect(sendMessageSpy).toHaveBeenCalledWith(`${sessionKey}_update`, { id: sut.id });
});
it("should debounce subject updates", async () => {
behaviorSubject.next("test2");
behaviorSubject.next("test3");
// await finishing of fire-and-forget operation
await awaitAsync();
expect(stateService.setInSessionMemory).toHaveBeenCalledTimes(1);
expect(stateService.setInSessionMemory).toHaveBeenCalledWith(sessionKey, "test3");
});
});
describe("A message is received", () => {

View File

@ -1,4 +1,11 @@
import { BehaviorSubject, concatMap, ReplaySubject, Subject, Subscription } from "rxjs";
import {
BehaviorSubject,
concatMap,
ReplaySubject,
Subject,
Subscription,
debounceTime,
} from "rxjs";
import { Utils } from "@bitwarden/common/misc/utils";
@ -13,6 +20,9 @@ export class SessionSyncer {
// ignore initial values
private ignoreNUpdates = 0;
private get debounceMs() {
return 500;
}
constructor(
private subject: Subject<any>,
@ -30,10 +40,8 @@ export class SessionSyncer {
init() {
switch (this.subject.constructor) {
case ReplaySubject:
// ignore all updates currently in the buffer
this.ignoreNUpdates = (this.subject as any)._buffer.length;
break;
// ignore all updates currently in the buffer
case ReplaySubject: // N = 1 due to debounce
case BehaviorSubject:
this.ignoreNUpdates = 1;
break;
@ -58,6 +66,7 @@ export class SessionSyncer {
// contexts. If so, this is handled by destruction of the context.
this.subscription = this.subject
.pipe(
debounceTime(this.debounceMs),
concatMap(async (next) => {
if (this.ignoreNUpdates > 0) {
this.ignoreNUpdates -= 1;
@ -92,8 +101,15 @@ export class SessionSyncer {
}
private async updateSession(value: any) {
await this.stateService.setInSessionMemory(this.metaData.sessionKey, value);
await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id });
try {
await this.stateService.setInSessionMemory(this.metaData.sessionKey, value);
await BrowserApi.sendMessage(this.updateMessageCommand, { id: this.id });
} catch (e) {
if (e.message === "Could not establish connection. Receiving end does not exist.") {
return;
}
throw e;
}
}
private get updateMessageCommand() {