45 lines
1.9 KiB
JavaScript
45 lines
1.9 KiB
JavaScript
// CentralUI-020: client-side helper for the SessionExpiry component's
|
|
// idle-logout check. Pings the given URL and reports the HTTP status code so
|
|
// the Blazor component can redirect to /login once the server reports 401.
|
|
//
|
|
// `redirect: "manual"` ensures a 302 (should the endpoint ever start
|
|
// redirecting) is reported as an opaque status rather than being followed
|
|
// transparently — the component only ever wants to see the real outcome.
|
|
export async function ping(url) {
|
|
try {
|
|
const resp = await fetch(url, {
|
|
method: "GET",
|
|
credentials: "same-origin",
|
|
cache: "no-store",
|
|
redirect: "manual",
|
|
headers: { "X-Requested-With": "XMLHttpRequest" }
|
|
});
|
|
return resp.status;
|
|
} catch {
|
|
// Network failure: report 0 so the caller treats it as inconclusive
|
|
// and retries on the next poll rather than logging the user out.
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// SignalR event traffic and automatic pings are not user activity. Only trusted
|
|
// browser input can schedule this antiforgery-protected request.
|
|
let lastReported = 0;
|
|
let reporting = false;
|
|
async function recordInput(event) {
|
|
if (!event.isTrusted || reporting || Date.now() - lastReported < 15000) return;
|
|
const token = document.querySelector('input[name="__RequestVerificationToken"]')?.value;
|
|
if (!token) return;
|
|
reporting = true;
|
|
try {
|
|
const response = await fetch('/auth/activity', {
|
|
method: 'POST', credentials: 'same-origin', cache: 'no-store',
|
|
headers: { 'RequestVerificationToken': token }
|
|
});
|
|
if (response.ok) lastReported = Date.now();
|
|
} catch { /* A failed request never advances the server's idle clock. */ }
|
|
finally { reporting = false; }
|
|
}
|
|
for (const name of ['pointerdown', 'keydown', 'wheel', 'touchstart'])
|
|
document.addEventListener(name, recordInput, { passive: true });
|