Change time allowed for server response in offline.js - javascript

I am using offline.js in a mobile web project. In the specific environment I am running in, the script seems to be a little over-zealous in reporting that the connection to the server is lost. How do I go about modifying it to allow the server a longer time to respond?

Try to set the check in Offline.options with the required xhr:
Offline.options = {
checks : {
xhr : {
url : function () {
return "/favicon.ico?_=" + ((new Date()).getTime());
},
timeout : 10000, // change this to the timeout you need
type : 'HEAD'
}
}
};
Check the source at line 28 for the defaults.
You can see the way to set the checks under the Checking header in the documentation.

Consider this:
Options (any can be provided as a function), with their defaults:
{
// Should we check the connection status immediatly on page load.
checkOnLoad: false,
// Should we monitor AJAX requests to help decide if we have a connection.
interceptRequests: true,
// Should we automatically retest periodically when the connection is down (set to false to disable).
reconnect: {
// **How many seconds should we wait before rechecking**.
initialDelay: 3,
// **How long should we wait between retries**.
delay: (1.5 * last delay, capped at 1 hour)
},
// Should we store and attempt to remake requests which fail while the connection is down.
requests: true,
// Should we show a snake game while the connection is down to keep the user entertained?
// It's not included in the normal build, you should bring in js/snake.js in addition to
// offline.min.js.
game: false
}

Related

Log a user out of a website when they put their computer to sleep

