# Server-Sent Events (SSE) Infrastructure This document describes the SSE implementation for real-time server-to-client communication in the Adopt-a-Street backend. ## Overview Server-Sent Events (SSE) provides a unidirectional real-time communication channel from server to client over HTTP. Unlike WebSockets, SSE is built on standard HTTP and automatically handles reconnection. ## Architecture ### Components 1. **SSE Service** (`services/sseService.js`) - Manages client connections and topic subscriptions - Provides methods for broadcasting and targeted messaging - Handles automatic cleanup on disconnect 2. **SSE Auth Middleware** (`middleware/sseAuth.js`) - JWT authentication for SSE connections - Supports token from query string, Authorization header, or x-auth-token header 3. **SSE Routes** (`routes/sse.js`) - `/api/sse/stream` - SSE stream endpoint - `/api/sse/subscribe` - Subscribe to topics - `/api/sse/unsubscribe` - Unsubscribe from topics ## API Endpoints ### GET /api/sse/stream Opens an SSE connection for the authenticated user. **Authentication:** Required (JWT via query parameter `?token=xxx` or Authorization header) **Response:** SSE stream with events **Example:** ```javascript const token = localStorage.getItem('token'); const eventSource = new EventSource(`/api/sse/stream?token=${token}`); eventSource.addEventListener('connected', (e) => { console.log('Connected:', JSON.parse(e.data)); }); eventSource.addEventListener('notification', (e) => { const data = JSON.parse(e.data); console.log('Notification:', data); }); eventSource.onerror = (error) => { console.error('SSE Error:', error); }; ``` ### POST /api/sse/subscribe Subscribe to one or more topics. **Authentication:** Required **Body:** ```json { "topics": ["events", "posts", "notifications"] } ``` **Response:** ```json { "success": true, "msg": "Subscribed to topics", "topics": ["events", "posts", "notifications"] } ``` ### POST /api/sse/unsubscribe Unsubscribe from topics. **Authentication:** Required **Body:** ```json { "topics": ["events"] } ``` **Response:** ```json { "success": true, "msg": "Unsubscribed from topics", "topics": ["events"] } ``` ## SSE Service Methods ### Client Management ```javascript const sseService = require('./services/sseService'); // Add a client (called automatically by /stream endpoint) sseService.addClient(userId, res); // Remove a client (called automatically on disconnect) sseService.removeClient(userId); ``` ### Topic Subscriptions ```javascript // Subscribe to topics sseService.subscribe(userId, ['events', 'posts']); // Unsubscribe from topics sseService.unsubscribe(userId, ['events']); ``` ### Broadcasting Messages ```javascript // Broadcast to all connected clients sseService.broadcast('announcement', { message: 'System maintenance in 10 minutes' }); // Broadcast to topic subscribers sseService.broadcastToTopic('events', 'eventUpdate', { eventId: 123, status: 'started' }); // Send to specific user sseService.sendToUser(userId, 'notification', { text: 'You have a new message', priority: 'high' }); ``` ### Statistics ```javascript // Get service statistics const stats = sseService.getStats(); // Returns: { totalClients: 5, totalTopics: 3, topics: { events: 3, posts: 2, notifications: 5 } } ``` ## Usage in Routes You can access the SSE service from any route handler: ```javascript router.post('/events', auth, async (req, res) => { try { // Create event... const event = await Event.create(req.body); // Notify subscribers via SSE const sse = req.app.get('sse'); sse.broadcastToTopic('events', 'newEvent', { eventId: event._id, title: event.title, date: event.date }); res.json({ success: true, event }); } catch (error) { res.status(500).json({ success: false, msg: 'Server error' }); } }); ``` ## Event Types SSE messages are sent with specific event types. Clients can listen for specific event types: ```javascript eventSource.addEventListener('newEvent', (e) => { // Handle new event }); eventSource.addEventListener('eventUpdate', (e) => { // Handle event update }); eventSource.addEventListener('notification', (e) => { // Handle notification }); ``` ## Common Topics Recommended topic names for consistency: - `events` - Event creation, updates, deletions - `posts` - Social feed posts - `tasks` - Task updates - `notifications` - User notifications - `rewards` - Badge and reward notifications - `leaderboard` - Leaderboard changes ## Message Format SSE messages follow this format: ``` event: eventType data: {"key": "value"} ``` The SSE service automatically formats messages correctly. ## Connection Management - **Heartbeat:** The server sends a heartbeat comment (`:heartbeat`) every 30 seconds to keep the connection alive - **Auto-reconnect:** Browsers automatically reconnect if the connection is lost - **Cleanup:** Clients are automatically removed when the connection closes ## Health Monitoring SSE statistics are included in the `/api/health` endpoint: ```json { "status": "healthy", "services": { "sse": { "totalClients": 5, "totalTopics": 3 } } } ``` ## Testing Run SSE tests: ```bash npm test -- __tests__/routes/sse.test.js ``` Demo script: ```bash node test-sse-demo.js ``` ## Security - All SSE connections require JWT authentication - Tokens can be passed via query string (for EventSource compatibility) or headers - Connections are validated on connect; invalid tokens receive 401 Unauthorized - Each user can only have one active SSE connection (new connections replace old ones) ## Performance Considerations - SSE uses HTTP/1.1 long-polling, so each connection uses one HTTP connection - Browser limit: ~6 connections per domain (HTTP/1.1) - For high-concurrency scenarios, consider using HTTP/2 (multiplexing) or WebSockets - The service is designed for low-memory usage with Map-based storage ## Comparison with Socket.IO The backend supports both SSE and Socket.IO: | Feature | SSE | Socket.IO | |---------|-----|-----------| | Direction | Server → Client | Bidirectional | | Protocol | HTTP | WebSocket + HTTP fallback | | Browser Support | All modern browsers | All browsers | | Auto-reconnect | Built-in | Built-in | | Use Case | Server push notifications | Real-time chat, collaboration | **When to use SSE:** - Server needs to push updates to clients - Unidirectional communication is sufficient - Simpler setup and infrastructure **When to use Socket.IO:** - Bidirectional real-time communication needed - Complex event patterns - Existing Socket.IO infrastructure ## Example: Complete Client Implementation ```javascript class SSEClient { constructor(token) { this.token = token; this.eventSource = null; this.connected = false; } connect() { this.eventSource = new EventSource(`/api/sse/stream?token=${this.token}`); this.eventSource.addEventListener('connected', (e) => { console.log('SSE Connected:', JSON.parse(e.data)); this.connected = true; this.subscribeToTopics(['events', 'notifications']); }); this.eventSource.addEventListener('newEvent', (e) => { const event = JSON.parse(e.data); this.handleNewEvent(event); }); this.eventSource.addEventListener('notification', (e) => { const notification = JSON.parse(e.data); this.handleNotification(notification); }); this.eventSource.onerror = (error) => { console.error('SSE Error:', error); this.connected = false; }; } async subscribeToTopics(topics) { const response = await fetch('/api/sse/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-auth-token': this.token }, body: JSON.stringify({ topics }) }); const data = await response.json(); console.log('Subscribed:', data); } handleNewEvent(event) { console.log('New event:', event); // Update UI with new event } handleNotification(notification) { console.log('Notification:', notification); // Show notification to user } disconnect() { if (this.eventSource) { this.eventSource.close(); this.connected = false; } } } // Usage const sseClient = new SSEClient(localStorage.getItem('token')); sseClient.connect(); ``` ## Troubleshooting ### Connection not established - Check that JWT token is valid and not expired - Verify token is passed correctly (query param or header) - Check browser console for CORS errors ### Not receiving messages - Verify client is subscribed to the correct topics - Check that server is broadcasting to the correct topic - Ensure client is still connected (check `eventSource.readyState`) ### High memory usage - Review the number of connected clients - Check for memory leaks in client connection handlers - Monitor SSE stats via `/api/health` endpoint