v5.0.9 — Surveillance timeout session EasyVista (compteur tick 1s, alertes 5min/2min)
This commit is contained in:
+161
-16
@@ -85,7 +85,7 @@ async function fetchPlanningXml(origin, phpsessid, unixDate) {
|
||||
`&day_start_hour=8` +
|
||||
`&day_end_hour=19`;
|
||||
console.log("[bg] fetchPlanningXml →", url.substring(0, 140));
|
||||
const r = await fetch(url, { credentials: "include" });
|
||||
const r = await evFetch(url, origin);
|
||||
console.log("[bg] status =", r.status);
|
||||
if (!r.ok) {
|
||||
// v4.2 : classifier l'erreur HTTP pour que le viewer affiche le bon
|
||||
@@ -100,6 +100,32 @@ async function fetchPlanningXml(origin, phpsessid, unixDate) {
|
||||
return xml;
|
||||
}
|
||||
|
||||
/**
|
||||
* v5.0.9 : wrapper autour de fetch() qui ajoute systématiquement les
|
||||
* headers de sécurité attendus par EasyVista (Referer, Sec-Fetch-Site,
|
||||
* X-Requested-With). Sans ces headers, EV renvoie soit un <script> de
|
||||
* redirection (CSRF check), soit une page de login, même avec une session
|
||||
* valide.
|
||||
*
|
||||
* Observé dans les captures réseau du navigateur :
|
||||
* Referer: https://itsma.etat-de-vaud.ch/index.php?eventName=HelpDesk_PlanningItem
|
||||
* Sec-Fetch-Site: same-origin
|
||||
* X-Requested-With: XMLHttpRequest (parfois)
|
||||
*
|
||||
* @param {string} url - URL complète à fetcher
|
||||
* @param {string} origin - origine EasyVista (pour construire le Referer)
|
||||
* @param {object} [opts] - options fetch (method, body, headers supplémentaires)
|
||||
*/
|
||||
async function evFetch(url, origin, opts = {}) {
|
||||
const defaultHeaders = {
|
||||
"Referer": `${origin}/index.php?eventName=HelpDesk_PlanningItem`,
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
};
|
||||
const headers = Object.assign({}, defaultHeaders, opts.headers || {});
|
||||
const fetchOpts = Object.assign({ credentials: "include" }, opts, { headers });
|
||||
return await fetch(url, fetchOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
* v4.2 : classifie un statut HTTP comme "session_expired" ou "ev_unreachable".
|
||||
* - 401, 403, 404 → session_expired (EV renvoie souvent 404 au lieu de rediriger
|
||||
@@ -118,7 +144,7 @@ function classifyHttpStatus(status) {
|
||||
*/
|
||||
async function fetchXhr2(origin, phpsessid, actionId) {
|
||||
const url = `${origin}/planning_xhr_2.php?PHPSESSID=${encodeURIComponent(phpsessid)}&id=${encodeURIComponent(actionId)}`;
|
||||
const r = await fetch(url, { credentials: "include" });
|
||||
const r = await evFetch(url, origin);
|
||||
if (!r.ok) {
|
||||
const err = new Error("HTTP " + r.status);
|
||||
err.kind = classifyHttpStatus(r.status);
|
||||
@@ -131,17 +157,7 @@ async function fetchXhr2(origin, phpsessid, actionId) {
|
||||
async function fetchFicheHtml(origin, phpsessid, formLink) {
|
||||
const url = `${origin}/index.php?${formLink}&PHPSESSID=${encodeURIComponent(phpsessid)}`;
|
||||
console.log("[bg] fetchFicheHtml →", url.substring(0, 120));
|
||||
// v5.0.8 : EasyVista retourne maintenant un <script> de redirection si on
|
||||
// fait la requête sans Referer. Probablement une protection CSRF ajoutée
|
||||
// récemment. On ajoute un Referer qui simule une navigation depuis la
|
||||
// page principale du planning.
|
||||
const r = await fetch(url, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Referer": `${origin}/index.php?eventName=HelpDesk_PlanningItem`,
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
}
|
||||
});
|
||||
const r = await evFetch(url, origin);
|
||||
if (!r.ok) {
|
||||
const err = new Error("HTTP " + r.status);
|
||||
err.kind = classifyHttpStatus(r.status);
|
||||
@@ -176,7 +192,7 @@ async function fetchTimelineApi(origin, phpsessid, guid, formId, formChecksum) {
|
||||
`&checksum=${encodeURIComponent(formChecksum)}` +
|
||||
`&type=todo§ionId=1&navigator=&nbRecord=0` +
|
||||
`&PHPSESSID=${encodeURIComponent(phpsessid)}`;
|
||||
const r = await fetch(url, { credentials: "include" });
|
||||
const r = await evFetch(url, origin);
|
||||
if (!r.ok) {
|
||||
const err = new Error("HTTP " + r.status);
|
||||
err.kind = classifyHttpStatus(r.status);
|
||||
@@ -190,9 +206,90 @@ async function fetchTimelineApi(origin, phpsessid, guid, formId, formChecksum) {
|
||||
// Détection "session invalide"
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* v5.0.9 : détecte plusieurs patterns de session invalide :
|
||||
* 1. Page de login classique EasyVista (customer_login, my.policy)
|
||||
* 2. Script de redirection court : <script>window.location.href = "..."</script>
|
||||
* (protection CSRF ou session expirée)
|
||||
* 3. URL de logout : index.php?...&logout=1
|
||||
* 4. Redirection vers le portail SSO : portail.etat-de-vaud.ch/sso/
|
||||
* 5. Réponse JSON avec "isLogged": false
|
||||
*/
|
||||
function looksLikeLoginPage(text) {
|
||||
// La page de login EasyVista contient cette chaîne
|
||||
return /customer_login|my\.policy/i.test((text || "").substring(0, 3000));
|
||||
const t = (text || "").substring(0, 3000);
|
||||
if (!t) return false;
|
||||
// Pattern 1 : page de login EV classique
|
||||
if (/customer_login|my\.policy/i.test(t)) return true;
|
||||
// Pattern 2 : script de redirection (< 500 chars = probablement juste ça)
|
||||
if (t.length < 500 && /<script[^>]*>\s*window\.location\.href\s*=/i.test(t)) return true;
|
||||
// Pattern 3 : URL de logout
|
||||
if (/[?&]logout=1/i.test(t)) return true;
|
||||
// Pattern 4 : redirection vers portail SSO
|
||||
if (/portail\.etat-de-vaud\.ch\/sso\//i.test(t)) return true;
|
||||
// Pattern 5 : JSON isLogged:false
|
||||
if (/"isLogged"\s*:\s*false/i.test(t)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// v5.0.9 : surveillance du timeout de session EasyVista
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* GET /timeout_ajax.php?__AJAX_TIMEOUT_FCT__=session_time
|
||||
*
|
||||
* Retourne le nombre de millisecondes restantes avant expiration de la
|
||||
* session EasyVista (0 à 1 800 000 = 30 min max).
|
||||
*
|
||||
* Attention : cette requête EST authentifiée et prolonge probablement la
|
||||
* session (comme toute requête PHP authentifiée). À utiliser avec parcimonie.
|
||||
*/
|
||||
async function fetchSessionTimeRemaining(origin, phpsessid) {
|
||||
const url = `${origin}/timeout_ajax.php`
|
||||
+ `?PHPSESSID=${encodeURIComponent(phpsessid)}`
|
||||
+ `&__AJAX_TIMEOUT_FCT__=session_time`;
|
||||
console.log("[bg] fetchSessionTimeRemaining →", url.substring(0, 120));
|
||||
const r = await evFetch(url, origin);
|
||||
if (!r.ok) {
|
||||
throw new Error("HTTP " + r.status);
|
||||
}
|
||||
const body = (await r.text()).trim();
|
||||
// Vérifier que c'est bien un nombre (sinon = session morte probable)
|
||||
if (!/^\d+$/.test(body)) {
|
||||
console.warn("[bg] réponse session_time anormale :", body.substring(0, 200));
|
||||
// Si c'est une page de login/redirect → session expirée
|
||||
if (looksLikeLoginPage(body)) {
|
||||
throw new Error("session_expired");
|
||||
}
|
||||
throw new Error("invalid_response");
|
||||
}
|
||||
const ms = parseInt(body, 10);
|
||||
console.log(`[bg] session_time = ${ms} ms = ${Math.round(ms/60000)} min`);
|
||||
return ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /timeout_ajax.php?__AJAX_TIMEOUT_FCT__=keep_connection
|
||||
*
|
||||
* Prolonge la session à 30 min. Retourne 1800000.
|
||||
*/
|
||||
async function extendSessionKeepAlive(origin, phpsessid) {
|
||||
const url = `${origin}/timeout_ajax.php`
|
||||
+ `?PHPSESSID=${encodeURIComponent(phpsessid)}`
|
||||
+ `&__AJAX_TIMEOUT_FCT__=keep_connection`;
|
||||
console.log("[bg] extendSessionKeepAlive →", url.substring(0, 120));
|
||||
const r = await evFetch(url, origin);
|
||||
if (!r.ok) {
|
||||
throw new Error("HTTP " + r.status);
|
||||
}
|
||||
const body = (await r.text()).trim();
|
||||
if (!/^\d+$/.test(body)) {
|
||||
if (looksLikeLoginPage(body)) throw new Error("session_expired");
|
||||
throw new Error("invalid_response");
|
||||
}
|
||||
const ms = parseInt(body, 10);
|
||||
console.log(`[bg] keep_connection → session prolongée à ${ms} ms`);
|
||||
return ms;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -872,6 +969,54 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === "getSessionRemaining") {
|
||||
// v5.0.9 : récupère le temps restant avant expiration de la session EV
|
||||
const session = await findEasyVistaSession();
|
||||
if (!session) {
|
||||
sendResponse({ ok: false, error: "no_session" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const remainingMs = await fetchSessionTimeRemaining(session.origin, session.phpsessid);
|
||||
sendResponse({ ok: true, remainingMs, phpsessid: session.phpsessid });
|
||||
} catch (err) {
|
||||
sendResponse({ ok: false, error: err.message || String(err) });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === "extendSession") {
|
||||
// v5.0.9 : prolonge la session EV à 30 min via keep_connection
|
||||
const session = await findEasyVistaSession();
|
||||
if (!session) {
|
||||
sendResponse({ ok: false, error: "no_session" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const remainingMs = await extendSessionKeepAlive(session.origin, session.phpsessid);
|
||||
sendResponse({ ok: true, remainingMs });
|
||||
} catch (err) {
|
||||
sendResponse({ ok: false, error: err.message || String(err) });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === "openEasyVistaLogin") {
|
||||
// v5.0.9 : ouvre EasyVista dans un nouvel onglet pour provoquer
|
||||
// le SSO Windows automatique (reconnexion transparente).
|
||||
const origin = msg.origin || "https://itsma.etat-de-vaud.ch";
|
||||
try {
|
||||
const tab = await chrome.tabs.create({
|
||||
url: `${origin}/index.php?eventName=HelpDesk_PlanningItem`,
|
||||
active: true
|
||||
});
|
||||
sendResponse({ ok: true, tabId: tab.id });
|
||||
} catch (err) {
|
||||
sendResponse({ ok: false, error: err.message || String(err) });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === "cleanupOldCaches") {
|
||||
const removed = await cleanupOldCaches(msg.daysToKeep || 7);
|
||||
sendResponse({ ok: true, removed });
|
||||
|
||||
Reference in New Issue
Block a user