1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-02 18:17:46 +01:00

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"; 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; protected abstract chromeStorageApi: chrome.storage.StorageArea;
async get<T>(key: string): Promise<T> { 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> { async save(key: string, obj: any): Promise<void> {
if (obj == null) { if (obj == null) {
// Fix safari not liking null in set // Fix safari not liking null in set
return new Promise<void>((resolve) => { return this.remove(key);
this.chromeStorageApi.remove(key, () => {
resolve();
});
});
} }
if (obj instanceof Set) { if (obj instanceof Set) {
@ -36,6 +32,7 @@ export default abstract class AbstractChromeStorageService implements AbstractSt
const keyedObj = { [key]: obj }; const keyedObj = { [key]: obj };
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
this.chromeStorageApi.set(keyedObj, () => { this.chromeStorageApi.set(keyedObj, () => {
this.updatesSubject.next({ key, value: obj, updateType: "save" });
resolve(); resolve();
}); });
}); });
@ -44,6 +41,7 @@ export default abstract class AbstractChromeStorageService implements AbstractSt
async remove(key: string): Promise<void> { async remove(key: string): Promise<void> {
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
this.chromeStorageApi.remove(key, () => { this.chromeStorageApi.remove(key, () => {
this.updatesSubject.next({ key, value: null, updateType: "remove" });
resolve(); resolve();
}); });
}); });

View File

