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:
- tracking article reading time
- auto saving form or document
- auto pausing game
- hiding video player controls
- auto logging out users for security reasons
I recently encountered a feature that involved that last example, auto logging out inactive users for security reasons.
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.
Step 1: Implement tracking logic
Let’s implement two functions. The first will be responsible for resetting our timer each time the user interacts with the app, and the second will handle situation when the user becomes inactive:
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.
let userActivityTimeout = null;
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.
Step 2: Tracking activation
Now we need to implement methods that are responsible for activating the tracking. In those methods, we add event listeners 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.
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.
Let’s do that now.
Step 3: Improve performance
First, we need to add one more variable that will keep reference to our throttler timeout.
let userActivityThrottlerTimeout = null
Then, we create a method that will create our throttler. In that method, we check if the throttler timeout already exists, and if it doesn’t, we create one that will fire the resetUserActivityTimeout
after specific period of time. That is the period for which all user activity will not trigger the tracking logic again. After that time the throttler timeout is cleared allowing the next interaction to reset the activity tracker.
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.
activateActivityTracker() {
window.addEventListener("mousemove", userActivityThrottler);
// ...
}
Bonus: Let’s reVue it!
Now that we have our activity tracking logic implemented let’s see how can move that logic to an application build with Vue. We will base the explanation on this example.
First we need to move all variables into our component’s data
, that is the place where all reactive props live.
export default {
data() {
return {
isInactive: false,
userActivityThrottlerTimeout: null,
userActivityTimeout: null
};
},
// ...
Then we move all our functions to methods
:
// ...
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.
<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 which are exactly what we need — specifically the beforeMount
hook. So let’s put it there.
// ...
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
. Let’s remove all listeners that we registered and clear all timeouts when the component’s lifecycle comes to an end.
// ...
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);
}
// ...
That’s a wrap!
This example concentrates purely on detecting user interaction with the application, reacting to it and firing a method when no interaction is detected within specific period of time. I wanted this example to be as universal as possible, so that’s why I leave the implementation of what should happened when an inactive user it detected to you.
I hope you will find this solution useful in your project!
Hello, Mateusz Rybczonek!
Could you tell me the code given in above is for real time website or experimental js like normal js coding?
Sorry for any poor english from above.
Hello Mahesh,
As mentioned below, this code is just a small part of the whole auto-logout functionality, the full feature must involve both frontend and backend part.
I imagined a few rough cases for inactivity detection that would require a more elaborate solution. What if your user’s mouse is broken. It continuously does micro movements. Or a user set-up a software that does it on purpose. Or, the user disabled the part of your script that is responsible for inactivity detection.
I’d rather complement your solution with a server-side solution that would check when was the last time the user asked for a resource or an action and ping that back to the app (or not, just directly log them out). Of course, most servers have built-in session expiration, but in some cases you wouldn’t want such a thing.
Hi Artur,
I agree, this is just a starter and it is only showing a way to detect an inactive user.
As you mentioned, usually there is a session on the backend that expires after certain period of time. That session is extended each time the user makes a call to the API.
This solution shows how you can detect if the user was inactive for a specific period of time and react to that inactivity. For example, you have session that expires after 10 mins, after 8 mins of inactivity you may want to show the user that the session will expire and he will be logged out if no action is taken.
Thank you for your comment.
This is a good start, but from personal experience is lacking a few important parts:
1. If security is the goal, client-side auto logout is not sufficient. The server must kill the session after a certain period of inactivity.
2. User may kill the browser or lose connection, again for security this should still cause the user to logout.
3. Because of 1 and 2 you are pretty much forced to display a “are you still there?” dialog, if “yes” is clicked ping the server to keep the session alive. While the user is active you may want to periodically ping the server to keep in sync with the client.
4. Client may have multiple tabs open, and you need to sync auth timeout across them. Localstorage works for this.
5. As a fallback, in case server and client get out of sync, especially since client is not reliable, you need to catch 400 errors and handle them.
Activity timeout is, in my experience, one of those things that seems simple but is actually a huge pain to do correctly.
Yeah, syncing between browser tabs using local storage seems like a great idea.
Hi Aaron,
I completely agree with your comment, this is just a starter and it is only showing a way to detect an inactive user.
Thank you for your comment.
Solution: just throttle a debounced logout method. Done.
Now there’s still code that is running each time the event happens. What about unregister the event listener?
Hi Jakob,
You can see the difference on those two sandboxes.
https://codesandbox.io/s/activity-tracker-vanilla-js-events-count-without-throttler-t89gx
https://codesandbox.io/s/activity-tracker-vanilla-js-events-count-with-throttler-4d3hy
Thanks for the tip about unregistering the event listeners, added to the examples.
Thanks a lot Mateusz!
This was really helpful :)