feat: SSE live fight spectating with spectator count

Enable real-time fight spectating for all live fights (not just human
fights). Multiple spectators can watch simultaneously via SSE. Spectator
count is tracked per-fight and broadcast with every SSE event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-08 19:46:39 +00:00
co-authored by Claude Opus 4.6
parent a540320901
commit 610e799605
18 changed files with 1555 additions and 397 deletions
+30 -3
View File
@@ -12,6 +12,13 @@ import { getPendingChallenge, submitHumanResponse } from '../engine/human-respon
export const fightsRouter = new Hono()
// Track spectator counts per fight
const spectatorCounts = new Map<string, number>()
export function getSpectatorCount(fightId: string): number {
return spectatorCounts.get(fightId) || 0
}
// List recent fights (with bot names)
fightsRouter.get('/', async (c) => {
const rows = await db.select()
@@ -322,10 +329,20 @@ fightsRouter.get('/:id/stream', (c) => {
const fightId = c.req.param('id')
return streamSSE(c, async (stream) => {
// Track spectator
spectatorCounts.set(fightId, (spectatorCounts.get(fightId) || 0) + 1)
const count = spectatorCounts.get(fightId)!
// Send initial spectator count
await stream.writeSSE({
event: 'spectator_count',
data: JSON.stringify({ count }),
})
const cleanup = fightEvents.on(fightId, (event) => {
stream.writeSSE({
event: event.type,
data: JSON.stringify(event.data),
data: JSON.stringify({ ...event.data, spectators: spectatorCounts.get(fightId) || 0 }),
})
})
@@ -333,14 +350,17 @@ fightsRouter.get('/:id/stream', (c) => {
if (event.fightId === fightId && event.type === 'fight_end') {
stream.writeSSE({
event: 'fight_end',
data: JSON.stringify(event.data),
data: JSON.stringify({ ...event.data, spectators: spectatorCounts.get(fightId) || 0 }),
})
}
})
try {
while (true) {
await stream.writeSSE({ event: 'ping', data: '' })
await stream.writeSSE({
event: 'ping',
data: JSON.stringify({ spectators: spectatorCounts.get(fightId) || 0 }),
})
await stream.sleep(5000)
const fight = await db.select({ status: schema.fights.status })
.from(schema.fights)
@@ -351,6 +371,13 @@ fightsRouter.get('/:id/stream', (c) => {
} catch {
// Client disconnected
} finally {
// Decrement spectator count
const current = spectatorCounts.get(fightId) || 1
if (current <= 1) {
spectatorCounts.delete(fightId)
} else {
spectatorCounts.set(fightId, current - 1)
}
cleanup()
cleanupGlobal()
}