API Xamle
L'API REST Xamle expose contenus, programmes, quiz, progression et événements de vos applications. Authentification JWT, réponses JSON enveloppées, collecte analytics par lots et SDK mobile Flutter. Une même API sert le backoffice, les applications mobiles et les intégrations.
URL de base & en-têtes
Toutes les routes sont préfixées par /api/v1. Une documentation Swagger interactive est exposée par le serveur sur /api/docs.
| Environnement | URL de base |
|---|---|
| Production | https://<domaine-api>/api/v1 |
| Développement local | http://localhost:3001/api/v1 |
Identifier l'application : X-App-Key
La plateforme est multi-applications. Chaque requête est rattachée à une application via un en-tête :
| En-tête | Qui l'envoie | Rôle |
|---|---|---|
X-App-Key | Apps mobiles & intégrations | Clé d'application (générée dans la console, rotation possible). Clé invalide ou révoquée → 401. |
X-App-Id | Backoffice web | Identifiant de l'application active, combiné au JWT admin. |
Sans en-tête, la requête est rattachée à l'application par défaut de la plateforme (rétrocompatibilité). Les routes strictement mobiles comme GET /mobile/config exigent X-App-Key.
/api/v1/mobile/configVoir la réponse{
"success": true,
"data": {
"app": { "id": "…", "slug": "xamle", "name": "Xamle" },
"capabilities": { "managesUsers": true, "managesAnalytics": true, "hasFeed": true },
"features": ["engagement.feed", "engagement.agents", "…"],
"branding": { /* thème Design Manager ou null */ },
"profileFields": [ /* champs du profil déclarés */ ]
}
}
Authentification
Deux populations, deux flux — dans les deux cas l'API délivre un couple access token + refresh token (JWT signés RS256) à passer en Authorization: Bearer <accessToken>. L'access token expire après 15 minutes ; le refresh token (7 jours) permet de renouveler la session avec rotation.
Comptes équipe (backoffice)
Connexion par e-mail et mot de passe. Si la 2FA est activée sur le compte, la réponse contient requires2FA et un tempToken à échanger avec le code à 6 chiffres.
# Connexion d'un compte équipe curl -X POST https://<domaine-api>/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "vous@exemple.sn", "password": "…" }' # → { "success": true, "data": { "accessToken": "…", "refreshToken": "…", "expiresIn": 900 } } # ou, si 2FA active : { "requires2FA": true, "tempToken": "…" } → POST /auth/2fa/verify
const res = await fetch("https://<domaine-api>/api/v1/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }) }); const { data } = await res.json(); // { accessToken, refreshToken, expiresIn } const me = await fetch("…/api/v1/auth/me", { headers: { Authorization: "Bearer " + data.accessToken } });
| Route | Description |
|---|---|
POST /auth/login | Connexion (limité à 5 tentatives / 15 min ; compte verrouillé 15 min après 5 échecs). |
POST /auth/2fa/verify | Échange tempToken + code à 6 chiffres contre les tokens. |
POST /auth/refresh | Renouvelle les tokens (rotation du refresh token). |
GET /auth/me | Profil, rôles et permissions effectives du compte connecté. |
GET /auth/invitation/:token · POST /auth/set-password | Flux d'invitation : vérification du lien puis définition du mot de passe. |
POST /auth/logout · POST /auth/logout/all | Révoque la session courante, ou toutes les sessions du compte. |
Apprenants (mobile) — OTP par SMS
Pas de mot de passe côté apprenant : la connexion se fait par numéro de téléphone + code SMS à 4 chiffres (valable 5 minutes). Un mode invité anonyme permet d'explorer l'app ; la session invité est fusionnée au compte lors de la vérification.
| Route | Description |
|---|---|
POST /mobile/auth/guest | Crée une session invité anonyme. |
POST /mobile/auth/otp/request | { "phoneNumber": "+221771234567" } — envoie le code SMS (3 demandes / min max). |
POST /mobile/auth/otp/verify | { phoneNumber, code, guestToken? } → { user, accessToken, refreshToken, expiresIn, isNew, missingFields }. |
POST /mobile/registration | Complète l'inscription avec les champs du profil requis. |
POST /mobile/auth/refresh | Renouvelle les tokens apprenant. |
GET /mobile/auth/me | Profil de l'apprenant + champs de profil manquants. |
Le JWT apprenant embarque l'identifiant de son application : un token émis pour une app est refusé (401) si la requête est faite avec la clé d'une autre app.
Réponses & erreurs
Toutes les réponses sont enveloppées de façon uniforme.
Succès
{ "success": true, "data": { /* payload */ }, "timestamp": "2026-09-07T10:00:00.000Z" }
Listes paginées
Les listes acceptent ?page= et ?limit= et renvoient un bloc meta :
{
"success": true,
"data": [ /* éléments */ ],
"meta": { "page": 1, "limit": 20, "total": 137, "totalPages": 7,
"hasNextPage": true, "hasPrevPage": false }
}
Erreurs
{
"success": false,
"statusCode": 400,
"message": "Validation failed",
"errors": [ /* détails éventuels */ ],
"requestId": "…",
"timestamp": "…",
"path": "/api/v1/…"
}
| Code | Signification |
|---|---|
400 | Validation stricte : toute propriété non prévue par le contrat est refusée (whitelist). Vérifiez les noms de champs. |
401 | Token absent/expiré, clé d'application invalide, ou token émis pour une autre application. |
403 | Permission manquante ou application hors de votre périmètre. |
409 | Conflit d'unicité (enregistrement déjà existant). |
429 | Limite de débit atteinte — réessayez plus tard. |
Chaque réponse porte un requestId (repris de l'en-tête X-Request-ID si fourni) : joignez-le à vos signalements de bug.
Endpoints mobiles
Référence des routes consommées par les applications apprenantes (et le SDK). Sauf mention public, elles exigent un JWT apprenant.
Contenus & programmes
| Route | Description |
|---|---|
GET /contents · /contents/:id | Bibliothèque publiée de l'application (vidéos, audios, documents). |
GET /programs · /programs/:id | Programmes et leur structure. Public (progression incluse si connecté). |
GET /modules/:id | Détail d'un module et de ses leçons. |
GET /glossary | Glossaire de l'application (public). |
POST /glossary/:id/views | Suivi : fiche ouverte par l'apprenant ({ "audio": true } = explication audio écoutée). |
Progression & évaluations
| Route | Description |
|---|---|
POST /progress/lessons/:lessonId | Marque la progression d'une leçon. |
GET /me/programs/:id/progress · /me/modules/:id/progress | Progression de l'apprenant. |
GET /me/stats · /me/rewards | Statistiques et récompenses ; attestation PDF via /me/certificates/:id/pdf. |
GET /quizzes/daily | Quiz du jour (public). |
GET /quizzes/:id · POST /quizzes/:id/attempts | Détail d'un quiz ; soumission d'une tentative, notée côté serveur. Suivi : timeTaken, context (STANDARD · DAILY · EXAM) et examAttemptId optionnels. |
GET /exam-sets · /exam-sets/:id | Séries d'examens blancs (public). |
POST /exam-sets/:id/attempts · /exam-sets/attempts/:attemptId/complete | Suivi d'un passage d'examen blanc : ouverture (renvoie attemptId, à joindre aux tentatives de quiz), puis clôture avec le résultat agrégé calculé côté serveur. |
Feed, profil & codes
| Route | Description |
|---|---|
GET /feed · /feed/:id | Fil d'actualité (si la feature est active) ; interactions POST /feed/:id/like|comment|share|view. |
GET /mobile/profile-fields · POST /mobile/profile-fields/values | Champs du profil déclarés et soumission des valeurs (dont le code agent, validé côté serveur). |
GET /me/notifications · POST /me/devices | Notifications in-app, marquage lu, enregistrement de l'appareil pour le push. |
POST /me/codes/validate · /me/codes/redeem | Validation puis activation d'un code d'accès. Idempotent : un code déjà consommé par le même apprenant confirme (ou rétablit) l'accès au lieu d'échouer ; les refus portent un motif machine reason. |
GET /me/entitlements | Droits d'accès de l'apprenant (programmes débloqués, origine, échéance) : la source de vérité du cadenas côté app. Servi avec un ETag (If-None-Match → 304) ; un push silencieux entitlements_updated signale qu'il faut relire. |
POST /progress/batch | Rejeu d'un lot de remontées de progression accumulées hors ligne (clé d'idempotence par item, progression jamais rétrogradée). POST /quizzes/:id/attempts accepte de même un en-tête Idempotency-Key. |
GET /offline/manifest · POST /offline/download/complete | Manifeste hors ligne (fichiers avec taille, version, disponibilité ; readiness: preparing tant qu'un MP4 n'est pas généré) et résultat d'un téléchargement (réussi / échoué / annulé, cause, octets, réseau). |
Le contrat exact des routes mobiles est exporté en OpenAPI (docs/api/mobile-openapi.json dans le repo) et consultable en interactif sur /api/docs.
API d'administration (backoffice)
Tout ce que fait la console passe par l'API d'administration, sous /api/v1/admin/*. Chaque requête porte le JWT d'un compte équipe (Authorization: Bearer …) et l'en-tête X-App-Id pour désigner l'application de travail. Chaque route exige une permission précise (format module:action) : sans elle → 403. Quelques opérations destructives sont réservées au rôle Admin.
La liste complète, avec les schémas de requête/réponse, est dans Swagger (/api/docs, tri par méthode, recherche intégrée). Ci-dessous : la carte des domaines et leurs routes principales.
Équipe, rôles & applications
| Route | Description · permission |
|---|---|
GET /admin/users · POST /admin/users/invite | Liste de l'équipe · invitation par e-mail — team:read / team:manage |
PUT /admin/users/:id/roles · /applications · /permissions | Remplace rôles cumulables, applications et grants à la carte — team:manage |
GET /admin/users/:id/permissions | Vue {rôles, héritées, additionnelles, effectives} — team:manage |
GET /admin/roles · /admin/permissions | Matrice réelle des rôles · catalogue des permissions — team:read |
GET/POST /admin/applications · POST /admin/applications/:id/rotate-key | Portefeuille d'apps, création (apps:create), rotation de la clé X-App-Key (apps:config) |
GET /admin/applications/mine | Apps du membre courant (alimente le sélecteur) — authentification seule |
Contenus (bibliothèque)
| Route | Description · permission |
|---|---|
GET/POST /admin/contents · PATCH /:id | CRUD des contenus (les « moments » sont un champ du contenu) — contents:read / contents:write |
POST /admin/contents/upload/video | Session d'upload Cloudflare Stream (tus résumable) → {uploadUrl, streamId} |
POST /admin/contents/:id/upload-url → /upload-complete | URL présignée PUT (audio/PDF/image → R2), puis confirmation |
GET /admin/contents/gdrive/files · POST /gdrive/import | Import direct Google Drive → Stream, suivi via GET /:id/import/status |
POST /:id/submit-review · /:id/approve · /:id/publish · /:id/unpublish · /:id/archive | Cycle de vie (approve = contents:moderate) |
/:id/captions/:language · /:id/audio · /:id/shares | Sous-titres, pistes audio multilingues, partage inter-apps |
POST /admin/contents/stream/webhook | Webhook entrant Cloudflare Stream (public, signé webhook-signature) |
Programmes, modules & évaluations
| Route | Description · permission |
|---|---|
GET/POST /admin/programs · /:id/publish · /:id/unpublish | Programmes et leur cycle de vie — programs:read / programs:write |
POST /admin/programs/:id/modules · PATCH /modules/reorder | Rattacher/créer des modules, ordre par glisser-déposer |
GET/POST /admin/modules · /:moduleId/lessons · /lessons/reorder | Modules réutilisables et leçons (contenu ou quiz) |
POST /admin/programs/:id/rules · /admin/modules/:id/rules | Règles de complétion → récompenses (points, badge, attestation) |
POST …/:id/quizzes (programs & modules) | Rattachement N-N des quiz aux modules/programmes |
GET/POST /admin/quizzes · /:id/attempts · /:id/stats | Éditeur de quiz (quizzes:write), tentatives & stats (analytics:read) |
/admin/quiz-schedules · /admin/exam-sets | Planning du quiz du jour · séries d'examens blancs — quizzes:write |
/admin/glossary (+ /categories, /:id/audio) | Glossaire illustré avec audios multilingues — glossary:write |
/admin/rewards | Catalogue des récompenses — programs:write |
Diffusion & engagement
| Route | Description · permission |
|---|---|
GET/POST /admin/feed · PATCH /reorder · /config | Fil d'actualité : ajout, ordre manuel/algo, thèmes — contents:write / feed:config |
POST /admin/notifications/send · GET /admin/notifications · /stats · /devices | Envoi (push FCM, SMS, in-app), historique, livraison — notifications:send / notifications:read |
/admin/campaigns · /:id/activate · /:id/pause · /:id/test | Campagnes : segments, déclencheurs, planification, test — campaigns:manage |
/admin/segments · /presets | Segments d'audience système et personnalisés |
/admin/codes/generate · /admin/codes · /:id/revoke · /batch/:batchId/revoke | Codes d'accès par lot, suivi d'usage, révocation — access-codes:manage |
/admin/agents · /cohorts (+ /:id/zones) · /overview · /export · /:id/send-recap | Agents de terrain : cohortes à objectifs, zones datées, KPIs, export paie, récap SMS — agents:read / agents:manage ; page publique GET /public/agents/:token/recap |
/admin/partners (validate / suspend / code / programs / ledger / payouts) | Fiches partenaires, code, registre financier, versements — partners:manage |
Utilisateurs mobiles, analytics & système
| Route | Description · permission |
|---|---|
/admin/app-users · /:id/activity · /:id/quiz-attempts/:attemptId · /:id/suspend · /:id/reset · /:id/export · /:id/request-deletion | Apprenants : fiche, journal d'activité (cours, quiz, quiz du jour, examens blancs, panneaux), détail d'une tentative, suspension, reset, export & suppression RGPD — app-users:read / app-users:manage |
/admin/profile-fields (+ /reorder) | Champs du profil mobile (types, étapes, ordre) — settings:technical |
/admin/analytics/* | Agrégats : overview, users (rétention D1/D7/D30, acquisition), usage (sessions, heatmap, top écrans), feed, learning (funnel, quiz par module), practice (quiz du jour, examens blancs, glossaire, availability), notifications, revenue, offline, schema-errors — analytics:read / reporting:read |
/admin/dashboard/* · /admin/reporting/exports/* | Widgets du tableau de bord · exports CSV asynchrones — reporting:read |
/admin/design-manager (draft / publish / rollback / preview) | Thème de l'app mobile par versions — design:read / design:manage |
/admin/moderation/reports · /admin/offline/resources | Signalements · ressources téléchargeables hors-ligne — moderation:manage / offline:manage |
/admin/settings/mail · /admin/settings/drive | SMTP et compte de service Google Drive, avec test — settings:technical |
/admin/audit | Journal d'audit append-only, filtrable par utilisateur — audit:read |
Les suppressions d'application, de programme, de quiz, de membre et les remboursements sont réservés au rôle Admin. Les lectures de certains modules d'édition (quiz, glossaire) exigent la permission d'écriture correspondante — un rôle purement « lecture » ne les voit pas.
Collecte analytics
Les événements produit sont envoyés par lots (1 à 500 événements) et acceptés en 202 Accepted : la validation métier et l'agrégation se font ensuite en file de traitement. L'authentification est optionnelle — les événements anonymes sont acceptés, et si un JWT apprenant est présent, l'identité de confiance est résolue côté serveur.
/api/v1/analytics/events202 Accepted · max 500 événements{
"events": [
{
"event_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", // unique — sert à la déduplication
"event_name": "lesson_completed",
"timestamp_occurred": "2026-09-07T14:22:05.120Z",
"anonymous_id": "device_3f9a2b1c", // requis
"session_id": "sess_2cb9e8a1",
"source": "client", // "client" | "server"
"context": { "app_version": "1.4.0", "platform": "android", "language": "fr" },
"properties": { "lesson_id": "…", "program_id": "…" }
}
]
}
// → { "accepted": 1, "rejected": 0, "rejected_ids": [] }
Règles
| Règle | Détail |
|---|---|
| Champs requis | event_id, event_name, timestamp_occurred (ISO 8601), anonymous_id, source. |
| Déduplication | Un event_id déjà reçu est ignoré : renvoyez un lot entier sans risque de doublon. |
| Application | Résolue côté serveur via X-App-Key — n'envoyez pas d'app_id (il serait refusé par la validation stricte). |
| Identité | Si la requête porte un JWT apprenant, l'identifiant utilisateur de confiance remplace le distinct_id déclaré. |
| Tracking plan | Les event_name sont validés contre le tracking plan de la plateforme (docs/analytics/tracking-plan.md) ; un nom inconnu est rejeté au traitement. |
Envoyez timestamp_occurred (moment réel) et, si l'envoi est différé (file hors-ligne), timestamp_sent : la latence d'acheminement reste mesurable.
SDK mobile Flutter
Le SDK officiel est un monorepo de 7 packages Flutter/Dart (repo xamle-mobile-sdk, releases coordonnées — tous les packages portent la même version, actuellement v0.9.1). Chaque package couvre un domaine ; vous n'embarquez que ce dont votre app a besoin. Prérequis : Dart ≥ 3.8, Flutter ≥ 3.24.
| Package | Rôle | |
|---|---|---|
xamle_sdk_core | Initialisation, client HTTP, tokens, capacités | requis |
xamle_sdk_auth | Invité, OTP SMS, profil apprenant | mode managed |
xamle_sdk_content | Programmes, quiz, progression, feed, codes, glossaire, examens | recommandé |
xamle_sdk_analytics | Collecte d'événements fiabilisée (file SQLite, batchs gzip) | recommandé |
xamle_sdk_offline | Téléchargement hors-ligne des programmes/modules | optionnel |
xamle_ui_player | Lecteurs vidéo & audio prêts à l'emploi (moments, vitesse) | optionnel |
xamle_sdk_partner | App « Pro » partenaire : élèves, paiements, gains | optionnel |
Installation (dépendance git)
Le repo est privé et rien n'est publié sur pub.dev : chaque package se consomme en dépendance git, tous épinglés sur la même ref. Les dépendances entre packages sont résolues automatiquement dans le clone.
dependencies:
xamle_sdk_core:
git: { url: https://github.com/Xamle-tech/xamle-mobile-sdk.git, ref: v0.9.1, path: packages/xamle_sdk_core }
xamle_sdk_auth:
git: { url: https://github.com/Xamle-tech/xamle-mobile-sdk.git, ref: v0.9.1, path: packages/xamle_sdk_auth }
xamle_sdk_content:
git: { url: https://github.com/Xamle-tech/xamle-mobile-sdk.git, ref: v0.9.1, path: packages/xamle_sdk_content }
xamle_sdk_analytics:
git: { url: https://github.com/Xamle-tech/xamle-mobile-sdk.git, ref: v0.9.1, path: packages/xamle_sdk_analytics }
# optionnels : xamle_sdk_offline, xamle_ui_player, xamle_sdk_partner (même url/ref)Selon la version de Flutter, pub peut buter sur les dépendances de chemin internes : remplacez alors ref: v0.9.1 par le SHA du commit tagué (git rev-list -n 1 v0.9.1). Gardez impérativement la même ref sur tous les packages.
Initialisation
XamleSdk.init est le point d'entrée unique. Il charge GET /mobile/config (avec cache disque : l'app démarre même hors-ligne dès le second lancement), fige les capacités de l'application et choisit le mode :
final sdk = await XamleSdk.init( appKey: 'xk_live_…', // clé de l'application (console → Réglages) baseUrl: 'https://<domaine-api>/api/v1', // requis // externalUserId: 'user-42', // requis en mode contentOnly uniquement deepLinks: [XamleDeepLink(key: 'program', label: 'Programme')], // écrans notifiables ); sdk.config.name; // nom de l'app sdk.mode; // XamleMode.managed | contentOnly sdk.capabilities.hasFeed; // capacités figées à l'init sdk.features.contentPrograms; // modules activés dans la console
| Mode | Quand | Comportement |
|---|---|---|
managed | L'app gère ses utilisateurs via Xamle (cas normal) | Auth OTP, progression, notifications, analytics complet — routes mobiles. |
contentOnly | Votre système d'utilisateurs existe déjà | Lecture des programmes via /delivery/* avec votre externalUserId ; analytics restreint à 5 événements d'apprentissage. |
Les gates de fonctionnalités sont intégrés : chaque repository vérifie que le module correspondant est activé pour l'app et lève une XamleCapabilityError explicite sinon — vos écrans peuvent aussi tester sdk.features.has(…) pour se masquer.
xamle_sdk_core — le socle
Un client HTTP unique (dio) que tous les packages partagent, avec quatre interceptors : en-tête X-App-Key systématique, Authorization: Bearer automatique, refresh sur 401 protégé par mutex (une seule requête de refresh même si dix appels échouent en même temps), et retry exponentiel sur erreurs 5xx/réseau (300 ms → 800 ms → 2 s).
Les tokens vivent dans un TokenStore chiffré (flutter_secure_storage — Keychain iOS, EncryptedSharedPreferences Android, avec récupération automatique si le stockage est corrompu). Les erreurs sont typées : XamleAuthError, XamleValidationError, XamleNetworkError, XamleCapabilityError, XamleConfigError — un simple on XamleAuthError suffit pour router vers l'écran de connexion.
xamle_sdk_auth — connexion apprenant (managed)
final auth = AuthRepository(); // ⚠️ à créer dès le boot : c'est elle qui branche // le refresh automatique des tokens sur le client // Mode invité (exploration sans compte) final guest = await auth.signInAsGuest(); // Connexion par OTP final req = await auth.requestOtp('+221771234567'); final res = await auth.verifyOtp(phoneNumber: '+221771234567', code: '1234', guestToken: guest.accessToken); // fusionne la session invité if (res.needsProfileCompletion) { // Champs du profil configurés dans la console (dont « Code agent ») final schema = await ProfileRepository().getFields(); await ProfileRepository().submitValues({'full_name': 'Awa Diop', 'code_agent': 'AWA-763'}); }
Autres méthodes : me() (profil + champs manquants), currentAccessToken(), logout() (efface les tokens locaux), updateProfile(…). Les repositories sont sans état : la session persistée vit dans le TokenStore ; la gestion d'état UI (Riverpod, Bloc…) reste celle de votre app.
xamle_sdk_content — contenus & progression
Huit repositories, un par domaine — tous branchés sur le client du core :
| Repository | Méthodes principales |
|---|---|
ProgramsRepository | list(page, limit, search, language, level) · getById(id) — renvoie l'arbre complet Programme → Modules → Leçons (avec URLs de lecture HLS et moments). |
ProgressRepository | reportLesson(lessonId, watchedPercent, completed, positionSeconds) (renvoie les récompenses fraîchement gagnées) · getProgramProgress · getMyStats · getMyRewards · downloadCertificate(earnedId) (PDF). |
QuizzesRepository | getById · getDaily() (quiz du jour) · submitAttempt(quizId, answers, timeTaken) — la correction et les bonnes réponses ne reviennent qu'avec le résultat. |
ExamsRepository | list() · getById(id) — séries d'examens blancs (notation via submitAttempt quiz par quiz). |
FeedRepository | getFeed(cursor, language, limit) (pagination par curseur) · like · comment · share · view(id, watchTime, completed). |
AccessCodesRepository | validate(code) (aperçu avant activation) · redeem(code) · partnerLink() (rattachement partenaire). |
NotificationsRepository | list · unreadCount · markRead/markAllRead · registerDevice(fcmToken, platform) pour le push. |
GlossaryRepository | list(category) — fiches avec média et audios multilingues. |
Les titres sont des I18nString : title.resolve('wo') renvoie la traduction demandée avec repli sur le français. Chaque leçon expose playbackUrl (HLS adaptatif) et moments (puces horodatées) — à passer tels quels au player.
xamle_sdk_analytics — collecte fiabilisée
await XamleAnalytics.instance.start(); // après XamleSdk.init MaterialApp( navigatorObservers: [XamleAnalytics.instance.navigatorObserver], // screen_viewed auto ... ); await XamleAnalytics.instance.track('lesson_completed', properties: {'lesson_id': id, 'program_id': programId});
Sous le capot : file SQLite locale (cap 2 000 événements), envoi par lots gzip toutes les 15 s vers POST /analytics/events, reprise avec backoff exponentiel après coupure, sessions automatiques (timeout 30 min), événements de cycle de vie (app_opened, session_started…) et contexte device injecté (modèle, OS, réseau, version d'app via AnalyticsConfig(appVersion: …)).
1) Le suivi d'écran automatique ne capte que les routes nommées (RouteSettings.name) — nommez vos routes, y compris avec go_router. 2) Utilisez les noms d'événements du tracking plan : un lot contenant un nom inconnu est rejeté au traitement.
xamle_ui_player — lecteurs prêts à l'emploi
Deux widgets autonomes (aucune dépendance au reste du SDK), thémables et localisables :
XamleVideoPlayer( source: XamleVideoSource.network(lesson.playbackUrl!), moments: lesson.moments.map(XamlePlayerMoment.fromJson).toList(), startAt: Duration(seconds: lastPosition), // reprise de lecture onProgress: (position, duration, completed) => progress.reportLesson(lessonId: lesson.id, // toutes les 5 s + pause + fin positionSeconds: position.inSeconds, completed: completed), onMomentTap: (m) => m.isQuiz ? openQuiz(m.targetId) : openGlossary(m.targetId), ) XamleAudioPlayer( source: XamleAudioSource.network(url), title: lesson.title.resolve(), speeds: [1.0, 1.25, 1.5, 2.0], // vitesse, avance/recul 10 s )
XamleVideoPlayer affiche les moments comme marqueurs sur la barre et puces cliquables au bon instant — l'ouverture du quiz ou de la fiche glossaire reste à la main de votre app (onMomentTap). onProgress est calibré pour alimenter reportLesson sans réglage.
xamle_sdk_offline — hors-ligne
OfflineRepository parle au serveur (catalogue des ressources téléchargeables, manifeste des fichiers, journalisation) ; OfflineStore gère l'appareil : download(type, targetId, onProgress) télécharge tous les fichiers d'un programme/module avec progression globale, downloaded() liste le local, localPath(…) donne le chemin à passer à XamleVideoSource.file(…), syncAll() met à jour ce qui a changé, delete(…) libère l'espace.
xamle_sdk_partner — app « Pro » partenaire
Pour l'application des partenaires (auto-écoles) : candidature et suivi d'agrément (me(), apply(…), upload du justificatif), tableau de bord (hub()), liste des élèves filtrable/triable, enregistrement des paiements par tranche (recordPayment — espèces, Wave, Orange Money…, annulable 48 h), encouragements et relances, gains (commissions + primes par période) et outils de partage (affiche PDF avec QR code). L'authentification est celle de xamle_sdk_auth (OTP) sur l'application Pro et sa propre clé.
Ordre d'intégration recommandé (managed)
needsProfileCompletion, affichez les champs de getFields() et soumettez via submitValues.XamleVideoPlayer avec onProgress → reportLesson.Points d'attention
| Sujet | À savoir |
|---|---|
| Versions | Tous les packages sur la même ref. flutter_secure_storage accepté de 9.2.2 à 10.x depuis la v0.9.1 — montez à 0.9.1 si votre app utilise secure_storage 10. |
| Android | Stockage chiffré ⇒ minSdkVersion ≥ 23. Permission INTERNET requise. |
| Audio en arrière-plan | XamleAudioPlayer s'appuie sur video_player : la lecture écran verrouillé demande la configuration native habituelle (background modes iOS / service Android). |
| Accès au repo | Repo git privé : chaque poste et la CI doivent avoir un accès (clé SSH ou token). |
XamleSdk.reset() | Vide le singleton mais pas les tokens ni le cache : pour une déconnexion complète, appelez aussi auth.logout(). |
Sécurité & limites de débit
Permissions
Les comptes équipe portent des permissions au format module:action (ex. contents:write, notifications:send, agents:read), issues de l'union de leurs rôles. Une route protégée sans la permission requise répond 403 — de même qu'une requête sur une application hors de votre périmètre.
Limites de débit
| Périmètre | Limite |
|---|---|
| Global (par client) | 10 req/s · 50 req/10 s · 100 req/min |
POST /auth/login | 5 tentatives / 15 min (puis verrouillage du compte 15 min). |
POST /mobile/auth/otp/request | 3 SMS / minute par client. |
Une réponse 429 indique un dépassement : espacez vos requêtes et privilégiez les envois par lots (analytics) plutôt que les appels unitaires.
Bonnes pratiques
| Faire | Ne pas faire |
|---|---|
| Passer par le SDK côté mobile (refresh, retry et files gérés). | Embarquer des identifiants de compte équipe dans une app cliente. |
| Régénérer la clé d'application en cas de fuite (console → Réglages). | Committer une clé X-App-Key de production dans un repo public. |
Transmettre X-Request-ID pour tracer vos appels. | Rejouer un lot analytics en changeant les event_id (doublons garantis). |