2

I am trying to get my website to set online users' status to 0 when they are inactive or close the browser. If they reopen the website or get back from idling (after timeout), I want the status to get back to 1 (unless they have been fully logged out due to long absence from the site)

Here is what I tried so far:

Inactive.php

include 'db.php';
mysql_query("UPDATE users SET status = 0 WHERE user_id = ".$_SESSION['user_id']."");

Check if browser closed

window.onbeforeunload = function() {
        $.ajax({
            url: 'inactive.php',
            type: 'GET',
            async: false,
            timeout: 4000
        });
    };

Check for idle timeout

var IDLE_TIMEOUT = 60; //seconds
var _idleSecondsCounter = 0;
document.onclick = function() {
    _idleSecondsCounter = 0;
};
document.onmousemove = function() {
    _idleSecondsCounter = 0;
};
document.onkeypress = function() {
    _idleSecondsCounter = 0;
};
window.setInterval(CheckIdleTime, 1000);

function CheckIdleTime() {
    _idleSecondsCounter++;
    var oPanel = document.getElementById("SecondsUntilExpire");
    if (oPanel)
        oPanel.innerHTML = (IDLE_TIMEOUT - _idleSecondsCounter) + "";
    if (_idleSecondsCounter >= IDLE_TIMEOUT) {
        alert("Time expired!");
        document.location.href = "inactive.php";
    }
}

My query doesn't seem to work. How can I tell it which user to check every x seconds?

Gadgetster
  • 473
  • 3
  • 12
  • 33

5 Answers5

3

The window.onbeforeunload is going to produce a race condition and won't be very reliable. Also you would want to use window.addEventListener('beforeunload', function () {...}); instead

The alert in CheckIdleTime will halt execution of the javascript, so the user will have to interact (click OK) to log out. Otherwise that approach seems fine.

Now for when a user leaves the page, generally you would set your cookies to expire on leaving the site, but it seems you want to have a running tally in your website of the active users.

For that you would probably want a two step approach, actively setting flags and timestamp for "last active." And also running a garbage collection script that sets user to "inactive" if you have not seen any activity from them in a bit.

In the event that you needed truely realtime logs of active users, you might be want to look into Node.js in particular Socket.IO which can deal much better with real time client-server IO.

Its likely far easier to run a query that updates the users to say that they are in fact active

 <script>
 setInterval(function () {
      $.post('active.php');
 }, 
 15000 // every 15 seconds
 );
 </script>

in active.php (assuming you add a new last_active DATETIME, and user_id is an int:

 mysql_query("UPDATE users SET status = 1, `last_active` = NOW() WHERE user_id = ". (int)$_SESSION['user_id']."");
 mysql_query("UPDATE users SET status = 0 WHERE `status` = 1 AND `last_active` < DATE_SUB(NOW(), INTERVAL 15 SECOND)"); // set all in active who have  not pinged in the last 15 seconds

Here might be how the schema looks

 CREATE TABLE `users`
    `id` IN NOT NULL,
    `status` INT(1) DEFAULT 0,
    `last_active` DATETIME
 );

you likely want to play around a bit with that "inactive" interval, and consider creating an index.

Whymarrh
  • 13,139
  • 14
  • 57
  • 108
