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 "); 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();