import http from 'http'; import process from 'process'; import { config } from '../../internal/config/config.js'; import { logger } from '../../internal/logger/logger.js'; import { RateLimiter } from '../../internal/middleware/ratelimit.js'; import { Store } from '../../internal/session/store.js'; import { Hub } from '../../internal/ws/hub.js'; import { setupWebSocket } from '../../internal/ws/handler.js'; import { startTimer } from '../../internal/timer/timer.js'; import { initializeDatabase, logSessionCreated } from '../../internal/db/db.js'; // Bootstrapping log logger.info('server_initiating', { addr: config.serverAddr, environment: config.environment }); const store = new Store(); const hub = new Hub(store); const limiter = new RateLimiter(config.rateLimitPerMin, 60000); // 1 minute window // Configure standard HTTP server to manage REST actions const server = http.createServer((req, res) => { const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`); // Endpoint: GET /health if (url.pathname === '/health' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'ok', active_sessions: store.getActiveCount(), connected_clients: hub.getConnectedClientsCount() })); return; } // Endpoint: POST /sessions // Pre-creates call session mapping. Authenticated using X-API-Key header. if (url.pathname === '/sessions' && req.method === 'POST') { const apiKeyHeader = req.headers['x-api-key']; if (!apiKeyHeader || apiKeyHeader !== config.apiKey) { logger.warn('unauthorized_session_creation_attempt', { remote_ip: req.socket.remoteAddress }); res.writeHead(401, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'unauthorized' })); return; } let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', async () => { let params; try { params = JSON.parse(body); } catch (err) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'invalid_json' })); return; } const { ride_id, driver_id, passenger_id, driver_ip, passenger_ip } = params; if (!ride_id || !driver_id || !passenger_id) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'missing_parameters' })); return; } let sess; try { // Enforce hard 120s maximum call duration sess = store.createSession(ride_id, driver_id, passenger_id, 120000, driver_ip, passenger_ip); } catch (err) { logger.warn('session_creation_failed', { ride_id, error: err.message }); res.writeHead(409, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'session_exists' })); return; } // Start 120s timeout countdown sess.timer = startTimer(120000, () => { hub.forceEndSession(sess.sessionID, 'max_duration_reached'); }); // Log session creation to database await logSessionCreated(sess.sessionID, sess.rideID, sess.driverID, sess.passengerID, sess.driverIP, sess.passengerIP); logger.info('session_created', { session_id: sess.sessionID, ride_id: sess.rideID, driver_id: sess.driverID, passenger_id: sess.passengerID, driver_ip: sess.driverIP, passenger_ip: sess.passengerIP }); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ session_id: sess.sessionID, ride_id: sess.rideID, expires_in: 120 })); }); return; } res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found'); }); // Bind WebSocket upgrades interceptor setupWebSocket(server, hub, limiter); // Resolve address binding parts const [host, portStr] = config.serverAddr.split(':'); const port = parseInt(portStr, 10); // Initialize database first then start server initializeDatabase().then(() => { server.listen(port, host, () => { logger.info('server_running', { addr: config.serverAddr }); }); }); // Graceful exit handling function shutdown(signal) { logger.info('server_stopping', { signal }); // Close HTTP server to stop accepting new traffic server.close((err) => { if (err) { logger.error('server_close_error', { error: err.message }); } logger.info('server_stopped_cleanly'); process.exit(0); }); // Warn active sockets and terminate connections hub.shutdownGracefully(); // Force close after 10s maximum timeout setTimeout(() => { logger.warn('server_shutdown_timeout_force_exit'); process.exit(1); }, 10000).unref(); } process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM'));