Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
100 lines
3.1 KiB
TypeScript
100 lines
3.1 KiB
TypeScript
import { useCallback } from "react";
|
|
import type { MutableRefObject } from "react";
|
|
import type { Host } from "../../../domain/models";
|
|
import type { SftpPane } from "./types";
|
|
|
|
interface UseSftpSessionErrorsParams {
|
|
getActivePane: (side: "left" | "right") => SftpPane | null;
|
|
leftTabsRef: MutableRefObject<{ tabs: SftpPane[]; activeTabId: string | null }>;
|
|
rightTabsRef: MutableRefObject<{ tabs: SftpPane[]; activeTabId: string | null }>;
|
|
updateActiveTab: (
|
|
side: "left" | "right",
|
|
updater: (prev: SftpPane) => SftpPane,
|
|
) => void;
|
|
navSeqRef: MutableRefObject<{ left: number; right: number }>;
|
|
lastConnectedHostRef: MutableRefObject<{ left: Host | "local" | null; right: Host | "local" | null }>;
|
|
releaseConnection: (connectionId: string) => Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* Whether we still know enough to reconnect after a session drop.
|
|
* Prefer reconnect over wiping the pane to the empty "select host" screen —
|
|
* that wipe was especially easy to hit when listing "/" failed/timed out on
|
|
* some hosts while the file list was empty or lastHost had raced away.
|
|
*/
|
|
export function canReconnectSftpPane(params: {
|
|
lastHost: Host | "local" | null;
|
|
connection: SftpPane["connection"];
|
|
}): boolean {
|
|
const { lastHost, connection } = params;
|
|
if (lastHost && lastHost !== "local") return true;
|
|
if (lastHost === "local") return true;
|
|
if (connection && !connection.isLocal && !!connection.hostId) return true;
|
|
if (connection?.isLocal) return true;
|
|
return false;
|
|
}
|
|
|
|
export const useSftpSessionErrors = ({
|
|
getActivePane,
|
|
leftTabsRef,
|
|
rightTabsRef,
|
|
updateActiveTab,
|
|
navSeqRef,
|
|
lastConnectedHostRef,
|
|
releaseConnection,
|
|
}: UseSftpSessionErrorsParams) =>
|
|
useCallback(
|
|
(side: "left" | "right", _error: Error) => {
|
|
const pane = getActivePane(side);
|
|
const sideTabs = side === "left" ? leftTabsRef.current : rightTabsRef.current;
|
|
|
|
if (!pane || !sideTabs.activeTabId) return;
|
|
|
|
if (pane.connection) {
|
|
void releaseConnection(pane.connection.id);
|
|
}
|
|
|
|
navSeqRef.current[side] += 1;
|
|
|
|
const lastHost = lastConnectedHostRef.current[side];
|
|
const canReconnect = canReconnectSftpPane({
|
|
lastHost,
|
|
connection: pane.connection,
|
|
});
|
|
|
|
if (canReconnect) {
|
|
// Keep the connection object (host identity + path) so the UI does not
|
|
// collapse into the empty host picker when listing root (or any path)
|
|
// fails with a transient session error on some servers.
|
|
updateActiveTab(side, (prev) => ({
|
|
...prev,
|
|
reconnecting: true,
|
|
loading: false,
|
|
error: "sftp.error.connectionLostReconnecting",
|
|
}));
|
|
return;
|
|
}
|
|
|
|
// No host identity left — fall back to empty picker.
|
|
updateActiveTab(side, (prev) => ({
|
|
...prev,
|
|
connection: null,
|
|
files: [],
|
|
loading: false,
|
|
reconnecting: false,
|
|
error: "sftp.error.sessionLost",
|
|
selectedFiles: new Set(),
|
|
filter: "",
|
|
}));
|
|
},
|
|
[
|
|
getActivePane,
|
|
leftTabsRef,
|
|
rightTabsRef,
|
|
updateActiveTab,
|
|
navSeqRef,
|
|
lastConnectedHostRef,
|
|
releaseConnection,
|
|
],
|
|
);
|