Spaces:
Sleeping
Sleeping
File size: 4,227 Bytes
b05b936 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | 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();
|