Initial proxy manager refactor

This commit is contained in:
Boki 2025-06-20 12:20:06 -04:00
parent 84cb14680b
commit da916222c1
6 changed files with 58 additions and 37 deletions

View file

@ -11,6 +11,7 @@ import { getLogger, setLoggerConfig, shutdownLoggers } from '@stock-bot/logger';
import { connectMongoDB } from '@stock-bot/mongodb-client'; import { connectMongoDB } from '@stock-bot/mongodb-client';
import { connectPostgreSQL } from '@stock-bot/postgres-client'; import { connectPostgreSQL } from '@stock-bot/postgres-client';
import { QueueManager, type QueueManagerConfig } from '@stock-bot/queue'; import { QueueManager, type QueueManagerConfig } from '@stock-bot/queue';
import { ProxyManager } from '@stock-bot/utils';
import { Shutdown } from '@stock-bot/shutdown'; import { Shutdown } from '@stock-bot/shutdown';
// Local imports // Local imports
import { exchangeRoutes, healthRoutes, queueRoutes } from './routes'; import { exchangeRoutes, healthRoutes, queueRoutes } from './routes';
@ -116,8 +117,7 @@ async function initializeServices() {
// Initialize proxy manager // Initialize proxy manager
logger.debug('Initializing proxy manager...'); logger.debug('Initializing proxy manager...');
const { proxyManager } = await import('@stock-bot/utils'); await ProxyManager.initialize();
await proxyManager.initialize();
logger.info('Proxy manager initialized'); logger.info('Proxy manager initialized');
// Initialize providers (register handlers and scheduled jobs) // Initialize providers (register handlers and scheduled jobs)
@ -257,4 +257,4 @@ startServer().catch(error => {
logger.info('Data service startup initiated'); logger.info('Data service startup initiated');
// Queue manager is available via QueueManager.getInstance() singleton pattern // ProxyManager class and singleton instance are available via @stock-bot/utils

View file

@ -2,7 +2,7 @@ import { getRandomUserAgent } from '@stock-bot/http';
import { getLogger } from '@stock-bot/logger'; import { getLogger } from '@stock-bot/logger';
import { getMongoDBClient } from '@stock-bot/mongodb-client'; import { getMongoDBClient } from '@stock-bot/mongodb-client';
import { QueueManager } from '@stock-bot/queue'; import { QueueManager } from '@stock-bot/queue';
import { proxyManager } from '@stock-bot/utils'; import { ProxyManager } from '@stock-bot/utils';
// Shared instances (module-scoped, not global) // Shared instances (module-scoped, not global)
let isInitialized = false; // Track if resources are initialized let isInitialized = false; // Track if resources are initialized
@ -90,7 +90,7 @@ export async function createSessions(): Promise<void> {
while (sessionCache[sessionId].length < 50) { while (sessionCache[sessionId].length < 50) {
logger.info(`Creating new session for ${sessionId}`); logger.info(`Creating new session for ${sessionId}`);
const proxyInfo = await proxyManager.getRandomProxy(); const proxyInfo = await ProxyManager.getInstance().getRandomProxy();
if (!proxyInfo) { if (!proxyInfo) {
logger.error('No proxy available for QM session creation'); logger.error('No proxy available for QM session creation');
break; // Skip session creation if no proxy is available break; // Skip session creation if no proxy is available
@ -294,7 +294,7 @@ async function searchAndSpawnJobs(
// API call function to search symbols via QM // API call function to search symbols via QM
async function searchQMSymbolsAPI(query: string): Promise<string[]> { async function searchQMSymbolsAPI(query: string): Promise<string[]> {
const proxyInfo = await proxyManager.getRandomProxy(); const proxyInfo = await ProxyManager.getInstance().getRandomProxy();
if (!proxyInfo) { if (!proxyInfo) {
throw new Error('No proxy available for QM API call'); throw new Error('No proxy available for QM API call');

View file

@ -7,7 +7,7 @@ import {
handlerRegistry, handlerRegistry,
type HandlerConfigWithSchedule, type HandlerConfigWithSchedule,
} from '@stock-bot/queue'; } from '@stock-bot/queue';
import { proxyManager } from '@stock-bot/utils'; import { ProxyManager } from '@stock-bot/utils';
const logger = getLogger('webshare-provider'); const logger = getLogger('webshare-provider');
@ -28,7 +28,7 @@ export function initializeWebShareProvider() {
if (proxies.length > 0) { if (proxies.length > 0) {
// Update the centralized proxy manager // Update the centralized proxy manager
await proxyManager.updateProxies(proxies); await ProxyManager.getInstance().updateProxies(proxies);
logger.info('Updated proxy manager with WebShare proxies', { logger.info('Updated proxy manager with WebShare proxies', {
count: proxies.length, count: proxies.length,
@ -66,7 +66,7 @@ export function initializeWebShareProvider() {
const validationResults = await validateStoredProxies(); const validationResults = await validateStoredProxies();
// Update proxy manager with validated proxies // Update proxy manager with validated proxies
await proxyManager.updateProxies(validationResults.workingProxies); await ProxyManager.getInstance().updateProxies(validationResults.workingProxies);
logger.info('Proxy validation completed', { logger.info('Proxy validation completed', {
totalChecked: validationResults.totalChecked, totalChecked: validationResults.totalChecked,
@ -87,7 +87,7 @@ export function initializeWebShareProvider() {
}), }),
'get-stats': createJobHandler(async () => { 'get-stats': createJobHandler(async () => {
const stats = proxyManager.getStats(); const stats = ProxyManager.getInstance().getStats();
logger.info('Proxy manager statistics', stats); logger.info('Proxy manager statistics', stats);
return stats; return stats;
}), }),
@ -128,7 +128,7 @@ export function initializeWebShareProvider() {
// Legacy function for backward compatibility - now uses centralized proxy manager // Legacy function for backward compatibility - now uses centralized proxy manager
export async function getProxy(): Promise<string | null> { export async function getProxy(): Promise<string | null> {
const proxy = await proxyManager.getRandomProxy(); const proxy = ProxyManager.getInstance().getRandomProxy();
if (!proxy) { if (!proxy) {
return null; return null;
} }

View file

@ -3,7 +3,7 @@
*/ */
import { getLogger } from '@stock-bot/logger'; import { getLogger } from '@stock-bot/logger';
import { HttpClient, type ProxyInfo } from '@stock-bot/http'; import { HttpClient, type ProxyInfo } from '@stock-bot/http';
import { proxyManager } from '@stock-bot/utils'; import { ProxyManager } from '@stock-bot/utils';
const logger = getLogger('webshare-tasks'); const logger = getLogger('webshare-tasks');
@ -94,7 +94,7 @@ export async function validateStoredProxies(): Promise<{
const testUrl = 'https://httpbin.org/ip'; // Simple IP echo service const testUrl = 'https://httpbin.org/ip'; // Simple IP echo service
// Get all proxies from proxy manager // Get all proxies from proxy manager
const allProxies = await proxyManager.getAllProxies(); const allProxies = ProxyManager.getInstance().getAllProxies();
if (allProxies.length === 0) { if (allProxies.length === 0) {
logger.warn('No proxies available for validation'); logger.warn('No proxies available for validation');

View file

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

View file

@ -9,12 +9,13 @@ import type { ProxyInfo } from '@stock-bot/http';
const logger = getLogger('proxy-manager'); const logger = getLogger('proxy-manager');
export class ProxyManager { export class ProxyManager {
private static instance: ProxyManager | null = null;
private cache: CacheProvider; private cache: CacheProvider;
private proxies: ProxyInfo[] = []; private proxies: ProxyInfo[] = [];
private lastUpdate: Date | null = null; private lastUpdate: Date | null = null;
private isInitialized = false; private isInitialized = false;
constructor() { private constructor() {
const databaseConfig = getDatabaseConfig(); const databaseConfig = getDatabaseConfig();
this.cache = createCache({ this.cache = createCache({
redisConfig: databaseConfig.dragonfly, redisConfig: databaseConfig.dragonfly,
@ -25,9 +26,9 @@ export class ProxyManager {
} }
/** /**
* Initialize the proxy manager - loads existing proxies from cache * Internal initialization - loads existing proxies from cache
*/ */
async initialize(): Promise<void> { private async initializeInternal(): Promise<void> {
if (this.isInitialized) { if (this.isInitialized) {
return; return;
} }
@ -47,17 +48,18 @@ export class ProxyManager {
} }
/** /**
* Get a random working proxy from the available pool * Get a random working proxy from the available pool (synchronous)
*/ */
async getRandomProxy(): Promise<ProxyInfo | null> { getRandomProxy(): ProxyInfo | null {
// Ensure initialized // Ensure initialized
if (!this.isInitialized) { if (!this.isInitialized) {
await this.initialize(); throw new Error('ProxyManager not initialized');
} }
// Load from cache if memory is empty // Return null if no proxies available
if (this.proxies.length === 0) { if (this.proxies.length === 0) {
await this.loadFromCache(); logger.warn('No proxies available in memory');
return null;
} }
// Filter for working proxies (not explicitly marked as non-working) // Filter for working proxies (not explicitly marked as non-working)
@ -96,15 +98,11 @@ export class ProxyManager {
} }
/** /**
* Get all working proxies * Get all working proxies (synchronous)
*/ */
async getWorkingProxies(): Promise<ProxyInfo[]> { getWorkingProxies(): ProxyInfo[] {
if (!this.isInitialized) { if (!this.isInitialized) {
await this.initialize(); throw new Error('ProxyManager not initialized');
}
if (this.proxies.length === 0) {
await this.loadFromCache();
} }
return this.proxies.filter(proxy => proxy.isWorking !== false); return this.proxies.filter(proxy => proxy.isWorking !== false);
@ -113,13 +111,9 @@ export class ProxyManager {
/** /**
* Get all proxies (working and non-working) * Get all proxies (working and non-working)
*/ */
async getAllProxies(): Promise<ProxyInfo[]> { getAllProxies(): ProxyInfo[] {
if (!this.isInitialized) { if (!this.isInitialized) {
await this.initialize(); throw new Error('ProxyManager not initialized');
}
if (this.proxies.length === 0) {
await this.loadFromCache();
} }
return [...this.proxies]; return [...this.proxies];
@ -250,7 +244,34 @@ export class ProxyManager {
logger.error('Failed to load proxies from cache', { error }); logger.error('Failed to load proxies from cache', { error });
} }
} }
/**
* Initialize the singleton instance
*/
static async initialize(): Promise<void> {
if (!ProxyManager.instance) {
ProxyManager.instance = new ProxyManager();
await ProxyManager.instance.initializeInternal();
}
} }
// Singleton instance for easy import /**
export const proxyManager = new ProxyManager(); * Get the singleton instance (must be initialized first)
*/
static getInstance(): ProxyManager {
if (!ProxyManager.instance) {
throw new Error('ProxyManager not initialized. Call ProxyManager.initialize() first.');
}
return ProxyManager.instance;
}
/**
* Reset the singleton instance (for testing)
*/
static reset(): void {
ProxyManager.instance = null;
}
}
// Export the class as default
export default ProxyManager;