Suche
Teil 3: Fast wie native

Angular Tutorial: Echte Push Notifications für Progressive Web-Apps

Manfred Steyer

© Shutterstock / Vector Tradition SM

Einige Browser unterstützen bereits heute Push Notifications und bieten somit ein weiteres Merkmal, das in der Vergangenheit nativen Anwendungen vorbehalten war. Durch den Einsatz von Service Workern können sie außerhalb des Browserfensters sogar dann erscheinen, wenn die Anwendung nicht läuft oder das Endgerät gesperrt ist.

Progressive Web-Apps verfolgen das Ziel, den von nativen Anwendungen gebotenen Komfort ins Web zu bringen. Dazu gehört die Möglichkeit, die Anwendung über den Home-Bildschirm ohne lange Ladezeiten zu starten, aber auch die Implementierung von Offlineszenarien zum Überbrücken schlechter oder fehlender Datenverbindungen. Zusätzlich unterstützen einige Browser auch schon den Empfang von Push Notifications. Wie bei ihren nativen Gegenstücken informieren sie den Benutzer selbst dann über aufgetretene Ereignisse, wenn er die Browseranwendung gerade nicht ausführt oder gar den Sperrbildschirm aktiviert hat.

Angular im Videotutoriallogo-entwickler tutorials

Im entwickler.tutorial Angular – eine Einführung bereitet Sie Manfred Steyer auf die Entwicklung mit dem Angular Framework vor. Steyer zeigt anhand eines praxisnahen Beispiels, wie man Services und Dependency Injection integriert, Pipes zur Formatierung von Ausgaben nutzt, Komponenten mit Bindings verwendet und professionell mit dem Angular CLI umgeht.

Jetzt anmelden!

Dazu blendet das System eine kurze Nachricht außerhalb des Anwendungsfensters ein (Abb. 1). Dieser Artikel geht auf die Implementierung solcher Szenarien ein. Dazu stellt er eine Erweiterung zur in der vorliegenden Artikelreihe verwendeten Angular-2-Anwendung vor. Den gesamten Quellcode stellt der Autor unter folgendem Link zur Verfügung.

Abb. 1: Push-Nachricht für Webanwendung erscheint außerhalb des Browserfensters

Push im Web

Push Notifications funktionieren im Web ähnlich wie bei klassischen Anwendungen: Der Client, hier der Browser, registriert sich bei einem Push-Dienst und erhält dabei eine ID (Abb. 2), die er an ein Web-API weitergibt. Um später eine Push-Nachricht an den Client zu senden, beauftragt es den Push-Dienst unter Angabe der ID. Durch dieses Vorgehen muss der Browser nur mit dem Push-Dienst und nicht mit den einzelnen Web-APIs in Kontakt bleiben.

Abb. 2: Nachrichten werden über einen Push-Dienst versendet

Derzeit unterstützen Chrome und Firefox die Standards für solche Benachrichtigungen, und auch andere Browserhersteller haben bereits Interesse daran bekundet. So gibt Microsoft bekannt, dass es gerade an einer Unterstützung für Edge arbeitet und Apple führt für die zugrunde liegenden Service Worker zumindest den Status under consideration an. Für die Nutzung in Chrome ist die Webanwendung im Vorfeld bei der Google Developer Console zu registrieren. Zusätzlich sollte dort für die Anwendung das Google Cloud Messaging API aktiviert und unter Credentials ein sogenannter API Key für den Browser (Browser Key) erzeugt werden. Damit authentifizieren sich die genutzten Web-APIs beim Push-Dienst (Punkt 3 in Abb. 2).

Bei der Registrierung generiert die Developer Console auch eine Projektnummer, die im Webmanifest als gcm_sender_id einzutragen ist (Listing 1). Hierbei handelt es sich um eine Google-spezifische Erweiterung. Eine detaillierte Beschreibung dieser Vorgehensweise finden Sie hier. Für die Nutzung in Firefox ist keine analoge vorbereitende Vorgehensweise nötig.

 

{
  "name": "Flight PWA-Demo",
  "short_name": "Flight-PWA",
  "gcm_sender_id": "1004270452653",
  […]
}

Client bei Push-Dienst anmelden

Damit eine Webanwendung die versendeten Benachrichtigungen auch bei geschlossenem Browserfenster verarbeiten kann, kümmert sich ein Service Worker darum. Diesen meldet das Web API beim Push-Dienst an, z. B. nach dem Start. Listing 2 zeigt die dazu nötigen Aufrufe. Dabei ist zu betonen, dass diese Anmeldung direkt in der Webawendung und nicht im Service Worker stattfinden muss. Die Behandlung der Benachrichtigungen, die der nächste Abschnitt beschreibt, findet hingegen im Service Worker statt.

 
setupPushNotifications() {

  let nav: any = navigator;

  if ('serviceWorker' in navigator) {
    nav.serviceWorker.ready.then(function(reg) {
      reg.pushManager.subscribe({
        userVisibleOnly: true
      }).then(function(sub) {
        console.log('Url:', sub.endpoint);
      });
    }).catch(function(error) {
      console.log('Fehler', error);
    });
  }
}

