rezo 1.0.18 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/adapters/curl.cjs +39 -34
  2. package/dist/adapters/curl.js +39 -34
  3. package/dist/adapters/entries/curl.d.ts +29 -17
  4. package/dist/adapters/entries/fetch.d.ts +29 -17
  5. package/dist/adapters/entries/http.d.ts +29 -17
  6. package/dist/adapters/entries/http2.d.ts +29 -17
  7. package/dist/adapters/entries/react-native.d.ts +29 -17
  8. package/dist/adapters/entries/xhr.d.ts +29 -17
  9. package/dist/adapters/fetch.cjs +42 -41
  10. package/dist/adapters/fetch.js +42 -41
  11. package/dist/adapters/http.cjs +184 -73
  12. package/dist/adapters/http.js +184 -73
  13. package/dist/adapters/http2.cjs +41 -36
  14. package/dist/adapters/http2.js +41 -36
  15. package/dist/adapters/index.cjs +6 -6
  16. package/dist/adapters/react-native.cjs +41 -27
  17. package/dist/adapters/react-native.js +41 -27
  18. package/dist/adapters/xhr.cjs +43 -38
  19. package/dist/adapters/xhr.js +43 -38
  20. package/dist/cache/index.cjs +13 -13
  21. package/dist/crawler.d.ts +29 -17
  22. package/dist/entries/crawler.cjs +5 -5
  23. package/dist/index.cjs +24 -24
  24. package/dist/index.d.ts +29 -17
  25. package/dist/platform/browser.d.ts +29 -17
  26. package/dist/platform/bun.d.ts +29 -17
  27. package/dist/platform/deno.d.ts +29 -17
  28. package/dist/platform/node.d.ts +29 -17
  29. package/dist/platform/react-native.d.ts +29 -17
  30. package/dist/platform/worker.d.ts +29 -17
  31. package/dist/plugin/index.cjs +36 -36
  32. package/dist/proxy/index.cjs +2 -2
  33. package/dist/queue/index.cjs +8 -8
  34. package/dist/utils/http-config.cjs +8 -4
  35. package/dist/utils/http-config.js +8 -4
  36. package/dist/utils/timing.cjs +90 -0
  37. package/dist/utils/timing.js +78 -0
  38. package/package.json +1 -1
@@ -20,6 +20,100 @@ import { isSameDomain, RezoPerformance } from '../utils/tools.js';
20
20
  import { getGlobalDNSCache } from '../cache/dns-cache.js';
21
21
  import { ResponseCache } from '../cache/response-cache.js';
22
22
  import dns from "dns";
