Compare commits

...

6 Commits

3 changed files with 53 additions and 9 deletions
+1 -1
View File
@@ -1 +1 @@
0.1.0.95
0.1.0.98
+11
View File
@@ -73,6 +73,17 @@ async function bootstrap(): Promise<void> {
return
}
if ('serviceWorker' in navigator && !import.meta.env.DEV) {
navigator.serviceWorker
.register('/sw.js', { scope: '/' })
.then((reg) => {
console.log('Service Worker registered successfully with scope:', reg.scope)
})
.catch((err) => {
console.error('Service Worker registration failed:', err)
})
}
const rootEl = document.getElementById('root')
if (!rootEl) {
throw new Error('Missing #root element')
+41 -8
View File
@@ -30,6 +30,27 @@ export function getNotificationPermission(): NotificationPermission | 'unsupport
let cachedVapidKey: string | null = null
let cachedRegistration: ServiceWorkerRegistration | null = null
async function getRegistrationCompat(timeoutMs = 8000): Promise<ServiceWorkerRegistration> {
if (!('serviceWorker' in navigator)) {
throw new Error('Service Worker is not supported by your browser')
}
try {
const reg = await navigator.serviceWorker.getRegistration()
if (reg) return reg
} catch (e) {
console.warn('Failed to get service worker registration directly:', e)
}
// Fallback to waiting for ready state with a timeout
const readyPromise = navigator.serviceWorker.ready
const timeoutPromise = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Timeout waiting for Service Worker ready state')), timeoutMs)
)
return Promise.race([readyPromise, timeoutPromise])
}
export async function preloadPushService(): Promise<void> {
if (!isPushSupported()) return
try {
@@ -37,7 +58,7 @@ export async function preloadPushService(): Promise<void> {
await fetchVapidPublicKey()
}
if (!cachedRegistration) {
cachedRegistration = await navigator.serviceWorker.ready
cachedRegistration = await getRegistrationCompat()
}
} catch (err) {
console.warn('Failed to preload push service:', err)
@@ -172,11 +193,14 @@ export async function subscribeToPush(): Promise<void> {
throw new Error('Push notifications are not supported on this device')
}
// Pre-resolve registration and VAPID key synchronously if preloaded.
// This keeps the user gesture active for iOS Safari.
const registration = cachedRegistration || await navigator.serviceWorker.ready
const publicKey = cachedVapidKey || await fetchVapidPublicKey()
// Pre-resolve registration using getRegistrationCompat to prevent ready state hangs
let registration = cachedRegistration
if (!registration) {
registration = await getRegistrationCompat()
cachedRegistration = registration
}
const publicKey = cachedVapidKey || await fetchVapidPublicKey()
if (!publicKey) {
throw new Error('Push notifications are not configured on this server')
}
@@ -189,11 +213,15 @@ export async function subscribeToPush(): Promise<void> {
const keyBytes = urlBase64ToUint8Array(publicKey)
const applicationServerKey = new Uint8Array(keyBytes)
// Always call subscribe to renew/ensure subscription without reusing stale state
const subscription = await registration.pushManager.subscribe({
// Always call subscribe with timeout to prevent silent hangs on push network errors
const subscribePromise = registration.pushManager.subscribe({
userVisibleOnly: true,
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)
}
@@ -201,7 +229,12 @@ export async function subscribeToPush(): Promise<void> {
export async function unsubscribeFromPush(): Promise<void> {
if (!isPushSupported()) return
const registration = cachedRegistration || await navigator.serviceWorker.ready
let registration = cachedRegistration
if (!registration) {
registration = await getRegistrationCompat()
cachedRegistration = registration
}
const subscription = await registration.pushManager.getSubscription()
if (!subscription) return