82 lines
2.4 KiB
TypeScript
82 lines
2.4 KiB
TypeScript
// bun preview/shot.ts <out.png> <url> [width] [height]
|
|
// Headless Chrome over CDP: the preview server must be running.
|
|
const [out, url, w = "340", h = "720"] = process.argv.slice(2);
|
|
const port = 9334;
|
|
const proc = Bun.spawn(
|
|
[
|
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
"--headless=new",
|
|
"--disable-gpu",
|
|
"--hide-scrollbars",
|
|
`--remote-debugging-port=${port}`,
|
|
`--window-size=${w},${h}`,
|
|
"--user-data-dir=/tmp/beaver-preview-chrome",
|
|
"about:blank",
|
|
],
|
|
{ stderr: "ignore", stdout: "ignore" }
|
|
);
|
|
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
let targets: { type: string; webSocketDebuggerUrl: string }[] = [];
|
|
for (let i = 0; i < 40; i += 1) {
|
|
try {
|
|
// biome-ignore lint/performance/noAwaitInLoops: polling until Chrome is up
|
|
targets = await (await fetch(`http://127.0.0.1:${port}/json`)).json();
|
|
if (targets.length) {
|
|
break;
|
|
}
|
|
} catch {
|
|
/* not up yet */
|
|
}
|
|
await wait(250);
|
|
}
|
|
const page = targets.find((t) => t.type === "page");
|
|
if (!page) {
|
|
proc.kill();
|
|
throw new Error("no page target");
|
|
}
|
|
const ws = new WebSocket(page.webSocketDebuggerUrl);
|
|
await new Promise((resolve) => {
|
|
ws.onopen = resolve;
|
|
});
|
|
let id = 0;
|
|
const pending = new Map<number, (v: { result?: { data?: string } }) => void>();
|
|
ws.onmessage = (m) => {
|
|
const d = JSON.parse(String(m.data));
|
|
if (d.id && pending.has(d.id)) {
|
|
pending.get(d.id)?.(d);
|
|
pending.delete(d.id);
|
|
}
|
|
};
|
|
const send = (method: string, params: Record<string, unknown> = {}) =>
|
|
new Promise<{ result?: { data?: string } }>((resolve) => {
|
|
id += 1;
|
|
pending.set(id, resolve);
|
|
ws.send(JSON.stringify({ id, method, params }));
|
|
});
|
|
await send("Emulation.setDeviceMetricsOverride", {
|
|
deviceScaleFactor: 2,
|
|
height: Number(h),
|
|
mobile: Number(w) < 500,
|
|
width: Number(w),
|
|
});
|
|
await send("Page.enable");
|
|
await send("Page.navigate", { url });
|
|
await wait(2500);
|
|
if (process.env.EVAL) {
|
|
const evaluated = await send("Runtime.evaluate", {
|
|
expression: process.env.EVAL,
|
|
returnByValue: true,
|
|
});
|
|
console.log(
|
|
JSON.stringify(
|
|
(evaluated.result as { result?: { value?: unknown } }).result?.value
|
|
)
|
|
);
|
|
await wait(400);
|
|
}
|
|
const shot = await send("Page.captureScreenshot", { format: "png" });
|
|
await Bun.write(out, Buffer.from(shot.result?.data ?? "", "base64"));
|
|
console.log(`wrote ${out}`);
|
|
ws.close();
|
|
proc.kill();
|