import { spawn } from 'child_process'; import { createServer } from 'http'; import WebSocket, { WebSocketServer } from 'ws'; const CHROME_PORT = 9222; const PORT = 7860; let chrome; function startChrome() { if (chrome) chrome.kill(); console.log('Starting Chrome...'); chrome = spawn('chromium', [ '--headless=new', '--no-sandbox', '--disable-gpu', `--remote-debugging-port=${CHROME_PORT}`, '--remote-debugging-address=127.0.0.1', '--disable-dev-shm-usage', '--no-zygote', '--window-size=1280,800', '--disable-breakpad', '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--disable-component-extensions-with-background-pages', '--no-first-run', '--no-default-browser-check', '--disable-sync', '--disable-component-update', '--disable-default-apps', '--disable-hang-monitor', ], { stdio: ['ignore', 'pipe', 'pipe'] }); chrome.stderr.on('data', d => console.error('CHROME:', d.toString().trimEnd())); chrome.on('exit', (code, sig) => { console.error('Chrome died exit=' + code + ' sig=' + sig + ', restarting in 3s...'); setTimeout(startChrome, 3000); }); } async function waitForChrome(retries = 30) { for (let i = 0; i < retries; i++) { try { const r = await fetch(`http://127.0.0.1:${CHROME_PORT}/json/version`); if (r.ok) { const j = await r.json(); console.log('Chrome ready, browser:', j.webSocketDebuggerUrl); return; } } catch {} await new Promise(r => setTimeout(r, 1000)); } console.error('Chrome not ready after', retries, 'retries'); } startChrome(); function wsProto(req) { return req.socket.encrypted || req.headers['x-forwarded-proto'] === 'https' ? 'wss' : 'ws'; } const server = createServer(async (req, res) => { if (req.url === '/' || req.url === '/health') { res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ status: 'ok', chrome: 'running' })); return; } try { const r = await fetch(`http://127.0.0.1:${CHROME_PORT}${req.url}`); const text = await r.text(); const h = { 'content-type': r.headers.get('content-type') || 'application/json' }; res.writeHead(r.status, h); res.end(text.replace( new RegExp(`ws://127\\.0\\.0\\.1:${CHROME_PORT}`, 'g'), `${wsProto(req)}://${req.headers.host}` )); } catch (e) { res.writeHead(502); res.end(JSON.stringify({ error: e.message })); } }); new WebSocketServer({ server }).on('connection', (ws, req) => { const url = req.url || '/'; const target = `ws://127.0.0.1:${CHROME_PORT}${url}`; const cws = new WebSocket(target); let cwsReady = false; const pending = []; ws.on('message', (d) => { const str = d.toString(); if (cwsReady) { try { cws.send(str); } catch {} } else { pending.push(str); } }); ws.on('close', () => { try { cws.close(); } catch {} }); cws.on('open', () => { cwsReady = true; for (const s of pending) try { cws.send(s); } catch {} pending.length = 0; cws.on('message', d => { try { ws.send(d.toString()); } catch {} }); cws.on('close', () => { try { ws.close(); } catch {} }); }); cws.on('error', () => { try { ws.close(1011); } catch {} }); }); await waitForChrome(); server.listen(PORT, '0.0.0.0', () => { console.log(`WSS proxy on 0.0.0.0:${PORT}`); });