/** * 用 page.route + window 钩子捕获 POST /user/quotes body */ import fs from "node:fs"; import path from "node:path"; import { resolveFlockQuoteAccount } from "@/lib/flock/resolve-account"; import { getFlockQuoteUrl, getFlockQuoteWaitMs } from "@/lib/flock/env"; import { createRpaBrowserContext } from "@/lib/rpa/browser-context"; import { launchRpaBrowser } from "@/lib/rpa/browser-launch"; import { gotoWithResilience } from "@/lib/rpa/page-goto"; import { DEFAULT_FLOCK_DEMO } from "@/workers/rpa/flock/demo-input"; import { clickFlockQuoteSubmit, fillFlockQuoteForm, waitForFlockQuoteForm, } from "@/workers/rpa/flock/form-interactions"; import type { FlockQuoteInput } from "@/workers/rpa/flock/types"; const OUT_DIR = path.join(process.cwd(), ".rpa", "flock-network-poc"); async function main(): Promise { fs.mkdirSync(OUT_DIR, { recursive: true }); const input: FlockQuoteInput = { pickupDate: DEFAULT_FLOCK_DEMO.pickupDateDisplay, pickupZip: DEFAULT_FLOCK_DEMO.pickupZip, deliveryZip: DEFAULT_FLOCK_DEMO.deliveryZip, palletCount: DEFAULT_FLOCK_DEMO.palletCount, totalWeightLb: DEFAULT_FLOCK_DEMO.totalWeightLb, lengthIn: DEFAULT_FLOCK_DEMO.lengthIn, widthIn: DEFAULT_FLOCK_DEMO.widthIn, heightIn: DEFAULT_FLOCK_DEMO.heightIn, }; const account = resolveFlockQuoteAccount(input); const captured: string[] = []; const browser = await launchRpaBrowser({ headless: process.env.RPA_HEADLESS !== "false", incognito: true, }); try { const context = await createRpaBrowserContext(browser); await context.addInitScript(() => { const w = window as unknown as { __flockQuoteBodies?: string[]; }; w.__flockQuoteBodies = []; const origFetch = window.fetch.bind(window); window.fetch = async (...args: Parameters) => { try { const inputArg = args[0]; const init = args[1]; const url = typeof inputArg === "string" ? inputArg : inputArg instanceof Request ? inputArg.url : String(inputArg); if (/api\.flockfreight\.com\/user\/quotes/i.test(url)) { let bodyText = ""; if (init?.body) { if (typeof init.body === "string") bodyText = init.body; else if (init.body instanceof URLSearchParams) { bodyText = init.body.toString(); } else if (init.body instanceof Blob) { bodyText = await init.body.text(); } else { bodyText = String(init.body); } } else if (inputArg instanceof Request) { bodyText = await inputArg.clone().text(); } w.__flockQuoteBodies?.push(bodyText); } } catch { /* ignore */ } return origFetch(...args); }; const XO = XMLHttpRequest.prototype.open; const XS = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function ( method: string, url: string | URL, ...rest: unknown[] ) { (this as unknown as { __flockUrl?: string }).__flockUrl = String(url); return XO.apply(this, [method, url, ...rest] as never); }; XMLHttpRequest.prototype.send = function (body?: Document | XMLHttpRequestBodyInit | null) { const url = (this as unknown as { __flockUrl?: string }).__flockUrl ?? ""; if (/api\.flockfreight\.com\/user\/quotes/i.test(url)) { const text = typeof body === "string" ? body : body == null ? "" : String(body); w.__flockQuoteBodies?.push(text); } return XS.call(this, body); }; }); await context.route("**/api.flockfreight.com/user/quotes**", async (route) => { const req = route.request(); const post = req.postData(); if (post) { captured.push(post); console.log(`[route] bodyLen=${post.length}`); } else { console.log(`[route] no postData method=${req.method()}`); } await route.continue(); }); const page = await context.newPage(); await gotoWithResilience(page, getFlockQuoteUrl()); if (!(await waitForFlockQuoteForm(page))) throw new Error("表单未出现"); const filled = await fillFlockQuoteForm(page, input); if (!filled.ok) throw new Error(filled.error ?? "填表失败"); if (!(await clickFlockQuoteSubmit(page))) throw new Error("无提交按钮"); const deadline = Date.now() + getFlockQuoteWaitMs(); while (Date.now() < deadline) { const fromPage = await page.evaluate(() => { const w = window as unknown as { __flockQuoteBodies?: string[] }; return w.__flockQuoteBodies ?? []; }); if (fromPage.length || captured.length) { for (const b of fromPage) { if (b && !captured.includes(b)) captured.push(b); } break; } await page.waitForTimeout(400); } await page.waitForTimeout(2000); const late = await page.evaluate(() => { const w = window as unknown as { __flockQuoteBodies?: string[] }; return w.__flockQuoteBodies ?? []; }); for (const b of late) { if (b && !captured.includes(b)) captured.push(b); } const outPath = path.join(OUT_DIR, "user-quotes-request-body.json"); fs.writeFileSync( outPath, JSON.stringify( { capturedAt: new Date().toISOString(), email: account.email, bodies: captured, }, null, 2, ), "utf8", ); console.log(`wrote ${outPath} count=${captured.length}`); if (!captured.length || !captured.some((b) => b.trim())) { console.error("仍未捕获到 body"); process.exit(3); } console.log(captured.find((b) => b.trim())!.slice(0, 5000)); } finally { await browser.close(); } } main().catch((e) => { console.error(e); process.exit(1); });