( url: string, maxRedirects: number = MAX_REDIRECTS, )
| 340 | } |
| 341 | |
| 342 | async function fetchWithRedirects( |
| 343 | url: string, |
| 344 | maxRedirects: number = MAX_REDIRECTS, |
| 345 | ): Promise<{ |
| 346 | finalUrl: string; |
| 347 | redirectChain: RedirectHop[]; |
| 348 | html: string; |
| 349 | headers: Headers; |
| 350 | statusCode: number; |
| 351 | timing: DetailedTiming; |
| 352 | }> { |
| 353 | const redirectChain: RedirectHop[] = []; |
| 354 | let currentUrl = url; |
| 355 | let finalHeaders: Headers | null = null; |
| 356 | let finalStatusCode = 0; |
| 357 | let finalHtml = ''; |
| 358 | const totalStartTime = Date.now(); |
| 359 | |
| 360 | let dnsTime = 0; |
| 361 | let connectTime = 0; |
| 362 | let tlsTime = 0; |
| 363 | let ttfbTime = 0; |
| 364 | |
| 365 | try { |
| 366 | const urlObj = new URL(currentUrl); |
| 367 | const hostname = urlObj.hostname; |
| 368 | const port = urlObj.port |
| 369 | ? Number.parseInt(urlObj.port, 10) |
| 370 | : urlObj.protocol === 'https:' |
| 371 | ? 443 |
| 372 | : 80; |
| 373 | |
| 374 | const dnsStart = Date.now(); |
| 375 | await dns.resolve4(hostname).catch(() => {}); |
| 376 | dnsTime = Date.now() - dnsStart; |
| 377 | |
| 378 | if (port === 443 || port === 80) { |
| 379 | const connTiming = await measureConnectionTime(hostname, port); |
| 380 | connectTime = connTiming.connectTime; |
| 381 | tlsTime = connTiming.tlsTime; |
| 382 | } |
| 383 | } catch { |
| 384 | // continue |
| 385 | } |
| 386 | |
| 387 | let firstRequestStartTime = 0; |
| 388 | |
| 389 | for (let i = 0; i < maxRedirects; i++) { |
| 390 | const controller = new AbortController(); |
| 391 | const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS); |
| 392 | |
| 393 | try { |
| 394 | const hopStartTime = Date.now(); |
| 395 | if (i === 0) { |
| 396 | firstRequestStartTime = hopStartTime; |
| 397 | } |
| 398 | |
| 399 | const response = await fetch(currentUrl, { |
no test coverage detected