diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest index 1eaace8..6a79cfd 100644 --- a/public/manifest.webmanifest +++ b/public/manifest.webmanifest @@ -10,7 +10,6 @@ "orientation": "portrait", "background_color": "#000000", "theme_color": "#000000", - "gcm_sender_id": "103953800507", "icons": [ { "src": "/images/app-icon-192.png", diff --git a/src/App.vue b/src/App.vue index d49cb8b..547b088 100644 --- a/src/App.vue +++ b/src/App.vue @@ -12,7 +12,12 @@ import { loginWithRemoteApp, resumeRemoteAppLogin, } from './services/signer' -import { notificationPermission, subscribeToNotifications } from './services/notifications' +import { + notificationPermission, + requestNotificationPermission, + subscribeToNotifications, + subscribeToNotificationsInBackground, +} from './services/notifications' const heroBackgrounds = Object.entries( import.meta.glob('../public/images/bg-*.{avif,webp,jpg,jpeg,png}', { @@ -1042,8 +1047,9 @@ const createMembership = async () => { } try { - await subscribeToNotifications() + await requestNotificationPermission() signupNotificationsEnabled.value = true + subscribeToNotificationsInBackground() } catch (error) { formError.value = error instanceof Error ? error.message : 'Could not enable notifications.' isCreatingMembership.value = false @@ -2116,6 +2122,7 @@ onMounted(async () => { saveSignupDraft() openSignup() } + subscribeToNotificationsInBackground() window.addEventListener('popstate', () => { currentPath.value = window.location.pathname }) diff --git a/src/services/notifications.js b/src/services/notifications.js index 6c0f940..0fcf150 100644 --- a/src/services/notifications.js +++ b/src/services/notifications.js @@ -19,6 +19,17 @@ export const notificationSupport = () => ({ secure: window.isSecureContext || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1', }) +export const requestNotificationPermission = async () => { + if (!('Notification' in window)) throw new Error('Notifications are not supported in this browser.') + const support = notificationSupport() + if (!support.secure) throw new Error('Notifications require HTTPS.') + const requested = Notification.permission === 'granted' ? 'granted' : await Notification.requestPermission() + permission.value = requested + if (requested !== 'granted') throw new Error('Notification permission was not granted.') + if ('serviceWorker' in navigator) await navigator.serviceWorker.register('/sw.js', { scope: '/' }).catch(() => null) + return true +} + const saveSubscription = async (subscription) => { const response = await fetch('/api/notifications/subscribe', { method: 'POST', @@ -38,7 +49,7 @@ const sameApplicationServerKey = (subscription, applicationServerKey) => { return existingBytes.every((byte, index) => byte === applicationServerKey[index]) } -export const subscribeToNotifications = async () => { +export const subscribeToNotifications = async ({ requestPermission = true } = {}) => { const support = notificationSupport() if (!support.supported) throw new Error('Push notifications are not supported in this browser.') if (!support.secure) throw new Error('Push notifications require HTTPS.') @@ -49,9 +60,8 @@ export const subscribeToNotifications = async () => { if (!keyData.configured) throw new Error('VAPID private key is not configured on the server.') const applicationServerKey = urlBase64ToUint8Array(keyData.publicKey) - const requested = Notification.permission === 'granted' ? 'granted' : await Notification.requestPermission() - permission.value = requested - if (requested !== 'granted') throw new Error('Notification permission was not granted.') + if (requestPermission) await requestNotificationPermission() + else if (Notification.permission !== 'granted') throw new Error('Notification permission was not granted.') await navigator.serviceWorker.register('/sw.js', { scope: '/' }) const registration = await navigator.serviceWorker.ready @@ -67,3 +77,19 @@ export const subscribeToNotifications = async () => { }) return saveSubscription(subscription) } + +export const subscribeToNotificationsInBackground = ({ attempts = 8 } = {}) => { + if (!('Notification' in window) || Notification.permission !== 'granted') return + const delays = [0, 2000, 5000, 10000, 20000, 45000, 90000, 180000] + const run = (attempt = 0) => { + window.setTimeout(async () => { + try { + await subscribeToNotifications({ requestPermission: false }) + } catch (error) { + console.warn('Notification push registration retry failed:', error instanceof Error ? error.message : error) + if (attempt + 1 < attempts) run(attempt + 1) + } + }, delays[Math.min(attempt, delays.length - 1)]) + } + run() +}