Simplify GET_TIME_REMAINING flow and add ended flag

- Add ended: true flag to timerParams when timer completes
- Simplify TIMER_EVENT to check ended flag first, then local timer, then publish SOURCE_TIMER_EVENT
- SOURCE_TIMER_EVENT only publishes GET_TIME_REMAINING if it has the timer
- Remove GET_TIME_REMAINING from handleTimerCommand helper
- Direct socket emission for local timers instead of Redis publish
- Updated test to verify socket emission

Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-24 15:57:42 +00:00
parent 4e0f3d5a33
commit d0eeca8d42
2 changed files with 72 additions and 39 deletions

View File

@@ -54,27 +54,6 @@ async function handleTimerCommand (timerEventSubtype, game, socketId, vars) {
);
}
break;
case GAME_PROCESS_COMMANDS.GET_TIME_REMAINING:
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;
}
}
@@ -391,31 +370,83 @@ const Events = [
id: EVENT_IDS.TIMER_EVENT,
stateChange: async (game, socketArgs, vars) => {},
communicate: async (game, socketArgs, vars) => {
const timer = vars.gameManager.timers[game.accessCode];
if (timer) {
await handleTimerCommand(vars.timerEventSubtype, game, vars.requestingSocketId, vars);
if (vars.timerEventSubtype === GAME_PROCESS_COMMANDS.GET_TIME_REMAINING) {
if (game.timerParams && game.timerParams.ended) {
const socket = vars.gameManager.namespace.sockets.get(vars.requestingSocketId);
if (socket) {
vars.gameManager.namespace.to(socket.id).emit(
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
0,
false
);
}
} else {
const timer = vars.gameManager.timers[game.accessCode];
if (timer) {
const socket = vars.gameManager.namespace.sockets.get(vars.requestingSocketId);
if (socket) {
vars.gameManager.namespace.to(socket.id).emit(
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
timer.currentTimeInMillis,
game.timerParams ? game.timerParams.paused : false
);
}
} else {
await vars.eventManager.publisher?.publish(
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
vars.eventManager.createMessageToPublish(
game.accessCode,
EVENT_IDS.SOURCE_TIMER_EVENT,
vars.instanceId,
JSON.stringify({ socketId: vars.requestingSocketId, timerEventSubtype: vars.timerEventSubtype })
)
);
}
}
} else {
await vars.eventManager.publisher?.publish(
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
vars.eventManager.createMessageToPublish(
game.accessCode,
EVENT_IDS.SOURCE_TIMER_EVENT,
vars.instanceId,
JSON.stringify({ socketId: vars.requestingSocketId, timerEventSubtype: vars.timerEventSubtype })
)
);
const timer = vars.gameManager.timers[game.accessCode];
if (timer) {
await handleTimerCommand(vars.timerEventSubtype, game, vars.requestingSocketId, vars);
} else {
await vars.eventManager.publisher?.publish(
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
vars.eventManager.createMessageToPublish(
game.accessCode,
EVENT_IDS.SOURCE_TIMER_EVENT,
vars.instanceId,
JSON.stringify({ socketId: vars.requestingSocketId, timerEventSubtype: vars.timerEventSubtype })
)
);
}
}
}
},
{
/* This event is a request from another instance to consult its timer data. In response
* to this event, this instance will check if it is home to a particular timer. */
id: EVENT_IDS.SOURCE_TIMER_EVENT,
stateChange: async (game, socketArgs, vars) => {},
communicate: async (game, socketArgs, vars) => {
const timer = vars.gameManager.timers[game.accessCode];
if (timer || socketArgs.timerEventSubtype === GAME_PROCESS_COMMANDS.GET_TIME_REMAINING) {
await handleTimerCommand(socketArgs.timerEventSubtype, game, socketArgs.socketId, vars);
if (socketArgs.timerEventSubtype === GAME_PROCESS_COMMANDS.GET_TIME_REMAINING) {
const timer = vars.gameManager.timers[game.accessCode];
if (timer) {
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: socketArgs.socketId,
timeRemaining: timer.currentTimeInMillis,
paused: game.timerParams ? game.timerParams.paused : false
})
)
);
}
} else {
const timer = vars.gameManager.timers[game.accessCode];
if (timer) {
await handleTimerCommand(socketArgs.timerEventSubtype, game, socketArgs.socketId, vars);
}
}
}
},
@@ -443,6 +474,7 @@ const Events = [
stateChange: async (game, socketArgs, vars) => {
game.timerParams.paused = false;
game.timerParams.timeRemaining = 0;
game.timerParams.ended = true;
},
communicate: async (game, socketArgs, vars) => {
vars.gameManager.namespace.in(game.accessCode).emit(GAME_PROCESS_COMMANDS.END_TIMER);

View File

@@ -584,6 +584,7 @@ describe('Events', () => {
it('should send a message to the thread if it is found', async () => {
const mockTimer = { currentTimeInMillis: 5000 };
gameManager.timers = { ABCD: mockTimer };
namespace.sockets.set('2', { id: '2' });
await Events.find((e) => e.id === EVENT_IDS.TIMER_EVENT)
.communicate(game, {}, {
gameManager: gameManager,
@@ -593,7 +594,7 @@ describe('Events', () => {
logger: { logLevel: 'trace' },
instanceId: 'test'
});
expect(eventManager.publisher.publish).toHaveBeenCalled();
expect(namespace.to).toHaveBeenCalledWith('2');
});
});
});