rezo 1.0.35 → 1.0.37

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 (43) hide show
  1. package/dist/adapters/curl.cjs +320 -9
  2. package/dist/adapters/curl.js +320 -9
  3. package/dist/adapters/entries/curl.d.ts +20 -2
  4. package/dist/adapters/entries/fetch.d.ts +20 -2
  5. package/dist/adapters/entries/http.d.ts +20 -2
  6. package/dist/adapters/entries/http2.d.ts +20 -2
  7. package/dist/adapters/entries/react-native.d.ts +20 -2
  8. package/dist/adapters/entries/xhr.d.ts +20 -2
  9. package/dist/adapters/fetch.cjs +10 -2
  10. package/dist/adapters/fetch.js +10 -2
  11. package/dist/adapters/http.cjs +204 -35
  12. package/dist/adapters/http.js +204 -35
  13. package/dist/adapters/http2.cjs +10 -2
  14. package/dist/adapters/http2.js +10 -2
  15. package/dist/adapters/index.cjs +6 -6
  16. package/dist/adapters/react-native.cjs +10 -2
  17. package/dist/adapters/react-native.js +10 -2
  18. package/dist/adapters/xhr.cjs +9 -1
  19. package/dist/adapters/xhr.js +9 -1
  20. package/dist/cache/index.cjs +13 -13
  21. package/dist/crawler.d.ts +20 -2
  22. package/dist/entries/crawler.cjs +5 -5
  23. package/dist/index.cjs +24 -24
  24. package/dist/index.d.ts +20 -2
  25. package/dist/platform/browser.d.ts +20 -2
  26. package/dist/platform/bun.d.ts +20 -2
  27. package/dist/platform/deno.d.ts +20 -2
  28. package/dist/platform/node.d.ts +20 -2
  29. package/dist/platform/react-native.d.ts +20 -2
  30. package/dist/platform/worker.d.ts +20 -2
  31. package/dist/plugin/index.cjs +36 -36
  32. package/dist/proxy/index.cjs +4 -4
  33. package/dist/queue/index.cjs +8 -8
  34. package/dist/responses/universal/index.cjs +11 -11
  35. package/dist/utils/agent-pool.cjs +204 -0
  36. package/dist/utils/agent-pool.js +201 -0
  37. package/dist/utils/http-config.cjs +24 -7
  38. package/dist/utils/http-config.js +24 -7
  39. package/dist/utils/index.cjs +2 -0
  40. package/dist/utils/index.js +2 -0
  41. package/dist/utils/staged-timeout.cjs +143 -0
  42. package/dist/utils/staged-timeout.js +139 -0
  43. package/package.json +1 -1
@@ -16,6 +16,251 @@ const { StreamResponse } = require('../responses/stream.cjs');
16
16
  const { DownloadResponse } = require('../responses/download.cjs');
17
17
  const { UploadResponse } = require('../responses/upload.cjs');
18
18
  const { RezoPerformance } = require('../utils/tools.cjs');
