Stephan Böni - Web Push Nachrichten

Neuigkeiten abonnieren?
Bitte bestätige die anschliessende Rückfrage deines Browsers.

  Nein
Notification

Web Push Benachrichtigungen

Web-Push-Benachrichtigungen sind kurze Nachrichten, die Websites an deine Geräte senden können, selbst wenn du die Website gerade nicht aktiv besuchst. Sie ähneln den Benachrichtigungen von Apps und werden vergleichbar auf deinem Desktop oder Mobilgerät angezeigt.

Hier findest du eine Anleitung, wie du Web-Push-Benachrichtigungen in deine Website einbinden und versenden kannst.

Service Worker vorausgesetzt

Damit Nachrichten zugestellt werden können, auch wenn man sich gar nicht auf der entsprechenden Website befindet, muss ein sogenannter Service Worker im Hintergrund laufen. Die Anleitung zur Einrichtung eines Service Workers solltest du also zuerst umsetzen, falls dir dieser noch fehlt. Damit sind dann alle Voraussetzungen erfüllt und wir können loslegen.

Service Worker installieren

Zustimmung einholen

Wir sind nett und fragen zuerst, ob man wirklich Benachrichtigungen erhalten will. Dazu zeigen wir ein einladendes Icon oder einen Button an. Bei Klick öffnet sich eine Dialogbox mit der Anfrage.

HTML <icon-button onclick="document.getElementById('dialogPush').showModal();" title="Benachrichtigungen abonnieren">⩍</icon-button> <dialog id="dialogPush"> <p> <strong>Neuigkeiten abonnieren?</strong><br> Bitte bestätige die anschliessende Rückfrage deines Browsers. </p> <button onclick="pushRegister(); dialog.close();">Ja</button> <a href="javascript:dialog.close();">Nein</a> </dialog>

Mit etwas CSS sieht das dann ganz nett aus. Wenn du oben rechts auf das Glocke-Icon klickst, siehst du es.

Damit sich die Dialogbox u.a. bei Klick auf den Hintergrund wieder schliesst, braucht es etwas Javascript.

default.js // Close Modal Dialog on Background Click const dialog = document.getElementsByTagName('dialog')[0]; dialog.addEventListener('click', function(event) { const rect = dialog.getBoundingClientRect(); const isInDialog = (rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width); if(!isInDialog) { dialog.close(); } });

Bei Klick auf den Ja-Button haben wir die Zustimmung eingeholt.

VAPID-Schlüssel erstellen

Zur Registrierung und fürs spätere Senden werden sogenannte VAPID-Schlüssel benötigt. Davon gibt es einen öffentlichen und einen privaten Schlüssel. Du kannst einen der vielen freien Generatoren nutzen, z.B.
https://www.attheminute.com/vapid-key-generator.

Jeder der beiden Schlüssel ist in einer eigenen Datei abzulegen. Der öffentliche auf dem Web-Server. Dieser ist 88 Zeichen lang und könnte so aussehen:

vapidPublicKey BGkMJWzPgWIkiVfBhV1Lu-kI4K20sDdYNhBBAbXzxkD8KqaSSzzNhbjg4OaASWPQNs60JVXu7aVe7jCX5W-Pqtg

Der private Schlüssel ist 44 Zeichen lang und ist auf dem Push-Server zu hinterlegen. Als Push-Server kann auch der eigene PC dienen. Aus Sicherheitsgründen sollte dies eher nicht der Web-Server sein. Dazu aber später mehr.

Benachrichtigung registrieren

Jetzt wird es spannend. Bei Klick auf den Ja-Button wird die Funktion pushRegister() aufgerufen. Diese ist in unserer default.js zu hinterlegen.

