Implementierung von Push-Benachrichtigungen: Einrichtung & Firebase

Avatar of Pascal Klau (@pascalaoms)
Pascal Klau (@pascalaoms) am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Sie kennen doch diese kleinen Benachrichtigungsfenster, die in der oberen rechten (Mac) oder unteren rechten (Windows) Ecke erscheinen, wenn zum Beispiel ein neuer Artikel auf unserem Lieblingsblog oder ein neues Video auf YouTube hochgeladen wurde? Das sind Push-Benachrichtigungen.

Ein Teil der Magie dieser Benachrichtigungen ist, dass sie erscheinen können, auch wenn wir uns gerade nicht auf dieser Website befinden, um uns diese Informationen zu übermitteln (nachdem Sie sie genehmigt haben). Auf Mobilgeräten können Sie sie, wo unterstützt, sogar schließen, wenn der Browser geschlossen ist, und trotzdem erhalten.

Artikelserie

  1. Einrichtung & Firebase (Sie sind hier!)
  2. Das Back-End
Notification on Mac via Chrome
Push-Benachrichtigung auf einem Mac in Chrome

Eine Benachrichtigung besteht aus dem Browserlogo, damit der Benutzer weiß, von welcher Software sie stammt, einem Titel, der URL der Website, von der sie gesendet wurde, einer kurzen Beschreibung und einem benutzerdefinierten Symbol.

Wir werden untersuchen, wie Push-Benachrichtigungen implementiert werden können. Da dies auf Service Workern basiert, schauen Sie sich diese Einstiegspunkte an, wenn Sie damit oder mit der allgemeinen Funktionalität der Push-API nicht vertraut sind.

Was wir erstellen werden

Vorschau unserer Push-Benachrichtigungs-Demo-Website

Um unser Benachrichtigungssystem zu testen, erstellen wir eine Seite mit

  • einem Abonnement-Button
  • einem Formular zum Hinzufügen von Beiträgen
  • einer Liste aller zuvor veröffentlichten Beiträge

Ein Repo auf Github mit dem vollständigen Code finden Sie hier und eine Vorschau des Projekts

Demo-Website anzeigen

Und ein Video davon in Aktion

Alle Werkzeuge sammeln

Sie können das Back-End-System frei wählen, das am besten zu Ihnen passt. Ich habe mich für Firebase entschieden, da es eine spezielle API bietet, die die Implementierung eines Push-Benachrichtigungsdienstes relativ einfach macht.

Wir brauchen

In diesem Teil konzentrieren wir uns nur auf das Front-End, einschließlich des Service Workers und des Manifests. Um Firebase nutzen zu können, müssen Sie sich jedoch auch registrieren und ein neues Projekt erstellen.

Implementierung der Abonnementlogik

HTML

Wir haben einen Button zum Abonnieren, der aktiviert wird, if 'serviceWorker' in navigator. Darunter ein einfaches Formular und eine Liste von Beiträgen.

<button id="push-button" disabled>Subscribe</button>

<form action="#">
  <input id="input-title">
  <label for="input-title">Post Title</label>
  <button type="submit" id="add-post">Add Post</button>
</form>

<ul id="list"></ul>

Firebase implementieren

Um Firebase nutzen zu können, müssen wir einige Skripte implementieren.

<script src="https://www.gstatic.com/firebasejs/4.1.3/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/4.1.3/firebase-database.js"></script>
<script src="https://www.gstatic.com/firebasejs/4.1.3/firebase-messaging.js"></script>

Nun können wir Firebase mit den Anmeldeinformationen unter Projekteinstellungen → Allgemein initialisieren. Die Sender-ID finden Sie unter Projekteinstellungen → Cloud Messaging. Die Einstellungen sind hinter dem Zahnradsymbol in der oberen linken Ecke verborgen.

firebase.initializeApp({
    apiKey: '<API KEY>',
    authDomain: '<PROJECT ID>.firebaseapp.com',
    databaseURL: 'https://<PROJECT ID>.firebaseio.com',
    projectId: '<PROJECT ID>',
    storageBucket: '<PROJECT ID>.appspot.com',
    messagingSenderId: '<SENDER ID>'
})

Service Worker Registrierung

Firebase bietet eine eigene Service-Worker-Einrichtung, indem eine Datei namens `firebase-messaging-sw.js` erstellt wird, die alle Funktionen zur Verarbeitung von Push-Benachrichtigungen enthält. Normalerweise muss Ihr Service Worker jedoch mehr tun. Mit der Methode useServiceWorker können wir Firebase mitteilen, auch unsere eigene Datei `service-worker.js` zu verwenden.

