|
|
/** 报价完成信号等待上限(毫秒) */
|
|
|
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";
|
|
|
}
|