Перейти к содержанию

Подписка по тегу объединяет пользователей, которые наблюдают за одним объектом. Когда объект меняется, сервер отправляет команду всем его наблюдателям. Для этого PHP создает подписку через CPullWatch::Add и отправляет команду через CPullWatch::AddToStack. Браузер принимает ее обработчиком BX.PULL.subscribe.

Используйте тег, когда состав получателей зависит от того, кто начал наблюдать за объектом. Если сервер уже знает конкретных получателей и наблюдение не нужно, выберите адресную отправку. В обоих случаях получение команды обрабатывает JavaScript.

Как тег определяет получателей

Тег — строка, которая обозначает область наблюдения. Например, example.reports.report.42 объединяет наблюдателей отчета 42. Формируйте тег на сервере из префикса приложения, типа объекта и его идентификатора. Такое соглашение помогает разделять подписки разных объектов и модулей.

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

Обмен проходит в следующем порядке:

  1. Сервер проверяет право пользователя на отчет и добавляет его в наблюдатели тега.

  2. Браузер регистрирует обработчик reportReady и включает продление наблюдения.

  3. После изменения отчета сервер отправляет команду по тегу.

  4. Обработчики в браузерах наблюдателей получают параметры и обновляют страницу.

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

Внимание

Тег не подтверждает право на объект. Методы CPullWatch не знают правил доступа к отчетам. Проверяйте права до создания подписки и учитывайте их изменение при отправке команд.

Подготовить пример

Сначала выполните пример из статьи Обновление страницы по команде сервера. Он проверяет транспорт, подключает pull.client и объявляет зависимость example.reports в публичной части. Сохраните страницу получателя /local/pull-demo/index.php. Ее обработчик и счетчик команд понадобятся для проверки подписок. Формы обработчиков разобраны в статье Обработка команд в JavaScript.

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

Подготовьте три тестовые учетные записи и отдельный профиль браузера для каждой. Двум пользователям разрешите наблюдать за отчетом, третьему — запретите. В коде ниже замените числа 101 и 102 реальными идентификаторами первых двух пользователей. Используйте целые числа, поскольку пример сравнивает их строго.

