60 lines
3.1 KiB
JavaScript
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);
|
|
}
|