diff --git a/public/sw.js b/public/sw.js index c8bc1e2..bd21f9d 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'l484-pwa-v4' +const CACHE_NAME = 'l484-pwa-v5' const APP_SHELL = [ '/', '/manifest.webmanifest', @@ -29,6 +29,19 @@ self.addEventListener('fetch', (event) => { if (!['http:', 'https:'].includes(url.protocol)) return if (url.pathname.startsWith('/api/')) return + if (event.request.mode === 'navigate') { + event.respondWith( + fetch(event.request) + .then((response) => { + const clone = response.clone() + caches.open(CACHE_NAME).then((cache) => cache.put('/', clone)) + return response + }) + .catch(() => caches.match('/') || caches.match(event.request)), + ) + return + } + event.respondWith( caches.match(event.request).then((cached) => cached || fetch(event.request).then((response) => { diff --git a/src/services/notifications.js b/src/services/notifications.js index 196f219..67da23b 100644 --- a/src/services/notifications.js +++ b/src/services/notifications.js @@ -9,7 +9,24 @@ const urlBase64ToArrayBuffer = (base64String) => { const rawData = window.atob(base64) const key = new Uint8Array([...rawData].map((char) => char.charCodeAt(0))) if (key.length !== 65 || key[0] !== 4) throw new Error('VAPID public key is not valid.') - return key.buffer + return key +} + +const isBraveBrowser = async () => { + try { + return Boolean(navigator.brave && await navigator.brave.isBrave()) + } catch { + return false + } +} + +const explainSubscribeError = async (error) => { + const message = error instanceof Error ? error.message : 'push service error' + const brave = await isBraveBrowser() + if (brave && /push service|registration failed|not available|permission/i.test(message)) { + return 'Brave is blocking Web Push. In Brave settings, enable "Use Google services for push messaging", then fully close and reopen the installed L484 app.' + } + return `Push registration failed: ${message}. Fully close and reopen the installed app, then try again.` } export const notificationPermission = permission @@ -34,16 +51,13 @@ const subscribeWithRetry = async (registration, applicationServerKey) => { try { return await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey }) } catch (error) { - await registration.unregister().catch(() => {}) - await new Promise((resolve) => window.setTimeout(resolve, 500)) - const freshRegistration = await navigator.serviceWorker.register('/sw.js', { scope: '/' }) - const readyRegistration = await navigator.serviceWorker.ready - const activeRegistration = readyRegistration || freshRegistration + await registration.update().catch(() => {}) + await new Promise((resolve) => window.setTimeout(resolve, 750)) + const activeRegistration = await navigator.serviceWorker.ready try { return await activeRegistration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey }) - } catch { - const message = error instanceof Error ? error.message : 'Push service error.' - throw new Error(`Push registration failed: ${message}. Close and reopen the installed app, then try again.`) + } catch (retryError) { + throw new Error(await explainSubscribeError(retryError || error)) } } } @@ -63,11 +77,12 @@ export const subscribeToNotifications = async () => { permission.value = requested if (requested !== 'granted') throw new Error('Notification permission was not granted.') - await navigator.serviceWorker.register('/sw.js', { scope: '/' }) - const registration = await navigator.serviceWorker.ready - const existing = await registration.pushManager.getSubscription() + const registered = await navigator.serviceWorker.register('/sw.js', { scope: '/' }) + await registered.update().catch(() => {}) + const readyRegistration = await navigator.serviceWorker.ready + const existing = await readyRegistration.pushManager.getSubscription() if (existing) return saveSubscription(existing) - const subscription = await subscribeWithRetry(registration, applicationServerKey) + const subscription = await subscribeWithRetry(readyRegistration, applicationServerKey) return saveSubscription(subscription) }