Victory
  • 5,811
  • 2
  • 26
  • 45
  • Could you show me how to do the code for when the browser is closed? and I just put alert for testing but it would have to run the query instead somehow to set status to 0. And for idling, i do have a timestamp for every move on the website but not sure how to set it to inactive (or status to 0) using ajax – Gadgetster Apr 07 '14 at 06:34
  • @Gadgetster I think that's the main take away, that you don't want to prove so much that someone is "inactive" you want to prove that they are "active" If you approach the problem that way it will be much less buggy and far more accurate. – Victory Apr 07 '14 at 06:37
  • so you are saying by default everyone is set to 0 but if they are active set to 1. Alright, how can I run a check with ajax that will update a query every 15sec? – Gadgetster Apr 07 '14 at 06:40
  • I have a last_active int and a user_id int. I did exactly how you said and i can't tell if it works or not. every time i tried to refresh the localhost to see if the status changed it takes it as im using the website and updates the time. I tried not touching anything to see if the user goes offline but it didn't do anything. – Gadgetster Apr 07 '14 at 18:13
  • `last_active` should be a `TIMESTAMP` or `DATETIME` that you update manually but not an int. – Victory Apr 07 '14 at 18:40
  • ok i changed it to timestamp and it seems to start working now. if i set the timestamp to update manually, i get 0000-00-00 00:00:00 in the last_action row – Gadgetster Apr 07 '14 at 18:58
  • @Gadgetster not sure i 100% follow but let me update my answer again. – Victory Apr 07 '14 at 21:09
  • ok that seems to work. But it only works when I close the browser which is a good start. Is there a way to do the same thing for idling (ie. not touching the computer while the website is open)? – Gadgetster Apr 07 '14 at 22:35
  • @Gadgetster So above the `$.post('active.php');` you would want to have an if statement that just returns if the user is not idle. So you can bind to `onmousemove`, `onkeyup`, or whatever. And have that set a flag `active`. If the flag is not set, you return. – Victory Apr 08 '14 at 10:04
  • would you mind updating the code with your explanation? – Gadgetster Apr 13 '14 at 18:24
1

This is what I am apply at my system to keep track user still online. I make the client site ping back every minute telling the DB he still online.

About the detect browser close you can check out this Close/kill the session when the browser or tab is closed by Daniel Melo

<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
var timeLeft = Number(60);

setInterval(function(){
    timeLeft--;
    if(timeLeft<0){
        expiredLogout();
    }
    $("#timer").html(timeLeft);
    //If timer less than 30, show message to user will logout soon
    if(timeLeft<30){
        $("#logoutMsg").show( "slow" );
    }else{
        $("#logoutMsg").hide( "slow" );
    }
    $("#logoutMsg").html('You will logout soon, in '+timeLeft+' sec.');
},1000);
//Initial Function
$(function() {
    //Mouse move top up the time left value;
    $(document).mousemove(function(event) {
        timeLeft = Number(60);
    });
    stillAlive();
    //Every 1 minute ping the active.php
    setInterval(function(){ stillAlive();},60000);
});
//Redirect to other page if time out 
function expiredLogout(){
    window.location = "inactive.php";
};
function stillAlive(){
    //Update userID 1 still active
    var postVal = {userID:1};
    $.post("active.php", postVal, null, "json")
    .success(function(data) {})
    .error(function() {})
    .complete(function() {});
};
</script>
<label id="timer"></label>
<div id="logoutMsg"></div>
</body>
</html>

Demo

Community
  • 1
  • 1
Cheong BC
  • 306
  • 1
  • 7
  • A few problems with your code, is that stillAlive() will continue to fire regardless of other events. There is nothing set to clearInterval(). Also, the server ping interval isn't reset after the user becomes active again. Additionally there is no handling of keydown/keyup, or click events. – Kraang Prime Apr 14 '14 at 07:03
  • Thanks @SanuelJackson, you are right, my code is not full event handling, I just provide part of the idea how I handle the user keep live. – Cheong BC Apr 14 '14 at 07:18
1

There is a javascript library that may be helpful.
This is Ifvisible.js.

It will allow you to detect when the user is no longer active.

For example you can do something like this:

//Handle tab switch or browser minimize states    
ifvisible.on("blur", function(){
    //ajax call for inactive.php -> SET status = 0
});

ifvisible.on("focus", function(){
    //ajax call for active.php -> SET status = 1
});

//Detection of inactivity for xx seconds
ifvisible.setIdleDuration(120); // Page will become idle after 120 seconds

ifvisible.on("idle", function(){
    //ajax call for inactive.php -> SET status = 0
});

ifvisible.on("wakeup", function(){
    //ajax call for active.php -> SET status = 1
});

Of course, you can call the same inactive.php program with a different parameter for know if you want to set the status to 1 or 0.
For example, with your ajax call:

