Proxy Manager
The ProxyManager class provides enterprise-grade proxy pool management. It handles rotation strategies, automatic health monitoring, cooldown and recovery, URL-based filtering, and a comprehensive hook system for proxy lifecycle events.
Quick Start
import { Rezo, ProxyManager } from 'rezo';
const manager = new ProxyManager({
rotation: 'random',
proxies: [
'http://proxy1.example.com:8080',
'http://proxy2.example.com:8080',
'socks5://proxy3.example.com:1080'
],
autoDisableDeadProxies: true,
maxFailures: 3
});
// Use with Rezo
const rezo = new Rezo({ proxyManager: manager });
await rezo.get('https://api.example.com/data'); Rotation Strategies
Random
Select a random proxy from the active pool for each request:
const manager = new ProxyManager({
rotation: 'random',
proxies: [
'http://proxy1.example.com:8080',
'http://proxy2.example.com:8080',
'http://proxy3.example.com:8080'
]
});
const proxy = manager.next('https://api.example.com'); Sequential
Cycle through proxies in order. Optionally rotate after N requests per proxy:
const manager = new ProxyManager({
rotation: 'sequential',
requestsPerProxy: 5, // Use each proxy for 5 requests, then move to the next
proxies: [
'http://proxy1.example.com:8080',
'http://proxy2.example.com:8080',
'http://proxy3.example.com:8080'
]
}); With requestsPerProxy: 1 (the default), each request uses the next proxy in the list.
Per-Proxy Limit
Each proxy is used for a maximum number of total requests, then permanently removed from the pool:
const manager = new ProxyManager({
rotation: 'per-proxy-limit',
limit: 100, // Each proxy handles at most 100 requests
proxies: [
'http://proxy1.example.com:8080',
'http://proxy2.example.com:8080'
]
}); Proxy Selection
next()
Returns the selected proxy for a URL, or null if the request should go direct:
const proxy = manager.next('https://api.example.com/data');
if (proxy) {
console.log(`Using ${proxy.protocol}://${proxy.host}:${proxy.port}`);
} select()
Returns a detailed selection result with the reason:
const result = manager.select('https://api.example.com/data');
console.log(result.proxy); // ProxyInfo | null
console.log(result.reason); // 'selected' | 'whitelist-no-match' | 'blacklist-match' | 'no-proxies-available' Whitelist and Blacklist Filtering
Control which URLs use the proxy pool. Whitelist and blacklist accept strings (domain matching) or RegExp patterns.
const manager = new ProxyManager({
rotation: 'random',
proxies: ['http://proxy.example.com:8080'],
// Only proxy requests to these domains
whitelist: [
'api.example.com', // Exact match + subdomains
/^https://secure./ // RegExp match against full URL
],
// Never proxy requests to these domains (checked after whitelist)
blacklist: [
'internal.example.com',
/localhost/
]
});
manager.shouldProxy('https://api.example.com/data'); // true
manager.shouldProxy('https://internal.example.com/data'); // false
manager.shouldProxy('https://other.example.com/data'); // false (not in whitelist) String patterns support subdomain matching: 'example.com' matches both example.com and api.example.com.
Health Monitoring
Automatically disable proxies that exceed a failure threshold:
const manager = new ProxyManager({
rotation: 'random',
proxies: ['http://proxy1.example.com:8080', 'http://proxy2.example.com:8080'],
autoDisableDeadProxies: true,
maxFailures: 3 // Disable after 3 consecutive failures (default)
}); Report successes and failures after each request:
const proxy = manager.next(url);
try {
const response = await fetch(url, { agent: createProxyAgent(proxy) });
manager.reportSuccess(proxy);
} catch (error) {
manager.reportFailure(proxy, error, url);
} A successful request resets the consecutive failure counter for that proxy.
Cooldown Recovery
Instead of permanently disabling failed proxies, configure a cooldown period so they automatically re-enable:
const manager = new ProxyManager({
rotation: 'random',
proxies: ['http://proxy1.example.com:8080', 'http://proxy2.example.com:8080'],
autoDisableDeadProxies: true,
maxFailures: 3,
cooldown: {
enabled: true,
durationMs: 60_000 // Re-enable after 60 seconds
}
});
// Shorthand:
const manager2 = new ProxyManager({
rotation: 'random',
proxies: ['http://proxy1.example.com:8080'],
autoDisableDeadProxies: true,
cooldownPeriod: 60_000 // Equivalent to cooldown: { enabled: true, durationMs: 60000 }
}); After the cooldown expires, the proxy is re-enabled with its failure counter reset.
waitForProxy()
When all proxies are in cooldown, waitForProxy() returns a promise that resolves when the first proxy recovers:
const proxy = manager.next(url);
if (!proxy && manager.isCoolingDown()) {
console.log(`Next proxy available in ${manager.nextCooldownMs()}ms`);
const recovered = await manager.waitForProxy();
console.log('Recovered proxy:', recovered.host);
} If no proxies are in cooldown (pool is permanently exhausted), the promise rejects.
Helper Methods
manager.hasAvailableProxies(); // true if any proxy is active
manager.isCoolingDown(); // true if any proxy is in cooldown
manager.nextCooldownMs(); // ms until next proxy recovers (0 if available, -1 if none recovering) Proxy Lifecycle Hooks
ProxyManager provides 8 hook points for observing and controlling proxy lifecycle events. Hooks can be set via the constructor or the hooks property.
Via Constructor
const manager = new ProxyManager({
rotation: 'random',
proxies: ['http://proxy1.example.com:8080'],
hooks: {
afterProxySelect: [(ctx) => {
console.log(`Selected proxy for ${ctx.url}: ${ctx.proxy?.host}`);
}],
afterProxyDisable: [(ctx) => {
console.log(`Proxy ${ctx.proxy.host} disabled: ${ctx.reason}`);
}]
}
}); Via Property
manager.hooks.beforeProxySelect.push((ctx) => {
// Return a ProxyInfo to override selection
if (ctx.url.includes('/priority-api/')) {
return ctx.proxies[0]; // Always use first proxy for priority APIs
}
}); Hook Reference
beforeProxySelect
Runs before proxy selection. Return a ProxyInfo to override the normal selection logic.
manager.hooks.beforeProxySelect.push((ctx) => {
// ctx.url - Request URL
// ctx.proxies - Available active proxies
// ctx.isRetry - Whether this is a retry attempt
// ctx.retryCount - Current retry count
return specificProxy; // or void for normal selection
}); afterProxySelect
Runs after a proxy is selected (or when going direct).
manager.hooks.afterProxySelect.push((ctx) => {
// ctx.url - Request URL
// ctx.proxy - Selected proxy (null if direct)
// ctx.reason - 'selected' | 'whitelist-no-match' | 'blacklist-match' | 'no-proxies-available'
}); beforeProxyError
Runs before a failure is recorded.
manager.hooks.beforeProxyError.push((ctx) => {
// ctx.proxy - The proxy that failed
// ctx.error - The error
// ctx.url - Request URL
// ctx.failureCount - Failure count after this error
// ctx.willBeDisabled - Whether the proxy will be disabled
}); afterProxyError
Runs after a failure is processed.
manager.hooks.afterProxyError.push((ctx) => {
// ctx.proxy - The proxy that failed
// ctx.error - The error
// ctx.action - 'retry-next-proxy' | 'disabled' | 'continue'
}); beforeProxyDisable
Runs before a proxy is disabled. Return false to prevent disabling.
manager.hooks.beforeProxyDisable.push((ctx) => {
// ctx.proxy - Proxy about to be disabled
// ctx.reason - 'dead' | 'limit-reached' | 'manual'
// ctx.state - Current proxy state
if (ctx.proxy.label === 'premium') {
return false; // Keep premium proxies active
}
}); afterProxyDisable
Runs after a proxy is disabled.
manager.hooks.afterProxyDisable.push((ctx) => {
// ctx.proxy - Disabled proxy
// ctx.reason - 'dead' | 'limit-reached' | 'manual'
// ctx.hasCooldown - Whether cooldown is enabled
// ctx.reenableAt - Timestamp when proxy will re-enable (if cooldown)
alertSystem.notify(`Proxy ${ctx.proxy.host} disabled: ${ctx.reason}`);
}); afterProxyRotate
Runs when the active proxy changes (sequential rotation or failure-based switch).
manager.hooks.afterProxyRotate.push((ctx) => {
// ctx.from - Previous proxy (null if first selection)
// ctx.to - New proxy
// ctx.reason - 'scheduled' | 'failure' | 'limit-reached'
}); afterProxyEnable
Runs when a proxy is re-enabled (cooldown expired or manual).
manager.hooks.afterProxyEnable.push((ctx) => {
// ctx.proxy - The re-enabled proxy
// ctx.reason - 'cooldown-expired' | 'manual'
}); afterProxySuccess
Runs when a request succeeds through a proxy.
manager.hooks.afterProxySuccess.push((ctx) => {
// ctx.proxy - The proxy that succeeded
// ctx.state - Current proxy state (requestCount, successCount, etc.)
}); onNoProxiesAvailable
Runs when no proxies are available for a request. Use for alerting or triggering proxy pool refresh.
manager.hooks.onNoProxiesAvailable.push((ctx) => {
// ctx.url - Request URL
// ctx.error - The error being thrown
// ctx.allProxies - All proxy states
// ctx.activeCount, ctx.disabledCount, ctx.cooldownCount
// ctx.disabledReasons - { dead: N, limitReached: N, manual: N }
// ctx.timestamp
alertSystem.critical('Proxy pool exhausted', ctx.disabledReasons);
}); Pool Management
Adding Proxies
// Single proxy
manager.add('http://new-proxy.example.com:8080');
manager.add({ protocol: 'socks5', host: '127.0.0.1', port: 1080 });
// Multiple proxies
manager.add([
'http://proxy4.example.com:8080',
'http://proxy5.example.com:8080'
]); Removing Proxies
manager.remove(proxy); // Remove by reference
manager.removeById(proxyId); // Remove by ID Resetting
Re-enable all proxies and reset all counters:
manager.reset(); Clearing
Remove all proxies and reset counters (pool becomes empty):
manager.clear(); Manually Disabling / Enabling
manager.disableProxy(proxy, 'manual');
manager.enableProxy(proxy, 'manual'); getStatus()
Get a complete snapshot of the proxy pool:
const status = manager.getStatus();
console.log(status);
// {
// active: [ProxyInfo, ...],
// disabled: [ProxyInfo, ...],
// cooldown: [ProxyInfo, ...],
// total: 5,
// rotation: 'random',
// totalRequests: 142,
// totalSuccesses: 138,
// totalFailures: 4
// } Individual Proxy State
const state = manager.getProxyState(proxy);
console.log(state);
// {
// proxy: { ... },
// requestCount: 47,
// failureCount: 0,
// successCount: 45,
// totalFailures: 2,
// isActive: true,
// lastSuccessAt: 1712150400000,
// lastFailureAt: 1712149200000
// } Aggregate Properties
manager.size; // Total proxies in pool
manager.totalRequests; // Total requests routed
manager.totalSuccesses; // Total successes
manager.totalFailures; // Total failures Cleanup
Destroy the manager when finished to clean up cooldown timers:
manager.destroy(); Unlike clear(), destroy() fully tears down the instance. Use clear() if you plan to add new proxies later.