// Drive headless Chromium via CDP: load a page, wait for window.__done, // print window.__result (and save a renderer snapshot if present). // Usage: node particle-probe.mjs [waitMs] import { spawn } from 'node:child_process'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; const [url, waitMs = '15000'] = process.argv.slice(2); const PORT = 9400 + Math.floor(Math.random() * 100); const HOME = process.env.HOME || os.homedir(); const CHROME = [ path.join(HOME, '.cache/ms-playwright/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell'), path.join(HOME, '.cache/ms-playwright/chromium-1234/chrome-linux64/chrome'), ].find((p) => fs.existsSync(p)); if (!CHROME) throw new Error('no chromium found'); const udd = fs.mkdtempSync(path.join(os.tmpdir(), 'cdp-probe-')); const chrome = spawn(CHROME, [ '--headless', `--remote-debugging-port=${PORT}`, `--user-data-dir=${udd}`, '--no-first-run', '--disable-gpu', '--enable-unsafe-swiftshader', '--no-sandbox', 'about:blank', ], { stdio: 'ignore' }); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); try { let v; for (let i = 0; i < 100; i++) { try { v = await (await fetch(`http://127.0.0.1:${PORT}/json/version`)).json(); break; } catch { await sleep(200); } } if (!v) throw new Error('CDP never came up'); let t; try { t = await (await fetch(`http://127.0.0.1:${PORT}/json/new?${encodeURIComponent(url)}`, { method: 'PUT' })).json(); } catch { t = await (await fetch(`http://127.0.0.1:${PORT}/json/new?${encodeURIComponent(url)}`, { method: 'POST' })).json(); } const ws = new WebSocket(t.webSocketDebuggerUrl); let id = 0; const p = new Map(); const send = (m, pa = {}) => new Promise((res, rej) => { const i = ++id; p.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method: m, params: pa })); }); ws.onmessage = (m) => { const x = JSON.parse(m.data); if (x.id && p.has(x.id)) { const { res, rej } = p.get(x.id); p.delete(x.id); x.error ? rej(new Error(x.error.message)) : res(x.result); } }; await new Promise((r) => (ws.onopen = r)); await send('Runtime.enable'); const t0 = Date.now(); let done = false; while (Date.now() - t0 < Number(waitMs)) { await sleep(250); const r = await send('Runtime.evaluate', { expression: 'window.__done === true', returnByValue: true }); if (r.result && r.result.value === true) { done = true; break; } } const r = await send('Runtime.evaluate', { expression: 'JSON.stringify(window.__result)', returnByValue: true }); if (r.exceptionDetails) throw new Error(JSON.stringify(r.exceptionDetails).slice(0, 400)); console.log('DONE:', done); console.log('RESULT:', r.result && r.result.value); const s = await send('Runtime.evaluate', { expression: 'document.querySelector("canvas").toDataURL("image/png")', returnByValue: true }); if (s.result && s.result.value) { fs.writeFileSync('/tmp/particle-probe.png', Buffer.from(s.result.value.split(',')[1], 'base64')); console.log('SHOT: /tmp/particle-probe.png'); } ws.close(); } finally { chrome.kill(); setTimeout(() => fs.rmSync(udd, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }), 1500); }