rezo 1.0.5 → 1.0.7

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 (49) hide show
  1. package/README.md +352 -9
  2. package/dist/adapters/curl.cjs +796 -0
  3. package/dist/adapters/curl.js +796 -0
  4. package/dist/adapters/entries/curl.d.ts +2407 -20
  5. package/dist/adapters/entries/fetch.d.ts +364 -20
  6. package/dist/adapters/entries/http.d.ts +364 -20
  7. package/dist/adapters/entries/http2.d.ts +364 -20
  8. package/dist/adapters/entries/react-native.d.ts +364 -20
  9. package/dist/adapters/entries/xhr.d.ts +364 -20
  10. package/dist/adapters/index.cjs +6 -6
  11. package/dist/adapters/picker.cjs +2 -2
  12. package/dist/adapters/picker.js +2 -2
  13. package/dist/cache/index.cjs +13 -13
  14. package/dist/core/hooks.cjs +2 -0
  15. package/dist/core/hooks.js +2 -0
  16. package/dist/core/rezo.cjs +2 -2
  17. package/dist/core/rezo.js +2 -2
  18. package/dist/crawler.d.ts +366 -22
  19. package/dist/entries/crawler.cjs +5 -5
  20. package/dist/index.cjs +23 -18
  21. package/dist/index.d.ts +631 -20
  22. package/dist/index.js +1 -0
  23. package/dist/platform/browser.d.ts +364 -20
  24. package/dist/platform/bun.d.ts +364 -20
  25. package/dist/platform/deno.d.ts +364 -20
  26. package/dist/platform/node.d.ts +364 -20
  27. package/dist/platform/react-native.d.ts +364 -20
  28. package/dist/platform/worker.d.ts +364 -20
  29. package/dist/plugin/crawler-options.cjs +1 -1
  30. package/dist/plugin/crawler-options.js +1 -1
  31. package/dist/plugin/crawler.cjs +2 -2
  32. package/dist/plugin/crawler.js +2 -2
  33. package/dist/plugin/index.cjs +36 -36
  34. package/dist/proxy/index.cjs +2 -2
  35. package/dist/proxy/manager.cjs +57 -2
  36. package/dist/proxy/manager.js +57 -2
  37. package/dist/queue/http-queue.cjs +313 -0
  38. package/dist/queue/http-queue.js +312 -0
  39. package/dist/queue/index.cjs +8 -0
  40. package/dist/queue/index.js +6 -0
  41. package/dist/queue/queue.cjs +346 -0
  42. package/dist/queue/queue.js +344 -0
  43. package/dist/queue/types.cjs +17 -0
  44. package/dist/queue/types.js +17 -0
  45. package/dist/types/curl-options.cjs +25 -0
  46. package/dist/types/curl-options.js +25 -0
  47. package/dist/utils/http-config.cjs +0 -15
  48. package/dist/utils/http-config.js +0 -15
  49. package/package.json +1 -2
