82 lines
2.4 KiB
TypeScript
82 lines
2.4 KiB
TypeScript
|
|
#!/usr/bin/env node
|
||
|
|
/**
|
||
|
|
* Pull the CSV from the headless browser via CDP and assemble it on disk.
|
||
|
|
*
|
||
|
|
* Usage:
|
||
|
|
* tsx scripts/_dump-browser-csv.ts <viewId> <expression> <out-path>
|
||
|
|
*
|
||
|
|
* Example:
|
||
|
|
* tsx scripts/_dump-browser-csv.ts stable-browser-session/xxx 'window.__hbCsv' scripts/nss-hex-bolts.csv
|
||
|
|
*/
|
||
|
|
import { writeFileSync } from "node:fs";
|
||
|
|
|
||
|
|
const [, , viewIdExpr, expression, outPath] = process.argv;
|
||
|
|
if (!viewIdExpr || !expression || !outPath) {
|
||
|
|
console.error("Usage: tsx _dump-browser-csv.ts <viewId> <expr> <out>");
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
const port = 9222;
|
||
|
|
const host = "127.0.0.1";
|
||
|
|
|
||
|
|
// Discover the page WebSocket URL.
|
||
|
|
async function getWsUrl(): Promise<string> {
|
||
|
|
const r = await fetch(`http://${host}:${port}/json`);
|
||
|
|
const tabs = (await r.json()) as Array<{ id: string; webSocketDebuggerUrl: string }>;
|
||
|
|
const target = tabs.find((t) => t.id.startsWith(viewIdExpr));
|
||
|
|
if (!target) throw new Error(`tab not found for viewId prefix ${viewIdExpr}`);
|
||
|
|
return target.webSocketDebuggerUrl;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function evalChunked(ws: WebSocket, expression: string): Promise<string> {
|
||
|
|
const CHUNK = 6000;
|
||
|
|
const full = await evalOnce(ws, expression);
|
||
|
|
const out: string[] = [];
|
||
|
|
for (let i = 0; i < full.length; i += CHUNK) {
|
||
|
|
out.push(full.substring(i, i + CHUNK));
|
||
|
|
}
|
||
|
|
return out.join("");
|
||
|
|
}
|
||
|
|
|
||
|
|
function evalOnce(
|
||
|
|
ws: WebSocket,
|
||
|
|
expression: string,
|
||
|
|
): Promise<string> {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const id = Math.floor(Math.random() * 1e9);
|
||
|
|
const onMsg = (ev: MessageEvent) => {
|
||
|
|
const msg = JSON.parse(ev.data);
|
||
|
|
if (msg.id !== id) return;
|
||
|
|
ws.removeEventListener("message", onMsg);
|
||
|
|
if (msg.error) reject(new Error(msg.error.message));
|
||
|
|
else resolve(msg.result?.value ?? "");
|
||
|
|
};
|
||
|
|
ws.addEventListener("message", onMsg);
|
||
|
|
ws.send(
|
||
|
|
JSON.stringify({
|
||
|
|
id,
|
||
|
|
method: "Runtime.evaluate",
|
||
|
|
params: { expression, returnByValue: true },
|
||
|
|
}),
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
const wsUrl = await getWsUrl();
|
||
|
|
const ws = new WebSocket(wsUrl);
|
||
|
|
await new Promise<void>((resolve, reject) => {
|
||
|
|
ws.addEventListener("open", () => resolve());
|
||
|
|
ws.addEventListener("error", (e) => reject(new Error(String(e))));
|
||
|
|
});
|
||
|
|
const csv = await evalChunked(ws, expression);
|
||
|
|
writeFileSync(outPath, csv);
|
||
|
|
console.log(`Wrote ${csv.length} bytes to ${outPath}`);
|
||
|
|
ws.close();
|
||
|
|
}
|
||
|
|
|
||
|
|
main().catch((e) => {
|
||
|
|
console.error("FATAL:", e);
|
||
|
|
process.exit(1);
|
||
|
|
});
|