Использование Sever Sent Events (SSE) в плагине

статья обновлена февраль 2021г. (внесены изменения, с учетом накопленных знаний)

В процессе создания и эксплуатации плагина WatchMan-Site7 мне пришла в голову идея создать такой механизм, который мог бы сам, без ручного вмешательства со стороны админа – обновлял основной экран посещений всех типов посетителей (людей, роботов) по мере их появления на сайте. Потому что – надоело: сидишь в админке и как дятел стучишь по клавише “обновить” экран браузера, что бы увидеть новых посетителей сайта. Пусть бы сам сайт посылал мне в браузер информацию о новых посещениях по мере их поступления!
Пришлось углубиться в теорию. И вот, что я накопал… Существуют три технологии динамической связи “клиент-сервер”:
1. AJAX (набор ранее существующих технологий – JS, PHP, HTML)
2. WebSocket (мощная вещь, но достаточно сложная в реализации)
3. SSE (Server-Sent Events)
Пришлось познакомиться с перечисленными технологиями, причем зная, что базовый класс WP_LIST_TABLE в WordPress адаптирован к технологии AJAX. И поэтому, подспудно, думая на какой технологии остановиться, я склонялся к AJAX. Однако, спустя некоторое время, я все-таки остановился на технологии SSE ( спецификация SSE ). И вот, как это удалось реализовать.
На стороне сервера:

фрагмент основного файла плагина watchman-site7.php … Здесь:
(строка 6) – подключается файл wms7-sse-backend.php в котором находится функционал по сбору и передаче одних данных на сторону клиента.
(строка 7) – активируется функция “wms7_sse_backend” при срабатывании хука “wp_ajax_backend” для того, кто залогинился.
(строка 11) – подключается файл wms7-sse-frontend.php в котором находится функционал по сбору и передаче других данных на сторону клиента.
(строка 12) – активируется функция “wms7_sse_frontend” при срабатывании хука “wp_ajax_frontend” для того, кто залогинился.
(строка 13) – активируется та же функция “wms7_sse_frontend” при срабатывании хука “wp_ajax_nopriv_frontend” для того, кто не залогинился.
<?php
...
/**
 * Used to send a count of records of visitor, number of unseen emails.
 */
require_once __DIR__ . "/includes/wms7-sse-backend.php";
add_action( "wp_ajax_backend", "wms7_sse_backend" );
/**
 * Used to transfer data about site visits to a widget - counter of visits.
 */
require_once __DIR__ . "/includes/wms7-sse-frontend.php";
add_action( "wp_ajax_frontend", "wms7_sse_frontend" );
add_action( "wp_ajax_nopriv_frontend", "wms7_sse_frontend" );
...
Теперь посмотрим на код в файле “/includes/wms7-sse-backend.php”, который был подключен выше. Тут находим в строке 40 функцию “wms7_sse_backend”, указанную выше в строке 7 основного файла плагина watchman-site7.php
<?php
/**
 * Description: Used to send a count of records of visitor, number of unseen emails.
 *
 * PHP version 7.4
 *
 * @category   Wms7-sse-backend.php
 * @package    WatchMan-Site7
 * @author     Oleg Klenitskiy <klenitskiy.oleg@mail.ru>
 * @version    4.0.0
 * @license    GPLv2 or later
 */

/**
 * Used for Serves to send information to the client browser (backend of site).
 *
 * @param string $data Number of visits records and mails inbox unseen.
 */
function wms7_send_backend( $data ) {

	if ( ! headers_sent() ) {
		header( 'Content-Type: text/event-stream' );
		header( 'Cache-Control: no-cache' );
		header( 'Connection: keep-alive' );
		header( 'Access-Control-Expose-Headers: *' );

		echo 'data: ' . json_encode( $data );
		echo ';' . json_encode( current_time( 'mysql' ) );
		echo "\n\n";
	}
	// check for output_buffering activation.
	if ( 0 !== count( ob_get_status() ) ) {
		ob_flush();
	}
	flush();
}
/**
 * Used for Serves to send information to the client browser (backend of site).
 */
function wms7_sse_backend() {
	$_wms7_nonce = filter_input( INPUT_GET, 'wms7_nonce', FILTER_SANITIZE_STRING );

	if ( !isset($_wms7_nonce) ) {
		wp_die( 'wms7_nonce: nonce is empty.' );
	}
	if ( $_wms7_nonce !== wp_create_nonce( 'wms7_nonce' ) ) {
		wp_die( 'wms7_nonce: nonce is incorrect.' );
	}

	wms7_send_backend( get_option('backend', '0,0') );

	wp_die();
}
/**
 * Used for to obtain the number of visits records.
 *
 * @return number.
 */
function wms7_count_rows() {
	global $wpdb;

	$results = $wpdb->get_var(
		$wpdb->prepare(
			"
            SELECT count(%s) FROM {$wpdb->prefix}watchman_site
            ",
			'*'
		)
	);// db call ok; no cache ok.

	return $results;
}

На стороне клиента:

/**
 * Description: Used to manage the plug-in on the client side.
 *
 * @category    Wms7_backend.js
 * @package     WatchMan-Site7
 * @author      Oleg Klenitskiy <klenitskiy.oleg@mail.ru>
 * @version     4.0.0
 * @license     GPLv2 or later
 */

/**
 * Process Control Server Sent Events on the client side (backend).
 */
function wms7_sse_backend() {
	let sse = document.getElementById( "sse" );

	if (sse.checked) {
		wms7_ctrl_sound("sse_on");
		// start SSE backend.
		localStorage.setItem("wms7_sse_backend", "on" );

		if ( window.EventSource ) {
			let wms7_source = new EventSource( wms7_ajax_url + "?action=backend" + "&wms7_nonce=" + wms7_nonce);
			wms7_source.onmessage = function(e) {
				let arr           = e.data.replace(/"/g, '').split( ";" );
				let arr_add       = arr[0].split( "," );
				console.log( "All visits=" + arr_add[0] + " New letters=" + arr_add[1] + " Server time=" + arr[1] + " Origin=" + e.origin );
				if (localStorage.getItem( "wms7_records_count" ) !== arr_add[0] || localStorage.getItem( "wms7_unseen_count" ) !== arr_add[1] ) {

					localStorage.setItem("wms7_records_count", arr_add[0] );
					localStorage.setItem("wms7_unseen_count", arr_add[1] );

					wms7_beep();
					location.replace( window.location.href );
				}
			}
		} else {
			alert( "Your browser does not support Server-Sent Events. Execution stopped." );
			return;
		}
	} else {
		wms7_ctrl_sound("sse_off")
		// stop SSE backend.
		localStorage.setItem("wms7_sse_backend", "off" );
		location.replace( window.location.href );
	}
	wms7_ctrl_btn_href();
}

Прокомментирую:
В JS коде, в строке 23 создается событие EventSource с названием action=backend, которое совпадает с название хука “wp_ajax_backend” в основном файле плагина watchman-site7.php в строке 7. Кроме этого, wms7_ajax_url = “https://you-site/wp-admin/admin-ajax.php”;
В JS коде, в строке 28 происходит сравнение ранее сохраненных данных в localStorage.getItem( “wms7_records_count” ) с вновь пришедшими данными и сохраняются в localStorage.setItem(“wms7_records_count”, arr_add[0] ) для последующего сравнения (когда придут с сервера новые данные). Затем, в строке 33 – звуковой сигнал, и в строке 34 – обновление экрана. Все действие можно наблюдать в консоли, нажав F12.
😉Совет:
Такой подход мог бы быть полезен при проведении каких-либо коммерческих акций, опросов, политических компаний, голосования…

Leave a Reply