diff --git a/apps/data-service/src/index.ts b/apps/data-service/src/index.ts index fcb5555..dc579c2 100644 --- a/apps/data-service/src/index.ts +++ b/apps/data-service/src/index.ts @@ -11,6 +11,7 @@ import { getLogger, setLoggerConfig, shutdownLoggers } from '@stock-bot/logger'; import { connectMongoDB } from '@stock-bot/mongodb-client'; import { connectPostgreSQL } from '@stock-bot/postgres-client'; import { QueueManager, type QueueManagerConfig } from '@stock-bot/queue'; +import { ProxyManager } from '@stock-bot/utils'; import { Shutdown } from '@stock-bot/shutdown'; // Local imports import { exchangeRoutes, healthRoutes, queueRoutes } from './routes'; @@ -116,8 +117,7 @@ async function initializeServices() { // Initialize 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'); // Initialize providers (register handlers and scheduled jobs) @@ -257,4 +257,4 @@ startServer().catch(error => { 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 diff --git a/apps/data-service/src/providers/qm.tasks.ts b/apps/data-service/src/providers/qm.tasks.ts index 588a798..2ca6c08 100644 --- a/apps/data-service/src/providers/qm.tasks.ts +++ b/apps/data-service/src/providers/qm.tasks.ts @@ -2,7 +2,7 @@ import { getRandomUserAgent } from '@stock-bot/http'; import { getLogger } from '@stock-bot/logger'; import { getMongoDBClient } from '@stock-bot/mongodb-client'; import { QueueManager } from '@stock-bot/queue'; -import { proxyManager } from '@stock-bot/utils'; +import { ProxyManager } from '@stock-bot/utils'; // Shared instances (module-scoped, not global) let isInitialized = false; // Track if resources are initialized @@ -90,7 +90,7 @@ export async function createSessions(): Promise { while (sessionCache[sessionId].length < 50) { logger.info(`Creating new session for ${sessionId}`); - const proxyInfo = await proxyManager.getRandomProxy(); + const proxyInfo = await ProxyManager.getInstance().getRandomProxy(); if (!proxyInfo) { logger.error('No proxy available for QM session creation'); break; // Skip session creation if no proxy is available @@ -294,7 +294,7 @@ async function searchAndSpawnJobs( // API call function to search symbols via QM async function searchQMSymbolsAPI(query: string): Promise { - const proxyInfo = await proxyManager.getRandomProxy(); + const proxyInfo = await ProxyManager.getInstance().getRandomProxy(); if (!proxyInfo) { throw new Error('No proxy available for QM API call'); diff --git a/apps/data-service/src/providers/webshare.provider.ts b/apps/data-service/src/providers/webshare.provider.ts index 644a559..9299b09 100644 --- a/apps/data-service/src/providers/webshare.provider.ts +++ b/apps/data-service/src/providers/webshare.provider.ts @@ -7,7 +7,7 @@ import { handlerRegistry, type HandlerConfigWithSchedule, } from '@stock-bot/queue'; -import { proxyManager } from '@stock-bot/utils'; +import { ProxyManager } from '@stock-bot/utils'; const logger = getLogger('webshare-provider'); @@ -28,7 +28,7 @@ export function initializeWebShareProvider() { if (proxies.length > 0) { // Update the centralized proxy manager - await proxyManager.updateProxies(proxies); + await ProxyManager.getInstance().updateProxies(proxies); logger.info('Updated proxy manager with WebShare proxies', { count: proxies.length, @@ -66,7 +66,7 @@ export function initializeWebShareProvider() { const validationResults = await validateStoredProxies(); // Update proxy manager with validated proxies - await proxyManager.updateProxies(validationResults.workingProxies); + await ProxyManager.getInstance().updateProxies(validationResults.workingProxies); logger.info('Proxy validation completed', { totalChecked: validationResults.totalChecked, @@ -87,7 +87,7 @@ export function initializeWebShareProvider() { }), 'get-stats': createJobHandler(async () => { - const stats = proxyManager.getStats(); + const stats = ProxyManager.getInstance().getStats(); logger.info('Proxy manager statistics', stats); return stats; }), @@ -128,7 +128,7 @@ export function initializeWebShareProvider() { // Legacy function for backward compatibility - now uses centralized proxy manager export async function getProxy(): Promise { - const proxy = await proxyManager.getRandomProxy(); + const proxy = ProxyManager.getInstance().getRandomProxy(); if (!proxy) { return null; } diff --git a/apps/data-service/src/providers/webshare.tasks.ts b/apps/data-service/src/providers/webshare.tasks.ts index 6bdc6f7..0fe2299 100644 --- a/apps/data-service/src/providers/webshare.tasks.ts +++ b/apps/data-service/src/providers/webshare.tasks.ts @@ -3,7 +3,7 @@ */ import { getLogger } from '@stock-bot/logger'; 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'); @@ -94,7 +94,7 @@ export async function validateStoredProxies(): Promise<{ const testUrl = 'https://httpbin.org/ip'; // Simple IP echo service // Get all proxies from proxy manager - const allProxies = await proxyManager.getAllProxies(); + const allProxies = ProxyManager.getInstance().getAllProxies(); if (allProxies.length === 0) { logger.warn('No proxies available for validation'); diff --git a/libs/utils/src/proxy/index.ts b/libs/utils/src/proxy/index.ts index 7965d13..56a1ecc 100644 --- a/libs/utils/src/proxy/index.ts +++ b/libs/utils/src/proxy/index.ts @@ -1,5 +1,5 @@ /** * 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 \ No newline at end of file diff --git a/libs/utils/src/proxy/proxy-manager.ts b/libs/utils/src/proxy/proxy-manager.ts index de31934..296ec20 100644 --- a/libs/utils/src/proxy/proxy-manager.ts +++ b/libs/utils/src/proxy/proxy-manager.ts @@ -9,12 +9,13 @@ import type { ProxyInfo } from '@stock-bot/http'; const logger = getLogger('proxy-manager'); export class ProxyManager { + private static instance: ProxyManager | null = null; private cache: CacheProvider; private proxies: ProxyInfo[] = []; private lastUpdate: Date | null = null; private isInitialized = false; - constructor() { + private constructor() { const databaseConfig = getDatabaseConfig(); this.cache = createCache({ 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 { + private async initializeInternal(): Promise { if (this.isInitialized) { 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 { + getRandomProxy(): ProxyInfo | null { // Ensure initialized 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) { - await this.loadFromCache(); + logger.warn('No proxies available in memory'); + return null; } // 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 { + getWorkingProxies(): ProxyInfo[] { if (!this.isInitialized) { - await this.initialize(); - } - - if (this.proxies.length === 0) { - await this.loadFromCache(); + throw new Error('ProxyManager not initialized'); } return this.proxies.filter(proxy => proxy.isWorking !== false); @@ -113,13 +111,9 @@ export class ProxyManager { /** * Get all proxies (working and non-working) */ - async getAllProxies(): Promise { + getAllProxies(): ProxyInfo[] { if (!this.isInitialized) { - await this.initialize(); - } - - if (this.proxies.length === 0) { - await this.loadFromCache(); + throw new Error('ProxyManager not initialized'); } return [...this.proxies]; @@ -250,7 +244,34 @@ export class ProxyManager { logger.error('Failed to load proxies from cache', { error }); } } + + /** + * Initialize the singleton instance + */ + static async initialize(): Promise { + if (!ProxyManager.instance) { + ProxyManager.instance = new ProxyManager(); + await ProxyManager.instance.initializeInternal(); + } + } + + /** + * 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; + } } -// Singleton instance for easy import -export const proxyManager = new ProxyManager(); \ No newline at end of file +// Export the class as default +export default ProxyManager; \ No newline at end of file