Appearance
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.htmlcharge 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.jss'exécute après l'eventAppliLoaded(le SDK est prêt,kappexiste). Forme habituelle : une IIFE(async function () { ... })();. Il n'y a PAS dewindow.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é viakapp.apps.run(voirreference/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(aliask) — l'instanceAppli, point d'entrée de tous les services. Immuable (Object.definePropertynon 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.preferences | Config 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.logsStorage | Historique empilé : get(count?) / add(data) / clear() |
kapp.rewards | Récompenses : winner(user) / loser(user) (envoyées au finish()) |
kapp.dynamicTriggers | Triggers à l'exécution : create(data) / update(id, data) / remove(id) ; trigger.onTrigger(cb) |
kapp.plugin | Pont overlay ↔ page viewers : emitViewers(users, ev, data) / emitStreamer(ev, data) / getViewersPluginUrl() |
kapp.apps | Lance une appli oneshot (par slug) ou un widget (par id) et attend sa data → {resolved, data?, reason?}. Générique : run(target, args?, opts?) où 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.textset | Textes 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 »
| Objectif | API |
|---|---|
| Lire une préférence streamer | kapp.preferences.get('global.maClé') ou prefs.global.maClé |
| Persister l'état global de l'appli | await kapp.dataStorage.set({...}) / await kapp.dataStorage.get() |
| Persister l'état d'un viewer | await user.setAppData({...}) / await user.getAppData() |
| Empiler un historique (scores, logs) | await kapp.logsStorage.add({...}) |
| Récompenser des gagnants/perdants | kapp.rewards.winner(user) / kapp.rewards.loser(user) puis kapp.finish() |
| Créer un trigger à l'exécution | const t = await kapp.dynamicTriggers.create({...}); t.onTrigger(async (...) => {...}) |
| Parler à la page des viewers | await kapp.plugin.emitViewers(null, 'event', {...}) |
| Texte traduit | text('maClé', { arg1: x }) |
| Afficher / masquer l'appli | kapp.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 data | const r = await kapp.apps.runApp('mon-slug', {…}); if (r.resolved) {…} |
| Laisser le streamer choisir la cible à lancer | input 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 viewer | await 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 / chat | services 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é silaunch-mode=external).scopes: permissions Twitch requises (chat:read,chat:write, …).services: services exposés par l'appli (voircommon/reference/services.md).run-output: format de sortie pourapps.run({type, description}, typesstring/string_list/int/bool/object/void) ;kind: contrat standard clé-en-main (ex.pick-string). Voirreference/app-to-app.md.window: taille/position.app-index:index.html.textset:textset.js.viewers-plugin/streamer-plugin: pages d'extension (voirreference/plugins.md).viewers-optout:truesi 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 (voirreference/launch-modes.md).prefs-panel/prefs-panel-index: script/document custom exécuté dans le formulaire de préférences du streamer, pontwindow.KappappsPrefs(voirreference/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)
- API V2 uniquement.
kapp.medias(pluriel),kapp.announces,kapp.textToSpeech. Pas de formes V1 (kapp.tts,kapp.playMedia…). - Tout est async :
awaitchaque appel service ; gettersUserréseau aussi (Promise.all). finish()est explicite : une appli ne se termine pas toute seule. Oublierfinish()= rewards jamais envoyées + appli qui reste ouverte.dataStorage(global) ≠userDataStorage(par viewer) ≠preferences(config streamer) : ne les confonds pas.dataStorage.set()écrase tout (pas de merge) : relis puis fusionne si besoin.