23
+ const debugLog = {
24
+ requestStart: (config, url, method) => {
25
+ if (config.debug) {
26
+ console.log(`
27
+ [Rezo Debug] ─────────────────────────────────────`);
28
+ console.log(`[Rezo Debug] ${method} ${url}`);
29
+ console.log(`[Rezo Debug] Request ID: ${config.requestId}`);
30
+ if (config.originalRequest?.headers) {
31
+ const headers = config.originalRequest.headers instanceof RezoHeaders ? config.originalRequest.headers.toObject() : config.originalRequest.headers;
32
+ console.log(`[Rezo Debug] Request Headers:`, JSON.stringify(headers, null, 2));
33
+ }
34
+ if (config.proxy && typeof config.proxy === "object") {
35
+ console.log(`[Rezo Debug] Proxy: ${config.proxy.protocol}://${config.proxy.host}:${config.proxy.port}`);
36
+ } else if (config.proxy && typeof config.proxy === "string") {
37
+ console.log(`[Rezo Debug] Proxy: ${config.proxy}`);
38
+ }
39
+ }
40
+ if (config.trackUrl) {
41
+ console.log(`[Rezo Track] → ${method} ${url}`);
42
+ }
43
+ },
44
+ redirect: (config, fromUrl, toUrl, statusCode, method) => {
45
+ if (config.debug) {
46
+ console.log(`[Rezo Debug] Redirect ${statusCode}: ${fromUrl}`);
47
+ console.log(`[Rezo Debug] → ${toUrl} (${method})`);
48
+ }
49
+ if (config.trackUrl) {
50
+ console.log(`[Rezo Track] ↳ ${statusCode} → ${toUrl}`);
51
+ }
52
+ },
53
+ retry: (config, attempt, maxRetries, statusCode, delay) => {
54
+ if (config.debug) {
55
+ console.log(`[Rezo Debug] Retry ${attempt}/${maxRetries} after status ${statusCode}${delay > 0 ? ` (waiting ${delay}ms)` : ""}`);
56
+ }
57
+ if (config.trackUrl) {
58
+ console.log(`[Rezo Track] ⟳ Retry ${attempt}/${maxRetries} (status ${statusCode})`);
59
+ }
60
+ },
61
+ maxRetries: (config, maxRetries) => {
62
+ if (config.debug) {
63
+ console.log(`[Rezo Debug] Max retries (${maxRetries}) reached, throwing error`);
64
+ }
65
+ if (config.trackUrl) {
66
+ console.log(`[Rezo Track] ✗ Max retries reached`);
67
+ }
68
+ },
69
+ response: (config, status, statusText, duration) => {
70
+ if (config.debug) {
71
+ console.log(`[Rezo Debug] Response: ${status} ${statusText} (${duration.toFixed(2)}ms)`);
72
+ }
73
+ if (config.trackUrl) {
74
+ console.log(`[Rezo Track] ✓ ${status} ${statusText}`);
75
+ }
76
+ },
77
+ responseHeaders: (config, headers) => {
78
+ if (config.debug) {
79
+ console.log(`[Rezo Debug] Response Headers:`, JSON.stringify(headers, null, 2));
80
+ }
81
+ },
82
+ cookies: (config, cookieCount) => {
83
+ if (config.debug && cookieCount > 0) {
84
+ console.log(`[Rezo Debug] Cookies received: ${cookieCount}`);
85
+ }
86
+ },
87
+ timing: (config, timing) => {
88
+ if (config.debug) {
89
+ const parts = [];
90
+ if (timing.dns)
91
+ parts.push(`DNS: ${timing.dns.toFixed(2)}ms`);
92
+ if (timing.connect)
93
+ parts.push(`Connect: ${timing.connect.toFixed(2)}ms`);
94
+ if (timing.tls)
95
+ parts.push(`TLS: ${timing.tls.toFixed(2)}ms`);
96
+ if (timing.ttfb)
97
+ parts.push(`TTFB: ${timing.ttfb.toFixed(2)}ms`);
98
+ if (timing.total)
99
+ parts.push(`Total: ${timing.total.toFixed(2)}ms`);
100
+ if (parts.length > 0) {
101
+ console.log(`[Rezo Debug] Timing: ${parts.join(" | ")}`);
102
+ }
103
+ }
104
+ },
105
+ complete: (config, finalUrl, redirectCount, duration) => {
106
+ if (config.debug) {
107
+ console.log(`[Rezo Debug] Complete: ${finalUrl}`);
108
+ if (redirectCount > 0) {
109
+ console.log(`[Rezo Debug] Redirects: ${redirectCount}`);
110
+ }
111
+ console.log(`[Rezo Debug] Total Duration: ${duration.toFixed(2)}ms`);
112
+ console.log(`[Rezo Debug] ─────────────────────────────────────
113
+ `);
114
+ }
115
+ }
116
+ };
23
117
  const responseCacheInstances = new Map;
