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

Jednym z problemów, które mogą być utrudnione podczas korzystania z web push, jest to, że wywoływanie komunikatów push jest bardzo trudne. Aby aktywować komunikat push, aplikacja musi wysłać żądanie POST do usługi push zgodnie z protokołem web push. Aby korzystać z funkcji push we wszystkich przeglądarkach, musisz użyć VAPID (inaczej kluczy serwera aplikacji), co zasadniczo wymaga ustawienia nagłówka z wartością potwierdzającą, że aplikacja może wysyłać wiadomości do użytkownika. Aby można było wysłać dane w trybie push, trzeba je zaszyfrować i dodać odpowiednie nagłówki, by przeglądarka mogła je prawidłowo odszyfrować.

Głównym problemem związanym ze stosowaniem push jest to, że gdy napotkasz problem, trudno go zdiagnozować. Wraz z czasem i większą obsługą przeglądarek się to poprawia, ale nie jest to łatwe. Z tego powodu zdecydowanie zalecamy korzystanie z biblioteki do szyfrowania, formatowania i uruchamiania wiadomości push.

Jeśli naprawdę chcecie dowiedzieć się, czym zajmują się biblioteki, omówimy je w następnej sekcji. Na razie przyjrzymy się zarządzaniu subskrypcjami i wykorzystywaniu istniejącej biblioteki Web push do wysyłania żądań push.

W tej sekcji będziemy korzystać z biblioteki węzłów web-push. Inne języki będą się różnić, ale nie będą zbyt podobne. Przyglądamy się Node, ponieważ jest to kod JavaScript, który powinien być najbardziej dostępny dla czytelników.

Wykonamy te kroki:

  1. Wyślij subskrypcję do naszego backendu i zapisz ją.
  2. Odzyskaj zapisane subskrypcje i aktywuj wiadomość push.

Zapisuję subskrypcje

Zapisywanie zapytań PushSubscription i wykonywanie na nich zapytań z poziomu bazy danych będzie się różnić w zależności od języka i bazy danych po stronie serwera, ale możesz zobaczyć przykład, jak to zrobić.

Na stronie demonstracyjnej żądanie PushSubscription jest wysyłane 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 wersji demonstracyjnej ma odbiornik żądania pasującego do punktu końcowego /api/save-subscription/:

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

W ten sposób weryfikujemy subskrypcję, by upewnić się, że żądanie jest prawidłowe i nie zawiera błędów:

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

W tej wersji demonstracyjnej do przechowywania subskrypcji służy nb. Jest to prosta baza danych oparta na plikach, ale możesz użyć dowolnej wybranej bazy danych. Używamy jej tylko ze względu na brak konieczności konfiguracji. W środowisku produkcyjnym należy używać czegoś bardziej niezawodnego. (często zachowuję dobrą wersję 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 wiadomości push

Gdy chodzi o wysyłanie wiadomości push, potrzebujemy zdarzenia, które aktywuje proces wysyłania wiadomości do użytkowników. Typowym podejściem jest utworzenie strony administratora, na której możesz skonfigurować i aktywować wiadomość push. Możesz jednak utworzyć program uruchamiany lokalnie lub dowolny inny sposób umożliwiający dostęp do listy komponentów PushSubscription i uruchamianie kodu w celu aktywowania komunikatu push.

Nasza wersja demonstracyjna zawiera stronę z informacjami o polubieniach administratora, która umożliwia wywołanie push. To tylko wersja demonstracyjna, to strona publiczna.

Przejdę po kolei przez każdy etap, żeby uruchomić wersję demonstracyjną. To proste zadanie dla wszystkich, także tych, którzy dopiero zaczynają korzystać z Node.

Podczas rozmowy z użytkownikiem omówiliśmy dodanie applicationServerKey do opcji subscribe(). Klucz prywatny będzie nam potrzebny.

W wersji demonstracyjnej wartości te są dodawane do aplikacji Node w ten sposób (znam nudny kod, ale chcę Cię poinformować, że to nie jest magia):

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

Następnie musimy zainstalować moduł web-push dla naszego serwera węzła:

npm install web-push --save

Następnie w skrypcie węzła wymagamy modułu web-push w ten sposób:

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

Teraz możemy zacząć korzystać z modułu web-push. Najpierw musimy poinformować moduł web-push o naszych kluczach serwera aplikacji. (Pamiętaj, że są one również nazywane kluczami VAPID, ponieważ taka jest nazwa specyfikacji).

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

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

Pamiętaj, że dołączono też ciąg „mailto:”. Musi to być adres URL lub adres e-mail mailto. Te informacje zostaną w rzeczywistości wysłane do usługi Web push w ramach żądania aktywującego push. Dzieje się tak, ponieważ jeśli usługa web push musi skontaktować się z nadawcą, ma informacje, które mu w tym pomogą.

Moduł web-push jest gotowy do użycia. Następnym krokiem jest aktywowanie komunikatu push.

Wersja demonstracyjna aktywuje wiadomości push za pomocą panelu administracyjnego udawania.

Zrzut ekranu strony administratora.

Kliknięcie przycisku „Aktywuj wiadomość push” spowoduje wysłanie żądania POST do /api/trigger-push-msg/, co jest sygnałem dla naszego backendu, że należy wysłać komunikaty push. Dlatego tworzymy trasę ekspresową dla tego punktu końcowego:

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

Po otrzymaniu takiego żądania pobieramy subskrypcje z bazy danych i w przypadku każdej z nich uruchamiamy komunikat push.

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 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 webpush.sendNotification() zwróci obietnicę. Jeśli wiadomość została wysłana, obietnica zostanie rozwiązana i nie będzie trzeba nic robić. Jeśli obietnica zostanie odrzucona, sprawdź błąd, ponieważ pokaże Ci on, czy PushSubscription jest nadal prawidłowy.

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 push, a niektóre z nich są bardziej pomocne niż inne.

W tym przykładzie sprawdza ona kody stanu 404 i 410, które są kodami stanu HTTP „Nie znaleziono” i „Brak”. Otrzymanie takiego identyfikatora oznacza, że subskrypcja wygasła lub straciła ważność. W takich sytuacjach musimy usunąć subskrypcje z naszej bazy danych.

W razie wystąpienia innego błędu po prostu throw err, co sprawi, że obietnica zwrócona przez użytkownika triggerPushMsg() zostanie odrzucona.

Pozostałe kody stanu omówimy w następnej sekcji, gdy bardziej szczegółowo zajmiemy się protokołem web push.

Po zapoznaniu się z subskrypcjami 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 etapy wdrażania:

  1. Utwórz interfejs API, aby wysyłać subskrypcje z naszej strony internetowej do naszego backendu, aby mógł je zapisać w bazie danych.
  2. Utwórz interfejs API aktywujący wysyłanie komunikatów push (w tym przypadku interfejs API wywoływany z udawanego panelu administracyjnego).
  3. Pobierz wszystkie subskrypcje z naszego backendu i wyślij wiadomość do każdej subskrypcji za pomocą jednej z bibliotek internetowych.

Niezależnie od backendu (Node, PHP, Python, ...) kroki wdrażania push będą takie same.

Następnie, do czego dokładnie służą te biblioteki web-push?

Co dalej

Laboratoria kodu