Skip to Content

Chapitre 8.3 — Le lancement

⏱️ TL;DR — C’est ici que cmi5 comble le premier trou de xAPI. Le LMS lance l’AU en ouvrant son <url> avec cinq paramètres d’URL : endpoint (l’URL du LRS), fetch (une URL à POSTer pour obtenir un jeton d’auth), actor (l’apprenant en JSON), registration (l’UUID de la tentative) et activityId (l’id de cet AU). L’AU POSTe fetch, reçoit {"auth-token":"…"}, puis lit ses données de lancement (LMS.LaunchData) via la State API : launchMode (Normal/Browse/Review), masteryScore, moveOn, returnURL. Une fois armé, il envoie son premier statement, initialized. Ce chapitre te fait dérouler ce protocole, ligne par ligne.

🎯 Objectifs

  • Connaître les cinq paramètres d’URL de lancement et le rôle de chacun.
  • Récupérer le jeton d’auth en POSTant l’URL fetch.
  • Lire LMS.LaunchData via la State API et exploiter launchMode.
  • Distinguer les trois modes de lancement (Normal, Browse, Review) et leur effet sur le suivi.
  • Diagnostiquer un lancement cmi5 qui échoue en nommant l’étape fautive.

Les cinq paramètres d’URL

Quand l’apprenant clique sur l’AU dans le LMS, celui-ci ouvre la <url> de l’AU en y greffant une chaîne de requête. Voici une URL de lancement réelle (ici l’AU « quiz » de FormaCampus), telle que le LMS la construit :

GET https://formacampus.fr/au/quiz/index.html ?endpoint=https%3A%2F%2Flrs.formacampus.fr%2Fxapi%2F &fetch=https%3A%2F%2Fmoodle.ecole.fr%2Fcmi5%2Ffetch%3Fk%3D9f2a7c &actor=%7B%22objectType%22%3A%22Agent%22%2C%22account%22%3A%7B...%7D%7D &registration=76c3f2b1-0f1a-4e2d-9a11-2b6c8e1d4f0a &activityId=https%3A%2F%2Fformacampus.fr%2Fau%2Fquiz

Décodons chaque paramètre (les valeurs arrivent URL-encodées) :

ParamètreRôleExemple (décodé)
endpointL’URL du LRS où l’AU enverra ses statementshttps://lrs.formacampus.fr/xapi/
fetchUne URL à POSTer pour récupérer le jeton d’authhttps://moodle.ecole.fr/cmi5/fetch?k=9f2a7c
actorL’apprenant, en JSON xAPI (un Agent){"objectType":"Agent","account":{...}}
registrationL’UUID de la tentative (relie tous les statements de la session)76c3f2b1-0f1a-4e2d-…
activityIdL’id de cet AU (celui du cmi5.xml)https://formacampus.fr/au/quiz

⚠️ Piège — Croire que l’AU a le droit de parler au LRS dès qu’il a l’endpoint. Non. L’endpoint seul ne suffit pas : l’AU n’a aucune crédential tant qu’il n’a pas POSTé le fetch pour obtenir son auth-token. Envoyer un statement avant d’avoir le jeton, c’est un 401 garanti. Le fetch est l’étape d’authentification obligatoire.

Le protocole, de bout en bout

Étape 1 — L’AU lit ses paramètres

Au chargement, l’AU parse sa propre URL :

// 1. L'AU lit ses parametres dans l'URL de lancement. const params = new URLSearchParams(window.location.search) const endpoint = params.get("endpoint") // URL du LRS const fetchUrl = params.get("fetch") // URL a POSTer pour le jeton const actor = JSON.parse(params.get("actor")) // l'apprenant, en JSON xAPI const registration = params.get("registration") // UUID de la tentative const activityId = params.get("activityId") // l'id de CET AU

Étape 2 — L’AU POSTe fetch et reçoit son jeton

L’AU envoie une requête POST (sans corps) vers l’URL fetch. C’est le LMS qui répond :

POST /cmi5/fetch?k=9f2a7c HTTP/1.1 Host: moodle.ecole.fr

Réponse en cas de succès — un JSON avec une clé, auth-token :

{ "auth-token": "d24a4c1e8b7f46a2b0c9e5f1a3d7b820" }

En cas d’échec, le LMS renvoie plutôt un couple error-code / error-text :

{ "error-code": "3", "error-text": "fetch URL déjà consommée" }

Le code JavaScript :

// 2. L'AU POSTe l'URL "fetch" pour recuperer son jeton d'auth. // NB : "fetch" est le NOM du parametre cmi5 ; a ne pas confondre // avec la fonction fetch() de JavaScript utilisee ci-dessous. async function recupererJeton(fetchUrl) { const reponse = await fetch(fetchUrl, { method: "POST" }) const data = await reponse.json() if (data["auth-token"]) return data["auth-token"] throw new Error(data["error-text"] || "echec de la recuperation du jeton") }

