1 // The wasm worker loader, shared by the wallet worker and the light-client workers.
2 //
3 // The worker is created with the module and the global it should wait for:
4 //
5 // new Worker('worker.js?wasm=tinywallet.wasm&global=tinywallet')
6 //
7 // The page then posts {kind: 'call', fn, args} and gets {kind: 'call', id, result} back. The
8 // args may carry MessagePorts, which is how the wallet is handed its chain channels.
9 10 const params = new URLSearchParams(self.location.search);
11 const wasmPath = params.get('wasm') || 'tinywallet.wasm';
12 const globalName = params.get('global') || 'tinywallet';
13 14 // The worker's console is invisible from the page, and a wasm module that fails to connect
15 // logs there and nowhere else: a page then shows "opening the workers…" forever with no
16 // reason. Every console line is forwarded with the ready/call messages so the page can show
17 // it, and the original output is kept.
18 for (const level of ['log', 'warn', 'error']) {
19 const original = console[level].bind(console);
20 console[level] = (...args) => {
21 original(...args);
22 try {
23 self.postMessage({ kind: 'log', level, text: args.map((arg) => String(arg)).join(' ') });
24 } catch (error) {
25 // A worker that cannot post is already gone; nothing to report to.
26 }
27 };
28 }
29 30 let started = null;
31 32 function ready() {
33 if (self[globalName]) return Promise.resolve();
34 if (!started) started = start();
35 return started;
36 }
37 38 async function start() {
39 importScripts('wasm_exec.js');
40 41 const go = new Go();
42 const response = await fetch(wasmPath);
43 const { instance } = await WebAssembly.instantiateStreaming(response, go.importObject);
44 45 // go.run never settles for a program that parks in select{}, which is what a worker does:
46 // it starts the module and returns to the event loop.
47 go.run(instance);
48 49 for (let attempt = 0; attempt < 600; attempt++) {
50 if (self[globalName]) return;
51 await new Promise((resolve) => setTimeout(resolve, 50));
52 }
53 54 throw new Error(wasmPath + ' did not start');
55 }
56 57 self.onmessage = async (event) => {
58 const message = event.data;
59 60 if (!message || message.kind !== 'call') return;
61 62 try {
63 await ready();
64 const target = self[globalName][message.fn];
65 if (typeof target !== 'function') throw new Error('no ' + message.fn + ' on ' + globalName);
66 const result = target.apply(null, message.args || []);
67 self.postMessage({ kind: 'call', id: message.id, result });
68 } catch (error) {
69 self.postMessage({ kind: 'call', id: message.id, error: String(error && error.message ? error.message : error) });
70 }
71 };
72 73 // The page needs to know when the module is up, because the first call is the one that
74 // creates the ports.
75 ready().then(
76 () => self.postMessage({ kind: 'ready' }),
77 (error) => self.postMessage({ kind: 'failed', error: String(error) }),
78 );
79