Detecting Inactive Users - CSS-Tricks - CSS-Tricks
Detecting Inactive Users - CSS-Tricks - CSS-Tricks
Most of the time you don’t really care about whether a user is actively engaged or temporarily
inactive on your application. Inactive, meaning, perhaps they got up to get a drink of water, or
more likely, changed tabs to do something else for a bit. There are situations, though, when
tracking the user activity and detecting inactive-ness might be handy.
Let’s think about few examples when you just might need that functionality:
I recently encountered a feature that involved that last example, auto logging out inactive
users for security reasons.
(#aa-why-should-we-care-about-auto-logout) Why
should we care about auto logout?
Many applications give users access to some amount of their personal data. Depending on the
purpose of the application, the amount and the value of that data may be different. It may only
be user’s name, but it may also be more sensitive data, like medical records, financial records,
etc.
There are chances that some users may forget to log out and leave the session open. How
many times has it happened to you? Maybe your phone suddenly rang, or you needed to leave
immediately, leaving the browser on. Leaving a user session open is dangerous as someone
else may use that session to extract sensitive data.
One way to fight this issue involves tracking if the user has interacted with the app within a
certain period of time, then trigger logout if that time is exceeded. You may want to show a
popover, or perhaps a timer that warns the user that logout is about to happen. Or you may
just logout immediately when inactive user is detected.
Going one level down, what we want to do is count the time that’s passed from the user’s last
interaction. If that time period is longer than our threshold, we want to fire our inactivity
handler. If the user performs an action before the threshold is breached, we reset the counter
and start counting again.
This article will show how we can implement such an activity tracking logic based on this
example (https://codesandbox.io/s/activity-tracker-vanilla-js-mvlnc) .
resetUserActivityTimeout – This will be our method that’s responsible for clearing the
existing timeout and starting a new one each time the user interacts with the application.
inactiveUserAction – This will be our method that is fired when the user activity timeout
runs out.
JavaScript
function resetUserActivityTimeout() {
clearTimeout(userActivityTimeout);
userActivityTimeout = setTimeout(() => {
inactiveUserAction();
}, INACTIVE_USER_TIME_THRESHOLD);
}
function inactiveUserAction() {
// logout logic
}
OK, so we have methods responsible for tracking the activity but we do not use them
anywhere yet.
(#aa-step-2-tracking-activation) Step 2: Tracking
activation
Now we need to implement methods that are responsible for activating the tracking. In those
methods, we add event listeners (https://developer.mozilla.org/en-
US/docs/Web/API/EventTarget/addEventListener) that will call our
resetUserActivityTimeout method when the event is detected. You can listen on as many
events as you want, but for simplicity, we will restrict that list to a few of the most common
ones.
JavaScript
function activateActivityTracker() {
window.addEventListener("mousemove", resetUserActivityTimeout);
window.addEventListener("scroll", resetUserActivityTimeout);
window.addEventListener("keydown", resetUserActivityTimeout);
window.addEventListener("resize", resetUserActivityTimeout);
}
That’s it. Our user tracking is ready. The only thing we need to do is to call the
activateActivityTracker on our page load.
We can leave it like this, but if you look closer, there is a serious performance issue with the
code we just committed. Each time the user interacts with the app, the whole logic runs.
That’s good, but look closer. There are some types of events that are fired an enormous
amount of times when the user interacts with the page, even if it isn’t necessary for our
tracking. Let’s look at mousemove event. Even if you move your mouse just a touch, mousemove
event will be fired dozens of times. This is a real performance killer. We can deal with that
issue by introducing a throttler that will allow the user activity logic to be fired only once per
specified time period.
JavaScript
JavaScript
userActivityThrottler() {
if (!userActivityThrottlerTimeout) {
userActivityThrottlerTimeout = setTimeout(() => {
resetUserActivityTimeout();
clearTimeout(userActivityThrottlerTimeout);
userActivityThrottlerTimeout = null;
}, USER_ACTIVITY_THROTTLER_TIME);
}
}
We just created a new method that should be fired on user interaction, so we need to
remember to change the event handlers from resetUserActivityTimeout to
userActivityThrottler in our activate logic.
JavaScript
activateActivityTracker() {
window.addEventListener("mousemove", userActivityThrottler);
// ...
}
JavaScript
export default {
data() {
return {
isInactive: false,
userActivityThrottlerTimeout: null,
userActivityTimeout: null
};
},
// ...
JavaScript
// ...
methods: {
activateActivityTracker() {...},
resetUserActivityTimeout() {...},
userActivityThrottler() {...},
inactiveUserAction() {...}
},
// ...
Since we are using Vue and it’s reactive system, we can drop all direct DOM manipulations i.
(i.e. document.getElementById("app").innerHTML) and depend on our isInactive data
property. We can access the data property directly in our component’s template like below.
JavaScript
<template>
<div id="app">
<p>User is inactive = {{ isInactive }}</p>
</div>
</template>
Last thing we need to do is to find a proper place to activate the tracking logic. Vue comes
with component lifecycle hooks (https://vuejs.org/v2/guide/instance.html#Lifecycle-
Diagram) which are exactly what we need — specifically the beforeMount
(https://vuejs.org/v2/api/#beforeMount) hook. So let’s put it there.
JavaScript
// ...
beforeMount() {
this.activateActivityTracker();
},
// ...
There is one more thing we can do. Since we are using timeouts and register event listeners on
window, it is always a good practice to clean up a little bit after ourselves. We can do that in
another lifecycle hook, beforeDestroy (https://vuejs.org/v2/api/#beforeDestroy) . Let’s
remove all listeners that we registered and clear all timeouts when the component’s lifecycle
comes to an end.
JavaScript
// ...
beforeDestroy() {
window.removeEventListener("mousemove", this.userActivityThrottler);
window.removeEventListener("scroll", this.userActivityThrottler);
window.removeEventListener("keydown", this.userActivityThrottler);
window.removeEventListener("resize", this.userActivityThrottler);
clearTimeout(this.userActivityTimeout);
clearTimeout(this.userActivityThrottlerTimeout);
}
// ...