View live demo Here.
Complete Code on Github Here.
Originally Posted on my blog.
I recently worked on an application which was primarily being used by customers in certain areas of West Africa. While Beta testing the app, we received several complaints of it not working properly. After further investigation we noticed most of the issues raised were due to failed or bad internet connectivity. Smooth operation of the software largely depended on stable internet.
So prior to building more robust features like autosave or temporary storage, I was tasked with implementing a light weight internet detect feature that would check for internet connectivity and notify the user when its bad/restored and the consequences of both. The major requirements' were;
- It must be light weight and implemented quickly
- It shouldn't be ugly because of 1 (no javascript alerts lol)
- It should have minimal to no dependencies (beyond basic/regular requirements for a web application)
I decided to use VanillaJS which will execute firstly once any view of the app runs. I would ping a file on a remote server and check for the response HTTP status. If it wasn’t successful i.e HTTP status code >= 200 & < 304. reference here, then I notify the user and offer some advice while still checking. If eventually the internet connectivity is restored, the application would notify the user of this.
Here is the core function:
var wasInternetConnectionBad = ''; //used to show restored internet notification only once
var internetCheckActions = function() {
var xhr = new XMLHttpRequest();
var file = "/img/nKekEaz4i6jeRtvxZRnY.jpg"; // the remote file to check for. You should replace this with yours
var randomNum = Math.round(Math.random() * 10000); //initiating a random value to revv the file
xhr.open('HEAD', file + "?rand=" + randomNum, true);
xhr.send();
xhr.addEventListener("readystatechange", processRequest, false);
function processRequest(e) {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 304) {
if (wasInternetConnectionBad == "1") {
// Internet connection is GOOD. Do what ever you like here
}
wasInternetConnectionBad = ''; //Clear flag
} else {
// Internet connection is BAD. Do what ever you like here
wasInternetConnectionBad = '1';
}
}
}
}
I decided to invoke the function when the DOM content loads and every subsequent 5 seconds. You can change this as you feel
// Invoke when DOM content has loaded
document.addEventListener('DOMContentLoaded', function () {
internetCheckActions();
});
//& every subsequent 5 seconds
var checkInternetConnection = function() {
setInterval(function () {
internetCheckActions();
}, 5000);
}
checkInternetConnection();
The code above is basically what you need. You can end here or continue to see the more complete solution.
The next step was to figure out how to notify the user of the state of things in nice way. This is where I normally just call a fancy Jquery notification plugin but we cant do that. So I built a very light weight and extensible notification widget. The cool thing is it can be used in other utility functions.
First lets create the HTML markup for the notification. Ideally this should be inserted as the first child of the BODY tag and sibling of what ever hosting element is decided should be the page content.
<img src="https://cdnjs.cloudflare.com/ajax/libs/slippry/1.4.0/images/sy-loader.gif" width="1" height="1"
style="position:absolute; z-index:-2;"> <!-- Attepmting to preload an animated loader image which will be used later. NOTE, different browser behave differently -->
<div class="rawNotificationArea" id="rawNotificationArea">
<div class="notification_message"></div>
</div>
Then add this CSS snippet inline, in the head tag to style our notification widget
<style>
.rawNotificationArea {
position: fixed;
top: 2px;
left: 0;
width: 100%;
text-align: center;
padding: 10px 0;
display: none;
z-index: 99999999;
}
.rawNotificationArea .notification_message {
max-width: 50%;
border: solid thin #888888;
color: #333;
background-color: #DCDCDC;
text-align: center;
padding: 5px 15px;
border-radius: 4px;
box-shadow: 2px 3px 20px rgba(0, 0, 0, 0.17);
display: inline-block;
text-align: center;
font-size: 14px;
letter-spacing: 1px;
}
.rawNotificationArea .notification_message.warning {
background-color: #fcf8e3;
border-color: #faf2cc;
color: #8a6d3b;
}
.rawNotificationArea .notification_message.success {
background-color: #dff0d8;
border-color: #d0e9c6;
color: #3c763d;
}
.rawNotificationArea .notification_message.info {
background-color: #d9edf7;
border-color: #bcdff1;
color: #31708f;
}
.rawNotificationArea .notification_message.danger, .rawNotificationArea .notification_message.error {
background-color: #f2dede;
border-color: #ebcccc;
color: #a94442;
}
</style>
And the JS for widget.
// Notification Widget
var nativeNotification = {
fadeEl: function() {
return (document.getElementById('content_body'));
},
messageHolder: function() {
return (document.getElementById('rawNotificationArea'));
},
contentFade: function() {
this.fadeEl().style.opacity = "0.5";
},
contentUnfade: function() {
this.fadeEl().style.opacity = "1.0";
},
notify: function(message, tone) {
this.messageHolder().innerHTML = '<span class="notification_message ' + tone + '">' + message + '</span>';
this.messageHolder().style.display = "block";
},
unotify: function() {
while (this.messageHolder().firstChild) {
this.messageHolder().removeChild(this.messageHolder().firstChild);
}
},
timedUnotify: function(time) {
setTimeout(function() {
nativeNotification.unotify();
}, time);
}
};
So using this in our core function, we will finally have something like this
//Detect internet status amd motify user
var wasInternetConnectionBad = ''; //used to show restored internet notification only once
var internetCheckActions = function() {
var xhr = new XMLHttpRequest();
var file = "/img/nKekEaz4i6jeRtvxZRnY.jpg"; // the remote file to check for. You should replace this with yours
var randomNum = Math.round(Math.random() * 10000); //initiating a random value to revv the file
xhr.open('HEAD', file + "?rand=" + randomNum, true);
xhr.send();
xhr.addEventListener("readystatechange", processRequest, false);
function processRequest(e) {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 304) {
if (wasInternetConnectionBad == "1") {
nativeNotification.contentUnfade();
nativeNotification.notify("Your internet connection has been restored", 'info');
nativeNotification.timedUnotify(5000);
}
wasInternetConnectionBad = '';
} else {
nativeNotification.contentFade();
nativeNotification.notify("We've detected a problem with your internet connection.\n Some functions may not work as expected<div><strong>Retrying <img src='https://cdnjs.cloudflare.com/ajax/libs/slippry/1.4.0/images/sy-loader.gif' width='20' height='20'></strong></div>", 'warning');
wasInternetConnectionBad = '1';
}
}
}
}
View live demo Here.
Complete Code on Github Here.
P.S
- This was a quick fix. Im looking forward to interesting ways people can improve this or even better, more efficient solutions
- Possible pending improvements' include *- Building this out into an easy to use JS plugin *- Experiment others ways of revving the file being used to used to check for connectivity. Currently the file is being revved using a querystring. See more on this here
- Not sure on this but I assume method being used here (i.e pinging a file using the web client to make repeated calls to a static file) is indempotent .. Im also looking forward to interesting conversations on this
Top comments (7)
A bad connection can be EXTREMELY slow, and a response time of few seconds can disrupt some applications also if all the requests got a 200 OK. You should check for the requests duration and alert if it's over one second.
I think you should also check the content of the file, can a bad connection drop some packets? As a side effect, knowing the length of the file you can calculate the bandwidth: maybe you have a good ping time but an ugly bandwidth, so the app broke because it's too slow to finish downloading some responses.
Also: timestamp instead of random, I think it's less CPU intensive 😊
In TCP it cannot. You either receive the packets correctly and in order from the OS, or the connection is terminated.
And I agree on using a timestamp, because 4 4 4 4 4 4 can be the result of multiple random() calls. There is a possibility of multiple users connecting via a proxy to get the same request. But I doubt proxies cache HEAD requests.
Oh, yeah, TCP, I didn't thought about it 😖
I didn't thought about proxy's caches either: maybe it would be better use some no-cache header.
Thanks a lot.
Can you expatiate more on this point
--> ...a response time of few seconds can disrupt some applications ...
Also, yea. Timestamp is a better choice 👍
In case of bad design of asynchronous calls with some degree of parallelization or sequentiality. A quick example because I'm writing on mobile: call A, then after one second call B, and the application assume that A will respond before B. On a bad connection A and B could return in the same instant, or even B before A. A poor coding choice could create an invalid state in the application. I'm speaking theoretically, but I've seen similar problems some months ago.
What if online and offline events?
Ive read several pieces that indicate that browser vendors seem not to agree on on how to define offline/online.
e.g some browser vendors define "onLine" as connected to "a network" as opposed to "the internet" while ive seen several bug reports for this on Google chrome.
So different browsers may have a tiny tweaks/hacks to get it to work for the requirement in the post.
If you have a different experience, having tested with multiple browsers, I'll like to know. 🙂