mirror of
https://github.com/dd060606/WebAurion-API.git
synced 2026-01-18 16:47:26 +01:00
refactor: update session handling and improve view state retrieval
This commit is contained in:
@@ -14,8 +14,7 @@ class NotesApi {
|
||||
return new Promise<NotesList[]>(async (resolve, reject) => {
|
||||
try {
|
||||
// On initialise le ViewState (obligatoire pour avoir une réponse correcteur du backend)
|
||||
// Ici 1_1 correspond au sous-menu 'Mes notes' dans la sidebar
|
||||
await this.session.getViewState("1_1");
|
||||
await this.session.getViewState("Mes notes");
|
||||
const response = await this.session.sendGET<string>(
|
||||
"faces/LearnerNotationListPage.xhtml",
|
||||
);
|
||||
|
||||
@@ -17,8 +17,7 @@ class PlanningApi {
|
||||
return new Promise<PlanningEvent[]>(async (resolve, reject) => {
|
||||
try {
|
||||
// On récupère le ViewState pour effectuer la requête
|
||||
// Ici 1_4 correspond au sous-menu 'Emploi du temps' dans la sidebar
|
||||
let viewState = await this.session.getViewState("1_4");
|
||||
let viewState = await this.session.getViewState("Mon planning");
|
||||
// On envoie enfin la requête pour obtenir l'emploi du temps
|
||||
const params = getJSFFormParams(
|
||||
"j_idt118",
|
||||
|
||||
@@ -1,26 +1,89 @@
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
import PlanningApi from "./PlanningApi";
|
||||
import { getJSFFormParams, getViewState } from "../utils/AurionUtils";
|
||||
import {
|
||||
getJSFFormParams,
|
||||
getName,
|
||||
getSidebarMenuId,
|
||||
getViewState,
|
||||
} from "../utils/AurionUtils";
|
||||
import NotesApi from "./NotesApi";
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
|
||||
export class Session {
|
||||
private client: AxiosInstance;
|
||||
private baseURL: string = "https://web.isen-ouest.fr/webAurion";
|
||||
|
||||
//Permet de sauvegarder le ViewState et le subMenuId pour les réutiliser dans les prochaines requêtes (optimisation)
|
||||
//Cela a pour but d'éviter d'effectuer 3 requêtes lorsque l'on refait la même demande (emploi du temps de la semaine suivante par exemple)
|
||||
private viewStateCache: string = "";
|
||||
private subMenuIdCache: string = "";
|
||||
|
||||
constructor(baseURL: string, token: string) {
|
||||
// Nom et prénom de l'utilisateur
|
||||
private username: string = "";
|
||||
|
||||
constructor() {
|
||||
this.client = axios.create({
|
||||
baseURL,
|
||||
baseURL: this.baseURL,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Cookie: `JSESSIONID=${token}`,
|
||||
"Accept-Language": "fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentifie un utilisateur avec un nom d'utilisateur et un mot de passe.
|
||||
*
|
||||
* @param {string} username - Le nom d'utilisateur de l'utilisateur.
|
||||
* @param {string} password - Le mot de passe de l'utilisateur.
|
||||
* @returns {Promise<void>} Une promesse qui se résout si l'authentification réussit.
|
||||
* @throws {Error} Si l'authentification échoue.
|
||||
*/
|
||||
public login(username: string, password: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("username", username);
|
||||
params.append("password", password);
|
||||
this.client
|
||||
.post("/login", params, {
|
||||
maxRedirects: 0,
|
||||
})
|
||||
.then((response) => {
|
||||
//Si la réponse est 302 et que le cookie est défini alors on retourne une nouvelle session
|
||||
if (
|
||||
response.status === 302 &&
|
||||
response.headers["set-cookie"]
|
||||
) {
|
||||
const token = response.headers["set-cookie"][0]
|
||||
.split(";")[0]
|
||||
.split("=")[1];
|
||||
//On ajoute le cookie JSESSIONID aux en-têtes par défaut pour les prochaines requêtes
|
||||
this.client.defaults.headers.Cookie = `JSESSIONID=${token}`;
|
||||
resolve();
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
//Si la réponse est 302 et que le cookie est défini alors on retourne une nouvelle session
|
||||
if (err.response && err.response.status === 302) {
|
||||
const token = err.response.headers["set-cookie"][0]
|
||||
.split(";")[0]
|
||||
.split("=")[1];
|
||||
//On ajoute le cookie JSESSIONID aux en-têtes par défaut pour les prochaines requêtes
|
||||
this.client.defaults.headers.Cookie = `JSESSIONID=${token}`;
|
||||
resolve();
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//On retourne le nom de l'utilisateur
|
||||
public getUsername(): string {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
// API pour le calendrier
|
||||
public getPlanningApi(): PlanningApi {
|
||||
return new PlanningApi(this);
|
||||
@@ -91,10 +154,10 @@ export class Session {
|
||||
}
|
||||
|
||||
// Récupération du ViewState pour effectuer les différentes requêtes
|
||||
public getViewState(subMenuId: string): Promise<string> {
|
||||
public getViewState(subMenuName: string): Promise<string> {
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
//On optimise l'accès au ViewState
|
||||
if (this.viewStateCache && this.subMenuIdCache === subMenuId) {
|
||||
if (this.viewStateCache && this.subMenuIdCache === subMenuName) {
|
||||
return resolve(this.viewStateCache);
|
||||
}
|
||||
try {
|
||||
@@ -105,8 +168,25 @@ export class Session {
|
||||
if (viewState) {
|
||||
// Ici 291906 correspond au menu 'Scolarité' dans la sidebar
|
||||
// Requête utile pour intialiser le ViewState (obligatoire pour effectuer une requête)
|
||||
await this.sendSidebarRequest("291906", viewState);
|
||||
const sidebarResponse = await this.sendSidebarRequest(
|
||||
"291906",
|
||||
viewState,
|
||||
);
|
||||
|
||||
// On récupère le sidebar_menuid correspondant au sous-menu demandé
|
||||
const subMenuId = getSidebarMenuId(
|
||||
sidebarResponse,
|
||||
subMenuName,
|
||||
);
|
||||
// Vérification de l'existence du subMenuId
|
||||
if (!subMenuId) {
|
||||
return reject(
|
||||
new Error(
|
||||
"Sidebar menu ID not found, subMenuName: " +
|
||||
subMenuName,
|
||||
),
|
||||
);
|
||||
}
|
||||
// On récupère le ViewState pour effectuer la prochaine requête
|
||||
viewState = await this.sendSidebarSubmenuRequest(
|
||||
subMenuId,
|
||||
@@ -114,13 +194,21 @@ export class Session {
|
||||
);
|
||||
if (viewState) {
|
||||
this.viewStateCache = viewState;
|
||||
this.subMenuIdCache = subMenuId;
|
||||
this.subMenuIdCache = subMenuName;
|
||||
return resolve(viewState);
|
||||
}
|
||||
}
|
||||
return reject(new Error("Viewstate not found"));
|
||||
return reject(
|
||||
new Error(
|
||||
"Viewstate not found, subMenuName: " + subMenuName,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
reject(new Error("Viewstate not found"));
|
||||
reject(
|
||||
new Error(
|
||||
"Viewstate not found, subMenuName: " + subMenuName,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -140,50 +228,4 @@ export class Session {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentifie un utilisateur avec un nom d'utilisateur et un mot de passe.
|
||||
*
|
||||
* @param {string} username - Le nom d'utilisateur de l'utilisateur.
|
||||
* @param {string} password - Le mot de passe de l'utilisateur.
|
||||
* @returns {Promise<Session>} Une promesse qui se résout avec une instance de Session si l'authentification réussit.
|
||||
* @throws {Error} Si l'authentification échoue.
|
||||
*/
|
||||
export function login(username: string, password: string): Promise<Session> {
|
||||
const baseURL = "https://web.isen-ouest.fr/webAurion";
|
||||
return new Promise<Session>((resolve, reject) => {
|
||||
const params = new URLSearchParams();
|
||||
params.append("username", username);
|
||||
params.append("password", password);
|
||||
axios
|
||||
.post(`${baseURL}/login`, params, {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
maxRedirects: 0,
|
||||
})
|
||||
.then((response) => {
|
||||
//Si la réponse est 302 et que le cookie est défini alors on retourne une nouvelle session
|
||||
if (response.status === 302 && response.headers["set-cookie"]) {
|
||||
const token = response.headers["set-cookie"][0]
|
||||
.split(";")[0]
|
||||
.split("=")[1];
|
||||
resolve(new Session(baseURL, token));
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
//Si la réponse est 302 et que le cookie est défini alors on retourne une nouvelle session
|
||||
if (err.response && err.response.status === 302) {
|
||||
const token = err.response.headers["set-cookie"][0]
|
||||
.split(";")[0]
|
||||
.split("=")[1];
|
||||
resolve(new Session(baseURL, token));
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default Session;
|
||||
|
||||
@@ -32,3 +32,50 @@ export function getJSFFormParams(
|
||||
params.append("javax.faces.ViewState", viewState);
|
||||
return params;
|
||||
}
|
||||
|
||||
// Récupération du prénom / nom de l'utilisateur lors de la connexion
|
||||
export function getName(html: string): string {
|
||||
const parser = load(html);
|
||||
//On recherche de l'élément qui contient le prénom et le nom
|
||||
const usernameElement = parser("li.ui-widget-header > h3");
|
||||
if (usernameElement.length > 0) {
|
||||
//On récupère le texte contenu dans l'élément
|
||||
const username = usernameElement.text();
|
||||
return username;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait le sidebar_menuid correspondant à un texte donné dans le menu.
|
||||
* @param {string} html - Le contenu HTML à parser
|
||||
* @param {string} label - Le texte du menu à rechercher (ex: "Mon planning" ou "Mes notes")
|
||||
* @returns {string|null} Le sidebar_menuid correspondant, ou null si non trouvé
|
||||
*/
|
||||
export function getSidebarMenuId(html: string, label: string): string | null {
|
||||
const parser = load(html);
|
||||
|
||||
// Cherche le span dont le texte commence par `label`
|
||||
const span = parser("span.ui-menuitem-text")
|
||||
.filter((_, el) => {
|
||||
const text = parser(el).text().trim();
|
||||
return text.startsWith(label);
|
||||
})
|
||||
.first();
|
||||
|
||||
if (!span.length) {
|
||||
// Non trouvé
|
||||
return null;
|
||||
}
|
||||
|
||||
// Trouve l’attribut onclick du lien parent
|
||||
const onclick = span.closest("a").attr("onclick");
|
||||
if (!onclick) {
|
||||
// Non trouvé
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extrait la valeur form:sidebar_menuid:'X_Y'
|
||||
const match = onclick.match(/'form:sidebar_menuid':'([^']+)'/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
@@ -1,47 +1,55 @@
|
||||
import { login } from "../src/api/Session";
|
||||
import Session from "../src/api/Session";
|
||||
describe("CacheTests", () => {
|
||||
it("should test for function performance", async () => {
|
||||
const username = process.env.TEST_USERNAME;
|
||||
const password = process.env.TEST_PASSWORD;
|
||||
if (!username || !password) {
|
||||
throw new Error(
|
||||
"TEST_USERNAME or TEST_PASSWORD is not set in environment variables.",
|
||||
it(
|
||||
"should test for function performance",
|
||||
async () => {
|
||||
const username = process.env.TEST_USERNAME;
|
||||
const password = process.env.TEST_PASSWORD;
|
||||
if (!username || !password) {
|
||||
throw new Error(
|
||||
"TEST_USERNAME or TEST_PASSWORD is not set in environment variables.",
|
||||
);
|
||||
}
|
||||
const session = new Session();
|
||||
await session.login(username, password);
|
||||
|
||||
//Première fois sans le cache on récupère l'emploi du temps
|
||||
let start = performance.now();
|
||||
let planning = await session.getPlanningApi().fetchPlanning();
|
||||
let end = performance.now();
|
||||
let duration = end - start;
|
||||
console.log(
|
||||
`fetchPlanning (sans cache): ${duration.toFixed(2)} ms`,
|
||||
);
|
||||
}
|
||||
|
||||
const session = await login(username, password);
|
||||
//Deuxième fois avec le cache on récupère l'emploi du temps
|
||||
start = performance.now();
|
||||
planning = await session.getPlanningApi().fetchPlanning();
|
||||
end = performance.now();
|
||||
duration = end - start;
|
||||
console.log(
|
||||
`fetchPlanning (avec cache): ${duration.toFixed(2)} ms`,
|
||||
);
|
||||
|
||||
//Première fois sans le cache on récupère l'emploi du temps
|
||||
let start = performance.now();
|
||||
let planning = await session.getPlanningApi().fetchPlanning();
|
||||
let end = performance.now();
|
||||
let duration = end - start;
|
||||
console.log(`fetchPlanning (sans cache): ${duration.toFixed(2)} ms`);
|
||||
//Première fois sans le cache on récupère les notes
|
||||
start = performance.now();
|
||||
let notes = await session.getNotesApi().fetchNotes();
|
||||
end = performance.now();
|
||||
duration = end - start;
|
||||
console.log(`fetchNotes (sans cache): ${duration.toFixed(2)} ms`);
|
||||
|
||||
//Deuxième fois avec le cache on récupère l'emploi du temps
|
||||
start = performance.now();
|
||||
planning = await session.getPlanningApi().fetchPlanning();
|
||||
end = performance.now();
|
||||
duration = end - start;
|
||||
console.log(`fetchPlanning (avec cache): ${duration.toFixed(2)} ms`);
|
||||
//Deuxième fois avec le cache on récupère les notes
|
||||
start = performance.now();
|
||||
notes = await session.getNotesApi().fetchNotes();
|
||||
end = performance.now();
|
||||
duration = end - start;
|
||||
console.log(`fetchNotes (avec cache): ${duration.toFixed(2)} ms`);
|
||||
|
||||
//Première fois sans le cache on récupère les notes
|
||||
start = performance.now();
|
||||
let notes = await session.getNotesApi().fetchNotes();
|
||||
end = performance.now();
|
||||
duration = end - start;
|
||||
console.log(`fetchNotes (sans cache): ${duration.toFixed(2)} ms`);
|
||||
|
||||
//Deuxième fois avec le cache on récupère les notes
|
||||
start = performance.now();
|
||||
notes = await session.getNotesApi().fetchNotes();
|
||||
end = performance.now();
|
||||
duration = end - start;
|
||||
console.log(`fetchNotes (avec cache): ${duration.toFixed(2)} ms`);
|
||||
|
||||
// console.log("Le planning", JSON.stringify(planning));
|
||||
// console.log("Les notes ", JSON.stringify(notes));
|
||||
expect(notes).toBeInstanceOf(Array);
|
||||
expect(planning).toBeInstanceOf(Array);
|
||||
});
|
||||
// console.log("Le planning", JSON.stringify(planning));
|
||||
// console.log("Les notes ", JSON.stringify(notes));
|
||||
expect(notes).toBeInstanceOf(Array);
|
||||
expect(planning).toBeInstanceOf(Array);
|
||||
},
|
||||
15 * 1000,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { login } from "../src/api/Session";
|
||||
import Session from "../src/api/Session";
|
||||
import { noteAverage } from "../src/utils/NotesUtils";
|
||||
describe("NotesApi", () => {
|
||||
it("should receive notes", async () => {
|
||||
@@ -10,7 +10,9 @@ describe("NotesApi", () => {
|
||||
);
|
||||
}
|
||||
|
||||
const session = await login(username, password);
|
||||
const session = new Session();
|
||||
await session.login(username, password);
|
||||
|
||||
const notes = await session.getNotesApi().fetchNotes();
|
||||
console.log(JSON.stringify(notes, null, 2));
|
||||
console.log("Les moyennes: ");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { login } from "../src/api/Session";
|
||||
import Session from "../src/api/Session";
|
||||
import { getScheduleDates } from "../src/utils/PlanningUtils";
|
||||
describe("PlanningApi", () => {
|
||||
it("should receive a planning", async () => {
|
||||
@@ -10,7 +10,9 @@ describe("PlanningApi", () => {
|
||||
);
|
||||
}
|
||||
|
||||
const session = await login(username, password);
|
||||
const session = new Session();
|
||||
await session.login(username, password);
|
||||
|
||||
const planning = await session.getPlanningApi().fetchPlanning();
|
||||
console.log(planning);
|
||||
expect(planning).toBeInstanceOf(Array);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { login } from "../src/api/Session";
|
||||
import Session from "../src/api/Session";
|
||||
describe("AuthApi", () => {
|
||||
it("should log in a user and receive a session", async () => {
|
||||
const username = process.env.TEST_USERNAME;
|
||||
@@ -9,7 +9,9 @@ describe("AuthApi", () => {
|
||||
);
|
||||
}
|
||||
|
||||
const result = await login(username, password);
|
||||
expect(result).toBeDefined();
|
||||
const session = new Session();
|
||||
await session.login(username, password);
|
||||
|
||||
expect(session).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user