Wysyłanie wiadomości za pomocą bibliotek Web Push

Jednym z problemów związanych z wiadomościami web push jest to, że ich wysyłanie jest bardzo trudne. Aby aktywować komunikat push, aplikacja musi wysłać żądanie POST do usługi push, korzystając z protokołu web push. Aby korzystać z przekazywania danych we wszystkich przeglądarkach, musisz użyć VAPID (inaczej kluczy serwera aplikacji), co w zasadzie wymaga ustawienia nagłówka z wartością potwierdzającą, że aplikacja może wysyłać wiadomości do użytkownika. Aby wysłać dane za pomocą wiadomości push, należy je zaszyfrować i dodać określone nagłówki, aby przeglądarka mogła prawidłowo odszyfrować wiadomość.

Głównym problemem z wysyłaniem powiadomień push jest to, że w razie wystąpienia problemu trudno go zdiagnozować. Poprawia się to wraz z upływem czasu i większą obsługą przeglądarek, ale nie jest to już łatwe. Z tego powodu zdecydowanie zalecamy użycie biblioteki do obsługi szyfrowania, formatowania i uruchamiania wiadomości push.

Jeśli naprawdę chcesz wiedzieć, czym zajmują się biblioteki, omówimy to w następnej sekcji. Na razie zajmiemy się zarządzaniem subskrypcjami i używaniem istniejącej biblioteki powiadomień push w WWW do wysyłania żądań powiadomień push.

W tej sekcji użyjemy biblioteki Node.js do przesyłania danych przez sieć. w pozostałych językach będą się nieco różnić, ale nie będą tak bardzo podobne. Rozważamy Node, ponieważ jest to JavaScript i powinien być najbardziej dostępny dla czytelników.

Wykonujemy te czynności:

  1. Prześlij subskrypcję do naszego backendu i zapisz ją.
  2. Pobieraj zapisane subskrypcje i uruchamiaj wiadomości push.

Zapisywanie subskrypcji

Zapisywanie poleceń PushSubscription i wysyłanie do nich zapytań w bazie danych będzie się różnić w zależności od języka i bazy danych po stronie serwera, ale warto zobaczyć przykład tego, jak to zrobić.

Na stronie demonstracyjnej PushSubscription jest wysyłany do naszego backendu za pomocą prostego żądania POST:

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 Express w naszej prezentacji ma detektor żądań pasujący do punktu końcowego /api/save-subscription/:

app.post('/api/save-subscription/', function (req, res) {

W tym przypadku weryfikujemy subskrypcję, aby mieć pewność, że żądanie jest prawidłowe i nie zawiera śmieci:

const isValidSaveRequest = (req, res) => {
  // Check the request body has at least an endpoint.
  if (!req.body || !req.body.endpoint) {
    // Not a valid subscription.
    res.status(400);
    res.setHeader('Content-Type', 'application/json');
    res.send(
      JSON.stringify({
        error: {
          id: 'no-endpoint',
          message: 'Subscription must have an endpoint.',
        },
      }),
    );
    return false;
  }
  return true;
};

Jeśli subskrypcja jest prawidłowa, musimy ją zapisać i zwrócić odpowiednią odpowiedź JSON:

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

Ta wersja demonstracyjna wykorzystuje do przechowywania subskrypcji nedb. Jest to prosta baza danych oparta na plikach, ale możesz użyć dowolnej innej bazy danych. Używamy tego narzędzia tylko dlatego, że nie wymaga żadnej konfiguracji. W środowisku produkcyjnym warto zastosować coś bardziej niezawodnego. (Zwykle korzystam ze starej, porządnej MySQL).

function saveSubscriptionToDatabase(subscription) {
  return new Promise(function (resolve, reject) {
    db.insert(subscription, function (err, newDoc) {
      if (err) {
        reject(err);
        return;
      }

      resolve(newDoc._id);
    });
  });
}

Wysyłanie powiadomień push

W przypadku wysyłania powiadomień push potrzebujemy jakiegoś zdarzenia, które spowoduje proces wysyłania wiadomości do użytkowników. Typowym sposobem jest utworzenie strony administratora, która umożliwia konfigurowanie i aktywowanie wiadomości push. Możesz jednak utworzyć program do uruchamiania lokalnie lub zastosować inne podejście, które umożliwia dostęp do listy PushSubscriptioni uruchomienie kodu w celu wywołania wiadomości push.

Nasza wersja demonstracyjna zawiera stronę „jak dla administratora”, która umożliwia wywołanie powiadomienia push. Ponieważ jest to wersja demonstracyjna, jest to strona publiczna.

Przeprowadzę Cię przez wszystkie kroki potrzebne do uruchomienia demonstracji. Będą to kroki dla początkujących, więc każdy, nawet osoby, które dopiero zaczynają przygodę z Node, będą mogły je wykonać.

W sekcji dotyczącej subskrypcji użytkownika omawialiśmy dodanie opcji applicationServerKey do opcji subscribe(). Ten klucz prywatny będzie potrzebny w backendzie.

W tym przykładzie te wartości są dodawane do aplikacji Node w ten sposób (nudny kod, ale chcemy, abyś wiedział, że nie ma tu żadnej magii):

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

Następnie musimy zainstalować moduł web-push na serwerze Node:

npm install web-push --save

Następnie w naszym skrypcie Node potrzebujemy modułu web-push:

const webpush = require('web-push');

Możemy teraz zacząć używać modułu web-push. Najpierw musimy podać modułowi web-push klucze serwera aplikacji. (Pamiętaj, że są one też nazywane kluczami VAPID, ponieważ tak brzmi nazwa specyfikacji)

const vapidKeys = {
  publicKey:
    'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U',
  privateKey: 'UUxI4O8-FbRouAevSmBQ6o18hgE4nSG3qwvJTfKc-ls',
};

webpush.setVapidDetails(
  'mailto:web-push-book@gauntface.com',
  vapidKeys.publicKey,
  vapidKeys.privateKey,
);

Zwróć uwagę, że dodaliśmy też ciąg „mailto:”. Ten ciąg znaków musi być adresem URL lub adresem e-mail w formacie „mailto”. Te informacje zostaną wysłane do usługi powiadomień push w ramach żądania wywołania powiadomienia push. Dzięki temu, jeśli usługa powiadomień web push będzie musiała skontaktować się z nadawcą, będzie mieć do tego odpowiednie informacje.

Moduł web-push jest już gotowy do użycia. Następnym krokiem jest wywołanie wiadomości push.

W tym filmie do wywołania powiadomień push używany jest fikcyjny panel administracyjny.

Zrzut ekranu strony administracyjnej

Kliknięcie przycisku „Wyślij wiadomość push” spowoduje wysłanie żądania POST do adresu /api/trigger-push-msg/, co jest sygnałem dla naszego backendu, aby wysłać wiadomości push. Dlatego tworzymy trasę w Express dla tego punktu końcowego:

app.post('/api/trigger-push-msg/', function (req, res) {

Gdy otrzymamy to żądanie, pobieramy subskrypcje z bazy danych i wywołujemy powiadomienie push dla każdej z nich.

return getSubscriptionsFromDatabase().then(function (subscriptions) {
  let promiseChain = Promise.resolve();

  for (let i = 0; i < subscriptions.length; i++) {
    const subscription = subscriptions[i];
    promiseChain = promiseChain.then(() => {
      return triggerPushMsg(subscription, dataToSend);
    });
  }

  return promiseChain;
});

Funkcja triggerPushMsg() może następnie używać biblioteki web-push do wysyłania wiadomości do udostępnionej subskrypcji.

const triggerPushMsg = function (subscription, dataToSend) {
  return webpush.sendNotification(subscription, dataToSend).catch((err) => {
    if (err.statusCode === 404 || err.statusCode === 410) {
      console.log('Subscription has expired or is no longer valid: ', err);
      return deleteSubscriptionFromDatabase(subscription._id);
    } else {
      throw err;
    }
  });
};

Wywołanie funkcji webpush.sendNotification() zwróci obietnicę. Jeśli wiadomość została wysłana, obietnica zostanie rozwiązana i nie musimy nic robić. Jeśli obietnica zostanie odrzucona, sprawdź błąd, ponieważ zweryfikujemy wtedy, czy pole PushSubscription jest nadal prawidłowe.

Aby określić typ błędu z usługi przesyłania, najlepiej sprawdzić kod stanu. Komunikaty o błędach różnią się w zależności od usługi push, a niektóre z nich są bardziej przydatne niż inne.

W tym przykładzie sprawdza kody stanu 404410, które są kodami stanu HTTP oznaczającymi „Nie znaleziono” i „Brak”. Jeśli otrzymamy któryś z nich, oznacza to, że subskrypcja wygasła lub straciła ważność. W takich sytuacjach musimy usunąć subskrypcje z naszej bazy danych.

W przypadku innych błędów po prostu throw err, co spowoduje, że obietnica zwrócona przez triggerPushMsg() zostanie odrzucona.

W następnej sekcji omówimy niektóre inne kody stanu, gdy przyjrzymy się bliżej protokołowi web push.

Po pętli subskrypcji musimy zwrócić odpowiedź JSON.

.then(() => {
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-send-messages',
    message: `We were unable to send messages to all subscriptions : ` +
        `'${err.message}'`
    }
}));
});

Omówiliśmy główne kroki wdrażania:

  1. Utwórz interfejs API do wysyłania subskrypcji ze strony internetowej do naszego backendu, aby można było zapisać je w bazie danych.
  2. Utwórz interfejs API, który będzie uruchamiać wysyłanie powiadomień push (w tym przypadku interfejs API wywoływany z udawanym panelem administracyjnym).
  3. Pobierz wszystkie subskrypcje z naszego backendu i wyślij wiadomość do każdej subskrypcji za pomocą jednej z bibliotek web-push.

Bez względu na backend (Node, PHP, Python itp.) etapy implementacji powiadomień push będą takie same.

Co dokładnie robią dla nas biblioteki web-push?

Co dalej

Codelabs