Skip to content

Commit d5e1afa

Browse files
committed
Several changes:
- Release notes: moved h3 to inside of <a> tag - Added idle timeout
1 parent 9e1cdf2 commit d5e1afa

File tree

9 files changed

+277
-101
lines changed

9 files changed

+277
-101
lines changed

ReleaseNotes.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ <h3>Path parameters</h3>
102102

103103
<p>A feature example demonstrating this was added in <tt>examples/feature/urlparams</tt>.</p>
104104

105-
<h3><a href="classWt_1_1WFileDropWidget.html">WFileDropWidget</a></h3>
105+
<a href="classWt_1_1WFileDropWidget.html"><h3>WFileDropWidget</h3></a>
106106

107107
<p>Added the ability to set a
108108
<a href="classWt_1_1WFileDropWidget.html#a9ad41a8986a7bbb25178e0cda850333d">JavaScript filter</a>, e.g. to

src/Wt/WApplication.C

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ WApplication::WApplication(const WEnvironment& env
127127
showLoadingIndicator_("showload", this),
128128
hideLoadingIndicator_("hideload", this),
129129
unloaded_(this, "Wt-unload"),
130+
idleTimeout_(this, "Wt-idleTimeout"),
130131
soundManager_(nullptr)
131132
{
132133
session_->setApplication(this);
@@ -281,6 +282,7 @@ WApplication::WApplication(const WEnvironment& env
281282
(std::unique_ptr<WLoadingIndicator>(new WDefaultLoadingIndicator()));
282283

283284
unloaded_.connect(this, &WApplication::doUnload);
285+
idleTimeout_.connect(this, &WApplication::doIdleTimeout);
284286
}
285287

286288
void WApplication::setJavaScriptClass(const std::string& javaScriptClass)
@@ -766,6 +768,21 @@ void WApplication::unload()
766768
quit();
767769
}
768770

771+
void WApplication::doIdleTimeout()
772+
{
773+
const Configuration& conf = environment().server()->configuration();
774+
775+
if (conf.idleTimeout() != -1)
776+
idleTimeout();
777+
}
778+
779+
void WApplication::idleTimeout()
780+
{
781+
const Configuration& conf = environment().server()->configuration();
782+
LOG_INFO("User idle for " << conf.idleTimeout() << " seconds, quitting due to idle timeout");
783+
quit();
784+
}
785+
769786
void WApplication::handleJavaScriptError(const std::string& errorText)
770787
{
771788
LOG_ERROR("JavaScript error: " << errorText);

src/Wt/WApplication.h

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2110,6 +2110,77 @@ class WT_API WApplication : public WObject
21102110
*/
21112111
virtual void unload();
21122112

2113+
/*! \brief Idle timeout handler
2114+
*
2115+
* If <tt>idle-timeout</tt> is set in the configuration, this method is called when
2116+
* the user seems idle for the number of seconds set in <tt>idle-timeout</tt>.
2117+
*
2118+
* This feature can be useful in security sensitive applications
2119+
* to prevent unauthorized users from taking over the session
2120+
* of a user that has moved away from or left behind
2121+
* the device from which they are accessing the %Wt application.
2122+
*
2123+
* The default implementation logs that a timeout has occurred,
2124+
* and calls quit().
2125+
*
2126+
* This method can be overridden to specify different timeout behaviour,
2127+
* e.g. to show a dialog that a user's session has expired, or that
2128+
* the session is about to expire.
2129+
*
2130+
* \if cpp
2131+
*
2132+
* Example for an expiration dialog:
2133+
*
2134+
* \code
2135+
* class MyApplication : public Wt::WApplication {
2136+
* public:
2137+
* MyApplication(Wt::WEnvironment &env)
2138+
* : WApplication(env)
2139+
* { }
2140+
*
2141+
* protected:
2142+
* virtual void idleTimeout() override
2143+
* {
2144+
* if (idleTimeoutDialog_)
2145+
* return; // Prevent multiple dialogs
2146+
*
2147+
* idleTimeoutDialog_ = addChild(std::make_unique<Wt::WDialog>("Idle timeout"));
2148+
* idleTimeoutDialog_->contents()->addNew<Wt::WText>("This session will automatically quit in 1 minute, "
2149+
* "press 'abort' to continue using the application");
2150+
* auto btn = idleTimeoutDialog_->footer()->addNew<Wt::WPushButton>("abort");
2151+
* btn->clicked().connect([this]{
2152+
* removeChild(idleTimeoutDialog_.get());
2153+
* });
2154+
* auto timer = idleTimeoutDialog_->addChild(std::make_unique<Wt::WTimer>());
2155+
* timer->setInterval(std::chrono::seconds{60});
2156+
* timer->setSingleShot(true);
2157+
* timer->timeout().connect([this]{
2158+
* quit();
2159+
* });
2160+
* timer->start();
2161+
* idleTimeoutDialog_->show();
2162+
* }
2163+
*
2164+
* private:
2165+
* Wt::Core::observing_ptr<Wt::WDialog> idleTimeoutDialog_;
2166+
* };
2167+
* \endcode
2168+
*
2169+
* \note The events currently counted as user activity are:
2170+
* - mousedown
2171+
* - mouseup
2172+
* - wheel
2173+
* - keydown
2174+
* - keyup
2175+
* - touchstart
2176+
* - touchend
2177+
* - pointerdown
2178+
* - pointerup
2179+
*
2180+
* \endif
2181+
*/
2182+
virtual void idleTimeout();
2183+
21132184
/**
21142185
* @brief handleJavaScriptError print javaScript errors to log file.
21152186
* You may want to overwrite it to render error page for example.
@@ -2231,6 +2302,7 @@ class WT_API WApplication : public WObject
22312302

22322303
EventSignal<> showLoadingIndicator_, hideLoadingIndicator_;
22332304
JSignal<> unloaded_;
2305+
JSignal<> idleTimeout_;
22342306

22352307
WContainerWidget *timerRoot() const { return timerRoot_; }
22362308
WEnvironment& env(); // short-hand for session_->env()
@@ -2275,6 +2347,7 @@ class WT_API WApplication : public WObject
22752347
void setExposeSignals(bool how) { exposeSignals_ = how; }
22762348
bool exposeSignals() const { return exposeSignals_; }
22772349
void doUnload();
2350+
void doIdleTimeout();
22782351

22792352
#ifndef WT_TARGET_JAVA
22802353
int startWaitingAtLock();

src/web/Configuration.C

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ void Configuration::reset()
200200
sessionTracking_ = URL;
201201
reloadIsNewSession_ = true;
202202
sessionTimeout_ = 600;
203+
idleTimeout_ = -1;
203204
bootstrapTimeout_ = 10;
204205
indicatorTimeout_ = 500;
205206
doubleClickTimeout_ = 200;
@@ -299,6 +300,12 @@ int Configuration::sessionTimeout() const
299300
return sessionTimeout_;
300301
}
301302

303+
int Configuration::idleTimeout() const
304+
{
305+
READ_LOCK;
306+
return idleTimeout_;
307+
}
308+
302309
int Configuration::keepAlive() const
303310
{
304311
int timeout = sessionTimeout();
@@ -902,6 +909,7 @@ void Configuration::readApplicationSettings(xml_node<> *app)
902909
}
903910

904911
setInt(sess, "timeout", sessionTimeout_);
912+
setInt(sess, "idle-timeout", idleTimeout_);
905913
setInt(sess, "bootstrap-timeout", bootstrapTimeout_);
906914
setInt(sess, "server-push-timeout", serverPushTimeout_);
907915
setBoolean(sess, "reload-is-new-session", reloadIsNewSession_);

src/web/Configuration.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ class WT_API Configuration
184184
SessionTracking sessionTracking() const;
185185
bool reloadIsNewSession() const;
186186
int sessionTimeout() const;
187+
int idleTimeout() const;
187188
int keepAlive() const; // sessionTimeout() / 2, or if sessionTimeout == -1, 1000000
188189
int multiSessionCookieTimeout() const; // sessionTimeout() * 2
189190
int bootstrapTimeout() const;
@@ -278,6 +279,7 @@ class WT_API Configuration
278279
SessionTracking sessionTracking_;
279280
bool reloadIsNewSession_;
280281
int sessionTimeout_;
282+
int idleTimeout_;
281283
int bootstrapTimeout_;
282284
int indicatorTimeout_;
283285
int doubleClickTimeout_;

src/web/WebRenderer.C

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,9 @@ void WebRenderer::serveMainscript(WebResponse& response)
10241024

10251025
script.setVar("KEEP_ALIVE", std::to_string(conf.keepAlive()));
10261026

1027+
script.setVar("IDLE_TIMEOUT", conf.idleTimeout() != -1 ?
1028+
std::to_string(conf.idleTimeout()) : std::string("null"));
1029+
10271030
script.setVar("INDICATOR_TIMEOUT", conf.indicatorTimeout());
10281031
script.setVar("SERVER_PUSH_TIMEOUT", conf.serverPushTimeout() * 1000);
10291032

src/web/skeleton/Wt.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2951,6 +2951,8 @@ var sessionUrl,
29512951
responsePending = null,
29522952
pollTimer = null,
29532953
keepAliveTimer = null,
2954+
idleTimeout = _$_IDLE_TIMEOUT_$_, /* idle timeout in seconds, null if disabled */
2955+
idleTimeoutTimer = null,
29542956
commErrors = 0,
29552957
serverPush = false,
29562958
updateTimeout = null;
@@ -2962,6 +2964,10 @@ function quit(hasQuitMessage) {
29622964
clearInterval(keepAliveTimer);
29632965
keepAliveTimer = null;
29642966
}
2967+
if (idleTimeoutTimer) {
2968+
clearTimeout(idleTimeoutTimer);
2969+
idleTimeoutTimer = null;
2970+
}
29652971
if (pollTimer) {
29662972
clearTimeout(pollTimer);
29672973
pollTimer = null;
@@ -2987,6 +2993,56 @@ function setTitle(title) {
29872993
document.title = title;
29882994
}
29892995

2996+
function doIdleTimeout() {
2997+
self.emit(self, 'Wt-idleTimeout');
2998+
idleTimeoutTimer = setTimeout(doIdleTimeout, idleTimeout * 1000);
2999+
}
3000+
3001+
function delayIdleTimeout() {
3002+
if (idleTimeoutTimer !== null) {
3003+
clearTimeout(idleTimeoutTimer);
3004+
idleTimeoutTimer = setTimeout(doIdleTimeout, idleTimeout * 1000);
3005+
}
3006+
}
3007+
3008+
function initIdleTimeout() {
3009+
var opts = true;
3010+
3011+
if (idleTimeout === null)
3012+
return;
3013+
3014+
idleTimeoutTimer = setTimeout(doIdleTimeout, idleTimeout * 1000);
3015+
3016+
try {
3017+
var options = Object.defineProperty({}, "passive", {
3018+
get: function() {
3019+
//passive supported
3020+
opts = {
3021+
capture: true,
3022+
passive: true
3023+
};
3024+
}
3025+
});
3026+
3027+
window.addEventListener('test', options, options);
3028+
window.removeEventListener('test', options, options);
3029+
} catch (err) {
3030+
opts = true; // passive not supported, only specify capture
3031+
}
3032+
3033+
if (document.addEventListener) {
3034+
document.addEventListener('mousedown', delayIdleTimeout, opts);
3035+
document.addEventListener('mouseup', delayIdleTimeout, opts);
3036+
document.addEventListener('wheel', delayIdleTimeout, opts);
3037+
document.addEventListener('keydown', delayIdleTimeout, opts);
3038+
document.addEventListener('keyup', delayIdleTimeout, opts);
3039+
document.addEventListener('touchstart', delayIdleTimeout, opts);
3040+
document.addEventListener('touchend', delayIdleTimeout, opts);
3041+
document.addEventListener('pointerdown', delayIdleTimeout, opts);
3042+
document.addEventListener('pointerup', delayIdleTimeout, opts);
3043+
}
3044+
}
3045+
29903046
function load(fullapp) {
29913047
if (loaded)
29923048
return;
@@ -3018,6 +3074,7 @@ function load(fullapp) {
30183074

30193075
WT.history._initialize();
30203076
initDragDrop();
3077+
initIdleTimeout();
30213078
loaded = true;
30223079

30233080
if (fullapp)

0 commit comments

Comments
 (0)