Files
website/src/lib/games-ingest/http.ts

40 lines
1.2 KiB
TypeScript

import { SourceError, sleep } from './types';
const UA = 'stanrunge.dev games tracker (personal, self-hosted)';
/** GET/POST JSON with a timeout, retrying 429/5xx a few times (honouring Retry-After). */
export async function fetchJson<T = unknown>(
url: string,
init: RequestInit & { retries?: number; allow?: number[] } = {}
): Promise<{ status: number; body: T }> {
const { retries = 3, allow = [], ...rest } = init;
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, {
...rest,
headers: { 'User-Agent': UA, Accept: 'application/json', ...(rest.headers ?? {}) },
signal: AbortSignal.timeout(30_000)
});
if ((res.status === 429 || res.status >= 500) && attempt < retries) {
const after = Number(res.headers.get('retry-after'));
await sleep(
Math.min(90, Number.isFinite(after) && after > 0 ? after : 5 * 2 ** attempt) * 1000
);
continue;
}
const text = await res.text();
if (!res.ok && !allow.includes(res.status)) {
throw new SourceError(
`${res.status} from ${new URL(url).host}${new URL(url).pathname}: ${text.slice(0, 160)}`,
res.status
);
}
let body: unknown;
try {
body = text ? JSON.parse(text) : null;
} catch {
body = text;
}
return { status: res.status, body: body as T };
}
}