From 7098580d1080fdd1ae6ed740289f2bde8dfa90a5 Mon Sep 17 00:00:00 2001 From: Shibo Lyu Date: Fri, 11 Oct 2024 02:19:01 +0800 Subject: [PATCH] feat: [wip] ActKeyStore --- src/lib/actKeyStore.ts | 119 ++++++++++++++++++++++++++++++++++ src/lib/db/schema/identity.ts | 2 +- 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/lib/actKeyStore.ts diff --git a/src/lib/actKeyStore.ts b/src/lib/actKeyStore.ts new file mode 100644 index 0000000..31315e1 --- /dev/null +++ b/src/lib/actKeyStore.ts @@ -0,0 +1,119 @@ +// Loosely based on https://github.com/infotechinc/key-storage-in-browser/blob/master/keystore.js + +import { BlahKeyPair, BlahPublicKey } from '@blah-im/core/crypto'; + +type SavedObject = { + idKeyId: string; + actKeyId: string; + privateKey: CryptoKey; +}; + +type QueryResult = { + idKeyId: string; + keypair: BlahKeyPair; +}; + +async function savedObjectToQueryResult(savedObject: SavedObject): Promise { + const publicKey = await BlahPublicKey.fromID(savedObject.actKeyId); + return { + idKeyId: savedObject.idKeyId, + keypair: new BlahKeyPair(publicKey, savedObject.privateKey) + }; +} + +export class ActKeyStore { + private db: IDBDatabase | null = null; + private dbName: string = 'WeblahActKeyStore'; + private objectStoreName: string = 'keys'; + + async open(): Promise { + if (!window.indexedDB) throw new Error('IndexedDB is not supported.'); + + const req = indexedDB.open(this.dbName, 1); + + return new Promise((fulfill, reject) => { + req.onsuccess = () => { + this.db = req.result; + fulfill(this); + }; + req.onerror = () => reject(req.error); + req.onblocked = () => reject(new Error('Database already open')); + req.onupgradeneeded = () => { + this.db = req.result; + if (!this.db.objectStoreNames.contains(this.objectStoreName)) { + const objStore = this.db.createObjectStore(this.objectStoreName); + objStore.createIndex('idKeyId', 'idKeyId', { unique: false }); + objStore.createIndex('actKeyId', 'actKeyId', { unique: true }); + } + }; + }); + } + + async saveActKeyPair(keypair: BlahKeyPair, idKeyId: string): Promise { + if (!this.db) throw new Error('ActKeyStore is not open.'); + + const savedObject: SavedObject = { + idKeyId, + actKeyId: keypair.id, + privateKey: keypair.privateKey + }; + + const transaction = this.db.transaction(this.objectStoreName, 'readwrite'); + + return await new Promise((fulfill, reject) => { + transaction.onerror = () => reject(transaction.error); + transaction.onabort = () => reject(transaction.error); + transaction.oncomplete = () => fulfill({ idKeyId, keypair }); + const objectStore = transaction.objectStore(this.objectStoreName); + objectStore.add(savedObject); + }); + } + + async fetchActKeyPair(actKeyId: string): Promise { + if (!this.db) throw new Error('ActKeyStore is not open.'); + + const transaction = this.db.transaction(this.objectStoreName, 'readonly'); + const objectStore = transaction.objectStore(this.objectStoreName); + + const request: IDBRequest = objectStore.index('actKeyId').get(actKeyId); + + const result = await new Promise((fulfill, reject) => { + request.onsuccess = () => fulfill(request.result); + request.onerror = () => reject(request.error); + }); + + if (!result) return null; + return await savedObjectToQueryResult(result); + } + + async fetchAllKeyPairs(): Promise { + if (!this.db) throw new Error('ActKeyStore is not open.'); + + const list: QueryResult[] = []; + + const transaction = this.db.transaction([this.objectStoreName], 'readonly'); + + return new Promise((fulfill, reject) => { + transaction.onerror = () => reject(transaction.error); + transaction.onabort = () => reject(transaction.error); + + const objectStore = transaction.objectStore(this.objectStoreName); + const cursor = objectStore.openCursor(); + + cursor.onsuccess = async () => { + const result = cursor.result; + if (result) { + list.push(await savedObjectToQueryResult(result.value)); + result.continue(); + } else { + fulfill(list); + } + }; + }); + } + + close() { + this.db?.close(); + this.db = null; + } +} diff --git a/src/lib/db/schema/identity.ts b/src/lib/db/schema/identity.ts index 6575b82..4008492 100644 --- a/src/lib/db/schema/identity.ts +++ b/src/lib/db/schema/identity.ts @@ -1,6 +1,6 @@ import { sqliteTable, text } from 'drizzle-orm/sqlite-core'; -export const identities = sqliteTable('identities', { +export const identities = sqliteTable('weblah-identities', { idKey: text('id_key').primaryKey(), actKeys: text('act_keys').notNull(), profileName: text('profile_name').notNull(),