import { usePage } from '@inertiajs/vue3'; import { computed, onMounted, 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 = atob(base64); return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0))); } export function usePushNotification() { const isSupported = computed( () => 'Notification' in window && 'serviceWorker' in navigator && 'PushManager' in window, ); const isSubscribed = ref(false); const isLoading = ref(false); const page = usePage(); const vapidPublicKey = (page.props as Record) .vapidPublicKey as string | undefined; async function checkSubscriptionStatus(): Promise { if (!isSupported.value) { return; } try { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); isSubscribed.value = !!subscription; } catch { isSubscribed.value = false; } } async function subscribe(): Promise { if (!isSupported.value || isLoading.value) { return; } const permission = await Notification.requestPermission(); if (permission !== 'granted') { return; } isLoading.value = true; try { const registration = await navigator.serviceWorker.ready; const publicKey = vapidPublicKey ?? import.meta.env.VITE_VAPID_PUBLIC_KEY ?? ''; const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicKey), }); const json = subscription.toJSON(); await fetch('/push-subscriptions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': ( document.querySelector( 'meta[name="csrf-token"]', ) as HTMLMetaElement )?.content ?? '', }, body: JSON.stringify({ endpoint: subscription.endpoint, publicKey: json.keys?.p256dh ?? '', authToken: json.keys?.auth ?? '', }), }); isSubscribed.value = true; } catch (error) { console.error('[PushNotification] Subscribe failed:', error); } finally { isLoading.value = false; } } async function unsubscribe(): Promise { if (!isSupported.value || isLoading.value) { return; } isLoading.value = true; try { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); if (!subscription) { isSubscribed.value = false; return; } await fetch('/push-subscriptions', { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': ( document.querySelector( 'meta[name="csrf-token"]', ) as HTMLMetaElement )?.content ?? '', }, body: JSON.stringify({ endpoint: subscription.endpoint }), }); await subscription.unsubscribe(); isSubscribed.value = false; } catch (error) { console.error('[PushNotification] Unsubscribe failed:', error); } finally { isLoading.value = false; } } async function toggle(): Promise { if (isSubscribed.value) { await unsubscribe(); } else { await subscribe(); } } onMounted(() => { void checkSubscriptionStatus(); }); return { isSupported, isSubscribed, isLoading, subscribe, unsubscribe, toggle, }; }