@ -19,7 +19,7 @@ const retries: OperationOptions = {
factor: 2, factor: 2,
}; };
export class LowdbStorageService implements AbstractStorageService { export class LowdbStorageService extends AbstractStorageService {
protected dataFilePath: string; protected dataFilePath: string;
private db: lowdb.LowdbSync<any>; private db: lowdb.LowdbSync<any>;
private defaults: any; private defaults: any;
@ -32,6 +32,7 @@ export class LowdbStorageService implements AbstractStorageService {
private allowCache = false, private allowCache = false,
private requireLock = false private requireLock = false
) { ) {
super();
this.defaults = defaults; this.defaults = defaults;
} }
@ -119,21 +120,23 @@ export class LowdbStorageService implements AbstractStorageService {
return this.get(key).then((v) => v != null); 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(); await this.waitForReady();
return this.lockDbFile(() => { return this.lockDbFile(() => {
this.readForNoCache(); this.readForNoCache();
this.db.set(key, obj).write(); this.db.set(key, obj).write();
this.updatesSubject.next({ key, value: obj, updateType: "save" });
this.logService.debug(`Successfully wrote ${key} to db`); this.logService.debug(`Successfully wrote ${key} to db`);
return; return;
}); });
} }
async remove(key: string): Promise<any> { async remove(key: string): Promise<void> {
await this.waitForReady(); await this.waitForReady();
return this.lockDbFile(() => { return this.lockDbFile(() => {
this.readForNoCache(); this.readForNoCache();
this.db.unset(key).write(); this.db.unset(key).write();
this.updatesSubject.next({ key, value: null, updateType: "remove" });
this.logService.debug(`Successfully removed ${key} from db`); this.logService.debug(`Successfully removed ${key} from db`);
return; 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 { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
export class NodeEnvSecureStorageService implements AbstractStorageService { export class NodeEnvSecureStorageService extends AbstractStorageService {
constructor( constructor(
private storageService: AbstractStorageService, private storageService: AbstractStorageService,
private logService: LogService, private logService: LogService,
private cryptoService: () => CryptoService private cryptoService: () => CryptoService
) {} ) {
super();
}
async get<T>(key: string): Promise<T> { async get<T>(key: string): Promise<T> {
const value = await this.storageService.get<string>(this.makeProtectedStorageKey(key)); 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; 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) { if (obj == null) {
return this.remove(key); return this.remove(key);
} }
@ -35,10 +37,13 @@ export class NodeEnvSecureStorageService implements AbstractStorageService {
} }
const protectedObj = await this.encrypt(obj); const protectedObj = await this.encrypt(obj);
await this.storageService.save(this.makeProtectedStorageKey(key), protectedObj); await this.storageService.save(this.makeProtectedStorageKey(key), protectedObj);
this.updatesSubject.next({ key, value: obj, updateType: "save" });
} }
remove(key: string): Promise<any> { async remove(key: string): Promise<void> {
return this.storageService.remove(this.makeProtectedStorageKey(key)); await this.storageService.remove(this.makeProtectedStorageKey(key));
this.updatesSubject.next({ key, value: null, updateType: "remove" });
return;
} }
private async encrypt(plainValue: string): Promise<string> { 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 { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options"; 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> { async get<T>(key: string, options?: StorageOptions): Promise<T> {
const val = await ipcRenderer.invoke("keytar", { const val = await ipcRenderer.invoke("keytar", {
action: "getPassword", action: "getPassword",
@ -22,20 +22,22 @@ export class ElectronRendererSecureStorageService implements AbstractStorageServ
return !!val; 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", { await ipcRenderer.invoke("keytar", {
action: "setPassword", action: "setPassword",
key: key, key: key,
keySuffix: options?.keySuffix ?? "", keySuffix: options?.keySuffix ?? "",
value: JSON.stringify(obj), 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", { await ipcRenderer.invoke("keytar", {
action: "deletePassword", action: "deletePassword",
key: key, key: key,
keySuffix: options?.keySuffix ?? "", 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"; 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> { get<T>(key: string): Promise<T> {
return ipcRenderer.invoke("storageService", { return ipcRenderer.invoke("storageService", {
action: "get", action: "get",
@ -17,18 +17,20 @@ export class ElectronRendererStorageService implements AbstractStorageService {
}); });
} }
save(key: string, obj: any): Promise<any> { async save<T>(key: string, obj: T): Promise<void> {
return ipcRenderer.invoke("storageService", { await ipcRenderer.invoke("storageService", {
action: "save", action: "save",
key: key, key: key,
obj: obj, obj: obj,
}); });
this.updatesSubject.next({ key, value: obj, updateType: "save" });
} }
remove(key: string): Promise<any> { async remove(key: string): Promise<void> {
return ipcRenderer.invoke("storageService", { await ipcRenderer.invoke("storageService", {
action: "remove", action: "remove",
key: key, 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">; type Options = BaseOptions<"get"> | BaseOptions<"has"> | SaveOptions | BaseOptions<"remove">;
export class ElectronStorageService implements AbstractStorageService { export class ElectronStorageService extends AbstractStorageService {
private store: ElectronStore; private store: ElectronStore;
constructor(dir: string, defaults = {}) { constructor(dir: string, defaults = {}) {
super();
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
NodeUtils.mkdirpSync(dir, "700"); NodeUtils.mkdirpSync(dir, "700");
} }
@ -75,11 +76,13 @@ export class ElectronStorageService implements AbstractStorageService {
obj = Array.from(obj); obj = Array.from(obj);
} }
this.store.set(key, obj); this.store.set(key, obj);
this.updatesSubject.next({ key, value: obj, updateType: "save" });
return Promise.resolve(); return Promise.resolve();
} }
remove(key: string): Promise<void> { remove(key: string): Promise<void> {
this.store.delete(key); this.store.delete(key);
this.updatesSubject.next({ key, value: null, updateType: "remove" });
return Promise.resolve(); 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"; import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
@Injectable() @Injectable()
export class HtmlStorageService implements AbstractStorageService { export class HtmlStorageService extends AbstractStorageService {
get defaultOptions(): StorageOptions { get defaultOptions(): StorageOptions {
return { htmlStorageLocation: HtmlStorageLocation.Session }; return { htmlStorageLocation: HtmlStorageLocation.Session };
} }
@ -52,6 +52,7 @@ export class HtmlStorageService implements AbstractStorageService {
window.sessionStorage.setItem(key, json); window.sessionStorage.setItem(key, json);
break; break;
} }
this.updatesSubject.next({ key, value: obj, updateType: "save" });
return Promise.resolve(); return Promise.resolve();
} }
@ -65,6 +66,7 @@ export class HtmlStorageService implements AbstractStorageService {
window.sessionStorage.removeItem(key); window.sessionStorage.removeItem(key);
break; break;
} }
this.updatesSubject.next({ key, value: null, updateType: "remove" });
return Promise.resolve(); return Promise.resolve();
} }
} }

View File

@ -1,6 +1,16 @@
import { Subject } from "rxjs";
import { MemoryStorageOptions, StorageOptions } from "../models/domain/storage-options"; import { MemoryStorageOptions, StorageOptions } from "../models/domain/storage-options";
export type StorageUpdateType = "save" | "remove";
export abstract class AbstractStorageService { 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 get<T>(key: string, options?: StorageOptions): Promise<T>;
abstract has(key: string, options?: StorageOptions): Promise<boolean>; abstract has(key: string, options?: StorageOptions): Promise<boolean>;
abstract save<T>(key: string, obj: T, options?: StorageOptions): Promise<void>; abstract save<T>(key: string, obj: T, options?: StorageOptions): Promise<void>;

View File

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