( underlying: typeof globalThis.fetch, options: HostedHttpClientOptions, )
| 207 | }; |
| 208 | |
| 209 | const guardFetch = ( |
| 210 | underlying: typeof globalThis.fetch, |
| 211 | options: HostedHttpClientOptions, |
| 212 | ): typeof globalThis.fetch => |
| 213 | (async (input, init) => { |
| 214 | const guardOptions = { |
| 215 | ...options, |
| 216 | resolveHostname: options.resolveHostname ?? resolveHostnameWithNodeDns, |
| 217 | }; |
| 218 | const maxRedirects = options.maxRedirects ?? 10; |
| 219 | let current: Parameters<typeof globalThis.fetch>[0] | URL = input; |
| 220 | let currentInit = init; |
| 221 | for (let redirects = 0; redirects <= maxRedirects; redirects++) { |
| 222 | const url = current instanceof Request ? current.url : String(current); |
| 223 | await Effect.runPromise(validateHostedOutboundUrl(url, guardOptions)); |
| 224 | const response = await underlying(current, { |
| 225 | ...currentInit, |
| 226 | redirect: "manual", |
| 227 | }); |
| 228 | if ( |
| 229 | response.status >= 300 && |
| 230 | response.status < 400 && |
| 231 | response.headers.has("location") && |
| 232 | redirects < maxRedirects |
| 233 | ) { |
| 234 | const next = new URL(response.headers.get("location")!, url); |
| 235 | // Cross-origin redirects are followed (the loop re-validates every |
| 236 | // hop), but credentials minted for the original origin must not leak |
| 237 | // to the redirect target — same as fetch/curl behavior. |
| 238 | if (next.origin !== new URL(url).origin) { |
| 239 | currentInit = stripCredentialHeaders(currentInit); |
| 240 | } |
| 241 | current = next.toString(); |
| 242 | continue; |
| 243 | } |
| 244 | return response; |
| 245 | } |
| 246 | return await underlying(current, { ...currentInit, redirect: "manual" }); |
| 247 | }) as typeof globalThis.fetch; |
| 248 | |
| 249 | export const makeHostedFetch = (options: HostedHttpClientOptions = {}): typeof globalThis.fetch => |
| 250 | // oxlint-disable-next-line executor/no-raw-fetch -- boundary: exposes a guarded Fetch API adapter for libraries that require fetch |
no test coverage detected