Jetzt können wir eine Variable userToken und eine Variable isSubscribed erstellen, die später verwendet werden.

const messaging = firebase.messaging(),
      database  = firebase.database(),
      pushBtn   = document.getElementById('push-button')

let userToken    = null,
    isSubscribed = false

window.addEventListener('load', () => {

    if ('serviceWorker' in navigator) {

        navigator.serviceWorker.register('/service-worker.js')
            .then(registration => {

                messaging.useServiceWorker(registration)

                initializePush()
            })
            .catch(err => console.log('Service Worker Error', err))

    } else {
        pushBtn.textContent = 'Push not supported.'
    }

})

Push-Setup initialisieren

Beachten Sie die Funktion initializePush() nach der Service Worker-Registrierung. Sie prüft, ob der aktuelle Benutzer bereits abonniert ist, indem sie nach einem Token in localStorage sucht. Wenn ein Token vorhanden ist, ändert sie den Button-Text und speichert den Token in einer Variable.

function initializePush() {

    userToken = localStorage.getItem('pushToken')

    isSubscribed = userToken !== null
    updateBtn()

    pushBtn.addEventListener('click', () => {
        pushBtn.disabled = true

        if (isSubscribed) return unsubscribeUser()

        return subscribeUser()
    })
}

Hier behandeln wir auch das Klickereignis auf den Abonnement-Button. Wir deaktivieren den Button bei einem Klick, um mehrfache Auslösungen zu vermeiden.

Button für Abonnement aktualisieren

Um den aktuellen Abonnementstatus widerzuspiegeln, müssen wir den Text und den Stil des Buttons anpassen. Wir können auch prüfen, ob der Benutzer Push-Benachrichtigungen beim Auffordern nicht zugelassen hat.

function updateBtn() {

    if (Notification.permission === 'denied') {
        pushBtn.textContent = 'Subscription blocked'
        return
    }

    pushBtn.textContent = isSubscribed ? 'Unsubscribe' : 'Subscribe'
    pushBtn.disabled = false
}

Benutzer abonnieren

Nehmen wir an, der Benutzer besucht uns zum ersten Mal in einem modernen Browser, ist also noch nicht abonniert. Außerdem werden Service Worker und Push API unterstützt. Wenn er auf den Button klickt, wird die Funktion subscribeUser() ausgelöst.

function subscribeUser() {

    messaging.requestPermission()
        .then(() => messaging.getToken())
        .then(token => {

            updateSubscriptionOnServer(token)
            isSubscribed = true
            userToken = token
            localStorage.setItem('pushToken', token)
            updateBtn()
        })
        .catch(err => console.log('Denied', err))

}

Hier bitten wir um Erlaubnis, Push-Benachrichtigungen an den Benutzer zu senden, indem wir messaging.requestPermission() aufrufen.

Der Browser fragt nach der Erlaubnis, Push-Benachrichtigungen zu senden.

Wenn der Benutzer diese Anfrage blockiert, wird der Button so angepasst, wie wir es in der Funktion updateBtn() implementiert haben. Wenn der Benutzer diese Anfrage zulässt, wird ein neuer Token generiert, in einer Variablen sowie in localStorage gespeichert. Der Token wird von updateSubscriptionOnServer() in unserer Datenbank gespeichert.

Abonnement in unserer Datenbank speichern

Wenn der Benutzer bereits abonniert war, zielen wir auf den richtigen Datenbankverweis, wo wir die Token gespeichert haben (in diesem Fall device_ids), suchen nach dem Token, den der Benutzer zuvor bereitgestellt hat, und entfernen ihn.

Andernfalls möchten wir den Token speichern. Mit .once('value') erhalten wir die Schlüsselwerte und können prüfen, ob der Token bereits vorhanden ist. Dies dient als zweite Schutzmaßnahme zur Suche in localStorage in initializePush(), da der Token aus verschiedenen Gründen von dort gelöscht werden könnte. Wir möchten nicht, dass der Benutzer mehrere Benachrichtigungen mit demselben Inhalt erhält.

