How to maintain Channel API connection when internet is offline

485 views Asked by At

I am using Google AppEngine's Channel API. I am having some issue to re-start the lost connection due to the user's network connection. When you loose the internet connection, channel call onError but it will not call onClose. As far as JavaScript object is concerned, the channel socket is open.

How do you handle lost connection due to the internet issue? I am thinking of 1) by trigger to close the channel and re-open it when RPC unrelated to the channel somewhere in the application succeeds for the first time (which indicates the internet is alive again) or 2) Use timer that runs all the time and pings the server for network status (which was the point of introducing the long polling to avoid consuming unwanted resource this way). Any other ideas would be great.

Observation: When the internet connection is dead, onError is called in incremental interval (10sec, 20sec, 40sec) twice. Once the internet connection is back, channel does not resume connection. It stops working without any indication that it is dead.

Thanks.

1

There are 1 answers

0
Takashi Matsuo On BEST ANSWER

When you see the javascript console, presumably you will see "400 Unknown SID Error". If so, here is my workaround for this. This is a service module for AngularJS, but please look at the onerror callback. Please try this workaround and let me know if it works or not.

Added: I neglected to answer your main question, but in my opinion, it is hard to determine if you're connected to the internet unless actually pinging the "internet". So probably you may want to use some retry logic similar to the following code with some tweak. In the following example, I just retrying 3 times, but you can do it more with some back offs. However, I think the best way to handle this is, when the app consume the retry max count, you can indicate the user that the app lost the connection, ideally showing a button or a link to re-connect to the channel service.

And, you can also track the connection on the server side, see: https://developers.google.com/appengine/docs/java/channel/#Java_Tracking_client_connections_and_disconnections

app.factory('channelService', ['$http', '$rootScope', '$timeout',
  function($http, $rootScope, $timeout) {
    var service = {};
    var isConnectionAlive = false;
    var callbacks = new Array();
    var retryCount = 0;
    var MAX_RETRY_COUNT = 3;

    service.registerCallback = function(pattern, callback) {
      callbacks.push({pattern: pattern, func: callback});
    };

    service.messageCallback = function(message) {
      for (var i = 0; i < callbacks.length; i++) {
        var callback = callbacks[i];
        if (message.data.match(callback.pattern)) {
          $rootScope.$apply(function() {
            callback.func(message);
          });
        }
      }
    };

    service.channelTokenCallback = function(channelToken) {
      var channel = new goog.appengine.Channel(channelToken);
      service.socket = channel.open();
      isConnectionAlive = false;
      service.socket.onmessage = service.messageCallback;

      service.socket.onerror = function(error) {
        console.log('Detected an error on the channel.');
        console.log('Channel Error: ' + error.description + '.');
        console.log('Http Error Code: ' + error.code);
        isConnectionAlive = false;
        if (error.description == 'Invalid+token.' || error.description == 'Token+timed+out.') {
          console.log('It should be recovered with onclose handler.');
        } else {
          // In this case, we need to manually close the socket.
          // See also: https://code.google.com/p/googleappengine/issues/detail?id=4940
          console.log('Presumably it is "Unknown SID Error". Try closing the socket manually.');
          service.socket.close();
        }
      };

      service.socket.onclose = function() {
        isConnectionAlive = false;
        console.log('Reconnecting to a new channel');
        openNewChannel();
      };

      console.log('A channel was opened successfully. Will check the ping in 20 secs.');
      $timeout(checkConnection, 20000, false);
    };

    function openNewChannel(isRetry) {
      console.log('Retrieving a clientId.');
      if (isRetry) {
        retryCount++;
      } else {
        retryCount = 0;
      }
      $http.get('/rest/channel')
          .success(service.channelTokenCallback)
          .error(function(data, status) {
            console.log('Can not retrieve a clientId');
            if (status != 403 && retryCount <= MAX_RETRY_COUNT) {
              console.log('Retrying to obtain a client id')
              openNewChannel(true);
            }
          })
    }

    function pingCallback() {
      console.log('Got a ping from the server.');
      isConnectionAlive = true;
    }

    function checkConnection() {
      if (isConnectionAlive) {
        console.log('Connection is alive.');
        return;
      }
      if (service.socket == undefined) {
        console.log('will open a new connection in 1 sec');
        $timeout(openNewChannel, 1000, false);
        return;
      }
      // Ping didn't arrive
      // Assuming onclose handler automatically open a new channel.
      console.log('Not receiving a ping, closing the connection');
      service.socket.close();
    }

    service.registerCallback(/P/, pingCallback);
    openNewChannel();

    return service;
  }]);