Skip to content

Pièges spécifiques aux applis

Pièges transverses (V1→V2, async, noms de services, arguments chat) : voir common/PITFALLS.md. Ci-dessous, ce qui est propre au runtime appli.

Cycle de vie

  • kapp.finish() est explicite. Une appli ne se termine pas quand script.js finit de s'exécuter (contrairement au widget). Si tu oublies finish(), l'appli reste ouverte et les rewards accumulées ne sont jamais envoyées (sendRewards() est appelé par finish()).
  • Pas de window.executeAppFunction : ton code tourne dans script.js après l'event AppliLoaded (généralement une IIFE (async () => { ... })()).
  • kapp.display() affiche la fenêtre ; pour une appli backtask sans UI, tu n'en as pas besoin.
  • Nettoyage avant sortie : kapp.onBeforeFinish(fn) / kapp.onBeforeAbort(fn) / kapp.onBeforeExit(status, fn) (handlers sync ou async, tous attendus).

Stockage : ne pas confondre les trois

  • kapp.preferences = config du streamer (définie via preferences.config.js, éditée dans le dashboard). Lecture via get(path) / global prefs. prefs n'est pas réactif : relis après un set.
  • kapp.dataStorage = état global de l'appli (un seul jeu de données par app+channel). set(data) écrase tout (pas de merge auto) → relis avec get() et fusionne toi-même.
  • kapp.userDataStorage / user.getAppData() / user.setAppData() = état par viewer. Lève une erreur (AppMethodError) hors contexte appli.
  • kapp.logsStorage = historique empilé (append-only via add()), pour scores/événements ; pas pour de l'état mutable.

Préférences

  • preferences.config.js est compilé en config/preferences.config.json par le HUB (transpile). N'édite pas le .json à la main, il sera écrasé.
  • Les chemins de get() suivent l'arbre des groupes : kapp.preferences.get('global.example.exempleItemTypeString').
  • get(path) renvoie undefined si le chemin n'existe pas — garde tes valeurs par défaut.
  • Le default d'un item mediaFile ne peut référencer QUE la bibliothèque publique Kappapps : default: { library: '<slug>' }. Une clé media (id de fichier channel) en default = CRIT à la validation ; un slug inexistant = CRIT ; un asset dépublié = WARN. La valeur hydratée ({file, volume, position}) se joue telle quelle avec kapp.medias.playMedia(pref).

Rewards

  • kapp.rewards.winner(user) / loser(user) accumulent localement ; rien n'est envoyé tant que sendRewards() (déclenché par finish()) n'a pas tourné.
  • Si un viewer est marqué winner et loser, la dernière valeur écrite gagne.

i18n

  • text(key, args) (global) = kapp.textset.get(key, args). Une valeur de textset.js peut être une string ou une fonction args => string (voir reference/i18n.md).
  • Si la clé est absente, text() renvoie la clé elle-même (pas d'erreur) — vérifie tes clés.

prefs / text existent en appli (l'inverse du widget)

  • Contrairement au widget, prefs et text() sont injectés en appli. Ne copie pas du code widget qui les évite.

Prefs panel custom (window.KappappsPrefs)

  • Sandbox opaque (allow-scripts sans allow-same-origin) : ton origine y est 'null' — pas de localStorage/cookies/accès au parent. Tout passe par window.KappappsPrefs (voir reference/prefs-panels.md).
  • CSP durcie script-src 'self' : ni CDN, ni <script> inline, même dans le fichier .html déclaré en prefs-panel-index. Ton JS vit dans un fichier séparé chargé en <script src="..."> relatif.
  • Remount à chaque sauvegarde du formulaire de préférences (nouvelle iframe, nouveau onReady) : ton état JS en mémoire ne survit pas — persiste ce qui compte via KappappsPrefs.setValue(), jamais dans une variable globale.
  • watch(paths) = préfixe de segment : observer un chemin de groupe couvre déjà tous ses descendants, inutile de lister chaque sous-champ.
  • Un WU customPanel n'a aucune valeur propre : ne t'attends pas à lire quoi que ce soit à son propre chemin.