fix: WebSocket reconnect state refresh, listener leak fixes, pin container images

- F4: Fetch fresh server state after WebSocket reconnect
- F5: Guard message polling timer with auth check, stop on logout
- F6: Remove NIP-07 listener in appLauncher close()
- F7: Initialize audio player once to prevent listener stacking
- S3: Pin all container images to specific versions, create image-versions.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-21 01:32:28 +00:00
parent 2443ae6bba
commit 0cca539a0f
13 changed files with 233 additions and 110 deletions

View File

@@ -7,46 +7,52 @@ const playing = ref(false)
const currentTime = ref(0)
const duration = ref(0)
const error = ref<string | null>(null)
let initialized = false
/** Create the Audio element and attach listeners once */
function init() {
if (initialized) return
initialized = true
audio.value = new Audio()
audio.value.addEventListener('timeupdate', () => {
currentTime.value = audio.value?.currentTime ?? 0
})
audio.value.addEventListener('loadedmetadata', () => {
duration.value = audio.value?.duration ?? 0
error.value = null
})
audio.value.addEventListener('ended', () => {
playing.value = false
})
audio.value.addEventListener('pause', () => {
playing.value = false
})
audio.value.addEventListener('play', () => {
playing.value = true
error.value = null
})
audio.value.addEventListener('error', () => {
playing.value = false
error.value = 'Could not play audio. File Browser may not be running.'
})
}
function play(src: string, name: string) {
if (!audio.value) {
audio.value = new Audio()
audio.value.addEventListener('timeupdate', () => {
currentTime.value = audio.value?.currentTime ?? 0
})
audio.value.addEventListener('loadedmetadata', () => {
duration.value = audio.value?.duration ?? 0
error.value = null
})
audio.value.addEventListener('ended', () => {
playing.value = false
})
audio.value.addEventListener('pause', () => {
playing.value = false
})
audio.value.addEventListener('play', () => {
playing.value = true
error.value = null
})
audio.value.addEventListener('error', () => {
playing.value = false
error.value = 'Could not play audio. File Browser may not be running.'
})
}
init()
error.value = null
if (currentSrc.value === src && playing.value) {
audio.value.pause()
audio.value!.pause()
return
}
if (currentSrc.value !== src) {
audio.value.src = src
audio.value!.src = src
currentSrc.value = src
currentName.value = name
}
audio.value.play()
audio.value!.play()
}
function pause() {

View File

@@ -54,10 +54,21 @@ export function useMessageToast() {
}
}
function isAuthenticated(): boolean {
return localStorage.getItem('neode-auth') === 'true'
}
function startPolling() {
if (pollTimer) return
if (!isAuthenticated()) return
loadReceivedMessages()
pollTimer = setInterval(loadReceivedMessages, MESSAGE_POLL_INTERVAL)
pollTimer = setInterval(() => {
if (!isAuthenticated()) {
stopPolling()
return
}
loadReceivedMessages()
}, MESSAGE_POLL_INTERVAL)
}
function stopPolling() {