Store full game for singleplayer, add more validation (#2031)

## Description:

onunload allows up to 64kb, but reducing the number of hash messages and
compressing using gzip, we can reduce the GameRecord size to stay under
64kb. I played a 10 minute game and the compressed GameRecord was only a
few kb.

Also verify the game is singleplayer and has only 1 player

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

evan
This commit is contained in:
evanpelle
2025-09-08 18:14:08 -07:00
committed by GitHub
parent 043462e28a
commit defb6bb1d4
6 changed files with 119 additions and 51 deletions
+37 -11
View File
@@ -1,3 +1,4 @@
import compression from "compression";
import express, { NextFunction, Request, Response } from "express";
import rateLimit from "express-rate-limit";
import http from "http";
@@ -81,6 +82,7 @@ export async function startWorker() {
});
app.set("trust proxy", 3);
app.use(compression());
app.use(express.json());
app.use(express.static(path.join(__dirname, "../../out")));
app.use(
@@ -247,18 +249,42 @@ export async function startWorker() {
});
app.post("/api/archive_singleplayer_game", async (req, res) => {
const result = GameRecordSchema.safeParse(req.body);
if (!result.success) {
const error = z.prettifyError(result.error);
log.info(error);
return res.status(400).json({ error });
}
try {
const record = req.body;
const gameRecord: GameRecord = result.data;
archive(gameRecord);
res.json({
success: true,
});
const result = GameRecordSchema.safeParse(record);
if (!result.success) {
const error = z.prettifyError(result.error);
log.info(error);
return res.status(400).json({ error });
}
const gameRecord: GameRecord = result.data;
if (gameRecord.info.config.gameType !== GameType.Singleplayer) {
log.warn(
`cannot archive singleplayer with game type ${gameRecord.info.config.gameType}`,
{
gameID: gameRecord.info.gameID,
},
);
return res.status(400).json({ error: "Invalid request" });
}
if (result.data.info.players.length !== 1) {
log.warn(`cannot archive singleplayer game multiple players`, {
gameID: gameRecord.info.gameID,
});
return res.status(400).json({ error: "Invalid request" });
}
archive(gameRecord);
res.json({
success: true,
});
} catch (error) {
log.error("Error processing archive request:", error);
res.status(500).json({ error: "Internal server error" });
}
});
app.post("/api/kick_player/:gameID/:clientID", async (req, res) => {