Häufige Benachrichtigungsmuster

Matt Gaunt

Wir schauen uns nun einige gängige Implementierungsmuster für Web-Push an.

Dazu werden verschiedene APIs verwendet, die im Service Worker verfügbar sind.

Ereignis zum Schließen der Benachrichtigung

Im letzten Abschnitt wurde beschrieben, wie auf notificationclick-Ereignisse gewartet werden kann.

Es gibt auch ein notificationclose-Ereignis, das aufgerufen wird, wenn der Nutzer eine Ihrer Benachrichtigungen schließt (d.h., der Nutzer klickt auf das Kreuz oder wischt die Benachrichtigung weg).

Dieses Ereignis wird normalerweise zu Analysezwecken verwendet, um die Nutzerinteraktion mit Benachrichtigungen zu verfolgen.

self.addEventListener('notificationclose', function (event) {
  const dismissedNotification = event.notification;

  const promiseChain = notificationCloseAnalytics();
  event.waitUntil(promiseChain);
});

Daten zu einer Benachrichtigung hinzufügen

Wenn eine Push-Nachricht empfangen wird, sind in der Regel Daten vorhanden, die nur dann nützlich sind, wenn der Nutzer auf die Benachrichtigung geklickt hat. z. B. die URL, die geöffnet werden soll, wenn auf eine Benachrichtigung geklickt wird.

Am einfachsten können Sie Daten aus einem Push-Ereignis an eine Benachrichtigung anhängen, indem Sie dem an showNotification() übergebenen Optionsobjekt einen data-Parameter hinzufügen. Beispiel:

const options = {
  body:
    'This notification has data attached to it that is printed ' +
    "to the console when it's clicked.",
  tag: 'data-notification',
  data: {
    time: new Date(Date.now()).toString(),
    message: 'Hello, World!',
  },
};
registration.showNotification('Notification with Data', options);

In einem Klick-Handler kann mit event.notification.data auf die Daten zugegriffen werden.

const notificationData = event.notification.data;
console.log('');
console.log('The notification data has the following parameters:');
Object.keys(notificationData).forEach((key) => {
  console.log(`  ${key}: ${notificationData[key]}`);
});
console.log('');

Fenster öffnen

Eine der häufigsten Reaktionen auf eine Benachrichtigung besteht darin, ein Fenster / einen Tab mit einer bestimmten URL zu öffnen. Dazu können Sie die clients.openWindow() API verwenden.

In unserem notificationclick-Ereignis würden wir Code wie den folgenden ausführen:

const examplePage = '/demos/notification-examples/example-page.html';
const promiseChain = clients.openWindow(examplePage);
event.waitUntil(promiseChain);

Im nächsten Abschnitt erfahren Sie, wie Sie überprüfen können, ob die Seite, auf die wir den Nutzer weiterleiten möchten, bereits geöffnet ist oder nicht. So können wir den geöffneten Tab fokussieren, anstatt neue Tabs zu öffnen.

Fokus auf ein vorhandenes Fenster verschieben

Wenn möglich, sollte bei jedem Klick auf eine Benachrichtigung ein neues Fenster geöffnet werden.

Bevor wir darauf eingehen, wie das geht, möchte ich darauf hinweisen, dass dies nur für Seiten deines Ursprungs möglich ist. Der Grund dafür ist, dass wir nur sehen können, welche Seiten geöffnet sind und zu unserer Website gehören. Dadurch wird verhindert, dass Entwickler alle Websites sehen, die ihre Nutzer besuchen.

Im vorherigen Beispiel ändern wir den Code, um zu sehen, ob /demos/notification-examples/example-page.html bereits geöffnet ist.

const urlToOpen = new URL(examplePage, self.location.origin).href;

