From c756d45e2426c168622e14546b322162187182a8 Mon Sep 17 00:00:00 2001 From: cottongin Date: Sat, 21 Mar 2026 00:07:10 -0400 Subject: [PATCH] fix: guard shard event emissions on both manuallyStopped and gameFinished Prevent stale events from shards that ended naturally (not via stopMonitor). handleMessage now gates on gameFinished in addition to manuallyStopped. handleEntityUpdate properly cleans up on gameFinished by emitting room.disconnected, removing from activeShards, and calling disconnect. handleError also removes from activeShards. Probe message handler and status broadcast bail out when the shard is stopped or the game has finished. Made-with: Cursor --- backend/utils/ecast-shard-client.js | 37 ++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/backend/utils/ecast-shard-client.js b/backend/utils/ecast-shard-client.js index 6a0a3a2..6709ae5 100644 --- a/backend/utils/ecast-shard-client.js +++ b/backend/utils/ecast-shard-client.js @@ -114,7 +114,9 @@ class EcastShardClient { this.stopStatusBroadcast(); this.statusInterval = setInterval(() => { this._refreshPlayerCount().finally(() => { - this.onEvent('game.status', this.getSnapshot()); + if (!this.manuallyStopped && !this.gameFinished) { + this.onEvent('game.status', this.getSnapshot()); + } }); }, 20000); } @@ -146,7 +148,7 @@ class EcastShardClient { const timeout = setTimeout(() => done(probe), 10000); probe.on('message', (data) => { - if (welcomed) return; + if (welcomed || this.manuallyStopped) { clearTimeout(timeout); done(probe); return; } try { const msg = JSON.parse(data.toString()); if (msg.opcode === 'client/welcome') { @@ -155,15 +157,17 @@ class EcastShardClient { if (playerCount > this.playerCount || playerNames.length !== this.playerNames.length) { this.playerCount = playerCount; this.playerNames = playerNames; - this.onEvent('lobby.player-joined', { - sessionId: this.sessionId, - gameId: this.gameId, - roomCode: this.roomCode, - playerName: playerNames[playerNames.length - 1] || '', - playerCount, - players: [...playerNames], - maxPlayers: this.maxPlayers, - }); + if (!this.manuallyStopped) { + this.onEvent('lobby.player-joined', { + sessionId: this.sessionId, + gameId: this.gameId, + roomCode: this.roomCode, + playerName: playerNames[playerNames.length - 1] || '', + playerCount, + players: [...playerNames], + maxPlayers: this.maxPlayers, + }); + } } else if (playerCount !== this.playerCount) { this.playerCount = playerCount; this.playerNames = playerNames; @@ -196,6 +200,7 @@ class EcastShardClient { } handleMessage(message) { + if (this.manuallyStopped || this.gameFinished) return; switch (message.opcode) { case 'client/welcome': this.handleWelcome(message.result); @@ -301,6 +306,15 @@ class EcastShardClient { playerCount: this.playerCount, players: [...this.playerNames], }); + this.onEvent('room.disconnected', { + sessionId: this.sessionId, + gameId: this.gameId, + roomCode: this.roomCode, + reason: 'room_closed', + finalPlayerCount: this.playerCount, + }); + activeShards.delete(`${this.sessionId}-${this.gameId}`); + this.disconnect(); } } } @@ -367,6 +381,7 @@ class EcastShardClient { reason: 'room_closed', finalPlayerCount: this.playerCount, }); + activeShards.delete(`${this.sessionId}-${this.gameId}`); this.disconnect(); } }