default.js // Web Push Notification: Register function pushRegister() { console.log('push register started.'); if('serviceWorker' in navigator) { console.log('service workers supported.'); navigator.serviceWorker.ready.then(function(registration) { console.log('service worker registered.'); return registration.pushManager.getSubscription() .then(async function(subscription) { if(subscription) { console.log('subscription already available.'); return subscription; } const response = await fetch('/includes/vapidPublicKey'); const vapidPublicKey = await response.text(); const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey); return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: convertedVapidKey }); }); }).then((subscription) => { fetch('/includes/register.php', { mode: 'cors', method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ subscription: subscription }) }).then((response) => { if(!response.ok) { console.log('https connection failed.'); } else { return response.json(); } }).then((messages) => { console.log(JSON.stringify(messages)); pushNotify('Du wirst nun Benachrichtigungen erhalten.'); }); }); } else { console.log('service workers are not supported.'); } }

Hiermit registrieren wir den Website-Besucher beim Service Worker und weisen ihm eine eindeutige Kennung zu. Diese bilden wir anhand des öffentlichen VAPID-Schlüssels, den wir mittels urlBase64ToUint8Array() konvertieren müsssen, da Google Chrome sonst an der 64-Bit-Codierung scheitern würde.

default.js // Web Push Notification: Key Encoding function urlBase64ToUint8Array(base64String) { let padding = '='.repeat((4 - base64String.length % 4) % 4); let base64 = (base64String + padding) .replace(/\-/g, '+') .replace(/_/g, '/'); let rawData = window.atob(base64); let outputArray = new Uint8Array(rawData.length); for(let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; }

Die generierte Kennung wird an die register.php übergeben. Dazu später mehr.

Schliesslich senden wir gleich mal eine Willkommens-Nachricht über die Funktion pushNotify().

default.js // Web Push Notification: Simple Notify function pushNotify(body) { const title = 'Meine Website'; const icon = 'https://www.boeni.com/includes/images/favicon512.png'; const url = 'https://www.boeni.com'; if(!('Notification' in window)) { console.log('Web browser does not support desktop notification'); } if(Notification.permission === 'granted') { const notification = new Notification(title, { icon: icon, body: body, }); notification.onclick = function() { window.open(url); }; setTimeout(function() { notification.close(); }, 5000); } }

Die Konstanten title, icon und url solltest du noch anpassen.

Kennung speichern

Damit wir später Nachrichten senden können, muss die oben generierte Kennung gespeichert werden. Diese Aufgabe übernimmt ein PHP-Script.

register.php <?php if(isset($_SERVER['HTTP_ORIGIN'])) { header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}"); header('Access-Control-Allow-Credentials: true'); header('Access-Control-Max-Age: 86400'); } header('Content-Type: application/json; charset=utf-8'); $messages = register(); echo json_encode($messages); function register() { // Set defaults $email = 'ich@example.com'; $subject = 'push notification register'; $msg = ''; $success = array(); $warning = array(); $error = array(); // Make sure that it is a POST request if(strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') != 0){ $msg = 'Request method must be POST'; mail($email, $subject, $msg); array_push($error, $msg); return(array($success, $warning, $error)); } // Get the JSON data from the input stream $json = trim(file_get_contents('php://input')); // Check JSON if(empty(json_decode($json))){ $msg = 'JSON is invalid ('.json_last_error_msg().') '.$json; mail($email, $subject, $msg); array_push($error, $msg); return(array($success, $warning, $error)); } // Write JSON to file $obj = json_decode($json, true); $auth = $obj['subscription']['keys']['auth']; $filename = $auth.'.json'; $file = fopen('register/'.$filename, 'w'); fwrite($file, $json); fclose($file); // Check File if(is_file('register/'.$filename)) { $msg = $auth.' registered.'; mail($email, $subject, $msg); } else { $msg = $filename.' not saved.'; mail($email, $subject, $msg); } array_push($error, $msg); return(array($success, $warning, $error)); } ?>

In der Variable $email solltest du noch deine E-Mail-Adresse hinterlegen. Dadurch wirst du per E-Mail informiert, falls etwas schief geht oder wenn sich jemand neu registriert.

Man beachte die Access-Control-Headers. Sie müssen für CORS gesetzt werden, damit wir nicht in einen HTTP-500-Error laufen.

Das PHP-Script schreibt die Kennung in eine JSON-Datei im Unterordner register. Den musst du zuerst anlegen.

