You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
104 lines
2.5 KiB
104 lines
2.5 KiB
import fs from "node:fs";
|
|
|
|
export type HarEntry = {
|
|
request: { url: string; method: string };
|
|
response: {
|
|
status: number;
|
|
content?: { mimeType?: string; text?: string };
|
|
};
|
|
};
|
|
|
|
export type HarFile = {
|
|
log?: { entries?: HarEntry[] };
|
|
};
|
|
|
|
export function readHarFile(harPath: string): HarFile {
|
|
const raw = fs.readFileSync(harPath, "utf8");
|
|
return JSON.parse(raw) as HarFile;
|
|
}
|
|
|
|
export function listHarEntries(har: HarFile): HarEntry[] {
|
|
return har.log?.entries ?? [];
|
|
}
|
|
|
|
export function parseHarResponseJson(entry: HarEntry): unknown | null {
|
|
const text = entry.response.content?.text;
|
|
if (!text?.trim()) {
|
|
return null;
|
|
}
|
|
try {
|
|
return JSON.parse(text) as unknown;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const QUOTE_URL_PATTERN =
|
|
/axel\/quote|api\.mothership\.com|plugins\.mothership\.com|services\.mothership\.com/i;
|
|
const QUOTE_URL_HINT = /quote|rate|pricing|graphql/i;
|
|
|
|
export function isQuoteHarEntry(entry: HarEntry): boolean {
|
|
if (entry.response.status < 200 || entry.response.status >= 300) {
|
|
return false;
|
|
}
|
|
const mime = entry.response.content?.mimeType ?? "";
|
|
if (!/json/i.test(mime) && !entry.response.content?.text?.trim().startsWith("{")) {
|
|
return false;
|
|
}
|
|
const url = entry.request.url.toLowerCase();
|
|
if (QUOTE_URL_PATTERN.test(url)) {
|
|
return true;
|
|
}
|
|
return QUOTE_URL_HINT.test(url) && /mothership/i.test(url);
|
|
}
|
|
|
|
export function scoreQuoteBody(body: unknown): number {
|
|
if (body == null || typeof body !== "object") {
|
|
return 0;
|
|
}
|
|
let score = 0;
|
|
const text = JSON.stringify(body).toLowerCase();
|
|
if (text.includes("rates")) {
|
|
score += 3;
|
|
}
|
|
if (text.includes("standard")) {
|
|
score += 2;
|
|
}
|
|
if (text.includes("guaranteed")) {
|
|
score += 2;
|
|
}
|
|
if (text.includes("freight") || text.includes("price")) {
|
|
score += 1;
|
|
}
|
|
if (text.includes("place_id") || text.includes("placeid")) {
|
|
score += 1;
|
|
}
|
|
return score;
|
|
}
|
|
|
|
export function pickBestQuoteEntry(entries: HarEntry[]): {
|
|
entry: HarEntry;
|
|
body: unknown;
|
|
} | null {
|
|
let best: { entry: HarEntry; body: unknown; score: number } | null = null;
|
|
|
|
for (const entry of entries) {
|
|
if (!isQuoteHarEntry(entry)) {
|
|
continue;
|
|
}
|
|
const body = parseHarResponseJson(entry);
|
|
if (body == null) {
|
|
continue;
|
|
}
|
|
const score = scoreQuoteBody(body);
|
|
if (score <= 0) {
|
|
continue;
|
|
}
|
|
if (!best || score > best.score) {
|
|
best = { entry, body, score };
|
|
}
|
|
}
|
|
|
|
return best ? { entry: best.entry, body: best.body } : null;
|
|
}
|