UNPKG

proxy-connection

Version:

Proxy client with automatic connection management, health checking, and fetch-like API

304 lines 11.8 kB
#!/usr/bin/env node "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProxyManager = exports.testProxy = void 0; const testProxy_1 = require("./utils/testProxy"); // import { proxiesList } from '../proxies-list'; const errorCodes_1 = require("./utils/errorCodes"); const logger_1 = require("./utils/logger"); const proxyRequest_1 = require("./utils/proxyRequest"); const constants_1 = require("../constants"); const index_1 = require("./utils/snowflakeId/index"); const getNextProxy_1 = require("./utils/getNextProxy"); // Export utility functions var testProxy_2 = require("./utils/testProxy"); Object.defineProperty(exports, "testProxy", { enumerable: true, get: function () { return testProxy_2.testProxy; } }); const addBreadcrumb = (logger, { config, proxy, errorCode }) => logger.addBreadcrumb({ category: 'ProxyManager', message: errorCode, level: 'error', // SeverityLevel.Error is not available, use string data: { config, proxy, errorCode } }); /** * ProxyManager */ class ProxyManager { proxies = []; liveProxies = []; run = false; logger; config; requestsStack = new Map(); constructor(proxies, options = {}) { const { sentryLogger, config, disableLogging = false } = options; // Initialize logger with application tags this.logger = disableLogging ? new logger_1.NullLogger() : (sentryLogger || new logger_1.FileLogger()); this.logger.addBreadcrumb({ category: 'ProxyManager', message: 'Initializing ProxyManager', level: 'info', data: { component: 'ProxyManager', version: '1.0.0' } }); this.logger.addBreadcrumb({ category: 'ProxyManager', message: 'proxyCount', level: 'info', data: { count: proxies.length } }); if (!proxies || proxies.length === 0) { this.logger.captureMessage(errorCodes_1.errorCodes.NO_PROXIES, 'error'); throw new Error('No proxies provided'); } this.proxies = Object.freeze(proxies.map((p) => ({ ip: p.ip, port: p.port, user: p.user, pass: p.pass }))); this.liveProxies = this.proxies.map((proxy) => ({ ...proxy, alive: true, latency: 0 })); this.config = Object.freeze({ ...constants_1.defaultProxyMangerConfig, ...config }); // Initialization logic this.run = true; this.checkProxyManagerConfig(); // Run health check immediately, then start interval this.rankProxies().catch((err) => { this.logger.captureException(err); }); this.initLiveProxies(); this.loopRankProxies(); this.bindRequest(); } bindRequest() { this.request = this.request.bind(this); } loopRankProxies() { const interval = setInterval(() => { if (this.run) { try { this.rankProxies().catch((err) => { this.logger.captureException(err); }); } catch (error) { this.logger.captureException(error); } this.logger.captureMessage('Proxies ranked', 'info'); } else { clearInterval(interval); this.logger.captureMessage('ProxyManager stopped', 'info'); } }, this.config.healthCheckInterval); } initLiveProxies() { // Only initialize if liveProxies is empty if (this.liveProxies.length === 0) { this.liveProxies = this.proxies.map((proxy) => ({ ...proxy, alive: true, latency: 0 })); } } async rankProxies() { if (this.proxies.length === 0) { throw new Error(errorCodes_1.errorMessages[errorCodes_1.errorCodes.NO_PROXIES]); } const withLatency = await Promise.all(this.proxies.map(async (proxy) => { const { latency, alive } = await (0, testProxy_1.testProxy)(proxy); return { ...proxy, alive, latency }; })); withLatency.sort((a, b) => a.latency - b.latency); const aliveRankedProxies = withLatency.filter(proxy => proxy.alive); if (aliveRankedProxies.length === 0) { throw new Error(errorCodes_1.errorMessages[errorCodes_1.errorCodes.NO_ALIVE_PROXIES]); } this.liveProxies = aliveRankedProxies; } async runAttempt(attemptParams) { const { requestConfig, proxy } = attemptParams; try { const response = await (0, proxyRequest_1.proxyRequest)(requestConfig, proxy); // Mark request as successful const requestState = this.requestsStack.get(attemptParams.requestId); if (requestState) { this.requestsStack.set(attemptParams.requestId, { ...requestState, success: true, attempts: [...requestState.attempts, { proxy, errorCode: null, success: true, ts: Date.now() }] }); } return response; } catch (exceptionData) { addBreadcrumb(this.logger, exceptionData); // Handle different types of exceptions let errorToLog; if (exceptionData && typeof exceptionData === 'object' && 'error' in exceptionData) { errorToLog = exceptionData.error; } else if (exceptionData instanceof Error) { errorToLog = exceptionData; } else { errorToLog = new Error(String(exceptionData)); } this.logger.captureException(errorToLog); const requestState = this.requestsStack.get(attemptParams.requestId); if (!requestState) { throw new Error('Request state not found'); } const newAttempt = { proxy, errorCode: exceptionData.errorCode || errorCodes_1.errorCodes.UNKNOWN_ERROR, success: false, ts: Date.now() }; this.requestsStack.set(attemptParams.requestId, { ...requestState, attempts: [...requestState.attempts, newAttempt] }); return this.proxyLoop(requestConfig, attemptParams.requestId); } } async checkProxyManagerConfig() { if (!this.config || Object.keys(this.config).length === 0) { const err = new Error('ProxyManager config is not defined'); this.logger.captureException(err); throw err; } if (!this.config.healthCheckUrl) { const err = new Error('ProxyManager healthCheckUrl is not defined'); this.logger.captureException(err); throw err; } if (this.config.maxTimeout <= 0) { const err = new Error('ProxyManager maxTimeout must be greater than 0'); this.logger.captureException(err); throw err; } if (this.proxies.length === 0) { const err = new Error(errorCodes_1.errorMessages[errorCodes_1.errorCodes.NO_PROXIES]); this.logger.captureException(err); throw err; } } async proxyLoop(requestConfig, requestId) { const requestState = this.requestsStack.get(requestId); if (!requestState) { const err = new Error('Request state not found'); this.logger.captureException(err); throw err; } if (this.liveProxies.length === 0) { const err = new Error(errorCodes_1.errorMessages[errorCodes_1.errorCodes.NO_ALIVE_PROXIES]); this.logger.captureException(err); throw err; } const proxy = (0, getNextProxy_1.getNextProxy)({ requestState: this.requestsStack.get(requestId), proxies: this.liveProxies, config: this.config }); if (!proxy) { // this means that no proxies are available // changeProxyLoop - config // loops - requestState const isNewLoopAvailable = this.requestsStack.get(requestId)?.loops < this.config.changeProxyLoop; if (isNewLoopAvailable) { this.requestsStack.set(requestId, { ...requestState, loops: (requestState?.loops || 0) + 1, attempts: [] // reset attempts for new loop }); return this.proxyLoop(requestConfig, requestId); } else { throw new Error(errorCodes_1.errorMessages[errorCodes_1.errorCodes.REQUEST_FAILED]); } } return this.runAttempt({ requestConfig, proxy, requestId }); } async request(url, options) { const requestId = (0, index_1.generateSnowflakeId)(); // Convert Headers object to plain object if needed (for OpenAI SDK compatibility) let headers = options?.headers || {}; if (headers && typeof headers === 'object' && 'entries' in headers && typeof headers.entries === 'function') { const plainHeaders = {}; const headersWithEntries = headers; for (const [key, value] of headersWithEntries.entries()) { plainHeaders[key] = value; } headers = plainHeaders; } // Merge url and options into a single RequestConfig object const requestConfig = { url, method: options?.method || 'GET', headers, ...(options?.body !== undefined && { body: options.body }) }; this.requestsStack.set(requestId, { retries: 0, success: false, attempts: [], loops: 0 }); return this.proxyLoop(requestConfig, requestId) .catch((error) => { // unexpected error const requestState = this.requestsStack.get(requestId); this.logger.setExtra('requestState', requestState); this.logger.captureException(error); throw error; // Re-throw to maintain error handling }) .finally(() => { const requestState = this.requestsStack.get(requestId); if (requestState?.success === true) { this.requestsStack.delete(requestId); } else { // else handle memory overflow this.logger.captureMessage('Proxy request failed', 'error'); this.logger.setExtra('requestState', requestState); this.logger.captureException(new Error('All attempts failed')); } }); } // Async getter for testing purposes - waits for health check to complete async getLiveProxiesList() { // Wait for initial health check to complete const liveProxies = await this.rankProxies().catch((err) => { this.logger.captureException(err); }).then(() => this.liveProxies); return liveProxies; } // Sync getter for internal use (returns current state) get liveProxiesListSync() { return this.liveProxies; } stop() { this.run = false; } } exports.ProxyManager = ProxyManager; //# sourceMappingURL=index.js.map