⚠️ Piège — L’URL fetch est à usage unique. Un rechargement de page qui re-POSTe le même fetch reçoit une erreur (error-code non nul) : le LMS considère ce jeton déjà délivré. Récupère le jeton une fois au lancement et conserve-le côté AU (mémoire, session storage) pour toute la session. Ne re-POSTe pas fetch à chaque appel LRS.

Étape 3 — L’AU lit LMS.LaunchData

Muni du jeton, l’AU ne fonce pas envoyer initialized : il lit d’abord ses données de lancement, stockées par le LMS dans un state particulier nommé LMS.LaunchData, récupérable via la State API de xAPI. Le jeton va dans l’en-tête Authorization :

GET https://lrs.formacampus.fr/xapi/activities/state ?activityId=https%3A%2F%2Fformacampus.fr%2Fau%2Fquiz &agent=%7B%22objectType%22%3A%22Agent%22%2C...%7D &registration=76c3f2b1-0f1a-4e2d-9a11-2b6c8e1d4f0a &stateId=LMS.LaunchData Authorization: Basic d24a4c1e8b7f46a2b0c9e5f1a3d7b820 X-Experience-API-Version: 1.0.3

La réponse est un JSON de configuration de la session :

{ "contextTemplate": { "contextActivities": { "grouping": [{ "id": "https://formacampus.fr/courses/securite-numerique" }] }, "extensions": { "https://w3id.org/xapi/cmi5/context/extensions/sessionid": "s-4f8a12" } }, "launchMode": "Normal", "masteryScore": 0.8, "moveOn": "CompletedOrPassed", "returnURL": "https://moodle.ecole.fr/mod/cmi5/return.php?id=42" }

Ce que l’AU en tire :

  • contextTemplate : un gabarit de contexte que l’AU doit fusionner dans chaque statement « cmi5 defined » qu’il enverra (il contient notamment le sessionid et le regroupement de cours). Tu n’inventes pas ce contexte : le LMS te le dicte.
  • launchMode : Normal, Browse ou Review (voir plus bas).
  • masteryScore : le seuil de réussite (ici 0.8) — l’AU doit l’utiliser pour trancher passed/failed.
  • moveOn : la condition de satisfaction (chapitre 8.5).
  • returnURL : où renvoyer l’apprenant quand l’AU se termine (le bouton « Retour au cours »).
// 3. Avec le jeton, l'AU lit ses donnees de lancement via la State API. async function lireLaunchData(endpoint, activityId, actor, registration, jeton) { const url = new URL(endpoint + "activities/state") url.searchParams.set("activityId", activityId) url.searchParams.set("agent", JSON.stringify(actor)) url.searchParams.set("registration", registration) url.searchParams.set("stateId", "LMS.LaunchData") const reponse = await fetch(url, { headers: { "Authorization": "Basic " + jeton, "X-Experience-API-Version": "1.0.3" } }) return reponse.json() // { launchMode, masteryScore, moveOn, returnURL, ... } }

Les trois modes de lancement

launchMode conditionne ce que l’AU a le droit d’affirmer. C’est un garde-fou essentiel, souvent ignoré.

launchModeSensL’AU peut-il « satisfaire » ?
NormalSession comptée pour de vraiOui : il peut envoyer completed, passed, failed
BrowseAperçu / feuilletageNon : suivi possible, mais pas de complétion/réussite comptée
ReviewRévision d’un AU déjà satisfaitNon : l’apprenant relit, on ne re-statue pas

⚠️ Piège — Émettre passed/completed en mode Review ou Browse. Si un apprenant qui révise un Au déjà validé déclenche un nouveau passed, tu fausses les données et tu peux perturber la satisfaction côté LMS. Toujours lire launchMode avant de décider quoi émettre : en Browse/Review, l’AU trace éventuellement l’activité (« cmi5 allowed ») mais n’envoie pas de statement de complétion/réussite « defined ».

💡 Réflexe — L’ordre est non négociable : (1) lire les paramètres d’URL, (2) POSTer fetch → jeton, (3) lire LMS.LaunchData, (4) seulement ensuite envoyer initialized. Sauter l’étape 3 (ne pas lire launchMode, masteryScore, moveOn) est le bug classique : l’AU se met à statuer sans savoir dans quel mode il tourne ni à quel seuil.

🔌 Côté intégration — Le protocole de lancement cmi5 est exactement ce qui rend un contenu « déployable dans un LMS » sans code sur mesure — c’est le pendant moderne du « find the API » de SCORM. Côté LMS, c’est lui qui génère l’actor, le registration, l’activityId, et qui héberge l’endpoint fetch délivrant le jeton. Si un client te dit « ton contenu ne remonte rien dans notre LMS », le premier réflexe est de vérifier ces quatre échanges dans les logs réseau : URL de lancement complète ? fetch renvoie-t-il un auth-token ? LMS.LaunchData est-il lu ? initialized part-il en Normal ?

