/** * SSE Routes * * Server-Sent Events endpoints for real-time session event streaming. */ const express = require('express'); const sseManager = require('../services/sse-manager'); const { validateSessionId } = require('../middleware/validation'); const router = express.Router(); /** * SSE endpoint for session events * GET /api/session/:sessionId/events * * Establishes a Server-Sent Events connection for streaming real-time * session events to the client. * * Events: * - connected: Initial connection confirmation * - session-output: Output from Claude Code process * - session-error: Error from session * - session-status: Session status update * - operations-detected: Claude operations detected in response * - operations-executed: Operations execution results * - approval-request: Command approval requested * - approval-confirmed: Command approval confirmed/expired * * Example: * const eventSource = new EventSource('/api/session/session-123/events'); * eventSource.addEventListener('session-output', (e) => { * const data = JSON.parse(e.data); * console.log('Output:', data.content); * }); */ router.get('/session/:sessionId/events', validateSessionId, (req, res) => { const { sessionId } = req.params; console.log(`[SSERoutes] New SSE connection request for session ${sessionId}`); console.log(`[SSERoutes] Client IP: ${req.ip}`); console.log(`[SSERoutes] User-Agent: ${req.get('User-Agent')?.substring(0, 100)}`); // Add SSE connection (response is kept open for streaming) sseManager.addConnection(sessionId, res, req); // Note: No response sent here - connection stays open for SSE streaming }); /** * Get connection status for a session * GET /api/session/:sessionId/events/status * * Returns information about active SSE connections for a session. * * Response: * { * "sessionId": "session-123", * "activeConnections": 2, * "timestamp": 1234567890 * } */ router.get('/session/:sessionId/events/status', validateSessionId, (req, res) => { const { sessionId } = req.params; const activeConnections = sseManager.getConnectionCount(sessionId); res.json({ sessionId, activeConnections, timestamp: Date.now() }); }); /** * Get global SSE stats (admin endpoint) * GET /api/sse/stats * * Returns overall SSE connection statistics. * * Response: * { * "totalSessions": 5, * "totalConnections": 12, * "sessions": { "session-1": 2, "session-2": 1, ... }, * "totalCreated": 50, * "totalClosed": 38, * "activeHeartbeats": 12 * } */ router.get('/sse/stats', (req, res) => { const stats = sseManager.getStats(); res.json(stats); }); /** * Test SSE endpoint (for development/testing) * GET /api/sse/test * * Sends test events every second for 10 seconds. * Only available in development mode. */ router.get('/sse/test', (req, res) => { if (process.env.NODE_ENV === 'production') { return res.status(404).json({ error: 'Not found' }); } res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); let count = 0; const maxEvents = 10; const interval = setInterval(() => { count++; res.write(`event: test\n`); res.write(`data: ${JSON.stringify({ message: `Test event ${count}`, count, timestamp: Date.now() })}\n`); res.write(`id: ${Date.now()}\n`); res.write('\n'); if (count >= maxEvents) { clearInterval(interval); res.write('event: end\n'); res.write('data: {"message":"Test complete"}\n\n'); res.end(); } }, 1000); req.on('close', () => { clearInterval(interval); }); }); module.exports = router;