diff --git a/src/backend/ssh/terminal.ts b/src/backend/ssh/terminal.ts index b41ebab3..a908fcda 100644 --- a/src/backend/ssh/terminal.ts +++ b/src/backend/ssh/terminal.ts @@ -137,10 +137,14 @@ async function createJumpHostChain( const clients: Client[] = []; try { - for (let i = 0; i < jumpHosts.length; i++) { - const jumpHostConfig = await resolveJumpHost(jumpHosts[i].hostId, userId); + // Fetch all jump host configurations in parallel + const jumpHostConfigs = await Promise.all( + jumpHosts.map((jh) => resolveJumpHost(jh.hostId, userId)), + ); - if (!jumpHostConfig) { + // Validate all configs resolved + for (let i = 0; i < jumpHostConfigs.length; i++) { + if (!jumpHostConfigs[i]) { sshLogger.error(`Jump host ${i + 1} not found`, undefined, { operation: "jump_host_chain", hostId: jumpHosts[i].hostId, @@ -148,6 +152,11 @@ async function createJumpHostChain( clients.forEach((c) => c.end()); return null; } + } + + // Connect through jump hosts sequentially + for (let i = 0; i < jumpHostConfigs.length; i++) { + const jumpHostConfig = jumpHostConfigs[i]; const jumpClient = new Client(); clients.push(jumpClient); @@ -623,7 +632,7 @@ wss.on("connection", async (ws: WebSocket, req) => { ); cleanupSSH(connectionTimeout); } - }, 120000); + }, 30000); let resolvedCredentials = { password, key, keyPassword, keyType, authType }; let authMethodNotAvailable = false; @@ -1053,10 +1062,10 @@ wss.on("connection", async (ws: WebSocket, req) => { tryKeyboard: true, keepaliveInterval: 30000, keepaliveCountMax: 3, - readyTimeout: 120000, + readyTimeout: 30000, tcpKeepAlive: true, tcpKeepAliveInitialDelay: 30000, - timeout: 120000, + timeout: 30000, env: { TERM: "xterm-256color", LANG: "en_US.UTF-8", @@ -1191,21 +1200,18 @@ wss.on("connection", async (ws: WebSocket, req) => { if ( hostConfig.useSocks5 && (hostConfig.socks5Host || - (hostConfig.socks5ProxyChain && (hostConfig.socks5ProxyChain as any).length > 0)) + (hostConfig.socks5ProxyChain && + (hostConfig.socks5ProxyChain as any).length > 0)) ) { try { - const socks5Socket = await createSocks5Connection( - ip, - port, - { - useSocks5: hostConfig.useSocks5, - socks5Host: hostConfig.socks5Host, - socks5Port: hostConfig.socks5Port, - socks5Username: hostConfig.socks5Username, - socks5Password: hostConfig.socks5Password, - socks5ProxyChain: hostConfig.socks5ProxyChain as any, - }, - ); + const socks5Socket = await createSocks5Connection(ip, port, { + useSocks5: hostConfig.useSocks5, + socks5Host: hostConfig.socks5Host, + socks5Port: hostConfig.socks5Port, + socks5Username: hostConfig.socks5Username, + socks5Password: hostConfig.socks5Password, + socks5ProxyChain: hostConfig.socks5ProxyChain as any, + }); if (socks5Socket) { connectConfig.sock = socks5Socket; diff --git a/src/ui/desktop/apps/terminal/Terminal.tsx b/src/ui/desktop/apps/terminal/Terminal.tsx index d4e6effc..fb56c95a 100644 --- a/src/ui/desktop/apps/terminal/Terminal.tsx +++ b/src/ui/desktop/apps/terminal/Terminal.tsx @@ -637,7 +637,7 @@ export const Terminal = forwardRef( attemptReconnection(); } } - }, 10000); + }, 15000); ws.send( JSON.stringify({ @@ -765,6 +765,7 @@ export const Terminal = forwardRef( ...hostConfig.terminalConfig, }; + // Send all environment variables immediately without delays if ( terminalConfig.environmentVariables && terminalConfig.environmentVariables.length > 0 @@ -777,11 +778,11 @@ export const Terminal = forwardRef( data: `export ${envVar.key}="${envVar.value}"\n`, }), ); - await new Promise((resolve) => setTimeout(resolve, 100)); } } } + // Send startup snippet immediately after env vars if (terminalConfig.startupSnippetId) { try { const snippets = await getSnippets(); @@ -796,13 +797,13 @@ export const Terminal = forwardRef( data: snippet.content + "\n", }), ); - await new Promise((resolve) => setTimeout(resolve, 200)); } } catch (err) { console.warn("Failed to execute startup snippet:", err); } } + // Execute mosh command immediately if enabled if (terminalConfig.autoMosh && ws.readyState === 1) { ws.send( JSON.stringify({ @@ -811,7 +812,7 @@ export const Terminal = forwardRef( }), ); } - }, 500); + }, 100); } else if (msg.type === "disconnected") { wasDisconnectedBySSH.current = true; setIsConnected(false); @@ -1405,41 +1406,34 @@ export const Terminal = forwardRef( setIsConnecting(true); - const readyFonts = - (document as { fonts?: { ready?: Promise } }).fonts - ?.ready instanceof Promise - ? (document as { fonts?: { ready?: Promise } }).fonts.ready - : Promise.resolve(); + // Start connection immediately without waiting for fonts + requestAnimationFrame(() => { + fitAddonRef.current?.fit(); + if (terminal && terminal.cols > 0 && terminal.rows > 0) { + scheduleNotify(terminal.cols, terminal.rows); + } + hardRefresh(); - readyFonts.then(() => { - requestAnimationFrame(() => { - fitAddonRef.current?.fit(); - if (terminal && terminal.cols > 0 && terminal.rows > 0) { - scheduleNotify(terminal.cols, terminal.rows); - } - hardRefresh(); + setVisible(true); + setIsReady(true); - setVisible(true); - setIsReady(true); + if (terminal && !splitScreen) { + terminal.focus(); + } - if (terminal && !splitScreen) { - terminal.focus(); - } + const jwtToken = getCookie("jwt"); - const jwtToken = getCookie("jwt"); + if (!jwtToken || jwtToken.trim() === "") { + setIsConnected(false); + setIsConnecting(false); + setConnectionError("Authentication required"); + return; + } - if (!jwtToken || jwtToken.trim() === "") { - setIsConnected(false); - setIsConnecting(false); - setConnectionError("Authentication required"); - return; - } + const cols = terminal.cols; + const rows = terminal.rows; - const cols = terminal.cols; - const rows = terminal.rows; - - connectToHost(cols, rows); - }); + connectToHost(cols, rows); }); }, [terminal, hostConfig, visible, isConnected, isConnecting, splitScreen]);