fix: websocket for upload image control,wasm initialize loading.
Hee Sung Son committed
Nov 8, 2025 at 00:27 UTC
392d41207fd226a31199f411db3966ffecff3009
2 files changed
+114
-82
cmd/webclient/polyfill.js
+84
-75
@@ -41,6 +41,7 @@
41
this._connId = null;
42
this._isClosed = false;
43
this._wsKey = generateWebSocketKey();
44
+ this._frameBuffer = new Uint8Array(0);
45
46
// Setup Service Worker message listener
47
this._setupMessageListener();
@@ -236,88 +237,96 @@
237
238
if (this.readyState !== WebSocket.OPEN) return;
239
239
- // Simple frame parsing - check if this is a complete frame
240
- if (data.length < 2) return;
241
-
242
- const byte1 = data[0];
243
- const byte2 = data[1];
244
-
245
- const fin = (byte1 & 0x80) !== 0;
246
- const opcode = byte1 & 0x0F;
247
- const masked = (byte2 & 0x80) !== 0;
248
- let payloadLen = byte2 & 0x7F;
249
-
250
- let offset = 2;
240
+ // Append incoming data to frame buffer
241
+ const newBuffer = new Uint8Array(this._frameBuffer.length + data.length);
242
+ newBuffer.set(this._frameBuffer);
243
+ newBuffer.set(data, this._frameBuffer.length);
244
+ this._frameBuffer = newBuffer;
245
+
246
+ // Process all complete frames in buffer
247
+ while (this._frameBuffer.length >= 2) {
248
+ const byte1 = this._frameBuffer[0];
249
+ const byte2 = this._frameBuffer[1];
250
+
251
+ const fin = (byte1 & 0x80) !== 0;
252
+ const opcode = byte1 & 0x0F;
253
+ const masked = (byte2 & 0x80) !== 0;
254
+ let payloadLen = byte2 & 0x7F;
255
+
256
+ let offset = 2;
257
+
258
+ // Handle extended payload length
259
+ if (payloadLen === 126) {
260
+ if (this._frameBuffer.length < 4) return; // Need more data
261
+ payloadLen = (this._frameBuffer[2] << 8) | this._frameBuffer[3];
262
+ offset = 4;
263
+ } else if (payloadLen === 127) {
264
+ if (this._frameBuffer.length < 10) return; // Need more data
265
+ // For simplicity, assuming payload < 2^32
266
+ payloadLen = (this._frameBuffer[6] << 24) | (this._frameBuffer[7] << 16) | (this._frameBuffer[8] << 8) | this._frameBuffer[9];
267
+ offset = 10;
268
+ }
269
252
- // Handle extended payload length
253
- if (payloadLen === 126) {
254
- if (data.length < 4) return; // Need more data
255
- payloadLen = (data[2] << 8) | data[3];
256
- offset = 4;
257
- } else if (payloadLen === 127) {
258
- if (data.length < 10) return; // Need more data
259
- // For simplicity, assuming payload < 2^32
260
- payloadLen = (data[6] << 24) | (data[7] << 16) | (data[8] << 8) | data[9];
261
- offset = 10;
262
- }
270
+ // Server messages should not be masked
271
+ if (masked) {
272
+ offset += 4; // Skip mask key
273
+ }
274
264
- // Server messages should not be masked
265
- if (masked) {
266
- offset += 4; // Skip mask key
267
- }
275
+ if (this._frameBuffer.length < offset + payloadLen) {
276
+ // Incomplete frame, wait for more data
277
+ return;
278
+ }
279
269
- if (data.length < offset + payloadLen) {
270
- // Incomplete frame, buffer it
271
- // TODO: Implement frame buffering
272
- return;
273
- }
280
+ const payload = this._frameBuffer.slice(offset, offset + payloadLen);
281
275
- const payload = data.slice(offset, offset + payloadLen);
282
+ // Remove processed frame from buffer
283
+ this._frameBuffer = this._frameBuffer.slice(offset + payloadLen);
284
277
- // Handle different opcodes
278
- if (opcode === 0x01) {
279
- // Text frame
280
- const text = new TextDecoder().decode(payload);
281
- const event = new MessageEvent("message", {
282
- data: text,
283
- origin: new URL(this.url).origin,
284
- });
285
- if (this.onmessage) {
286
- this.onmessage(event);
287
- }
288
- this.dispatchEvent(event);
289
- } else if (opcode === 0x02) {
290
- // Binary frame
291
- let eventData;
292
- if (this.binaryType === "blob") {
293
- eventData = new Blob([payload]);
294
- } else {
295
- eventData = payload.buffer;
296
- }
297
- const event = new MessageEvent("message", {
298
- data: eventData,
299
- origin: new URL(this.url).origin,
300
- });
301
- if (this.onmessage) {
302
- this.onmessage(event);
303
- }
304
- this.dispatchEvent(event);
305
- } else if (opcode === 0x08) {
306
- // Close frame
307
- let code = 1000;
308
- let reason = "";
309
- if (payload.length >= 2) {
310
- code = (payload[0] << 8) | payload[1];
311
- if (payload.length > 2) {
312
- reason = new TextDecoder().decode(payload.slice(2));
285
+ // Handle different opcodes
286
+ if (opcode === 0x01) {
287
+ // Text frame
288
+ const text = new TextDecoder().decode(payload);
289
+ const event = new MessageEvent("message", {
290
+ data: text,
291
+ origin: new URL(this.url).origin,
292
+ });
293
+ if (this.onmessage) {
294
+ this.onmessage(event);
295
+ }
296
+ this.dispatchEvent(event);
297
+ } else if (opcode === 0x02) {
298
+ // Binary frame
299
+ let eventData;
300
+ if (this.binaryType === "blob") {
301
+ eventData = new Blob([payload]);
302
+ } else {
303
+ eventData = payload.buffer;
304
+ }
305
+ const event = new MessageEvent("message", {
306
+ data: eventData,
307
+ origin: new URL(this.url).origin,
308
+ });
309
+ if (this.onmessage) {
310
+ this.onmessage(event);
311
+ }
312
+ this.dispatchEvent(event);
313
+ } else if (opcode === 0x08) {
314
+ // Close frame
315
+ let code = 1000;
316
+ let reason = "";
317
+ if (payload.length >= 2) {
318
+ code = (payload[0] << 8) | payload[1];
319
+ if (payload.length > 2) {
320
+ reason = new TextDecoder().decode(payload.slice(2));
321
+ }
322
}
323
+ this._handleDataClose({ code, reason });
324
+ } else if (opcode === 0x09) {
325
+ // Ping - send pong
326
+ this._sendPong(payload);
327
+ } else if (opcode === 0x0A) {
328
+ // Pong - ignore
329
}
315
- this._handleDataClose({ code, reason });
316
- } else if (opcode === 0x09) {
317
- // Ping - send pong
318
- this._sendPong(payload);
319
- } else if (opcode === 0x0A) {
320
- // Pong - ignore
330
}
331
}
332
cmd/webclient/service-worker.js
+30
-7
@@ -216,15 +216,26 @@ self.addEventListener("fetch", (e) => {
216
}
217
);
218
}
219
+ }
220
+
221
+ // Wait for WASM to be ready (increased timeout for Safari)
222
+ let waitCount = 0;
223
+ const maxWait = 100; // 10 seconds (100 × 100ms)
224
+ while (typeof __go_jshttp === "undefined" && waitCount < maxWait) {
225
+ await new Promise((resolve) => setTimeout(resolve, 100));
226
+ waitCount++;
227
+ }
228
220
- let waitCount = 0;
221
- while (loading && waitCount < 50) {
222
- if (typeof __go_jshttp !== "undefined") {
223
- break;
229
+ // If still not ready after timeout, return error
230
+ if (typeof __go_jshttp === "undefined") {
231
+ console.error("[SW] WASM not ready after timeout");
232
+ return new Response(
233
+ "WASM initialization timeout. Please refresh the page.",
234
+ {
235
+ status: 503,
236
+ statusText: "Service Unavailable",
237
}
225
- await new Promise((resolve) => setTimeout(resolve, 100));
226
- waitCount++;
227
- }
238
+ );
239
}
240
241
try {
@@ -234,6 +245,18 @@ self.addEventListener("fetch", (e) => {
245
console.error("[SW] Request handling error:", error);
246
__go_jshttp = undefined;
247
await init();
248
+
249
+ // Wait again after reinit
250
+ waitCount = 0;
251
+ while (typeof __go_jshttp === "undefined" && waitCount < maxWait) {
252
+ await new Promise((resolve) => setTimeout(resolve, 100));
253
+ waitCount++;
254
+ }
255
+
256
+ if (typeof __go_jshttp === "undefined") {
257
+ throw new Error("WASM reinitialization failed");
258
+ }
259
+
260
const resp = await __go_jshttp(e.request);
261
return resp;
262
}