Keep setInterval running while tab is completely closed - javascript

Trying to make a real-time clock that uses setInterval (centisecond based) but I want it to run when the tab is not open or even when the computer is off. Is this even possible? If you have heard of the game cookie clicker, I'm pretty sure it at least runs when the tab is completely closed out of, so how do I replicate it, and if I cant, are there any other methods I can use to make a digital clock?

It's not possible to run code while a tab is closed or the machine is off. The best you could do is to periodically save information to localStorage, and then, whenever the script runs again, retrieve the saved information from localStorage and run all the calculations necessary to get up-to-date. Or, if it's as simple as a date, you might just check Date.now() every time the tab is opened.
Here's a very simplistic implementation:
const info = localStorage.savedInfo
? JSON.parse(localStorage.savedInfo)
: { count: 0, date: Date.now() };
const now = Date.now();
if (info.date < now) {
info.count += Math.floor((now - info.date) / 1000);
info.date = now;
}
function tick() {
info.count++;
console.log(info.count);
info.date = Date.now();
localStorage.savedInfo = JSON.stringify(info);
setTimeout(tick, 1000);
}
tick();
https://jsfiddle.net/gn9128ea/1/

Related

Basic javascript countdown timer that is accurate even on mobile while screen is off [duplicate]

This question already has answers here:
How to create an accurate timer in javascript?
(15 answers)
Closed last month.
Basically, I am trying to emulate something just like https://www.online-stopwatch.com/countdown-timer/ for use in a web app. Works fine using settimeout, but when the screen is turned off on Android, for example, javascript stops running (or is severely delayed) so a 5 minute timer might take 15 minutes to ring, or 5 minutes to ring.
Any solution would be much appreciated. I've also tried support workers but those also get stopped when the screen is off.
I can't figure out what technology they are using on the aforementioned website, but it looks like animation or something, and from my tests IS accurate even when the screen is off.
Thank you!
Edit: Nope - I did a number of more tests and even their timer does not work reliably on android.
Edit: the question was actually about a website being able to trigger some kind of sound, alert or notification while the phone screen is off, not about keeping track of the time. That is not possible due to permissions (thankfully).
Apps have access to additional permissions that might make this feasible to do as an app.
Original answer basically answering "How to measure elapsed time in a browser on mobile from clicking a button even when screen is off or browser is closed":
What I would do is as soon as the timer is started you store the timer start time in localstorage and then use a setInterval to update the UI based on your stored value. I would make this accurate to the second to avoid a very heavy UI update cycle. Something like this works (tested on Android / Opera)
(Cannot use a snippet due to localStorage allow-same-origin)
<div>-</div>
<button>Start</button>
<script>
const out = document.querySelector("div")
const btn = document.querySelector("button")
let timer, startTime;
btn.addEventListener("click", () => {
// If timer is started, reset
if (timer) {
clearInterval(timer)
}
startTime = Date.now()
localStorage.setItem("startTime", startTime)
timer = setInterval(() => {
const now = Date.now()
const millis = now - Number(localStorage.startTime)
const seconds = Math.floor((millis / 1000) % 60)
const minutes = Math.floor((millis / 1000 / 60) % 60)
out.innerText = minutes + ":" + seconds
}, 1000)
console.log("Started interval timer", timer)
})
</script>
You could make it even work between browser shutdowns by checking the localstorage during page load

Keep the JS/jQuery code working in Safari when the tab is not active

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.
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.
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.
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:
How can I make setInterval also work when a tab is inactive in Chrome?
iOS 5 pauses JavaScript when tab is not active
Safari JavaScript setTimeout stops when minimized
Chrome: timeouts/interval suspended in background tabs?
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?
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".
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);
});

stop Chrome from restoring the session

Is there any way to tell chrome to NOT restore the session when re-opening a closed page? Maybe a special header?
When my employees log into my company app it is very state dependant. If they are inactive for 60 minutes I want to log them out and delete all data relevant to what they were doing, but the problem is that Chome has this handy feature where it will re-open your page right where you left off and not question a thing. My website will not know that they had the browser closed for two days and the setTimeout will not fire for another 50 minutes.
I have another couple of wacky workarounds, but I would prefer it if I could just tell Chrome to not try salvaging old sessions and instead treat every opening like it was just opened for the first time. maybe a disable caching through javascript?
Edit:
I am using IIS to serve the Angular 9 static html and javascript.
So, as you mentioned, you're using a static website without a backend. Though you haven't mentioned anything, I'm assuming you're using sessionStorage or localStorage to handle authentication. If that's the case, what you can do is set a timer whenever a user logs in and maintain a localStorage to keep track of the idle time.
let obj_date = new Date();
let miliseconds = obj_date.getTime(); // Returns the number of miliseconds since 1970/01/01
localStorage.setItem("idle_time",miliseconds);
After that, keep calling the following function from within something like setInterval() every 10,20,30 or 60 seconds (as per your choice) to check if that time limit has expired.
function check_if_session_expired() {
let max_idle_minutes=60;
let miliseconds_now = obj_date.getTime();
let get_idle_time_in_miliseconds = localStorage.getItem("idle_time");
let one_minute_to_milisecond = 1000 * 60;
if ((Math.round(miliseconds_now / one_minute_to_milisecond) - Math.round(get_idle_time_in_miliseconds / one_minute_to_milisecond)) >= max_idle_minutes) {
console.log("expired");
//logout the user and clear sessionStorage/localStorage if you want
} else {
localStorage.setItem("idle_time",miliseconds_now);
}
}
You can use cookies to so the same.
What you want to do is destroy session from server side. Check the code below which is written in php. However, if you get the idea, you can use anything to implement it.
<?php
session_start();
//Expire the session if user is inactive for 60 minutes or more.
$expireAfter = 60;
//Assign the current timestamp as the user's latest activity
$_SESSION['last_action'] = time();
//Check to see if our "last action" session variable has been set.
if(isset($_SESSION['last_action'])){
//Figure out how many seconds have passed since the user was last active.
$secondsInactive = time() - $_SESSION['last_action'];
//Convert our minutes into seconds.
$expireAfterSeconds = $expireAfter * 60;
//Check to see if they have been inactive for too long.
if($secondsInactive >= $expireAfterSeconds){
//User has been inactive for too long. Kill their session.
session_destroy();
unset($_SESSION);
header("Location: http://".$_SERVER['SERVER_NAME'].":".$_SERVER['SERVER_PORT']."/example/login.php");
exit;
}
}
This is just a simple implementation and you can definitely extend it to make it work the way you want.

