Besoin d'aide avec les mises à jour en temps réel de MessageBus dans le plugin de loterie (développé avec l'aide de l'IA)

Salut tout le monde,

Je développe un plugin de loterie pour Discourse où les utilisateurs peuvent participer en répondant à un sujet de loterie. Divulgation complète : je ne suis pas programmeur - je construis ceci entièrement avec l’aide de l’IA (Claude Code), donc il se peut que je manque des concepts fondamentaux. La fonctionnalité de base fonctionne, mais j’ai des problèmes avec les mises à jour en temps réel à l’aide de MessageBus. J’apprécierais toute aide sur la bonne approche.

Ce que j’essaie de réaliser

  • Créer un message de loterie à l’aide de BBCode [lottery]...[/lottery]
  • Afficher une carte de loterie dans le message indiquant le nombre de participants
  • Lorsque les utilisateurs répondent pour participer, tous les spectateurs devraient voir le nombre de participants se mettre à jour en temps réel sans actualisation de la page
  • Similaire à la façon dont le plugin discourse-calendar met à jour les informations sur les événements

Implémentation actuelle

Backend (Ruby)

Modèle Lottery (app/models/lottery.rb) :

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

  MessageBus.publish(channel, message)
end

Modèle LotteryParticipant (app/models/lottery_participant.rb) :

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

private

def publish_lottery_update
  self.lottery.publish_update!
end

Les journaux backend confirment que MessageBus.publish est appelé avec succès :

[Lottery] 📡 Publishing MessageBus update:
  Channel: /lottery/27
  Message: {:id=>52}
[Lottery] ✅ MessageBus.publish completed

Frontend (JavaScript)

Décorateur (discourse-lottery-decorator.gjs) :

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

  if (!post?.lottery_data) return;

  // Vérifier si déjà décoré (l'IA a suggéré d'utiliser l'attribut 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" });

Composant (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;

      // L'IA a suggéré de lier manuellement le callback
      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 message received!');

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

Modèle (models/lottery.js) :

export default class Lottery {
  @tracked _stats;

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

  updateFromLottery(lottery) {
    // Met à jour toutes les propriétés, y compris les statistiques
    this.stats = lottery.stats || {};
    // ... autres propriétés
  }
}

Le problème

Seule la page du participant se met à jour en temps réel, mais les pages des autres spectateurs ne se mettent pas à jour sans actualisation.

Ce qui fonctionne :

  • :white_check_mark: Lorsqu’un utilisateur répond pour participer, sa propre page affiche la mise à jour du nombre de participants en temps réel
  • :white_check_mark: Le backend publie les messages MessageBus avec succès
  • :white_check_mark: Le message MessageBus est reçu sur la page du participant (les journaux affichent “:bell: MessageBus message received !”)
  • :white_check_mark: Le modèle est mis à jour avec le nouveau nombre de participants (les journaux affichent le nombre passant de 0 à 1)

Ce qui ne fonctionne pas :

  • :cross_mark: Les autres spectateurs qui regardent le sujet de loterie ne voient pas le nombre se mettre à jour en temps réel
  • :cross_mark: La page du créateur de la loterie ne se met pas à jour
  • :cross_mark: La page de tout autre utilisateur ayant le sujet de loterie ouvert ne se met pas à jour
  • Ils doivent tous actualiser manuellement la page pour voir le nombre de participants mis à jour

De plus, je vois le composant être détruit et recréé plusieurs fois dans la console :

[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27
[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27

Cela suggère que decorateCookedElement est déclenché plusieurs fois, provoquant la destruction et la recréation des composants, ce qui pourrait casser l’abonnement MessageBus pour les autres spectateurs.

Questions

  1. L’utilisation de l’attribut data-decorated sur l’élément cuit est-elle la bonne façon d’empêcher la décoration en double ? L’IA a suggéré cette approche, mais je n’ai pas trouvé de documentation officielle confirmant qu’il s’agit du modèle recommandé.

  2. Pourquoi l’interface utilisateur ne se met-elle pas à jour même si la propriété du modèle suivi change ? Le @tracked _stats devrait déclencher un nouveau rendu lorsqu’il est mis à jour via this.stats = lottery.stats, mais le nombre de participants affiché reste le même. Y a-t-il un problème avec la façon dont j’utilise @tracked ?

  3. Devrais-je utiliser une approche différente entièrement ? J’ai demandé à l’IA de suivre le modèle discourse-calendar, mais peut-être que je manque quelque chose de fondamental sur la façon dont Discourse gère les mises à jour en temps réel.

  4. La suppression du lotteryNode d’origine cause-t-elle le problème ? L’IA a d’abord essayé de ne pas le supprimer, mais cela a fait que la carte de loterie était masquée par le CSS (.cooked > .discourse-lottery { display: none; }). Existe-t-il un meilleur modèle ?

Comme je ne suis pas programmeur, je pose peut-être des questions basiques - toute indication vers la documentation ou des exemples serait incroyablement utile ! Merci !

2 « J'aime »