// if inactive : SET status = 0
    $.ajax({
        url: 'inactive.php?status=0',
        type: 'GET',
        async: false,
        timeout: 4000
    });
// if active : SET status = 1
    $.ajax({
        url: 'inactive.php?status=1',
        type: 'GET',
        async: false,
        timeout: 4000
    });

In your Inactive.php :

if ($_GET["status"] == "1") // status = 1 -> active
    $tatus = "1";
else // status = 0 -> inactive
    $tatus = "0";

mysql_query("UPDATE users SET status = " . $tatus . " WHERE user_id = ".$_SESSION['user_id']."");

go to the website for more infos about Ifvisible.js.

I hope it will help you. :)

doydoy44
  • 5,720
  • 4
  • 29
  • 45
  • That ifevery.js is quite nice. Bump for cool alternative to jquery-idletimer.js -- how active is that project ? – Kraang Prime Apr 14 '14 at 07:08
  • @SanuelJackson: Hi, congratulation for the bounty. :). I'm sorry, I don't understand your question : `how active is that project ?`. – doydoy44 Apr 14 '14 at 08:54
  • What I mean by that is -- the `ifvisible.js` script is maintained -- how frequent are the updates to it, or was it just recent --- or does it look like it will be adandoned by the original developer. – Kraang Prime Apr 14 '14 at 14:01
  • @SanuelJackson: Alright! :), I'm sorry for my answer, but I do not know, I found on a blog, and I found it really good. ;) On Github, there was a last commit there two months, so it does not look abandoned. but it is not very active. May be that it has no bugs. :D – doydoy44 Apr 14 '14 at 14:07
  • It does seem fairly stable. I will definitely run some tests on it for completeness in the idle detection segment. It doesn't seem to have a callback for onresume - closest is onEvery(), however that activity occurs only when page is visible again. Thank you for the info tho, will stick with jquery-idletimer (at least until i write in a resume function if it allows for it) ^.^ – Kraang Prime Apr 14 '14 at 14:14
1

Revised code

This code will make reference to the example I wrote available here >> jquery-idle with jquery-ui Dialog

Libraries used :

Embedded Library Example:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="http://thorst.github.io/jquery-idletimer/prod//src/idle-timer.js"></script>
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/themes/smoothness/jquery-ui.css" />
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"></script>

Without jQuery dialog :

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script src="http://thorst.github.io/jquery-idletimer/prod//src/idle-timer.js"></script>

Please keep in mind that you may switch out the dialog code with whatever your prefered dialog method is. I included jquery-ui for dialog to keep things as simple as possible. This also does not handle the beforeunload event, as you have that covered in your code, however I would suggest read further here >> beforeunload stackoverflow article <<

Explanation

HTML


This line of code is for the placeholder where the countdown timer will be stored. To simplify things, I also use this when the timer has expired to display "Session Expired"

<div id="sessionSecondsRemaining" style="font-size: 22px; font-weight: bolder;"></div>

This is a very simple Modal dialog using jQuery UI. You can expand on it or replace it at your leisure.

<div id="dialog-confirm" title="Logout" style="display: none;">
<p><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;">Your session has expired.</span></p>
</div>

Without jQuery Dialog

<div id="sessionSecondsRemaining" style="display:none;"></div>

CSS


This is just a small hack due to a bug in the grayed background not displaying properly for jQuery UI modal dialogs (why has this not been fixed yet -- facepalm )

/* override jquery ui overlay style */
.ui-widget-overlay {
   background-image: none !important; /* FF fix */
   background: #000 url(images/new-ui-overlay.png) 50% 50% repeat-x;
}

Without jQuery Dialog

  • No CSS needed

Javascript