🧭 Sur FormaCampus — Premier lancement du module « Sécurité numérique » en cmi5 depuis le Moodle d’une école : rien ne remonte. Diagnostic par étapes. L’URL de lancement est complète (les cinq paramètres présents) ✔. Le POST fetch renvoie bien un auth-token ✔. Mais le GET LMS.LaunchData répond 401. Cause : l’AU envoyait le jeton sans le préfixe attendu dans l’en-tête Authorization. Corrigé, LMS.LaunchData se lit, l’AU découvre launchMode: "Normal", masteryScore: 0.8, et envoie initialized. Le suivi démarre. Nommer l’étape (ici l’auth de l’appel State) a permis d’isoler la panne en minutes.

📚 La spec — Les paramètres de lancement, le mécanisme fetch/auth-token, le state LMS.LaunchData et les valeurs de launchMode sont définis dans la cmi5 Specification d’ADL, sections « Content Launch » et « LMS Launch Data ». La State API (activities/state) et l’en-tête X-Experience-API-Version viennent de la spec xAPI (Partie 7). Détails et version courante sur adlnet.gov.

✏️ Exercices

Exercice 1 — Décode le lancement. On te donne une URL de lancement contenant ...&launchMode absent de l’URL mais endpoint, fetch, actor, registration, activityId présents. Où l’AU trouvera-t-il le launchMode ? Et à quoi sert le fetch puisque l’AU a déjà l’endpoint ?

✅ Solution

Le launchMode n’est pas dans l’URL : il est dans LMS.LaunchData, que l’AU lit via la State API (aux côtés de masteryScore, moveOn, returnURL). Quant au fetch : l’endpoint dit est le LRS, mais l’AU n’a aucune crédential pour lui parler. Le fetch est l’URL (hébergée par le LMS) que l’AU POSTe pour obtenir son auth-token, à placer ensuite dans l’en-tête Authorization de tous ses appels LRS. Sans ce jeton : 401.

Exercice 2 — Diagnostique. Un AU cmi5 « fonctionne chez le dev » mais, chez le client, chaque appel au LRS renvoie 401. Le POST fetch, lui, réussit et renvoie un auth-token. Quelles pistes ?

✅ Solution

Le fetch réussit : le problème est après, dans l’usage du jeton. Pistes, de la plus fréquente à la moins : (1) le jeton n’est pas placé (ou mal placé) dans l’en-tête Authorization des appels LRS — préfixe manquant, en-tête absent ; (2) l’en-tête obligatoire X-Experience-API-Version manque ou a une valeur incompatible ; (3) le jeton a été re-demandé (re-POST du fetch à usage unique) et le second est invalide ; (4) l’endpoint utilisé pour construire l’URL du LRS diffère de celui reçu (barre finale, chemin /xapi/ oublié). « Chez le dev » ça passait sans doute parce qu’un LRS de test acceptait des crédentials en dur — ce que le LRS du client, lui, refuse.

🧠 Quiz de révision

1. Quels sont les cinq paramètres d’URL de lancement cmi5 ?

endpoint (URL du LRS), fetch (URL à POSTer pour le jeton d’auth), actor (l’apprenant en JSON xAPI), registration (UUID de la tentative) et activityId (l’id de l’AU lancé, celui du cmi5.xml).

2. Comment l’AU obtient-il le droit de parler au LRS ?

Il POSTe l’URL fetch (hébergée par le LMS) et reçoit un JSON {"auth-token":"…"}. Il place ensuite ce jeton dans l’en-tête Authorization de tous ses appels au LRS. L’endpoint seul ne donne aucun droit.

3. Qu’est-ce que LMS.LaunchData et comment le lit-on ?

C’est un state posé par le LMS, lu via la State API (activities/state, stateId=LMS.LaunchData). Il contient launchMode, masteryScore, moveOn, returnURL et un contextTemplate à fusionner dans les statements cmi5 defined.

4. À quoi servent les modes Browse et Review ?

Ce sont des launchMode où l’AU ne doit pas statuer : Browse = aperçu/feuilletage, Review = révision d’un AU déjà satisfait. Dans ces modes, pas de completed/passed/failed comptés. Seul Normal autorise la satisfaction.

5. Pourquoi ne faut-il POSTer l’URL fetch qu’une seule fois ?

Parce qu’elle est à usage unique : le LMS ne délivre le jeton qu’une fois. Un re-POST (ex. rechargement de page) reçoit une erreur error-code. Il faut récupérer le jeton une fois et le conserver pour toute la session.


Chapitre suivant : Les verbes cmi5 — le cycle imposé de launched à terminated, et la différence entre « cmi5 defined » et « cmi5 allowed ».

Last updated on