store-wasm-host.mjs raw
1 // store-wasm-host.mjs — Worker bootstrap for store.wasm.
2 //
3 // Owns the 'smesh' IndexedDB (events + DMs). Receives S_* messages from the
4 // supervisor, executes IDB operations, returns SR_* responses.
5 //
6 // No WebSocket or DOM access. Bridge surface: store_worker_* + idb_*.
7
8 import {
9 makeCoreHelpers, makeWasi, makeCommonBridge,
10 computeBuildHash,
11 } from './bridge-common.mjs';
12 import * as idb from './$runtime/idb.mjs';
13
14 let mem, xp;
15 const _sabTable = new Map();
16 const _sabSeqRef = { value: 0 };
17 const _spawnedWorkerChans = new Map();
18 const _wasmUrlRef = { value: null };
19 const _buildHashRef = { value: null };
20
21 const _enc = new TextEncoder();
22 const _dec = new TextDecoder();
23
24 function readStr(ptr, len) {
25 if (len <= 0) return '';
26 try {
27 return _dec.decode(new Uint8Array(mem.buffer, ptr >>> 0, len));
28 } catch (err) {
29 self.postMessage('["__ERROR","readStr ptr=' + ptr + ' len=' + len + ' err=' + String(err) + '"]');
30 throw err;
31 }
32 }
33
34 function writeStr(s) {
35 const bytes = _enc.encode('' + s);
36 const ptr = xp.__alloc(bytes.length) >>> 0;
37 try {
38 new Uint8Array(mem.buffer, ptr, bytes.length).set(bytes);
39 } catch (err) {
40 self.postMessage('["__ERROR","writeStr ptr=' + ptr + ' len=' + bytes.length + ' err=' + String(err) + '"]');
41 throw err;
42 }
43 return [ptr, bytes.length];
44 }
45
46 function writeI32(addr, val) {
47 new DataView(mem.buffer).setInt32(addr >>> 0, val, true);
48 }
49
50 function writeBytes(data) {
51 const u = (data instanceof Uint8Array) ? data : (data && data.$array != null
52 ? (() => { const u = new Uint8Array(data.$length); for (let i = 0; i < data.$length; i++) u[i] = data.$array[data.$offset + i]; return u; })()
53 : (typeof data === 'string' ? _enc.encode(data) : new Uint8Array(0)));
54 const ptr = xp.__alloc(u.length) >>> 0;
55 new Uint8Array(mem.buffer, ptr, u.length).set(u);
56 return [ptr, u.length];
57 }
58
59 function readBytes(ptr, len) {
60 if (len <= 0) return new Uint8Array(0);
61 return new Uint8Array(mem.buffer, ptr >>> 0, len);
62 }
63
64 let _wasmDead = false;
65 function _trap(fn) {
66 try { fn(); } catch (err) {
67 if (!_wasmDead) {
68 _wasmDead = true;
69 self.postMessage('["__WASM_FATAL","' + jsonEsc(String(err)) + '"]');
70 }
71 }
72 }
73
74 function cb0(id) { _trap(() => xp.__cb0(id)); }
75 function cbs(id, s) {
76 const [ptr, len] = writeStr(s);
77 _trap(() => xp.__cbs(id, ptr, len));
78 }
79 function cbb(id, val) { _trap(() => xp.__cbb(id, val ? 1 : 0)); }
80 function cbss(id, s1, s2) {
81 const [p1, l1] = writeStr(s1);
82 const [p2, l2] = writeStr(s2);
83 _trap(() => xp.__cbss(id, p1, l1, p2, l2));
84 }
85 function cbdata(id, data) {
86 const [ptr, len] = writeBytes(data);
87 _trap(() => xp.__cbdata(id, ptr, len));
88 }
89
90 const h = { readStr, readBytes, writeStr, writeI32, writeBytes, cb0, cbs, cbb, cbdata };
91
92 let _workerMsgCBID = 0;
93
94 function jsonEsc(s) {
95 return String(s)
96 .replace(/\\/g, '\\\\')
97 .replace(/"/g, '\\"')
98 .replace(/\n/g, '\\n')
99 .replace(/\r/g, '\\r')
100 .replace(/\t/g, '\\t');
101 }
102
103 function deliverToWasm(msg) {
104 if (_workerMsgCBID) cbs(_workerMsgCBID, msg);
105 }
106
107 const bridge = {
108 // --- store worker-internal bridge ---
109 store_worker_on_message(cbID) {
110 _workerMsgCBID = cbID;
111 },
112 store_worker_post(ptr, len) {
113 self.postMessage(readStr(ptr, len));
114 },
115 store_worker_set_timeout(ms, cbID) {
116 return setTimeout(() => cb0(cbID), ms);
117 },
118 store_worker_clear_timeout(handle) {
119 clearTimeout(handle);
120 },
121 store_worker_now_seconds() {
122 return BigInt(Math.floor(Date.now() / 1000));
123 },
124
125 // --- idb (IndexedDB for Nostr events and DMs) ---
126 idb_set_enc_key(p, l, _c) { idb.SetEncKey(readStr(p, l)); },
127 idb_open(cbID) { idb.Open(() => cb0(cbID)); },
128 idb_save_event(p, l, _c, cbID) { idb.SaveEvent(readStr(p, l), (v) => cbb(cbID, v)); },
129 idb_query_events(p, l, _c, cbID) { idb.QueryEvents(readStr(p, l), (s) => cbs(cbID, s)); },
130 idb_save_dm(p, l, _c, cbID) { idb.SaveDM(readStr(p, l), (s) => cbs(cbID, s)); },
131 idb_query_dms(pp, pl, _pc, limit, until, cbID) {
132 idb.QueryDMs(readStr(pp, pl), limit, Number(until), (s) => cbs(cbID, s));
133 },
134 idb_get_conversation_list(cbID) { idb.GetConversationList((s) => cbs(cbID, s)); },
135 idb_clear_dms_by_peer(p, l, _c, cbID) { idb.ClearDMsByPeer(readStr(p, l), () => cb0(cbID)); },
136 idb_set_version(p, l, _c) { idb.SetVersion(readStr(p, l)); },
137 idb_mls_save_group(gp, gl, _gc, sp, sl, _sc, cbID) {
138 idb.MlsSaveGroup(readStr(gp, gl), readStr(sp, sl), () => cb0(cbID));
139 },
140 idb_mls_load_group(gp, gl, _gc, cbID) {
141 idb.MlsLoadGroup(readStr(gp, gl), (s) => cbs(cbID, s));
142 },
143 idb_mls_list_groups(cbID) { idb.MlsListGroups((s) => cbs(cbID, s)); },
144 idb_mls_save_kpp(p, l, _c, cbID) { idb.MlsSaveKPP(readStr(p, l), () => cb0(cbID)); },
145 idb_mls_load_kpp(cbID) { idb.MlsLoadKPP((s) => cbs(cbID, s)); },
146
147 // --- KV stores (profiles, settings, cache) ---
148 idb_kv_get(sp, sl, _sc, kp, kl, _kc, cbID) {
149 idb.KVGet(readStr(sp, sl), readStr(kp, kl), (v) => cbs(cbID, v));
150 },
151 idb_kv_put(sp, sl, _sc, kp, kl, _kc, vp, vl, _vc) {
152 idb.KVPut(readStr(sp, sl), readStr(kp, kl), readStr(vp, vl));
153 },
154 idb_kv_get_all(sp, sl, _sc, fnID, doneID) {
155 idb.KVGetAll(readStr(sp, sl), (k, v) => cbss(fnID, k, v), () => cb0(doneID));
156 },
157
158 // --- common (channel ops, spawn, subtle_random_bytes) ---
159 ...makeCommonBridge(h, _sabTable, _sabSeqRef, _wasmUrlRef, _buildHashRef, _spawnedWorkerChans),
160 };
161
162 const wasi = makeWasi(() => mem);
163
164 self.addEventListener('unhandledrejection', function(ev) {
165 self.postMessage('["__ERROR","unhandledrejection: ' + jsonEsc(String(ev.reason)) + '"]');
166 });
167
168 self.onmessage = async function(e) {
169 const d = e.data;
170
171 if (d && d.type === 'init' && d.mode === 'root') {
172 try {
173 const wasmBytes = await fetch(d.wasmUrl, { cache: 'no-store' }).then(r => r.arrayBuffer());
174 const buildHash = await computeBuildHash(wasmBytes);
175 _wasmUrlRef.value = d.wasmUrl;
176 _buildHashRef.value = buildHash;
177
178 const { instance } = await WebAssembly.instantiate(wasmBytes,
179 { bridge, wasi_snapshot_preview1: wasi });
180 mem = instance.exports.memory;
181 xp = instance.exports;
182 _trap(() => xp._start());
183 } catch (err) {
184 self.postMessage('["__ERROR","boot: ' + jsonEsc(String(err)) + '"]');
185 }
186 return;
187 }
188
189 if (typeof d === 'string') {
190 deliverToWasm(d);
191 }
192 };
193