Reverse Engineering d'Aurion : L'Architecture aurion-sdk
/ 11 min de lecture
Table des matières
Dans le monde de l’enseignement supérieur, l’accès programmatique aux données étudiantes (notes, emplois du temps, bilans d’assiduité) se heurte quasi-systématiquement à une barrière technique : l’absence d’API moderne. L’un des progiciels les plus répandus dans les universités et grandes écoles françaises est Aurion, un ERP universitaire reposant sur la pile d’entreprise JavaServer Faces (JSF) et la bibliothèque de composants UI PrimeFaces.
Pour automatiser l’extraction de ces données, le réflexe habituel consiste à déployer un navigateur headless comme Puppeteer ou Playwright. Pourtant, cette approche est un gouffre en ressources : un navigateur consomme facilement 300 à 500 Mo de mémoire vive par instance, impose des dépendances binaires lourdes, et affiche une latence de plusieurs secondes par opération.
Pour offrir une alternative légère, portable et ultra-rapide, j’ai entrepris la rétro-ingénierie complète des flux réseau d’Aurion et développé aurion-sdk (FRFlo/aurion-sdk). Ce SDK TypeScript autonome émule directement le protocole HTTP et le cycle de vie des formulaires JSF, s’exécutant sur n’importe quel runtime JavaScript (Node.js, Bun, Edge) avec une empreinte mémoire inférieure à 15 Mo et des temps de réponse d’un ordre de grandeur plus rapides.
Voici le deep dive technique de sa conception, de la gestion du jeton ViewState à son arbre d’états de navigation et son moteur de cache à double niveau.
Vue d’ensemble de l’Architecture de aurion-sdk
aurion-sdk sépare strictement la gestion du protocole réseau, la navigation dans l’arbre d’état JSF et la normalisation des données métier au sein de couches découplées :
flowchart LR
App["Application Utilisateur"] --> Session["AurionSession<br>(Point d'Entrée Haut Niveau)"]
Session --> Transport["AurionTransport<br>(Couche Réseau HTTP & Cookies)"]
Session --> SessionCache["Cache de Valeurs Normalisées"]
Transport --> TransportCache["Cache de Réponses Brutes"]
Session --> Parsers["Moteur de Parseurs XML/HTML"]
Transport <-->|"Requêtes HTTP Fetch"| AurionApp["Portail Web Aurion (Serveur JSF)"]
Parsers --> Session
AurionSession: Façade publique exposant des méthodes asynchrones fortement typées (getGrades(),getPlanning(),getAbsences(),getPromotions()). Elle orchestre l’authentification paresseuse (lazy login) et coordonne les couches de cache.AurionTransport: Moteur réseau gérant l’authentification, le suivi manuel des redirections HTTP (302), le conteneur de cookies en mémoire (InMemoryCookieJar) et l’injection des en-têtes PrimeFaces.- Parseurs Spécialisés (
parsers/*) : Modules indépendants convertissant les réponses XML partielles et fragments HTML en structures TypeScript immuables.
1. Dépasser le Verrou de JSF : ViewState et Requêtes AJAX
JavaServer Faces est un framework où l’état des composants de l’interface graphique est conservé en mémoire sur le serveur applicatif. Les échanges client-serveur ne sont pas des appels REST déclaratifs, mais des soumissions partielles de formulaires pilotées par PrimeFaces.
Le Cycle de Vie du javax.faces.ViewState
Chaque page servie par Aurion contient un champ de formulaire caché : javax.faces.ViewState. Ce jeton cryptographique représente l’état exact de l’arborescence des composants côté serveur.
Si un script client émet une requête HTTP POST sans fournir rigoureusement le dernier ViewState délivré par l’interaction précédente, le serveur réagit immédiatement par une exception ViewExpiredException et détruit la session.
sequenceDiagram
autonumber
actor Client as AurionTransport
participant Server as Serveur Aurion (JSF)
Client->>Server: GET /login (Affichage page initiale)
Server-->>Client: HTML + Set-Cookie: JSESSIONID + ViewState v1
Client->>Server: POST /login (username, password, ViewState v1)
Server-->>Client: 302 Found (Redirection /MainMenu) + ViewState v2
Client->>Server: POST /MainMenu (PrimeFaces AJAX, navigation menu, ViewState v2)
Server-->>Client: XML partial-response (<update id="form:planning">) + ViewState v3
Note over Client: Extraction regex du nouveau ViewState v3 et des données
Émulation des En-têtes et Paramètres PrimeFaces AJAX
Lorsqu’un utilisateur clique sur un bouton dans Aurion, la bibliothèque PrimeFaces intercepte l’événement et génère une requête XMLHttpRequest spécifique. aurion-sdk reproduit exactement cette signature réseau via l’API Fetch native :
const PRIMEFACES_AJAX_HEADERS = { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/xml, text/xml, */*; q=0.01", "Faces-Request": "partial/ajax", "X-Requested-With": "XMLHttpRequest",} as const;
const payload = new URLSearchParams({ "javax.faces.partial.ajax": "true", "javax.faces.source": menuId, "javax.faces.partial.execute": menuId, "javax.faces.partial.render": "form:corpsPage", [menuId]: menuId, "javax.faces.ViewState": currentViewState,});La réponse renvoyée n’est pas du JSON, mais un document XML <partial-response> contenant des blocs <update id="..."> encapsulant des fragments HTML bruts au sein de sections <![CDATA[...]]>.
2. Le Moteur Réseau : AurionTransport
La couche AurionTransport isole toute la plomberie réseau nécessaire pour maintenir une conversation cohérente avec un serveur JSF.
Gestion des Cookies en Mémoire (InMemoryCookieJar)
Aurion repose sur des cookies de session volatils (JSESSIONID, cookies de routeurs et équilibreurs de charge). Comme le SDK est conçu pour tourner sur des environnements variés (Node.js, Bun, workers serverless) où l’API Fetch ne persiste pas automatiquement les cookies entre requêtes, aurion-sdk embarque son propre gestionnaire conforme RFC :
- Association des cookies par domaine et par chemin d’URL.
- Prise en compte des directives d’expiration (
Max-Age,Expires). - Injection automatique des en-têtes
Cookielors des requêtes sortantes et parsing des en-têtesSet-Cookieen retour.
Suivi Manuel des Redirections HTTP 302
L’un des pièges fréquents avec l’API Fetch standard est le comportement redirect: "follow". Lors d’une redirection automatique, le runtime suit le code 302 en passant d’une méthode POST à une méthode GET, ce qui empêche d’intercepter les cookies intermédiaires ou les paramètres de localisation (Location) émis à chaque étape de la phase de login d’Aurion.
AurionTransport désactive les redirections automatiques et implémente une boucle de suivi manuel (fetchWithManualRedirects) jusqu’à une limite de 10 rebonds :
- Interception de chaque transition de statut HTTP (301, 302, 303, 307).
- Sauvegarde immédiate des cookies émis lors de chaque palier dans le
cookieJar. - Détection des pages de blocage ou d’erreurs de mot de passe avant d’atteindre le tableau de bord principal.
Déduplication des Requêtes d’Authentification
Si plusieurs composants d’une application appellent simultanément session.getGrades() et session.getPlanning(), AurionTransport évite d’ouvrir deux sessions concurrentes grâce à un verrou de promesse partagé (loginPromise) :
async login(): Promise<void> { if (this.authenticated) return;
if (!this.loginPromise) { this.loginPromise = this.loginInternal(); }
try { await this.loginPromise; this.authenticated = true; } finally { this.loginPromise = null; }}3. L’Arbre d’États de Navigation et Résolution Dynamique
Dans Aurion, il est impossible d’accéder directement à une page via une URL statique classique. Pour afficher les notes ou le planning, le client doit d’abord interagir avec la racine de session (root), déplier le sous-menu parent (*Menu), ce qui déclenche côté serveur la création d’un formulaire contextuel avec un identifiant dynamique (formId), avant d’atteindre la page finale (*Page).
Pour orchestrer ces transitions de manière déterministe et performante, aurion-sdk implémente un arbre hiérarchique de nœuds de navigation (navigationNodes) :
flowchart TD
Root["root (État Initial Racine)<br>viewState, idInit, formId"]
GradesMenu["gradesMenu<br>parentId: root"]
PlanningMenu["planningMenu<br>parentId: root"]
AbsencesMenu["absencesMenu<br>parentId: root"]
GradesPage["gradesPage (Vue Notes)<br>parentId: gradesMenu<br>formIdGrade"]
PlanningPage["planningPage (Vue Planning)<br>parentId: planningMenu<br>formIdPlanning"]
AbsencesPage["absencesPage (Vue Assiduité)<br>parentId: absencesMenu"]
Root --> GradesMenu
Root --> PlanningMenu
Root --> AbsencesMenu
GradesMenu --> GradesPage
PlanningMenu --> PlanningPage
AbsencesMenu --> AbsencesPage
Chaque nœud de cet arbre mémorise son identifiant, le pointeur vers son parent (parentId), son horodatage de création, et l’état JSF associé :
type AurionNavigationNodeId = | "root" | "gradesMenu" | "gradesPage" | "planningMenu" | "planningPage" | "absencesMenu" | "absencesPage";
interface AurionNavigationNode { id: AurionNavigationNodeId; parentId: AurionNavigationNodeId | null; createdAt: number; state: unknown;}Invalidation Récursive en Cascade
Le point fort de cette modélisation en arbre réside dans sa résilience face aux erreurs d’état du serveur. Lorsque JSF invalide une vue ou qu’un formulaire expire (ViewExpiredException, isRecoverableNavigationError), aurion-sdk applique une invalidation récursive en cascade (invalidateNavigationNode) :
private invalidateNavigationNode(id: AurionNavigationNodeId): void { this.navigationNodes.delete(id);
for (const node of Array.from(this.navigationNodes.values())) { if (node.parentId === id) { this.invalidateNavigationNode(node.id); } }}Si le nœud planningMenu est invalidé, sa feuille planningPage est automatiquement purgée du cache. Si la racine root expire, l’arbre entier s’effondre proprement, forçant le SDK à ré-instancier l’arborescence depuis la page initiale sans laisser de nœuds fantômes orphelins.
Gain de Requêtes : De 4 Allers-Retours à 1 Seul
Sur un démarrage à froid (Cold Tree), atteindre le planning nécessite une séquence de 4 requêtes HTTP distinctes :
GET /login&POST /login(authentification)GET /MainMenu(root)POST /MainMenu(planningMenu, découverte de l’ID de formulaire)POST /Planning.xhtml(planningPage, extraction des événements).
Une fois l’arbre de navigation chaud (Warm Tree), aurion-sdk réutilise directement le nœud planningPage mémorisé. Les requêtes ultérieures sautent toutes les étapes intermédiaires pour exécuter un unique appel HTTP POST direct, divisant la latence par quatre.
4. Cache à Double Niveau : Transport et Métier
Interroger un serveur d’entreprise à chaque appel applicatif crée une latence superflue. aurion-sdk intègre un système de cache à deux étages configurable via l’interface unifiée AurionCacheStore :
flowchart TD
Call["Appel SDK (ex: getPlanning)"] --> CheckSessionCache{"Cache Métier Disponible ?"}
CheckSessionCache -->|Oui| ReturnNormalized["Retour Immédiat Objet Typé (0 ms)"]
CheckSessionCache -->|Non| CallTransport["Requête AurionTransport"]
CallTransport --> CheckTransportCache{"Cache HTTP Transport ?"}
CheckTransportCache -->|Oui| ReturnRaw["Retour Corps XML/HTML du Cache"]
CheckTransportCache -->|Non| NetworkReq["Requête Réseau Aurion"]
NetworkReq --> SaveTransport["Sauvegarde Cache HTTP"]
SaveTransport --> ReturnRaw
ReturnRaw --> Parse["Parseur & Normalisation"]
Parse --> SaveSession["Sauvegarde Cache Métier"]
SaveSession --> ReturnNormalized
Étage 1 : Cache de Transport (transport)
Stocke les réponses HTTP brutes (status, headers, body) indexées par méthode, URL et empreinte du payload. Il évite de répéter des requêtes POST identiques lorsque plusieurs parties de l’application consomment la même page brute.
Étage 2 : Cache de Session / Valeurs (session)
Mémorise les tableaux d’objets métier déjà normalisés (AurionGrade[], AurionAbsence[]).
Pour le planning (getPlanning), le SDK intègre une logique d’approximation temporelle (planningTimeRangeApproximationMs). Si vous demandez les cours du 1er au 15 du mois, puis que vous redemandez une plage légèrement décalée mais déjà couverte par la fenêtre en cache, le SDK réutilise les données locales sans émettre de requête réseau superflue.
5. Moteur de Parseurs et Typage Strict
Pour extraire les informations des blocs XML renvoyés par PrimeFaces sans alourdir le projet avec un moteur de rendu de DOM complet comme JSDOM, le dossier src/parsers/ exploite des extracteurs ciblés et performants :
Planning FullCalendar (parsers/planning.ts)
Aurion utilise en interne le composant FullCalendar de PrimeFaces. Le SDK parse les objets d’événements injectés dans les balises de script XML, décode les identifiants d’événements, extrait la date de début et de fin, et isole les informations de salle et d’enseignant.
Relevé de Notes et Absences (parsers/grades.ts, parsers/absences.ts)
Analyse des balises ui-datatable pour extraire chaque ligne de tableau, convertir les virgules décimales en nombres flottants JavaScript, et structurer les coefficients et moyennes de groupe :
export interface AurionGrade { code: string; name: string; date: Date; grade: number; average: number; min: number; max: number; coefficient: number;}Typage et Gestion des Erreurs (errors.ts)
Le SDK encapsule toutes les anomalies potentielles au sein de la classe AurionError :
export type AurionErrorCode = | "AURION_AUTHENTICATION_ERROR" // Mauvais identifiants ou compte verrouillé | "AURION_NAVIGATION_ERROR" // Formulaire JSF introuvable ou ViewState expiré | "AURION_PARSING_ERROR" // Modification de la structure HTML d'Aurion | "AURION_TRANSPORT_ERROR" // Erreur réseau ou timeout HTTP | "AURION_UNKNOWN_ERROR";6. Guide d’Utilisation
L’utilisation du SDK se résume à instancier une session et à interroger les méthodes souhaitées :
import { AurionSession } from "aurion-sdk";
const session = new AurionSession({ username: process.env.AURION_USERNAME!, password: process.env.AURION_PASSWORD!,});
// Récupération des notesconst grades = await session.getGrades();for (const grade of grades) { console.log(`${grade.name}: ${grade.grade}/20 (Coeff: ${grade.coefficient})`);}
// Récupération du planning sur une période donnéeconst planning = await session.getPlanning({ start: new Date("2026-10-01"), end: new Date("2026-10-31"),});for (const event of planning) { console.log( `[${event.type}] ${event.title} - Salle: ${event.rooms.join(", ")}`, );}Comparatif : aurion-sdk vs Navigateur Headless
| Métrique | Puppeteer / Playwright | aurion-sdk |
|---|---|---|
| Consommation Mémoire | ~300 à 500 Mo par instance | < 15 Mo |
| Temps d’Initialisation | 1 500 à 3 000 ms (lancement Chromium) | 0 ms (immédiat) |
| Latence d’une Requête Planning | 4 000 à 7 000 ms | 200 à 450 ms |
| Compatibilité Serverless / Edge | Complexe (nécessite des builds custom) | Native (Node, Bun, Workers, Deno) |
| Fiabilité des Données | Dépendante du rendu graphique et des sélecteurs CSS | Protocolaire, typée et normalisée |
Conclusion
En abandonnant l’automatisation par navigateur lourd au profit d’une rétro-ingénierie protocolaire directe, aurion-sdk prouve qu’il est possible de dialoguer avec des architectures d’entreprise historiques (JSF/PrimeFaces) avec des performances de premier ordre.
Grâce à son émulation précise du cycle de vie ViewState, son arbre d’états de navigation résilient avec invalidation en cascade, et son cache à double niveau, la bibliothèque offre un pont moderne, typé et léger pour intégrer les données universitaires au sein d’applications contemporaines.
Le code source est disponible en open source sur GitHub : github.com/FRFlo/aurion-sdk.