/** * IndexedDB-based message body storage for Android/browser. * Mirrors FileMessageStore API from @bobfrankston/mailx-store. * Stores RFC 2822 message bodies as blobs keyed by accountId/folderId/uid. */ const DB_NAME = "mailx-bodies"; const DB_VERSION = 1; const STORE_NAME = "messages"; function openDb(): Promise { return new Promise((resolve, reject) => { const req = indexedDB.open(DB_NAME, DB_VERSION); req.onupgradeneeded = () => { const db = req.result; if (!db.objectStoreNames.contains(STORE_NAME)) { db.createObjectStore(STORE_NAME); } }; req.onsuccess = () => resolve(req.result); req.onerror = () => reject(req.error); }); } function makeKey(accountId: string, folderId: number, uid: number): string { return `${accountId}/${folderId}/${uid}`; } export class WebMessageStore { private dbPromise: Promise; constructor() { this.dbPromise = openDb(); } async putMessage(accountId: string, folderId: number, uid: number, raw: Uint8Array | ArrayBuffer): Promise { const db = await this.dbPromise; const key = makeKey(accountId, folderId, uid); const data = raw instanceof Uint8Array ? raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength) : raw; return new Promise((resolve, reject) => { const tx = db.transaction(STORE_NAME, "readwrite"); tx.objectStore(STORE_NAME).put(data, key); tx.oncomplete = () => resolve(key); tx.onerror = () => reject(tx.error); }); } async getMessage(accountId: string, folderId: number, uid: number): Promise { const db = await this.dbPromise; const key = makeKey(accountId, folderId, uid); return new Promise((resolve, reject) => { const tx = db.transaction(STORE_NAME, "readonly"); const req = tx.objectStore(STORE_NAME).get(key); req.onsuccess = () => { if (req.result) { resolve(new Uint8Array(req.result as ArrayBuffer)); } else { resolve(null); } }; req.onerror = () => reject(req.error); }); } async deleteMessage(accountId: string, folderId: number, uid: number): Promise { const db = await this.dbPromise; const key = makeKey(accountId, folderId, uid); return new Promise((resolve, reject) => { const tx = db.transaction(STORE_NAME, "readwrite"); tx.objectStore(STORE_NAME).delete(key); tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error); }); } async hasMessage(accountId: string, folderId: number, uid: number): Promise { const db = await this.dbPromise; const key = makeKey(accountId, folderId, uid); return new Promise((resolve, reject) => { const tx = db.transaction(STORE_NAME, "readonly"); const req = tx.objectStore(STORE_NAME).count(IDBKeyRange.only(key)); req.onsuccess = () => resolve(req.result > 0); req.onerror = () => reject(req.error); }); } /** Clear all stored bodies — used for "Reset Store" */ async clear(): Promise { const db = await this.dbPromise; return new Promise((resolve, reject) => { const tx = db.transaction(STORE_NAME, "readwrite"); tx.objectStore(STORE_NAME).clear(); tx.oncomplete = () => resolve(); tx.onerror = () => reject(tx.error); }); } }