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) etactivityId(l’id de cet AU). L’AU POSTefetch, 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.LaunchDatavia la State API et exploiterlaunchMode. - 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
®istration=76c3f2b1-0f1a-4e2d-9a11-2b6c8e1d4f0a
&activityId=https%3A%2F%2Fformacampus.fr%2Fau%2FquizDécodons chaque paramètre (les valeurs arrivent URL-encodées) :
| Paramètre | Rôle | Exemple (décodé) |
|---|---|---|
endpoint | L’URL du LRS où l’AU enverra ses statements | https://lrs.formacampus.fr/xapi/ |
fetch | Une URL à POSTer pour récupérer le jeton d’auth | https://moodle.ecole.fr/cmi5/fetch?k=9f2a7c |
actor | L’apprenant, en JSON xAPI (un Agent) | {"objectType":"Agent","account":{...}} |
registration | L’UUID de la tentative (relie tous les statements de la session) | 76c3f2b1-0f1a-4e2d-… |
activityId | L’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’endpointseul ne suffit pas : l’AU n’a aucune crédential tant qu’il n’a pas POSTé lefetchpour obtenir sonauth-token. Envoyer un statement avant d’avoir le jeton, c’est un 401 garanti. Lefetchest 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.frRé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
fetchest à usage unique. Un rechargement de page qui re-POSTe le mêmefetchreçoit une erreur (error-codenon 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 pasfetchà 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
®istration=76c3f2b1-0f1a-4e2d-9a11-2b6c8e1d4f0a
&stateId=LMS.LaunchData
Authorization: Basic d24a4c1e8b7f46a2b0c9e5f1a3d7b820
X-Experience-API-Version: 1.0.3La 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 lesessionidet le regroupement de cours). Tu n’inventes pas ce contexte : le LMS te le dicte.launchMode:Normal,BrowseouReview(voir plus bas).masteryScore: le seuil de réussite (ici0.8) — l’AU doit l’utiliser pour trancherpassed/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é.
launchMode | Sens | L’AU peut-il « satisfaire » ? |
|---|---|---|
Normal | Session comptée pour de vrai | Oui : il peut envoyer completed, passed, failed |
Browse | Aperçu / feuilletage | Non : suivi possible, mais pas de complétion/réussite comptée |
Review | Révision d’un AU déjà satisfait | Non : l’apprenant relit, on ne re-statue pas |
⚠️ Piège — Émettre
passed/completeden modeReviewouBrowse. Si un apprenant qui révise un Au déjà validé déclenche un nouveaupassed, tu fausses les données et tu peux perturber la satisfaction côté LMS. Toujours lirelaunchModeavant de décider quoi émettre : enBrowse/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) lireLMS.LaunchData, (4) seulement ensuite envoyerinitialized. Sauter l’étape 3 (ne pas lirelaunchMode,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, leregistration, l’activityId, et qui héberge l’endpointfetchdé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 ?fetchrenvoie-t-il unauth-token?LMS.LaunchDataest-il lu ?initializedpart-il enNormal?
🧭 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
fetchrenvoie bien unauth-token✔. Mais le GETLMS.LaunchDatarépond 401. Cause : l’AU envoyait le jeton sans le préfixe attendu dans l’en-têteAuthorization. Corrigé,LMS.LaunchDatase lit, l’AU découvrelaunchMode: "Normal",masteryScore: 0.8, et envoieinitialized. 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 stateLMS.LaunchDataet les valeurs delaunchModesont définis dans la cmi5 Specification d’ADL, sections « Content Launch » et « LMS Launch Data ». La State API (activities/state) et l’en-têteX-Experience-API-Versionviennent de la spec xAPI (Partie 7). Détails et version courante suradlnet.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 où 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 ?
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 ?
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 ?
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 ».