Das Beispiel in Listing 2 weist zunächst das Browserobjekt navigator zur any-Variable nav zu, um ohne zusätzliche Typings für TypeScript auszukommen. Danach richtet es einen Handler für das Service-Worker-Ereignis ready ein. Der Service Worker nutzt die Methode subscribe des pushManagers, um den Client beim Push-Dienst anzumelden. Subscribe erhält per Definition ein Objekt, das die Anmeldung näher beschreibt. Mit der darin zu findenden Eigenschaft userVisibleOnly schreibt sich die Anwendung für Push-Nachrichten ein, die direkt dem Benutzer anzuzeigen sind. Dabei handelt es sich um die einzige Art von Nachrichten, die beim Verfassen des vorliegenden Texts unterstützt wurden, weshalb für diese Eigenschaft zwingend der Wert true anzuführen war.

Das Ergebnis von subscribe ist ein Promise, das im Erfolgsfall eine sogenannte PushSubscription bekannt gibt. Diese informiert mit ihrer Eigenschaft endpoint unter anderem über einen URL, an den sich Web-APIs zum Versenden einer Benachrichtigung wenden können. Innerhalb des URL findet sich die besprochene ID, die auf den aktuellen Client verweist. In einer Implementierung für den Produktiveinsatz würde der Client nun diesen URL samt ID an das Web-API weitergeben. Zur Vereinfachung gibt das vorliegende Beispiel diese Informationen lediglich auf der Konsole aus.

Abmelden

Möchte eine Anwendung keine weiteren Nachrichten mehr erhalten, kann sie sich mit der Methode unsubscribe der PushSubscription wieder abmelden.

Auf Push reagieren

Im Service Worker kümmert sich ein Event Handler für das Ereignis push um eingehende Benachrichtigungen (Listing 3). Um bei der Nutzung von TypeScript auch hier ohne zusätzliche Typings auszukommen, weist das Beispiel die Variable self der mit any typisierten Variable context zu. Wie bei solchen Handlern üblich, übergibt er einen Promise an die Methode waitUntil des erhaltenen Event-Objekts. Auf diese Weise informiert er den Browser über den Abschluss sämtlicher für das Ereignis angestoßener asynchroner Operationen.

Die hier betrachtete Implementierung synchronisiert die lokalen Daten mit den am Server zur Verfügung stehenden. Dazu kommt die im vorangegangenen Teil dieser Artikelserie beschriebene Methode sync des BuchungServices zum Einsatz. Danach präsentiert der Service Worker dem Benutzer eine Nachricht, die der Browser außerhalb des Browserfensters anzeigt. Hierzu nutzt er die Methode showNotification. Sie nimmt einen Titel, einen Text (body) sowie ein anzuzeigendes Icon entgegen. Zusätzlich erhält showNotification einen Tag. Diesen Tag nutzt der Browser, um die Art der Push-Nachricht zu identifizieren. Das ist hilfreich, wenn sich gleichartige Push-Nachrichten zeitlich überlappen. In diesem Fall ersetzt der Browser die letzte Nachricht durch die gerade empfangene, anstatt beide nebeneinander zu präsentieren.

 
var context:any = self;
let bs:BuchungService = […];

context.addEventListener('push', function(event: any) {
  console.log('Push message', event);
  var title = 'Aktualisierte Daten';
  event.waitUntil(bs.sync().then(p => 
    context.registration.showNotification(title, {
    body: 'Ihre Daten wurden aktualisiert',
    icon: '/images/touch/icon-128x128.png',
    tag: 'new-data'
    })));
});

Auf Klick reagieren

Klickt der Benutzer auf die außerhalb des Browserfensters präsentierte Nachricht, löst der Browser beim Service Worker das Ereignis notificationclick aus. Auf diese Weise kann der Service Worker eine mit der Nachricht assoziierte Aktion anstoßen. Das Beispiel in Listing 4 veranschaulicht das. Es geht sämtliche Browserfenster durch und prüft, ob die betroffene Webanwendung bereits in einem dieser Fenster geladen ist. Dazu vergleicht es den URL der Browserfenster mit dem URL der Anwendung. Findet es auf diesem Weg ein solches Fenster, gibt es ihm den Fokus. Ansonsten öffnet es die Anwendung in einem neuen Fenster.

 

declare var clients: any;

self.addEventListener('notificationclick', function(event: any) {
  console.log('Notification click: tag ', event.notification.tag);
  event.notification.close();
  var url = 'http://localhost:8080';
  event.waitUntil(
    clients.matchAll({
      type: 'window'
    })
    .then(function(windowClients) {
      console.debug("win-count: " + windowClients.length);
      for (var i = 0; i < windowClients.length; i++) {
        var client = windowClients[i];
        console.debug(" > client-url: " + client.url + ", url: " + url);
        let clientUrl: string = client.url;
        if ( clientUrl.startsWith(url) && 'focus' in client) {
          return client.focus();
        }
      }
      if (clients.openWindow) {
        return clients.openWindow(url);
      }
    })
  );
});

