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