mirror of
https://github.com/bitwarden/browser
synced 2026-02-11 22:13:32 +00:00
Implement get$
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Subject, filter, firstValueFrom, map, merge, timeout } from "rxjs";
|
||||
import { Subject, defer, filter, firstValueFrom, map, merge, shareReplay, timeout } from "rxjs";
|
||||
|
||||
import { CollectionService, DefaultCollectionService } from "@bitwarden/admin-console/common";
|
||||
import {
|
||||
@@ -103,7 +103,6 @@ import { StateFactory } from "@bitwarden/common/platform/factories/state-factory
|
||||
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
|
||||
// eslint-disable-next-line no-restricted-imports -- Used for dependency creation
|
||||
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
|
||||
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||
import { clearCaches } from "@bitwarden/common/platform/misc/sequentialize";
|
||||
import { Account } from "@bitwarden/common/platform/models/domain/account";
|
||||
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
|
||||
@@ -262,7 +261,6 @@ import { BrowserPlatformUtilsService } from "../platform/services/platform-utils
|
||||
import { PopupViewCacheBackgroundService } from "../platform/services/popup-view-cache-background.service";
|
||||
import { BrowserSdkClientFactory } from "../platform/services/sdk/browser-sdk-client-factory";
|
||||
import { BackgroundTaskSchedulerService } from "../platform/services/task-scheduler/background-task-scheduler.service";
|
||||
import { BackgroundMemoryStorageService } from "../platform/storage/background-memory-storage.service";
|
||||
import { BrowserStorageServiceProvider } from "../platform/storage/browser-storage-service.provider";
|
||||
import { OffscreenStorageService } from "../platform/storage/offscreen-storage.service";
|
||||
import { SyncServiceListener } from "../platform/sync/sync-service.listener";
|
||||
@@ -463,20 +461,9 @@ export default class MainBackground {
|
||||
this.offscreenDocumentService,
|
||||
);
|
||||
|
||||
this.secureStorageService = this.storageService; // secure storage is not supported in browsers, so we use local storage and warn users when it is used
|
||||
|
||||
if (BrowserApi.isManifestVersion(3)) {
|
||||
// manifest v3 can reuse the same storage. They are split for v2 due to lacking a good sync mechanism, which isn't true for v3
|
||||
this.memoryStorageForStateProviders = new BrowserMemoryStorageService(); // mv3 stores to storage.session
|
||||
this.memoryStorageService = this.memoryStorageForStateProviders;
|
||||
} else {
|
||||
this.memoryStorageForStateProviders = new BackgroundMemoryStorageService(); // mv2 stores to memory
|
||||
this.memoryStorageService = this.memoryStorageForStateProviders;
|
||||
}
|
||||
|
||||
if (BrowserApi.isManifestVersion(3)) {
|
||||
// Creates a session key for mv3 storage of large memory items
|
||||
const sessionKey = new Lazy(async () => {
|
||||
const sessionKey = defer(async () => {
|
||||
// Key already in session storage
|
||||
const sessionStorage = new BrowserMemoryStorageService();
|
||||
const existingKey = await sessionStorage.get<SymmetricCryptoKey>("session-key");
|
||||
@@ -495,7 +482,7 @@ export default class MainBackground {
|
||||
);
|
||||
await sessionStorage.save("session-key", derivedKey);
|
||||
return derivedKey;
|
||||
});
|
||||
}).pipe(shareReplay({ bufferSize: 1, refCount: false }));
|
||||
|
||||
this.largeObjectMemoryStorageForStateProviders = new LocalBackedSessionStorageService(
|
||||
sessionKey,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { filter, mergeMap } from "rxjs";
|
||||
import { concat, filter, map, mergeMap, Observable, share } from "rxjs";
|
||||
|
||||
import {
|
||||
AbstractStorageService,
|
||||
@@ -41,9 +41,14 @@ export const objToStore = (obj: any) => {
|
||||
export default abstract class AbstractChromeStorageService
|
||||
implements AbstractStorageService, ObservableStorageService
|
||||
{
|
||||
onChanged$: Observable<{ [key: string]: chrome.storage.StorageChange }>;
|
||||
updates$;
|
||||
|
||||
constructor(protected chromeStorageApi: chrome.storage.StorageArea) {
|
||||
this.onChanged$ = fromChromeEvent(this.chromeStorageApi.onChanged).pipe(
|
||||
map(([change]) => change),
|
||||
share(),
|
||||
);
|
||||
this.updates$ = fromChromeEvent(this.chromeStorageApi.onChanged).pipe(
|
||||
filter(([changes]) => {
|
||||
// Our storage services support changing only one key at a time. If more are changed, it's due to
|
||||
@@ -75,6 +80,32 @@ export default abstract class AbstractChromeStorageService
|
||||
return true;
|
||||
}
|
||||
|
||||
get$<T>(key: string): Observable<T | null> {
|
||||
const initialValue$ = new Observable<T>((subscriber) => {
|
||||
this.chromeStorageApi.get(key, (obj) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
subscriber.error(chrome.runtime.lastError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj != null && obj[key] != null) {
|
||||
subscriber.next(this.processGetObject<T>(obj[key]));
|
||||
} else {
|
||||
subscriber.next(null);
|
||||
}
|
||||
|
||||
subscriber.complete();
|
||||
});
|
||||
});
|
||||
|
||||
const keyUpdates$: Observable<T> = this.onChanged$.pipe(
|
||||
filter((change) => change[key] != null),
|
||||
map((change) => change[key].newValue ?? null),
|
||||
);
|
||||
|
||||
return concat(initialValue$, keyUpdates$);
|
||||
}
|
||||
|
||||
async get<T>(key: string): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.chromeStorageApi.get(key, (obj) => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { of } from "rxjs";
|
||||
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { FakeStorageService, makeEncString } from "@bitwarden/common/spec";
|
||||
@@ -28,7 +28,7 @@ describe("LocalBackedSessionStorage", () => {
|
||||
logService = mock<LogService>();
|
||||
|
||||
sut = new LocalBackedSessionStorageService(
|
||||
new Lazy(async () => sessionKey),
|
||||
of(sessionKey),
|
||||
localStorage,
|
||||
encryptService,
|
||||
platformUtilsService,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Subject } from "rxjs";
|
||||
import { concat, EMPTY, firstValueFrom, Observable, Subject } from "rxjs";
|
||||
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
StorageUpdate,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { compareValues } from "@bitwarden/common/platform/misc/compare-values";
|
||||
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
@@ -31,7 +30,7 @@ export class LocalBackedSessionStorageService
|
||||
updates$ = this.updatesSubject.asObservable();
|
||||
|
||||
constructor(
|
||||
private readonly sessionKey: Lazy<Promise<SymmetricCryptoKey>>,
|
||||
private readonly sessionKey$: Observable<SymmetricCryptoKey>,
|
||||
private readonly localStorage: AbstractStorageService,
|
||||
private readonly encryptService: EncryptService,
|
||||
private readonly platformUtilsService: PlatformUtilsService,
|
||||
@@ -71,12 +70,28 @@ export class LocalBackedSessionStorageService
|
||||
return this.cache[key] as T;
|
||||
}
|
||||
|
||||
const value = await this.getLocalSessionValue(await this.sessionKey.get(), key);
|
||||
const value = await this.getLocalSessionValue(await firstValueFrom(this.sessionKey$), key);
|
||||
|
||||
this.cache[key] = value;
|
||||
return value as T;
|
||||
}
|
||||
|
||||
get$<T>(key: string) {
|
||||
const initialValue$ = new Observable<T>((subscriber) => {
|
||||
//
|
||||
const cachedValue = this.cache[key] as T;
|
||||
if (cachedValue !== undefined) {
|
||||
subscriber.next(cachedValue);
|
||||
subscriber.complete();
|
||||
}
|
||||
|
||||
// Get value from session
|
||||
});
|
||||
|
||||
// TODO: Connect something to get updates from elsewhere
|
||||
return concat(initialValue$, EMPTY);
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
return (await this.get(key)) != null;
|
||||
}
|
||||
@@ -104,13 +119,13 @@ export class LocalBackedSessionStorageService
|
||||
|
||||
this.cache[key] = obj;
|
||||
await this.updateLocalSessionValue(key, obj);
|
||||
this.updatesSubject.next({ key, updateType: "save" });
|
||||
// this.updatesSubject.next({ key, updateType: "save" });
|
||||
}
|
||||
|
||||
async remove(key: string): Promise<void> {
|
||||
this.cache[key] = null;
|
||||
await this.updateLocalSessionValue(key, null);
|
||||
this.updatesSubject.next({ key, updateType: "remove" });
|
||||
// this.updatesSubject.next({ key, updateType: "remove" });
|
||||
}
|
||||
|
||||
private async getLocalSessionValue(encKey: SymmetricCryptoKey, key: string): Promise<unknown> {
|
||||
@@ -140,7 +155,10 @@ export class LocalBackedSessionStorageService
|
||||
}
|
||||
|
||||
const valueJson = JSON.stringify(value);
|
||||
const encValue = await this.encryptService.encrypt(valueJson, await this.sessionKey.get());
|
||||
const encValue = await this.encryptService.encrypt(
|
||||
valueJson,
|
||||
await firstValueFrom(this.sessionKey$),
|
||||
);
|
||||
await this.localStorage.save(this.sessionStorageKey(key), encValue.encryptedString);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,12 +33,6 @@ export class BackgroundMemoryStorageService extends MemoryStorageService {
|
||||
data: Array.from(Object.keys(this.store)),
|
||||
});
|
||||
});
|
||||
this.updates$.subscribe((update) => {
|
||||
this.broadcastMessage({
|
||||
action: "subject_update",
|
||||
data: update,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async onMessageFromForeground(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Observable, Subject, filter, firstValueFrom, map } from "rxjs";
|
||||
import { EMPTY, Observable, Subject, concat, filter, firstValueFrom, map } from "rxjs";
|
||||
|
||||
import {
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
StorageUpdate,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
@@ -11,7 +12,10 @@ import { fromChromeEvent } from "../browser/from-chrome-event";
|
||||
import { MemoryStoragePortMessage } from "./port-messages";
|
||||
import { portName } from "./port-name";
|
||||
|
||||
export class ForegroundMemoryStorageService extends AbstractStorageService {
|
||||
export class ForegroundMemoryStorageService
|
||||
extends AbstractStorageService
|
||||
implements ObservableStorageService
|
||||
{
|
||||
private _port: chrome.runtime.Port;
|
||||
private _backgroundResponses$: Observable<MemoryStoragePortMessage>;
|
||||
private updatesSubject = new Subject<StorageUpdate>();
|
||||
@@ -19,13 +23,10 @@ export class ForegroundMemoryStorageService extends AbstractStorageService {
|
||||
get valuesRequireDeserialization(): boolean {
|
||||
return true;
|
||||
}
|
||||
updates$;
|
||||
|
||||
constructor(private partitionName?: string) {
|
||||
super();
|
||||
|
||||
this.updates$ = this.updatesSubject.asObservable();
|
||||
|
||||
let name = portName(chrome.storage.session);
|
||||
if (this.partitionName) {
|
||||
name = `${name}_${this.partitionName}`;
|
||||
@@ -59,6 +60,23 @@ export class ForegroundMemoryStorageService extends AbstractStorageService {
|
||||
async get<T>(key: string): Promise<T> {
|
||||
return await this.delegateToBackground<T>("get", key);
|
||||
}
|
||||
|
||||
get$<T>(key: string) {
|
||||
return concat(
|
||||
new Observable<T>((subscriber) => {
|
||||
this.delegateToBackground<T>("get", key)
|
||||
.then((value) => {
|
||||
subscriber.next(value);
|
||||
subscriber.complete();
|
||||
})
|
||||
.catch((err) => {
|
||||
subscriber.error(err);
|
||||
});
|
||||
}),
|
||||
EMPTY, // TODO: Make a connection to background to hear about updates
|
||||
);
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
return await this.delegateToBackground<boolean>("has", key);
|
||||
}
|
||||
@@ -104,7 +122,7 @@ export class ForegroundMemoryStorageService extends AbstractStorageService {
|
||||
private handleInitialize(data: string[]) {
|
||||
// TODO: this isn't a save, but we don't have a better indicator for this
|
||||
data.forEach((key) => {
|
||||
this.updatesSubject.next({ key, updateType: "save" });
|
||||
// this.updatesSubject.next({ key, updateType: "save" });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* @jest-environment ../../libs/shared/test.environment.ts
|
||||
*/
|
||||
|
||||
import { trackEmissions } from "@bitwarden/common/../spec/utils";
|
||||
|
||||
import { mockPorts } from "../../../spec/mock-port.spec-util";
|
||||
|
||||
import { BackgroundMemoryStorageService } from "./background-memory-storage.service";
|
||||
@@ -54,15 +52,15 @@ describe("foreground background memory storage interaction", () => {
|
||||
expect(actionSpy).toHaveBeenCalledWith(key);
|
||||
});
|
||||
|
||||
test("background updates push to foreground", async () => {
|
||||
const key = "key";
|
||||
const value = "value";
|
||||
const updateType = "save";
|
||||
const emissions = trackEmissions(foreground.updates$);
|
||||
await background.save(key, value);
|
||||
// test("background updates push to foreground", async () => {
|
||||
// const key = "key";
|
||||
// const value = "value";
|
||||
// const updateType = "save";
|
||||
// const emissions = trackEmissions(foreground.updates$);
|
||||
// await background.save(key, value);
|
||||
|
||||
expect(emissions).toEqual([{ key, updateType }]);
|
||||
});
|
||||
// expect(emissions).toEqual([{ key, updateType }]);
|
||||
// });
|
||||
|
||||
test("background should message only the requesting foreground", async () => {
|
||||
const secondForeground = new ForegroundMemoryStorageService();
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
import {
|
||||
AbstractStorageService,
|
||||
StorageUpdate,
|
||||
} from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
|
||||
import { HtmlStorageLocation } from "@bitwarden/common/platform/enums";
|
||||
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
|
||||
|
||||
@Injectable()
|
||||
export class HtmlStorageService implements AbstractStorageService {
|
||||
private updatesSubject = new Subject<StorageUpdate>();
|
||||
|
||||
get defaultOptions(): StorageOptions {
|
||||
return { htmlStorageLocation: HtmlStorageLocation.Session };
|
||||
}
|
||||
@@ -21,11 +15,6 @@ export class HtmlStorageService implements AbstractStorageService {
|
||||
get valuesRequireDeserialization(): boolean {
|
||||
return true;
|
||||
}
|
||||
updates$;
|
||||
|
||||
constructor() {
|
||||
this.updates$ = this.updatesSubject.asObservable();
|
||||
}
|
||||
|
||||
get<T>(key: string, options: StorageOptions = this.defaultOptions): Promise<T> {
|
||||
let json: string = null;
|
||||
@@ -69,7 +58,6 @@ export class HtmlStorageService implements AbstractStorageService {
|
||||
window.sessionStorage.setItem(key, json);
|
||||
break;
|
||||
}
|
||||
this.updatesSubject.next({ key, updateType: "save" });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -83,7 +71,6 @@ export class HtmlStorageService implements AbstractStorageService {
|
||||
window.sessionStorage.removeItem(key);
|
||||
break;
|
||||
}
|
||||
this.updatesSubject.next({ key, updateType: "remove" });
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MockProxy, mock } from "jest-mock-extended";
|
||||
import { Subject } from "rxjs";
|
||||
import { concat, filter, map, Observable, of, Subject } from "rxjs";
|
||||
|
||||
import {
|
||||
AbstractStorageService,
|
||||
@@ -20,11 +20,11 @@ export class FakeStorageService implements AbstractStorageService, ObservableSto
|
||||
* amount of calls. It is not recommended to use this to mock implementations as
|
||||
* they are not respected.
|
||||
*/
|
||||
mock: MockProxy<AbstractStorageService>;
|
||||
mock: MockProxy<AbstractStorageService & ObservableStorageService>;
|
||||
|
||||
constructor(initial?: Record<string, unknown>) {
|
||||
this.store = initial ?? {};
|
||||
this.mock = mock<AbstractStorageService>();
|
||||
this.mock = mock<AbstractStorageService & ObservableStorageService>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,8 +48,15 @@ export class FakeStorageService implements AbstractStorageService, ObservableSto
|
||||
return this._valuesRequireDeserialization;
|
||||
}
|
||||
|
||||
get updates$() {
|
||||
return this.updatesSubject.asObservable();
|
||||
get$<T>(key: string): Observable<T> {
|
||||
this.mock.get$(key);
|
||||
return concat(
|
||||
of((this.store[key] as T) ?? null),
|
||||
this.updatesSubject.pipe(
|
||||
filter((update) => update.key == key),
|
||||
map((update) => (this.store[key] as T) ?? null),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
get<T>(key: string, options?: StorageOptions): Promise<T> {
|
||||
|
||||
@@ -9,12 +9,7 @@ export type StorageUpdate = {
|
||||
};
|
||||
|
||||
export interface ObservableStorageService {
|
||||
/**
|
||||
* Provides an {@link Observable} that represents a stream of updates that
|
||||
* have happened in this storage service or in the storage this service provides
|
||||
* an interface to.
|
||||
*/
|
||||
get updates$(): Observable<StorageUpdate>;
|
||||
get$<T>(key: string): Observable<T>;
|
||||
}
|
||||
|
||||
export abstract class AbstractStorageService {
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
import { AbstractStorageService, StorageUpdate } from "../abstractions/storage.service";
|
||||
import { AbstractStorageService } from "../abstractions/storage.service";
|
||||
|
||||
export class MemoryStorageService extends AbstractStorageService {
|
||||
protected store = new Map<string, unknown>();
|
||||
private updatesSubject = new Subject<StorageUpdate>();
|
||||
|
||||
get valuesRequireDeserialization(): boolean {
|
||||
return false;
|
||||
}
|
||||
get updates$() {
|
||||
return this.updatesSubject.asObservable();
|
||||
}
|
||||
|
||||
get<T>(key: string): Promise<T> {
|
||||
if (this.store.has(key)) {
|
||||
@@ -35,13 +29,11 @@ export class MemoryStorageService extends AbstractStorageService {
|
||||
// Needed to ensure ownership of all memory by the context running the storage service
|
||||
const toStore = structuredClone(obj);
|
||||
this.store.set(key, toStore);
|
||||
this.updatesSubject.next({ key, updateType: "save" });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
remove(key: string): Promise<void> {
|
||||
this.store.delete(key);
|
||||
this.updatesSubject.next({ key, updateType: "remove" });
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import {
|
||||
Observable,
|
||||
ReplaySubject,
|
||||
defer,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
merge,
|
||||
share,
|
||||
switchMap,
|
||||
tap,
|
||||
timeout,
|
||||
timer,
|
||||
} from "rxjs";
|
||||
import { Observable, ReplaySubject, firstValueFrom, map, share, tap, timeout, timer } from "rxjs";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { StorageKey } from "../../../types/state";
|
||||
@@ -44,22 +32,16 @@ export abstract class StateBase<T, KeyDef extends KeyDefinitionRequirements<T>>
|
||||
protected readonly keyDefinition: KeyDef,
|
||||
protected readonly logService: LogService,
|
||||
) {
|
||||
const storageUpdate$ = storageService.updates$.pipe(
|
||||
filter((storageUpdate) => storageUpdate.key === key),
|
||||
switchMap(async (storageUpdate) => {
|
||||
if (storageUpdate.updateType === "remove") {
|
||||
return null;
|
||||
let state$ = this.storageService.get$<T>(key).pipe(
|
||||
map((value) => {
|
||||
if (this.storageService.valuesRequireDeserialization) {
|
||||
return this.keyDefinition.deserializer(value as Jsonify<T>);
|
||||
}
|
||||
|
||||
return await getStoredValue(key, storageService, keyDefinition.deserializer);
|
||||
return value;
|
||||
}),
|
||||
);
|
||||
|
||||
let state$ = merge(
|
||||
defer(() => getStoredValue(key, storageService, keyDefinition.deserializer)),
|
||||
storageUpdate$,
|
||||
);
|
||||
|
||||
if (keyDefinition.debug.enableRetrievalLogging) {
|
||||
state$ = state$.pipe(
|
||||
tap({
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Subject } from "rxjs";
|
||||
import { concat, filter, map, of, Subject } from "rxjs";
|
||||
|
||||
import {
|
||||
AbstractStorageService,
|
||||
ObservableStorageService,
|
||||
StorageUpdate,
|
||||
StorageUpdateType,
|
||||
} from "../../abstractions/storage.service";
|
||||
|
||||
export class MemoryStorageService
|
||||
@@ -13,14 +13,11 @@ export class MemoryStorageService
|
||||
implements ObservableStorageService
|
||||
{
|
||||
protected store: Record<string, string> = {};
|
||||
private updatesSubject = new Subject<StorageUpdate>();
|
||||
private updatesSubject = new Subject<{ key: string; updateType: StorageUpdateType }>();
|
||||
|
||||
get valuesRequireDeserialization(): boolean {
|
||||
return true;
|
||||
}
|
||||
get updates$() {
|
||||
return this.updatesSubject.asObservable();
|
||||
}
|
||||
|
||||
get<T>(key: string): Promise<T> {
|
||||
const json = this.store[key];
|
||||
@@ -31,6 +28,31 @@ export class MemoryStorageService
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
private getValue<T>(key: string): T | null {
|
||||
const json = this.store[key];
|
||||
if (json) {
|
||||
return JSON.parse(json) as T;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
get$<T>(key: string) {
|
||||
return concat(
|
||||
of(this.getValue<T>(key)),
|
||||
this.updatesSubject.pipe(
|
||||
filter((update) => update.key === key),
|
||||
map((update) => {
|
||||
if (update.updateType === "remove") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.getValue<T>(key);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
return (await this.get(key)) != null;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ export class PrimarySecondaryStorageService
|
||||
implements AbstractStorageService, ObservableStorageService
|
||||
{
|
||||
// Only follow the primary storage service as updates should all be done to both
|
||||
updates$ = this.primaryStorageService.updates$;
|
||||
|
||||
constructor(
|
||||
private readonly primaryStorageService: AbstractStorageService & ObservableStorageService,
|
||||
@@ -25,6 +24,11 @@ export class PrimarySecondaryStorageService
|
||||
return this.primaryStorageService.valuesRequireDeserialization;
|
||||
}
|
||||
|
||||
get$<T>(key: string) {
|
||||
// TODO: What is best here?
|
||||
return this.primaryStorageService.get$<T>(key);
|
||||
}
|
||||
|
||||
async get<T>(key: string, options?: StorageOptions): Promise<T> {
|
||||
const primaryValue = await this.primaryStorageService.get<T>(key, options);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import { concat, filter, map, Observable, of, Subject } from "rxjs";
|
||||
|
||||
import {
|
||||
AbstractStorageService,
|
||||
@@ -10,24 +10,35 @@ import {
|
||||
import { StorageOptions } from "../models/domain/storage-options";
|
||||
|
||||
export class WindowStorageService implements AbstractStorageService, ObservableStorageService {
|
||||
private readonly updatesSubject = new Subject<StorageUpdate>();
|
||||
private readonly updatesSubject = new Subject<StorageUpdate & { value: unknown }>();
|
||||
|
||||
updates$: Observable<StorageUpdate>;
|
||||
constructor(private readonly storage: Storage) {
|
||||
this.updates$ = this.updatesSubject.asObservable();
|
||||
}
|
||||
constructor(private readonly storage: Storage) {}
|
||||
|
||||
get valuesRequireDeserialization(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
get<T>(key: string, options?: StorageOptions): Promise<T> {
|
||||
get$<T>(key: string): Observable<T> {
|
||||
return concat(
|
||||
of(this.getValue(key)),
|
||||
this.updatesSubject.pipe(
|
||||
filter((update) => update.key === key),
|
||||
map((update) => update.value as T),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private getValue<T>(key: string) {
|
||||
const jsonValue = this.storage.getItem(key);
|
||||
if (jsonValue != null) {
|
||||
return Promise.resolve(JSON.parse(jsonValue) as T);
|
||||
return JSON.parse(jsonValue) as T;
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
get<T>(key: string, options?: StorageOptions): Promise<T> {
|
||||
return Promise.resolve(this.getValue(key));
|
||||
}
|
||||
|
||||
async has(key: string, options?: StorageOptions): Promise<boolean> {
|
||||
@@ -44,12 +55,12 @@ export class WindowStorageService implements AbstractStorageService, ObservableS
|
||||
}
|
||||
|
||||
this.storage.setItem(key, JSON.stringify(obj));
|
||||
this.updatesSubject.next({ key, updateType: "save" });
|
||||
this.updatesSubject.next({ key, updateType: "save", value: obj });
|
||||
}
|
||||
|
||||
remove(key: string, options?: StorageOptions): Promise<void> {
|
||||
this.storage.removeItem(key);
|
||||
this.updatesSubject.next({ key, updateType: "remove" });
|
||||
this.updatesSubject.next({ key, updateType: "remove", value: null });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user