Skip to Content

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. Oublier Terminate (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) et Commit (persiste réellement).
  • Savoir pourquoi Terminate / LMSFinish est 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.

TempsSCORM 1.2SCORM 2004Rôle
OuvrirLMSInitialize("")Initialize("")Démarre la communication. Obligatoire avant tout.
ÉcrireLMSSetValue(elt, val)SetValue(elt, val)Pose une valeur (statut, score…) en mémoire côté API.
PersisterLMSCommit("")Commit("")Demande au LMS d’enregistrer ce qui a été posé.
FermerLMSFinish("")Terminate("")Clôt la session ; persiste implicitement le reste.

Deux règles d’or encadrent ce tableau :

  1. Rien avant l’ouverture, rien après la fermeture. Un SetValue avant Initialize ou après Terminate échoue (erreur « not initialized » / « after termination », chapitre 4.5). L’ordre n’est pas indicatif : il est imposé.
  2. 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éflexeCommit régulièrement aux points de progression (fin de section, réponse à un quiz, changement de page), pas à chaque frappe. Un Commit par mini-étape « au cas où » sur-sollicite le serveur ; zéro Commit en dehors de Terminate risque de tout perdre si la fermeture est brutale. Le juste milieu : persister aux jalons et compter sur Terminate pour le reste.

Pourquoi Terminate est vital

Terminate (2004) / LMSFinish (1.2) fait trois choses d’un coup :

  1. Il persiste définitivement tout ce qui n’a pas encore été committé.
  2. Il calcule certaines valeurs de clôture — typiquement cmi.core.session_time (1.2) / cmi.session_time (2004), la durée de la session.
  3. 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. Sans Commit ni Terminate, la valeur peut mourir dans le tampon du navigateur. Toujours clore la session : c’est Terminate/LMSFinish qui 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 un LMSFinish / 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 jamais LMSFinish. 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 : brancher LMSFinish (précédé d’un LMSCommit) sur pagehide/beforeunload, comme dans closeSession(). 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, LMSFinish en 1.2 ; Initialize, Commit, Terminate en 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 que Terminate/LMSFinish persiste 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 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 ?

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 ?

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.

Last updated on