This is a bizarre one. We have a Laravel website, and on said site we have a timer per user, where they get 15 minutes of being inactive before being booted.
We do this through a timer that sits on the page in a react component, it works as we want it to, but now we have a new issue: If a user is logged in and shut the lid of their laptop the website should boot them. Banks do this, Schools and Universities do this, Government sites also do this. So it is possible, just not sure how.
We do use web sockets, using laravel-websockets library and Echo. What I would like to see happen is:
Once you close your laptop boot you to the login screen. So the next time you open the laptop and login, and see the browser you are on the login screen. It doesn't have to happen that quickly, but we need a way to send something to the front end basically telling them to refresh the page, once the session is killed, we set the session lifetime on laravel of 15 minutes.
Some people have suggested in other similar questions:
to create a custom web-socket handler
To compare the session cookie (in the browser) with the user cookie on the back end.
To have a timer running on the front end (we do, it just stops when you close the laptop lid)
The most popular one seems to be using web-sockets, listening for the user to disconnect and then boot them, which is fine and all, but then how do you send a request to a browser thats suspended to then boot them?
I have found requestIdleCallback() But again, I don't think this is what I want if I already have a heartbeat timer on the site. It also doesn't work in all browsers.
I am very lost here on how to accomplish this, the example I can give is:
Log in to your bank, put your computer to sleep, wait 15-20 minutes, awaken the computer, log in and see your bank now has you on the login screen. That's what I want. But I don't know how to accomplish that.
You cant send events to a "sleeping" browser from the back end, and while yes this would have to be a back end solution, how do you update the front end then, so that they are on the logout screen when they reawaken the laptop or computer?
First, let's expand on why Banking websites log you out after 15 minutes without activity. It's a PCI requirement for security.
PCI-DSS requirement 8.1.8:
8.1.8 If a session has been idle for more than 15 minutes, require the user to re-authenticate to re-activate the terminal or session.
In order to achieve this the solution is actually far more primitive than you imagine it to be. It neither requires the use of websockets nor knowing anything about the state of the client's machine (sleep or awake or otherwise). All that is required is knowing the time between the current request using that session and last request using the same session and ensuring they are not greater than 15 minutes apart. If they are the user is to be re-authenticated. If they aren't you may proceed with the request.
The "session timed out" message
You're probably then wondering (if it's that simple) how does the session timed out message appear when you put the computer to sleep and wake it back up. This part is deceptively simple.
When the computer is put to sleep the browser actually disconnects all TCP/IP connections which in turn shuts down the event loop in the javascript engine. So timers don't work. But when the browser wakes up again it attempts to refresh some things including the page itself. So when the page is refreshed the request goes back out to the server invoking the server to require the user re-authenticate.
However, this won't account for the javascript message modal (if that's what you're referring to) which some banking websites do. Also not all browsers do a hard refresh on the page in all scenarios. So another approach can be taken. Rather than have a timer in the browser that times out after 15 minutes you can simply store the page load time in javascript as a timestamp and have a 1 second interval time out that compares that timestamp to the computer's current timestamp. If they are more than 15 minutes apart, the session should be terminated.
window.onload = function() {
sessionStart = Date.now();
timer = setInterval(function() {
if (Date.now() - sessionStart > 15 * 60 * 1000) {
clearTimeout(timer);
alert("Session Timed out!");
window.location = "http://www.example.com/login";
}
}, 1000);
};
Even if the computer goes to sleep and the timer stops, the session will eventually time out on the server side (see section below for details) and when the computer wakes up again the timer with a 1 second interval will eventually startup again, invoking the message (as if the user timed out while the computer was asleep). The time lost between the time the computer went to sleep and the time the computer wakes up won't matter as the timestamp will remain in memory. The disconnect between the client and server is unimportant because they don't need to communicate this information in order for the session to be properly terminated on the server side. The server can do its own garbage collection and terminate the session without communication from the client (i.e asynchronously).
Believe it or not Banks don't care about activity inside of the client. They only care about request activity to the server. So if you're wondering how do they keep the session alive for greater than 15 minutes when the user is on the same page for that long, they simply send a AJAX request in the background to refresh the session after asking the user if they still want to continue.
This can be done in the same onload event callback we used earlier like so:
window.onload = function() {
sessionStart = Date.now();
timer = setInterval(function() {
if (Date.now() - sessionStart > 10 * 60 * 1000) {
if (confirm("Your session is about to timeout. Do you wish to continue?")) {
// send ajax request to refresh session TTL here
// reset the timer
sessionStart = Date.now();
}
} else if (Date.now() - sessionStart > 15 * 60 * 1000) {
clearTimeout(timer);
alert("Session Timed out!");
window.location = "http://www.example.com/login";
}
}, 1000);
};
Handling session termination on the server side
To handle the session termination on the server side there are several approaches. Depending on which one you use you will need different tactics. One is using PHP's default session handler and setting the session.max_lifetime to expire after 15 minutes (this deletes the session data entirely on the server side thus invalidating the client's cookie).
If you let the default session handler mechanism do it you can run into issues depending on which handler is used (files, memcached, redis, custom, etc).
With the files (default handler) the garbage collection happens in one of two ways:
Most Debian based systems do their own GC through a cron job (which works out great for your scenario)
Other distros let PHP's default GC mechanism handle it, which is based on a probabilistic outcome from each incoming request to PHP that checks file mtime's on session files and deletes those past their session.max_lifetime. The problem with this approach is that on low-traffic sites a session could potentially sit there on the server for a long time until enough requests come in (depending on the session.gc_probability score) to invoke the GC to clean up the session files.
With memcached and redis based handlers you don't have this problem. They will handle purging the memory automatically. Sessions may still remain in physical memory for a time past their lifetime, but the daemon will not be able to access them. If you're concerned about this bit for security you can encrypt your sessions at rest or find a key/value store that has stricter memory purging GC mechanism.
With a custom session handler you will have to build your own GC mechanism. Through SessionHandlerInterface you'd implement a gc method that hand you the session's maximum lifetime interval and you'd be responsible for verifying if the session has passed its lifetime based on this interval and do your garbage collection from there.
You can also setup a separate end-point that checks the session TTL (via asynchronous AJAX request on the client side) and sends back a response if the session has expired (forcing the javascript to re-authenticate the user).
UPDATE
Regarding the WebSocket request, I assume you're using Laravel WebSockets with pusher. Pusher.io does not support timeout, you can read this support article "Do you plan to add a connection timeout feature to the Channels pusher-js client library?". You can test it out if you enable Laravel debug mode (APP_DEBUG=true inside .env) and beggin laravel-websockets from terminal (php artisan websockets:serve) so you can see the output log events. If you try to close the laptop lid or set computer to hibernation mode (sleep), you won't see any messages regarding this event. You cannot do it with the pusher protocol. There is the member_removed Presence event, but that triggers only when you close the tab or you logout. Of course you can trigger your client custom event to the presence channel, but to do that you also need a timer setup to the client side and you'll have to create a service provider for the laravel-websockets server like this github issue "Exist a way to implement webhooks?".
Some people have suggested in other similar questions:
...
To have a timer running on the front end (we do, it just stops when you close the laptop lid)
That happens because client timers halt execution on hibernation, thus they continue from where they were before. But if you use a date variable to save the time, that variable will not get updated when the computer goes to hibernation, thus you'll know when it goes out from sleep by checking that date variable which in compare to current time will have significant difference and will be greater than the timer interval.
Implementing time logic in client
You can also see this implementation to this related Q/A: Can any desktop browsers detect when the computer resumes from sleep?
You can setup a timer in the client to run each minute. We won't rely on the timer interval, but instead that timer will check an outer scope date variable if the time span since last timer is greater than 15 minutes; if it is, then that means that the browser/JS halted execution for some reason, possibly hibernation of the device (sleep) and then you redirect the user to the logout route.
Example JS client code:
// Set a variable to check previous time
let clientSession = new Date;
// Setup the client session checking timer
let clientSessionTimer = setInterval(() => {
const now = new Date;
// Get how many seconds have passed since last check
const secondsSpan = (now - clientSession) / 1000;
// If the 1 minute timer has exceeded 15 minutes trigger logout and clear timer
if (secondsSpan > (60 * 15)) {
// For some reason JS halted execution, so we'll proceed with logging out
clearInterval(clientSessionTimer);
window.location.href = '/logout/session'
} else {
// The timer runs as it should, update the clientSession time
clientSession = now;
}
}, 1000 * 60);
You can check this simple example but using 1 second timer with 15 seconds logout here. Best to test it on a laptop with closing the lid and then open it again after 15 seconds a minute of two, because if you have many programs running, the computer takes some time to save memory state so to complete hibernation mode and halt execution.
Web Workers Example
You can even use Web Workers API to setup a web worker to be much safer:
Page JS code:
const logoutWorker = new Worker('logoutWorker.js');
logoutWorker.onmessage = function (ev) {
if (ev && ev.data === 'wakeup') {
logoutWorker.terminate();
// window.location.href = '/logout/session'
} else {
// The timer runs as it should, nothing to do
}
}
Web worker logoutWorker.js code:
let clientSession = new Date();
let clientSessionTimer = setInterval(() => {
const now = new Date;
const secondsSpan = (now - clientSession) / 1000;
if (secondsSpan > 15) {
postMessage('wakeup'); // Send a message wakeup to the worker page
clearInterval(clientSessionTimer); // Clear the timer
} else {
clientSession = now; // Update the clientSession timer variable
postMessage('update'); // And post a message to the page ONLY IF needed
}
}, 1000);
You can also check the Web Worker example with the same 15 seconds timer here.
So Idea is behind setInterval and Sockets,
setInterval is supported in most browsers and javascript WbsocketApi is supported in almost everybrowser.
Brief overview:
setInterval() - this function behaviour is following when your computer is at sleep/suspended/hibernate mode it is paused and when you are at awaken mode it resumes itself.
The following code does the following, at first(maybe at the same time but) it starts php server_socket listening to the connections,
than javascript websocket api sends current timestamp in Unix timestamp milliseconds in every 2 seconds you can have 1 second it is up to you.
after that php server socket is getting this time and checks if it has anything like previous time to compare, when the code is first instantiated php does not has anything like previous time to compare it to the time which was sent from javascript websocket, so php does nothing but saves this time in the session called 'prev_time' and waits for another time data to be recieved from javascript socket, so here begins second cycle.
when php server socket new time data from javascript WebsocketApi it checks it has anything like previous time to compare to this newly received time data, it means that php checks if session called 'prev_time' exists, as we are in the second cycle php discovers that it exists, grabs it's value and does following $diff = $new_time - $prev_time, $diff will be 2 seconds or 2000 miliseconds because remember our setInterval cycle happens in every 2 seconds and time format we are sending is in miliseconds,
than php checks if($diff<3000) if difference is less than 3000 if it is it knows that user is active, again you can manipulate this seconds as you wish, I choose 3000 because possible latency in the network which is almost impossible but you know I am always cautious when it comes to networks, so let's continue, when php determines that user is active php just resets 'prev_time' session with the value of $new_time which was newly received and just for testing purposes it sends message back to javascript socket,
but if $diff is more than 3000 it means that something paused our setInterval and there is only way it can happen and I think you already know what I am saying, so in the else logic of ( if($diff<3000) ) you can logout user by destroying specific session and if you want to redirect you can send some text to javacript socket and create a logic which will execute window.location = "/login" depending on the text, that's it here is the code:
First it is index.html file just to load javascript:
<html>
<body>
<div id="printer"></div>
<script src="javascript_client_socket.js"></script>
</body>
</html>
then it is javascript it is not really beautifully coded but you can figure out READ COMMENTS THEY ARE IMPORTANT:
var socket = new WebSocket('ws://localhost:34237'); // connecting to socket
// Open the socket
socket.onopen = function(event) { // detecting when connection is established
setInterval(function(){ //seting interval for 2 seconds
var date = new Date(); //grabing current date
var nowtime = Date.parse(date); // parisng it in miliseconds
var msg = 'I am the client.'; //jsut testing message
// Send an initial message
socket.send(nowtime); //sending the time to php socket
},2000);
};
// Listen for messages
socket.onmessage = function(event) { //print text which will be sent by php socket
console.log('php: ' + event.data);
};
// Listen for socket closes
socket.onclose = function(event) {
console.log('Client notified socket has closed', event);
};
now here is part of php code, don't worry down there is full code too but this part is actually what does above mentioned jobs you will meet other functions too but they are for decoding and working with javascript sockets so it is actual thing right here READ COMMENTS THEY ARE IMPORTANT:
<?php
$decoded_data = unmask($data /* $data is actual data received from javascript socket */); //grabbing data and unmasking it | unmasking is for javascript sockets don't mind this
print("< ".$decoded_data."\n");
$response = strrev($decoded_data);
$jsTime = (int) $decoded_data; /* time sent by javascript in MILISECONDS IN UNIX FORMAT */
if (isset($_SESSION['prev_time'])) { /** check if we have stored previous time in the session */
$prev_time = (int) $_SESSION['prev_time']; /** grabbing the previous time from session */
$diff = $jsTime-$prev_time; /** getting the difference newly sent time and previous time by subtracting */
print("$jsTime - $prev_time = $diff"); /** printing the difference */
if($diff<3000){ /** checking if difference is less than 3 second if it is it means pc was not at sleep
*** you can manipulate and have for example 1 second = 1000ms */
socket_write($client,encode("You are active! your pc is awakend"));
$_SESSION['prev_time'] = $jsTime; /** saving newly sent time as previous time for future testing whcih will happen in two seconds in our case*/
}else { /** if it is more than 3 seconds it means that javascript setInterval function was paused and resumed after 3 seconds
** So it means that it was at sleep because when your PC is at sleep/suspended/hibernate mode setINterval gets pauesd */
socket_write($client,encode("You are not active! your pc is at sleep"));
$_SESSION['prev_time'] = $jsTime;
}
}else { /** if we have not saved the previous time in session save it */
$_SESSION['prev_time'] = $jsTime;
}
print_r($_SESSION);
?>
And here is the full code of php:
<?php
//Code by: Nabi KAZ <www.nabi.ir>
session_abort();
// set some variables
$host = "127.0.0.1";
$port = 34237;
date_default_timezone_set("UTC");
// don't timeout!
set_time_limit(0);
// create socket
$socket = socket_create(AF_INET, SOCK_STREAM, 0)or die("Could not create socket\n");
// bind socket to port
$result = socket_bind($socket, $host, $port)or die("Could not bind to socket\n");
// start listening for connections
$result = socket_listen($socket, 20)or die("Could not set up socket listener\n");
$flag_handshake = false;
$client = null;
do {
if (!$client) {
// accept incoming connections
// client another socket to handle communication
$client = socket_accept($socket)or die("Could not accept incoming connection\n");
}
$bytes = #socket_recv($client, $data, 2048, 0);
if ($flag_handshake == false) {
if ((int)$bytes == 0)
continue;
//print("Handshaking headers from client: ".$data."\n");
if (handshake($client, $data, $socket)) {
$flag_handshake = true;
}
}
elseif($flag_handshake == true) {
/*
**** Main section for detectin sleep or not **
*/
if ($data != "") {
$decoded_data = unmask($data /* $data is actual data received from javascript socket */); //grabbing data and unmasking it | unmasking is for javascript sockets don't mind this
print("< ".$decoded_data."\n");
$response = strrev($decoded_data);
$jsTime = (int) $decoded_data; /* time sent by javascript in MILISECONDS IN UNIX FORMAT */
if (isset($_SESSION['prev_time'])) { /** check if we have stored previous time in the session */
$prev_time = (int) $_SESSION['prev_time']; /** grabbing the previous time from session */
$diff = $jsTime-$prev_time; /** getting the difference newly sent time and previous time by subtracting */
print("$jsTime - $prev_time = $diff"); /** printing the difference */
if($diff<3000){ /** checking if difference is less than 3 second if it is it means pc was not at sleep
*** you can manipulate and have for example 1 second = 1000ms */
socket_write($client,encode("You are active! your pc is awakend"));
$_SESSION['prev_time'] = $jsTime; /** saving newly sent time as previous time for future testing whcih will happen in two seconds in our case*/
}else { /** if it is more than 3 seconds it means that javascript setInterval function was paused and resumed after 3 seconds
** So it means that it was at sleep because when your PC is at sleep/suspended/hibernate mode setINterval gets pauesd */
socket_write($client,encode("You are not active! your pc is at sleep"));
$_SESSION['prev_time'] = $jsTime;
}
}else { /** if we have not saved the previous time in session save it */
$_SESSION['prev_time'] = $jsTime;
}
print_r($_SESSION);
/*
**** end of Main section for detectin sleep or not **
*/
}
}
} while (true);
// close sockets
socket_close($client);
socket_close($socket);
$client = null;
$flag_handshake = false;
function handshake($client, $headers, $socket) {
if (preg_match("/Sec-WebSocket-Version: (.*)\r\n/", $headers, $match))
$version = $match[1];
else {
print("The client doesn't support WebSocket");
return false;
}
if ($version == 13) {
// Extract header variables
if (preg_match("/GET (.*) HTTP/", $headers, $match))
$root = $match[1];
if (preg_match("/Host: (.*)\r\n/", $headers, $match))
$host = $match[1];
if (preg_match("/Origin: (.*)\r\n/", $headers, $match))
$origin = $match[1];
if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $headers, $match))
$key = $match[1];
$acceptKey = $key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
$acceptKey = base64_encode(sha1($acceptKey, true));
$upgrade = "HTTP/1.1 101 Switching Protocols\r\n".
"Upgrade: websocket\r\n".
"Connection: Upgrade\r\n".
"Sec-WebSocket-Accept: $acceptKey".
"\r\n\r\n";
socket_write($client, $upgrade);
return true;
} else {
print("WebSocket version 13 required (the client supports version {$version})");
return false;
}
}
function unmask($payload) {
$length = ord($payload[1]) & 127;
if ($length == 126) {
$masks = substr($payload, 4, 4);
$data = substr($payload, 8);
}
elseif($length == 127) {
$masks = substr($payload, 10, 4);
$data = substr($payload, 14);
}
else {
$masks = substr($payload, 2, 4);
$data = substr($payload, 6);
}
$text = '';
for ($i = 0; $i < strlen($data); ++$i) {
$text .= $data[$i] ^ $masks[$i % 4];
}
return $text;
}
function encode($text) {
// 0x1 text frame (FIN + opcode)
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($text);
if ($length <= 125)
$header = pack('CC', $b1, $length);
elseif($length > 125 && $length < 65536)$header = pack('CCS', $b1, 126, $length);
elseif($length >= 65536)
$header = pack('CCN', $b1, 127, $length);
return $header.$text;
}
NOTE READ IT:
$new_time variable is $jsTime in Code
create folder and just copy and paste this in files run php socket with the command: php -f server_socket.php go to the localhost and test it open console to see messages it will say "you are active" or "you are not active"(when you come from sleep); your executin will happen when user will come from sleep not when they are at sleep cause at that moment everything is cached in pagefile(windows) or in swap(linux)
I think i have a idea, you've discussed a lot about how bank login/logout system works.
Case-1: Access of webpage to user for unlimited time if user is active
Whenever user logged in, Start a timer on your backend(set the time limit whatever you want), let's say 15 mins. Now what it means?? It means if user doesn't perform any activity on the webpage, then we'll log him/her out.
Now, from front you can send the user activity to your backend(could be send using socket or long polling), which will basically reset the timer and user can use the webpage actively for whatever time they want.
If user put their PC on sleep, timer won't reset and you can invalidate the session once timer ends.
If you want to invalidate the user session as soon as they put their pc on sleep, you may set the limit of the session validate time. For example, when user logs-in, we'll create the session which will be valid only for 10 secs, and once we receive the user activity request, we can reset the timer and provide a new session key.
I hope this helps you. Let me know if you've any question.
If you update a timestamp in the user record with each heartbeat received from the client (e.g. a column called "lastHeartbeat"), you could use your database's Scheduled Event functionality to log them off without any need for the browser to be active on the client side. See Working with MySQL Scheduled Events
DELIMITER $$
DROP EVENT IF EXIST autoLogOff_event;
CREATE EVENT autoLogOff_event
ON SCHEDULE EVERY 1 MINUTE
STARTS CURRENT_TIMESTAMP
ENDS CURRENT_TIMESTAMP + INTERVAL 24 HOUR
DO
BEGIN
UPDATE users SET logOffTime = NOW()
WHERE lastHeartbeat < NOW( ) - INTERVAL 20 MINUTE
END
END $$
DELIMITER ;
I wrote a script to detect if the machine went to sleep. Idea is that when the machine is in sleep mood all all scripts will stop. Therefore if we keep track of the current time within a timeInterval. Everytime the timeInterval triggers the current time minus(-) the new time should be close enough to the timeInterval. Therefor if we want to check if timer was idle for X time, we can check if the time difference is more than X.
Example blow checks if the computer was put to sleep for more than 15s. Please note when you put the computer to sleep it make take about another extra 15s to idea all processors. (When tested on MY PC).
(function() {
this.SleepTimer = function() {
// console.log('sleep timer initiated');
// Create global element references
this.sleepTimer = null;
this.maxTime = null;
this.curDate = null;
this.newDate = null;
this.timer = null;
this.timeInterval = 1000;
this.sleepTimer = new CustomEvent("sleepTimer", {
"detail": {
"maxTime":this.maxTime,
"idelFor": this.newDate - this.curDate,
"timer": this.timer
}
});
// Define option defaults
var defaults = {
maxTime: 10000,
timeInterval: 1000,
autoStart: true,
console: false,
onStart: null,
onIdel: null
}
// Create options by extending defaults with the passed in arugments
if (arguments[0] && typeof arguments[0] === "object") {
this.options = extendDefaults(defaults, arguments[0]);
}
if (this.options.timeInterval) {
this.timeInterval = Math.max(1000, this.options.timeInterval);
this.maxTime = Math.max(this.options.maxTime, 10000);
} else {
this.options = defaults;
}
if(this.options.autoStart === true) this.start()
// Utility method to extend defaults with user options
}
function extendDefaults(source, properties) {
var property;
for (property in properties) {
if (properties.hasOwnProperty(property)) {
source[property] = properties[property];
}
}
return source;
}
SleepTimer.prototype.start = function(){
var _ = this;
this.options.onStart()
this.curDate = Date.now();
this.timer = setInterval(function() {
_.newDate = Date.now();
var diff = _.newDate - _.curDate;
// for debugging
if(_.options.console && diff > _.timeInterval){
console.log('Your PC was idel for ' + diff / 1000 + 's of ' + _.maxTime /1000 + 's. TimeInterval is set to ' + _.timeInterval / 1000 + 's');
}
if (diff < _.maxTime) {
_.curDate = _.newDate;
} else {
_.options.onIdel();
// alert('You have been idle for ' + diff / 1000 + 's');
clearTimeout(_.timer);
}
}, this.timeInterval); // seconds
}
}());
var sleepTimer = new SleepTimer({
maxTime: 15000,
console: true,
onStart: function(){
console.log('sleepTimer started.');
},
onIdel: function(){
alert('Your session expired! Please login again.');
}
});
I have implemented the exact same requirement using AWS Cognito, with Lambda Authorizers, & Redis, I can't share the code at this stage but I can tell you everything how it is implemented with these components, the same concepts can be used with other non AWS components.
Firstly with implementing an inactivity logout, you will have to do it server side, as if somebody simply turns off their computer, the front-end website would not log them out. I used the concept of ACTIVE users. When users successfully authenticates, I store with a TTL of 15 minutes in Redis an entry with a key of their username & a value of ACTIVE (it can be username+sessionid if you want to allow multiple sessions for a given user at the same time).
In my custom Authorizers when a user is ACTIVE & they have a valid Token, I grant them access to the protected resource AND most importantly, I do another put in Redis with the username & ACTIVE.
Whenever the user logs out, I log them out in my identity management solution (Cognito) & I mark them as INACTIVE. Note that if a user does not hit the API within 15 minutes they will no longer have an entry of ACTIVE against their username and will not be able to access the API anymore & have to sign in again, for which they will be redirected to do.
There are many things to consider with this approach, for one thing often Authorizers cache results for some amount of time, and if say you cache the result for 5 minutes as an example, then you user might be logged off in 10 minutes as your user could hit the cache instead of the Authorizer which would not refresh the ACTIVE entry.
It's also important that you make sure whatever you use to store if a given user is ACTIVE is highly available and will recover quickly in the event of a failure.
The approach of using a cache store this way is similar to how token invalidation is retrofitted to stateless authorization protocols such as OAuth2.
We've been using this approach for a few months now, it seems to work fine for us, it can be a bit of an annoying requirement to handle, I'd had expected in AWS world that there would be a ready to use out of the box solution for this but there was none to speak of.

