interface CompletionNotificationPayload { title: string body?: string icon?: string tag?: string } export interface CompletionNotificationPermissionResult { granted: boolean reason?: 'unsupported' | 'insecure' | 'denied' } function withTimeout(promise: Promise, timeoutMs: number, fallback: T): Promise { return new Promise((resolve) => { const timer = window.setTimeout(() => resolve(fallback), timeoutMs) promise.then( value => { window.clearTimeout(timer) resolve(value) }, () => { window.clearTimeout(timer) resolve(fallback) }, ) }) } function supportsBrowserNotification(): boolean { return typeof window !== 'undefined' && 'Notification' in window } function isBrowserNotificationSecureContext(): boolean { if (typeof window === 'undefined') return false return window.isSecureContext } function browserNotificationOptions(payload: CompletionNotificationPayload): NotificationOptions { return { body: payload.body, icon: payload.icon ? new URL(payload.icon, window.location.origin).href : undefined, tag: payload.tag, } } async function showServiceWorkerNotification(payload: CompletionNotificationPayload): Promise { if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) return false try { const registration = await withTimeout( navigator.serviceWorker.register('/notification-sw.js'), 3000, null, ) if (!registration) return false await withTimeout(navigator.serviceWorker.ready, 3000, registration) await registration.showNotification(payload.title, browserNotificationOptions(payload)) return true } catch (err) { console.warn('Failed to show service worker notification:', err) return false } } export async function requestCompletionNotificationPermission(): Promise { if (!supportsBrowserNotification()) return { granted: false, reason: 'unsupported' } if (!isBrowserNotificationSecureContext()) return { granted: false, reason: 'insecure' } if (Notification.permission === 'granted') return { granted: true } if (Notification.permission === 'denied') return { granted: false, reason: 'denied' } try { const permission = await withTimeout( Notification.requestPermission(), 5000, 'default' as NotificationPermission, ) return permission === 'granted' ? { granted: true } : { granted: false, reason: permission === 'denied' ? 'denied' : 'unsupported' } } catch { return { granted: false, reason: 'unsupported' } } } export async function showCompletionNotification(payload: CompletionNotificationPayload): Promise { if (!supportsBrowserNotification() || !isBrowserNotificationSecureContext() || Notification.permission !== 'granted') { return false } try { if (await showServiceWorkerNotification(payload)) return true const notification = new Notification(payload.title, browserNotificationOptions(payload)) notification.onclick = () => { window.focus() notification.close() } return true } catch (err) { console.warn('Failed to show browser completion notification:', err) return false } }