Push auslösen

Um die Anwendung zu testen, können die Developer Tools in Chrome eine Push Notification simulieren. Dazu findet sich ein Link mit der Beschriftung Push im Knoten „Service Workers“ des Menübefehls Resources (Abb. 3).

Abb. 3: Push-Nachricht über Developer Tools simulieren

Web-APIs können hingegen eine Push Notification auslösen, indem sie per POST eine HTTP-Anfrage an den bei der Registrierung erhaltenen URL senden (Listing 5). Um auf diesem Weg Chrome zu benachrichtigen, muss sich das Web-API über den Authorization-Header mit dem in der Google Developer Console erhaltenen Browser-Key authentifizieren. Für Firefox ist eine solche Authentifizierung nicht vorgesehen, er verlangt jedoch einen Header TTL (Time to live). Damit gibt das Web-API an, wie lange die Benachrichtigung anzuzeigen ist. Im betrachteten Beispiel entscheidet sich der Aufrufer für zwanzig Sekunden.

 

POST https://android.googleapis.com/gcm/send/epFCsb4[...] HTTP/1.1

Authorization: key=AIzaSyDGFJo7_qMFJRa5tVjHAfyPWjFEQBOe47o

TTL: 20

Die darauf folgende Antwort informiert das Web-API, ob die definierte Push Notification erfolgreich zugestellt wurde. Der Statuscode 201 weist z. B. auf eine erfolgreiche Zustellung hin. In manchen Fällen ist es wünschenswert, im Rahmen von Push Notifications zusätzliche anwendungsspezifische Informationen an den Browser zu übersenden. Als der vorliegende Text verfasst wurde, unterstütze Chrome solche Szenarien nicht.

Ein Grund dafür sind Sicherheitsbedenken. Beispielsweise müsste man in solchen Fällen sicherstellen, dass Angreifer keine gefälschten Daten an den Browser senden können. Zusätzlich ist zu garantieren, dass auch nur der adressierte Browser die Nachricht lesen kann. Firefox beugt solchen Angriffen vor, indem er eine Verschlüsselung der übersendeten Nutzdaten verlangt. Dazu nutzt das Web-API einen Schlüssel, den Firefox im Zuge der Registrierung beim Push-Dienst bekannt gibt. Die Webanwendung müsste diesen Schlüssel abrufen und neben dem besprochenen URL an das Web-API übersenden. Damit diese sensiblen Informationen nur von den Kommunikationspartnern gelesen werden können, ist hierbei HTTPS zu nutzen. Möchte das Web-API bei einer Benachrichtigung Daten übersenden, muss es diese anschließend mit dem empfangenen Schlüssel verschlüsseln. Da jedoch Chrome solche Szenarien noch gar nicht unterstützt, bietet es sich derzeit zur Vereinfachung an, auf das Übersenden von Daten im Rahmen von Benachrichtigungen zu verzichten. Alternativ dazu kann der Service Worker beim Empfang von Nachrichten nochmals via HTTPS das Web-API aufrufen, um sich über den Grund der empfangenen Benachrichtigung zu informieren.

Fazit

Durch die Unterstützung von Push Notifications kommen Progressive Web-Apps ihrem Ziel, die Annehmlichkeiten nativer Anwendungen zu bieten, einen Schritt näher. Derzeit bieten Chrome und Firefox Unterstützung dafür, andere Browserhersteller haben bereits Interesse bekundet. Wie bei vielen Möglichkeiten, die Progressive Web Applications bieten, basieren auch Push Notifications auf Service Workern. Dadurch, dass sie im Hintergrund, losgelöst von der Webanwendung, laufen, können sie selbst dann Push-Nachrichten verarbeiten, wenn die Browseranwendung nicht läuft.

Im Sinne progressiver Erweiterungen ist eine Webanwendung so zu gestalten, dass sie selbst bei einer fehlenden Browserunterstützung den Kern ihrer Funktionalität bieten kann. Das bedeutet, dass die Anwendung prüfen muss, ob der Browser Push unterstützt und sich nicht darauf verlassen darf. Vielmehr sind auch Alternativen für die Präsentation von Nachrichten vorzusehen.

Geschrieben von
Manfred Steyer
Manfred Steyer
Manfred Steyer ist selbstständiger Trainer und Berater mit Fokus auf Angular 2. In seinem aktuellen Buch "AngularJS: Moderne Webanwendungen und Single Page Applications mit JavaScript" behandelt er die vielen Seiten des populären JavaScript-Frameworks aus der Feder von Google. Web: www.softwarearchitekt.at
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.