Add updates$ stream to existing storageServices

This commit is contained in:
Matt Gibson 2023-10-05 14:34:19 -04:00
parent 604671d70c
commit 823d9546fe
No known key found for this signature in database
GPG Key ID: 0B3AF4C7D6472DD1
9 changed files with 54 additions and 27 deletions

View File

@ -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<T>(key: string): Promise<T> {
@ -22,11 +22,7 @@ export default abstract class AbstractChromeStorageService implements AbstractSt
async save(key: string, obj: any): Promise<void> {
if (obj == null) {
// Fix safari not liking null in set
return new Promise<void>((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<void>((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<void> {
return new Promise<void>((resolve) => {
this.chromeStorageApi.remove(key, () => {
this.updatesSubject.next({ key, value: null, updateType: "remove" });
resolve();
});
});

View File

@ -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<any>;
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<any> {
async save(key: string, obj: any): Promise<void> {
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<any> {
async remove(key: string): Promise<void> {
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;
});

View File

@ -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<T>(key: string): Promise<T> {
const value = await this.storageService.get<string>(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<any> {
async save(key: string, obj: any): Promise<void> {
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<any> {
return this.storageService.remove(this.makeProtectedStorageKey(key));
async remove(key: string): Promise<void> {
await this.storageService.remove(this.makeProtectedStorageKey(key));
this.updatesSubject.next({ key, value: null, updateType: "remove" });
return;
}
private async encrypt(plainValue: string): Promise<string> {

View File

@ -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<T>(key: string, options?: StorageOptions): Promise<T> {
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<any> {
async save<T>(key: string, obj: T, options?: StorageOptions): Promise<void> {
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<any> {
async remove(key: string, options?: StorageOptions): Promise<void> {
await ipcRenderer.invoke("keytar", {
action: "deletePassword",
key: key,
keySuffix: options?.keySuffix ?? "",
});
this.updatesSubject.next({ key, value: null, updateType: "remove" });
}
}

View File

@ -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<T>(key: string): Promise<T> {
return ipcRenderer.invoke("storageService", {
action: "get",
@ -17,18 +17,20 @@ export class ElectronRendererStorageService implements AbstractStorageService {
});
}
save(key: string, obj: any): Promise<any> {
return ipcRenderer.invoke("storageService", {
async save<T>(key: string, obj: T): Promise<void> {
await ipcRenderer.invoke("storageService", {
action: "save",
key: key,
obj: obj,
});
this.updatesSubject.next({ key, value: obj, updateType: "save" });
}
remove(key: string): Promise<any> {
return ipcRenderer.invoke("storageService", {
async remove(key: string): Promise<void> {
await ipcRenderer.invoke("storageService", {
action: "remove",
key: key,
});
this.updatesSubject.next({ key, value: null, updateType: "remove" });
}
}

View File

@ -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<void> {
this.store.delete(key);
this.updatesSubject.next({ key, value: null, updateType: "remove" });
return Promise.resolve();
}
}

View File

@ -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();
}
}

View File

@ -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<T>(key: string, options?: StorageOptions): Promise<T>;
abstract has(key: string, options?: StorageOptions): Promise<boolean>;
abstract save<T>(key: string, obj: T, options?: StorageOptions): Promise<void>;

View File

@ -1,7 +1,7 @@
import { AbstractMemoryStorageService } from "../abstractions/storage.service";
export class MemoryStorageService extends AbstractMemoryStorageService {
private store = new Map<string, any>();
private store = new Map<string, unknown>();
get<T>(key: string): Promise<T> {
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<any> {
save<T>(key: string, obj: T): Promise<void> {
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<any> {
remove(key: string): Promise<void> {
this.store.delete(key);
this.updatesSubject.next({ key, value: null, updateType: "remove" });
return Promise.resolve();
}