diff --git a/src/main/index.ts b/src/main/index.ts index 857aec7..feaf1e1 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -41,6 +41,13 @@ function createWindow(): void { } } +process.on('unhandledRejection', (reason) => { + console.error('[main] unhandledRejection:', reason) +}) +process.on('uncaughtException', (err) => { + console.error('[main] uncaughtException:', err) +}) + app.whenReady().then(() => { registerIpc() // OS-level effects (login item, sleep blocker) must apply even if the diff --git a/src/main/ssh.ts b/src/main/ssh.ts index b5bc92b..41983d1 100644 --- a/src/main/ssh.ts +++ b/src/main/ssh.ts @@ -53,7 +53,9 @@ export interface SshServiceDeps { timeoutMs?: { prompt: number; connect: number } } -const DEFAULT_TIMEOUTS = { prompt: 30_000, connect: 15_000 } +// Host-key confirmation is a security decision — users may take time to +// compare fingerprints. 30s timed out on people who merely screenshot it. +const DEFAULT_TIMEOUTS = { prompt: 120_000, connect: 15_000 } /** * Open an ssh session (connect + auth + open shell) for `conn`. @@ -107,7 +109,8 @@ export async function connectSsh( try { fingerprint = fingerprintOf(hostKey) status = deps.knownHosts.check(conn.host, conn.port, hostKey).status - } catch { + } catch (err) { + console.error(`[ssh] knownHosts.check threw: ${(err as Error).message}`) fingerprint = fingerprintOf(hostKey) status = 'new' } @@ -287,7 +290,10 @@ function promptUser( */ export function resolveHostKey(promptId: string, action: 'accept' | 'reject'): void { const pending = pendingPrompts.get(promptId) - if (!pending) return + if (!pending) { + console.error(`[ssh] resolveHostKey: no pending prompt ${promptId}`) + return + } clearTimeout(pending.timer) pendingPrompts.delete(promptId) pending.resolve(action === 'accept') diff --git a/src/renderer/src/workspace/Workspace.tsx b/src/renderer/src/workspace/Workspace.tsx index 43ea67e..3ee836d 100644 --- a/src/renderer/src/workspace/Workspace.tsx +++ b/src/renderer/src/workspace/Workspace.tsx @@ -248,9 +248,21 @@ export default function Workspace({ onOpenSettings }: WorkspaceProps): React.JSX [message] ) - const handleConnectRequest = useCallback((conn: SshConnection): void => { - setRequestedConn(conn) - }, []) + const handleConnectRequest = useCallback( + (conn: SshConnection): void => { + // Ask-at-connect connections go through the secret prompt first; saved + // credentials must connect IMMEDIATELY — routing them through ConnectFlow + // would only ever *render* a "connecting" spinner without firing openSession. + const needsPassword = conn.auth === 'password' && conn.askPasswordAtConnect + const needsPassphrase = conn.auth === 'privateKey' && conn.askPassphraseAtConnect + if (needsPassword || needsPassphrase) { + setRequestedConn(conn) + return + } + void connect(conn, {}) + }, + [connect] + ) const handleConnectCancel = useCallback((): void => { setRequestedConn(null)