As @David stated, there is no system event that will tell you if you really have a working internet connection.
The logic
Create a boolean vm.isInternetUp
and assume the user is connected. There are three reasons why a user can be disconnected:
- If an offline event is fired by the browser, meaning there is no wifi, 3g or ethernet connection
- If the device is connected to a wifi channel, but the wifi channel has no internet.
- There could also be a 3G/ethernet connection without internet, but that was not relevant in our case (not included in this post).
Now we just need to detect when one of these conditions happen, which turns vm.isInternetUp
to false. When that flag is turned to offline, we start a loop with a http request to our API until we get a positive response.
It's quite cumbersome, but in this way we don't overload our server with continuous http requests to check the status of the internet connection. Only one http will reach the server when the user gets back online.
The Code
There are different ways to implement this (look at @webruster's answer with online/offline events). I chose using interceptors because it best fits my specific needs.
vm.isInternetUp = true //assume internet is up at startup
var interceptor = {
request: function(config) {
return vm.checkForInternet(config);
},
responseError: function(reject){
return vm.handleBadWifi(reject)
}
}
For reason 1, I use angular's http request interceptor
to check at every request whether we're connected to a wifi. If not, it means we're offline.
vm.checkForInternet = function(config) {
if (angular.isUndefined(window.Connection)) {
// The project is run in a web browser so window.Connection doesn’t exist. Assume we have internet.
console.log("window.Connection doesn't exist")
return config;
}
//if there is no internet
if (navigator.connection.type === Connection.NONE || !vm.isInternetUp) {
vm.isInternetUp = false
vm.reconnect();
}
else {
console.log("connected but bad wifi")
}
}
return config;
}
For reason 2, I use $http responseError interceptor
intercepting all http requests. As the documentation states, reject.status = -1 usually means the request was aborted
; we conclude from that that there is no internet connection.
vm.handleBadWifi = function(reject) {
if (navigator.connection.type == Connection.WIFI && reject.status == -1) {
vm.isInternetUp = false
vm.checkForInternet(); //this will launch reconnection loop until connected again
}
return $q.reject(reject);
}
And finally the reconnection loops that stops as soon as we get a positive request. It's quite long because we cannot use angular's $http service as we are in an $http interceptor. Using $http would cause a circular dependency.
vm.reconnect = function() {
//Restart only if we were previously disconnected and after checking that we really have the internet
if (!vm.isInternetUp & !vm.reconnecting) {
console.log("attempting http call")
vm.reconnecting = true;
//backend-host is only pinged if internet was previously disconnected. In this way it does not overload the server.
//we don't use $http because that would induce a circular dependency (we're in an $http interceptor here). As this is a pure js http call this request will not be intercepted.
pureJsHttpGet(backendHost + 'projects/',vm.reconnectSuccess,vm.reconnectError)
}
}
vm.reconnectSuccess = function () {
vm.isInternetUp = true;
vm.reconnecting = false;
}
vm.reconnectError = function (error) {
$timeout(function () {
vm.reconnecting = false;
vm.checkForInternet();
},3000)
console.error("Did not manage to find an internet connection. Retrying in 3 sec", error)
if (navigator.connection.type === Connection.WIFI){
//create a fake reject error so that the wifi errorhandler knows the request failed
var reject = {status:-1}
vm.handleBadWifi(reject)
}
}
function pureJsHttpGet(theUrl, successCallback, errorCallback){
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
successCallback(xmlHttp.responseText);
else if (xmlHttp.readyState == 4 && xmlHttp.status == 0)
errorCallback(xmlHttp)
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}