proxy-connection
Version:
Proxy client with automatic connection management, health checking, and fetch-like API
304 lines • 11.8 kB
JavaScript
"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