Appearance
Cycle de vie d'une appli
Une installation conserve ses préférences et ses stockages. L'exécution JavaScript est temporaire : variables, timers et écouteurs disparaissent avec son iframe. Un redémarrage crée une nouvelle exécution (kapp.uuid), sans restaurer automatiquement sa partie en mémoire. Voir stockages, manifeste et déclencheurs et transports.
Démarrage
Conserve le bootstrap : index.html charge le SDK app V2 depuis https://kappapps.app/overlay/app/appli-2.0.js, écoute AppliLoaded sur document, puis charge le style et le script métier. Si tu écris ton propre bootstrap, enregistre cet écouteur avant le chargement du SDK pour ne pas manquer l'événement. À l'entrée de script.js, kapp / k, les préférences et le textset sont initialisés.
AppliLoaded signifie que le SDK est prêt pour ton code, pas que tes assets, services métier ou pages plugins sont prêts. Charge tes ressources, applique les préférences et enregistre tes gestionnaires avant d'accepter les interactions. Une page plugin a un cycle indépendant : son KappappsHost.onReady confirme seulement son bridge local. Voir synchronisation des plugins.
Ton code tourne habituellement dans une IIFE. Il n'y a pas de window.executeAppFunction à déclarer. La fin du script ne ferme pas l'appli : il faut appeler finish() ou abort().
Affichage et sortie
| Méthode | Effet |
|---|---|
kapp.display(): void | Demande l'affichage. Une appli sans visuel n'en a pas besoin. |
kapp.undisplay(): void | Masque sans arrêter le code, les timers ou les interactions. |
await kapp.finish(result?) | Envoie les récompenses accumulées, puis masque, exécute les hooks de fin, nettoie les ressources SDK et signale la fin au parent. |
await kapp.abort(reason = null) | Exécute les hooks d'interruption, nettoie les ressources SDK et signale l'arrêt au parent. Ne distribue pas les récompenses accumulées. |
Le résultat de finish(result) sert à un appelant utilisant kapp.apps. Le retour de la Promise de finish n'est pas un accusé de réception du parent ni un reçu métier de cet appelant.
Échec des récompenses : Appli.finish() les attend avant le chemin commun de fermeture. Si leur requête rejette, finish() rejette et les hooks de fin n'ont pas encore tourné. Les listes de récompenses sont déjà vidées ; ne rappelle pas finish et ne reconstruis pas la liste pour « réessayer » sans vérifier le résultat métier. Une erreur réseau peut laisser la distribution incertaine. Prévois un arrêt explicite après diagnostic, sans redistribuer. Voir récompenses.
Le chemin commun de sortie partage une seule Promise et n'émet qu'un seul signal terminal. Cela ne protège pas tout le travail avant ce chemin, notamment les récompenses d'Appli.finish(). Verrouille les actions terminales avant le premier await : double clic, timer et message plugin peuvent arriver ensemble. Un arrêt forcé peut aussi intervenir pendant une requête ; il ne l'annule pas rétroactivement côté serveur.
Hooks : ordre, erreurs et délai
kapp.onBeforeExit('*', handler): nettoyage commun aux fins et interruptions. Passe explicitement les deux arguments.kapp.onBeforeFinish(handler): travail spécifique à une fin normale.kapp.onBeforeAbort(handler): travail spécifique à une interruption.
Enregistre-les dès l'initialisation. Les handlers sélectionnés sont lancés en parallèle. Pour imposer un ordre, mets les étapes dans un seul handler avec des await successifs. Ne rappelle jamais finish() / abort() depuis un hook : il attendrait sa propre sortie.
Le groupe de hooks dispose de 10 secondes au total. Une exception synchrone ou un rejet transforme la fin en interruption (app-exit-hook-failed) ; une attente trop longue produit app-exit-hook-timeout. Un échec du nettoyage interne produit app-exit-cleanup-failed. Le résultat normal n'est alors pas transmis. Les hooks d'abort ne sont pas rejoués si un hook de finish échoue : place le nettoyage indispensable dans onBeforeExit('*', ...).
Les 10 s ne couvrent ni les récompenses préalables ni tout le nettoyage SDK. Le délai ne peut pas interrompre une boucle JavaScript bloquante et ne cancelle pas la Promise d'un hook. Après les hooks, le SDK libère ses requêtes HTTP, attentes de réponse et ressources médias. Libère tes propres timers, callbacks DOM et abonnements aux services. Pour events.off, conserve la même fonction que celle passée à on.
Un crash, la destruction de l'iframe ou la fermeture du navigateur ne garantit aucun hook. Sauvegarde les étapes importantes pendant l'exécution ; un hook final ne constitue pas à lui seul une stratégie de persistance. beforeunload ne garantit pas davantage la fin d'une écriture réseau.
Recette : une seule action terminale
Variante à intégrer au bootstrap : ajoute dans le HTML deux boutons id="finish" et id="cancel". Ils supposent une surface interactive, par exemple la preview externe ; l'overlay OBS n'est pas cliquable. Le code termine aussi après 30 s. Il ne distribue aucune récompense tant que ton code n'en accumule pas.
js
(() => {
let closing = false;
let terminal = null;
const inputs = new AbortController();
const timer = setTimeout(() => { void close('finish'); }, 30_000);
function stopInputs() {
closing = true;
clearTimeout(timer);
inputs.abort(); // retire les listeners DOM qui utilisent ce signal
}
kapp.onBeforeExit('*', stopInputs); // couvre aussi l'arrêt forcé
function close(status) {
if (terminal) return terminal;
if (closing) return Promise.resolve();
stopInputs(); // verrou synchrone, avant tout appel réseau
terminal = Promise.resolve().then(async () => {
if (status === 'abort') {
await kapp.abort('cancelled');
return;
}
try {
await kapp.finish({completed: true});
} catch (error) {
console.error('Fin impossible ; résultat des récompenses à vérifier', error?.reason, error?.status);
await kapp.abort('finish-failed'); // aucun renvoi des récompenses
}
}).catch(error => {
console.error('Arrêt impossible', error?.reason, error?.status);
});
return terminal;
}
document.getElementById('finish').addEventListener('click', () => { void close('finish'); }, {signal: inputs.signal});
document.getElementById('cancel').addEventListener('click', () => { void close('abort'); }, {signal: inputs.signal});
kapp.display();
})();Si ton appli reçoit aussi du chat ou des messages plugins, refuse les nouvelles actions dès closing, puis désabonne les handlers dans stopInputs. Les opérations déjà parties peuvent encore finir : contrôle à nouveau l'état après une attente et avant une nouvelle mutation. Ce verrou local ne rend pas une opération serveur transactionnelle.