2022-12-16 00:20:46 +01:00
|
|
|
import {
|
|
|
|
BehaviorSubject,
|
|
|
|
concatMap,
|
|
|
|
ReplaySubject,
|
|
|
|
Subject,
|
|
|
|
Subscription,
|
|
|
|
debounceTime,
|
|
|
|
} from "rxjs";
|
2022-08-16 14:05:03 +02:00
|
|
|
|
|
|
|
import { Utils } from "@bitwarden/common/misc/utils";
|
|
|
|
|
|
|
|
import { BrowserApi } from "../../browser/browserApi";
|
2022-11-23 23:26:57 +01:00
|
|
|
import { BrowserStateService } from "../../services/abstractions/browser-state.service";
|
2022-08-16 14:05:03 +02:00
|
|
|
|
|
|
|
import { SyncedItemMetadata } from "./sync-item-metadata";
|
|
|
|
|
|
|
|
export class SessionSyncer {
|
|
|
|
subscription: Subscription;
|
|
|
|
id = Utils.newGuid();
|
|
|
|
|
2022-11-23 23:26:57 +01:00
|
|
|
// ignore initial values
|
|
|
|
private ignoreNUpdates = 0;
|
2022-12-16 00:20:46 +01:00
|
|
|
private get debounceMs() {
|
|
|
|
return 500;
|
|
|
|
}
|
2022-08-16 14:05:03 +02:00
|
|
|
|
|
|
|
constructor(
|
2022-11-23 23:26:57 +01:00
|
|
|
private subject: Subject<any>,
|
|
|
|
private stateService: BrowserStateService,
|
2022-08-16 14:05:03 +02:00
|
|
|
private metaData: SyncedItemMetadata
|
|
|
|
) {
|
2022-11-23 23:26:57 +01:00
|
|
|
if (!(subject instanceof Subject)) {
|
|
|
|
throw new Error("subject must inherit from Subject");
|
2022-08-16 14:05:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (metaData.ctor == null && metaData.initializer == null) {
|
|
|
|
throw new Error("ctor or initializer must be provided");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
init() {
|
2022-11-23 23:26:57 +01:00
|
|
|
switch (this.subject.constructor) {
|
2022-12-16 00:20:46 +01:00
|
|
|
// ignore all updates currently in the buffer
|
|
|
|
case ReplaySubject: // N = 1 due to debounce
|
2022-11-23 23:26:57 +01:00
|
|
|
case BehaviorSubject:
|
|
|
|
this.ignoreNUpdates = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2022-08-16 14:05:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
this.observe();
|
2022-12-06 23:26:42 +01:00
|
|
|
// must be synchronous
|
|
|
|
this.stateService.hasInSessionMemory(this.metaData.sessionKey).then((hasInSessionMemory) => {
|
|
|
|
if (hasInSessionMemory) {
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
});
|
2022-11-23 23:26:57 +01:00
|
|
|
|
2022-08-16 14:05:03 +02:00
|
|
|
this.listenForUpdates();
|
|
|
|
}
|
|
|
|
|
|
|
|
private observe() {
|
|
|
|
// 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.
|
2022-11-23 23:26:57 +01:00
|
|
|
this.subscription = this.subject
|
2022-08-26 18:09:28 +02:00
|
|
|
.pipe(
|
2022-12-16 00:20:46 +01:00
|
|
|
debounceTime(this.debounceMs),
|
2022-08-26 18:09:28 +02:00
|
|
|
concatMap(async (next) => {
|
2022-11-23 23:26:57 +01:00
|
|
|
if (this.ignoreNUpdates > 0) {
|
|
|
|
this.ignoreNUpdates -= 1;
|
2022-08-26 18:09:28 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
await this.updateSession(next);
|
|
|
|
})
|
|
|
|
)
|
|
|
|
.subscribe();
|
2022-08-16 14:05:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private listenForUpdates() {
|
|
|
|
// This is an unawaited promise, but it will be executed asynchronously in the background.
|
|
|
|
BrowserApi.messageListener(
|
|
|
|
this.updateMessageCommand,
|
|
|
|
async (message) => await this.updateFromMessage(message)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateFromMessage(message: any) {
|
|
|
|
if (message.command != this.updateMessageCommand || message.id === this.id) {
|
|
|
|
return;
|
|
|
|
}
|
2022-11-23 23:26:57 +01:00
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
async update() {
|
2022-09-22 14:51:14 +02:00
|
|
|
const builder = SyncedItemMetadata.builder(this.metaData);
|
|
|
|
const value = await this.stateService.getFromSessionMemory(this.metaData.sessionKey, builder);
|
2022-11-23 23:26:57 +01:00
|
|
|
this.ignoreNUpdates = 1;
|
|
|
|
this.subject.next(value);
|
2022-08-16 14:05:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private async updateSession(value: any) {
|
2022-12-16 00:20:46 +01:00
|
|
|
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;
|
|
|
|
}
|
2022-08-16 14:05:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private get updateMessageCommand() {
|
2022-08-16 17:59:50 +02:00
|
|
|
return `${this.metaData.sessionKey}_update`;
|
2022-08-16 14:05:03 +02:00
|
|
|
}
|
|
|
|
}
|