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 wywołać wiadomość push, aplikacja musi wysłać żądanie POST do usługi push zgodnie z protokołem web push. Aby korzystać z wiadomości push we wszystkich przeglądarkach, musisz użyć VAPID (czyli kluczy serwera aplikacji), co wymaga ustawienia nagłówka z wartością potwierdzającą, że Twoja 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ć. Z czasem sytuacja się poprawia, ponieważ rośnie liczba przeglądarek obsługujących tę funkcję, ale wciąż nie jest to łatwe. Z tego powodu zdecydowanie zalecamy użycie biblioteki do obsługi szyfrowania, formatowania i uruchamiania wiadomości push.

Jeśli naprawdę chcesz dowiedzieć się, co robią biblioteki, przeczytaj następną sekcję. 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 innych językach mogą wystąpić pewne różnice, ale nie będą one zbyt duże. Rozważamy Node, ponieważ jest to JavaScript i powinien być najbardziej dostępny dla czytelników.

Wykonaj 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 i wyszukiwanie wartości PushSubscription w bazie danych różni się w zależności od wybranego języka po stronie serwera i bazy danych, 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 naszym przykładzie ma odbiornik żądań dopasowania dla 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 ważna, 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.',
        },
      }),
    );
  });

W tym pokazie do przechowywania subskrypcji używana jest baza danych nedb, czyli prosta baza danych oparta na plikach. Możesz jednak użyć dowolnej bazy danych. Używamy go tylko dlatego, że nie wymaga konfiguracji. W przypadku środowiska produkcyjnego lepiej użyć czegoś bardziej niezawodnego. (zwykle używam starego, dobrego 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 podejściem jest utworzenie strony administracyjnej, która umożliwia skonfigurowanie i wywołanie powiadomienia 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 wersji demonstracyjnej. 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(). Potrzebujemy tego klucza prywatnego na zapleczu.

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

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 poinformować moduł web-push o kluczach 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.

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

W tym filmie do wywołania powiadomień push używany jest fałszywy 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żyć biblioteki web-push, aby wysłać wiadomość do podanej 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 zrealizowana i nie musimy nic robić. Jeśli obietnica zostanie odrzucona, musisz sprawdzić błąd, ponieważ poinformuje Cię on, czy PushSubscription jest nadal ważna.

Aby określić typ błędu usługi push, najlepiej sprawdzić kod stanu. Komunikaty o błędach różnią się w zależności od usługi przesyłania powiadomień 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 jeden z tych komunikatów, oznacza to, że subskrypcja wygasła lub nie jest już ważna. 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 przejściu przez wszystkie subskrypcje 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

Code labs