function updateSubscriptionOnServer(token) {

    if (isSubscribed) {
        return database.ref('device_ids')
                .equalTo(token)
                .on('child_added', snapshot => snapshot.ref.remove())
    }

    database.ref('device_ids').once('value')
        .then(snapshots => {
            let deviceExists = false

            snapshots.forEach(childSnapshot => {
                if (childSnapshot.val() === token) {
                    deviceExists = true
                    return console.log('Device already registered.');
                }

            })

            if (!deviceExists) {
                console.log('Device subscribed');
                return database.ref('device_ids').push(token)
            }
        })
}

Benutzer abbestellen

Wenn der Benutzer nach erneutem Abonnieren auf den Button klickt, wird sein Token gelöscht. Wir setzen unsere Variablen userToken und isSubscribed zurück, entfernen den Token aus localStorage und aktualisieren unseren Button erneut.

function unsubscribeUser() {

    messaging.deleteToken(userToken)
        .then(() => {
            updateSubscriptionOnServer(userToken)
            isSubscribed = false
            userToken = null
            localStorage.removeItem('pushToken')
            updateBtn()
        })
        .catch(err => console.log('Error unsubscribing', err))
}

Damit der Service Worker weiß, dass wir Firebase verwenden, importieren wir die Skripte zuerst in `service-worker.js`.

importScripts('https://www.gstatic.com/firebasejs/4.1.3/firebase-app.js')
importScripts('https://www.gstatic.com/firebasejs/4.1.3/firebase-database.js')
importScripts('https://www.gstatic.com/firebasejs/4.1.3/firebase-messaging.js')

Wir müssen Firebase erneut initialisieren, da der Service Worker nicht auf die Daten in unserer `main.js`-Datei zugreifen kann.

firebase.initializeApp({
    apiKey: "<API KEY>",
    authDomain: "<PROJECT ID>.firebaseapp.com",
    databaseURL: "https://<PROJECT ID>.firebaseio.com",
    projectId: "<PROJECT ID>",
    storageBucket: "<PROJECT ID>.appspot.com",
    messagingSenderId: "<SENDER ID>"
})

Darunter fügen wir alle Ereignisse rund um die Behandlung des Benachrichtigungsfensters hinzu. In diesem Beispiel schließen wir die Benachrichtigung und öffnen eine Website, nachdem darauf geklickt wurde.

self.addEventListener('notificationclick', event => {
    event.notification.close()

    event.waitUntil(
        self.clients.openWindow('https://artofmyself.com')
    )
})

Ein weiteres Beispiel wäre die Synchronisierung von Daten im Hintergrund. Lesen Sie Googles Artikel dazu.

Nachrichten anzeigen, wenn auf der Website

Wenn wir Benachrichtigungen über neue Beiträge abonniert haben, aber gleichzeitig den Blog besuchen, wenn ein neuer Beitrag veröffentlicht wird, erhalten wir keine Benachrichtigung.

Eine Möglichkeit, dies zu lösen, ist die Anzeige einer anderen Art von Nachricht auf der Website selbst, wie z. B. eine kleine Snackbar am unteren Rand.

Um die Nutzlast der Nachricht abzufangen, rufen wir die Methode onMessage von Firebase Messaging auf.

Das Styling in diesem Beispiel verwendet Material Design Lite.

<div id="snackbar" class="mdl-js-snackbar mdl-snackbar">
  <div class="mdl-snackbar__text"></div>
  <button class="mdl-snackbar__action" type="button"></button>
</div>
import 'material-design-lite'

messaging.onMessage(payload => {

    const snackbarContainer = document.querySelector('#snackbar')

    let data = {
        message: payload.notification.title,
        timeout: 5000,
        actionHandler() {
            location.reload()
        },
        actionText: 'Reload'
    }
    snackbarContainer.MaterialSnackbar.showSnackbar(data)
})

Manifest hinzufügen

Der letzte Schritt für diesen Teil der Serie ist das Hinzufügen der Google Cloud Messaging Sender ID zur Datei `manifest.json`. Diese ID stellt sicher, dass Firebase Nachrichten an unsere App senden darf. Wenn Sie noch kein Manifest haben, erstellen Sie eines und fügen Sie Folgendes hinzu. Ändern Sie den Wert nicht.

{
  "gcm_sender_id": "103953800507"
}

Jetzt sind wir im Front-End fertig. Übrig bleibt die Erstellung unserer eigentlichen Datenbank und der Funktionen zur Überwachung von Datenbankänderungen im nächsten Artikel.

Artikelserie

  1. Einrichtung & Firebase (Sie sind hier!)
  2. Das Back-End