socios-comerciales.skyballo.../astro-partner-hub-main/src/lib/notificaciones.ts

95 lines
3.4 KiB
TypeScript

// Notificaciones: toasts dentro de la app + soporte de Web Push nativo
// mediante el Service Worker de mensajería en /sw.js.
import { toast } from "sonner";
// Clave pública VAPID (configurable con VITE_VAPID_KEY).
export const VAPID_KEY =
import.meta.env["VITE_VAPID_KEY"] ?? "wLVf8PBUOuPW8Zn5l_ksTTiaf9wb3OSSDaJAuxKPru0";
/** Alias retrocompatible. */
export const PUBLIC_VAPID_KEY = VAPID_KEY;
export function urlBase64ToUint8Array(base64String: string): Uint8Array {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
const raw = atob(base64);
const salida = new Uint8Array(raw.length);
for (let i = 0; i < raw.length; i += 1) salida[i] = raw.charCodeAt(i);
return salida;
}
export async function registrarServiceWorker(): Promise<ServiceWorkerRegistration | null> {
if (typeof window === "undefined" || !("serviceWorker" in navigator)) return null;
try {
return await navigator.serviceWorker.register("/sw.js", { scope: "/" });
} catch (error) {
console.warn("No se pudo registrar el Service Worker de notificaciones", error);
return null;
}
}
export async function pedirPermisoNotificaciones(): Promise<NotificationPermission | "unsupported"> {
if (typeof window === "undefined" || !("Notification" in window)) return "unsupported";
if (Notification.permission === "granted") return "granted";
try {
return await Notification.requestPermission();
} catch {
return "denied";
}
}
export type ResultadoPush =
| { ok: true; suscripcion: PushSubscription }
| { ok: false; motivo: "unsupported" | "denied" | "sin-clave" | "error" };
export async function suscribirNotificacionesPush(): Promise<ResultadoPush> {
if (typeof window === "undefined" || !("serviceWorker" in navigator) || !("PushManager" in window)) {
return { ok: false, motivo: "unsupported" };
}
const permiso = await pedirPermisoNotificaciones();
if (permiso !== "granted") return { ok: false, motivo: "denied" };
if (!VAPID_KEY || VAPID_KEY.startsWith("REEMPLAZA")) {
return { ok: false, motivo: "sin-clave" };
}
try {
const registration = (await navigator.serviceWorker.getRegistration("/sw.js")) ?? (await registrarServiceWorker());
if (!registration) return { ok: false, motivo: "error" };
const activo = await navigator.serviceWorker.ready;
const existente = await activo.pushManager.getSubscription();
const suscripcion =
existente ??
(await activo.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(VAPID_KEY) as BufferSource,
}));
console.log("Push Subscription JSON:", JSON.stringify(suscripcion));
await fetch("/api/guardar-suscripcion", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(suscripcion.toJSON()),
});
return { ok: true, suscripcion };
} catch (error) {
console.warn("No se pudo suscribir a notificaciones push", error);
return { ok: false, motivo: "error" };
}
}
export function notificar(titulo: string, cuerpo: string) {
toast.success(titulo, { description: cuerpo });
if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "granted") {
try {
new Notification(titulo, { body: cuerpo, icon: "/icons/icon-192.png" });
} catch {
/* silencioso: algunos navegadores exigen Service Worker */
}
}
}