[Перевод] Как работает JS: веб push-уведомления

Сегодня публикуем перевод девятой части серии статей, посвящённых применению веб-технологий и JavaScript. В этом материале мы исследуем веб push-уведомления. А именно, поговорим о механизмах, лежащих в их основе, и о том, как осуществляется подписка на уведомления, как устроены процессы их отправки и получения.

gucqlsy7zd2youxyaif05yrkodk.jpeg

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

Обзор


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

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

Push API и Notifications API


То, что мы называем тут «веб push-уведомлениями», на самом деле, представлено двумя технологиями. Это — Push API, которое используется, когда сервер передаёт сообщение сервис-воркеру, и Notifications API, которое применяется, когда сервис-воркер, или скрипт в самом веб-приложении, намеревается показать пользователю уведомление.

Push API


Для реализации механизма push-уведомлений нужно выполнить следующие три шага:

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


Рассмотрим всё это подробнее.

Проверка возможностей браузера


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

  • Проверка наличия serviceWorker в объекте navigator.
  • Проверка наличия PushManager в объекте window.


Вот как это выглядит:

if (!('serviceWorker' in navigator)) { 
  // Браузер не поддерживает сервис-воркеры. 
  return; 
}

if (!('PushManager' in window)) { 
  // Браузер не поддерживает push-уведомления.
  return; 
}


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

Регистрация сервис-воркера


Если после проверки браузера оказалось, что он поддерживает то, что нам нужно, можно переходить к регистрации сервис-воркера. О том, как это сделать, мы уже говорили.

Получение разрешения пользователя


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

API для получения разрешений устроено сравнительно просто. Единственное, на что тут надо обратить внимание, заключается в том, что сейчас применяются две версии этого API.

В более старой его версии оно принимало функцию обратного вызова, теперь оно возвращает промис. Это и является источником проблемы, так как нельзя заранее узнать, какая версия API реализована в текущем браузере. Поэтому нужно поддерживать оба этих подхода.

Выглядит это так:

function requestPermission() {
  return new Promise(function(resolve, reject) {
    const permissionResult = Notification.requestPermission(function(result) {
      // Поддержка устаревшей версии с функцией обратного вызова.
      resolve(result);
    });

    if (permissionResult) {
      permissionResult.then(resolve, reject);
    }
  })
  .then(function(permissionResult) {
    if (permissionResult !== 'granted') {
      throw new Error('Permission not granted.');
    }
  });
}


Вызов Notification.requestPermission() приведёт к показу следующего окна.

5feab17631572ce86088d266b60f60c7.png


Запрос разрешения на показ уведомлений

После того, как пользователь отреагирует на запрос разрешения, нажав на кнопку Allow (Разрешить), Block (Блокировать), или закрыв окно, мы получим результат в виде строки, в которой, в соответствии с выбором пользователя, будет содержаться ‘granted’, ‘denied’, или ‘default’.

Помните о том, что если пользователь нажмёт на кнопку Block, ваше приложение не сможет больше запрашивать у него разрешение на показ уведомлений до тех пор, пока он самостоятельно не «разблокирует» его. Для того чтобы это сделать, пользователю придётся покопаться в настройках браузера.

Подписка пользователя с помощью PushManager


После того, как мы убедились в возможности регистрации сервис-воркера и получили разрешение пользователя на показ уведомлений, можно оформить подписку, вызвав, во время регистрации сервис-воркера, метод registration.pushManager.subscribe().

Вот как всё это, включая регистрацию сервис-воркера, выглядит:

function subscribeUserToPush() {
  return navigator.serviceWorker.register('service-worker.js')
  .then(function(registration) {
    var subscribeOptions = {
      userVisibleOnly: true,
      applicationServerKey: btoa(
        'BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U'
      )
    };

    return registration.pushManager.subscribe(subscribeOptions);
  })
  .then(function(pushSubscription) {
    console.log('PushSubscription: ', JSON.stringify(pushSubscription));
    return pushSubscription;
  });
}


