rate limit start & create private lobby, change max game duration to 3 hours

This commit is contained in:
Evan
2025-02-23 15:56:14 -08:00
parent dcde7136cd
commit 65878a8886
3 changed files with 49 additions and 16 deletions
+10 -4
View File
@@ -63,11 +63,11 @@ export class GameManager {
hasActiveGame(gameID: GameID): boolean {
const game = this.games
.filter((g) => g.id == gameID)
.filter(
(g) => g.phase() == GamePhase.Lobby || g.phase() == GamePhase.Active
)
.find((g) => g.id == gameID);
return game != null;
);
return game.length > 0;
}
// TODO: stop private games to prevent memory leak.
@@ -106,7 +106,13 @@ export class GameManager {
.forEach((g) => {
g.start();
});
finished.map((g) => g.endGame()); // Fire and forget
finished.forEach((g) => {
try {
g.endGame();
} catch (error) {
console.log(`error ending game ${g.id}: `, error);
}
});
this.games = [...lobbies, ...active];
}
}
+4 -7
View File
@@ -32,7 +32,7 @@ export class GameServer {
duration: 1, // per 1 second
});
private maxGameDuration = 5 * 60 * 60 * 1000; // 5 hours
private maxGameDuration = 3 * 60 * 60 * 1000; // 3 hours
private turns: Turn[] = [];
private intents: Intent[] = [];
@@ -224,8 +224,8 @@ export class GameServer {
async endGame() {
// Close all WebSocket connections
clearInterval(this.endTurnIntervalID);
this.activeClients.forEach((client) => {
client.ws.removeAllListeners("message"); // TODO: remove this?
this.allClients.forEach((client) => {
client.ws.removeAllListeners("message");
if (client.ws.readyState === WebSocket.OPEN) {
client.ws.close(1000, "game has ended");
}
@@ -298,10 +298,7 @@ export class GameServer {
}
}
this.activeClients = alive;
if (
now >
this.createdAt + this.config.lobbyLifetime() + this.maxGameDuration
) {
if (now > this.createdAt + this.maxGameDuration) {
console.warn(`${this.id}: game past max duration ${this.id}`);
return GamePhase.Finished;
}
+35 -5
View File
@@ -49,6 +49,11 @@ const rateLimiter = new RateLimiterMemory({
duration: 1, // per 1 second
});
const updateRateLimiter = new RateLimiterMemory({
points: 10,
duration: 240, // 4 minutes
});
const gm = new GameManager(getServerConfig());
const bot = new DiscordBot();
@@ -65,15 +70,32 @@ app.get("/lobbies", (req: Request, res: Response) => {
res.send(lobbiesString);
});
app.post("/private_lobby", (req, res) => {
app.post("/private_lobby", async (req, res) => {
let clientIP = "";
try {
clientIP = req.ip || req.socket.remoteAddress || "unknown";
await updateRateLimiter.consume(clientIP);
} catch (error) {
console.warn(`create private lobby rate limited for IP ${clientIP}`);
return;
}
const id = gm.createPrivateGame();
console.log("creating private lobby with id ${id}");
console.log(`ip ${clientIP} creating private lobby with id ${id}`);
res.json({
id: id,
});
});
app.post("/archive_singleplayer_game", (req, res) => {
app.post("/archive_singleplayer_game", async (req, res) => {
let clientIP = "";
try {
clientIP = req.ip || req.socket.remoteAddress || "unknown";
await updateRateLimiter.consume(clientIP);
} catch (error) {
console.warn(`archive singleplayer game limited for IP ${clientIP}`);
return;
}
try {
const gameRecord: GameRecord = req.body;
const clientIP = req.ip || req.socket.remoteAddress || "unknown"; // Added this line
@@ -99,12 +121,20 @@ app.post("/archive_singleplayer_game", (req, res) => {
}
});
app.post("/start_private_lobby/:id", (req, res) => {
app.post("/start_private_lobby/:id", async (req, res) => {
let clientIP = "";
try {
clientIP = req.ip || req.socket.remoteAddress || "unknown";
await updateRateLimiter.consume(clientIP);
} catch (error) {
console.warn(`start private lobby rate limited for IP ${clientIP}`);
return;
}
console.log(`starting private lobby with id ${req.params.id}`);
gm.startPrivateGame(req.params.id);
});
app.put("/private_lobby/:id", (req, res) => {
app.put("/private_lobby/:id", async (req, res) => {
const lobbyID = req.params.id;
gm.updateGameConfig(lobbyID, {
gameMap: req.body.gameMap,