rezo 1.0.0

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 (135) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +1507 -0
  3. package/assets/icon.svg +37 -0
  4. package/assets/logo-dark.svg +47 -0
  5. package/assets/logo.svg +58 -0
  6. package/dist/adapters/curl.cjs +1034 -0
  7. package/dist/adapters/curl.js +1031 -0
  8. package/dist/adapters/entries/curl.cjs +4 -0
  9. package/dist/adapters/entries/curl.d.ts +2136 -0
  10. package/dist/adapters/entries/curl.js +2 -0
  11. package/dist/adapters/entries/fetch.cjs +2 -0
  12. package/dist/adapters/entries/fetch.d.ts +2127 -0
  13. package/dist/adapters/entries/fetch.js +1 -0
  14. package/dist/adapters/entries/http.cjs +2 -0
  15. package/dist/adapters/entries/http.d.ts +2126 -0
  16. package/dist/adapters/entries/http.js +1 -0
  17. package/dist/adapters/entries/http2.cjs +4 -0
  18. package/dist/adapters/entries/http2.d.ts +2136 -0
  19. package/dist/adapters/entries/http2.js +2 -0
  20. package/dist/adapters/entries/react-native.cjs +2 -0
  21. package/dist/adapters/entries/react-native.d.ts +2126 -0
  22. package/dist/adapters/entries/react-native.js +1 -0
  23. package/dist/adapters/entries/xhr.cjs +2 -0
  24. package/dist/adapters/entries/xhr.d.ts +2127 -0
  25. package/dist/adapters/entries/xhr.js +1 -0
  26. package/dist/adapters/fetch.cjs +740 -0
  27. package/dist/adapters/fetch.js +739 -0
  28. package/dist/adapters/http.cjs +1153 -0
  29. package/dist/adapters/http.js +1151 -0
  30. package/dist/adapters/http2.cjs +957 -0
  31. package/dist/adapters/http2.js +956 -0
  32. package/dist/adapters/index.cjs +6 -0
  33. package/dist/adapters/index.js +7 -0
  34. package/dist/adapters/picker.cjs +342 -0
  35. package/dist/adapters/picker.js +331 -0
  36. package/dist/adapters/react-native.cjs +545 -0
  37. package/dist/adapters/react-native.js +544 -0
  38. package/dist/adapters/xhr.cjs +622 -0
  39. package/dist/adapters/xhr.js +621 -0
  40. package/dist/cache/dns-cache.cjs +118 -0
  41. package/dist/cache/dns-cache.js +113 -0
  42. package/dist/cache/file-cacher.cjs +264 -0
  43. package/dist/cache/file-cacher.js +261 -0
  44. package/dist/cache/index.cjs +13 -0
  45. package/dist/cache/index.js +5 -0
  46. package/dist/cache/lru-cache.cjs +96 -0
  47. package/dist/cache/lru-cache.js +93 -0
  48. package/dist/cache/response-cache.cjs +314 -0
  49. package/dist/cache/response-cache.js +310 -0
  50. package/dist/cache/url-store.cjs +288 -0
  51. package/dist/cache/url-store.js +285 -0
  52. package/dist/core/hooks.cjs +133 -0
  53. package/dist/core/hooks.js +120 -0
  54. package/dist/core/rezo.cjs +464 -0
  55. package/dist/core/rezo.js +458 -0
  56. package/dist/crawler.d.ts +6255 -0
  57. package/dist/dom/index.cjs +1 -0
  58. package/dist/dom/index.d.ts +23 -0
  59. package/dist/dom/index.js +1 -0
  60. package/dist/entries/crawler.cjs +5 -0
  61. package/dist/entries/crawler.js +2 -0
  62. package/dist/errors/rezo-error.cjs +722 -0
  63. package/dist/errors/rezo-error.js +716 -0
  64. package/dist/index.cjs +34 -0
  65. package/dist/index.d.ts +3335 -0
  66. package/dist/index.js +26 -0
  67. package/dist/platform/browser.cjs +9 -0
  68. package/dist/platform/browser.d.ts +3203 -0
  69. package/dist/platform/browser.js +7 -0
  70. package/dist/platform/bun.cjs +9 -0
  71. package/dist/platform/bun.d.ts +3203 -0
  72. package/dist/platform/bun.js +7 -0
  73. package/dist/platform/deno.cjs +9 -0
  74. package/dist/platform/deno.d.ts +3203 -0
  75. package/dist/platform/deno.js +7 -0
  76. package/dist/platform/node.cjs +9 -0
  77. package/dist/platform/node.d.ts +3203 -0
  78. package/dist/platform/node.js +7 -0
  79. package/dist/platform/react-native.cjs +9 -0
  80. package/dist/platform/react-native.d.ts +3203 -0
  81. package/dist/platform/react-native.js +7 -0
  82. package/dist/platform/worker.cjs +9 -0
  83. package/dist/platform/worker.d.ts +3203 -0
  84. package/dist/platform/worker.js +7 -0
  85. package/dist/plugin/addon/decodo/index.cjs +1 -0
  86. package/dist/plugin/addon/decodo/index.js +1 -0
  87. package/dist/plugin/addon/decodo/options.cjs +1 -0
  88. package/dist/plugin/addon/decodo/options.js +1 -0
  89. package/dist/plugin/addon/oxylabs/index.cjs +1 -0
  90. package/dist/plugin/addon/oxylabs/index.js +1 -0
  91. package/dist/plugin/addon/oxylabs/options.cjs +1 -0
  92. package/dist/plugin/addon/oxylabs/options.js +1 -0
  93. package/dist/plugin/crawler-options.cjs +1 -0
  94. package/dist/plugin/crawler-options.js +1 -0
  95. package/dist/plugin/crawler.cjs +519 -0
  96. package/dist/plugin/crawler.js +517 -0
  97. package/dist/plugin/index.cjs +36 -0
  98. package/dist/plugin/index.js +32 -0
  99. package/dist/proxy/index.cjs +142 -0
  100. package/dist/proxy/index.js +139 -0
  101. package/dist/responses/buildError.cjs +452 -0
  102. package/dist/responses/buildError.js +441 -0
  103. package/dist/responses/buildResponse.cjs +365 -0
  104. package/dist/responses/buildResponse.js +361 -0
  105. package/dist/responses/download.cjs +54 -0
  106. package/dist/responses/download.js +52 -0
  107. package/dist/responses/stream.cjs +60 -0
  108. package/dist/responses/stream.js +58 -0
  109. package/dist/responses/upload.cjs +54 -0
  110. package/dist/responses/upload.js +52 -0
  111. package/dist/types/cookies.cjs +394 -0
  112. package/dist/types/cookies.js +391 -0
  113. package/dist/types/download.cjs +10 -0
  114. package/dist/types/download.js +10 -0
  115. package/dist/types/rezo-request.cjs +131 -0
  116. package/dist/types/rezo-request.js +131 -0
  117. package/dist/utils/agent-merger.cjs +111 -0
  118. package/dist/utils/agent-merger.js +108 -0
  119. package/dist/utils/compression.cjs +84 -0
  120. package/dist/utils/compression.js +82 -0
  121. package/dist/utils/cookies.cjs +514 -0
  122. package/dist/utils/cookies.js +511 -0
  123. package/dist/utils/data-operations.cjs +75 -0
  124. package/dist/utils/data-operations.js +73 -0
  125. package/dist/utils/form-data.cjs +164 -0
  126. package/dist/utils/form-data.js +161 -0
  127. package/dist/utils/headers.cjs +162 -0
  128. package/dist/utils/headers.js +161 -0
  129. package/dist/utils/http-config.cjs +723 -0
  130. package/dist/utils/http-config.js +718 -0
  131. package/dist/utils/index.cjs +8 -0
  132. package/dist/utils/index.js +8 -0
  133. package/dist/utils/tools.cjs +18 -0
  134. package/dist/utils/tools.js +15 -0
  135. package/package.json +172 -0
