fix: PWA-Update-Banner nach Reload zuverlässig ausblenden

needRefresh zurücksetzen, Reload-Fallback ergänzen und kurze Suppression nach Update,
damit die Benachrichtigung nicht erneut erscheint.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-29 18:33:43 +02:00
co-authored by Cursor
parent 66a32e0367
commit 268500237d
2 changed files with 54 additions and 9 deletions
+5 -6
View File
@@ -5,17 +5,16 @@ import { usePwaUpdate } from '../hooks/usePwaUpdate.js'
export default function PwaUpdatePrompt() { export default function PwaUpdatePrompt() {
const { t } = useTranslation() const { t } = useTranslation()
const { needRefresh, updateApp } = usePwaUpdate() const { needRefresh, updateApp, dismissUpdate } = usePwaUpdate()
const [updating, setUpdating] = useState(false) const [updating, setUpdating] = useState(false)
const [dismissed, setDismissed] = useState(false)
if (!needRefresh || dismissed) return null if (!needRefresh) return null
const handleUpdate = async () => { const handleUpdate = async () => {
setUpdating(true) setUpdating(true)
try { try {
await updateApp() await updateApp()
} finally { } catch {
setUpdating(false) setUpdating(false)
} }
} }
@@ -43,7 +42,7 @@ export default function PwaUpdatePrompt() {
<button <button
type="button" type="button"
className="pwa-update-link" className="pwa-update-link"
onClick={() => setDismissed(true)} onClick={dismissUpdate}
> >
{t('pwa.later')} {t('pwa.later')}
</button> </button>
@@ -52,7 +51,7 @@ export default function PwaUpdatePrompt() {
<button <button
type="button" type="button"
className="pwa-update-close" className="pwa-update-close"
onClick={() => setDismissed(true)} onClick={dismissUpdate}
aria-label={t('pwa.later')} aria-label={t('pwa.later')}
> >
<X size={18} /> <X size={18} />
+49 -3
View File
@@ -2,9 +2,26 @@ import { useEffect, useRef } from 'react'
import { useRegisterSW } from 'virtual:pwa-register/react' import { useRegisterSW } from 'virtual:pwa-register/react'
const UPDATE_CHECK_INTERVAL_MS = 60 * 60 * 1000 const UPDATE_CHECK_INTERVAL_MS = 60 * 60 * 1000
const UPDATE_SUPPRESS_KEY = 'pwa_update_suppress_until'
const UPDATE_SUPPRESS_MS = 30_000
const UPDATE_RELOAD_FALLBACK_MS = 2000
function isUpdateSuppressed(): boolean {
const suppressUntil = Number(sessionStorage.getItem(UPDATE_SUPPRESS_KEY) || '0')
return Date.now() < suppressUntil
}
function suppressUpdatePrompt(): void {
sessionStorage.setItem(UPDATE_SUPPRESS_KEY, String(Date.now() + UPDATE_SUPPRESS_MS))
}
function clearUpdateSuppression(): void {
sessionStorage.removeItem(UPDATE_SUPPRESS_KEY)
}
function scheduleUpdateChecks(registration: ServiceWorkerRegistration): () => void { function scheduleUpdateChecks(registration: ServiceWorkerRegistration): () => void {
const checkForUpdate = () => { const checkForUpdate = () => {
if (isUpdateSuppressed()) return
registration.update().catch(() => {}) registration.update().catch(() => {})
} }
@@ -27,28 +44,57 @@ export function usePwaUpdate() {
const cleanupRef = useRef<(() => void) | null>(null) const cleanupRef = useRef<(() => void) | null>(null)
const { const {
needRefresh: [needRefresh], needRefresh: [needRefresh, setNeedRefresh],
updateServiceWorker updateServiceWorker
} = useRegisterSW({ } = useRegisterSW({
immediate: true, immediate: true,
onNeedReload() {
clearUpdateSuppression()
setNeedRefresh(false)
window.location.reload()
},
onNeedRefresh() {
if (isUpdateSuppressed()) return
setNeedRefresh(true)
},
onRegisteredSW(_swUrl: string, registration: ServiceWorkerRegistration | undefined) { onRegisteredSW(_swUrl: string, registration: ServiceWorkerRegistration | undefined) {
if (!registration) return if (!registration) return
if (isUpdateSuppressed() || !registration.waiting) {
setNeedRefresh(false)
}
cleanupRef.current?.() cleanupRef.current?.()
cleanupRef.current = scheduleUpdateChecks(registration) cleanupRef.current = scheduleUpdateChecks(registration)
} }
}) })
useEffect(() => { useEffect(() => {
if (isUpdateSuppressed()) {
setNeedRefresh(false)
}
return () => { return () => {
cleanupRef.current?.() cleanupRef.current?.()
cleanupRef.current = null cleanupRef.current = null
} }
}, []) }, [setNeedRefresh])
const updateApp = async () => { const updateApp = async () => {
setNeedRefresh(false)
suppressUpdatePrompt()
await updateServiceWorker(true) await updateServiceWorker(true)
// vite-plugin-pwa reloads via the "controlling" event; fallback if that does not fire.
window.setTimeout(() => {
window.location.reload()
}, UPDATE_RELOAD_FALLBACK_MS)
} }
return { needRefresh, updateApp } const dismissUpdate = () => {
setNeedRefresh(false)
}
return { needRefresh, updateApp, dismissUpdate }
} }