Skip to content

Commit 3813411

Browse files
committed
luci-base: mark background poll XHRs with X-Ubus-No-Touch header
LuCI polls rpcd every few seconds to refresh the page and check session status. These background XHRs inadvertently reset the rpcd session idle timer, so 'option sessiontime' in /etc/config/rpcd has no effect in practice — the session never expires while a LuCI tab is open, even if the user is not actively using it. Fix this by setting an X-Ubus-No-Touch: 1 request header on XHRs that are not driven by a user gesture, so the server side can opt out of refreshing the idle timer for them. Capture-phase document listeners for common user interaction event types record a timestamp (Request.lastUserInteraction) on every gesture. Request.request() computes opt.background synchronously — before the Promise.resolve() deferral — by comparing that timestamp against a BACKGROUND_THRESHOLD (5 s). This survives async continuations: RPCs issued from .then() chains after an intervening network round-trip or modal confirmation (e.g. ui.changes.apply, uci.save) are treated as foreground for as long as the user's last gesture is recent. For the batched ubus path flushRequestQueue() sets X-Ubus-No-Touch only when every entry in the batch carries opt.background, so a foreground RPC sharing a batch with a background poll still touches the session. Companion patches: rpcd: honour notouch in session/access uhttpd: parse X-Ubus-No-Touch and forward notouch to rpcd Signed-off-by: Michael Pfeifroth <micpf@westermo.com>
1 parent 1c70b2b commit 3813411

1 file changed

Lines changed: 27 additions & 0 deletions

File tree

  • modules/luci-base/htdocs/luci-static/resources

modules/luci-base/htdocs/luci-static/resources/luci.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,14 @@
514514

515515
const requestQueue = [];
516516

517+
/* Requests made within BACKGROUND_THRESHOLD ms of a user gesture are
518+
* foreground (session-touching); all others are background.
519+
*/
520+
const BACKGROUND_THRESHOLD = 5000;
521+
for (const type of ['click', 'submit', 'change', 'input', 'keydown', 'mousedown', 'touchstart']) {
522+
document.addEventListener(type, () => { Request.lastUserInteraction = Date.now() }, true);
523+
}
524+
517525
/**
518526
* Check whether a Request.options object is eligible to be queued for RPC
519527
* batching.
@@ -570,6 +578,8 @@
570578
reqopt.content[i] = batch[i][0].content;
571579
}
572580

581+
reqopt.background = batch.every(e => e[0].background);
582+
573583
requestQueue.length = 0;
574584

575585
Request.request(rpcBaseURL, reqopt).then(reply => {
@@ -605,6 +615,8 @@
605615

606616
interceptors: [],
607617

618+
lastUserInteraction: 0,
619+
608620
/**
609621
* Turn the given relative URL into an absolute URL if necessary.
610622
*
@@ -665,6 +677,11 @@
665677
* @property {Object<string, string>} [header]
666678
* Specifies HTTP headers to set for the request.
667679
*
680+
* @property {boolean} [background]
681+
* Whether the request is a background poll that should not reset the
682+
* server-side session idle timer. Determined automatically from
683+
* recent user interaction; set explicitly to override.
684+
*
668685
* @property {function()} [progress]
669686
* An optional request callback function which receives ProgressEvent
670687
* instances as sole argument during the HTTP request transfer.
@@ -689,6 +706,10 @@
689706
* The resulting HTTP response.
690707
*/
691708
request(target, options) {
709+
const background = ('background' in (options ?? {}))
710+
? options.background
711+
: (Date.now() - Request.lastUserInteraction) > BACKGROUND_THRESHOLD;
712+
692713
return Promise.resolve(target).then(url => {
693714
const state = { xhr: new XMLHttpRequest(), url: this.expandURL(url), start: Date.now() };
694715
const opt = Object.assign({}, options, state);
@@ -698,6 +719,9 @@
698719

699720
return new Promise((resolveFn, rejectFn) => {
700721
opt.xhr.onreadystatechange = callback.bind(opt, resolveFn, rejectFn);
722+
723+
opt.background = background;
724+
701725
opt.method = String(opt.method ?? 'GET').toUpperCase();
702726

703727
if ('query' in opt) {
@@ -787,6 +811,9 @@
787811
contenttype = opt.headers[header];
788812
}
789813

814+
if (opt.background)
815+
opt.xhr.setRequestHeader('X-Ubus-No-Touch', '1');
816+
790817
if ('progress' in opt && 'upload' in opt.xhr)
791818
opt.xhr.upload.addEventListener('progress', opt.progress);
792819

0 commit comments

Comments
 (0)