Reverse Engineering Aurion: The aurion-sdk Architecture
/ 9 min read
Table des matières
Across higher education institutions, programmatic access to student records (grades, lecture timetables, attendance summaries) frequently faces a major roadblock: the absence of modern REST APIs. One of the most prevalent administrative software suites across French universities and engineering schools is Aurion, an academic ERP built on the enterprise JavaServer Faces (JSF) framework paired with the PrimeFaces UI component library.
To automate data retrieval from these platforms, developers typically default to deploying headless browsers like Puppeteer or Playwright. However, this approach carries heavy resource overhead: headless Chromium easily consumes 300 to 500 MB of RAM per instance, introduces bloated binary dependencies, and suffers from multi-second execution latency per page transition.
To provide a lightweight, portable, and ultra-fast alternative, I conducted a complete protocol reverse engineering of Aurion’s internal HTTP exchanges and built aurion-sdk (FRFlo/aurion-sdk). This standalone TypeScript SDK directly emulates the HTTP protocol and the JSF component lifecycle, running on any JavaScript runtime (Node.js, Bun, Edge) with a memory footprint under 15 MB and response times an order of magnitude faster.
Here is the technical deep dive into its design, from ViewState lifecycle management to its navigation state tree and dual-layer caching engine.
Architectural Overview of aurion-sdk
aurion-sdk strictly isolates network protocol handling, JSF state graph navigation, and domain normalization into decoupled layers:
flowchart LR
App["Consumer Application"] --> Session["AurionSession<br>(High-Level Public Entrypoint)"]
Session --> Transport["AurionTransport<br>(HTTP Layer & Cookie Jar)"]
Session --> SessionCache["Normalized Value Cache"]
Transport --> TransportCache["Raw Response Cache"]
Session --> Parsers["XML/HTML Parser Engine"]
Transport <-->|"HTTP Fetch Requests"| AurionApp["Aurion Web Portal (JSF Server)"]
Parsers --> Session
AurionSession: The public-facing façade exposing strongly typed asynchronous methods (getGrades(),getPlanning(),getAbsences(),getPromotions()). It orchestrates lazy authentication and coordinates caching tiers.AurionTransport: The network layer handling authentication handshakes, manual HTTP redirect inspection (302), an RFC-compliant in-memory cookie jar (InMemoryCookieJar), and PrimeFaces header injection.- Dedicated Parsers (
parsers/*): Modular, standalone functions converting XML partial responses and HTML fragments into immutable TypeScript data models.
1. Bypassing the JSF Barrier: ViewState & AJAX Submissions
JavaServer Faces is a component-oriented server-side rendering framework where component state is preserved in server memory. Client-server exchanges are not declarative REST queries, but stateful partial form submissions orchestrated by PrimeFaces.
The javax.faces.ViewState Lifecycle
Every HTML view rendered by Aurion includes a mandatory hidden input: javax.faces.ViewState. This cryptographic token represents the exact state of the component tree on the server.
If a client script executes an HTTP POST request without echoing the exact ViewState issued by the previous interaction, the server immediately throws a ViewExpiredException and destroys the session.
sequenceDiagram
autonumber
actor Client as AurionTransport
participant Server as Aurion Server (JSF)
Client->>Server: GET /login (Initial landing page)
Server-->>Client: HTML + Set-Cookie: JSESSIONID + ViewState v1
Client->>Server: POST /login (username, password, ViewState v1)
Server-->>Client: 302 Found (Redirect to /MainMenu) + ViewState v2
Client->>Server: POST /MainMenu (PrimeFaces AJAX, menu navigation, ViewState v2)
Server-->>Client: XML partial-response (<update id="form:planning">) + ViewState v3
Note over Client: Regex extraction of new ViewState v3 and payload data
Emulating PrimeFaces AJAX Headers & Payloads
When a user triggers an action in Aurion, PrimeFaces intercepts the event and dispatches an XMLHttpRequest with custom headers. aurion-sdk matches this signature over native Fetch:
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,});The response is an XML <partial-response> containing <update id="..."> sections wrapping raw HTML within <![CDATA[...]]> blocks.
2. The Network Layer: AurionTransport
The AurionTransport module encapsulates all network concerns required to maintain stateful dialogue with a JSF server.
In-Memory Cookie Management (InMemoryCookieJar)
Aurion relies on volatile session cookies (JSESSIONID, load balancer affinity cookies). Because the SDK is designed for runtimes (Node.js, Bun, Edge workers) where native Fetch does not persist cookies across sequential requests, aurion-sdk embeds an RFC-compliant in-memory cookie jar:
- Scopes cookies by domain and URL path boundaries.
- Respects expiration metadata (
Max-Age,Expires). - Injects
Cookierequest headers and parses inboundSet-Cookieheaders automatically.
Manual Inspection of HTTP 302 Redirects
A pitfall when using Fetch with redirect: "follow" is that automated redirection converts POST requests to GET upon encountering a 302 code. This drops intermediate cookie updates and hides Location headers emitted during Aurion’s multi-step authentication handshake.
AurionTransport disables automated redirection and runs a controlled redirection loop (fetchWithManualRedirects) up to a maximum depth of 10 hops:
- Captures intermediate HTTP status codes (301, 302, 303, 307).
- Commits cookies emitted at each hop immediately into the
cookieJar. - Identifies login failure screens and account locks before navigating to the main portal.
Mutex-Style Authentication Deduplication
If multiple parts of an application trigger session.getGrades() and session.getPlanning() simultaneously, AurionTransport prevents duplicate concurrent login attempts using a shared promise lock (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. The Navigation State Tree & Dynamic Form Resolution
In Aurion, views cannot be opened by hitting static URLs directly. Accessing grades or timetables requires traversing a hierarchy: initializing the authenticated root (root), triggering sidebar expansion (*Menu), capturing the server’s dynamically assigned form identifier (formId), and rendering the final target view (*Page).
To manage these transitions deterministically and maximize query throughput, aurion-sdk implements an explicit hierarchical navigation state tree (navigationNodes):
flowchart TD
Root["root (Initial Root State)<br>viewState, idInit, formId"]
GradesMenu["gradesMenu<br>parentId: root"]
PlanningMenu["planningMenu<br>parentId: root"]
AbsencesMenu["absencesMenu<br>parentId: root"]
GradesPage["gradesPage (Grades View)<br>parentId: gradesMenu<br>formIdGrade"]
PlanningPage["planningPage (Timetable View)<br>parentId: planningMenu<br>formIdPlanning"]
AbsencesPage["absencesPage (Attendance View)<br>parentId: absencesMenu"]
Root --> GradesMenu
Root --> PlanningMenu
Root --> AbsencesMenu
GradesMenu --> GradesPage
PlanningMenu --> PlanningPage
AbsencesMenu --> AbsencesPage
Each node in this tree records its identifier, a reference to its parent (parentId), a creation timestamp, and the associated JSF view state:
type AurionNavigationNodeId = | "root" | "gradesMenu" | "gradesPage" | "planningMenu" | "planningPage" | "absencesMenu" | "absencesPage";
interface AurionNavigationNode { id: AurionNavigationNodeId; parentId: AurionNavigationNodeId | null; createdAt: number; state: unknown;}Cascading Recursive Invalidation
The primary strength of modeling session state as a tree is resilience against upstream state desynchronization. When JSF invalidates a component view or encounters a ViewExpiredException (isRecoverableNavigationError), aurion-sdk executes a cascading recursive invalidation (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); } }}If planningMenu is invalidated, its child leaf planningPage is pruned automatically. If the root node expires, the entire tree is purged, forcing a clean re-initialization on the next attempt without leaving orphaned stale nodes.
Request Collapse: From 4 Round-Trips Down to 1
During a cold startup (Cold Tree), reaching the timetable requires a sequence of 4 distinct HTTP round-trips:
GET /login&POST /login(authentication)GET /MainMenu(rootstate)POST /MainMenu(planningMenu, dynamic form resolution)POST /Planning.xhtml(planningPage, timetable payload extraction).
Once the navigation tree is warm (Warm Tree), aurion-sdk bypasses all intermediate hops and queries the cached planningPage node directly. Subsequent requests collapse into a single targeted HTTP POST request, cutting query latency fourfold.
4. Dual-Layer Caching (Transport & Domain Tiers)
Repeatedly querying an enterprise server on every function call introduces unnecessary latency. aurion-sdk includes an integrated two-tier cache configurable via the AurionCacheStore interface:
flowchart TD
Call["SDK Invocation (e.g., getPlanning)"] --> CheckSessionCache{"Domain Value in Cache?"}
CheckSessionCache -->|Yes| ReturnNormalized["Return Cached Typed Model (0 ms)"]
CheckSessionCache -->|No| CallTransport["Dispatch AurionTransport Request"]
CallTransport --> CheckTransportCache{"Raw HTTP Response in Cache?"}
CheckTransportCache -->|Yes| ReturnRaw["Return Cached Raw Body"]
CheckTransportCache -->|No| NetworkReq["Dispatch Network Fetch"]
NetworkReq --> SaveTransport["Commit to Transport Cache"]
SaveTransport --> ReturnRaw
ReturnRaw --> Parse["Parse & Normalize"]
Parse --> SaveSession["Commit to Domain Cache"]
SaveSession --> ReturnNormalized
Tier 1: Transport Cache (transport)
Caches raw HTTP responses (status, headers, body) indexed by HTTP method, target URL, and request payload digest. It avoids duplicate network fetches when multiple components request the same raw page.
Tier 2: Session / Value Cache (session)
Retains parsed and normalized domain objects (AurionGrade[], AurionAbsence[]).
For timetable retrieval (getPlanning), the SDK implements temporal window approximation (planningTimeRangeApproximationMs). If an application queries events from day 1 to 15, and later requests a sub-range within that window, the SDK reuses the cached model without issuing redundant network requests.
5. Lightweight Parsers & Strict Typing
To parse XML partial-responses without importing heavyweight DOM emulators like JSDOM, src/parsers/ relies on lightweight, targeted extractors:
Timetable Extraction (parsers/planning.ts)
Aurion embeds PrimeFaces’ FullCalendar integration. The SDK parses JSON event arrays serialized within XML script blocks, isolating course titles, start/end dates, classroom locations, and teaching staff.
Grade & Attendance Parsing (parsers/grades.ts, parsers/absences.ts)
Processes PrimeFaces ui-datatable markup to extract table rows, normalizes European decimal commas into JavaScript floats, and associates coefficients with course codes:
export interface AurionGrade { code: string; name: string; date: Date; grade: number; average: number; min: number; max: number; coefficient: number;}Error Taxonomy (errors.ts)
All failures are classified under the typed AurionError hierarchy:
export type AurionErrorCode = | "AURION_AUTHENTICATION_ERROR" // Invalid credentials or locked account | "AURION_NAVIGATION_ERROR" // Missing JSF form or expired ViewState | "AURION_PARSING_ERROR" // Upstream HTML/XML structure shift | "AURION_TRANSPORT_ERROR" // Network failure or HTTP timeout | "AURION_UNKNOWN_ERROR";6. Usage Guide
Using the SDK requires initializing a session and calling the desired methods:
import { AurionSession } from "aurion-sdk";
const session = new AurionSession({ username: process.env.AURION_USERNAME!, password: process.env.AURION_PASSWORD!,});
// Fetching gradesconst grades = await session.getGrades();for (const grade of grades) { console.log(`${grade.name}: ${grade.grade}/20 (Coeff: ${grade.coefficient})`);}
// Fetching schedule for a given date rangeconst 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} - Room: ${event.rooms.join(", ")}`, );}Benchmark: aurion-sdk vs Headless Browsers
| Metric | Puppeteer / Playwright | aurion-sdk |
|---|---|---|
| Memory Footprint | ~300 to 500 MB per instance | < 15 MB |
| Startup Latency | 1,500 to 3,000 ms (Chromium boot) | 0 ms (instant) |
| Timetable Fetch Latency | 4,000 to 7,000 ms | 200 to 450 ms |
| Serverless / Edge Compatibility | Complex (requires custom Chromium binaries) | Native (Node, Bun, Workers, Deno) |
| Data Reliability | Bound to CSS selectors and visual rendering | Protocol-level, typed, normalized |
Conclusion
By swapping bulky browser automation for direct protocol reverse engineering, aurion-sdk proves that enterprise legacy software (JSF/PrimeFaces) can be queried reliably with high-performance modern tooling.
With its precise ViewState tracking, hierarchical navigation state tree with cascading invalidation, and dual-layer caching, the library provides a clean, portable foundation for developers needing programmatic access to academic portals.
The source code is open source on GitHub: github.com/FRFlo/aurion-sdk.