import { readFile } from "node:fs/promises"; import { existsSync } from "node:fs"; import { resolve } from "node:path"; import { Fetch, HttpMethod, HttpRequest, HttpResponse, createFetch } from "@/utils/net"; type FetchInterceptorResponse = string | string[] | HttpResponse | unknown; type FetchInterceptorHandler = (args: string[], request: HttpRequest, url: string) => FetchInterceptorResponse | Promise; type FetchInterceptor = { baseUrl?: string; requiredHeaders?: string[]; } & { [Method in HttpMethod]?: { [url: string]: FetchInterceptorHandler; }; }; const FAKE_FETCH: Fetch = (url, request) => Promise.reject( new Error(`Unsupported request: '${normalizeHttpMethod(request?.method)} ${url}'`) ); function normalizeHttpMethod(method: string): HttpMethod { return (method?.toUpperCase() as HttpMethod) || "GET"; } async function normalizeResponse(response: FetchInterceptorResponse): Promise { if (typeof response === "string") { return HttpResponse.text(response); } if (Array.isArray(response) && response.length && response.every(x => typeof x === "string")) { const path = resolve(...response); if (existsSync(path)) { const content = await readFile(path, "utf8"); return HttpResponse.text(content); } } if (typeof response["url"] === "string" && typeof response["blob"] === "function") { return response as HttpResponse; } return HttpResponse.json(response); } export function createFakeFetch(interceptor: FetchInterceptor): Fetch { const baseUrl = interceptor.baseUrl?.endsWith("/") ? interceptor.baseUrl.slice(0, -1) : (interceptor.baseUrl || ""); const requiredHeaders = interceptor.requiredHeaders || []; const methods = interceptor; return createFetch({ handler: FAKE_FETCH }).use(async (url, request, next) => { url = String(url); const urlPath = url.startsWith(baseUrl) ? url.substring(baseUrl.length) : url; const handlerEntry = Object.entries(methods[normalizeHttpMethod(request?.method)] || {}) .map(([urlMatcher, handler]) => [urlPath.match(urlMatcher), handler] as const) .find(([match]) => match !== null); if (!handlerEntry || !requiredHeaders.every(header => request?.headers?.[header])) { return await next(url, request); } const args = handlerEntry[0].slice(1); const response = await handlerEntry[1](args, request, url); return await normalizeResponse(response); }); } export function createCombinedFetch(...fetchComponents: Fetch[]): Fetch { const handlers = [...fetchComponents].reverse(); return async (url, request) => { for (let i = handlers.length - 1; i >= 0; i--) { try { return await handlers[i](url, request); } catch (error: unknown) { if (i === 0) { throw error; } } } return HttpResponse.error(); }; }