Метод registration.pushManager.subscribe(options) принимает объект options, который содержит ряд параметров, некоторые из которых необязательны:

  • userVisibleOnly. Это логическое значение указывает на то, что сформированная подписка будет использована лишь для показа сообщений. Этот параметр должен быть установлен в true, в противном случае мы столкнёмся с ошибкой (у этого есть исторические причины).
  • applicationServerKey. Это — DOMString в кодировке Base64, или объект ArrayBuffer, который содержит открытый ключ, который push-сервер будет использовать для аутентификации сервера приложения.


Сервер веб-приложения должен сгенерировать пару уникальных для сервера ключей приложения (их ещё называют VAPID-ключами). В эту пару входят открытый и закрытый ключи. Закрытый ключ сервер хранит в тайне, а открытый передаёт клиенту. Ключи позволяют сервису push-уведомлений знать о том, какой сервер приложения подписал пользователя, и быть уверенным в том, что это — тот же самый сервер, который отправляет уведомления конкретному пользователю.

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

Браузер передаёт applicationServerKey (открытый ключ) push-сервису в ходе оформления подписки. Это означает, что push-сервис сможет связать открытый ключ приложения с подпиской, PushSubscription.

Вот что здесь происходит:

  • Веб-приложение загружается и вызывает subscribe(), передавая серверный ключ.
  • Браузер выполняет сетевой запрос к push-сервису, который сгенерирует адрес точки входа в собственное API, свяжет этот адрес с ключом и вернёт сведения о нём браузеру.
  • Браузер добавит эти сведения к объекту PushSubscription, который возвращается через промис subscribe().


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

Объект PushSubscription


Объект PushSubscription содержит всю информацию, необходимую для отправки push-уведомлений на устройство пользователя. Вот как он выглядит:

{
  "endpoint": "https://domain.pushservice.com/some-id",
  "keys": {
    "p256dh":
"BIPUL12DLfytvTajnryr3PJdAgXS3HGMlLqndGcJGabyhHheJYlNGCeXl1dn18gSJ1WArAPIxr4gK0_dQds4yiI=",
    "auth":"FPssMOQPmLmXWmdSTdbKVw=="
  }
}


Свойство endpoint представляет собой URL сервиса push-уведомлений, точку входа в API. Для того, чтобы отправить уведомление, надо выполнить POST-запрос по этому URL.

Объект keys содержит сведения, используемые для шифрования данных сообщения, отправляемых в push-уведомлении.

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

027066ea5cf39260b2a4a77532fb5657.png


Получение разрешений, формирование объекта PushSubscription и отправка его на сервер

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


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

Push-сервисы


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

Обратите внимание на то, что push-сервисы — это сторонние службы, которые вы, как разработчик веб-приложения, не контролируете. Ваши серверы — это те серверы, которые взаимодействуют с push-сервисами через API. В качестве примера push-сервиса можно привести Google FСM.

Push-сервис берёт на себя выполнение множества сложных задач. Например, если браузер не в сети, push-сервис поставит сообщения в очередь и будет ждать, перед отправкой сообщения, до тех пор, пока браузер не окажется доступным.

Каждый браузер может использовать любой push-сервис, разработчик веб-приложения не влияет на выбор push-сервиса. Все push-сервисы, однако, имеют одинаковые API, поэтому разнообразие таких сервисов не создаёт проблем с реализацией механизмов push-уведомлений. Для того, чтобы получить URL, который будет обрабатывать запросы на отправку ваших push-сообщений, нужно обратиться к сохранённому ранее значению параметра endpoint объекта PushSubscription.

API push-сервисов


API push-сервисов предоставляет инструменты для отправки сообщений пользователям. Оно представлено протоколом Web Push Protocol, который является стандартом IETF, определяющим порядок работы с push-сервисами.

Данные, которые отправляют в push-сообщении, должны быть зашифрованы. Так разработчик не даёт push-сервисам просматривать данные, отправляемые пользователям. Это важно, так как именно браузер решает, какой push-сервис использовать (это могут быть push-сервисы, которые недостаточно безопасны).

