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(); },