Subskrybowanie użytkownika

Pierwszym krokiem jest uzyskanie od użytkownika zgody na wysyłanie do niego powiadomień push, a potem możemy uzyskać dostęp do PushSubscription.

Interfejs API JavaScript do tego celu jest dość prosty, więc przyjrzyjmy się jego logice.

Wykrywanie cech

Najpierw musimy sprawdzić, czy przeglądarka, której używasz, obsługuje wiadomości push. Możemy sprawdzić, czy powiadomienia push są obsługiwane, wykonując 2 proste czynności.

  1. Sprawdź, czy w navigatorze jest serviceWorker.
  2. Sprawdź, czy w oknie jest widoczna usługa PushManager.
if (!('serviceWorker' in navigator)) {
  // Service Worker isn't supported on this browser, disable or hide UI.
  return;
}

if (!('PushManager' in window)) {
  // Push isn't supported on this browser, disable or hide UI.
  return;
}

Chociaż obsługa przeglądarek w przypadku zarówno usług w tle, jak i wiadomości push stale się rozwija, zawsze warto wykrywać funkcje w obu przypadkach i stopniowo ulepszać.

Rejestrowanie skryptu service worker

Dzięki funkcji wykrywania funkcji wiemy, że obsługiwane są zarówno serwisy, jak i wiadomości push. Kolejnym krokiem jest „zarejestrowanie” naszego service workera.

Gdy rejestrujemy usługę działającą w tle, przekazujemy przeglądarce informacje o tym, gdzie znajduje się plik usługi. Plik jest nadal tylko w języku JavaScript, ale przeglądarka „daje mu dostęp” do interfejsów API usługi, w tym do interfejsu push. Aby być dokładniejszym, przeglądarka uruchamia plik w środowisku usługi.

Aby zarejestrować skrypt service worker, wywołaj funkcję navigator.serviceWorker.register(), podając ścieżkę do pliku. W ten sposób:

function registerServiceWorker() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      console.log('Service worker successfully registered.');
      return registration;
    })
    .catch(function (err) {
      console.error('Unable to register service worker.', err);
    });
}

Ta funkcja informuje przeglądarkę, że mamy plik service worker i gdzie się on znajduje. W tym przypadku plik usługi znajduje się w folderze /service-worker.js. Po wywołaniu funkcji register() przeglądarka wykona te czynności:

  1. Pobierz plik usługi.

  2. Uruchom kod JavaScript.

  3. Jeśli wszystko działa prawidłowo i nie ma błędów, obietnica zwrócona przez register() zostanie spełniona. Jeśli wystąpią jakiekolwiek błędy, obietnica zostanie odrzucona.

Jeśli register() odrzuci kod, sprawdź w Narzędziach deweloperskich w Chrome, czy nie zawiera on literówek ani błędów.

Gdy register() zostanie rozwiązany, zwróci ServiceWorkerRegistration. Użyjemy tej rejestracji do uzyskania dostępu do interfejsu PushManager API.

Zgodność interfejsu PushManager API z przeglądarkami

Obsługa przeglądarek

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

Źródło

Prośba o uprawnienia

Zarejestrowaliśmy naszego pracownika w tle i jesteśmy gotowi do subskrybowania użytkownika. Następnym krokiem jest uzyskanie od niego zgody na wysyłanie mu powiadomień push.

Interfejs API służący do uzyskiwania uprawnień jest stosunkowo prosty, ale ma tę wadę, że niedawno zmienił zachowanie z wywołania zwrotnego na zwracanie obietnicy. Problem w tym, że nie możemy określić, która wersja interfejsu API jest implementowana przez bieżącą przeglądarkę, więc musisz zaimplementować obie wersje i obsługiwać obie.

