diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx index 6403cc6..1d1f714 100644 --- a/apps/web/src/components/terminal.tsx +++ b/apps/web/src/components/terminal.tsx @@ -56,6 +56,7 @@ export const TerminalComponent: React.FC = ({ }); const lastPingRef = useRef(0); const heartbeatCheckRef = useRef(null); + const isUnmountingRef = useRef(false); const calculateFontSize = useCallback(() => { if (!isMobile) return fontSize; @@ -70,10 +71,12 @@ export const TerminalComponent: React.FC = ({ const wsHost = apiUrl.replace(/^https?:\/\//, "").replace(/\/+$/, ""); const wsUrl = `${wsProtocol}//${wsHost}/ws/tool-instances/${instanceId}/terminal`; + console.log(`[Terminal WS] Connecting to ${wsUrl} (attempt ${reconnectAttemptsRef.current + 1}/${RECONNECT_ATTEMPTS + 1})`); const ws = new WebSocket(wsUrl); wsRef.current = ws; ws.onopen = () => { + console.log(`[Terminal WS] Connected successfully`); setStatus("connected"); setError(null); reconnectAttemptsRef.current = 0; @@ -94,9 +97,10 @@ export const TerminalComponent: React.FC = ({ } heartbeatCheckRef.current = window.setInterval(() => { const elapsed = Date.now() - lastPingRef.current; + console.log(`[Terminal WS] Heartbeat check: lastPing=${elapsed}ms ago`); if (elapsed > 60000) { // No ping for 60 seconds, connection may be dead - console.warn("Terminal heartbeat timeout, reconnecting..."); + console.warn("[Terminal WS] Heartbeat timeout (>60s), closing connection"); ws.close(4000, "Heartbeat timeout"); } }, 30000); @@ -137,6 +141,7 @@ export const TerminalComponent: React.FC = ({ } else if (msg.type === "ping") { // Respond with pong and update last ping time lastPingRef.current = Date.now(); + console.log(`[Terminal WS] Received ping, sending pong`); if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: "pong" })); } @@ -148,6 +153,7 @@ export const TerminalComponent: React.FC = ({ }; ws.onclose = (event) => { + console.log(`[Terminal WS] Connection closed: code=${event.code}, reason="${event.reason}", wasClean=${event.wasClean}, attempts=${reconnectAttemptsRef.current}`); setStatus("disconnected"); // Clean up heartbeat check @@ -163,19 +169,30 @@ export const TerminalComponent: React.FC = ({ if (reconnectAttemptsRef.current < RECONNECT_ATTEMPTS) { reconnectAttemptsRef.current++; const delay = RECONNECT_DELAY_BASE * Math.pow(2, reconnectAttemptsRef.current - 1); + console.log(`[Terminal WS] Will retry in ${delay}ms (attempt ${reconnectAttemptsRef.current}/${RECONNECT_ATTEMPTS})`); setTimeout(() => { + if (isUnmountingRef.current) { + console.log(`[Terminal WS] Component unmounting, skipping reconnect`); + return; + } if (document.visibilityState !== "hidden") { connectWebSocket(); + } else { + console.log(`[Terminal WS] Tab hidden, skipping reconnect`); } }, delay); + } else { + console.log(`[Terminal WS] Max reconnection attempts (${RECONNECT_ATTEMPTS}) reached`); } } else if (event.code === 4000) { // Server closed old connection for concurrent connection - don't reconnect // The new connection is already established + console.log(`[Terminal WS] Server closed old connection (concurrent/heartbeat)`); } }; - ws.onerror = () => { + ws.onerror = (error) => { + console.error(`[Terminal WS] Error event fired`, error); setStatus("error"); setError("WebSocket error"); }; @@ -353,7 +370,9 @@ export const TerminalComponent: React.FC = ({ // Visibility API for reconnection const handleVisibilityChange = () => { + console.log(`[Terminal WS] Visibility changed to: ${document.visibilityState}, wsState=${ws?.readyState}`); if (document.visibilityState === "visible" && ws && ws.readyState !== WebSocket.OPEN) { + console.log(`[Terminal WS] Tab visible, resetting reconnect attempts and reconnecting`); reconnectAttemptsRef.current = 0; connectWebSocket(); } @@ -361,6 +380,7 @@ export const TerminalComponent: React.FC = ({ document.addEventListener("visibilitychange", handleVisibilityChange); return () => { + isUnmountingRef.current = true; clearTimeout(resizeTimeout); clearTimeout(windowResizeTimeout); clearTimeout(headerHideTimeout); @@ -368,7 +388,11 @@ export const TerminalComponent: React.FC = ({ window.removeEventListener("resize", handleWindowResize); document.removeEventListener("visibilitychange", handleVisibilityChange); if (ws) { - ws.close(); + ws.close(1000, "Component unmounting"); + } + if (heartbeatCheckRef.current) { + window.clearInterval(heartbeatCheckRef.current); + heartbeatCheckRef.current = null; } term.dispose(); };