export const usePush = () => { const isSupported = ref(false) const subscription = ref(null) const checkSupport = () => { isSupported.value = 'serviceWorker' in navigator && 'PushManager' in window } const getRegistration = async (): Promise => { if (!('serviceWorker' in navigator)) return null return navigator.serviceWorker.ready } const subscribe = async (): Promise => { const registration = await getRegistration() if (!registration) return false try { const existing = await registration.pushManager.getSubscription() if (existing) { subscription.value = existing await sendSubscriptionToServer(existing) return true } // In production, get VAPID public key from server const vapidKey = await $fetch('/api/push/vapid-public-key').catch(() => null) const applicationServerKey = vapidKey ? (urlBase64ToUint8Array(vapidKey) as unknown as ArrayBuffer) : undefined const sub = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey }) subscription.value = sub await sendSubscriptionToServer(sub) return true } catch (err) { console.error('Push subscription failed:', err) return false } } const sendSubscriptionToServer = async (sub: PushSubscription) => { await $fetch('/api/push/subscribe', { method: 'POST', body: { subscription: sub.toJSON() } }) } const unsubscribe = async (): Promise => { const registration = await getRegistration() if (!registration) return false const existing = await registration.pushManager.getSubscription() if (existing) { await existing.unsubscribe() subscription.value = null } return true } const urlBase64ToUint8Array = (base64String: string): Uint8Array => { const padding = '='.repeat((4 - (base64String.length % 4)) % 4) const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/') const rawData = atob(base64) return Uint8Array.from([...rawData].map(char => char.charCodeAt(0))) } const showLocalNotification = (title: string, options?: NotificationOptions) => { if ('Notification' in window && Notification.permission === 'granted') { navigator.serviceWorker.ready.then(reg => { reg.showNotification(title, { icon: '/pwa-192x192.svg', badge: '/favicon.svg', ...options as any }) }) } } const requestPermission = async (): Promise => { if (!('Notification' in window)) return false const permission = await Notification.requestPermission() return permission === 'granted' } onMounted(() => { checkSupport() }) return { isSupported, subscription, subscribe, unsubscribe, requestPermission, showLocalNotification } }