const express = require('express'); const http = require('http'); const socketIO = require('socket.io'); const cors = require('cors'); const path = require('path'); const { initializeDatabase, db, supabase } = require('./database'); const GameManager = require('./gameManager'); const app = express(); const server = http.createServer(app); const io = socketIO(server, { cors: { origin: "*", methods: ["GET", "POST"] } }); const PORT = process.env.PORT || 3000; // Middleware app.use(cors()); app.use(express.json()); app.use(express.static(path.join(__dirname))); // Serve index.html for root route app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'index.html')); }); // Database health check endpoint with detailed diagnostics app.get('/api/db-status', async (req, res) => { const startTime = Date.now(); const dbConfig = require('./db.config.js'); let status = { connected: false, latency: 0, writeCapable: false, timestamp: new Date().toISOString(), error: null, // Additional diagnostic info for testing phase supabaseUrl: dbConfig.supabaseUrl || 'unknown', database: 'Supabase PostgreSQL', connectionType: 'Supabase Client' }; try { console.log(`[DB-STATUS] Testing Supabase connection to ${status.supabaseUrl}...`); // Test connection with a simple query const { data, error } = await supabase .from('players') .select('id') .limit(1); const latency = Date.now() - startTime; if (!error || error.code === 'PGRST116') { // PGRST116 = no rows found (table exists but empty) status.connected = true; status.latency = latency; console.log(`[DB-STATUS] ✅ Connection successful (${latency}ms)`); // Test write capability try { console.log(`[DB-STATUS] Testing write capability...`); // Try to insert a test record const testUsername = `_test_${Date.now()}`; const { data: insertData, error: insertError } = await supabase .from('players') .insert([{ username: testUsername }]) .select('id') .single(); if (!insertError && insertData) { // Clean up test record await supabase .from('players') .delete() .eq('id', insertData.id); status.writeCapable = true; console.log(`[DB-STATUS] ✅ Write test successful`); } else { throw insertError; } } catch (writeError) { console.error(`[DB-STATUS] ❌ Write test failed:`, writeError.message); status.writeCapable = false; status.error = `Write test failed: ${writeError.message}`; } } else { throw error; } } catch (error) { console.error(`[DB-STATUS] ❌ Connection failed:`, error.message); console.error(`[DB-STATUS] Error details:`, error); status.connected = false; status.latency = Date.now() - startTime; // Provide more detailed error messages let errorMessage = error.message; if (error.code === '42P01') { errorMessage = `Table 'players' does not exist. Please run the SQL schema in Supabase SQL Editor.`; } else if (error.message && error.message.includes('Invalid API key')) { errorMessage = `Invalid Supabase API key. Check your db.config.js file.`; } else if (error.message && error.message.includes('fetch')) { errorMessage = `Cannot reach Supabase. Check your supabaseUrl in db.config.js.`; } status.error = errorMessage; } res.json(status); }); // Initialize game manager const gameManager = new GameManager(io, db); // Socket.io connection handling io.on('connection', (socket) => { console.log(`✅ Player connected: ${socket.id}`); // Player registration socket.on('register_player', async (data) => { console.log('📝 Registration request from', socket.id, 'username:', data.username); const result = await gameManager.registerPlayer(socket, data.username); console.log('📝 Registration result:', result); socket.emit('registration_result', result); }); // Send challenge socket.on('send_challenge', async (data) => { const result = await gameManager.sendChallenge(socket, data.targetUsername, data.boardSize); socket.emit('challenge_result', result); }); // Accept challenge socket.on('accept_challenge', async (data) => { const result = await gameManager.acceptChallenge(socket, data.challengeId); if (result.success) { socket.emit('challenge_accepted', result); } else { socket.emit('challenge_error', result); } }); // Decline challenge socket.on('decline_challenge', (data) => { gameManager.declineChallenge(socket, data.challengeId); }); // Make move socket.on('make_move', async (data) => { const result = await gameManager.handleMove(socket, data); socket.emit('move_result', result); }); // Heartbeat to keep session alive socket.on('heartbeat', async () => { await gameManager.heartbeat(socket); }); // Request active players socket.on('request_active_players', async () => { const activePlayers = await db.getActivePlayers(); socket.emit('active_players_update', activePlayers); }); // Disconnect socket.on('disconnect', async () => { console.log(`❌ Player disconnected: ${socket.id}`); await gameManager.handleDisconnect(socket); }); }); // Cleanup stale sessions periodically setInterval(async () => { await db.cleanupStaleSessions(); }, 60000); // Every minute // Error handling process.on('unhandledRejection', (error) => { console.error('Unhandled promise rejection:', error); }); // Initialize database and start server async function startServer() { try { await initializeDatabase(); server.listen(PORT, () => { console.log(''); console.log('═══════════════════════════════════════════════════'); console.log('🎮 Connect-5 Multiplayer Server'); console.log('═══════════════════════════════════════════════════'); console.log(`🌐 Server running on port ${PORT}`); console.log(`📡 WebSocket server ready`); console.log(`🗄️ Database connected`); console.log(''); console.log(`🎯 Open your browser to: http://localhost:${PORT}`); console.log('═══════════════════════════════════════════════════'); console.log(''); }); } catch (error) { console.error('Failed to start server:', error); process.exit(1); } } startServer();