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

Web3: обновление интерфейса по событиям контракта

Оглавление курса «Web3-интерфейс: реестр в браузере» · Урок 24 из 30.

После новой публикации открытая карточка может устареть. События контракта помогают узнать, что следует перечитать состояние. В этом уроке подпишемся на Published и Deactivated, ограничим обработку курсом 17 и сохраним явное различие уведомления и текущего getter. Результат — события запускают обновление, но не подменяют собой снимок реестра.

Предполагаются publicClient, полный ABI и контроллер чтения. Viem 2.37.9 используется в закреплённом профиле. Подписки и программы не запускались. Предполагается локальный endpoint с подходящей поддержкой наблюдения; конкретный транспорт и его доступность должны быть определены при самостоятельной лаборатории.

Событие является фактом вызова

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

Номер курса является индексированным параметром обоих событий. Клиент может ограничить фильтр этим значением. Адрес экземпляра тоже ограничивает источник: не собираем события всех контрактов с похожими именами. Сеть задаётся Public Client, а не текущим расширением кошелька.

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

Подписки для двух событий

Создадим watch-course.js. Обработчик уведомляет контроллер, что данные могут устареть. Он не отправляет никакую транзакцию и не использует кошелёк.

import { publicClient } from './client.js';
import { catalogAbi } from './catalog-abi.js';

export function watchCourse(address, courseId, refresh, onError) {
  const options = { address, abi: catalogAbi, args: { courseId }, onError };
  const stopPublished = publicClient.watchContractEvent({
    ...options,
    eventName: 'Published',
    onLogs() { refresh(); },
  });
  const stopDeactivated = publicClient.watchContractEvent({
    ...options,
    eventName: 'Deactivated',
    onLogs() { refresh(); },
  });
  return () => { stopPublished(); stopDeactivated(); };
}

Возвращаемая функция является ресурсом жизненного цикла страницы и вызывается при удалении карточки. Повторное создание подписок без очистки приведёт к нескольким обновлениям на одно уведомление. Контроллер чтения объединяет или ограничивает такие запросы, но правильное владение подпиской всё равно необходимо.

На HTTP-транспорте наблюдение может использовать опрос и возможности фильтров узла; на другом транспорте поведение отличается. Нельзя обещать мгновенную доставку только по имени watch. Endpoint может не поддерживать нужный метод или терять временный фильтр. Ошибка наблюдения показывается отдельно от уже сохранённого снимка.

Несколько уведомлений подряд

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

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

При большой нагрузке добавляют небольшое объединение запросов во времени. Такая задержка является политикой интерфейса и не должна скрывать факт устаревшего снимка. Пользователь видит «есть обновление» или статус фонового чтения, а не вечную надпись «актуально» во время задержки.

Пропущенное уведомление

Подписка может прерваться из-за сети, сна устройства или перезагрузки страницы. Отсутствие события в браузере не означает отсутствие изменения реестра. При возвращении вкладки выполняется новое чтение, а при необходимости истории — ограниченный backfill. Поэтому кнопка ручного обновления остаётся полезной даже при наличии автоматического наблюдения.

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

Нельзя считать событие разрешением на действие посетителя. Например, уведомление Published не должно автоматически открыть кошелёк и предложить купить доступ. Оно сообщает об изменении источника, а решение о подписи принимается пользователем отдельно. Это сохраняет предсказуемость страницы.

Чтение во время редактирования

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

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

Для упражнения задайте две публикации, деактивацию и потерю связи между ними. Ожидается, что карточка после восстановления показывает последнее прочитанное состояние, а журнал истории сохраняет отдельные факты с проверкой происхождения. Итог — событие является сигналом и исторической записью, но не единственным источником текущей карточки. API наблюдения описан в watchContractEvent.

Уведомление после удаления страницы

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

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

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

Оглавление серии