Chapitre 4.2 — Le cycle Initialize → Commit → Terminate
⏱️ TL;DR — Une session SCORM a un cycle de vie strict, comme une connexion : on ouvre (
LMSInitialize("")en 1.2,Initialize("")en 2004), on écrit au fil de l’eau (SetValue), on persiste de temps en temps (LMSCommit/Commit), et on ferme proprement (LMSFinish/Terminate). L’appel de fermeture n’est pas une politesse : c’est lui qui garantit que tout est enregistré côté LMS. OublierTerminate(ou le rater parce que l’onglet se ferme d’un coup), c’est risquer de perdre la session entière — le score et la complétion écrits juste avant peuvent ne jamais être persistés. Ce chapitre déroule le cycle, montre la séquence exacte, et donne un mini-SCO complet et robuste.
🎯 Objectifs
- Enchaîner les quatre temps d’une session : ouvrir, écrire, persister, fermer.
- Comprendre la différence entre
SetValue(met en mémoire côté API) etCommit(persiste réellement). - Savoir pourquoi
Terminate/LMSFinishest vital, et comment l’appeler malgré une fermeture d’onglet. - Écrire un mini-SCO complet qui respecte le contrat, en 1.2 comme en 2004.
Les quatre temps d’une session
Le runtime SCORM se pilote comme une ressource qu’on ouvre et qu’on referme. Entre les deux, on lit et on écrit ; mais rien n’a de sens hors de la session ouverte.
| Temps | SCORM 1.2 | SCORM 2004 | Rôle |
|---|---|---|---|
| Ouvrir | LMSInitialize("") | Initialize("") | Démarre la communication. Obligatoire avant tout. |
| Écrire | LMSSetValue(elt, val) | SetValue(elt, val) | Pose une valeur (statut, score…) en mémoire côté API. |
| Persister | LMSCommit("") | Commit("") | Demande au LMS d’enregistrer ce qui a été posé. |
| Fermer | LMSFinish("") | Terminate("") | Clôt la session ; persiste implicitement le reste. |
Deux règles d’or encadrent ce tableau :
- Rien avant l’ouverture, rien après la fermeture. Un
SetValueavantInitializeou aprèsTerminateéchoue (erreur « not initialized » / « after termination », chapitre 4.5). L’ordre n’est pas indicatif : il est imposé. - Un seul cycle par lancement. On initialise une fois, on termine une fois. Réinitialiser une session déjà terminée est une erreur.
SetValue ne persiste pas : Commit, oui
Le malentendu le plus coûteux du runtime : croire que SetValue enregistre. Non. SetValue pose la valeur dans le tampon que gère l’objet API du LMS. Ça peut rester en mémoire du navigateur un bon moment. Ce qui déclenche l’écriture durable côté serveur, c’est :
- un appel explicite à
Commit/LMSCommit, ou - l’appel de fermeture
Terminate/LMSFinish, qui persiste ce qui traîne avant de clore.
api.LMSSetValue("cmi.core.score.raw", "85"); // en mémoire, PAS encore sauvé
api.LMSSetValue("cmi.core.lesson_status", "completed");
api.LMSCommit(""); // MAINTENANT le LMS enregistre. On peut fermer l'onglet.💡 Réflexe —
Commitrégulièrement aux points de progression (fin de section, réponse à un quiz, changement de page), pas à chaque frappe. UnCommitpar mini-étape « au cas où » sur-sollicite le serveur ; zéroCommiten dehors deTerminaterisque de tout perdre si la fermeture est brutale. Le juste milieu : persister aux jalons et compter surTerminatepour le reste.
Pourquoi Terminate est vital
Terminate (2004) / LMSFinish (1.2) fait trois choses d’un coup :
- Il persiste définitivement tout ce qui n’a pas encore été committé.
- Il calcule certaines valeurs de clôture — typiquement
cmi.core.session_time(1.2) /cmi.session_time(2004), la durée de la session. - Il signale au LMS que la tentative est finie, ce qui peut déclencher le rollup (remontée d’état vers l’activité parente) en SCORM 2004.
Oublier cet appel, c’est le piège n°2 du runtime (le n°1 étant le statut, chapitre 4.4). Le symptôme est vicieux : à l’écran, tout va bien ; l’apprenant a « terminé » ; le SetValue("completed") a bien été appelé. Mais comme rien n’a été committé et que la session n’a jamais été close, le LMS peut jeter la session à la fermeture de l’onglet. Résultat : progression perdue, complétion jamais remontée.
⚠️ Piège — Compter sur le seul
SetValue("...completed")pour que ça remonte. SansCommitniTerminate, la valeur peut mourir dans le tampon du navigateur. Toujours clore la session : c’estTerminate/LMSFinishqui scelle le résultat.
Le problème de la fermeture brutale
L’apprenant clique sur la croix de l’onglet. Ton beau Terminate(), prévu « à la fin », n’est jamais appelé. C’est le cas le plus fréquent de perte de données. La parade : brancher la fermeture sur les événements de déchargement de la page.
// Filet de sécurité : tenter une clôture propre si l'utilisateur ferme/quitte.
let terminated = false;
function terminateOnce(api, version) {
if (terminated || !api) return;
terminated = true;
if (version === "1.2") {
api.LMSCommit(""); // on persiste ce qui reste
api.LMSFinish(""); // puis on clôt
} else {
api.Commit("");
api.Terminate("");
}
}
// pagehide couvre la plupart des fermetures/navigations (mobile inclus).
window.addEventListener("pagehide", () => terminateOnce(api, version));
// beforeunload en complément sur desktop.
window.addEventListener("beforeunload", () => terminateOnce(api, version));🔌 Côté intégration — Un LMS n’attend pas que tu sois poli : il applique le contrat. S’il ne reçoit pas de
Terminate, il est en droit de considérer la session comme interrompue et de ne pas valider la complétion. Quand une école te dit « votre module ne valide pas », la première question de l’intégrateur est : « émet-il bien unLMSFinish/Terminate? ». Un SCO qui clôt proprement passe les tests de conformité ; un SCO qui « oublie » de clore les rate.
Un mini-SCO complet
Voici un SCO minimal mais correct : il trouve l’API (chapitre 4.1), ouvre, marque « en cours », persiste, puis, à la complétion, pose le statut et le score et ferme. Il gère les deux versions et le filet de sécurité de fermeture.
// mini-sco.js — squelette de SCO conforme (SCORM 1.2 & 2004)
import { getScormAPI } from "./findAPI.js";
let api = null;
let version = null;
let terminated = false;
// --- Petits adaptateurs qui masquent les différences 1.2 / 2004 ---------
function initialize() {
return version === "1.2" ? api.LMSInitialize("") : api.Initialize("");
}
function setValue(elt, val) {
return version === "1.2"
? api.LMSSetValue(elt, String(val))
: api.SetValue(elt, String(val));
}
function commit() {
return version === "1.2" ? api.LMSCommit("") : api.Commit("");
}
function terminate() {
return version === "1.2" ? api.LMSFinish("") : api.Terminate("");
}
// --- Cycle de vie -------------------------------------------------------
function startSession() {
const found = getScormAPI();
api = found.api;
version = found.version;
if (!api) {
console.warn("[SCORM] Pas d'API : le module ne tracera pas.");
return false; // mode dégradé, on n'appelle rien d'autre
}
initialize();
// Marquer « en cours » dès l'ouverture (le bon réflexe, cf. chapitre 4.4).
if (version === "1.2") {
setValue("cmi.core.lesson_status", "incomplete");
} else {
setValue("cmi.completion_status", "incomplete");
}
commit();
return true;
}
// Appelé quand l'apprenant a réellement fini le contenu.
function completeSession(scoreRaw) {
if (!api) return;
if (version === "1.2") {
setValue("cmi.core.score.raw", scoreRaw); // 0..100
setValue("cmi.core.lesson_status", "completed"); // le statut qui compte
} else {
setValue("cmi.score.raw", scoreRaw);
setValue("cmi.score.scaled", (scoreRaw / 100).toFixed(2)); // -1..1
setValue("cmi.completion_status", "completed");
}
commit(); // on persiste tout de suite
closeSession(); // puis on clôt
}
function closeSession() {
if (terminated || !api) return;
terminated = true;
commit(); // dernier filet
terminate(); // clôture officielle
}
// Filets de fermeture brutale.
window.addEventListener("pagehide", closeSession);
window.addEventListener("beforeunload", closeSession);
// Démarrage.
startSession();
// ... plus tard, à la fin du module : completeSession(85);Ce squelette est volontairement simple, mais il coche toutes les cases du contrat : API trouvée, session ouverte, statut « en cours » puis « terminé », score écrit, Commit aux bons moments, Terminate garanti même en fermeture brutale. C’est exactement ce qui manque dans le module défaillant de FormaCampus.
🧭 Sur FormaCampus — On a établi au chapitre 4.1 que l’API est bien trouvée. En instrumentant le module fautif, on découvre le vrai coupable : le SCO appelle bien
LMSInitialize, mais jamaisLMSFinish. Le développeur avait câblé la clôture sur un bouton « Quitter » que personne ne clique — les stagiaires ferment l’onglet. Conséquence : la session n’est jamais close, le LMS la traite comme interrompue, la complétion ne remonte pas. La correction tient en une ligne conceptuelle : brancherLMSFinish(précédé d’unLMSCommit) surpagehide/beforeunload, comme danscloseSession(). Reste un second bug, sur le statut lui-même, qu’on traite au chapitre 4.4.
📚 La spec — Les noms d’API (
LMSInitialize,LMSCommit,LMSFinishen 1.2 ;Initialize,Commit,Terminateen 2004) et l’ordre imposé (initialiser avant toute écriture, terminer une seule fois) sont définis dans le livre RTE publié par ADL (adlnet.gov). La spec précise queTerminate/LMSFinishpersiste l’état non committé — d’où son caractère non optionnel.
✏️ Exercices
Exercice 1 — Trouver le bug de persistance. Ce SCO 1.2 pose bien le statut, mais l’école signale que « parfois, ça remonte, parfois non ». Explique et corrige.
api.LMSInitialize("");
// ... l'apprenant parcourt le module ...
api.LMSSetValue("cmi.core.lesson_status", "completed");
api.LMSSetValue("cmi.core.score.raw", "90");
// (le code s'arrête là ; la clôture est sur un bouton "Quitter" optionnel)✅ Solution
Le statut et le score sont posés avec LMSSetValue, donc en mémoire seulement. Il n’y a ni LMSCommit ni LMSFinish dans le flux normal : la persistance dépend entièrement du bouton « Quitter » que l’apprenant peut ne jamais cliquer. D’où l’aléatoire (« parfois ça remonte » = quand par chance le LMS a committé, ou quand l’apprenant a cliqué le bouton).
Correction : LMSCommit("") juste après avoir posé statut + score, et un LMSFinish("") garanti — idéalement branché sur pagehide/beforeunload pour couvrir la fermeture d’onglet. Voir completeSession() + closeSession().
Exercice 2 — SetValue après Terminate. Un dev veut « re-sauver le score au cas où » après avoir fermé la session :
api.Terminate("");
api.SetValue("cmi.score.raw", "88"); // sécurité ?Que se passe-t-il ?
✅ Solution
Ça échoue. Après Terminate, la session est close : tout SetValue (ou GetValue, Commit) renvoie false et positionne une erreur du type « après terminaison » (récupérable via GetLastError/GetDiagnostic, chapitre 4.5). Ce n’est pas une sécurité, c’est une erreur silencieuse. La règle : écrire et committer AVANT de terminer, jamais après. Pour « re-sauver », on Commit avant le Terminate, pas après.
🧠 Quiz de révision
1. Quelle méthode ouvre la session en SCORM 1.2 ? En 2004 ?
LMSInitialize("") en SCORM 1.2, Initialize("") en SCORM 2004. Aucune lecture/écriture n’est valide avant cet appel.
2. Quelle est la différence entre SetValue et Commit ?
SetValue et Commit ?SetValue pose une valeur en mémoire côté objet API (rien n’est encore durablement enregistré). Commit (LMSCommit/Commit) demande au LMS de persister ce qui a été posé. SetValue sans Commit (ni Terminate) peut être perdu.
3. Pourquoi Terminate / LMSFinish est-il indispensable ?
Terminate / LMSFinish est-il indispensable ?Parce qu’il persiste l’état non committé, calcule des valeurs de clôture (comme la durée de session) et signale la fin de tentative (déclenchant le rollup en 2004). Sans lui, le LMS peut considérer la session comme interrompue et ne pas valider la complétion.
4. Comment garantir la clôture quand l’apprenant ferme brutalement l’onglet ?
En branchant la clôture (Commit puis Terminate/LMSFinish) sur les événements pagehide et beforeunload, avec un garde terminated pour ne clore qu’une fois. Ne jamais compter sur un bouton « Quitter » optionnel.
5. Peut-on appeler SetValue après Terminate ?
SetValue après Terminate ?Non. Après terminaison, toute lecture/écriture échoue avec une erreur « après terminaison ». On écrit et on committe avant de terminer ; on ne rouvre pas une session close.
Chapitre suivant : 4.3 — Le modèle de données CMI — maintenant qu’on sait ouvrir et fermer, voyons quel vocabulaire on lit et écrit entre les deux.