UNPKG

@iflow-mcp/ejmockler-brutalist

Version:

Deploy Claude, Codex & Gemini CLI agents to demolish your work before users do. Real file analysis. Brutal honesty. Now with conversation continuation & intelligent pagination.

321 lines 11.6 kB
import { EventEmitter } from 'events'; import { logger } from '../logger.js'; /** * Enhanced Server-Sent Events transport with session isolation * * Features: * - Session-scoped connections with isolation guarantees * - Connection pooling with resource limits * - Heartbeat monitoring and automatic cleanup * - Graceful connection management * - Event filtering and routing by session * - Memory-efficient streaming with backpressure */ export class EnhancedSSETransport extends EventEmitter { connections = new Map(); sessionManager; heartbeatInterval = null; // Configuration constants MAX_CONNECTIONS = 100; HEARTBEAT_INTERVAL = 30000; // 30 seconds CONNECTION_TIMEOUT = 300000; // 5 minutes MAX_EVENTS_PER_CONNECTION = 10000; constructor(sessionManager) { super(); this.sessionManager = sessionManager; this.startHeartbeat(); // Handle session events this.sessionManager.on('streamingEvent', this.handleSessionEvent.bind(this)); this.sessionManager.on('sessionComplete', this.handleSessionComplete.bind(this)); } /** * Establish SSE connection for a session */ async connect(req, res, sessionId) { const connectionId = `${sessionId}-${Date.now()}`; const clientOrigin = req.headers.origin || 'unknown'; // Validate session exists if (!this.sessionManager.hasSession(sessionId)) { res.status(404).json({ error: 'Session not found' }); return; } // Check connection limits if (this.connections.size >= this.MAX_CONNECTIONS) { logger.warn(`🚫 SSE connection limit reached (${this.MAX_CONNECTIONS})`); res.status(503).json({ error: 'Connection limit reached' }); return; } // Set SSE headers res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': clientOrigin, 'Access-Control-Allow-Credentials': 'true' }); // Send initial connection event this.sendEvent(res, { type: 'connection', data: { connectionId, sessionId, connectedAt: Date.now() } }); // Create connection record const connection = { id: connectionId, sessionId, response: res, clientOrigin, connectedAt: Date.now(), lastActivity: Date.now(), eventsSent: 0, isActive: true }; this.connections.set(connectionId, connection); logger.info(`🔗 SSE connection established: ${connectionId} for session ${sessionId}`); // Handle client disconnect req.on('close', () => { this.disconnect(connectionId, 'client_disconnect'); }); req.on('error', (error) => { logger.error(`💥 SSE connection error for ${connectionId}:`, error); this.disconnect(connectionId, 'connection_error'); }); // Send buffered events for this session this.sendBufferedEvents(connection); } /** * Handle streaming events from session manager */ handleSessionEvent(event) { if (!event.sessionId) { logger.warn('⚠️ Received streaming event without session ID'); return; } // Find connections for this session const sessionConnections = Array.from(this.connections.values()) .filter(conn => conn.sessionId === event.sessionId && conn.isActive); if (sessionConnections.length === 0) { logger.debug(`📭 No active SSE connections for session ${event.sessionId}`); return; } // Send event to all session connections for (const connection of sessionConnections) { this.sendStreamingEvent(connection, event); } } /** * Handle session completion */ handleSessionComplete(sessionId) { logger.info(`🏁 Session ${sessionId} completed, closing SSE connections`); const sessionConnections = Array.from(this.connections.values()) .filter(conn => conn.sessionId === sessionId); for (const connection of sessionConnections) { this.sendEvent(connection.response, { type: 'session_complete', data: { sessionId, completedAt: Date.now() } }); this.disconnect(connection.id, 'session_complete'); } } /** * Send streaming event to specific connection */ sendStreamingEvent(connection, event) { if (!connection.isActive) { return; } // Check event limit if (connection.eventsSent >= this.MAX_EVENTS_PER_CONNECTION) { logger.warn(`📊 Connection ${connection.id} reached event limit`); this.disconnect(connection.id, 'event_limit_reached'); return; } try { this.sendEvent(connection.response, { type: 'streaming_event', data: event }); connection.eventsSent++; connection.lastActivity = Date.now(); } catch (error) { logger.error(`💥 Failed to send event to ${connection.id}:`, error); this.disconnect(connection.id, 'send_error'); } } /** * Send buffered events for a newly connected session */ sendBufferedEvents(connection) { const sessionContext = this.sessionManager.getSession(connection.sessionId); if (!sessionContext) { return; } const bufferedEvents = sessionContext.eventBuffer.flush(connection.sessionId); if (bufferedEvents && bufferedEvents.events.length > 0) { logger.info(`📤 Sending ${bufferedEvents.events.length} buffered events to ${connection.id}`); for (const event of bufferedEvents.events) { this.sendStreamingEvent(connection, event); } } } /** * Send raw SSE event */ sendEvent(res, event) { const eventId = Date.now().toString(); const eventData = JSON.stringify(event.data); res.write(`id: ${eventId}\n`); res.write(`event: ${event.type}\n`); res.write(`data: ${eventData}\n\n`); } /** * Disconnect and cleanup SSE connection */ disconnect(connectionId, reason) { const connection = this.connections.get(connectionId); if (!connection) { return; } logger.info(`🔌 Disconnecting SSE ${connectionId}: ${reason}`); connection.isActive = false; try { if (!connection.response.destroyed) { this.sendEvent(connection.response, { type: 'disconnect', data: { reason, disconnectedAt: Date.now(), eventsSent: connection.eventsSent } }); connection.response.end(); } } catch (error) { logger.debug(`Failed to send disconnect event: ${error}`); } this.connections.delete(connectionId); this.emit('connectionClosed', { connectionId, sessionId: connection.sessionId, reason, duration: Date.now() - connection.connectedAt, eventsSent: connection.eventsSent }); } /** * Start heartbeat monitoring */ startHeartbeat() { this.heartbeatInterval = setInterval(() => { this.checkConnectionHealth(); }, this.HEARTBEAT_INTERVAL); // Allow Node.js to exit if this is the only active timer this.heartbeatInterval.unref(); } /** * Check connection health and cleanup stale connections */ checkConnectionHealth() { const now = Date.now(); const staleConnections = []; for (const [connectionId, connection] of this.connections) { if (!connection.isActive) { staleConnections.push(connectionId); continue; } // Check for timeout if (now - connection.lastActivity > this.CONNECTION_TIMEOUT) { logger.info(`⏰ Connection ${connectionId} timed out`); staleConnections.push(connectionId); continue; } // Send heartbeat try { this.sendEvent(connection.response, { type: 'heartbeat', data: { timestamp: now, sessionId: connection.sessionId } }); connection.lastActivity = now; } catch (error) { logger.debug(`Heartbeat failed for ${connectionId}, marking for cleanup`); staleConnections.push(connectionId); } } // Cleanup stale connections for (const connectionId of staleConnections) { this.disconnect(connectionId, 'stale_connection'); } if (this.connections.size > 0) { logger.debug(`💓 Heartbeat: ${this.connections.size} active SSE connections`); } } /** * Get connection statistics */ getStats() { const activeConnections = Array.from(this.connections.values()) .filter(conn => conn.isActive); const sessionDistribution = {}; let totalEvents = 0; for (const connection of activeConnections) { sessionDistribution[connection.sessionId] = (sessionDistribution[connection.sessionId] || 0) + 1; totalEvents += connection.eventsSent; } return { totalConnections: this.connections.size, activeConnections: activeConnections.length, sessionDistribution, averageEventsPerConnection: activeConnections.length > 0 ? totalEvents / activeConnections.length : 0 }; } /** * Force disconnect all connections for a session */ disconnectSession(sessionId, reason = 'forced_disconnect') { const sessionConnections = Array.from(this.connections.values()) .filter(conn => conn.sessionId === sessionId); for (const connection of sessionConnections) { this.disconnect(connection.id, reason); } } /** * Cleanup and shutdown transport */ shutdown() { logger.info('🛑 Shutting down SSE transport'); if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } // Disconnect all connections const connectionIds = Array.from(this.connections.keys()); for (const connectionId of connectionIds) { this.disconnect(connectionId, 'server_shutdown'); } this.removeAllListeners(); } /** * Get active connection for session (for testing) */ getSessionConnections(sessionId) { return Array.from(this.connections.values()) .filter(conn => conn.sessionId === sessionId && conn.isActive); } } //# sourceMappingURL=sse-transport.js.map