Harden the update-check pipeline against silent failures (#58)

checkForUpdate() was a bare `void registration?.update()` -- a failed
fetch (most plausible right when it's triggered by a WS reconnect, i.e.
the network just flapped from a backend restart) vanished with nothing
caught or logged, leaving only the hourly interval as a fallback. Now
logs the failure instead of swallowing it, and a third trigger checks
for an update whenever a backgrounded tab becomes visible again, so a
tab that misses both the reconnect-triggered check and the hourly timer
still gets a chance the moment someone actually looks at it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-28 16:14:38 -06:00
co-authored by Claude Sonnet 5
parent 072405eb2d
commit 157f1e30ac
2 changed files with 27 additions and 1 deletions
+16
View File
@@ -1,3 +1,4 @@
import { useEffect } from 'react'
import { useRegisterSW } from 'virtual:pwa-register/react'
import { checkForUpdate, setSwRegistration } from '../lib/swUpdate'
import './UpdateBanner.css'
@@ -26,6 +27,21 @@ export function UpdateBanner() {
},
})
useEffect(() => {
// #58: a third trigger, alongside WS-reconnect and the hourly interval
// above -- a tab backgrounded across a deploy gets checked the moment
// someone actually looks at it again, rather than waiting on whichever
// of those two happens to land first. Cheap insurance against either
// one missing its moment (e.g. the reconnect-triggered check landing
// during the same network blip that caused the reconnect, and failing
// -- see checkForUpdate's own comment).
function handleVisibilityChange() {
if (document.visibilityState === 'visible') checkForUpdate()
}
document.addEventListener('visibilitychange', handleVisibilityChange)
return () => document.removeEventListener('visibilitychange', handleVisibilityChange)
}, [])
if (!needRefresh) return null
return (
+11 -1
View File
@@ -10,5 +10,15 @@ export function setSwRegistration(reg: ServiceWorkerRegistration): void {
}
export function checkForUpdate(): void {
void registration?.update()
// #58: this used to be a bare `void registration?.update()` -- if the
// fetch failed (most plausible right when it's triggered by a WS
// reconnect, i.e. the network just flapped from a backend restart), the
// rejection vanished with nothing to catch it and nothing logged. The
// only other trigger was an hourly interval, so a client that hit this
// at the wrong moment could sit stale for up to an hour with zero trace
// of why. This doesn't fix a bad network, but it stops the failure from
// being silent, and callers still don't need to handle anything.
registration?.update().catch((err: unknown) => {
console.error('Service worker update check failed', err)
})
}