feat: add periodic game.status broadcast and live status REST endpoint

Add 20-second game.status WebSocket heartbeat from active shard monitors
containing full game state, and GET /status-live REST endpoint for on-demand
polling. Fix missing token destructuring in SessionInfo causing crash.
Relax frontend polling from 3s to 60s since WebSocket events now cover
real-time updates. Bump version to 0.6.0.

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-20 21:05:19 -04:00
parent a7bd0650eb
commit 34637d6d2c
8 changed files with 328 additions and 9 deletions

View File

@@ -41,6 +41,7 @@ All REST endpoints are prefixed with `/api/` except `/health`.
- `GET /api/sessions/{id}`
- `GET /api/sessions/{id}/games`
- `GET /api/sessions/{id}/votes`
- `GET /api/sessions/{sessionId}/games/{sessionGameId}/status-live`
- `GET /api/votes`
- `GET /api/stats`
- `POST /api/pick`
@@ -139,6 +140,7 @@ Most list endpoints return full result sets. The exception is `GET /api/votes`,
| PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/room-code` | Yes | Update room code for session game |
| POST | `/api/sessions/{sessionId}/games/{sessionGameId}/start-player-check` | Yes | Start room monitor for player count |
| POST | `/api/sessions/{sessionId}/games/{sessionGameId}/stop-player-check` | Yes | Stop room monitor |
| GET | `/api/sessions/{sessionId}/games/{sessionGameId}/status-live` | No | Get live game status from shard monitor |
| PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/player-count` | Yes | Update player count for session game |
### Picker

View File

@@ -22,6 +22,7 @@ Sessions represent a gaming night. Only one session can be active at a time. Gam
| PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/status` | Bearer | Update session game status |
| DELETE | `/api/sessions/{sessionId}/games/{sessionGameId}` | Bearer | Remove game from session |
| PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/room-code` | Bearer | Update room code for session game |
| GET | `/api/sessions/{sessionId}/games/{sessionGameId}/status-live` | No | Get live game status from shard monitor |
| POST | `/api/sessions/{sessionId}/games/{sessionGameId}/start-player-check` | Bearer | Start room monitor |
| POST | `/api/sessions/{sessionId}/games/{sessionGameId}/stop-player-check` | Bearer | Stop room monitor |
| PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/player-count` | Bearer | Update player count for session game |
@@ -832,6 +833,82 @@ curl -o session-5.txt "http://localhost:5000/api/sessions/5/export" \
---
## GET /api/sessions/{sessionId}/games/{sessionGameId}/status-live
Get the live game status from an active shard monitor. If no monitor is running, falls back to data from the database. No authentication required.
The same data is broadcast every 20 seconds via the `game.status` WebSocket event to subscribed clients.
**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.
### Authentication
None required.
### Path Parameters
| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |
### Response
**200 OK** — Live shard data (when monitor is active):
```json
{
"sessionId": 1,
"gameId": 5,
"roomCode": "LSBN",
"appTag": "drawful2international",
"maxPlayers": 8,
"playerCount": 4,
"players": ["Alice", "Bob", "Charlie", "Diana"],
"lobbyState": "CanStart",
"gameState": "Lobby",
"gameStarted": false,
"gameFinished": false,
"monitoring": true
}
```
**200 OK** — DB fallback (when no monitor is active):
```json
{
"sessionId": 1,
"gameId": 5,
"roomCode": "LSBN",
"appTag": null,
"maxPlayers": 8,
"playerCount": 4,
"players": [],
"lobbyState": null,
"gameState": null,
"gameStarted": false,
"gameFinished": true,
"monitoring": false,
"title": "Drawful 2",
"packName": "Jackbox Party Pack 8",
"status": "completed"
}
```
### Error Responses
| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |
### Example
```bash
curl "http://localhost:5000/api/sessions/5/games/14/status-live"
```
---
## POST /api/sessions/{sessionId}/games/{sessionGameId}/start-player-check
Start the room monitor for a session game. The game must have a room code.

View File

@@ -136,6 +136,7 @@ Must be authenticated.
| `game.started` | Game transitioned to Gameplay (broadcast to subscribers) |
| `game.ended` | Game finished (broadcast to subscribers) |
| `room.disconnected` | Shard lost connection to Jackbox room (broadcast to subscribers) |
| `game.status` | Periodic game state heartbeat every 20s (broadcast to subscribers) |
| `player-count.updated` | Manual player count override (broadcast to subscribers) |
| `vote.received` | Live vote recorded (broadcast to subscribers) |
@@ -318,6 +319,29 @@ All server-sent events use this envelope:
Possible `reason` values: `room_closed`, `room_not_found`, `connection_failed`, `role_rejected`, `manually_stopped`.
### game.status
- **Broadcast to:** Clients subscribed to the session
- **Triggered by:** Periodic 20-second heartbeat from an active shard monitor. Also available on demand via `GET /api/sessions/{sessionId}/games/{sessionGameId}/status-live`.
**Data:**
```json
{
"sessionId": 1,
"gameId": 5,
"roomCode": "LSBN",
"appTag": "drawful2international",
"maxPlayers": 8,
"playerCount": 4,
"players": ["Alice", "Bob", "Charlie", "Diana"],
"lobbyState": "CanStart",
"gameState": "Lobby",
"gameStarted": false,
"gameFinished": false,
"monitoring": true
}
```
### player-count.updated
- **Broadcast to:** Clients subscribed to the session
@@ -536,6 +560,10 @@ ws.onmessage = (event) => {
console.log('Room disconnected:', msg.data.reason);
break;
case 'game.status':
console.log('Status heartbeat:', msg.data.roomCode, '- players:', msg.data.playerCount, '- state:', msg.data.gameState);
break;
case 'player-count.updated':
console.log('Player count:', msg.data.playerCount, 'for game', msg.data.gameId);
break;