166 lines
4.6 KiB
JavaScript
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;
|