Нужна помощь с обновлениями в реальном времени MessageBus в плагине лотереи (разработан с помощью ИИ)

Всем привет,

Я разрабатываю плагин для лотереи на платформе Discourse, где пользователи могут участвовать, отвечая на тему лотереи. Полное раскрытие информации: я не программист — я создаю этот плагин полностью с помощью ИИ (Claude Code), поэтому, возможно, упускаю какие-то фундаментальные концепции. Базовая функциональность работает, но у меня возникают проблемы с обновлениями в реальном времени через MessageBus. Буду благодарен за любые советы по правильному подходу.

Чего я пытаюсь достичь

  • Создать пост лотереи с использованием BBCode [lottery]...[/lottery]
  • Отображать карточку лотереи в посте с количеством участников
  • При ответах пользователей для участия все зрители должны видеть обновление количества участников в реальном времени без перезагрузки страницы
  • Аналогично тому, как плагин discourse-calendar обновляет информацию о событиях

Текущая реализация

Бэкенд (Ruby)

Модель Lottery (app/models/lottery.rb):

def publish_update!
  channel = "/lottery/#{self.post.topic_id}"
  message = { id: self.id }

  MessageBus.publish(channel, message)
end

Модель LotteryParticipant (app/models/lottery_participant.rb):

after_commit :publish_lottery_update, on: [:create, :destroy]

private

def publish_lottery_update
  self.lottery.publish_update!
end

Логи бэкенда подтверждают, что вызов MessageBus.publish выполняется успешно:

[Lottery] 📡 Публикация обновления MessageBus:
  Канал: /lottery/27
  Сообщение: {:id=>52}
[Lottery] ✅ MessageBus.publish завершено

Фронтенд (JavaScript)

Декоратор (discourse-lottery-decorator.gjs):

api.decorateCookedElement((cooked, helper) => {
  const post = helper.getModel();

  if (!post?.lottery_data) return;

  // Проверка, был ли элемент уже декорирован (ИИ предложил использовать data-атрибут)
  if (cooked.dataset.lotteryDecorated === "true") return;

  const lotteryNode = cooked.querySelector(".discourse-lottery");
  if (!lotteryNode) return;

  const wrapper = document.createElement("div");
  lotteryNode.before(wrapper);

  const lottery = Lottery.create(post.lottery_data);

  helper.renderGlimmer(
    wrapper,
    <template><DiscourseLottery @lotteryData={{lottery}} /></template>
  );

  lotteryNode.remove();
  cooked.dataset.lotteryDecorated = "true";
}, { id: "discourse-lottery" });

Компонент (discourse-lottery/index.gjs):

export default class DiscourseLottery extends Component {
  @service messageBus;
  @service lotteryApi;

  constructor() {
    super(...arguments);

    const { lotteryData } = this.args;
    if (lotteryData?.topicId) {
      this.lotteryPath = `/lottery/${lotteryData.topicId}`;
      this.lotteryData = lotteryData;

      // ИИ предложил вручную привязать обратный вызов
      this._boundOnLotteryUpdate = this._onLotteryUpdate.bind(this);
      this.messageBus.subscribe(this.lotteryPath, this._boundOnLotteryUpdate);

      registerDestructor(this, () => {
        this.messageBus.unsubscribe(this.lotteryPath, this._boundOnLotteryUpdate);
      });
    }
  }

  async _onLotteryUpdate(msg) {
    console.log('[Lottery Component] 🔔 Сообщение MessageBus получено!');

    const updatedData = await this.lotteryApi.lottery(this.lotteryData.topicId);
    this.lotteryData.updateFromLottery(updatedData);
  }
}

Модель (models/lottery.js):

export default class Lottery {
  @tracked _stats;

  set stats(stats) {
    this._stats = LotteryStats.create(stats || {});
  }

  updateFromLottery(lottery) {
    // Обновление всех свойств, включая статистику
    this.stats = lottery.stats || {};
    // ... другие свойства
  }
}

Проблема

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

Что работает:

  • :white_check_mark: Когда пользователь отвечает для участия, его собственная страница показывает обновление количества участников в реальном времени
  • :white_check_mark: Бэкенд успешно публикует сообщения MessageBus
  • :white_check_mark: Сообщение MessageBus получает страница участника (в логах видно “:bell: Сообщение MessageBus получено!”)
  • :white_check_mark: Модель обновляется с новым количеством участников (в логах видно увеличение с 0 до 1)

Что не работает:

  • :cross_mark: Другие зрители, наблюдающие за темой лотереи, не видят обновления количества в реальном времени
  • :cross_mark: Страница создателя лотереи не обновляется
  • :cross_mark: Страница любого другого пользователя, у которого открыта тема лотереи, не обновляется
  • Им всем приходится вручную перезагружать страницу, чтобы увидеть обновленное количество участников

Кроме того, я вижу, что компонент многократно уничтожается и создается заново в консоли:

[Lottery Component] ✅ Подписка MessageBus установлена
[Lottery Component] 🧹 Очистка MessageBus для темы: 27
[Lottery Component] ✅ Подписка MessageBus установлена
[Lottery Component] 🧹 Очистка MessageBus для темы: 27

Это указывает на то, что decorateCookedElement вызывается несколько раз, что приводит к уничтожению и повторному созданию компонентов, что, возможно, нарушает подписку MessageBus для других зрителей.

Вопросы

  1. Является ли использование атрибута data-decorated для элемента cooked правильным способом предотвращения дублирования декорирования? ИИ предложил этот подход, но я не смог найти официальную документацию, подтверждающую, что это рекомендуемый паттерн.

  2. Почему UI не обновляется, хотя отслеживаемое свойство модели изменяется? @tracked _stats должно вызывать перерисовку при обновлении через this.stats = lottery.stats, но отображаемое количество участников остается прежним. Неужели что-то не так с моим использованием @tracked?

  3. Не стоит ли мне использовать совершенно другой подход? Я попросил ИИ следовать паттерну discourse-calendar, но, возможно, я упускаю что-то фундаментальное в том, как Discourse обрабатывает обновления в реальном времени.

  4. Не является ли удаление исходного lotteryNode причиной проблемы? ИИ изначально пытался не удалять его, но это приводило к тому, что карточка лотереи скрывалась из-за CSS (.cooked > .discourse-lottery { display: none; }). Есть ли лучший паттерн?

Поскольку я не программист, возможно, я задаю базовые вопросы — любые ссылки на документацию или примеры будут невероятно полезны! Спасибо!

2 лайка