diff --git a/components/push-manager.tsx b/components/push-manager.tsx index 50a5b9c..378c39f 100644 --- a/components/push-manager.tsx +++ b/components/push-manager.tsx @@ -1,35 +1,49 @@ "use client" import { useState, useEffect } from "react" -import { Bell, BellOff, Loader2 } from "lucide-react" +import { Bell, BellOff, Loader2, AlertTriangle } from "lucide-react" import { toast } from "sonner" import { subscribeUser, unsubscribeUser } from "@/app/actions/subscription" import { Button } from "@/components/ui/button" function urlBase64ToUint8Array(base64String: string) { - const padding = '='.repeat((4 - base64String.length % 4) % 4) - const base64 = (base64String + padding) - .replace(/\-/g, '+') - .replace(/_/g, '/') + try { + 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) + const rawData = window.atob(base64) + const outputArray = new Uint8Array(rawData.length) - for (let i = 0; i < rawData.length; ++i) { - outputArray[i] = rawData.charCodeAt(i) + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i) + } + return outputArray + } catch (e) { + console.error("VAPID Key conversion failed", e) + throw new Error("Invalid VAPID Key format") } - return outputArray } export function PushSubscriptionSettings({ planId }: { planId: string }) { const [isSupported, setIsSupported] = useState(false) const [subscription, setSubscription] = useState(null) const [loading, setLoading] = useState(false) + const [debugInfo, setDebugInfo] = useState(null) useEffect(() => { - if ('serviceWorker' in navigator && 'PushManager' in window && process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY) { + const checks = [] + if (!('serviceWorker' in navigator)) checks.push("No Service Worker support") + if (!('PushManager' in window)) checks.push("No PushManager support") + if (!process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY) checks.push("Missing VAPID Key") + + if (checks.length === 0) { setIsSupported(true) registerServiceWorker() + } else { + console.warn("Push not supported:", checks.join(", ")) + setDebugInfo(checks.join(", ")) } }, []) @@ -38,16 +52,19 @@ export function PushSubscriptionSettings({ planId }: { planId: string }) { const registration = await navigator.serviceWorker.ready const sub = await registration.pushManager.getSubscription() setSubscription(sub) - } catch (e) { + } catch (e: any) { console.error("SW registration error", e) + setDebugInfo(`SW Error: ${e.message}`) } } async function subscribe() { setLoading(true) + setDebugInfo(null) try { - // Explicitly request permission first + console.log("Requesting permission...") const permission = await Notification.requestPermission() + console.log("Permission result:", permission) if (permission === 'denied') { toast.error("Notifications are blocked in your browser settings.") @@ -61,17 +78,23 @@ export function PushSubscriptionSettings({ planId }: { planId: string }) { return } + console.log("Waiting for SW ready...") const registration = await navigator.serviceWorker.ready + console.log("SW Ready. Subscribing...") + const sub = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!) }) + + console.log("Subscribed locally:", sub) setSubscription(sub) await subscribeUser(planId, sub.toJSON()) toast.success("Push Notifications enabled") - } catch (error) { + } catch (error: any) { console.error("Subscription failed", error) - toast.error("Failed to enable notifications. Please check site permissions.") + setDebugInfo(`Error: ${error.message || "Unknown error"}`) + toast.error("Failed to enable notifications. See debug info.") } finally { setLoading(false) } @@ -94,26 +117,34 @@ export function PushSubscriptionSettings({ planId }: { planId: string }) { } } - if (!isSupported) return null + if (!isSupported && !debugInfo) return null return ( -
-
-

Device Notifications

-

- Receive updates on this device -

+
+
+
+

Device Notifications

+

+ Receive updates on this device +

+
+ {subscription ? ( + + ) : ( + + )}
- {subscription ? ( - - ) : ( - + {debugInfo && ( +
+ + {debugInfo} +
)}
) diff --git a/lib/notifications.ts b/lib/notifications.ts index f3dfaa0..2cd6789 100644 --- a/lib/notifications.ts +++ b/lib/notifications.ts @@ -39,6 +39,7 @@ export async function sendPlanNotification(planId: string, message: string, webh }); if (subscriptions.length > 0) { + console.log(`[Push] Found ${subscriptions.length} subscriptions for plan ${planId}`); const payload = { title: "Cat Sitting Planner", body: message, @@ -56,12 +57,24 @@ export async function sendPlanNotification(planId: string, message: string, webh subscriptions.forEach(sub => { promises.push((async () => { - const res = await sendPushNotification(sub, payload); - if (!res.success && (res.statusCode === 410 || res.statusCode === 404)) { - await prisma.pushSubscription.delete({ where: { id: sub.id } }); + try { + console.log(`[Push] Sending to endpoint ${sub.endpoint.slice(0, 20)}...`); + const res = await sendPushNotification(sub, payload); + console.log(`[Push] Result for ${sub.endpoint.slice(0, 20)}...:`, res); + if (!res.success) { + console.error(`[Push] Failed for endpoint: ${res.statusCode}`); + if (res.statusCode === 410 || res.statusCode === 404) { + console.log(`[Push] Deleting stale subscription`); + await prisma.pushSubscription.delete({ where: { id: sub.id } }); + } + } + } catch (err) { + console.error(`[Push] Error inside loop:`, err); } })()); }); + } else { + console.log(`[Push] No subscriptions found for plan ${planId}`); } } catch (e) { console.error("Failed to fetch/send push subscriptions", e); diff --git a/lib/push.ts b/lib/push.ts index ff98dcc..4dfa81b 100644 --- a/lib/push.ts +++ b/lib/push.ts @@ -18,13 +18,15 @@ interface PushSubscriptionData { export async function sendPushNotification(subscription: PushSubscriptionData, payload: any) { try { - await webpush.sendNotification({ + console.log(`[PushLib] Sending to ${subscription.endpoint.slice(0, 30)}...`); + const result = await webpush.sendNotification({ endpoint: subscription.endpoint, keys: { p256dh: subscription.p256dh, auth: subscription.auth, } }, JSON.stringify(payload)); + console.log(`[PushLib] Success: ${result.statusCode}`); return { success: true, statusCode: 201 }; } catch (error: any) { if (error.statusCode === 410 || error.statusCode === 404) { diff --git a/public/push-sw.js b/public/push-sw.js index 2b43add..2a5968b 100644 --- a/public/push-sw.js +++ b/public/push-sw.js @@ -1,8 +1,13 @@ self.addEventListener('push', function (event) { - if (!event.data) return; + console.log('[SW] Push Received', event); + if (!event.data) { + console.log('[SW] No data provided in push event'); + return; + } try { const data = event.data.json(); + console.log('[SW] Push Data:', data); const title = data.title || 'Cat Sitting Planner'; const options = { body: data.body || '', @@ -15,6 +20,8 @@ self.addEventListener('push', function (event) { event.waitUntil( self.registration.showNotification(title, options) + .then(() => console.log('[SW] Notification shown')) + .catch(e => console.error('[SW] Error showing notification:', e)) ); } catch (err) { console.error('Error processing push event:', err);