Subskrybowanie użytkownika

Najpierw musimy uzyskać od użytkownika zgodę 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.

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ź zasób serviceWorker w narzędziu navigator.
  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;
}

Liczba obsługiwanych przeglądarek jest coraz większa w przypadku mechanizmów Service Worker i komunikatów push, jednak zawsze dobrym pomysłem jest stosowanie wykrywania w obu tych usługach i stopniowo ulepszane.

Rejestrowanie skryptu service worker

Dzięki funkcji wykrywania wiemy, że obsługiwane są zarówno mechanizmy Service Worker, jak i Push. Następnym krokiem jest „zarejestrowanie” skryptu service worker.

Gdy rejestrujemy usługę wtyczki, przekazujemy przeglądarce informacje o tym, gdzie znajduje się plik usługi wtyczki. Plik jest nadal tylko w języku JavaScript, ale przeglądarka „daje mu dostęp” do interfejsów API usługi wtyczki, w tym do interfejsu push. Dokładniej rzecz ujmując, przeglądarka uruchamia plik w środowisku instancji roboczej 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ę znajduje. W tym przypadku plik service worker znajduje się pod adresem /service-worker.js. Po wywołaniu funkcji register() przeglądarka wykona te czynności:

  1. Pobierz plik usługi roboczej.

  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 żądanie, dokładnie sprawdź kod JavaScript pod kątem literówek lub błędów w Narzędziach deweloperskich w Chrome.

Po rozwiązaniu problemu register() zwraca wartość 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 zgody użytkownika na wysyłanie mu powiadomień push.

Interfejs API służący do uzyskiwania uprawnień jest stosunkowo prosty, ale ma tę wadę, że niedawno zmienił sposób działania z wywołania zwrotnego na zwracanie obietnicy. Problem w tym, że nie wiemy, która wersja API jest zaimplementowana w bieżącej przeglądarce, więc musisz zaimplementować obie wersje i obsługiwać oba.

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ładzie kodu obietnica zwracana przez askPermission() jest rozwiązywana, jeśli pozwolenie zostało przyznane. W przeciwnym razie obietnica jest odrzucana.

Jedną z skrajnych przypadków, z którymi musisz się zastanowić, jest sytuacja, w której 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 o nie prosi.

W późniejszym czasie przyjrzymy się, jak o takie pozwolenie proszą niektóre popularne witryny.

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 do przeglądarek dodano tryb push, nie było pewności, czy programiści powinni być w stanie wysyłać wiadomości push, a nie wyświetlać powiadomień. 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 nie ma możliwości wyświetlenia cichego powiadomienia push).

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

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

Obecnie wygląda na to, że dyskretne przekazywanie push nigdy nie zostanie wdrożone w Chrome. 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 o „kluczach serwera aplikacji”. „Klucze serwera aplikacji” są używane przez usługę push do identyfikowania aplikacji subskrybującej użytkownika i zapewniania, że ta sama aplikacja przesyła do użytkownika wiadomości.

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. 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 punkt końcowy do przeglądarki.
  3. Przeglądarka doda ten punkt końcowy do PushSubscription, który jest zwracany za pomocą subscribe().

Ilustracja publicznego klucza serwera aplikacji jest używana 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

Z technicznego punktu widzenia atrybut applicationServerKey jest 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. Za każdym razem, gdy czytasz coś na temat „kluczy serwera aplikacji” lub „kluczy VAPID”, pamiętaj, że chodzi o to samo.

Tworzenie kluczy serwera aplikacji

Publiczny i prywatny zestaw kluczy serwera aplikacji możesz utworzyć na stronie web-push-codelab.glitch.me. Możesz też wygenerować klucze za pomocą wiersza poleceń web-push:

    $ 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 subscription()

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 odpowiedzi otrzymujemy obietnicę, która przekształca się w funkcję PushSubscription, co powoduje powstanie kodu podobnego do tego:

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 wysyłania wiadomości push do tego użytkownika. 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ług 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 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 dokładnie rozważyć zalety i wady uzasadnionego wysyłania powiadomień w porównaniu z niechcący wysyłaniem spamu tylko po to, aby subskrypcja nie wygasła. Podsumowując, nie próbuj walczyć z przeglądarką, aby chronić użytkownika przed 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 już subskrypcję push, chcesz ją wysłać na swój 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 danym PushSubscription na naszym serwerze możemy wysyłać użytkownikom wiadomości, kiedy tylko chcemy.

Regularne odnawianie subskrypcji, aby zapobiec wygaśnięciu

Gdy subskrybujesz powiadomienia push, często otrzymujesz PushSubscription.expirationTime w wysokości 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 uważnie 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 często zadawanych pytań:

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 korzysta z 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 popularny interfejs API nosi nazwę Web Push Protocol i opisuje żądanie sieciowe, które musi wysłać aplikacja, aby aktywować komunikat push.

Czy jeśli subskrybuję użytkownika na komputerze, czy on też ma subskrypcję na telefonie?

Nie. Użytkownik musi zarejestrować się w przypadku każdej przeglądarki, w której chce otrzymywać wiadomości. Warto też pamiętać, że będzie to wymagać udzielenia pozwolenia na każdym z urządzeń.

Co dalej

Codelabs