19
+ const { ResponseCache } = require('../cache/response-cache.cjs');
20
+ const debugLog = {
21
+ requestStart: (config, url, method) => {
22
+ if (config.debug) {
23
+ console.log(`
24
+ [Rezo Debug] ─────────────────────────────────────`);
25
+ console.log(`[Rezo Debug] ${method} ${url}`);
26
+ console.log(`[Rezo Debug] Request ID: ${config.requestId}`);
27
+ if (config.originalRequest?.headers) {
28
+ const headers = config.originalRequest.headers instanceof RezoHeaders ? config.originalRequest.headers.toObject() : config.originalRequest.headers;
29
+ console.log(`[Rezo Debug] Request Headers:`, JSON.stringify(headers, null, 2));
30
+ }
31
+ if (config.proxy && typeof config.proxy === "object") {
32
+ console.log(`[Rezo Debug] Proxy: ${config.proxy.protocol}://${config.proxy.host}:${config.proxy.port}`);
33
+ } else if (config.proxy && typeof config.proxy === "string") {
34
+ console.log(`[Rezo Debug] Proxy: ${config.proxy}`);
35
+ }
36
+ }
37
+ if (config.trackUrl) {
38
+ console.log(`[Rezo Track] → ${method} ${url}`);
39
+ }
40
+ },
41
+ redirect: (config, fromUrl, toUrl, statusCode, method) => {
42
+ if (config.debug) {
43
+ console.log(`[Rezo Debug] Redirect ${statusCode}: ${fromUrl}`);
44
+ console.log(`[Rezo Debug] → ${toUrl} (${method})`);
45
+ }
46
+ if (config.trackUrl) {
47
+ console.log(`[Rezo Track] ↳ ${statusCode} → ${toUrl}`);
48
+ }
49
+ },
50
+ retry: (config, attempt, maxRetries, statusCode, delay) => {
51
+ if (config.debug) {
52
+ console.log(`[Rezo Debug] Retry ${attempt}/${maxRetries} after status ${statusCode}${delay > 0 ? ` (waiting ${delay}ms)` : ""}`);
53
+ }
54
+ if (config.trackUrl) {
55
+ console.log(`[Rezo Track] ⟳ Retry ${attempt}/${maxRetries} (status ${statusCode})`);
56
+ }
57
+ },
58
+ maxRetries: (config, maxRetries) => {
59
+ if (config.debug) {
60
+ console.log(`[Rezo Debug] Max retries (${maxRetries}) reached, throwing error`);
61
+ }
62
+ if (config.trackUrl) {
63
+ console.log(`[Rezo Track] ✗ Max retries reached`);
64
+ }
65
+ },
66
+ response: (config, status, statusText, duration) => {
67
+ if (config.debug) {
68
+ console.log(`[Rezo Debug] Response: ${status} ${statusText} (${duration.toFixed(2)}ms)`);
69
+ }
70
+ if (config.trackUrl) {
71
+ console.log(`[Rezo Track] ✓ ${status} ${statusText}`);
72
+ }
73
+ },
74
+ responseHeaders: (config, headers) => {
75
+ if (config.debug) {
76
+ console.log(`[Rezo Debug] Response Headers:`, JSON.stringify(headers, null, 2));
77
+ }
78
+ },
79
+ cookies: (config, cookieCount) => {
80
+ if (config.debug && cookieCount > 0) {
81
+ console.log(`[Rezo Debug] Cookies received: ${cookieCount}`);
82
+ }
83
+ },
84
+ timing: (config, timing) => {
85
+ if (config.debug) {
86
+ const parts = [];
87
+ if (timing.dns)
88
+ parts.push(`DNS: ${timing.dns.toFixed(2)}ms`);
89
+ if (timing.connect)
90
+ parts.push(`Connect: ${timing.connect.toFixed(2)}ms`);
91
+ if (timing.tls)
92
+ parts.push(`TLS: ${timing.tls.toFixed(2)}ms`);
93
+ if (timing.ttfb)
94
+ parts.push(`TTFB: ${timing.ttfb.toFixed(2)}ms`);
95
+ if (timing.total)
96
+ parts.push(`Total: ${timing.total.toFixed(2)}ms`);
97
+ if (parts.length > 0) {
98
+ console.log(`[Rezo Debug] Timing: ${parts.join(" | ")}`);
99
+ }
100
+ }
101
+ },
102
+ complete: (config, finalUrl, redirectCount, duration) => {
103
+ if (config.debug) {
104
+ console.log(`[Rezo Debug] Complete: ${finalUrl}`);
105
+ if (redirectCount > 0) {
106
+ console.log(`[Rezo Debug] Redirects: ${redirectCount}`);
107
+ }
108
+ console.log(`[Rezo Debug] Total Duration: ${duration.toFixed(2)}ms`);
109
+ console.log(`[Rezo Debug] ─────────────────────────────────────
110
+ `);
111
+ }
112
+ }
113
+ };
114
+ const responseCacheInstances = new Map;
115
+ function getCacheConfigKey(option) {
116
+ if (option === true)
117
+ return "default";
118
+ if (option === false)
119
+ return "disabled";
120
+ const cfg = option;
121
+ return JSON.stringify({
122
+ cacheDir: cfg.cacheDir || null,
123
+ ttl: cfg.ttl || 300000,
124
+ maxEntries: cfg.maxEntries || 500,
125
+ methods: cfg.methods || ["GET", "HEAD"],
126
+ respectHeaders: cfg.respectHeaders !== false
127
+ });
128
+ }
129
+ function getResponseCache(option) {
130
+ const key = getCacheConfigKey(option);
131
+ let cache = responseCacheInstances.get(key);
132
+ if (!cache) {
133
+ cache = new ResponseCache(option);
134
+ const existing = responseCacheInstances.get(key);
135
+ if (existing) {
136
+ return existing;
137
+ }
138
+ responseCacheInstances.set(key, cache);
139
+ }
140
+ return cache;
141
+ }
142
+ function parseCacheControlFromHeaders(headers) {
143
+ const cacheControl = headers["cache-control"] || "";
144
+ return {
145
+ noCache: cacheControl.includes("no-cache"),
146
+ mustRevalidate: cacheControl.includes("must-revalidate")
147
+ };
148
+ }
149
+ function buildCachedRezoResponse(cached, config) {
150
+ const headers = new RezoHeaders(cached.headers);
151
+ return {
152
+ data: cached.data,
153
+ status: cached.status,
154
+ statusText: cached.statusText,
155
+ headers,
156
+ finalUrl: cached.url,
157
+ urls: [cached.url],
158
+ contentType: cached.headers["content-type"],
159
+ contentLength: parseInt(cached.headers["content-length"] || "0", 10) || 0,
160
+ cookies: {
161
+ array: [],
162
+ serialized: [],
163
+ netscape: "",
164
+ string: "",
165
+ setCookiesString: []
166
+ },
167
+ config: {
168
+ ...config,
169
+ url: cached.url,
170
+ method: "GET",
171
+ headers,
172
+ adapterUsed: "curl",
173
+ fromCache: true
174
+ }
175
+ };
176
+ }
177
+ function buildUrlTree(config, finalUrl) {
178
+ const urls = [];
179
+ if (config.rawUrl) {
180
+ urls.push(config.rawUrl);
181
+ } else if (config.url) {
182
+ const urlStr = typeof config.url === "string" ? config.url : config.url.toString();
183
+ urls.push(urlStr);
184
+ }
185
+ if (config.redirectHistory && config.redirectHistory.length > 0) {
186
+ for (const redirect of config.redirectHistory) {
187
+ const redirectUrl = typeof redirect.url === "string" ? redirect.url : redirect.url?.toString?.() || "";
188
+ if (redirectUrl && urls[urls.length - 1] !== redirectUrl) {
189
+ urls.push(redirectUrl);
190
+ }
191
+ }
192
+ }
193
+ if (finalUrl && (urls.length === 0 || urls[urls.length - 1] !== finalUrl)) {
194
+ urls.push(finalUrl);
195
+ }
196
+ return urls.length > 0 ? urls : [finalUrl];
197
+ }
198
+ async function updateCookies(config, cookieStrings, url) {
199
+ if (!cookieStrings || cookieStrings.length === 0)
200
+ return;
201
+ const tempJar = new RezoCookieJar;
202
+ tempJar.setCookiesSync(cookieStrings, url);
203
+ const parsedCookies = tempJar.cookies();
204
+ const acceptedCookies = [];
205
+ let hookError = null;
206
+ if (config.hooks?.beforeCookie && config.hooks.beforeCookie.length > 0) {
207
+ for (const cookie of parsedCookies.array) {
208
+ let shouldAccept = true;
209
+ for (const hook of config.hooks.beforeCookie) {
210
+ try {
211
+ const result = await hook({
212
+ cookie,
213
+ source: "response",
214
+ url,
215
+ isValid: true
216
+ }, config);
217
+ if (result === false) {
218
+ shouldAccept = false;
219
+ break;
220
+ }
221
+ } catch (err) {
222
+ hookError = err;
223
+ if (config.debug) {
224
+ console.log("[Rezo Debug] beforeCookie hook error:", err);
225
+ }
226
+ }
227
+ }
228
+ if (shouldAccept) {
229
+ acceptedCookies.push(cookie);
230
+ }
231
+ }
232
+ } else {
233
+ acceptedCookies.push(...parsedCookies.array);
234
+ }
235
+ const acceptedCookieStrings = acceptedCookies.map((c) => c.toSetCookieString());
236
+ if (!config.disableCookieJar && config.cookieJar) {
237
+ config.cookieJar.setCookiesSync(acceptedCookieStrings, url);
238
+ }
239
+ if (config.useCookies) {
240
+ const existingArray = config.responseCookies?.array || [];
241
+ for (const cookie of acceptedCookies) {
242
+ const existingIndex = existingArray.findIndex((c) => c.key === cookie.key && c.domain === cookie.domain);
243
+ if (existingIndex >= 0) {
244
+ existingArray[existingIndex] = cookie;
245
+ } else {
246
+ existingArray.push(cookie);
247
+ }
248
+ }
249
+ const mergedJar = new RezoCookieJar(existingArray, url);
250
+ config.responseCookies = mergedJar.cookies();
251
+ }
252
+ if (!hookError && config.hooks?.afterCookie && config.hooks.afterCookie.length > 0) {
253
+ for (const hook of config.hooks.afterCookie) {
254
+ try {
255
+ await hook(acceptedCookies, config);
256
+ } catch (err) {
257
+ if (config.debug) {
258
+ console.log("[Rezo Debug] afterCookie hook error:", err);
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
19
264
  function mergeRequestAndResponseCookies(requestCookies, responseCookies) {
20
265
  if (!requestCookies || requestCookies.length === 0) {
21
266
  return responseCookies;
@@ -1282,7 +1527,7 @@ class CurlCommandBuilder {
1282
1527
  }
1283
1528
  }
1284
1529
  buildCookieConfig(config) {
1285
- if (!config.enableCookieJar) {
1530
+ if (config.disableCookieJar) {
1286
1531
  return;
1287
1532
  }
1288
1533
  const cookieJarFile = this.tempFiles.createTempFile("cookies", ".txt");
@@ -1483,10 +1728,18 @@ class CurlResponseParser {
1483
1728
  config.transfer.bodySize = responseBody.length;
1484
1729
  config.transfer.headerSize = headerSection.length;
1485
1730
  config.responseCookies = cookies;
1486
- const urls = [config.url];
1487
- if (stats["redirect_url"]) {
1488
- urls.push(stats["redirect_url"]);
1489
- }
1731
+ const finalUrl = stats["redirect_url"] || config.url || "";
1732
+ const urls = buildUrlTree(config, finalUrl);
1733
+ const timingDurations = getTimingDurations(config);
1734
+ debugLog.responseHeaders(config, headers);
1735
+ debugLog.timing(config, {
1736
+ dns: timingDurations.dns,
1737
+ connect: timingDurations.tcp,
1738
+ tls: timingDurations.tls,
1739
+ ttfb: timingDurations.firstByte,
1740
+ total: timingDurations.total
1741
+ });
1742
+ debugLog.cookies(config, cookies.array?.length || 0);
1490
1743
  return {
1491
1744
  data,
1492
1745
  status,
@@ -1496,7 +1749,7 @@ class CurlResponseParser {
1496
1749
  config,
1497
1750
  contentType: contentType || undefined,
1498
1751
  contentLength: parseInt(stats["size_download"]) || responseBody.length,
1499
- finalUrl: stats["redirect_url"] || config.url,
1752
+ finalUrl,
1500
1753
  urls
1501
1754
  };
1502
1755
  }
@@ -1859,9 +2112,43 @@ async function executeRequest(options, defaultOptions, jar) {
1859
2112
  throw new RezoError("No proxy available: All proxies exhausted or URL did not match whitelist/blacklist", config, "UNQ_NO_PROXY_AVAILABLE", originalRequest);
1860
2113
  }
1861
2114
  }
1862
- const isStream = options._isStream;
1863
- const isDownload = options._isDownload || !!options.fileName || !!options.saveTo;
1864
- const isUpload = options._isUpload;
2115
+ const cacheOption = options.cache;
2116
+ const method = (options.method || "GET").toUpperCase();
2117
+ const requestUrl = typeof originalRequest.url === "string" ? originalRequest.url : originalRequest.url?.toString() || "";
2118
+ let cache;
2119
+ let requestHeaders;
2120
+ let cachedEntry;
2121
+ let needsRevalidation = false;
2122
+ const isStream = options._isStream || options.responseType === "stream";
2123
+ const isDownload = options._isDownload || !!options.fileName || !!options.saveTo || options.responseType === "download";
2124
+ const isUpload = options._isUpload || options.responseType === "upload";
2125
+ if (cacheOption && !isStream && !isDownload && !isUpload) {
2126
+ cache = getResponseCache(cacheOption);
2127
+ requestHeaders = originalRequest.headers instanceof RezoHeaders ? Object.fromEntries(originalRequest.headers.entries()) : originalRequest.headers;
2128
+ cachedEntry = cache.get(method, requestUrl, requestHeaders);
2129
+ if (cachedEntry) {
2130
+ const cacheControl = parseCacheControlFromHeaders(cachedEntry.headers);
2131
+ if (cacheControl.noCache || cacheControl.mustRevalidate) {
2132
+ needsRevalidation = true;
2133
+ } else {
2134
+ return buildCachedRezoResponse(cachedEntry, config);
2135
+ }
2136
+ }
2137
+ const conditionalHeaders = cache.getConditionalHeaders(method, requestUrl, requestHeaders);
2138
+ if (conditionalHeaders) {
2139
+ if (originalRequest.headers instanceof RezoHeaders) {
2140
+ for (const [key, value] of Object.entries(conditionalHeaders)) {
2141
+ originalRequest.headers.set(key, value);
2142
+ }
2143
+ } else {
2144
+ originalRequest.headers = {
2145
+ ...originalRequest.headers,
2146
+ ...conditionalHeaders
2147
+ };
2148
+ }
2149
+ }
2150
+ }
2151
+ debugLog.requestStart(config, requestUrl, method);
1865
2152
  let streamResponse;
1866
2153
  let downloadResponse;
1867
2154
  let uploadResponse;
@@ -1911,6 +2198,23 @@ async function executeRequest(options, defaultOptions, jar) {
1911
2198
  if (proxyManager && selectedProxy) {
1912
2199
  proxyManager.reportSuccess(selectedProxy);
1913
2200
  }
2201
+ const duration = perform.now();
2202
+ debugLog.response(config, response.status, response.statusText, duration);
2203
+ debugLog.cookies(config, response.cookies?.array?.length || 0);
2204
+ if (cache) {
2205
+ if (response.status === 304 && cachedEntry) {
2206
+ const responseHeaders = response.headers instanceof RezoHeaders ? Object.fromEntries(response.headers.entries()) : response.headers;
2207
+ const updatedCached = cache.updateRevalidated(method, requestUrl, responseHeaders, requestHeaders);
2208
+ if (updatedCached) {
2209
+ return buildCachedRezoResponse(updatedCached, config);
2210
+ }
2211
+ return buildCachedRezoResponse(cachedEntry, config);
2212
+ }
2213
+ if (response.status >= 200 && response.status < 300) {
2214
+ cache.set(method, requestUrl, response, requestHeaders);
2215
+ }
2216
+ }
2217
+ debugLog.complete(config, response.finalUrl || requestUrl, config.redirectHistory?.length || 0, duration);
1914
2218
  if (response.status >= 400) {
1915
2219
  const httpError = builErrorFromResponse(`Request failed with status code ${response.status}`, response, config, originalRequest);
1916
2220
  if (config.retry) {
@@ -1920,12 +2224,19 @@ async function executeRequest(options, defaultOptions, jar) {
1920
2224
  const retryDelay = config.retry.retryDelay || 0;
1921
2225
  const incrementDelay = config.retry.incrementDelay || false;
1922
2226
  const delay = incrementDelay ? retryDelay * (config.retryAttempts + 1) : retryDelay;
2227
+ debugLog.retry(config, config.retryAttempts + 1, maxRetries, response.status, delay);
2228
+ if (config.hooks?.beforeRetry && config.hooks.beforeRetry.length > 0) {
2229
+ for (const hook of config.hooks.beforeRetry) {
2230
+ await hook(config, httpError, config.retryAttempts + 1);
2231
+ }
2232
+ }
1923
2233
  if (delay > 0) {
1924
2234
  await new Promise((resolve) => setTimeout(resolve, delay));
1925
2235
  }
1926
2236
  config.retryAttempts++;
1927
2237
  return executeRequest(options, defaultOptions, jar);
1928
2238
  }
2239
+ debugLog.maxRetries(config, maxRetries);
1929
2240
  }
1930
2241
  throw httpError;
1931
2242
  }