fix: track and clear all intervals and event listeners
- FightPage.vue: store SSE listener refs in array, removeEventListener on each before close() in disconnectSSE() - FightScene.ts: add cleanupTimers Set with trackedInterval/trackedTimeout helpers; replace key setInterval calls (projectile trails, entrance flames, talking animation, exhaust effects, dimensional shift, hole fade) with tracked versions; clear all on scene destroy() - rate-limit.ts: export cleanupInterval handle for graceful shutdown Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
010e18fbd3
commit
7bd110684d
@@ -389,17 +389,24 @@ async function initLiveScene() {
|
||||
}
|
||||
|
||||
// --- SSE connection ---
|
||||
const sseListeners: { event: string; handler: EventListener }[] = []
|
||||
|
||||
function addSSEListener(es: EventSource, event: string, handler: (e: MessageEvent) => void) {
|
||||
es.addEventListener(event, handler as EventListener)
|
||||
sseListeners.push({ event, handler: handler as EventListener })
|
||||
}
|
||||
|
||||
function connectSSE() {
|
||||
eventSource = new EventSource(`/api/fights/${fightId.value}/stream`)
|
||||
|
||||
eventSource.addEventListener('spectator_count', (e) => {
|
||||
addSSEListener(eventSource, 'spectator_count', (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data)
|
||||
spectatorCount.value = data.count || 0
|
||||
} catch { /* ignore */ }
|
||||
})
|
||||
|
||||
eventSource.addEventListener('ping', (e) => {
|
||||
addSSEListener(eventSource, 'ping', (e) => {
|
||||
try {
|
||||
if (e.data) {
|
||||
const data = JSON.parse(e.data)
|
||||
@@ -408,21 +415,19 @@ function connectSSE() {
|
||||
} catch { /* ignore */ }
|
||||
})
|
||||
|
||||
eventSource.addEventListener('reaction', (e) => {
|
||||
addSSEListener(eventSource, 'reaction', (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data)
|
||||
if (data.emoji && data.counts) {
|
||||
// Spawn floating emoji in the live view
|
||||
spawnLiveReactionEmoji(data.emoji)
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
})
|
||||
|
||||
eventSource.addEventListener('round_start', (e) => {
|
||||
addSSEListener(eventSource, 'round_start', (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data)
|
||||
currentChallengeInfo.value = { type: data.challenge.type, label: data.challenge.label }
|
||||
// Show challenge question in battle log
|
||||
liveLogItems.value.push({
|
||||
type: 'challenge',
|
||||
round: data.round,
|
||||
@@ -435,10 +440,9 @@ function connectSSE() {
|
||||
}
|
||||
})
|
||||
|
||||
eventSource.addEventListener('human_challenge', async (e) => {
|
||||
addSSEListener(eventSource, 'human_challenge', async (e) => {
|
||||
try {
|
||||
const sseData = JSON.parse(e.data)
|
||||
// Fetch from API to get choices (SSE event doesn't include them)
|
||||
if (myBot.value) {
|
||||
const res = await fetch(`/api/fights/${fightId.value}/challenge/${myBot.value.id}`)
|
||||
if (res.ok) {
|
||||
@@ -453,7 +457,6 @@ function connectSSE() {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback: use SSE data without choices
|
||||
if (roundCooldown.value > 0) {
|
||||
pendingChallengeData.value = { data: sseData, receivedAt: Date.now() }
|
||||
} else {
|
||||
@@ -464,7 +467,7 @@ function connectSSE() {
|
||||
}
|
||||
})
|
||||
|
||||
eventSource.addEventListener('round_end', (e) => {
|
||||
addSSEListener(eventSource, 'round_end', (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data)
|
||||
if (data.spectators !== undefined) spectatorCount.value = data.spectators
|
||||
@@ -474,7 +477,7 @@ function connectSSE() {
|
||||
}
|
||||
})
|
||||
|
||||
eventSource.addEventListener('fight_end', (e) => {
|
||||
addSSEListener(eventSource, 'fight_end', (e) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data)
|
||||
if (data.spectators !== undefined) spectatorCount.value = data.spectators
|
||||
@@ -486,7 +489,6 @@ function connectSSE() {
|
||||
|
||||
let sseRetries = 0
|
||||
eventSource.onerror = () => {
|
||||
// EventSource auto-reconnects, but if it keeps failing, reconnect manually with backoff
|
||||
sseRetries++
|
||||
if (sseRetries > 5 && eventSource) {
|
||||
eventSource.close()
|
||||
@@ -501,7 +503,14 @@ function connectSSE() {
|
||||
}
|
||||
|
||||
function disconnectSSE() {
|
||||
if (eventSource) { eventSource.close(); eventSource = null }
|
||||
if (eventSource) {
|
||||
for (const { event, handler } of sseListeners) {
|
||||
eventSource.removeEventListener(event, handler)
|
||||
}
|
||||
sseListeners.length = 0
|
||||
eventSource.close()
|
||||
eventSource = null
|
||||
}
|
||||
spectatorCount.value = 0
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user