refactored out proxymanager from webshare to make it reusable

This commit is contained in:
Boki 2025-06-20 11:54:59 -04:00
parent 98aa414231
commit 84cb14680b
8 changed files with 622 additions and 109 deletions

View file

@ -2,3 +2,4 @@ export * from './calculations/index';
export * from './common';
export * from './dateUtils';
export * from './generic-functions';
export * from './proxy';

View file

@ -0,0 +1,5 @@
/**
* Proxy management utilities
*/
export { ProxyManager, proxyManager } from './proxy-manager';
export type { ProxyInfo } from '@stock-bot/http'; // Re-export for convenience

View file

@ -0,0 +1,256 @@
/**
* Centralized Proxy Manager - Handles proxy storage, retrieval, and caching
*/
import { createCache, type CacheProvider } from '@stock-bot/cache';
import { getDatabaseConfig } from '@stock-bot/config';
import { getLogger } from '@stock-bot/logger';
import type { ProxyInfo } from '@stock-bot/http';
const logger = getLogger('proxy-manager');
export class ProxyManager {
private cache: CacheProvider;
private proxies: ProxyInfo[] = [];
private lastUpdate: Date | null = null;
private isInitialized = false;
constructor() {
const databaseConfig = getDatabaseConfig();
this.cache = createCache({
redisConfig: databaseConfig.dragonfly,
keyPrefix: 'proxies:',
ttl: 86400, // 24 hours
enableMetrics: true,
});
}
/**
* Initialize the proxy manager - loads existing proxies from cache
*/
async initialize(): Promise<void> {
if (this.isInitialized) {
return;
}
try {
logger.info('Initializing proxy manager...');
await this.loadFromCache();
this.isInitialized = true;
logger.info('Proxy manager initialized', {
proxiesLoaded: this.proxies.length,
lastUpdate: this.lastUpdate,
});
} catch (error) {
logger.error('Failed to initialize proxy manager', { error });
this.isInitialized = true; // Set to true anyway to avoid infinite retries
}
}
/**
* Get a random working proxy from the available pool
*/
async getRandomProxy(): Promise<ProxyInfo | null> {
// Ensure initialized
if (!this.isInitialized) {
await this.initialize();
}
// Load from cache if memory is empty
if (this.proxies.length === 0) {
await this.loadFromCache();
}
// Filter for working proxies (not explicitly marked as non-working)
const workingProxies = this.proxies.filter(proxy => proxy.isWorking !== false);
if (workingProxies.length === 0) {
logger.warn('No working proxies available');
return null;
}
// Return random proxy with preference for recently successful ones
const sortedProxies = workingProxies.sort((a, b) => {
// Prefer proxies with better success rates
const aRate = a.successRate || 0;
const bRate = b.successRate || 0;
return bRate - aRate;
});
// Take from top 50% of best performing proxies
const topProxies = sortedProxies.slice(0, Math.max(1, Math.floor(sortedProxies.length * 0.5)));
const selectedProxy = topProxies[Math.floor(Math.random() * topProxies.length)];
if (!selectedProxy) {
logger.warn('No proxy selected from available pool');
return null;
}
logger.debug('Selected proxy', {
host: selectedProxy.host,
port: selectedProxy.port,
successRate: selectedProxy.successRate,
totalAvailable: workingProxies.length,
});
return selectedProxy;
}
/**
* Get all working proxies
*/
async getWorkingProxies(): Promise<ProxyInfo[]> {
if (!this.isInitialized) {
await this.initialize();
}
if (this.proxies.length === 0) {
await this.loadFromCache();
}
return this.proxies.filter(proxy => proxy.isWorking !== false);
}
/**
* Get all proxies (working and non-working)
*/
async getAllProxies(): Promise<ProxyInfo[]> {
if (!this.isInitialized) {
await this.initialize();
}
if (this.proxies.length === 0) {
await this.loadFromCache();
}
return [...this.proxies];
}
/**
* Update the proxy pool with new proxies
*/
async updateProxies(proxies: ProxyInfo[]): Promise<void> {
try {
logger.info('Updating proxy pool', { newCount: proxies.length, existingCount: this.proxies.length });
this.proxies = proxies;
this.lastUpdate = new Date();
// Store to cache
await this.cache.set('active-proxies', proxies);
await this.cache.set('last-update', this.lastUpdate.toISOString());
const workingCount = proxies.filter(p => p.isWorking !== false).length;
logger.info('Proxy pool updated successfully', {
totalProxies: proxies.length,
workingProxies: workingCount,
lastUpdate: this.lastUpdate,
});
} catch (error) {
logger.error('Failed to update proxy pool', { error });
throw error;
}
}
/**
* Add or update a single proxy in the pool
*/
async updateProxy(proxy: ProxyInfo): Promise<void> {
const existingIndex = this.proxies.findIndex(
p => p.host === proxy.host && p.port === proxy.port && p.protocol === proxy.protocol
);
if (existingIndex >= 0) {
this.proxies[existingIndex] = { ...this.proxies[existingIndex], ...proxy };
logger.debug('Updated existing proxy', { host: proxy.host, port: proxy.port });
} else {
this.proxies.push(proxy);
logger.debug('Added new proxy', { host: proxy.host, port: proxy.port });
}
// Update cache
await this.updateProxies(this.proxies);
}
/**
* Remove a proxy from the pool
*/
async removeProxy(host: string, port: number, protocol: string): Promise<void> {
const initialLength = this.proxies.length;
this.proxies = this.proxies.filter(
p => !(p.host === host && p.port === port && p.protocol === protocol)
);
if (this.proxies.length < initialLength) {
await this.updateProxies(this.proxies);
logger.debug('Removed proxy', { host, port, protocol });
}
}
/**
* Get proxy statistics
*/
getStats(): {
totalProxies: number;
workingProxies: number;
lastUpdate: Date | null;
successRate: number;
} {
const workingProxies = this.proxies.filter(p => p.isWorking !== false);
const totalSuccessRate = this.proxies.reduce((sum, p) => sum + (p.successRate || 0), 0);
const avgSuccessRate = this.proxies.length > 0 ? totalSuccessRate / this.proxies.length : 0;
return {
totalProxies: this.proxies.length,
workingProxies: workingProxies.length,
lastUpdate: this.lastUpdate,
successRate: avgSuccessRate,
};
}
/**
* Clear all proxies from memory and cache
*/
async clearProxies(): Promise<void> {
this.proxies = [];
this.lastUpdate = null;
await this.cache.del('active-proxies');
await this.cache.del('last-update');
logger.info('Cleared all proxies');
}
/**
* Check if proxy manager is ready
*/
isReady(): boolean {
return this.isInitialized;
}
/**
* Load proxies from cache storage
*/
private async loadFromCache(): Promise<void> {
try {
const cachedProxies = await this.cache.get<ProxyInfo[]>('active-proxies');
const lastUpdateStr = await this.cache.get<string>('last-update');
if (cachedProxies && Array.isArray(cachedProxies)) {
this.proxies = cachedProxies;
this.lastUpdate = lastUpdateStr ? new Date(lastUpdateStr) : null;
logger.debug('Loaded proxies from cache', {
count: this.proxies.length,
lastUpdate: this.lastUpdate,
});
} else {
logger.debug('No cached proxies found');
}
} catch (error) {
logger.error('Failed to load proxies from cache', { error });
}
}
}
// Singleton instance for easy import
export const proxyManager = new ProxyManager();

View file

@ -6,6 +6,10 @@
},
"include": ["src/**/*"],
"references": [
{ "path": "../types" }
{ "path": "../types" },
{ "path": "../cache" },
{ "path": "../config" },
{ "path": "../logger" },
{ "path": "../http" }
]
}