mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-10 11:54:35 +00:00
6e23c26723
Verified findings from a code audit and fixed the legitimate, well-scoped ones. Notable: crashed cluster workers were never respawned because worker.process.env is undefined in the exit handler, so WORKER_ID was always undefined (confirmed empirically). Fixes: - Master: respawn crashed workers via a worker.id -> WORKER_ID registry instead of the always-undefined worker.process.env.WORKER_ID. - GameServer: report Finished when _hasEnded so abandoned private lobbies are cleaned up instead of lingering until max duration; remove sockets from the websockets Set on close/rejoin; reset hasReachedMaxPlayerCount when a player leaves before start (avoids premature under-capacity starts, e.g. ranked 1v1). - Worker: reap WebSocket connections that upgrade but never authenticate (30s join timeout) to prevent Slowloris-style FD exhaustion. - ServerEnv: add a 1h TTL to the cached JWKS public key so IdP key rotation doesn't break all logins until a restart. - SpawnExecution: ignore a client-supplied tile in random-spawn mode so a forged spawn intent can't pick its own coordinates (+ test). - GameRenderer / MultiTabDetector: remove window listeners on teardown (bind once so add/removeEventListener references match). - LocalPersistantStats: guard localStorage access with try/catch so sandboxed iframes / disabled-storage contexts don't crash game start. - StructurePass: reuse per-frame uniform arrays instead of allocating each draw. Also documents a known, intentionally-unfixed auth gap on /api/archive_singleplayer_game (pending a product decision on logged-out users). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
46 lines
1.7 KiB
TypeScript
46 lines
1.7 KiB
TypeScript
import { SpawnExecution } from "../src/core/execution/SpawnExecution";
|
|
import { Game, PlayerInfo, PlayerType } from "../src/core/game/Game";
|
|
import { TileRef } from "../src/core/game/GameMap";
|
|
import { GameID } from "../src/core/Schemas";
|
|
import { setup } from "./util/Setup";
|
|
|
|
const GAME_ID: GameID = "game_id";
|
|
const PLAYER_ID = "p_id";
|
|
|
|
async function spawnWith(
|
|
randomSpawn: boolean,
|
|
x: number,
|
|
y: number,
|
|
): Promise<{ game: Game; injected: TileRef; spawnTile: TileRef | undefined }> {
|
|
const game = await setup("plains", { randomSpawn });
|
|
game.addPlayer(new PlayerInfo("p", PlayerType.Human, null, PLAYER_ID));
|
|
const injected = game.map().ref(x, y);
|
|
game.addExecution(
|
|
new SpawnExecution(GAME_ID, game.player(PLAYER_ID).info(), injected),
|
|
);
|
|
game.executeNextTick(); // init the execution
|
|
game.executeNextTick(); // run the execution
|
|
return { game, injected, spawnTile: game.player(PLAYER_ID).spawnTile() };
|
|
}
|
|
|
|
describe("Random spawn cannot be overridden by a client-supplied tile", () => {
|
|
test("non-random mode honors the requested tile", async () => {
|
|
const { injected, spawnTile } = await spawnWith(false, 50, 50);
|
|
expect(spawnTile).toBe(injected);
|
|
});
|
|
|
|
test("random mode ignores the injected tile", async () => {
|
|
const a = await spawnWith(true, 50, 50);
|
|
const b = await spawnWith(true, 60, 60);
|
|
|
|
// The player still spawns on a valid land tile.
|
|
expect(a.spawnTile).toBeDefined();
|
|
expect(a.game.isLand(a.spawnTile!)).toBe(true);
|
|
|
|
// If the injected tile were honored, the two runs would spawn at the two
|
|
// distinct injected tiles. Because random spawn ignores it and uses the
|
|
// deterministic per-player seed instead, both runs land on the same tile.
|
|
expect(a.spawnTile).toBe(b.spawnTile);
|
|
});
|
|
});
|