/** * CHAMPIONS OF LUNERARIA - Portal & Control Center Server * Production-ready, zero-dependency Node.js HTTP server with REST APIs, * Server-Sent Events (SSE) for live telemetry, and static asset streaming. */ const http = require('http'); const fs = require('fs'); const path = require('path'); const url = require('url'); const telemetry = require('./src/data/telemetry'); const PORT = parseInt(process.env.PORT, 10) || 8080; const PUBLIC_DIR = path.join(__dirname, 'public'); const MIME_TYPES = { '.html': 'text/html; charset=UTF-8', '.css': 'text/css; charset=UTF-8', '.js': 'application/javascript; charset=UTF-8', '.json': 'application/json; charset=UTF-8', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.mp4': 'video/mp4', '.ttf': 'font/ttf', '.otf': 'font/otf', '.woff': 'font/woff', '.woff2': 'font/woff2' }; // Connected SSE clients for live metrics push const sseClients = new Set(); // Broadcast SSE update every 2 seconds setInterval(() => { if (sseClients.size === 0) return; const payload = JSON.stringify(telemetry.getLiveMetrics()); sseClients.forEach(res => { try { res.write(`data: ${payload}\n\n`); } catch (e) { sseClients.delete(res); } }); }, 2000); const server = http.createServer((req, res) => { const parsedUrl = url.parse(req.url, true); const pathname = parsedUrl.pathname; // CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } // ------------------------------------------------------------------------ // API Routes // ------------------------------------------------------------------------ if (pathname === '/api/metrics' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(telemetry.getLiveMetrics())); return; } if (pathname === '/api/benchmarks' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(telemetry.getBenchmarks())); return; } if (pathname === '/api/downloads/history' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(telemetry.getDailyDownloads())); return; } if (pathname === '/api/logs' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(telemetry.getLogs())); return; } // Server-Sent Events (SSE) Live Feed if (pathname === '/api/events' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); res.write(`data: ${JSON.stringify(telemetry.getLiveMetrics())}\n\n`); sseClients.add(res); req.on('close', () => { sseClients.delete(res); }); return; } // Operations Control APIs (POST) if (pathname.startsWith('/api/control/') && req.method === 'POST') { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', () => { let data = {}; try { if (body) data = JSON.parse(body); } catch (e) {} if (pathname === '/api/control/tickrate') { const newRate = telemetry.setTickrate(data.rate || 20); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, targetTickrate: newRate })); return; } if (pathname === '/api/control/maintenance') { const mode = telemetry.toggleMaintenance(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, maintenanceMode: mode })); return; } if (pathname === '/api/control/spike') { const newBaseline = telemetry.simulateSpike(data.amount || 25); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, ccuBaseline: newBaseline })); return; } if (pathname === '/api/control/broadcast') { const msg = data.message || 'Server-Ankündigung'; telemetry.addLog('WARN', 'BROADCAST', msg); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, message: msg })); return; } res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Unknown control action' })); }); return; } // ------------------------------------------------------------------------ // Static Page Routes // ------------------------------------------------------------------------ let filePath = ''; if (pathname === '/' || pathname === '/index.html') { filePath = path.join(PUBLIC_DIR, 'index.html'); } else if (pathname === '/dashboard' || pathname === '/dashboard.html') { filePath = path.join(PUBLIC_DIR, 'dashboard.html'); } else { filePath = path.join(PUBLIC_DIR, pathname); } // Security: prevent directory traversal if (!filePath.startsWith(PUBLIC_DIR)) { res.writeHead(403); res.end('Access Denied'); return; } fs.stat(filePath, (err, stats) => { if (err || !stats.isFile()) { res.writeHead(404, { 'Content-Type': 'text/plain; charset=UTF-8' }); res.end('404 Not Found - Champions of Luneraria'); return; } const ext = path.extname(filePath).toLowerCase(); const contentType = MIME_TYPES[ext] || 'application/octet-stream'; // Support video streaming (HTTP 206 Partial Content) if (ext === '.mp4') { const range = req.headers.range; const fileSize = stats.size; if (range) { const parts = range.replace(/bytes=/, '').split('-'); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1; const chunksize = (end - start) + 1; const fileStream = fs.createReadStream(filePath, { start, end }); res.writeHead(206, { 'Content-Range': `bytes ${start}-${end}/${fileSize}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4' }); fileStream.pipe(res); return; } } // Standard static file response res.writeHead(200, { 'Content-Type': contentType, 'Content-Length': stats.size, 'Cache-Control': ext === '.html' ? 'no-cache' : 'public, max-age=86400' }); fs.createReadStream(filePath).pipe(res); }); }); server.listen(PORT, '0.0.0.0', () => { console.log('==============================================================='); console.log(` ⚔️ CHAMPIONS OF LUNERARIA - PORTAL & CONTROL CENTER READY`); console.log('==============================================================='); console.log(` 🌐 Öffentliche Website: http://localhost:${PORT}/`); console.log(` 🎛️ Schaltzentrale / Ops: http://localhost:${PORT}/dashboard`); console.log(` 📊 Live Telemetrie API: http://localhost:${PORT}/api/metrics`); console.log(` 📈 Benchmarks API: http://localhost:${PORT}/api/benchmarks`); console.log('---------------------------------------------------------------'); });