const WebSocket = require('ws'); const express = require('express'); const path = require('path'); const pty = require('node-pty'); const http = require('http'); const { app } = require('electron'); const args = process.argv.join(' '); const portMatch = args.match(/--port[=\s]+(\d+)/); const PORT = portMatch ? parseInt(portMatch[1], 10) : 9997; const SHELL = process.platform === 'win32' ? 'powershell.exe' : 'bash'; const expressApp = express(); const server = http.createServer(expressApp); const wss = new WebSocket.Server({ server }); expressApp.use(express.static(path.join(__dirname, '../src/frontend'))); expressApp.get('/', (req, res) => { res.sendFile(path.join(__dirname, '../src/frontend/index.html')); }); const terminals = new Map(); wss.on('connection', (ws, req) => { const urlParams = new URLSearchParams(req.url.split('?')[1]); const shell = urlParams.get('shell') || SHELL; console.log(`[${new Date().toISOString()}] New terminal connection: shell=${shell}`); let ptyProcess; try { ptyProcess = pty.spawn(shell, [], { name: 'xterm-256color', cols: 80, rows: 24, cwd: process.env.HOME || process.env.USERPROFILE || '/', env: process.env }); } catch (err) { console.error('Failed to spawn PTY:', err); ws.close(1000, 'Failed to start shell'); return; } terminals.set(ws, ptyProcess); ptyProcess.onData((data) => { if (ws.readyState === WebSocket.OPEN) { ws.send(data); } }); ptyProcess.onExit(({ exitCode, signal }) => { console.log(`[${new Date().toISOString()}] PTY exited: code=${exitCode}, signal=${signal}`); if (ws.readyState === WebSocket.OPEN) { ws.send('\r\n[Process exited]\r\n'); ws.close(); } terminals.delete(ws); }); ws.on('message', (message) => { ptyProcess.write(message); }); ws.on('close', () => { console.log(`[${new Date().toISOString()}] Terminal connection closed`); ptyProcess.kill(); terminals.delete(ws); }); ws.on('error', (err) => { console.error('WebSocket error:', err); ptyProcess.kill(); terminals.delete(ws); }); }); process.on('SIGINT', () => { console.log('\nShutting down...'); terminals.forEach((proc) => proc.kill()); process.exit(0); }); app.commandLine.appendSwitch('disable-gpu'); app.commandLine.appendSwitch('no-sandbox'); app.commandLine.appendSwitch('headless'); app.disableHardwareAcceleration(); app.on('window-all-closed', () => { }); app.whenReady().then(() => { server.listen(PORT, () => { console.log(` ╔═══════════════════════════════════════════════════════════╗ ║ XCTerminal (Electron Headless) ║ ╠═══════════════════════════════════════════════════════════╣ ║ Server running at: http://localhost:${PORT} ║ ║ WebSocket endpoint: ws://localhost:${PORT} ║ ║ ║ ║ Usage: XCTerminal.exe --port 9997 ║ ╚═══════════════════════════════════════════════════════════╝ `); }); }); process.on('exit', () => { terminals.forEach((proc) => proc.kill()); });