import express from "express"; import { createServer } from "http"; import { Server } from "socket.io"; import cors from "cors"; import dotenv from "dotenv"; import path from "path"; import fs from "fs"; import { fileURLToPath } from "url"; import { GameManager } from "./services/gameManager"; import { setupSocketHandlers } from "./sockets/gameSocket"; // Get __dirname equivalent in ES modules const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Load environment variables // Priority: .env.local > .env (load .env first, then .env.local overwrites) // When running via ts-node: __dirname = backend/src/, so go up 1 level // When running compiled: __dirname = dist/backend/src/, so go up 3 levels const isDev = __dirname.includes("/src") && !__dirname.includes("/dist"); const levelsUp = isDev ? "../" : "../../../"; const envPath = path.join(__dirname, levelsUp, ".env"); const envLocalPath = path.join(__dirname, levelsUp, ".env.local"); // Load .env first (base configuration) if (fs.existsSync(envPath)) { dotenv.config({ path: envPath }); console.log("[Config] Loaded .env"); } else { console.log(`[Config] .env not found at: ${envPath}`); } // Load .env.local second (overrides .env for local development) if (fs.existsSync(envLocalPath)) { dotenv.config({ path: envLocalPath, override: true }); console.log("[Config] Loaded .env.local (overriding .env)"); } else { console.log(`[Config] .env.local not found at: ${envLocalPath}`); } const app = express(); const httpServer = createServer(app); const io = new Server(httpServer, { cors: { origin: process.env.NODE_ENV === "production" ? process.env.CLIENT_URL || "http://localhost:5173" : true, // Allow all origins in development methods: ["GET", "POST"], credentials: true } }); // Create GameManager instance const gameManager = new GameManager(); const PORT = parseInt(process.env.PORT || "3000", 10); const HOST = process.env.HOST || "0.0.0.0"; console.log(`[Config] Server Configuration:`); console.log(`[Config] PORT: ${PORT}`); console.log(`[Config] HOST: ${HOST}`); console.log(`[Config] NODE_ENV: ${process.env.NODE_ENV || "development"}`); console.log(`[Config] CLIENT_URL: ${process.env.CLIENT_URL || "not set"}`); // Middleware app.use(cors()); app.use(express.json()); // Serve static files from frontend build (for production) if (process.env.NODE_ENV === "production") { const frontendPath = path.join(__dirname, "../../app/dist"); app.use(express.static(frontendPath)); // Serve index.html for all routes (SPA support) app.get("*", (req: any, res: any, next: any) => { if (req.path.startsWith("/health") || req.path.startsWith("/socket.io")) { return next(); } res.sendFile(path.join(frontendPath, "index.html")); }); } // Health check endpoint app.get("/health", (_req: any, res: any) => { res.json({ status: "ok", timestamp: new Date().toISOString() }); }); // Socket.io connection handling io.on("connection", (socket: any) => { console.log(`New client connected: ${socket.id}`); // Setup game-related socket handlers setupSocketHandlers(io, socket, gameManager); // Echo test handler (for testing) socket.on("echo", (data: any, callback: any) => { const timestamp = new Date().toISOString(); const responseMessage = `Hello from server! Received: "${data.message}" at ${timestamp}`; console.log(`[Echo] Client ${socket.id}: ${data.message}`); // Send response via callback if (callback && typeof callback === "function") { callback({ message: responseMessage, serverTime: timestamp, clientTime: data.timestamp }); } }); socket.on("disconnect", () => { console.log(`Client disconnected: ${socket.id}`); }); }); // Start server httpServer.listen(PORT, HOST, () => { console.log(`Server running on ${HOST}:${PORT}`); console.log(`Health check: http://${HOST}:${PORT}/health`); console.log(`Environment: ${process.env.NODE_ENV || "development"}`); });