From 823d9546fe059da23ce3353782b4f4134bb36262 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Thu, 5 Oct 2023 14:34:19 -0400 Subject: [PATCH] Add updates$ stream to existing storageServices --- .../abstract-chrome-storage-api.service.ts | 10 ++++------ .../platform/services/lowdb-storage.service.ts | 9 ++++++--- .../services/node-env-secure-storage.service.ts | 15 ++++++++++----- .../electron-renderer-secure-storage.service.ts | 8 +++++--- .../services/electron-renderer-storage.service.ts | 12 +++++++----- .../platform/services/electron-storage.service.ts | 5 ++++- apps/web/src/app/core/html-storage.service.ts | 4 +++- .../src/platform/abstractions/storage.service.ts | 10 ++++++++++ .../platform/services/memory-storage.service.ts | 8 +++++--- 9 files changed, 54 insertions(+), 27 deletions(-) diff --git a/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts b/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts index 5e9c14fd3c..26fb41ee28 100644 --- a/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts +++ b/apps/browser/src/platform/services/abstractions/abstract-chrome-storage-api.service.ts @@ -1,6 +1,6 @@ import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; -export default abstract class AbstractChromeStorageService implements AbstractStorageService { +export default abstract class AbstractChromeStorageService extends AbstractStorageService { protected abstract chromeStorageApi: chrome.storage.StorageArea; async get(key: string): Promise { @@ -22,11 +22,7 @@ export default abstract class AbstractChromeStorageService implements AbstractSt async save(key: string, obj: any): Promise { if (obj == null) { // Fix safari not liking null in set - return new Promise((resolve) => { - this.chromeStorageApi.remove(key, () => { - resolve(); - }); - }); + return this.remove(key); } if (obj instanceof Set) { @@ -36,6 +32,7 @@ export default abstract class AbstractChromeStorageService implements AbstractSt const keyedObj = { [key]: obj }; return new Promise((resolve) => { this.chromeStorageApi.set(keyedObj, () => { + this.updatesSubject.next({ key, value: obj, updateType: "save" }); resolve(); }); }); @@ -44,6 +41,7 @@ export default abstract class AbstractChromeStorageService implements AbstractSt async remove(key: string): Promise { return new Promise((resolve) => { this.chromeStorageApi.remove(key, () => { + this.updatesSubject.next({ key, value: null, updateType: "remove" }); resolve(); }); }); diff --git a/apps/cli/src/platform/services/lowdb-storage.service.ts b/apps/cli/src/platform/services/lowdb-storage.service.ts index bd5895907c..abc1a71cb9 100644 --- a/apps/cli/src/platform/services/lowdb-storage.service.ts +++ b/apps/cli/src/platform/services/lowdb-storage.service.ts @@ -19,7 +19,7 @@ const retries: OperationOptions = { factor: 2, }; -export class LowdbStorageService implements AbstractStorageService { +export class LowdbStorageService extends AbstractStorageService { protected dataFilePath: string; private db: lowdb.LowdbSync; private defaults: any; @@ -32,6 +32,7 @@ export class LowdbStorageService implements AbstractStorageService { private allowCache = false, private requireLock = false ) { + super(); this.defaults = defaults; } @@ -119,21 +120,23 @@ export class LowdbStorageService implements AbstractStorageService { return this.get(key).then((v) => v != null); } - async save(key: string, obj: any): Promise { + async save(key: string, obj: any): Promise { await this.waitForReady(); return this.lockDbFile(() => { this.readForNoCache(); this.db.set(key, obj).write(); + this.updatesSubject.next({ key, value: obj, updateType: "save" }); this.logService.debug(`Successfully wrote ${key} to db`); return; }); } - async remove(key: string): Promise { + async remove(key: string): Promise { await this.waitForReady(); return this.lockDbFile(() => { this.readForNoCache(); this.db.unset(key).write(); + this.updatesSubject.next({ key, value: null, updateType: "remove" }); this.logService.debug(`Successfully removed ${key} from db`); return; }); diff --git a/apps/cli/src/platform/services/node-env-secure-storage.service.ts b/apps/cli/src/platform/services/node-env-secure-storage.service.ts index 491ec32cb6..4624aeb4ce 100644 --- a/apps/cli/src/platform/services/node-env-secure-storage.service.ts +++ b/apps/cli/src/platform/services/node-env-secure-storage.service.ts @@ -5,12 +5,14 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; -export class NodeEnvSecureStorageService implements AbstractStorageService { +export class NodeEnvSecureStorageService extends AbstractStorageService { constructor( private storageService: AbstractStorageService, private logService: LogService, private cryptoService: () => CryptoService - ) {} + ) { + super(); + } async get(key: string): Promise { const value = await this.storageService.get(this.makeProtectedStorageKey(key)); @@ -25,7 +27,7 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { return (await this.get(key)) != null; } - async save(key: string, obj: any): Promise { + async save(key: string, obj: any): Promise { if (obj == null) { return this.remove(key); } @@ -35,10 +37,13 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { } const protectedObj = await this.encrypt(obj); await this.storageService.save(this.makeProtectedStorageKey(key), protectedObj); + this.updatesSubject.next({ key, value: obj, updateType: "save" }); } - remove(key: string): Promise { - return this.storageService.remove(this.makeProtectedStorageKey(key)); + async remove(key: string): Promise { + await this.storageService.remove(this.makeProtectedStorageKey(key)); + this.updatesSubject.next({ key, value: null, updateType: "remove" }); + return; } private async encrypt(plainValue: string): Promise { diff --git a/apps/desktop/src/platform/services/electron-renderer-secure-storage.service.ts b/apps/desktop/src/platform/services/electron-renderer-secure-storage.service.ts index ce3923af5f..2fa277f070 100644 --- a/apps/desktop/src/platform/services/electron-renderer-secure-storage.service.ts +++ b/apps/desktop/src/platform/services/electron-renderer-secure-storage.service.ts @@ -3,7 +3,7 @@ import { ipcRenderer } from "electron"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; -export class ElectronRendererSecureStorageService implements AbstractStorageService { +export class ElectronRendererSecureStorageService extends AbstractStorageService { async get(key: string, options?: StorageOptions): Promise { const val = await ipcRenderer.invoke("keytar", { action: "getPassword", @@ -22,20 +22,22 @@ export class ElectronRendererSecureStorageService implements AbstractStorageServ return !!val; } - async save(key: string, obj: any, options?: StorageOptions): Promise { + async save(key: string, obj: T, options?: StorageOptions): Promise { await ipcRenderer.invoke("keytar", { action: "setPassword", key: key, keySuffix: options?.keySuffix ?? "", value: JSON.stringify(obj), }); + this.updatesSubject.next({ key, value: obj, updateType: "save" }); } - async remove(key: string, options?: StorageOptions): Promise { + async remove(key: string, options?: StorageOptions): Promise { await ipcRenderer.invoke("keytar", { action: "deletePassword", key: key, keySuffix: options?.keySuffix ?? "", }); + this.updatesSubject.next({ key, value: null, updateType: "remove" }); } } diff --git a/apps/desktop/src/platform/services/electron-renderer-storage.service.ts b/apps/desktop/src/platform/services/electron-renderer-storage.service.ts index 3810e10ab5..c4eadd1551 100644 --- a/apps/desktop/src/platform/services/electron-renderer-storage.service.ts +++ b/apps/desktop/src/platform/services/electron-renderer-storage.service.ts @@ -2,7 +2,7 @@ import { ipcRenderer } from "electron"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; -export class ElectronRendererStorageService implements AbstractStorageService { +export class ElectronRendererStorageService extends AbstractStorageService { get(key: string): Promise { return ipcRenderer.invoke("storageService", { action: "get", @@ -17,18 +17,20 @@ export class ElectronRendererStorageService implements AbstractStorageService { }); } - save(key: string, obj: any): Promise { - return ipcRenderer.invoke("storageService", { + async save(key: string, obj: T): Promise { + await ipcRenderer.invoke("storageService", { action: "save", key: key, obj: obj, }); + this.updatesSubject.next({ key, value: obj, updateType: "save" }); } - remove(key: string): Promise { - return ipcRenderer.invoke("storageService", { + async remove(key: string): Promise { + await ipcRenderer.invoke("storageService", { action: "remove", key: key, }); + this.updatesSubject.next({ key, value: null, updateType: "remove" }); } } diff --git a/apps/desktop/src/platform/services/electron-storage.service.ts b/apps/desktop/src/platform/services/electron-storage.service.ts index 51fb9cfe9c..2488203aeb 100644 --- a/apps/desktop/src/platform/services/electron-storage.service.ts +++ b/apps/desktop/src/platform/services/electron-storage.service.ts @@ -33,10 +33,11 @@ interface SaveOptions extends BaseOptions<"save"> { type Options = BaseOptions<"get"> | BaseOptions<"has"> | SaveOptions | BaseOptions<"remove">; -export class ElectronStorageService implements AbstractStorageService { +export class ElectronStorageService extends AbstractStorageService { private store: ElectronStore; constructor(dir: string, defaults = {}) { + super(); if (!fs.existsSync(dir)) { NodeUtils.mkdirpSync(dir, "700"); } @@ -75,11 +76,13 @@ export class ElectronStorageService implements AbstractStorageService { obj = Array.from(obj); } this.store.set(key, obj); + this.updatesSubject.next({ key, value: obj, updateType: "save" }); return Promise.resolve(); } remove(key: string): Promise { this.store.delete(key); + this.updatesSubject.next({ key, value: null, updateType: "remove" }); return Promise.resolve(); } } diff --git a/apps/web/src/app/core/html-storage.service.ts b/apps/web/src/app/core/html-storage.service.ts index 370f475a50..fb74f2c777 100644 --- a/apps/web/src/app/core/html-storage.service.ts +++ b/apps/web/src/app/core/html-storage.service.ts @@ -5,7 +5,7 @@ import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/ import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; @Injectable() -export class HtmlStorageService implements AbstractStorageService { +export class HtmlStorageService extends AbstractStorageService { get defaultOptions(): StorageOptions { return { htmlStorageLocation: HtmlStorageLocation.Session }; } @@ -52,6 +52,7 @@ export class HtmlStorageService implements AbstractStorageService { window.sessionStorage.setItem(key, json); break; } + this.updatesSubject.next({ key, value: obj, updateType: "save" }); return Promise.resolve(); } @@ -65,6 +66,7 @@ export class HtmlStorageService implements AbstractStorageService { window.sessionStorage.removeItem(key); break; } + this.updatesSubject.next({ key, value: null, updateType: "remove" }); return Promise.resolve(); } } diff --git a/libs/common/src/platform/abstractions/storage.service.ts b/libs/common/src/platform/abstractions/storage.service.ts index cdd731f9d6..79cf8745b2 100644 --- a/libs/common/src/platform/abstractions/storage.service.ts +++ b/libs/common/src/platform/abstractions/storage.service.ts @@ -1,6 +1,16 @@ +import { Subject } from "rxjs"; + import { MemoryStorageOptions, StorageOptions } from "../models/domain/storage-options"; +export type StorageUpdateType = "save" | "remove"; + export abstract class AbstractStorageService { + protected readonly updatesSubject = new Subject<{ + key: string; + value: unknown; + updateType: StorageUpdateType; + }>(); + readonly updates$ = this.updatesSubject.asObservable(); abstract get(key: string, options?: StorageOptions): Promise; abstract has(key: string, options?: StorageOptions): Promise; abstract save(key: string, obj: T, options?: StorageOptions): Promise; diff --git a/libs/common/src/platform/services/memory-storage.service.ts b/libs/common/src/platform/services/memory-storage.service.ts index b4d65c0ffa..41c65ee7d5 100644 --- a/libs/common/src/platform/services/memory-storage.service.ts +++ b/libs/common/src/platform/services/memory-storage.service.ts @@ -1,7 +1,7 @@ import { AbstractMemoryStorageService } from "../abstractions/storage.service"; export class MemoryStorageService extends AbstractMemoryStorageService { - private store = new Map(); + private store = new Map(); get(key: string): Promise { if (this.store.has(key)) { @@ -15,16 +15,18 @@ export class MemoryStorageService extends AbstractMemoryStorageService { return (await this.get(key)) != null; } - save(key: string, obj: any): Promise { + save(key: string, obj: T): Promise { if (obj == null) { return this.remove(key); } this.store.set(key, obj); + this.updatesSubject.next({ key, value: obj, updateType: "save" }); return Promise.resolve(); } - remove(key: string): Promise { + remove(key: string): Promise { this.store.delete(key); + this.updatesSubject.next({ key, value: null, updateType: "remove" }); return Promise.resolve(); }