Multiple Aborted Ajax Calls Waiting in IE

EDIT - PROBLEM SOLVED : It ended up being a server side problem. More details in comments below.
My webpage allows the user to update a graph based on parameters. When a user changes selection (checking/unchecking options), the graph is updated by an Ajax call.
The problem happens when the user quickly checks/unchecks options. When it happens, I made sure the current request is aborted and a new one is made. Here's my simplified code :
function refreshGraph() {
// Abort current running request (if any)
var currentlyLoading = (xhrDashboard != null && xhrDashboard !== undefined);
if (currentlyLoading) {
xhrDashboard.onreadystatechange = null;
xhrDashboard.abort();
}
// Ajax call.
var ajaxParams = {
url: '/Dashboard/ReadyMix/GraphPartial',
data: parameters,
traditional: true,
cache: false,
type: 'POST',
success: function (responseData) {
$(".graphContainer").html(responseData);
},
error: function (xhr, text_status, error_thrown) {
$(".graphZone").html("Error during loading...");
},
complete: function (xhr) {
xhrDashboard = null;
}
};
xhrDashboard = $.ajax(ajaxParams);
}
In Chrome I get the expected behavior. The first (or latest) call is cancelled and the new one is made no matter how many changes the user makes in a short lapse of time (ex : 5 option changes in a few seconds).
Internet Explorer (version 11 tested on multiple machines) start waiting indefinely from the moment I change option fast enough to abort 2 request and start a 3rd one. When launching my app in debug (Visual Studio) it works fine, but as soon as I put it on our sandbox server the problem occurs.
Here's a screenshots with Chrome (sandbox server)
Chrome network screenshot when aborting ajax requests then making a new one
Now here's a screenshot of IE stuck on "waiting"
IE network screenshot when stuck on waiting after aborted ajax requests
Any suggestion would be appreciated.

