Decouple signup from push service outages

This commit is contained in:
Dorian
2026-05-15 18:25:58 -05:00
parent d32930340c
commit 625cceb50c
3 changed files with 39 additions and 7 deletions

View File

@@ -10,7 +10,6 @@
"orientation": "portrait",
"background_color": "#000000",
"theme_color": "#000000",
"gcm_sender_id": "103953800507",
"icons": [
{
"src": "/images/app-icon-192.png",

View File

@@ -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
})

View File

@@ -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()
}