Для каждого push-сообщения задаются следующие свойства:

  • TTL — определяет срок, который недоставленное push-уведомление может провести в очереди до его удаления.
  • Priority — задаёт приоритет сообщения, что позволяет push-сервису отправлять только высокоприоритетные сообщения в том случае, если нужно экономить заряд батареи устройства пользователя.
  • Topic — назначает push-уведомлению имя темы, что приведёт к замене ожидающих доставки сообщений с той же темой. В результате, как только устройство пользователя окажется активным, пользователь получит актуальное сообщение.


719ab11ecd90ed104e5912b2318fa459.png


Сервер разработчика веб-приложения, push-сервер, и браузер, в который поступает сообщение

Push-события в браузере


Как только сообщение будет отправлено push-сервису, оно будет пребывать в состоянии ожидания до тех пор, пока не произойдёт одно из следующих событий:

  • Устройство вышло в сеть.
  • Срок хранения сообщения, заданный с помощью параметра TTL, истёк.


Когда push-сервис доставляет сообщение, браузер получает его, расшифровывает, и вызывает событие push в зарегистрированном ранее сервис-воркере.

Самое интересное здесь то, что браузер может вызывать сервис-воркер даже в том случае, если соответствующая ему веб-страница не открыта. Тут происходит следующее:

  • Push-уведомление приходит в браузер, который его расшифровывает.
  • Браузер активирует сервис-воркер.
  • Событие push передаётся сервис-воркеру.


Код, который используется для настройки обработчика события push, выглядит так же, как код любого другого обработчика событий, написанного на JavaScript:

self.addEventListener('push', function(event) {
  if (event.data) {
    console.log('This push event has data: ', event.data.text());
  } else {
    console.log('This push event has no data.');
  }
});


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

Конструкция сервис-воркера event.waitUntil(promise) сообщает браузеру о том, что, до разрешения промиса, сервис-воркер занят обработкой уведомления, и браузеру не следует завершать работу сервис-воркера до завершения этой работы.

Вот пример кода для обработки события push:

self.addEventListener('push', function(event) {
  var promise = self.registration.showNotification('Push notification!');

  event.waitUntil(promise);
});


Вызов self.registration.showNotification() приводит к выводу уведомления, которое может увидеть пользователь, этот вызов возвращает промис, который будет разрешён как только уведомление будет показано.

Метод showNotification(title, options) позволяет настроить внешний вид уведомления в соответствии с нуждами разработчика. Так, параметр title — это строка, а параметр options — это объект примерно следующего содержания:

{
  "//": "Visual Options",
  "body": "",
  "icon": "",
  "image": "",
  "badge": "",
  "vibrate": "",
  "sound": "",
  "dir": "",

  "//": "Behavioural Options",
  "tag": "",
  "data": "",
  "requireInteraction": "",
  "renotify": "",
  "silent": "",

  "//": "Both Visual & Behavioural Options",
  "actions": "",

  "//": "Information Option. No visual affect.",
  "timestamp": ""
}


Здесь можно почитать подробности о настройке внешнего вида уведомлений.

Итоги


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

Автор этого материала говорит, что в его компании, SessionStack, планируют использовать push-уведомления для того, чтобы сообщать пользователям о сбоях, проблемах или аномалиях в их проектах. Это позволит им мгновенно узнавать о внештатных ситуациях и принимать соответствующие меры.

Предыдущие части цикла статей:

Часть 1: Как работает JS: обзор движка, механизмов времени выполнения, стека вызовов
Часть 2: Как работает JS: о внутреннем устройстве V8 и оптимизации кода
Часть 3: Как работает JS: управление памятью, четыре вида утечек памяти и борьба с ними
Часть 4: Как работает JS: цикл событий, асинхронность и пять способов улучшения кода с помощью async / await
Часть 5: Как работает JS: WebSocket и HTTP/2+SSE. Что выбрать?
Часть 6: Как работает JS: особенности и сфера применения WebAssembly
Часть 7: Как работает JS: веб-воркеры и пять сценариев их использования
Часть 8: Как работает JS: сервис-воркеры

Уважаемые читатели! Пользуетесь ли вы push-уведомлениями в своих проектах?

1ba550d25e8846ce8805de564da6aa63.png

© Habrahabr.ru