@@ -1,36 +1,36 @@
1
- const _mod_9qwi8e = require('./crawler.cjs');
2
- exports.Crawler = _mod_9qwi8e.Crawler;;
3
- const _mod_kpl4t4 = require('./crawler-options.cjs');
4
- exports.CrawlerOptions = _mod_kpl4t4.CrawlerOptions;;
5
- const _mod_o6bmw3 = require('../cache/file-cacher.cjs');
6
- exports.FileCacher = _mod_o6bmw3.FileCacher;;
7
- const _mod_9vlknl = require('../cache/url-store.cjs');
8
- exports.UrlStore = _mod_9vlknl.UrlStore;;
9
- const _mod_31b8nd = require('./addon/oxylabs/index.cjs');
10
- exports.Oxylabs = _mod_31b8nd.Oxylabs;;
11
- const _mod_63rr5e = require('./addon/oxylabs/options.cjs');
12
- exports.OXYLABS_BROWSER_TYPES = _mod_63rr5e.OXYLABS_BROWSER_TYPES;
13
- exports.OXYLABS_COMMON_LOCALES = _mod_63rr5e.OXYLABS_COMMON_LOCALES;
14
- exports.OXYLABS_COMMON_GEO_LOCATIONS = _mod_63rr5e.OXYLABS_COMMON_GEO_LOCATIONS;
15
- exports.OXYLABS_US_STATES = _mod_63rr5e.OXYLABS_US_STATES;
16
- exports.OXYLABS_EUROPEAN_COUNTRIES = _mod_63rr5e.OXYLABS_EUROPEAN_COUNTRIES;
17
- exports.OXYLABS_ASIAN_COUNTRIES = _mod_63rr5e.OXYLABS_ASIAN_COUNTRIES;
18
- exports.getRandomOxylabsBrowserType = _mod_63rr5e.getRandomBrowserType;
19
- exports.getRandomOxylabsLocale = _mod_63rr5e.getRandomLocale;
20
- exports.getRandomOxylabsGeoLocation = _mod_63rr5e.getRandomGeoLocation;;
21
- const _mod_h4ktbx = require('./addon/decodo/index.cjs');
22
- exports.Decodo = _mod_h4ktbx.Decodo;;
23
- const _mod_t9ve3n = require('./addon/decodo/options.cjs');
24
- exports.DECODO_DEVICE_TYPES = _mod_t9ve3n.DECODO_DEVICE_TYPES;
25
- exports.DECODO_HEADLESS_MODES = _mod_t9ve3n.DECODO_HEADLESS_MODES;
26
- exports.DECODO_COMMON_LOCALES = _mod_t9ve3n.DECODO_COMMON_LOCALES;
27
- exports.DECODO_COMMON_COUNTRIES = _mod_t9ve3n.DECODO_COMMON_COUNTRIES;
28
- exports.DECODO_EUROPEAN_COUNTRIES = _mod_t9ve3n.DECODO_EUROPEAN_COUNTRIES;
29
- exports.DECODO_ASIAN_COUNTRIES = _mod_t9ve3n.DECODO_ASIAN_COUNTRIES;
30
- exports.DECODO_US_STATES = _mod_t9ve3n.DECODO_US_STATES;
31
- exports.DECODO_COMMON_CITIES = _mod_t9ve3n.DECODO_COMMON_CITIES;
32
- exports.getRandomDecodoDeviceType = _mod_t9ve3n.getRandomDeviceType;
33
- exports.getRandomDecodoLocale = _mod_t9ve3n.getRandomLocale;
34
- exports.getRandomDecodoCountry = _mod_t9ve3n.getRandomCountry;
35
- exports.getRandomDecodoCity = _mod_t9ve3n.getRandomCity;
36
- exports.generateDecodoSessionId = _mod_t9ve3n.generateSessionId;;
1
+ const _mod_gtcptp = require('./crawler.cjs');
2
+ exports.Crawler = _mod_gtcptp.Crawler;;
3
+ const _mod_3p3u6d = require('./crawler-options.cjs');
4
+ exports.CrawlerOptions = _mod_3p3u6d.CrawlerOptions;;
5
+ const _mod_umel97 = require('../cache/file-cacher.cjs');
6
+ exports.FileCacher = _mod_umel97.FileCacher;;
7
+ const _mod_kjgxkr = require('../cache/url-store.cjs');
8
+ exports.UrlStore = _mod_kjgxkr.UrlStore;;
9
+ const _mod_u41p6l = require('./addon/oxylabs/index.cjs');
10
+ exports.Oxylabs = _mod_u41p6l.Oxylabs;;
11
+ const _mod_6hmgeg = require('./addon/oxylabs/options.cjs');
12
+ exports.OXYLABS_BROWSER_TYPES = _mod_6hmgeg.OXYLABS_BROWSER_TYPES;
13
+ exports.OXYLABS_COMMON_LOCALES = _mod_6hmgeg.OXYLABS_COMMON_LOCALES;
14
+ exports.OXYLABS_COMMON_GEO_LOCATIONS = _mod_6hmgeg.OXYLABS_COMMON_GEO_LOCATIONS;
15
+ exports.OXYLABS_US_STATES = _mod_6hmgeg.OXYLABS_US_STATES;
16
+ exports.OXYLABS_EUROPEAN_COUNTRIES = _mod_6hmgeg.OXYLABS_EUROPEAN_COUNTRIES;
17
+ exports.OXYLABS_ASIAN_COUNTRIES = _mod_6hmgeg.OXYLABS_ASIAN_COUNTRIES;
18
+ exports.getRandomOxylabsBrowserType = _mod_6hmgeg.getRandomBrowserType;
19
+ exports.getRandomOxylabsLocale = _mod_6hmgeg.getRandomLocale;
20
+ exports.getRandomOxylabsGeoLocation = _mod_6hmgeg.getRandomGeoLocation;;
21
+ const _mod_7v9zq0 = require('./addon/decodo/index.cjs');
22
+ exports.Decodo = _mod_7v9zq0.Decodo;;
23
+ const _mod_i0yci3 = require('./addon/decodo/options.cjs');
24
+ exports.DECODO_DEVICE_TYPES = _mod_i0yci3.DECODO_DEVICE_TYPES;
25
+ exports.DECODO_HEADLESS_MODES = _mod_i0yci3.DECODO_HEADLESS_MODES;
26
+ exports.DECODO_COMMON_LOCALES = _mod_i0yci3.DECODO_COMMON_LOCALES;
27
+ exports.DECODO_COMMON_COUNTRIES = _mod_i0yci3.DECODO_COMMON_COUNTRIES;
28
+ exports.DECODO_EUROPEAN_COUNTRIES = _mod_i0yci3.DECODO_EUROPEAN_COUNTRIES;
29
+ exports.DECODO_ASIAN_COUNTRIES = _mod_i0yci3.DECODO_ASIAN_COUNTRIES;
30
+ exports.DECODO_US_STATES = _mod_i0yci3.DECODO_US_STATES;
31
+ exports.DECODO_COMMON_CITIES = _mod_i0yci3.DECODO_COMMON_CITIES;
32
+ exports.getRandomDecodoDeviceType = _mod_i0yci3.getRandomDeviceType;
33
+ exports.getRandomDecodoLocale = _mod_i0yci3.getRandomLocale;
34
+ exports.getRandomDecodoCountry = _mod_i0yci3.getRandomCountry;
35
+ exports.getRandomDecodoCity = _mod_i0yci3.getRandomCity;
36
+ exports.generateDecodoSessionId = _mod_i0yci3.generateSessionId;;
@@ -1,8 +1,8 @@
1
1
  const { SocksProxyAgent: RezoSocksProxy } = require("socks-proxy-agent");
