- PVSM.RU - https://www.pvsm.ru -

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

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

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

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

Обзор

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

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

Push API и Notifications API

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

Push API

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В более старой его версии оно принимало функцию обратного вызова, теперь оно возвращает промис. Это и является источником проблемы, так как нельзя заранее узнать, какая версия 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() приведёт к показу следующего окна.

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

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

После того, как пользователь отреагирует на запрос разрешения, нажав на кнопку 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-уведомлений знать о том, какой сервер приложения подписал пользователя, и быть уверенным в том, что это — тот же самый сервер, который отправляет уведомления конкретному пользователю.

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

Браузер передаёт 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-уведомлений соответствующему пользователю.

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

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

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

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

Push-сервисы

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

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

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

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

API push-сервисов

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

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

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

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

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

Сервер разработчика веб-приложения, 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": "<String>",
  "icon": "<URL String>",
  "image": "<URL String>",
  "badge": "<URL String>",
  "vibrate": "<Array of Integers>",
  "sound": "<URL String>",
  "dir": "<String of 'auto' | 'ltr' | 'rtl'>",

  "//": "Behavioural Options",
  "tag": "<String>",
  "data": "<Anything>",
  "requireInteraction": "<boolean>",
  "renotify": "<Boolean>",
  "silent": "<Boolean>",

  "//": "Both Visual & Behavioural Options",
  "actions": "<Array of Strings>",

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

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

Итоги

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

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

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

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

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

Автор: ru_vds

Источник [18]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/274514

Ссылки в тексте:

[1] Image: https://habrahabr.ru/company/ruvds/blog/350486/

[2] сервис-воркеры: https://habrahabr.ru/company/ruvds/blog/349858/

[3] Push API: https://developer.mozilla.org/en-US/docs/Web/API/Push_API

[4] Notifications API: https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API

[5] этого API: https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission

[6] сюда: https://web-push-codelab.glitch.me/

[7] Google FСM: https://firebase.google.com/docs/cloud-messaging/

[8] Web Push Protocol: https://tools.ietf.org/html/draft-ietf-webpush-protocol-12

[9] Здесь: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification

[10] SessionStack: https://www.sessionstack.com/

[11] Как работает JS: обзор движка, механизмов времени выполнения, стека вызовов: https://habrahabr.ru/company/ruvds/blog/337042/

[12] Как работает JS: о внутреннем устройстве V8 и оптимизации кода: https://habrahabr.ru/company/ruvds/blog/337460/

[13] Как работает JS: управление памятью, четыре вида утечек памяти и борьба с ними: https://habrahabr.ru/company/ruvds/blog/338150/

[14] Как работает JS: цикл событий, асинхронность и пять способов улучшения кода с помощью async / await: https://habrahabr.ru/company/ruvds/blog/340508/

[15] Как работает JS: WebSocket и HTTP/2+SSE. Что выбрать?: https://habrahabr.ru/company/ruvds/blog/342346/

[16] Как работает JS: особенности и сфера применения WebAssembly: https://habrahabr.ru/company/ruvds/blog/343568/

[17] Как работает JS: веб-воркеры и пять сценариев их использования: https://habrahabr.ru/company/ruvds/blog/348424/

[18] Источник: https://habrahabr.ru/post/350486/?utm_source=habrahabr&utm_medium=rss&utm_campaign=350486