/** * SSH IPC Handlers * 处理 SSH 连接、Shell、会话、端口转发相关 IPC */ import { ipcMain, IpcMainInvokeEvent } from 'electron' import type { SSHService } from '../services/ssh-service' import type { ZmodemService } from '../services/zmodem-service' import { createLogger } from '../utils/logger' const logger = createLogger('SSH_IPC') // ===== 彩色调试日志 ===== const DEBUG = true function debug(...args: any[]) { if (DEBUG) console.log('[SSH_IPC]', ...args) } function logHeader(msg: string) { console.log(`%c${msg}`, 'background: #9b59b6; color: #fff; padding: 4px 8px; border-radius: 4px; font-weight: bold;') } function logInfo(label: string, value: any) { console.log(`%c${label}:%c ${value}`, 'background: #2c3e50; color: #fff; padding: 2px 6px; border-radius: 3px;', 'color: #3498db; font-weight: bold;') } function logSuccess(msg: string) { console.log(`%c✓ ${msg}`, 'color: #27ae60; font-weight: bold;') } function logError(msg: string) { console.log(`%c✗ ${msg}`, 'color: #e74c3c; font-weight: bold;') } // IPC channel names export const SSH_CHANNELS = { CONNECT: 'ssh:connect', DISCONNECT: 'ssh:disconnect', EXEC: 'ssh:exec', CREATE_SHELL: 'ssh:createShell', SHELL_WRITE: 'ssh:shellWrite', SHELL_RESIZE: 'ssh:shellResize', FORWARD_LOCAL: 'ssh:forwardLocal', FORWARD_REMOTE: 'ssh:forwardRemote', FORWARD_DYNAMIC: 'ssh:forwardDynamic', SHELL_DATA: 'ssh:shellData', } as const export interface SSHIPCOptions { sshService: SSHService zmodemService: ZmodemService mainWindowGetter: () => Electron.BrowserWindow | null } export function registerSSHIPCHandlers(options: SSHIPCOptions): void { const { sshService, zmodemService, mainWindowGetter } = options // SSH connect ipcMain.handle(SSH_CHANNELS.CONNECT, async (_event: IpcMainInvokeEvent, config) => { try { const connection = await sshService.connect(config) return { success: true, connectionId: connection.id } } catch (error: any) { return { success: false, error: error.message } } }) // SSH disconnect ipcMain.handle(SSH_CHANNELS.DISCONNECT, async (_event: IpcMainInvokeEvent, connectionId: string) => { try { await sshService.disconnect(connectionId) return { success: true } } catch (error: any) { return { success: false, error: error.message } } }) // SSH exec command ipcMain.handle(SSH_CHANNELS.EXEC, async (_event: IpcMainInvokeEvent, connectionId: string, command: string) => { try { const result = await sshService.exec(connectionId, command) return { success: true, result } } catch (error: any) { return { success: false, error: error.message } } }) // SSH create shell ipcMain.handle(SSH_CHANNELS.CREATE_SHELL, async (event: IpcMainInvokeEvent, connectionId: string, options?: { cols?: number; rows?: number }, shellId?: string) => { console.log('=== SSH_IPC CREATE_SHELL ===') console.log(' connectionId:', connectionId) console.log(' options:', options) console.log(' preferredShellId:', shellId) try { const preferredShellId = shellId logger.info('[SSH_IPC] Creating shell for connection:', connectionId, 'preferredId:', preferredShellId) debug('>>> Calling sshService.createShell...') const shell = await sshService.createShell(connectionId, options || {}, (shellId, data) => { // 检查 sender 是否已被销毁 if (event.sender.isDestroyed()) { console.log('=== SHELL DATA CALLBACK - SKIPPED (sender destroyed) ===') return } const text = data.toString('utf-8') console.log('=== SHELL DATA CALLBACK ===') console.log(' shellId:', shellId) console.log(' data length:', data.length) console.log(' data preview:', text.substring(0, 100).replace(/\n/g, '\\n')) // 打印所有收到的数据前100字符用于调试 if (text.length > 0) { logger.info(`[SSH_IPC] Shell data from ${shellId}:`, text.substring(0, 100).replace(/\n/g, '\\n')) } try { console.log('>>> Sending SHELL_DATA to renderer...') event.sender.send(SSH_CHANNELS.SHELL_DATA, connectionId, shellId, data) console.log('>>> SHELL_DATA sent successfully') } catch (err) { console.error('>>> FAILED to send SHELL_DATA:', err) } zmodemService.feedShellData(shellId, data) }, preferredShellId) logger.info('[SSH_IPC] Shell created:', shell.id) debug('>>> Shell created successfully:', shell.id) return { success: true, shellId: shell.id } } catch (error: any) { logger.error('[SSH_IPC] Shell creation failed:', error) debug('>>> Shell creation FAILED:', error.message) return { success: false, error: error.message } } }) // SSH shell write ipcMain.handle(SSH_CHANNELS.SHELL_WRITE, async (_event: IpcMainInvokeEvent, shellId: string, data: string) => { try { sshService.shellWrite(shellId, data) return { success: true } } catch (error: any) { return { success: false, error: error.message } } }) // SSH shell resize ipcMain.handle(SSH_CHANNELS.SHELL_RESIZE, async (_event: IpcMainInvokeEvent, shellId: string, cols: number, rows: number) => { try { sshService.shellResize(shellId, cols, rows) return { success: true } } catch (error: any) { return { success: false, error: error.message } } }) // SSH local port forward ipcMain.handle(SSH_CHANNELS.FORWARD_LOCAL, async ( _event: IpcMainInvokeEvent, connectionId: string, localPort: number, remoteHost: string, remotePort: number ) => { logHeader('🔀 IPC FORWARD_LOCAL') logInfo('connectionId', connectionId) logInfo('localPort', localPort) logInfo('remoteHost', remoteHost) logInfo('remotePort', remotePort) try { const result = await sshService.forwardLocal(connectionId, localPort, remoteHost, remotePort) logSuccess(`Local forward bound to port ${result.boundPort}`) return { success: true, boundPort: result.boundPort } } catch (error: any) { logError(`Local forward failed: ${error.message}`) return { success: false, error: error.message } } }) // SSH remote port forward ipcMain.handle(SSH_CHANNELS.FORWARD_REMOTE, async ( _event: IpcMainInvokeEvent, connectionId: string, remotePort: number, localHost: string, localPort: number ) => { logHeader('🔀 IPC FORWARD_REMOTE') logInfo('connectionId', connectionId) logInfo('remotePort', remotePort) logInfo('localHost', localHost) logInfo('localPort', localPort) try { const result = await sshService.forwardRemote(connectionId, remotePort, localHost, localPort) logSuccess('Remote forward established') return { success: true } } catch (error: any) { logError(`Remote forward failed: ${error.message}`) return { success: false, error: error.message } } }) // SSH dynamic port forward (SOCKS5) ipcMain.handle(SSH_CHANNELS.FORWARD_DYNAMIC, async ( _event: IpcMainInvokeEvent, connectionId: string, localPort: number ) => { logHeader('🧦 IPC FORWARD_DYNAMIC (SOCKS5)') logInfo('connectionId', connectionId) logInfo('localPort', localPort) try { const result = await sshService.forwardDynamic(connectionId, localPort) logSuccess(`SOCKS5 proxy started on port ${result.boundPort}`) return { success: true, boundPort: result.boundPort } } catch (error: any) { logError(`Dynamic forward failed: ${error.message}`) return { success: false, error: error.message } } }) }