orbit/dev/particle-probe.mjs

60 lines
3.1 KiB
JavaScript

// 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 <url> [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);
}