function askPermission() {
  return new Promise(function (resolve, reject) {
    const permissionResult = Notification.requestPermission(function (result) {
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  }).then(function (permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error("We weren't granted permission.");
    }
  });
}

W powyższym kodzie ważnym fragmentem kodu jest wywołanie funkcji Notification.requestPermission(). Ta metoda wyświetla użytkownikowi prompt:

Prośba o przyznanie uprawnień w Chrome na komputery i urządzenia mobilne

Gdy użytkownik zareaguje na prośbę o przyznanie uprawnień, naciskając Zezwól, Zablokuj lub po prostu ją zamykając, otrzymamy wynik w postaci ciągu znaków: 'granted', 'default' lub 'denied'.

W powyższym przykładowym kodzie obietnica zwracana przez askPermission() jest rozwiązywana, jeśli uprawnienie zostało przyznane. W przeciwnym razie obietnica jest odrzucana.

Jeden z takich szczególnych przypadków to sytuacja, gdy użytkownik kliknie przycisk „Zablokuj”. W takim przypadku aplikacja internetowa nie będzie mogła ponownie poprosić użytkownika o pozwolenie. Będą musieli ręcznie „odblokować” aplikację, zmieniając stan uprawnień, który jest ukryty w panelu ustawień. Dokładnie zastanów się, jak i kiedy prosić użytkownika o pozwolenie, ponieważ jeśli kliknie on opcję blokowania, nie będzie można łatwo cofnąć tej decyzji.

Dobra wiadomość jest taka, że większość użytkowników chętnie udziela uprawnień, o ile wie, dlaczego aplikacja o nie prosi.

W następnym artykule przyjrzymy się temu, jak niektóre popularne witryny proszą o takie uprawnienia.

Subskrypcja użytkownika za pomocą interfejsu PushManager

Gdy zarejestrujemy naszego pracownika usługi i uzyskamy uprawnienia, możemy zapisać użytkownika, wywołując funkcję registration.pushManager.subscribe().

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

Podczas wywoływania metody subscribe() przekazujemy obiekt options, który zawiera zarówno wymagane, jak i opcjonalne parametry.

Przyjrzyjmy się wszystkim opcjom, które możemy podać.

Opcje userVisibleOnly

Gdy powiadomienia push zostały po raz pierwszy dodane do przeglądarek, nie było pewności, czy deweloperzy powinni mieć możliwość wysyłania powiadomień push bez wyświetlania powiadomienia. Jest to powszechnie nazywane cichym powiadomieniem push, ponieważ użytkownik nie wie, że coś się dzieje w tle.

Obawialiśmy się, że deweloperzy mogą robić nieprzyjemne rzeczy, takie jak śledzenie lokalizacji użytkownika bez jego wiedzy.

Aby uniknąć tego scenariusza i dać autorom specyfikacji czas na zastanowienie się, jak najlepiej obsługiwać tę funkcję, dodano opcję userVisibleOnly. Przekazanie wartości true jest symboliczną zgodą z przeglądarką na wyświetlanie powiadomienia za każdym razem, gdy aplikacja internetowa otrzyma powiadomienie push (czyli bez opcji bezdźwiękowego powiadomienia push).

Obecnie musisz podać wartość true. Jeśli nie uwzględnisz klucza userVisibleOnly ani wartości w elementach false, pojawi się ten komunikat o błędzie:

Chrome obsługuje obecnie interfejs Push API tylko w przypadku subskrypcji, które będą skutkować wyświetlaniem wiadomości użytkownikowi. Możesz to zgłosić, dzwoniąc na numer pushManager.subscribe({userVisibleOnly: true}). Więcej informacji znajdziesz na stronie https://goo.gl/yqv4Q4.

Obecnie wygląda na to, że w Chrome nigdy nie zostanie wdrożona ogólna funkcja bezgłośnego przesyłania powiadomień. Zamiast tego autorzy specyfikacji badają koncepcję interfejsu Budget API, który pozwoli aplikacjom internetowym na wysyłanie określonej liczby nieaktywnych powiadomień push na podstawie wykorzystania aplikacji internetowej.

opcja applicationServerKey,

W poprzedniej sekcji wspomnieliśmy już o „kluczach serwera aplikacji”. „Klucze serwera aplikacji” są używane przez usługę powiadomień push do identyfikowania aplikacji, która subskrybuje użytkownika, oraz do zapewnienia, że ta sama aplikacja wysyła wiadomości do tego użytkownika.

Klucze serwera aplikacji to klucz publiczny i prywatny, które są unikalne dla Twojej aplikacji. Klucz prywatny powinien być znany tylko aplikacji, a klucz publiczny można swobodnie udostępniać.

Opcja applicationServerKey przekazana do wywołania subscribe() to klucz publiczny aplikacji. Podczas subskrybowania użytkownika przeglądarka przekazuje te informacje usłudze przesyłania informacji, co oznacza, że usługa przesyłania informacji może powiązać klucz publiczny aplikacji z identyfikatorem PushSubscription użytkownika.

Te kroki ilustruje diagram poniżej.

  1. Aplikacja internetowa jest wczytana w przeglądarce, a Ty wywołujesz funkcję subscribe(), przekazując klucz publiczny serwera aplikacji.
  2. Następnie przeglądarka wysyła żądanie sieciowe do usługi push, która wygeneruje punkt końcowy, powiąże go z kluczem publicznym aplikacji i zwróci go do przeglądarki.
  3. Przeglądarka doda ten punkt końcowy do PushSubscription, który jest zwracany za pomocą subscribe().

Ilustracja pokazująca, jak klucz publiczny serwera aplikacji jest używany w metodzie subskrypcji.

Gdy później chcesz wysłać wiadomość push, musisz utworzyć nagłówek Authorization, który będzie zawierać informacje podpisane kluczem prywatnym serwera aplikacji. Gdy usługa przesyłania powiadomień push otrzyma prośbę o wysłanie powiadomienia push, może zweryfikować podpisany nagłówek Authorization, sprawdzając klucz publiczny powiązany z punktem końcowym, który odbiera żądanie. Jeśli podpis jest prawidłowy, usługa push wie, że pochodzi z serwera aplikacji z odpowiednim kluczem prywatnym. Jest to zasadniczo środek bezpieczeństwa, który uniemożliwia innym osobom wysyłanie wiadomości do użytkowników aplikacji.

Sposób używania klucza prywatnego serwera aplikacji podczas wysyłania wiadomości

Parametr applicationServerKey jest technicznie opcjonalny. Najprostsza implementacja w Chrome wymaga jednak tego, a inne przeglądarki mogą wymagać tego w przyszłości. W Firefoxie jest to opcjonalne.

Specyfikacja, która określa, jaki powinien być klucz serwera aplikacji, to specyfikacja VAPID. Zawsze, gdy czytasz coś na temat „kluczy serwera aplikacji” lub „kluczy VAPID”, pamiętaj, że chodzi o to samo.

Tworzenie kluczy serwera aplikacji

Aby utworzyć publiczny i prywatny zestaw kluczy serwera aplikacji, otwórz stronę web-push-codelab.glitch.me lub wygeneruj klucze za pomocą web-push command line w ten sposób:

    $ npm install -g web-push
    $ web-push generate-vapid-keys

Klucze te musisz utworzyć tylko raz dla swojej aplikacji. Pamiętaj, aby zachować prywatność klucza prywatnego. (Tak, właśnie to powiedziałem).

Uprawnienia i subscribe()

Wywołanie funkcji subscribe() ma 1 skutek uboczny. Jeśli w momencie wywołania funkcji subscribe() Twoja aplikacja internetowa nie ma uprawnień do wyświetlania powiadomień, przeglądarka poprosi Cię o te uprawnienia. Jest to przydatne, jeśli Twój interfejs użytkownika działa z tym przepływem danych, ale jeśli chcesz mieć większą kontrolę (a większość deweloperów chce) – trzymaj się interfejsu API Notification.requestPermission(), którego użyliśmy wcześniej.

Czym jest subskrypcja powiadomień push?

Wywołujemy funkcję subscribe(), przekazując jej kilka opcji. W zamian otrzymujemy obietnicę, która zwraca wartość PushSubscription, co powoduje powstanie kodu w takiej postaci:

function subscribeUserToPush() {
  return navigator.serviceWorker
    .register('/service-worker.js')
    .then(function (registration) {
      const subscribeOptions = {
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array(
          'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
        ),
      };

      return registration.pushManager.subscribe(subscribeOptions);
    })
    .then(function (pushSubscription) {
      console.log(
        'Received PushSubscription: ',
        JSON.stringify(pushSubscription),
      );
      return pushSubscription;
    });
}

Obiekt PushSubscription zawiera wszystkie wymagane informacje potrzebne do wysłania użytkownikowi wiadomości push. Jeśli wydrukujesz zawartość za pomocą JSON.stringify(), zobaczysz:

    {
      "endpoint": "https://some.pushservice.com/something-unique",
      "keys": {
        "p256dh":
    "BIPUL12DLfytvTajnryr2PRdAgXS3HGKiLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WAkAPIxr4gK0_dQds4yiI=",
        "auth":"FPssNDTKnInHVndSTdbKFw=="
      }
    }

endpoint to adres URL usługi push. Aby wywołać wiadomość push, wyślij żądanie POST na ten adres URL.

Obiekt keys zawiera wartości używane do szyfrowania danych wiadomości wysyłanych za pomocą wiadomości push (omówimy je w dalszej części tej sekcji).

Regularne odnawianie subskrypcji, aby zapobiec jej wygaśnięciu

Gdy subskrybujesz powiadomienia push, często otrzymujesz PushSubscription.expirationTime null. Teoretycznie oznacza to, że subskrypcja nigdy nie wygaśnie (w przeciwieństwie do otrzymania DOMHighResTimeStamp, który informuje o dokładnym momencie wygaśnięcia subskrypcji). W praktyce jednak przeglądarki często pozwalają na wygaśnięcie subskrypcji, np. gdy przez dłuższy czas nie były odbierane powiadomienia push lub gdy przeglądarka wykryje, że użytkownik nie używa aplikacji, która ma uprawnienia do wysyłania powiadomień push. Jednym ze sposobów zapobiegania temu jest ponowne subskrybowanie użytkownika po każdym otrzymanym powiadomieniu, jak pokazano w tym fragmencie kodu. Wymaga to wysyłania powiadomień z wystarczającą częstotliwością, aby przeglądarka nie anulowała automatycznie subskrypcji. Należy bardzo uważnie rozważyć zalety i wady uzasadnionego wysyłania powiadomień w porównaniu z niechcianym wysyłaniem spamu tylko po to, aby subskrypcja nie wygasła. Nie próbuj zwalczać działań przeglądarki, które mają na celu ochronę użytkownika przed dawno zapomnianymi subskrypcjami powiadomień.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

Wysyłanie subskrypcji na serwer

Gdy masz subskrypcję push, możesz ją wysłać na serwer. To, jak to zrobisz, zależy od Ciebie, ale możesz użyć parametru JSON.stringify(), aby uzyskać wszystkie niezbędne dane z obiektu subscription. Możesz też uzyskać ten sam wynik ręcznie:

const subscriptionObject = {
  endpoint: pushSubscription.endpoint,
  keys: {
    p256dh: pushSubscription.getKeys('p256dh'),
    auth: pushSubscription.getKeys('auth'),
  },
};

// The above is the same output as:

const subscriptionObjectToo = JSON.stringify(pushSubscription);

Wysyłanie subskrypcji odbywa się na stronie internetowej w ten sposób:

function sendSubscriptionToBackEnd(subscription) {
  return fetch('/api/save-subscription/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(subscription),
  })
    .then(function (response) {
      if (!response.ok) {
        throw new Error('Bad status code from server.');
      }

      return response.json();
    })
    .then(function (responseData) {
      if (!(responseData.data && responseData.data.success)) {
        throw new Error('Bad response from server.');
      }
    });
}

