// Page-side handle to the vault worker. Every VaultCore method (and the free functions) becomes an // async call into the worker; errors come back as `CoreError`s with the same `code`s. // // import { connectVault } from "@kestrove/core/client.js"; // const vault = await connectVault(); // IndexedDB "kestrove" // if (!(await vault.hasAccount())) { ... } // await vault.unlock(password, secretKey); // Argon2id runs in the worker // const items = await vault.listItems(); export async function connectVault(options = {}) { const worker = options.worker ?? new Worker(new URL("./worker.js", import.meta.url), { type: "module", name: "kestrove-vault" }); let next = 1; const pending = new Map(); worker.onmessage = ({ data: { id, ok, err } }) => { const p = pending.get(id); if (!p) return; pending.delete(id); if (err) p.reject(Object.assign(new Error(err.message), err)); else p.resolve(ok); }; worker.onerror = (event) => { const e = Object.assign(new Error(event.message || "vault worker failed"), { name: "CoreError", code: "Internal" }); for (const p of pending.values()) p.reject(e); pending.clear(); }; const call = (method, args) => new Promise((resolve, reject) => { const id = next++; pending.set(id, { resolve, reject }); worker.postMessage({ id, method, args }); }); try { await call("open", [options.dbName ?? null, Boolean(options.inMemory)]); } catch (e) { worker.terminate(); // e.g. "InUse": the vault is open in another tab throw e; } const handle = { /** Frees the core, releases the tab lock and stops the worker (key material goes with it). */ async close() { try { await call("close", []); } finally { worker.terminate(); } }, }; return new Proxy(handle, { get(target, prop) { if (prop in target) return target[prop]; if (typeof prop !== "string" || prop === "then") return undefined; return (...args) => call(prop, args); }, }); }