Fix cross-instance timer communication and expired timer handling

- GET_TIME_REMAINING now publishes response through Redis for cross-instance communication
- SOURCE_TIMER_EVENT handles GET_TIME_REMAINING even when timer is not running locally
- Returns 0 time remaining when timer has expired or timerParams is null
- Updated test to expect Redis publish instead of direct method call

Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-24 04:46:39 +00:00
parent a2f88446d9
commit 4e0f3d5a33
3 changed files with 31 additions and 13 deletions

View File

@@ -55,7 +55,25 @@ async function handleTimerCommand (timerEventSubtype, game, socketId, vars) {
}
break;
case GAME_PROCESS_COMMANDS.GET_TIME_REMAINING:
await vars.gameManager.getTimeRemaining(game, socketId);
const timer = vars.gameManager.timers[game.accessCode];
const timeRemaining = timer
? timer.currentTimeInMillis
: (game.timerParams ? game.timerParams.timeRemaining : 0);
const paused = game.timerParams ? game.timerParams.paused : false;
await vars.eventManager.publisher.publish(
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
vars.eventManager.createMessageToPublish(
game.accessCode,
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
vars.instanceId,
JSON.stringify({
socketId: socketId,
timeRemaining: timeRemaining,
paused: paused
})
)
);
break;
}
}
@@ -396,7 +414,7 @@ const Events = [
stateChange: async (game, socketArgs, vars) => {},
communicate: async (game, socketArgs, vars) => {
const timer = vars.gameManager.timers[game.accessCode];
if (timer) {
if (timer || socketArgs.timerEventSubtype === GAME_PROCESS_COMMANDS.GET_TIME_REMAINING) {
await handleTimerCommand(socketArgs.timerEventSubtype, game, socketArgs.socketId, vars);
}
}

View File

@@ -174,16 +174,16 @@ class GameManager {
this.namespace.to(socketId).emit(
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
timer.currentTimeInMillis,
game.timerParams.paused
game.timerParams ? game.timerParams.paused : false
);
} else {
if (game.timerParams) {
this.namespace.to(socketId).emit(
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
game.timerParams.timeRemaining,
game.timerParams.paused
);
}
const timeRemaining = game.timerParams ? game.timerParams.timeRemaining : 0;
const paused = game.timerParams ? game.timerParams.paused : false;
this.namespace.to(socketId).emit(
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
timeRemaining,
paused
);
}
}
};

View File

@@ -584,16 +584,16 @@ describe('Events', () => {
it('should send a message to the thread if it is found', async () => {
const mockTimer = { currentTimeInMillis: 5000 };
gameManager.timers = { ABCD: mockTimer };
spyOn(gameManager, 'getTimeRemaining').and.returnValue(Promise.resolve());
await Events.find((e) => e.id === EVENT_IDS.TIMER_EVENT)
.communicate(game, {}, {
gameManager: gameManager,
eventManager: eventManager,
timerEventSubtype: GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
requestingSocketId: '2',
logger: { logLevel: 'trace' }
logger: { logLevel: 'trace' },
instanceId: 'test'
});
expect(gameManager.getTimeRemaining).toHaveBeenCalledWith(game, '2');
expect(eventManager.publisher.publish).toHaveBeenCalled();
});
});
});