function base64UrlToUint8Array(base64Url: string): Uint8Array { const padding = "=".repeat((4 - (base64Url.length % 4)) % 4); const base64 = (base64Url + padding).replace(/-/g, "+").replace(/_/g, "/"); const raw = atob(base64); const out = new Uint8Array(raw.length); for (let i = 0; i < raw.length; i += 1) out[i] = raw.charCodeAt(i); return out; } function chiaviDa(sub: PushSubscription) { const json = sub.toJSON(); return { p256dh: json.keys?.["p256dh"] ?? "", auth: json.keys?.["auth"] ?? "" }; } export function pushSupportato() { return ( typeof window !== "undefined" && "serviceWorker" in navigator && "PushManager" in window && "Notification" in window ); } /** Aggiorna il worker già installato anche nelle sessioni lunghe della webapp. */ export function mantieniWorkerPushAggiornato(): () => void { if (!pushSupportato()) return () => {}; let inCorso = false; const aggiorna = async () => { if (document.visibilityState !== "visible" || inCorso) return; inCorso = true; try { const reg = await navigator.serviceWorker.getRegistration("/push-sw.js"); // Non registrare né iscrivere chi non ha mai attivato le notifiche. await reg?.update(); } catch { // Offline: il worker attivo resta valido. Si riprova al prossimo ritorno nell'app. } finally { inCorso = false; } }; void aggiorna(); document.addEventListener("visibilitychange", aggiorna); return () => document.removeEventListener("visibilitychange", aggiorna); } export async function statoNotifiche(): Promise { if (!pushSupportato()) return false; const reg = await navigator.serviceWorker.getRegistration("/push-sw.js"); const sub = await reg?.pushManager.getSubscription(); return Boolean(sub); } export async function attivaNotifiche(giocatoreId: string): Promise { if (!pushSupportato()) throw new Error("Notifiche non supportate su questo dispositivo"); const permesso = await Notification.requestPermission(); if (permesso !== "granted") throw new Error("Permesso notifiche negato"); const config = await fetch("/api/public/push-config").then((r) => r.json()); if (!config?.publicKey) throw new Error("Notifiche non configurate"); const reg = await navigator.serviceWorker.register("/push-sw.js"); await navigator.serviceWorker.ready; const esistente = await reg.pushManager.getSubscription(); const sub = esistente ?? (await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: base64UrlToUint8Array(config.publicKey) as BufferSource, })); const res = await fetch("/api/public/push-subscribe", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ endpoint: sub.endpoint, giocatoreId, ...chiaviDa(sub) }), }); if (!res.ok) throw new Error("Salvataggio iscrizione non riuscito"); } export async function disattivaNotifiche(): Promise { if (!pushSupportato()) return; const reg = await navigator.serviceWorker.getRegistration("/push-sw.js"); const sub = await reg?.pushManager.getSubscription(); if (sub) { await fetch("/api/public/push-subscribe", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ endpoint: sub.endpoint }), }); await sub.unsubscribe(); } await reg?.unregister(); }