24
118
  function getCacheConfigKey(option) {
25
119
  if (option === true)
@@ -246,6 +340,8 @@ async function executeHttp1Request(fetchOptions, config, options, perform, fs, s
246
340
  config.setSignal();
247
341
  const timeoutClearInstanse = config.timeoutClearInstanse;
248
342
  delete config.timeoutClearInstanse;
343
+ const requestUrl = fetchOptions.fullUrl ? String(fetchOptions.fullUrl) : "";
344
+ debugLog.requestStart(config, requestUrl, fetchOptions.method || "GET");
249
345
  const eventEmitter = streamResult || downloadResult || uploadResult;
250
346
  if (eventEmitter) {
251
347
  eventEmitter.emit("initiated");
@@ -284,17 +380,14 @@ async function executeHttp1Request(fetchOptions, config, options, perform, fs, s
284
380
  throw response;
285
381
  }
286
382
  if (maxRetries <= retries) {
287
- if (config.debug) {
288
- console.log(`Max retries (${maxRetries}) reached, throwing the last error`);
289
- }
383
+ debugLog.maxRetries(config, maxRetries);
290
384
  throw response;
291
385
  }
292
386
  retries++;
293
- if (config.debug) {
294
- console.log(`Request failed with status code ${responseStatusCode}, retrying...${retryDelay > 0 ? " in " + (incrementDelay ? retryDelay * retries : retryDelay) + "ms" : ""}`);
295
- }
387
+ const currentDelay = incrementDelay ? retryDelay * retries : retryDelay;
388
+ debugLog.retry(config, retries, maxRetries, responseStatusCode, currentDelay);
296
389
  if (retryDelay > 0) {
297
- await new Promise((resolve) => setTimeout(resolve, incrementDelay ? retryDelay * retries : retryDelay));
390
+ await new Promise((resolve) => setTimeout(resolve, currentDelay));
298
391
  }
299
392
  }
300
393
  config.retryAttempts++;
@@ -302,6 +395,16 @@ async function executeHttp1Request(fetchOptions, config, options, perform, fs, s
302
395
  continue;
303
396
  }
304
397
  if (statusOnNext === "success") {
398
+ const totalDuration = performance.now() - timing.startTime;
399
+ debugLog.response(config, response.status, response.statusText, totalDuration);
400
+ if (response.headers) {
401
+ const headersObj = response.headers instanceof RezoHeaders ? response.headers.toObject() : response.headers;
402
+ debugLog.responseHeaders(config, headersObj);
403
+ }
404
+ if (response.cookies?.array) {
405
+ debugLog.cookies(config, response.cookies.array.length);
406
+ }
407
+ debugLog.complete(config, response.finalUrl || requestUrl, config.redirectCount, totalDuration);
305
408
  return response;
306
409
  }
307
410
  if (statusOnNext === "redirect") {
@@ -363,8 +466,8 @@ async function executeHttp1Request(fetchOptions, config, options, perform, fs, s
363
466
  addedOptions.customHeaders = customHeaders;
364
467
  addedOptions.fullUrl = fetchOptions.fullUrl;
365
468
  delete options.params;
469
+ const fromUrl = fetchOptions.fullUrl;
366
470
  fetchOptions.fullUrl = location;
367
- let commented = false;
368
471
  if (typeof onRedirect === "object" && onRedirect.redirect) {
369
472
  const method = onRedirect.method || fetchOptions.method;
370
473
  config.method = method;
@@ -375,26 +478,16 @@ async function executeHttp1Request(fetchOptions, config, options, perform, fs, s
375
478
  } else if (onRedirect.body) {
376
479
  options.body = onRedirect.body;
377
480
  }
378
- if (config.debug) {
379
- commented = true;
380
- console.log(`
381
- Redirecting to: ${fetchOptions.fullUrl} using ${method} method`);
382
- }
481
+ debugLog.redirect(config, fromUrl, fetchOptions.fullUrl, redirectCode, method);
383
482
  if (onRedirect.setHeaders) {
384
483
  addedOptions.customHeaders = onRedirect.setHeaders;
385
484
  }
386
485
  } else if (response.status === 301 || response.status === 302 || response.status === 303) {
387
- if (config.debug) {
388
- commented = true;
389
- console.log(`
390
- Redirecting to: ${fetchOptions.fullUrl} using GET method`);
391
- }
486
+ debugLog.redirect(config, fromUrl, fetchOptions.fullUrl, redirectCode, "GET");
392
487
  options.method = "GET";
393
488
  delete options.body;
394
- } else
395
- commented = false;
396
- if (config.debug && !commented) {
397
- console.log(`Redirecting to: ${fetchOptions.fullUrl}`);
489
+ } else {
490
+ debugLog.redirect(config, fromUrl, fetchOptions.fullUrl, redirectCode, fetchOptions.method);
398
491
  }
399
492
  const __ = prepareHTTPOptions(fetchOptions, config.cookieJar, addedOptions, config);
400
493
  fetchOptions = __.fetchOptions;
@@ -414,11 +507,10 @@ Redirecting to: ${fetchOptions.fullUrl} using GET method`);
414
507
  duration: perform.now()
415
508
  });
416
509
  perform.reset();
417
- if (config.debug) {
418
- console.log(`Request failed with status code ${response.status}, retrying...${retryDelay > 0 ? " in " + (incrementDelay ? retryDelay * retries : retryDelay) + "ms" : ""}`);
419
- }
510
+ const currentDelay = incrementDelay ? retryDelay * retries : retryDelay;
511
+ debugLog.retry(config, retries, maxRetries, response.status, currentDelay);
420
512
  if (retryDelay > 0) {
421
- await new Promise((resolve) => setTimeout(resolve, incrementDelay ? retryDelay * retries : retryDelay));
513
+ await new Promise((resolve) => setTimeout(resolve, currentDelay));
422
514
  }
423
515
  continue;
424
516
  }
@@ -464,9 +556,9 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
464
556
  const requestOptions = buildHTTPOptions(fetchOptions, isSecure, url);
465
557
  try {
466
558
  const req = httpModule.request(requestOptions, async (res) => {
467
- if (!config.timing.ttfbMs) {
559
+ if (!timing.firstByteTime) {
468
560
  timing.firstByteTime = performance.now();
469
- config.timing.ttfbMs = timing.firstByteTime - timing.startTime;
561
+ config.timing.responseStart = timing.firstByteTime;
470
562
  }
471
563
  const { statusCode, statusMessage, headers, httpVersion, socket } = res;
472
564
  const { remoteAddress, remotePort, localAddress, localPort } = socket;
@@ -496,8 +588,8 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
496
588
  contentLength: contentLength ? parseInt(contentLength, 10) : undefined,
497
589
  cookies: cookieArray,
498
590
  timing: {
499
- firstByte: config.timing.ttfbMs,
500
- total: performance.now() - timing.startTime
591
+ firstByte: config.timing.responseStart - config.timing.startTime,
592
+ total: performance.now() - config.timing.startTime
501
593
  }
502
594
  };
503
595
  eventEmitter.emit("headers", headersEvent);
@@ -582,14 +674,7 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
582
674
  finalUrl: url.toString(),
583
675
  cookies: config.cookieJar?.cookies() || { array: [], map: {} },
584
676
  urls: [url.toString()],
585
- timing: {
586
- total: config.timing.durationMs || 0,
587
- dns: config.timing.dnsMs,
588
- tcp: config.timing.tcpMs,
589
- tls: config.timing.tlsMs,
590
- firstByte: config.timing.ttfbMs,
591
- download: config.timing.transferMs
592
- },
677
+ timing: getTimingDurations(config),
593
678
  config: sanitizeConfig(config)
594
679
  };
595
680
  streamResult.emit("done", streamFinishEvent);
@@ -646,14 +731,10 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
646
731
  fileName: filename,
647
732
  fileSize: contentLengthCounter,
648
733
  timing: {
649
- total: config.timing.durationMs || 0,
650
- dns: config.timing.dnsMs,
651
- tcp: config.timing.tcpMs,
652
- tls: config.timing.tlsMs,
653
- firstByte: config.timing.ttfbMs,
654
- download: config.timing.transferMs || 0
734
+ ...getTimingDurations(config),
735
+ download: getTimingDurations(config).download || 0
655
736
  },
656
- averageSpeed: config.timing.transferMs ? contentLengthCounter / config.timing.transferMs * 1000 : 0,
737
+ averageSpeed: getTimingDurations(config).download ? contentLengthCounter / getTimingDurations(config).download * 1000 : 0,
657
738
  config: sanitizeConfig(config)
658
739
  };
659
740
  downloadResult.emit("finish", finishEvent);
@@ -746,16 +827,16 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
746
827
  uploadSize: config.transfer.requestSize || 0,
747
828
  fileName: uploadResult.fileName,
748
829
  timing: {
749
- total: config.timing.durationMs || 0,
750
- dns: config.timing.dnsMs,
751
- tcp: config.timing.tcpMs,
752
- tls: config.timing.tlsMs,
753
- upload: config.timing.ttfbMs || 0,
754
- waiting: config.timing.ttfbMs && config.timing.transferMs ? config.timing.transferMs - config.timing.ttfbMs : 0,
755
- download: config.timing.transferMs
830
+ total: getTimingDurations(config).total,
831
+ dns: getTimingDurations(config).dns,
832
+ tcp: getTimingDurations(config).tcp,
833
+ tls: getTimingDurations(config).tls,
834
+ upload: getTimingDurations(config).firstByte || 0,
835
+ waiting: getTimingDurations(config).download > 0 && getTimingDurations(config).firstByte > 0 ? getTimingDurations(config).download - getTimingDurations(config).firstByte : 0,
836
+ download: getTimingDurations(config).download
756
837
  },
757
- averageUploadSpeed: config.timing.ttfbMs && config.transfer.requestSize ? config.transfer.requestSize / config.timing.ttfbMs * 1000 : 0,
758
- averageDownloadSpeed: config.timing.transferMs ? contentLengthCounter / config.timing.transferMs * 1000 : 0,
838
+ averageUploadSpeed: getTimingDurations(config).firstByte && config.transfer.requestSize ? config.transfer.requestSize / getTimingDurations(config).firstByte * 1000 : 0,
839
+ averageDownloadSpeed: getTimingDurations(config).download ? contentLengthCounter / getTimingDurations(config).download * 1000 : 0,
759
840
  config: sanitizeConfig(config)
760
841
  };
761
842
  uploadResult.emit("finish", uploadFinishEvent);
@@ -794,17 +875,19 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
794
875
  socket.unref();
795
876
  }
796
877
  timing.dnsStart = performance.now();
878
+ config.timing.domainLookupStart = timing.dnsStart;
797
879
  socket.on("lookup", () => {
798
- if (!config.timing.dnsMs) {
799
- if (timing.dnsStart)
800
- config.timing.dnsMs = performance.now() - timing.dnsStart;
880
+ if (!timing.dnsEnd) {
881
+ timing.dnsEnd = performance.now();
882
+ config.timing.domainLookupEnd = timing.dnsEnd;
801
883
  timing.tcpStart = performance.now();
884
+ config.timing.connectStart = timing.tcpStart;
802
885
  }
803
886
  });
804
887
  socket.on("secureConnect", () => {
805
- if (!config.timing.tlsMs) {
806
- if (timing.tlsStart && !config.timing.tlsMs)
807
- config.timing.tlsMs = performance.now() - timing.tlsStart;
888
+ if (!timing.tlsEnd && timing.tlsStart) {
889
+ timing.tlsEnd = performance.now();
890
+ config.timing.connectEnd = timing.tlsEnd;
808
891
  }
809
892
  const tls = {
810
893
  cipher: socket.getCipher(),
@@ -828,11 +911,13 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
828
911
  };
829
912
  });
830
913
  socket.on("connect", () => {
831
- if (!config.timing.tcpMs) {
832
- if (timing.tcpStart)
833
- config.timing.tcpMs = performance.now() - timing.tcpStart;
834
- if (isSecure)
914
+ if (!timing.tcpEnd) {
915
+ timing.tcpEnd = performance.now();
916
+ config.timing.connectEnd = timing.tcpEnd;
917
+ if (isSecure) {
835
918
  timing.tlsStart = performance.now();
919
+ config.timing.secureConnectionStart = timing.tlsStart;
920
+ }
836
921
  }
837
922
  const { remoteAddress, remotePort, localAddress, localPort, remoteFamily } = socket;
838
923
  if (remoteAddress && !config.network.remoteAddress) {
@@ -903,10 +988,15 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
903
988
  });
904
989
  }
905
990
  function updateTiming(config, timing, contentLength, contentLengthCounter, rawHeaders) {
906
- config.timing.endTimestamp = Date.now();
907
- const elapsedMs = performance.now() - timing.startTime;
908
- config.timing.durationMs = elapsedMs;
909
- config.timing.transferMs = timing.firstByteTime ? performance.now() - timing.firstByteTime : elapsedMs;
991
+ const now = performance.now();
992
+ config.timing.domainLookupStart = timing.dnsStart || config.timing.startTime;
993
+ config.timing.domainLookupEnd = timing.dnsEnd || timing.dnsStart || config.timing.startTime;
994
+ config.timing.connectStart = timing.tcpStart || timing.dnsEnd || config.timing.startTime;
995
+ config.timing.secureConnectionStart = timing.tlsStart || 0;
996
+ config.timing.connectEnd = timing.tlsEnd || timing.tcpEnd || timing.tcpStart || config.timing.startTime;
997
+ config.timing.requestStart = timing.tlsEnd || timing.tcpEnd || config.timing.startTime;
998
+ config.timing.responseStart = timing.firstByteTime || config.timing.requestStart;
999
+ config.timing.responseEnd = now;
910
1000
  const bodySize = parseInt(contentLength || "0", 10) || contentLengthCounter;
911
1001
  config.transfer.bodySize = bodySize;
912
1002
  let headerSize = 0;
@@ -939,6 +1029,17 @@ function updateTiming(config, timing, contentLength, contentLengthCounter, rawHe
939
1029
  };
940
1030
  }
941
1031
  }
1032
+ function getTimingDurations(config) {
1033
+ const t = config.timing;
1034
+ return {
1035
+ total: t.responseEnd - t.startTime,
1036
+ dns: t.domainLookupEnd - t.domainLookupStart,
1037
+ tcp: t.secureConnectionStart > 0 ? t.secureConnectionStart - t.connectStart : t.connectEnd - t.connectStart,
1038
+ tls: t.secureConnectionStart > 0 ? t.connectEnd - t.secureConnectionStart : undefined,
1039
+ firstByte: t.responseStart - t.startTime,
1040
+ download: t.responseEnd - t.responseStart
1041
+ };
1042
+ }
942
1043
  let dnsCache = null;
943
1044
  function createDNSLookup(cache) {
944
1045
  return (hostname, optionsOrCallback, callbackOrUndefined) => {
@@ -1076,9 +1177,19 @@ async function setInitialConfig(config, fetchOptions, isSecure, url, httpModule,
1076
1177
  browser: false,
1077
1178
  ssl: isSecure
1078
1179
  };
1079
- const timestampToUse = actualTimestamp || Date.now();
1080
- config.timing = config.timing || { startTimestamp: timestampToUse, endTimestamp: 0, durationMs: 0 };
1081
- config.timing.startTimestamp = config.timing.startTimestamp || timestampToUse;
1180
+ const startTime = performance.now();
1181
+ config.timing = config.timing || {
1182
+ startTime,
1183
+ domainLookupStart: startTime,
1184
+ domainLookupEnd: startTime,
1185
+ connectStart: startTime,
1186
+ secureConnectionStart: 0,
1187
+ connectEnd: startTime,
1188
+ requestStart: startTime,
1189
+ responseStart: startTime,
1190
+ responseEnd: startTime
1191
+ };
1192
+ config.timing.startTime = config.timing.startTime || startTime;
1082
1193
  config.maxRedirectsReached = false;
1083
1194
  config.responseCookies = {
1084
1195
  array: [],
@@ -1092,11 +1203,11 @@ async function setInitialConfig(config, fetchOptions, isSecure, url, httpModule,
1092
1203
  };
1093
1204
  config.retryAttempts = 0;
1094
1205
  config.errors = [];
1095
- config.debug = fetchOptions.debug || false;
1206
+ config.debug = config.debug || fetchOptions.debug || false;
1096
1207
  config.requestId = generateRequestId();
1097
1208
  config.sessionId = fetchOptions.sessionId || generateSessionId();
1098
1209
  config.traceId = generateTraceId();
1099
- config.timestamp = config.timing.startTimestamp;
1210
+ config.timestamp = Date.now();
1100
1211
  config.trackingData = {};
1101
1212
  config.transfer = {
1102
1213
  requestSize: 0,
@@ -200,6 +200,30 @@ class Http2SessionPool {
200
200
  this.closeAllSessions();
201
201
  }
202
202
  }
203
+ function updateTiming(config, timing, contentLengthCounter) {
204
+ const now = performance.now();
205
+ config.timing.domainLookupStart = timing.dnsStart || config.timing.startTime;
206
+ config.timing.domainLookupEnd = timing.dnsEnd || timing.dnsStart || config.timing.startTime;
207
+ config.timing.connectStart = timing.tcpStart || timing.dnsEnd || config.timing.startTime;
208
+ config.timing.secureConnectionStart = timing.tlsStart || 0;
209
+ config.timing.connectEnd = timing.tcpEnd || timing.tlsEnd || timing.tcpStart || config.timing.startTime;
210
+ config.timing.requestStart = timing.tcpEnd || config.timing.startTime;
211
+ config.timing.responseStart = timing.firstByteTime || config.timing.requestStart;
212
+ config.timing.responseEnd = now;
213
+ config.transfer.bodySize = contentLengthCounter;
214
+ config.transfer.responseSize = contentLengthCounter;
215
+ }
216
+ function getTimingDurations(config) {
217
+ const t = config.timing;
218
+ return {
219
+ total: t.responseEnd - t.startTime,
220
+ dns: t.domainLookupEnd - t.domainLookupStart,
221
+ tcp: t.secureConnectionStart > 0 ? t.secureConnectionStart - t.connectStart : t.connectEnd - t.connectStart,
222
+ tls: t.secureConnectionStart > 0 ? t.connectEnd - t.secureConnectionStart : undefined,
223
+ firstByte: t.responseStart - t.startTime,
224
+ download: t.responseEnd - t.responseStart
225
+ };
226
+ }
203
227
  const responseCacheInstances = new Map;
204
228
  function getCacheConfigKey(option) {
205
229
  if (option === true)
@@ -489,10 +513,11 @@ async function executeHttp2Request(fetchOptions, config, options, perform, fs, s
489
513
  const maxRetries = config?.retry?.maxRetries || 0;
490
514
  const incrementDelay = config?.retry?.incrementDelay || false;
491
515
  const statusCodes = config?.retry?.statusCodes;
516
+ const startTime = performance.now();
492
517
  const timing = {
493
- startTime: performance.now(),
494
- startTimestamp: Date.now()
518
+ startTime
495
519
  };
520
+ config.timing.startTime = startTime;
496
521
  const ABSOLUTE_MAX_ATTEMPTS = 50;
497
522
  const visitedUrls = new Set;
498
523
  let totalAttempts = 0;
@@ -662,7 +687,6 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
662
687
  config.isSecure = isSecure;
663
688
  config.finalUrl = url.href;
664
689
  config.network.protocol = "h2";
665
- config.timing.startTimestamp = timing.startTimestamp;
666
690
  }
667
691
  const headers = {
668
692
  [http2.constants.HTTP2_HEADER_METHOD]: fetchOptions.method.toUpperCase(),
@@ -739,9 +763,9 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
739
763
  responseHeaders = headers;
740
764
  status = Number(headers[http2.constants.HTTP2_HEADER_STATUS]) || 200;
741
765
  statusText = getStatusText(status);
742
- if (!config.timing.ttfbMs) {
766
+ if (!timing.firstByteTime) {
743
767
  timing.firstByteTime = performance.now();
744
- config.timing.ttfbMs = timing.firstByteTime - timing.startTime;
768
+ config.timing.responseStart = timing.firstByteTime;
745
769
  }
746
770
  const location = headers["location"];
747
771
  const isRedirect = status >= 300 && status < 400 && location;
@@ -760,8 +784,8 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
760
784
  contentLength: headers["content-length"] ? parseInt(headers["content-length"], 10) : undefined,
761
785
  cookies: config.responseCookies?.array || [],
762
786
  timing: {
763
- firstByte: config.timing.ttfbMs,
764
- total: performance.now() - timing.startTime
787
+ firstByte: config.timing.responseStart - config.timing.startTime,
788
+ total: performance.now() - config.timing.startTime
765
789
  }
766
790
  };
767
791
  eventEmitter.emit("headers", headersEvent);
@@ -798,9 +822,7 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
798
822
  }
799
823
  });
800
824
  req.on("end", async () => {
801
- config.timing.endTimestamp = Date.now();
802
- config.timing.durationMs = performance.now() - timing.startTime;
803
- config.timing.transferMs = timing.firstByteTime ? performance.now() - timing.firstByteTime : config.timing.durationMs;
825
+ updateTiming(config, timing, contentLengthCounter);
804
826
  if (!config.transfer) {
805
827
  config.transfer = { requestSize: 0, responseSize: 0, headerSize: 0, bodySize: 0 };
806
828
  }
@@ -820,8 +842,6 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
820
842
  config.transfer.requestSize = Buffer.byteLength(JSON.stringify(body), "utf8");
821
843
  }
822
844
  }
823
- config.transfer.bodySize = contentLengthCounter;
824
- config.transfer.responseSize = contentLengthCounter;
825
845
  (sessionPool || Http2SessionPool.getInstance()).releaseSession(url);
826
846
  if (_stats.statusOnNext === "redirect") {
827
847
  const partialResponse = {
@@ -920,14 +940,10 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
920
940
  fileName: config.fileName,
921
941
  fileSize: responseBody.length,
922
942
  timing: {
923
- total: config.timing.durationMs || 0,
924
- dns: config.timing.dnsMs,
925
- tcp: config.timing.tcpMs,
926
- tls: config.timing.tlsMs,
927
- firstByte: config.timing.ttfbMs,
928
- download: config.timing.transferMs || 0
943
+ ...getTimingDurations(config),
944
+ download: getTimingDurations(config).download || 0
929
945
  },
930
- averageSpeed: config.timing.transferMs ? responseBody.length / config.timing.transferMs * 1000 : 0,
946
+ averageSpeed: getTimingDurations(config).download ? responseBody.length / getTimingDurations(config).download * 1000 : 0,
931
947
  config: sanitizeConfig(config)
932
948
  };
933
949
  downloadResult.emit("finish", downloadFinishEvent);
@@ -962,14 +978,7 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
962
978
  finalUrl: url.href,
963
979
  cookies: config.responseCookies || { array: [], serialized: [], netscape: "", string: "", setCookiesString: [] },
964
980
  urls: buildUrlTree(config, url.href),
965
- timing: {
966
- total: config.timing.durationMs || 0,
967
- dns: config.timing.dnsMs,
968
- tcp: config.timing.tcpMs,
969
- tls: config.timing.tlsMs,
970
- firstByte: config.timing.ttfbMs,
971
- download: config.timing.transferMs
972
- },
981
+ timing: getTimingDurations(config),
973
982
  config: sanitizeConfig(config)
974
983
  };
975
984
  streamResult.emit("finish", streamFinishEvent);
@@ -992,16 +1001,12 @@ async function executeHttp2Stream(config, fetchOptions, requestCount, timing, _s
992
1001
  urls: buildUrlTree(config, url.href),
993
1002
  uploadSize: config.transfer.requestSize || 0,
994
1003
  timing: {
995
- total: config.timing.durationMs || 0,
996
- dns: config.timing.dnsMs,
997
- tcp: config.timing.tcpMs,
998
- tls: config.timing.tlsMs,
999
- upload: config.timing.transferMs || 0,
1000
- waiting: config.timing.ttfbMs || 0,
1001
- download: config.timing.transferMs
1004
+ ...getTimingDurations(config),
1005
+ upload: getTimingDurations(config).firstByte || 0,
1006
+ waiting: getTimingDurations(config).download > 0 && getTimingDurations(config).firstByte > 0 ? getTimingDurations(config).download - getTimingDurations(config).firstByte : 0
1002
1007
  },
1003
- averageUploadSpeed: config.timing.transferMs ? (config.transfer.requestSize || 0) / config.timing.transferMs * 1000 : 0,
1004
- averageDownloadSpeed: config.timing.transferMs ? contentLengthCounter / config.timing.transferMs * 1000 : 0,
1008
+ averageUploadSpeed: getTimingDurations(config).firstByte && config.transfer.requestSize ? config.transfer.requestSize / getTimingDurations(config).firstByte * 1000 : 0,
1009
+ averageDownloadSpeed: getTimingDurations(config).download ? contentLengthCounter / getTimingDurations(config).download * 1000 : 0,
1005
1010
  config: sanitizeConfig(config)
1006
1011
  };
1007
1012
  uploadResult.emit("finish", uploadFinishEvent);