retropulse/backend/server.js
2025-09-02 22:27:25 +05:30

166 lines
4.6 KiB
JavaScript

// backend/server.js
// Load environment variables from .env file
require('dotenv').config();
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const path = require('path');
// Create Express app and HTTP server
const app = express();
const server = http.createServer(app);
const PORT = process.env.PORT || 3000;
// Middleware to parse JSON requests
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Serve static files from public directory
app.use(express.static(path.join(__dirname, '../public')));
// Basic route for the main page
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, '../public/index.html'));
});
// Route for overlay page with username parameter
app.get('/overlay/:username', (req, res) => {
res.sendFile(path.join(__dirname, '../public/overlay.html'));
});
// API Routes
const apiRoutes = require('./routes/api');
app.use('/api', apiRoutes);
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'OK',
message: 'RetroPulse server is running',
timestamp: new Date().toISOString()
});
});
// WebSocket Setup
const retroClient = require('./retroClient');
// Create WebSocket server
const wss = new WebSocket.Server({
server,
path: '/ws'
});
// Store active connections by username
const userConnections = new Map();
wss.on('connection', async (ws, req) => {
// Extract username from query parameters
const url = new URL(req.url, `http://localhost:${PORT}`);
const username = url.searchParams.get('user');
if (!username) {
ws.send(JSON.stringify({
error: 'Username parameter is required (?user=yourname)'
}));
ws.close();
return;
}
console.log(`🔌 WebSocket connected for user: ${username}`);
// Store the connection
userConnections.set(username, ws);
// Send initial data immediately
try {
const initialData = await retroClient.getOverlayData(username, 5);
ws.send(JSON.stringify({
type: 'initial',
data: initialData
}));
} catch (error) {
ws.send(JSON.stringify({
type: 'error',
message: `Failed to load initial data: ${error.message}`
}));
}
// Set up polling for updates every 10 seconds
const pollInterval = setInterval(async () => {
if (ws.readyState !== WebSocket.OPEN) {
clearInterval(pollInterval);
userConnections.delete(username);
return;
}
try {
const updatedData = await retroClient.getOverlayData(username, 5);
ws.send(JSON.stringify({
type: 'update',
data: updatedData
}));
} catch (error) {
console.error(`Error fetching updates for ${username}:`, error.message);
}
}, 10000); // Poll every 10 seconds
// Handle client disconnect
ws.on('close', () => {
clearInterval(pollInterval);
userConnections.delete(username);
console.log(`🔌 WebSocket disconnected for user: ${username}`);
});
ws.on('error', (error) => {
console.error(`WebSocket error for ${username}:`, error.message);
});
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error('Server error:', err);
res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? err.message : 'Something went wrong'
});
});
// Handle 404 for unmatched routes
app.use((req, res) => {
res.status(404).json({
error: 'Not found',
message: `Route ${req.method} ${req.path} not found`
});
});
// Start the server (HTTP and WebSocket together)
server.listen(PORT, () => {
console.log(`🎮 RetroPulse server is running on http://localhost:${PORT}`);
console.log(`📊 Health check: http://localhost:${PORT}/health`);
console.log(`🎯 Main page: http://localhost:${PORT}/`);
console.log(`📺 Overlay example: http://localhost:${PORT}/overlay/your-username`);
console.log(`🔌 WebSocket endpoint: ws://localhost:${PORT}/ws?user=your-username`);
});
// Graceful shutdown handling
process.on('SIGTERM', () => {
console.log('📴 Received SIGTERM, shutting down gracefully');
server.close(() => {
console.log('✅ Server closed');
process.exit(0);
});
});
process.on('SIGINT', () => {
console.log('📴 Received SIGINT, shutting down gracefully');
server.close(() => {
console.log('✅ Server closed');
process.exit(0);
});
});
// Export server for potential testing
module.exports = server;