Timer with performance.now()

I want to realize timer using performance.now because Date.now depends on users clock (performance.now doesn't), and "Date.now-way" may crash the timer, when user changes system clock.
And I want to store timer in localStorage. When user close tab or browser and open it, timer have to resume.
Important condition: I want to take into account the time, when the browser/tab was closed. For example, user close browser, when timer value is 10 sec. After 10 sec user open the browser - timer value should be 20 sec.
This is the problem, that I faced.
This is code without using localStorage:
const timerNode = document.getElementById('timer');
const initialTimestamp = performance.now();
setInterval(() => {
const seconds = getSeconds(initialTimestamp);
timerNode.innerHTML = seconds;
}, 200);
function getSeconds(initialTimestamp) {
return Math.round((performance.now() - initialTimestamp) / 1000);
}
<div id='timer'></div>
Is there any way to store timer in localStorage using performance.now and resume it in cases, that I mentioned above (and observing the condition)?
Or maybe there is another way to get UNIX timestamp, that is not depends on system clock?
To demonstrate problem with Date.now() run this code and change system time, for example, set date to few days ago. You will see, that value of the timer is negative.
const timerNode = document.getElementById('timer');
const initialTimestamp = Date.now();
setInterval(() => {
const seconds = getSeconds(initialTimestamp);
timerNode.innerHTML = seconds;
}, 200);
function getSeconds(initialTimestamp) {
return Math.round((Date.now() - initialTimestamp) / 1000);
}
<div id='timer'></div>
Yes, you would use localStorage for this to persist across browser sessions.
You would use setItem to set the initial time and getItem to access it in new browser sessions. When the new browser session starts, you would check if a previous initial time existed. If it did, use it; if it didn't, create it.
var initialTimestamp = localStorage.getItem('initialTimestamp');
// If we previously set an initialTimestamp, convert it from string to number.
if (initialTimestamp) {
initialTimestamp = parseInt(initialTimestamp);
}
// If we never previously set an initial timestamp, create one now.
else {
initialTimestamp = performance.now();
localStorage.setItem('initialTimestamp', initialTimestamp);
}

How to have a timer which cannot be modified in javascript?

Basically, I am designing a quiz application with limited time. Use selects answer to a question and the next question loads using an Ajax request. All questions must be answered within a time frame of, say 2 minutes.
A clock ticks away to show how much time is left and as soon as it hits 0, results are shown. Now since the timer will be implemented using window.setTimeout(), it is possible that the value of timer variable be modified using an external bookmarklet or something like that. Anyway I can prevent this? I think this is implemented on file sharing sites like megaupload. Any forgery on the timer variable results in request for file being rejected.
Have .setTimeout() call an AJAX method on your server to synch time. Don't rely on the client time. You could also store the start time on the server for a quiz, and then check the end time when the quiz is posted.
You need to add a validation in your server side. When the client want to load the next question using an Ajax request, check whether deadline arrived.
The timer in client side js just a presention layer.
If the function runs as a immediately called function expression, then there are no global variables and nothing for a local script to subvert. Of course there's nothing to stop a user from reading your code and formulating a spoof, but anything to do with javascript is open to such attacks.
As others have said, use the server to validate requests based on the clock, do not rely on it to guarantee anything. Here's a simple count down that works from a start time so attempts to dealy execution won't work. There are no global variables to reset or modify either.
e.g.
(function (){
// Place to write count down
var el = document.getElementById('secondsLeft');
var starttime,
timeout,
limit = 20; // Timelimit in seconds
// Function to run about every second
function nextTick() {
var d = new Date();
// Set start time the first time
if (!starttime) starttime = d.getTime();
var diff = d.getTime() - starttime;
// Only run for period
if (diff < (limit * 1000)) {
el.innerHTML = limit - (diff/1000 | 0);
} else {
// Time's up
el.innerHTML = 0;
clearTimeout(timeout);
}
}
// Kick it off
timeout = window.setInterval(nextTick, 1000);
}());

Categories

Resources