mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-09-22 18:45:43 -04:00
The staleness window was unsound. calculateAuthHash() keys the hash to the clock hour it was minted in and getAuthUser() accepts the last ZM_AUTH_HASH_TTL hourly buckets, so a hash dies at the top of an hour rather than at some age. generateAuthHash() then serves the cached one until it is half a TTL old, so what arrives can already be nearly spent: on the defaults a hash minted at 10:59 is still handed out at 11:58 and is refused at 12:00. A client stamping that arrival as fresh for an hour skips the probe until 12:58 and restarts its streams on a dead hash - the exact failure this was written to prevent. No fixed window is safe, because the remaining life of a hash we hold can be anything down to zero, and AUTH_STALE_MS also ignored the configured ZM_AUTH_HASH_TTL. So drop AUTH_STALE_MS, authIsStale() and authFreshAt, and have whenAuthFresh() revalidate. The one case that can still skip the probe is having no hash at all - authentication off, or a relay form that does not use one - where there is nothing that can expire and nothing a probe would report. revalidateAuth() already shares one request between concurrent callers, so a resume that wakes several of these still costs a single probe, and that is what the montage code did unconditionally before any of this. refreshTablesPendingVisibility() now returns as soon as it finds nothing was deferred. It is bound on every classic page including the unauthenticated ones, and the version before this ran the whole auth path on an empty queue, so merely becoming visible could fire a probe with no work behind it. Tests: two authIsStale cases removed with the function, two whenAuthFresh cases added - a probe is sent and the callback held until it answers, and no probe is sent when there is no hash. Reintroducing a fast path fails the first. Full JS suite green, ESLint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UkQwahn9pi1y4wJe9BTxjM
73 lines
3.1 KiB
JavaScript
73 lines
3.1 KiB
JavaScript
'use strict';
|
|
|
|
// Helpers shared by the bootstrap-table views (events, console, log, frames,
|
|
// reports, snapshots, watch). Loaded as a plain browser script before the view
|
|
// scripts (web/skins/classic/includes/functions.php), so the functions below are
|
|
// globals by the time any of them runs. Also CommonJS-exported for node unit
|
|
// tests (tests/js/table-helpers.test.js).
|
|
|
|
// Tables whose ajax request was skipped because the page was hidden, waiting to
|
|
// be refreshed once it is shown again.
|
|
const tablesPendingVisibility = [];
|
|
|
|
// Decide whether to skip a bootstrap-table ajax request because the page is
|
|
// hidden. Returns true when the caller should return without issuing it.
|
|
//
|
|
// Skipping alone is not enough. Bootstrap-table calls its ajax function on init
|
|
// as well as on refresh, and a skipped request is never re-issued, so the table
|
|
// renders "No matching records found" over a result it never asked for. Nothing
|
|
// brings it back and the user has to refresh by hand (issue #5026). Recording
|
|
// the table here means it is refreshed the moment the page becomes visible.
|
|
//
|
|
// A page can be hidden for the whole of its load - opened in a background tab,
|
|
// restored, or simply behind another window - so this is not only about a tab
|
|
// the user switched away from later.
|
|
function deferTableRequestWhileHidden(table) {
|
|
if (document.visibilityState !== 'hidden') return false;
|
|
table.bootstrapTable('hideLoading');
|
|
// A table can be deferred repeatedly (init, then auto-refresh ticks) and only
|
|
// needs refreshing once.
|
|
if (tablesPendingVisibility.indexOf(table) === -1) {
|
|
tablesPendingVisibility.push(table);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Refresh every table whose request was skipped while hidden. The auth hash we
|
|
// were holding may have expired during the hide, so wait for a confirmed one
|
|
// rather than have every deferred table 403 (auth-helpers.js). whenAuthFresh is
|
|
// absent under node; refresh directly there.
|
|
function refreshTablesPendingVisibility() {
|
|
if (document.visibilityState === 'hidden') return;
|
|
// Drain before refreshing: refresh() calls the ajax function synchronously,
|
|
// which would otherwise re-add the table while we are still iterating.
|
|
const tables = tablesPendingVisibility.splice(0, tablesPendingVisibility.length);
|
|
// Nothing was deferred, so there is nothing to authenticate for. This handler
|
|
// is bound on every classic page including the unauthenticated ones, and
|
|
// whenAuthFresh() can send a probe, so becoming visible must stay a no-op
|
|
// when there is no work.
|
|
if (!tables.length) return;
|
|
const refresh = function() {
|
|
for (let i = 0; i < tables.length; i++) {
|
|
tables[i].bootstrapTable('refresh');
|
|
}
|
|
};
|
|
if (typeof whenAuthFresh === 'function') {
|
|
whenAuthFresh(refresh);
|
|
} else {
|
|
refresh();
|
|
}
|
|
}
|
|
|
|
if (typeof document !== 'undefined' && document.addEventListener) {
|
|
document.addEventListener('visibilitychange', refreshTablesPendingVisibility);
|
|
}
|
|
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = {
|
|
deferTableRequestWhileHidden,
|
|
refreshTablesPendingVisibility,
|
|
tablesPendingVisibility,
|
|
};
|
|
}
|