/** * QM Session Manager - Centralized session state management */ import { getRandomUserAgent } from '@stock-bot/utils'; import { QM_SESSION_IDS, SESSION_CONFIG } from './config'; import type { QMSession } from './types'; export class QMSessionManager { private static instance: QMSessionManager | null = null; private sessionCache: Record = {}; private isInitialized = false; private constructor() { // Initialize session cache with known session IDs Object.values(QM_SESSION_IDS).forEach(sessionId => { this.sessionCache[sessionId] = []; }); } static getInstance(): QMSessionManager { if (!QMSessionManager.instance) { QMSessionManager.instance = new QMSessionManager(); } return QMSessionManager.instance; } /** * Get a random session for the given session ID */ getSession(sessionId: string): QMSession | null { const sessions = this.sessionCache[sessionId]; if (!sessions || sessions.length === 0) { return null; } // Filter out sessions with excessive failures const validSessions = sessions.filter(session => session.failedCalls <= SESSION_CONFIG.MAX_FAILED_CALLS); if (validSessions.length === 0) { return null; } return validSessions[Math.floor(Math.random() * validSessions.length)]; } /** * Add a session to the cache */ addSession(sessionId: string, session: QMSession): void { if (!this.sessionCache[sessionId]) { this.sessionCache[sessionId] = []; } this.sessionCache[sessionId].push(session); } /** * Get all sessions for a session ID */ getSessions(sessionId: string): QMSession[] { return this.sessionCache[sessionId] || []; } /** * Get session count for all session IDs */ getSessionCount(): number { return Object.values(this.sessionCache).reduce((total, sessions) => total + sessions.length, 0); } /** * Clean up failed sessions */ cleanupFailedSessions(): number { let removedCount = 0; Object.keys(this.sessionCache).forEach(sessionId => { const initialCount = this.sessionCache[sessionId].length; this.sessionCache[sessionId] = this.sessionCache[sessionId].filter( session => session.failedCalls <= SESSION_CONFIG.MAX_FAILED_CALLS ); removedCount += initialCount - this.sessionCache[sessionId].length; }); return removedCount; } getQmHeaders(): Record { return { 'User-Agent': getRandomUserAgent(), Accept: '*/*', 'Accept-Language': 'en', 'Sec-Fetch-Mode': 'cors', Origin: 'https://www.quotemedia.com', Referer: 'https://www.quotemedia.com/', }; } /** * Check if more sessions are needed for a session ID */ needsMoreSessions(sessionId: string): boolean { const sessions = this.sessionCache[sessionId] || []; const validSessions = sessions.filter(session => session.failedCalls <= SESSION_CONFIG.MAX_FAILED_CALLS); return validSessions.length < SESSION_CONFIG.MIN_SESSIONS; } /** * Check if session ID is at capacity */ isAtCapacity(sessionId: string): boolean { const sessions = this.sessionCache[sessionId] || []; return sessions.length >= SESSION_CONFIG.MAX_SESSIONS; } /** * Get session cache statistics */ getStats() { const stats: Record = {}; Object.entries(this.sessionCache).forEach(([sessionId, sessions]) => { const validSessions = sessions.filter(session => session.failedCalls <= SESSION_CONFIG.MAX_FAILED_CALLS); const failedSessions = sessions.filter(session => session.failedCalls > SESSION_CONFIG.MAX_FAILED_CALLS); stats[sessionId] = { total: sessions.length, valid: validSessions.length, failed: failedSessions.length }; }); return stats; } /** * Mark manager as initialized */ setInitialized(initialized: boolean = true): void { this.isInitialized = initialized; } /** * Check if manager is initialized */ getInitialized(): boolean { return this.isInitialized; } }