Network Information API Polyfill

One of the many new HTML5 APIs slowly being implemented by browsers is the Network Information API[2]. It exposes information about the type of network that the connecting device is using. In theory, this allows developers to optimize content around the connection speed of the user. However, as with most HTML5 APIs it is supported only by some browsers with/without prefixes, and has a legacy implementation, so a polyfill is useful when working with the API.

How do it…

The API is located at window.navigator.connection. It has a type property and an ontypechange event. Here is a polyfill I created, based on work by Aurelio De Rosa[1]:

(function() {
  var oConnection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
  var sType;
  var aCallbacks = [];

  // Obviously this is not accurate.
  function fnGuessType(iBandwidth) {
    if (iBandwidth > 1) {
      return 'ethernet';
    } else if (iBandwidth > .5) {
      return 'wifi';
    } else if (iBandwidth === 0) {
      return 'none';
    } else {
      return 'cellular';
    }
  }

  // Simple function to iterate over the callbacks.
  function fnCallbackIter(fn) {
    for (var i = aCallbacks.length - 1; 0 <= i; i--) {
      fn(aCallbacks[i]);
    }
  }

  if (oConnection) {
    // API is available.
    if ('metered' in oConnection) {
      // Legacy API, create obfuscation polyfill.
      sType = fnGuessType(oConnection.bandwidth);

      // If the bandwidth changes drastically, execute callbacks.
      oConnection.addEventListener('change', function(event) {
        var sNewType = fnGuessType(oConnection.bandwidth);
        if (sType !== sNewType) {
          sType = sNewType;
          fnCallbackIter(function(fnCallback) {
            fnCallback.call(navigator.connection, event);
          });
        }
      });

      navigator.connection = {
        addEventListener: function(sName, fnCallback) {
          var bFoundCallback = false;
          if (sName === 'typechange') {
            // Assert the callback doesn't exist before appending.
            fnCallbackIter(function(fnCallbackInner) {
              if (fnCallback === fnCallbackInner) {
                bFoundCallback = true;
              }
            });
            if (!bFoundCallback) {
              console.log('1');
              aCallbacks.push(fnCallback);
            }
          } else {
            // Some other event... pass through.
            oConnection.addEventListener.apply(this, arguments);
          }
        },
        removeEventListener: function(sName, fnCallback) {
          var aNewCallbacks = [];
          if (sName === 'typechange') {
            if (fnCallback) {
              // Create a new list of callbacks without the provided one.
              aNewCallbacks = [];
              fnCallbackIter(function(fnCallbackInner) {
                if (fnCallback !== fnCallbackInner) {
                  aNewCallbacks.push(fnCallbackInner);
                }
              });
            }
            aCallbacks = aNewCallbacks;
            console.log(aCallbacks);
          } else {
            // Some other event... pass through.
            oConnection.addEventListener.apply(this, arguments);
          }
        },
        type: 'unknown'
      };
    }

    // Don’t change the connection object.
  } else {
    // API doesn't exist, create empty polyfill.
    navigator.connection = {
      addEventListener: function() {},
      removeEventListener: function() {},
      type: 'unknown'
    };
  }
}());

How it works…

The polyfill has three paths: leave the browser implemented connection API alone, replace it with an empty polyfill object, or replace it with a polyfill to mask the legacy version. The first two need no explanation, except the possible type values are:

  • bluetooth
  • cellular – connected via mobile network (edge, 3G, 4G, etc.)
  • ethernet
  • none – no internet connection
  • wifi
  • other – not unknown, but not one of the above either
  • unknown – couldn’t determine connection type

The legacy API exposes the metered (boolean indicating the connection is metered) and bandwidth (rate in MB/s) properties instead of type. While this is probably more meaningful information, it was difficult to implement and has been replaced with just the type property.

The majority of the legacy polyfill is to implement versions of addEventListener and removeEventListener to mask the DOM event handler functions with custom ones, so that the typechange event can be used instead of the legacy change event. Additionally, the code estimates the type based on the bandwidth value. Obviously, this is very inaccurate, but I believe it returns values in the spirit of how you might use type in a real project.

