const express = require('express'); /** * Server-Sent Events route factory * Pushes real-time updates to connected dashboard clients * @param {Object} deps - Dependencies * @param {Object} deps.resourceMonitor - Container resource monitor * @param {Object} deps.healthChecker - Health checker * @param {Object} deps.updateManager - Update manager * @param {Function} deps.logError - Error logging function * @returns {express.Router} */ module.exports = function({ resourceMonitor, healthChecker, updateManager, logError }) { const router = express.Router(); const clients = new Set(); function broadcast(event, data) { const msg = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`; for (const res of clients) { try { res.write(msg); } catch (_) { clients.delete(res); } } } // --- Wire up EventEmitter listeners --- // Resource monitor events if (resourceMonitor) { resourceMonitor.on('alert', (data) => { broadcast('resource-alert', data); }); resourceMonitor.on('auto-restart', (data) => { broadcast('auto-restart', data); }); } // Health checker events if (healthChecker) { healthChecker.on('status-check', (data) => { broadcast('status-change', { serviceId: data.serviceId, name: data.name, status: data.status, responseTime: data.responseTime, timestamp: data.timestamp }); }); healthChecker.on('incident-created', (data) => { broadcast('incident', { type: 'created', ...data }); }); healthChecker.on('incident-resolved', (data) => { broadcast('incident', { type: 'resolved', ...data }); }); } // Update manager events if (updateManager) { updateManager.on('update-available', (data) => { broadcast('update-available', data); }); updateManager.on('update-start', (data) => { broadcast('update-start', data); }); updateManager.on('update-complete', (data) => { broadcast('update-complete', data); }); updateManager.on('update-failed', (data) => { broadcast('update-failed', data); }); updateManager.on('auto-update-start', (data) => { broadcast('auto-update-start', data); }); updateManager.on('auto-update-complete', (data) => { broadcast('auto-update-complete', data); }); } // SSE endpoint router.get('/stream', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'X-Accel-Buffering': 'no', }); // Send initial connected event res.write(`event: connected\ndata: ${JSON.stringify({ clients: clients.size + 1 })}\n\n`); clients.add(res); // Heartbeat every 30s const heartbeat = setInterval(() => { try { res.write(': heartbeat\n\n'); } catch (_) { cleanup(); } }, 30000); function cleanup() { clearInterval(heartbeat); clients.delete(res); } req.on('close', cleanup); req.on('error', cleanup); }); // Client count (useful for debugging) router.get('/clients', (req, res) => { res.json({ success: true, count: clients.size }); }); return router; };