Typowe wzorce powiadomień

Przyjrzyjmy się kilku typowym wzorcom implementacji push.

Konieczne będzie użycie kilku różnych interfejsów API dostępnych w skrypcie service worker.

Zdarzenie zamknięcia powiadomienia

W poprzedniej sekcji pokazaliśmy, jak można nasłuchiwać zdarzeń notificationclick.

Istnieje też zdarzenie notificationclose, które jest wywoływane, gdy użytkownik odrzuci jedno z Twoich powiadomienia (tzn. zamiast kliknąć powiadomienie, użytkownik klika krzyżyk lub przesuwa po powiadomienie).

To zdarzenie jest zwykle używane do śledzenia zaangażowania użytkowników w statystykach.

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

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

Dodawanie danych do powiadomienia

W przypadku otrzymania wiadomości push dane często zawierają tylko przydatne, jeśli użytkownik kliknął powiadomienie. Na przykład adres URL które powinny się otwierać po kliknięciu powiadomienia.

Najprostszy sposób na pobranie danych ze zdarzenia push i dołączenie ich do jest dodanie parametru data do przekazanego obiektu options na showNotification() w następujący sposób:

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);

Dostęp do danych w module obsługi kliknięć można uzyskać za pomocą event.notification.data.

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('');

Otwieranie okna

Jedną z najczęstszych reakcji na powiadomienie jest otwarcie okno / kartę do konkretnego adresu URL. Możemy to zrobić za pomocą clients.openWindow() API.

W zdarzeniu notificationclick uruchomimy taki kod:

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

W następnej sekcji dowiemy się, jak sprawdzić, czy strona, na którą chcemy kierować użytkowników, jest są już otwarte. Dzięki temu możemy skupić się na otwartej karcie, zamiast otwierać nową .

Zaznacz istniejące okno

Gdy to możliwe, należy skoncentrować się na jednym oknie, a nie otwierać je za każdym razem, gdy użytkownik kliknie powiadomienie.

Zanim omówimy, jak to zrobić, warto zwrócić uwagę na to, jest dostępny tylko w przypadku stron w pierwotnej witrynie. To dlatego, że możemy zobaczyć tylko otwarte strony należące do naszej witryny. Zapobiega to programistów tak, aby nie mogli zobaczyć wszystkich witryn przeglądanych przez użytkowników.

Posługując się poprzednim przykładem, zmienimy kod, aby sprawdzić, Aplikacja /demos/notification-examples/example-page.html jest już otwarta.

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);

Przeanalizujmy kod.

Najpierw analizujemy naszą przykładową stronę za pomocą interfejsu API URL. Oto fajna sztuczka, którą kupiłem od Jeffa Posnicka. Wywołanie funkcji new URL() za pomocą obiektu location spowoduje zwraca bezwzględny URL, jeśli przekazywany ciąg znaków jest względny (tzn. / zmieni się https://example.com/).

Adres URL jest ustawiany jako bezwzględny, co umożliwia późniejsze dopasowanie do adresu URL okna.

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

Następnie otrzymujemy listę obiektów WindowClient, która jest listą aktualnie otwartych kart i okien. (Pamiętaj, że są to tylko karty z informacjami o miejscu początkowym).

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

Opcje przekazywane do matchAll informują przeglądarkę, że chcemy aby wyszukać „okno” typu klienta (tzn. szukaj kart i okien), i wykluczać instancje internetowe). includeUncontrolled umożliwia nam wyszukiwanie wszystkie karty z punktu początkowego, które nie są kontrolowane przez bieżącą usługę czyli skrypt service worker uruchamiający ten kod. Ogólnie rzecz biorąc, przy wywoływaniu funkcji matchAll() zawsze ma wartość prawda includeUncontrolled.

Przechwytujemy zwrócona obietnicę jako obiekt promiseChain, aby przekazać ją do funkcji event.waitUntil(), aby utrzymać przy życiu nasz skrypt service worker.

Gdy obietnica matchAll() się spełni, powtórzymy zwrócone klienty okna i porównują ich adresy URL z tymi, które chcemy otworzyć. Jeśli znajdziemy dopasowanie, skupiamy się na tym, który zwróci uwagę użytkownika na to okno. Aby się skupić, matchingClient.focus() połączenie.

Jeśli nie uda nam się znaleźć pasującego klienta, otworzymy nowe okno, tak jak w poprzedniej sekcji.

.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);
  }
});

Scalam powiadomienia

Zauważyliśmy, że dodanie tagu do powiadomienia powoduje, że istniejące powiadomienie z tym samym tagiem zostanie zastąpione.

Aby zawęzić zakres powiadomień, możesz użyć funkcji zwijania powiadomień Interfejs API powiadomień. Rozważ użycie aplikacji do obsługi czatu, do której deweloper mógłby chcieć wysłać nowe powiadomienie. wyświetli komunikat podobny do „Masz dwie wiadomości od Mateusza” zamiast pokazywać tylko najnowsze filmy .

Możesz to zrobić lub zmodyfikować bieżące powiadomienia w inny sposób przy użyciu registration.getNotifications() Interfejs API umożliwiający dostęp do wszystkich obecnie widocznych powiadomień z Twojej aplikacji internetowej.

