/** 报价完成信号等待上限(毫秒) */ export const RPA_QUOTE_CAPTURE_TIMEOUT_MS = 25_000; /** 提交后额外收集契约响应的短暂窗口(毫秒) */ export const RPA_QUOTE_POST_SUBMIT_SETTLE_MS = 2_000; /** sign-up-quote 结果页 widget 拉价等待(毫秒)— 须等 axel/quote 响应,不可盲目缩短 */ export const RPA_SIGN_UP_QUOTE_SETTLE_MS = 18_000; const IGNORE_URL_FRAGMENTS = [ "segment.io", "segment.com", "google-analytics", "googletagmanager", "facebook.net", "hotjar", "sentry.io", "fullstory", "intercom", ".png", ".jpg", ".css", ".woff", ".woff2", ".js", ".map", ] as const; function isJsonContentType(contentType: string): boolean { const ct = contentType.toLowerCase(); return ( ct.includes("json") || ct.includes("application/graphql") || ct.includes("text/plain") ); } /** axel/quote 白名单(阶段0 录证) */ export { AXEL_QUOTE_URL_FRAGMENT, isAxelQuoteResponseUrl } from "@/workers/rpa/quote-capture/axel-contract"; export function isJsonNetworkCandidate(url: string, contentType: string): boolean { const lower = url.toLowerCase(); if (IGNORE_URL_FRAGMENTS.some((frag) => lower.includes(frag))) { return false; } return isJsonContentType(contentType); } /** HAR 校准辅助:逗号分隔 URL 子串或 /regex/flags(仅 telemetry,不参与 runtime) */ export function parsePinnedQuoteApiPatterns(): RegExp[] { const raw = process.env.MOTHERSHIP_QUOTE_API_URL_PATTERNS?.trim(); if (!raw) { return []; } const patterns: RegExp[] = []; for (const part of raw.split(",").map((s) => s.trim()).filter(Boolean)) { const slash = part.match(/^\/(.+)\/([a-z]*)$/i); if (slash) { patterns.push(new RegExp(slash[1], slash[2] || "i")); continue; } const escaped = part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); patterns.push(new RegExp(escaped, "i")); } return patterns; } /** debug:契约命中 URL 与 HAR pinned 是否一致(不参与决策) */ export function formatPinnedTelemetry(url: string): string { const pinned = parsePinnedQuoteApiPatterns(); if (pinned.length === 0) { return "pinned=未配置"; } const matched = pinned.some((p) => p.test(url)); return matched ? "pinned=match" : "pinned=miss"; }