allow 3 send winner msgs, in case a client reconnects

This commit is contained in:
evanpelle
2026-03-14 11:30:12 -07:00
parent da6969e726
commit f6167d2d94
2 changed files with 11 additions and 6 deletions
+7 -4
View File
@@ -7,11 +7,14 @@ const MAX_BYTES_PER_MINUTE = 25 * 1024; // 25KB/min per client
const MAX_INTENT_BYTES = 500; // intents are stored in turns, keep them small
export type RateLimitResult = "ok" | "limit" | "kick";
// Allow 3 winner messages per client since a player can rejoin and resend.
const MAX_WINNER_MSGS = 3;
interface ClientBucket {
perSecond: RateLimiter;
perMinute: RateLimiter;
bytesPerMinute: RateLimiter;
hasSentWinnerMsg: boolean;
winnerMsgCount: number;
}
export class ClientMsgRateLimiter {
@@ -23,8 +26,8 @@ export class ClientMsgRateLimiter {
// Winner message contains stats for all players and can be large (100s of KB).
// It bypasses the byte rate limit but is strictly limited to one per client.
if (type === "winner") {
if (bucket.hasSentWinnerMsg) return "kick";
bucket.hasSentWinnerMsg = true;
if (bucket.winnerMsgCount >= MAX_WINNER_MSGS) return "kick";
bucket.winnerMsgCount++;
return "ok";
}
@@ -61,7 +64,7 @@ export class ClientMsgRateLimiter {
tokensPerInterval: MAX_BYTES_PER_MINUTE,
interval: "minute",
}),
hasSentWinnerMsg: false,
winnerMsgCount: 0,
};
this.buckets.set(clientID, bucket);
return bucket;
+4 -2
View File
@@ -42,9 +42,11 @@ describe("ClientMsgRateLimiter", () => {
expect(limiter.check(CLIENT_A, "winner", 50000)).toBe("ok");
});
it("kicks on second winner message", () => {
it("allows up to 3 winner messages", () => {
const limiter = new ClientMsgRateLimiter();
limiter.check(CLIENT_A, "winner", 50000);
expect(limiter.check(CLIENT_A, "winner", 50000)).toBe("ok");
expect(limiter.check(CLIENT_A, "winner", 50000)).toBe("ok");
expect(limiter.check(CLIENT_A, "winner", 50000)).toBe("ok");
expect(limiter.check(CLIENT_A, "winner", 50000)).toBe("kick");
});