|
| 1 | +import { USER_AGENT } from '../constants' |
| 2 | + |
1 | 3 | export interface RetryOptions { |
2 | 4 | maxRetries?: number |
3 | 5 | baseDelayMs?: number |
@@ -46,9 +48,33 @@ export async function fetchWithRetry( |
46 | 48 | let lastResponse: Response | undefined |
47 | 49 | let lastError: unknown |
48 | 50 |
|
| 51 | + // Inject User-Agent so Cloudflare can identify traffic from this server. |
| 52 | + // When input is a Request, clone it to preserve its headers alongside the new header. |
| 53 | + // When input is a string/URL, merge into init.headers as a plain object. |
| 54 | + let fetchInput: RequestInfo |
| 55 | + let fetchInit: RequestInit | undefined |
| 56 | + if (input instanceof Request) { |
| 57 | + const headers = new Headers(input.headers) |
| 58 | + headers.set('User-Agent', USER_AGENT) |
| 59 | + fetchInput = new Request(input, { headers }) |
| 60 | + fetchInit = init |
| 61 | + } else { |
| 62 | + fetchInput = input |
| 63 | + // Spread existing headers as a plain object to preserve casing, then set User-Agent last |
| 64 | + // so it always takes precedence over any caller-supplied value. |
| 65 | + const existingHeaders = |
| 66 | + init?.headers instanceof Headers |
| 67 | + ? Object.fromEntries(init.headers) |
| 68 | + : ((init?.headers as Record<string, string> | undefined) ?? {}) |
| 69 | + fetchInit = { |
| 70 | + ...init, |
| 71 | + headers: { ...existingHeaders, 'User-Agent': USER_AGENT } |
| 72 | + } |
| 73 | + } |
| 74 | + |
49 | 75 | for (let attempt = 0; attempt <= opts.maxRetries; attempt++) { |
50 | 76 | try { |
51 | | - const response = await fetch(input, init) |
| 77 | + const response = await fetch(fetchInput, fetchInit) |
52 | 78 |
|
53 | 79 | if (response.status !== 429) { |
54 | 80 | return response |
|
0 commit comments