import { create } from 'zustand'; import { api } from '../lib/api.ts'; interface PushState { isSupported: boolean; isSubscribed: boolean; permission: NotificationPermission; subscribe: () => Promise; unsubscribe: () => Promise; requestPermission: () => Promise; checkSubscription: () => Promise; } function urlBase64ToUint8Array(base64String: string): BufferSource { const padding = '='.repeat((4 - (base64String.length % 4)) % 4); const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } export const usePushStore = create((set, get) => ({ isSupported: 'serviceWorker' in navigator && 'PushManager' in window, isSubscribed: false, permission: typeof Notification !== 'undefined' ? Notification.permission : 'default', requestPermission: async () => { if (!get().isSupported) return 'denied'; const permission = await Notification.requestPermission(); set({ permission }); return permission; }, checkSubscription: async () => { if (!get().isSupported) return; try { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); set({ isSubscribed: !!subscription }); } catch { // ignore } }, subscribe: async () => { if (!get().isSupported) return; try { // Request notification permission first const permission = await get().requestPermission(); if (permission !== 'granted') return; // Register service worker const registration = await navigator.serviceWorker.register('/sw.js'); await navigator.serviceWorker.ready; // Get VAPID public key from server const { publicKey } = await api.get<{ publicKey: string }>('/push/vapid-public-key'); if (!publicKey) return; // Subscribe to push const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicKey), }); const sub = subscription.toJSON(); const keys = sub.keys as { p256dh: string; auth: string }; // Send subscription to server await api.post('/push/subscribe', { endpoint: sub.endpoint!, p256dh: keys.p256dh, auth: keys.auth, }); set({ isSubscribed: true }); } catch (error) { console.error('Failed to subscribe to push:', error); } }, unsubscribe: async () => { if (!get().isSupported) return; try { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); if (subscription) { await api.post('/push/unsubscribe', { endpoint: subscription.endpoint }); await subscription.unsubscribe(); } set({ isSubscribed: false }); } catch (error) { console.error('Failed to unsubscribe from push:', error); } }, }));