Zobaczmy, jak możemy użyć tego interfejsu API do implementacji przykładu czatu.

W aplikacji do obsługi czatu załóżmy, że każde powiadomienie zawiera dane, które obejmują nazwę użytkownika.

Najpierw musimy znaleźć wszystkie otwarte powiadomienia użytkownika z nazwa użytkownika. Pobierzemy registration.getNotifications() i zapętlimy je, aby sprawdzić notification.data dla określonej nazwy użytkownika:

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;
});

Następnym krokiem jest zastąpienie tego powiadomienia nowym.

W tej aplikacji do obsługi fałszywych wiadomości będziemy śledzić liczbę nowych wiadomości, dodając do powiadomienia i zwiększaj tę wartość z każdym nowym powiadomieniem.

.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
  );
});

Jeśli powiadomienie jest wyświetlane, zwiększamy liczbę wiadomości i ustawiamy odpowiednio tytuł i treść powiadomienia. Jeśli nie ma powiadomień, tworzymy nowe powiadomienie z: newMessageCount o wartości 1.

W rezultacie pierwsza wiadomość wygląda tak:

Pierwsze powiadomienie bez scalania.

Drugie powiadomienie zwinęłoby je w następujący sposób:

Drugie powiadomienie o scalaniu.

Zaletą takiego podejścia jest to, że jeśli użytkownik zobaczy wyświetlane jedne powiadomienia, będą bardziej spójne. niż tylko zastąpić powiadomienie najnowszą wiadomością.

Wyjątkiem od reguły

Moim zdaniem musisz pokazywać powiadomienie, gdy otrzymasz powiadomienie. To co jest prawdą większości. Sytuacja, w której nie musisz pokazywać powiadomienia, użytkownik korzysta z Twojej witryny.

W wydarzeniu push możesz sprawdzić, czy musisz wyświetlić powiadomienie, analizując klienty okna i szukając jednego z nich

Kod pozwalający uzyskać wszystkie okna i szukać aktywnego okna wygląda tak:

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;
    });
}

Korzystamy z clients.matchAll() wszystkich klientów okiennych, a następnie zapętlamy je, sprawdzając parametr focused.

Funkcja ta pozwala nam określić, czy w zdarzeniu push należy wyświetlić powiadomienie:

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);

Wysyłanie wiadomości do strony z poziomu zdarzenia push

Zauważyliśmy, że można pominąć wyświetlanie powiadomienia, jeśli użytkownik obecnie korzysta z witryny. Ale Co zrobić, jeśli nadal chcesz powiadomić użytkownika o wystąpieniu zdarzenia, ale powiadomienie zbyt ciężka ręka?

Jednym ze sposobów jest wysłanie wiadomości z skryptu service worker na stronę, dzięki czemu strona internetowa może wyświetlać użytkownikowi powiadomienie lub aktualizację informujące o wydarzeniu. Przydaje się to w przypadku w sytuacjach, gdy subtelne powiadomienie na stronie jest lepsze i bardziej przyjazne dla użytkownika.

Weźmy np. informację, że nasza aplikacja internetowa jest obecnie skoncentrowana możemy wysłać wiadomość na każdą otwartą stronę, na przykład:

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);

Na każdej stronie nasłuchujemy wiadomości, dodając zdarzenie dotyczące wiadomości detektor:

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

W tym detektorze wiadomości możesz zrobić wszystko, co chcesz: wyświetlać niestandardowy interfejs lub całkowicie zignoruj tę wiadomość.

Warto również zauważyć, że jeśli nie zdefiniujesz na stronie odbiornika wiadomości, wiadomości od skryptu service worker nie będą działać.

Zapisz stronę w pamięci podręcznej i otwórz okno

Jeden ze scenariuszy, który nie jest w tym przewodniku, ale warto omówić, jest taki: poprawić ogólny komfort korzystania z Twojej aplikacji internetowej, zapisując w pamięci podręcznej strony internetowe, które użytkownicy będą odwiedzać po klikając powiadomienie.

Wymaga to skonfigurowania mechanizmu Service Worker w celu obsługi zdarzeń fetch, ale jeśli wdrożysz detektor zdarzeń fetch, upewnij się, wykorzystać w zdarzeniu push, zapisując stronę i zasoby w pamięci podręcznej które będzie konieczne przed wyświetleniem powiadomienia.

Zgodność z przeglądarką

Wydarzenie notificationclose

Obsługa przeglądarek

  • Chrome: 50.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 16.

Źródło

Clients.openWindow()

Obsługa przeglądarek

  • Chrome: 40
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1

Źródło

ServiceWorkerRegistration.getNotifications()

Obsługa przeglądarek

  • Chrome: 40
  • Edge: 17.
  • Firefox: 44.
  • Safari: 16.

Źródło

clients.matchAll()

Obsługa przeglądarek

  • Chrome: 42.
  • Edge: 17.
  • Firefox: 54.
  • Safari: 11.1

Źródło

Aby uzyskać więcej informacji, przeczytaj wprowadzenie do mechanizmów Service Worker post.

Co dalej

Ćwiczenia z kodowania