This section is where you can configure the parameters for jquery-idletimer.

        var
            session = {
                //Logout Settings
                inactiveTimeout: 10000,     //(ms) The time until we display a warning message
                warningTimeout: 10000,      //(ms) The time until we log them out
                minWarning: 5000,           //(ms) If they come back to page (on mobile), The minumum amount, before we just log them out
                warningStart: null,         //Date time the warning was started
                warningTimer: null,         //Timer running every second to countdown to logout
                logout: function () {       //Logout function once warningTimeout has expired
                    //window.location = settings.autologout.logouturl;
                },

                //Keepalive Settings
                keepaliveTimer: null,
                keepaliveUrl: "",   // set the Keep Alive URL here (aka keepalive.php)
                keepaliveInterval: 5000,     //(ms) the interval to call said url
                keepAlive: function () {
                    $.ajax({ url: session.keepaliveUrl });
                }
            }
        ;

To add 'keepalive.php' support, simply set the full URL for where keepalive.php is located (and any parameters you wish to pass, since you are using sessions, you shouldn't need any).

                keepaliveUrl: "http://example.com/keepalive.php",   // set the Keep Alive URL here (aka keepalive.php)

This line, initializes and sets the value in the #sessionSecondsRemaining div.

                $('#sessionSecondsRemaining').html(Math.round((session.warningTimeout - diff) / 1000));

This section is where you would put the code that controls your dialog warning the user of a session expiration countdown (usually #sessionSecondsRemaining would be in this dialog)

                $( "#dialog-confirm" ).dialog({
                    resizable: false,
                    height:140,
                    modal: true,
                    buttons: {
                        "Extend": function() {
                            clearTimeout(session.warningTimer);
                            $( this ).dialog( "close" );
                        },
                        Cancel: function() {
                            session.logout();
                            $( this ).dialog( "close" );
                        }
                    }
                });

Without jQuery Dialog

  • Remove the last block

If you notice, 'Extend', terminates the warning timer, and Cancel calls the logout function (also configurable above)

Lastly, this block is very important to what happens in the event the timer counts down to zero, and for controlling the display of the countdown inside #sessionSecondsRemaining

                    if (remaining >= 0) {
                        $('#sessionSecondsRemaining').html(remaining);
                    } else {
                        $( '#dialog-confirm' ).dialog( "close" );
                        clearInterval(session.warningTimer);
                        $( '#sessionSecondsRemaining' ).html('Session Expired');
                        session.logout();
                    }

Under the else, is probably the only spot you will really need to modify in the above block. In there, I call the session.logout() function (should really be the last line after dialogs are cleaned up, but this is just a demo). This is where you close the dialogs, and/or redirect the user to the session expired page, or display the message. If staying on the same page, make sure to clearInterval(session.warningTimer);. If not, then that line does not matter.

Without jQuery Dialog

                    if (remaining >= 0) {
                        $('#sessionSecondsRemaining').html(remaining);
                    } else {
                        clearInterval(session.warningTimer);
                        session.logout();
                    }

keepalive.php

if (session_status() !== PHP_SESSION_ACTIVE) { session_start(); }
include 'db.php';
$maxtimeout = 15; // Seconds for max timeout before forcing session reset on other users.
mysql_query("UPDATE users SET status = 1 WHERE user_id = ".$_SESSION['user_id']."");
mysql_query("UPDATE users SET status = 0 WHERE user_id <> ".$_SESSION['user_id']." AND (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(`timestamp_field`)) > " . $maxtimeout . "";

This task should be set to run server-side to cleanup the database from any strays (if you have a lot of activity, then you won't need this script)

cron.php

include 'db.php';
// Set this for a longer timeout than in keepalive.php
$maxtimeout = 90; // Seconds for max timeout before forcing session reset on other users.
mysql_query("UPDATE users SET status = 0 WHERE (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(`timestamp_field`)) > " . $maxtimeout . "";
Community
  • 1
  • 1
Kraang Prime
  • 9,981
  • 10
  • 58
  • 124
  • i have the session_start somewhere above just didn't include it here. Will this code work for idle on the website and for browser close? I need the user to be 0 for either case and 1 for active on the website – Gadgetster Apr 14 '14 at 04:50
  • The javascript above sends a "ping" to the server to keep the session alive. The problem with closing the window, is that some browsers do not allow for that event to occur unless they are browsing to a different site. For example, if you close the entire browser down, you will not get an event fired --- However... if you add a timestamp field to your database, and set it to ON_UPDATE = NOW(), then you can have another script (via cron), check to see if there are users who havn't pinged the server in XX duration, and set them to 0. – Kraang Prime Apr 14 '14 at 04:54
  • @Gadgetster - if you want, I can write an example. It's an asyncronous method on keeping the sessions active and the database up-to date. – Kraang Prime Apr 14 '14 at 04:56
  • @Gadgetster - actually, I just noticed that the answer by 'Victory' is exactly that. Just make sure on every script to start the session before exec'ing `$_SESSION` var stuff. The only thing is his code is dependant upon any user pinging the server during a time-frame, so the accuracy may not be the best depending on user base. – Kraang Prime Apr 14 '14 at 05:00
  • Yeah victory's code does work on browser close. I am trying to figure out how to combine your idling check with his browser close check code – Gadgetster Apr 14 '14 at 05:24
  • should I create a keepalive.php that is separate from active.php and have the two codes do their own thing? would that over load the system with too many request from to the server or something? – Gadgetster Apr 14 '14 at 05:26
  • I tried to create keepalive.php and including your codes in. I put 30 for IDLE_TIMEOUT and nothing seemed to happen. – Gadgetster Apr 14 '14 at 05:34
  • ok -- I have written a very extensive Idle check solution -- bear with me as it's a bit of a doozy -- combines the jquery-idle library + jqueryui, (jquery of course), and minimal css. I will scrap the above and write into this and explain as best I can. – Kraang Prime Apr 14 '14 at 05:52
  • @Gadgetster - complete explanation is up. I hope this helps you complete the solution. I have taken some very complex (reliable) methods and simplified them as best I can for you. – Kraang Prime Apr 14 '14 at 06:25
  • @Gadgetster - I have made some minor adjustments to this to make sure all bases are covered. The `beforeunload` stuff, you will still need to handle (and i dug up a SO article on that issue specifically for you), however, even without that, if a users session expires and another user is active (and/or you have cron.php set to run as a cron job (scheduled on the server), then you should be covered give or take a few seconds). – Kraang Prime Apr 14 '14 at 06:42
  • Thank you for the time and effort to put the answer together. really appreciate it. Just a question, is there a way to not use these libraries and to achieve similar result? I don't really care telling the user how long the have left. It should log out if they haven't used it for a set time – Gadgetster Apr 14 '14 at 07:16
  • That is pretty straight-forward. Just remove the dialog code. Unfortunately, using a library for the idle process tremendously simplifies things as there is a lot involved with monitoring user activity accurately. You can try swapping out the jquery-idletimer javascript with `doydoy44` ifvisible.js although I can't vouch for it as I havn't tested it to the extend I have used jquery-idletimer. Also, if you don't need the dialog, then you can remove the code that opens the dialog, and add `display:none;` for #`sessionSecondsRemaining`. – Kraang Prime Apr 14 '14 at 07:24
  • @Gadgetster - Additionally, you will also be able to remove the jquery-ui javascript and css (leaving only jquery-idletimer + jquery itself) – Kraang Prime Apr 14 '14 at 07:25
  • @Gadgetster - I updated the answer to clarify which parts of the script to modify to remove jQuery UI stuff completely. So you can use either method :) – Kraang Prime Apr 14 '14 at 07:31
0

The first question would be how you define "idle". What terminates an "idle" period? Bringing the focus to the browser window? Scrolling the page? Clicking on the page? Clicking a button? Clicking a link? - The latter seems to be the approach most sites use, and this can be done on the server side alone: If the user did not open another page on your site for, say, 5 minutes, then he is considered idle. After 15 minutes he is logged out by the system.

JimmyB
  • 12,101
  • 2
  • 28
  • 44
  • pretty much if there is not movement at all, like he went to get coffee and left the website on for an hour I want his status to change to 0 (offline). Can you show me how to do that? – Gadgetster Apr 04 '14 at 18:01
  • if you look in his code posted in the question, you will notice he has assigned to click(), mousemove(), keypress() and onbeforeunload(). – Kraang Prime Apr 14 '14 at 07:10