2
2
  const { HttpsProxyAgent: RezoHttpsSocks } = require("https-proxy-agent");
3
3
  const { HttpProxyAgent: RezoHttpSocks } = require("http-proxy-agent");
4
- const _mod_4nmwf9 = require('./manager.cjs');
5
- exports.ProxyManager = _mod_4nmwf9.ProxyManager;;
4
+ const _mod_0g3tce = require('./manager.cjs');
5
+ exports.ProxyManager = _mod_0g3tce.ProxyManager;;
6
6
  function createOptions(uri, opts) {
7
7
  if (uri instanceof URL || typeof uri === "string") {
8
8
  return {
@@ -1,3 +1,4 @@
1
+ const { parseProxyString } = require('./index.cjs');
1
2
  function generateProxyId() {
2
3
  return `proxy_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
3
4
  }
@@ -20,16 +21,29 @@ class ProxyManager {
20
21
  beforeProxyDisable: [],
21
22
  afterProxyDisable: [],
22
23
  afterProxyRotate: [],
23
- afterProxyEnable: []
24
+ afterProxyEnable: [],
25
+ onNoProxiesAvailable: []
24
26
  };
25
27
  constructor(config) {
28
+ const parsedProxies = [];
29
+ for (const proxy of config.proxies) {
30
+ if (typeof proxy === "string") {
31
+ const parsed = parseProxyString(proxy);
32
+ if (parsed) {
33
+ parsedProxies.push(parsed);
34
+ }
35
+ } else {
36
+ parsedProxies.push(proxy);
37
+ }
38
+ }
26
39
  this.config = {
27
40
  failWithoutProxy: true,
28
41
  autoDisableDeadProxies: false,
29
42
  maxFailures: 3,
30
43
  retryWithNextProxy: false,
31
44
  maxProxyRetries: 3,
32
- ...config
45
+ ...config,
46
+ proxies: parsedProxies
33
47
  };
34
48
  for (const proxy of this.config.proxies) {
35
49
  if (!proxy.id) {
@@ -441,6 +455,47 @@ class ProxyManager {
441
455
  }
442
456
  }
443
457
  }
458
+ runOnNoProxiesAvailableHooksSync(context) {
459
+ for (const hook of this.hooks.onNoProxiesAvailable) {
460
+ try {
461
+ hook(context);
462
+ } catch (error) {
463
+ console.error("[ProxyManager] onNoProxiesAvailable hook error:", error);
464
+ }
465
+ }
466
+ }
467
+ async runOnNoProxiesAvailableHooks(context) {
468
+ for (const hook of this.hooks.onNoProxiesAvailable) {
469
+ try {
470
+ await hook(context);
471
+ } catch (error) {
472
+ console.error("[ProxyManager] onNoProxiesAvailable hook error:", error);
473
+ }
474
+ }
475
+ }
476
+ notifyNoProxiesAvailable(url, error) {
477
+ const allProxies = Array.from(this.states.values());
478
+ const active = allProxies.filter((s) => s.isActive);
479
+ const disabled = allProxies.filter((s) => !s.isActive && !s.reenableAt);
480
+ const cooldown = allProxies.filter((s) => !s.isActive && s.reenableAt);
481
+ const disabledReasons = {
482
+ dead: allProxies.filter((s) => s.disabledReason === "dead").length,
483
+ limitReached: allProxies.filter((s) => s.disabledReason === "limit-reached").length,
484
+ manual: allProxies.filter((s) => s.disabledReason === "manual").length
485
+ };
486
+ const context = {
487
+ url,
488
+ error,
489
+ allProxies,
490
+ activeCount: active.length,
491
+ disabledCount: disabled.length,
492
+ cooldownCount: cooldown.length,
493
+ disabledReasons,
494
+ timestamp: Date.now()
495
+ };
496
+ this.runOnNoProxiesAvailableHooksSync(context);
497
+ return context;
498
+ }
444
499
  }
445
500
 
446
501
  exports.ProxyManager = ProxyManager;
@@ -1,3 +1,4 @@
1
+ import { parseProxyString } from './index.js';
1
2
  function generateProxyId() {
2
3
  return `proxy_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
3
4
  }
@@ -20,16 +21,29 @@ export class ProxyManager {
20
21
  beforeProxyDisable: [],
21
22
  afterProxyDisable: [],
22
23
  afterProxyRotate: [],
23
- afterProxyEnable: []
24
+ afterProxyEnable: [],
25
+ onNoProxiesAvailable: []
24
26
  };
25
27
  constructor(config) {
28
+ const parsedProxies = [];
29
+ for (const proxy of config.proxies) {
30
+ if (typeof proxy === "string") {
31
+ const parsed = parseProxyString(proxy);
32
+ if (parsed) {
33
+ parsedProxies.push(parsed);
34
+ }
35
+ } else {
36
+ parsedProxies.push(proxy);
37
+ }
38
+ }
26
39
  this.config = {
27
40
  failWithoutProxy: true,
28
41
  autoDisableDeadProxies: false,
29
42
  maxFailures: 3,
30
43
  retryWithNextProxy: false,
31
44
  maxProxyRetries: 3,
32
- ...config
45
+ ...config,
46
+ proxies: parsedProxies
33
47
  };
34
48
  for (const proxy of this.config.proxies) {
35
49
  if (!proxy.id) {
@@ -441,4 +455,45 @@ export class ProxyManager {
441
455
  }
442
456
  }
443
457
  }
458
+ runOnNoProxiesAvailableHooksSync(context) {
459
+ for (const hook of this.hooks.onNoProxiesAvailable) {
460
+ try {
461
+ hook(context);
462
+ } catch (error) {
463
+ console.error("[ProxyManager] onNoProxiesAvailable hook error:", error);
464
+ }
465
+ }
466
+ }
467
+ async runOnNoProxiesAvailableHooks(context) {
468
+ for (const hook of this.hooks.onNoProxiesAvailable) {
469
+ try {
470
+ await hook(context);
471
+ } catch (error) {
472
+ console.error("[ProxyManager] onNoProxiesAvailable hook error:", error);
473
+ }
474
+ }
475
+ }
476
+ notifyNoProxiesAvailable(url, error) {
477
+ const allProxies = Array.from(this.states.values());
478
+ const active = allProxies.filter((s) => s.isActive);
479
+ const disabled = allProxies.filter((s) => !s.isActive && !s.reenableAt);
480
+ const cooldown = allProxies.filter((s) => !s.isActive && s.reenableAt);
481
+ const disabledReasons = {
482
+ dead: allProxies.filter((s) => s.disabledReason === "dead").length,
483
+ limitReached: allProxies.filter((s) => s.disabledReason === "limit-reached").length,
484
+ manual: allProxies.filter((s) => s.disabledReason === "manual").length
485
+ };
486
+ const context = {
487
+ url,
488
+ error,
489
+ allProxies,
490
+ activeCount: active.length,
491
+ disabledCount: disabled.length,
492
+ cooldownCount: cooldown.length,
493
+ disabledReasons,
494
+ timestamp: Date.now()
495
+ };
496
+ this.runOnNoProxiesAvailableHooksSync(context);
497
+ return context;
498
+ }
444
499
  }
@@ -0,0 +1,313 @@
1
+ const { RezoQueue } = require('./queue.cjs');
2
+ const { HttpMethodPriority } = require('./types.cjs');
3
+ function extractDomain(url) {
4
+ try {
5
+ const parsed = new URL(url);
6
+ return parsed.hostname;
7
+ } catch {
8
+ const match = url.match(/^(?:https?:\/\/)?([^/:]+)/i);
9
+ return match?.[1] ?? "unknown";
10
+ }
11
+ }
12
+ function generateId() {
13
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 9)}`;
14
+ }
15
+
16
+ class HttpQueue extends RezoQueue {
17
+ domainQueues = new Map;
18
+ domainPending = new Map;
19
+ domainPaused = new Map;
20
+ domainRateLimited = new Map;
21
+ domainConcurrencyLimits = new Map;
22
+ httpStatsData;
23
+ httpEventHandlers = new Map;
24
+ httpConfig;
25
+ constructor(config = {}) {
26
+ super(config);
27
+ this.httpConfig = {
28
+ ...this.config,
29
+ domainConcurrency: config.domainConcurrency ?? 1 / 0,
30
+ requestsPerSecond: config.requestsPerSecond ?? 0,
31
+ respectRetryAfter: config.respectRetryAfter ?? true,
32
+ respectRateLimitHeaders: config.respectRateLimitHeaders ?? true,
33
+ autoRetry: config.autoRetry ?? false,
34
+ maxRetries: config.maxRetries ?? 3,
35
+ retryDelay: config.retryDelay ?? 1000,
36
+ retryStatusCodes: config.retryStatusCodes ?? [429, 500, 502, 503, 504]
37
+ };
38
+ if (typeof config.domainConcurrency === "object") {
39
+ for (const [domain, limit] of Object.entries(config.domainConcurrency)) {
40
+ this.domainConcurrencyLimits.set(domain, limit);
41
+ }
42
+ }
43
+ this.httpStatsData = {
44
+ ...this.stats,
45
+ byDomain: {},
46
+ retries: 0,
47
+ rateLimitHits: 0
48
+ };
49
+ if (this.httpConfig.requestsPerSecond > 0) {
50
+ this.config.interval = 1000;
51
+ this.config.intervalCap = this.httpConfig.requestsPerSecond;
52
+ }
53
+ }
54
+ start() {
55
+ super.start();
56
+ this.tryRunHttpNext();
57
+ }
58
+ get httpStats() {
59
+ return {
60
+ ...this.stats,
61
+ byDomain: { ...this.httpStatsData.byDomain },
62
+ retries: this.httpStatsData.retries,
63
+ rateLimitHits: this.httpStatsData.rateLimitHits
64
+ };
65
+ }
66
+ addHttp(fn, options = {}) {
67
+ return new Promise((resolve, reject) => {
68
+ const domain = options.domain ?? "default";
69
+ const method = (options.method ?? "GET").toUpperCase();
70
+ const effectivePriority = options.priority ?? (HttpMethodPriority[method] ?? HttpMethodPriority.GET);
71
+ const maxRetries = typeof options.retry === "number" ? options.retry : options.retry === true ? this.httpConfig.maxRetries : 0;
72
+ const task = {
73
+ id: options.id ?? generateId(),
74
+ fn,
75
+ priority: effectivePriority,
76
+ timeout: options.timeout ?? this.config.timeout,
77
+ signal: options.signal,
78
+ resolve,
79
+ reject,
80
+ addedAt: Date.now(),
81
+ domain,
82
+ method,
83
+ retryCount: 0,
84
+ maxRetries: this.httpConfig.autoRetry ? Math.max(maxRetries, this.httpConfig.maxRetries) : maxRetries,
85
+ retryDelay: options.retryDelay
86
+ };
87
+ if (options.signal?.aborted) {
88
+ reject(new Error("Task was cancelled before starting"));
89
+ return;
90
+ }
91
+ options.signal?.addEventListener("abort", () => {
92
+ this.cancelHttp(task.id);
93
+ });
94
+ this.ensureDomainStats(domain);
95
+ this.insertHttpTask(task);
96
+ this.emitHttp("add", { id: task.id, priority: task.priority });
97
+ if (!this.isPaused) {
98
+ this.tryRunHttpNext();
99
+ }
100
+ });
101
+ }
102
+ pauseDomain(domain) {
103
+ this.domainPaused.set(domain, true);
104
+ }
105
+ resumeDomain(domain) {
106
+ this.domainPaused.delete(domain);
107
+ this.emitHttp("domainAvailable", { domain });
108
+ this.tryRunHttpNext();
109
+ }
110
+ setDomainConcurrency(domain, limit) {
111
+ this.domainConcurrencyLimits.set(domain, limit);
112
+ this.tryRunHttpNext();
113
+ }
114
+ getDomainState(domain) {
115
+ return {
116
+ pending: this.domainPending.get(domain) ?? 0,
117
+ size: this.domainQueues.get(domain)?.length ?? 0,
118
+ isPaused: this.domainPaused.get(domain) ?? false,
119
+ rateLimitedUntil: this.domainRateLimited.get(domain)
120
+ };
121
+ }
122
+ handleRateLimit(domain, retryAfter) {
123
+ const until = Date.now() + retryAfter * 1000;
124
+ this.domainRateLimited.set(domain, until);
125
+ this.httpStatsData.rateLimitHits++;
126
+ this.ensureDomainStats(domain);
127
+ this.httpStatsData.byDomain[domain].rateLimited++;
128
+ this.emitHttp("rateLimited", { domain, retryAfter });
129
+ setTimeout(() => {
130
+ this.domainRateLimited.delete(domain);
131
+ this.emitHttp("domainAvailable", { domain });
132
+ this.tryRunHttpNext();
133
+ }, retryAfter * 1000);
134
+ }
135
+ cancelHttp(id) {
136
+ for (const [domain, queue] of this.domainQueues.entries()) {
137
+ const index = queue.findIndex((t) => t.id === id);
138
+ if (index !== -1) {
139
+ const [task] = queue.splice(index, 1);
140
+ task.reject(new Error("Task was cancelled"));
141
+ this.ensureDomainStats(domain);
142
+ this.httpStatsData.byDomain[domain].failed++;
143
+ this.emitHttp("cancelled", { id });
144
+ return true;
145
+ }
146
+ }
147
+ return false;
148
+ }
149
+ onHttp(event, handler) {
150
+ if (!this.httpEventHandlers.has(event)) {
151
+ this.httpEventHandlers.set(event, new Set);
152
+ }
153
+ this.httpEventHandlers.get(event).add(handler);
154
+ }
155
+ offHttp(event, handler) {
156
+ this.httpEventHandlers.get(event)?.delete(handler);
157
+ }
158
+ clearHttp() {
159
+ for (const [domain, queue] of this.domainQueues.entries()) {
160
+ this.ensureDomainStats(domain);
161
+ for (const task of queue) {
162
+ task.reject(new Error("Queue was cleared"));
163
+ this.httpStatsData.byDomain[domain].failed++;
164
+ this.emitHttp("cancelled", { id: task.id });
165
+ }
166
+ }
167
+ this.domainQueues.clear();
168
+ this.domainPending.clear();
169
+ }
170
+ destroy() {
171
+ this.clearHttp();
172
+ this.httpEventHandlers.clear();
173
+ super.destroy();
174
+ }
175
+ insertHttpTask(task) {
176
+ const domain = task.domain ?? "default";
177
+ if (!this.domainQueues.has(domain)) {
178
+ this.domainQueues.set(domain, []);
179
+ }
180
+ const queue = this.domainQueues.get(domain);
181
+ let insertIndex = queue.length;
182
+ for (let i = 0;i < queue.length; i++) {
183
+ if (task.priority > queue[i].priority) {
184
+ insertIndex = i;
185
+ break;
186
+ }
187
+ }
188
+ queue.splice(insertIndex, 0, task);
189
+ }
190
+ getDomainLimit(domain) {
191
+ const specificLimit = this.domainConcurrencyLimits.get(domain);
192
+ if (specificLimit !== undefined)
193
+ return specificLimit;
194
+ if (typeof this.httpConfig.domainConcurrency === "number") {
195
+ return this.httpConfig.domainConcurrency;
196
+ }
197
+ return 1 / 0;
198
+ }
199
+ canRunDomain(domain) {
200
+ if (this.domainPaused.get(domain))
201
+ return false;
202
+ const rateLimitedUntil = this.domainRateLimited.get(domain);
203
+ if (rateLimitedUntil && Date.now() < rateLimitedUntil)
204
+ return false;
205
+ const pending = this.domainPending.get(domain) ?? 0;
206
+ const limit = this.getDomainLimit(domain);
207
+ return pending < limit;
208
+ }
209
+ tryRunHttpNext() {
210
+ if (this.isPaused)
211
+ return;
212
+ for (const [domain, queue] of this.domainQueues.entries()) {
213
+ if (queue.length === 0)
214
+ continue;
215
+ if (!this.canRunDomain(domain))
216
+ continue;
217
+ const task = queue.shift();
218
+ this.runHttpTask(task);
219
+ }
220
+ }
221
+ async runHttpTask(task) {
222
+ const domain = task.domain ?? "default";
223
+ const pending = (this.domainPending.get(domain) ?? 0) + 1;
224
+ this.domainPending.set(domain, pending);
225
+ this.ensureDomainStats(domain);
226
+ this.httpStatsData.byDomain[domain].pending++;
227
+ this.emitHttp("start", { id: task.id });
228
+ const startTime = Date.now();
229
+ let timeoutId;
230
+ try {
231
+ let result;
232
+ if (task.timeout && task.timeout > 0) {
233
+ result = await Promise.race([
234
+ task.fn(),
235
+ new Promise((_, reject) => {
236
+ timeoutId = setTimeout(() => {
237
+ this.emitHttp("timeout", { id: task.id });
238
+ reject(new Error(`Task ${task.id} timed out after ${task.timeout}ms`));
239
+ }, task.timeout);
240
+ })
241
+ ]);
242
+ } else {
243
+ result = await task.fn();
244
+ }
245
+ if (timeoutId)
246
+ clearTimeout(timeoutId);
247
+ const duration = Date.now() - startTime;
248
+ this.httpStatsData.byDomain[domain].completed++;
249
+ this.emitHttp("completed", { id: task.id, result, duration });
250
+ task.resolve(result);
251
+ } catch (error) {
252
+ if (timeoutId)
253
+ clearTimeout(timeoutId);
254
+ const shouldRetry = task.retryCount < task.maxRetries;
255
+ if (shouldRetry) {
256
+ task.retryCount++;
257
+ this.httpStatsData.retries++;
258
+ const delay = this.getRetryDelay(task);
259
+ this.emitHttp("retry", {
260
+ id: task.id,
261
+ attempt: task.retryCount,
262
+ error
263
+ });
264
+ setTimeout(() => {
265
+ this.insertHttpTask(task);
266
+ this.tryRunHttpNext();
267
+ }, delay);
268
+ } else {
269
+ this.httpStatsData.byDomain[domain].failed++;
270
+ this.emitHttp("error", { id: task.id, error });
271
+ task.reject(error);
272
+ }
273
+ } finally {
274
+ const newPending = (this.domainPending.get(domain) ?? 1) - 1;
275
+ this.domainPending.set(domain, newPending);
276
+ this.httpStatsData.byDomain[domain].pending = newPending;
277
+ this.tryRunHttpNext();
278
+ }
279
+ }
280
+ getRetryDelay(task) {
281
+ if (task.retryDelay !== undefined) {
282
+ return task.retryDelay;
283
+ }
284
+ const baseDelay = typeof this.httpConfig.retryDelay === "function" ? this.httpConfig.retryDelay(task.retryCount) : this.httpConfig.retryDelay;
285
+ return baseDelay * Math.pow(2, task.retryCount - 1);
286
+ }
287
+ ensureDomainStats(domain) {
288
+ if (!this.httpStatsData.byDomain[domain]) {
289
+ this.httpStatsData.byDomain[domain] = {
290
+ pending: 0,
291
+ completed: 0,
292
+ failed: 0,
293
+ rateLimited: 0
294
+ };
295
+ }
296
+ }
297
+ emitHttp(event, data) {
298
+ const handlers = this.httpEventHandlers.get(event);
299
+ if (handlers) {
300
+ for (const handler of handlers) {
301
+ try {
302
+ handler(data);
303
+ } catch {}
304
+ }
305
+ }
306
+ if (event in this.config) {
307
+ this.emit(event, data);
308
+ }
309
+ }
310
+ }
311
+
312
+ exports.extractDomain = extractDomain;
313
+ exports.HttpQueue = HttpQueue;