refactor(service-worker): simplify WASM loading and add retry mechanism
- Removed promise-based duplicate loading prevention for WASM - Updated Go version in commented wasm_exec URL to 1.25.3 - Introduced proxy_handler with error handling and automatic retry on failure - Streamlined activate event to directly call runWASM instead of wrapper This refactor improves reliability by allowing retries when the portal proxy fails, while simplifying the code structure.
lemon-mint committed
Oct 31, 2025 at 12:22 UTC
cba90c7bc7b14a8d03167e9a0f98f63db214ef68
3 files changed
+70
-77
Dockerfile.frontend
new
+22
@@ -0,0 +1,22 @@
1
+FROM golang:1 AS builder
2
+
3
+WORKDIR /src
4
+
5
+COPY go.mod go.sum ./
6
+RUN --mount=type=cache,target=/go/pkg/mod \
7
+ go mod download
8
+
9
+COPY . .
10
+
11
+RUN GOOS=js GOARCH=wasm go build -trimpath -ldflags="-s -w" -o bin/main.wasm ./cmd/webclient
12
+
13
+FROM nginx
14
+
15
+COPY --from=builder /src/cmd/webclient/index.html /usr/share/nginx/html/index.html
16
+COPY --from=builder /src/cmd/webclient/service-worker.js /usr/share/nginx/html/service-worker.js
17
+COPY --from=builder /src/cmd/webclient/wasm_exec.js /usr/share/nginx/html/wasm_exec.js
18
+COPY --from=builder /src/bin/main.wasm /usr/share/nginx/html/main.wasm
19
+
20
+EXPOSE 80
21
+
22
+CMD ["nginx", "-g", "daemon off;"]
cmd/webclient/main.wasm
Binary files a/cmd/webclient/main.wasm and /dev/null differ
cmd/webclient/service-worker.js
+48
-77
@@ -1,68 +1,40 @@
1
-// 1. Import WASM execution environment
2
-// (You can use CDN or local path)
3
-// const wasm_exec_URL = "https://cdn.jsdelivr.net/gh/golang/go@go1.19/misc/wasm/wasm_exec.js";
1
+// const wasm_exec_URL = "https://cdn.jsdelivr.net/gh/golang/go@go1.25.3/misc/wasm/wasm_exec.js";
2
const wasm_exec_URL = "/wasm_exec.js";
3
importScripts(wasm_exec_URL);
4
7
-// --- Global constants and variables ---
8
-
5
+let _portal_proxy;
6
const wasm_URL = "/main.wasm";
10
-// Path matching with importScripts
7
const CACHE_NAME = "WASM_Cache_v1";
8
13
-// Promise to manage WASM loading state (prevents duplicate loading)
14
-let wasmReadyPromise = null;
15
-
16
-/**
17
- * Loads and executes Go WASM.
18
- */
9
async function runWASM() {
20
- const go = new Go();
21
- const cache = await caches.open(CACHE_NAME);
22
- let wasm_file;
10
+ const go = new Go();
11
+ const cache = await caches.open(CACHE_NAME);
12
+ let wasm_file;
13
24
- const cache_wasm = await cache.match(wasm_URL);
25
-
26
- if (cache_wasm) {
27
- console.log("Service Worker: Loading WASM from cache...");
28
- wasm_file = await cache_wasm.arrayBuffer();
29
- } else {
30
- console.warn("Service Worker: WASM not in cache. Fetching from network...");
31
- const resp = await fetch(wasm_URL);
32
- wasm_file = await resp.arrayBuffer();
33
- await cache.put(wasm_URL, new Response(wasm_file.slice(0)));
34
- }
14
+ const cache_wasm = await cache.match(wasm_URL);
15
36
- console.log("Service Worker: Instantiating WebAssembly...");
37
- const { instance } = await WebAssembly.instantiate(wasm_file, go.importObject);
16
+ if (cache_wasm) {
17
+ console.log("Service Worker: Loading WASM from cache...");
18
+ wasm_file = await cache_wasm.arrayBuffer();
19
+ } else {
20
+ console.warn("Service Worker: WASM not in cache. Fetching from network...");
21
+ const resp = await fetch(wasm_URL);
22
+ wasm_file = await resp.arrayBuffer();
23
+ await cache.put(wasm_URL, new Response(wasm_file.slice(0)));
24
+ }
25
39
- // go.run() executes Go's main() and returns
40
- // when _portal_proxy callback is registered
41
- go.run(instance);
42
- console.log("Service Worker: Go WASM execution complete. _portal_proxy is ready.");
43
-}
26
+ console.log("Service Worker: Instantiating WebAssembly...");
27
+ const { instance } = await WebAssembly.instantiate(wasm_file, go.importObject);
28
45
-/**
46
- * Wrapper function that ensures runWASM() is executed only once.
47
- * @returns {Promise<void>} Promise that resolves when WASM is ready
48
- */
49
-function getWasmReady() {
50
- if (!wasmReadyPromise) {
51
- console.log("Service Worker: Starting WASM loading...");
52
- wasmReadyPromise = runWASM().catch(err => {
53
- console.error("Service Worker: WASM execution failed:", err);
54
- wasmReadyPromise = null; // Allow retry on next request if failed
55
- throw err; // Propagate error to caller (fetch handler)
56
- });
57
- }
58
- return wasmReadyPromise;
29
+ // go.run() executes Go's main() and returns
30
+ // when _portal_proxy callback is registered
31
+ go.run(instance);
32
+ console.log("Service Worker: Go WASM execution complete. _portal_proxy is ready.");
33
}
34
61
-
62
-// --- 1. Install event listener ---
35
self.addEventListener('install', (event) => {
36
console.log('Service Worker: Installing...');
65
-
37
+
38
event.waitUntil(
39
(async () => {
40
const cache = await caches.open(CACHE_NAME);
@@ -85,40 +57,39 @@ self.addEventListener('activate', (event) => {
57
await self.clients.claim();
58
// Preload WASM to prepare for next fetch requests
59
console.log('Service Worker: Starting Go WASM preloading...');
88
- await getWasmReady();
60
+ await runWASM();
61
console.log('Service Worker: Go WASM preloading complete.');
62
})()
63
);
64
});
65
66
95
-// --- 3. Fetch event listener ---
96
-// Pass all requests to Go handler.
97
-self.addEventListener('fetch', (event) => {
98
- const url = new URL(event.request.url);
99
- console.log(`Service Worker: Forwarding request to Portal Proxy handler: ${url.pathname}`);
100
-
101
- event.respondWith((async () => {
102
- try {
103
- // Wait until WASM is ready
104
- await getWasmReady();
67
+async function sleep(ms) {
68
+ return new Promise(resolve => setTimeout(resolve, ms));
69
+}
70
106
- if (typeof _portal_proxy !== 'undefined') {
107
- // WASM is ready and handler function exists
71
+async function proxy_handler(event) {
72
+ console.log('Service Worker: Fetch event:', event.request);
73
+ if (typeof _portal_proxy != 'undefined') {
74
+ event.respondWith((async () => {
75
+ try {
76
+ const resp = await _portal_proxy(event.request);
77
+ return resp;
78
+ } catch {
79
+ _portal_proxy = undefined;
80
+ await runWASM();
81
const resp = await _portal_proxy(event.request);
82
return resp;
110
- } else {
111
- // Abnormal situation where function doesn't exist even though getWasmReady() succeeded
112
- console.error("Service Worker: WASM loading succeeded but _portal_proxy is not defined.");
113
- return new Response("WASM handler is not available.", { status: 500 });
83
}
115
- } catch (err) {
116
- // 1. getWasmReady() failure (WASM load/execution failure)
117
- // 2. _portal_http(event.request) failure (Go handler internal error)
118
- console.error(`Service Worker: Portal Proxy handler processing failed (falling back to network): ${err}`, event.request.url);
119
-
120
- // Fallback to network when WASM handler fails
121
- return fetch(event.request);
122
- }
123
- })());
124
-});
\ No newline at end of file
84
+ })());
85
+ return;
86
+ }
87
+ console.log('Service Worker: _portal_proxy is not defined. Fetch event ignored.');
88
+
89
+ _portal_proxy = undefined;
90
+ await runWASM();
91
+ await sleep(100);
92
+ await proxy_handler(event);
93
+}
94
+
95
+self.addEventListener('fetch', proxy_handler);