Service Worker einrichten

Damit wir später Nachrichten senden können, muss der Service Worker natürlich wissen, was er tun muss.

service-worker.js // Register event listener for the notification push event self.addEventListener('push', e => { console.log('Service Worker: Push Notification Send'); const title = 'Meine Website'; const icon = 'https://www.boeni.com/includes/images/favicon512.png'; const body = e.data.text() || 'Web Push Notification from Service Worker'; // Keep the service worker alive until the notification is created e.waitUntil( self.registration.showNotification(title, { icon: icon, body: body }) ); }); // Register event listener for the notification click event self.addEventListener('notificationclick', e => { console.log('Service Worker: Push Notification Click'); e.notification.close(); // This looks to see if the current is already open and focuses if it is e.waitUntil( clients .matchAll({ type: 'window', }) .then((clientList) => { for (const client of clientList) { if (client.url === '/' && 'focus' in client) return client.focus(); } if (clients.openWindow) return clients.openWindow('/'); }), ); });

Die Konstanten title und icon solltest du noch anpassen.

Damit ist seitens des Web-Servers alles eingerichtet, um Push-Benachrichtigungen zu versenden.

Die gesamte service-worker.js und default.js kannst du direkt herunterladen, falls du keine Lust hast, die obigen Code-Schnipsel zusammenzusetzen.

Push-Server einrichten

Der Push-Server verwendet web-push, um Nachrichten zu senden. Er sollte idealerweise nicht derselbe Rechner wie der Web-Server sein.

Ich habe mich für die npm-Variante entschieden und diese global an meinem Rechner installiert:

npm install web-push -g

Damit hast du bereits einen funktionierenden Push-Server.

Push-Nachricht senden

Zurück zur register.php. Diese hat wie erwähnt pro registrierenden Besucher im Unterordner register eine json-Datei angelegt. Diese Dateien solltest du nun auf deinen Push-Server kopieren.

scp -p webserver:/includes/register/*.json .

Mit folgenden Befehl kannst du schliesslich eine Benachrichtigung senden:

web-push send-notification \ --endpoint=https://fcm.googleapis.com/fcm/send/d61c5u920dw:APA91bEmnw8utjDYCqSRplFMVCzQMg9e5XxpYajvh37mv2QIlISdasBFLbFca9ZZ4Uqcya0ck-SP84YJUEnWsVr3mwYfaDB7vGtsDQuEpfDdcIqOX_wrCRkBW2NDWRZ9qUz9hSgtI3sY \ --key=BL7ELU24fJTAlH5Kyl8N6BDCac8u8li_U5PIwG963MOvdYs9s7LSzj8x_7v7RFdLZ9Eap50PiiyF5K0TDAis7t0 \ --auth=juarI8x__VnHvsOgfeAPHg \ --vapid-subject=mailto:ich@example.com \ --vapid-pubkey=BGkMJWzPgWIkiVfBhV1Lu-kI4K20sDdYNhBBAbXzxkD8KqaSSzzNhbjg4OaASWPQNs60JVXu7aVe7jCX5W-Pqtg \ --vapid-pvtkey=I0_d0vnesxbBSUmlDdOKibGo6vEXRO-Vu88QlSlm5j0 \ --payload=Hallo

Die Werte endpoint, key und auth entimmst du der json-Datei. Als vapid-subject hinterlegst du deine E-Mail-Adresse. Und die öffentlichen und privaten VAPID-Schlüssel hast du dir ja bereits erstellt.

Gratulation!

Du hast es geschafft.

Tipp: Falls du meine vorgeschlagende Local Storage-Einbindung nutzen willst, kannst du die Subscription auch dort hinterlegen, wo sie mit dem Server automatisch synchronisiert wird. Hierfür musst du den gesamten Abschnitt fetch('/includes/register.php', { ... }); mit der Zeile createUserCredentials('push_subscription', subscription); ersetzen.

Falls du an weiteren News und Anleitungen von mir interessiert bist, abonniere meine Web-Push-Benachrichtigungen.