Project Files
src / services / fetchHtml.ts
// fetchHtml.ts
const DEFAULT_HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
// Removed hardcoded google.com Referer — it triggers bot detection on many sites
};
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export async function fetchHtml(
url: string,
options?: {
retries?: number;
timeoutMs?: number;
},
): Promise<string> {
const retries = options?.retries ?? 2;
const timeoutMs = options?.timeoutMs ?? 15000;
let lastError: unknown;
for (let attempt = 0; attempt <= retries; attempt += 1) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
method: "GET",
headers: DEFAULT_HEADERS,
redirect: "follow",
signal: controller.signal,
});
clearTimeout(timeout);
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}`.trim());
}
return await response.text();
} catch (error) {
clearTimeout(timeout);
lastError = error;
if (attempt < retries) {
await sleep(500 * (attempt + 1));
continue;
}
}
}
const message = lastError instanceof Error ? lastError.message : String(lastError);
throw new Error(`Failed to fetch ${url}: ${message}`);
}