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 }