Serwer węzła odbiera to żądanie i zapisuje dane w bazie danych, aby można było z nich później skorzystać.

app.post('/api/save-subscription/', function (req, res) {
  if (!isValidSaveRequest(req, res)) {
    return;
  }

  return saveSubscriptionToDatabase(req.body)
    .then(function (subscriptionId) {
      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify({data: {success: true}}));
    })
    .catch(function (err) {
      res.status(500);
      res.setHeader('Content-Type', 'application/json');
      res.send(
        JSON.stringify({
          error: {
            id: 'unable-to-save-subscription',
            message:
              'The subscription was received but we were unable to save it to our database.',
          },
        }),
      );
    });
});

Dzięki temu, że dane PushSubscription są na naszym serwerze, możemy wysyłać użytkownikom wiadomości, kiedy tylko chcemy.

Regularne odnawianie subskrypcji, aby zapobiec jej wygaśnięciu

Gdy subskrybujesz powiadomienia push, często otrzymujesz PushSubscription.expirationTime null. Teoretycznie oznacza to, że subskrypcja nigdy nie wygaśnie (w przeciwieństwie do otrzymania DOMHighResTimeStamp, który informuje o dokładnym momencie wygaśnięcia subskrypcji). W praktyce jednak przeglądarki często pozwalają na wygaśnięcie subskrypcji, np. gdy przez długi czas nie były odbierane powiadomienia push lub gdy przeglądarka wykryje, że użytkownik nie używa aplikacji, która ma uprawnienia do wysyłania powiadomień push. Jednym ze sposobów zapobiegania temu jest ponowne subskrybowanie użytkownika po każdym otrzymanym powiadomieniu, jak pokazano w tym fragmencie kodu. Musisz wysyłać powiadomienia wystarczająco często, aby przeglądarka nie anulowała automatycznie subskrypcji. Należy bardzo dokładnie rozważyć zalety i wady uzasadnionych potrzeb związanych z powiadomieniami w porównaniu z wysyłaniem spamu tylko po to, aby subskrypcja nie wygasła. Nie próbuj zwalczać działań przeglądarki, które mają na celu ochronę użytkownika przed dawno zapomnianymi subskrypcjami powiadomień.