How to determine that an SSE connection was closed?

We have an SSE (Server-Sent Events) connection open in JavaScript which can time to time get closed, either because of server restarts or other causes. In that case it would be good to reestablish the connection. How to do it? Is there a way to find out on the client side that the connection was closed?
Here: https://developer.mozilla.org/en-US/docs/Web/API/EventSource I found only a way to close the connection, but no callback or a test method for determining whether the connection is still alive.
Thank you for your help.
If the connection is closed (in such a way that the browser can realize it), it will auto-connect. And it tends to do this quickly (default is 3 seconds in Chrome, 5 seconds in Firefox). readyState will be CONNECTING (0) while it is doing this. It is only ever CLOSED (2) if there was some problem connecting in the first place (e.g. due to a CORS issue). Once CLOSED, it does not retry.
I prefer to add a keep-alive mechanism on top, as the browser cannot always detect dead sockets (not to mention a remote server process that is locked up, etc.). See ch.5 of Data Push Apps with HTML5 SSE for detailed code, but basically it involves having the server send a message every 15 seconds, then a JavaScript timer that runs for 20 seconds, but is reset each time a message is received. If the timer ever does expire, we close the connection and reconnect.
EventSource API Update
EventSource API now has three event handlers:
onerror
onmessage
onopen
These should be enough to handle everything you need on the client side.
Something like this:
const ssEvent = new EventSource( eventUrl );
ssEvent.onopen = function (evt) {
// handle newly opened connection
}
ssEvent.onerror = function (evt) {
// handle dropped or failed connection
}
ssEvent.onmessage = function (evt) {
// handle new event from server
}
Ref: mozilla.org : EventSource : Event handlers
Browser support for EventSource API: onopen - caniuse.com
Check readyState property:
var es = new EventSource();
// Сheck that connection is not closed
es.readyState !== 2;
// or
es.readyState !== EventSource.CLOSED;
It is best not to try to determine if the connection was closed. I do not think there is a way to do it. Server Side Events work differently in all of the browsers, but they all close the connection during certain circumstances. Chrome, for example, closes the connection on 502 errors while a server is restarted. So, it is best to use a keep-alive as others suggest or reconnect on every error. Keep-alive only reconnects at a specified interval that must be kept long enough to avoid overwhelming the server. Reconnecting on every error has the lowest possible delay. However, it is only possible if you take an approach that keeps server load to a minimum. Below, I demonstrate an approach that reconnects at a reasonable rate.
This code uses a debounce function along with reconnect interval doubling. It works well, connecting at 1 second, 4, 8, 16...up to a maximum of 64 seconds at which it keeps retrying at the same rate.
function isFunction(functionToCheck) {
return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]';
}
function debounce(func, wait) {
var timeout;
var waitFunc;
return function() {
if (isFunction(wait)) {
waitFunc = wait;
}
else {
waitFunc = function() { return wait };
}
var context = this, args = arguments;
var later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, waitFunc());
};
}
// reconnectFrequencySeconds doubles every retry
var reconnectFrequencySeconds = 1;
var evtSource;
var reconnectFunc = debounce(function() {
setupEventSource();
// Double every attempt to avoid overwhelming server
reconnectFrequencySeconds *= 2;
// Max out at ~1 minute as a compromise between user experience and server load
if (reconnectFrequencySeconds >= 64) {
reconnectFrequencySeconds = 64;
}
}, function() { return reconnectFrequencySeconds * 1000 });
function setupEventSource() {
evtSource = new EventSource(/* URL here */);
evtSource.onmessage = function(e) {
// Handle even here
};
evtSource.onopen = function(e) {
// Reset reconnect frequency upon successful connection
reconnectFrequencySeconds = 1;
};
evtSource.onerror = function(e) {
evtSource.close();
reconnectFunc();
};
}
setupEventSource();

