Skip to content

SDK Appli Kappapps — mémo (API V2 réelle)

Tu écris une appli : un package web autonome (jeu ou outil) qu'un streamer installe sur son channel. Contrairement à un widget (éphémère, déclenché par un événement), une appli est persistante : elle est lancée par le streamer et vit jusqu'à ce qu'elle se termine elle-même (kapp.finish()) ou soit fermée.

Contrat d'exécution

  • index.html charge le SDK puis ton code. Pattern standard :
    html
    <script src="https://kappapps.app/overlay/app/appli-2.0.js"></script>
    <script>
      document.addEventListener('AppliLoaded', async () => {
        // injecte style.css + script.js (cache-busté tant que la version n'est pas soumise)
      });
    </script>
  • Ton script.js s'exécute après l'event AppliLoaded (le SDK est prêt, kapp existe). Forme habituelle : une IIFE (async function () { ... })();. Il n'y a PAS de window.executeAppFunction (ça, c'est le contrat widget — à ne pas confondre).
  • kapp.display() affiche la fenêtre de l'appli ; kapp.undisplay() la masque.
  • Fin explicite : await kapp.finish() termine proprement (envoie d'abord les rewards accumulées). kapp.finish(result) renvoie en plus une data à l'appli qui m'a lancé via kapp.apps.run (voir reference/app-to-app.md). kapp.abort('raison') interrompt. Hooks : kapp.onBeforeFinish(fn), kapp.onBeforeAbort(fn), kapp.onBeforeExit(status, fn).

Globaux injectés (NE PAS redéclarer)

  • kapp (alias k) — l'instance Appli, point d'entrée de tous les services. Immuable (Object.defineProperty non writable).
  • prefs — l'arbre complet des préférences streamer (= kapp.preferences.preferences). Lecture seule pratique, pas réactif.
  • text(key, args?) — i18n de l'appli (= kapp.textset.get(key, args)), résolu dans la langue du channel.
  • wait(ms)await wait(1000).
  • $ / jQuery.

Services propres à l'Appli (absents en widget)

kapp.<service>Rôle
kapp.preferencesConfig streamer persistante (get(path), set(changes)) ; global prefs
kapp.dataStorageÉtat global de l'appli (par app+channel) : get() / set(data) / delete()
kapp.userDataStorageÉtat par viewer : set(user_id, data) / reset(labels?) (ou user.getAppData() / user.setAppData())
kapp.logsStorageHistorique empilé : get(count?) / add(data) / clear()
kapp.rewardsRécompenses : winner(user) / loser(user) (envoyées au finish())
kapp.dynamicTriggersTriggers à l'exécution : create(data) / update(id, data) / remove(id) ; trigger.onTrigger(cb)
kapp.pluginPont overlay ↔ page viewers : emitViewers(users, ev, data) / emitStreamer(ev, data) / getViewersPluginUrl()
kapp.appsLance une appli oneshot (par slug) ou un widget (par id) et attend sa data → {resolved, data?, reason?}. Générique : run(target, args?, opts?)target = {type:'app', slug} ou {type:'widget', id} (forme produite par l'input de préférence runTarget, dispatch auto, invalid_target si malformé). Raccourcis : runApp(slug, …) / runWidget(id, …). Découverte : list() (unifié, tagué type), listApps(), listWidgets(). Args validés contre le schéma déclaré de la cible (type string_list dispo) ; appli : sortie déclarable via run-output/kind, unicité (busy) ; widget : id local non portable, sortie non typée, concurrence OK. Scope apps:run. Voir reference/app-to-app.md
kapp.textsetTextes traduits de l'appli : get(key, args)

Services partagés (identiques aux widgets — doc dans common/reference/)

kapp.chat, kapp.emotes, kapp.medias, kapp.announces, kapp.textToSpeech, kapp.users, kapp.twitch, kapp.poll / kapp.prediction, kapp.ai, kapp.dom, kapp.events, kapp.overlay.

« Je veux… → utilise »

ObjectifAPI
Lire une préférence streamerkapp.preferences.get('global.maClé') ou prefs.global.maClé
Persister l'état global de l'appliawait kapp.dataStorage.set({...}) / await kapp.dataStorage.get()
Persister l'état d'un viewerawait user.setAppData({...}) / await user.getAppData()
Empiler un historique (scores, logs)await kapp.logsStorage.add({...})
Récompenser des gagnants/perdantskapp.rewards.winner(user) / kapp.rewards.loser(user) puis kapp.finish()
Créer un trigger à l'exécutionconst t = await kapp.dynamicTriggers.create({...}); t.onTrigger(async (...) => {...})
Parler à la page des viewersawait kapp.plugin.emitViewers(null, 'event', {...})
Texte traduittext('maClé', { arg1: x })
Afficher / masquer l'applikapp.display() / kapp.undisplay()
Terminer l'appli (et payer les rewards)await kapp.finish()
Lister les apps oneshot appelables (+ leurs args)const apps = await kapp.apps.list()
Lancer une autre appli oneshot et attendre sa dataconst r = await kapp.apps.runApp('mon-slug', {…}); if (r.resolved) {…}
Laisser le streamer choisir la cible à lancerinput pref runTarget puis kapp.apps.run(prefs.cible) (voir reference/app-to-app.md)
Renvoyer une data à l'appli qui m'a lancéawait kapp.finish({ … })
Afficher les badges d'un viewer (abo tier/mois, modo, VIP…)await kapp.emotes.getBadges(ev) + ev.getSubscriberMonths() (voir common/reference/emotes.md)
Vérifier le sub/follow actuel d'un viewerawait user.getSubscriptionStatus() / await user.getFollowStatus() (live, scope channel-stats:read)
Laisser le streamer choisir un média (avec un son par défaut fourni)input pref mediaFile + default: { library: 'slug' } puis kapp.medias.playMedia(prefs.monSon) (voir reference/preferences.md)
Jouer un asset public Kappapps (soundboard fourni)await kapp.medias.playLibrary('slug', { volume: 70 }) (catalogue : kapp.medias.library())
Jouer un son / image / TTS / IA / chatservices partagés (voir common/reference/)
Lire/écrire les préférences en temps réel depuis un panneau custom (iframe sandboxée)window.KappappsPrefs dans un fichier déclaré par prefs-panel-index (script global) ou wuOptions.source d'un WU customPanel (voir reference/prefs-panels.md)

infos.json (manifeste de l'appli)

  • launch-mode : oneshot (lancée puis se termine), backtask (fond, sans UI obligatoire), external (fenêtre popout dédiée).
  • externable : ajoute un bouton « lancer en popout » (ignoré si launch-mode=external).
  • scopes : permissions Twitch requises (chat:read, chat:write, …).
  • services : services exposés par l'appli (voir common/reference/services.md).
  • run-output : format de sortie pour apps.run ({type, description}, types string/string_list/int/bool/object/void) ; kind : contrat standard clé-en-main (ex. pick-string). Voir reference/app-to-app.md.
  • window : taille/position. app-index : index.html. textset : textset.js.
  • viewers-plugin / streamer-plugin : pages d'extension (voir reference/plugins.md).
  • viewers-optout : true si l'appli peut cibler un viewer sans qu'il l'ait demandé — il pourra alors se retirer lui-même, et devient invisible pour l'appli (voir reference/launch-modes.md).
  • prefs-panel / prefs-panel-index : script/document custom exécuté dans le formulaire de préférences du streamer, pont window.KappappsPrefs (voir reference/prefs-panels.md).

preferences.config.js

Décrit l'arbre de préférences (groupes + items typés), compilé en config/preferences.config.json par le HUB (transpile). Réutilise des structures via modules/Model.js. Voir reference/preferences.md.

Top pièges (détails dans reference/ et common/PITFALLS.md)

  1. API V2 uniquement. kapp.medias (pluriel), kapp.announces, kapp.textToSpeech. Pas de formes V1 (kapp.tts, kapp.playMedia…).
  2. Tout est async : await chaque appel service ; getters User réseau aussi (Promise.all).
  3. finish() est explicite : une appli ne se termine pas toute seule. Oublier finish() = rewards jamais envoyées + appli qui reste ouverte.
  4. dataStorage (global) ≠ userDataStorage (par viewer) ≠ preferences (config streamer) : ne les confonds pas.
  5. dataStorage.set() écrase tout (pas de merge) : relis puis fusionne si besoin.