import { defineStore } from 'pinia' import { ref } from 'vue' function urlBase64ToUint8Array(base64String: string): Uint8Array { const padding = '='.repeat((4 - (base64String.length % 4)) % 4) const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/') const rawData = window.atob(base64) const buf = new ArrayBuffer(rawData.length) const outputArray = new Uint8Array(buf) for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i) } return outputArray } export const usePushStore = defineStore('push', () => { const isSupported = ref( 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window ) const permission = ref( 'Notification' in window ? Notification.permission : 'denied' ) const isSubscribed = ref(false) const loading = ref(false) const error = ref(null) async function checkSubscription(): Promise { if (!isSupported.value) return try { const reg = await navigator.serviceWorker.ready const sub = await reg.pushManager.getSubscription() isSubscribed.value = !!sub } catch { isSubscribed.value = false } } async function subscribe(): Promise { if (!isSupported.value) { error.value = 'Push notifications not supported in this browser' return } loading.value = true error.value = null try { // Request permission permission.value = await Notification.requestPermission() if (permission.value !== 'granted') { error.value = 'Notification permission denied' return } // Fetch VAPID public key const resp = await fetch('/api/push/vapid-public-key') if (!resp.ok) { error.value = 'Push notifications not configured on server' return } const { publicKey } = await resp.json() // Register service worker const reg = await navigator.serviceWorker.register('/sw.js') await navigator.serviceWorker.ready // Subscribe const sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicKey), }) // Save to server const saveResp = await fetch('/api/push/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(sub.toJSON()), }) if (!saveResp.ok) throw new Error('Failed to save subscription') isSubscribed.value = true } catch (e: unknown) { error.value = e instanceof Error ? e.message : 'Failed to subscribe' } finally { loading.value = false } } async function unsubscribe(): Promise { loading.value = true error.value = null try { const reg = await navigator.serviceWorker.ready const sub = await reg.pushManager.getSubscription() if (sub) { await fetch('/api/push/subscribe', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ endpoint: sub.endpoint }), }) await sub.unsubscribe() } isSubscribed.value = false } catch (e: unknown) { error.value = e instanceof Error ? e.message : 'Failed to unsubscribe' } finally { loading.value = false } } return { isSupported, permission, isSubscribed, loading, error, checkSubscription, subscribe, unsubscribe } })