Recall Long Polling AJAX Request When it Fails/Network offline

I am implementing a successful long polling within PHP/Node.js application. I have created a routine to launch the long polling AJAX request after the waking up of the computer (after sleep mode) as below.
The problem is that the AJAX request fails due to internet connectivity as it needs some time to get ready and this leads AJAX request to fail. I need to recall this request again until the internet is back but I can't find any way to know if the previous request has failed to send new one and track its status.
I am not using the Jquery and I don't want to use it.
I am able to create a timeout for direct AJAX calls if they don't reach a server within a timeout seconds, but the long polling request status is pending at server for 40 seconds and I need to detect if it fails after 2 seconds from sending.
Is there any solution to do with xmlHTTP object?
I would greatly appreciate your help. Thanks.
var program ={
init: function(){
this.isSleep = function(lastTime){
var lastTime = lastTime;
clearTimeout(program.tt)
program.tt = setTimeout(function(){
var currentTime = new Date().getTime();
if(currentTime > (lastTime + 2000*2) ){
// request fails if the internet connection was not ready
ajax.call({ // long polling request......});
}
program.isSleep(new Date().getTime());
}, 2000);
};
this.isSleep(new Date().getTime());
}
}
Set a variable to true, then call a timer for 2 seconds. If the ajax returns turn that variable to false, if the timer is fired then check your variable and do whatever you need to do.

Error: The page has been destroyed and can no longer be used

I'm developing an add-on for the first time. It puts a little widget in the status bar that displays the number of unread Google Reader items. To accommodate this, the add-on process queries the Google Reader API every minute and passes the response to the widget. When I run cfx test I get this error:
Error: The page has been destroyed and can no longer be used.
I made sure to catch the widget's detach event and stop the refresh timer in response, but I'm still seeing the error. What am I doing wrong? Here's the relevant code:
// main.js - Main entry point
const tabs = require('tabs');
const widgets = require('widget');
const data = require('self').data;
const timers = require("timers");
const Request = require("request").Request;
function refreshUnreadCount() {
// Put in Google Reader API request
Request({
url: "https://www.google.com/reader/api/0/unread-count?output=json",
onComplete: function(response) {
// Ignore response if we encountered a 404 (e.g. user isn't logged in)
// or a different HTTP error.
// TODO: Can I make this work when third-party cookies are disabled?
if (response.status == 200) {
monitorWidget.postMessage(response.json);
} else {
monitorWidget.postMessage(null);
}
}
}).get();
}
var monitorWidget = widgets.Widget({
// Mandatory widget ID string
id: "greader-monitor",
// A required string description of the widget used for
// accessibility, title bars, and error reporting.
label: "GReader Monitor",
contentURL: data.url("widget.html"),
contentScriptFile: [data.url("jquery-1.7.2.min.js"), data.url("widget.js")],
onClick: function() {
// Open Google Reader when the widget is clicked.
tabs.open("https://www.google.com/reader/view/");
},
onAttach: function(worker) {
// If the widget's inner width changes, reflect that in the GUI
worker.port.on("widthReported", function(newWidth) {
worker.width = newWidth;
});
var refreshTimer = timers.setInterval(refreshUnreadCount, 60000);
// If the monitor widget is destroyed, make sure the timer gets cancelled.
worker.on("detach", function() {
timers.clearInterval(refreshTimer);
});
refreshUnreadCount();
}
});
// widget.js - Status bar widget script
// Every so often, we'll receive the updated item feed. It's our job
// to parse it.
self.on("message", function(json) {
if (json == null) {
$("span#counter").attr("class", "");
$("span#counter").text("N/A");
} else {
var newTotal = 0;
for (var item in json.unreadcounts) {
newTotal += json.unreadcounts[item].count;
}
// Since the cumulative reading list count is a separate part of the
// unread count info, we have to divide the total by 2.
newTotal /= 2;
$("span#counter").text(newTotal);
// Update style
if (newTotal > 0)
$("span#counter").attr("class", "newitems");
else
$("span#counter").attr("class", "");
}
// Reports the current width of the widget
self.port.emit("widthReported", $("div#widget").width());
});
Edit: I've uploaded the project in its entirety to this GitHub repository.
I think if you use the method monitorWidget.port.emit("widthReported", response.json); you can fire the event. It the second way to communicate with the content script and the add-on script.
Reference for the port communication
Reference for the communication with postMessage
I guess that this message comes up when you call monitorWidget.postMessage() in refreshUnreadCount(). The obvious cause for it would be: while you make sure to call refreshUnreadCount() only when the worker is still active, this function will do an asynchronous request which might take a while. So by the time this request completes the worker might be destroyed already.
One solution would be to pass the worker as a parameter to refreshUnreadCount(). It could then add its own detach listener (remove it when the request is done) and ignore the response if the worker was detached while the request was performed.
function refreshUnreadCount(worker) {
var detached = false;
function onDetach()
{
detached = true;
}
worker.on("detach", onDetach);
Request({
...
onComplete: function(response) {
worker.removeListener("detach", onDetach);
if (detached)
return; // Nothing to update with out data
...
}
}).get();
}
Then again, using try..catch to detect this situation and suppress the error would probably be simpler - but not exactly a clean solution.
I've just seen your message on irc, thanks for reporting your issues.
You are facing some internal bug in the SDK. I've opened a bug about that here.
You should definitely keep the first version of your code, where you send messages to the widget, i.e. widget.postMessage (instead of worker.postMessage). Then we will have to fix the bug I linked to in order to just make your code work!!
Then I suggest you to move the setInterval to the toplevel, otherwise you will fire multiple interval and request, one per window. This attach event is fired for each new firefox window.

Categories

Resources