mirror of
https://github.com/mendableai/firecrawl.git
synced 2025-10-26 07:30:22 +00:00
214 lines
4.5 KiB
TypeScript
214 lines
4.5 KiB
TypeScript
import { Logger } from "winston";
|
|
import { z, ZodError } from "zod";
|
|
import { v4 as uuid } from "uuid";
|
|
import * as Sentry from "@sentry/node";
|
|
|
|
export type RobustFetchParams<Schema extends z.Schema<any>> = {
|
|
url: string;
|
|
logger: Logger;
|
|
method: "GET" | "POST" | "DELETE" | "PUT";
|
|
body?: any;
|
|
headers?: Record<string, string>;
|
|
schema?: Schema;
|
|
dontParseResponse?: boolean;
|
|
ignoreResponse?: boolean;
|
|
ignoreFailure?: boolean;
|
|
requestId?: string;
|
|
tryCount?: number;
|
|
tryCooldown?: number;
|
|
};
|
|
|
|
export async function robustFetch<
|
|
Schema extends z.Schema<any>,
|
|
Output = z.infer<Schema>,
|
|
>({
|
|
url,
|
|
logger,
|
|
method = "GET",
|
|
body,
|
|
headers,
|
|
schema,
|
|
ignoreResponse = false,
|
|
ignoreFailure = false,
|
|
requestId = uuid(),
|
|
tryCount = 1,
|
|
tryCooldown,
|
|
}: RobustFetchParams<Schema>): Promise<Output> {
|
|
const params = {
|
|
url,
|
|
logger,
|
|
method,
|
|
body,
|
|
headers,
|
|
schema,
|
|
ignoreResponse,
|
|
ignoreFailure,
|
|
tryCount,
|
|
tryCooldown,
|
|
};
|
|
|
|
let request: Response;
|
|
try {
|
|
request = await fetch(url, {
|
|
method,
|
|
headers: {
|
|
...(body instanceof FormData
|
|
? {}
|
|
: body !== undefined
|
|
? {
|
|
"Content-Type": "application/json",
|
|
}
|
|
: {}),
|
|
...(headers !== undefined ? headers : {}),
|
|
},
|
|
...(body instanceof FormData
|
|
? {
|
|
body,
|
|
}
|
|
: body !== undefined
|
|
? {
|
|
body: JSON.stringify(body),
|
|
}
|
|
: {}),
|
|
});
|
|
} catch (error) {
|
|
if (!ignoreFailure) {
|
|
Sentry.captureException(error);
|
|
if (tryCount > 1) {
|
|
logger.debug(
|
|
"Request failed, trying " + (tryCount - 1) + " more times",
|
|
{ params, error, requestId },
|
|
);
|
|
return await robustFetch({
|
|
...params,
|
|
requestId,
|
|
tryCount: tryCount - 1,
|
|
});
|
|
} else {
|
|
logger.debug("Request failed", { params, error, requestId });
|
|
throw new Error("Request failed", {
|
|
cause: {
|
|
params,
|
|
requestId,
|
|
error,
|
|
},
|
|
});
|
|
}
|
|
} else {
|
|
return null as Output;
|
|
}
|
|
}
|
|
|
|
if (ignoreResponse === true) {
|
|
return null as Output;
|
|
}
|
|
|
|
const response = {
|
|
status: request.status,
|
|
headers: request.headers,
|
|
body: await request.text(), // NOTE: can this throw an exception?
|
|
};
|
|
|
|
if (request.status >= 300) {
|
|
if (tryCount > 1) {
|
|
logger.debug(
|
|
"Request sent failure status, trying " + (tryCount - 1) + " more times",
|
|
{ params, request, response, requestId },
|
|
);
|
|
if (tryCooldown !== undefined) {
|
|
await new Promise((resolve) =>
|
|
setTimeout(() => resolve(null), tryCooldown),
|
|
);
|
|
}
|
|
return await robustFetch({
|
|
...params,
|
|
requestId,
|
|
tryCount: tryCount - 1,
|
|
});
|
|
} else {
|
|
logger.debug("Request sent failure status", {
|
|
params,
|
|
request,
|
|
response,
|
|
requestId,
|
|
});
|
|
throw new Error("Request sent failure status", {
|
|
cause: {
|
|
params,
|
|
request,
|
|
response,
|
|
requestId,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
let data: Output;
|
|
try {
|
|
data = JSON.parse(response.body);
|
|
} catch (error) {
|
|
logger.debug("Request sent malformed JSON", {
|
|
params,
|
|
request,
|
|
response,
|
|
requestId,
|
|
});
|
|
throw new Error("Request sent malformed JSON", {
|
|
cause: {
|
|
params,
|
|
request,
|
|
response,
|
|
requestId,
|
|
},
|
|
});
|
|
}
|
|
|
|
if (schema) {
|
|
try {
|
|
data = schema.parse(data);
|
|
} catch (error) {
|
|
if (error instanceof ZodError) {
|
|
logger.debug("Response does not match provided schema", {
|
|
params,
|
|
request,
|
|
response,
|
|
requestId,
|
|
error,
|
|
schema,
|
|
});
|
|
throw new Error("Response does not match provided schema", {
|
|
cause: {
|
|
params,
|
|
request,
|
|
response,
|
|
requestId,
|
|
error,
|
|
schema,
|
|
},
|
|
});
|
|
} else {
|
|
logger.debug("Parsing response with provided schema failed", {
|
|
params,
|
|
request,
|
|
response,
|
|
requestId,
|
|
error,
|
|
schema,
|
|
});
|
|
throw new Error("Parsing response with provided schema failed", {
|
|
cause: {
|
|
params,
|
|
request,
|
|
response,
|
|
requestId,
|
|
error,
|
|
schema,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|