Skip to content

window.KappappsPrefs (prefs panel custom)

Le dev peut fournir un script/document exécuté dans le formulaire de préférences du streamer (à la Stream Deck Property Inspector), avec lecture/écriture temps réel des préférences via window.KappappsPrefs. Deux étages, cumulables : un script global (déclaré dans infos.json, monté au-dessus de tout le formulaire) et des zones WU customPanel (ciblées dans l'arbre de préférences, preferences.config.js).

Déclaration (infos.json)

  • prefs-panel (dossier) / prefs-panel-index (fichier d'entrée) — miroir de streamer-plugin / streamer-plugin-index. Monté au-dessus de tout le formulaire, peu importe la validité de la structure de préférences.
  • ⚠️ La zone du script global démarre repliée à 0 px : appelle KappappsPrefs.resize(h) ou autoResize() dans onReady pour la rendre visible. Un script purement logique (sans UI) peut simplement ne jamais resize — il reste invisible, c'est voulu.
  • Le fichier d'entrée (et wuOptions.source d'un customPanel, voir plus bas) peut être .html/.htm (servi tel quel) ou .js (coquille HTML générée par le serveur, CSP durcie, charge le SDK puis ton script — voir « Sucre .js » plus bas).

WU customPanel (zone ciblée)

Dans preferences.config.js :

js
demoPanel: {
    type: 'customPanel',
    label: { en: 'My panel', fr: 'Mon panneau' },
    wuOptions: { source: 'zone.js', height: 200, autoResize: true }
}
  • wuOptions.source (obligatoire) : fichier relatif au dossier prefs-panel (.html/.htm/.js) — pas de traversal (.., chemin absolu).
  • wuOptions.height : hauteur initiale en px, clampée 0–2000 (défaut 260).
  • wuOptions.autoResize : true laisse le panneau piloter lui-même sa hauteur (KappappsPrefs.autoResize()).
  • Un customPanel n'a aucune valeur propre (jamais mappé — getModelPreferences() expose sa clé à null, comme un viewer) et est interdit comme collectionItems d'une collection (CRIT à la validation de structure). La clé prefs-panel doit exister dans infos.json, sinon CRIT aussi.

window.KappappsPrefs

Singleton global disponible dans tout fichier prefs-panel (global ou WU) une fois le SDK chargé (<script src="/assets/prefs_panel_sdk/sdk.js">).

MéthodeRôle
onReady(cb)Appelé une fois le pont établi, avec {values, language, appli, version, kind} (kind : 'global' ou 'workUnit'). values est le snapshot complet des préférences au moment du handshake.
getValues(paths?)Promise<Record<string, unknown>> — relit les préférences depuis le formulaire, toujours un aller-retour frais (pas le snapshot d'onReady). Sans paths, retourne l'arbre complet.
setValue(path, value) / setValues(changes)Promise<{applied, errors}> — écrit une ou plusieurs préférences via le wu.setValue() natif du champ ciblé (validations/hideIf du champ toujours appliquées côté form).
onChange(cb(path, value)) + watch(paths)S'abonner aux changements d'un ou plusieurs chemins — voir « watch = préfixe » ci-dessous.
onSubmit(cb)Appelé juste avant la sauvegarde du formulaire.
onState(cb({hidden, disabled}))Zones WU uniquement : notifié quand le customPanel est caché/désactivé par un hideIf/disableIf, ou lors du submit. Jamais émis pour le script global (pas de WU associé).
resize(height)Redimensionne l'iframe (pré-clampé 0–2000 côté SDK, re-clampé et throttlé 100 ms côté hôte).
autoResize(target = document.body)Observe target (ResizeObserver) et appelle resize() automatiquement à chaque changement de taille.
box({sticky?, collapsed?, hidden?})Modificateurs visuels du wrapper (position: sticky, hauteur forcée à 0, display: none).
broadcast(data) / onBroadcast(cb)Relais entre les iframes prefs-panel de la même app (script global + zones WU). Jamais de contact direct iframe↔iframe.

Pattern (script global : lire puis écrire)

js
KappappsPrefs.onReady(async (info) => {
    const values = await KappappsPrefs.getValues(['maSection.monChamp']);
    console.log(values['maSection.monChamp']);

    KappappsPrefs.onChange((path, value) => {
        if (path === 'maSection.monChamp') console.log('changé →', value);
    });
    KappappsPrefs.watch(['maSection.monChamp']);

    await KappappsPrefs.setValue('maSection.monChamp', 'nouvelle valeur');
});

Sucre .js (coquille générée)

Si prefs-panel-index (ou wuOptions.source d'un customPanel) pointe vers un .js, le serveur génère automatiquement une coquille HTML (CSP script-src 'self') qui charge le SDK puis ton script — tu n'écris QUE le .js, aucun HTML :

js
KappappsPrefs.onReady((info) => {
    document.body.textContent = 'kind: ' + info.kind;
    KappappsPrefs.autoResize(document.body);
});

Exemple complet

apps/testing/1.0.0/prefs-panel/ (script global index.html/index.js + zone WU .js zone.js, déclarés par l'onglet « Prefs panel (custom dev script) » de config/preferences.config.json).

Pièges

  • Script global invisible ? Sa zone démarre à 0 px : sans resize()/autoResize() dans onReady, ton panneau est monté mais ne se voit pas.
  • Fichier .html : reset les marges UA ET le box-sizing (html, body { margin: 0 } + *, *::before, *::after { box-sizing: border-box }) — sinon 8 px de marge navigateur et les paddings en content-box parasitent le rendu et l'auto-resize. La coquille générée du sucre .js fait les deux pour toi.
  • kappapps.app/assets/styles/kappstrap.css dans ton panel : autorisé par la CSP (style-src https:), mais kappstrap pose body { width: 100% } en supposant le reset border-box de Bootstrap, chargé par le site mais PAS par ton iframe — sans le reset ci-dessus, débordement horizontal garanti.
  • Sandbox opaque (allow-scripts sans allow-same-origin) : ton origine est 'null' — pas de localStorage/cookies/accès au parent. Le pont passe exclusivement par window.KappappsPrefs.
  • CSP durcie script-src 'self' : ni CDN, ni <script> inline. Tout ton JS doit vivre dans un fichier séparé chargé en <script src="..."> relatif — y compris pour le fichier .html déclaré en prefs-panel-index (voir l'exemple ci-dessus).
  • Remount à chaque sauvegarde : le formulaire de préférences est un fragment qui se recharge intégralement à chaque save (nouvelle iframe, nouveau onReady). Ton état JS en mémoire ne survit pas — persiste ce qui compte via une préférence (setValue), jamais dans une variable globale.
  • watch = préfixe de segment : observer un chemin de groupe (maSection) couvre déjà tous ses descendants (maSection.*) — inutile de lister chaque sous-champ.
  • getValues() fait toujours un aller-retour : ce n'est pas une lecture du snapshot d'onReady, await-le à chaque fois où tu veux une valeur fraîche.
  • customPanel n'a aucune valeur propre : ne t'attends pas à lire quoi que ce soit à son propre chemin — passe par d'autres champs du formulaire.