mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
fix(ssh-terminal): close owner sockets when presentations unmount (#1501)
This commit is contained in:
1 parent
24def2b613
commit
c265758cdd
2 files changed
+98
-29
No files matched your search
@@ -412,7 +412,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
isAttachingSessionRef.current = false;
|
||||
|
||||
return () => {};
|
||||
}, [hostConfig.id]);
|
||||
}, [hostConfig.id, hostConfig.instanceId]);
|
||||
const connectionAttemptIdRef = useRef(0);
|
||||
const totpTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const connectionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
@@ -2886,40 +2886,34 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
useEffect(() => {
|
||||
isMountedRef.current = true;
|
||||
|
||||
const currentHostId = hostConfig.id;
|
||||
return () => {
|
||||
if (!isMountedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
currentHostIdRef.current !== currentHostId &&
|
||||
currentHostIdRef.current !== null
|
||||
) {
|
||||
isUnmountingRef.current = true;
|
||||
shouldNotReconnectRef.current = true;
|
||||
isReconnectingRef.current = false;
|
||||
setIsConnecting(false);
|
||||
if (reconnectTimeoutRef.current)
|
||||
clearTimeout(reconnectTimeoutRef.current);
|
||||
if (connectionTimeoutRef.current)
|
||||
clearTimeout(connectionTimeoutRef.current);
|
||||
if (totpTimeoutRef.current) clearTimeout(totpTimeoutRef.current);
|
||||
if (pingIntervalRef.current) {
|
||||
clearInterval(pingIntervalRef.current);
|
||||
pingIntervalRef.current = null;
|
||||
}
|
||||
if (pongTimeoutRef.current) {
|
||||
clearTimeout(pongTimeoutRef.current);
|
||||
pongTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
if (webSocketRef.current) {
|
||||
webSocketRef.current.close();
|
||||
}
|
||||
|
||||
isMountedRef.current = false;
|
||||
isUnmountingRef.current = true;
|
||||
shouldNotReconnectRef.current = true;
|
||||
isReconnectingRef.current = false;
|
||||
setIsConnecting(false);
|
||||
if (reconnectTimeoutRef.current)
|
||||
clearTimeout(reconnectTimeoutRef.current);
|
||||
if (connectionTimeoutRef.current)
|
||||
clearTimeout(connectionTimeoutRef.current);
|
||||
if (totpTimeoutRef.current) clearTimeout(totpTimeoutRef.current);
|
||||
if (pingIntervalRef.current) {
|
||||
clearInterval(pingIntervalRef.current);
|
||||
pingIntervalRef.current = null;
|
||||
}
|
||||
if (pongTimeoutRef.current) {
|
||||
clearTimeout(pongTimeoutRef.current);
|
||||
pongTimeoutRef.current = null;
|
||||
}
|
||||
|
||||
if (webSocketRef.current) {
|
||||
webSocketRef.current.close();
|
||||
}
|
||||
|
||||
isMountedRef.current = false;
|
||||
};
|
||||
}, [hostConfig.id, hostConfig.instanceId]);
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import ts from "typescript";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import sourceText from "../../../src/frontend/terminal/Terminal.tsx?raw";
|
||||
|
||||
const source = ts.createSourceFile(
|
||||
"Terminal.tsx",
|
||||
sourceText,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
ts.ScriptKind.TSX,
|
||||
);
|
||||
let effect: string | undefined;
|
||||
function visit(node: ts.Node) {
|
||||
if (
|
||||
ts.isCallExpression(node) &&
|
||||
node.expression.getText(source) === "useEffect" &&
|
||||
node.arguments[0]?.getText(source).includes("isMountedRef.current = true")
|
||||
) {
|
||||
effect = node.arguments[0].getText(source);
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
visit(source);
|
||||
if (!effect) throw new Error("Missing terminal lifecycle effect");
|
||||
const mount = new Function(
|
||||
"state",
|
||||
ts.transpileModule(
|
||||
`
|
||||
const { isMountedRef, hostConfig, currentHostIdRef, isUnmountingRef,
|
||||
shouldNotReconnectRef, isReconnectingRef, setIsConnecting,
|
||||
reconnectTimeoutRef, connectionTimeoutRef, totpTimeoutRef,
|
||||
pingIntervalRef, pongTimeoutRef, webSocketRef } = state;
|
||||
return (${effect})();
|
||||
`,
|
||||
{ compilerOptions: { target: ts.ScriptTarget.ES2022 } },
|
||||
).outputText,
|
||||
) as (state: Record<string, unknown>) => () => void;
|
||||
|
||||
afterEach(() => vi.useRealTimers());
|
||||
|
||||
describe("terminal unmount", () => {
|
||||
it.each([1, 2, null])(
|
||||
"closes the socket when current host is %s",
|
||||
(currentHost) => {
|
||||
vi.useFakeTimers();
|
||||
const timeout = () => ({ current: setTimeout(() => {}, 10000) });
|
||||
const state = {
|
||||
isMountedRef: { current: false },
|
||||
hostConfig: { id: 1, instanceId: "collab-present-room" },
|
||||
currentHostIdRef: { current: currentHost },
|
||||
isUnmountingRef: { current: false },
|
||||
shouldNotReconnectRef: { current: false },
|
||||
isReconnectingRef: { current: true },
|
||||
setIsConnecting: vi.fn(),
|
||||
reconnectTimeoutRef: timeout(),
|
||||
connectionTimeoutRef: timeout(),
|
||||
totpTimeoutRef: timeout(),
|
||||
pongTimeoutRef: timeout(),
|
||||
pingIntervalRef: { current: setInterval(() => {}, 1000) },
|
||||
webSocketRef: { current: { close: vi.fn() } },
|
||||
};
|
||||
const unmount = mount(state);
|
||||
expect(state.isMountedRef.current).toBe(true);
|
||||
unmount();
|
||||
expect(state.webSocketRef.current.close).toHaveBeenCalledOnce();
|
||||
expect(state.shouldNotReconnectRef.current).toBe(true);
|
||||
expect(state.isUnmountingRef.current).toBe(true);
|
||||
expect(state.isReconnectingRef.current).toBe(false);
|
||||
expect(state.isMountedRef.current).toBe(false);
|
||||
expect(vi.getTimerCount()).toBe(0);
|
||||
unmount();
|
||||
expect(state.webSocketRef.current.close).toHaveBeenCalledOnce();
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user