nexu-io--open-design
128 行
4.2 KiB
JavaScript
可执行文件
128 行
4.2 KiB
JavaScript
可执行文件
#!/usr/bin/env node
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import crypto from "node:crypto";
|
|
import { loadPlaywright, launchChromium } from "./lib/playwright-loader.mjs";
|
|
|
|
function usage() {
|
|
console.log(`Usage:
|
|
node scripts/network-capture.mjs --url <url> --out RECON/network [--label original] [--wait 5000] [--max-bytes 1000000]
|
|
|
|
Captures browser requests and saves JSON/text XHR/fetch responses as fixtures.
|
|
`);
|
|
}
|
|
|
|
function parseArgs(argv) {
|
|
const out = { url: "", outDir: "RECON/network", label: "site", waitMs: 5000, maxBytes: 1_000_000 };
|
|
for (let i = 0; i < argv.length; i += 1) {
|
|
const arg = argv[i];
|
|
if (arg === "--help" || arg === "-h") out.help = true;
|
|
else if (arg === "--url") out.url = argv[++i] || "";
|
|
else if (arg === "--out") out.outDir = argv[++i] || "RECON/network";
|
|
else if (arg === "--label") out.label = argv[++i] || "site";
|
|
else if (arg === "--wait") out.waitMs = Number(argv[++i] || "5000");
|
|
else if (arg === "--max-bytes") out.maxBytes = Number(argv[++i] || "1000000");
|
|
else throw new Error(`Unexpected argument: ${arg}`);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function safeFixtureName(url, contentType) {
|
|
const parsed = new URL(url);
|
|
const cleanPath = parsed.pathname.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "response";
|
|
const hash = crypto.createHash("sha1").update(url).digest("hex").slice(0, 10);
|
|
const ext = contentType.includes("json") ? ".json" : ".txt";
|
|
return `${cleanPath}-${hash}${ext}`;
|
|
}
|
|
|
|
function shouldSave(resourceType, contentType) {
|
|
if (!["xhr", "fetch"].includes(resourceType)) return false;
|
|
return /json|text|graphql|javascript/i.test(contentType || "");
|
|
}
|
|
|
|
try {
|
|
const args = parseArgs(process.argv.slice(2));
|
|
if (args.help || !args.url) {
|
|
usage();
|
|
process.exit(args.help ? 0 : 1);
|
|
}
|
|
|
|
const outDir = path.resolve(args.outDir);
|
|
const fixturesDir = path.join(outDir, "fixtures");
|
|
fs.mkdirSync(fixturesDir, { recursive: true });
|
|
|
|
const { chromium } = loadPlaywright();
|
|
const browser = await launchChromium(chromium);
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
|
|
|
const requests = [];
|
|
const responses = [];
|
|
|
|
page.on("request", (request) => {
|
|
requests.push({
|
|
url: request.url(),
|
|
method: request.method(),
|
|
resourceType: request.resourceType(),
|
|
headers: request.headers(),
|
|
postData: request.postData() || "",
|
|
});
|
|
});
|
|
|
|
page.on("response", async (response) => {
|
|
const request = response.request();
|
|
const headers = response.headers();
|
|
const contentType = headers["content-type"] || "";
|
|
const entry = {
|
|
url: response.url(),
|
|
status: response.status(),
|
|
ok: response.ok(),
|
|
method: request.method(),
|
|
resourceType: request.resourceType(),
|
|
contentType,
|
|
fixture: "",
|
|
error: "",
|
|
};
|
|
|
|
if (shouldSave(request.resourceType(), contentType)) {
|
|
try {
|
|
const body = await response.body();
|
|
if (body.length <= args.maxBytes) {
|
|
const file = path.join(fixturesDir, safeFixtureName(response.url(), contentType));
|
|
fs.writeFileSync(file, body);
|
|
entry.fixture = path.relative(outDir, file);
|
|
entry.bytes = body.length;
|
|
} else {
|
|
entry.error = `body too large: ${body.length}`;
|
|
}
|
|
} catch (error) {
|
|
entry.error = error.message;
|
|
}
|
|
}
|
|
|
|
responses.push(entry);
|
|
});
|
|
|
|
await page.goto(args.url, { waitUntil: "domcontentloaded", timeout: 45000 });
|
|
await page.waitForLoadState("networkidle", { timeout: args.waitMs }).catch(() => {});
|
|
if (args.waitMs > 0) await page.waitForTimeout(args.waitMs);
|
|
await browser.close();
|
|
|
|
const manifest = {
|
|
label: args.label,
|
|
url: args.url,
|
|
capturedAt: new Date().toISOString(),
|
|
requestCount: requests.length,
|
|
responseCount: responses.length,
|
|
fixtureCount: responses.filter((response) => response.fixture).length,
|
|
requests,
|
|
responses,
|
|
};
|
|
|
|
const manifestFile = path.join(outDir, `${args.label}-network.json`);
|
|
fs.writeFileSync(manifestFile, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
console.log(manifestFile);
|
|
} catch (error) {
|
|
console.error(`network-capture failed: ${error.message}`);
|
|
process.exit(1);
|
|
}
|