Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

$http error handling: distinguishing user offline from other errors

I have a simple contact form, which Angular submits via AJAX to my app on Google App Engine. (A POST handler uses the send_mail function to email the website owner). This is the client code:

$http.post('', jQuery.param(user), {
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
}).success(function(data, status, headers, config) {
    //...                        
}).error(function(data, status, headers, config) {
    alert('Please check your Internet connection and try again.');
});

Obviously the alert() is handling all errors. Assuming no errors in my server-side code, I'm guessing the chances of App Engine returning anything other than an HTTP status code of 200 is low. However I would still like to distinguish between server errors and the user having lost their connection.

I was thinking of using XMLHttpRequest's textStatus as per this SO answer, but it doesn't appear to be available to the $http.error() callback. I also thought of using Offline.js but I don't need most of what it does so that would seem like wasted bytes in this case.

The $http.error() status I get when offline is 0, but I'm not sure how cross-browser reliable that's going to be. What should I be using?

like image 976
KnewB Avatar asked Dec 12 '14 19:12

KnewB


2 Answers

Before giving you the solution I just wanted to highlight the problems with browser provided is-offline flag

  1. Different browser's offline flag has different meanging

    a. for some browsers it means internet is not there

    b. for some browsers it means intranet is not there

    c. some browsers do allow offline mode, so even if there is no internet, you are not actually offline.

  2. not all browsers support offline in consistent way

Another problem I had with using browser based flag is development scenario, where having internet is not necessary, and that should not trigger the offline mode (for me I block all user interaction If my website goes offline). You can solve this problem by having another indicator telling you if you are in dev/prod, etc.

And most imp part, why do we care to find if browser is in offline mode, is because we do care only if our website is reachable or not, we don't actually care if the internet is there or not. So even if browser tell us it is offline, it is not exactly what we want. there is a tiny difference between what we want and what browser provides.

So considering all of above, I have solved the problem using an offline directive which I am using to block user interaction if user is offline

csapp.directive('csOffline', ["$http", '$interval', "$timeout", "$modal", function ($http, $interval, $timeout, $modal) {
    var linkFn = function (scope) {

        scope.interval = 10; //seconds

        var checkConnection = function () {

            $http({
                method: 'HEAD',
                url: document.location.pathname + "?rand=" + Math.floor((1 + Math.random()) * 0x10000)
            })
                .then(function (response) {
                     $scope.isOnline = true;
                }).catch(function () {
                     $scope.isOnline = false;
                });
        };

        scope.isOnline = true;

        $interval(checkConnection, scope.interval * 1000);

        scope.$watch('isOnline', function (newVal) {
            console.log("isOnline: ", newVal);
            //custom logic here
        });

    };

    return {
        scope: {},
        restrict: 'E',
        link: linkFn,
    };
}]);

I was about to use offline.js, it was too much and most of which I didnt need, so this is the solution I came up with which is purely in angular.js.

please note that http interceptor is invoked during these calls, I wanted to avoid that, hence I had used $.ajax for the calls

        $.ajax({
            type: "HEAD",
            url: document.location.pathname + "?rand=" + Math.floor((1 + Math.random()) * 0x10000),
            contentType: "application/json",
            error: function () {
                scope.isOnline = false;
            },
            success: function (data, textStatus, xhr) {
                var status = xhr.status;
                scope.isOnline = status >= 200 && status < 300 || status === 304;
            }
        }
        );

you can replace the logic inside isOnline true/false, with whatever custom logic you want.

like image 159
harishr Avatar answered Oct 06 '22 01:10

harishr


I'd go with response.status === 0 check. I've tested it using the code below (needs to be put on a webserver that you can switch on/off at will) and I'm getting 0 in current versions of Google Chrome, Mozilla Firefox, and Internet Explorer. You may use it to test all browsers you want to support.


Code for testing connection status:

<!DOCTYPE html>
<html>
    <head>
        <title>Connection status test</title>
        <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js"></script>
        <script type="text/javascript">
            var log = [],
                pendingCount = 0,
                pendingLimit = 5;
            angular.module('app', [])
                .run(function ($rootScope) {
                    $rootScope.log = log;
                })
                .run(function ($http, $interval, $rootScope) {
                    $interval(function () {
                        if (pendingCount >= pendingLimit) {
                            return;
                        }

                        var item = {
                            time: Date.now(),
                            text: 'Pending...'
                        };
                        ++pendingCount;
                        $http.get('.', {})
                            .then(function () {
                                item.text = 'Done';
                            }, function (response) {
                                item.text = 'Done (status ' + response.status + ')';
                            })
                            .finally(function () {
                                --pendingCount;
                            });
                        log.unshift(item);
                    }, 1000);
                });
        </script>
        <style rel="stylesheet" type="text/css">
            ul {
                list-style: none;
                padding: 0;
            }
        </style>
    </head>
    <body ng-app="app">
        <ul>
            <li ng-repeat="item in log">{{item.time | date:'HH:mm:ss'}}: {{item.text}}</li>
        </ul>
    </body>
</html>
like image 38
hon2a Avatar answered Oct 06 '22 02:10

hon2a