aller au contenu
FRFlo
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
  1. 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.
  2. 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.
  3. 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 Cookie lors des requêtes sortantes et parsing des en-têtes Set-Cookie en 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 :

  1. GET /login & POST /login (authentification)
  2. GET /MainMenu (root)
  3. POST /MainMenu (planningMenu, découverte de l’ID de formulaire)
  4. 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 notes
const 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ée
const 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.