4

I have a JS/jQuery code as shown below in which in which I want to keep the JS/jQuery code working when the session tab is not active.

The following code perfectly fine in Google Chrome but it doesn't work in Safari.

jQuery(document).ready(function ($) {

    let lastActivity = <?php echo time(); ?>;  // Line A

    let now = <?php echo time(); ?>;

    let logoutAfter = 3600;   // page will logout after 1800 seconds if there is no activity

    let userName = "<?php echo $_SESSION['user_name']; ?>";

    let timer = setInterval(function () {
        now++;
        let delta = now - lastActivity;
        console.log(delta);  // Line A
        if (delta > logoutAfter) {
            clearInterval(timer);
            //DO AJAX REQUEST TO close.php
            $.ajax({
                url: "/control/admin.php",
                type: 'GET', // GET also fine
                data: {action: 'logout', user_name: userName},
                success: function (data) {
                    window.location.href = "admin.php";
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    alert(textStatus);
                }
            });
        }
    }, 1000); //<-- you can increase it( till <= logoutAfter ) for better performance as suggested by @"Space Coding"
});

The value at Line A doesn't get incremented in Safari when the tab is not active but it works perfectly fine in Google Chrome. In Google Chrome, it works as expected.

Don't Panic
  • 13,965
  • 5
  • 32
  • 51
flash
  • 1,455
  • 11
  • 61
  • 132
  • It should be sufficient to include this script before loading any other js on your site: https://github.com/turuslan/HackTimer/blob/master/HackTimer.min.js (I found here: https://stackoverflow.com/questions/5927284/how-can-i-make-setinterval-also-work-when-a-tab-is-inactive-in-chrome) – alotropico Jul 24 '20 at 22:40
  • 1
    To be clear, is it desktop Safari that isn't working correctly or mobile (iPad/iPhone) Safari? – Chris Haas Jul 25 '20 at 13:07
  • @ChrisHaas Its desktop safari that isn't working properly. – flash Jul 25 '20 at 16:04

3 Answers3

0

You can replace counter (it counts seconds) with calculating time difference.

let lastActivity = new Date();
let logoutAfter = 3600;
...
let delta = (new Date()).getTime() - lastActivity.getTime();
if (delta > logoutAfter) {
    ...
}

P.S. So it must work even if the script itself is frozen when tab is inactive. Interval handler will be called at the moment when user activate this tab.

vdem
  • 82
  • 4
  • Hi, you have made changes in this line `let lastActivity = ; // Line A` ? I am not allowed to make changes in the line as it stores the last activity. – flash Jul 22 '20 at 13:25
  • @flash Ok let it be as before, - should work too I think. – vdem Jul 22 '20 at 14:12
  • @flash Forgot to say, replace lastActivity.getTime() with lastActivity – vdem Jul 22 '20 at 14:23
  • This http://jsfiddle.net/zsr8nd4p is what I have tried but it still doesn't work in safari. Let me know what changes I need to make in the fiddle to make it work. – flash Jul 22 '20 at 19:45
0

This approach will not work properly with multiple tabs opened. If user open new tab and started working in it, the earlier tab will logout the user as he is not active in that tab.

To overcome this, I will suggest to check the last active time from server using ajax call instead of doing it with javascript only.
Akshay Vanjare
  • 655
  • 3
  • 10
  • I think that will be another question. I am wondering if you know any reason why my code above is not working in safari. – flash Jul 25 '20 at 14:15
  • In WebExtension background pages, timers don't work properly. This is because background pages don't actually stay loaded all the time: the browser can unload them if they are not being used, and restore them when they are needed. There may be other reasons too, please refer link: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Reasons_for_delays_longer_than_specified – Akshay Vanjare Jul 25 '20 at 14:35
  • You can also use this: https://stackoverflow.com/a/5927432/1808009 – Akshay Vanjare Jul 25 '20 at 14:42
0

According to this very thorough (but old) answer, setInterval() execution on inactive tabs is limited to max 1/s, on both Safari and Chrome - but not stopped. There are also plenty of questions here on SO about Javascript getting paused or de-prioritised on inactive tabs, some of which include solutions:

Probably the best option to do what you are trying is to use Web workers:

Web Workers are a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface.

There is an example of how to do that in an answer to one of the questions above.

But there is also a much simpler option, though you should evaluate if it is safe considering you are relying on this to log users out.

