Реализуем пуш-уведомления на фронтенде и бэкенде
В предыдущей статье об iss-observer.com я обещал рассказать о технической стороне реализации пуш-уведомлений. Изначально я планировал сконцентрироваться на проблемах, с которыми пришлось иметь дело в процессе работы над iss-observer.com. Теперь я думаю, будет полезнее посвятить материал базовым вопросам, и уточнять детали, где это необходимо. Обращаю ваше внимание, что фронтенд частично опирается на этот урок.
Если вы хотите углубиться в исходный код, то смотрите в GitHub-репозиторий и на пример, демонстрирующий его работу.
Фронтенд
Начнем с клиентской части. Первое, что нужно сделать — убедиться, что браузер поддерживает пуш-уведомления. Если да, загружаем наш JavaScript.
Прежде чем писать код, выполним ряд требований. Нам потребуются Application Server Keys (VAPID Key). Получаем их здесь, либо с помощью библиотеки web-push. Кстати, она нам еще потребуется для бэкенд-части. Устанавливаем библиотеку: npm install -g web-push , генерируем ключи: web-push generate-vapid-keys . В независимости от способа в результате у вас должны быть закрытый ключ (private key) и открытый ключ (public key). Сохраните их в надежном месте.
До появления спецификации Application server key/VAPID первые браузеры (Chrome, Opera) реализовали функциональность пуш-уведомлений с помощью Google Cloud Messaging. Все современные браузеры, за исключением Samsung Internet, поддерживают VAPID. Поэтому в этой статье я не буду касаться GCM. Как добавить поддержку для Samsung Internet и старых версий Chrome и Opera — читайте здесь.
Посмотрим на push.js. Здесь мы регистрируем сервис-воркер и подписываемся на уведомления:
В первую очередь создаем константу addServiceKey : ей присваиваем значение с открытым ключ VAPID (о нем мы говорили выше). Также создаем несколько элементов и переменных. Записываем функцию urlB64ToUint8Array() : она понадобится для конвертации ключа из base64 в Uint8Array.
Затем декларируем функцию updatePushButton() . Мы будем вызывать её каждый раз при изменении статуса уведомлений, чтобы обновить отвечающие за него элементы интерфейса.
Далее видим функцию регистрации подписки subscribeUser() . Как вы наверно заметили, в начале скрипта была объявлена переменная let serviceWorkerRegistration . Она содержит результат регистрации сервис-воркера: посмотрите в конец файла, мы записываем его в переменную в момент регистрации.
Метод subscribe() возвращает промис, а в качестве аргумента принимает объект с двумя свойствами:
- userVisibilityOnly : булево значение. Параметр сообщает, что подписка будет использоваться только для сообщений, эффект которых виден для пользователя. Устанавливаем значение true .
- applicationServiceKey : номер открытого ключа, он используется сервером для отправки уведомлений. Так как ключ должен быть в формате UInt8Attay, используем уже знакомую нам функцию.
Теперь отправляем данные подписки на сервер. Смело используем Fetch API, так как все браузеры, которые поддерживают пуш-уведомления, поддерживают и Fetch. После получения ответа выводим результат — завершена ли подписка успешно.
Этот этап прекрасно подходит для уточнения нужных параметров у пользователя. Например, на iss-observer.com он может выбрать время получения уведомлений (утро и/или вечер). Я также отправляю на сервер данные о стране, регионе и городе, по которому пользователь хочет получать уведомления; данные сохраняются и могут потребоваться при отправке уведомлений.
После этого, создаем функцию отписки unsubscribeUser() . Используем метод getSubscription() объекта PushManager , с его помощью получаем детали подписки, которые отправляем на сервер (снова Fetch API). На этот раз, чтобы удалить её базы.
Записываем функцию initPush() . В ней — событие для кнопки pushButton, которое вызывает функцию подписки или отписки в зависимости от текущего состояния. После, не забываем обновить это состояние. Использованный в примере код можно найти на GitHub.
Последний шаг — регистрация сервис-воркера.
Сервис-воркер
В этой части мы разберем два необходимых для реализации пуш-уведомлений события: push и notificationonclick .
Начнем с push . Проверяем содержимое объекта notificationData (свойства title , body и icon ), и, если не находим их, присваиваем дефолтные значения. После вызываем метод showNotification , он покажет уведомление пользователю.
В дополнение к трём перечисленным свойствам могут использоваться и другие, например, badge , tag , vibrate . На момент написания этой статьи (февраль 2017) многие из них поддерживались только некоторыми браузерами. title , body и icon , доступных во всех браузерах, поэтому ограничимся ими.
Событие notificationOnClick срабатывает в момент клика по уведомлению. Сначала закрываем уведомление. Затем проверяем, открыт ли наш сайт в текущей вкладке браузера, если нет, то открываем его с помощью openWindow() .
Бэкенд
Переходим к серверной части, в которой мы используем web-push library. В нашем случае это реализация библиотеки для Node.js, но версии для PHP, Java и C# также доступны.
Я предполагаю, что у вас есть базовые знания Node.js и опыт использования Express. В ином случае, я рекомендую вам ознакомиться с ними прежде чем продолжить.
Итак, в первую очередь:
- устанавливаем библиотеку командой npm install web-push --save ,
- получаем доступ к ней с помощью require : const webPush = require('web-push');
Теперь передаем данные VAPID. Помимо сгенерированной в начале пары ключей, нужно указать адрес электронной почты (с префиксом mailto: ) либо URL сайта. Контактные данные могут потребоваться сервису для связи с вами. Обратите внимание на комментарии: я предпочел сохранить ключи в переменную окружения. Вы можете поступить так же или выбрать свой метод, но главное помните, закрытый ключ должен быть всегда защищен от обращений извне. Собственно поэтому он так и назван.
Переходим к функции подписки:
В функции отправки подписки на сервер, получаем доступ к объекту subscription . В нем — значение endpoint и ключи доступа. Здесь я не буду останавливаться на вопросах работы с базой данных. Для примера укажу только, что для демо использована Mongo DB.
Прим. переводчика: endpoint — это уникальный URI, создаваемый для каждого пользователя индивидуально в соответствии с паттерном: p256dh — открытый ключ, auth — закрытый ключ.
Затем получаем наше первое уведомление — то, которое приветствует подписавшегося пользователя. Метод sendNotification() принимает три аргумента:
- данные подписки, получаемые от браузера;
- информацию для пользователя (заголовок, сообщение, иконка — свойства title , body , icon соответственно);
- объект options , см. подробнее.
TTL (Time To Live) — срок жизни уведомления —по умолчанию четыре недели. Это значит, что оно будет ожидать появления пользователя онлайн в течение этого срока. Например, если вы отправили уведомление пользователю в оффлайне, и он подключится к сети только через две недели, сообщение все равно будет доставлено. В моем случае разумно изменить TTL на более короткий срок.
Если пользователь отменяет подписку, удаляем информацию о ней из базы.
Дополнительно
Вероятно, каждый из вас столкнется со своим случаем применения пуш-уведомлений, и просто скопировать код не получится. Однако я надеюсь, что этот урок поможет вам реализовать искомую функциональность как на стороне клиента, так и на стороне сервера.
Демо проекта, исходный код опубликован на GitHub.
Для более глубокого погружения в тему рекомендую бесплатную книгу Web Push Book и примеры на servicewore.rs.
Если вам есть что спросить, или есть что добавить, пишите в Twitter или по электронной почте.