Stock-Rivals / server.ts
RonitP1's picture
Upload 16 files
b05b936 verified
import express from "express";
import { createServer } from "http";
import { Server } from "socket.io";
import path from "path";
import https from "https";
async function startServer() {
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
pingTimeout: 60000, // 60 seconds to wait for pong
pingInterval: 25000, // ping every 25 seconds to keep alive
cors: {
origin: "*",
methods: ["GET", "POST"]
},
});
// Ping every 14 minutes to prevent spin-down (if URL is provided)
const APP_URL = process.env.APP_URL || process.env.RAILWAY_STATIC_URL;
if (APP_URL) {
setInterval(() => {
const url = APP_URL.startsWith('http') ? APP_URL : `https://${APP_URL}`;
https.get(url, (res) => {
console.log(`Keep-alive ping to ${url} status:`, res.statusCode);
}).on('error', (err) => {
console.error(`Keep-alive ping to ${url} error:`, err.message);
});
}, 840000);
}
const PORT = parseInt(process.env.PORT || "3000");
// Room state management (minimal, just to relay)
const rooms = new Map();
io.on("connection", (socket) => {
console.log("User connected:", socket.id);
socket.on("join", ({ roomId, username, maxPlayers, playerId }) => {
if (!rooms.has(roomId)) {
rooms.set(roomId, {
hostId: socket.id,
hostPlayerId: playerId,
players: [],
maxPlayers: maxPlayers || 10
});
}
const room = rooms.get(roomId);
const existingPlayer = room.players.find(p => p.playerId === playerId);
if (existingPlayer) {
// Reconnection
existingPlayer.id = socket.id;
existingPlayer.name = username || existingPlayer.name;
socket.join(roomId);
// If they were host, update hostId to new socket.id
if (room.hostPlayerId === playerId) {
room.hostId = socket.id;
}
} else {
// New join
if (room.players.length >= room.maxPlayers) {
socket.emit("error_message", "Room is full");
return;
}
socket.join(roomId);
room.players.push({ id: socket.id, playerId, name: username });
}
io.to(roomId).emit("lobby_update", {
roomId,
players: room.players,
hostId: room.hostId,
maxPlayers: room.maxPlayers
});
});
socket.on("start_game", ({ roomId, initialState }) => {
io.to(roomId).emit("start_game", initialState);
});
socket.on("action", ({ roomId, action }) => {
const room = rooms.get(roomId);
if (room) {
io.to(room.hostId).emit("action_received", { playerId: socket.id, action });
}
});
socket.on("state_update", ({ roomId, state }) => {
io.to(roomId).emit("state_update", state);
});
socket.on("disconnect", () => {
console.log("User disconnected:", socket.id);
// We don't immediately remove players to allow reconnection.
// We only clean up if the room becomes completely empty or after a long timeout.
// For this simple implementation, we'll just leave them in the room.
// A more robust version would mark them as 'offline'.
});
});
if (process.env.NODE_ENV !== "production") {
const { createServer: createViteServer } = await import("vite");
const vite = await createViteServer({
server: { middlewareMode: true },
appType: "spa",
});
app.use(vite.middlewares);
} else {
const distPath = path.join(process.cwd(), "dist");
app.use(express.static(distPath));
app.get("*", (req, res) => {
const indexPath = path.join(distPath, "index.html");
res.sendFile(indexPath, (err) => {
if (err) {
res.status(500).send("Build artifacts not found. Please run 'npm run build' first.");
}
});
});
}
// Health check route
app.get("/api/health", (req, res) => {
res.json({ status: "ok", mode: process.env.NODE_ENV || 'development' });
});
httpServer.listen(PORT, "0.0.0.0", () => {
console.log(`Server running on port ${PORT} in ${process.env.NODE_ENV || 'development'} mode`);
});
}
startServer();