/* In the Service Worker. */

self.addEventListener('push', function(event) {
  console.log('Received a push message', event);

  // Display notification or handle data
  // Example: show a notification
  const title = 'New Notification';
  const body = 'You have new updates!';
  const icon = '/images/icon.png';
  const tag = 'simple-push-demo-notification-tag';

  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag: tag
    })
  );

  // Attempt to resubscribe after receiving a notification
  event.waitUntil(resubscribeToPush());
});

function resubscribeToPush() {
  return self.registration.pushManager.getSubscription()
    .then(function(subscription) {
      if (subscription) {
        return subscription.unsubscribe();
      }
    })
    .then(function() {
      return self.registration.pushManager.subscribe({
        userVisibleOnly: true,
        applicationServerKey: urlBase64ToUint8Array('YOUR_PUBLIC_VAPID_KEY_HERE')
      });
    })
    .then(function(subscription) {
      console.log('Resubscribed to push notifications:', subscription);
      // Optionally, send new subscription details to your server
    })
    .catch(function(error) {
      console.error('Failed to resubscribe:', error);
    });
}

Najczęstsze pytania

Oto kilka najczęstszych pytań, które ludzie zadają w tym momencie:

Czy mogę zmienić usługę przesyłania informacji powiadomień push, której używa przeglądarka?

Nie. Usługa push jest wybierana przez przeglądarkę i jak widać w przypadku wywołania subscribe(), przeglądarka wysyła żądania sieciowe do usługi push, aby pobrać szczegóły, które składają się na PushSubscription.

Każda przeglądarka używa innej usługi Push, czy nie mają one różnych interfejsów API?

Wszystkie usługi powiadomień push będą wymagać tego samego interfejsu API.

Ten wspólny interfejs API nazywa się protokół Web Push i opisuje żądanie sieci, które aplikacja musi wysłać, aby wywołać wiadomość push.

Jeśli użytkownik zarejestruje się na komputerze, czy będzie też subskrybować na telefonie?

Niestety nie. Użytkownik musi zarejestrować się w przypadku każdej przeglądarki, w której chce otrzymywać wiadomości. Warto też pamiętać, że użytkownik będzie musiał przyznać uprawnienia na każdym urządzeniu.

Co dalej

Code labs