My testing of your code reflects the question I linked to earlier which describes setInterval() being slowed, but not stopped. For me, Safari (v 13.1, macOS 10.14.6) does not actually fully pause Javascript, but slows down execution of the loop, by increasing amounts. I see this by opening the dev console, and watching the output of the console.log(delta) messages - they slow right down, first running only every 2s, then 4s, and so on, though sometimes faster. But they do not stop.

That output also gives a hint about the problem, and the solution. The delta values shown on the console do not represent the real time difference since lastActivity. They are just incrementing numbers. If you see a delta value appear on the console 10 seconds after the last one, it should logically be +10, right? But it is not, it is just one higher.

And that's the problem here - the code is not counting the true time difference, it is just counting iterations of the loop:

let timer = setInterval(function () {
    now++;    // <-- problem

This code correctly sets now to the current time only if setInterval() runs exactly every second. But we know that when the tab is inactive, it does not. In that case it is just counting the number of times the loop runs, which has no relation to the real time elapsed.

To solve this problem, we have to determine now based on the real time. To do that, let's switch to using JS to calculate our timestamps (PHP is rendered only once, on page load, so if you use it inside the loop it will just stay fixed at the initial value):

// Note that JS gives us milliseconds, not seconds
let lastActivity = Date.now();
let now = Date.now();
let logoutAfter = 3600 * 1000;

let timer = setInterval(function () {
    // PHP won't work, time() is rendered only once, on page load 
    // let now = <?php echo time(); ?>;
    now = Date.now();
    let delta = now - lastActivity;
    console.log('New timer loop, now:', now, '; delta:', delta);

Now, even if there is a pause of 10s between iterations, delta will be the true measure of time elapsed since the page was loaded. So even if the user switches away to another tab, every time the loop runs, it will correctly track time, even if it doesn't happen every second.

So what does this mean in your case?

  1. According to your report, JS is not running at all in the inactive tab. In that case, it can happen that the tab stays in the logged-in state, long past the time the user should have been logged out. However, assuming JS starts up again when you switch back the tab, the very first iteration of the loop will correctly calculate the time elapsed. If it is greater than your logout period, you will be logged out. So even though the tab stayed logged in longer than it should have, the user can't use it, since as soon as they switch to it they will be logged out. Note that "as soon" actually means "within 1 second plus the time it takes for the AJAX query to successfully log the user out".

  2. In my testing, JS does not stop in an inactive Safari tab, but slows right down. In this case, it would mean that the user would be automatically logged out on the inactive tab, though not right at the time they should be. If the loop runs say every 8s, it could mean that the user would be logged out up to 7s later than they should have been. If iterations slow down even more, the delay can potentially be even more. Assuming JS starts up again as normal as soon as the user switches back the tab, behaviour will be exactly as above, the first iteration in that case will log them out.

EDIT

Here's simplified, complete code, and a JSFiddle showing it running and working.

jQuery(document).ready(function($) {
    let lastActivity = Date.now();
    let now = Date.now();
    let logoutAfter = 3600 * 1000;

    let timer = setInterval(function() {    
        now = Date.now();
        let delta = now - lastActivity;
        console.log('New timer loop, now:', now, '; delta:', delta);
    
        if (delta > logoutAfter) {
            alert('logout!');
        }
    }, 1000); 
});
Don't Panic
  • 13,965
  • 5
  • 32
  • 51
  • Thanks for the detailed explanation. I copy pasted your code in the [fiddle](http://jsfiddle.net/h3x261y9/) The value of `delta` remains 0 in the console always where `now` prints the current timestamp but on console the value of timer continues unless there is an activity or user refreshes the page. – flash Jul 27 '20 at 20:25
  • I am wondering if there is any way we can check when the current timestamp is greater than the last activity of the user by 3600 seconds. I want specific actions to happen when the current timestamp is greater than the last activity by 3600 seconds. – flash Jul 27 '20 at 20:26
  • @flash Your fiddle is missing the only real change I made to your code - the line that determines `now`! :-) `now = Date.now();`. Without anything to change `now`, yes you are just subtracting the same 2 numbers each loop. [Here's a (simplified) JSFiddle of my code, working](http://jsfiddle.net/dont_panic/wg90jvoz/). I've also added it to the question. – Don't Panic Jul 27 '20 at 22:45
  • @flash Does this make sense? – Don't Panic Jul 30 '20 at 08:20
  • @flash I will let you know what issues I am having now. – flash Jul 31 '20 at 14:39
  • @flash ... helloooooo ... ? :-) Did my answer help, in the end? – Don't Panic Sep 02 '20 at 07:47