Логування активності з використанням Web Beacon API
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.