/* * Benachrichtigungen auf diesem Gerät ein- und ausschalten. * * Web Push braucht drei Dinge, und alle drei gibt es hier schon: eine * sichere Verbindung, einen Service Worker (sw.js im Wurzelverzeichnis) und * ein Schlüsselpaar (VAPID, liegt auf der NAS). Eine eigene App braucht es * nicht - unter iOS muss die Seite allerdings einmal über "Zum * Home-Bildschirm" abgelegt werden, sonst verweigert Safari die Erlaubnis. * * Das Abo gehört dem Browser, nicht dem Konto: Jedes Gerät meldet sich * einzeln an. Gespeichert wird es in homeMesh.push_abos (ajax/push.php), * verschickt vom Runner auf der NAS. */ const PUSH_ENDPUNKT = "ajax/push.php"; let pushStand = { schluessel: "", abos: [] }; /** base64url aus der Datenbank in den Bytesalat, den der Browser will. */ function pushSchluesselBytes(text) { const voll = (text + "=".repeat((4 - text.length % 4) % 4)).replace(/-/g, "+").replace(/_/g, "/"); const roh = atob(voll); return Uint8Array.from(roh, z => z.charCodeAt(0)); } /** Was der Browser über sich verrät, auf das Nötige gekürzt. */ function pushBrowserName() { const ua = navigator.userAgent; const name = /Edg\//.test(ua) ? "Edge" : /OPR\//.test(ua) ? "Opera" : /Chrome\//.test(ua) ? "Chrome" : /Safari\//.test(ua) ? "Safari" : /Firefox\//.test(ua) ? "Firefox" : "Browser"; const os = /Android/.test(ua) ? "Android" : /iPhone|iPad/.test(ua) ? "iOS" : /Windows/.test(ua) ? "Windows" : /Mac OS/.test(ua) ? "Mac" : ""; return os ? name + " · " + os : name; } function pushMoeglich() { return "serviceWorker" in navigator && "PushManager" in window && "Notification" in window; } async function pushAnfrage(action, body) { const antwort = await fetch(PUSH_ENDPUNKT + "?action=" + action, body ? { method: "POST", headers: { "Content-Type": "application/json", "Requested-With-Ajax": "ajax" }, body: JSON.stringify(body), } : { cache: "no-store" }); const d = await antwort.json(); if (d.error) throw new Error(d.error); return d; } /** Das Abo dieses Browsers, oder null. */ async function pushAbo() { if (!pushMoeglich()) return null; const reg = await navigator.serviceWorker.getRegistration(); return reg ? await reg.pushManager.getSubscription() : null; } /** * Auf diesem Gerät anmelden. * * Die Reihenfolge ist wichtig: erst den Service Worker, dann die Erlaubnis, * dann das Abo. Fragt man zuerst nach der Erlaubnis und der Service Worker * scheitert danach, hat der Nutzer eine Erlaubnis vergeben, die zu nichts * führt - und der Browser fragt kein zweites Mal. */ async function pushAnmelden(name) { if (!pushMoeglich()) { throw new Error("Dieser Browser kann keine Benachrichtigungen empfangen."); } if (!pushStand.schluessel) { throw new Error("Auf der NAS fehlt der Schlüssel (push_vapid.json)."); } const reg = await navigator.serviceWorker.register("sw.js"); await navigator.serviceWorker.ready; const erlaubnis = await Notification.requestPermission(); if (erlaubnis !== "granted") { throw new Error(erlaubnis === "denied" ? "Benachrichtigungen sind für diese Seite blockiert – im Browser unter „Website-Einstellungen“ wieder erlauben." : "Ohne Erlaubnis geht es nicht."); } const abo = await reg.pushManager.getSubscription() || await reg.pushManager.subscribe({ // Ohne diese Zusage weigern sich die Browser: Jede Nachricht muss den // Nutzer erreichen, stille Datenübertragung ist nicht erlaubt. userVisibleOnly: true, applicationServerKey: pushSchluesselBytes(pushStand.schluessel), }); const d = await pushAnfrage("anmelden", { abo: abo.toJSON(), name: name || pushBrowserName(), }); pushStand.abos = d.abos; return d; } /** Auf diesem Gerät abmelden - Abo im Browser und Zeile in der Datenbank. */ async function pushAbmelden() { const abo = await pushAbo(); if (!abo) return; const endpoint = abo.endpoint; await abo.unsubscribe(); const d = await pushAnfrage("abmelden", { endpoint: endpoint }); pushStand.abos = d.abos; } /** Ein fremdes Gerät aus der Liste werfen (Handy verkauft, Browser weg). */ async function pushEntfernen(id) { const d = await pushAnfrage("entfernen", { id: id }); pushStand.abos = d.abos; } /** Probemeldung über den Runner an alle angemeldeten Geräte. */ async function pushProbe() { await pushAnfrage("probe", { titel: "Probe", text: "Wenn das ankommt, stimmt der Weg." }); } async function pushStandLaden() { pushStand = await pushAnfrage("stand"); return pushStand; }