diff --git a/admin/server.js b/admin/server.js index 20f5cbf..c3d9718 100644 --- a/admin/server.js +++ b/admin/server.js @@ -9,9 +9,22 @@ dotenv.config({ path: `.env.${ENV}` }); const app = express(); const PORT = process.env.PORT || 3002; +// Disable Express poweredBy header for performance +app.disable('x-powered-by'); + // Parse JSON bodies app.use(express.json()); +// Optimize Express for SSE performance +app.use((req, res, next) => { + // Disable buffering for SSE endpoints + if (req.path === '/api/sse') { + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + } + next(); +}); + // Track SSE connections let sseConnections = 0; @@ -41,16 +54,24 @@ app.get('/api/sse', (req, res) => { const connectionId = sseConnections; console.log(`[SSE] New connection #${connectionId} established. Total: ${sseConnections}`); + // Optimized headers for better performance res.writeHead(200, { 'Content-Type': 'text/event-stream', - 'Cache-Control': 'no-cache', + 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': 'Cache-Control' + 'Access-Control-Allow-Headers': 'Cache-Control', + 'X-Accel-Buffering': 'no', // Disable nginx buffering + 'Content-Encoding': 'identity' // Disable compression }); // Send initial connection message res.write('data: {"type": "connected", "message": "SSE connection established"}\n\n'); + + // Send keepalive heartbeat every 30 seconds + const heartbeatInterval = setInterval(() => { + res.write(': heartbeat\n\n'); + }, 30000); // Set up polling interval for game state updates const pollInterval = setInterval(async () => { @@ -90,13 +111,14 @@ app.get('/api/sse', (req, res) => { message: `Error fetching game stats: ${error.message}` })}\n\n`); } - }, 500); // Poll every 500ms + }, 250); // Poll every 250ms for faster updates // Clean up on client disconnect req.on('close', () => { sseConnections--; console.log(`[SSE] Connection #${connectionId} closed. Total: ${sseConnections}`); clearInterval(pollInterval); + clearInterval(heartbeatInterval); }); }); diff --git a/admin/src/services/adminService.ts b/admin/src/services/adminService.ts index b601096..3bf0098 100644 --- a/admin/src/services/adminService.ts +++ b/admin/src/services/adminService.ts @@ -47,6 +47,8 @@ class AdminService { await this.initializeServerUrl() this.callback = callback + + // Configure EventSource with better error handling this.eventSource = new EventSource('/api/sse') this.eventSource.onopen = () => { @@ -57,11 +59,14 @@ class AdminService { this.eventSource.onmessage = (event) => { try { + // Skip heartbeat messages + if (event.data.trim() === '') return + const data = JSON.parse(event.data) console.log('[AdminService] SSE message received:', data.type, data.timestamp) this.callback?.(data) } catch (error) { - console.error('[AdminService] Error parsing SSE message:', error) + console.error('[AdminService] Error parsing SSE message:', error, 'Raw data:', event.data) } } @@ -69,6 +74,14 @@ class AdminService { console.error('[AdminService] SSE connection error:', error) this.isConnected = false this.connectionCallback?.(false) + + // Auto-reconnect after 5 seconds + setTimeout(() => { + if (!this.isConnected) { + console.log('[AdminService] Attempting to reconnect...') + this.connect(this.callback!) + } + }, 5000) } }