Отправка сообщений с помощью веб-push-библиотек

Одна из болевых точек при работе с веб-push заключается в том, что запуск push-сообщения чрезвычайно «удобен». Чтобы вызвать push-сообщение, приложению необходимо отправить POST-запрос к push-сервису в соответствии с протоколом web-push . Чтобы использовать push во всех браузерах, вам необходимо использовать VAPID (также известный как ключи сервера приложений), который по сути требует установки заголовка со значением, доказывающим, что ваше приложение может отправлять сообщения пользователю. Чтобы отправить данные с помощью push-сообщения, данные необходимо зашифровать и добавить определенные заголовки, чтобы браузер мог правильно расшифровать сообщение.

Основная проблема с запуском push-уведомлений заключается в том, что если вы столкнетесь с проблемой, ее будет сложно диагностировать. Со временем ситуация улучшается и расширяется поддержка браузеров, но это далеко не просто. По этой причине я настоятельно рекомендую использовать библиотеку для шифрования, форматирования и запуска вашего push-сообщения.

Если вы действительно хотите узнать, что делают библиотеки, мы рассмотрим это в следующем разделе. Сейчас мы рассмотрим управление подписками и использование существующей веб-библиотеки push-уведомлений для отправки push-запросов.

В этом разделе мы будем использовать библиотеку Web-Push Node . Другие языки будут иметь различия, но они не будут слишком непохожими. Мы рассматриваем Node, поскольку это JavaScript и он должен быть наиболее доступным для читателей.

Мы пройдем следующие шаги:

  1. Отправьте подписку на наш бэкэнд и сохраните ее.
  2. Получите сохраненные подписки и инициируйте push-сообщение.

Сохранение подписок

Сохранение и запрос PushSubscription из базы данных будет зависеть от языка вашего сервера и выбора базы данных, но может быть полезно увидеть пример того, как это можно сделать.

На демонстрационной веб-странице PushSubscription отправляется на наш сервер путем выполнения простого запроса 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.');
      }
    });
}

Сервер Express в нашей демонстрации имеет соответствующий прослушиватель запросов для конечной точки /api/save-subscription/ :

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

В этом маршруте мы проверяем подписку, чтобы убедиться, что запрос в порядке и не содержит мусора:

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;
};

Если подписка действительна, нам нужно сохранить ее и вернуть соответствующий ответ 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.',
        },
      }),
    );
  });

В этой демонстрации для хранения подписок используется nedb. Это простая файловая база данных, но вы можете использовать любую базу данных по вашему выбору. Мы используем это только потому, что оно не требует никакой настройки. Для производства хотелось бы использовать что-то более надежное. (Я склонен придерживаться старого доброго MySQL.)

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

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

Отправка push-сообщений

Когда дело доходит до отправки push-сообщения, нам в конечном итоге нужно какое-то событие, которое инициирует процесс отправки сообщения пользователям. Распространенный подход — создание страницы администратора, на которой можно настроить и запустить push-сообщение. Но вы можете создать программу для локального запуска или любой другой подход, который позволит получить доступ к списку PushSubscription и запустить код для запуска push-сообщения.

В нашей демо-версии есть страница «Мне нравится администратору», которая позволяет вам инициировать отправку push-уведомлений. Поскольку это всего лишь демо-версия, это общедоступная страница.

Я собираюсь рассмотреть каждый шаг, необходимый для того, чтобы демо-версия заработала. Это будут детские шаги, чтобы каждый мог следовать им, включая тех, кто впервые работает с Node.

Когда мы обсуждали подписку пользователя, мы рассмотрели добавление applicationServerKey к опциям subscribe() . Этот закрытый ключ нам понадобится на серверной стороне.

В демо-версии эти значения добавляются в наше приложение Node следующим образом (скучный код, который я знаю, но хочу, чтобы вы знали, что никакого волшебства здесь нет):

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

Далее нам нужно установить модуль web-push для нашего Node-сервера:

npm install web-push --save

Затем в нашем Node-скрипте нам нужен модуль web-push например:

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

Теперь мы можем начать использовать модуль web-push . Сначала нам нужно сообщить модулю web-push о ключах нашего сервера приложений. (Помните, что они также известны как ключи VAPID, потому что это название спецификации.)

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

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

Обратите внимание, что мы также включили строку «mailto:». Эта строка должна быть либо URL-адресом, либо адресом электронной почты mailto. Эта часть информации фактически будет отправлена ​​в службу веб-push как часть запроса на запуск push-уведомлений. Причина, по которой это делается, заключается в том, что, если службе веб-пуш необходимо связаться с отправителем, у них есть некоторая информация, которая позволит им это сделать.

После этого модуль web-push готов к использованию, следующим шагом будет запуск push-сообщения.

В демо-версии используется воображаемая панель администратора для запуска push-сообщений.

Скриншот страницы администратора.

Нажатие кнопки «Trigger Push Message» приведет к отправке POST-запроса к /api/trigger-push-msg/ , который является сигналом для нашего бэкэнда отправлять push-сообщения, поэтому мы создаем экспресс-маршрут для этой конечной точки:

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

Когда этот запрос получен, мы извлекаем подписки из базы данных и для каждой из них запускаем 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;
});

Функция triggerPushMsg() может затем использовать библиотеку веб-push для отправки сообщения в предоставленную подписку.

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;
    }
  });
};

Вызов webpush.sendNotification() вернет обещание. Если сообщение было отправлено успешно, обещание будет разрешено, и нам ничего не нужно делать. Если обещание отклоняется, вам необходимо изучить ошибку, поскольку она сообщит вам, действительна ли PushSubscription или нет.

Чтобы определить тип ошибки службы push-уведомлений, лучше всего посмотреть код состояния. Сообщения об ошибках различаются в зависимости от службы push-уведомлений, и некоторые из них более полезны, чем другие.

В этом примере он проверяет коды состояния 404 и 410 , которые являются кодами состояния HTTP для «Не найден» и «Ушел». Если мы получим одно из них, это означает, что срок действия подписки истек или она больше не действительна. В этих сценариях нам необходимо удалить подписки из нашей базы данных.

В случае какой-либо другой ошибки мы просто throw err , что приведет к отклонению обещания, возвращаемого triggerPushMsg() .

Мы рассмотрим некоторые другие коды состояния в следующем разделе, когда будем более подробно рассматривать протокол web push.

После прохождения подписок нам нужно вернуть ответ в формате 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}'`
    }
}));
});

Мы рассмотрели основные этапы реализации:

  1. Создайте API для отправки подписок с нашей веб-страницы на наш сервер, чтобы он мог сохранять их в базе данных.
  2. Создайте API для запуска отправки push-сообщений (в данном случае API, вызываемый из воображаемой панели администратора).
  3. Получите все подписки из нашего бэкэнда и отправьте сообщение каждой подписке с помощью одной из библиотек веб-push .

Независимо от вашего бэкэнда (Node, PHP, Python и т. д.), шаги по реализации push будут одинаковыми.

Далее, что именно делают для нас эти веб-push-библиотеки?

Куда идти дальше

Лаборатории кода