Files
CRAPP/src/lib/push-client.ts
T

97 lines
3.3 KiB
TypeScript
Raw Normal View History

2026-08-06 08:36:47 +02:00
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);
}
2026-08-06 08:36:47 +02:00
export async function statoNotifiche(): Promise<boolean> {
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<void> {
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<void> {
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();
}