encrypted_store.ts (2843B)
1 import { err_msg, handle_err, type IdbClientConfig, type ResolveError } from "@radroots/utils"; 2 import { createStore, type UseStore } from "idb-keyval"; 3 import type { CryptoDecryptOutcome, CryptoStoreConfig, IWebCryptoService, LegacyKeyConfig } from "../crypto/types.js"; 4 import { WebCryptoService } from "../crypto/service.js"; 5 import { idb_store_ensure } from "./store.js"; 6 7 export type WebEncryptedStoreConfig = { 8 idb_config: IdbClientConfig; 9 store_id: string; 10 idb_error: string; 11 legacy_key?: LegacyKeyConfig | null; 12 iv_length?: number; 13 crypto_service?: IWebCryptoService; 14 }; 15 16 export interface IWebEncryptedStore { 17 get_config(): IdbClientConfig; 18 get_store_id(): string; 19 get_store(): Promise<ResolveError<UseStore>>; 20 encrypt_bytes(bytes: Uint8Array): Promise<ResolveError<Uint8Array>>; 21 decrypt_record(blob: Uint8Array): Promise<ResolveError<CryptoDecryptOutcome>>; 22 } 23 24 export class WebEncryptedStore implements IWebEncryptedStore { 25 private readonly config: IdbClientConfig; 26 private readonly store_id: string; 27 private readonly idb_error: string; 28 private readonly crypto: IWebCryptoService; 29 private store: UseStore | null = null; 30 private store_ready: Promise<void> | null = null; 31 32 constructor(config: WebEncryptedStoreConfig) { 33 this.config = config.idb_config; 34 this.store_id = config.store_id; 35 this.idb_error = config.idb_error; 36 this.crypto = config.crypto_service ?? new WebCryptoService(); 37 const store_config: CryptoStoreConfig = { 38 store_id: this.store_id, 39 iv_length: config.iv_length 40 }; 41 if (config.legacy_key) store_config.legacy_key = config.legacy_key; 42 this.crypto.register_store_config(store_config); 43 } 44 45 public get_config(): IdbClientConfig { 46 return { 47 database: this.config.database, 48 store: this.config.store 49 }; 50 } 51 52 public get_store_id(): string { 53 return this.store_id; 54 } 55 56 public async get_store(): Promise<ResolveError<UseStore>> { 57 if (typeof indexedDB === "undefined") return err_msg(this.idb_error); 58 try { 59 if (!this.store_ready) this.store_ready = idb_store_ensure(this.config.database, this.config.store); 60 await this.store_ready; 61 if (!this.store) this.store = createStore(this.config.database, this.config.store); 62 return this.store; 63 } catch (e) { 64 return handle_err(e); 65 } 66 } 67 68 public async encrypt_bytes(bytes: Uint8Array): Promise<ResolveError<Uint8Array>> { 69 return await this.crypto.encrypt(this.store_id, bytes); 70 } 71 72 public async decrypt_record(blob: Uint8Array): Promise<ResolveError<CryptoDecryptOutcome>> { 73 return await this.crypto.decrypt_record(this.store_id, blob); 74 } 75 }