[Перевод] Логирование активности с использованием Web Beacon API

orqbjuhannguixlf_fyehlsv0zy.jpeg


Beacon API — это основанный на JavaScript интерфейс для:

отправки небольшого количества данных на сервер с браузера, без ожидания ответа. В этой статье, мы рассмотрим в каких случаях будет полезен Beacon API, чем он отличается от использования XMLHTTPRequest (Ajax) для тех же целей и как его использовать.


Для чего нам очередной API?

Beacon API используется для отправки небольших по объему данных на сервер без ожидания ответа. Последняя часть утверждения является наиболее интересной. Beacon API разработан специально для того, что бы можно было отправить данные и забыть о них. Не нужно ожидать ответ, так как его и не будет.

Метафора с открытками, это такие карточки которые люди посылают/посылали друг другу. Как правило, на них писали небольшой по объему текст («Ты где? А я на море лол.», «Тут у меня шикарная погода, не то что у тебя в офисе»), кидали в почту и забывали. Никто не ожидал ответа по типу «Я уже выехал за тобой», «У меня в офисе чудесно».

Существует множество случаев, когда подход «отправил и забыл» будет уместен.


Отслеживание статистики и Аналитическая информация

Это первое, что приходит на ум. Такие большие решения как Google Analytics могут предоставлять хороший обзор на базовые вещи. Но если мы хотим, что-то более кастомизированное? Нам необходимо написать немного кода для отслеживания того, что происходит на странице (как пользователи взаимодействуют с компонентами, как далеко они скролят, какие страницы были отображены до первой продажи), затем отправить эти данные на сервер когда пользователь покидает страницу. Beacon идеально подходит для решения такой задачи, так как мы просто отправляем данные, и нам ненужен ответ от сервера.


Дебаг и Логирование

Другое применение это логирования информации из JavaScript кода. Представьте себе ситуацию когда у вас большое приложение с богатым UI/UX. Все тесты зеленые, а на проде периодически всплывает ошибка о которой вы знаете, но не можете продебажить ее из за не хватки информации. В данном случае вы можете ипользовать Beacon для диагностики.

На самом деле любая задача с логированиям может быть решешина с использованием Beacon. Это может быть создание точек сохранения в играх, сбор информации об использоании нового функционала, запись результатов тестирования и так далее. Если это, что-то, что происходит в браузере и вы хотите, что бы сервер знал об этом, Beacon это, то, что нужно.


Разве мы не делали этого ранее?

Я знаю о чем вы думаете. Ничто из этого не ново? Мы общаемся с севером посредством XMLHTTPRequest уже более 10 лет. Недавно мы начали использовать Fetch API, что по факту делает то же самое, просто с новым Promise интерфейсом. Так зачем нам еще один Beacon API?

Ключевая особенность в том, что нам не нужен ответ от сервера. Браузер может поставить в очередь запрос и отправить данные не блокируя выполнение какого либо кода. Так как в это впрягается браузер, для нас не важно выполняется ли еще код или нет, браузер просто будет себе тихонько отправлять запросы в фоне.

C Beacon API не нужно дожидаться лучшего момента для CPU, сети. Просто добавить в очередь запрос с помощью beacon практически нечего не стоит.

Для того, что бы понять почему это важно, достаточно взглянуть на то, как и где обычно используются подобная логика. Например для того, что бы измерить как долго пользователь находится на странице, нам необходимо отправить запрос на сервер как можно ближе к концу сессии.

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

Вы же понимаете насколько HTTP запросы медленные? И последнее, что вы хотите, так это впихивать HTTP запрос между переходами.


Пробуем Beacon API

Базовый пример использования очень прост:

let result = navigator.sendBeacon(url, data);

result булевое значение. Если браузер добавил запрос в очередь — true, если нет false.


Использование navigator.sendBeacon ()

navigator.sendBeacon принимает два параметра. Первый это URL на который будет послан запрос, второй это данные которые необходимо отправить. Запрос имеет вид HTTP POST.

data — этот параметр может принимать несколько форматов данных, все те с которыми работает Fetch API. Это может быть Blob, BufferSource, FormData или URLSearchParams и тд.

Мне нравится использовать FormData для простых key-value данных, это не сложный и простой в использовании класс.

// URL куда отправить данные
let url = '/api/my-endpoint';

// Создание нового FormData
let data = new FormData();
data.append('hello', 'world');

let result = navigator.sendBeacon(url, data);

if (result) { 
  console.log('Добавлено в очередь!');
} else {
  console.log('Ошибка.');
}


Поддержка браузерами

Поддержка этого API вполне себе солидная. Единственный браузер который не поддерживает, это Internet Explorer (не ожидал я такого) и Opera Mini. Но в Edge все работает. В большинстве случаев поддержка есть, но лучше на всякий случай проверить:

if (navigator.sendBeacon) {
  // Beacon код
} else {
  // Использовать XHR?
}


Пример: логируем время проведенное на странице

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

Так как нас интересует только время проведенное на странице, а не настоящее время, мы можем использовать performance.now() для получения базового timestamp при загрузке страницы:

let startTime = performance.now();

Давайте обернем небольшой кусочек логики в удобную в использовании функцию:

let logVisit = function() {
  // Test that we have support
  if (!navigator.sendBeacon) return true;

  // URL to send the data to, e.g.
  let url = '/api/log-visit';

  // Data to send
  let data = new FormData();
  data.append('start', startTime);
  data.append('end', performance.now());
  data.append('url', document.URL);

  // Let's go!
  navigator.sendBeacon(url, data);
};

И наконец нам надо вызвать эту функцию когда пользователь покидает страницу. Первая мысль была использовать unload, но Safari на Mac, похоже блокирует запрос по соображениям безопасности. По этому лучше использовать beforeunload:

window.addEventListener('beforeunload', logVisit);

Когда страница выгружается (или перед этим), наша функция logVisit() будет вызвана и если браузер поддерживает Beacon API, отправит запрос на сервер.


Пару моментов

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


GDPR

Просто помните о нем.


DNT: DO NOT TRACK

В дополнение, браузеры имеют опцию которая позволяет пользователям обозначить, что они не хотят, что бы их активность отслеживалась. Do Not Track отправляет HTTP хедер, который выглядит так:

DNT: 1

Если вы отслеживаете данные которые могут индицировать пользователя и в хедерах запросов есть DNT: 1, то лучше послушать пользователя и не сохранять какие-либо данные. Например использую PHP это можно проверить следующим образом:

if (!empty($_SERVER['HTTP_DNT'])) { 
  // Не хочу, не надо
}


В заключение

Beacon API действительно очень удобный способ для отправки данных на сервер, особенно в контексте логирования. Поддержка браузерами на достаточно хорошем уровне и позволяет вам легко логировать любую информацию без каких либо негативных последсвий для производительности и отзывчивости вашего UI. Non-blocking природа этих запросов играет в этом очень хорошую роль, это горазно быстрей альтернатив XHR и Fetch.

od9-sglbgmr5sjagemivcierh0k.png

© Habrahabr.ru