From 092635be5a447ea46081decb551b9da2989abc07 Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Wed, 1 Jul 2026 20:54:24 +0200 Subject: [PATCH] fix(realtime): guarda cleanup de WebSocket em CONNECTING (StrictMode) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fechar o socket ainda em CONNECTING (double-mount do StrictMode em dev) gerava "WebSocket is closed before the connection is established". closeSocket() adia o close para o onopen e neutraliza handlers; reconexão por queda inesperada intacta. Co-Authored-By: Claude Opus 4.8 --- frontend/services/realtime.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/frontend/services/realtime.ts b/frontend/services/realtime.ts index cdee028..e8db0b1 100644 --- a/frontend/services/realtime.ts +++ b/frontend/services/realtime.ts @@ -44,11 +44,30 @@ function connect() { sock.onerror = () => { try { sock.close(); } catch { /* ignore */ } }; } +// Fecha um socket que NÓS abrimos. Se ainda está em CONNECTING, chamar close() +// agora dispara "WebSocket is closed before the connection is established" +// (comum no double-mount do React StrictMode em DEV) → adiamos o close para o +// onopen. Em todos os casos neutralizamos os handlers para o socket morto não +// disparar reconexão (a reconexão automática por queda inesperada continua no +// onclose original definido em connect()). +function closeSocket(sock: WebSocket | null) { + if (!sock) return; + sock.onmessage = null; + sock.onerror = null; + sock.onclose = null; + if (sock.readyState === WebSocket.CONNECTING) { + sock.onopen = () => { try { sock.close(); } catch { /* ignore */ } }; + } else { + sock.onopen = null; + try { sock.close(); } catch { /* ignore */ } + } +} + function ensureConnected() { const url = buildUrl(); if (!url) return; // Clínica/token mudou → reconecta no canal certo. - if (ws && url !== currentUrl) { try { ws.close(); } catch { /* ignore */ } ws = null; } + if (ws && url !== currentUrl) { closeSocket(ws); ws = null; } if (!ws || ws.readyState === WebSocket.CLOSING || ws.readyState === WebSocket.CLOSED) connect(); } @@ -61,13 +80,13 @@ export const Realtime = { handlers.delete(handler); if (handlers.size === 0) { if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } - if (ws) { try { ws.close(); } catch { /* ignore */ } ws = null; } + if (ws) { closeSocket(ws); ws = null; } } }; }, /** Força reconexão (ex.: ao trocar de workspace). */ reconnect() { - if (ws) { try { ws.close(); } catch { /* ignore */ } ws = null; } + if (ws) { closeSocket(ws); ws = null; } backoff = 1000; ensureConnected(); },