const promiseChain = clients
  .matchAll({
    type: 'window',
    includeUncontrolled: true,
  })
  .then((windowClients) => {
    let matchingClient = null;

    for (let i = 0; i < windowClients.length; i++) {
      const windowClient = windowClients[i];
      if (windowClient.url === urlToOpen) {
        matchingClient = windowClient;
        break;
      }
    }

    if (matchingClient) {
      return matchingClient.focus();
    } else {
      return clients.openWindow(urlToOpen);
    }
  });

event.waitUntil(promiseChain);

Gehen wir den Code durch.

Zuerst parsen wir unsere Beispielseite mithilfe der URL API. Das ist ein netter Trick, den ich von Jeff Posnick übernommen habe. Beim Aufrufen von new URL() mit dem location-Objekt wird eine absolute URL zurückgegeben, wenn der übergebene String relativ ist (d.h. / wird zu https://example.com/).

Wir definieren die URL absolut, damit wir sie später mit den Fenster-URLs abgleichen können.

const urlToOpen = new URL(examplePage, self.location.origin).href;

Dann erhalten wir eine Liste der WindowClient-Objekte, also die Liste der aktuell geöffneten Tabs und Fenster. Beachten Sie, dass es sich hierbei nur um Tabs für Ihren Ursprung handelt.

const promiseChain = clients.matchAll({
  type: 'window',
  includeUncontrolled: true,
});

Die an matchAll übergebenen Optionen informieren den Browser darüber, dass nur nach Clients vom Typ „Fenster“ gesucht werden soll. Suchen Sie also einfach nach Tabs und Fenstern und schließen Sie Web Worker aus. Mit includeUncontrolled können wir nach allen Tabs Ihres Ursprungs suchen, die nicht vom aktuellen Service Worker kontrolliert werden, d.h. dem Service Worker, der diesen Code ausführt. Im Allgemeinen sollte includeUncontrolled beim Aufrufen von matchAll() immer auf „true“ gesetzt sein.

Wir erfassen das zurückgegebene Versprechen als promiseChain, damit wir es später an event.waitUntil() übergeben können, um unseren Service Worker am Leben zu halten.

Wenn das Promise matchAll() aufgelöst wird, durchlaufen wir die zurückgegebenen Window-Clients und vergleichen ihre URLs mit der URL, die wir öffnen möchten. Wenn wir eine Übereinstimmung finden, fokussieren wir diese Kundschaft, wodurch dieses Fenster zur Aufmerksamkeit der Nutzenden führt. Die Fokussierung ist über den matchingClient.focus()-Aufruf abgeschlossen.

Wenn wir keinen passenden Kunden finden können, öffnen wir ein neues Fenster wie im vorherigen Abschnitt.

.then((windowClients) => {
  let matchingClient = null;

  for (let i = 0; i < windowClients.length; i++) {
    const windowClient = windowClients[i];
    if (windowClient.url === urlToOpen) {
      matchingClient = windowClient;
      break;
    }
  }

  if (matchingClient) {
    return matchingClient.focus();
  } else {
    return clients.openWindow(urlToOpen);
  }
});

Benachrichtigungen zusammenführen

Wir haben festgestellt, dass durch das Hinzufügen eines Tags zu einer Benachrichtigung eine Funktion ersetzt wird, bei der bereits vorhandene Benachrichtigungen mit demselben Tag ersetzt werden.

Mit der Minimierung von Benachrichtigungen mit der Notifications API können Sie jedoch noch komplexer werden. Stellen Sie sich eine Chat-App vor, bei der der Entwickler eine neue Benachrichtigung möchte, die eine Nachricht wie „Du hast zwei Nachrichten von Matt“ und nicht nur die aktuelle Nachricht enthält.

Mit der API registration.getNotifications() haben Sie Zugriff auf alle derzeit sichtbaren Benachrichtigungen für Ihre Web-App. Sie haben aber auch die Möglichkeit, aktuelle Benachrichtigungen auf andere Weise zu bearbeiten.

Sehen wir uns an, wie wir diese API verwenden können, um das Chat-Beispiel zu implementieren.

Gehen wir davon aus, dass in unserer Chat-App jede Benachrichtigung Daten wie einen Nutzernamen enthält.

Zuerst suchen wir alle offenen Benachrichtigungen für einen Nutzer mit einem bestimmten Nutzernamen. Wir rufen registration.getNotifications() ab, durchlaufen eine Schleife und prüfen notification.data auf einen bestimmten Nutzernamen:

const promiseChain = registration.getNotifications().then((notifications) => {
  let currentNotification;

  for (let i = 0; i < notifications.length; i++) {
    if (notifications[i].data && notifications[i].data.userName === userName) {
      currentNotification = notifications[i];
    }
  }

  return currentNotification;
});

Im nächsten Schritt ersetzen Sie diese Benachrichtigung durch eine neue Benachrichtigung.

In dieser Anwendung für gefälschte Nachrichten verfolgen wir die Anzahl neuer Nachrichten, indem wir den Daten der neuen Benachrichtigung einen Zählwert hinzufügen und diesen mit jeder neuen Benachrichtigung erhöhen.

.then((currentNotification) => {
  let notificationTitle;
  const options = {
    icon: userIcon,
  }

  if (currentNotification) {
    // We have an open notification, let's do something with it.
    const messageCount = currentNotification.data.newMessageCount + 1;

    options.body = `You have ${messageCount} new messages from ${userName}.`;
    options.data = {
      userName: userName,
      newMessageCount: messageCount
    };
    notificationTitle = `New Messages from ${userName}`;

    // Remember to close the old notification.
    currentNotification.close();
  } else {
    options.body = `"${userMessage}"`;
    options.data = {
      userName: userName,
      newMessageCount: 1
    };
    notificationTitle = `New Message from ${userName}`;
  }

  return registration.showNotification(
    notificationTitle,
    options
  );
});

Wenn gerade eine Benachrichtigung angezeigt wird, erhöhen wir die Anzahl der Nachrichten und legen den Titel und den Nachrichtentext der Benachrichtigung entsprechend fest. Wenn keine Benachrichtigungen vorhanden sind, erstellen wir eine neue Benachrichtigung mit einem newMessageCount von 1.

Das Ergebnis ist, dass die erste Nachricht so aussieht:

Erste Benachrichtigung ohne Zusammenführung.

In einer zweiten Benachrichtigung werden die Benachrichtigungen so minimiert:

Zweite Benachrichtigung mit Zusammenführung.

Das Tolle an diesem Ansatz ist, dass, wenn Ihr Nutzer sieht, dass die Benachrichtigungen übereinander angezeigt werden, sie stimmiger aussieht, als die Benachrichtigung einfach durch die neueste Nachricht zu ersetzen.

Die Ausnahme von der Regel

Ich habe gesagt, dass Sie eine Benachrichtigung anzeigen müssen, wenn Sie einen Push erhalten, und dies trifft in den meisten Fällen zu. Das eine Szenario, in dem du keine Benachrichtigung anzeigen musst, ist, wenn der Nutzer deine Website geöffnet und konzentriert hat.

Innerhalb des Push-Ereignisses können Sie prüfen, ob Sie eine Benachrichtigung anzeigen müssen oder nicht. Dazu prüfen Sie die Fenster-Clients und suchen nach einem fokussierten Fenster.

Der Code zum Abrufen aller Fenster und der Suche nach einem fokussierten Fenster sieht wie folgt aus:

function isClientFocused() {
  return clients
    .matchAll({
      type: 'window',
      includeUncontrolled: true,
    })
    .then((windowClients) => {
      let clientIsFocused = false;

      for (let i = 0; i < windowClients.length; i++) {
        const windowClient = windowClients[i];
        if (windowClient.focused) {
          clientIsFocused = true;
          break;
        }
      }

      return clientIsFocused;
    });
}

Wir verwenden clients.matchAll(), um alle unsere Fenster-Clients abzurufen, und prüfen dann den focused-Parameter.

Innerhalb unseres Push-Ereignisses würden wir diese Funktion verwenden, um zu entscheiden, ob wir eine Benachrichtigung anzeigen müssen:

const promiseChain = isClientFocused().then((clientIsFocused) => {
  if (clientIsFocused) {
    console.log("Don't need to show a notification.");
    return;
  }

  // Client isn't focused, we need to show a notification.
  return self.registration.showNotification('Had to show a notification.');
});

event.waitUntil(promiseChain);

Nachrichten an eine Seite über ein Push-Ereignis senden

Wir haben festgestellt, dass du die Anzeige einer Benachrichtigung überspringen kannst, wenn sich der Nutzer gerade auf deiner Website befindet. Aber was ist, wenn du den Nutzer trotzdem darüber informieren möchtest, dass ein Ereignis eingetreten ist, eine Benachrichtigung aber zu schwer von der Hand geht?

Ein Ansatz besteht darin, eine Nachricht vom Service Worker an die Seite zu senden. Auf diese Weise kann dem Nutzer auf der Webseite eine Benachrichtigung oder ein Update angezeigt werden, um ihn über das Ereignis zu informieren. Dies ist nützlich, wenn eine dezente Benachrichtigung auf der Seite für den Nutzer besser und nutzerfreundlicher ist.

Nehmen wir an, wir haben einen Push erhalten und überprüft, ob die Webanwendung im Fokus ist. Dann können wir auf jeder geöffneten Seite „eine Nachricht posten“:

const promiseChain = isClientFocused().then((clientIsFocused) => {
  if (clientIsFocused) {
    windowClients.forEach((windowClient) => {
      windowClient.postMessage({
        message: 'Received a push message.',
        time: new Date().toString(),
      });
    });
  } else {
    return self.registration.showNotification('No focused windows', {
      body: 'Had to show a notification instead of messaging each page.',
    });
  }
});

event.waitUntil(promiseChain);

Auf jeder Seite warten wir auf Nachrichten, indem wir einen Nachrichtenereignis-Listener hinzufügen:

navigator.serviceWorker.addEventListener('message', function (event) {
  console.log('Received a message from service worker: ', event.data);
});

Mit diesem Nachrichten-Listener können Sie alles tun, was Sie möchten, eine benutzerdefinierte UI auf Ihrer Seite anzeigen oder die Nachricht vollständig ignorieren.

Wenn Sie auf Ihrer Webseite keinen Nachrichten-Listener definieren, werden die Nachrichten des Service Workers nicht ausgeführt.

Seite im Cache speichern und Fenster öffnen

Ein Szenario, das in diesem Leitfaden nicht behandelt wird, aber es wert ist, darauf eingehen zu können, besteht darin, dass Sie die Gesamtnutzung Ihrer Webanwendung verbessern können, indem Sie Webseiten im Cache speichern, die Nutzer nach dem Klicken auf Ihre Benachrichtigung im Cache speichern.

Dazu muss Ihr Service Worker für die Verarbeitung von fetch-Ereignissen eingerichtet sein. Wenn Sie jedoch einen fetch-Event-Listener implementieren, sollten Sie ihn in Ihrem push-Ereignis nutzen. Speichern Sie dazu die Seite und die Assets im Cache, die Sie zum Anzeigen der Benachrichtigung benötigen.

Browserkompatibilität

Das Ereignis notificationclose

Unterstützte Browser

  • 50
  • 17
  • 44
  • 16

Quelle

Clients.openWindow()

Unterstützte Browser

  • 40
  • 17
  • 44
  • 11.1

Quelle

ServiceWorkerRegistration.getNotifications()

Unterstützte Browser

  • 40
  • 17
  • 44
  • 16

Quelle

clients.matchAll()

Unterstützte Browser

  • 42
  • 17
  • 54
  • 11.1

Quelle

Weitere Informationen finden Sie in dieser Einführung in Service Worker.

Weitere Informationen

Code-Labs