mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 14:45:24 +00:00
feat: optional auto reconnect for dropped ssh sessions (#1321)
This commit is contained in:
1 parent
41d0c0ab85
commit
911fc3bc69
6 files changed
+129
-3
No files matched your search
@@ -720,6 +720,16 @@ export function HostTerminalSettings({
|
||||
onChange={(v) => setField("autoMosh", v)}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("hosts.autoReconnectLabel")}
|
||||
defaultKey="autoReconnect"
|
||||
description={t("hosts.autoReconnectDesc")}
|
||||
>
|
||||
<FakeSwitch
|
||||
checked={form.autoReconnect}
|
||||
onChange={(v) => setField("autoReconnect", v)}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("hosts.passwordPromptAutoFillLabel")}
|
||||
defaultKey="passwordPromptAutoFill"
|
||||
|
||||
@@ -282,6 +282,8 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
const terminalInputDisposableRef = useRef<{ dispose(): void } | null>(null);
|
||||
const localEchoRef = useRef<TerminalLocalEcho | null>(null);
|
||||
const sessionOriginRef = useRef<"local" | "remote">("local");
|
||||
// Set while an auto reconnect runs, so the old output stays on screen.
|
||||
const keepScrollbackRef = useRef(false);
|
||||
const customKeybindingsRef = useRef<CustomKeybinding[]>([]);
|
||||
const resizeTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||
const wasDisconnectedBySSH = useRef(false);
|
||||
@@ -1514,6 +1516,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
|
||||
if (reconnectAttempts.current >= maxReconnectAttempts) {
|
||||
setIsConnecting(false);
|
||||
keepScrollbackRef.current = false;
|
||||
shouldNotReconnectRef.current = true;
|
||||
setShowDisconnectedOverlay(true);
|
||||
addLog({
|
||||
@@ -1526,7 +1529,11 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
|
||||
isReconnectingRef.current = true;
|
||||
|
||||
if (terminal && !isAttachingSessionRef.current) {
|
||||
if (
|
||||
terminal &&
|
||||
!isAttachingSessionRef.current &&
|
||||
!keepScrollbackRef.current
|
||||
) {
|
||||
terminal.clear();
|
||||
}
|
||||
|
||||
@@ -1564,7 +1571,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
}
|
||||
|
||||
if (terminal && hostConfig) {
|
||||
if (!isAttachingSessionRef.current) {
|
||||
if (!isAttachingSessionRef.current && !keepScrollbackRef.current) {
|
||||
terminal.clear();
|
||||
}
|
||||
const cols = terminal.cols;
|
||||
@@ -1929,6 +1936,13 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
errorMessage.toLowerCase().includes("timeout") ||
|
||||
errorMessage.toLowerCase().includes("network")
|
||||
) {
|
||||
if (keepScrollbackRef.current && !isUnmountingRef.current) {
|
||||
// The host is still unreachable; try again after the backoff.
|
||||
isConnectingRef.current = false;
|
||||
wasDisconnectedBySSH.current = false;
|
||||
attemptReconnection();
|
||||
return;
|
||||
}
|
||||
updateConnectionError(errorMessage);
|
||||
setIsConnected(false);
|
||||
if (terminal) {
|
||||
@@ -1948,6 +1962,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
errorMessage.toLowerCase().includes("key"))) ||
|
||||
errorMessage.toLowerCase().includes("incorrect password")
|
||||
) {
|
||||
keepScrollbackRef.current = false;
|
||||
updateConnectionError(errorMessage);
|
||||
setIsConnecting(false);
|
||||
shouldNotReconnectRef.current = true;
|
||||
@@ -1960,6 +1975,10 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
updateConnectionError(errorMessage);
|
||||
setIsConnecting(false);
|
||||
} else if (msg.type === "connected") {
|
||||
if (keepScrollbackRef.current) {
|
||||
keepScrollbackRef.current = false;
|
||||
reconnectAttempts.current = 0;
|
||||
}
|
||||
wasConnectedRef.current = true;
|
||||
setIsConnected(true);
|
||||
setIsConnecting(false);
|
||||
@@ -2061,7 +2080,23 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
if (onClose) onClose();
|
||||
} else if (wasConnectedRef.current) {
|
||||
wasConnectedRef.current = false;
|
||||
setShowDisconnectedOverlay(true);
|
||||
if (
|
||||
termSettingsRef.current.autoReconnect &&
|
||||
!isUnmountingRef.current
|
||||
) {
|
||||
wasDisconnectedBySSH.current = false;
|
||||
shouldNotReconnectRef.current = false;
|
||||
reconnectAttempts.current = 0;
|
||||
keepScrollbackRef.current = true;
|
||||
terminal?.write(
|
||||
`
|
||||
[2m${t("terminal.autoReconnecting")}[0m
|
||||
`,
|
||||
);
|
||||
attemptReconnection();
|
||||
} else {
|
||||
setShowDisconnectedOverlay(true);
|
||||
}
|
||||
} else if (!connectionErrorRef.current) {
|
||||
updateConnectionError(
|
||||
msg.message || t("terminal.connectionRejected"),
|
||||
@@ -2386,6 +2421,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
|
||||
if (
|
||||
!wasConnectedRef.current &&
|
||||
!isAttachingSessionRef.current &&
|
||||
!keepScrollbackRef.current &&
|
||||
event.wasClean &&
|
||||
(event.code === 1005 || event.code === 1000)
|
||||
) {
|
||||
|
||||
@@ -81,6 +81,7 @@ export interface TerminalBehavior {
|
||||
localEcho: HostLocalEcho;
|
||||
passwordPromptAutoFill: boolean;
|
||||
sudoPasswordAutoFill: boolean;
|
||||
autoReconnect: boolean;
|
||||
}
|
||||
|
||||
/** Everything the host editor's Terminal tab edits. */
|
||||
@@ -121,6 +122,7 @@ export const BEHAVIOR_KEYS = [
|
||||
"localEcho",
|
||||
"passwordPromptAutoFill",
|
||||
"sudoPasswordAutoFill",
|
||||
"autoReconnect",
|
||||
] as const satisfies readonly (keyof TerminalBehavior)[];
|
||||
|
||||
export const INHERIT_APPEARANCE_KEY = "inheritAppearance";
|
||||
@@ -169,6 +171,7 @@ export const DEFAULT_BEHAVIOR: TerminalBehavior = {
|
||||
localEcho: "auto",
|
||||
passwordPromptAutoFill: true,
|
||||
sudoPasswordAutoFill: false,
|
||||
autoReconnect: false,
|
||||
};
|
||||
|
||||
export const DEFAULT_HOST_TERMINAL_SETTINGS: HostTerminalSettings = {
|
||||
|
||||
Reference in new issue
Block a user