Files
CRAPP/src/lib/push-client.ts
T
davideandClaude Opus 5 fb6bde279e Toglie la notifica di prova dalle Opzioni.
Era impalcatura per diagnosticare "le push non arrivano": ha fatto il suo
lavoro, la causa e' documentata nei limiti noti. Lasciare il pulsante avrebbe
tenuto in piedi anche la route push-prova e notificaDiProva, senza piu' nessuno
che le usa.

Via il pulsante, la funzione client, la route e i test che la coprivano. Resta
in git se dovesse servire di nuovo.

inviaPush continua a tornare { stato, corpo } e a loggare i rifiuti: serve alle
tre route che mandano notifiche, non solo alla prova.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 15:16:41 +02:00

97 lines
3.3 KiB
TypeScript

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<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();
}