( url: string, requestOptions: RequestInit, localHostname: string, )
| 235 | } |
| 236 | |
| 237 | export async function makeHttpRequest( |
| 238 | url: string, |
| 239 | requestOptions: RequestInit, |
| 240 | localHostname: string, |
| 241 | ): Promise<{ output: Json; isError: boolean }> { |
| 242 | // TODO: Don't handle redirects manually |
| 243 | // Why handle 3XX's manually? Because Companies House likes 302 redirects, |
| 244 | // but it throws an error if you have the headers from the first request set |
| 245 | // (specifically the Authorization header) |
| 246 | let response = await exponentialRetryWrapper( |
| 247 | fetch, |
| 248 | [url, { ...requestOptions, redirect: "manual" }], |
| 249 | 3, |
| 250 | ); |
| 251 | if (response.status >= 300 && response.status < 400) { |
| 252 | // Try requesting from here without auth headers |
| 253 | console.info("Attempting manual redirect"); |
| 254 | |
| 255 | if (!response.headers.has("location")) { |
| 256 | return { |
| 257 | output: { |
| 258 | status: "Redirect failed as original request did not return location", |
| 259 | }, |
| 260 | isError: true, |
| 261 | }; |
| 262 | } |
| 263 | const requestOptionsCopy = { |
| 264 | ...requestOptions, |
| 265 | headers: { ...requestOptions.headers }, |
| 266 | }; |
| 267 | const headers = requestOptionsCopy.headers; |
| 268 | if (headers) { |
| 269 | if ("Authorization" in headers) delete headers["Authorization"]; |
| 270 | if ("authorization" in headers) delete headers["authorization"]; |
| 271 | } |
| 272 | requestOptions.headers = headers; |
| 273 | const { origin } = new URL(url); |
| 274 | |
| 275 | const redirectUrl = response.headers.get("location")!.includes(origin) |
| 276 | ? response.headers.get("location")! |
| 277 | : new URL(response.headers.get("location")!, origin).href; |
| 278 | |
| 279 | console.info("Attempting fetch with redirected url: " + redirectUrl); |
| 280 | requestOptionsCopy.headers = headers; |
| 281 | response = await fetch(redirectUrl, { ...requestOptionsCopy }); |
| 282 | } |
| 283 | |
| 284 | // Deal with response with potentially empty body (stackoverflow.com/a/51320025) |
| 285 | const responseStatus = response.status ?? 0; |
| 286 | console.info("Response status:" + responseStatus); |
| 287 | const responseText = await response.text(); |
| 288 | // If there's no response body, return a status message |
| 289 | if (!responseText) { |
| 290 | return responseStatus >= 200 && responseStatus < 300 |
| 291 | ? { |
| 292 | output: { |
| 293 | status: responseStatus, |
| 294 | message: "Action completed successfully", |
nothing calls this directly
no test coverage detected