Johnsteve Costanos
Fix Chrome stability: add --no-zygote and other flags to prevent OOM-denied crashes
d8d1a41 | 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}`); | |
| }); | |