rezo 1.0.22 → 1.0.24

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.
@@ -129,7 +129,7 @@ function sanitizeConfig(config) {
129
129
  delete sanitized.data;
130
130
  return sanitized;
131
131
  }
132
- function parseCookiesFromHeaders(headers, url, config) {
132
+ async function parseCookiesFromHeaders(headers, url, config) {
133
133
  let setCookieHeaders = [];
134
134
  if (typeof headers.getSetCookie === "function") {
135
135
  setCookieHeaders = headers.getSetCookie() || [];
@@ -149,13 +149,59 @@ function parseCookiesFromHeaders(headers, url, config) {
149
149
  setCookiesString: []
150
150
  };
151
151
  }
152
+ const tempJar = new RezoCookieJar;
153
+ tempJar.setCookiesSync(setCookieHeaders, url);
154
+ const parsedCookies = tempJar.cookies();
155
+ const acceptedCookies = [];
156
+ let hookError = null;
157
+ if (config?.hooks?.beforeCookie && config.hooks.beforeCookie.length > 0) {
158
+ for (const cookie of parsedCookies.array) {
159
+ let shouldAccept = true;
160
+ for (const hook of config.hooks.beforeCookie) {
161
+ try {
162
+ const result = await hook({
163
+ cookie,
164
+ source: "response",
165
+ url,
166
+ isValid: true
167
+ }, config);
168
+ if (result === false) {
169
+ shouldAccept = false;
170
+ break;
171
+ }
172
+ } catch (err) {
173
+ hookError = err;
174
+ if (config.debug) {
175
+ console.log("[Rezo Debug] beforeCookie hook error:", err);
176
+ }
177
+ }
178
+ }
179
+ if (shouldAccept) {
180
+ acceptedCookies.push(cookie);
181
+ }
182
+ }
183
+ } else {
184
+ acceptedCookies.push(...parsedCookies.array);
185
+ }
186
+ const acceptedCookieStrings = acceptedCookies.map((c) => c.cookieString());
152
187
  const jar = new RezoCookieJar;
153
- jar.setCookiesSync(setCookieHeaders, url);
188
+ jar.setCookiesSync(acceptedCookieStrings, url);
154
189
  if (config?.enableCookieJar && config?.cookieJar) {
155
- config.cookieJar.setCookiesSync(setCookieHeaders, url);
190
+ config.cookieJar.setCookiesSync(acceptedCookieStrings, url);
156
191
  }
157
192
  const cookies = jar.cookies();
158
193
  cookies.setCookiesString = setCookieHeaders;
194
+ if (!hookError && config?.hooks?.afterCookie && config.hooks.afterCookie.length > 0) {
195
+ for (const hook of config.hooks.afterCookie) {
196
+ try {
197
+ await hook(acceptedCookies, config);
198
+ } catch (err) {
199
+ if (config.debug) {
200
+ console.log("[Rezo Debug] afterCookie hook error:", err);
201
+ }
202
+ }
203
+ }
204
+ }
159
205
  return cookies;
160
206
  }
161
207
  function mergeRequestAndResponseCookies(config, responseCookies, url) {
@@ -385,8 +431,14 @@ async function executeFetchRequest(fetchOptions, config, options, perform, strea
385
431
  }
386
432
  retries++;
387
433
  config.retryAttempts++;
434
+ const currentDelay = incrementDelay ? retryDelay * retries : retryDelay;
435
+ if (config.hooks?.beforeRetry && config.hooks.beforeRetry.length > 0) {
436
+ for (const hook of config.hooks.beforeRetry) {
437
+ await hook(config, response, retries);
438
+ }
439
+ }
388
440
  if (retryDelay > 0) {
389
- await new Promise((resolve) => setTimeout(resolve, incrementDelay ? retryDelay * retries : retryDelay));
441
+ await new Promise((resolve) => setTimeout(resolve, currentDelay));
390
442
  }
391
443
  }
392
444
  continue;
@@ -584,13 +636,17 @@ async function executeSingleFetchRequest(config, fetchOptions, requestCount, tim
584
636
  const responseHeaders = fromFetchHeaders(response.headers);
585
637
  const contentType = response.headers.get("content-type") || "";
586
638
  const contentLength = response.headers.get("content-length");
587
- const cookies = parseCookiesFromHeaders(response.headers, url.href, config);
639
+ const cookies = await parseCookiesFromHeaders(response.headers, url.href, config);
588
640
  config.responseCookies = cookies;
589
641
  const location = response.headers.get("location");
590
642
  const isRedirect = status >= 300 && status < 400 && location;
591
643
  if (isRedirect) {
592
644
  _stats.statusOnNext = "redirect";
593
- _stats.redirectUrl = new URL(location, url).href;
645
+ const redirectUrlObj = new URL(location, url);
646
+ if (!redirectUrlObj.hash && url.hash) {
647
+ redirectUrlObj.hash = url.hash;
648
+ }
649
+ _stats.redirectUrl = redirectUrlObj.href;
594
650
  const partialResponse = {
595
651
  data: "",
596
652
  status,
@@ -129,7 +129,7 @@ function sanitizeConfig(config) {
129
129
  delete sanitized.data;
130
130
  return sanitized;
131
131
  }
132
- function parseCookiesFromHeaders(headers, url, config) {
132
+ async function parseCookiesFromHeaders(headers, url, config) {
133
133
  let setCookieHeaders = [];
134
134
  if (typeof headers.getSetCookie === "function") {
135
135
  setCookieHeaders = headers.getSetCookie() || [];
@@ -149,13 +149,59 @@ function parseCookiesFromHeaders(headers, url, config) {
149
149
  setCookiesString: []
150
150
  };
151
151
  }
152
+ const tempJar = new RezoCookieJar;
153
+ tempJar.setCookiesSync(setCookieHeaders, url);
154
+ const parsedCookies = tempJar.cookies();
155
+ const acceptedCookies = [];
156
+ let hookError = null;
157
+ if (config?.hooks?.beforeCookie && config.hooks.beforeCookie.length > 0) {
158
+ for (const cookie of parsedCookies.array) {
159
+ let shouldAccept = true;
160
+ for (const hook of config.hooks.beforeCookie) {
161
+ try {
162
+ const result = await hook({
163
+ cookie,
164
+ source: "response",
165
+ url,
166
+ isValid: true
167
+ }, config);
168
+ if (result === false) {
169
+ shouldAccept = false;
170
+ break;
171
+ }
172
+ } catch (err) {
173
+ hookError = err;
174
+ if (config.debug) {
175
+ console.log("[Rezo Debug] beforeCookie hook error:", err);
176
+ }
177
+ }
178
+ }
179
+ if (shouldAccept) {
180
+ acceptedCookies.push(cookie);
181
+ }
182
+ }
183
+ } else {
184
+ acceptedCookies.push(...parsedCookies.array);
185
+ }
186
+ const acceptedCookieStrings = acceptedCookies.map((c) => c.cookieString());
152
187
  const jar = new RezoCookieJar;
153
- jar.setCookiesSync(setCookieHeaders, url);
188
+ jar.setCookiesSync(acceptedCookieStrings, url);
154
189
  if (config?.enableCookieJar && config?.cookieJar) {
155
- config.cookieJar.setCookiesSync(setCookieHeaders, url);
190
+ config.cookieJar.setCookiesSync(acceptedCookieStrings, url);
156
191
  }
157
192
  const cookies = jar.cookies();
158
193
  cookies.setCookiesString = setCookieHeaders;
194
+ if (!hookError && config?.hooks?.afterCookie && config.hooks.afterCookie.length > 0) {
195
+ for (const hook of config.hooks.afterCookie) {
196
+ try {
197
+ await hook(acceptedCookies, config);
198
+ } catch (err) {
199
+ if (config.debug) {
200
+ console.log("[Rezo Debug] afterCookie hook error:", err);
201
+ }
202
+ }
203
+ }
204
+ }
159
205
  return cookies;
160
206
  }
161
207
  function mergeRequestAndResponseCookies(config, responseCookies, url) {
@@ -385,8 +431,14 @@ async function executeFetchRequest(fetchOptions, config, options, perform, strea
385
431
  }
386
432
  retries++;
387
433
  config.retryAttempts++;
434
+ const currentDelay = incrementDelay ? retryDelay * retries : retryDelay;
435
+ if (config.hooks?.beforeRetry && config.hooks.beforeRetry.length > 0) {
436
+ for (const hook of config.hooks.beforeRetry) {
437
+ await hook(config, response, retries);
438
+ }
439
+ }
388
440
  if (retryDelay > 0) {
389
- await new Promise((resolve) => setTimeout(resolve, incrementDelay ? retryDelay * retries : retryDelay));
441
+ await new Promise((resolve) => setTimeout(resolve, currentDelay));
390
442
  }
391
443
  }
392
444
  continue;
@@ -584,13 +636,17 @@ async function executeSingleFetchRequest(config, fetchOptions, requestCount, tim
584
636
  const responseHeaders = fromFetchHeaders(response.headers);
585
637
  const contentType = response.headers.get("content-type") || "";
586
638
  const contentLength = response.headers.get("content-length");
587
- const cookies = parseCookiesFromHeaders(response.headers, url.href, config);
639
+ const cookies = await parseCookiesFromHeaders(response.headers, url.href, config);
588
640
  config.responseCookies = cookies;
589
641
  const location = response.headers.get("location");
590
642
  const isRedirect = status >= 300 && status < 400 && location;
591
643
  if (isRedirect) {
592
644
  _stats.statusOnNext = "redirect";
593
- _stats.redirectUrl = new URL(location, url).href;
645
+ const redirectUrlObj = new URL(location, url);
646
+ if (!redirectUrlObj.hash && url.hash) {
647
+ redirectUrlObj.hash = url.hash;
648
+ }
649
+ _stats.redirectUrl = redirectUrlObj.href;
594
650
  const partialResponse = {
595
651
  data: "",
596
652
  status,
@@ -290,10 +290,19 @@ async function executeRequest(options, defaultOptions, jar) {
290
290
  try {
291
291
  const res = executeHttp1Request(config.fetchOptions, mainConfig, config.options, perform, d_options.fs, streamResponse, downloadResponse, uploadResponse);
292
292
  if (streamResponse) {
293
+ res.catch((err) => {
294
+ streamResponse.emit("error", err);
295
+ });
293
296
  return streamResponse;
294
297
  } else if (downloadResponse) {
298
+ res.catch((err) => {
299
+ downloadResponse.emit("error", err);
300
+ });
295
301
  return downloadResponse;
296
302
  } else if (uploadResponse) {
303
+ res.catch((err) => {
304
+ uploadResponse.emit("error", err);
305
+ });
297
306
  return uploadResponse;
298
307
  }
299
308
  const response = await res;
@@ -386,6 +395,11 @@ async function executeHttp1Request(fetchOptions, config, options, perform, fs, s
386
395
  retries++;
387
396
  const currentDelay = incrementDelay ? retryDelay * retries : retryDelay;
388
397
  debugLog.retry(config, retries, maxRetries, responseStatusCode, currentDelay);
398
+ if (config.hooks?.beforeRetry && config.hooks.beforeRetry.length > 0) {
399
+ for (const hook of config.hooks.beforeRetry) {
400
+ await hook(config, response, retries);
401
+ }
402
+ }
389
403
  if (retryDelay > 0) {
390
404
  await new Promise((resolve) => setTimeout(resolve, currentDelay));
391
405
  }
@@ -411,17 +425,32 @@ async function executeHttp1Request(fetchOptions, config, options, perform, fs, s
411
425
  const addedOptions = {};
412
426
  const location = _stats.redirectUrl;
413
427
  if (!location || !_stats.redirectUrl) {
414
- throw builErrorFromResponse("Redirect location not found", response, config, fetchOptions);
428
+ const redirectError = builErrorFromResponse("Redirect location not found", response, config, fetchOptions);
429
+ _stats.statusOnNext = "error";
430
+ if (!config.errors)
431
+ config.errors = [];
432
+ config.errors.push({ attempt: config.retryAttempts + 1, error: redirectError, duration: perform.now() });
433
+ throw redirectError;
415
434
  }
416
435
  if (config.maxRedirects === 0) {
417
436
  config.maxRedirectsReached = true;
418
- throw builErrorFromResponse(`Redirects are disabled (maxRedirects=0)`, response, config, fetchOptions);
437
+ const redirectError = builErrorFromResponse(`Redirects are disabled (maxRedirects=0)`, response, config, fetchOptions);
438
+ _stats.statusOnNext = "error";
439
+ if (!config.errors)
440
+ config.errors = [];
441
+ config.errors.push({ attempt: config.retryAttempts + 1, error: redirectError, duration: perform.now() });
442
+ throw redirectError;
419
443
  }
420
444
  const enableCycleDetection = config.enableRedirectCycleDetection === true;
421
445
  if (enableCycleDetection) {
422
446
  const normalizedRedirectUrl = _stats.redirectUrl.toLowerCase();
423
447
  if (visitedUrls.has(normalizedRedirectUrl)) {
424
- throw builErrorFromResponse(`Redirect cycle detected: attempting to revisit ${_stats.redirectUrl}`, response, config, fetchOptions);
448
+ const redirectError = builErrorFromResponse(`Redirect cycle detected: attempting to revisit ${_stats.redirectUrl}`, response, config, fetchOptions);
449
+ _stats.statusOnNext = "error";
450
+ if (!config.errors)
451
+ config.errors = [];
452
+ config.errors.push({ attempt: config.retryAttempts + 1, error: redirectError, duration: perform.now() });
453
+ throw redirectError;
425
454
  }
426
455
  visitedUrls.add(normalizedRedirectUrl);
427
456
  }
@@ -438,15 +467,30 @@ async function executeHttp1Request(fetchOptions, config, options, perform, fs, s
438
467
  if (typeof onRedirect !== "undefined") {
439
468
  if (typeof onRedirect === "boolean") {
440
469
  if (!onRedirect) {
441
- throw builErrorFromResponse("Redirect denied by user", response, config, fetchOptions);
470
+ const redirectError = builErrorFromResponse("Redirect denied by user", response, config, fetchOptions);
471
+ _stats.statusOnNext = "error";
472
+ if (!config.errors)
473
+ config.errors = [];
474
+ config.errors.push({ attempt: config.retryAttempts + 1, error: redirectError, duration: perform.now() });
475
+ throw redirectError;
442
476
  }
443
477
  } else if (!onRedirect.redirect) {
444
- throw builErrorFromResponse("Redirect denied by user", response, config, fetchOptions);
478
+ const redirectError = builErrorFromResponse("Redirect denied by user", response, config, fetchOptions);
479
+ _stats.statusOnNext = "error";
480
+ if (!config.errors)
481
+ config.errors = [];
482
+ config.errors.push({ attempt: config.retryAttempts + 1, error: redirectError, duration: perform.now() });
483
+ throw redirectError;
445
484
  }
446
485
  }
447
486
  if (config.redirectCount >= config.maxRedirects && config.maxRedirects > 0) {
448
487
  config.maxRedirectsReached = true;
449
- throw builErrorFromResponse(`Max redirects (${config.maxRedirects}) reached`, response, config, fetchOptions);
488
+ const redirectError = builErrorFromResponse(`Max redirects (${config.maxRedirects}) reached`, response, config, fetchOptions);
489
+ _stats.statusOnNext = "error";
490
+ if (!config.errors)
491
+ config.errors = [];
492
+ config.errors.push({ attempt: config.retryAttempts + 1, error: redirectError, duration: perform.now() });
493
+ throw redirectError;
450
494
  }
451
495
  config.redirectHistory.push({
452
496
  url: fetchOptions.fullUrl,
@@ -574,7 +618,7 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
574
618
  const location = headers["location"] || headers["Location"];
575
619
  const contentLength = headers["content-length"];
576
620
  const cookies = headers["set-cookie"];
577
- updateCookies(config, headers, url.href);
621
+ await updateCookies(config, headers, url.href);
578
622
  const cookieArray = config.responseCookies?.array || [];
579
623
  delete headers["set-cookie"];
580
624
  _stats.redirectUrl = undefined;
@@ -641,7 +685,11 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
641
685
  if (isRedirected)
642
686
  _stats.statusOnNext = "redirect";
643
687
  if (isRedirected && location) {
644
- _stats.redirectUrl = new URL(location, url).href;
688
+ const redirectUrlObj = new URL(location, url);
689
+ if (!redirectUrlObj.hash && url.hash) {
690
+ redirectUrlObj.hash = url.hash;
691
+ }
692
+ _stats.redirectUrl = redirectUrlObj.href;
645
693
  if (config.redirectCount) {
646
694
  config.redirectCount++;
647
695
  } else {
@@ -871,19 +919,52 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
871
919
  });
872
920
  }
873
921
  });
922
+ req.on("error", (err) => {
923
+ _stats.statusOnNext = "error";
924
+ const error = buildSmartError(config, fetchOptions, err);
925
+ resolve(error);
926
+ });
874
927
  req.on("socket", (socket) => {
875
- if (socket && typeof socket.unref === "function") {
876
- socket.unref();
928
+ if (socket && typeof socket.ref === "function") {
929
+ socket.ref();
877
930
  }
931
+ socket.on("error", (err) => {
932
+ _stats.statusOnNext = "error";
933
+ const error = buildSmartError(config, fetchOptions, err);
934
+ resolve(error);
935
+ });
936
+ socket.on("close", () => {
937
+ if (socket && typeof socket.unref === "function") {
938
+ socket.unref();
939
+ }
940
+ });
878
941
  timing.dnsStart = performance.now();
879
942
  config.timing.domainLookupStart = timing.dnsStart;
880
- socket.on("lookup", () => {
943
+ socket.on("lookup", (err, address, family) => {
881
944
  if (!timing.dnsEnd) {
882
945
  timing.dnsEnd = performance.now();
883
946
  config.timing.domainLookupEnd = timing.dnsEnd;
884
947
  timing.tcpStart = performance.now();
885
948
  config.timing.connectStart = timing.tcpStart;
886
949
  }
950
+ if (config.hooks?.onDns && config.hooks.onDns.length > 0) {
951
+ const familyNum = typeof family === "number" ? family : family === "IPv6" ? 6 : 4;
952
+ for (const hook of config.hooks.onDns) {
953
+ try {
954
+ hook({
955
+ hostname: url.hostname,
956
+ address: address || "",
957
+ family: familyNum,
958
+ duration: timing.dnsEnd - timing.dnsStart,
959
+ timestamp: Date.now()
960
+ }, config);
961
+ } catch (err) {
962
+ if (config.debug) {
963
+ console.log("[Rezo Debug] onDns hook error:", err);
964
+ }
965
+ }
966
+ }
967
+ }
887
968
  });
888
969
  socket.on("secureConnect", () => {
889
970
  if (!timing.tlsEnd && timing.tlsStart) {
@@ -910,6 +991,31 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
910
991
  hostnameMatch: cert.subject?.CN === url.hostname,
911
992
  chainValid: true
912
993
  };
994
+ if (config.hooks?.onTls && config.hooks.onTls.length > 0) {
995
+ for (const hook of config.hooks.onTls) {
996
+ try {
997
+ hook({
998
+ protocol: tlsVersion,
999
+ cipher: cipher?.name || "",
1000
+ authorized: !socket.authorizationError,
1001
+ authorizationError: socket.authorizationError,
1002
+ certificate: cert ? {
1003
+ subject: cert.subject?.CN || "",
1004
+ issuer: cert.issuer?.CN || "",
1005
+ validFrom: cert.valid_from || "",
1006
+ validTo: cert.valid_to || "",
1007
+ fingerprint: cert.fingerprint || ""
1008
+ } : undefined,
1009
+ duration: timing.tlsEnd - timing.tlsStart,
1010
+ timestamp: Date.now()
1011
+ }, config);
1012
+ } catch (err) {
1013
+ if (config.debug) {
1014
+ console.log("[Rezo Debug] onTls hook error:", err);
1015
+ }
1016
+ }
1017
+ }
1018
+ }
913
1019
  });
914
1020
  socket.on("connect", () => {
915
1021
  if (!timing.tcpEnd) {
@@ -928,6 +1034,24 @@ async function request(config, fetchOptions, requestCount, timing, _stats, respo
928
1034
  config.network.localPort = localPort;
929
1035
  config.network.family = remoteFamily;
930
1036
  }
1037
+ if (config.hooks?.onSocket && config.hooks.onSocket.length > 0) {
1038
+ for (const hook of config.hooks.onSocket) {
1039
+ try {
1040
+ hook({
1041
+ type: "connect",
1042
+ localAddress,
1043
+ localPort,
1044
+ remoteAddress,
1045
+ remotePort,
1046
+ timestamp: Date.now()
1047
+ }, socket);
1048
+ } catch (err) {
1049
+ if (config.debug) {
1050
+ console.log("[Rezo Debug] onSocket hook error:", err);
1051
+ }
1052
+ }
1053
+ }
1054
+ }
931
1055
  });
932
1056
  });
933
1057
  req.on("error", (error) => {
@@ -1323,18 +1447,52 @@ function parseProxy(proxy, isScure = true, rejectUnauthorized = false) {
1323
1447
  }
1324
1448
  return rezoProxy(proxy);
1325
1449
  }
1326
- function updateCookies(config, headers, url) {
1450
+ async function updateCookies(config, headers, url) {
1327
1451
  const cookies = headers["set-cookie"];
1328
1452
  if (cookies) {
1329
1453
  const jar = new RezoCookieJar;
1454
+ const tempJar = new RezoCookieJar;
1455
+ tempJar.setCookiesSync(cookies, url);
1456
+ const parsedCookies = tempJar.cookies();
1457
+ const acceptedCookies = [];
1458
+ let hookError = null;
1459
+ if (config.hooks?.beforeCookie && config.hooks.beforeCookie.length > 0) {
1460
+ for (const cookie of parsedCookies.array) {
1461
+ let shouldAccept = true;
1462
+ for (const hook of config.hooks.beforeCookie) {
1463
+ try {
1464
+ const result = await hook({
1465
+ cookie,
1466
+ source: "response",
1467
+ url,
1468
+ isValid: true
1469
+ }, config);
1470
+ if (result === false) {
1471
+ shouldAccept = false;
1472
+ break;
1473
+ }
1474
+ } catch (err) {
1475
+ hookError = err;
1476
+ if (config.debug) {
1477
+ console.log("[Rezo Debug] beforeCookie hook error:", err);
1478
+ }
1479
+ }
1480
+ }
1481
+ if (shouldAccept) {
1482
+ acceptedCookies.push(cookie);
1483
+ }
1484
+ }
1485
+ } else {
1486
+ acceptedCookies.push(...parsedCookies.array);
1487
+ }
1488
+ const acceptedCookieStrings = acceptedCookies.map((c) => c.cookieString());
1330
1489
  if (config.enableCookieJar && config.cookieJar) {
1331
- config.cookieJar.setCookiesSync(cookies, url);
1490
+ config.cookieJar.setCookiesSync(acceptedCookieStrings, url);
1332
1491
  }
1333
- jar.setCookiesSync(cookies, url);
1492
+ jar.setCookiesSync(acceptedCookieStrings, url);
1334
1493
  if (config.useCookies) {
1335
- const parsedCookies = jar.cookies();
1336
1494
  const existingArray = config.responseCookies?.array || [];
1337
- for (const cookie of parsedCookies.array) {
1495
+ for (const cookie of acceptedCookies) {
1338
1496
  const existingIndex = existingArray.findIndex((c) => c.key === cookie.key && c.domain === cookie.domain);
1339
1497
  if (existingIndex >= 0) {
1340
1498
  existingArray[existingIndex] = cookie;
@@ -1345,6 +1503,17 @@ function updateCookies(config, headers, url) {
1345
1503
  const mergedJar = new RezoCookieJar(existingArray, url);
1346
1504
  config.responseCookies = mergedJar.cookies();
1347
1505
  }
1506
+ if (!hookError && config.hooks?.afterCookie && config.hooks.afterCookie.length > 0) {
1507
+ for (const hook of config.hooks.afterCookie) {
1508
+ try {
1509
+ await hook(acceptedCookies, config);
1510
+ } catch (err) {
1511
+ if (config.debug) {
1512
+ console.log("[Rezo Debug] afterCookie hook error:", err);
1513
+ }
1514
+ }
1515
+ }
1516
+ }
1348
1517
  }
1349
1518
  }
1350
1519