host.mjs raw
1 // WASM Loader — instantiates a Moxie WASM binary with all bridge imports.
2 // Reuses existing jsruntime modules for browser API surface.
3
4 import * as dom from './dom.mjs';
5 import * as webgl from './webgl.mjs';
6 import * as app from './app.mjs';
7 import * as text from './text.mjs';
8 import * as localstorage from './localstorage.mjs';
9 import * as ws from './ws.mjs';
10 import * as schnorr from './schnorr.mjs';
11 import * as ed25519 from './ed25519.mjs';
12 import * as x25519 from './x25519.mjs';
13 import * as p256 from './p256.mjs';
14 import * as poly1305 from './poly1305.mjs';
15 import * as crypto from './crypto.mjs';
16 import * as subtle from './subtle.mjs';
17 import * as signer from './signer.mjs';
18 import * as ext from './ext.mjs';
19 import * as idb from './idb.mjs';
20 import * as sw from './sw.mjs';
21 import * as markdown from './markdown.mjs';
22 import * as node from './node.mjs';
23 import * as sabChannel from './sab-channel.mjs';
24
25 let memory;
26 let instance;
27 const encoder = new TextEncoder();
28 const decoder = new TextDecoder();
29
30 function readStr(ptr, len) {
31 if (len <= 0) return '';
32 return decoder.decode(new Uint8Array(memory.buffer, ptr, len));
33 }
34
35 function readBytes(ptr, len) {
36 if (len <= 0) return new Uint8Array(0);
37 return new Uint8Array(memory.buffer, ptr, len).slice();
38 }
39
40 function writeStr(s) {
41 const buf = encoder.encode(s);
42 const ptr = instance.exports.__alloc(buf.length);
43 new Uint8Array(memory.buffer, ptr, buf.length).set(buf);
44 return { ptr, len: buf.length };
45 }
46
47 function writeBytes(b) {
48 if (!b || b.length === 0) return { ptr: 0, len: 0 };
49 const ptr = instance.exports.__alloc(b.length);
50 new Uint8Array(memory.buffer, ptr, b.length).set(b instanceof Uint8Array ? b : new Uint8Array(b));
51 return { ptr, len: b.length };
52 }
53
54 function setOutStr(outPtr, outLen, s) {
55 const { ptr, len } = writeStr(s);
56 const dv = new DataView(memory.buffer);
57 dv.setInt32(outPtr, ptr, true);
58 dv.setInt32(outLen, len, true);
59 }
60
61 function setOutBytes(outPtr, outLen, b) {
62 const { ptr, len } = writeBytes(b);
63 const dv = new DataView(memory.buffer);
64 dv.setInt32(outPtr, ptr, true);
65 dv.setInt32(outLen, len, true);
66 }
67
68 function setOutI32(addr, val) {
69 new DataView(memory.buffer).setInt32(addr, val, true);
70 }
71
72 // Callback dispatch — always deferred via queueMicrotask.
73 function cb0(id) { queueMicrotask(() => instance.exports.__cb0(id)); }
74 function cbs(id, s) {
75 queueMicrotask(() => {
76 const { ptr, len } = writeStr(s);
77 instance.exports.__cbs(id, ptr, len);
78 });
79 }
80 function cbb(id, v) { queueMicrotask(() => instance.exports.__cbb(id, v ? 1 : 0)); }
81 function cbi(id, v) { queueMicrotask(() => instance.exports.__cbi(id, v | 0)); }
82 function cbis(id, i, s) {
83 queueMicrotask(() => {
84 const { ptr, len } = writeStr(s);
85 instance.exports.__cbis(id, i | 0, ptr, len);
86 });
87 }
88 function cbiis(id, a, b, s) {
89 queueMicrotask(() => {
90 const { ptr, len } = writeStr(s);
91 instance.exports.__cbiis(id, a | 0, b | 0, ptr, len);
92 });
93 }
94 function cbss(id, s1, s2) {
95 queueMicrotask(() => {
96 const a = writeStr(s1);
97 const b = writeStr(s2);
98 instance.exports.__cbss(id, a.ptr, a.len, b.ptr, b.len);
99 });
100 }
101 function cb6i(id, a, b, c, d, e, f) {
102 queueMicrotask(() => instance.exports.__cb6i(id, a|0, b|0, c|0, d|0, e|0, f|0));
103 }
104 function cbii(id, a, b) { queueMicrotask(() => instance.exports.__cbii(id, a|0, b|0)); }
105 function cbiii(id, a, b, c) { queueMicrotask(() => instance.exports.__cbiii(id, a|0, b|0, c|0)); }
106 function cbdata(id, bytes) {
107 queueMicrotask(() => {
108 const { ptr, len } = writeBytes(bytes);
109 instance.exports.__cbdata(id, ptr, len);
110 });
111 }
112
113 // Parse null-separated string list.
114 function splitNull(ptr, len, count) {
115 if (count <= 0) return [];
116 const s = readStr(ptr, len);
117 return s.split('\0');
118 }
119
120 // Track JS wrappers for WASM event listeners so RemoveEventListener works.
121 const jsListeners = new Map();
122 function listenerKey(elId, event, cbID) { return `${elId}:${event}:${cbID}`; }
123
124 // Hook response tracking for nested callbacks.
125 const hookResponses = new Map();
126 let hookNextReq = 1;
127
128 // buildBridge returns the import object for the "bridge" module. The same
129 // shape is used by the page and by a spawned domain's Worker; the two differ
130 // only in which end of the channel rings they own, so the channel table and
131 // the worker flag are parameters.
132 //
133 // opts.channels: Map<int32, SharedArrayBuffer> of rings bound at spawn. On
134 // the page side this starts empty and channel_create fills it; in a worker
135 // the handles arrive from the parent and this table is pre-populated so the
136 // child reads the far end of the same rings.
137 // opts.isWorker: 1 inside a spawned domain, 0 in the page.
138 export function buildBridge(opts = {}) {
139 const handles = opts.channels || new Map();
140 let next = 1;
141 const isWorker = opts.isWorker ? 1 : 0;
142 return {
143 // --- localstorage ---
144 localstorage_get_item(kp, kl, _kc, op, ol) {
145 setOutStr(op, ol, localstorage.GetItem(readStr(kp, kl)));
146 },
147 localstorage_set_item(kp, kl, _kc, vp, vl, _vc) {
148 localstorage.SetItem(readStr(kp, kl), readStr(vp, vl));
149 },
150 localstorage_remove_item(kp, kl, _kc) {
151 localstorage.RemoveItem(readStr(kp, kl));
152 },
153
154 // --- crypto ---
155 crypto_pubkey_from_seckey(p, l, _c, op, ol) {
156 setOutBytes(op, ol, crypto.PubKeyFromSecKey(readBytes(p, l)));
157 },
158 crypto_sign_schnorr(skp, skl, _skc, mp, ml, _mc, ap, al, _ac, op, ol) {
159 setOutBytes(op, ol, crypto.SignSchnorr(readBytes(skp, skl), readBytes(mp, ml), readBytes(ap, al)));
160 },
161 crypto_verify_schnorr(pkp, pkl, _pkc, mp, ml, _mc, sp, sl, _sc) {
162 return crypto.VerifySchnorr(readBytes(pkp, pkl), readBytes(mp, ml), readBytes(sp, sl)) ? 1 : 0;
163 },
164
165 // --- schnorr ---
166 schnorr_pubkey_from_seckey(p, l, _c, op, ol, ok) {
167 try {
168 const r = schnorr.PubKeyFromSecKey(readBytes(p, l));
169 setOutBytes(op, ol, r); setOutI32(ok, r ? 1 : 0);
170 } catch { setOutI32(ok, 0); }
171 },
172 schnorr_sign(skp, skl, _skc, mp, ml, _mc, ap, al, _ac, op, ol, ok) {
173 try {
174 const r = schnorr.SignSchnorr(readBytes(skp, skl), readBytes(mp, ml), readBytes(ap, al));
175 setOutBytes(op, ol, r); setOutI32(ok, r ? 1 : 0);
176 } catch { setOutI32(ok, 0); }
177 },
178 schnorr_verify(pkp, pkl, _pkc, mp, ml, _mc, sp, sl, _sc) {
179 return schnorr.VerifySchnorr(readBytes(pkp, pkl), readBytes(mp, ml), readBytes(sp, sl)) ? 1 : 0;
180 },
181 schnorr_ecdh(skp, skl, _skc, pkp, pkl, _pkc, op, ol, ok) {
182 try {
183 const r = schnorr.ECDH(readBytes(skp, skl), readBytes(pkp, pkl));
184 setOutBytes(op, ol, r); setOutI32(ok, r ? 1 : 0);
185 } catch { setOutI32(ok, 0); }
186 },
187 schnorr_sha256sum(p, l, _c, op, ol) {
188 setOutBytes(op, ol, schnorr.SHA256Sum(readBytes(p, l)));
189 },
190 schnorr_scalar_add_mod_n(ap, al, _ac, bp, bl, _bc, op, ol, ok) {
191 try {
192 const r = schnorr.ScalarAddModN(readBytes(ap, al), readBytes(bp, bl));
193 setOutBytes(op, ol, r); setOutI32(ok, r ? 1 : 0);
194 } catch { setOutI32(ok, 0); }
195 },
196 schnorr_compressed_pubkey(p, l, _c, op, ol, ok) {
197 try {
198 const r = schnorr.CompressedPubKey(readBytes(p, l));
199 setOutBytes(op, ol, r); setOutI32(ok, r ? 1 : 0);
200 } catch { setOutI32(ok, 0); }
201 },
202
203 // --- ed25519 ---
204 ed25519_new_key_from_seed(p, l, _c, op, ol) {
205 setOutBytes(op, ol, ed25519.NewKeyFromSeed(readBytes(p, l)));
206 },
207 ed25519_sign(sp, sl, _sc, mp, ml, _mc, op, ol) {
208 setOutBytes(op, ol, ed25519.Sign(readBytes(sp, sl), readBytes(mp, ml)));
209 },
210 ed25519_verify(pkp, pkl, _pkc, mp, ml, _mc, sp, sl, _sc) {
211 return ed25519.Verify(readBytes(pkp, pkl), readBytes(mp, ml), readBytes(sp, sl)) ? 1 : 0;
212 },
213
214 // --- x25519 ---
215 x25519_scalar_mult(sp, sl, _sc, pp, pl, _pc, op, ol) {
216 setOutBytes(op, ol, x25519.ScalarMult(readBytes(sp, sl), readBytes(pp, pl)));
217 },
218 x25519_scalar_base_mult(sp, sl, _sc, op, ol) {
219 setOutBytes(op, ol, x25519.ScalarBaseMult(readBytes(sp, sl)));
220 },
221
222 // --- p256 ---
223 p256_scalar_base_mult(p, l, _c, op, ol) {
224 setOutBytes(op, ol, p256.ScalarBaseMult(readBytes(p, l)));
225 },
226 p256_scalar_mult(sp, sl, _sc, pp, pl, _pc, op, ol) {
227 setOutBytes(op, ol, p256.ScalarMult(readBytes(sp, sl), readBytes(pp, pl)));
228 },
229 p256_sign(sp, sl, _sc, mp, ml, _mc, op, ol) {
230 setOutBytes(op, ol, p256.Sign(readBytes(sp, sl), readBytes(mp, ml)));
231 },
232 p256_verify(pkp, pkl, _pkc, mp, ml, _mc, sp, sl, _sc) {
233 return p256.Verify(readBytes(pkp, pkl), readBytes(mp, ml), readBytes(sp, sl)) ? 1 : 0;
234 },
235 p256_reduce_scalar(p, l, _c, op, ol) {
236 setOutBytes(op, ol, p256.ReduceScalar(readBytes(p, l)));
237 },
238 p256_valid_scalar(p, l, _c) {
239 return p256.ValidScalar(readBytes(p, l)) ? 1 : 0;
240 },
241
242 // --- poly1305 ---
243 poly1305_mac(kp, kl, _kc, dp, dl, _dc, op, ol) {
244 setOutBytes(op, ol, poly1305.MAC(readBytes(kp, kl), readBytes(dp, dl)));
245 },
246 poly1305_verify(kp, kl, _kc, dp, dl, _dc, tp, tl, _tc) {
247 return poly1305.Verify(readBytes(kp, kl), readBytes(dp, dl), readBytes(tp, tl)) ? 1 : 0;
248 },
249
250 // --- markdown ---
251 markdown_render(p, l, _c, op, ol) {
252 setOutStr(op, ol, markdown.Render(readStr(p, l)));
253 },
254
255 // --- dom ---
256 dom_body() { return dom.Body(); },
257 dom_create_element(p, l, _c) { return dom.CreateElement(readStr(p, l)); },
258 dom_create_text_node(p, l, _c) { return dom.CreateTextNode(readStr(p, l)); },
259 dom_get_element_by_id(p, l, _c) { return dom.GetElementById(readStr(p, l)); },
260 dom_query_selector(p, l, _c) { return dom.QuerySelector(readStr(p, l)); },
261 dom_query_selector_from(root, p, l, _c) { return dom.QuerySelectorFrom(root, readStr(p, l)); },
262 dom_append_child(parent, child) { dom.AppendChild(parent, child); },
263 dom_remove_child(parent, child) { dom.RemoveChild(parent, child); },
264 dom_remove(el) { dom.Remove(el); },
265 dom_insert_before(parent, nc, rc) { dom.InsertBefore(parent, nc, rc); },
266 dom_first_child(el) { return dom.FirstChild(el); },
267 dom_first_element_child(el) { return dom.FirstElementChild(el); },
268 dom_next_sibling(el) { return dom.NextSibling(el); },
269 dom_release_element(el) { dom.ReleaseElement(el); },
270 dom_set_attribute(el, np, nl, _nc, vp, vl, _vc) {
271 dom.SetAttribute(el, readStr(np, nl), readStr(vp, vl));
272 },
273 dom_remove_attribute(el, np, nl, _nc) { dom.RemoveAttribute(el, readStr(np, nl)); },
274 dom_get_attribute(el, np, nl, _nc, op, ol) { setOutStr(op, ol, dom.GetAttribute(el, readStr(np, nl))); },
275 dom_set_text_content(el, p, l, _c) { dom.SetTextContent(el, readStr(p, l)); },
276 dom_set_inner_html(el, p, l, _c) { dom.SetInnerHTML(el, readStr(p, l)); },
277 dom_set_style(el, pp, pl, _pc, vp, vl, _vc) {
278 dom.SetStyle(el, readStr(pp, pl), readStr(vp, vl));
279 },
280 dom_add_class(el, p, l, _c) { dom.AddClass(el, readStr(p, l)); },
281 dom_remove_class(el, p, l, _c) { dom.RemoveClass(el, readStr(p, l)); },
282 dom_focus(el) { dom.Focus(el); },
283 dom_set_property(el, pp, pl, _pc, vp, vl, _vc) {
284 dom.SetProperty(el, readStr(pp, pl), readStr(vp, vl));
285 },
286 dom_get_property(el, pp, pl, _pc, op, ol) {
287 setOutStr(op, ol, dom.GetProperty(el, readStr(pp, pl)));
288 },
289 dom_bounding_client_left(el) { return dom.BoundingClientLeft(el); },
290 dom_get_bounding_rect(el, cbID) {
291 const r = dom.GetBoundingRect_sync ? dom.GetBoundingRect_sync(el) : null;
292 if (r) { cb6i(cbID, r.left|0, r.top|0, r.right|0, r.bottom|0, r.width|0, r.height|0); }
293 else { dom.GetBoundingRect(el, (l,t,r,b,w,h) => cb6i(cbID, l,t,r,b,w,h)); }
294 },
295 dom_get_viewport_height() { return dom.GetViewportHeight(); },
296 dom_get_viewport_width() { return dom.GetViewportWidth(); },
297 dom_add_event_listener(elId, ep, el2, _ec, cbID) {
298 const el = dom.getElement(elId);
299 if (!el) return;
300 const event = readStr(ep, el2);
301 const fn = () => cb0(cbID);
302 el.addEventListener(event, fn);
303 jsListeners.set(listenerKey(elId, event, cbID), fn);
304 },
305 dom_add_self_event_listener(elId, ep, el2, _ec, cbID) {
306 const el = dom.getElement(elId);
307 if (!el) return;
308 const event = readStr(ep, el2);
309 const fn = (e) => { if (e.target === e.currentTarget) cb0(cbID); };
310 el.addEventListener(event, fn);
311 jsListeners.set(listenerKey(elId, event, cbID), fn);
312 },
313 dom_add_enter_key_listener(elId, cbID) {
314 const el = dom.getElement(elId);
315 if (!el) return;
316 const fn = (e) => { if (e.key === 'Enter') { e.preventDefault(); cb0(cbID); } };
317 el.addEventListener('keydown', fn);
318 jsListeners.set(listenerKey(elId, 'keydown:enter', cbID), fn);
319 },
320 dom_remove_event_listener(elId, ep, el2, _ec, cbID) {
321 const el = dom.getElement(elId);
322 if (!el) return;
323 const event = readStr(ep, el2);
324 const key = listenerKey(elId, event, cbID);
325 const fn = jsListeners.get(key);
326 if (fn) { el.removeEventListener(event, fn); jsListeners.delete(key); }
327 },
328 dom_request_animation_frame(cbID) { requestAnimationFrame(() => cb0(cbID)); },
329 dom_observe_resize(elId, cbID) { dom.ObserveResize(elId, cbID); },
330 dom_unobserve_resize(elId) { dom.UnobserveResize(elId); },
331 dom_on_keydown(el, handlerID) {
332 const element = dom.getElement(el);
333 if (!element) return;
334 element.addEventListener('keydown', (e) => {
335 const { ptr, len } = writeStr(e.key);
336 const prevented = instance.exports.__hook_keydown(handlerID, ptr, len);
337 if (prevented) e.preventDefault();
338 });
339 },
340 dom_insert_mention_chip(el, charCount, np, nl, _nc, nmp, nml, _nmc, pp, pl, _pc) {
341 dom.InsertMentionChip(el, charCount, readStr(np, nl), readStr(nmp, nml), readStr(pp, pl));
342 },
343 dom_set_timeout(cbID, ms) {
344 return setTimeout(() => cb0(cbID), ms);
345 },
346 dom_clear_timeout(id) { clearTimeout(id); },
347 dom_fetch_text(p, l, _c, cbID) {
348 dom.FetchText(readStr(p, l), (s) => cbs(cbID, s));
349 },
350 dom_fetch_relay_info(p, l, _c, cbID) {
351 dom.FetchRelayInfo(readStr(p, l), (s) => cbs(cbID, s));
352 },
353 dom_fetch_put_blob_base64(up, ul, _uc, dp, dl, _dc, cp, cl, _cc, ap, al, _ac, cbID) {
354 dom.FetchPutBlobBase64(readStr(up, ul), readStr(dp, dl), readStr(cp, cl), readStr(ap, al), (s) => cbs(cbID, s));
355 },
356 dom_idb_set_enc_key(p, l, _c) { dom.IDBSetEncKey(readStr(p, l)); },
357 dom_idb_get(sp, sl, _sc, kp, kl, _kc, cbID) {
358 dom.IDBGet(readStr(sp, sl), readStr(kp, kl), (s) => cbs(cbID, s));
359 },
360 dom_idb_put(sp, sl, _sc, kp, kl, _kc, vp, vl, _vc) {
361 dom.IDBPut(readStr(sp, sl), readStr(kp, kl), readStr(vp, vl));
362 },
363 dom_idb_get_all(sp, sl, _sc, fnID, doneID) {
364 dom.IDBGetAll(readStr(sp, sl), (k, v) => cbss(fnID, k, v), () => cb0(doneID));
365 },
366 dom_prefers_dark() { return dom.PrefersDark() ? 1 : 0; },
367 dom_console_log(p, l, _c) { console.log(readStr(p, l)); },
368 dom_confirm(p, l, _c) { return confirm(readStr(p, l)) ? 1 : 0; },
369 dom_post_to_sw(p, l, _c) { dom.PostToSW(readStr(p, l)); },
370 dom_on_sw_message(cbID) { dom.OnSWMessage((s) => cbs(cbID, s)); },
371 dom_push_state(p, l, _c) { dom.PushState(readStr(p, l)); },
372 dom_replace_state(p, l, _c) { dom.ReplaceState(readStr(p, l)); },
373 dom_back() { history.back(); },
374 dom_location_reload() { location.reload(); },
375 dom_hard_refresh() { dom.HardRefresh(); },
376 dom_clear_storage_prefix(p, l, _c) { dom.ClearStoragePrefix(readStr(p, l)); },
377 dom_timezone_offset_seconds() { return -(new Date().getTimezoneOffset()) * 60; },
378 dom_read_clipboard(cbID) { dom.ReadClipboard((s) => cbs(cbID, s)); },
379 dom_write_clipboard(p, l, _c, cbID) { dom.WriteClipboard(readStr(p, l), (ok) => cbb(cbID, ok)); },
380 dom_get_path(op, ol) { setOutStr(op, ol, location.pathname); },
381 dom_get_hash(op, ol) { setOutStr(op, ol, location.hash); },
382 dom_hostname(op, ol) { setOutStr(op, ol, location.hostname); },
383 dom_port(op, ol) { setOutStr(op, ol, location.port); },
384 dom_user_agent(op, ol) { setOutStr(op, ol, navigator.userAgent); },
385 dom_on_pop_state(cbID) {
386 window.addEventListener('popstate', () => cbs(cbID, location.pathname));
387 },
388 dom_pick_file_text(p, l, _c, cbID) { dom.PickFileText(readStr(p, l), (s) => cbs(cbID, s)); },
389 dom_download_text(fp, fl, _fc, cp, cl, _cc, mp, ml, _mc) {
390 dom.DownloadText(readStr(fp, fl), readStr(cp, cl), readStr(mp, ml));
391 },
392 dom_on_pull_refresh(el, ind, cbID) { dom.OnPullRefresh(el, ind, () => cb0(cbID)); },
393 dom_now_seconds() { return BigInt(Math.floor(Date.now() / 1000)); },
394 dom_pick_file_base64(p, l, _c, cbID) {
395 dom.PickFileBase64(readStr(p, l), (d, m) => cbss(cbID, d, m));
396 },
397 dom_on_paste_image(el, cbID) { dom.OnPasteImage(el, (d, m) => cbss(cbID, d, m)); },
398 dom_on_drop_image(el, cbID) { dom.OnDropImage(el, (d, m) => cbss(cbID, d, m)); },
399
400 // --- ws ---
401 ws_dial(up, ul, _uc, msgID, openID, closeID, errID) {
402 return ws.Dial(readStr(up, ul),
403 (conn, s) => cbis(msgID, conn, s),
404 (conn) => cbi(openID, conn),
405 (conn, code, reason) => cbiis(closeID, conn, code, reason),
406 (conn) => cbi(errID, conn),
407 );
408 },
409 ws_send(conn, mp, ml, _mc) { return ws.Send(conn, readStr(mp, ml)) ? 1 : 0; },
410 ws_close(conn) { ws.Close(conn); },
411 ws_ready_state(conn) { return ws.ReadyState(conn); },
412
413 // --- bc ---
414 bc_open(np, nl, _nc, cbID) {
415 return (typeof BroadcastChannel !== 'undefined')
416 ? (() => { const bc = new BroadcastChannel(readStr(np, nl)); bc.onmessage = (e) => cbs(cbID, String(e.data)); return 1; })()
417 : 0;
418 },
419 bc_send(handle, mp, ml, _mc) { /* placeholder - needs handle tracking */ },
420 bc_close(handle) { /* placeholder */ },
421
422 // --- wasm ---
423 wasm_load_go_wasm(ep, el, _ec, wp, wl, _wc, cbID) {
424 // Placeholder: loads another Go WASM binary
425 cb0(cbID);
426 },
427
428 // --- node ---
429 node_on_line(cbID) { if (node.OnLine) node.OnLine((s) => cbs(cbID, s)); },
430 node_on_close(cbID) { if (node.OnClose) node.OnClose(() => cb0(cbID)); },
431 node_write_line(p, l, _c) { if (node.WriteLine) node.WriteLine(readStr(p, l)); },
432 node_write_err(p, l, _c) { if (node.WriteErr) node.WriteErr(readStr(p, l)); },
433 node_exit(code) { if (node.Exit) node.Exit(code); },
434 node_now_seconds() { return BigInt(Math.floor(Date.now() / 1000)); },
435
436 // --- subtle ---
437 subtle_random_bytes(p, l, _c) {
438 const buf = new Uint8Array(memory.buffer, p, l);
439 globalThis.crypto.getRandomValues(buf);
440 },
441 subtle_aes_cbc_encrypt(kp, kl, _kc, ip, il, _ic, pp, pl, _pc, cbID) {
442 subtle.AESCBCEncrypt(readBytes(kp, kl), readBytes(ip, il), readBytes(pp, pl), (r) => cbdata(cbID, r));
443 },
444 subtle_aes_cbc_decrypt(kp, kl, _kc, ip, il, _ic, cp, cl, _cc, cbID) {
445 subtle.AESCBCDecrypt(readBytes(kp, kl), readBytes(ip, il), readBytes(cp, cl), (r) => cbdata(cbID, r));
446 },
447 subtle_aes_gcm_encrypt(kp, kl, _kc, ip, il, _ic, pp, pl, _pc, cbID) {
448 subtle.AESGCMEncrypt(readBytes(kp, kl), readBytes(ip, il), readBytes(pp, pl), (r) => cbdata(cbID, r));
449 },
450 subtle_aes_gcm_decrypt(kp, kl, _kc, ip, il, _ic, cp, cl, _cc, cbID) {
451 subtle.AESGCMDecrypt(readBytes(kp, kl), readBytes(ip, il), readBytes(cp, cl), (r) => cbdata(cbID, r));
452 },
453 subtle_pbkdf2_derive_key(pp, pl, _pc, sp, sl, _sc, iterations, cbID) {
454 subtle.PBKDF2DeriveKey(readStr(pp, pl), readBytes(sp, sl), iterations, (r) => cbdata(cbID, r));
455 },
456 subtle_argon2id_derive_key(pp, pl, _pc, sp, sl, _sc, t, m, p, dkLen, cbID) {
457 subtle.Argon2idDeriveKey(readStr(pp, pl), readBytes(sp, sl), t, m, p, dkLen, (r) => cbdata(cbID, r));
458 },
459 subtle_sha256_hex(p, l, _c, cbID) {
460 subtle.SHA256Hex(readBytes(p, l), (s) => cbs(cbID, s));
461 },
462 subtle_hmac_sha512(kp, kl, _kc, dp, dl, _dc, cbID) {
463 subtle.HMACSHA512(readBytes(kp, kl), readBytes(dp, dl), (r) => cbdata(cbID, r));
464 },
465 subtle_pbkdf2_sha512(pp, pl, _pc, sp, sl, _sc, iterations, dkLen, cbID) {
466 subtle.PBKDF2SHA512(readStr(pp, pl), readBytes(sp, sl), iterations, dkLen, (r) => cbdata(cbID, r));
467 },
468
469 // --- signer ---
470 signer_has_signer() { return signer.HasSigner() ? 1 : 0; },
471 signer_has_mls() { return signer.HasMLS() ? 1 : 0; },
472 signer_get_public_key(cbID) { signer.GetPublicKey((s) => cbs(cbID, s)); },
473 signer_sign_event(p, l, _c, cbID) { signer.SignEvent(readStr(p, l), (s) => cbs(cbID, s)); },
474 signer_get_shared_secret(p, l, _c, cbID) { signer.GetSharedSecret(readStr(p, l), (s) => cbs(cbID, s)); },
475 signer_nip04_decrypt(pkp, pkl, _pkc, cp, cl, _cc, cbID) {
476 signer.Nip04Decrypt(readStr(pkp, pkl), readStr(cp, cl), (s) => cbs(cbID, s));
477 },
478 signer_nip04_encrypt(pkp, pkl, _pkc, pp, pl, _pc, cbID) {
479 signer.Nip04Encrypt(readStr(pkp, pkl), readStr(pp, pl), (s) => cbs(cbID, s));
480 },
481 signer_nip44_decrypt(pkp, pkl, _pkc, cp, cl, _cc, cbID) {
482 signer.Nip44Decrypt(readStr(pkp, pkl), readStr(cp, cl), (s) => cbs(cbID, s));
483 },
484 signer_nip44_encrypt(pkp, pkl, _pkc, pp, pl, _pc, cbID) {
485 signer.Nip44Encrypt(readStr(pkp, pkl), readStr(pp, pl), (s) => cbs(cbID, s));
486 },
487 signer_is_installed(cbID) { signer.IsInstalled((v) => cbb(cbID, v)); },
488 signer_get_vault_status(cbID) { signer.GetVaultStatus((s) => cbs(cbID, s)); },
489 signer_create_vault(p, l, _c, cbID) { signer.CreateVault(readStr(p, l), (v) => cbb(cbID, v)); },
490 signer_unlock_vault(p, l, _c, cbID) { signer.UnlockVault(readStr(p, l), (v) => cbb(cbID, v)); },
491 signer_lock_vault(cbID) { signer.LockVault(() => cb0(cbID)); },
492 signer_list_identities(cbID) { signer.ListIdentities((s) => cbs(cbID, s)); },
493 signer_add_identity(p, l, _c, cbID) { signer.AddIdentity(readStr(p, l), (v) => cbb(cbID, v)); },
494 signer_nsec_login(p, l, _c, cbID) { signer.NsecLogin(readStr(p, l), (v) => cbb(cbID, v)); },
495 signer_switch_identity(p, l, _c, cbID) { signer.SwitchIdentity(readStr(p, l), (v) => cbb(cbID, v)); },
496 signer_export_vault(p, l, _c, cbID) { signer.ExportVault(readStr(p, l), (s) => cbs(cbID, s)); },
497 signer_import_vault(p, l, _c, cbID) { signer.ImportVault(readStr(p, l), (v) => cbb(cbID, v)); },
498 signer_remove_identity(p, l, _c, cbID) { signer.RemoveIdentity(readStr(p, l), (v) => cbb(cbID, v)); },
499 signer_generate_mnemonic(cbID) { signer.GenerateMnemonic((s) => cbs(cbID, s)); },
500 signer_validate_mnemonic(p, l, _c, cbID) { signer.ValidateMnemonic(readStr(p, l), (v) => cbb(cbID, v)); },
501 signer_create_hd_vault(pp, pl, _pc, np, nl, _nc, cbID) {
502 signer.CreateHDVault(readStr(pp, pl), readStr(np, nl), (s) => cbs(cbID, s));
503 },
504 signer_restore_hd_vault(pp, pl, _pc, mp, ml, _mc, np, nl, _nc, cbID) {
505 signer.RestoreHDVault(readStr(pp, pl), readStr(mp, ml), readStr(np, nl), (v) => cbb(cbID, v));
506 },
507 signer_derive_identity(p, l, _c, cbID) { signer.DeriveIdentity(readStr(p, l), (s) => cbs(cbID, s)); },
508 signer_get_mnemonic(cbID) { signer.GetMnemonic((s) => cbs(cbID, s)); },
509 signer_probe_account(index, cbID) { signer.ProbeAccount(index, (s) => cbs(cbID, s)); },
510 signer_is_hd(cbID) { signer.IsHD((v) => cbb(cbID, v)); },
511 signer_reset_extension(cbID) { signer.ResetExtension((v) => cbb(cbID, v)); },
512 signer_nwc_list(cbID) { signer.NwcList((s) => cbs(cbID, s)); },
513 signer_nwc_add(up, ul, _uc, ap, al, _ac, createdAt, cbID) {
514 signer.NwcAdd(readStr(up, ul), readStr(ap, al), Number(createdAt), (s) => cbs(cbID, s));
515 },
516 signer_nwc_remove(p, l, _c, cbID) { signer.NwcRemove(readStr(p, l), (v) => cbb(cbID, v)); },
517 signer_nwc_build_request(ip, il, _ic, mp, ml, _mc, pp, pl, _pc, ep, el2, _ec, createdAt, cbID) {
518 signer.NwcBuildRequest(readStr(ip, il), readStr(mp, ml), readStr(pp, pl), readStr(ep, el2), Number(createdAt), (s) => cbs(cbID, s));
519 },
520 signer_nwc_parse_response(ip, il, _ic, cp, cl, _cc, ep, el2, _ec, cbID) {
521 signer.NwcParseResponse(readStr(ip, il), readStr(cp, cl), readStr(ep, el2), (s) => cbs(cbID, s));
522 },
523
524 // --- idb ---
525 idb_set_enc_key(p, l, _c) { idb.SetEncKey(readStr(p, l)); },
526 idb_open(cbID) { idb.Open(() => cb0(cbID)); },
527 idb_save_event(p, l, _c, cbID) { idb.SaveEvent(readStr(p, l), (v) => cbb(cbID, v)); },
528 idb_query_events(p, l, _c, cbID) { idb.QueryEvents(readStr(p, l), (s) => cbs(cbID, s)); },
529 idb_save_dm(p, l, _c, cbID) { idb.SaveDM(readStr(p, l), (s) => cbs(cbID, s)); },
530 idb_query_dms(pp, pl, _pc, limit, until, cbID) {
531 idb.QueryDMs(readStr(pp, pl), limit, Number(until), (s) => cbs(cbID, s));
532 },
533 idb_get_conversation_list(cbID) { idb.GetConversationList((s) => cbs(cbID, s)); },
534 idb_clear_dms_by_peer(p, l, _c, cbID) { idb.ClearDMsByPeer(readStr(p, l), () => cb0(cbID)); },
535 idb_set_version(p, l, _c) { idb.SetVersion(readStr(p, l)); },
536 idb_mls_save_group(gp, gl, _gc, sp, sl, _sc, cbID) {
537 idb.MlsSaveGroup(readStr(gp, gl), readStr(sp, sl), () => cb0(cbID));
538 },
539 idb_mls_load_group(gp, gl, _gc, cbID) {
540 idb.MlsLoadGroup(readStr(gp, gl), (s) => cbs(cbID, s));
541 },
542 idb_mls_list_groups(cbID) { idb.MlsListGroups((s) => cbs(cbID, s)); },
543 idb_mls_save_kpp(p, l, _c, cbID) { idb.MlsSaveKPP(readStr(p, l), () => cb0(cbID)); },
544 idb_mls_load_kpp(cbID) { idb.MlsLoadKPP((s) => cbs(cbID, s)); },
545
546 // --- ext ---
547 ext_storage_get(kp, kl, _kc, cbID) { ext.StorageGet(readStr(kp, kl), (s) => cbs(cbID, s)); },
548 ext_storage_set(kp, kl, _kc, vp, vl, _vc) { ext.StorageSet(readStr(kp, kl), readStr(vp, vl)); },
549 ext_storage_remove(kp, kl, _kc) { ext.StorageRemove(readStr(kp, kl)); },
550 ext_on_message(_unused) {
551 ext.OnMessage((method, params, tabID, respond) => {
552 const reqID = hookNextReq++;
553 hookResponses.set(reqID, respond);
554 const m = writeStr(method);
555 const p = writeStr(params);
556 queueMicrotask(() => instance.exports.__hook_ext_on_message(reqID, m.ptr, m.len, p.ptr, p.len, tabID));
557 });
558 },
559 ext_on_message_respond(reqID, p, l, _c) {
560 const respond = hookResponses.get(reqID);
561 if (respond) { hookResponses.delete(reqID); respond(readStr(p, l)); }
562 },
563 ext_send_message_to_tab(tabID, mp, ml, _mc) { ext.SendMessageToTab(tabID, readStr(mp, ml)); },
564 ext_get_active_tab(cbID) { ext.GetActiveTab((id, url) => cbis(cbID, id, url)); },
565 ext_console_log(p, l, _c) { console.log(readStr(p, l)); },
566 ext_session_get(kp, kl, _kc, cbID) { ext.SessionGet(readStr(kp, kl), (s) => cbs(cbID, s)); },
567 ext_session_set(kp, kl, _kc, vp, vl, _vc) { ext.SessionSet(readStr(kp, kl), readStr(vp, vl)); },
568 ext_is_in_page() { return ext.IsInPage() ? 1 : 0; },
569
570 // --- registry ---
571 registry_set_seckey(p, l, _c) { self.$hooks = self.$hooks || {}; self.$hooks.seckey = readStr(p, l); },
572 registry_seckey(op, ol) { setOutStr(op, ol, (self.$hooks && self.$hooks.seckey) || ''); },
573 registry_set_pubkey(p, l, _c) { self.$hooks = self.$hooks || {}; self.$hooks.pubkey = readStr(p, l); },
574 registry_pubkey(op, ol) { setOutStr(op, ol, (self.$hooks && self.$hooks.pubkey) || ''); },
575 registry_set_has_key(v) { self.$hooks = self.$hooks || {}; self.$hooks.hasKey = v !== 0; },
576 registry_has_key() { return (self.$hooks && self.$hooks.hasKey) ? 1 : 0; },
577
578 registry_on_marmot_init(cbID) { self.$hooks = self.$hooks || {}; self.$hooks.marmotInit = (s) => cbs(cbID, s); },
579 registry_on_marmot_send(cbID) { self.$hooks = self.$hooks || {}; self.$hooks.marmotSend = (r, c) => cbss(cbID, r, c); },
580 registry_on_marmot_subscribe(cbID) { self.$hooks = self.$hooks || {}; self.$hooks.marmotSubscribe = () => cb0(cbID); },
581 registry_on_marmot_publish_kp(cbID) { self.$hooks = self.$hooks || {}; self.$hooks.marmotPublishKP = (s) => cbs(cbID, s); },
582 registry_on_marmot_list_groups(cbID) { self.$hooks = self.$hooks || {}; self.$hooks.marmotListGroups = (s) => cbs(cbID, s); },
583 registry_on_save_dm_record(cbID) { self.$hooks = self.$hooks || {}; self.$hooks.saveDMRecord = (s) => cbs(cbID, s); },
584 registry_on_broadcast_to_clients(cbID) { self.$hooks = self.$hooks || {}; self.$hooks.broadcastToClients = (s) => cbs(cbID, s); },
585 registry_on_send_to_client(cbID) { self.$hooks = self.$hooks || {}; self.$hooks.sendToClient = (id, m) => cbss(cbID, id, m); },
586
587 registry_on_encrypt_nip04() {
588 self.$hooks = self.$hooks || {};
589 self.$hooks.encryptNip04 = (pk, ct) => new Promise(resolve => {
590 const reqID = hookNextReq++;
591 hookResponses.set(reqID, resolve);
592 const p = writeStr(pk); const c = writeStr(ct);
593 queueMicrotask(() => instance.exports.__hook_encrypt_nip04(reqID, p.ptr, p.len, c.ptr, c.len));
594 });
595 },
596 registry_on_encrypt_nip17() {
597 self.$hooks = self.$hooks || {};
598 self.$hooks.encryptNip17 = (pk, ct) => new Promise(resolve => {
599 const reqID = hookNextReq++;
600 hookResponses.set(reqID, resolve);
601 const p = writeStr(pk); const c = writeStr(ct);
602 queueMicrotask(() => instance.exports.__hook_encrypt_nip17(reqID, p.ptr, p.len, c.ptr, c.len));
603 });
604 },
605 registry_on_decrypt_dm() {
606 self.$hooks = self.$hooks || {};
607 self.$hooks.decryptDM = (ev) => new Promise(resolve => {
608 const reqID = hookNextReq++;
609 hookResponses.set(reqID, resolve);
610 const e = writeStr(ev);
611 queueMicrotask(() => instance.exports.__hook_decrypt_dm(reqID, e.ptr, e.len));
612 });
613 },
614 registry_hook_respond_s(reqID, p, l, _c) {
615 const resolve = hookResponses.get(reqID);
616 if (resolve) { hookResponses.delete(reqID); resolve(readStr(p, l)); }
617 },
618 registry_hook_respond_ss(reqID, p1, l1, _c1, p2, l2, _c2) {
619 const resolve = hookResponses.get(reqID);
620 if (resolve) { hookResponses.delete(reqID); resolve([readStr(p1, l1), readStr(p2, l2)]); }
621 },
622
623 registry_encrypt_nip04(pkp, pkl, _pkc, cp, cl, _cc, cbID) {
624 if (self.$hooks && self.$hooks.encryptNip04) {
625 self.$hooks.encryptNip04(readStr(pkp, pkl), readStr(cp, cl)).then((s) => cbs(cbID, s));
626 }
627 },
628 registry_encrypt_nip17(pkp, pkl, _pkc, cp, cl, _cc, cbID) {
629 if (self.$hooks && self.$hooks.encryptNip17) {
630 self.$hooks.encryptNip17(readStr(pkp, pkl), readStr(cp, cl)).then(([a, b]) => cbss(cbID, a, b));
631 }
632 },
633 registry_decrypt_dm(p, l, _c, cbID) {
634 if (self.$hooks && self.$hooks.decryptDM) {
635 self.$hooks.decryptDM(readStr(p, l)).then((s) => cbs(cbID, s));
636 }
637 },
638 registry_make_dm_record(pp, pl, _pc, fp, fl, _fc, cp, cl, _cc, ts, prp, prl, _prc, ep, el2, _ec, op, ol) {
639 // Build DM record JSON directly
640 const peer = readStr(pp, pl), from = readStr(fp, fl), content = readStr(cp, cl);
641 const proto = readStr(prp, prl), eid = readStr(ep, el2);
642 const r = JSON.stringify({peer, from, content, ts: Number(ts), proto, eid});
643 setOutStr(op, ol, r);
644 },
645 registry_marmot_init(p, l, _c) { if (self.$hooks && self.$hooks.marmotInit) self.$hooks.marmotInit(readStr(p, l)); },
646 registry_marmot_send(rp, rl, _rc, cp, cl, _cc) { if (self.$hooks && self.$hooks.marmotSend) self.$hooks.marmotSend(readStr(rp, rl), readStr(cp, cl)); },
647 registry_marmot_subscribe() { if (self.$hooks && self.$hooks.marmotSubscribe) self.$hooks.marmotSubscribe(); },
648 registry_marmot_publish_kp(p, l, _c) { if (self.$hooks && self.$hooks.marmotPublishKP) self.$hooks.marmotPublishKP(readStr(p, l)); },
649 registry_marmot_list_groups(p, l, _c) { if (self.$hooks && self.$hooks.marmotListGroups) self.$hooks.marmotListGroups(readStr(p, l)); },
650 registry_save_dm_record(p, l, _c) { if (self.$hooks && self.$hooks.saveDMRecord) self.$hooks.saveDMRecord(readStr(p, l)); },
651 registry_broadcast_to_clients(p, l, _c) { if (self.$hooks && self.$hooks.broadcastToClients) self.$hooks.broadcastToClients(readStr(p, l)); },
652 registry_send_to_client(ip, il, _ic, mp, ml, _mc) { if (self.$hooks && self.$hooks.sendToClient) self.$hooks.sendToClient(readStr(ip, il), readStr(mp, ml)); },
653 registry_has_hook(p, l, _c) { return (self.$hooks && self.$hooks[readStr(p, l)]) ? 1 : 0; },
654 registry_load_module(p, l, _c, cbID) {
655 // Module loading is moxiejs-specific; in WASM all modules are compiled in
656 cb0(cbID);
657 },
658
659 // --- sw ---
660 sw_on_install(cbID) { self.addEventListener('install', (e) => cbi(cbID, 0)); },
661 sw_on_activate(cbID) { self.addEventListener('activate', (e) => cbi(cbID, 0)); },
662 sw_on_fetch(cbID) { self.addEventListener('fetch', (e) => cbi(cbID, 0)); },
663 sw_on_message(cbID) { self.addEventListener('message', (e) => cbi(cbID, 0)); },
664 sw_wait_until(event, cbID) { /* simplified - actual impl needs event ref */ cb0(cbID); },
665 sw_respond_with(event, resp) { /* needs event/response tracking */ },
666 sw_respond_with_network(event) { /* pass-through */ },
667 sw_respond_with_cache_first(event) { /* needs event tracking */ },
668 sw_respond_with_network_first(event) { /* needs event tracking */ },
669 sw_get_request_url(event, op, ol) { setOutStr(op, ol, ''); },
670 sw_get_request_path(event, op, ol) { setOutStr(op, ol, ''); },
671 sw_get_message_data(event, op, ol) { setOutStr(op, ol, ''); },
672 sw_get_message_client_id(event, op, ol) { setOutStr(op, ol, ''); },
673 sw_origin(op, ol) { setOutStr(op, ol, self.location ? self.location.origin : ''); },
674 sw_skip_waiting() { if (self.skipWaiting) self.skipWaiting(); },
675 sw_claim_clients(cbID) {
676 if (self.clients && self.clients.claim) self.clients.claim().then(() => cb0(cbID));
677 else cb0(cbID);
678 },
679 sw_match_clients(cbID) { /* simplified */ },
680 sw_post_message(client, p, l, _c) { /* needs client handle tracking */ },
681 sw_post_message_json(client, p, l, _c) { /* needs client handle tracking */ },
682 sw_get_client_by_id(p, l, _c, cbID) { cbii(cbID, 0, 0); },
683 sw_navigate(client, p, l, _c) { /* needs client handle tracking */ },
684 sw_cache_open(p, l, _c, cbID) {
685 caches.open(readStr(p, l)).then(() => cbi(cbID, 1));
686 },
687 sw_cache_add_all(cache, up, ul, _uc, count, cbID) {
688 const urls = splitNull(up, ul, count);
689 caches.open('v1').then(c => c.addAll(urls)).then(() => cb0(cbID));
690 },
691 sw_cache_from_manifests(cache, fp, fl, _fc, count, cbID) { cb0(cbID); },
692 sw_cache_put(cache, up, ul, _uc, resp, cbID) { cb0(cbID); },
693 sw_cache_match(p, l, _c, cbID) { cbi(cbID, 0); },
694 sw_cache_delete(p, l, _c, cbID) {
695 caches.delete(readStr(p, l)).then(() => cb0(cbID));
696 },
697 sw_fetch(p, l, _c, cbID) {
698 fetch(readStr(p, l)).then(r => cbii(cbID, 1, r.ok ? 1 : 0)).catch(() => cbii(cbID, 0, 0));
699 },
700 sw_fetch_all(up, ul, _uc, count, eachID, doneID) {
701 const urls = splitNull(up, ul, count);
702 Promise.all(urls.map((u, i) => fetch(u).then(r => cbiii(eachID, i, 1, r.ok ? 1 : 0)).catch(() => cbiii(eachID, i, 0, 0))))
703 .then(() => cb0(doneID));
704 },
705 sw_response_ok(resp) { return 0; },
706 sw_sse_connect(p, l, _c, cbID) {
707 const es = new EventSource(readStr(p, l));
708 es.onmessage = (e) => cbs(cbID, e.data);
709 return 1;
710 },
711 sw_sse_close(id) { /* needs handle tracking */ },
712 sw_set_timeout(ms, cbID) { return setTimeout(() => cb0(cbID), ms); },
713 sw_clear_timeout(t) { clearTimeout(t); },
714 sw_now_seconds() { return BigInt(Math.floor(Date.now() / 1000)); },
715 sw_now_millis() { return BigInt(Date.now()); },
716 sw_log(p, l, _c) { console.log(readStr(p, l)); },
717
718 // --- webgl ---
719 webgl_CreateContext(canvasId) { return webgl.CreateContext(canvasId); },
720 webgl_CreateContextFromHandle(el) { return webgl.CreateContextFromHandle(el); },
721 webgl_DeleteContext(ctx) { webgl.DeleteContext(ctx); },
722 webgl_Enable(ctx, cap) { webgl.Enable(ctx, cap); },
723 webgl_Disable(ctx, cap) { webgl.Disable(ctx, cap); },
724 webgl_Viewport(ctx, x, y, w, h) { webgl.Viewport(ctx, x, y, w, h); },
725 webgl_Scissor(ctx, x, y, w, h) { webgl.Scissor(ctx, x, y, w, h); },
726 webgl_BlendFunc(ctx, src, dst) { webgl.BlendFunc(ctx, src, dst); },
727 webgl_BlendFuncSeparate(ctx, sr, dr, sa, da) { webgl.BlendFuncSeparate(ctx, sr, dr, sa, da); },
728 webgl_BlendEquation(ctx, mode) { webgl.BlendEquation(ctx, mode); },
729 webgl_DepthFunc(ctx, fn) { webgl.DepthFunc(ctx, fn); },
730 webgl_DepthMask(ctx, flag) { webgl.DepthMask(ctx, flag); },
731 webgl_ClearColor(ctx, r, g, b, a) { webgl.ClearColor(ctx, r, g, b, a); },
732 webgl_ClearDepth(ctx, d) { webgl.ClearDepth(ctx, d); },
733 webgl_Clear(ctx, mask) { webgl.Clear(ctx, mask); },
734 webgl_Flush(ctx) { webgl.Flush(ctx); },
735 webgl_Finish(ctx) { webgl.Finish(ctx); },
736 webgl_GetError(ctx) { return webgl.GetError(ctx); },
737 webgl_GetInteger(ctx, pname) { return webgl.GetInteger(ctx, pname); },
738 webgl_IsEnabled(ctx, cap) { return webgl.IsEnabled(ctx, cap); },
739 webgl_PixelStorei(ctx, pname, param) { webgl.PixelStorei(ctx, pname, param); },
740 webgl_ReadPixels(ctx, x, y, w, h, fmt, typ, bufPtr, bufLen) { webgl.ReadPixels(ctx, x, y, w, h, fmt, typ, bufPtr, bufLen); },
741 webgl_CreateBuffer(ctx) { return webgl.CreateBuffer(ctx); },
742 webgl_DeleteBuffer(ctx, buf) { webgl.DeleteBuffer(ctx, buf); },
743 webgl_BindBuffer(ctx, target, buf) { webgl.BindBuffer(ctx, target, buf); },
744 webgl_BufferData(ctx, target, ptr, len, usage) { webgl.BufferData(ctx, target, ptr, len, usage); },
745 webgl_BufferSubData(ctx, target, off, ptr, len) { webgl.BufferSubData(ctx, target, off, ptr, len); },
746 webgl_CreateTexture(ctx) { return webgl.CreateTexture(ctx); },
747 webgl_DeleteTexture(ctx, tex) { webgl.DeleteTexture(ctx, tex); },
748 webgl_BindTexture(ctx, target, tex) { webgl.BindTexture(ctx, target, tex); },
749 webgl_ActiveTexture(ctx, unit) { webgl.ActiveTexture(ctx, unit); },
750 webgl_TexParameteri(ctx, target, pname, param) { webgl.TexParameteri(ctx, target, pname, param); },
751 webgl_TexImage2D(ctx, target, lvl, ifmt, w, h, border, fmt, typ, ptr, len) { webgl.TexImage2D(ctx, target, lvl, ifmt, w, h, border, fmt, typ, ptr, len); },
752 webgl_TexStorage2D(ctx, target, lvls, ifmt, w, h) { webgl.TexStorage2D(ctx, target, lvls, ifmt, w, h); },
753 webgl_TexSubImage2D(ctx, target, lvl, xo, yo, w, h, fmt, typ, ptr, len) { webgl.TexSubImage2D(ctx, target, lvl, xo, yo, w, h, fmt, typ, ptr, len); },
754 webgl_CopyTexSubImage2D(ctx, target, lvl, xo, yo, x, y, w, h) { webgl.CopyTexSubImage2D(ctx, target, lvl, xo, yo, x, y, w, h); },
755 webgl_GenerateMipmap(ctx, target) { webgl.GenerateMipmap(ctx, target); },
756 webgl_CreateFramebuffer(ctx) { return webgl.CreateFramebuffer(ctx); },
757 webgl_DeleteFramebuffer(ctx, fbo) { webgl.DeleteFramebuffer(ctx, fbo); },
758 webgl_BindFramebuffer(ctx, target, fbo) { webgl.BindFramebuffer(ctx, target, fbo); },
759 webgl_FramebufferTexture2D(ctx, target, att, texTarget, tex, lvl) { webgl.FramebufferTexture2D(ctx, target, att, texTarget, tex, lvl); },
760 webgl_CheckFramebufferStatus(ctx, target) { return webgl.CheckFramebufferStatus(ctx, target); },
761 webgl_InvalidateFramebuffer(ctx, target, att) { webgl.InvalidateFramebuffer(ctx, target, att); },
762 webgl_CreateRenderbuffer(ctx) { return webgl.CreateRenderbuffer(ctx); },
763 webgl_DeleteRenderbuffer(ctx, rbo) { webgl.DeleteRenderbuffer(ctx, rbo); },
764 webgl_BindRenderbuffer(ctx, target, rbo) { webgl.BindRenderbuffer(ctx, target, rbo); },
765 webgl_RenderbufferStorage(ctx, target, ifmt, w, h) { webgl.RenderbufferStorage(ctx, target, ifmt, w, h); },
766 webgl_FramebufferRenderbuffer(ctx, target, att, rbtarget, rbo) { webgl.FramebufferRenderbuffer(ctx, target, att, rbtarget, rbo); },
767 webgl_CreateShader(ctx, typ) { return webgl.CreateShader(ctx, typ); },
768 webgl_DeleteShader(ctx, sh) { webgl.DeleteShader(ctx, sh); },
769 webgl_ShaderSource(ctx, sh, ptr, len) { webgl.ShaderSource(ctx, sh, ptr, len); },
770 webgl_CompileShader(ctx, sh) { webgl.CompileShader(ctx, sh); },
771 webgl_GetShaderParameter(ctx, sh, pname) { return webgl.GetShaderParameter(ctx, sh, pname); },
772 webgl_GetShaderInfoLog(ctx, sh, ptr, len) { return webgl.GetShaderInfoLog(ctx, sh, ptr, len); },
773 webgl_CreateProgram(ctx) { return webgl.CreateProgram(ctx); },
774 webgl_DeleteProgram(ctx, prog) { webgl.DeleteProgram(ctx, prog); },
775 webgl_AttachShader(ctx, prog, sh) { webgl.AttachShader(ctx, prog, sh); },
776 webgl_LinkProgram(ctx, prog) { webgl.LinkProgram(ctx, prog); },
777 webgl_UseProgram(ctx, prog) { webgl.UseProgram(ctx, prog); },
778 webgl_GetProgramParameter(ctx, prog, pname) { return webgl.GetProgramParameter(ctx, prog, pname); },
779 webgl_GetProgramInfoLog(ctx, prog, ptr, len) { return webgl.GetProgramInfoLog(ctx, prog, ptr, len); },
780 webgl_BindAttribLocation(ctx, prog, idx, ptr, len) { webgl.BindAttribLocation(ctx, prog, idx, ptr, len); },
781 webgl_GetUniformLocation(ctx, prog, ptr, len) { return webgl.GetUniformLocation(ctx, prog, ptr, len); },
782 webgl_Uniform1f(ctx, loc, v) { webgl.Uniform1f(ctx, loc, v); },
783 webgl_Uniform2f(ctx, loc, v0, v1) { webgl.Uniform2f(ctx, loc, v0, v1); },
784 webgl_Uniform3f(ctx, loc, v0, v1, v2) { webgl.Uniform3f(ctx, loc, v0, v1, v2); },
785 webgl_Uniform4f(ctx, loc, v0, v1, v2, v3) { webgl.Uniform4f(ctx, loc, v0, v1, v2, v3); },
786 webgl_Uniform1i(ctx, loc, v) { webgl.Uniform1i(ctx, loc, v); },
787 webgl_EnableVertexAttribArray(ctx, idx) { webgl.EnableVertexAttribArray(ctx, idx); },
788 webgl_DisableVertexAttribArray(ctx, idx) { webgl.DisableVertexAttribArray(ctx, idx); },
789 webgl_VertexAttribPointer(ctx, idx, size, typ, norm, stride, off) { webgl.VertexAttribPointer(ctx, idx, size, typ, norm, stride, off); },
790 webgl_DrawArrays(ctx, mode, first, count) { webgl.DrawArrays(ctx, mode, first, count); },
791 webgl_DrawElements(ctx, mode, count, typ, off) { webgl.DrawElements(ctx, mode, count, typ, off); },
792
793 // --- text ---
794 text_Measure(fp, fl, sz, tp, tl, wp, hp) { text.Measure(fp, fl, sz, tp, tl, wp, hp); },
795 text_Render(fp, fl, sz, tp, tl, mw, dp, dc) { return text.Render(fp, fl, sz, tp, tl, mw, dp, dc); },
796
797 // --- app ---
798 app_CreateFullscreenCanvas() { app.CreateFullscreenCanvas(); },
799 app_CreateWebGLContext() { return app.CreateWebGLContext(); },
800 app_GetDevicePixelRatio() { return app.GetDevicePixelRatio(); },
801 app_GetCanvasCSSSize(wPtr, hPtr) { app.GetCanvasCSSSize(wPtr, hPtr); },
802 app_SetCanvasBacking(w, h) { app.SetCanvasBacking(w, h); },
803 app_RequestRAF() { app.RequestRAF(); },
804 app_SetCursor(ptr, len) { app.SetCursor(ptr, len); },
805 app_SetTitle(ptr, len) { app.SetTitle(ptr, len); },
806 app_SetFullscreen(full) { app.SetFullscreen(full); },
807 app_RegisterPointerEvents() { app.RegisterPointerEvents(); },
808 app_RegisterKeyEvents() { app.RegisterKeyEvents(); },
809 app_RegisterResizeEvents() { app.RegisterResizeEvents(); },
810 // Spawn bridge: channels are SPSC rings over SharedArrayBuffer, and a
811 // domain is a dedicated Worker running the same module. The module only
812 // ever sees an int32 handle, so the host keeps the table.
813 channel_create(slotSize, slotCount) {
814 const h = next++;
815 handles.set(h, sabChannel.create(slotSize, slotCount));
816 return h;
817 },
818 channel_send(h, ptr, len) {
819 const sab = handles.get(h);
820 if (sab) sabChannel.send(sab, readBytes(ptr, len));
821 },
822 channel_recv(h, ptr, cap) {
823 const sab = handles.get(h);
824 if (!sab) return -1;
825 const dst = new Uint8Array(memory.buffer, ptr, cap);
826 return sabChannel.recv(sab, dst);
827 },
828 channel_close(h) {
829 const sab = handles.get(h);
830 if (sab) { sabChannel.close(sab); handles.delete(h); }
831 },
832 is_worker_context() { return isWorker; },
833 spawn_domain(fnIdx, argPtr, argLen, chanHandlesPtr, nChans) {
834 // Workers are started from the host: the module cannot create one. The
835 // entry index and the bound channel handles are handed to the child over
836 // its own message port so it can bind the same rings from the far end.
837 //
838 // Every domain runs in a Worker, root and child alike, so being a worker
839 // is not a reason to refuse: the runtime only requires that spawn is
840 // reached from a worker context (channel receives block in Atomics.wait,
841 // which the page thread forbids), and a Worker may start another Worker.
842 const args = readBytes(argPtr, argLen);
843 const chans = [];
844 const dv = new DataView(memory.buffer);
845 for (let i = 0; i < nChans; i++) {
846 const h = dv.getInt32(chanHandlesPtr + i * 4, true);
847 if (handles.has(h)) chans.push({ handle: h, sab: handles.get(h) });
848 }
849 spawnWorker(fnIdx, args, chans);
850 },
851 };
852 }
853
854 // buildWorkerBridge returns the bridge for a spawned domain: the same
855 // functions as the page, over the channel rings the parent handed over.
856 export function buildWorkerBridge(channels) {
857 return buildBridge({ channels, isWorker: true });
858 }
859
860 // buildHostImports returns the non-bridge import object every Moxie wasm
861 // module needs: the WASI preview1 surface it actually calls, plus the env
862 // shims the stdlib closure pulls in. mem is a getter for the module's memory,
863 // not the memory itself, because these imports are built before instantiation
864 // completes and the buffer only exists once the instance is returned.
865 export function buildHostImports(mem) {
866 return {
867 wasi_snapshot_preview1: {
868 fd_write(fd, iovs, iovs_len, nwritten) {
869 const dv = new DataView(mem().buffer);
870 let totalWritten = 0;
871 for (let i = 0; i < iovs_len; i++) {
872 const ptr = dv.getUint32(iovs + i * 8, true);
873 const len = dv.getUint32(iovs + i * 8 + 4, true);
874 const s = decoder.decode(new Uint8Array(mem().buffer, ptr, len));
875 // The module runs in a Worker, so its output cannot reach the page
876 // through the console alone: forward each write over the port and
877 // let the page decide where stdout and stderr go.
878 globalThis.postMessage({ kind: fd === 1 ? 'stdout' : 'stderr', text: s });
879 totalWritten += len;
880 }
881 dv.setUint32(nwritten, totalWritten, true);
882 return 0;
883 },
884 // A domain has no stdin: report EOF so runtime.buffered returns 0 and
885 // os.Stdin.Read yields instead of blocking on a read that never comes.
886 fd_read(fd, iovs, iovs_len, nread) {
887 const dv = new DataView(mem().buffer);
888 dv.setUint32(nread, 0, true);
889 return 0;
890 },
891 clock_time_get(clockID, precision, time) {
892 const now = clockID === 1
893 ? performance.now() * 1_000_000
894 : Date.now() * 1_000_000;
895 const dv = new DataView(mem().buffer);
896 dv.setBigInt64(time, BigInt(Math.floor(now)), true);
897 return 0;
898 },
899 },
900 // Host shims for pkg/mxutil. Those functions are declared with
901 // //export, which makes them imports under wasm, and mxutil arrives in
902 // an application's closure because the standard library's errors
903 // package uses its GrowCap helper. A browser app has no filesystem, so
904 // these report failure rather than pretending to work; they are only
905 // reached if something calls them, which nothing in a page does.
906 //
907 // Anything else the module imports from env is a host shim that no page
908 // code reaches (the whole symbol is pulled in by the stdlib closure).
909 // Resolving unknown names to a no-op keeps the module loadable; naming
910 // each one would mean editing this file every time the closure shifts.
911 env: new Proxy({
912 mxc_readfile: () => 0,
913 mxc_filesize: () => -1,
914 mxc_getenv: () => 0,
915 mxc_listdir: () => 0,
916 mxc_writefile: () => -1,
917 mxc_mkdir: () => -1,
918 // libc write, pulled in by the compiler-side C shims.
919 write: () => -1,
920 // syscall.Exit has no process to terminate in a browser. Report the
921 // code through the host's error path so it is visible, not silent.
922 'syscall.Exit': (code) => { throw new Error(`exit ${code | 0}`); },
923 }, {
924 get(target, prop) {
925 return prop in target ? target[prop] : (() => 0);
926 },
927 }),
928 };
929 }
930
931 // spawnWorker starts a new domain for a Moxie spawn. The child runs the same
932 // module in a dedicated Worker; the module cannot create one itself, and the
933 // bound channel rings travel to the child as SharedArrayBuffers, which is the
934 // only inter-domain state there is.
935 function spawnWorker(fnIdx, argBytes, chans) {
936 startDomain({
937 kind: 'entry',
938 wasm: globalThis.__moxie_wasm_url,
939 fnIdx,
940 args: argBytes,
941 chans: chans.map(c => ({ handle: c.handle, sab: c.sab })),
942 }, null, true);
943 }
944
945 // startDomain creates the Worker that runs a Moxie domain and posts it its
946 // boot message. A domain always runs in its own Worker: channel receives
947 // block in Atomics.wait, which the browser forbids on the page thread, and a
948 // domain that spawns must itself be able to create a Worker.
949 export function startDomain(boot, onSettled, relayUp) {
950 const w = new Worker(new URL('./worker-host.mjs', import.meta.url), { type: 'module' });
951 // A spawned domain reports to the domain that spawned it, not to the page, so
952 // its output would otherwise stop one hop short and never be seen. relayUp
953 // forwards it along the chain; in the page the same postMessage has no
954 // listener and is ignored, so this is safe at every level.
955 const up = (kind, text) => {
956 if (relayUp) globalThis.postMessage({ kind, text });
957 };
958 w.addEventListener('message', (e) => {
959 const m = e.data;
960 if (!m) return;
961 if (m.kind === 'stdout') { console.log(m.text); up('stdout', m.text); }
962 else if (m.kind === 'stderr') { console.error(m.text); up('stderr', m.text); }
963 else if (m.kind === 'done') { w.terminate(); onSettled && onSettled(null); }
964 else if (m.kind === 'error') {
965 w.terminate();
966 if (onSettled) onSettled(new Error(m.error));
967 // A spawned domain has no caller to reject: the parent reaches it only
968 // through channels. Relay the failure to the page rather than dropping
969 // it, or a domain that dies on startup just leaves the parent waiting.
970 else globalThis.postMessage({ kind: 'stderr', text: '[domain] ' + m.error + '\n' });
971 }
972 });
973 w.addEventListener('error', (e) => {
974 const msg = String(e.message || e.error || e);
975 if (onSettled) onSettled(new Error(msg));
976 else globalThis.postMessage({ kind: 'stderr', text: '[domain] ' + msg + '\n' });
977 });
978 w.postMessage(boot);
979 return w;
980 }
981
982 // setHostModule points the host-side callbacks at a freshly instantiated
983 // module. Called by the Worker host once its instance exists, so reverse
984 // callbacks (__cb*) reach the right instance.
985 export function setHostModule(inst) {
986 instance = inst;
987 memory = inst.exports.memory;
988 }
989
990
991