@@ -0,0 +1,622 @@
1
+ const { RezoError } = require('../errors/rezo-error.cjs');
2
+ const { buildSmartError, builErrorFromResponse } = require('../responses/buildError.cjs');
3
+ const { Cookie } = require('../utils/cookies.cjs');
4
+ const RezoFormData = require('../utils/form-data.cjs');
5
+ const { getDefaultConfig, prepareHTTPOptions } = require('../utils/http-config.cjs');
6
+ const { RezoHeaders } = require('../utils/headers.cjs');
7
+ const { RezoURLSearchParams } = require('../utils/data-operations.cjs');
8
+ const { StreamResponse } = require('../responses/stream.cjs');
9
+ const { DownloadResponse } = require('../responses/download.cjs');
10
+ const { UploadResponse } = require('../responses/upload.cjs');
11
+ const { RezoPerformance } = require('../utils/tools.cjs');
12
+ const { ResponseCache } = require('../cache/response-cache.cjs');
13
+ const Environment = {
14
+ isBrowser: typeof window !== "undefined" && typeof document !== "undefined",
15
+ hasXHR: typeof XMLHttpRequest !== "undefined",
16
+ hasFormData: typeof FormData !== "undefined",
17
+ hasBlob: typeof Blob !== "undefined"
18
+ };
19
+ const responseCacheInstances = new Map;
20
+ function getCacheConfigKey(option) {
21
+ if (option === true)
22
+ return "default";
23
+ if (option === false)
24
+ return "disabled";
25
+ const cfg = option;
26
+ return JSON.stringify({
27
+ cacheDir: cfg.cacheDir || null,
28
+ ttl: cfg.ttl || 300000,
29
+ maxEntries: cfg.maxEntries || 500,
30
+ methods: cfg.methods || ["GET", "HEAD"],
31
+ respectHeaders: cfg.respectHeaders !== false
32
+ });
33
+ }
34
+ function getResponseCache(option) {
35
+ const key = getCacheConfigKey(option);
36
+ let cache = responseCacheInstances.get(key);
37
+ if (!cache) {
38
+ cache = new ResponseCache(option);
39
+ responseCacheInstances.set(key, cache);
40
+ }
41
+ return cache;
42
+ }
43
+ function parseCacheControlFromHeaders(headers) {
44
+ const cacheControl = headers["cache-control"] || "";
45
+ return {
46
+ noCache: cacheControl.includes("no-cache"),
47
+ mustRevalidate: cacheControl.includes("must-revalidate")
48
+ };
49
+ }
50
+ function buildCachedRezoResponse(cached, config) {
51
+ const headers = new RezoHeaders(cached.headers);
52
+ return {
53
+ data: cached.data,
54
+ status: cached.status,
55
+ statusText: cached.statusText,
56
+ headers,
57
+ finalUrl: cached.url,
58
+ urls: [cached.url],
59
+ contentType: cached.headers["content-type"],
60
+ contentLength: parseInt(cached.headers["content-length"] || "0", 10) || 0,
61
+ cookies: {
62
+ array: [],
63
+ serialized: [],
64
+ netscape: "",
65
+ string: "",
66
+ setCookiesString: []
67
+ },
68
+ config: {
69
+ ...config,
70
+ url: cached.url,
71
+ method: "GET",
72
+ headers,
73
+ adapterUsed: "xhr",
74
+ fromCache: true
75
+ }
76
+ };
77
+ }
78
+ function buildUrlTree(config, finalUrl) {
79
+ const urls = [];
80
+ if (config.rawUrl) {
81
+ urls.push(config.rawUrl);
82
+ } else if (config.url) {
83
+ const urlStr = typeof config.url === "string" ? config.url : String(config.url);
84
+ urls.push(urlStr);
85
+ }
86
+ if (finalUrl && (urls.length === 0 || urls[0] !== finalUrl)) {
87
+ urls.push(finalUrl);
88
+ }
89
+ return urls.length > 0 ? urls : [finalUrl];
90
+ }
91
+ function sanitizeConfig(config) {
92
+ const sanitized = { ...config };
93
+ delete sanitized.data;
94
+ return sanitized;
95
+ }
96
+ function parseXHRHeaders(xhr) {
97
+ const headerString = xhr.getAllResponseHeaders();
98
+ const headers = {};
99
+ if (headerString) {
100
+ const lines = headerString.trim().split(`\r
101
+ `);
102
+ for (const line of lines) {
103
+ const colonIndex = line.indexOf(":");
104
+ if (colonIndex > 0) {
105
+ const key = line.substring(0, colonIndex).trim().toLowerCase();
106
+ const value = line.substring(colonIndex + 1).trim();
107
+ headers[key] = value;
108
+ }
109
+ }
110
+ }
111
+ return new RezoHeaders(headers);
112
+ }
113
+ function getCookiesFromDocument(url) {
114
+ const cookies = {
115
+ array: [],
116
+ serialized: [],
117
+ netscape: "",
118
+ string: "",
119
+ setCookiesString: []
120
+ };
121
+ if (typeof document === "undefined" || !document.cookie) {
122
+ return cookies;
123
+ }
124
+ const cookieString = document.cookie;
125
+ const pairs = cookieString.split(";");
126
+ for (const pair of pairs) {
127
+ const [name, ...valueParts] = pair.trim().split("=");
128
+ const value = valueParts.join("=");
129
+ if (name && value !== undefined) {
130
+ const hostname = new URL(url).hostname;
131
+ const cookie = new Cookie({
132
+ key: name.trim(),
133
+ value: value.trim(),
134
+ domain: hostname,
135
+ path: "/"
136
+ });
137
+ cookies.array.push(cookie);
138
+ }
139
+ }
140
+ cookies.string = cookies.array.map((c) => `${c.key}=${c.value}`).join("; ");
141
+ cookies.serialized = cookies.array.map((c) => c.toJSON());
142
+ cookies.netscape = cookies.array.map((c) => c.toNetscapeFormat()).join(`
143
+ `);
144
+ return cookies;
145
+ }
146
+ function prepareXHRBody(body) {
147
+ if (!body)
148
+ return null;
149
+ if (body instanceof URLSearchParams || body instanceof RezoURLSearchParams) {
150
+ return body.toString();
151
+ }
152
+ if (typeof FormData !== "undefined" && body instanceof FormData) {
153
+ return body;
154
+ }
155
+ if (body instanceof RezoFormData) {
156
+ const buffer = body.getBuffer();
157
+ if (buffer) {
158
+ return new Blob([new Uint8Array(buffer)], { type: `multipart/form-data; boundary=${body.getBoundary()}` });
159
+ }
160
+ return null;
161
+ }
162
+ if (typeof body === "object" && !(body instanceof ArrayBuffer) && !(body instanceof Blob)) {
163
+ return JSON.stringify(body);
164
+ }
165
+ return body;
166
+ }
167
+ function toXHRHeaders(headers) {
168
+ if (!headers)
169
+ return {};
170
+ if (headers instanceof RezoHeaders) {
171
+ return headers.toObject();
172
+ }
173
+ return headers;
174
+ }
175
+ async function executeRequest(options, defaultOptions, jar) {
176
+ if (!Environment.hasXHR) {
177
+ throw new Error("XMLHttpRequest is not available in this environment");
178
+ }
179
+ if (!options.responseType) {
180
+ options.responseType = "auto";
181
+ }
182
+ const d_options = await getDefaultConfig(defaultOptions);
183
+ const configResult = prepareHTTPOptions(options, jar, { defaultOptions: d_options });
184
+ let mainConfig = configResult.config;
185
+ const fetchOptions = configResult.fetchOptions;
186
+ const perform = new RezoPerformance;
187
+ const cacheOption = options.cache;
188
+ const method = (options.method || "GET").toUpperCase();
189
+ const requestUrl = typeof fetchOptions.url === "string" ? fetchOptions.url : fetchOptions.url?.toString() || "";
190
+ let cache;
191
+ let requestHeaders;
192
+ let cachedEntry;
193
+ if (cacheOption) {
194
+ cache = getResponseCache(cacheOption);
195
+ requestHeaders = fetchOptions.headers instanceof RezoHeaders ? Object.fromEntries(fetchOptions.headers.entries()) : fetchOptions.headers;
196
+ cachedEntry = cache.get(method, requestUrl, requestHeaders);
197
+ if (cachedEntry) {
198
+ const cacheControl = parseCacheControlFromHeaders(cachedEntry.headers);
199
+ if (!cacheControl.noCache && !cacheControl.mustRevalidate) {
200
+ return buildCachedRezoResponse(cachedEntry, mainConfig);
201
+ }
202
+ }
203
+ const conditionalHeaders = cache.getConditionalHeaders(method, requestUrl, requestHeaders);
204
+ if (conditionalHeaders) {
205
+ const headers = fetchOptions.headers instanceof RezoHeaders ? fetchOptions.headers : new RezoHeaders(fetchOptions.headers || {});
206
+ if (conditionalHeaders.etag) {
207
+ headers.set("If-None-Match", conditionalHeaders.etag);
208
+ }
209
+ if (conditionalHeaders.lastModified) {
210
+ headers.set("If-Modified-Since", conditionalHeaders.lastModified);
211
+ }
212
+ fetchOptions.headers = headers;
213
+ }
214
+ }
215
+ const isStream = options._isStream;
216
+ const isDownload = options._isDownload || !!options.fileName || !!options.saveTo;
217
+ const isUpload = options._isUpload;
218
+ let streamResponse;
219
+ let downloadResponse;
220
+ let uploadResponse;
221
+ if (isStream) {
222
+ streamResponse = new StreamResponse;
223
+ } else if (isDownload) {
224
+ const fileName = options.fileName || options.saveTo || "";
225
+ const url = typeof fetchOptions.url === "string" ? fetchOptions.url : fetchOptions.url?.toString() || "";
226
+ downloadResponse = new DownloadResponse(fileName, url);
227
+ } else if (isUpload) {
228
+ const url = typeof fetchOptions.url === "string" ? fetchOptions.url : fetchOptions.url?.toString() || "";
229
+ uploadResponse = new UploadResponse(url);
230
+ }
231
+ const res = executeXHRRequest(fetchOptions, mainConfig, options, perform, streamResponse, downloadResponse, uploadResponse);
232
+ if (streamResponse) {
233
+ return streamResponse;
234
+ } else if (downloadResponse) {
235
+ return downloadResponse;
236
+ } else if (uploadResponse) {
237
+ return uploadResponse;
238
+ }
239
+ const response = await res;
240
+ if (cache && !isStream && !isDownload && !isUpload) {
241
+ if (response.status === 304 && cachedEntry) {
242
+ const responseHeaders = response.headers instanceof RezoHeaders ? Object.fromEntries(response.headers.entries()) : response.headers;
243
+ const updatedCached = cache.updateRevalidated(method, requestUrl, responseHeaders, requestHeaders);
244
+ if (updatedCached) {
245
+ return buildCachedRezoResponse(updatedCached, mainConfig);
246
+ }
247
+ return buildCachedRezoResponse(cachedEntry, mainConfig);
248
+ }
249
+ if (response.status >= 200 && response.status < 300) {
250
+ cache.set(method, requestUrl, response, requestHeaders);
251
+ }
252
+ }
253
+ return response;
254
+ }
255
+ async function executeXHRRequest(fetchOptions, config, options, perform, streamResult, downloadResult, uploadResult) {
256
+ let retries = 0;
257
+ const retryDelay = config?.retry?.retryDelay || 0;
258
+ const maxRetries = config?.retry?.maxRetries || 0;
259
+ const incrementDelay = config?.retry?.incrementDelay || false;
260
+ const statusCodes = config?.retry?.statusCodes;
261
+ const timing = {
262
+ startTime: performance.now(),
263
+ startTimestamp: Date.now()
264
+ };
265
+ const ABSOLUTE_MAX_ATTEMPTS = 50;
266
+ let totalAttempts = 0;
267
+ config.setSignal();
268
+ const eventEmitter = streamResult || downloadResult || uploadResult;
269
+ if (eventEmitter) {
270
+ eventEmitter.emit("initiated");
271
+ }
272
+ while (true) {
273
+ totalAttempts++;
274
+ if (totalAttempts > ABSOLUTE_MAX_ATTEMPTS) {
275
+ const error = builErrorFromResponse(`Absolute maximum attempts (${ABSOLUTE_MAX_ATTEMPTS}) exceeded.`, { status: 0, statusText: "Max Attempts Exceeded" }, config, fetchOptions);
276
+ throw error;
277
+ }
278
+ try {
279
+ const response = await executeSingleXHRRequest(config, fetchOptions, timing, streamResult, downloadResult, uploadResult);
280
+ if (response instanceof RezoError) {
281
+ config.errors.push({
282
+ attempt: config.retryAttempts + 1,
283
+ error: response,
284
+ duration: perform.now()
285
+ });
286
+ perform.reset();
287
+ if (!config.retry) {
288
+ throw response;
289
+ }
290
+ if (config.retry.condition) {
291
+ const isPassed = await config.retry.condition(response);
292
+ if (typeof isPassed === "boolean" && isPassed === false) {
293
+ throw response;
294
+ }
295
+ } else {
296
+ if (statusCodes && !statusCodes.includes(response.status || 0)) {
297
+ throw response;
298
+ }
299
+ if (maxRetries <= retries) {
300
+ throw response;
301
+ }
302
+ retries++;
303
+ if (retryDelay > 0) {
304
+ await new Promise((resolve) => setTimeout(resolve, incrementDelay ? retryDelay * retries : retryDelay));
305
+ }
306
+ }
307
+ config.retryAttempts++;
308
+ continue;
309
+ }
310
+ return response;
311
+ } catch (error) {
312
+ if (error instanceof RezoError) {
313
+ throw error;
314
+ }
315
+ throw buildSmartError(config, fetchOptions, error);
316
+ }
317
+ }
318
+ }
319
+ function executeSingleXHRRequest(config, fetchOptions, timing, streamResult, downloadResult, uploadResult) {
320
+ return new Promise((resolve) => {
321
+ try {
322
+ const { fullUrl, body } = fetchOptions;
323
+ const url = fullUrl || (typeof fetchOptions.url === "string" ? fetchOptions.url : fetchOptions.url?.toString() || "");
324
+ const isSecure = url.startsWith("https:");
325
+ config.adapterUsed = "xhr";
326
+ config.isSecure = isSecure;
327
+ config.finalUrl = url;
328
+ config.network.protocol = isSecure ? "https" : "http";
329
+ config.timing.startTimestamp = timing.startTimestamp;
330
+ const xhr = new XMLHttpRequest;
331
+ xhr.open(fetchOptions.method.toUpperCase(), url, true);
332
+ const headers = toXHRHeaders(fetchOptions.headers);
333
+ for (const [key, value] of Object.entries(headers)) {
334
+ if (value !== undefined && value !== null) {
335
+ xhr.setRequestHeader(key, String(value));
336
+ }
337
+ }
338
+ const responseType = config.responseType || fetchOptions.responseType || "auto";
339
+ if (responseType === "blob") {
340
+ xhr.responseType = "blob";
341
+ } else if (responseType === "arrayBuffer" || responseType === "buffer") {
342
+ xhr.responseType = "arraybuffer";
343
+ } else if (responseType === "json") {
344
+ xhr.responseType = "json";
345
+ } else {
346
+ xhr.responseType = "text";
347
+ }
348
+ if (config.timeout) {
349
+ xhr.timeout = config.timeout;
350
+ }
351
+ if (config.enableCookieJar) {
352
+ xhr.withCredentials = true;
353
+ }
354
+ const eventEmitter = streamResult || downloadResult || uploadResult;
355
+ if (eventEmitter) {
356
+ const reqHeaders = fetchOptions.headers instanceof RezoHeaders ? fetchOptions.headers.toObject() : fetchOptions.headers || {};
357
+ const startEvent = {
358
+ url,
359
+ method: fetchOptions.method.toUpperCase(),
360
+ headers: new RezoHeaders(reqHeaders),
361
+ timestamp: timing.startTime,
362
+ timeout: fetchOptions.timeout,
363
+ maxRedirects: config.maxRedirects
364
+ };
365
+ eventEmitter.emit("start", startEvent);
366
+ }
367
+ if (config.signal) {
368
+ config.signal.addEventListener("abort", () => {
369
+ xhr.abort();
370
+ });
371
+ }
372
+ xhr.onprogress = (event) => {
373
+ if (!config.timing.ttfbMs) {
374
+ timing.firstByteTime = performance.now();
375
+ config.timing.ttfbMs = timing.firstByteTime - timing.startTime;
376
+ }
377
+ if (eventEmitter) {
378
+ const progressEvent = {
379
+ loaded: event.loaded,
380
+ total: event.total || 0,
381
+ percentage: event.total ? Math.round(event.loaded / event.total * 100) : 0,
382
+ speed: 0,
383
+ averageSpeed: 0,
384
+ estimatedTime: 0,
385
+ timestamp: Date.now()
386
+ };
387
+ eventEmitter.emit("progress", progressEvent);
388
+ eventEmitter.emit("download-progress", progressEvent);
389
+ }
390
+ };
391
+ if (xhr.upload && uploadResult) {
392
+ xhr.upload.onprogress = (event) => {
393
+ const progressEvent = {
394
+ loaded: event.loaded,
395
+ total: event.total || 0,
396
+ percentage: event.total ? Math.round(event.loaded / event.total * 100) : 0,
397
+ speed: 0,
398
+ averageSpeed: 0,
399
+ estimatedTime: 0,
400
+ timestamp: Date.now()
401
+ };
402
+ uploadResult.emit("progress", progressEvent);
403
+ uploadResult.emit("upload-progress", progressEvent);
404
+ };
405
+ }
406
+ xhr.onload = () => {
407
+ if (!config.timing.ttfbMs) {
408
+ timing.firstByteTime = performance.now();
409
+ config.timing.ttfbMs = timing.firstByteTime - timing.startTime;
410
+ }
411
+ config.timing.endTimestamp = Date.now();
412
+ config.timing.durationMs = performance.now() - timing.startTime;
413
+ config.timing.transferMs = timing.firstByteTime ? performance.now() - timing.firstByteTime : config.timing.durationMs;
414
+ const status = xhr.status;
415
+ const statusText = xhr.statusText;
416
+ const responseHeaders = parseXHRHeaders(xhr);
417
+ const contentType = xhr.getResponseHeader("content-type") || "";
418
+ const contentLength = xhr.getResponseHeader("content-length");
419
+ const cookies = getCookiesFromDocument(url);
420
+ config.responseCookies = cookies;
421
+ if (eventEmitter) {
422
+ const headersEvent = {
423
+ status,
424
+ statusText,
425
+ headers: responseHeaders,
426
+ contentType,
427
+ contentLength: contentLength ? parseInt(contentLength, 10) : undefined,
428
+ cookies: cookies.array,
429
+ timing: {
430
+ firstByte: config.timing.ttfbMs,
431
+ total: performance.now() - timing.startTime
432
+ }
433
+ };
434
+ eventEmitter.emit("headers", headersEvent);
435
+ eventEmitter.emit("status", status, statusText);
436
+ eventEmitter.emit("cookies", cookies.array);
437
+ if (downloadResult) {
438
+ downloadResult.status = status;
439
+ downloadResult.statusText = statusText;
440
+ } else if (uploadResult) {
441
+ uploadResult.status = status;
442
+ uploadResult.statusText = statusText;
443
+ }
444
+ }
445
+ let responseData;
446
+ let bodySize = 0;
447
+ if (xhr.responseType === "blob") {
448
+ responseData = xhr.response;
449
+ bodySize = xhr.response?.size || 0;
450
+ } else if (xhr.responseType === "arraybuffer") {
451
+ responseData = xhr.response;
452
+ bodySize = xhr.response?.byteLength || 0;
453
+ } else if (xhr.responseType === "json") {
454
+ responseData = xhr.response;
455
+ bodySize = JSON.stringify(xhr.response || "").length;
456
+ } else {
457
+ const text = xhr.responseText || "";
458
+ bodySize = text.length;
459
+ if (responseType === "auto" && contentType.includes("application/json")) {
460
+ try {
461
+ responseData = JSON.parse(text);
462
+ } catch {
463
+ responseData = text;
464
+ }
465
+ } else if (responseType === "json") {
466
+ try {
467
+ responseData = JSON.parse(text);
468
+ } catch {
469
+ responseData = text;
470
+ }
471
+ } else {
472
+ responseData = text;
473
+ }
474
+ }
475
+ config.transfer.bodySize = bodySize;
476
+ config.transfer.responseSize = bodySize;
477
+ if (status >= 400) {
478
+ const error = builErrorFromResponse(`HTTP Error ${status}: ${statusText}`, {
479
+ status,
480
+ statusText,
481
+ headers: responseHeaders,
482
+ data: responseData
483
+ }, config, fetchOptions);
484
+ if (eventEmitter) {
485
+ eventEmitter.emit("error", error);
486
+ }
487
+ resolve(error);
488
+ return;
489
+ }
490
+ const finalResponse = {
491
+ data: responseData,
492
+ status,
493
+ statusText,
494
+ headers: responseHeaders,
495
+ cookies,
496
+ config,
497
+ contentType,
498
+ contentLength: bodySize,
499
+ finalUrl: xhr.responseURL || url,
500
+ urls: buildUrlTree(config, xhr.responseURL || url)
501
+ };
502
+ if (streamResult) {
503
+ const streamFinishEvent = {
504
+ status,
505
+ statusText,
506
+ headers: responseHeaders,
507
+ contentType,
508
+ contentLength: bodySize,
509
+ finalUrl: xhr.responseURL || url,
510
+ cookies,
511
+ urls: buildUrlTree(config, xhr.responseURL || url),
512
+ timing: {
513
+ total: config.timing.durationMs || 0,
514
+ dns: config.timing.dnsMs,
515
+ tcp: config.timing.tcpMs,
516
+ tls: config.timing.tlsMs,
517
+ firstByte: config.timing.ttfbMs,
518
+ download: config.timing.transferMs
519
+ },
520
+ config: sanitizeConfig(config)
521
+ };
522
+ streamResult.emit("finish", streamFinishEvent);
523
+ streamResult.emit("done", streamFinishEvent);
524
+ streamResult.emit("end");
525
+ streamResult._markFinished();
526
+ }
527
+ if (downloadResult) {
528
+ const downloadFinishEvent = {
529
+ status,
530
+ statusText,
531
+ headers: responseHeaders,
532
+ contentType,
533
+ contentLength: bodySize,
534
+ finalUrl: xhr.responseURL || url,
535
+ cookies,
536
+ urls: buildUrlTree(config, xhr.responseURL || url),
537
+ fileName: config.fileName || "",
538
+ fileSize: bodySize,
539
+ timing: {
540
+ total: config.timing.durationMs || 0,
541
+ dns: config.timing.dnsMs,
542
+ tcp: config.timing.tcpMs,
543
+ tls: config.timing.tlsMs,
544
+ firstByte: config.timing.ttfbMs,
545
+ download: config.timing.transferMs || 0
546
+ },
547
+ averageSpeed: config.timing.transferMs ? bodySize / config.timing.transferMs * 1000 : 0,
548
+ config: sanitizeConfig(config)
549
+ };
550
+ downloadResult.emit("finish", downloadFinishEvent);
551
+ downloadResult.emit("done", downloadFinishEvent);
552
+ downloadResult._markFinished();
553
+ }
554
+ if (uploadResult) {
555
+ const uploadFinishEvent = {
556
+ response: {
557
+ status,
558
+ statusText,
559
+ headers: responseHeaders,
560
+ data: responseData,
561
+ contentType,
562
+ contentLength: bodySize
563
+ },
564
+ finalUrl: xhr.responseURL || url,
565
+ cookies,
566
+ urls: buildUrlTree(config, xhr.responseURL || url),
567
+ uploadSize: config.transfer.requestSize || 0,
568
+ timing: {
569
+ total: config.timing.durationMs || 0,
570
+ dns: config.timing.dnsMs,
571
+ tcp: config.timing.tcpMs,
572
+ tls: config.timing.tlsMs,
573
+ upload: config.timing.transferMs || 0,
574
+ waiting: config.timing.ttfbMs || 0,
575
+ download: config.timing.transferMs
576
+ },
577
+ averageUploadSpeed: config.timing.transferMs ? (config.transfer.requestSize || 0) / config.timing.transferMs * 1000 : 0,
578
+ averageDownloadSpeed: config.timing.transferMs ? bodySize / config.timing.transferMs * 1000 : 0,
579
+ config: sanitizeConfig(config)
580
+ };
581
+ uploadResult.emit("finish", uploadFinishEvent);
582
+ uploadResult.emit("done", uploadFinishEvent);
583
+ uploadResult._markFinished();
584
+ }
585
+ resolve(finalResponse);
586
+ };
587
+ xhr.onerror = () => {
588
+ const error = buildSmartError(config, fetchOptions, new Error("Network error"));
589
+ if (eventEmitter) {
590
+ eventEmitter.emit("error", error);
591
+ }
592
+ resolve(error);
593
+ };
594
+ xhr.ontimeout = () => {
595
+ const error = buildSmartError(config, fetchOptions, new Error(`Request timeout after ${config.timeout}ms`));
596
+ if (eventEmitter) {
597
+ eventEmitter.emit("error", error);
598
+ }
599
+ resolve(error);
600
+ };
601
+ xhr.onabort = () => {
602
+ const error = buildSmartError(config, fetchOptions, new Error("Request aborted"));
603
+ if (eventEmitter) {
604
+ eventEmitter.emit("error", error);
605
+ }
606
+ resolve(error);
607
+ };
608
+ const preparedBody = prepareXHRBody(body);
609
+ xhr.send(preparedBody);
610
+ } catch (error) {
611
+ const rezoError = buildSmartError(config, fetchOptions, error);
612
+ const eventEmitter = streamResult || downloadResult || uploadResult;
613
+ if (eventEmitter) {
614
+ eventEmitter.emit("error", rezoError);
615
+ }
616
+ resolve(rezoError);
617
+ }
618
+ });
619
+ }
620
+
621
+ exports.Environment = Environment;
622
+ exports.executeRequest = executeRequest;