@emircansahin/ghostfetch 0.1.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.
@@ -0,0 +1,411 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.GhostFetch = void 0;
7
+ const cycletls_1 = __importDefault(require("cycletls"));
8
+ const proxy_manager_1 = require("./proxy-manager");
9
+ const classifier_1 = require("./classifier");
10
+ const errors_1 = require("./errors");
11
+ const DEFAULT_DELAYS = [1000, 2000, 4000];
12
+ const DEFAULT_TIMEOUT = 30000;
13
+ const HEALTH_BATCH_CONCURRENCY = 10;
14
+ const HEALTH_RETRY_DELAYS = [0, 15000, 60000]; // immediate, +15s, +60s
15
+ class GhostFetch {
16
+ constructor(config = {}) {
17
+ this.cycleTLS = null;
18
+ this.initPromise = null;
19
+ this.interceptors = [];
20
+ this.refreshTimer = null;
21
+ this.refreshing = false;
22
+ this.healthCheckPromise = null;
23
+ this.config = config;
24
+ // Start with empty proxy list — healthCheck will populate it
25
+ this.proxyManager = new proxy_manager_1.ProxyManager([], config.ban);
26
+ this.retryDefaults = { delays: config.retry?.delays ?? DEFAULT_DELAYS };
27
+ // Auto health check on init if proxies provided
28
+ if (config.proxies?.length) {
29
+ this.healthCheckPromise = this.healthCheckProxies(config.proxies).catch((err) => {
30
+ // Prevent unhandled rejection — return empty result so ready() resolves safely
31
+ return { total: config.proxies.length, healthy: 0, dead: config.proxies.length, countries: {}, proxies: {} };
32
+ });
33
+ }
34
+ // Start proxy refresh interval only if both callback and interval are provided
35
+ if (config.onProxyRefresh && config.proxyRefreshInterval) {
36
+ this.refreshTimer = setInterval(() => this.refreshProxies(), config.proxyRefreshInterval);
37
+ }
38
+ }
39
+ /**
40
+ * Wait until the initial health check is complete.
41
+ * Returns health check results with proxy details and country info.
42
+ * Requests automatically wait for this internally.
43
+ *
44
+ * @example
45
+ * const result = await client.ready();
46
+ * console.log(result);
47
+ * // {
48
+ * // total: 10, healthy: 8, dead: 2,
49
+ * // countries: { US: 3, DE: 5 },
50
+ * // proxies: { 'http://...@host:8001': 'US', 'http://...@host:8002': 'DE', ... }
51
+ * // }
52
+ */
53
+ async ready() {
54
+ if (this.healthCheckPromise)
55
+ return this.healthCheckPromise;
56
+ return { total: 0, healthy: 0, dead: 0, countries: {}, proxies: {} };
57
+ }
58
+ /** Lazy-initialize CycleTLS instance. */
59
+ async ensureClient() {
60
+ if (this.cycleTLS)
61
+ return this.cycleTLS;
62
+ if (!this.initPromise) {
63
+ this.initPromise = (0, cycletls_1.default)().then((client) => {
64
+ this.cycleTLS = client;
65
+ });
66
+ }
67
+ await this.initPromise;
68
+ return this.cycleTLS;
69
+ }
70
+ /** Add a custom interceptor for site-specific error handling. */
71
+ addInterceptor(interceptor) {
72
+ this.interceptors.push(interceptor);
73
+ }
74
+ /** Remove an interceptor by name. */
75
+ removeInterceptor(name) {
76
+ this.interceptors = this.interceptors.filter((i) => i.name !== name);
77
+ }
78
+ /** Manually refresh the proxy list via the onProxyRefresh callback. */
79
+ async refreshProxies() {
80
+ if (!this.config.onProxyRefresh)
81
+ return;
82
+ if (this.refreshing)
83
+ return; // prevent overlapping refreshes
84
+ // Wait for any in-progress health check (e.g., initial startup)
85
+ if (this.healthCheckPromise)
86
+ await this.healthCheckPromise;
87
+ this.refreshing = true;
88
+ try {
89
+ const proxies = await this.config.onProxyRefresh();
90
+ // Health check first — only replace if it succeeds
91
+ // healthCheckProxies calls replaceProxies internally with healthy list
92
+ await this.healthCheckProxies(proxies);
93
+ }
94
+ finally {
95
+ this.refreshing = false;
96
+ }
97
+ }
98
+ /** Get proxy manager stats. */
99
+ get stats() {
100
+ return {
101
+ totalProxies: this.proxyManager.total,
102
+ availableProxies: this.proxyManager.available,
103
+ bannedProxies: this.proxyManager.banned,
104
+ };
105
+ }
106
+ // --- HTTP methods ---
107
+ async get(url, options) {
108
+ return this.request('GET', url, options);
109
+ }
110
+ async post(url, options) {
111
+ return this.request('POST', url, options);
112
+ }
113
+ async put(url, options) {
114
+ return this.request('PUT', url, options);
115
+ }
116
+ async delete(url, options) {
117
+ return this.request('DELETE', url, options);
118
+ }
119
+ async patch(url, options) {
120
+ return this.request('PATCH', url, options);
121
+ }
122
+ async head(url, options) {
123
+ return this.request('HEAD', url, options);
124
+ }
125
+ // --- Core request logic ---
126
+ async request(method, url, options) {
127
+ // Wait for health check to finish before first request
128
+ await this.ready();
129
+ const delays = options?.retry?.delays ?? this.retryDefaults.delays ?? DEFAULT_DELAYS;
130
+ const maxAttempts = delays.length + 1; // first attempt + retries
131
+ const forceProxy = options?.forceProxy ?? this.config.forceProxy ?? false;
132
+ let lastError = null;
133
+ let lastFailedProxy = null;
134
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
135
+ // Wait before retry (not on first attempt)
136
+ if (attempt > 0) {
137
+ await sleep(delays[attempt - 1]);
138
+ }
139
+ // Pick a proxy
140
+ const proxy = options?.proxy ?? await this.pickProxy(forceProxy, lastFailedProxy, options?.country);
141
+ try {
142
+ const response = await this.executeRequest(method, url, proxy, options);
143
+ // 1. Per-request interceptor takes highest priority
144
+ if (options?.interceptor) {
145
+ const reqAction = options.interceptor.check(response);
146
+ if (reqAction !== null) {
147
+ const result = this.handleInterceptorAction(reqAction, 'request', response, proxy);
148
+ if (result === 'return') {
149
+ return response;
150
+ }
151
+ lastError = result.error;
152
+ lastFailedProxy = proxy;
153
+ continue;
154
+ }
155
+ // null → fall through to instance interceptors
156
+ }
157
+ // 2. Instance-level interceptors — first match takes full ownership
158
+ const { matched, action, interceptor } = (0, classifier_1.checkInterceptors)(url, response, [...this.interceptors]);
159
+ if (matched) {
160
+ if (action === 'skip' || action === null) {
161
+ if (proxy)
162
+ this.proxyManager.reportSuccess(proxy);
163
+ return response;
164
+ }
165
+ const result = this.handleInterceptorAction(action, interceptor?.name ?? 'unnamed', response, proxy);
166
+ if (result === 'return') {
167
+ return response;
168
+ }
169
+ lastError = result.error;
170
+ lastFailedProxy = proxy;
171
+ continue;
172
+ }
173
+ // 3. Cloudflare JS challenge (only when no interceptor claimed the response)
174
+ if ((0, classifier_1.isCloudflareChallenge)(response)) {
175
+ throw new errors_1.CloudflareJSChallengeError(url, proxy ?? undefined);
176
+ }
177
+ // 4. No interceptor matched → check default retry statuses (429, 503, 407)
178
+ const defaultRetry = (0, classifier_1.checkDefaultRetryStatus)(response.status);
179
+ if (defaultRetry) {
180
+ if (proxy) {
181
+ if (defaultRetry === 'proxy') {
182
+ this.proxyManager.reportFailure(proxy);
183
+ }
184
+ else {
185
+ this.proxyManager.reportSuccess(proxy);
186
+ }
187
+ }
188
+ lastError = new errors_1.GhostFetchRequestError({
189
+ type: defaultRetry,
190
+ message: `HTTP ${response.status}`,
191
+ status: response.status,
192
+ body: response.body,
193
+ proxy: proxy ?? undefined,
194
+ });
195
+ lastFailedProxy = proxy;
196
+ continue;
197
+ }
198
+ // 4. Normal response — return it
199
+ if (proxy)
200
+ this.proxyManager.reportSuccess(proxy);
201
+ return response;
202
+ }
203
+ catch (error) {
204
+ if (error instanceof errors_1.CloudflareJSChallengeError) {
205
+ throw error;
206
+ }
207
+ if (error instanceof errors_1.NoProxyAvailableError) {
208
+ throw error;
209
+ }
210
+ const errorType = (0, classifier_1.classifyError)(error);
211
+ const requestError = error instanceof errors_1.GhostFetchRequestError
212
+ ? error
213
+ : new errors_1.GhostFetchRequestError({
214
+ type: errorType,
215
+ message: error instanceof Error ? error.message : String(error),
216
+ proxy: proxy ?? undefined,
217
+ cause: error,
218
+ });
219
+ if (proxy) {
220
+ if (errorType === 'proxy') {
221
+ this.proxyManager.reportFailure(proxy);
222
+ }
223
+ else if (errorType === 'server') {
224
+ this.proxyManager.reportSuccess(proxy);
225
+ }
226
+ // 'ambiguous' — do nothing
227
+ }
228
+ lastError = requestError;
229
+ lastFailedProxy = proxy;
230
+ }
231
+ }
232
+ throw new errors_1.MaxRetriesExceededError(maxAttempts, lastError);
233
+ }
234
+ /**
235
+ * Pick a proxy based on forceProxy and country settings.
236
+ * - If forceProxy: wait until a proxy is available (blocks)
237
+ * - If !forceProxy: return null if none available (proceed without proxy)
238
+ */
239
+ async pickProxy(forceProxy, exclude, country) {
240
+ const opts = { exclude, country };
241
+ const proxy = this.proxyManager.getProxy(opts);
242
+ if (proxy)
243
+ return proxy;
244
+ // No proxy available
245
+ if (this.proxyManager.total === 0) {
246
+ // No proxies configured at all
247
+ if (forceProxy)
248
+ throw new errors_1.NoProxyAvailableError();
249
+ return null;
250
+ }
251
+ // Proxies exist but all banned or none match the country filter
252
+ if (forceProxy) {
253
+ // If filtering by country and no proxies exist for that country, fail immediately
254
+ if (country && this.proxyManager.getProxiesByCountry(country).length === 0) {
255
+ throw new errors_1.NoProxyAvailableError();
256
+ }
257
+ // Wait until one becomes available (ban expires or refresh happens)
258
+ return this.proxyManager.waitForProxy(opts);
259
+ }
260
+ // Not forced — proceed without proxy
261
+ return null;
262
+ }
263
+ /** Handle an interceptor action (retry/ban/skip). Returns 'return' to send response, or { error } to continue retry loop. */
264
+ handleInterceptorAction(action, name, response, proxy) {
265
+ if (action === 'skip') {
266
+ if (proxy)
267
+ this.proxyManager.reportSuccess(proxy);
268
+ return 'return';
269
+ }
270
+ if (action === 'ban') {
271
+ if (proxy)
272
+ this.proxyManager.reportFailure(proxy);
273
+ return {
274
+ error: new errors_1.GhostFetchRequestError({
275
+ type: 'proxy',
276
+ message: `Interceptor "${name}": ban (HTTP ${response.status})`,
277
+ status: response.status,
278
+ body: response.body,
279
+ proxy: proxy ?? undefined,
280
+ }),
281
+ };
282
+ }
283
+ // retry
284
+ if (proxy)
285
+ this.proxyManager.reportSuccess(proxy);
286
+ return {
287
+ error: new errors_1.GhostFetchRequestError({
288
+ type: 'server',
289
+ message: `Interceptor "${name}": retry (HTTP ${response.status})`,
290
+ status: response.status,
291
+ body: response.body,
292
+ proxy: proxy ?? undefined,
293
+ }),
294
+ };
295
+ }
296
+ async executeRequest(method, url, proxy, options) {
297
+ const client = await this.ensureClient();
298
+ const timeout = options?.timeout ?? this.config.timeout ?? DEFAULT_TIMEOUT;
299
+ const headers = {
300
+ ...this.config.headers,
301
+ ...options?.headers,
302
+ };
303
+ const cycleTLSOptions = {
304
+ headers,
305
+ timeout,
306
+ disableRedirect: false,
307
+ };
308
+ if (proxy) {
309
+ cycleTLSOptions.proxy = proxy;
310
+ }
311
+ if (this.config.ja3) {
312
+ cycleTLSOptions.ja3 = this.config.ja3;
313
+ }
314
+ if (this.config.userAgent) {
315
+ cycleTLSOptions.userAgent = this.config.userAgent;
316
+ }
317
+ if (options?.body) {
318
+ cycleTLSOptions.body = typeof options.body === 'string'
319
+ ? options.body
320
+ : JSON.stringify(options.body);
321
+ if (typeof options.body !== 'string') {
322
+ const hasContentType = Object.keys(headers).some((k) => k.toLowerCase() === 'content-type');
323
+ if (!hasContentType) {
324
+ headers['content-type'] = 'application/json';
325
+ }
326
+ }
327
+ }
328
+ const response = await client(url, cycleTLSOptions, method.toLowerCase());
329
+ return {
330
+ status: response.status,
331
+ headers: (response.headers ?? {}),
332
+ body: response.body == null ? '' : typeof response.body === 'string' ? response.body : JSON.stringify(response.body),
333
+ url: response.finalUrl || url,
334
+ };
335
+ }
336
+ /**
337
+ * Health check + country resolution for proxies.
338
+ * Each proxy gets up to 3 attempts (0s, +15s, +60s) to reach ipinfo.io.
339
+ * Healthy proxies are added to the pool with their country code.
340
+ * Failed proxies are discarded.
341
+ */
342
+ async healthCheckProxies(proxies) {
343
+ const client = await this.ensureClient();
344
+ const healthy = [];
345
+ const proxyDetails = {};
346
+ const countries = {};
347
+ const countryEntries = [];
348
+ // Process in batches
349
+ for (let i = 0; i < proxies.length; i += HEALTH_BATCH_CONCURRENCY) {
350
+ const batch = proxies.slice(i, i + HEALTH_BATCH_CONCURRENCY);
351
+ await Promise.allSettled(batch.map(async (proxy) => {
352
+ for (let attempt = 0; attempt < HEALTH_RETRY_DELAYS.length; attempt++) {
353
+ if (attempt > 0) {
354
+ await sleep(HEALTH_RETRY_DELAYS[attempt]);
355
+ }
356
+ try {
357
+ const res = await client('https://ipinfo.io/json', { proxy, timeout: 10000, headers: {} }, 'get');
358
+ const data = typeof res.body === 'string' ? JSON.parse(res.body) : res.body;
359
+ const country = data.country ?? null;
360
+ if (country) {
361
+ countryEntries.push([proxy, country]);
362
+ countries[country] = (countries[country] ?? 0) + 1;
363
+ }
364
+ proxyDetails[proxy] = country;
365
+ healthy.push(proxy);
366
+ return;
367
+ }
368
+ catch {
369
+ // Will retry or discard
370
+ }
371
+ }
372
+ // All 3 attempts failed — proxy is dead
373
+ proxyDetails[proxy] = null;
374
+ }));
375
+ }
376
+ // Add only healthy proxies to the manager, then restore country data
377
+ this.proxyManager.replaceProxies(healthy);
378
+ for (const [proxy, country] of countryEntries) {
379
+ this.proxyManager.setCountry(proxy, country);
380
+ }
381
+ return {
382
+ total: proxies.length,
383
+ healthy: healthy.length,
384
+ dead: proxies.length - healthy.length,
385
+ countries,
386
+ proxies: proxyDetails,
387
+ };
388
+ }
389
+ /** Gracefully shut down the CycleTLS instance and clear timers. */
390
+ async destroy() {
391
+ if (this.refreshTimer) {
392
+ clearInterval(this.refreshTimer);
393
+ this.refreshTimer = null;
394
+ }
395
+ if (this.cycleTLS) {
396
+ try {
397
+ await this.cycleTLS.exit();
398
+ }
399
+ catch {
400
+ // CycleTLS may throw ESRCH when the Go process is already gone
401
+ }
402
+ this.cycleTLS = null;
403
+ this.initPromise = null;
404
+ }
405
+ }
406
+ }
407
+ exports.GhostFetch = GhostFetch;
408
+ function sleep(ms) {
409
+ return new Promise((resolve) => setTimeout(resolve, ms));
410
+ }
411
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MaxRetriesExceededError = exports.NoProxyAvailableError = exports.CloudflareJSChallengeError = exports.GhostFetchRequestError = void 0;
4
+ class GhostFetchRequestError extends Error {
5
+ constructor(opts) {
6
+ super(opts.message);
7
+ this.name = 'GhostFetchRequestError';
8
+ this.type = opts.type;
9
+ this.status = opts.status;
10
+ this.body = opts.body;
11
+ this.proxy = opts.proxy;
12
+ this.cause = opts.cause;
13
+ }
14
+ }
15
+ exports.GhostFetchRequestError = GhostFetchRequestError;
16
+ class CloudflareJSChallengeError extends GhostFetchRequestError {
17
+ constructor(url, proxy) {
18
+ super({
19
+ type: 'server',
20
+ message: `Cloudflare JS challenge detected at ${url}. This requires a headless browser (e.g. puppeteer-extra with stealth plugin).`,
21
+ proxy,
22
+ });
23
+ this.name = 'CloudflareJSChallengeError';
24
+ }
25
+ }
26
+ exports.CloudflareJSChallengeError = CloudflareJSChallengeError;
27
+ class NoProxyAvailableError extends Error {
28
+ constructor() {
29
+ super('No proxies available — all proxies are banned or the proxy list is empty.');
30
+ this.name = 'NoProxyAvailableError';
31
+ }
32
+ }
33
+ exports.NoProxyAvailableError = NoProxyAvailableError;
34
+ class MaxRetriesExceededError extends Error {
35
+ constructor(attempts, lastError) {
36
+ super(`Max retries exceeded (${attempts} attempts). Last error: ${lastError.message}`);
37
+ this.name = 'MaxRetriesExceededError';
38
+ this.lastError = lastError;
39
+ this.attempts = attempts;
40
+ }
41
+ }
42
+ exports.MaxRetriesExceededError = MaxRetriesExceededError;
43
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":";;;AAEA,MAAa,sBAAuB,SAAQ,KAAK;IAO/C,YAAY,IAAqB;QAC/B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IAC1B,CAAC;CACF;AAhBD,wDAgBC;AAED,MAAa,0BAA2B,SAAQ,sBAAsB;IACpE,YAAY,GAAW,EAAE,KAAc;QACrC,KAAK,CAAC;YACJ,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,uCAAuC,GAAG,gFAAgF;YACnI,KAAK;SACN,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;IAC3C,CAAC;CACF;AATD,gEASC;AAED,MAAa,qBAAsB,SAAQ,KAAK;IAC9C;QACE,KAAK,CAAC,2EAA2E,CAAC,CAAC;QACnF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AALD,sDAKC;AAED,MAAa,uBAAwB,SAAQ,KAAK;IAIhD,YAAY,QAAgB,EAAE,SAAiC;QAC7D,KAAK,CAAC,yBAAyB,QAAQ,2BAA2B,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC;QACvF,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;QACtC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAVD,0DAUC"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MaxRetriesExceededError = exports.NoProxyAvailableError = exports.CloudflareJSChallengeError = exports.GhostFetchRequestError = exports.ProxyManager = exports.GhostFetch = void 0;
4
+ var client_1 = require("./client");
5
+ Object.defineProperty(exports, "GhostFetch", { enumerable: true, get: function () { return client_1.GhostFetch; } });
6
+ var proxy_manager_1 = require("./proxy-manager");
7
+ Object.defineProperty(exports, "ProxyManager", { enumerable: true, get: function () { return proxy_manager_1.ProxyManager; } });
8
+ var errors_1 = require("./errors");
9
+ Object.defineProperty(exports, "GhostFetchRequestError", { enumerable: true, get: function () { return errors_1.GhostFetchRequestError; } });
10
+ Object.defineProperty(exports, "CloudflareJSChallengeError", { enumerable: true, get: function () { return errors_1.CloudflareJSChallengeError; } });
11
+ Object.defineProperty(exports, "NoProxyAvailableError", { enumerable: true, get: function () { return errors_1.NoProxyAvailableError; } });
12
+ Object.defineProperty(exports, "MaxRetriesExceededError", { enumerable: true, get: function () { return errors_1.MaxRetriesExceededError; } });
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAAsC;AAA7B,oGAAA,UAAU,OAAA;AACnB,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AACrB,mCAKkB;AAJhB,gHAAA,sBAAsB,OAAA;AACtB,oHAAA,0BAA0B,OAAA;AAC1B,+GAAA,qBAAqB,OAAA;AACrB,iHAAA,uBAAuB,OAAA"}
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProxyManager = void 0;
4
+ const errors_1 = require("./errors");
5
+ /** Failures within this window (ms) from different requests count as 1. */
6
+ const DEDUP_WINDOW = 1000;
7
+ /** How often to poll for an available proxy when waiting (ms). */
8
+ const WAIT_POLL_INTERVAL = 2000;
9
+ const DEFAULT_BAN = {
10
+ maxFailures: 3,
11
+ duration: 60 * 60 * 1000, // 1 hour
12
+ };
13
+ class ProxyManager {
14
+ constructor(proxies, banConfig) {
15
+ this.proxies = [];
16
+ this.banMap = new Map();
17
+ this.countryMap = new Map(); // proxy → country code
18
+ this.proxies = [...proxies];
19
+ this.banConfig = banConfig === false ? false : { ...DEFAULT_BAN, ...banConfig };
20
+ }
21
+ /**
22
+ * Get a random non-banned proxy.
23
+ * Supports exclude (skip last failed proxy) and country filter.
24
+ * Returns null if none available.
25
+ */
26
+ getProxy(opts) {
27
+ // Backwards compat: allow passing just exclude string
28
+ const { exclude, country } = typeof opts === 'string' || opts === null || opts === undefined
29
+ ? { exclude: opts ?? undefined, country: undefined }
30
+ : opts;
31
+ let available = this.getAvailableProxies();
32
+ // Filter by country if requested
33
+ if (country) {
34
+ const upper = country.toUpperCase();
35
+ available = available.filter((p) => this.countryMap.get(p) === upper);
36
+ }
37
+ const candidates = exclude
38
+ ? available.filter((p) => p !== exclude)
39
+ : available;
40
+ // If excluding leaves nothing but there are available proxies, fall back
41
+ const pool = candidates.length > 0 ? candidates : available;
42
+ if (pool.length === 0)
43
+ return null;
44
+ return pool[Math.floor(Math.random() * pool.length)];
45
+ }
46
+ /**
47
+ * Wait until a proxy becomes available (bans expire or list is refreshed).
48
+ * Resolves with the proxy string. Supports country filter.
49
+ *
50
+ * Automatically calculates timeout from the earliest ban expiry.
51
+ * If no ban will ever expire (shouldn't happen), times out after 5 minutes.
52
+ */
53
+ waitForProxy(opts) {
54
+ const immediate = this.getProxy(opts);
55
+ if (immediate)
56
+ return Promise.resolve(immediate);
57
+ // Calculate max wait from earliest ban expiry + buffer
58
+ const maxWait = this.getEarliestBanExpiry() ?? 5 * 60 * 1000;
59
+ return new Promise((resolve, reject) => {
60
+ const timeout = setTimeout(() => {
61
+ clearInterval(interval);
62
+ reject(new errors_1.NoProxyAvailableError());
63
+ }, maxWait);
64
+ const interval = setInterval(() => {
65
+ const proxy = this.getProxy(opts);
66
+ if (proxy) {
67
+ clearTimeout(timeout);
68
+ clearInterval(interval);
69
+ resolve(proxy);
70
+ }
71
+ }, WAIT_POLL_INTERVAL);
72
+ });
73
+ }
74
+ /** Get ms until the earliest ban expires, or null if no active bans. */
75
+ getEarliestBanExpiry() {
76
+ if (this.banConfig === false)
77
+ return null;
78
+ const now = Date.now();
79
+ let earliest = Infinity;
80
+ for (const [, entry] of this.banMap) {
81
+ if (!entry.bannedAt)
82
+ continue;
83
+ const expiresAt = entry.bannedAt + this.banConfig.duration;
84
+ const remaining = expiresAt - now;
85
+ if (remaining > 0 && remaining < earliest) {
86
+ earliest = remaining;
87
+ }
88
+ }
89
+ // Add 1s buffer so the ban is definitely expired when we check
90
+ return earliest === Infinity ? null : earliest + 1000;
91
+ }
92
+ /** Get all currently available (non-banned) proxies. */
93
+ getAvailableProxies() {
94
+ if (this.banConfig === false)
95
+ return [...this.proxies];
96
+ const now = Date.now();
97
+ const duration = this.banConfig.duration;
98
+ return this.proxies.filter((proxy) => {
99
+ const ban = this.banMap.get(proxy);
100
+ if (!ban)
101
+ return true;
102
+ if (!ban.bannedAt)
103
+ return true;
104
+ if (now - ban.bannedAt >= duration) {
105
+ this.banMap.delete(proxy);
106
+ return true;
107
+ }
108
+ return false;
109
+ });
110
+ }
111
+ /**
112
+ * Report a proxy failure. Returns true if the proxy got banned.
113
+ *
114
+ * Concurrent dedup: if the last failure was within DEDUP_WINDOW ms,
115
+ * this call is ignored (multiple parallel requests failing at the same
116
+ * moment count as a single failure).
117
+ */
118
+ reportFailure(proxy) {
119
+ if (this.banConfig === false)
120
+ return false;
121
+ const now = Date.now();
122
+ const entry = this.banMap.get(proxy);
123
+ if (entry && (now - entry.lastFailure) < DEDUP_WINDOW) {
124
+ // Check if actually still banned (not expired)
125
+ return entry.bannedAt > 0 && (now - entry.bannedAt) < this.banConfig.duration;
126
+ }
127
+ const failCount = (entry?.failCount ?? 0) + 1;
128
+ if (failCount >= this.banConfig.maxFailures) {
129
+ this.banMap.set(proxy, { bannedAt: now, failCount, lastFailure: now });
130
+ return true;
131
+ }
132
+ this.banMap.set(proxy, { bannedAt: 0, failCount, lastFailure: now });
133
+ return false;
134
+ }
135
+ /** Report a proxy success — resets its fail count. */
136
+ reportSuccess(proxy) {
137
+ this.banMap.delete(proxy);
138
+ }
139
+ /** Replace the proxy list and clear all bans + country data. */
140
+ replaceProxies(proxies) {
141
+ this.proxies = [...proxies];
142
+ this.banMap.clear();
143
+ this.countryMap.clear();
144
+ }
145
+ /** Set country for a proxy. */
146
+ setCountry(proxy, country) {
147
+ this.countryMap.set(proxy, country.toUpperCase());
148
+ }
149
+ /** Get country for a proxy (or undefined if not resolved). */
150
+ getCountry(proxy) {
151
+ return this.countryMap.get(proxy);
152
+ }
153
+ /** Get all proxies for a specific country. */
154
+ getProxiesByCountry(country) {
155
+ const upper = country.toUpperCase();
156
+ return this.proxies.filter((p) => this.countryMap.get(p) === upper);
157
+ }
158
+ /** Get total proxy count. */
159
+ get total() {
160
+ return this.proxies.length;
161
+ }
162
+ /** Get available (non-banned) proxy count. */
163
+ get available() {
164
+ return this.getAvailableProxies().length;
165
+ }
166
+ /** Get banned proxy count. */
167
+ get banned() {
168
+ return this.total - this.available;
169
+ }
170
+ }
171
+ exports.ProxyManager = ProxyManager;
172
+ //# sourceMappingURL=proxy-manager.js.map