import fs from 'node:fs'; import net from 'node:net'; import path from 'node:path'; import { encodeAppControlResponse, parseAppControlRequestLine, type AppControlResponse, } from '../../shared/app-control'; export interface AppControlServerOptions { socketPath: string; platform?: NodeJS.Platform; handleArgv: (argv: string[]) => void; logDebug?: (message: string) => void; logWarn?: (message: string, error?: unknown) => void; } export interface AppControlServerHandle { close: () => void; } function prepareSocketPath(socketPath: string, platform: NodeJS.Platform): void { if (platform === 'win32') return; fs.mkdirSync(path.dirname(socketPath), { recursive: true }); fs.rmSync(socketPath, { force: true }); } function cleanupSocketPath(socketPath: string, platform: NodeJS.Platform): void { if (platform === 'win32') return; try { fs.rmSync(socketPath, { force: true }); } catch { // ignore } } function writeResponse(socket: net.Socket, response: AppControlResponse): void { socket.end(encodeAppControlResponse(response)); } export function startAppControlServer(options: AppControlServerOptions): AppControlServerHandle { const platform = options.platform ?? process.platform; prepareSocketPath(options.socketPath, platform); const server = net.createServer((socket) => { let buffer = ''; let byteCount = 0; let handled = false; socket.on('error', (error) => { if (handled) return; handled = true; options.logWarn?.('App control client socket error.', error); socket.destroy(); }); socket.on('data', (chunk) => { if (handled) return; byteCount += chunk.length; buffer += chunk.toString('utf8'); if (byteCount > 65536) { handled = true; writeResponse(socket, { ok: false, error: 'App control request too large' }); return; } const newlineIndex = buffer.indexOf('\n'); if (newlineIndex < 0) return; handled = true; try { const request = parseAppControlRequestLine(buffer.slice(0, newlineIndex)); options.handleArgv(request.argv); writeResponse(socket, { ok: true }); } catch (error) { options.logWarn?.('Failed to handle app control command.', error); writeResponse(socket, { ok: false, error: error instanceof Error ? error.message : String(error), }); } }); }); server.on('error', (error) => { options.logWarn?.(`App control socket failed: ${options.socketPath}`, error); }); server.listen(options.socketPath, () => { options.logDebug?.(`App control socket listening: ${options.socketPath}`); }); let closed = false; return { close: () => { if (closed) return; closed = true; try { server.close(); } catch { // ignore } cleanupSocketPath(options.socketPath, platform); }, }; }