fix(bot): harden ws.ts integration seam
- maybeAbandon Promise no longer floats from setTimeout - broadcastSinceLast loses dead extra parameter - bot-slot token is randomized so a third party can't hijack the bot's color by guessing a fixed placeholder
This commit is contained in:
@@ -93,10 +93,11 @@ function makeSlot(token: PlayerToken, now: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function makeBotSlot(now: number) {
|
function makeBotSlot(now: number) {
|
||||||
// Synthetic slot: occupies the player's color but never connects.
|
// Synthetic slot: occupies the player's color but never connects. The token
|
||||||
// Token is a 24-char placeholder; never matches a real client.
|
// is randomized (same shape as a real client token) so a third party can't
|
||||||
|
// hijack the bot's color by guessing a fixed placeholder.
|
||||||
return {
|
return {
|
||||||
token: 'bot' + 'x'.repeat(21),
|
token: newPlayerToken(),
|
||||||
socket: null,
|
socket: null,
|
||||||
joinedAt: now,
|
joinedAt: now,
|
||||||
rateBucket: { tokens: RATE_LIMIT.capacity, last: now },
|
rateBucket: { tokens: RATE_LIMIT.capacity, last: now },
|
||||||
|
|||||||
@@ -32,20 +32,12 @@ async function pokeBot(game: Game): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function broadcastSinceLast(
|
function broadcastSinceLast(game: Game): void {
|
||||||
game: Game,
|
|
||||||
extra?: { touchedPieceFor?: Color; touchedPiece?: import('@blind-chess/shared').Square },
|
|
||||||
): void {
|
|
||||||
for (const c of ['w', 'b'] as const) {
|
for (const c of ['w', 'b'] as const) {
|
||||||
const lastIdx = game.lastBroadcastIdx[c];
|
const lastIdx = game.lastBroadcastIdx[c];
|
||||||
const all = game.announcements;
|
const all = game.announcements;
|
||||||
const slice = all.slice(lastIdx).filter((a) => a.audience === 'both' || a.audience === c);
|
const slice = all.slice(lastIdx).filter((a) => a.audience === 'both' || a.audience === c);
|
||||||
sendUpdateTo(
|
sendUpdateTo(game, c, slice);
|
||||||
game,
|
|
||||||
c,
|
|
||||||
slice,
|
|
||||||
extra?.touchedPieceFor === c ? { touchedPiece: extra.touchedPiece } : undefined,
|
|
||||||
);
|
|
||||||
game.lastBroadcastIdx[c] = all.length;
|
game.lastBroadcastIdx[c] = all.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,7 +241,7 @@ function onClose(ctx: SocketCtx): void {
|
|||||||
if (game.status === 'active') {
|
if (game.status === 'active') {
|
||||||
game.disconnectAt[color] = Date.now();
|
game.disconnectAt[color] = Date.now();
|
||||||
// Schedule grace timer.
|
// Schedule grace timer.
|
||||||
setTimeout(() => maybeAbandon(game, color), GRACE_MS + 100);
|
setTimeout(() => { void maybeAbandon(game, color); }, GRACE_MS + 100);
|
||||||
}
|
}
|
||||||
notifyPeer(game, color, false, Date.now() + GRACE_MS);
|
notifyPeer(game, color, false, Date.now() + GRACE_MS);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user