fix: add timeouts to SW ready and push subscribe promises to prevent silent hangs during push activation

This commit is contained in:
2026-06-02 20:19:32 +02:00
parent b0195601de
commit a9c712be45
+27 -7
View File
@@ -172,11 +172,18 @@ export async function subscribeToPush(): Promise<void> {
throw new Error('Push notifications are not supported on this device') throw new Error('Push notifications are not supported on this device')
} }
// Pre-resolve registration and VAPID key synchronously if preloaded. // Pre-resolve registration with timeout to prevent silent hangs
// This keeps the user gesture active for iOS Safari. let registration = cachedRegistration
const registration = cachedRegistration || await navigator.serviceWorker.ready if (!registration) {
const publicKey = cachedVapidKey || await fetchVapidPublicKey() const readyPromise = navigator.serviceWorker.ready
const readyTimeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Timeout waiting for Service Worker ready state')), 8000)
)
registration = await Promise.race([readyPromise, readyTimeout])
cachedRegistration = registration
}
const publicKey = cachedVapidKey || await fetchVapidPublicKey()
if (!publicKey) { if (!publicKey) {
throw new Error('Push notifications are not configured on this server') throw new Error('Push notifications are not configured on this server')
} }
@@ -189,11 +196,15 @@ export async function subscribeToPush(): Promise<void> {
const keyBytes = urlBase64ToUint8Array(publicKey) const keyBytes = urlBase64ToUint8Array(publicKey)
const applicationServerKey = new Uint8Array(keyBytes) const applicationServerKey = new Uint8Array(keyBytes)
// Always call subscribe to renew/ensure subscription without reusing stale state // Always call subscribe with timeout to prevent silent hangs on push network errors
const subscription = await registration.pushManager.subscribe({ const subscribePromise = registration.pushManager.subscribe({
userVisibleOnly: true, userVisibleOnly: true,
applicationServerKey applicationServerKey
}) })
const subscribeTimeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Timeout establishing subscription with push service (FCM/APNs)')), 12000)
)
const subscription = await Promise.race([subscribePromise, subscribeTimeout])
await saveSubscriptionToServer(subscription) await saveSubscriptionToServer(subscription)
} }
@@ -201,7 +212,16 @@ export async function subscribeToPush(): Promise<void> {
export async function unsubscribeFromPush(): Promise<void> { export async function unsubscribeFromPush(): Promise<void> {
if (!isPushSupported()) return if (!isPushSupported()) return
const registration = cachedRegistration || await navigator.serviceWorker.ready let registration = cachedRegistration
if (!registration) {
const readyPromise = navigator.serviceWorker.ready
const readyTimeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Timeout waiting for Service Worker ready state')), 8000)
)
registration = await Promise.race([readyPromise, readyTimeout])
cachedRegistration = registration
}
const subscription = await registration.pushManager.getSubscription() const subscription = await registration.pushManager.getSubscription()
if (!subscription) return if (!subscription) return