Heartbeat API
Heartbeat API – це легкий спосіб періодично (кожні 15-120 секунд) опитувати сервер щодо нових даних і потім використовувати їх на стороні клієнта (браузера).
Heartbeat API був доданий в WordPress 3.6 і спочатку був потрібен для двох речей:
- Попереджати користувача про те, що пост редагується кимось іншим на даний момент.
- Перевіряти, чи не закінчилася сесія авторизації та запуск popup-вікна з проханням авторизуватися повторно.
Згодом цим механізмом стали користуватися теми та плагіни.
Heartbeat API за замовчуванням працює тільки в адмін-панелі, але його можна легко використовувати і у фронті.
Heartbeat API працює на основі класичного AJAX в WordPress .
Принцип роботи
Після завантаження сторінки спеціальний JavaScript код (heartbeat код) запускає таймер. Через рівні проміжки часу спрацьовує JS подія heartbeat-send і запускаються підчеплені цієї події JS функції.
JS функції формують та відсилають дані у файл admin-ajax.php . У цьому файлі спрацьовує WP хук:
- wp_ajax_heartbeat – якщо авторизовано
- wp_ajax_nopriv_heartbeat – якщо не авторизовано
На обидва ці хуки за замовчуванням підчеплені однойменні функції: wp_ajax_heartbeat() та wp_ajax_nopriv_heartbeat() . Таким чином, при хартбіті запиті в PHP спрацьовує одна з функцій:
- wp_ajax_heartbeat() – якщо авторизовано
- wp_ajax_nopriv_heartbeat() – якщо не авторизовано
Далі функція викликає три хуки, через які по суті і потрібно працювати з Хартбіт:
Якщо авторизовано:
// якщо є $_POST['data'] $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id ); // спрацьовує завжди $response = apply_filters( 'heartbeat_send', $response, $screen_id); // спрацьовує завжди do_action( 'heartbeat_tick', $response, $screen_id);
Якщо не авторизовано:
// якщо є $_POST['data'] $response = apply_filters( 'heartbeat_nopriv_received', $response, $data, $screen_id ); // спрацьовує завжди $response = apply_filters( 'heartbeat_nopriv_send', $response, $screen_id); // спрацьовує завжди do_action( 'heartbeat_nopriv_tick', $response, $screen_id);
$screen_id дорівнюватиме
front
для фронтенда.Після спрацьовування всіх вищеописаних хуків PHP повертає назад користувачу (в браузер) дані (змінна $response ) у форматі JSON.
При отриманні даних у JS спрацьовує подію heartbeat-tick .
На основі надісланих даних проводиться та чи інша робота: пропозиція авторизуватися знову, повідомлення від клієнта менеджеру, повідомлення з чату Telegram, повідомлення про нові коментарі тощо.
Використання Heartbeat API
Heartbeat API за замовчуванням доступний лише в адмінці. Щоб використовувати цей механізм у фронті (лицьової частини), треба підключити скрипт heartbeat.js або вказати його в залежностях у своєму скрипті.
// 1 варіант. Підключаємо JS-скрипт heartbeat wp_enqueue_script( 'heartbeat'); //2 Варіант. Підключаємо heartbeat, як залежність у нашому скрипті wp_enqueue_script( 'script-name', get_template_directory_uri() .'/js/example.js', array('heartbeat'), '1.0', true );
Далі для використання Heartbeat API потрібно пройти три етапи. Не важливо, чи робиться це для фронту чи адмінки.
1. Надсилання даних на сервер
JS подія heartbeat-send
спрацьовує перед відправкою даних на сервер і це найвдаліший момент додати свої дані до колекції даних Heartbeat.
У дефолтній збірці WordPress у вкладці браузера можна побачити при запитанні ajax щось подібне:
interval: 60 _nonce: bab7ce80c5 action: heartbeat screen_id: dashboard has_focus: false
Додамо до цієї колекції свої дані:
За правилами гарного тону JS код треба розміщувати у файлі та підключати на хуку admin_print_footer_scripts
<?php // Підключимо наш скрипт в адмінці add_action( 'admin_print_footer_scripts', function () { ?> <script> jQuery(document).on( 'heartbeat-send', function (event, data) { // Додаємо свої дані до колекції даних Heartbeat. data.myplugin_field = 'some_data'; }); </script> <?php } );
У вкладці браузера можна переглянути, як дані відправилися:
data[myplugin_field]: some_data interval: 60 _nonce: bab7ce80c5 action: heartbeat screen_id: dashboard has_focus: false
У прикладі було відправлено просто рядок some_data , але можна відправляти будь-які дані: числа, масиви, об’єкти і т.д.
2. Прийом даних на сервері та відповідь
Насамперед давайте подивимося, що за умовчанням повертає сервер:
{ "wp-auth-check":true, "server_time":1520878390 }
На хуку heartbeat_received можна доповнити масив, які формують ядро/плагіни/тема своїми даними:
// Фільтр для роботи з надісланими Heartbeat даними. add_filter( 'heartbeat_received', 'myplugin_receive_heartbeat', 10, 2); /** * Приймає Heartbeat дані та формує відповідь. * * @param array $response Дані Heartbeat для відправлення у фронтенд. * @param array $data Дані, надіслані з фронтенду (unslashed). * * @return array */ function myplugin_receive_heartbeat( $response, $data ) { // Якщо наші дані прийшли, то повертаємо оригінал відповіді. if ( empty( $data['myplugin_field'] ) ) { return $response; } /** * Звертаємось до наших даних. * Ключ масиву збігається з властивістю об'єкта в JS, який ми помістили дані. */ $received_data = $data['myplugin_field']; // Для прикладу порахуємо скільки у переданому рядку символів. $count_symbol = mb_strlen( $received_data ); $response['myplugin_strlen'] = 'Кількість знаків у переданій фразі'. $count_symbol; // Повернемо доповнений масив Heartbeat нашими даними. return $response; }
Тепер сервер поверне наступний набір даних:
{ "myplugin_strlen":"Кількість знаків у переданій фразі 9" "wp-auth-check":true, "server_time":1520878390 }
3. Обробка відповіді сервера
Після того, як сервер повернув об’єкт із даними, обробимо їх:
<?php add_action( 'admin_print_footer_scripts', function () { ?> <script> jQuery( document ).on( 'heartbeat-tick', function ( event, data, textStatus, jqXHR ) { // event – об'єкт події // data - дані, що надходять // textStatus - статус виконання запиту, наприклад success. // jqXHR – об'єкт запиту console.log(event, data, textStatus, jqXHR); // Перевіримо, чи є наші дані і якщо ні – зупинимо скрипт. if ( ! data.myplugin_strlen ) { return; } // Виведемо дані на екран через alert() alert(data.myplugin_strlen); }); </script> <?php } );
Ми розглянули три етапи, але не кожне завдання потребує першого або третього етапу.
Під капотом
PHP
PHP функціонал зосереджений у файлі ajax-actions.php .
Функція wp_ajax_heartbeat()
Цю функцію не потрібно використовувати, вона автоматично викликається у файлі admin-ajax.php через ajax хуки. Вона перевіряє запит і викликає хуки пов’язані з хартбіт: heartbeat_send , heartbeat_tick .
Тобто функція обробляє Heartbeat запити від зареєстрованих користувачів. Перш ніж обробити запит, перевіряє наявність nonce коду. Якщо його немає – перериває роботу та повертає помилку. Якщо термін дії nonce коду минув – повертає інформацію про це і на сторінці з’являється спливаюче вікно з проханням авторизуватися. Також у відповідь додає час сервера функцією time() .
Свій функціонал можна додати через фільтри та події. Див. нижче.
Фільтр heartbeat_received
Фільтрує отримані дані з heartbeat. Спрацьовує, якщо є дані у $data
.
-
$response
(масив) - Дані для відповіді Heartbeat.
-
$data
(масив) -
Надіслані дані в масиві
$_POST . -
$screen_id
(рядок) -
ID екрану. У php збігається з
$current_screen->id
і c глобальним
pagenow
у JS.
приклад
add_filter( 'heartbeat_received', 'myplugin_receive_heartbeat', 10, 3); function myplugin_receive_heartbeat( $response, $data, $screen_id ) { // Якщо наші дані прийшли, то повертаємо оригінал відповіді. if ( empty( $data['myplugin_field'] ) ) { return $response; } /** * Звертаємось до наших даних. * Ключ масиву збігається з міткою (handle) у JS, яка була присвоєна даним. */ $received_data = $data['myplugin_field']; // Нехай приходить рядок і для прикладу порахуємо скільки у цьому рядку символів. $count_symbol = mb_strlen( $received_data ); $response['myplugin_field_strlen'] = 'Кількість знаків у переданій фразі' . $count_symbol; // Повернемо доповнений масив Heartbeat нашими даними. return $response; }
Фільтр heartbeat_send
Фільтрує дані відповіді Heartbeat.
-
$response
(масив) - Дані для відповіді Heartbeat.
-
$screen_id
(рядок) - ID екрану.
Приклад із ядра WordPress
У відповідь heartbeat додається інформація про статус авторизації користувача.
add_filter( 'heartbeat_send', 'wp_auth_check'); function wp_auth_check( $response ) { $response['wp-auth-check'] = is_user_logged_in() && empty( $GLOBALS['login_grace_period'] ); return $response; }
Простий приклад :
Додамо у відповідь heartbeat інформацію про кількість неперевірених коментарів.
add_filter( 'heartbeat_send', 'myplugin_send_heartbeat', 10, 2); function myplugin_send_heartbeat( $response, $screen_id ) { $comments_count = wp_count_comments(); $response['moderated'] = $comments_count->moderated; return $response; }
Heartbeat_tick _
Теж що і heartbeat_send , тільки ця подія, і в ньому не треба нічого змінювати, а прийнято робити щось перед відповіддю сервера браузеру. Тобто. це чудове місце для реалізації будь-яких дій дій, наприклад, відправлення листа.
Спрацьовує після формування heartbeat даних, але перед тим, як сервер віддасть їх клієнту.
-
$response
(масив) - Дані для відповіді Heartbeat.
-
$screen_id
(рядок) - ID екрану.
приклад
Якщо прийшли якісь певні дані, сповістити адміністратора листом.
add_action( 'heartbeat_tick', 'myplugin_heartbeat_tick', 10, 2); function myplugin_heartbeat_tick( $response, $screen_id ) { if( ! empty($response['some_key']) ){ wp_mail('[email protected]', 'Тема листа', 'Зміст листа'); } }
Фільтр heartbeat_settings
Дозволяє змінити опції в JS об’єкті wp.heartbeat.settings
.
// Змінить дефолтний інтервал опитування сервера із 60 секунд на 30. add_filter( 'heartbeat_settings', function ( $ settings ) { $settings['mainInterval'] = 30; return $settings; } );
Хуки та функція wp_ajax_nopriv_heartbeat()
Для взаємодії з неавторизованими користувачами у фронті замість функції wp_ajax_heartbeat() спрацьовує функція wp_ajax_nopriv_heartbeat() . Вона відрізняє від першої лише тим, що не перевіряє код і статус авторизації, а також до її складу входять хуки з іншими іменами, але аналогічною поведінкою:
JavaScript та jQuery
JavaScript функціонал зосереджений у файлі heartbeat.js .
Heartbeat -send
На цій події можна додати свої дані до колекції heartbeat перед відправкою їх на сервер.
jQuery( document ).on( 'heartbeat-send', function ( event, data ) { // Додаємо дані в Heartbeat. Потім на сервері дані можна взяти з масиву за ключом myplugin_customfield. data.myplugin_customfield = 'some_data'; });
Heartbeat -tick
Спрацьовує щоразу, коли від сервера приходять heartbeat дані. На цій події оперуємо із надісланими даними.
jQuery(document).on('heartbeat-tick', function (event, data, textStatus, jqXHR) { // event – об'єкт події // data - дані, що надходять // textStatus - статус виконання запиту, наприклад success. // jqXHR – об'єкт запиту console.log(event, data, textStatus, jqXHR); // Перевіряємо, чи надішли дані від нашого плагіна/теми. if (!data.data.myplugin_responce_data) { return; } alert('Ці дані повернулися від мого плагіна/теми: + data.myplugin_responce_data); });
Також можна відстежити подію для потрібної відповіді за міткою даних:
$(document).on( 'heartbeat-tick.myplugin_responce_data', function( event, data, textStatus, jqXHR ) { // Код });
Heartbeat -error
Спрацьовує щоразу, коли запит до сервера був провалений, тобто спрацювало $.ajax().fail() . На цій події можна відстежити, яка саме сталася помилка і зробити щось за потребою.
jQuery( document ).on( 'heartbeat-error', function ( jqXHR, textStatus, error ) { // jqXHR – об'єкт запиту. // textStatus – статус виконання запиту. // error - текстовий варіант помилки (може бути abort, timeout, error, parsererror, empty, unknown). console.log(jqXHR, textStatus, error); if( 'timeout' === error ){ alert('Пройшло 30 секунд, а сервер так і не відповів. І це сумно!'); } });
Інші події
Heartbeat API має у складі ще кілька подій, але вони використовуються при розробці дуже рідко:
- heartbeat-connection-lost
- heartbeat-connection-restored
- heartbeat-nonces-expired
JavaScript об’єкт wp.heartbeat
Робота з подіями не єдиний спосіб взаємодіяти з Heartbeat API. Справа в тому, що серцем механізму є клас Heartbeat()
, екземпляр якого поміщений у змінну window.wp.heartbeat
та доступний для розробників при написанні JavaScript коду.
Розглянемо методи цього.
Метод enqueue()
Додає дані в чергу для надсилання наступного XHR. Оскільки дані надсилаються асинхронно, ця функція не повертає відповідь XHR. Побачити відповідь можна на події heartbeat-tick
. Якщо один і той самий мітка використовується кілька разів, дані не перезаписуються, коли третій аргумент noOverwrite
має значення true
. Використовуйте wp.heartbeat.isQueued(‘handle’) , щоб побачити, чи були дані вже поставлені в чергу для цієї позначки.
wp.heartbeat.enqueue(handle, data, noOverwrite);
-
handle
(рядок) - Унікальна мітка (дескриптор) даних, що відправляються. На бекенд використовується як ключ масиву для отримання переданих значень.
-
data
(будь-які) - Надані дані.
-
noOverwrite
(логічний) - Чи перезаписувати існуючі дані у черзі.
Повертає true якщо дані були поставлені в чергу і false якщо немає.
// Додамо у чергу дані для відправки. wp.heartbeat.enqueue( 'my-plugin-data', { 'param1': 'param value 1', 'param2': 'param value 2', }, false );
Метод getQueuedItem()
Повертає дані, що стоять у черзі, за їх міткою (Handle).
wp.heartbeat.getQueuedItem(handle);
-
handle
(рядок) - Унікальна мітка (дескриптор) даних, що відправляються.
// Додаємо дані в чергу wp.heartbeat.enqueue( 'my-plugin', { 'param1': 'value 1', 'param2': 'value 2', }, false ); // Отримаємо дані var myData = wp.heartbeat.getQueuedItem('my-plugin') console.log(myData); /* повернеться { 'param1': 'value 1', 'param2': 'value 2', } /
Метод isQueued()
Перевіряє, чи є дані з певною міткою у черзі.
wp.heartbeat.isQueued(handle);
-
handle
(рядок) - Унікальна мітка (дескриптор) даних, що відправляються.
Повертає true якщо дані є в черзі і false якщо немає.
// У консолі відобразиться false console.log(wp.heartbeat.isQueued('my-plugin')); // Додаємо дані в чергу wp.heartbeat.enqueue( 'my-plugin', { 'param1': 'value 1', 'param2': 'value 2', }, false ); // У консолі відобразиться true console.log(wp.heartbeat.isQueued('my-plugin')); // true
Метод dequeue()
Видаляє з черги дані щодо їх мітки (handle).
wp.heartbeat.dequeue(handle);
- handle (рядок)
- Унікальна мітка (дескриптор) даних, що відправляються.
// Додаємо дані в чергу wp.heartbeat.enqueue( 'my-plugin', { 'param1': 'value 1', 'param2': 'value 2', }, false ); // У консолі відобразиться true, дані 'my-plugin' у черзі є console.log(wp.heartbeat.isQueued('my-plugin')); // true // Видалимо дані з черги wp.heartbeat.dequeue('my-plugin'); // У консолі відобразиться false, дані 'my-plugin' у черзі відсутні console.log(wp.heartbeat.isQueued('my-plugin')); // false
Метод interval()
Встановлює або повертає інтервал опитування сервера за секунди.
wp.heartbeat.interval( speed, ticks );
-
speed
(рядок/число) -
Швидкість опитування за секунди. Може бути ‘fast’ або 5, 15, 30, 60, 120, ‘long-polling’ (експериментально). Якщо вікно не у фокусі, інтервал уповільнюється до 2 хвилин. Якщо кількість секунд передається як число, тип повинен бути саме числом, а не рядком.
Типово: 60 -
ticks
(число) -
Цей аргумент застосовується, якщо аргумент
speed
дорівнює
‘fast’ або
5 . Дозволяє вказати скільки разів надсилати запити з цим інтервалом. Можна вказати не більше 30 секунд, тобто максимум кожні 5 секунд сервер опитуватиметься протягом 2 хвилин 30 секунд. Після цього speed повертається до значення за промовчанням, тобто 60 секунд.
Типово: 30
// У консолі відобразиться 60 (значення за замовчуванням) console.log(wp.heartbeat.interval()); // 60 wp.heartbeat.interval(30); console.log(wp.heartbeat.interval()); //> 30
Приклад передачі змінної як число:
var interval = mytheme.custom_interval; // змінна містить 30 wp.heartbeat.interval( parseInt( interval ) ); console.log(wp.heartbeat.interval()); // 30
Метод hasFocus()
Перевіряє чи має вікно (або будь-який локальний iframe у ньому) фокус чи активний користувач. Повертає true чи false. Звертається до властивості settings.hasFocus . Працює на основі нативного document.hasFocus() (якщо доступний та оновлює settings.hasFocus за таймером кожні 10 секунд). Якщо потрібно перевіряти фокус частіше ніж кожні 10 секунд, використовуйте document.hasFocus() .
wp.heartbeat.hasFocus();
Нижче код кожні 5 секунд перевірятиме, чи активна вкладка у браузері, де використовується heartbeat, і виводити у консоль браузера відповідне повідомлення.
setInterval(function() { if (wp.heartbeat.hasFocus()) { console.log('Додаток у фокусі.'); } else { console.log('Фокус втрачений. Ви згорнули браузер, переключися на іншу вкладку або програму.'); } }, 5000);
Метод disableSuspend()
Вимикає припинення. Слід використовувати лише в тому випадку, коли heartbeat виконує такі важливі завдання, як автозбереження, пост-блокування тощо. Використання цього функціоналу на багатьох екранах може призвести до перевантаження облікового запису хостингу користувача, якщо кілька вікон/вкладок браузера залишаються відкритими протягом тривалого часу.
wp.heartbeat.disableSuspend();
Метод connectNow()
Відразу відправляє heartbeat запит незалежно від стану hasFocus
. Не відкриватиме два одночасних з’єднання. Якщо підключення виконується, буде підключено знову після завершення поточного підключення. Особливо зручний при тестуванні heartbeat функціоналу, тому що можна зробити запит одразу, в тому числі і з консолі браузера.
wp.heartbeat.connectNow();
Метод hasConnectionError()
Перевіряє, чи є помилка підключення.
if (wp.heartbeat.hasConnectionError()) { console.log('Останній запит було завершено з помилкою.'); } else { console.log('Помилок немає'); }
Приклади
Немає нічого кращого, ніж приклади у вигляді реальних плагінів, які завжди можна знайти у репозиторії WordPress .
Спостереження за коментарями в адмінці
Даний плагін, як приклад, при heartbeat запиті збирає дані про коментарі, що очікують модерації, і оновлює лічильники в сайдбарі та адмінбарі. Спрацьовує як у адмінці, так і у фронті. І лише у тих користувачів, які мають права модерувати коментарі.
Складається із двох файлів.
admin-comment-notice.php
add_action( 'wp_enqueue_scripts', 'acn_enqueue_scripts' ); add_action( 'admin_enqueue_scripts', 'acn_enqueue_scripts' ); add_filter( 'heartbeat_send', 'acn_heartbeat_send'); /** * Додає дані в heartbeat відповідь. * * @param array $response * * @return array */ function acn_heartbeat_send( $response ) { if ( ! current_user_can( 'moderate_comments' ) ) { return $response; } $count = wp_count_comments(); $count = absint( $count->moderated ); $ i18n = number_format_i18n ($ count); // Адмін-сайдбар $menu = '<span class="awaiting-mod count-' . $count . '"><span class="pending-count">' . $i18n. '</span></span>'; $menu = sprintf( __( 'Comments %s' ), $menu ); // Адмін-бар $text = sprintf( _n( '%s comment awaiting moderation', '%s comments awaiting moderation', $count ), $i18n ); $bar = '<span class="ab-icon"></span>'; $bar .= '<span class="ab-label awaiting-mod pending-count count-' . $count . '" aria-hidden="true">' . $i18n. '</span>'; $bar .= '<span class="screen-reader-text">' . $text. '</span>'; //Дані $response['acn'] = array( 'menu' => $menu, 'bar' => $bar, 'count' => $i18n, ); return $response; } /** * Підключає скрипт плагіна. */ function acn_enqueue_scripts() { if ( is_admin_bar_showing() && current_user_can( 'moderate_comments' ) ) { $script_url = plugins_url( 'scripts.js', __FILE__ ); wp_enqueue_script( 'acn-script', $script_url, array( 'heartbeat' ) ); } }
scripts.js
jQuery(document).on('heartbeat-tick', function (event, data) { // Перевіримо, чи є наші дані і якщо ні – зупинимо скрипт. if (data.acn === undefined) { return; } // Знаходимо контейнери, у яких змінюватимемо вміст. var $menu = jQuery('#menu-comments').find('.wp-menu-name'); var $bar = jQuery('#wp-admin-bar-comments').find('a'); // Змінюємо вміст контейнерів. jQuery($menu).html(data.acn.menu); jQuery($bar).html(data.acn.bar); });
У WordPress за замовчуванням
Після надсилання Хартбіт запиту, WP обробляє за замовчуванням лише кілька операцій. Щоб дізнатися які, подивимося, що прикріплюється до хуків: heartbeat_received , heartbeat_send та heartbeat_tick . Тобто. як ВП змінює відповідь сервера
heartbeat_received
Файл wp-admin/includes/admin-filters.php :
add_filter( 'heartbeat_received', 'wp_check_locked_posts', 10, 3); add_filter( 'heartbeat_received', 'wp_refresh_post_lock', 10, 3); add_filter( 'heartbeat_received', 'heartbeat_autosave', 500, 2);
Файл wp-includes/class-wp-customize-manager.php :
add_filter( 'heartbeat_received', array( $this, 'check_changeset_lock_with_heartbeat' ), 10, 3);
heartbeat_send
Файл: wp-includes/default-filters.php
add_filter( 'heartbeat_send', 'wp_auth_check'); add_filter( 'heartbeat_nopriv_send', 'wp_auth_check');
heartbeat_tick
Нічого не прикріплюється за умовчанням.
Базовий налаштування Хартбіт, встановлюються через фільтр:
add_filter( 'heartbeat_settings', 'wp_heartbeat_settings');
Література
Під час створення статті використовувалися матеріали:
- Вихідний код файлу heartbeat.js .
- Офіційна документація з Heartbeat API
- Стаття на code.tutsplus.com – The Heartbeat API: Getting Started
- Стаття на code.tutsplus.com – Heartbeat API: Using Heartbeat in a Plugin