171 lines
5.8 KiB
JavaScript
171 lines
5.8 KiB
JavaScript
import { chromium } from "playwright";
|
|
import { spawn } from "node:child_process";
|
|
import { existsSync, readdirSync } from "node:fs";
|
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
import net from "node:net";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
|
|
const sourceUrl = process.argv[2];
|
|
const holdMs = Number(process.env.DEBUG_HOLD_MS || "900000");
|
|
const port = Number(process.env.DEBUG_PORT || "9222");
|
|
const proxyPort = process.env.DEBUG_PROXY_PORT ? Number(process.env.DEBUG_PROXY_PORT) : null;
|
|
const cdpOnly = process.env.DEBUG_CDP_ONLY === "1";
|
|
|
|
if (!sourceUrl) {
|
|
console.error("usage: node scripts/debug-chatgpt-share.mjs <chatgpt-share-url>");
|
|
process.exit(2);
|
|
}
|
|
|
|
let browser;
|
|
let context;
|
|
let browserProcess;
|
|
let proxyServer;
|
|
let userDataDir;
|
|
|
|
function now() {
|
|
return new Date().toISOString();
|
|
}
|
|
|
|
function findChromiumExecutable() {
|
|
const preferred = process.env.DEBUG_CHROMIUM_EXECUTABLE || chromium.executablePath();
|
|
if (existsSync(preferred)) return preferred;
|
|
|
|
const browsersRoot = process.env.PLAYWRIGHT_BROWSERS_PATH || path.dirname(path.dirname(preferred));
|
|
const headlessShell = readdirSync(browsersRoot)
|
|
.filter((entry) => entry.startsWith("chromium_headless_shell-"))
|
|
.sort()
|
|
.reverse()
|
|
.map((entry) => path.join(browsersRoot, entry, "chrome-headless-shell-linux64", "chrome-headless-shell"))
|
|
.find((candidate) => existsSync(candidate));
|
|
|
|
if (headlessShell) return headlessShell;
|
|
throw new Error(`No Chromium executable found. Tried ${preferred} and ${browsersRoot}/chromium_headless_shell-*.`);
|
|
}
|
|
|
|
async function close() {
|
|
if (context) await context.close().catch(() => undefined);
|
|
if (browser) await browser.close().catch(() => undefined);
|
|
if (proxyServer) await new Promise((resolve) => proxyServer.close(resolve)).catch(() => undefined);
|
|
if (browserProcess && !browserProcess.killed) browserProcess.kill("SIGTERM");
|
|
if (userDataDir) await rm(userDataDir, { recursive: true, force: true }).catch(() => undefined);
|
|
}
|
|
|
|
process.on("SIGINT", () => close().finally(() => process.exit(130)));
|
|
process.on("SIGTERM", () => close().finally(() => process.exit(143)));
|
|
|
|
userDataDir = await mkdtemp(path.join(os.tmpdir(), "aishare-debug-"));
|
|
const executablePath = findChromiumExecutable();
|
|
console.log(`[${now()}] Chromium executable: ${executablePath}`);
|
|
browserProcess = spawn(executablePath, [
|
|
"--headless",
|
|
"--no-sandbox",
|
|
"--disable-dev-shm-usage",
|
|
"--disable-gpu",
|
|
"--hide-scrollbars",
|
|
"--mute-audio",
|
|
"--window-size=1360,900",
|
|
"--remote-debugging-address=0.0.0.0",
|
|
`--remote-debugging-port=${port}`,
|
|
`--user-data-dir=${userDataDir}`,
|
|
cdpOnly ? sourceUrl : "about:blank"
|
|
], {
|
|
stdio: ["ignore", "pipe", "pipe"]
|
|
});
|
|
|
|
browserProcess.stdout.on("data", (data) => process.stdout.write(data));
|
|
browserProcess.stderr.on("data", (data) => process.stderr.write(data));
|
|
browserProcess.on("exit", (code, signal) => {
|
|
console.log(`[${now()}] Chromium exited code=${code} signal=${signal}`);
|
|
});
|
|
|
|
let devtoolsReady = false;
|
|
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
const response = await fetch(`http://127.0.0.1:${port}/json/version`).catch(() => null);
|
|
if (response?.ok) {
|
|
devtoolsReady = true;
|
|
break;
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
}
|
|
if (!devtoolsReady) throw new Error(`Chromium DevTools endpoint did not start on 127.0.0.1:${port}.`);
|
|
|
|
if (proxyPort) {
|
|
proxyServer = net.createServer((client) => {
|
|
const upstream = net.connect(port, "127.0.0.1");
|
|
client.pipe(upstream);
|
|
upstream.pipe(client);
|
|
client.on("error", () => upstream.destroy());
|
|
upstream.on("error", () => client.destroy());
|
|
});
|
|
await new Promise((resolve, reject) => {
|
|
proxyServer.once("error", reject);
|
|
proxyServer.listen(proxyPort, "0.0.0.0", resolve);
|
|
});
|
|
console.log(`[${now()}] DevTools proxy: 0.0.0.0:${proxyPort} -> 127.0.0.1:${port}`);
|
|
}
|
|
|
|
if (cdpOnly) {
|
|
console.log(`[${now()}] CDP-only mode; holding browser for ${Math.round(holdMs / 1000)}s.`);
|
|
await new Promise((resolve) => setTimeout(resolve, holdMs));
|
|
await close();
|
|
process.exit(0);
|
|
}
|
|
|
|
browser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`);
|
|
|
|
context = await browser.newContext({
|
|
viewport: { width: 1360, height: 900 },
|
|
deviceScaleFactor: 1,
|
|
locale: "en-US"
|
|
});
|
|
|
|
const page = await context.newPage();
|
|
page.setDefaultTimeout(30_000);
|
|
|
|
console.log(`[${now()}] Chromium DevTools port: ${port}`);
|
|
console.log(`[${now()}] Opening ${sourceUrl}`);
|
|
|
|
await page.goto(sourceUrl, { waitUntil: "commit", timeout: 45_000 }).catch((error) => {
|
|
console.log(`[${now()}] goto did not complete: ${error.message}`);
|
|
});
|
|
await page.waitForLoadState("domcontentloaded", { timeout: 20_000 }).catch(() => undefined);
|
|
await page.waitForLoadState("networkidle", { timeout: 10_000 }).catch(() => undefined);
|
|
await page.waitForTimeout(3000);
|
|
|
|
const summary = await page.evaluate(() => {
|
|
const selectors = [
|
|
"[data-message-author-role]",
|
|
"[data-message-id]",
|
|
"[data-testid]",
|
|
"main article",
|
|
"main [role=listitem]",
|
|
"main .markdown",
|
|
"main [class*=markdown]",
|
|
"script[type='application/json']",
|
|
"script#__NEXT_DATA__"
|
|
];
|
|
|
|
const counts = Object.fromEntries(selectors.map((selector) => [selector, document.querySelectorAll(selector).length]));
|
|
const title = document.querySelector("h1")?.textContent?.trim() || document.title;
|
|
const bodySample = (document.body.textContent || "").replace(/\s+/g, " ").trim().slice(0, 600);
|
|
const testIds = Array.from(document.querySelectorAll("[data-testid]"))
|
|
.map((element) => element.getAttribute("data-testid"))
|
|
.filter(Boolean)
|
|
.slice(0, 80);
|
|
|
|
return {
|
|
href: location.href,
|
|
title,
|
|
counts,
|
|
testIds,
|
|
bodySample
|
|
};
|
|
});
|
|
|
|
console.log(JSON.stringify(summary, null, 2));
|
|
console.log(`[${now()}] Holding browser for ${Math.round(holdMs / 1000)}s.`);
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, holdMs));
|
|
await close();
|