Merge branch 'dev' of ssh://git.commumedia.org:2222/alex/headquarter into dev
This commit is contained in:
@@ -56,6 +56,7 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
});
|
});
|
||||||
const lastPingRef = useRef<number>(0);
|
const lastPingRef = useRef<number>(0);
|
||||||
const heartbeatCheckRef = useRef<number | null>(null);
|
const heartbeatCheckRef = useRef<number | null>(null);
|
||||||
|
const isUnmountingRef = useRef(false);
|
||||||
|
|
||||||
const calculateFontSize = useCallback(() => {
|
const calculateFontSize = useCallback(() => {
|
||||||
if (!isMobile) return fontSize;
|
if (!isMobile) return fontSize;
|
||||||
@@ -70,10 +71,12 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
const wsHost = apiUrl.replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
const wsHost = apiUrl.replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
||||||
const wsUrl = `${wsProtocol}//${wsHost}/ws/tool-instances/${instanceId}/terminal`;
|
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);
|
const ws = new WebSocket(wsUrl);
|
||||||
wsRef.current = ws;
|
wsRef.current = ws;
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
|
console.log(`[Terminal WS] Connected successfully`);
|
||||||
setStatus("connected");
|
setStatus("connected");
|
||||||
setError(null);
|
setError(null);
|
||||||
reconnectAttemptsRef.current = 0;
|
reconnectAttemptsRef.current = 0;
|
||||||
@@ -94,9 +97,10 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
}
|
}
|
||||||
heartbeatCheckRef.current = window.setInterval(() => {
|
heartbeatCheckRef.current = window.setInterval(() => {
|
||||||
const elapsed = Date.now() - lastPingRef.current;
|
const elapsed = Date.now() - lastPingRef.current;
|
||||||
|
console.log(`[Terminal WS] Heartbeat check: lastPing=${elapsed}ms ago`);
|
||||||
if (elapsed > 60000) {
|
if (elapsed > 60000) {
|
||||||
// No ping for 60 seconds, connection may be dead
|
// 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");
|
ws.close(4000, "Heartbeat timeout");
|
||||||
}
|
}
|
||||||
}, 30000);
|
}, 30000);
|
||||||
@@ -137,6 +141,7 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
} else if (msg.type === "ping") {
|
} else if (msg.type === "ping") {
|
||||||
// Respond with pong and update last ping time
|
// Respond with pong and update last ping time
|
||||||
lastPingRef.current = Date.now();
|
lastPingRef.current = Date.now();
|
||||||
|
console.log(`[Terminal WS] Received ping, sending pong`);
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
ws.send(JSON.stringify({ type: "pong" }));
|
ws.send(JSON.stringify({ type: "pong" }));
|
||||||
}
|
}
|
||||||
@@ -148,6 +153,7 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
ws.onclose = (event) => {
|
ws.onclose = (event) => {
|
||||||
|
console.log(`[Terminal WS] Connection closed: code=${event.code}, reason="${event.reason}", wasClean=${event.wasClean}, attempts=${reconnectAttemptsRef.current}`);
|
||||||
setStatus("disconnected");
|
setStatus("disconnected");
|
||||||
|
|
||||||
// Clean up heartbeat check
|
// Clean up heartbeat check
|
||||||
@@ -163,19 +169,30 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
if (reconnectAttemptsRef.current < RECONNECT_ATTEMPTS) {
|
if (reconnectAttemptsRef.current < RECONNECT_ATTEMPTS) {
|
||||||
reconnectAttemptsRef.current++;
|
reconnectAttemptsRef.current++;
|
||||||
const delay = RECONNECT_DELAY_BASE * Math.pow(2, reconnectAttemptsRef.current - 1);
|
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(() => {
|
setTimeout(() => {
|
||||||
|
if (isUnmountingRef.current) {
|
||||||
|
console.log(`[Terminal WS] Component unmounting, skipping reconnect`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (document.visibilityState !== "hidden") {
|
if (document.visibilityState !== "hidden") {
|
||||||
connectWebSocket();
|
connectWebSocket();
|
||||||
|
} else {
|
||||||
|
console.log(`[Terminal WS] Tab hidden, skipping reconnect`);
|
||||||
}
|
}
|
||||||
}, delay);
|
}, delay);
|
||||||
|
} else {
|
||||||
|
console.log(`[Terminal WS] Max reconnection attempts (${RECONNECT_ATTEMPTS}) reached`);
|
||||||
}
|
}
|
||||||
} else if (event.code === 4000) {
|
} else if (event.code === 4000) {
|
||||||
// Server closed old connection for concurrent connection - don't reconnect
|
// Server closed old connection for concurrent connection - don't reconnect
|
||||||
// The new connection is already established
|
// 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");
|
setStatus("error");
|
||||||
setError("WebSocket error");
|
setError("WebSocket error");
|
||||||
};
|
};
|
||||||
@@ -358,7 +375,9 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
|
|
||||||
// Visibility API for reconnection
|
// Visibility API for reconnection
|
||||||
const handleVisibilityChange = () => {
|
const handleVisibilityChange = () => {
|
||||||
|
console.log(`[Terminal WS] Visibility changed to: ${document.visibilityState}, wsState=${ws?.readyState}`);
|
||||||
if (document.visibilityState === "visible" && ws && ws.readyState !== WebSocket.OPEN) {
|
if (document.visibilityState === "visible" && ws && ws.readyState !== WebSocket.OPEN) {
|
||||||
|
console.log(`[Terminal WS] Tab visible, resetting reconnect attempts and reconnecting`);
|
||||||
reconnectAttemptsRef.current = 0;
|
reconnectAttemptsRef.current = 0;
|
||||||
connectWebSocket();
|
connectWebSocket();
|
||||||
}
|
}
|
||||||
@@ -366,6 +385,7 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
isUnmountingRef.current = true;
|
||||||
clearTimeout(resizeTimeout);
|
clearTimeout(resizeTimeout);
|
||||||
clearTimeout(windowResizeTimeout);
|
clearTimeout(windowResizeTimeout);
|
||||||
clearTimeout(headerHideTimeout);
|
clearTimeout(headerHideTimeout);
|
||||||
@@ -373,7 +393,11 @@ export const TerminalComponent: React.FC<TerminalProps> = ({
|
|||||||
window.removeEventListener("resize", handleWindowResize);
|
window.removeEventListener("resize", handleWindowResize);
|
||||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||||
if (ws) {
|
if (ws) {
|
||||||
ws.close();
|
ws.close(1000, "Component unmounting");
|
||||||
|
}
|
||||||
|
if (heartbeatCheckRef.current) {
|
||||||
|
window.clearInterval(heartbeatCheckRef.current);
|
||||||
|
heartbeatCheckRef.current = null;
|
||||||
}
|
}
|
||||||
term.dispose();
|
term.dispose();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user