@mark2messmore/proxy-rotator
v0.1.1
Published
Provider-agnostic proxy rotation for Node.js with built-in Webshare and BrightData support. Drop-in fetch wrapper with automatic retry, IP rotation, and block detection.
Maintainers
Readme
@mark2messmore/proxy-rotator
Provider-agnostic proxy rotation for Node.js. Drop-in fetch wrapper with automatic retry, IP rotation, block detection, and browser-fingerprint matching. Built-in support for Webshare and BrightData, plus a clean ProxyProvider interface for custom providers.
- Zero-config rotating endpoints — Webshare
-rotatesuffix and BrightData-session-modifiers handled for you - Automatic retry with block detection (status codes + challenge-page heuristics)
- Consistent browser fingerprints — matching
User-Agent,Sec-Ch-Ua-*, andAccept-*headers per session - Global dispatcher hook — route any SDK that uses
fetch(OpenAI,@google/genai, etc.) through rotating proxies - Node 20+ native — uses
undiciunder the hood, no legacyhttps-proxy-agentdependency
Install
npm install @mark2messmore/proxy-rotatorQuick start
import { ProxyRotator, BrightDataProvider, WebshareProvider } from '@mark2messmore/proxy-rotator';
const rotator = new ProxyRotator({
providers: [
new BrightDataProvider({
customerId: process.env.BRD_CUSTOMER_ID!,
zone: 'residential',
password: process.env.BRD_PASSWORD!,
country: 'us',
}),
new WebshareProvider({
username: process.env.WEBSHARE_USER!,
password: process.env.WEBSHARE_PASS!,
country: 'US',
}),
],
maxRetries: 3,
onRotate: (e) => console.log(`[${e.kind}] ${e.provider} attempt ${e.attempt}`),
});
const { response, bodyText, attempts } = await rotator.fetch('https://example.com');
console.log(`Got ${response.status} after ${attempts} attempts`);Providers
BrightData
new BrightDataProvider({
customerId: 'hl_abc12345', // from dashboard (no "brd-customer-" prefix)
zone: 'residential', // the zone name you created
password: '...',
country: 'us', // optional default country filter
session: 'sticky', // 'rotating' (default) or 'sticky'
apiToken: '...', // optional — needed for .usage()
});Supported zones: datacenter, isp, residential, mobile, unlocker, serp.
Webshare
new WebshareProvider({
username: '...',
password: '...',
mode: 'rotating', // 'rotating' (default, uses p.webshare.io:80 + -rotate suffix)
// 'list' (fetches /api/v2/proxy/list and round-robins)
country: 'US',
apiToken: '...', // required for mode:'list' and .usage()
});Routing SDKs through proxies
Some SDKs (like @google/genai) use global fetch with no proxy option. Use installGlobalProxy:
import { installGlobalProxy, BrightDataProvider } from '@mark2messmore/proxy-rotator';
import { GoogleGenAI } from '@google/genai';
const provider = new BrightDataProvider({ /* ... */ });
const dispose = installGlobalProxy(provider, { country: 'us' });
const ai = new GoogleGenAI({});
await ai.models.generateContent({
model: 'gemini-2.5-flash',
contents: 'hello',
});
dispose(); // restore default dispatcherCustom providers
Implement the ProxyProvider interface:
import type { ProxyProvider, NextProxyOptions, ProxyCredentials } from '@mark2messmore/proxy-rotator';
class MyCustomProvider implements ProxyProvider {
readonly name = 'my-provider';
async next(options: NextProxyOptions = {}): Promise<ProxyCredentials> {
return { url: 'http://user:[email protected]:8080' };
}
}API
new ProxyRotator(options)
| Option | Type | Default | Description |
|-----------------|----------------------------|------------------|-----------------------------------------------------|
| providers | ProxyProvider[] | required | Providers to rotate across |
| strategy | RotationStrategy | 'round-robin' | 'round-robin' | 'random' | 'least-recently-used' | 'weighted' |
| maxRetries | number | 3 | Attempts before throwing |
| timeoutMs | number | 30000 | Per-request timeout |
| userAgents | string[] | built-in list | User-agent pool (empty = disable UA rotation) |
| defaultHeaders| Record<string, string> | {} | Headers added to every request |
| onRotate | (e: RotateEvent) => void | — | Per-event hook for logging / metrics |
.fetch(url, opts?) => Promise<FetchResult>
FetchResult = { response, bodyText, proxy, attempts }. The body is pre-read so soft-block detection can inspect it; use response.headers / .status as normal.
Block detection
Hard blocks: 403, 407, 429, 503, and network errors (ECONNRESET, ETIMEDOUT, etc.).
Soft blocks: response body contains captcha, cloudflare, access denied, are you human, checking your browser, attention required, unusual traffic.
Local development
Secrets (BrightData + Webshare credentials) are managed via Doppler. One-time setup:
# 1. Install CLI (once per machine)
winget install Doppler.doppler # or: scoop install doppler / brew install dopplerhq/cli/doppler
# 2. Authenticate (opens browser)
doppler login
# 3. Bind this repo to the Doppler project/config
doppler setup --project proxy-rotator --config dev --no-interactive
# 4. Run the live test — Doppler injects secrets into the process
npm run liveKeys expected in the Doppler config (see .env.example for descriptions):
BRD_CUSTOMER_ID, BRD_ZONE, BRD_PASSWORD, BRD_API_TOKEN,
WEBSHARE_USERNAME, WEBSHARE_PASSWORD, WEBSHARE_API_TOKEN.
If you'd rather use a local .env file (not recommended — secrets live in git history if committed by mistake), run npm run live:env instead.
License
MIT