Whilst this is a fun experiment, use it in production at your own risk. I definitely had some weird data. For example my laptop using the legacy polyfill while connected via wifi, always reported the value Infinite for the bandwidth, so the polyfill type was set to ethernet. However, I do have more than 10MB/s download speeds, so perhaps I exceeded FireFox’s maximum bandwidth value. I was not able to test the polyfill on my mobile devices as none of my devices had the legacy version.

I think the best use for the Network Information API would be to load a page with the lowest viable resolution assets, and dynamically insert larger, more detailed assets if the type is
wifi or ethernet.

There’s more…

There is more value in estimating bandwidth than there is from knowing the type, as you can be connected using wifi to another cellphone and shouldn’t assume that wifi means a fast connection. It might be more useful to polyfill the current API with the legacy API by estimating the bandwidth. This could be achieved by timing the download of several small files and calculating a rough bandwidth.

References

  1. HTML5: Network Information API
  2. MDN: Network Information API
  3. Chrome Canary has an experimental implementation of NetInfo API

Detecting Object Mutations by Counting Properties

Have you ever included a library and wonder, "how much did this library add to the window object", or passed an object into a function and asked yourself, "did that function modify my object"? Instead of reading the source code, this article shows a quick trick for answering these questions.

How do it…

For starters we need a function to count the number of properties on an object:
 function fnCountProperties(o) { var ...

Using Google Play Games on the Web

As many of you know, I now work for Google on the Play Games Team. We provide APIs for game developers, implementing useful features like leaderboards and achievements, so the developer doesn't have to. While many Android developers are using our services, adoption on the web could be better, so lets take a look at how to integrate the Google Play Games Services achievements into a web game.

Getting ready

Become a Google developer ...

Site Outage Fixed - Down for 32 hours :(

I apologize that the site has been down for a couple of days. It was a perfect storm of events. For starters, Amazon told me about two weeks ago that my server was going to be terminated and that I should do something about it (they do this now and again, as they upgrade equipment). And like any good developer, I was procrastinating until the last minute (Wednesday this week). Then on Tuesday, I received ...

Event Bubble & Capture Phases

One of the less understood, but powerful feature of browser events are their phases. According to the W3C level 2 spec there are three phases[1]: AT_TARGET=2, BUBBLING_PHASE=3, and CAPTURING_PHASE=1. Most browsers also implement a fourth phase[2]: NONE=0.

Getting ready

Just a quick note that everything discussed in this article is for modern browsers (all browsers except IE <9). Prior to IE 9, Internet Explorer used its own event system, instead of conforming ...

Passing Objects into addEventListener Instead of Functions

I was reviewing the browser event stack the other day and was reminded of a rarely used feature of addEventListener that allows developers to autobind the execution context object, instead of requiring a call to bind or using a library, that is worth sharing, if you weren’t already aware.

How do it…

Typically, when attaching an event, we write:
 var myObj = { handleEvent: function (evt) { // 'this' will be scoped ...

Running Android Tests on a Device or Emulator

I have not been doing much web development lately, so its been difficult to come with interesting topics. However, I have been doing a lot of android development and since many engineers have to work cross discipline, I think an android article be relevant. This article will discuss how to run unit tests against your android code, directly on the android device or emulator.

Getting ready

You will need to install the

Cherry-Picking for Fun and Profit

In Git, it is often useful to merge one specific commit from one branch into another. Frequently this happens when Using Git Interactive Rebase With Feature Branches, as you develop in the branch, you realize that one or more of your commits should be added to master right away, but not all the CLs. Enter git cherry-pick <changelist hash> for the win! The cherry pick command allows you to merge one CL from a ...

Gaming Engine - Snake Demo v2

Last week I was busy at GDC and have not had time to put together a detailed article, so in the spirit of GDC, I thought I would share the latest iteration of my HTML5 gaming engine (still very rough). There has been a lot of progress around the Game class to support stages (or levels) and a score board to track the player’s score. The stages are demoed by a new version ...

Use && Instead of Semicolon to Separate Commands

Today’s article will be short and will cover a bash topic that frustrates me to no end. Please don’t use ; to separate commands, when you mean &&. There is an important difference between the two and many developers never realize that they want to be using && in their scripts.

How do it…

Here is a common oneliner that you might use to compile a package:
 ./configure ; make ; make ...