bc.mjs raw
1 // TinyJS Runtime — Bus Bridge (Relay SW)
2 // Routes bus messages via MessagePort to/from the page.
3 // Listener registered at top level to catch port before Open() runs.
4
5 // Global error handler — catches errors and forwards to page via bus port.
6 self.addEventListener('error', (e) => {
7 console.error('relay-sw uncaught:', e.message, e.filename, e.lineno);
8 if (self._busPort) {
9 self._busPort.postMessage('{"from":"relay","to":"shell","msg":["LOG","relay","UNCAUGHT: ' + String(e.message).replace(/"/g, '\\"').replace(/\n/g, ' ') + ' at ' + String(e.filename).replace(/"/g, '') + ':' + e.lineno + '"]}');
10 }
11 });
12 self.addEventListener('unhandledrejection', (e) => {
13 var msg = e.reason ? (e.reason.message || String(e.reason)) : 'unknown';
14 console.error('relay-sw unhandled rejection:', msg);
15 if (self._busPort) {
16 self._busPort.postMessage('{"from":"relay","to":"shell","msg":["LOG","relay","REJECTION: ' + String(msg).replace(/"/g, '\\"').replace(/\n/g, ' ') + '"]}');
17 }
18 });
19
20 let _onMessage = null;
21 let _queue = [];
22
23 // waitUntil with a 25s promise — tells browser "I'm busy, don't terminate."
24 // With 10s keepalive interval from page, there are always 2-3 overlapping
25 // promises. Browser MUST keep SW alive while any waitUntil promise is pending.
26 function _holdOpen(ev) {
27 if (ev.waitUntil) ev.waitUntil(new Promise(r => setTimeout(r, 25000)));
28 }
29
30 // Capture port immediately — before goroutine scheduler calls Open().
31 self.addEventListener('message', (ev) => {
32 if (ev.data && ev.data.type === 'bus-port' && ev.ports && ev.ports[0]) {
33 self._busPort = ev.ports[0];
34 self._busPort.onmessage = _portHandler;
35 // Flush queued messages.
36 for (const m of _queue) self._busPort.postMessage(m);
37 _queue = [];
38 _holdOpen(ev);
39 return;
40 }
41 // Keepalive from page — extend SW lifetime, nothing else needed.
42 if (ev.data === 'keepalive') {
43 _holdOpen(ev);
44 return;
45 }
46 // Direct string message (fallback).
47 const d = ev.data;
48 if (typeof d === 'string' && d.length > 0 && d[0] === '{' && _onMessage) {
49 _holdOpen(ev);
50 try {
51 _onMessage(d);
52 } catch (e) {
53 console.error('relay-sw: direct msg handler CRASH:', e.message, e.stack);
54 }
55 }
56 });
57
58 function _portHandler(pev) {
59 const d = pev.data;
60 // Health check: page sends PING, we respond PONG to prove we're alive.
61 if (d === 'PING') {
62 if (self._busPort) self._busPort.postMessage('PONG');
63 return;
64 }
65 if (typeof d === 'string' && d.length > 0 && d[0] === '{' && _onMessage) {
66 try {
67 _onMessage(d);
68 } catch (e) {
69 console.error('relay-sw: bus handler CRASH:', e.message, e.stack);
70 // Forward crash to shell SW via port so it reaches the page console.
71 if (self._busPort) {
72 self._busPort.postMessage('{"from":"relay","to":"shell","msg":["LOG","relay","BUS CRASH: ' + String(e.message).replace(/"/g, '\\"') + '"]}');
73 }
74 }
75 }
76 }
77
78 export function Open(name, onMessage) {
79 _onMessage = onMessage;
80 // If port arrived before Open(), wire up handler now.
81 if (self._busPort) {
82 self._busPort.onmessage = _portHandler;
83 }
84 return 1;
85 }
86
87 export function Send(bcId, msg) {
88 if (self._busPort) {
89 try { self._busPort.postMessage(msg); }
90 catch (e) { console.error('relay bus send failed:', e.message); }
91 } else {
92 _queue.push(msg);
93 }
94 }
95
96 export function Close(bcId) {}
97