// Web Worker — runs XSalsa20-Poly1305 decrypt + utf8 decode off the main // thread. The conv-key lookup (which is network-bound) stays in the main // thread; only the CPU-heavy AEAD + UTF-8 step runs here. // // Message protocol: // request: { id: string, items: Array<{ id, ciphertext, nonce, key }> } // response: { id: string, results: Array<{ id, plaintext: string | null }> } /// import sodium from 'libsodium-wrappers-sumo'; interface DecryptItem { id: string; ciphertext: Uint8Array; nonce: Uint8Array; key: Uint8Array; } interface DecryptRequest { id: string; items: DecryptItem[]; } interface DecryptResult { id: string; plaintext: string | null; } interface DecryptResponse { id: string; results: DecryptResult[]; } let sodiumReady: Promise | null = null; async function ensureSodium(): Promise { if (!sodiumReady) sodiumReady = sodium.ready.then(() => sodium); return sodiumReady; } function decodeOne(s: typeof sodium, item: DecryptItem): string | null { try { const plain = s.crypto_secretbox_open_easy(item.ciphertext, item.nonce, item.key); return new TextDecoder('utf-8', { fatal: false }).decode(plain); } catch { return null; } } self.addEventListener('message', (ev: MessageEvent) => { const req = ev.data; void (async () => { const s = await ensureSodium(); const results: DecryptResult[] = req.items.map((item) => ({ id: item.id, plaintext: decodeOne(s, item), })); const resp: DecryptResponse = { id: req.id, results }; (self as unknown as Worker).postMessage(resp); })(); });