'use client'; import { useState, useEffect } from 'react'; export function usePushNotifications() { const [permission, setPermission] = useState('default'); const [isSubscribed, setIsSubscribed] = useState(false); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { if ('Notification' in window) { setPermission(Notification.permission); checkSubscription(); } }, []); const checkSubscription = async () => { if (!('serviceWorker' in navigator) || !('PushManager' in window)) { return; } try { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); setIsSubscribed(!!subscription); } catch (err) { console.error('Failed to check subscription:', err); } }; const requestPermission = async () => { if (!('Notification' in window)) { setError('Notifications not supported'); return false; } try { const result = await Notification.requestPermission(); setPermission(result); return result === 'granted'; } catch (err) { console.error('Failed to request permission:', err); setError('Failed to request permission'); return false; } }; const subscribe = async () => { if (!('serviceWorker' in navigator) || !('PushManager' in window)) { setError('Push notifications not supported'); return false; } setIsLoading(true); setError(null); try { // Request permission if not granted if (permission !== 'granted') { const granted = await requestPermission(); if (!granted) { setError('Permission denied'); setIsLoading(false); return false; } } // Get service worker registration const registration = await navigator.serviceWorker.ready; // Get VAPID public key from server const response = await fetch('/api/push/vapid-key'); const { publicKey } = await response.json(); if (!publicKey) { throw new Error('VAPID public key not available'); } // Subscribe to push notifications const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicKey), }); // Send subscription to server const saveResponse = await fetch('/api/push/subscribe', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(subscription.toJSON()), }); if (!saveResponse.ok) { throw new Error('Failed to save subscription'); } setIsSubscribed(true); setIsLoading(false); return true; } catch (err) { console.error('Failed to subscribe:', err); setError(err instanceof Error ? err.message : 'Failed to subscribe'); setIsLoading(false); return false; } }; const unsubscribe = async () => { if (!('serviceWorker' in navigator) || !('PushManager' in window)) { return false; } setIsLoading(true); setError(null); try { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); if (subscription) { // Unsubscribe from push manager await subscription.unsubscribe(); // Remove subscription from server await fetch('/api/push/unsubscribe', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ endpoint: subscription.endpoint }), }); } setIsSubscribed(false); setIsLoading(false); return true; } catch (err) { console.error('Failed to unsubscribe:', err); setError(err instanceof Error ? err.message : 'Failed to unsubscribe'); setIsLoading(false); return false; } }; return { permission, isSubscribed, isLoading, error, subscribe, unsubscribe, requestPermission, }; } // Helper function to convert VAPID key 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 outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; }