123 lines
3.7 KiB
JavaScript
123 lines
3.7 KiB
JavaScript
import React from "react";
|
|
import { supabase, hasSupabaseConfig } from "../supabaseClient";
|
|
|
|
const VAPID_PUBLIC_KEY = "BBHRiMfIGQf3EjnnPQ6QS-jS2oset-YMmufJSvw-0a974ULxLaxQqZkfB0ldz-tqH6B9WAIh9Et86i0ezjuedmU";
|
|
|
|
function urlBase64ToUint8Array(base64String) {
|
|
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
|
const base64 = (base64String + padding).replace(/\-/g, "+").replace(/_/g, "/");
|
|
const rawData = window.atob(base64);
|
|
return Uint8Array.from(rawData, (c) => c.charCodeAt(0));
|
|
}
|
|
|
|
export async function subscribeUserToPush(userId) {
|
|
if (!hasSupabaseConfig || !userId) return null;
|
|
if (!("serviceWorker" in navigator) || !("PushManager" in window)) {
|
|
console.warn("Push notifications not supported");
|
|
return null;
|
|
}
|
|
|
|
const registration = await navigator.serviceWorker.ready;
|
|
let subscription = await registration.pushManager.getSubscription();
|
|
|
|
if (!subscription) {
|
|
try {
|
|
subscription = await registration.pushManager.subscribe({
|
|
userVisibleOnly: true,
|
|
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
|
|
});
|
|
} catch (err) {
|
|
console.error("Push subscription failed:", err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
if (!session?.access_token) return null;
|
|
|
|
const res = await fetch(`${supabase.supabaseUrl}/functions/v1/subscribe-push`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${session.access_token}`,
|
|
},
|
|
body: JSON.stringify({ subscription: subscription.toJSON() }),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
console.error("Failed to save push subscription:", await res.text());
|
|
return null;
|
|
}
|
|
|
|
return subscription;
|
|
}
|
|
|
|
export async function unsubscribeUserFromPush() {
|
|
if (!("serviceWorker" in navigator)) return;
|
|
|
|
const registration = await navigator.serviceWorker.ready;
|
|
const subscription = await registration.pushManager.getSubscription();
|
|
if (!subscription) return;
|
|
|
|
const endpoint = subscription.endpoint;
|
|
await subscription.unsubscribe();
|
|
|
|
const { data: { session } } = await supabase.auth.getSession();
|
|
if (session?.access_token) {
|
|
await fetch(`${supabase.supabaseUrl}/functions/v1/unsubscribe-push`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${session.access_token}`,
|
|
},
|
|
body: JSON.stringify({ endpoint }),
|
|
});
|
|
}
|
|
}
|
|
|
|
export function usePushNotifications(userId) {
|
|
const [isSupported, setIsSupported] = React.useState(false);
|
|
const [isSubscribed, setIsSubscribed] = React.useState(false);
|
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
|
|
React.useEffect(() => {
|
|
const supported = "serviceWorker" in navigator && "PushManager" in window && "Notification" in window;
|
|
setIsSupported(supported);
|
|
if (!supported) return;
|
|
|
|
navigator.serviceWorker.ready.then((reg) => {
|
|
reg.pushManager.getSubscription().then((sub) => {
|
|
setIsSubscribed(!!sub);
|
|
});
|
|
});
|
|
}, []);
|
|
|
|
const subscribe = React.useCallback(async () => {
|
|
if (!isSupported || !userId) return;
|
|
setIsLoading(true);
|
|
try {
|
|
const permission = await Notification.requestPermission();
|
|
if (permission !== "granted") {
|
|
setIsSubscribed(false);
|
|
return;
|
|
}
|
|
const sub = await subscribeUserToPush(userId);
|
|
setIsSubscribed(!!sub);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [isSupported, userId]);
|
|
|
|
const unsubscribe = React.useCallback(async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
await unsubscribeUserFromPush();
|
|
setIsSubscribed(false);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
return { isSupported, isSubscribed, isLoading, subscribe, unsubscribe };
|
|
}
|