Создайте /local/pull-demo/watch.php для управления наблюдением и отправки команды. Для проверки истечения понадобится еще одна страница — /local/pull-demo/watch-status.php. Ее код приведен в разделе Проверить истечение без продления. Как и в быстром старте, исключите /local/pull-demo/* из композитного кеша и дополнительного кеширования HTML, если они включены.

В примере используются те же данные, что и в статье Обновление страницы по команде сервера. Модуль example.reports отправляет команду reportReady с числом 42 в reportId и строкой в text. Отчет остается условным. Код проверяет подписки, но не формирует файл и не сохраняет результат работы. Первоначальное чтение и восстановление данных описаны в статье Обработка команд в JavaScript.

Создать подписку с проверкой доступа

Страница получает идентификатор пользователя из текущей сессии, а тег формирует на сервере. Через форму можно выбрать только действие. Отправка формы перезагружает страницу.

Пример решает три задачи:

  • проверяет доступ к отчету и сессию перед изменением подписки,

  • создает или удаляет подписку и отправляет команду наблюдателям,

  • показывает форму и включает автопродление для подписанного пользователя.

После создания подписки или отправки команды страница снова включает продление. После удаления она оставляет продление выключенным.

Создайте /local/pull-demo/watch.php и добавьте в него код страницы.

<?php

use Bitrix\Main\Loader;
use Bitrix\Main\UI\Extension;

require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/header.php';

$APPLICATION->SetTitle('Наблюдение за отчетом 42');

// Задаем учебное правило доступа и тег на сервере
$allowedUserIds = [101, 102];
$reportId = 42;
$tag = 'example.reports.report.' . $reportId;
$userId = (int)$USER->GetID();

if (!$USER->IsAuthorized())
{
    echo '<p>Войдите на сайт и откройте страницу повторно.</p>';
}
elseif (!Loader::includeModule('pull'))
{
    echo '<p>Не удалось подключить модуль pull.</p>';
}
else
{
    $canRead = in_array($userId, $allowedUserIds, true);
    $startAccepted = false;

    // GET показывает форму, POST меняет наблюдение или отправляет команду
    if ($_SERVER['REQUEST_METHOD'] === 'POST')
    {
        if (!check_bitrix_sessid())
        {
            echo '<p>Проверка сессии не пройдена. Обновите страницу.</p>';
        }
        else
        {
            $action = $_POST['action'] ?? '';

            // Пользователь может удалить свою подписку и после потери доступа
            if ($action === 'stop')
            {
                \CPullWatch::Delete($userId, $tag);
                echo '<p>Серверная подписка удалена.</p>';
            }
            elseif (!$canRead)
            {
                echo '<p>Нет доступа к отчету 42.</p>';
            }
            elseif ($action === 'start')
            {
                $startAccepted = \CPullWatch::Add($userId, $tag, true);
                echo $startAccepted
                    ? '<p>Вызов создания подписки выполнен.</p>'
                    : '<p>Не удалось добавить подписку.</p>';
            }
            elseif ($action === 'send')
            {
                // Исключаем наблюдателей, которые потеряли доступ
                foreach (\CPullWatch::GetUserList($tag) as $watcherId)
                {
                    if (!in_array((int)$watcherId, $allowedUserIds, true))
                    {
                        \CPullWatch::Delete((int)$watcherId, $tag);
                    }
                }

                \CPullWatch::AddToStack($tag, [
                    'module_id' => 'example.reports',
                    'command' => 'reportReady',
                    'params' => [
                        'reportId' => $reportId,
                        'text' => 'Отчет 42 готов.',
                    ],
                ]);

                echo '<p>Вызов отправки выполнен. Проверьте получателей.</p>';
            }
        }
    }

    if (!$canRead)
    {
        echo '<p>Наблюдение и отправка недоступны для этой учетной записи.</p>';
    }

    Extension::load('pull.client');
    ?>
    <p>Текущий пользователь: <?= $userId ?></p>
    <form method="post" action="/local/pull-demo/watch.php">
        <?= bitrix_sessid_post() ?>
        <?php if ($canRead): ?>
            <button type="submit" name="action" value="start">Начать наблюдение</button>
            <button type="submit" name="action" value="send">Отправить команду</button>
        <?php endif; ?>
        <button type="submit" name="action" value="stop">Прекратить наблюдение</button>
    </form>
    <p><a href="/local/pull-demo/watch.php">Проверить подписку</a></p>
    <p>
        <a href="/local/pull-demo/index.php" target="_blank" rel="noopener">
            Открыть страницу получателя
        </a>
    </p>
    <?php

    // Определяем наблюдение для текущего пользователя после обработки формы
    $isWatching = false;
    if ($canRead)
    {
        $watcherIds = array_map('intval', \CPullWatch::GetUserList($tag));
        $isWatching = in_array($userId, $watcherIds, true);
    }

    // При открытии страницы продлеваем только существующую подписку
    if ($_SERVER['REQUEST_METHOD'] === 'GET' && $isWatching)
    {
        $extended = \CPullWatch::Extend($userId, [$tag]);
        $isWatching = $extended[$tag] ?? false;
    }

    // Подтверждаем серверную запись отдельным GET после завершения изменений
    if ($_SERVER['REQUEST_METHOD'] === 'GET' && $canRead)
    {
        echo $isWatching
            ? '<p>Вы есть в списке наблюдателей.</p>'
            : '<p>Вас нет в списке наблюдателей.</p>';
    }

    // Включаем продление и после GET, и после POST с действующей подпиской
    if ($canRead && ($isWatching || $startAccepted))
    {
        ?>
        <p id="watch-client-status">Подготовка автопродления...</p>
        <script>
        BX.ready(function () {
            const status = document.getElementById('watch-client-status');
            if (!BX.PULL || typeof BX.PULL.extendWatch !== 'function')
            {
                status.textContent = 'Клиент недоступен. Автопродление не включено.';
                return;
            }

            BX.PULL.extendWatch('example.reports.report.42', true);
            status.textContent = 'Тег добавлен в очередь автопродления.';
        });
        </script>
        <?php
    }
}

// Даем модулю завершить запись подписок и отправку команд
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/footer.php';

После нажатия Начать наблюдение дождитесь завершения запроса и перейдите по ссылке Проверить подписку. Надпись «Вы есть в списке наблюдателей» подтверждает серверную запись. При этом GET продлевает существующую подписку после проверки доступа, но не создает новую. Оставьте эту страницу открытой. Она включает автопродление. Страницу получателя откройте в отдельной вкладке.

После кнопки Отправить команду автопродление включается снова, если текущий пользователь остается наблюдателем. Надпись «Тег добавлен в очередь автопродления» подтверждает только настройку клиента. Ответ сервера проверьте по разделу Проверить ответ продления. Не обновляйте POST-страницу для проверки. Браузер может повторить действие формы. Используйте ссылку Проверить подписку.

В учебном примере список $allowedUserIds заменяет проверку доступа к отчету. Он разрешает и наблюдение, и отправку тестовой команды. В приложении проверяйте право чтения перед подпиской, а право изменения или запуска операции — перед отправкой. Не принимайте произвольный тег или идентификатор получателя из запроса.

Что делает CPullWatch::Add

Метод принимает идентификатор пользователя $userId, строку $tag и необязательный флаг $immediate. Значение $immediate по умолчанию равно false. При добавлении новой записи флаг true запускает ее запись сразу. Повторный вызов для существующей подписки может отложить обновление до завершения запроса, поэтому пример сохраняет штатный эпилог.

Метод возвращает false при нулевом идентификаторе пользователя или пустом теге. Результат true не подтверждает подключение браузера и доставку команды. Повторный вызов для той же пары пользователя и тега поддерживает существующее наблюдение. Отдельную подписку для каждой вкладки создавать не нужно.

Наблюдение для гостя

Метод CPullWatch::Add допускает отрицательный идентификатор гостя на этапе проверки аргументов. Это еще не подтверждает возможность создать подписку. Для нее нужен персональный канал гостя. В реализации, где CPullChannel::Get() проверяет активность пользователя для любого ненулевого ID, отрицательный ID не проходит проверку. Сначала выполните проверку совместимости персонального канала гостя. Если канал не получен, не продолжайте этот сценарий.

На совместимой реализации получите отрицательный идентификатор из проверенной гостевой сессии по правилам гостевой адресации. Не подставляйте произвольное отрицательное число вместо отсутствующего идентификатора авторизованного пользователя. Значение 0 для подписки не подходит.

Пример выше требует авторизации. Штатное продление через pull.watch.extend использует идентификатор текущего пользователя из $USER->GetID(), поэтому переносить этот сценарий на гостя без отдельной проверки нельзя. Даже при доступном канале для гостевого наблюдения нужно отдельно обеспечить проверку доступа, идентификацию и продление на сервере. Готового гостевого примера здесь нет.

Отправить команду наблюдателям

Метод CPullWatch::AddToStack($tag, $parameters) выбирает пользователей, подписанных на тег, и передает им команду. В примере $parameters содержит обычные поля module_id, command и params. Менять обработчик страницы /local/pull-demo/index.php не нужно. Он принимает ту же команду reportReady.

В аргументе $tag можно передать непустую строку, кроме '0', или непустой массив строк с тегами. Для массива метод объединяет получателей без повторения пользователя в списке. По умолчанию отправка использует персональные каналы пользователей.

Необязательное поле $parameters['skip_users'] исключает пользователей из получателей этой команды. Передавайте массив целочисленных идентификаторов, например, [(int)$USER->GetID()] для текущего авторизованного пользователя. Если поле отсутствует или массив пуст, дополнительных исключений нет. Исключение из отправки не удаляет подписку.

Чтобы пропустить автора изменения, замените вызов AddToStack в ветке send учебного примера следующим кодом. Сохраните проверку доступа и удаление наблюдателей, потерявших права, перед этим вызовом:

\CPullWatch::AddToStack($tag, [
    'module_id' => 'example.reports',
    'command' => 'reportReady',
    'params' => [
        'reportId' => $reportId,
        'text' => 'Отчет 42 готов.',
    ],
    'skip_users' => [$userId],
]);

При двух наблюдателях команду получит только второй пользователь. Для основной проверки доставки обоим наблюдателям верните вызов без skip_users.

Метод AddToStack возвращает false, если аргумент $tag не проходит проверку PHP empty(). Это относится к пустой строке, строке '0' и пустому массиву. После этой проверки метод возвращает true, даже если наблюдателей нет. Он не передает вызывающему коду результат внутреннего добавления события. Поэтому результат вызова нельзя считать подтверждением доставки. Проверяйте счетчики на страницах получателей.

В приложении отправляйте команду после успешного сохранения отчета. Выбор данных и момент отправки описаны в статье Отправка команд из PHP. Если команда содержит только сигнал об изменении, браузер должен повторно прочитать отчет с проверкой актуальных прав.

Получить список наблюдателей

Метод CPullWatch::GetUserList($tag) возвращает массив идентификаторов пользователей в виде строк. Для строгого сравнения с целочисленными идентификаторами преобразуйте значения:

$watcherIds = array_map(
    'intval',
    \CPullWatch::GetUserList('example.reports.report.42')
);

Пустой массив означает, что по тегу нет записей наблюдения. Наличие пользователя в массиве не подтверждает его подключение или актуальное право на отчет. Не используйте список подписок вместо проверки доступа.

Продлить и прекратить наблюдение

У серверной записи и клиентского обработчика разные задачи. Серверная запись определяет получателя, автопродление поддерживает запись, а JS-обработчик обновляет интерфейс. При закрытии интерфейса учитывайте каждый из этих механизмов.

Проверить вариант браузерного клиента

Расширение pull.client может подключать разные реализации:

  • pull.client.js — если новая реализация не включена для пользователя,

  • dist/pull.client.js — если константа PULL_NEW_CLIENT_ENABLE имеет истинное значение или идентификатор пользователя указан в опции модуля pull с именем new_client_users.

Выбор может различаться для двух тестовых пользователей. Уточните эти настройки у администратора. Если ресурсы загружаются отдельными файлами, проверьте путь клиента во вкладке Network. При объединении JavaScript в общий файл ориентируйтесь на настройки и состав подключенного расширения.

Внимание

Проверяйте не только первый ответ pull.watch.extend, но и следующий запрос без перезагрузки страницы. Если повторного запроса нет, длительное наблюдение не подтверждено. Сопоставьте подключенную реализацию клиента с ее настройками и проверьте ошибки в Console и Network.

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

Продлить существующую подписку

После создания серверной подписки вызовите в браузере:

BX.PULL.extendWatch('example.reports.report.42', true);

Метод BX.PULL.extendWatch(tag, force) добавляет тег в очередь автопродления клиента:

  • tag — непустая строка с тегом существующей серверной подписки,

  • force — необязательный флаг типа boolean. Передайте true, чтобы запланировать ближайший запрос с короткой задержкой. Если аргумент не передан, JavaScript использует undefined: короткая задержка не включается, как и при false.

Флаг force ускоряет планирование запроса, но не обновляет серверную запись синхронно. В примере GET страницы отдельно продлевает существующую запись. Проверяйте фактический запрос и его ответ во вкладке Network. Наличие тега в клиентской очереди еще не подтверждает продление.

Продление обновляет существующую серверную запись через CPullWatch::Extend. Оно не создает удаленную подписку и не проверяет прикладные права на отчет. Если сервер сообщает, что записи нет, клиент убирает тег из очереди. Чтобы возобновить наблюдение, снова проверьте права, вызовите CPullWatch::Add, затем включите продление.

Без продления запись удаляет агент CPullWatch::CheckExpireAgent. Агент запускает PHP-код по расписанию. Его выполнение может задержаться. Устройство этого механизма описано в статье Агенты и фоновые задачи.

Удаление записи зависит от запуска агента. Это не точный таймер прекращения доставки. Пока запись существует, GetUserList и AddToStack могут учитывать ее. Для немедленного отзыва доступа удаляйте подписку явно.

Проверить ответ продления

На странице watch.php откройте инструменты разработчика и вкладку Network. Найдите запрос метода pull.watch.extend, который клиент отправляет после включения продления. При обычном REST-транспорте имя метода присутствует в адресе запроса. При пакетной отправке ищите его среди команд пакета. Это служебный запрос клиента Framework. Вручную отправлять его для примера не нужно.

В ответе найдите результат именно этого метода. Для существующей подписки он содержит следующий фрагмент:

{
    "example.reports.report.42": true
}

В отдельном REST-ответе этот объект находится в поле result. При пакетной отправке откройте результат соответствующей команды пакета. Значение true у нужного тега означает, что сервер нашел и продлил запись для текущего пользователя. HTTP-статус 200 без проверки результата этого не подтверждает.

Если записи нет, результат содержит:

{
    "example.reports.report.42": false
}

Значение false не создает подписку. Снова начните наблюдение через серверную проверку прав. Если ответ содержит ошибку или результат нужного метода отсутствует, продление не подтверждено. Проверьте авторизацию, сессию и ошибки запроса.

После первого успешного ответа проверьте повторное продление:

  1. Оставьте watch.php открытой в активной вкладке, а журнал Network — включенным. Зафиксируйте время ответа с true для нужного тега.

  2. Не перезагружайте страницу, не отправляйте форму и не вызывайте extendWatch повторно. Сохраняйте соединение. Его восстановление может отдельно запустить таймер.

  3. Дождитесь следующего запроса pull.watch.extend и проверьте true для того же тега. В описанной реализации ориентир — 29 минут после обработки предыдущего ответа. Задержки браузерных таймеров могут увеличить ожидание.

Если повторного запроса нет, проверьте ошибки JavaScript, состояние соединения и выбранную реализацию клиента. Наличие серверной записи на watch-status.php не подтверждает повторное продление. Запись могла сохраниться с первого запроса или продлеваться другим клиентом.

Остановить автопродление и удалить запись

При закрытии интерфейса уберите тег из клиентской очереди:

BX.PULL.clearWatch('example.reports.report.42');

Метод clearWatch есть в браузерном pull.client. В мобильной веб-странице с mobile.pull.client одноименный метод передает запрос клиенту мобильного приложения. В обоих случаях он прекращает автопродление, но не удаляет запись на сервере. Выбор клиентской библиотеки описан в статье Обработка команд в JavaScript.

Чтобы прекратить серверное наблюдение, отправьте запрос своему PHP-обработчику и вызовите:

\CPullWatch::Delete($userId, 'example.reports.report.42');

Идентификатор $userId для пользовательского запроса берите из текущей сессии. Проверяйте CSRF-токен и разрешайте удаление собственной подписки даже после потери доступа к отчету. Именно так работает кнопка Прекратить наблюдение в примере.

Метод Delete возвращает true после выполнения удаления, в том числе если записи уже не было. Если не передать тег или передать null, метод удалит все наблюдения пользователя. Для закрытия одного отчета всегда указывайте его тег.

В учебном примере отправка формы перезагружает страницу управления и завершает работу ее JS-контекста. В интерфейсе без перезагрузки вызовите clearWatch, выполните серверное удаление и снимите обработчик команды через функцию, возвращенную BX.PULL.subscribe. Порядок снятия обработчиков разобран в статье Обработка команд в JavaScript.

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

Внимание

Если один тег используют несколько компонентов одной страницы, согласуйте начало и завершение наблюдения между ними. Вызов clearWatch убирает тег из общей очереди этого клиента, а Delete удаляет серверную подписку пользователя целиком. Удалять ее при закрытии одного из нескольких открытых представлений может быть преждевременно.

Отозвать доступ к отчету

Изменение прикладных прав не удаляет подписку автоматически. При отзыве доступа вызовите CPullWatch::Delete для пользователя и тега. Одновременно запретите повторное создание подписки в обработчике приложения. Автопродление не должно служить проверкой доступа.

Учебный отправитель перед рассылкой удаляет наблюдателей, которых больше нет в $allowedUserIds. Это позволяет проверить отзыв доступа без отдельной системы прав. В приложении связывайте удаление с изменением прав, а перед отправкой конфиденциальных данных повторно проверяйте получателей. Права могут измениться между проверкой и отправкой: эти действия не выполняются как одна неделимая операция.

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

Проверить жизненный цикл подписки

Проверьте подписку на примере отчета 42. Два пользователя начинают наблюдение и получают команду о готовности. Затем один прекращает наблюдение. Следующую команду должен получить только оставшийся подписчик.

В каждом профиле откройте /local/pull-demo/index.php и дождитесь регистрации обработчика. Управляйте наблюдением через /local/pull-demo/watch.php. Для этих проверок используйте кнопку Отправить команду на странице наблюдения. Форма send.php из быстрого старта отправляет адресную команду независимо от тега.

Получить команду двумя пользователями

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

  2. Убедитесь, что обе страницы показывают наличие подписки. Оставьте их открытыми для продления.

  3. Запишите счетчики на страницах получателей и отправьте команду из первого профиля.

  4. Проверьте, что оба счетчика увеличились, а страницы показывают «Отчет 42 готов.».

  5. Под третьим пользователем откройте страницу наблюдения. Кнопки начала и отправки должны отсутствовать. Подделанный POST с action=start и действительным токеном тоже не должен создавать подписку. Новая команда по тегу не должна увеличивать его счетчик.

Для наблюдения отведите, например, 30 секунд. Это интервал проверки, а не гарантированная задержка доставки. Если команда не пришла обоим пользователям, сначала повторите проверку транспорта из быстрого старта.

Проверить удаление и повторную подписку

  1. Под вторым пользователем нажмите Прекратить наблюдение и дождитесь завершения запроса.

  2. Перейдите по ссылке Проверить подписку. Страница должна сообщить, что пользователя нет в списке.

  3. Оставьте обе страницы получателей открытыми и запишите счетчики. Отправьте новую команду от первого пользователя.

  4. Убедитесь, что увеличился только счетчик первого пользователя.

  5. Повторно прекратите наблюдение второго пользователя. Ошибки из-за отсутствующей записи быть не должно.

  6. Снова начните наблюдение второго пользователя и проверьте подписку. Новую команду должны получить оба получателя.

Проверить истечение без продления

Создайте /local/pull-demo/watch-status.php. Страница показывает наличие подписки только для текущего авторизованного пользователя. Она не создает подписку и не вызывает extendWatch, поэтому ее можно обновлять во время ожидания очистки.

<?php

use Bitrix\Main\Loader;

require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/header.php';

$APPLICATION->SetTitle('Проверка истечения наблюдения');

if (!$USER->IsAuthorized())
{
    echo '<p>Войдите под пользователем, подписку которого проверяете.</p>';
}
elseif (!Loader::includeModule('pull'))
{
    echo '<p>Не удалось подключить модуль pull.</p>';
}
else
{
    $userId = (int)$USER->GetID();
    $watcherIds = array_map(
        'intval',
        \CPullWatch::GetUserList('example.reports.report.42')
    );

    echo '<p>Текущий пользователь: ' . $userId . '</p>';
    echo in_array($userId, $watcherIds, true)
        ? '<p>Серверная подписка еще существует.</p>'
        : '<p>Серверной подписки нет.</p>';
}

require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/footer.php';

Проверку выполняйте на тестовой установке с работающими агентами:

  1. Создайте подписку второго пользователя и подтвердите получение команды обоими наблюдателями.

  2. Под вторым пользователем откройте watch-status.php. Убедитесь, что страница показывает существующую подписку.

  3. Закройте все вкладки watch.php второго пользователя. Оставьте его index.php и watch-status.php открытыми. Если тег используется в других клиентах, остановите продление и в них. Не нажимайте Прекратить наблюдение. В этой проверке запись должен удалить агент.

  4. Запишите время прекращения продления. Периодически обновляйте только watch-status.php, пока не появится сообщение «Серверной подписки нет». Учитывайте возможный последний запрос продления, который уже был отправлен перед закрытием вкладок.

  5. После исчезновения записи отправьте новую команду от первого пользователя. Его счетчик должен увеличиться, счетчик второго — остаться прежним.

  6. Откройте watch.php второго пользователя, снова начните наблюдение и подтвердите получение следующей команды.

В реализации модуля для MySQL агент CPullWatch::CheckExpireAgent удаляет записи, которые не продлевались 32 минуты. Этот порог задан в коде метода. Удаление зависит от времени запуска агента.

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

Проверить продление и отзыв прав

|

|| Проверка | Действие | Ожидаемый результат || || Продление | Оставьте watch.php открытой. Во вкладке Network найдите запрос pull.watch.extend по разделу Проверить ответ продления | Результат метода содержит "example.reports.report.42": true || || Повторное продление | После первого успешного ответа дождитесь следующего запроса без перезагрузки, отправки формы и переподключения | Повторный запрос содержит тот же тег, результат метода для него равен true || || Продление после отправки | На watch.php отправьте команду, оставаясь наблюдателем. Дождитесь надписи о добавлении тега в очередь и нового запроса продления | Перезагрузка формы не прекратила автопродление || || Остановка автопродления | В консоли watch.php второго пользователя выполните BX.PULL.clearWatch('example.reports.report.42'). Не обновляйте эту страницу и не нажимайте ее кнопки. Новую команду отправьте от первого пользователя | Запись еще существует, новая команда может прийти. Состояние записи можно проверить через watch-status.php || || Отзыв доступа | Удалите идентификатор второго пользователя из $allowedUserIds, сохраните файл и отправьте команду от первого | Отправитель удаляет наблюдение второго пользователя, его счетчик не увеличивается. Повторная подписка ему недоступна || || Восстановление доступа | Верните идентификатор в список и откройте страницу управления вторым пользователем | Право восстановлено, но удаленная подписка сама не появляется. Нужно снова нажать Начать наблюдение || || Ошибка сессии | Отправьте POST без sessid | Страница сообщает об ошибке сессии, подписка не меняется и команда не отправляется || |#

После проверки удалите тестовые страницы watch.php и watch-status.php, если они больше не нужны. В приложении создавайте наблюдение после проверки доступа, поддерживайте его только пока нужен интерфейс и удаляйте при отзыве прав. Применение подписки в компоненте описано в статье Обновление компонента после фоновой операции.