Bug 1281619 - Land version 1.4.1 of the Loop system add-on in mozilla-central, rs=Standard8 for already reviewed code. a=sylvestre
authorMark Banner <standard8@mozilla.com>
Wed, 22 Jun 2016 21:47:37 +0100
changeset 339709 0e888cae95ca4b6b51037121ad241bd3901ba3e1
parent 339708 bb8525b71ab4539650b831039342a5e6381c799a
child 339710 cdb052e0c1e465c68440f04ab02ffc7175acfc1a
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8, sylvestre
bugs1281619
milestone49.0a2
Bug 1281619 - Land version 1.4.1 of the Loop system add-on in mozilla-central, rs=Standard8 for already reviewed code. a=sylvestre
browser/extensions/loop/bootstrap.js
browser/extensions/loop/chrome/content/modules/MozLoopAPI.jsm
browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
browser/extensions/loop/chrome/content/panels/css/desktop.css
browser/extensions/loop/chrome/content/panels/js/conversation.js
browser/extensions/loop/chrome/content/panels/js/desktopViews.js
browser/extensions/loop/chrome/content/panels/js/panel.js
browser/extensions/loop/chrome/content/panels/js/roomStore.js
browser/extensions/loop/chrome/content/panels/js/roomViews.js
browser/extensions/loop/chrome/content/panels/test/desktopViews_test.js
browser/extensions/loop/chrome/content/panels/test/roomStore_test.js
browser/extensions/loop/chrome/content/preferences/prefs.js
browser/extensions/loop/chrome/content/shared/css/common.css
browser/extensions/loop/chrome/content/shared/js/actions.js
browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
browser/extensions/loop/chrome/content/shared/js/loopapi-client.js
browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js
browser/extensions/loop/chrome/content/shared/js/textChatView.js
browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
browser/extensions/loop/chrome/content/shared/test/otSdkDriver_test.js
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.css
browser/extensions/loop/chrome/content/shared/test/vendor/mocha.js
browser/extensions/loop/chrome/content/shared/test/vendor/react-dom-server.js
browser/extensions/loop/chrome/content/shared/vendor/react-dom-prod.js
browser/extensions/loop/chrome/content/shared/vendor/react-dom.js
browser/extensions/loop/chrome/content/shared/vendor/react-prod.js
browser/extensions/loop/chrome/content/shared/vendor/react.js
browser/extensions/loop/chrome/locale/ast/loop.properties
browser/extensions/loop/chrome/locale/az/loop.properties
browser/extensions/loop/chrome/locale/ca/loop.properties
browser/extensions/loop/chrome/locale/cs/loop.properties
browser/extensions/loop/chrome/locale/cy/loop.properties
browser/extensions/loop/chrome/locale/de/loop.properties
browser/extensions/loop/chrome/locale/el/loop.properties
browser/extensions/loop/chrome/locale/en-GB/loop.properties
browser/extensions/loop/chrome/locale/en-US/loop.properties
browser/extensions/loop/chrome/locale/es-AR/loop.properties
browser/extensions/loop/chrome/locale/es-CL/loop.properties
browser/extensions/loop/chrome/locale/es-MX/loop.properties
browser/extensions/loop/chrome/locale/et/loop.properties
browser/extensions/loop/chrome/locale/fa/loop.properties
browser/extensions/loop/chrome/locale/fy-NL/loop.properties
browser/extensions/loop/chrome/locale/fy/loop.properties
browser/extensions/loop/chrome/locale/he/loop.properties
browser/extensions/loop/chrome/locale/id/loop.properties
browser/extensions/loop/chrome/locale/it/loop.properties
browser/extensions/loop/chrome/locale/ko/loop.properties
browser/extensions/loop/chrome/locale/nl/loop.properties
browser/extensions/loop/chrome/locale/nn-NO/loop.properties
browser/extensions/loop/chrome/locale/pl/loop.properties
browser/extensions/loop/chrome/locale/pt-PT/loop.properties
browser/extensions/loop/chrome/locale/rm/loop.properties
browser/extensions/loop/chrome/locale/ro/loop.properties
browser/extensions/loop/chrome/locale/ru/loop.properties
browser/extensions/loop/chrome/locale/sk/loop.properties
browser/extensions/loop/chrome/locale/sr/loop.properties
browser/extensions/loop/chrome/locale/tr/loop.properties
browser/extensions/loop/chrome/skin/linux/platform.css
browser/extensions/loop/chrome/skin/windows/platform.css
browser/extensions/loop/chrome/test/mochitest/browser.ini
browser/extensions/loop/chrome/test/mochitest/browser_mozLoop_appVersionInfo.js
browser/extensions/loop/chrome/test/mochitest/browser_mozLoop_socialShare.js
browser/extensions/loop/chrome/test/mochitest/browser_mozLoop_telemetry.js
browser/extensions/loop/install.rdf.in
browser/extensions/loop/test/functional/config.py
browser/extensions/loop/test/functional/loopTestDriver.py
browser/extensions/loop/test/functional/manifest.ini
browser/extensions/loop/test/functional/test_1_browser_call.py
--- a/browser/extensions/loop/bootstrap.js
+++ b/browser/extensions/loop/bootstrap.js
@@ -533,17 +533,18 @@ var WindowListener = {
 
         var state = "";
         var mozL10nId = "loop-call-button3";
         var suffix = ".tooltiptext";
         if (this.MozLoopService.errors.size) {
           state = "error";
           mozL10nId += "-error";} else 
         if (this.isSlideshowOpen) {
-          state = "slideshow";} else 
+          state = "slideshow";
+          suffix = ".label";} else 
         if (this.MozLoopService.screenShareActive) {
           state = "action";
           mozL10nId += "-screensharing";} else 
         if (aReason == "login" && this.MozLoopService.userProfile) {
           state = "active";
           mozL10nId += "-active";
           suffix += "2";} else 
         if (this.MozLoopService.doNotDisturb) {
@@ -658,17 +659,19 @@ var WindowListener = {
           return;}
 
 
         this.activeSound = new window.Audio();
         this.activeSound.src = "chrome://loop/content/shared/sounds/" + name + ".ogg";
         this.activeSound.load();
         this.activeSound.play();
 
-        this.activeSound.addEventListener("ended", function () {_this11.activeSound = undefined;}, false);}, 
+        this.activeSound.addEventListener("ended", function () {
+          _this11.activeSound = undefined;}, 
+        false);}, 
 
 
       /**
        * Start listening to selected tab changes and notify any content page that's
        * listening to 'BrowserSwitch' push messages.  Also sets up a "joined"
        * and "left" listener for LoopRooms so that we can toggle the infobar
        * sharing messages when people come and go.
        *
@@ -739,51 +742,17 @@ var WindowListener = {
 
         // Remove shared pointers related events
         gBrowser.removeEventListener("mousemove", this);
         gBrowser.removeEventListener("click", this);
         this.removeRemoteCursor();
 
         this._listeningToTabSelect = false;
         this._browserSharePaused = false;
-        this._currentRoomToken = null;
-
-        this._sendTelemetryEventsIfNeeded();}, 
-
-
-      /**
-       * Sends telemetry events for pause/ resume buttons if needed.
-       */
-      _sendTelemetryEventsIfNeeded: function _sendTelemetryEventsIfNeeded() {
-        // The user can't click Resume button without clicking Pause button first.
-        if (!this._pauseButtonClicked) {
-          return;}
-
-
-        var buckets = this.constants.SHARING_SCREEN;
-        this.LoopAPI.sendMessageToHandler({ 
-          name: "TelemetryAddValue", 
-          data: [
-          "LOOP_INFOBAR_ACTION_BUTTONS", 
-          buckets.PAUSED] });
-
-
-
-        if (this._resumeButtonClicked) {
-          this.LoopAPI.sendMessageToHandler({ 
-            name: "TelemetryAddValue", 
-            data: [
-            "LOOP_INFOBAR_ACTION_BUTTONS", 
-            buckets.RESUMED] });}
-
-
-
-
-        this._pauseButtonClicked = false;
-        this._resumeButtonClicked = false;}, 
+        this._currentRoomToken = null;}, 
 
 
       /**
        *  If sharing is active, paints and positions the remote cursor
        *  over the screen
        *
        *  @param cursorData Object with the correct position for the cursor
        *                    {
@@ -937,21 +906,18 @@ var WindowListener = {
             var guestPresent = _this13.LoopRooms.getNumParticipants(_this13._currentRoomToken) > 1;
             var stringObj = _this13._setInfoBarStrings(guestPresent, _this13._browserSharePaused);
             bar.label = stringObj.message;
             bar.classList.toggle("paused", _this13._browserSharePaused);
             buttonNode.label = stringObj.label;
             buttonNode.accessKey = stringObj.accesskey;
             LoopUI.MozLoopService.toggleBrowserSharing(_this13._browserSharePaused);
             if (_this13._browserSharePaused) {
-              _this13._pauseButtonClicked = true;
               // if paused we stop sharing remote cursors
-              _this13.removeRemoteCursor();} else 
-            {
-              _this13._resumeButtonClicked = true;}
+              _this13.removeRemoteCursor();}
 
             return true;}, 
 
           type: "pause" }, 
 
         { 
           label: this._getString("infobar_button_disconnect_label"), 
           accessKey: this._getString("infobar_button_disconnect_accesskey"), 
@@ -1413,20 +1379,20 @@ function startup(data) {
 
 
   // Wait for any new browser windows to open.
   wm.addListener(WindowListener);
 
   // Load our stylesheets.
   var styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"].
   getService(Components.interfaces.nsIStyleSheetService);
-  var sheets = ["chrome://loop-shared/skin/loop.css"];
+  var sheets = [
+  "chrome://loop-shared/skin/loop.css", 
+  "chrome://loop/skin/platform.css"];var _iteratorNormalCompletion2 = true;var _didIteratorError2 = false;var _iteratorError2 = undefined;try {
 
-  if (AppConstants.platform != "linux") {
-    sheets.push("chrome://loop/skin/platform.css");}var _iteratorNormalCompletion2 = true;var _didIteratorError2 = false;var _iteratorError2 = undefined;try {
 
 
     for (var _iterator2 = sheets[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {var sheet = _step2.value;
       var styleSheetURI = Services.io.newURI(sheet, null, null);
       styleSheetService.loadAndRegisterSheet(styleSheetURI, 
       styleSheetService.AUTHOR_SHEET);}} catch (err) {_didIteratorError2 = true;_iteratorError2 = err;} finally {try {if (!_iteratorNormalCompletion2 && _iterator2.return) {_iterator2.return();}} finally {if (_didIteratorError2) {throw _iteratorError2;}}}}
 
 
--- a/browser/extensions/loop/chrome/content/modules/MozLoopAPI.jsm
+++ b/browser/extensions/loop/chrome/content/modules/MozLoopAPI.jsm
@@ -79,72 +79,30 @@ var cloneableError = function cloneableE
 
 
 var getObjectAPIFunctionName = function getObjectAPIFunctionName(action) {
   var funcName = action.split(":").pop();
   return funcName.charAt(0).toLowerCase() + funcName.substr(1);};
 
 
 /**
- * Retrieves a list of Social Providers from the Social API that are explicitly
- * capable of sharing URLs.
- * It also adds a listener that is fired whenever a new Provider is added or
- * removed.
- *
- * @return {Array} Sorted list of share-capable Social Providers.
- */
-var updateSocialProvidersCache = function updateSocialProvidersCache() {
-  var providers = [];var _iteratorNormalCompletion2 = true;var _didIteratorError2 = false;var _iteratorError2 = undefined;try {
-
-    for (var _iterator2 = Social.providers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {var provider = _step2.value;
-      if (!provider.shareURL) {
-        continue;}
-
-
-      // Only pass the relevant data on to content.
-      providers.push({ 
-        iconURL: provider.iconURL, 
-        name: provider.name, 
-        origin: provider.origin });}} catch (err) {_didIteratorError2 = true;_iteratorError2 = err;} finally {try {if (!_iteratorNormalCompletion2 && _iterator2.return) {_iterator2.return();}} finally {if (_didIteratorError2) {throw _iteratorError2;}}}
-
-
-
-  var providersWasSet = !!gSocialProviders;
-  // Replace old with new.
-  gSocialProviders = providers.sort(function (a, b) {return (
-      a.name.toLowerCase().localeCompare(b.name.toLowerCase()));});
-
-  // Start listening for changes in the social provider list, if we're not
-  // doing that yet.
-  if (!providersWasSet) {
-    Services.obs.addObserver(updateSocialProvidersCache, "social:providers-changed", false);} else 
-  {
-    // Dispatch an event to content to let stores freshen-up.
-    LoopAPIInternal.broadcastPushMessage("SocialProvidersChanged");}
-
-
-  return gSocialProviders;};
-
-
-/**
  *  Checks that [browser.js]'s global variable `gMultiProcessBrowser` is active,
  *  instead of checking on first available browser element.
  *  :see bug 1257243 comment 5:
  */
 var isMultiProcessActive = function isMultiProcessActive() {
   var win = Services.wm.getMostRecentWindow("navigator:browser");
   return !!win.gMultiProcessBrowser;};
 
 
 var gAppVersionInfo = null;
 var gBrowserSharingListeners = new Set();
 var gBrowserSharingWindows = new Set();
 var gPageListeners = null;
 var gOriginalPageListeners = null;
-var gSocialProviders = null;
 var gStringBundle = null;
 var gStubbedMessageHandlers = null;
 var kBatchMessage = "Batch";
 var kMaxLoopCount = 10;
 var kMessageName = "Loop:Message";
 var kPushMessageName = "Loop:Message:Push";
 var kPushSubscription = "pushSubscription";
 var kRoomsPushPrefix = "Rooms:";
@@ -273,37 +231,16 @@ var kMessageHandlers = {
     MozLoopService.addConversationContext(windowId, { 
       sessionId: sessionId, 
       callId: callid });
 
     reply();}, 
 
 
   /**
-   * Activates the Social Share panel with the Social Provider panel opened
-   * when the popup open.
-   *
-   * @param {Object}   message Message meant for the handler function, containing
-   *                           the following parameters in its `data` property:
-   *                           [ ]
-   * @param {Function} reply   Callback function, invoked with the result of this
-   *                           message handler. The result will be sent back to
-   *                           the senders' channel.
-   */
-  AddSocialShareProvider: function AddSocialShareProvider(message, reply) {
-    var win = Services.wm.getMostRecentWindow("navigator:browser");
-    if (!win || !win.SocialShare) {
-      reply();
-      return;}
-
-    win.SocialShare.showDirectory(win.LoopUI.toolbarButton.anchor);
-    reply();}, 
-
-
-  /**
    * Composes an email via the external protocol service.
    *
    * @param {Object}   message Message meant for the handler function, containing
    *                           the following parameters in its `data` property:
    *                           [
    *                             {String} subject   Subject of the email to send
    *                             {String} body      Body message of the email to send
    *                             {String} recipient Recipient email address (optional)
@@ -410,19 +347,19 @@ var kMessageHandlers = {
     if (gStringBundle) {
       reply(gStringBundle);
       return;}
 
 
     // Get the map of strings.
     var strings = MozLoopService.getStrings();
     // Convert it to an object.
-    gStringBundle = {};var _iteratorNormalCompletion3 = true;var _didIteratorError3 = false;var _iteratorError3 = undefined;try {
-      for (var _iterator3 = strings.entries()[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {var _ref = _step3.value;var _ref2 = _slicedToArray(_ref, 2);var key = _ref2[0];var value = _ref2[1];
-        gStringBundle[key] = value;}} catch (err) {_didIteratorError3 = true;_iteratorError3 = err;} finally {try {if (!_iteratorNormalCompletion3 && _iterator3.return) {_iterator3.return();}} finally {if (_didIteratorError3) {throw _iteratorError3;}}}
+    gStringBundle = {};var _iteratorNormalCompletion2 = true;var _didIteratorError2 = false;var _iteratorError2 = undefined;try {
+      for (var _iterator2 = strings.entries()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {var _ref = _step2.value;var _ref2 = _slicedToArray(_ref, 2);var key = _ref2[0];var value = _ref2[1];
+        gStringBundle[key] = value;}} catch (err) {_didIteratorError2 = true;_iteratorError2 = err;} finally {try {if (!_iteratorNormalCompletion2 && _iterator2.return) {_iterator2.return();}} finally {if (_didIteratorError2) {throw _iteratorError2;}}}
 
     reply(gStringBundle);}, 
 
 
   /**
    * Fetch all constants that are used both on the client and the chrome-side.
    *
    * @param {Object}   message Message meant for the handler function, containing
@@ -433,20 +370,17 @@ var kMessageHandlers = {
    *                           the senders' channel.
    */
   GetAllConstants: function GetAllConstants(message, reply) {
     reply({ 
       COPY_PANEL: COPY_PANEL, 
       LOOP_SESSION_TYPE: LOOP_SESSION_TYPE, 
       LOOP_MAU_TYPE: LOOP_MAU_TYPE, 
       ROOM_CREATE: ROOM_CREATE, 
-      ROOM_DELETE: ROOM_DELETE, 
-      SHARING_ROOM_URL: SHARING_ROOM_URL, 
-      SHARING_SCREEN: SHARING_SCREEN, 
-      TWO_WAY_MEDIA_CONN_LENGTH: TWO_WAY_MEDIA_CONN_LENGTH });}, 
+      SHARING_ROOM_URL: SHARING_ROOM_URL });}, 
 
 
 
   /**
    * Returns the app version information for use during feedback.
    *
    * @param {Object}   message Message meant for the handler function, containing
    *                           the following parameters in its `data` property:
@@ -550,28 +484,28 @@ var kMessageHandlers = {
    * @param {Object}   message Message meant for the handler function, containing
    *                           the following parameters in its `data` property:
    *                           [ ]
    * @param {Function} reply   Callback function, invoked with the result of this
    *                           message handler. The result will be sent back to
    *                           the senders' channel.
    */
   GetErrors: function GetErrors(message, reply) {
-    var errors = {};var _iteratorNormalCompletion4 = true;var _didIteratorError4 = false;var _iteratorError4 = undefined;try {
-      for (var _iterator4 = MozLoopService.errors[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {var _ref3 = _step4.value;var _ref4 = _slicedToArray(_ref3, 2);var type = _ref4[0];var error = _ref4[1];
+    var errors = {};var _iteratorNormalCompletion3 = true;var _didIteratorError3 = false;var _iteratorError3 = undefined;try {
+      for (var _iterator3 = MozLoopService.errors[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {var _ref3 = _step3.value;var _ref4 = _slicedToArray(_ref3, 2);var type = _ref4[0];var error = _ref4[1];
         // if error.error is an nsIException, just delete it since it's hard
         // to clone across the boundary.
         if (error.error instanceof Ci.nsIException) {
           MozLoopService.log.debug("Warning: Some errors were omitted from MozLoopAPI.errors " + 
           "due to issues copying nsIException across boundaries.", 
           error.error);
           delete error.error;}
 
 
-        errors[type] = cloneableError(error);}} catch (err) {_didIteratorError4 = true;_iteratorError4 = err;} finally {try {if (!_iteratorNormalCompletion4 && _iterator4.return) {_iterator4.return();}} finally {if (_didIteratorError4) {throw _iteratorError4;}}}
+        errors[type] = cloneableError(error);}} catch (err) {_didIteratorError3 = true;_iteratorError3 = err;} finally {try {if (!_iteratorNormalCompletion3 && _iterator3.return) {_iterator3.return();}} finally {if (_didIteratorError3) {throw _iteratorError3;}}}
 
     return reply(errors);}, 
 
 
   /**
    * Returns true if this profile has an encryption key.
    *
    * @param {Object}   message Message meant for the handler function, containing
@@ -695,35 +629,16 @@ var kMessageHandlers = {
 
         reply(pageData);});});
 
 
     win.gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetPageData");}, 
 
 
   /**
-   * Returns a sorted list of Social Providers that can share URLs. See
-   * `updateSocialProvidersCache()` for more information.
-   *
-   * @param {Object}   message Message meant for the handler function, containing
-   *                           the following parameters in its `data` property:
-   *                           [ ]
-   * @param {Function} reply   Callback function, invoked with the result of this
-   *                           message handler. The result will be sent back to
-   *                           the senders' channel.
-   * @return {Array} Sorted list of share-capable Social Providers.
-   */
-  GetSocialShareProviders: function GetSocialShareProviders(message, reply) {
-    if (!gSocialProviders) {
-      updateSocialProvidersCache();}
-
-    reply(gSocialProviders);}, 
-
-
-  /**
    * Gets an object with data that represents the currently
    * authenticated user's identity.
    *
    * @param {Object}   message Message meant for the handler function, containing
    *                           the following parameters in its `data` property:
    *                           [ ]
    * @param {Function} reply   Callback function, invoked with the result of this
    *                           message handler. The result will be sent back to
@@ -985,25 +900,25 @@ var kMessageHandlers = {
       return;}var _message$data7 = _slicedToArray(
 
 
     message.data, 1);var windowId = _message$data7[0];
     gBrowserSharingListeners.delete(windowId);
     if (gBrowserSharingListeners.size > 0) {
       // There are still clients listening in, so keep on listening...
       reply();
-      return;}var _iteratorNormalCompletion5 = true;var _didIteratorError5 = false;var _iteratorError5 = undefined;try {
+      return;}var _iteratorNormalCompletion4 = true;var _didIteratorError4 = false;var _iteratorError4 = undefined;try {
 
 
-      for (var _iterator5 = gBrowserSharingWindows[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {var win = _step5.value;
+      for (var _iterator4 = gBrowserSharingWindows[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {var win = _step4.value;
         win = win.get();
         if (!win) {
           continue;}
 
-        win.LoopUI.stopBrowserSharing();}} catch (err) {_didIteratorError5 = true;_iteratorError5 = err;} finally {try {if (!_iteratorNormalCompletion5 && _iterator5.return) {_iterator5.return();}} finally {if (_didIteratorError5) {throw _iteratorError5;}}}
+        win.LoopUI.stopBrowserSharing();}} catch (err) {_didIteratorError4 = true;_iteratorError4 = err;} finally {try {if (!_iteratorNormalCompletion4 && _iterator4.return) {_iterator4.return();}} finally {if (_didIteratorError4) {throw _iteratorError4;}}}
 
 
     NewTabURL.reset();
 
     gBrowserSharingWindows.clear();
     reply();}, 
 
 
@@ -1086,68 +1001,32 @@ var kMessageHandlers = {
    */
   SetScreenShareState: function SetScreenShareState(message, reply) {var _message$data9 = _slicedToArray(
     message.data, 2);var windowId = _message$data9[0];var active = _message$data9[1];
     MozLoopService.setScreenShareState(windowId, active);
     reply();}, 
 
 
   /**
-   * Share a room URL with the Social API.
-   *
-   * @param {Object}   message Message meant for the handler function, containing
-   *                           the following parameters in its `data` property:
-   *                           [
-   *                             {String} providerOrigin URL fragment that identifies
-   *                                                     a social provider
-   *                             {String} roomURL        URL of a room
-   *                             {String} title          Title of the sharing message
-   *                             {String} body           Body of the sharing message
-   *                           ]
-   * @param {Function} reply   Callback function, invoked with the result of this
-   *                           message handler. The result will be sent back to
-   *                           the senders' channel.
-   */
-  SocialShareRoom: function SocialShareRoom(message, reply) {
-    var win = Services.wm.getMostRecentWindow("navigator:browser");
-    if (!win || !win.SocialShare) {
-      reply();
-      return;}var _message$data10 = _slicedToArray(
-
-
-    message.data, 4);var providerOrigin = _message$data10[0];var roomURL = _message$data10[1];var title = _message$data10[2];var body = _message$data10[3];
-    var graphData = { 
-      url: roomURL, 
-      title: title };
-
-    if (body) {
-      graphData.body = body;}
-
-    win.SocialShare.sharePage(providerOrigin, graphData, null, 
-    win.LoopUI.toolbarButton.anchor);
-    reply();}, 
-
-
-  /**
    * Adds a value to a telemetry histogram.
    *
    * @param {Object}   message Message meant for the handler function, containing
    *                           the following parameters in its `data` property:
    *                           [
    *                             {String} histogramId Name of the telemetry histogram
    *                                                  to update.
    *                             {String} value       Label of bucket to increment
    *                                                   in the histogram.
    *                           ]
    * @param {Function} reply   Callback function, invoked with the result of this
    *                           message handler. The result will be sent back to
    *                           the senders' channel.
    */
-  TelemetryAddValue: function TelemetryAddValue(message, reply) {var _message$data11 = _slicedToArray(
-    message.data, 2);var histogramId = _message$data11[0];var value = _message$data11[1];
+  TelemetryAddValue: function TelemetryAddValue(message, reply) {var _message$data10 = _slicedToArray(
+    message.data, 2);var histogramId = _message$data10[0];var value = _message$data10[1];
 
     if (histogramId === "LOOP_ACTIVITY_COUNTER") {
       var pref = "mau." + kMauPrefMap.get(value);
       var prefDate = MozLoopService.getLoopPref(pref) * 1000;
       var delta = Date.now() - prefDate;
 
       // Send telemetry event if period (30 days) passed.
       // 0 is default value for pref.
@@ -1184,23 +1063,23 @@ var LoopAPIInternal = {
 
 
     Cu.import("resource://gre/modules/RemotePageManager.jsm");
 
     gPageListeners = [new RemotePages("about:looppanel"), 
     new RemotePages("about:loopconversation"), 
     // Slideshow added here to expose the loop api to make L10n work.
     // XXX Can remove once slideshow is made remote.
-    new RemotePages("chrome://loop/content/panels/slideshow.html")];var _iteratorNormalCompletion6 = true;var _didIteratorError6 = false;var _iteratorError6 = undefined;try {
-      for (var _iterator6 = gPageListeners[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {var page = _step6.value;
+    new RemotePages("chrome://loop/content/panels/slideshow.html")];var _iteratorNormalCompletion5 = true;var _didIteratorError5 = false;var _iteratorError5 = undefined;try {
+      for (var _iterator5 = gPageListeners[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {var page = _step5.value;
         page.addMessageListener(kMessageName, this.handleMessage.bind(this));}
 
 
       // Subscribe to global events:
-    } catch (err) {_didIteratorError6 = true;_iteratorError6 = err;} finally {try {if (!_iteratorNormalCompletion6 && _iterator6.return) {_iterator6.return();}} finally {if (_didIteratorError6) {throw _iteratorError6;}}}Services.obs.addObserver(this.handleStatusChanged, "loop-status-changed", false);}, 
+    } catch (err) {_didIteratorError5 = true;_iteratorError5 = err;} finally {try {if (!_iteratorNormalCompletion5 && _iterator5.return) {_iterator5.return();}} finally {if (_didIteratorError5) {throw _iteratorError5;}}}Services.obs.addObserver(this.handleStatusChanged, "loop-status-changed", false);}, 
 
 
   /**
    * Handles incoming messages from RemotePageManager that are sent from Loop
    * content pages.
    *
    * @param {Object} message Object containing the following fields:
    *                         - {MessageManager} target Where the message came from
@@ -1346,24 +1225,24 @@ var LoopAPIInternal = {
           var prettyEventName = e.charAt(0).toUpperCase() + e.substr(1);
           try {
             message.target.sendAsyncMessage(kPushMessageName, [pushMessagePrefix + 
             prettyEventName, data]);} 
           catch (ex) {
             MozLoopService.log.debug("Unable to send event through to target: " + 
             ex.message);
             // Unregister event handlers when the message port is unreachable.
-            var _iteratorNormalCompletion7 = true;var _didIteratorError7 = false;var _iteratorError7 = undefined;try {for (var _iterator7 = events[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {var eventName = _step7.value;
-                api.off(eventName, handlerFunc);}} catch (err) {_didIteratorError7 = true;_iteratorError7 = err;} finally {try {if (!_iteratorNormalCompletion7 && _iterator7.return) {_iterator7.return();}} finally {if (_didIteratorError7) {throw _iteratorError7;}}}}};var _iteratorNormalCompletion8 = true;var _didIteratorError8 = false;var _iteratorError8 = undefined;try {
+            var _iteratorNormalCompletion6 = true;var _didIteratorError6 = false;var _iteratorError6 = undefined;try {for (var _iterator6 = events[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {var eventName = _step6.value;
+                api.off(eventName, handlerFunc);}} catch (err) {_didIteratorError6 = true;_iteratorError6 = err;} finally {try {if (!_iteratorNormalCompletion6 && _iterator6.return) {_iterator6.return();}} finally {if (_didIteratorError6) {throw _iteratorError6;}}}}};var _iteratorNormalCompletion7 = true;var _didIteratorError7 = false;var _iteratorError7 = undefined;try {
 
 
 
 
-          for (var _iterator8 = events[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {var eventName = _step8.value;
-            api.on(eventName, handlerFunc);}} catch (err) {_didIteratorError8 = true;_iteratorError8 = err;} finally {try {if (!_iteratorNormalCompletion8 && _iterator8.return) {_iterator8.return();}} finally {if (_didIteratorError8) {throw _iteratorError8;}}}
+          for (var _iterator7 = events[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {var eventName = _step7.value;
+            api.on(eventName, handlerFunc);}} catch (err) {_didIteratorError7 = true;_iteratorError7 = err;} finally {try {if (!_iteratorNormalCompletion7 && _iterator7.return) {_iterator7.return();}} finally {if (_didIteratorError7) {throw _iteratorError7;}}}
 
         reply();
         return { v: void 0 };}();if ((typeof _ret === "undefined" ? "undefined" : _typeof(_ret)) === "object") return _ret.v;}
 
 
     if (typeof api[funcName] != "function") {
       reply(cloneableError("Sorry, function '" + funcName + "' does not exist!"));
       return;}
@@ -1383,49 +1262,45 @@ var LoopAPIInternal = {
   /**
    * Send an event to the content window to indicate that the state on the chrome
    * side was updated.
    *
    * @param {name} name Name of the event
    */
   broadcastPushMessage: function broadcastPushMessage(name, data) {
     if (!gPageListeners) {
-      return;}var _iteratorNormalCompletion9 = true;var _didIteratorError9 = false;var _iteratorError9 = undefined;try {
+      return;}var _iteratorNormalCompletion8 = true;var _didIteratorError8 = false;var _iteratorError8 = undefined;try {
 
-      for (var _iterator9 = gPageListeners[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) {var page = _step9.value;
+      for (var _iterator8 = gPageListeners[Symbol.iterator](), _step8; !(_iteratorNormalCompletion8 = (_step8 = _iterator8.next()).done); _iteratorNormalCompletion8 = true) {var page = _step8.value;
         try {
           page.sendAsyncMessage(kPushMessageName, [name, data]);} 
         catch (ex) {
           // Only make noise when the Remote Page Manager needs more time to
           // initialize.
           if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED) {
-            throw ex;}}}} catch (err) {_didIteratorError9 = true;_iteratorError9 = err;} finally {try {if (!_iteratorNormalCompletion9 && _iterator9.return) {_iterator9.return();}} finally {if (_didIteratorError9) {throw _iteratorError9;}}}}, 
+            throw ex;}}}} catch (err) {_didIteratorError8 = true;_iteratorError8 = err;} finally {try {if (!_iteratorNormalCompletion8 && _iterator8.return) {_iterator8.return();}} finally {if (_didIteratorError8) {throw _iteratorError8;}}}}, 
 
 
 
 
 
   /**
    * De the reverse of `initialize` above; unhook page and event listeners.
    */
   destroy: function destroy() {
     if (!gPageListeners) {
-      return;}var _iteratorNormalCompletion10 = true;var _didIteratorError10 = false;var _iteratorError10 = undefined;try {
+      return;}var _iteratorNormalCompletion9 = true;var _didIteratorError9 = false;var _iteratorError9 = undefined;try {
 
-      for (var _iterator10 = gPageListeners[Symbol.iterator](), _step10; !(_iteratorNormalCompletion10 = (_step10 = _iterator10.next()).done); _iteratorNormalCompletion10 = true) {var listener = _step10.value;
-        listener.destroy();}} catch (err) {_didIteratorError10 = true;_iteratorError10 = err;} finally {try {if (!_iteratorNormalCompletion10 && _iterator10.return) {_iterator10.return();}} finally {if (_didIteratorError10) {throw _iteratorError10;}}}
+      for (var _iterator9 = gPageListeners[Symbol.iterator](), _step9; !(_iteratorNormalCompletion9 = (_step9 = _iterator9.next()).done); _iteratorNormalCompletion9 = true) {var listener = _step9.value;
+        listener.destroy();}} catch (err) {_didIteratorError9 = true;_iteratorError9 = err;} finally {try {if (!_iteratorNormalCompletion9 && _iterator9.return) {_iterator9.return();}} finally {if (_didIteratorError9) {throw _iteratorError9;}}}
 
     gPageListeners = null;
 
     // Unsubscribe from global events.
-    Services.obs.removeObserver(this.handleStatusChanged, "loop-status-changed");
-    // Stop listening for changes in the social provider list, if necessary.
-    if (gSocialProviders) {
-      Services.obs.removeObserver(updateSocialProvidersCache, "social:providers-changed");}} };
-
+    Services.obs.removeObserver(this.handleStatusChanged, "loop-status-changed");} };
 
 
 
 this.LoopAPI = Object.freeze({ 
   /* @see LoopAPIInternal#initialize */
   initialize: function initialize() {
     LoopAPIInternal.initialize();}, 
 
--- a/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
+++ b/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
@@ -7,30 +7,16 @@
 Components;var Ci = _Components.interfaces;var Cu = _Components.utils;var Cr = _Components.results;
 
 var LOOP_SESSION_TYPE = { 
   GUEST: 1, 
   FXA: 2 };
 
 
 /**
- * Values that we segment 2-way media connection length telemetry probes
- * into.
- *
- * @type {{SHORTER_THAN_10S: Number, BETWEEN_10S_AND_30S: Number,
- *   BETWEEN_30S_AND_5M: Number, MORE_THAN_5M: Number}}
- */
-var TWO_WAY_MEDIA_CONN_LENGTH = { 
-  SHORTER_THAN_10S: 0, 
-  BETWEEN_10S_AND_30S: 1, 
-  BETWEEN_30S_AND_5M: 2, 
-  MORE_THAN_5M: 3 };
-
-
-/**
  * Values that we segment sharing a room URL action telemetry probes into.
  *
  * @type {{COPY_FROM_PANEL: Number, COPY_FROM_CONVERSATION: Number,
  *    EMAIL_FROM_CALLFAILED: Number, EMAIL_FROM_CONVERSATION: Number,
  *    FACEBOOK_FROM_CONVERSATION: Number, EMAIL_FROM_PANEL: Number}}
  */
 var SHARING_ROOM_URL = { 
   COPY_FROM_PANEL: 0, 
@@ -47,36 +33,16 @@ var SHARING_ROOM_URL = {
  * @type {{CREATE_SUCCESS: Number, CREATE_FAIL: Number}}
  */
 var ROOM_CREATE = { 
   CREATE_SUCCESS: 0, 
   CREATE_FAIL: 1 };
 
 
 /**
- * Values that we segment room delete action telemetry probes into.
- *
- * @type {{DELETE_SUCCESS: Number, DELETE_FAIL: Number}}
- */
-var ROOM_DELETE = { 
-  DELETE_SUCCESS: 0, 
-  DELETE_FAIL: 1 };
-
-
-/**
- * Values that we segment sharing screen pause/ resume action telemetry probes into.
- *
- * @type {{PAUSED: Number, RESUMED: Number}}
- */
-var SHARING_SCREEN = { 
-  PAUSED: 0, 
-  RESUMED: 1 };
-
-
-/**
  * Values that we segment copy panel action telemetry probes into.
  *
  * @enum {Number}
  */
 var COPY_PANEL = { 
   // Copy panel was shown to the user.
   SHOWN: 0, 
   // User selected "no" and to allow the panel to show again.
@@ -121,26 +87,22 @@ Cu.import("resource://gre/modules/Promis
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
 
 Cu.importGlobalProperties(["URL"]);
 
 this.EXPORTED_SYMBOLS = ["MozLoopService", "LOOP_SESSION_TYPE", "LOOP_MAU_TYPE", 
-"TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_ROOM_URL", "SHARING_SCREEN", "COPY_PANEL", 
-"ROOM_CREATE", "ROOM_DELETE"];
+"SHARING_ROOM_URL", "COPY_PANEL", "ROOM_CREATE"];
 
 XPCOMUtils.defineConstant(this, "LOOP_SESSION_TYPE", LOOP_SESSION_TYPE);
-XPCOMUtils.defineConstant(this, "TWO_WAY_MEDIA_CONN_LENGTH", TWO_WAY_MEDIA_CONN_LENGTH);
 XPCOMUtils.defineConstant(this, "SHARING_ROOM_URL", SHARING_ROOM_URL);
-XPCOMUtils.defineConstant(this, "SHARING_SCREEN", SHARING_SCREEN);
 XPCOMUtils.defineConstant(this, "COPY_PANEL", COPY_PANEL);
 XPCOMUtils.defineConstant(this, "ROOM_CREATE", ROOM_CREATE);
-XPCOMUtils.defineConstant(this, "ROOM_DELETE", ROOM_DELETE);
 XPCOMUtils.defineConstant(this, "LOOP_MAU_TYPE", LOOP_MAU_TYPE);
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoopAPI", 
 "chrome://loop/content/modules/MozLoopAPI.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport", 
 "resource://gre/modules/media/RTCStatsReport.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "loopUtils", 
@@ -824,17 +786,19 @@ var MozLoopServiceInternal = {
     mm.addMessageListener("Loop:GetAllWebrtcStats", function getAllStats(message) {
       mm.removeMessageListener("Loop:GetAllWebrtcStats", getAllStats);var _message$data = 
 
       message.data;var allStats = _message$data.allStats;var logs = _message$data.logs;
       var internalFormat = allStats.reports[0]; // filtered on peerConnectionID
 
       var report = convertToRTCStatsReport(internalFormat);
       var logStr = "";
-      logs.forEach(function (s) {logStr += s + "\n";});
+      logs.forEach(function (s) {
+        logStr += s + "\n";});
+
 
       // We have stats and logs.
 
       // Create worker job. ping = saved telemetry ping file header + payload
       //
       // Prepare payload according to https://wiki.mozilla.org/Loop/Telemetry
 
       var ai = Services.appinfo;
--- a/browser/extensions/loop/chrome/content/panels/css/desktop.css
+++ b/browser/extensions/loop/chrome/content/panels/css/desktop.css
@@ -146,53 +146,16 @@ html[dir="rtl"] .share-action-group > .i
   width: 28px;
 }
 
 .share-action-group > .invite-button > div {
   display: inline;
   color: #4a4a4a;
 }
 
-.share-service-dropdown {
-  color: #000;
-  text-align: start;
-  bottom: auto;
-  top: 0;
-  overflow: hidden;
-  overflow-y: auto;
-}
-
-/* When the dropdown is showing a vertical scrollbar, compensate for its width. */
-body[platform="other"] .share-service-dropdown.overflow > .dropdown-menu-item,
-body[platform="win"] .share-service-dropdown.overflow > .dropdown-menu-item {
-  padding-inline-end: 20px;
-}
-
-.share-service-dropdown > .dropdown-menu-item > .icon {
-  width: 14px;
-  height: 14px;
-  margin-right: 4px;
-}
-
-.dropdown-menu-item > .icon-add-share-service {
-  background-image: url("../img/icons-16x16.svg#add");
-  background-repeat: no-repeat;
-  background-size: 12px 12px;
-  width: 12px;
-  height: 12px;
-}
-
-.dropdown-menu-item:hover > .icon-add-share-service {
-  background-image: url("../img/icons-16x16.svg#add-hover");
-}
-
-.dropdown-menu-item:hover:active > .icon-add-share-service {
-  background-image: url("../img/icons-16x16.svg#add-active");
-}
-
 .share-panel-container {
   position: absolute;
   top: 0;
   left: 0;
   bottom: 0;
   right: 0;
   z-index: 999;
 }
--- a/browser/extensions/loop/chrome/content/panels/js/conversation.js
+++ b/browser/extensions/loop/chrome/content/panels/js/conversation.js
@@ -174,19 +174,16 @@ loop.conversation = function (mozL10n) {
       var sdkDriver = new loop.OTSdkDriver({ 
         constants: constants, 
         isDesktop: true, 
         useDataChannels: true, 
         dispatcher: dispatcher, 
         sdk: OT });
 
 
-      // expose for functional tests
-      loop.conversation._sdkDriver = sdkDriver;
-
       // Create the stores.
       var activeRoomStore = new loop.store.ActiveRoomStore(dispatcher, { 
         isDesktop: true, 
         sdkDriver: sdkDriver });
 
       var conversationAppStore = new loop.store.ConversationAppStore(dispatcher, { 
         activeRoomStore: activeRoomStore, 
         feedbackPeriod: results[++requestIdx], 
@@ -231,21 +228,13 @@ loop.conversation = function (mozL10n) {
 
 
       loop.request("TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", constants.LOOP_MAU_TYPE.OPEN_CONVERSATION);});}
 
 
 
   return { 
     AppControllerView: AppControllerView, 
-    init: init, 
-
-    /**
-     * Exposed for the use of functional tests to be able to check
-     * metric-related execution as the call sequence progresses.
-     *
-     * @type loop.OTSdkDriver
-     */
-    _sdkDriver: null };}(
+    init: init };}(
 
 document.mozL10n);
 
 document.addEventListener("DOMContentLoaded", loop.conversation.init);
--- a/browser/extensions/loop/chrome/content/panels/js/desktopViews.js
+++ b/browser/extensions/loop/chrome/content/panels/js/desktopViews.js
@@ -152,18 +152,17 @@ loop.shared.desktopViews = function (moz
     propTypes: { 
       callback: React.PropTypes.func, 
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, 
       error: React.PropTypes.object, 
       facebookEnabled: React.PropTypes.bool.isRequired, 
       locationForMetrics: React.PropTypes.string.isRequired, 
       // This data is supplied by the activeRoomStore.
       roomData: React.PropTypes.object.isRequired, 
-      show: React.PropTypes.bool.isRequired, 
-      socialShareProviders: React.PropTypes.array }, 
+      show: React.PropTypes.bool.isRequired }, 
 
 
     render: function render() {var _this = this;
       if (!this.props.show || !this.props.roomData.roomUrl) {
         return null;}
 
 
       var cx = classNames;
@@ -199,99 +198,23 @@ loop.shared.desktopViews = function (moz
         function () {
           if (_this.props.facebookEnabled) {
             return React.createElement(FacebookShareButton, { 
               callback: _this.props.callback, 
               dispatcher: _this.props.dispatcher, 
               locationForMetrics: _this.props.locationForMetrics, 
               roomData: _this.props.roomData });}
 
-          return null;}()), 
-
-
-        React.createElement(SocialShareDropdown, { 
-          dispatcher: this.props.dispatcher, 
-          ref: "menu", 
-          roomUrl: this.props.roomData.roomUrl, 
-          show: this.state.showMenu, 
-          socialShareProviders: this.props.socialShareProviders })));} });
-
-
-
-
-
-  var SocialShareDropdown = React.createClass({ displayName: "SocialShareDropdown", 
-    propTypes: { 
-      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, 
-      roomUrl: React.PropTypes.string, 
-      show: React.PropTypes.bool.isRequired, 
-      socialShareProviders: React.PropTypes.array }, 
-
-
-    handleAddServiceClick: function handleAddServiceClick(event) {
-      event.preventDefault();
-
-      this.props.dispatcher.dispatch(new sharedActions.AddSocialShareProvider());}, 
-
-
-    handleProviderClick: function handleProviderClick(event) {
-      event.preventDefault();
-
-      var origin = event.currentTarget.dataset.provider;
-      var provider = this.props.socialShareProviders.
-      filter(function (socialProvider) {
-        return socialProvider.origin === origin;})[
-      0];
+          return null;}())));} });
 
-      this.props.dispatcher.dispatch(new sharedActions.ShareRoomUrl({ 
-        provider: provider, 
-        roomUrl: this.props.roomUrl, 
-        previews: [] }));}, 
-
-
-
-    render: function render() {
-      // Don't render a thing when no data has been fetched yet.
-      if (!this.props.socialShareProviders) {
-        return null;}
-
-
-      var cx = classNames;
-      var shareDropdown = cx({ 
-        "share-service-dropdown": true, 
-        "dropdown-menu": true, 
-        "visually-hidden": true, 
-        "hide": !this.props.show });
-
-
-      return (
-        React.createElement("ul", { className: shareDropdown }, 
-        React.createElement("li", { className: "dropdown-menu-item", onClick: this.handleAddServiceClick }, 
-        React.createElement("i", { className: "icon icon-add-share-service" }), 
-        React.createElement("span", null, mozL10n.get("share_add_service_button"))), 
-
-        this.props.socialShareProviders.length ? React.createElement("li", { className: "dropdown-menu-separator" }) : null, 
-
-        this.props.socialShareProviders.map(function (provider, idx) {
-          return (
-            React.createElement("li", { className: "dropdown-menu-item", 
-              "data-provider": provider.origin, 
-              key: "provider-" + idx, 
-              onClick: this.handleProviderClick }, 
-            React.createElement("img", { className: "icon", src: provider.iconURL }), 
-            React.createElement("span", null, provider.name)));}.
-
-
-        bind(this))));} });
 
 
 
 
 
 
   return { 
     CopyLinkButton: CopyLinkButton, 
     EmailLinkButton: EmailLinkButton, 
     FacebookShareButton: FacebookShareButton, 
-    SharePanelView: SharePanelView, 
-    SocialShareDropdown: SocialShareDropdown };}(
+    SharePanelView: SharePanelView };}(
 
 navigator.mozL10n || document.mozL10n);
--- a/browser/extensions/loop/chrome/content/panels/js/panel.js
+++ b/browser/extensions/loop/chrome/content/panels/js/panel.js
@@ -682,17 +682,19 @@ loop.panel = _.extend(loop.panel || {}, 
     if (clickYPos + menuNodeHeight >= listTop + listHeight) {
       // Position above click area.
       topPos = clickYPos - menuNodeHeight - listTop - clickOffset;} else 
     {
       // Position below click area.
       topPos = clickYPos - listTop + clickOffset;}
 
     // Ensure menu is not cut off at top
-    if (topPos < 0) {topPos = 0;}
+    if (topPos < 0) {
+      topPos = 0;}
+
 
     return topPos;}
 
 
   /**
    * Dropdown menu for each conversation entry.
    * Because the container element has overflow we need to position the menu
    * absolutely and have a different element as offset parent for it. We need
@@ -1123,18 +1125,17 @@ loop.panel = _.extend(loop.panel || {}, 
         React.createElement("div", { className: "share-panel-overlay", onClick: this.handleClosePanel }), 
         React.createElement(sharedDesktopViews.SharePanelView, { 
           callback: this.handleClosePanel, 
           dispatcher: this.props.dispatcher, 
           error: this.state.error, 
           facebookEnabled: this.state.facebookEnabled, 
           locationForMetrics: "panel", 
           roomData: roomData, 
-          show: true, 
-          socialShareProviders: this.state.socialShareProviders })));} });
+          show: true })));} });
 
 
 
 
 
   var RenameRoomView = React.createClass({ displayName: "RenameRoomView", 
     mixins: [sharedMixins.WindowCloseMixin], 
 
--- a/browser/extensions/loop/chrome/content/panels/js/roomStore.js
+++ b/browser/extensions/loop/chrome/content/panels/js/roomStore.js
@@ -72,29 +72,27 @@ loop.store = loop.store || {};
      */
     maxRoomCreationSize: MAX_ROOM_CREATION_SIZE, 
 
     /**
      * Registered actions.
      * @type {Array}
      */
     actions: [
-    "addSocialShareProvider", 
     "createRoom", 
     "createdRoom", 
     "createRoomError", 
     "copyRoomUrl", 
     "deleteRoom", 
     "deleteRoomError", 
     "emailRoomUrl", 
     "facebookShareRoomUrl", 
     "getAllRooms", 
     "getAllRoomsError", 
     "openRoom", 
-    "shareRoomUrl", 
     "updateRoomContext", 
     "updateRoomContextDone", 
     "updateRoomContextError", 
     "updateRoomList"], 
 
 
     initialize: function initialize(options) {
       if (!options.constants) {
@@ -343,20 +341,17 @@ loop.store = loop.store || {};
       ["NotifyUITour", "Loop:RoomURLCopied"]);
 
       var from = actionData.from;
       var bucket = this._constants.SHARING_ROOM_URL["COPY_FROM_" + from.toUpperCase()];
       if (typeof bucket === "undefined") {
         console.error("No URL sharing type bucket found for '" + from + "'");
         return;}
 
-      loop.requestMulti(
-      ["TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket], 
-      ["TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE]);}, 
-
+      loop.request("TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE);}, 
 
 
     /**
      * Emails a room url.
      *
      * @param  {sharedActions.EmailRoomUrl} actionData The action data.
      */
     emailRoomUrl: function emailRoomUrl(actionData) {
@@ -368,17 +363,16 @@ loop.store = loop.store || {};
       "EMAIL_FROM_" + (from || "").toUpperCase()];
 
       if (typeof bucket === "undefined") {
         console.error("No URL sharing type bucket found for '" + from + "'");
         return;}
 
       loop.requestMulti(
       ["NotifyUITour", "Loop:RoomURLEmailed"], 
-      ["TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket], 
       ["TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE]);}, 
 
 
 
     /**
      * Share a room url with Facebook
      *
      * @param  {sharedActions.FacebookShareRoomUrl} actionData The action data.
@@ -406,78 +400,31 @@ loop.store = loop.store || {};
 
 
       var from = actionData.from;
       var bucket = this._constants.SHARING_ROOM_URL["FACEBOOK_FROM_" + from.toUpperCase()];
       if (typeof bucket === "undefined") {
         console.error("No URL sharing type bucket found for '" + from + "'");
         return;}
 
-      loop.requestMulti(
-      ["TelemetryAddValue", "LOOP_SHARING_ROOM_URL", bucket], 
-      ["TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE]);}, 
-
-
-
-    /**
-     * Share a room url.
-     *
-     * @param  {sharedActions.ShareRoomUrl} actionData The action data.
-     */
-    shareRoomUrl: function shareRoomUrl(actionData) {
-      var providerOrigin = new URL(actionData.provider.origin).hostname;
-      var shareTitle = "";
-      var shareBody = null;
-
-      switch (providerOrigin) {
-        case "mail.google.com":
-          shareTitle = mozL10n.get("share_email_subject7");
-          shareBody = mozL10n.get("share_email_body7", { 
-            callUrl: actionData.roomUrl });
-
-          shareBody += mozL10n.get("share_email_footer2");
-          break;
-        case "twitter.com":
-        default:
-          shareTitle = mozL10n.get("share_tweet", { 
-            clientShortname2: mozL10n.get("clientShortname2") });
-
-          break;}
-
-
-      loop.requestMulti(
-      ["SocialShareRoom", actionData.provider.origin, actionData.roomUrl, 
-      shareTitle, shareBody], 
-      ["NotifyUITour", "Loop:RoomURLShared"]);}, 
-
-
-    /**
-     * Open the share panel to add a Social share provider.
-     */
-    addSocialShareProvider: function addSocialShareProvider() {
-      loop.request("AddSocialShareProvider");}, 
+      loop.request("TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_SHARE);}, 
 
 
     /**
      * Creates a new room.
      *
      * @param {sharedActions.DeleteRoom} actionData The action data.
      */
     deleteRoom: function deleteRoom(actionData) {
       loop.request("Rooms:Delete", actionData.roomToken).then(function (result) {
         var isError = result && result.isError;
         if (isError) {
           this.dispatchAction(new sharedActions.DeleteRoomError({ error: result }));}
 
-        var buckets = this._constants.ROOM_DELETE;
-        loop.requestMulti(
-        ["TelemetryAddValue", "LOOP_ROOM_DELETE", buckets[isError ? 
-        "DELETE_FAIL" : "DELETE_SUCCESS"]], 
-        ["TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_DELETE]);}.
-
+        loop.request("TelemetryAddValue", "LOOP_ACTIVITY_COUNTER", this._constants.LOOP_MAU_TYPE.ROOM_DELETE);}.
       bind(this));}, 
 
 
     /**
      * Executed when a room deletion error occurs.
      *
      * @param {sharedActions.DeleteRoomError} actionData The action data.
      */
--- a/browser/extensions/loop/chrome/content/panels/js/roomViews.js
+++ b/browser/extensions/loop/chrome/content/panels/js/roomViews.js
@@ -367,18 +367,17 @@ loop.roomViews = function (mozL10n) {
                 showHangup: this.props.chatWindowDetached, 
                 video: { enabled: !this.state.videoMuted, visible: true } }), 
               React.createElement(sharedDesktopViews.SharePanelView, { 
                 dispatcher: this.props.dispatcher, 
                 error: this.state.error, 
                 facebookEnabled: this.props.facebookEnabled, 
                 locationForMetrics: "conversation", 
                 roomData: roomData, 
-                show: shouldRenderInvitationOverlay, 
-                socialShareProviders: this.state.socialShareProviders }))));}}} });
+                show: shouldRenderInvitationOverlay }))));}}} });
 
 
 
 
 
 
 
 
--- a/browser/extensions/loop/chrome/content/panels/test/desktopViews_test.js
+++ b/browser/extensions/loop/chrome/content/panels/test/desktopViews_test.js
@@ -262,101 +262,9 @@ describe("loop.shared.desktopViews", fun
       expect(ReactDOM.findDOMNode(view)).eql(null);});
 
 
     it("should not display the panel when roomUrl is not defined", function () {
       view = mountTestComponent({ 
         roomData: {} });
 
 
-      expect(ReactDOM.findDOMNode(view)).eql(null);});});
-
-
-
-  describe("SocialShareDropdown", function () {
-    var fakeProvider, view;
-
-    beforeEach(function () {
-      fakeProvider = { 
-        name: "foo", 
-        origin: "https://foo", 
-        iconURL: "http://example.com/foo.png" };});
-
-
-
-    afterEach(function () {
-      fakeProvider = null;});
-
-
-    function mountTestComponent(props) {
-      props = _.extend({ 
-        dispatcher: dispatcher, 
-        show: true }, 
-      props);
-      return TestUtils.renderIntoDocument(
-      React.createElement(sharedDesktopViews.SocialShareDropdown, props));}
-
-
-    describe("#render", function () {
-      it("should show no contents when the Social Providers have not been fetched yet", function () {
-        view = mountTestComponent();
-
-        expect(ReactDOM.findDOMNode(view)).to.eql(null);});
-
-
-      it("should show an empty list when no Social Providers are available", function () {
-        view = mountTestComponent({ 
-          socialShareProviders: [] });
-
-
-        var node = ReactDOM.findDOMNode(view);
-        expect(node.querySelector(".icon-add-share-service")).to.not.eql(null);
-        expect(node.querySelectorAll(".dropdown-menu-item").length).to.eql(1);});
-
-
-      it("should show a list of available Social Providers", function () {
-        view = mountTestComponent({ 
-          socialShareProviders: [fakeProvider] });
-
-
-        var node = ReactDOM.findDOMNode(view);
-        expect(node.querySelector(".icon-add-share-service")).to.not.eql(null);
-        expect(node.querySelector(".dropdown-menu-separator")).to.not.eql(null);
-
-        var dropdownNodes = node.querySelectorAll(".dropdown-menu-item");
-        expect(dropdownNodes.length).to.eql(2);
-        expect(dropdownNodes[1].querySelector("img").src).to.eql(fakeProvider.iconURL);
-        expect(dropdownNodes[1].querySelector("span").textContent).
-        to.eql(fakeProvider.name);});});
-
-
-
-    describe("#handleAddServiceClick", function () {
-      it("should dispatch an action when the 'add provider' item is clicked", function () {
-        view = mountTestComponent({ 
-          socialShareProviders: [] });
-
-
-        var addItem = ReactDOM.findDOMNode(view).querySelector(".dropdown-menu-item:first-child");
-        React.addons.TestUtils.Simulate.click(addItem);
-
-        sinon.assert.calledOnce(dispatcher.dispatch);
-        sinon.assert.calledWithExactly(dispatcher.dispatch, 
-        new sharedActions.AddSocialShareProvider());});});
-
-
-
-    describe("#handleProviderClick", function () {
-      it("should dispatch an action when a provider item is clicked", function () {
-        view = mountTestComponent({ 
-          roomUrl: "http://example.com", 
-          socialShareProviders: [fakeProvider] });
-
-
-        var providerItem = ReactDOM.findDOMNode(view).querySelector(".dropdown-menu-item:last-child");
-        React.addons.TestUtils.Simulate.click(providerItem);
-
-        sinon.assert.calledOnce(dispatcher.dispatch);
-        sinon.assert.calledWithExactly(dispatcher.dispatch, 
-        new sharedActions.ShareRoomUrl({ 
-          provider: fakeProvider, 
-          roomUrl: "http://example.com", 
-          previews: [] }));});});});});
+      expect(ReactDOM.findDOMNode(view)).eql(null);});});});
--- a/browser/extensions/loop/chrome/content/panels/test/roomStore_test.js
+++ b/browser/extensions/loop/chrome/content/panels/test/roomStore_test.js
@@ -36,20 +36,16 @@ describe("loop.store.RoomStore", functio
             EMAIL_FROM_CONVERSATION: 3, 
             FACEBOOK_FROM_CONVERSATION: 4, 
             EMAIL_FROM_PANEL: 5 }, 
 
           ROOM_CREATE: { 
             CREATE_SUCCESS: 0, 
             CREATE_FAIL: 1 }, 
 
-          ROOM_DELETE: { 
-            DELETE_SUCCESS: 0, 
-            DELETE_FAIL: 1 }, 
-
           LOOP_MAU_TYPE: { 
             OPEN_PANEL: 0, 
             OPEN_CONVERSATION: 1, 
             ROOM_OPEN: 2, 
             ROOM_SHARE: 3, 
             ROOM_DELETE: 4 } };}, 
 
 
@@ -99,17 +95,19 @@ describe("loop.store.RoomStore", functio
       roomUrl: "http://sample/3jKS_Els9IU", 
       roomName: "Third Room Name", 
       maxSize: 3, 
       clientMaxSize: 2, 
       participants: [], 
       ctime: 1405518241 }];
 
 
-    document.mozL10n.get = function (str) {return str;};});
+    document.mozL10n.get = function (str) {
+      return str;};});
+
 
 
   afterEach(function () {
     sandbox.restore();
     LoopMochaUtils.restore();});
 
 
   describe("#constructor", function () {
@@ -500,76 +498,30 @@ describe("loop.store.RoomStore", functio
 
         store.deleteRoom(new sharedActions.DeleteRoom({ 
           roomToken: fakeRoomToken }));
 
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch, 
         new sharedActions.DeleteRoomError({ 
-          error: err }));});
-
-
-
-      it("should log a telemetry event when the operation is successful", function () {
-        store.deleteRoom(new sharedActions.DeleteRoom({ 
-          roomToken: fakeRoomToken }));
-
-
-        sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
-        sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0), 
-        "LOOP_ROOM_DELETE", 0);});
+          error: err }));});});
 
 
-      it("should log a telemetry event when the operation fails", function () {
-        var err = new Error("fake");
-        err.isError = true;
-        requestStubs["Rooms:Delete"].returns(err);
-
-        store.deleteRoom(new sharedActions.DeleteRoom({ 
-          roomToken: fakeRoomToken }));
-
-
-        sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
-        sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0), 
-        "LOOP_ROOM_DELETE", 1);});});
-
 
 
     describe("#copyRoomUrl", function () {
       it("should copy the room URL", function () {
         store.copyRoomUrl(new sharedActions.CopyRoomUrl({ 
           roomUrl: "http://invalid", 
           from: "conversation" }));
 
 
         sinon.assert.calledOnce(requestStubs.CopyString);
-        sinon.assert.calledWithExactly(requestStubs.CopyString, "http://invalid");});
-
-
-      it("should send a telemetry event for copy from panel", function () {
-        store.copyRoomUrl(new sharedActions.CopyRoomUrl({ 
-          roomUrl: "http://invalid", 
-          from: "panel" }));
-
-
-        sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
-        sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0), 
-        "LOOP_SHARING_ROOM_URL", 0);});
-
-
-      it("should send a telemetry event for copy from conversation", function () {
-        store.copyRoomUrl(new sharedActions.CopyRoomUrl({ 
-          roomUrl: "http://invalid", 
-          from: "conversation" }));
-
-
-        sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
-        sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0), 
-        "LOOP_SHARING_ROOM_URL", 1);});});
+        sinon.assert.calledWithExactly(requestStubs.CopyString, "http://invalid");});});
 
 
 
     describe("#emailRoomUrl", function () {
       it("should call composeCallUrlEmail to email the url", function () {
         sandbox.stub(sharedUtils, "composeCallUrlEmail");
 
         store.emailRoomUrl(new sharedActions.EmailRoomUrl({ 
@@ -642,86 +594,17 @@ describe("loop.store.RoomStore", functio
         store.facebookShareRoomUrl(new sharedActions.FacebookShareRoomUrl({ 
           from: "conversation", 
           roomUrl: room }));
 
 
         sinon.assert.calledOnce(requestStubs.OpenURL);
         sinon.assert.calledWithMatch(requestStubs.OpenURL, sharingSite);
         sinon.assert.calledWithMatch(requestStubs.OpenURL, room);
-        sinon.assert.calledWithMatch(requestStubs.OpenURL, fallback);});
-
-
-      it("should send a telemetry event for facebook share from conversation", function () {
-        store.facebookShareRoomUrl(new sharedActions.FacebookShareRoomUrl({ 
-          from: "conversation", 
-          roomUrl: "http://invalid" }));
-
-
-        sinon.assert.calledTwice(requestStubs.TelemetryAddValue);
-        sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue.getCall(0), 
-        "LOOP_SHARING_ROOM_URL", 4);});});
-
-
-
-    describe("#shareRoomUrl", function () {
-      var socialShareRoomStub;
-
-      beforeEach(function () {
-        socialShareRoomStub = sinon.stub();
-        LoopMochaUtils.stubLoopRequest({ 
-          SocialShareRoom: socialShareRoomStub });});
-
-
-
-      it("should pass the correct data for GMail sharing", function () {
-        var roomUrl = "http://invalid";
-        var origin = "https://mail.google.com/v1";
-        store.shareRoomUrl(new sharedActions.ShareRoomUrl({ 
-          roomUrl: roomUrl, 
-          provider: { 
-            origin: origin } }));
-
-
-
-        sinon.assert.calledOnce(socialShareRoomStub);
-        sinon.assert.calledWithExactly(socialShareRoomStub, 
-        origin, 
-        roomUrl, 
-        "share_email_subject7", 
-        "share_email_body7" + 
-        "share_email_footer2");});
-
-
-      it("should pass the correct data for all other Social Providers", function () {
-        var roomUrl = "http://invalid2";
-        var origin = "https://twitter.com/share";
-        store.shareRoomUrl(new sharedActions.ShareRoomUrl({ 
-          roomUrl: roomUrl, 
-          provider: { 
-            origin: origin } }));
-
-
-
-        sinon.assert.calledOnce(socialShareRoomStub);
-        sinon.assert.calledWithExactly(socialShareRoomStub, origin, 
-        roomUrl, "share_tweet", null);});});
-
-
-
-    describe("#addSocialShareProvider", function () {
-      it("should invoke to the correct mozLoop function", function () {
-        var stub = sinon.stub();
-        LoopMochaUtils.stubLoopRequest({ 
-          AddSocialShareProvider: stub });
-
-
-        store.addSocialShareProvider(new sharedActions.AddSocialShareProvider());
-
-        sinon.assert.calledOnce(stub);});});
+        sinon.assert.calledWithMatch(requestStubs.OpenURL, fallback);});});
 
 
 
     describe("#setStoreState", function () {
       it("should update store state data", function () {
         store.setStoreState({ pendingCreation: true });
 
         expect(store.getStoreState().pendingCreation).eql(true);});
@@ -1033,43 +916,43 @@ describe("loop.store.RoomStore", functio
 
 
     it("should log telemetry event when sharing a room (copy link)", function () {
       store.copyRoomUrl(new sharedActions.CopyRoomUrl({ 
         roomUrl: "http://invalid", 
         from: "conversation" }));
 
 
-      sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
-      sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1), 
+      sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
+      sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"], 
       "LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);});
 
 
     it("should log telemetry event when sharing a room (email)", function () {
       store.emailRoomUrl(new sharedActions.EmailRoomUrl({ 
         roomUrl: "http://invalid", 
         from: "conversation" }));
 
 
-      sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
-      sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1), 
+      sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
+      sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"], 
       "LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);});
 
 
     it("should log telemetry event when sharing a room (facebook)", function () {
       store.facebookShareRoomUrl(new sharedActions.FacebookShareRoomUrl({ 
         roomUrl: "http://invalid", 
         from: "conversation" }));
 
 
-      sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
-      sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1), 
+      sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
+      sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"], 
       "LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_SHARE);});
 
 
     it("should log telemetry event when deleting a room", function () {
       store.deleteRoom(new sharedActions.DeleteRoom({ 
         roomToken: "42abc" }));
 
 
-      sinon.assert.calledTwice(requestStubs["TelemetryAddValue"]);
-      sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"].getCall(1), 
+      sinon.assert.calledOnce(requestStubs["TelemetryAddValue"]);
+      sinon.assert.calledWithExactly(requestStubs["TelemetryAddValue"], 
       "LOOP_ACTIVITY_COUNTER", store._constants.LOOP_MAU_TYPE.ROOM_DELETE);});});});
--- a/browser/extensions/loop/chrome/content/preferences/prefs.js
+++ b/browser/extensions/loop/chrome/content/preferences/prefs.js
@@ -1,12 +1,12 @@
 pref("loop.enabled", true);
 pref("loop.remote.autostart", true);
 #ifdef LOOP_DEV_XPI
-pref("loop.server", "https://loop-dev.stage.mozaws.net/v0");
+pref("loop.server", "https://loop.dev.mozaws.net/v0");
 pref("loop.linkClicker.url", "https://loop-webapp-dev.stage.mozaws.net/");
 #else
 pref("loop.server", "https://loop.services.mozilla.com/v0");
 pref("loop.linkClicker.url", "https://hello.firefox.com/");
 #endif
 pref("loop.gettingStarted.latestFTUVersion", 1);
 pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
 pref("loop.gettingStarted.resumeOnFirstJoin", false);
@@ -19,17 +19,16 @@ pref("loop.ping.interval", 1800000);
 pref("loop.ping.timeout", 10000);
 pref("loop.copy.showLimit", 3);
 pref("loop.copy.shown", false);
 pref("loop.copy.throttler", "copy.loop.services.mozilla.com");
 pref("loop.copy.ticket", -1);
 pref("loop.debug.loglevel", "Error");
 pref("loop.debug.dispatcher", false);
 pref("loop.debug.sdk", false);
-pref("loop.debug.twoWayMediaTelemetry", false);
 pref("loop.feedback.dateLastSeenSec", 0);
 pref("loop.feedback.periodSec", 15770000); // 6 months.
 pref("loop.feedback.formURL", "https://www.surveygizmo.com/s3/2651383/Firefox-Hello-Product-Survey-II?version=%APP_VERSION%");
 pref("loop.feedback.manualFormURL", "https://www.mozilla.org/firefox/hello/feedbacksurvey/");
 pref("loop.logDomains", false);
 pref("loop.mau.openPanel", 0);
 pref("loop.mau.openConversation", 0);
 pref("loop.mau.roomOpen", 0);
--- a/browser/extensions/loop/chrome/content/shared/css/common.css
+++ b/browser/extensions/loop/chrome/content/shared/css/common.css
@@ -58,20 +58,16 @@ p {
    * Force the display: none as it can conflict with other display.
    * You usually want to avoid !important statements as much as
    * possible. In this case, it makes sense as it's unlikely we want a
    * class to undo the hide feature.
    */
   display: none !important;
 }
 
-.visually-hidden {
-  visibility: hidden;
-}
-
 .tc {
   text-align: center;
 }
 
 .full-width {
   width: 100%;
 }
 
@@ -461,23 +457,16 @@ html[dir="rtl"] .dropdown-menu {
   border-bottom-right-radius: 2px;
   border-bottom-left-radius: 2px;
 }
 
 .dropdown-menu-item:hover {
   background-color: #dbf7ff;
 }
 
-.dropdown-menu-separator {
-  height: 1px;
-  margin: 2px -2px 1px -2px;
-  border-top: 1px solid #dedede;
-  background-color: #fff;
-}
-
 /* Custom checkbox */
 
 .checkbox-wrapper {
   -moz-user-select: none;
   user-select: none;
 }
 
 .checkbox {
--- a/browser/extensions/loop/chrome/content/shared/js/actions.js
+++ b/browser/extensions/loop/chrome/content/shared/js/actions.js
@@ -413,34 +413,16 @@ loop.shared.actions = function () {
      */
     FacebookShareRoomUrl: Action.define("facebookShareRoomUrl", { 
       from: String, 
       roomUrl: String
       // roomOrigin: String
     }), 
 
     /**
-     * Share a room url via the Social API.
-     * XXX: should move to some roomActions module - refs bug 1079284
-     * @provider: one of the share-capable Social Providers included
-     * @roomUrl: the URL that is shared
-     */
-    ShareRoomUrl: Action.define("shareRoomUrl", { 
-      provider: Object, 
-      roomUrl: String }), 
-
-
-    /**
-     * Open the share panel to add a Social share provider.
-     * XXX: should move to some roomActions module - refs bug 1079284
-     */
-    AddSocialShareProvider: Action.define("addSocialShareProvider", {}), 
-
-
-    /**
      * XXX: should move to some roomActions module - refs bug 1079284
      */
     RoomFailure: Action.define("roomFailure", { 
       error: Object, 
       // True when the failures occurs in the join room request to the loop-server.
       failedJoinRequest: Boolean }), 
 
 
@@ -453,36 +435,27 @@ loop.shared.actions = function () {
     UpdateRoomInfo: Action.define("updateRoomInfo", { 
       // participants: Array - Optional.
       // roomContextUrls: Array - Optional.
       // See https://wiki.mozilla.org/Loop/Architecture/Context#Format_of_context.value
       // roomDescription: String - Optional.
       // roomInfoFailure: String - Optional.
       // roomName: String - Optional.
       // roomState: String - Optional.
-      roomUrl: String
-      // socialShareProviders: Array - Optional.
-    }), 
+      roomUrl: String }), 
+
 
     /**
      * Notifies if the user agent will handle the room or not.
      */
     UserAgentHandlesRoom: Action.define("userAgentHandlesRoom", { 
       handlesRoom: Boolean }), 
 
 
     /**
-     * Updates the Social API information when it is received.
-     * XXX: should move to some roomActions module - refs bug 1079284
-     */
-    UpdateSocialShareInfo: Action.define("updateSocialShareInfo", { 
-      socialShareProviders: Array }), 
-
-
-    /**
      * Starts the process for the user to join the room.
      * XXX: should move to some roomActions module - refs bug 1079284
      */
     JoinRoom: Action.define("joinRoom", {}), 
 
 
     /**
      * A special action for metrics logging to define what type of join
--- a/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
+++ b/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
@@ -50,18 +50,17 @@ loop.store.ActiveRoomStore = function (m
   var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
 
   var OPTIONAL_ROOMINFO_FIELDS = { 
     participants: "participants", 
     roomContextUrls: "roomContextUrls", 
     roomDescription: "roomDescription", 
     roomInfoFailure: "roomInfoFailure", 
     roomName: "roomName", 
-    roomState: "roomState", 
-    socialShareProviders: "socialShareProviders" };
+    roomState: "roomState" };
 
 
   var updateContextTimer = null;
 
   /**
    * Active room store.
    *
    * @param {loop.Dispatcher} dispatcher  The dispatcher for dispatching actions
@@ -153,18 +152,16 @@ loop.store.ActiveRoomStore = function (m
         // The description for a room as stored in the context data.
         roomDescription: null, 
         // Room information failed to be obtained for a reason. See ROOM_INFO_FAILURES.
         roomInfoFailure: null, 
         // The name of the room.
         roomName: null, 
         // True when sharing screen has been paused.
         streamPaused: false, 
-        // Social API state.
-        socialShareProviders: null, 
         // True if media has been connected both-ways.
         mediaConnected: false, 
         // True if a chat message was sent or received during a session.
         // Read more at https://wiki.mozilla.org/Loop/Session.
         chatMessageExchanged: false };}, 
 
 
 
@@ -268,38 +265,35 @@ loop.store.ActiveRoomStore = function (m
       "feedbackComplete", 
       "mediaStreamCreated", 
       "mediaStreamDestroyed", 
       "remoteVideoStatus", 
       "videoDimensionsChanged", 
       "startBrowserShare", 
       "endScreenShare", 
       "toggleBrowserSharing", 
-      "updateSocialShareInfo", 
       "connectionStatus", 
       "mediaConnected", 
       "videoScreenStreamChanged"];
 
       // Register actions that are only used on Desktop.
       if (this._isDesktop) {
         // 'receivedTextChatMessage' and  'sendTextChatMessage' actions are only
         // registered for Telemetry. Once measured, they're unregistered.
         actions.push("receivedTextChatMessage", "sendTextChatMessage");}
 
       this.dispatcher.register(this, actions);
 
       this._onUpdateListener = this._handleRoomUpdate.bind(this);
       this._onDeleteListener = this._handleRoomDelete.bind(this);
-      this._onSocialShareUpdate = this._handleSocialShareUpdate.bind(this);
 
       var roomToken = this._storeState.roomToken;
       loop.request("Rooms:PushSubscription", ["delete:" + roomToken, "update:" + roomToken]);
       loop.subscribe("Rooms:Delete:" + roomToken, this._handleRoomDelete.bind(this));
-      loop.subscribe("Rooms:Update:" + roomToken, this._handleRoomUpdate.bind(this));
-      loop.subscribe("SocialProvidersChanged", this._onSocialShareUpdate);}, 
+      loop.subscribe("Rooms:Update:" + roomToken, this._handleRoomUpdate.bind(this));}, 
 
 
     /**
      * Execute setupWindowData event action from the dispatcher. This gets
      * the room data from the Loop API, and dispatches an UpdateRoomInfo event.
      * It also dispatches JoinRoom as this action is only applicable to the desktop
      * client, and needs to auto-join.
      *
@@ -315,39 +309,34 @@ loop.store.ActiveRoomStore = function (m
         roomState: ROOM_STATES.GATHER, 
         roomToken: actionData.roomToken, 
         windowId: actionData.windowId });
 
 
       this._registerPostSetupActions();
 
       // Get the window data from the Loop API.
-      return loop.requestMulti(
-      ["Rooms:Get", actionData.roomToken], 
-      ["GetSocialShareProviders"]).
-      then(function (results) {
-        var room = results[0];
-        var socialShareProviders = results[1];
+      return loop.request("Rooms:Get", actionData.roomToken).then(function (result) {
+        var room = result;
 
-        if (room.isError) {
+        if (result.isError) {
           this.dispatchAction(new sharedActions.RoomFailure({ 
-            error: room, 
+            error: result, 
             failedJoinRequest: false }));
 
           return;}
 
 
         this.dispatchAction(new sharedActions.UpdateRoomInfo({ 
           participants: room.participants, 
           roomContextUrls: room.decryptedContext.urls, 
           roomDescription: room.decryptedContext.description, 
           roomName: room.decryptedContext.roomName, 
           roomState: ROOM_STATES.READY, 
-          roomUrl: room.roomUrl, 
-          socialShareProviders: socialShareProviders }));
+          roomUrl: room.roomUrl }));
 
 
         // For the conversation window, we need to automatically join the room.
         this.dispatchAction(new sharedActions.JoinRoom());}.
       bind(this));}, 
 
 
     /**
@@ -544,28 +533,16 @@ loop.store.ActiveRoomStore = function (m
      */
     userAgentHandlesRoom: function userAgentHandlesRoom(actionData) {
       this.setStoreState({ 
         userAgentHandlesRoom: actionData.handlesRoom });}, 
 
 
 
     /**
-     * Handles the updateSocialShareInfo action. Updates the room data with new
-     * Social API info.
-     *
-     * @param  {sharedActions.UpdateSocialShareInfo} actionData
-     */
-    updateSocialShareInfo: function updateSocialShareInfo(actionData) {
-      this.setStoreState({ 
-        socialShareProviders: actionData.socialShareProviders });}, 
-
-
-
-    /**
      * Handles room updates notified by the Loop rooms API.
      *
      * @param {Object} roomData  The new roomData.
      */
     _handleRoomUpdate: function _handleRoomUpdate(roomData) {
       this.dispatchAction(new sharedActions.UpdateRoomInfo({ 
         roomContextUrls: roomData.decryptedContext.urls, 
         roomDescription: roomData.decryptedContext.description, 
@@ -581,28 +558,16 @@ loop.store.ActiveRoomStore = function (m
      */
     _handleRoomDelete: function _handleRoomDelete() {
       this._sdkDriver.forceDisconnectAll(function () {
         window.close();});}, 
 
 
 
     /**
-     * Handles an update of the position of the Share widget and changes to list
-     * of Social API providers, notified by the Loop API.
-     */
-    _handleSocialShareUpdate: function _handleSocialShareUpdate() {
-      loop.request("GetSocialShareProviders").then(function (result) {
-        this.dispatchAction(new sharedActions.UpdateSocialShareInfo({ 
-          socialShareProviders: result }));}.
-
-      bind(this));}, 
-
-
-    /**
      * Checks that there are audio and video devices available, and joins the
      * room if there are. If there aren't then it will dispatch a ConnectionFailure
      * action with NO_MEDIA.
      */
     _checkDevicesAndJoinRoom: function _checkDevicesAndJoinRoom() {
       // XXX Ideally we'd do this check before joining a room, but we're waiting
       // for the UX for that. See bug 1166824. In the meantime this gives us
       // additional information for analysis.
@@ -746,19 +711,16 @@ loop.store.ActiveRoomStore = function (m
         apiKey: actionData.apiKey, 
         sessionToken: actionData.sessionToken, 
         sessionId: actionData.sessionId, 
         roomState: ROOM_STATES.JOINED });
 
 
       this._setRefreshTimeout(actionData.expires);
 
-      // Only send media telemetry on one side of the call: the desktop side.
-      actionData.sendTwoWayMediaTelemetry = this._isDesktop;
-
       this._sdkDriver.connectSession(actionData);
 
       loop.request("AddConversationContext", this._storeState.windowId, 
       actionData.sessionId, "");}, 
 
 
     /**
      * Handles recording when the sdk has connected to the servers.
@@ -1156,20 +1118,16 @@ loop.store.ActiveRoomStore = function (m
         // If the user agent is handling the room, all we need to do is advance
         // to the next state.
         this.setStoreState({ 
           roomState: nextState });
 
         return;}
 
 
-      if (loop.standaloneMedia) {
-        loop.standaloneMedia.multiplexGum.reset();}
-
-
       if (this._browserSharingListener) {
         // Remove the browser sharing listener as we don't need it now.
         loop.unsubscribe("BrowserSwitch", this._browserSharingListener);
         this._browserSharingListener = null;}
 
 
       // We probably don't need to end screen share separately, but lets be safe.
       this._sdkDriver.disconnectSession();
@@ -1258,20 +1216,18 @@ loop.store.ActiveRoomStore = function (m
       actionData.contentType !== CHAT_CONTENT_TYPES.TEXT) {
         return;}
 
 
       this.setStoreState({ chatMessageExchanged: true });
       // There's no need to listen to these actions anymore.
       this.dispatcher.unregister(this, [
       "receivedTextChatMessage", 
-      "sendTextChatMessage"]);
+      "sendTextChatMessage"]);}, 
 
-      // Ping telemetry of this session with successful message(s) exchange.
-      loop.request("TelemetryAddValue", "LOOP_ROOM_SESSION_WITHCHAT", 1);}, 
 
 
     /**
      * Handles received text chat messages. For telemetry purposes only.
      *
      * @param {sharedActions.ReceivedTextChatMessage} actionData
      */
     receivedTextChatMessage: function receivedTextChatMessage(actionData) {
--- a/browser/extensions/loop/chrome/content/shared/js/loopapi-client.js
+++ b/browser/extensions/loop/chrome/content/shared/js/loopapi-client.js
@@ -73,17 +73,19 @@ var loop = loop || {};
 
       gListenersMap[seq] = resolve;
 
       gRootObj.sendAsyncMessage(kMessageName, payload);});};
 
 
 
   // These functions should only be used in unit tests.
-  loop.request.inspect = function () {return _.extend({}, gListenersMap);};
+  loop.request.inspect = function () {
+    return _.extend({}, gListenersMap);};
+
   loop.request.reset = function () {
     gListeningForMessages = false;
     gListenersMap = {};};
 
 
   loop.storedRequests = {};
 
   /**
@@ -182,17 +184,19 @@ var loop = loop || {};
 
     if (!gSubscriptionsMap[name]) {
       gSubscriptionsMap[name] = [];}
 
     gSubscriptionsMap[name].push(callback);};
 
 
   // These functions should only be used in unit tests.
-  loop.subscribe.inspect = function () {return _.extend({}, gSubscriptionsMap);};
+  loop.subscribe.inspect = function () {
+    return _.extend({}, gSubscriptionsMap);};
+
   loop.subscribe.reset = function () {
     gListeningForPushMessages = false;
     gSubscriptionsMap = {};};
 
 
   /**
    * Cancel a subscription to a specific push message.
    *
--- a/browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js
+++ b/browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js
@@ -40,25 +40,16 @@ loop.OTSdkDriver = function () {
     this._resetMetrics();
 
     this.dispatcher.register(this, [
     "setupStreamElements", 
     "setMute", 
     "toggleBrowserSharing"]);
 
 
-    // Set loop.debug.twoWayMediaTelemetry to true in the browser
-    // by changing the hidden pref loop.debug.twoWayMediaTelemetry using
-    // about:config, or use
-    //
-    // localStorage.setItem("debug.twoWayMediaTelemetry", true);
-    loop.shared.utils.getBoolPreference("debug.twoWayMediaTelemetry", function (enabled) {
-      this._debugTwoWayMediaTelemetry = enabled;}.
-    bind(this));
-
     // Set loop.debug.sdk to true in the browser, or in standalone:
     // localStorage.setItem("debug.sdk", true);
     loop.shared.utils.getBoolPreference("debug.sdk", function (enabled) {
       // We don't bother with the else case - as we only create one instance of
       // OTSdkDriver per window, and hence, we leave the sdk set to its default
       // value.
       if (enabled) {
         this.sdk.setLogLevel(this.sdk.DEBUG);}}.
@@ -255,30 +246,22 @@ loop.OTSdkDriver = function () {
 
     /**
      * Connects a session for the SDK, listening to the required events.
      *
      * sessionData items:
      * - sessionId: The OT session ID
      * - apiKey: The OT API key
      * - sessionToken: The token for the OT session
-     * - sendTwoWayMediaTelemetry: boolean should we send telemetry on length
-     *                             of media sessions.  Callers should ensure
-     *                             that this is only set for one side of the
-     *                             session so that things don't get
-     *                             double-counted.
      *
      * @param {Object} sessionData The session data for setting up the OT session.
      */
     connectSession: function connectSession(sessionData) {
       this.session = this.sdk.initSession(sessionData.sessionId);
 
-      this._sendTwoWayMediaTelemetry = !!sessionData.sendTwoWayMediaTelemetry;
-      this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_UNINITIALIZED);
-
       this.session.on("sessionDisconnected", 
       this._onSessionDisconnected.bind(this));
       this.session.on("connectionCreated", this._onConnectionCreated.bind(this));
       this.session.on("connectionDestroyed", 
       this._onConnectionDestroyed.bind(this));
       this.session.on("streamCreated", this._onRemoteStreamCreated.bind(this));
       this.session.on("streamDestroyed", this._onRemoteStreamDestroyed.bind(this));
       this.session.on("streamPropertyChanged", this._onStreamPropertyChanged.bind(this));
@@ -319,28 +302,25 @@ loop.OTSdkDriver = function () {
         "streamCreated streamDestroyed");
         this.publisher.destroy();
         delete this.publisher;}
 
 
       // Now reset the metrics as well.
       this._resetMetrics();
 
-      this._noteConnectionLengthIfNeeded(this._getTwoWayMediaStartTime(), performance.now());
-
       // Also, tidy these variables ready for next time.
       delete this._sessionConnected;
       delete this._publisherReady;
       delete this._publishedLocalStream;
       delete this._subscribedRemoteStream;
       delete this._mockPublisherEl;
       delete this._publisherChannel;
       delete this._subscriberChannel;
-      this.connections = {};
-      this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_UNINITIALIZED);}, 
+      this.connections = {};}, 
 
 
     /**
      * Oust all users from an ongoing session. This is typically done when a room
      * owner deletes the room.
      *
      * @param {Function} callback Function to be invoked once all connections are
      *                            ousted
@@ -402,18 +382,16 @@ loop.OTSdkDriver = function () {
     _onConnectionDestroyed: function _onConnectionDestroyed(event) {
       var connection = event.connection;
       if (connection && connection.id in this.connections) {
         delete this.connections[connection.id];}
 
 
       this._notifyMetricsEvent("Session.connectionDestroyed", "peer");
 
-      this._noteConnectionLengthIfNeeded(this._getTwoWayMediaStartTime(), performance.now());
-
       this.dispatcher.dispatch(new sharedActions.RemotePeerDisconnected({ 
         peerHungup: event.reason === "clientDisconnected" }));}, 
 
 
 
     /**
      * Handles the session event for the connection for this client being
      * destroyed.
@@ -430,18 +408,16 @@ loop.OTSdkDriver = function () {
         case "forceDisconnected":
           reason = FAILURE_DETAILS.EXPIRED_OR_INVALID;
           break;
         default:
           // Other cases don't need to be handled.
           return;}
 
 
-      this._noteConnectionLengthIfNeeded(this._getTwoWayMediaStartTime(), 
-      performance.now());
       this._notifyMetricsEvent("Session." + event.reason);
       this.dispatcher.dispatch(new sharedActions.ConnectionFailure({ 
         reason: reason }));}, 
 
 
 
     /**
      * Handles the connection event for a newly connecting peer.
@@ -521,16 +497,18 @@ loop.OTSdkDriver = function () {
         case "Session.streamCreated":
           this._metrics.recvStreams++;
           break;
         case "Session.streamDestroyed":
           this._metrics.recvStreams--;
           break;
         case "Session.networkDisconnected":
         case "Session.forceDisconnected":
+        case "Session.subscribeCompleted":
+        case "Session.screen.subscribeCompleted":
           break;
         default:
           // We don't want unexpected events being sent to the server, so
           // filter out the unexpected, and let the known ones through.
           if (!/^sdk\.(exception|datachannel)/.test(eventName)) {
             console.error("Unexpected event name", eventName);
             return;}}
 
@@ -641,19 +619,19 @@ loop.OTSdkDriver = function () {
 
       this.dispatcher.dispatch(new sharedActions.MediaStreamCreated({ 
         hasAudio: sdkSubscriberObject.stream[STREAM_PROPERTIES.HAS_AUDIO], 
         hasVideo: sdkSubscriberObject.stream[STREAM_PROPERTIES.HAS_VIDEO], 
         isLocal: false, 
         srcMediaElement: sdkSubscriberVideo }));
 
 
+      this._notifyMetricsEvent("Session.subscribeCompleted");
       this._subscribedRemoteStream = true;
       if (this._checkAllStreamsConnected()) {
-        this._setTwoWayMediaStartTime(performance.now());
         this.dispatcher.dispatch(new sharedActions.MediaConnected());}
 
 
       this._setupDataChannelIfNeeded(sdkSubscriberObject);}, 
 
 
     /**
      * This method is passed as the "completionHandler" parameter to the SDK's
@@ -672,19 +650,20 @@ loop.OTSdkDriver = function () {
 
       var sdkSubscriberVideo = subscriberVideo ? subscriberVideo : 
       this._mockScreenShareEl.querySelector("video");
 
       // XXX no idea why this is necessary in addition to the dispatch in
       // _handleRemoteScreenShareCreated.  Maybe these should be separate
       // actions.  But even so, this shouldn't be necessary....
       this.dispatcher.dispatch(new sharedActions.ReceivingScreenShare({ 
-        receiving: true, srcMediaElement: sdkSubscriberVideo }));}, 
+        receiving: true, srcMediaElement: sdkSubscriberVideo }));
 
 
+      this._notifyMetricsEvent("Session.screen.subscribeCompleted");}, 
 
 
     /**
      * Once a remote stream has been subscribed to, this triggers the data
      * channel set-up routines. A data channel cannot be requested before this
      * time as the peer connection is not set up.
      *
      * @param {OT.Subscriber} sdkSubscriberObject The subscriber object for the stream.
@@ -869,67 +848,16 @@ loop.OTSdkDriver = function () {
           isLocal: true, 
           videoType: event.stream.videoType, 
           dimensions: event.stream[STREAM_PROPERTIES.VIDEO_DIMENSIONS] }));}}, 
 
 
 
 
     /**
-     * Implementation detail, may be set to one of the CONNECTION_START_TIME
-     * constants, or a positive integer in milliseconds.
-     *
-     * @private
-     */
-    __twoWayMediaStartTime: undefined, 
-
-    /**
-     * Used as a guard to make sure we don't inadvertently use an
-     * uninitialized value.
-     */
-    CONNECTION_START_TIME_UNINITIALIZED: -1, 
-
-    /**
-     * Use as a guard to ensure that we don't note any bidirectional sessions
-     * twice.
-     */
-    CONNECTION_START_TIME_ALREADY_NOTED: -2, 
-
-    /**
-     * Set and get the start time of the two-way media connection.  These
-     * are done as wrapper functions so that we can log sets to make manual
-     * verification of various telemetry scenarios possible.  The get API is
-     * analogous in order to follow the principle of least surprise for
-     * people consuming this code.
-     *
-     * If this._sendTwoWayMediaTelemetry is not true, returns immediately
-     * without making any changes, since this data is not used, and it makes
-     * reading the logs confusing for manual verification of both ends of the
-     * call in the same browser, which is a case we care about.
-     *
-     * @param start  start time in milliseconds, as returned by
-     *               performance.now()
-     * @private
-     */
-    _setTwoWayMediaStartTime: function _setTwoWayMediaStartTime(start) {
-      if (!this._sendTwoWayMediaTelemetry) {
-        return;}
-
-
-      this.__twoWayMediaStartTime = start;
-      if (this._debugTwoWayMediaTelemetry) {
-        console.log("Loop Telemetry: noted two-way connection start, " + 
-        "start time in ms:", start);}}, 
-
-
-    _getTwoWayMediaStartTime: function _getTwoWayMediaStartTime() {
-      return this.__twoWayMediaStartTime;}, 
-
-
-    /**
      * Handles the event when the remote stream is destroyed.
      *
      * @param {StreamEvent} event The event details:
      * https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
      */
     _onRemoteStreamDestroyed: function _onRemoteStreamDestroyed(event) {
       this._notifyMetricsEvent("Session.streamDestroyed");
 
@@ -1045,45 +973,46 @@ loop.OTSdkDriver = function () {
 
 
     /**
      * Handles exceptions being raised by the OT SDK.
      *
      * @param  {OT.Event} event
      */
     _onOTException: function _onOTException(event) {
+      var baseException = "sdk.exception.";
+      if (event.target && event.target === this.screenshare) {
+        baseException += "screen.";}
+
+
       switch (event.code) {
         case OT.ExceptionCodes.PUBLISHER_ICE_WORKFLOW_FAILED:
         case OT.ExceptionCodes.SUBSCRIBER_ICE_WORKFLOW_FAILED:
           this.dispatcher.dispatch(new sharedActions.ConnectionFailure({ 
             reason: FAILURE_DETAILS.ICE_FAILED }));
 
-          this._notifyMetricsEvent("sdk.exception." + event.code);
+          this._notifyMetricsEvent(baseException + event.code);
           break;
         case OT.ExceptionCodes.TERMS_OF_SERVICE_FAILURE:
           this.dispatcher.dispatch(new sharedActions.ConnectionFailure({ 
             reason: FAILURE_DETAILS.TOS_FAILURE }));
 
           // We still need to log the exception so that the server knows why this
           // attempt failed.
-          this._notifyMetricsEvent("sdk.exception." + event.code);
+          this._notifyMetricsEvent(baseException + event.code);
           break;
         case OT.ExceptionCodes.UNABLE_TO_PUBLISH:
           // Don't report errors for GetUserMedia events as these are expected if
           // the user denies the prompt.
           if (event.message !== "GetUserMedia") {
-            var baseException = "sdk.exception.";
-            if (event.target && event.target === this.screenshare) {
-              baseException += "screen.";}
-
             this._notifyMetricsEvent(baseException + event.code + "." + event.message);}
 
           break;
         default:
-          this._notifyMetricsEvent("sdk.exception." + event.code);
+          this._notifyMetricsEvent(baseException + event.code);
           break;}}, 
 
 
 
     /**
      * Handles publishing of property changes to a stream.
      */
     _onStreamPropertyChanged: function _onStreamPropertyChanged(event) {
@@ -1145,17 +1074,16 @@ loop.OTSdkDriver = function () {
     _maybePublishLocalStream: function _maybePublishLocalStream() {
       if (this._sessionConnected && this._publisherReady) {
         // We are clear to publish the stream to the session.
         this.session.publish(this.publisher);
 
         // Now record the fact, and check if we've got all media yet.
         this._publishedLocalStream = true;
         if (this._checkAllStreamsConnected()) {
-          this._setTwoWayMediaStartTime(performance.now());
           this.dispatcher.dispatch(new sharedActions.MediaConnected());}}}, 
 
 
 
 
     /**
      * Used to check if both local and remote streams are available
      * and send an action if they are.
@@ -1210,97 +1138,13 @@ loop.OTSdkDriver = function () {
       delete this.screenshare;
       delete this._mockScreenSharePreviewEl;}, 
 
 
     /**
      * Called when a screenshare stream is published.
      */
     _onScreenShareStreamCreated: function _onScreenShareStreamCreated() {
-      this._notifyMetricsEvent("Publisher.streamCreated");}, 
-
-
-    /*
-     * XXX all of the bi-directional media connection telemetry stuff in this
-     * file, (much, but not all, of it is below) should be hoisted into its
-     * own object for maintainability and clarity, also in part because this
-     * stuff only wants to run one side of the connection, not both (tracked
-     * by bug 1145237).
-     */
-
-    /**
-     * A hook exposed only for the use of the functional tests so that
-     * they can check that the bi-directional media count is being updated
-     * correctly.
-     *
-     * @type number
-     * @private
-     */
-    _connectionLengthNotedCalls: 0, 
-
-    /**
-     * Wrapper for adding a keyed value that also updates
-     * connectionLengthNoted calls and sets the twoWayMediaStartTime to
-     * this.CONNECTION_START_TIME_ALREADY_NOTED.
-     *
-     * @param {number} callLengthSeconds  the call length in seconds
-     * @private
-     */
-    _noteConnectionLength: function _noteConnectionLength(callLengthSeconds) {
-      var buckets = this._constants.TWO_WAY_MEDIA_CONN_LENGTH;
-
-      var bucket = buckets.SHORTER_THAN_10S;
-      if (callLengthSeconds >= 10 && callLengthSeconds <= 30) {
-        bucket = buckets.BETWEEN_10S_AND_30S;} else 
-      if (callLengthSeconds > 30 && callLengthSeconds <= 300) {
-        bucket = buckets.BETWEEN_30S_AND_5M;} else 
-      if (callLengthSeconds > 300) {
-        bucket = buckets.MORE_THAN_5M;}
-
-
-      loop.request("TelemetryAddValue", "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", bucket);
-      this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_ALREADY_NOTED);
-
-      this._connectionLengthNotedCalls++;
-      if (this._debugTwoWayMediaTelemetry) {
-        console.log("Loop Telemetry: noted two-way media connection " + 
-        "in bucket: ", bucket);}}, 
+      this._notifyMetricsEvent("Publisher.streamCreated");} };
 
 
 
-    /**
-     * Note connection length if it's valid (the startTime has been initialized
-     * and is not later than endTime) and not yet already noted.  If
-     * this._sendTwoWayMediaTelemetry is not true, we return immediately.
-     *
-     * @param {number} startTime  in milliseconds
-     * @param {number} endTime  in milliseconds
-     * @private
-     */
-    _noteConnectionLengthIfNeeded: function _noteConnectionLengthIfNeeded(startTime, endTime) {
-      if (!this._sendTwoWayMediaTelemetry) {
-        return;}
-
-
-      if (startTime === this.CONNECTION_START_TIME_ALREADY_NOTED || 
-      startTime === this.CONNECTION_START_TIME_UNINITIALIZED || 
-      startTime > endTime) {
-        if (this._debugTwoWayMediaTelemetry) {
-          console.log("_noteConnectionLengthIfNeeded called with " + 
-          " invalid params, either the calls were never" + 
-          " connected or there is a bug; startTime:", startTime, 
-          "endTime:", endTime);}
-
-        return;}
-
-
-      var callLengthSeconds = (endTime - startTime) / 1000;
-      this._noteConnectionLength(callLengthSeconds);}, 
-
-
-    /**
-     * If set to true, make it easy to test/verify 2-way media connection
-     * telemetry code operation by viewing the logs.
-     */
-    _debugTwoWayMediaTelemetry: false };
-
-
   return OTSdkDriver;}();
--- a/browser/extensions/loop/chrome/content/shared/js/textChatView.js
+++ b/browser/extensions/loop/chrome/content/shared/js/textChatView.js
@@ -212,17 +212,20 @@ loop.shared.views.chat = function (mozL1
         React.createElement("div", { className: "text-chat-scroller" }, 
 
         loop.shared.utils.isDesktop() ? null : 
         React.createElement(TextChatHeader, { chatHeaderName: headerName }), 
 
 
         this.props.messageList.map(function (entry, i) {
           if (entry.type === CHAT_MESSAGE_TYPES.SPECIAL) {
-            if (!this.props.showInitialContext) {return null;}
+            if (!this.props.showInitialContext) {
+              return null;}
+
+
             switch (entry.contentType) {
               case CHAT_CONTENT_TYPES.CONTEXT:
                 return (
                   React.createElement("div", { className: "context-url-view-wrapper", key: i }, 
                   React.createElement(sharedViews.ContextUrlView, { 
                     allowClick: true, 
                     description: entry.message, 
                     dispatcher: this.props.dispatcher, 
--- a/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
@@ -8,18 +8,17 @@ describe("loop.store.ActiveRoomStore", f
   var sharedActions = loop.shared.actions;
   var sharedUtils = loop.shared.utils;
   var REST_ERRNOS = loop.shared.utils.REST_ERRNOS;
   var ROOM_STATES = loop.store.ROOM_STATES;
   var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
   var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
   var ROOM_INFO_FAILURES = loop.shared.utils.ROOM_INFO_FAILURES;
-  var sandbox, dispatcher, store, requestStubs, fakeSdkDriver, fakeMultiplexGum;
-  var standaloneMediaRestore;
+  var sandbox, dispatcher, store, requestStubs, fakeSdkDriver;
   var clock;
 
   beforeEach(function () {
     sandbox = LoopMochaUtils.createSandbox();
     clock = sandbox.useFakeTimers();
 
     LoopMochaUtils.stubLoopRequest(requestStubs = { 
       GetLoopPref: sinon.stub(), 
@@ -33,17 +32,16 @@ describe("loop.store.ActiveRoomStore", f
         roomUrl: "http://invalid" }), 
 
       "Rooms:Join": sinon.stub().returns({}), 
       "Rooms:RefreshMembership": sinon.stub().returns({ expires: 42 }), 
       "Rooms:SendConnectionStatus": sinon.stub(), 
       "Rooms:PushSubscription": sinon.stub(), 
       SetScreenShareState: sinon.stub(), 
       GetActiveTabWindowId: sandbox.stub().returns(42), 
-      GetSocialShareProviders: sinon.stub().returns([]), 
       TelemetryAddValue: sinon.stub() });
 
 
     dispatcher = new loop.Dispatcher();
     sandbox.stub(dispatcher, "dispatch");
     sandbox.stub(window, "close");
 
     fakeSdkDriver = { 
@@ -51,38 +49,28 @@ describe("loop.store.ActiveRoomStore", f
       disconnectSession: sinon.stub(), 
       forceDisconnectAll: sinon.stub().callsArg(0), 
       retryPublishWithoutVideo: sinon.stub(), 
       startScreenShare: sinon.stub(), 
       switchAcquiredWindow: sinon.stub(), 
       endScreenShare: sinon.stub().returns(true) };
 
 
-    fakeMultiplexGum = { 
-      reset: sandbox.spy() };
-
-
-    standaloneMediaRestore = loop.standaloneMedia;
-    loop.standaloneMedia = { 
-      multiplexGum: fakeMultiplexGum };
-
-
     store = new loop.store.ActiveRoomStore(dispatcher, { 
       sdkDriver: fakeSdkDriver });
 
 
     sandbox.stub(document.mozL10n ? document.mozL10n : navigator.mozL10n, "get", function (x) {
       return x;});});
 
 
 
   afterEach(function () {
     sandbox.restore();
-    LoopMochaUtils.restore();
-    loop.standaloneMedia = standaloneMediaRestore;});
+    LoopMochaUtils.restore();});
 
 
   describe("#constructor", function () {
     it("should throw an error if sdkDriver is missing", function () {
       expect(function () {
         new loop.store.ActiveRoomStore(dispatcher, { mozLoop: {} });}).
       to.Throw(/sdkDriver/);});});
 
@@ -177,25 +165,16 @@ describe("loop.store.ActiveRoomStore", f
         error: fakeError, 
         failedJoinRequest: false }));
 
 
       expect(store._storeState.roomState).eql(ROOM_STATES.FAILED);
       expect(store._storeState.failureReason).eql(FAILURE_DETAILS.EXPIRED_OR_INVALID);});
 
 
-    it("should reset the multiplexGum", function () {
-      store.roomFailure(new sharedActions.RoomFailure({ 
-        error: fakeError, 
-        failedJoinRequest: false }));
-
-
-      sinon.assert.calledOnce(fakeMultiplexGum.reset);});
-
-
     it("should disconnect from the servers via the sdk", function () {
       store.roomFailure(new sharedActions.RoomFailure({ 
         error: fakeError, 
         failedJoinRequest: false }));
 
 
       sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);});
 
@@ -365,18 +344,17 @@ describe("loop.store.ActiveRoomStore", f
         sinon.assert.calledTwice(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch, 
         new sharedActions.UpdateRoomInfo({ 
           roomContextUrls: undefined, 
           roomDescription: undefined, 
           participants: [], 
           roomName: fakeRoomData.decryptedContext.roomName, 
           roomState: ROOM_STATES.READY, 
-          roomUrl: fakeRoomData.roomUrl, 
-          socialShareProviders: [] }));});});
+          roomUrl: fakeRoomData.roomUrl }));});});
 
 
 
 
     it("should dispatch a JoinRoom action if the get is successful", 
     function () {
       return store.setupWindowData(new sharedActions.SetupWindowData({ 
         windowId: "42", 
@@ -801,38 +779,16 @@ describe("loop.store.ActiveRoomStore", f
       store.userAgentHandlesRoom(new sharedActions.UserAgentHandlesRoom({ 
         handlesRoom: true }));
 
 
       expect(store.getStoreState().userAgentHandlesRoom).eql(true);});});
 
 
 
-  describe("#updateSocialShareInfo", function () {
-    var fakeSocialShareInfo;
-
-    beforeEach(function () {
-      fakeSocialShareInfo = { 
-        socialShareProviders: [{ 
-          name: "foo", 
-          origin: "https://example.com", 
-          iconURL: "icon.png" }] };});
-
-
-
-
-    it("should save the Social API information", function () {
-      store.updateSocialShareInfo(new sharedActions.UpdateSocialShareInfo(fakeSocialShareInfo));
-
-      var state = store.getStoreState();
-      expect(state.socialShareProviders).
-      eql(fakeSocialShareInfo.socialShareProviders);});});
-
-
-
   describe("#joinRoom", function () {
     var hasDevicesStub;
 
     beforeEach(function () {
       store.setStoreState({ roomState: ROOM_STATES.READY });
       hasDevicesStub = sandbox.stub(loop.shared.utils, "hasAudioOrVideoDevices");});
 
 
@@ -1123,38 +1079,16 @@ describe("loop.store.ActiveRoomStore", f
 
       store.joinedRoom(actionData);
 
       sinon.assert.calledOnce(fakeSdkDriver.connectSession);
       sinon.assert.calledWithExactly(fakeSdkDriver.connectSession, 
       actionData);});
 
 
-    it("should pass 'sendTwoWayMediaTelemetry' as true to connectSession if " + 
-    "store._isDesktop is true", function () {
-      store._isDesktop = true;
-
-      store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
-
-      sinon.assert.calledOnce(fakeSdkDriver.connectSession);
-      sinon.assert.calledWithMatch(fakeSdkDriver.connectSession, 
-      sinon.match.has("sendTwoWayMediaTelemetry", true));});
-
-
-    it("should pass 'sendTwoWayTelemetry' as false to connectionSession if " + 
-    "store._isDesktop is false", function () {
-      store._isDesktop = false;
-
-      store.joinedRoom(new sharedActions.JoinedRoom(fakeJoinedData));
-
-      sinon.assert.calledOnce(fakeSdkDriver.connectSession);
-      sinon.assert.calledWithMatch(fakeSdkDriver.connectSession, 
-      sinon.match.has("sendTwoWayMediaTelemetry", false));});
-
-
     it("should call LoopAPI.AddConversationContext", function () {
       var actionData = new sharedActions.JoinedRoom(fakeJoinedData);
 
       return store.setupWindowData(new sharedActions.SetupWindowData({ 
         windowId: "42", 
         type: "room" })).
       then(function () {
         store.joinedRoom(actionData);
@@ -1240,22 +1174,16 @@ describe("loop.store.ActiveRoomStore", f
 
 
     it("should store the failure reason", function () {
       store.connectionFailure(connectionFailureAction);
 
       expect(store.getStoreState().failureReason).eql("FAIL");});
 
 
-    it("should reset the multiplexGum", function () {
-      store.connectionFailure(connectionFailureAction);
-
-      sinon.assert.calledOnce(fakeMultiplexGum.reset);});
-
-
     it("should disconnect from the servers via the sdk", function () {
       store.connectionFailure(connectionFailureAction);
 
       sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);});
 
 
     it("should clear any existing timeout", function () {
       sandbox.stub(window, "clearTimeout");
@@ -1307,17 +1235,16 @@ describe("loop.store.ActiveRoomStore", f
     it("should not do any other cleanup if the user agent is handling the room", function () {
       store.setStoreState({ 
         standalone: true, 
         userAgentHandlesRoom: true });
 
 
       store.connectionFailure(connectionFailureAction);
 
-      sinon.assert.notCalled(fakeMultiplexGum.reset);
       sinon.assert.notCalled(fakeSdkDriver.disconnectSession);});});
 
 
 
   describe("#setMute", function () {
     it("should save the mute state for the audio stream", function () {
       store.setStoreState({ audioMuted: false });
 
@@ -1953,22 +1880,16 @@ describe("loop.store.ActiveRoomStore", f
       store.screenSharingState(new sharedActions.ScreenSharingState({ 
         state: SCREEN_SHARE_STATES.INACTIVE }));
 
 
       sinon.assert.calledOnce(requestStubs.SetScreenShareState);
       sinon.assert.calledWithExactly(requestStubs.SetScreenShareState, "1234", false);});
 
 
-    it("should reset the multiplexGum", function () {
-      store.windowUnload();
-
-      sinon.assert.calledOnce(fakeMultiplexGum.reset);});
-
-
     it("should disconnect from the servers via the sdk", function () {
       store.windowUnload();
 
       sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);});
 
 
     it("should clear any existing timeout", function () {
       sandbox.stub(window, "clearTimeout");
@@ -2022,22 +1943,16 @@ describe("loop.store.ActiveRoomStore", f
     beforeEach(function () {
       store.setStoreState({ 
         roomState: ROOM_STATES.JOINED, 
         roomToken: "fakeToken", 
         sessionToken: "1627384950" });});
 
 
 
-    it("should reset the multiplexGum", function () {
-      store.leaveRoom();
-
-      sinon.assert.calledOnce(fakeMultiplexGum.reset);});
-
-
     it("should disconnect from the servers via the sdk", function () {
       store.leaveRoom();
 
       sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);});
 
 
     it("should clear any existing timeout", function () {
       sandbox.stub(window, "clearTimeout");
@@ -2124,34 +2039,16 @@ describe("loop.store.ActiveRoomStore", f
 
       store.leaveRoom();
 
       expect(store._storeState.roomName).eql("fred");
       expect(store._storeState.roomContextUrls).eql([{ fake: 1 }]);});});
 
 
 
-  describe("#_handleSocialShareUpdate", function () {
-    it("should dispatch an UpdateRoomInfo action", function () {
-      store._handleSocialShareUpdate();
-
-      sinon.assert.calledOnce(dispatcher.dispatch);
-      sinon.assert.calledWithExactly(dispatcher.dispatch, 
-      new sharedActions.UpdateSocialShareInfo({ 
-        socialShareProviders: [] }));});
-
-
-
-    it("should call respective mozLoop methods", function () {
-      store._handleSocialShareUpdate();
-
-      sinon.assert.calledOnce(requestStubs.GetSocialShareProviders);});});
-
-
-
   describe("#_handleTextChatMessage", function () {
     beforeEach(function () {
       var fakeRoomData = { 
         decryptedContext: { 
           roomName: "Monkeys" }, 
 
         participants: [], 
         roomUrl: "http://invalid" };
@@ -2199,32 +2096,17 @@ describe("loop.store.ActiveRoomStore", f
 
     it("should not do anything for non-chat messages", function () {
       store._handleTextChatMessage(new sharedActions.SendTextChatMessage({ 
         contentType: CHAT_CONTENT_TYPES.CONTEXT, 
         message: "Hello!", 
         sentTimestamp: "1970-01-01T00:00:00.000Z" }));
 
 
-      assertWeDidNothing();});
-
-
-    it("should ping telemetry when a chat message arrived or is to be sent", function () {
-      store._handleTextChatMessage(new sharedActions.ReceivedTextChatMessage({ 
-        contentType: CHAT_CONTENT_TYPES.TEXT, 
-        message: "Hello!", 
-        receivedTimestamp: "1970-01-01T00:00:00.000Z" }));
-
-
-      sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
-      sinon.assert.calledWithExactly(requestStubs.TelemetryAddValue, 
-      "LOOP_ROOM_SESSION_WITHCHAT", 1);
-      expect(store.getStoreState().chatMessageExchanged).eql(true);
-      expect(dispatcher._eventData.hasOwnProperty("receivedTextChatMessage")).eql(false);
-      expect(dispatcher._eventData.hasOwnProperty("sendTextChatMessage")).eql(false);});});
+      assertWeDidNothing();});});
 
 
 
   describe("Events", function () {
     describe("update:{roomToken}", function () {
       beforeEach(function () {
         var fakeRoomData = { 
           decryptedContext: { 
--- a/browser/extensions/loop/chrome/content/shared/test/otSdkDriver_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/otSdkDriver_test.js
@@ -8,17 +8,17 @@ describe("loop.OTSdkDriver", function ()
   var sharedActions = loop.shared.actions;
   var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS;
   var STREAM_PROPERTIES = loop.shared.utils.STREAM_PROPERTIES;
   var SCREEN_SHARE_STATES = loop.shared.utils.SCREEN_SHARE_STATES;
   var CHAT_CONTENT_TYPES = loop.shared.utils.CHAT_CONTENT_TYPES;
   var CURSOR_MESSAGE_TYPES = loop.shared.utils.CURSOR_MESSAGE_TYPES;
 
   var sandbox, constants;
-  var dispatcher, driver, requestStubs, publisher, screenshare, sdk, session;
+  var dispatcher, driver, publisher, screenshare, sdk, session;
   var sessionData, subscriber, publisherConfig, fakeEvent;
 
   beforeEach(function () {
     sandbox = LoopMochaUtils.createSandbox();
 
     fakeEvent = { 
       preventDefault: sinon.stub() };
 
@@ -26,21 +26,16 @@ describe("loop.OTSdkDriver", function ()
       fake: "config" };
 
     sessionData = { 
       apiKey: "1234567890", 
       sessionId: "3216549870", 
       sessionToken: "1357924680" };
 
 
-    LoopMochaUtils.stubLoopRequest(requestStubs = { 
-      TelemetryAddValue: sinon.stub(), 
-      GetLoopPref: sinon.stub() });
-
-
     dispatcher = new loop.Dispatcher();
 
     sandbox.stub(dispatcher, "dispatch");
 
     session = _.extend({ 
       connect: sinon.stub(), 
       disconnect: sinon.stub(), 
       publish: sinon.stub(), 
@@ -79,22 +74,16 @@ describe("loop.OTSdkDriver", function ()
       ExceptionCodes: { 
         CONNECT_FAILED: 1006, 
         TERMS_OF_SERVICE_FAILURE: 1026, 
         UNABLE_TO_PUBLISH: 1500 } };
 
 
 
     constants = { 
-      TWO_WAY_MEDIA_CONN_LENGTH: { 
-        SHORTER_THAN_10S: 0, 
-        BETWEEN_10S_AND_30S: 1, 
-        BETWEEN_30S_AND_5M: 2, 
-        MORE_THAN_5M: 3 }, 
-
       SHARING_STATE_CHANGE: { 
         WINDOW_ENABLED: 0, 
         WINDOW_DISABLED: 1, 
         BROWSER_ENABLED: 2, 
         BROWSER_DISABLED: 3 } };
 
 
 
@@ -139,33 +128,16 @@ describe("loop.OTSdkDriver", function ()
 
       expect(driver._metrics).eql({ 
         connections: 0, 
         sendStreams: 0, 
         recvStreams: 0 });});
 
 
 
-    it("should enable debug for two way media telemetry if required", function () {
-      // Simulate the pref being enabled.
-      sandbox.stub(loop.shared.utils, "getBoolPreference", function (prefName, callback) {
-        if (prefName === "debug.twoWayMediaTelemetry") {
-          callback(true);}});
-
-
-
-      driver = new loop.OTSdkDriver({ 
-        constants: constants, 
-        dispatcher: dispatcher, 
-        sdk: sdk });
-
-
-      expect(driver._debugTwoWayMediaTelemetry).eql(true);});
-
-
     it("should enable debug on the sdk if required", function () {
       // Simulate the pref being enabled.
       sandbox.stub(loop.shared.utils, "getBoolPreference", function (prefName, callback) {
         if (prefName === "debug.sdk") {
           callback(true);}});
 
 
 
@@ -441,25 +413,16 @@ describe("loop.OTSdkDriver", function ()
 
     it("should connect the session", function () {
       driver.connectSession(sessionData);
 
       sinon.assert.calledOnce(session.connect);
       sinon.assert.calledWith(session.connect, "1234567890", "1357924680");});
 
 
-    it("should set the two-way media start time to 'uninitialized' " + 
-    "when sessionData.sendTwoWayMediaTelemetry is true'", function () {
-      driver.connectSession(_.extend(sessionData, 
-      { sendTwoWayMediaTelemetry: true }));
-
-      expect(driver._getTwoWayMediaStartTime()).to.eql(
-      driver.CONNECTION_START_TIME_UNINITIALIZED);});
-
-
     describe("On connection complete", function () {
       beforeEach(function () {
         sandbox.stub(window.console, "error");});
 
 
       it("should publish the stream if the publisher is ready", function () {
         driver._publisherReady = true;
         session.connect.callsArg(2);
@@ -632,119 +595,17 @@ describe("loop.OTSdkDriver", function ()
 
 
 
     it("should destroy the publisher", function () {
       driver.publisher = publisher;
 
       driver.disconnectSession();
 
-      sinon.assert.calledOnce(publisher.destroy);});
-
-
-    it("should call _noteConnectionLengthIfNeeded with connection duration", function () {
-      driver.session = session;
-      var startTime = 1;
-      var endTime = 3;
-      driver._sendTwoWayMediaTelemetry = true;
-      driver._setTwoWayMediaStartTime(startTime);
-      sandbox.stub(performance, "now").returns(endTime);
-      sandbox.stub(driver, "_noteConnectionLengthIfNeeded");
-
-      driver.disconnectSession();
-
-      sinon.assert.calledWith(driver._noteConnectionLengthIfNeeded, startTime, 
-      endTime);});
-
-
-    it("should reset the two-way media connection start time", function () {
-      driver.session = session;
-      var startTime = 1;
-      driver._sendTwoWayMediaTelemetry = true;
-      driver._setTwoWayMediaStartTime(startTime);
-      sandbox.stub(performance, "now");
-      sandbox.stub(driver, "_noteConnectionLengthIfNeeded");
-
-      driver.disconnectSession();
-
-      expect(driver._getTwoWayMediaStartTime()).to.eql(
-      driver.CONNECTION_START_TIME_UNINITIALIZED);});});
-
-
-
-  describe("#_noteConnectionLengthIfNeeded", function () {
-    var startTimeMS;
-    beforeEach(function () {
-      startTimeMS = 1;
-      driver._sendTwoWayMediaTelemetry = true;
-      driver._setTwoWayMediaStartTime(startTimeMS);});
-
-
-    it("should set two-way media start time to CONNECTION_START_TIME_ALREADY_NOTED", function () {
-      var endTimeMS = 3;
-      driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
-
-      expect(driver._getTwoWayMediaStartTime()).to.eql(
-      driver.CONNECTION_START_TIME_ALREADY_NOTED);});
-
-
-    it("should record telemetry with SHORTER_THAN_10S for calls less than 10s", function () {
-      var endTimeMS = 9000;
-
-      driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
-
-      sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
-      sinon.assert.calledWith(requestStubs.TelemetryAddValue, 
-      "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", 
-      constants.TWO_WAY_MEDIA_CONN_LENGTH.SHORTER_THAN_10S);});
-
-
-    it("should call record telemetry with BETWEEN_10S_AND_30S for 15s calls", 
-    function () {
-      var endTimeMS = 15000;
-
-      driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
-
-      sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
-      sinon.assert.calledWith(requestStubs.TelemetryAddValue, 
-      "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", 
-      constants.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_10S_AND_30S);});
-
-
-    it("should call record telemetry with BETWEEN_30S_AND_5M for 60s calls", 
-    function () {
-      var endTimeMS = 60 * 1000;
-
-      driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
-
-      sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
-      sinon.assert.calledWith(requestStubs.TelemetryAddValue, 
-      "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", 
-      constants.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_30S_AND_5M);});
-
-
-    it("should call record telemetry with MORE_THAN_5M for 10m calls", function () {
-      var endTimeMS = 10 * 60 * 1000;
-
-      driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
-
-      sinon.assert.calledOnce(requestStubs.TelemetryAddValue);
-      sinon.assert.calledWith(requestStubs.TelemetryAddValue, 
-      "LOOP_TWO_WAY_MEDIA_CONN_LENGTH_1", 
-      constants.TWO_WAY_MEDIA_CONN_LENGTH.MORE_THAN_5M);});
-
-
-    it("should not call record telemetry if driver._sendTwoWayMediaTelemetry is false", 
-    function () {
-      var endTimeMS = 10 * 60 * 1000;
-      driver._sendTwoWayMediaTelemetry = false;
-
-      driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
-
-      sinon.assert.notCalled(requestStubs.TelemetryAddValue);});});
+      sinon.assert.calledOnce(publisher.destroy);});});
 
 
 
   describe("#forceDisconnectAll", function () {
     it("should not disconnect anything when not connected", function () {
       driver.session = session;
       driver.forceDisconnectAll(function () {});
 
@@ -914,35 +775,18 @@ describe("loop.OTSdkDriver", function ()
 
         sinon.assert.called(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch, 
         new sharedActions.ConnectionStatus({ 
           event: "Session.connectionDestroyed", 
           state: "waiting", 
           connections: 0, 
           sendStreams: 0, 
-          recvStreams: 0 }));});
-
-
-
-      it("should call _noteConnectionLengthIfNeeded with connection duration", function () {
-        driver.session = session;
-        var startTime = 1;
-        var endTime = 3;
-        driver._sendTwoWayMediaTelemetry = true;
-        driver._setTwoWayMediaStartTime(startTime);
-        sandbox.stub(performance, "now").returns(endTime);
-        sandbox.stub(driver, "_noteConnectionLengthIfNeeded");
-
-        session.trigger("connectionDestroyed", { 
-          reason: "clientDisconnected" });
-
-
-        sinon.assert.calledWith(driver._noteConnectionLengthIfNeeded, startTime, 
-        endTime);});});
+          recvStreams: 0 }));});});
+
 
 
 
     describe("sessionDisconnected", function () {
       it("should notify metrics", function () {
         session.trigger("sessionDisconnected", { 
           reason: "networkDisconnected" });
 
@@ -976,35 +820,17 @@ describe("loop.OTSdkDriver", function ()
         session.trigger("sessionDisconnected", { 
           reason: "forceDisconnected" });
 
 
         sinon.assert.calledTwice(dispatcher.dispatch);
         sinon.assert.calledWithMatch(dispatcher.dispatch, 
         sinon.match.hasOwn("name", "connectionFailure"));
         sinon.assert.calledWithMatch(dispatcher.dispatch, 
-        sinon.match.hasOwn("reason", FAILURE_DETAILS.EXPIRED_OR_INVALID));});
-
-
-      it("should call _noteConnectionLengthIfNeeded with connection duration", function () {
-        driver.session = session;
-        var startTime = 1;
-        var endTime = 3;
-        driver._sendTwoWayMediaTelemetry = true;
-        driver._setTwoWayMediaStartTime(startTime);
-        sandbox.stub(performance, "now").returns(endTime);
-        sandbox.stub(driver, "_noteConnectionLengthIfNeeded");
-
-        session.trigger("sessionDisconnected", { 
-          reason: "networkDisconnected" });
-
-
-        sinon.assert.calledWith(driver._noteConnectionLengthIfNeeded, startTime, 
-        endTime);});});
-
+        sinon.match.hasOwn("reason", FAILURE_DETAILS.EXPIRED_OR_INVALID));});});
 
 
 
     describe("streamCreated (publisher/local)", function () {
       var stream, fakeMockVideo;
 
       beforeEach(function () {
         driver._mockPublisherEl = document.createElement("div");
@@ -1161,38 +987,32 @@ describe("loop.OTSdkDriver", function ()
           session.trigger("streamCreated", { stream: fakeStream });
 
           // Called twice due to the VideoDimensionsChanged above.
           sinon.assert.called(dispatcher.dispatch);
           sinon.assert.calledWithMatch(dispatcher.dispatch, 
           new sharedActions.MediaConnected({}));});
 
 
-        it("should store the start time when both streams are up and" + 
-        " driver._sendTwoWayMediaTelemetry is true", function () {
-          driver._sendTwoWayMediaTelemetry = true;
+        it("should dispatch a connectionStatus action if both streams are up", function () {
           driver._publishedLocalStream = true;
-          var startTime = 1;
-          sandbox.stub(performance, "now").returns(startTime);
 
           session.trigger("streamCreated", { stream: fakeStream });
 
-          expect(driver._getTwoWayMediaStartTime()).to.eql(startTime);});
-
-
-        it("should not store the start time when both streams are up and" + 
-        " driver._isDesktop is false", function () {
-          driver._isDesktop = false;
-          driver._publishedLocalStream = true;
-          var startTime = 73;
-          sandbox.stub(performance, "now").returns(startTime);
-
-          session.trigger("streamCreated", { stream: fakeStream });
-
-          expect(driver._getTwoWayMediaStartTime()).to.not.eql(startTime);});
+          // Called twice due to the VideoDimensionsChanged above.
+          sinon.assert.called(dispatcher.dispatch);
+          sinon.assert.calledWithMatch(dispatcher.dispatch, 
+          new sharedActions.ConnectionStatus({ 
+            event: "Session.subscribeCompleted", 
+            state: "receiving", 
+            // Local stream connection is faked, so connections/sendStreams=0.
+            connections: 0, 
+            sendStreams: 0, 
+            recvStreams: 1 }));});
+
 
 
         describe("Data channel setup", function () {
           var fakeChannel;
 
           beforeEach(function () {
             fakeChannel = _.extend({}, Backbone.Events);
             fakeStream.connection = fakeConnection;
@@ -1360,17 +1180,55 @@ describe("loop.OTSdkDriver", function ()
         it("should dispatch a ReceivingScreenShare action for screen sharing streams", function () {
           fakeStream.videoType = "screen";
 
           session.trigger("streamCreated", { stream: fakeStream });
 
           // Called twice due to the VideoDimensionsChanged above.
           sinon.assert.called(dispatcher.dispatch);
           sinon.assert.calledWithExactly(dispatcher.dispatch, 
-          new sharedActions.ReceivingScreenShare({ receiving: true }));});});});
+          new sharedActions.ReceivingScreenShare({ receiving: true }));});
+
+
+        describe("screen share subscribe completed", function () {
+          beforeEach(function () {
+            fakeStream.videoType = "screen";
+
+            session.subscribe.yieldsOn(driver, null, fakeSubscriberObject, 
+            videoElement).returns(this.fakeSubscriberObject);});
+
+
+          it("should dispatch ReceivingScreenShare on completion", function () {
+            fakeStream.connection = fakeConnection;
+            fakeStream.hasVideo = false;
+
+            session.trigger("streamCreated", { stream: fakeStream });
+
+            sinon.assert.called(dispatcher.dispatch);
+            sinon.assert.calledWithExactly(dispatcher.dispatch, 
+            new sharedActions.ReceivingScreenShare({ 
+              receiving: true, 
+              srcMediaElement: videoElement }));});
+
+
+
+          it("should dispatch a connectionStatus action", function () {
+            session.trigger("streamCreated", { stream: fakeStream });
+
+            // Called twice due to the VideoDimensionsChanged above.
+            sinon.assert.called(dispatcher.dispatch);
+            sinon.assert.calledWithMatch(dispatcher.dispatch, 
+            new sharedActions.ConnectionStatus({ 
+              event: "Session.screen.subscribeCompleted", 
+              state: "receiving", 
+              connections: 0, 
+              sendStreams: 0, 
+              recvStreams: 1 }));});});});});
+
+
 
 
 
 
     describe("streamDestroyed: publisher/local", function () {
       it("should dispatch a ConnectionStatus action", function () {
         driver._metrics.sendStreams = 1;
         driver._metrics.recvStreams = 1;
--- a/browser/extensions/loop/chrome/content/shared/test/vendor/mocha.css
+++ b/browser/extensions/loop/chrome/content/shared/test/vendor/mocha.css
@@ -256,16 +256,25 @@ body {
   margin: 0;
   color: #888;
   z-index: 1;
 }
 
 #mocha-stats .progress {
   float: right;
   padding-top: 0;
+  
+  /**
+   * Set safe initial values, so mochas .progress does not inherit these
+   * properties from Bootstrap .progress (which causes .progress height to
+   * equal line height set in Bootstrap).
+   */
+  height: auto;
+  box-shadow: none;
+  background-color: initial;
 }
 
 #mocha-stats em {
   color: black;
 }
 
 #mocha-stats a {
   text-decoration: none;
--- a/browser/extensions/loop/chrome/content/shared/test/vendor/mocha.js
+++ b/browser/extensions/loop/chrome/content/shared/test/vendor/mocha.js
@@ -1,39 +1,202 @@
 (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-(function (process){
-module.exports = process.env.COV
-  ? require('./lib-cov/mocha')
-  : require('./lib/mocha');
-
-}).call(this,require('_process'))
-},{"./lib-cov/mocha":undefined,"./lib/mocha":14,"_process":51}],2:[function(require,module,exports){
+(function (process,global){
+/**
+ * Shim process.stdout.
+ */
+
+process.stdout = require('browser-stdout')();
+
+var Mocha = require('./lib/mocha');
+
+/**
+ * Create a Mocha instance.
+ *
+ * @return {undefined}
+ */
+
+var mocha = new Mocha({ reporter: 'html' });
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date;
+var setTimeout = global.setTimeout;
+var setInterval = global.setInterval;
+var clearTimeout = global.clearTimeout;
+var clearInterval = global.clearInterval;
+
+var uncaughtExceptionHandlers = [];
+
+var originalOnerrorHandler = global.onerror;
+
+/**
+ * Remove uncaughtException listener.
+ * Revert to original onerror handler if previously defined.
+ */
+
+process.removeListener = function(e, fn){
+  if ('uncaughtException' == e) {
+    if (originalOnerrorHandler) {
+      global.onerror = originalOnerrorHandler;
+    } else {
+      global.onerror = function() {};
+    }
+    var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn);
+    if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); }
+  }
+};
+
+/**
+ * Implements uncaughtException listener.
+ */
+
+process.on = function(e, fn){
+  if ('uncaughtException' == e) {
+    global.onerror = function(err, url, line){
+      fn(new Error(err + ' (' + url + ':' + line + ')'));
+      return !mocha.allowUncaught;
+    };
+    uncaughtExceptionHandlers.push(fn);
+  }
+};
+
+// The BDD UI is registered by default, but no UI will be functional in the
+// browser without an explicit call to the overridden `mocha.ui` (see below).
+// Ensure that this default UI does not expose its methods to the global scope.
+mocha.suite.removeAllListeners('pre-require');
+
+var immediateQueue = []
+  , immediateTimeout;
+
+function timeslice() {
+  var immediateStart = new Date().getTime();
+  while (immediateQueue.length && (new Date().getTime() - immediateStart) < 100) {
+    immediateQueue.shift()();
+  }
+  if (immediateQueue.length) {
+    immediateTimeout = setTimeout(timeslice, 0);
+  } else {
+    immediateTimeout = null;
+  }
+}
+
+/**
+ * High-performance override of Runner.immediately.
+ */
+
+Mocha.Runner.immediately = function(callback) {
+  immediateQueue.push(callback);
+  if (!immediateTimeout) {
+    immediateTimeout = setTimeout(timeslice, 0);
+  }
+};
+
+/**
+ * Function to allow assertion libraries to throw errors directly into mocha.
+ * This is useful when running tests in a browser because window.onerror will
+ * only receive the 'message' attribute of the Error.
+ */
+mocha.throwError = function(err) {
+  Mocha.utils.forEach(uncaughtExceptionHandlers, function (fn) {
+    fn(err);
+  });
+  throw err;
+};
+
+/**
+ * Override ui to ensure that the ui functions are initialized.
+ * Normally this would happen in Mocha.prototype.loadFiles.
+ */
+
+mocha.ui = function(ui){
+  Mocha.prototype.ui.call(this, ui);
+  this.suite.emit('pre-require', global, null, this);
+  return this;
+};
+
+/**
+ * Setup mocha with the given setting options.
+ */
+
+mocha.setup = function(opts){
+  if ('string' == typeof opts) opts = { ui: opts };
+  for (var opt in opts) this[opt](opts[opt]);
+  return this;
+};
+
+/**
+ * Run mocha, returning the Runner.
+ */
+
+mocha.run = function(fn){
+  var options = mocha.options;
+  mocha.globals('location');
+
+  var query = Mocha.utils.parseQuery(global.location.search || '');
+  if (query.grep) mocha.grep(new RegExp(query.grep));
+  if (query.fgrep) mocha.grep(query.fgrep);
+  if (query.invert) mocha.invert();
+
+  return Mocha.prototype.run.call(mocha, function(err){
+    // The DOM Document is not available in Web Workers.
+    var document = global.document;
+    if (document && document.getElementById('mocha') && options.noHighlighting !== true) {
+      Mocha.utils.highlightTags('code');
+    }
+    if (fn) fn(err);
+  });
+};
+
+/**
+ * Expose the process shim.
+ * https://github.com/mochajs/mocha/pull/916
+ */
+
+Mocha.process = process;
+
+/**
+ * Expose mocha.
+ */
+
+global.Mocha = Mocha;
+global.mocha = mocha;
+
+// this allows test/acceptance/required-tokens.js to pass; thus,
+// you can now do `const describe = require('mocha').describe` in a
+// browser context (assuming browserification).  should fix #880
+module.exports = global;
+
+}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./lib/mocha":14,"_process":58,"browser-stdout":42}],2:[function(require,module,exports){
 /* eslint-disable no-unused-vars */
 module.exports = function(type) {
   return function() {};
 };
 
 },{}],3:[function(require,module,exports){
 /**
  * Module exports.
  */
 
 exports.EventEmitter = EventEmitter;
 
 /**
- * Object#hasOwnProperty reference.
+ * Object#toString reference.
  */
 var objToString = Object.prototype.toString;
 
 /**
  * Check if a value is an array.
  *
  * @api private
  * @param {*} val The value to test.
- * @return {boolean} true if the value is a boolean, otherwise false.
+ * @return {boolean} true if the value is an array, otherwise false.
  */
 function isArray(val) {
   return objToString.call(val) === '[object Array]';
 }
 
 /**
  * Event emitter constructor.
  *
@@ -571,17 +734,17 @@ module.exports = function(suite) {
     /**
      * Describe a specification or test-case
      * with the given `title` and callback `fn`
      * acting as a thunk.
      */
 
     var it = context.it = context.specify = function(title, fn) {
       var suite = suites[0];
-      if (suite.pending) {
+      if (suite.isPending()) {
         fn = null;
       }
       var test = new Test(title, fn);
       test.file = file;
       suite.addTest(test);
       return test;
     };
 
@@ -608,17 +771,17 @@ module.exports = function(suite) {
      * Number of attempts to retry.
      */
     context.it.retries = function(n) {
       context.retries(n);
     };
   });
 };
 
-},{"../suite":37,"../test":38,"./common":9,"escape-string-regexp":68}],9:[function(require,module,exports){
+},{"../suite":37,"../test":38,"./common":9,"escape-string-regexp":49}],9:[function(require,module,exports){
 'use strict';
 
 /**
  * Functions common to more than one interface.
  *
  * @param {Suite[]} suites
  * @param {Context} context
  * @return {Object} An object containing common functions.
@@ -686,17 +849,17 @@ module.exports = function(suites, contex
        */
       skip: function(title) {
         context.test(title);
       },
 
       /**
        * Number of retry attempts
        *
-       * @param {string} n
+       * @param {number} n
        */
       retries: function(n) {
         context.retries(n);
       }
     }
   };
 };
 
@@ -704,17 +867,17 @@ module.exports = function(suites, contex
 /**
  * Module dependencies.
  */
 
 var Suite = require('../suite');
 var Test = require('../test');
 
 /**
- * TDD-style interface:
+ * Exports-style (as Node.js module) interface:
  *
  *     exports.Array = {
  *       '#indexOf()': {
  *         'should return -1 when the value is not present': function() {
  *
  *         },
  *
  *         'should return the correct index when the value is present': function() {
@@ -860,17 +1023,17 @@ module.exports = function(suite) {
       mocha.grep(new RegExp(reString));
     };
 
     context.test.skip = common.test.skip;
     context.test.retries = common.test.retries;
   });
 };
 
-},{"../suite":37,"../test":38,"./common":9,"escape-string-regexp":68}],13:[function(require,module,exports){
+},{"../suite":37,"../test":38,"./common":9,"escape-string-regexp":49}],13:[function(require,module,exports){
 /**
  * Module dependencies.
  */
 
 var Suite = require('../suite');
 var Test = require('../test');
 var escapeRe = require('escape-string-regexp');
 
@@ -944,17 +1107,17 @@ module.exports = function(suite) {
     };
 
     /**
      * Describe a specification or test-case with the given `title` and
      * callback `fn` acting as a thunk.
      */
     context.test = function(title, fn) {
       var suite = suites[0];
-      if (suite.pending) {
+      if (suite.isPending()) {
         fn = null;
       }
       var test = new Test(title, fn);
       test.file = file;
       suite.addTest(test);
       return test;
     };
 
@@ -968,17 +1131,17 @@ module.exports = function(suite) {
       mocha.grep(new RegExp(reString));
     };
 
     context.test.skip = common.test.skip;
     context.test.retries = common.test.retries;
   });
 };
 
-},{"../suite":37,"../test":38,"./common":9,"escape-string-regexp":68}],14:[function(require,module,exports){
+},{"../suite":37,"../test":38,"./common":9,"escape-string-regexp":49}],14:[function(require,module,exports){
 (function (process,global,__dirname){
 /*!
  * mocha
  * Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
  * MIT Licensed
  */
 
 /**
@@ -1071,32 +1234,16 @@ function Mocha(options) {
   }
   this.useColors(options.useColors);
   if (options.enableTimeouts !== null) {
     this.enableTimeouts(options.enableTimeouts);
   }
   if (options.slow) {
     this.slow(options.slow);
   }
-
-  this.suite.on('pre-require', function(context) {
-    exports.afterEach = context.afterEach || context.teardown;
-    exports.after = context.after || context.suiteTeardown;
-    exports.beforeEach = context.beforeEach || context.setup;
-    exports.before = context.before || context.suiteSetup;
-    exports.describe = context.describe || context.suite;
-    exports.it = context.it || context.test;
-    exports.setup = context.setup || context.beforeEach;
-    exports.suiteSetup = context.suiteSetup || context.before;
-    exports.suiteTeardown = context.suiteTeardown || context.after;
-    exports.suite = context.suite || context.describe;
-    exports.teardown = context.teardown || context.afterEach;
-    exports.test = context.test || context.it;
-    exports.run = context.run;
-  });
 }
 
 /**
  * Enable or disable bailing on the first failure.
  *
  * @api public
  * @param {boolean} [bail]
  */
@@ -1174,16 +1321,33 @@ Mocha.prototype.ui = function(name) {
   if (!this._ui) {
     try {
       this._ui = require(name);
     } catch (err) {
       throw new Error('invalid interface "' + name + '"');
     }
   }
   this._ui = this._ui(this.suite);
+
+  this.suite.on('pre-require', function(context) {
+    exports.afterEach = context.afterEach || context.teardown;
+    exports.after = context.after || context.suiteTeardown;
+    exports.beforeEach = context.beforeEach || context.setup;
+    exports.before = context.before || context.suiteSetup;
+    exports.describe = context.describe || context.suite;
+    exports.it = context.it || context.test;
+    exports.setup = context.setup || context.beforeEach;
+    exports.suiteSetup = context.suiteSetup || context.before;
+    exports.suiteTeardown = context.suiteTeardown || context.after;
+    exports.suite = context.suite || context.describe;
+    exports.teardown = context.teardown || context.afterEach;
+    exports.test = context.test || context.it;
+    exports.run = context.run;
+  });
+
   return this;
 };
 
 /**
  * Load registered files.
  *
  * @api private
  */
@@ -1474,17 +1638,17 @@ Mocha.prototype.run = function(fn) {
       fn && fn(failures);
     }
   }
 
   return runner.run(done);
 };
 
 }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},"/lib")
-},{"./context":6,"./hook":7,"./interfaces":11,"./reporters":22,"./runnable":35,"./runner":36,"./suite":37,"./test":38,"./utils":39,"_process":51,"escape-string-regexp":68,"growl":69,"path":41}],15:[function(require,module,exports){
+},{"./context":6,"./hook":7,"./interfaces":11,"./reporters":22,"./runnable":35,"./runner":36,"./suite":37,"./test":38,"./utils":39,"_process":58,"escape-string-regexp":49,"growl":51,"path":43}],15:[function(require,module,exports){
 /**
  * Helpers.
  */
 
 var s = 1000;
 var m = s * 60;
 var h = m * 60;
 var d = h * 24;
@@ -1795,18 +1959,18 @@ exports.list = function(failures) {
     var fmt = color('error title', '  %s) %s:\n')
       + color('error message', '     %s')
       + color('error stack', '\n%s\n');
 
     // msg
     var msg;
     var err = test.err;
     var message;
-    if (err.message) {
-      message = err.message;
+    if (err.message && typeof err.message.toString === 'function') {
+      message = err.message + '';
     } else if (typeof err.inspect === 'function') {
       message = err.inspect() + '';
     } else {
       message = '';
     }
     var stack = err.stack || message;
     var index = stack.indexOf(message);
     var actual = err.actual;
@@ -2112,17 +2276,17 @@ var objToString = Object.prototype.toStr
  * @param {Object} b
  * @return {boolean}
  */
 function sameType(a, b) {
   return objToString.call(a) === objToString.call(b);
 }
 
 }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"../ms":15,"../utils":39,"_process":51,"diff":67,"supports-color":41,"tty":5}],18:[function(require,module,exports){
+},{"../ms":15,"../utils":39,"_process":58,"diff":48,"supports-color":43,"tty":5}],18:[function(require,module,exports){
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 var utils = require('../utils');
 
 /**
@@ -2246,17 +2410,17 @@ function Dot(runner) {
 }
 
 /**
  * Inherit from `Base.prototype`.
  */
 inherits(Dot, Base);
 
 }).call(this,require('_process'))
-},{"../utils":39,"./base":17,"_process":51}],20:[function(require,module,exports){
+},{"../utils":39,"./base":17,"_process":58}],20:[function(require,module,exports){
 (function (process,__dirname){
 /**
  * Module dependencies.
  */
 
 var JSONCov = require('./json-cov');
 var readFileSync = require('fs').readFileSync;
 var join = require('path').join;
@@ -2306,17 +2470,17 @@ function coverageClass(coveragePctg) {
   }
   if (coveragePctg >= 25) {
     return 'low';
   }
   return 'terrible';
 }
 
 }).call(this,require('_process'),"/lib/reporters")
-},{"./json-cov":23,"_process":51,"fs":41,"jade":41,"path":41}],21:[function(require,module,exports){
+},{"./json-cov":23,"_process":58,"fs":43,"jade":43,"path":43}],21:[function(require,module,exports){
 (function (global){
 /* eslint-env browser */
 
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
@@ -2390,27 +2554,29 @@ function HTML(runner) {
     progress = new Progress();
   }
 
   if (!root) {
     return error('#mocha div missing, add it to your document');
   }
 
   // pass toggle
-  on(passesLink, 'click', function() {
+  on(passesLink, 'click', function(evt) {
+    evt.preventDefault();
     unhide();
     var name = (/pass/).test(report.className) ? '' : ' pass';
     report.className = report.className.replace(/fail|pass/g, '') + name;
     if (report.className.trim()) {
       hideSuitesWithout('test pass');
     }
   });
 
   // failure toggle
-  on(failuresLink, 'click', function() {
+  on(failuresLink, 'click', function(evt) {
+    evt.preventDefault();
     unhide();
     var name = (/fail/).test(report.className) ? '' : ' fail';
     report.className = report.className.replace(/fail|pass/g, '') + name;
     if (report.className.trim()) {
       hideSuitesWithout('test fail');
     }
   });
 
@@ -2438,98 +2604,92 @@ function HTML(runner) {
 
   runner.on('suite end', function(suite) {
     if (suite.root) {
       return;
     }
     stack.shift();
   });
 
+  runner.on('pass', function(test) {
+    var url = self.testURL(test);
+    var markup = '<li class="test pass %e"><h2>%e<span class="duration">%ems</span> '
+      + '<a href="%s" class="replay">‣</a></h2></li>';
+    var el = fragment(markup, test.speed, test.title, test.duration, url);
+    self.addCodeToggle(el, test.body);
+    appendToStack(el);
+    updateStats();
+  });
+
   runner.on('fail', function(test) {
-    // For type = 'test' its possible that the test failed due to multiple
-    // done() calls. So report the issue here.
-    if (test.type === 'hook'
-      || test.type === 'test') {
-      runner.emit('test end', test);
-    }
-  });
-
-  runner.on('test end', function(test) {
+    var el = fragment('<li class="test fail"><h2>%e <a href="%e" class="replay">‣</a></h2></li>',
+      test.title, self.testURL(test));
+    var stackString; // Note: Includes leading newline
+    var message = test.err.toString();
+
+    // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
+    // check for the result of the stringifying.
+    if (message === '[object Error]') {
+      message = test.err.message;
+    }
+
+    if (test.err.stack) {
+      var indexOfMessage = test.err.stack.indexOf(test.err.message);
+      if (indexOfMessage === -1) {
+        stackString = test.err.stack;
+      } else {
+        stackString = test.err.stack.substr(test.err.message.length + indexOfMessage);
+      }
+    } else if (test.err.sourceURL && test.err.line !== undefined) {
+      // Safari doesn't give you a stack. Let's at least provide a source line.
+      stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')';
+    }
+
+    stackString = stackString || '';
+
+    if (test.err.htmlMessage && stackString) {
+      el.appendChild(fragment('<div class="html-error">%s\n<pre class="error">%e</pre></div>',
+        test.err.htmlMessage, stackString));
+    } else if (test.err.htmlMessage) {
+      el.appendChild(fragment('<div class="html-error">%s</div>', test.err.htmlMessage));
+    } else {
+      el.appendChild(fragment('<pre class="error">%e%e</pre>', message, stackString));
+    }
+
+    self.addCodeToggle(el, test.body);
+    appendToStack(el);
+    updateStats();
+  });
+
+  runner.on('pending', function(test) {
+    var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title);
+    appendToStack(el);
+    updateStats();
+  });
+
+  function appendToStack(el) {
+    // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
+    if (stack[0]) {
+      stack[0].appendChild(el);
+    }
+  }
+
+  function updateStats() {
     // TODO: add to stats
     var percent = stats.tests / this.total * 100 | 0;
     if (progress) {
       progress.update(percent).draw(ctx);
     }
 
     // update stats
     var ms = new Date() - stats.start;
     text(passes, stats.passes);
     text(failures, stats.failures);
     text(duration, (ms / 1000).toFixed(2));
-
-    // test
-    var el;
-    if (test.state === 'passed') {
-      var url = self.testURL(test);
-      el = fragment('<li class="test pass %e"><h2>%e<span class="duration">%ems</span> <a href="%s" class="replay">‣</a></h2></li>', test.speed, test.title, test.duration, url);
-    } else if (test.pending) {
-      el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title);
-    } else {
-      el = fragment('<li class="test fail"><h2>%e <a href="%e" class="replay">‣</a></h2></li>', test.title, self.testURL(test));
-      var stackString; // Note: Includes leading newline
-      var message = test.err.toString();
-
-      // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
-      // check for the result of the stringifying.
-      if (message === '[object Error]') {
-        message = test.err.message;
-      }
-
-      if (test.err.stack) {
-        var indexOfMessage = test.err.stack.indexOf(test.err.message);
-        if (indexOfMessage === -1) {
-          stackString = test.err.stack;
-        } else {
-          stackString = test.err.stack.substr(test.err.message.length + indexOfMessage);
-        }
-      } else if (test.err.sourceURL && test.err.line !== undefined) {
-        // Safari doesn't give you a stack. Let's at least provide a source line.
-        stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')';
-      }
-
-      stackString = stackString || '';
-
-      if (test.err.htmlMessage && stackString) {
-        el.appendChild(fragment('<div class="html-error">%s\n<pre class="error">%e</pre></div>', test.err.htmlMessage, stackString));
-      } else if (test.err.htmlMessage) {
-        el.appendChild(fragment('<div class="html-error">%s</div>', test.err.htmlMessage));
-      } else {
-        el.appendChild(fragment('<pre class="error">%e%e</pre>', message, stackString));
-      }
-    }
-
-    // toggle code
-    // TODO: defer
-    if (!test.pending) {
-      var h2 = el.getElementsByTagName('h2')[0];
-
-      on(h2, 'click', function() {
-        pre.style.display = pre.style.display === 'none' ? 'block' : 'none';
-      });
-
-      var pre = fragment('<pre><code>%e</code></pre>', utils.clean(test.body));
-      el.appendChild(pre);
-      pre.style.display = 'none';
-    }
-
-    // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
-    if (stack[0]) {
-      stack[0].appendChild(el);
-    }
-  });
+  }
 }
 
 /**
  * Makes a URL, preserving querystring ("search") parameters.
  *
  * @param {string} s
  * @return {string} A new URL.
  */
@@ -2558,16 +2718,34 @@ HTML.prototype.suiteURL = function(suite
  *
  * @param {Object} [test]
  */
 HTML.prototype.testURL = function(test) {
   return makeUrl(test.fullTitle());
 };
 
 /**
+ * Adds code toggle functionality for the provided test's list element.
+ *
+ * @param {HTMLLIElement} el
+ * @param {string} contents
+ */
+HTML.prototype.addCodeToggle = function(el, contents) {
+  var h2 = el.getElementsByTagName('h2')[0];
+
+  on(h2, 'click', function() {
+    pre.style.display = pre.style.display === 'none' ? 'block' : 'none';
+  });
+
+  var pre = fragment('<pre><code>%e</code></pre>', utils.clean(contents));
+  el.appendChild(pre);
+  pre.style.display = 'none';
+};
+
+/**
  * Display error `msg`.
  *
  * @param {string} msg
  */
 function error(msg) {
   document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg));
 }
 
@@ -2639,17 +2817,17 @@ function on(el, event, fn) {
   if (el.addEventListener) {
     el.addEventListener(event, fn, false);
   } else {
     el.attachEvent('on' + event, fn);
   }
 }
 
 }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"../browser/progress":4,"../utils":39,"./base":17,"escape-string-regexp":68}],22:[function(require,module,exports){
+},{"../browser/progress":4,"../utils":39,"./base":17,"escape-string-regexp":49}],22:[function(require,module,exports){
 // Alias exports to a their normalized format Mocha#reporter to prevent a need
 // for dynamic (try/catch) requires, which Browserify doesn't handle.
 exports.Base = exports.base = require('./base');
 exports.Dot = exports.dot = require('./dot');
 exports.Doc = exports.doc = require('./doc');
 exports.TAP = exports.tap = require('./tap');
 exports.JSON = exports.json = require('./json');
 exports.HTML = exports.html = require('./html');
@@ -2815,17 +2993,17 @@ function clean(test) {
     duration: test.duration,
     currentRetry: test.currentRetry(),
     fullTitle: test.fullTitle(),
     title: test.title
   };
 }
 
 }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"./base":17,"_process":51}],24:[function(require,module,exports){
+},{"./base":17,"_process":58}],24:[function(require,module,exports){
 (function (process){
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 
 /**
@@ -2879,17 +3057,17 @@ function clean(test) {
     title: test.title,
     fullTitle: test.fullTitle(),
     duration: test.duration,
     currentRetry: test.currentRetry()
   };
 }
 
 }).call(this,require('_process'))
-},{"./base":17,"_process":51}],25:[function(require,module,exports){
+},{"./base":17,"_process":58}],25:[function(require,module,exports){
 (function (process){
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 
 /**
@@ -2973,17 +3151,17 @@ function errorJSON(err) {
   var res = {};
   Object.getOwnPropertyNames(err).forEach(function(key) {
     res[key] = err[key];
   }, err);
   return res;
 }
 
 }).call(this,require('_process'))
-},{"./base":17,"_process":51}],26:[function(require,module,exports){
+},{"./base":17,"_process":58}],26:[function(require,module,exports){
 (function (process){
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 var inherits = require('../utils').inherits;
 var cursor = Base.cursor;
@@ -3069,17 +3247,17 @@ function Landing(runner) {
 }
 
 /**
  * Inherit from `Base.prototype`.
  */
 inherits(Landing, Base);
 
 }).call(this,require('_process'))
-},{"../utils":39,"./base":17,"_process":51}],27:[function(require,module,exports){
+},{"../utils":39,"./base":17,"_process":58}],27:[function(require,module,exports){
 (function (process){
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 var inherits = require('../utils').inherits;
 var color = Base.color;
@@ -3134,17 +3312,17 @@ function List(runner) {
 }
 
 /**
  * Inherit from `Base.prototype`.
  */
 inherits(List, Base);
 
 }).call(this,require('_process'))
-},{"../utils":39,"./base":17,"_process":51}],28:[function(require,module,exports){
+},{"../utils":39,"./base":17,"_process":58}],28:[function(require,module,exports){
 (function (process){
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 var utils = require('../utils');
 
@@ -3235,17 +3413,17 @@ function Markdown(runner) {
   runner.on('end', function() {
     process.stdout.write('# TOC\n');
     process.stdout.write(generateTOC(runner.suite));
     process.stdout.write(buf);
   });
 }
 
 }).call(this,require('_process'))
-},{"../utils":39,"./base":17,"_process":51}],29:[function(require,module,exports){
+},{"../utils":39,"./base":17,"_process":58}],29:[function(require,module,exports){
 (function (process){
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 var inherits = require('../utils').inherits;
 
@@ -3275,17 +3453,17 @@ function Min(runner) {
 }
 
 /**
  * Inherit from `Base.prototype`.
  */
 inherits(Min, Base);
 
 }).call(this,require('_process'))
-},{"../utils":39,"./base":17,"_process":51}],30:[function(require,module,exports){
+},{"../utils":39,"./base":17,"_process":58}],30:[function(require,module,exports){
 (function (process){
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 var inherits = require('../utils').inherits;
 
@@ -3540,17 +3718,17 @@ NyanCat.prototype.rainbowify = function(
  *
  * @param {string} string A message to write to stdout.
  */
 function write(string) {
   process.stdout.write(string);
 }
 
 }).call(this,require('_process'))
-},{"../utils":39,"./base":17,"_process":51}],31:[function(require,module,exports){
+},{"../utils":39,"./base":17,"_process":58}],31:[function(require,module,exports){
 (function (process){
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 var inherits = require('../utils').inherits;
 var color = Base.color;
@@ -3633,17 +3811,17 @@ function Progress(runner, options) {
 }
 
 /**
  * Inherit from `Base.prototype`.
  */
 inherits(Progress, Base);
 
 }).call(this,require('_process'))
-},{"../utils":39,"./base":17,"_process":51}],32:[function(require,module,exports){
+},{"../utils":39,"./base":17,"_process":58}],32:[function(require,module,exports){
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 var inherits = require('../utils').inherits;
 var color = Base.color;
 var cursor = Base.cursor;
@@ -3922,18 +4100,18 @@ XUnit.prototype.test = function(test) {
   var attrs = {
     classname: test.parent.fullTitle(),
     name: test.title,
     time: (test.duration / 1000) || 0
   };
 
   if (test.state === 'failed') {
     var err = test.err;
-    this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + '\n' + err.stack))));
-  } else if (test.pending) {
+    this.write(tag('testcase', attrs, false, tag('failure', {}, false, escape(err.message) + '\n' + escape(err.stack))));
+  } else if (test.isPending()) {
     this.write(tag('testcase', attrs, false, tag('skipped', {}, true)));
   } else {
     this.write(tag('testcase', attrs, true));
   }
 };
 
 /**
  * HTML tag helper.
@@ -3957,26 +4135,18 @@ function tag(name, attrs, close, content
 
   tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end;
   if (content) {
     tag += content + '</' + name + end;
   }
   return tag;
 }
 
-/**
- * Return cdata escaped CDATA `str`.
- */
-
-function cdata(str) {
-  return '<![CDATA[' + escape(str) + ']]>';
-}
-
 }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"../utils":39,"./base":17,"_process":51,"fs":41,"mkdirp":70,"path":41}],35:[function(require,module,exports){
+},{"../utils":39,"./base":17,"_process":58,"fs":43,"mkdirp":55,"path":43}],35:[function(require,module,exports){
 (function (global){
 /**
  * Module dependencies.
  */
 
 var EventEmitter = require('events').EventEmitter;
 var Pending = require('./pending');
 var debug = require('debug')('mocha:runnable');
@@ -4015,25 +4185,27 @@ module.exports = Runnable;
  * @param {Function} fn
  * @api private
  * @param {string} title
  * @param {Function} fn
  */
 function Runnable(title, fn) {
   this.title = title;
   this.fn = fn;
+  this.body = (fn || '').toString();
   this.async = fn && fn.length;
   this.sync = !this.async;
   this._timeout = 2000;
   this._slow = 75;
   this._enableTimeouts = true;
   this.timedOut = false;
   this._trace = new Error('done() called multiple times');
   this._retries = -1;
   this._currentRetry = 0;
+  this.pending = false;
 }
 
 /**
  * Inherit from `EventEmitter.prototype`.
  */
 inherits(Runnable, EventEmitter);
 
 /**
@@ -4094,23 +4266,32 @@ Runnable.prototype.enableTimeouts = func
   debug('enableTimeouts %s', enabled);
   this._enableTimeouts = enabled;
   return this;
 };
 
 /**
  * Halt and mark as pending.
  *
- * @api private
+ * @api public
  */
 Runnable.prototype.skip = function() {
   throw new Pending();
 };
 
 /**
+ * Check if this runnable or its parent suite is marked as pending.
+ *
+ * @api private
+ */
+Runnable.prototype.isPending = function() {
+  return this.pending || (this.parent && this.parent.isPending());
+};
+
+/**
  * Set number of retries.
  *
  * @api private
  */
 Runnable.prototype.retries = function(n) {
   if (!arguments.length) {
     return this._retries;
   }
@@ -4272,17 +4453,17 @@ Runnable.prototype.run = function(fn) {
   if (this.allowUncaught) {
     callFn(this.fn);
     done();
     return;
   }
 
   // sync or promise-returning
   try {
-    if (this.pending) {
+    if (this.isPending()) {
       done();
     } else {
       callFn(this.fn);
     }
   } catch (err) {
     done(utils.getError(err));
   }
 
@@ -4833,31 +5014,26 @@ Runner.prototype.runTests = function(sui
       if (self._grep !== self._defaultGrep) {
         Runner.immediately(next);
       } else {
         next();
       }
       return;
     }
 
-    function parentPending(suite) {
-      return suite.pending || (suite.parent && parentPending(suite.parent));
-    }
-
-    // pending
-    if (test.pending || parentPending(test.parent)) {
+    if (test.isPending()) {
       self.emit('pending', test);
       self.emit('test end', test);
       return next();
     }
 
     // execute test and hook(s)
     self.emit('test', self.test = test);
     self.hookDown('beforeEach', function(err, errSuite) {
-      if (suite.pending) {
+      if (suite.isPending()) {
         self.emit('pending', test);
         self.emit('test end', test);
         return next();
       }
       if (err) {
         return hookErr(err, errSuite, false);
       }
       self.currentRunnable = self.test;
@@ -5225,17 +5401,17 @@ function extraGlobals() {
       return ['errno'];
     }
   }
 
   return [];
 }
 
 }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-},{"./pending":16,"./runnable":35,"./utils":39,"_process":51,"debug":2,"events":3}],37:[function(require,module,exports){
+},{"./pending":16,"./runnable":35,"./utils":39,"_process":58,"debug":2,"events":3}],37:[function(require,module,exports){
 /**
  * Module dependencies.
  */
 
 var EventEmitter = require('events').EventEmitter;
 var Hook = require('./hook');
 var utils = require('./utils');
 var inherits = utils.inherits;
@@ -5256,19 +5432,16 @@ exports = module.exports = Suite;
  * @api public
  * @param {Suite} parent
  * @param {string} title
  * @return {Suite}
  */
 exports.create = function(parent, title) {
   var suite = new Suite(title, parent.ctx);
   suite.parent = parent;
-  if (parent.pending) {
-    suite.pending = true;
-  }
   title = suite.fullTitle();
   parent.addSuite(suite);
   return suite;
 };
 
 /**
  * Initialize a new `Suite` with the given `title` and `ctx`.
  *
@@ -5405,25 +5578,34 @@ Suite.prototype.bail = function(bail) {
     return this._bail;
   }
   debug('bail %s', bail);
   this._bail = bail;
   return this;
 };
 
 /**
+ * Check if this suite or its parent suite is marked as pending.
+ *
+ * @api private
+ */
+Suite.prototype.isPending = function() {
+  return this.pending || (this.parent && this.parent.isPending());
+};
+
+/**
  * Run `fn(test[, done])` before running tests.
  *
  * @api private
  * @param {string} title
  * @param {Function} fn
  * @return {Suite} for chaining
  */
 Suite.prototype.beforeAll = function(title, fn) {
-  if (this.pending) {
+  if (this.isPending()) {
     return this;
   }
   if (typeof title === 'function') {
     fn = title;
     title = fn.name;
   }
   title = '"before all" hook' + (title ? ': ' + title : '');
 
@@ -5443,17 +5625,17 @@ Suite.prototype.beforeAll = function(tit
  * Run `fn(test[, done])` after running tests.
  *
  * @api private
  * @param {string} title
  * @param {Function} fn
  * @return {Suite} for chaining
  */
 Suite.prototype.afterAll = function(title, fn) {
-  if (this.pending) {
+  if (this.isPending()) {
     return this;
   }
   if (typeof title === 'function') {
     fn = title;
     title = fn.name;
   }
   title = '"after all" hook' + (title ? ': ' + title : '');
 
@@ -5473,17 +5655,17 @@ Suite.prototype.afterAll = function(titl
  * Run `fn(test[, done])` before each test case.
  *
  * @api private
  * @param {string} title
  * @param {Function} fn
  * @return {Suite} for chaining
  */
 Suite.prototype.beforeEach = function(title, fn) {
-  if (this.pending) {
+  if (this.isPending()) {
     return this;
   }
   if (typeof title === 'function') {
     fn = title;
     title = fn.name;
   }
   title = '"before each" hook' + (title ? ': ' + title : '');
 
@@ -5503,17 +5685,17 @@ Suite.prototype.beforeEach = function(ti
  * Run `fn(test[, done])` after each test case.
  *
  * @api private
  * @param {string} title
  * @param {Function} fn
  * @return {Suite} for chaining
  */
 Suite.prototype.afterEach = function(title, fn) {
-  if (this.pending) {
+  if (this.isPending()) {
     return this;
   }
   if (typeof title === 'function') {
     fn = title;
     title = fn.name;
   }
   title = '"after each" hook' + (title ? ': ' + title : '');
 
@@ -5641,17 +5823,16 @@ module.exports = Test;
  * @api private
  * @param {String} title
  * @param {Function} fn
  */
 function Test(title, fn) {
   Runnable.call(this, title, fn);
   this.pending = !fn;
   this.type = 'test';
-  this.body = (fn || '').toString();
 }
 
 /**
  * Inherit from `Runnable.prototype`.
  */
 inherits(Test, Runnable);
 
 Test.prototype.clone = function() {
@@ -5679,16 +5860,17 @@ Test.prototype.clone = function() {
 var basename = require('path').basename;
 var debug = require('debug')('mocha:watch');
 var exists = require('fs').existsSync || require('path').existsSync;
 var glob = require('glob');
 var join = require('path').join;
 var readdirSync = require('fs').readdirSync;
 var statSync = require('fs').statSync;
 var watchFile = require('fs').watchFile;
+var toISOString = require('to-iso-string');
 
 /**
  * Ignored directories.
  */
 
 var ignore = ['node_modules', '.git'];
 
 exports.inherits = require('util').inherits;
@@ -6121,17 +6303,17 @@ function jsonStringify(object, spaces, d
     // primitive types
     return _stringify(object);
   }
 
   depth = depth || 1;
   var space = spaces * depth;
   var str = isArray(object) ? '[' : '{';
   var end = isArray(object) ? ']' : '}';
-  var length = object.length || exports.keys(object).length;
+  var length = typeof object.length === 'number' ? object.length : exports.keys(object).length;
   // `.repeat()` polyfill
   function repeat(s, n) {
     return new Array(n).join(s);
   }
 
   function _stringify(val) {
     switch (exports.type(val)) {
       case 'null':
@@ -6139,25 +6321,29 @@ function jsonStringify(object, spaces, d
         val = '[' + val + ']';
         break;
       case 'array':
       case 'object':
         val = jsonStringify(val, spaces, depth + 1);
         break;
       case 'boolean':
       case 'regexp':
+      case 'symbol':
       case 'number':
         val = val === 0 && (1 / val) === -Infinity // `-0`
           ? '-0'
           : val.toString();
         break;
       case 'date':
-        var sDate = isNaN(val.getTime())        // Invalid date
-          ? val.toString()
-          : val.toISOString();
+        var sDate;
+        if (isNaN(val.getTime())) { // Invalid date
+          sDate = val.toString();
+        } else {
+          sDate = val.toISOString ? val.toISOString() : toISOString(val);
+        }
         val = '[Date: ' + sDate + ']';
         break;
       case 'buffer':
         var json = val.toJSON();
         // Based on the toJSON result
         json = json.data && json.type ? json.data : json;
         val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
         break;
@@ -6165,17 +6351,17 @@ function jsonStringify(object, spaces, d
         val = (val === '[Function]' || val === '[Circular]')
           ? val
           : JSON.stringify(val); // string
     }
     return val;
   }
 
   for (var i in object) {
-    if (!object.hasOwnProperty(i)) {
+    if (!Object.prototype.hasOwnProperty.call(object, i)) {
       continue; // not my business
     }
     --length;
     str += '\n ' + repeat(' ', space)
       + (isArray(object) ? '' : '"' + i + '": ') // key
       + _stringify(object[i])                     // value
       + (length ? ',' : '');                     // comma
   }
@@ -6264,16 +6450,17 @@ exports.canonicalize = function(value, s
           canonicalizedObj[key] = exports.canonicalize(value[key], stack);
         });
       });
       break;
     case 'date':
     case 'number':
     case 'regexp':
     case 'boolean':
+    case 'symbol':
       canonicalizedObj = value;
       break;
     default:
       canonicalizedObj = value + '';
   }
 
   return canonicalizedObj;
 };
@@ -6398,26 +6585,143 @@ exports.stackTraceFilter = function() {
         return list;
       }
 
       if (is.node && isNodeInternal(line)) {
         return list;
       }
 
       // Clean up cwd(absolute)
-      list.push(line.replace(cwd, ''));
+      if (/\(?.+:\d+:\d+\)?$/.test(line)) {
+        line = line.replace(cwd, '');
+      }
+
+      list.push(line);
       return list;
     }, []);
 
     return stack.join('\n');
   };
 };
 
 }).call(this,require('_process'),require("buffer").Buffer)
-},{"_process":51,"buffer":43,"debug":2,"fs":41,"glob":41,"path":41,"util":66}],40:[function(require,module,exports){
+},{"_process":58,"buffer":45,"debug":2,"fs":43,"glob":43,"path":43,"to-iso-string":72,"util":75}],40:[function(require,module,exports){
+'use strict'
+
+exports.toByteArray = toByteArray
+exports.fromByteArray = fromByteArray
+
+var lookup = []
+var revLookup = []
+var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
+
+function init () {
+  var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+  for (var i = 0, len = code.length; i < len; ++i) {
+    lookup[i] = code[i]
+    revLookup[code.charCodeAt(i)] = i
+  }
+
+  revLookup['-'.charCodeAt(0)] = 62
+  revLookup['_'.charCodeAt(0)] = 63
+}
+
+init()
+
+function toByteArray (b64) {
+  var i, j, l, tmp, placeHolders, arr
+  var len = b64.length
+
+  if (len % 4 > 0) {
+    throw new Error('Invalid string. Length must be a multiple of 4')
+  }
+
+  // the number of equal signs (place holders)
+  // if there are two placeholders, than the two characters before it
+  // represent one byte
+  // if there is only one, then the three characters before it represent 2 bytes
+  // this is just a cheap hack to not do indexOf twice
+  placeHolders = b64[len - 2] === '=' ? 2 : b64[len - 1] === '=' ? 1 : 0
+
+  // base64 is 4/3 + up to two characters of the original data
+  arr = new Arr(len * 3 / 4 - placeHolders)
+
+  // if there are placeholders, only get up to the last complete 4 chars
+  l = placeHolders > 0 ? len - 4 : len
+
+  var L = 0
+
+  for (i = 0, j = 0; i < l; i += 4, j += 3) {
+    tmp = (revLookup[b64.charCodeAt(i)] << 18) | (revLookup[b64.charCodeAt(i + 1)] << 12) | (revLookup[b64.charCodeAt(i + 2)] << 6) | revLookup[b64.charCodeAt(i + 3)]
+    arr[L++] = (tmp >> 16) & 0xFF
+    arr[L++] = (tmp >> 8) & 0xFF
+    arr[L++] = tmp & 0xFF
+  }
+
+  if (placeHolders === 2) {
+    tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4)
+    arr[L++] = tmp & 0xFF
+  } else if (placeHolders === 1) {
+    tmp = (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2)
+    arr[L++] = (tmp >> 8) & 0xFF
+    arr[L++] = tmp & 0xFF
+  }
+
+  return arr
+}
+
+function tripletToBase64 (num) {
+  return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]
+}
+
+function encodeChunk (uint8, start, end) {
+  var tmp
+  var output = []
+  for (var i = start; i < end; i += 3) {
+    tmp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
+    output.push(tripletToBase64(tmp))
+  }
+  return output.join('')
+}
+
+function fromByteArray (uint8) {
+  var tmp
+  var len = uint8.length
+  var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
+  var output = ''
+  var parts = []
+  var maxChunkLength = 16383 // must be multiple of 3
+
+  // go through the array every three bytes, we'll deal with trailing stuff later
+  for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
+    parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)))
+  }
+
+  // pad the end with zeros, but make sure to not forget the extra bytes
+  if (extraBytes === 1) {
+    tmp = uint8[len - 1]
+    output += lookup[tmp >> 2]
+    output += lookup[(tmp << 4) & 0x3F]
+    output += '=='
+  } else if (extraBytes === 2) {
+    tmp = (uint8[len - 2] << 8) + (uint8[len - 1])
+    output += lookup[tmp >> 10]
+    output += lookup[(tmp >> 4) & 0x3F]
+    output += lookup[(tmp << 2) & 0x3F]
+    output += '='
+  }
+
+  parts.push(output)
+
+  return parts.join('')
+}
+
+},{}],41:[function(require,module,exports){
+
+},{}],42:[function(require,module,exports){
 (function (process){
 var WritableStream = require('stream').Writable
 var inherits = require('util').inherits
 
 module.exports = BrowserStdout
 
 
 inherits(BrowserStdout, WritableStream)
@@ -6436,38 +6740,149 @@ BrowserStdout.prototype._write = functio
     console.log(output)
   } else {
     console.log(this.label+':', output)
   }
   process.nextTick(cb)
 }
 
 }).call(this,require('_process'))
-},{"_process":51,"stream":63,"util":66}],41:[function(require,module,exports){
-
-},{}],42:[function(require,module,exports){
+},{"_process":58,"stream":59,"util":75}],43:[function(require,module,exports){
 arguments[4][41][0].apply(exports,arguments)
-},{"dup":41}],43:[function(require,module,exports){
+},{"dup":41}],44:[function(require,module,exports){
+(function (global){
+'use strict';
+
+var buffer = require('buffer');
+var Buffer = buffer.Buffer;
+var SlowBuffer = buffer.SlowBuffer;
+var MAX_LEN = buffer.kMaxLength || 2147483647;
+exports.alloc = function alloc(size, fill, encoding) {
+  if (typeof Buffer.alloc === 'function') {
+    return Buffer.alloc(size, fill, encoding);
+  }
+  if (typeof encoding === 'number') {
+    throw new TypeError('encoding must not be number');
+  }
+  if (typeof size !== 'number') {
+    throw new TypeError('size must be a number');
+  }
+  if (size > MAX_LEN) {
+    throw new RangeError('size is too large');
+  }
+  var enc = encoding;
+  var _fill = fill;
+  if (_fill === undefined) {
+    enc = undefined;
+    _fill = 0;
+  }
+  var buf = new Buffer(size);
+  if (typeof _fill === 'string') {
+    var fillBuf = new Buffer(_fill, enc);
+    var flen = fillBuf.length;
+    var i = -1;
+    while (++i < size) {
+      buf[i] = fillBuf[i % flen];
+    }
+  } else {
+    buf.fill(_fill);
+  }
+  return buf;
+}
+exports.allocUnsafe = function allocUnsafe(size) {
+  if (typeof Buffer.allocUnsafe === 'function') {
+    return Buffer.allocUnsafe(size);
+  }
+  if (typeof size !== 'number') {
+    throw new TypeError('size must be a number');
+  }
+  if (size > MAX_LEN) {
+    throw new RangeError('size is too large');
+  }
+  return new Buffer(size);
+}
+exports.from = function from(value, encodingOrOffset, length) {
+  if (typeof Buffer.from === 'function' && (!global.Uint8Array || Uint8Array.from !== Buffer.from)) {
+    return Buffer.from(value, encodingOrOffset, length);
+  }
+  if (typeof value === 'number') {
+    throw new TypeError('"value" argument must not be a number');
+  }
+  if (typeof value === 'string') {
+    return new Buffer(value, encodingOrOffset);
+  }
+  if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
+    var offset = encodingOrOffset;
+    if (arguments.length === 1) {
+      return new Buffer(value);
+    }
+    if (typeof offset === 'undefined') {
+      offset = 0;
+    }
+    var len = length;
+    if (typeof len === 'undefined') {
+      len = value.byteLength - offset;
+    }
+    if (offset >= value.byteLength) {
+      throw new RangeError('\'offset\' is out of bounds');
+    }
+    if (len > value.byteLength - offset) {
+      throw new RangeError('\'length\' is out of bounds');
+    }
+    return new Buffer(value.slice(offset, offset + len));
+  }
+  if (Buffer.isBuffer(value)) {
+    var out = new Buffer(value.length);
+    value.copy(out, 0, 0, value.length);
+    return out;
+  }
+  if (value) {
+    if (Array.isArray(value) || (typeof ArrayBuffer !== 'undefined' && value.buffer instanceof ArrayBuffer) || 'length' in value) {
+      return new Buffer(value);
+    }
+    if (value.type === 'Buffer' && Array.isArray(value.data)) {
+      return new Buffer(value.data);
+    }
+  }
+
+  throw new TypeError('First argument must be a string, Buffer, ' + 'ArrayBuffer, Array, or array-like object.');
+}
+exports.allocUnsafeSlow = function allocUnsafeSlow(size) {
+  if (typeof Buffer.allocUnsafeSlow === 'function') {
+    return Buffer.allocUnsafeSlow(size);
+  }
+  if (typeof size !== 'number') {
+    throw new TypeError('size must be a number');
+  }
+  if (size >= MAX_LEN) {
+    throw new RangeError('size is too large');
+  }
+  return new SlowBuffer(size);
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"buffer":45}],45:[function(require,module,exports){
+(function (global){
 /*!
  * The buffer module from node.js, for the browser.
  *
  * @author   Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
  * @license  MIT
  */
+/* eslint-disable no-proto */
+
+'use strict'
 
 var base64 = require('base64-js')
 var ieee754 = require('ieee754')
-var isArray = require('is-array')
+var isArray = require('isarray')
 
 exports.Buffer = Buffer
 exports.SlowBuffer = SlowBuffer
 exports.INSPECT_MAX_BYTES = 50
-Buffer.poolSize = 8192 // not used by this implementation
-
-var rootParent = {}
 
 /**
  * If `Buffer.TYPED_ARRAY_SUPPORT`:
  *   === true    Use Uint8Array implementation (fastest)
  *   === false   Use Object implementation (most compatible, even IE6)
  *
  * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
  * Opera 11.6+, iOS 4.2+.
@@ -6475,255 +6890,320 @@ var rootParent = {}
  * Due to various browser bugs, sometimes the Object implementation will be used even
  * when the browser supports typed arrays.
  *
  * Note:
  *
  *   - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,
  *     See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.
  *
- *   - Safari 5-7 lacks support for changing the `Object.prototype.constructor` property
- *     on objects.
- *
  *   - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.
  *
  *   - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of
  *     incorrect length in some situations.
 
  * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
  * get the Object implementation, which is slower but behaves correctly.
  */
-Buffer.TYPED_ARRAY_SUPPORT = (function () {
-  function Bar () {}
+Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined
+  ? global.TYPED_ARRAY_SUPPORT
+  : typedArraySupport()
+
+/*
+ * Export kMaxLength after typed array support is determined.
+ */
+exports.kMaxLength = kMaxLength()
+
+function typedArraySupport () {
   try {
     var arr = new Uint8Array(1)
     arr.foo = function () { return 42 }
-    arr.constructor = Bar
     return arr.foo() === 42 && // typed array instances can be augmented
-        arr.constructor === Bar && // constructor can be set
         typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`
         arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`
   } catch (e) {
     return false
   }
-})()
+}
 
 function kMaxLength () {
   return Buffer.TYPED_ARRAY_SUPPORT
     ? 0x7fffffff
     : 0x3fffffff
 }
 
-/**
- * Class: Buffer
- * =============
- *
- * The Buffer constructor returns instances of `Uint8Array` that are augmented
- * with function properties for all the node `Buffer` API functions. We use
- * `Uint8Array` so that square bracket notation works as expected -- it returns
- * a single octet.
- *
- * By augmenting the instances, we can avoid modifying the `Uint8Array`
- * prototype.
- */
-function Buffer (arg) {
-  if (!(this instanceof Buffer)) {
-    // Avoid going through an ArgumentsAdaptorTrampoline in the common case.
-    if (arguments.length > 1) return new Buffer(arg, arguments[1])
-    return new Buffer(arg)
-  }
-
-  this.length = 0
-  this.parent = undefined
+function createBuffer (that, length) {
+  if (kMaxLength() < length) {
+    throw new RangeError('Invalid typed array length')
+  }
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    // Return an augmented `Uint8Array` instance, for best performance
+    that = new Uint8Array(length)
+    that.__proto__ = Buffer.prototype
+  } else {
+    // Fallback: Return an object instance of the Buffer class
+    if (that === null) {
+      that = new Buffer(length)
+    }
+    that.length = length
+  }
+
+  return that
+}
+
+/**
+ * The Buffer constructor returns instances of `Uint8Array` that have their
+ * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of
+ * `Uint8Array`, so the returned instances will have all the node `Buffer` methods
+ * and the `Uint8Array` methods. Square bracket notation works as expected -- it
+ * returns a single octet.
+ *
+ * The `Uint8Array` prototype remains unmodified.
+ */
+
+function Buffer (arg, encodingOrOffset, length) {
+  if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) {
+    return new Buffer(arg, encodingOrOffset, length)
+  }
 
   // Common case.
   if (typeof arg === 'number') {
-    return fromNumber(this, arg)
-  }
-
-  // Slightly less common case.
-  if (typeof arg === 'string') {
-    return fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8')
-  }
-
-  // Unusual.
-  return fromObject(this, arg)
-}
-
-function fromNumber (that, length) {
-  that = allocate(that, length < 0 ? 0 : checked(length) | 0)
+    if (typeof encodingOrOffset === 'string') {
+      throw new Error(
+        'If encoding is specified then the first argument must be a string'
+      )
+    }
+    return allocUnsafe(this, arg)
+  }
+  return from(this, arg, encodingOrOffset, length)
+}
+
+Buffer.poolSize = 8192 // not used by this implementation
+
+// TODO: Legacy, not needed anymore. Remove in next major version.
+Buffer._augment = function (arr) {
+  arr.__proto__ = Buffer.prototype
+  return arr
+}
+
+function from (that, value, encodingOrOffset, length) {
+  if (typeof value === 'number') {
+    throw new TypeError('"value" argument must not be a number')
+  }
+
+  if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) {
+    return fromArrayBuffer(that, value, encodingOrOffset, length)
+  }
+
+  if (typeof value === 'string') {
+    return fromString(that, value, encodingOrOffset)
+  }
+
+  return fromObject(that, value)
+}
+
+/**
+ * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError
+ * if value is a number.
+ * Buffer.from(str[, encoding])
+ * Buffer.from(array)
+ * Buffer.from(buffer)
+ * Buffer.from(arrayBuffer[, byteOffset[, length]])
+ **/
+Buffer.from = function (value, encodingOrOffset, length) {
+  return from(null, value, encodingOrOffset, length)
+}
+
+if (Buffer.TYPED_ARRAY_SUPPORT) {
+  Buffer.prototype.__proto__ = Uint8Array.prototype
+  Buffer.__proto__ = Uint8Array
+  if (typeof Symbol !== 'undefined' && Symbol.species &&
+      Buffer[Symbol.species] === Buffer) {
+    // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97
+    Object.defineProperty(Buffer, Symbol.species, {
+      value: null,
+      configurable: true
+    })
+  }
+}
+
+function assertSize (size) {
+  if (typeof size !== 'number') {
+    throw new TypeError('"size" argument must be a number')
+  }
+}
+
+function alloc (that, size, fill, encoding) {
+  assertSize(size)
+  if (size <= 0) {
+    return createBuffer(that, size)
+  }
+  if (fill !== undefined) {
+    // Only pay attention to encoding if it's a string. This
+    // prevents accidentally sending in a number that would
+    // be interpretted as a start offset.
+    return typeof encoding === 'string'
+      ? createBuffer(that, size).fill(fill, encoding)
+      : createBuffer(that, size).fill(fill)
+  }
+  return createBuffer(that, size)
+}
+
+/**
+ * Creates a new filled Buffer instance.
+ * alloc(size[, fill[, encoding]])
+ **/
+Buffer.alloc = function (size, fill, encoding) {
+  return alloc(null, size, fill, encoding)
+}
+
+function allocUnsafe (that, size) {
+  assertSize(size)
+  that = createBuffer(that, size < 0 ? 0 : checked(size) | 0)
   if (!Buffer.TYPED_ARRAY_SUPPORT) {
-    for (var i = 0; i < length; i++) {
+    for (var i = 0; i < size; i++) {
       that[i] = 0
     }
   }
   return that
 }
 
+/**
+ * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance.
+ * */
+Buffer.allocUnsafe = function (size) {
+  return allocUnsafe(null, size)
+}
+/**
+ * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance.
+ */
+Buffer.allocUnsafeSlow = function (size) {
+  return allocUnsafe(null, size)
+}
+
 function fromString (that, string, encoding) {
-  if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8'
-
-  // Assumption: byteLength() return value is always < kMaxLength.
+  if (typeof encoding !== 'string' || encoding === '') {
+    encoding = 'utf8'
+  }
+
+  if (!Buffer.isEncoding(encoding)) {
+    throw new TypeError('"encoding" must be a valid string encoding')
+  }
+
   var length = byteLength(string, encoding) | 0
-  that = allocate(that, length)
+  that = createBuffer(that, length)
 
   that.write(string, encoding)
   return that
 }
 
-function fromObject (that, object) {
-  if (Buffer.isBuffer(object)) return fromBuffer(that, object)
-
-  if (isArray(object)) return fromArray(that, object)
-
-  if (object == null) {
-    throw new TypeError('must start with number, buffer, array or string')
-  }
-
-  if (typeof ArrayBuffer !== 'undefined') {
-    if (object.buffer instanceof ArrayBuffer) {
-      return fromTypedArray(that, object)
-    }
-    if (object instanceof ArrayBuffer) {
-      return fromArrayBuffer(that, object)
-    }
-  }
-
-  if (object.length) return fromArrayLike(that, object)
-
-  return fromJsonObject(that, object)
-}
-
-function fromBuffer (that, buffer) {
-  var length = checked(buffer.length) | 0
-  that = allocate(that, length)
-  buffer.copy(that, 0, 0, length)
-  return that
-}
-
-function fromArray (that, array) {
-  var length = checked(array.length) | 0
-  that = allocate(that, length)
-  for (var i = 0; i < length; i += 1) {
-    that[i] = array[i] & 255
-  }
-  return that
-}
-
-// Duplicate of fromArray() to keep fromArray() monomorphic.
-function fromTypedArray (that, array) {
-  var length = checked(array.length) | 0
-  that = allocate(that, length)
-  // Truncating the elements is probably not what people expect from typed
-  // arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior
-  // of the old Buffer constructor.
-  for (var i = 0; i < length; i += 1) {
-    that[i] = array[i] & 255
-  }
-  return that
-}
-
-function fromArrayBuffer (that, array) {
-  if (Buffer.TYPED_ARRAY_SUPPORT) {
-    // Return an augmented `Uint8Array` instance, for best performance
-    array.byteLength
-    that = Buffer._augment(new Uint8Array(array))
-  } else {
-    // Fallback: Return an object instance of the Buffer class
-    that = fromTypedArray(that, new Uint8Array(array))
-  }
-  return that
-}
-
 function fromArrayLike (that, array) {
   var length = checked(array.length) | 0
-  that = allocate(that, length)
+  that = createBuffer(that, length)
   for (var i = 0; i < length; i += 1) {
     that[i] = array[i] & 255
   }
   return that
 }
 
-// Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object.
-// Returns a zero-length buffer for inputs that don't conform to the spec.
-function fromJsonObject (that, object) {
-  var array
-  var length = 0
-
-  if (object.type === 'Buffer' && isArray(object.data)) {
-    array = object.data
-    length = checked(array.length) | 0
-  }
-  that = allocate(that, length)
-
-  for (var i = 0; i < length; i += 1) {
-    that[i] = array[i] & 255
+function fromArrayBuffer (that, array, byteOffset, length) {
+  array.byteLength // this throws if `array` is not a valid ArrayBuffer
+
+  if (byteOffset < 0 || array.byteLength < byteOffset) {
+    throw new RangeError('\'offset\' is out of bounds')
+  }
+
+  if (array.byteLength < byteOffset + (length || 0)) {
+    throw new RangeError('\'length\' is out of bounds')
+  }
+
+  if (length === undefined) {
+    array = new Uint8Array(array, byteOffset)
+  } else {
+    array = new Uint8Array(array, byteOffset, length)
+  }
+
+  if (Buffer.TYPED_ARRAY_SUPPORT) {
+    // Return an augmented `Uint8Array` instance, for best performance
+    that = array
+    that.__proto__ = Buffer.prototype
+  } else {
+    // Fallback: Return an object instance of the Buffer class
+    that = fromArrayLike(that, array)
   }
   return that
 }
 
-function allocate (that, length) {
-  if (Buffer.TYPED_ARRAY_SUPPORT) {
-    // Return an augmented `Uint8Array` instance, for best performance
-    that = Buffer._augment(new Uint8Array(length))
-  } else {
-    // Fallback: Return an object instance of the Buffer class
-    that.length = length
-    that._isBuffer = true
-  }
-
-  var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1
-  if (fromPool) that.parent = rootParent
-
-  return that
+function fromObject (that, obj) {
+  if (Buffer.isBuffer(obj)) {
+    var len = checked(obj.length) | 0
+    that = createBuffer(that, len)
+
+    if (that.length === 0) {
+      return that
+    }
+
+    obj.copy(that, 0, 0, len)
+    return that
+  }
+
+  if (obj) {
+    if ((typeof ArrayBuffer !== 'undefined' &&
+        obj.buffer instanceof ArrayBuffer) || 'length' in obj) {
+      if (typeof obj.length !== 'number' || isnan(obj.length)) {
+        return createBuffer(that, 0)
+      }
+      return fromArrayLike(that, obj)
+    }
+
+    if (obj.type === 'Buffer' && isArray(obj.data)) {
+      return fromArrayLike(that, obj.data)
+    }
+  }
+
+  throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.')
 }
 
 function checked (length) {
   // Note: cannot use `length < kMaxLength` here because that fails when
   // length is NaN (which is otherwise coerced to zero.)
   if (length >= kMaxLength()) {
     throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
                          'size: 0x' + kMaxLength().toString(16) + ' bytes')
   }
   return length | 0
 }
 
-function SlowBuffer (subject, encoding) {
-  if (!(this instanceof SlowBuffer)) return new SlowBuffer(subject, encoding)
-
-  var buf = new Buffer(subject, encoding)
-  delete buf.parent
-  return buf
+function SlowBuffer (length) {
+  if (+length != length) { // eslint-disable-line eqeqeq
+    length = 0
+  }
+  return Buffer.alloc(+length)
 }
 
 Buffer.isBuffer = function isBuffer (b) {
   return !!(b != null && b._isBuffer)
 }
 
 Buffer.compare = function compare (a, b) {
   if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
     throw new TypeError('Arguments must be Buffers')
   }
 
   if (a === b) return 0
 
   var x = a.length
   var y = b.length
 
-  var i = 0
-  var len = Math.min(x, y)
-  while (i < len) {
-    if (a[i] !== b[i]) break
-
-    ++i
-  }
-
-  if (i !== len) {
-    x = a[i]
-    y = b[i]
+  for (var i = 0, len = Math.min(x, y); i < len; ++i) {
+    if (a[i] !== b[i]) {
+      x = a[i]
+      y = b[i]
+      break
+    }
   }
 
   if (x < y) return -1
   if (y < x) return 1
   return 0
 }
 
 Buffer.isEncoding = function isEncoding (encoding) {
@@ -6741,58 +7221,73 @@ Buffer.isEncoding = function isEncoding 
     case 'utf-16le':
       return true
     default:
       return false
   }
 }
 
 Buffer.concat = function concat (list, length) {
-  if (!isArray(list)) throw new TypeError('list argument must be an Array of Buffers.')
+  if (!isArray(list)) {
+    throw new TypeError('"list" argument must be an Array of Buffers')
+  }
 
   if (list.length === 0) {
-    return new Buffer(0)
+    return Buffer.alloc(0)
   }
 
   var i
   if (length === undefined) {
     length = 0
     for (i = 0; i < list.length; i++) {
       length += list[i].length
     }
   }
 
-  var buf = new Buffer(length)
+  var buffer = Buffer.allocUnsafe(length)
   var pos = 0
   for (i = 0; i < list.length; i++) {
-    var item = list[i]
-    item.copy(buf, pos)
-    pos += item.length
-  }
-  return buf
+    var buf = list[i]
+    if (!Buffer.isBuffer(buf)) {
+      throw new TypeError('"list" argument must be an Array of Buffers')
+    }
+    buf.copy(buffer, pos)
+    pos += buf.length
+  }
+  return buffer
 }
 
 function byteLength (string, encoding) {
-  if (typeof string !== 'string') string = '' + string
+  if (Buffer.isBuffer(string)) {
+    return string.length
+  }
+  if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' &&
+      (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) {
+    return string.byteLength
+  }
+  if (typeof string !== 'string') {
+    string = '' + string
+  }
 
   var len = string.length
   if (len === 0) return 0
 
   // Use a for loop to avoid recursion
   var loweredCase = false
   for (;;) {
     switch (encoding) {
       case 'ascii':
       case 'binary':
       // Deprecated
       case 'raw':
       case 'raws':
         return len
       case 'utf8':
       case 'utf-8':
+      case undefined:
         return utf8ToBytes(string).length
       case 'ucs2':
       case 'ucs-2':
       case 'utf16le':
       case 'utf-16le':
         return len * 2
       case 'hex':
         return len >>> 1
@@ -6802,30 +7297,52 @@ function byteLength (string, encoding) {
         if (loweredCase) return utf8ToBytes(string).length // assume utf8
         encoding = ('' + encoding).toLowerCase()
         loweredCase = true
     }
   }
 }
 Buffer.byteLength = byteLength
 
-// pre-set for values that may exist in the future
-Buffer.prototype.length = undefined
-Buffer.prototype.parent = undefined
-
 function slowToString (encoding, start, end) {
   var loweredCase = false
 
-  start = start | 0
-  end = end === undefined || end === Infinity ? this.length : end | 0
+  // No need to verify that "this.length <= MAX_UINT32" since it's a read-only
+  // property of a typed array.
+
+  // This behaves neither like String nor Uint8Array in that we set start/end
+  // to their upper/lower bounds if the value passed is out of range.
+  // undefined is handled specially as per ECMA-262 6th Edition,
+  // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization.
+  if (start === undefined || start < 0) {
+    start = 0
+  }
+  // Return early if start > this.length. Done here to prevent potential uint32
+  // coercion fail below.
+  if (start > this.length) {
+    return ''
+  }
+
+  if (end === undefined || end > this.length) {
+    end = this.length
+  }
+
+  if (end <= 0) {
+    return ''
+  }
+
+  // Force coersion to uint32. This will also coerce falsey/NaN values to 0.
+  end >>>= 0
+  start >>>= 0
+
+  if (end <= start) {
+    return ''
+  }
 
   if (!encoding) encoding = 'utf8'
-  if (start < 0) start = 0
-  if (end > this.length) end = this.length
-  if (end <= start) return ''
 
   while (true) {
     switch (encoding) {
       case 'hex':
         return hexSlice(this, start, end)
 
       case 'utf8':
       case 'utf-8':
@@ -6849,16 +7366,49 @@ function slowToString (encoding, start, 
       default:
         if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
         encoding = (encoding + '').toLowerCase()
         loweredCase = true
     }
   }
 }
 
+// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect
+// Buffer instances.
+Buffer.prototype._isBuffer = true
+
+function swap (b, n, m) {
+  var i = b[n]
+  b[n] = b[m]
+  b[m] = i
+}
+
+Buffer.prototype.swap16 = function swap16 () {
+  var len = this.length
+  if (len % 2 !== 0) {
+    throw new RangeError('Buffer size must be a multiple of 16-bits')
+  }
+  for (var i = 0; i < len; i += 2) {
+    swap(this, i, i + 1)
+  }
+  return this
+}
+
+Buffer.prototype.swap32 = function swap32 () {
+  var len = this.length
+  if (len % 4 !== 0) {
+    throw new RangeError('Buffer size must be a multiple of 32-bits')
+  }
+  for (var i = 0; i < len; i += 4) {
+    swap(this, i, i + 3)
+    swap(this, i + 1, i + 2)
+  }
+  return this
+}
+
 Buffer.prototype.toString = function toString () {
   var length = this.length | 0
   if (length === 0) return ''
   if (arguments.length === 0) return utf8Slice(this, 0, length)
   return slowToString.apply(this, arguments)
 }
 
 Buffer.prototype.equals = function equals (b) {
@@ -6872,73 +7422,155 @@ Buffer.prototype.inspect = function insp
   var max = exports.INSPECT_MAX_BYTES
   if (this.length > 0) {
     str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')
     if (this.length > max) str += ' ... '
   }
   return '<Buffer ' + str + '>'
 }
 
-Buffer.prototype.compare = function compare (b) {
-  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
-  if (this === b) return 0
-  return Buffer.compare(this, b)
-}
-
-Buffer.prototype.indexOf = function indexOf (val, byteOffset) {
-  if (byteOffset > 0x7fffffff) byteOffset = 0x7fffffff
-  else if (byteOffset < -0x80000000) byteOffset = -0x80000000
+Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) {
+  if (!Buffer.isBuffer(target)) {
+    throw new TypeError('Argument must be a Buffer')
+  }
+
+  if (start === undefined) {
+    start = 0
+  }
+  if (end === undefined) {
+    end = target ? target.length : 0
+  }
+  if (thisStart === undefined) {
+    thisStart = 0
+  }
+  if (thisEnd === undefined) {
+    thisEnd = this.length
+  }
+
+  if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) {
+    throw new RangeError('out of range index')
+  }
+
+  if (thisStart >= thisEnd && start >= end) {
+    return 0
+  }
+  if (thisStart >= thisEnd) {
+    return -1
+  }
+  if (start >= end) {
+    return 1
+  }
+
+  start >>>= 0
+  end >>>= 0
+  thisStart >>>= 0
+  thisEnd >>>= 0
+
+  if (this === target) return 0
+
+  var x = thisEnd - thisStart
+  var y = end - start
+  var len = Math.min(x, y)
+
+  var thisCopy = this.slice(thisStart, thisEnd)
+  var targetCopy = target.slice(start, end)
+
+  for (var i = 0; i < len; ++i) {
+    if (thisCopy[i] !== targetCopy[i]) {
+      x = thisCopy[i]
+      y = targetCopy[i]
+      break
+    }
+  }
+
+  if (x < y) return -1
+  if (y < x) return 1
+  return 0
+}
+
+function arrayIndexOf (arr, val, byteOffset, encoding) {
+  var indexSize = 1
+  var arrLength = arr.length
+  var valLength = val.length
+
+  if (encoding !== undefined) {
+    encoding = String(encoding).toLowerCase()
+    if (encoding === 'ucs2' || encoding === 'ucs-2' ||
+        encoding === 'utf16le' || encoding === 'utf-16le') {
+      if (arr.length < 2 || val.length < 2) {
+        return -1
+      }
+      indexSize = 2
+      arrLength /= 2
+      valLength /= 2
+      byteOffset /= 2
+    }
+  }
+
+  function read (buf, i) {
+    if (indexSize === 1) {
+      return buf[i]
+    } else {
+      return buf.readUInt16BE(i * indexSize)
+    }
+  }
+
+  var foundIndex = -1
+  for (var i = 0; byteOffset + i < arrLength; i++) {
+    if (read(arr, byteOffset + i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) {
+      if (foundIndex === -1) foundIndex = i
+      if (i - foundIndex + 1 === valLength) return (byteOffset + foundIndex) * indexSize
+    } else {
+      if (foundIndex !== -1) i -= i - foundIndex
+      foundIndex = -1
+    }
+  }
+  return -1
+}
+
+Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) {
+  if (typeof byteOffset === 'string') {
+    encoding = byteOffset
+    byteOffset = 0
+  } else if (byteOffset > 0x7fffffff) {
+    byteOffset = 0x7fffffff
+  } else if (byteOffset < -0x80000000) {
+    byteOffset = -0x80000000
+  }
   byteOffset >>= 0
 
   if (this.length === 0) return -1
   if (byteOffset >= this.length) return -1
 
   // Negative offsets start from the end of the buffer
   if (byteOffset < 0) byteOffset = Math.max(this.length + byteOffset, 0)
 
   if (typeof val === 'string') {
-    if (val.length === 0) return -1 // special case: looking for empty string always fails
-    return String.prototype.indexOf.call(this, val, byteOffset)
-  }
+    val = Buffer.from(val, encoding)
+  }
+
   if (Buffer.isBuffer(val)) {
-    return arrayIndexOf(this, val, byteOffset)
+    // special case: looking for empty string/buffer always fails
+    if (val.length === 0) {
+      return -1
+    }
+    return arrayIndexOf(this, val, byteOffset, encoding)
   }
   if (typeof val === 'number') {
     if (Buffer.TYPED_ARRAY_SUPPORT && Uint8Array.prototype.indexOf === 'function') {
       return Uint8Array.prototype.indexOf.call(this, val, byteOffset)
     }
-    return arrayIndexOf(this, [ val ], byteOffset)
-  }
-
-  function arrayIndexOf (arr, val, byteOffset) {
-    var foundIndex = -1
-    for (var i = 0; byteOffset + i < arr.length; i++) {
-      if (arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex]) {
-        if (foundIndex === -1) foundIndex = i
-        if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex
-      } else {
-        foundIndex = -1
-      }
-    }
-    return -1
+    return arrayIndexOf(this, [ val ], byteOffset, encoding)
   }
 
   throw new TypeError('val must be string, number or Buffer')
 }
 
-// `get` is deprecated
-Buffer.prototype.get = function get (offset) {
-  console.log('.get() is deprecated. Access using array indexes instead.')
-  return this.readUInt8(offset)
-}
-
-// `set` is deprecated
-Buffer.prototype.set = function set (v, offset) {
-  console.log('.set() is deprecated. Access using array indexes instead.')
-  return this.writeUInt8(v, offset)
+Buffer.prototype.includes = function includes (val, byteOffset, encoding) {
+  return this.indexOf(val, byteOffset, encoding) !== -1
 }
 
 function hexWrite (buf, string, offset, length) {
   offset = Number(offset) || 0
   var remaining = buf.length - offset
   if (!length) {
     length = remaining
   } else {
@@ -6952,17 +7584,17 @@ function hexWrite (buf, string, offset, 
   var strLen = string.length
   if (strLen % 2 !== 0) throw new Error('Invalid hex string')
 
   if (length > strLen / 2) {
     length = strLen / 2
   }
   for (var i = 0; i < length; i++) {
     var parsed = parseInt(string.substr(i * 2, 2), 16)
-    if (isNaN(parsed)) throw new Error('Invalid hex string')
+    if (isNaN(parsed)) return i
     buf[offset + i] = parsed
   }
   return i
 }
 
 function utf8Write (buf, string, offset, length) {
   return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
 }
@@ -7001,27 +7633,26 @@ Buffer.prototype.write = function write 
       length = length | 0
       if (encoding === undefined) encoding = 'utf8'
     } else {
       encoding = length
       length = undefined
     }
   // legacy write(string, encoding, offset, length) - remove in v0.13
   } else {
-    var swap = encoding
-    encoding = offset
-    offset = length | 0
-    length = swap
+    throw new Error(
+      'Buffer.write(string, encoding, offset[, length]) is no longer supported'
+    )
   }
 
   var remaining = this.length - offset
   if (length === undefined || length > remaining) length = remaining
 
   if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {
-    throw new RangeError('attempt to write outside buffer bounds')
+    throw new RangeError('Attempt to write outside buffer bounds')
   }
 
   if (!encoding) encoding = 'utf8'
 
   var loweredCase = false
   for (;;) {
     switch (encoding) {
       case 'hex':
@@ -7226,27 +7857,26 @@ Buffer.prototype.slice = function slice 
   } else if (end > len) {
     end = len
   }
 
   if (end < start) end = start
 
   var newBuf
   if (Buffer.TYPED_ARRAY_SUPPORT) {
-    newBuf = Buffer._augment(this.subarray(start, end))
+    newBuf = this.subarray(start, end)
+    newBuf.__proto__ = Buffer.prototype
   } else {
     var sliceLen = end - start
     newBuf = new Buffer(sliceLen, undefined)
     for (var i = 0; i < sliceLen; i++) {
       newBuf[i] = this[i + start]
     }
   }
 
-  if (newBuf.length) newBuf.parent = this.parent || this
-
   return newBuf
 }
 
 /*
  * Need to make sure that buffer isn't trying to write out of bounds.
  */
 function checkOffset (offset, ext, length) {
   if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
@@ -7405,90 +8035,96 @@ Buffer.prototype.readDoubleLE = function
 }
 
 Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
   if (!noAssert) checkOffset(offset, 8, this.length)
   return ieee754.read(this, offset, false, 52, 8)
 }
 
 function checkInt (buf, value, offset, ext, max, min) {
-  if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance')
-  if (value > max || value < min) throw new RangeError('value is out of bounds')
-  if (offset + ext > buf.length) throw new RangeError('index out of range')
+  if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance')
+  if (value > max || value < min) throw new RangeError('"value" argument is out of bounds')
+  if (offset + ext > buf.length) throw new RangeError('Index out of range')
 }
 
 Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
   value = +value
   offset = offset | 0
   byteLength = byteLength | 0
-  if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)
+  if (!noAssert) {
+    var maxBytes = Math.pow(2, 8 * byteLength) - 1
+    checkInt(this, value, offset, byteLength, maxBytes, 0)
+  }
 
   var mul = 1
   var i = 0
   this[offset] = value & 0xFF
   while (++i < byteLength && (mul *= 0x100)) {
     this[offset + i] = (value / mul) & 0xFF
   }
 
   return offset + byteLength
 }
 
 Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
   value = +value
   offset = offset | 0
   byteLength = byteLength | 0
-  if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)
+  if (!noAssert) {
+    var maxBytes = Math.pow(2, 8 * byteLength) - 1
+    checkInt(this, value, offset, byteLength, maxBytes, 0)
+  }
 
   var i = byteLength - 1
   var mul = 1
   this[offset + i] = value & 0xFF
   while (--i >= 0 && (mul *= 0x100)) {
     this[offset + i] = (value / mul) & 0xFF
   }
 
   return offset + byteLength
 }
 
 Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
   value = +value
   offset = offset | 0
   if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
   if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
-  this[offset] = value
+  this[offset] = (value & 0xff)
   return offset + 1
 }
 
 function objectWriteUInt16 (buf, value, offset, littleEndian) {
   if (value < 0) value = 0xffff + value + 1
   for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) {
     buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>
       (littleEndian ? i : 1 - i) * 8
   }
 }
 
 Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
   value = +value
   offset = offset | 0
   if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
   if (Buffer.TYPED_ARRAY_SUPPORT) {
-    this[offset] = value
+    this[offset] = (value & 0xff)
     this[offset + 1] = (value >>> 8)
   } else {
     objectWriteUInt16(this, value, offset, true)
   }
   return offset + 2
 }
 
 Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
   value = +value
   offset = offset | 0
   if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
   if (Buffer.TYPED_ARRAY_SUPPORT) {
     this[offset] = (value >>> 8)
-    this[offset + 1] = value
+    this[offset + 1] = (value & 0xff)
   } else {
     objectWriteUInt16(this, value, offset, false)
   }
   return offset + 2
 }
 
 function objectWriteUInt32 (buf, value, offset, littleEndian) {
   if (value < 0) value = 0xffffffff + value + 1
@@ -7500,32 +8136,32 @@ function objectWriteUInt32 (buf, value, 
 Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
   value = +value
   offset = offset | 0
   if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
   if (Buffer.TYPED_ARRAY_SUPPORT) {
     this[offset + 3] = (value >>> 24)
     this[offset + 2] = (value >>> 16)
     this[offset + 1] = (value >>> 8)
-    this[offset] = value
+    this[offset] = (value & 0xff)
   } else {
     objectWriteUInt32(this, value, offset, true)
   }
   return offset + 4
 }
 
 Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
   value = +value
   offset = offset | 0
   if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
   if (Buffer.TYPED_ARRAY_SUPPORT) {
     this[offset] = (value >>> 24)
     this[offset + 1] = (value >>> 16)
     this[offset + 2] = (value >>> 8)
-    this[offset + 3] = value
+    this[offset + 3] = (value & 0xff)
   } else {
     objectWriteUInt32(this, value, offset, false)
   }
   return offset + 4
 }
 
 Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
   value = +value
@@ -7533,19 +8169,22 @@ Buffer.prototype.writeIntLE = function w
   if (!noAssert) {
     var limit = Math.pow(2, 8 * byteLength - 1)
 
     checkInt(this, value, offset, byteLength, limit - 1, -limit)
   }
 
   var i = 0
   var mul = 1
-  var sub = value < 0 ? 1 : 0
+  var sub = 0
   this[offset] = value & 0xFF
   while (++i < byteLength && (mul *= 0x100)) {
+    if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) {
+      sub = 1
+    }
     this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
   }
 
   return offset + byteLength
 }
 
 Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
   value = +value
@@ -7553,67 +8192,70 @@ Buffer.prototype.writeIntBE = function w
   if (!noAssert) {
     var limit = Math.pow(2, 8 * byteLength - 1)
 
     checkInt(this, value, offset, byteLength, limit - 1, -limit)
   }
 
   var i = byteLength - 1
   var mul = 1
-  var sub = value < 0 ? 1 : 0
+  var sub = 0
   this[offset + i] = value & 0xFF
   while (--i >= 0 && (mul *= 0x100)) {
+    if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) {
+      sub = 1
+    }
     this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
   }
 
   return offset + byteLength
 }
 
 Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
   value = +value
   offset = offset | 0
   if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
   if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
   if (value < 0) value = 0xff + value + 1
-  this[offset] = value
+  this[offset] = (value & 0xff)
   return offset + 1
 }
 
 Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
   value = +value
   offset = offset | 0
   if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
   if (Buffer.TYPED_ARRAY_SUPPORT) {
-    this[offset] = value
+    this[offset] = (value & 0xff)
     this[offset + 1] = (value >>> 8)
   } else {
     objectWriteUInt16(this, value, offset, true)
   }
   return offset + 2
 }
 
 Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
   value = +value
   offset = offset | 0
   if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
   if (Buffer.TYPED_ARRAY_SUPPORT) {
     this[offset] = (value >>> 8)
-    this[offset + 1] = value
+    this[offset + 1] = (value & 0xff)
   } else {
     objectWriteUInt16(this, value, offset, false)
   }
   return offset + 2
 }
 
 Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
   value = +value
   offset = offset | 0
   if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
   if (Buffer.TYPED_ARRAY_SUPPORT) {
-    this[offset] = value
+    this[offset] = (value & 0xff)
     this[offset + 1] = (value >>> 8)
     this[offset + 2] = (value >>> 16)
     this[offset + 3] = (value >>> 24)
   } else {
     objectWriteUInt32(this, value, offset, true)
   }
   return offset + 4
 }
@@ -7622,27 +8264,26 @@ Buffer.prototype.writeInt32BE = function
   value = +value
   offset = offset | 0
   if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
   if (value < 0) value = 0xffffffff + value + 1
   if (Buffer.TYPED_ARRAY_SUPPORT) {
     this[offset] = (value >>> 24)
     this[offset + 1] = (value >>> 16)
     this[offset + 2] = (value >>> 8)
-    this[offset + 3] = value
+    this[offset + 3] = (value & 0xff)
   } else {
     objectWriteUInt32(this, value, offset, false)
   }
   return offset + 4
 }
 
 function checkIEEE754 (buf, value, offset, ext, max, min) {
-  if (value > max || value < min) throw new RangeError('value is out of bounds')
-  if (offset + ext > buf.length) throw new RangeError('index out of range')
-  if (offset < 0) throw new RangeError('index out of range')
+  if (offset + ext > buf.length) throw new RangeError('Index out of range')
+  if (offset < 0) throw new RangeError('Index out of range')
 }
 
 function writeFloat (buf, value, offset, littleEndian, noAssert) {
   if (!noAssert) {
     checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
   }
   ieee754.write(buf, value, offset, littleEndian, 23, 4)
   return offset + 4
@@ -7706,144 +8347,92 @@ Buffer.prototype.copy = function copy (t
       target[i + targetStart] = this[i + start]
     }
   } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {
     // ascending copy from start
     for (i = 0; i < len; i++) {
       target[i + targetStart] = this[i + start]
     }
   } else {
-    target._set(this.subarray(start, start + len), targetStart)
+    Uint8Array.prototype.set.call(
+      target,
+      this.subarray(start, start + len),
+      targetStart
+    )
   }
 
   return len
 }
 
-// fill(value, start=0, end=buffer.length)
-Buffer.prototype.fill = function fill (value, start, end) {
-  if (!value) value = 0
-  if (!start) start = 0
-  if (!end) end = this.length
-
-  if (end < start) throw new RangeError('end < start')
-
-  // Fill 0 bytes; we're done
-  if (end === start) return
-  if (this.length === 0) return
-
-  if (start < 0 || start >= this.length) throw new RangeError('start out of bounds')
-  if (end < 0 || end > this.length) throw new RangeError('end out of bounds')
+// Usage:
+//    buffer.fill(number[, offset[, end]])
+//    buffer.fill(buffer[, offset[, end]])
+//    buffer.fill(string[, offset[, end]][, encoding])
+Buffer.prototype.fill = function fill (val, start, end, encoding) {
+  // Handle string cases:
+  if (typeof val === 'string') {
+    if (typeof start === 'string') {
+      encoding = start
+      start = 0
+      end = this.length
+    } else if (typeof end === 'string') {
+      encoding = end
+      end = this.length
+    }
+    if (val.length === 1) {
+      var code = val.charCodeAt(0)
+      if (code < 256) {
+        val = code
+      }
+    }
+    if (encoding !== undefined && typeof encoding !== 'string') {
+      throw new TypeError('encoding must be a string')
+    }
+    if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) {
+      throw new TypeError('Unknown encoding: ' + encoding)
+    }
+  } else if (typeof val === 'number') {
+    val = val & 255
+  }
+
+  // Invalid ranges are not set to a default, so can range check early.
+  if (start < 0 || this.length < start || this.length < end) {
+    throw new RangeError('Out of range index')
+  }
+
+  if (end <= start) {
+    return this
+  }
+
+  start = start >>> 0
+  end = end === undefined ? this.length : end >>> 0
+
+  if (!val) val = 0
 
   var i
-  if (typeof value === 'number') {
+  if (typeof val === 'number') {
     for (i = start; i < end; i++) {
-      this[i] = value
+      this[i] = val
     }
   } else {
-    var bytes = utf8ToBytes(value.toString())
+    var bytes = Buffer.isBuffer(val)
+      ? val
+      : utf8ToBytes(new Buffer(val, encoding).toString())
     var len = bytes.length
-    for (i = start; i < end; i++) {
-      this[i] = bytes[i % len]
+    for (i = 0; i < end - start; i++) {
+      this[i + start] = bytes[i % len]
     }
   }
 
   return this
 }
 
-/**
- * Creates a new `ArrayBuffer` with the *copied* memory of the buffer instance.
- * Added in Node 0.12. Only available in browsers that support ArrayBuffer.
- */
-Buffer.prototype.toArrayBuffer = function toArrayBuffer () {
-  if (typeof Uint8Array !== 'undefined') {
-    if (Buffer.TYPED_ARRAY_SUPPORT) {
-      return (new Buffer(this)).buffer
-    } else {
-      var buf = new Uint8Array(this.length)
-      for (var i = 0, len = buf.length; i < len; i += 1) {
-        buf[i] = this[i]
-      }
-      return buf.buffer
-    }
-  } else {
-    throw new TypeError('Buffer.toArrayBuffer not supported in this browser')
-  }
-}
-
 // HELPER FUNCTIONS
 // ================
 
-var BP = Buffer.prototype
-
-/**
- * Augment a Uint8Array *instance* (not the Uint8Array class!) with Buffer methods
- */
-Buffer._augment = function _augment (arr) {
-  arr.constructor = Buffer
-  arr._isBuffer = true
-
-  // save reference to original Uint8Array set method before overwriting
-  arr._set = arr.set
-
-  // deprecated
-  arr.get = BP.get
-  arr.set = BP.set
-
-  arr.write = BP.write
-  arr.toString = BP.toString
-  arr.toLocaleString = BP.toString
-  arr.toJSON = BP.toJSON
-  arr.equals = BP.equals
-  arr.compare = BP.compare
-  arr.indexOf = BP.indexOf
-  arr.copy = BP.copy
-  arr.slice = BP.slice
-  arr.readUIntLE = BP.readUIntLE
-  arr.readUIntBE = BP.readUIntBE
-  arr.readUInt8 = BP.readUInt8
-  arr.readUInt16LE = BP.readUInt16LE
-  arr.readUInt16BE = BP.readUInt16BE
-  arr.readUInt32LE = BP.readUInt32LE
-  arr.readUInt32BE = BP.readUInt32BE
-  arr.readIntLE = BP.readIntLE
-  arr.readIntBE = BP.readIntBE
-  arr.readInt8 = BP.readInt8
-  arr.readInt16LE = BP.readInt16LE
-  arr.readInt16BE = BP.readInt16BE
-  arr.readInt32LE = BP.readInt32LE
-  arr.readInt32BE = BP.readInt32BE
-  arr.readFloatLE = BP.readFloatLE
-  arr.readFloatBE = BP.readFloatBE
-  arr.readDoubleLE = BP.readDoubleLE
-  arr.readDoubleBE = BP.readDoubleBE
-  arr.writeUInt8 = BP.writeUInt8
-  arr.writeUIntLE = BP.writeUIntLE
-  arr.writeUIntBE = BP.writeUIntBE
-  arr.writeUInt16LE = BP.writeUInt16LE
-  arr.writeUInt16BE = BP.writeUInt16BE
-  arr.writeUInt32LE = BP.writeUInt32LE
-  arr.writeUInt32BE = BP.writeUInt32BE
-  arr.writeIntLE = BP.writeIntLE
-  arr.writeIntBE = BP.writeIntBE
-  arr.writeInt8 = BP.writeInt8
-  arr.writeInt16LE = BP.writeInt16LE
-  arr.writeInt16BE = BP.writeInt16BE
-  arr.writeInt32LE = BP.writeInt32LE
-  arr.writeInt32BE = BP.writeInt32BE
-  arr.writeFloatLE = BP.writeFloatLE
-  arr.writeFloatBE = BP.writeFloatBE
-  arr.writeDoubleLE = BP.writeDoubleLE
-  arr.writeDoubleBE = BP.writeDoubleBE
-  arr.fill = BP.fill
-  arr.inspect = BP.inspect
-  arr.toArrayBuffer = BP.toArrayBuffer
-
-  return arr
-}
-
 var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g
 
 function base64clean (str) {
   // Node strips out invalid characters like \n and \t from the string, base64-js does not
   str = stringtrim(str).replace(INVALID_BASE64_RE, '')
   // Node converts strings with length < 2 to ''
   if (str.length < 2) return ''
   // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
@@ -7897,17 +8486,17 @@ function utf8ToBytes (string, units) {
       // 2 leads in a row
       if (codePoint < 0xDC00) {
         if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
         leadSurrogate = codePoint
         continue
       }
 
       // valid surrogate pair
-      codePoint = leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00 | 0x10000
+      codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000
     } else if (leadSurrogate) {
       // valid bmp char, but last char was a lead
       if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
     }
 
     leadSurrogate = null
 
     // encode utf8
@@ -7975,2528 +8564,29 @@ function base64ToBytes (str) {
 function blitBuffer (src, dst, offset, length) {
   for (var i = 0; i < length; i++) {
     if ((i + offset >= dst.length) || (i >= src.length)) break
     dst[i + offset] = src[i]
   }
   return i
 }
 
-},{"base64-js":44,"ieee754":45,"is-array":46}],44:[function(require,module,exports){
-var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
-
-;(function (exports) {
-	'use strict';
-
-  var Arr = (typeof Uint8Array !== 'undefined')
-    ? Uint8Array
-    : Array
-
-	var PLUS   = '+'.charCodeAt(0)
-	var SLASH  = '/'.charCodeAt(0)
-	var NUMBER = '0'.charCodeAt(0)
-	var LOWER  = 'a'.charCodeAt(0)
-	var UPPER  = 'A'.charCodeAt(0)
-	var PLUS_URL_SAFE = '-'.charCodeAt(0)
-	var SLASH_URL_SAFE = '_'.charCodeAt(0)
-
-	function decode (elt) {
-		var code = elt.charCodeAt(0)
-		if (code === PLUS ||
-		    code === PLUS_URL_SAFE)
-			return 62 // '+'
-		if (code === SLASH ||
-		    code === SLASH_URL_SAFE)
-			return 63 // '/'
-		if (code < NUMBER)
-			return -1 //no match
-		if (code < NUMBER + 10)
-			return code - NUMBER + 26 + 26
-		if (code < UPPER + 26)
-			return code - UPPER
-		if (code < LOWER + 26)
-			return code - LOWER + 26
-	}
-
-	function b64ToByteArray (b64) {
-		var i, j, l, tmp, placeHolders, arr
-
-		if (b64.length % 4 > 0) {
-			throw new Error('Invalid string. Length must be a multiple of 4')
-		}
-
-		// the number of equal signs (place holders)
-		// if there are two placeholders, than the two characters before it
-		// represent one byte
-		// if there is only one, then the three characters before it represent 2 bytes
-		// this is just a cheap hack to not do indexOf twice
-		var len = b64.length
-		placeHolders = '=' === b64.charAt(len - 2) ? 2 : '=' === b64.charAt(len - 1) ? 1 : 0
-
-		// base64 is 4/3 + up to two characters of the original data
-		arr = new Arr(b64.length * 3 / 4 - placeHolders)
-
-		// if there are placeholders, only get up to the last complete 4 chars
-		l = placeHolders > 0 ? b64.length - 4 : b64.length
-
-		var L = 0
-
-		function push (v) {
-			arr[L++] = v
-		}
-
-		for (i = 0, j = 0; i < l; i += 4, j += 3) {
-			tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3))
-			push((tmp & 0xFF0000) >> 16)
-			push((tmp & 0xFF00) >> 8)
-			push(tmp & 0xFF)
-		}
-
-		if (placeHolders === 2) {
-			tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4)
-			push(tmp & 0xFF)
-		} else if (placeHolders === 1) {
-			tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2)
-			push((tmp >> 8) & 0xFF)
-			push(tmp & 0xFF)
-		}
-
-		return arr
-	}
-
-	function uint8ToBase64 (uint8) {
-		var i,
-			extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes
-			output = "",
-			temp, length
-
-		function encode (num) {
-			return lookup.charAt(num)
-		}
-
-		function tripletToBase64 (num) {
-			return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F)
-		}
-
-		// go through the array every three bytes, we'll deal with trailing stuff later
-		for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
-			temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
-			output += tripletToBase64(temp)
-		}
-
-		// pad the end with zeros, but make sure to not forget the extra bytes
-		switch (extraBytes) {
-			case 1:
-				temp = uint8[uint8.length - 1]
-				output += encode(temp >> 2)
-				output += encode((temp << 4) & 0x3F)
-				output += '=='
-				break
-			case 2:
-				temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1])
-				output += encode(temp >> 10)
-				output += encode((temp >> 4) & 0x3F)
-				output += encode((temp << 2) & 0x3F)
-				output += '='
-				break
-		}
-
-		return output
-	}
-
-	exports.toByteArray = b64ToByteArray
-	exports.fromByteArray = uint8ToBase64
-}(typeof exports === 'undefined' ? (this.base64js = {}) : exports))
-
-},{}],45:[function(require,module,exports){
-exports.read = function (buffer, offset, isLE, mLen, nBytes) {
-  var e, m
-  var eLen = nBytes * 8 - mLen - 1
-  var eMax = (1 << eLen) - 1
-  var eBias = eMax >> 1
-  var nBits = -7
-  var i = isLE ? (nBytes - 1) : 0
-  var d = isLE ? -1 : 1
-  var s = buffer[offset + i]
-
-  i += d
-
-  e = s & ((1 << (-nBits)) - 1)
-  s >>= (-nBits)
-  nBits += eLen
-  for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
-
-  m = e & ((1 << (-nBits)) - 1)
-  e >>= (-nBits)
-  nBits += mLen
-  for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
-
-  if (e === 0) {
-    e = 1 - eBias
-  } else if (e === eMax) {
-    return m ? NaN : ((s ? -1 : 1) * Infinity)
-  } else {
-    m = m + Math.pow(2, mLen)
-    e = e - eBias
-  }
-  return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
-}
-
-exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
-  var e, m, c
-  var eLen = nBytes * 8 - mLen - 1
-  var eMax = (1 << eLen) - 1
-  var eBias = eMax >> 1
-  var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
-  var i = isLE ? 0 : (nBytes - 1)
-  var d = isLE ? 1 : -1
-  var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
-
-  value = Math.abs(value)
-
-  if (isNaN(value) || value === Infinity) {
-    m = isNaN(value) ? 1 : 0
-    e = eMax
-  } else {
-    e = Math.floor(Math.log(value) / Math.LN2)
-    if (value * (c = Math.pow(2, -e)) < 1) {
-      e--
-      c *= 2
-    }
-    if (e + eBias >= 1) {
-      value += rt / c
-    } else {
-      value += rt * Math.pow(2, 1 - eBias)
-    }
-    if (value * c >= 2) {
-      e++
-      c /= 2
-    }
-
-    if (e + eBias >= eMax) {
-      m = 0
-      e = eMax
-    } else if (e + eBias >= 1) {
-      m = (value * c - 1) * Math.pow(2, mLen)
-      e = e + eBias
-    } else {
-      m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
-      e = 0
-    }
-  }
-
-  for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
-
-  e = (e << mLen) | m
-  eLen += mLen
-  for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
-
-  buffer[offset + i - d] |= s * 128
-}
-
-},{}],46:[function(require,module,exports){
-
-/**
- * isArray
- */
-
-var isArray = Array.isArray;
-
-/**
- * toString
- */
-
-var str = Object.prototype.toString;
-
-/**
- * Whether or not the given `val`
- * is an array.
- *
- * example:
- *
- *        isArray([]);
- *        // > true
- *        isArray(arguments);
- *        // > false
- *        isArray('');
- *        // > false
- *
- * @param {mixed} val
- * @return {bool}
- */
-
-module.exports = isArray || function (val) {
-  return !! val && '[object Array]' == str.call(val);
+function isnan (val) {
+  return val !== val // eslint-disable-line no-self-compare
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"base64-js":40,"ieee754":52,"isarray":46}],46:[function(require,module,exports){
+var toString = {}.toString;
+
+module.exports = Array.isArray || function (arr) {
+  return toString.call(arr) == '[object Array]';
 };
 
 },{}],47:[function(require,module,exports){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-function EventEmitter() {
-  this._events = this._events || {};
-  this._maxListeners = this._maxListeners || undefined;
-}
-module.exports = EventEmitter;
-
-// Backwards-compat with node 0.10.x
-EventEmitter.EventEmitter = EventEmitter;
-
-EventEmitter.prototype._events = undefined;
-EventEmitter.prototype._maxListeners = undefined;
-
-// By default EventEmitters will print a warning if more than 10 listeners are
-// added to it. This is a useful default which helps finding memory leaks.
-EventEmitter.defaultMaxListeners = 10;
-
-// Obviously not all Emitters should be limited to 10. This function allows
-// that to be increased. Set to zero for unlimited.
-EventEmitter.prototype.setMaxListeners = function(n) {
-  if (!isNumber(n) || n < 0 || isNaN(n))
-    throw TypeError('n must be a positive number');
-  this._maxListeners = n;
-  return this;
-};
-
-EventEmitter.prototype.emit = function(type) {
-  var er, handler, len, args, i, listeners;
-
-  if (!this._events)
-    this._events = {};
-
-  // If there is no 'error' event listener then throw.
-  if (type === 'error') {
-    if (!this._events.error ||
-        (isObject(this._events.error) && !this._events.error.length)) {
-      er = arguments[1];
-      if (er instanceof Error) {
-        throw er; // Unhandled 'error' event
-      }
-      throw TypeError('Uncaught, unspecified "error" event.');
-    }
-  }
-
-  handler = this._events[type];
-
-  if (isUndefined(handler))
-    return false;
-
-  if (isFunction(handler)) {
-    switch (arguments.length) {
-      // fast cases
-      case 1:
-        handler.call(this);
-        break;
-      case 2:
-        handler.call(this, arguments[1]);
-        break;
-      case 3:
-        handler.call(this, arguments[1], arguments[2]);
-        break;
-      // slower
-      default:
-        len = arguments.length;
-        args = new Array(len - 1);
-        for (i = 1; i < len; i++)
-          args[i - 1] = arguments[i];
-        handler.apply(this, args);
-    }
-  } else if (isObject(handler)) {
-    len = arguments.length;
-    args = new Array(len - 1);
-    for (i = 1; i < len; i++)
-      args[i - 1] = arguments[i];
-
-    listeners = handler.slice();
-    len = listeners.length;
-    for (i = 0; i < len; i++)
-      listeners[i].apply(this, args);
-  }
-
-  return true;
-};
-
-EventEmitter.prototype.addListener = function(type, listener) {
-  var m;
-
-  if (!isFunction(listener))
-    throw TypeError('listener must be a function');
-
-  if (!this._events)
-    this._events = {};
-
-  // To avoid recursion in the case that type === "newListener"! Before
-  // adding it to the listeners, first emit "newListener".
-  if (this._events.newListener)
-    this.emit('newListener', type,
-              isFunction(listener.listener) ?
-              listener.listener : listener);
-
-  if (!this._events[type])
-    // Optimize the case of one listener. Don't need the extra array object.
-    this._events[type] = listener;
-  else if (isObject(this._events[type]))
-    // If we've already got an array, just append.
-    this._events[type].push(listener);
-  else
-    // Adding the second element, need to change to array.
-    this._events[type] = [this._events[type], listener];
-
-  // Check for listener leak
-  if (isObject(this._events[type]) && !this._events[type].warned) {
-    var m;
-    if (!isUndefined(this._maxListeners)) {
-      m = this._maxListeners;
-    } else {
-      m = EventEmitter.defaultMaxListeners;
-    }
-
-    if (m && m > 0 && this._events[type].length > m) {
-      this._events[type].warned = true;
-      console.error('(node) warning: possible EventEmitter memory ' +
-                    'leak detected. %d listeners added. ' +
-                    'Use emitter.setMaxListeners() to increase limit.',
-                    this._events[type].length);
-      if (typeof console.trace === 'function') {
-        // not supported in IE 10
-        console.trace();
-      }
-    }
-  }
-
-  return this;
-};
-
-EventEmitter.prototype.on = EventEmitter.prototype.addListener;
-
-EventEmitter.prototype.once = function(type, listener) {
-  if (!isFunction(listener))
-    throw TypeError('listener must be a function');
-
-  var fired = false;
-
-  function g() {
-    this.removeListener(type, g);
-
-    if (!fired) {
-      fired = true;
-      listener.apply(this, arguments);
-    }
-  }
-
-  g.listener = listener;
-  this.on(type, g);
-
-  return this;
-};
-
-// emits a 'removeListener' event iff the listener was removed
-EventEmitter.prototype.removeListener = function(type, listener) {
-  var list, position, length, i;
-
-  if (!isFunction(listener))
-    throw TypeError('listener must be a function');
-
-  if (!this._events || !this._events[type])
-    return this;
-
-  list = this._events[type];
-  length = list.length;
-  position = -1;
-
-  if (list === listener ||
-      (isFunction(list.listener) && list.listener === listener)) {
-    delete this._events[type];
-    if (this._events.removeListener)
-      this.emit('removeListener', type, listener);
-
-  } else if (isObject(list)) {
-    for (i = length; i-- > 0;) {
-      if (list[i] === listener ||
-          (list[i].listener && list[i].listener === listener)) {
-        position = i;
-        break;
-      }
-    }
-
-    if (position < 0)
-      return this;
-
-    if (list.length === 1) {
-      list.length = 0;
-      delete this._events[type];
-    } else {
-      list.splice(position, 1);
-    }
-
-    if (this._events.removeListener)
-      this.emit('removeListener', type, listener);
-  }
-
-  return this;
-};
-
-EventEmitter.prototype.removeAllListeners = function(type) {
-  var key, listeners;
-
-  if (!this._events)
-    return this;
-
-  // not listening for removeListener, no need to emit
-  if (!this._events.removeListener) {
-    if (arguments.length === 0)
-      this._events = {};
-    else if (this._events[type])
-      delete this._events[type];
-    return this;
-  }
-
-  // emit removeListener for all listeners on all events
-  if (arguments.length === 0) {
-    for (key in this._events) {
-      if (key === 'removeListener') continue;
-      this.removeAllListeners(key);
-    }
-    this.removeAllListeners('removeListener');
-    this._events = {};
-    return this;
-  }
-
-  listeners = this._events[type];
-
-  if (isFunction(listeners)) {
-    this.removeListener(type, listeners);
-  } else {
-    // LIFO order
-    while (listeners.length)
-      this.removeListener(type, listeners[listeners.length - 1]);
-  }
-  delete this._events[type];
-
-  return this;
-};
-
-EventEmitter.prototype.listeners = function(type) {
-  var ret;
-  if (!this._events || !this._events[type])
-    ret = [];
-  else if (isFunction(this._events[type]))
-    ret = [this._events[type]];
-  else
-    ret = this._events[type].slice();
-  return ret;
-};
-
-EventEmitter.listenerCount = function(emitter, type) {
-  var ret;
-  if (!emitter._events || !emitter._events[type])
-    ret = 0;
-  else if (isFunction(emitter._events[type]))
-    ret = 1;
-  else
-    ret = emitter._events[type].length;
-  return ret;
-};
-
-function isFunction(arg) {
-  return typeof arg === 'function';
-}
-
-function isNumber(arg) {
-  return typeof arg === 'number';
-}
-
-function isObject(arg) {
-  return typeof arg === 'object' && arg !== null;
-}
-
-function isUndefined(arg) {
-  return arg === void 0;
-}
-
-},{}],48:[function(require,module,exports){
-if (typeof Object.create === 'function') {
-  // implementation from standard node.js 'util' module
-  module.exports = function inherits(ctor, superCtor) {
-    ctor.super_ = superCtor
-    ctor.prototype = Object.create(superCtor.prototype, {
-      constructor: {
-        value: ctor,
-        enumerable: false,
-        writable: true,
-        configurable: true
-      }
-    });
-  };
-} else {
-  // old school shim for old browsers
-  module.exports = function inherits(ctor, superCtor) {
-    ctor.super_ = superCtor
-    var TempCtor = function () {}
-    TempCtor.prototype = superCtor.prototype
-    ctor.prototype = new TempCtor()
-    ctor.prototype.constructor = ctor
-  }
-}
-
-},{}],49:[function(require,module,exports){
-module.exports = Array.isArray || function (arr) {
-  return Object.prototype.toString.call(arr) == '[object Array]';
-};
-
-},{}],50:[function(require,module,exports){
-exports.endianness = function () { return 'LE' };
-
-exports.hostname = function () {
-    if (typeof location !== 'undefined') {
-        return location.hostname
-    }
-    else return '';
-};
-
-exports.loadavg = function () { return [] };
-
-exports.uptime = function () { return 0 };
-
-exports.freemem = function () {
-    return Number.MAX_VALUE;
-};
-
-exports.totalmem = function () {
-    return Number.MAX_VALUE;
-};
-
-exports.cpus = function () { return [] };
-
-exports.type = function () { return 'Browser' };
-
-exports.release = function () {
-    if (typeof navigator !== 'undefined') {
-        return navigator.appVersion;
-    }
-    return '';
-};
-
-exports.networkInterfaces
-= exports.getNetworkInterfaces
-= function () { return {} };
-
-exports.arch = function () { return 'javascript' };
-
-exports.platform = function () { return 'browser' };
-
-exports.tmpdir = exports.tmpDir = function () {
-    return '/tmp';
-};
-
-exports.EOL = '\n';
-
-},{}],51:[function(require,module,exports){
-// shim for using process in browser
-
-var process = module.exports = {};
-var queue = [];
-var draining = false;
-var currentQueue;
-var queueIndex = -1;
-
-function cleanUpNextTick() {
-    draining = false;
-    if (currentQueue.length) {
-        queue = currentQueue.concat(queue);
-    } else {
-        queueIndex = -1;
-    }
-    if (queue.length) {
-        drainQueue();
-    }
-}
-
-function drainQueue() {
-    if (draining) {
-        return;
-    }
-    var timeout = setTimeout(cleanUpNextTick);
-    draining = true;
-
-    var len = queue.length;
-    while(len) {
-        currentQueue = queue;
-        queue = [];
-        while (++queueIndex < len) {
-            if (currentQueue) {
-                currentQueue[queueIndex].run();
-            }
-        }
-        queueIndex = -1;
-        len = queue.length;
-    }
-    currentQueue = null;
-    draining = false;
-    clearTimeout(timeout);
-}
-
-process.nextTick = function (fun) {
-    var args = new Array(arguments.length - 1);
-    if (arguments.length > 1) {
-        for (var i = 1; i < arguments.length; i++) {
-            args[i - 1] = arguments[i];
-        }
-    }
-    queue.push(new Item(fun, args));
-    if (queue.length === 1 && !draining) {
-        setTimeout(drainQueue, 0);
-    }
-};
-
-// v8 likes predictible objects
-function Item(fun, array) {
-    this.fun = fun;
-    this.array = array;
-}
-Item.prototype.run = function () {
-    this.fun.apply(null, this.array);
-};
-process.title = 'browser';
-process.browser = true;
-process.env = {};
-process.argv = [];
-process.version = ''; // empty string to avoid regexp issues
-process.versions = {};
-
-function noop() {}
-
-process.on = noop;
-process.addListener = noop;
-process.once = noop;
-process.off = noop;
-process.removeListener = noop;
-process.removeAllListeners = noop;
-process.emit = noop;
-
-process.binding = function (name) {
-    throw new Error('process.binding is not supported');
-};
-
-process.cwd = function () { return '/' };
-process.chdir = function (dir) {
-    throw new Error('process.chdir is not supported');
-};
-process.umask = function() { return 0; };
-
-},{}],52:[function(require,module,exports){
-module.exports = require("./lib/_stream_duplex.js")
-
-},{"./lib/_stream_duplex.js":53}],53:[function(require,module,exports){
-(function (process){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-// a duplex stream is just a stream that is both readable and writable.
-// Since JS doesn't have multiple prototypal inheritance, this class
-// prototypally inherits from Readable, and then parasitically from
-// Writable.
-
-module.exports = Duplex;
-
-/*<replacement>*/
-var objectKeys = Object.keys || function (obj) {
-  var keys = [];
-  for (var key in obj) keys.push(key);
-  return keys;
-}
-/*</replacement>*/
-
-
-/*<replacement>*/
-var util = require('core-util-is');
-util.inherits = require('inherits');
-/*</replacement>*/
-
-var Readable = require('./_stream_readable');
-var Writable = require('./_stream_writable');
-
-util.inherits(Duplex, Readable);
-
-forEach(objectKeys(Writable.prototype), function(method) {
-  if (!Duplex.prototype[method])
-    Duplex.prototype[method] = Writable.prototype[method];
-});
-
-function Duplex(options) {
-  if (!(this instanceof Duplex))
-    return new Duplex(options);
-
-  Readable.call(this, options);
-  Writable.call(this, options);
-
-  if (options && options.readable === false)
-    this.readable = false;
-
-  if (options && options.writable === false)
-    this.writable = false;
-
-  this.allowHalfOpen = true;
-  if (options && options.allowHalfOpen === false)
-    this.allowHalfOpen = false;
-
-  this.once('end', onend);
-}
-
-// the no-half-open enforcer
-function onend() {
-  // if we allow half-open state, or if the writable side ended,
-  // then we're ok.
-  if (this.allowHalfOpen || this._writableState.ended)
-    return;
-
-  // no more data can be written.
-  // But allow more writes to happen in this tick.
-  process.nextTick(this.end.bind(this));
-}
-
-function forEach (xs, f) {
-  for (var i = 0, l = xs.length; i < l; i++) {
-    f(xs[i], i);
-  }
-}
-
-}).call(this,require('_process'))
-},{"./_stream_readable":55,"./_stream_writable":57,"_process":51,"core-util-is":58,"inherits":48}],54:[function(require,module,exports){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-// a passthrough stream.
-// basically just the most minimal sort of Transform stream.
-// Every written chunk gets output as-is.
-
-module.exports = PassThrough;
-
-var Transform = require('./_stream_transform');
-
-/*<replacement>*/
-var util = require('core-util-is');
-util.inherits = require('inherits');
-/*</replacement>*/
-
-util.inherits(PassThrough, Transform);
-
-function PassThrough(options) {
-  if (!(this instanceof PassThrough))
-    return new PassThrough(options);
-
-  Transform.call(this, options);
-}
-
-PassThrough.prototype._transform = function(chunk, encoding, cb) {
-  cb(null, chunk);
-};
-
-},{"./_stream_transform":56,"core-util-is":58,"inherits":48}],55:[function(require,module,exports){
-(function (process){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-module.exports = Readable;
-
-/*<replacement>*/
-var isArray = require('isarray');
-/*</replacement>*/
-
-
-/*<replacement>*/
-var Buffer = require('buffer').Buffer;
-/*</replacement>*/
-
-Readable.ReadableState = ReadableState;
-
-var EE = require('events').EventEmitter;
-
-/*<replacement>*/
-if (!EE.listenerCount) EE.listenerCount = function(emitter, type) {
-  return emitter.listeners(type).length;
-};
-/*</replacement>*/
-
-var Stream = require('stream');
-
-/*<replacement>*/
-var util = require('core-util-is');
-util.inherits = require('inherits');
-/*</replacement>*/
-
-var StringDecoder;
-
-
-/*<replacement>*/
-var debug = require('util');
-if (debug && debug.debuglog) {
-  debug = debug.debuglog('stream');
-} else {
-  debug = function () {};
-}
-/*</replacement>*/
-
-
-util.inherits(Readable, Stream);
-
-function ReadableState(options, stream) {
-  var Duplex = require('./_stream_duplex');
-
-  options = options || {};
-
-  // the point at which it stops calling _read() to fill the buffer
-  // Note: 0 is a valid value, means "don't call _read preemptively ever"
-  var hwm = options.highWaterMark;
-  var defaultHwm = options.objectMode ? 16 : 16 * 1024;
-  this.highWaterMark = (hwm || hwm === 0) ? hwm : defaultHwm;
-
-  // cast to ints.
-  this.highWaterMark = ~~this.highWaterMark;
-
-  this.buffer = [];
-  this.length = 0;
-  this.pipes = null;
-  this.pipesCount = 0;
-  this.flowing = null;
-  this.ended = false;
-  this.endEmitted = false;
-  this.reading = false;
-
-  // a flag to be able to tell if the onwrite cb is called immediately,
-  // or on a later tick.  We set this to true at first, because any
-  // actions that shouldn't happen until "later" should generally also
-  // not happen before the first write call.
-  this.sync = true;
-
-  // whenever we return null, then we set a flag to say
-  // that we're awaiting a 'readable' event emission.
-  this.needReadable = false;
-  this.emittedReadable = false;
-  this.readableListening = false;
-
-
-  // object stream flag. Used to make read(n) ignore n and to
-  // make all the buffer merging and length checks go away
-  this.objectMode = !!options.objectMode;
-
-  if (stream instanceof Duplex)
-    this.objectMode = this.objectMode || !!options.readableObjectMode;
-
-  // Crypto is kind of old and crusty.  Historically, its default string
-  // encoding is 'binary' so we have to make this configurable.
-  // Everything else in the universe uses 'utf8', though.
-  this.defaultEncoding = options.defaultEncoding || 'utf8';
-
-  // when piping, we only care about 'readable' events that happen
-  // after read()ing all the bytes and not getting any pushback.
-  this.ranOut = false;
-
-  // the number of writers that are awaiting a drain event in .pipe()s
-  this.awaitDrain = 0;
-
-  // if true, a maybeReadMore has been scheduled
-  this.readingMore = false;
-
-  this.decoder = null;
-  this.encoding = null;
-  if (options.encoding) {
-    if (!StringDecoder)
-      StringDecoder = require('string_decoder/').StringDecoder;
-    this.decoder = new StringDecoder(options.encoding);
-    this.encoding = options.encoding;
-  }
-}
-
-function Readable(options) {
-  var Duplex = require('./_stream_duplex');
-
-  if (!(this instanceof Readable))
-    return new Readable(options);
-
-  this._readableState = new ReadableState(options, this);
-
-  // legacy
-  this.readable = true;
-
-  Stream.call(this);
-}
-
-// Manually shove something into the read() buffer.
-// This returns true if the highWaterMark has not been hit yet,
-// similar to how Writable.write() returns true if you should
-// write() some more.
-Readable.prototype.push = function(chunk, encoding) {
-  var state = this._readableState;
-
-  if (util.isString(chunk) && !state.objectMode) {
-    encoding = encoding || state.defaultEncoding;
-    if (encoding !== state.encoding) {
-      chunk = new Buffer(chunk, encoding);
-      encoding = '';
-    }
-  }
-
-  return readableAddChunk(this, state, chunk, encoding, false);
-};
-
-// Unshift should *always* be something directly out of read()
-Readable.prototype.unshift = function(chunk) {
-  var state = this._readableState;
-  return readableAddChunk(this, state, chunk, '', true);
-};
-
-function readableAddChunk(stream, state, chunk, encoding, addToFront) {
-  var er = chunkInvalid(state, chunk);
-  if (er) {
-    stream.emit('error', er);
-  } else if (util.isNullOrUndefined(chunk)) {
-    state.reading = false;
-    if (!state.ended)
-      onEofChunk(stream, state);
-  } else if (state.objectMode || chunk && chunk.length > 0) {
-    if (state.ended && !addToFront) {
-      var e = new Error('stream.push() after EOF');
-      stream.emit('error', e);
-    } else if (state.endEmitted && addToFront) {
-      var e = new Error('stream.unshift() after end event');
-      stream.emit('error', e);
-    } else {
-      if (state.decoder && !addToFront && !encoding)
-        chunk = state.decoder.write(chunk);
-
-      if (!addToFront)
-        state.reading = false;
-
-      // if we want the data now, just emit it.
-      if (state.flowing && state.length === 0 && !state.sync) {
-        stream.emit('data', chunk);
-        stream.read(0);
-      } else {
-        // update the buffer info.
-        state.length += state.objectMode ? 1 : chunk.length;
-        if (addToFront)
-          state.buffer.unshift(chunk);
-        else
-          state.buffer.push(chunk);
-
-        if (state.needReadable)
-          emitReadable(stream);
-      }
-
-      maybeReadMore(stream, state);
-    }
-  } else if (!addToFront) {
-    state.reading = false;
-  }
-
-  return needMoreData(state);
-}
-
-
-
-// if it's past the high water mark, we can push in some more.
-// Also, if we have no data yet, we can stand some
-// more bytes.  This is to work around cases where hwm=0,
-// such as the repl.  Also, if the push() triggered a
-// readable event, and the user called read(largeNumber) such that
-// needReadable was set, then we ought to push more, so that another
-// 'readable' event will be triggered.
-function needMoreData(state) {
-  return !state.ended &&
-         (state.needReadable ||
-          state.length < state.highWaterMark ||
-          state.length === 0);
-}
-
-// backwards compatibility.
-Readable.prototype.setEncoding = function(enc) {
-  if (!StringDecoder)
-    StringDecoder = require('string_decoder/').StringDecoder;
-  this._readableState.decoder = new StringDecoder(enc);
-  this._readableState.encoding = enc;
-  return this;
-};
-
-// Don't raise the hwm > 128MB
-var MAX_HWM = 0x800000;
-function roundUpToNextPowerOf2(n) {
-  if (n >= MAX_HWM) {
-    n = MAX_HWM;
-  } else {
-    // Get the next highest power of 2
-    n--;
-    for (var p = 1; p < 32; p <<= 1) n |= n >> p;
-    n++;
-  }
-  return n;
-}
-
-function howMuchToRead(n, state) {
-  if (state.length === 0 && state.ended)
-    return 0;
-
-  if (state.objectMode)
-    return n === 0 ? 0 : 1;
-
-  if (isNaN(n) || util.isNull(n)) {
-    // only flow one buffer at a time
-    if (state.flowing && state.buffer.length)
-      return state.buffer[0].length;
-    else
-      return state.length;
-  }
-
-  if (n <= 0)
-    return 0;
-
-  // If we're asking for more than the target buffer level,
-  // then raise the water mark.  Bump up to the next highest
-  // power of 2, to prevent increasing it excessively in tiny
-  // amounts.
-  if (n > state.highWaterMark)
-    state.highWaterMark = roundUpToNextPowerOf2(n);
-
-  // don't have that much.  return null, unless we've ended.
-  if (n > state.length) {
-    if (!state.ended) {
-      state.needReadable = true;
-      return 0;
-    } else
-      return state.length;
-  }
-
-  return n;
-}
-
-// you can override either this method, or the async _read(n) below.
-Readable.prototype.read = function(n) {
-  debug('read', n);
-  var state = this._readableState;
-  var nOrig = n;
-
-  if (!util.isNumber(n) || n > 0)
-    state.emittedReadable = false;
-
-  // if we're doing read(0) to trigger a readable event, but we
-  // already have a bunch of data in the buffer, then just trigger
-  // the 'readable' event and move on.
-  if (n === 0 &&
-      state.needReadable &&
-      (state.length >= state.highWaterMark || state.ended)) {
-    debug('read: emitReadable', state.length, state.ended);
-    if (state.length === 0 && state.ended)
-      endReadable(this);
-    else
-      emitReadable(this);
-    return null;
-  }
-
-  n = howMuchToRead(n, state);
-
-  // if we've ended, and we're now clear, then finish it up.
-  if (n === 0 && state.ended) {
-    if (state.length === 0)
-      endReadable(this);
-    return null;
-  }
-
-  // All the actual chunk generation logic needs to be
-  // *below* the call to _read.  The reason is that in certain
-  // synthetic stream cases, such as passthrough streams, _read
-  // may be a completely synchronous operation which may change
-  // the state of the read buffer, providing enough data when
-  // before there was *not* enough.
-  //
-  // So, the steps are:
-  // 1. Figure out what the state of things will be after we do
-  // a read from the buffer.
-  //
-  // 2. If that resulting state will trigger a _read, then call _read.
-  // Note that this may be asynchronous, or synchronous.  Yes, it is
-  // deeply ugly to write APIs this way, but that still doesn't mean
-  // that the Readable class should behave improperly, as streams are
-  // designed to be sync/async agnostic.
-  // Take note if the _read call is sync or async (ie, if the read call
-  // has returned yet), so that we know whether or not it's safe to emit
-  // 'readable' etc.
-  //
-  // 3. Actually pull the requested chunks out of the buffer and return.
-
-  // if we need a readable event, then we need to do some reading.
-  var doRead = state.needReadable;
-  debug('need readable', doRead);
-
-  // if we currently have less than the highWaterMark, then also read some
-  if (state.length === 0 || state.length - n < state.highWaterMark) {
-    doRead = true;
-    debug('length less than watermark', doRead);
-  }
-
-  // however, if we've ended, then there's no point, and if we're already
-  // reading, then it's unnecessary.
-  if (state.ended || state.reading) {
-    doRead = false;
-    debug('reading or ended', doRead);
-  }
-
-  if (doRead) {
-    debug('do read');
-    state.reading = true;
-    state.sync = true;
-    // if the length is currently zero, then we *need* a readable event.
-    if (state.length === 0)
-      state.needReadable = true;
-    // call internal read method
-    this._read(state.highWaterMark);
-    state.sync = false;
-  }
-
-  // If _read pushed data synchronously, then `reading` will be false,
-  // and we need to re-evaluate how much data we can return to the user.
-  if (doRead && !state.reading)
-    n = howMuchToRead(nOrig, state);
-
-  var ret;
-  if (n > 0)
-    ret = fromList(n, state);
-  else
-    ret = null;
-
-  if (util.isNull(ret)) {
-    state.needReadable = true;
-    n = 0;
-  }
-
-  state.length -= n;
-
-  // If we have nothing in the buffer, then we want to know
-  // as soon as we *do* get something into the buffer.
-  if (state.length === 0 && !state.ended)
-    state.needReadable = true;
-
-  // If we tried to read() past the EOF, then emit end on the next tick.
-  if (nOrig !== n && state.ended && state.length === 0)
-    endReadable(this);
-
-  if (!util.isNull(ret))
-    this.emit('data', ret);
-
-  return ret;
-};
-
-function chunkInvalid(state, chunk) {
-  var er = null;
-  if (!util.isBuffer(chunk) &&
-      !util.isString(chunk) &&
-      !util.isNullOrUndefined(chunk) &&
-      !state.objectMode) {
-    er = new TypeError('Invalid non-string/buffer chunk');
-  }
-  return er;
-}
-
-
-function onEofChunk(stream, state) {
-  if (state.decoder && !state.ended) {
-    var chunk = state.decoder.end();
-    if (chunk && chunk.length) {
-      state.buffer.push(chunk);
-      state.length += state.objectMode ? 1 : chunk.length;
-    }
-  }
-  state.ended = true;
-
-  // emit 'readable' now to make sure it gets picked up.
-  emitReadable(stream);
-}
-
-// Don't emit readable right away in sync mode, because this can trigger
-// another read() call => stack overflow.  This way, it might trigger
-// a nextTick recursion warning, but that's not so bad.
-function emitReadable(stream) {
-  var state = stream._readableState;
-  state.needReadable = false;
-  if (!state.emittedReadable) {
-    debug('emitReadable', state.flowing);
-    state.emittedReadable = true;
-    if (state.sync)
-      process.nextTick(function() {
-        emitReadable_(stream);
-      });
-    else
-      emitReadable_(stream);
-  }
-}
-
-function emitReadable_(stream) {
-  debug('emit readable');
-  stream.emit('readable');
-  flow(stream);
-}
-
-
-// at this point, the user has presumably seen the 'readable' event,
-// and called read() to consume some data.  that may have triggered
-// in turn another _read(n) call, in which case reading = true if
-// it's in progress.
-// However, if we're not ended, or reading, and the length < hwm,
-// then go ahead and try to read some more preemptively.
-function maybeReadMore(stream, state) {
-  if (!state.readingMore) {
-    state.readingMore = true;
-    process.nextTick(function() {
-      maybeReadMore_(stream, state);
-    });
-  }
-}
-
-function maybeReadMore_(stream, state) {
-  var len = state.length;
-  while (!state.reading && !state.flowing && !state.ended &&
-         state.length < state.highWaterMark) {
-    debug('maybeReadMore read 0');
-    stream.read(0);
-    if (len === state.length)
-      // didn't get any data, stop spinning.
-      break;
-    else
-      len = state.length;
-  }
-  state.readingMore = false;
-}
-
-// abstract method.  to be overridden in specific implementation classes.
-// call cb(er, data) where data is <= n in length.
-// for virtual (non-string, non-buffer) streams, "length" is somewhat
-// arbitrary, and perhaps not very meaningful.
-Readable.prototype._read = function(n) {
-  this.emit('error', new Error('not implemented'));
-};
-
-Readable.prototype.pipe = function(dest, pipeOpts) {
-  var src = this;
-  var state = this._readableState;
-
-  switch (state.pipesCount) {
-    case 0:
-      state.pipes = dest;
-      break;
-    case 1:
-      state.pipes = [state.pipes, dest];
-      break;
-    default:
-      state.pipes.push(dest);
-      break;
-  }
-  state.pipesCount += 1;
-  debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts);
-
-  var doEnd = (!pipeOpts || pipeOpts.end !== false) &&
-              dest !== process.stdout &&
-              dest !== process.stderr;
-
-  var endFn = doEnd ? onend : cleanup;
-  if (state.endEmitted)
-    process.nextTick(endFn);
-  else
-    src.once('end', endFn);
-
-  dest.on('unpipe', onunpipe);
-  function onunpipe(readable) {
-    debug('onunpipe');
-    if (readable === src) {
-      cleanup();
-    }
-  }
-
-  function onend() {
-    debug('onend');
-    dest.end();
-  }
-
-  // when the dest drains, it reduces the awaitDrain counter
-  // on the source.  This would be more elegant with a .once()
-  // handler in flow(), but adding and removing repeatedly is
-  // too slow.
-  var ondrain = pipeOnDrain(src);
-  dest.on('drain', ondrain);
-
-  function cleanup() {
-    debug('cleanup');
-    // cleanup event handlers once the pipe is broken
-    dest.removeListener('close', onclose);
-    dest.removeListener('finish', onfinish);
-    dest.removeListener('drain', ondrain);
-    dest.removeListener('error', onerror);
-    dest.removeListener('unpipe', onunpipe);
-    src.removeListener('end', onend);
-    src.removeListener('end', cleanup);
-    src.removeListener('data', ondata);
-
-    // if the reader is waiting for a drain event from this
-    // specific writer, then it would cause it to never start
-    // flowing again.
-    // So, if this is awaiting a drain, then we just call it now.
-    // If we don't know, then assume that we are waiting for one.
-    if (state.awaitDrain &&
-        (!dest._writableState || dest._writableState.needDrain))
-      ondrain();
-  }
-
-  src.on('data', ondata);
-  function ondata(chunk) {
-    debug('ondata');
-    var ret = dest.write(chunk);
-    if (false === ret) {
-      debug('false write response, pause',
-            src._readableState.awaitDrain);
-      src._readableState.awaitDrain++;
-      src.pause();
-    }
-  }
-
-  // if the dest has an error, then stop piping into it.
-  // however, don't suppress the throwing behavior for this.
-  function onerror(er) {
-    debug('onerror', er);
-    unpipe();
-    dest.removeListener('error', onerror);
-    if (EE.listenerCount(dest, 'error') === 0)
-      dest.emit('error', er);
-  }
-  // This is a brutally ugly hack to make sure that our error handler
-  // is attached before any userland ones.  NEVER DO THIS.
-  if (!dest._events || !dest._events.error)
-    dest.on('error', onerror);
-  else if (isArray(dest._events.error))
-    dest._events.error.unshift(onerror);
-  else
-    dest._events.error = [onerror, dest._events.error];
-
-
-
-  // Both close and finish should trigger unpipe, but only once.
-  function onclose() {
-    dest.removeListener('finish', onfinish);
-    unpipe();
-  }
-  dest.once('close', onclose);
-  function onfinish() {
-    debug('onfinish');
-    dest.removeListener('close', onclose);
-    unpipe();
-  }
-  dest.once('finish', onfinish);
-
-  function unpipe() {
-    debug('unpipe');
-    src.unpipe(dest);
-  }
-
-  // tell the dest that it's being piped to
-  dest.emit('pipe', src);
-
-  // start the flow if it hasn't been started already.
-  if (!state.flowing) {
-    debug('pipe resume');
-    src.resume();
-  }
-
-  return dest;
-};
-
-function pipeOnDrain(src) {
-  return function() {
-    var state = src._readableState;
-    debug('pipeOnDrain', state.awaitDrain);
-    if (state.awaitDrain)
-      state.awaitDrain--;
-    if (state.awaitDrain === 0 && EE.listenerCount(src, 'data')) {
-      state.flowing = true;
-      flow(src);
-    }
-  };
-}
-
-
-Readable.prototype.unpipe = function(dest) {
-  var state = this._readableState;
-
-  // if we're not piping anywhere, then do nothing.
-  if (state.pipesCount === 0)
-    return this;
-
-  // just one destination.  most common case.
-  if (state.pipesCount === 1) {
-    // passed in one, but it's not the right one.
-    if (dest && dest !== state.pipes)
-      return this;
-
-    if (!dest)
-      dest = state.pipes;
-
-    // got a match.
-    state.pipes = null;
-    state.pipesCount = 0;
-    state.flowing = false;
-    if (dest)
-      dest.emit('unpipe', this);
-    return this;
-  }
-
-  // slow case. multiple pipe destinations.
-
-  if (!dest) {
-    // remove all.
-    var dests = state.pipes;
-    var len = state.pipesCount;
-    state.pipes = null;
-    state.pipesCount = 0;
-    state.flowing = false;
-
-    for (var i = 0; i < len; i++)
-      dests[i].emit('unpipe', this);
-    return this;
-  }
-
-  // try to find the right one.
-  var i = indexOf(state.pipes, dest);
-  if (i === -1)
-    return this;
-
-  state.pipes.splice(i, 1);
-  state.pipesCount -= 1;
-  if (state.pipesCount === 1)
-    state.pipes = state.pipes[0];
-
-  dest.emit('unpipe', this);
-
-  return this;
-};
-
-// set up data events if they are asked for
-// Ensure readable listeners eventually get something
-Readable.prototype.on = function(ev, fn) {
-  var res = Stream.prototype.on.call(this, ev, fn);
-
-  // If listening to data, and it has not explicitly been paused,
-  // then call resume to start the flow of data on the next tick.
-  if (ev === 'data' && false !== this._readableState.flowing) {
-    this.resume();
-  }
-
-  if (ev === 'readable' && this.readable) {
-    var state = this._readableState;
-    if (!state.readableListening) {
-      state.readableListening = true;
-      state.emittedReadable = false;
-      state.needReadable = true;
-      if (!state.reading) {
-        var self = this;
-        process.nextTick(function() {
-          debug('readable nexttick read 0');
-          self.read(0);
-        });
-      } else if (state.length) {
-        emitReadable(this, state);
-      }
-    }
-  }
-
-  return res;
-};
-Readable.prototype.addListener = Readable.prototype.on;
-
-// pause() and resume() are remnants of the legacy readable stream API
-// If the user uses them, then switch into old mode.
-Readable.prototype.resume = function() {
-  var state = this._readableState;
-  if (!state.flowing) {
-    debug('resume');
-    state.flowing = true;
-    if (!state.reading) {
-      debug('resume read 0');
-      this.read(0);
-    }
-    resume(this, state);
-  }
-  return this;
-};
-
-function resume(stream, state) {
-  if (!state.resumeScheduled) {
-    state.resumeScheduled = true;
-    process.nextTick(function() {
-      resume_(stream, state);
-    });
-  }
-}
-
-function resume_(stream, state) {
-  state.resumeScheduled = false;
-  stream.emit('resume');
-  flow(stream);
-  if (state.flowing && !state.reading)
-    stream.read(0);
-}
-
-Readable.prototype.pause = function() {
-  debug('call pause flowing=%j', this._readableState.flowing);
-  if (false !== this._readableState.flowing) {
-    debug('pause');
-    this._readableState.flowing = false;
-    this.emit('pause');
-  }
-  return this;
-};
-
-function flow(stream) {
-  var state = stream._readableState;
-  debug('flow', state.flowing);
-  if (state.flowing) {
-    do {
-      var chunk = stream.read();
-    } while (null !== chunk && state.flowing);
-  }
-}
-
-// wrap an old-style stream as the async data source.
-// This is *not* part of the readable stream interface.
-// It is an ugly unfortunate mess of history.
-Readable.prototype.wrap = function(stream) {
-  var state = this._readableState;
-  var paused = false;
-
-  var self = this;
-  stream.on('end', function() {
-    debug('wrapped end');
-    if (state.decoder && !state.ended) {
-      var chunk = state.decoder.end();
-      if (chunk && chunk.length)
-        self.push(chunk);
-    }
-
-    self.push(null);
-  });
-
-  stream.on('data', function(chunk) {
-    debug('wrapped data');
-    if (state.decoder)
-      chunk = state.decoder.write(chunk);
-    if (!chunk || !state.objectMode && !chunk.length)
-      return;
-
-    var ret = self.push(chunk);
-    if (!ret) {
-      paused = true;
-      stream.pause();
-    }
-  });
-
-  // proxy all the other methods.
-  // important when wrapping filters and duplexes.
-  for (var i in stream) {
-    if (util.isFunction(stream[i]) && util.isUndefined(this[i])) {
-      this[i] = function(method) { return function() {
-        return stream[method].apply(stream, arguments);
-      }}(i);
-    }
-  }
-
-  // proxy certain important events.
-  var events = ['error', 'close', 'destroy', 'pause', 'resume'];
-  forEach(events, function(ev) {
-    stream.on(ev, self.emit.bind(self, ev));
-  });
-
-  // when we try to consume some more bytes, simply unpause the
-  // underlying stream.
-  self._read = function(n) {
-    debug('wrapped _read', n);
-    if (paused) {
-      paused = false;
-      stream.resume();
-    }
-  };
-
-  return self;
-};
-
-
-
-// exposed for testing purposes only.
-Readable._fromList = fromList;
-
-// Pluck off n bytes from an array of buffers.
-// Length is the combined lengths of all the buffers in the list.
-function fromList(n, state) {
-  var list = state.buffer;
-  var length = state.length;
-  var stringMode = !!state.decoder;
-  var objectMode = !!state.objectMode;
-  var ret;
-
-  // nothing in the list, definitely empty.
-  if (list.length === 0)
-    return null;
-
-  if (length === 0)
-    ret = null;
-  else if (objectMode)
-    ret = list.shift();
-  else if (!n || n >= length) {
-    // read it all, truncate the array.
-    if (stringMode)
-      ret = list.join('');
-    else
-      ret = Buffer.concat(list, length);
-    list.length = 0;
-  } else {
-    // read just some of it.
-    if (n < list[0].length) {
-      // just take a part of the first list item.
-      // slice is the same for buffers and strings.
-      var buf = list[0];
-      ret = buf.slice(0, n);
-      list[0] = buf.slice(n);
-    } else if (n === list[0].length) {
-      // first list is a perfect match
-      ret = list.shift();
-    } else {
-      // complex case.
-      // we have enough to cover it, but it spans past the first buffer.
-      if (stringMode)
-        ret = '';
-      else
-        ret = new Buffer(n);
-
-      var c = 0;
-      for (var i = 0, l = list.length; i < l && c < n; i++) {
-        var buf = list[0];
-        var cpy = Math.min(n - c, buf.length);
-
-        if (stringMode)
-          ret += buf.slice(0, cpy);
-        else
-          buf.copy(ret, c, 0, cpy);
-
-        if (cpy < buf.length)
-          list[0] = buf.slice(cpy);
-        else
-          list.shift();
-
-        c += cpy;
-      }
-    }
-  }
-
-  return ret;
-}
-
-function endReadable(stream) {
-  var state = stream._readableState;
-
-  // If we get here before consuming all the bytes, then that is a
-  // bug in node.  Should never happen.
-  if (state.length > 0)
-    throw new Error('endReadable called on non-empty stream');
-
-  if (!state.endEmitted) {
-    state.ended = true;
-    process.nextTick(function() {
-      // Check that we didn't get one last unshift.
-      if (!state.endEmitted && state.length === 0) {
-        state.endEmitted = true;
-        stream.readable = false;
-        stream.emit('end');
-      }
-    });
-  }
-}
-
-function forEach (xs, f) {
-  for (var i = 0, l = xs.length; i < l; i++) {
-    f(xs[i], i);
-  }
-}
-
-function indexOf (xs, x) {
-  for (var i = 0, l = xs.length; i < l; i++) {
-    if (xs[i] === x) return i;
-  }
-  return -1;
-}
-
-}).call(this,require('_process'))
-},{"./_stream_duplex":53,"_process":51,"buffer":43,"core-util-is":58,"events":47,"inherits":48,"isarray":49,"stream":63,"string_decoder/":64,"util":42}],56:[function(require,module,exports){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
-// a transform stream is a readable/writable stream where you do
-// something with the data.  Sometimes it's called a "filter",
-// but that's not a great name for it, since that implies a thing where
-// some bits pass through, and others are simply ignored.  (That would
-// be a valid example of a transform, of course.)
-//
-// While the output is causally related to the input, it's not a
-// necessarily symmetric or synchronous transformation.  For example,
-// a zlib stream might take multiple plain-text writes(), and then
-// emit a single compressed chunk some time in the future.
-//
-// Here's how this works:
-//
-// The Transform stream has all the aspects of the readable and writable
-// stream classes.  When you write(chunk), that calls _write(chunk,cb)
-// internally, and returns false if there's a lot of pending writes
-// buffered up.  When you call read(), that calls _read(n) until
-// there's enough pending readable data buffered up.
-//
-// In a transform stream, the written data is placed in a buffer.  When
-// _read(n) is called, it transforms the queued up data, calling the
-// buffered _write cb's as it consumes chunks.  If consuming a single
-// written chunk would result in multiple output chunks, then the first
-// outputted bit calls the readcb, and subsequent chunks just go into
-// the read buffer, and will cause it to emit 'readable' if necessary.
-//
-// This way, back-pressure is actually determined by the reading side,
-// since _read has to be called to start processing a new chunk.  However,
-// a pathological inflate type of transform can cause excessive buffering
-// here.  For example, imagine a stream where every byte of input is
-// interpreted as an integer from 0-255, and then results in that many
-// bytes of output.  Writing the 4 bytes {ff,ff,ff,ff} would result in
-// 1kb of data being output.  In this case, you could write a very small
-// amount of input, and end up with a very large amount of output.  In
-// such a pathological inflating mechanism, there'd be no way to tell
-// the system to stop doing the transform.  A single 4MB write could
-// cause the system to run out of memory.
-//
-// However, even in such a pathological case, only a single written chunk
-// would be consumed, and then the rest would wait (un-transformed) until
-// the results of the previous transformed chunk were consumed.
-
-module.exports = Transform;
-
-var Duplex = require('./_stream_duplex');
-
-/*<replacement>*/
-var util = require('core-util-is');
-util.inherits = require('inherits');
-/*</replacement>*/
-
-util.inherits(Transform, Duplex);
-
-
-function TransformState(options, stream) {
-  this.afterTransform = function(er, data) {
-    return afterTransform(stream, er, data);
-  };
-
-  this.needTransform = false;
-  this.transforming = false;
-  this.writecb = null;
-  this.writechunk = null;
-}
-
-function afterTransform(stream, er, data) {
-  var ts = stream._transformState;
-  ts.transforming = false;
-
-  var cb = ts.writecb;
-
-  if (!cb)
-    return stream.emit('error', new Error('no writecb in Transform class'));
-
-  ts.writechunk = null;
-  ts.writecb = null;
-
-  if (!util.isNullOrUndefined(data))
-    stream.push(data);
-
-  if (cb)
-    cb(er);
-
-  var rs = stream._readableState;
-  rs.reading = false;
-  if (rs.needReadable || rs.length < rs.highWaterMark) {
-    stream._read(rs.highWaterMark);
-  }
-}
-
-
-function Transform(options) {
-  if (!(this instanceof Transform))
-    return new Transform(options);
-
-  Duplex.call(this, options);
-
-  this._transformState = new TransformState(options, this);
-
-  // when the writable side finishes, then flush out anything remaining.
-  var stream = this;
-
-  // start out asking for a readable event once data is transformed.
-  this._readableState.needReadable = true;
-
-  // we have implemented the _read method, and done the other things
-  // that Readable wants before the first _read call, so unset the
-  // sync guard flag.
-  this._readableState.sync = false;
-
-  this.once('prefinish', function() {
-    if (util.isFunction(this._flush))
-      this._flush(function(er) {
-        done(stream, er);
-      });
-    else
-      done(stream);
-  });
-}
-
-Transform.prototype.push = function(chunk, encoding) {
-  this._transformState.needTransform = false;
-  return Duplex.prototype.push.call(this, chunk, encoding);
-};
-
-// This is the part where you do stuff!
-// override this function in implementation classes.
-// 'chunk' is an input chunk.
-//
-// Call `push(newChunk)` to pass along transformed output
-// to the readable side.  You may call 'push' zero or more times.
-//
-// Call `cb(err)` when you are done with this chunk.  If you pass
-// an error, then that'll put the hurt on the whole operation.  If you
-// never call cb(), then you'll never get another chunk.
-Transform.prototype._transform = function(chunk, encoding, cb) {
-  throw new Error('not implemented');
-};
-
-Transform.prototype._write = function(chunk, encoding, cb) {
-  var ts = this._transformState;
-  ts.writecb = cb;
-  ts.writechunk = chunk;
-  ts.writeencoding = encoding;
-  if (!ts.transforming) {
-    var rs = this._readableState;
-    if (ts.needTransform ||
-        rs.needReadable ||
-        rs.length < rs.highWaterMark)
-      this._read(rs.highWaterMark);
-  }
-};
-
-// Doesn't matter what the args are here.
-// _transform does all the work.
-// That we got here means that the readable side wants more data.
-Transform.prototype._read = function(n) {
-  var ts = this._transformState;
-
-  if (!util.isNull(ts.writechunk) && ts.writecb && !ts.transforming) {
-    ts.transforming = true;
-    this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform);
-  } else {
-    // mark that we need a transform, so that any data that comes in
-    // will get processed, now that we've asked for it.
-    ts.needTransform = true;
-  }
-};
-
-
-function done(stream, er) {
-  if (er)
-    return stream.emit('error', er);
-
-  // if there's nothing in the write buffer, then that means
-  // that nothing more will ever be provided
-  var ws = stream._writableState;
-  var ts = stream._transformState;
-
-  if (ws.length)
-    throw new Error('calling transform done when ws.length != 0');
-
-  if (ts.transforming)
-    throw new Error('calling transform done when still transforming');
-
-  return stream.push(null);
-}
-
-},{"./_stream_duplex":53,"core-util-is":58,"inherits":48}],57:[function(require,module,exports){
-(function (process){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-// A bit simpler than readable streams.
-// Implement an async ._write(chunk, cb), and it'll handle all
-// the drain event emission and buffering.
-
-module.exports = Writable;
-
-/*<replacement>*/
-var Buffer = require('buffer').Buffer;
-/*</replacement>*/
-
-Writable.WritableState = WritableState;
-
-
-/*<replacement>*/
-var util = require('core-util-is');
-util.inherits = require('inherits');
-/*</replacement>*/
-
-var Stream = require('stream');
-
-util.inherits(Writable, Stream);
-
-function WriteReq(chunk, encoding, cb) {
-  this.chunk = chunk;
-  this.encoding = encoding;
-  this.callback = cb;
-}
-
-function WritableState(options, stream) {
-  var Duplex = require('./_stream_duplex');
-
-  options = options || {};
-
-  // the point at which write() starts returning false
-  // Note: 0 is a valid value, means that we always return false if
-  // the entire buffer is not flushed immediately on write()
-  var hwm = options.highWaterMark;
-  var defaultHwm = options.objectMode ? 16 : 16 * 1024;
-  this.highWaterMark = (hwm || hwm === 0) ? hwm : defaultHwm;
-
-  // object stream flag to indicate whether or not this stream
-  // contains buffers or objects.
-  this.objectMode = !!options.objectMode;
-
-  if (stream instanceof Duplex)
-    this.objectMode = this.objectMode || !!options.writableObjectMode;
-
-  // cast to ints.
-  this.highWaterMark = ~~this.highWaterMark;
-
-  this.needDrain = false;
-  // at the start of calling end()
-  this.ending = false;
-  // when end() has been called, and returned
-  this.ended = false;
-  // when 'finish' is emitted
-  this.finished = false;
-
-  // should we decode strings into buffers before passing to _write?
-  // this is here so that some node-core streams can optimize string
-  // handling at a lower level.
-  var noDecode = options.decodeStrings === false;
-  this.decodeStrings = !noDecode;
-
-  // Crypto is kind of old and crusty.  Historically, its default string
-  // encoding is 'binary' so we have to make this configurable.
-  // Everything else in the universe uses 'utf8', though.
-  this.defaultEncoding = options.defaultEncoding || 'utf8';
-
-  // not an actual buffer we keep track of, but a measurement
-  // of how much we're waiting to get pushed to some underlying
-  // socket or file.
-  this.length = 0;
-
-  // a flag to see when we're in the middle of a write.
-  this.writing = false;
-
-  // when true all writes will be buffered until .uncork() call
-  this.corked = 0;
-
-  // a flag to be able to tell if the onwrite cb is called immediately,
-  // or on a later tick.  We set this to true at first, because any
-  // actions that shouldn't happen until "later" should generally also
-  // not happen before the first write call.
-  this.sync = true;
-
-  // a flag to know if we're processing previously buffered items, which
-  // may call the _write() callback in the same tick, so that we don't
-  // end up in an overlapped onwrite situation.
-  this.bufferProcessing = false;
-
-  // the callback that's passed to _write(chunk,cb)
-  this.onwrite = function(er) {
-    onwrite(stream, er);
-  };
-
-  // the callback that the user supplies to write(chunk,encoding,cb)
-  this.writecb = null;
-
-  // the amount that is being written when _write is called.
-  this.writelen = 0;
-
-  this.buffer = [];
-
-  // number of pending user-supplied write callbacks
-  // this must be 0 before 'finish' can be emitted
-  this.pendingcb = 0;
-
-  // emit prefinish if the only thing we're waiting for is _write cbs
-  // This is relevant for synchronous Transform streams
-  this.prefinished = false;
-
-  // True if the error was already emitted and should not be thrown again
-  this.errorEmitted = false;
-}
-
-function Writable(options) {
-  var Duplex = require('./_stream_duplex');
-
-  // Writable ctor is applied to Duplexes, though they're not
-  // instanceof Writable, they're instanceof Readable.
-  if (!(this instanceof Writable) && !(this instanceof Duplex))
-    return new Writable(options);
-
-  this._writableState = new WritableState(options, this);
-
-  // legacy.
-  this.writable = true;
-
-  Stream.call(this);
-}
-
-// Otherwise people can pipe Writable streams, which is just wrong.
-Writable.prototype.pipe = function() {
-  this.emit('error', new Error('Cannot pipe. Not readable.'));
-};
-
-
-function writeAfterEnd(stream, state, cb) {
-  var er = new Error('write after end');
-  // TODO: defer error events consistently everywhere, not just the cb
-  stream.emit('error', er);
-  process.nextTick(function() {
-    cb(er);
-  });
-}
-
-// If we get something that is not a buffer, string, null, or undefined,
-// and we're not in objectMode, then that's an error.
-// Otherwise stream chunks are all considered to be of length=1, and the
-// watermarks determine how many objects to keep in the buffer, rather than
-// how many bytes or characters.
-function validChunk(stream, state, chunk, cb) {
-  var valid = true;
-  if (!util.isBuffer(chunk) &&
-      !util.isString(chunk) &&
-      !util.isNullOrUndefined(chunk) &&
-      !state.objectMode) {
-    var er = new TypeError('Invalid non-string/buffer chunk');
-    stream.emit('error', er);
-    process.nextTick(function() {
-      cb(er);
-    });
-    valid = false;
-  }
-  return valid;
-}
-
-Writable.prototype.write = function(chunk, encoding, cb) {
-  var state = this._writableState;
-  var ret = false;
-
-  if (util.isFunction(encoding)) {
-    cb = encoding;
-    encoding = null;
-  }
-
-  if (util.isBuffer(chunk))
-    encoding = 'buffer';
-  else if (!encoding)
-    encoding = state.defaultEncoding;
-
-  if (!util.isFunction(cb))
-    cb = function() {};
-
-  if (state.ended)
-    writeAfterEnd(this, state, cb);
-  else if (validChunk(this, state, chunk, cb)) {
-    state.pendingcb++;
-    ret = writeOrBuffer(this, state, chunk, encoding, cb);
-  }
-
-  return ret;
-};
-
-Writable.prototype.cork = function() {
-  var state = this._writableState;
-
-  state.corked++;
-};
-
-Writable.prototype.uncork = function() {
-  var state = this._writableState;
-
-  if (state.corked) {
-    state.corked--;
-
-    if (!state.writing &&
-        !state.corked &&
-        !state.finished &&
-        !state.bufferProcessing &&
-        state.buffer.length)
-      clearBuffer(this, state);
-  }
-};
-
-function decodeChunk(state, chunk, encoding) {
-  if (!state.objectMode &&
-      state.decodeStrings !== false &&
-      util.isString(chunk)) {
-    chunk = new Buffer(chunk, encoding);
-  }
-  return chunk;
-}
-
-// if we're already writing something, then just put this
-// in the queue, and wait our turn.  Otherwise, call _write
-// If we return false, then we need a drain event, so set that flag.
-function writeOrBuffer(stream, state, chunk, encoding, cb) {
-  chunk = decodeChunk(state, chunk, encoding);
-  if (util.isBuffer(chunk))
-    encoding = 'buffer';
-  var len = state.objectMode ? 1 : chunk.length;
-
-  state.length += len;
-
-  var ret = state.length < state.highWaterMark;
-  // we must ensure that previous needDrain will not be reset to false.
-  if (!ret)
-    state.needDrain = true;
-
-  if (state.writing || state.corked)
-    state.buffer.push(new WriteReq(chunk, encoding, cb));
-  else
-    doWrite(stream, state, false, len, chunk, encoding, cb);
-
-  return ret;
-}
-
-function doWrite(stream, state, writev, len, chunk, encoding, cb) {
-  state.writelen = len;
-  state.writecb = cb;
-  state.writing = true;
-  state.sync = true;
-  if (writev)
-    stream._writev(chunk, state.onwrite);
-  else
-    stream._write(chunk, encoding, state.onwrite);
-  state.sync = false;
-}
-
-function onwriteError(stream, state, sync, er, cb) {
-  if (sync)
-    process.nextTick(function() {
-      state.pendingcb--;
-      cb(er);
-    });
-  else {
-    state.pendingcb--;
-    cb(er);
-  }
-
-  stream._writableState.errorEmitted = true;
-  stream.emit('error', er);
-}
-
-function onwriteStateUpdate(state) {
-  state.writing = false;
-  state.writecb = null;
-  state.length -= state.writelen;
-  state.writelen = 0;
-}
-
-function onwrite(stream, er) {
-  var state = stream._writableState;
-  var sync = state.sync;
-  var cb = state.writecb;
-
-  onwriteStateUpdate(state);
-
-  if (er)
-    onwriteError(stream, state, sync, er, cb);
-  else {
-    // Check if we're actually ready to finish, but don't emit yet
-    var finished = needFinish(stream, state);
-
-    if (!finished &&
-        !state.corked &&
-        !state.bufferProcessing &&
-        state.buffer.length) {
-      clearBuffer(stream, state);
-    }
-
-    if (sync) {
-      process.nextTick(function() {
-        afterWrite(stream, state, finished, cb);
-      });
-    } else {
-      afterWrite(stream, state, finished, cb);
-    }
-  }
-}
-
-function afterWrite(stream, state, finished, cb) {
-  if (!finished)
-    onwriteDrain(stream, state);
-  state.pendingcb--;
-  cb();
-  finishMaybe(stream, state);
-}
-
-// Must force callback to be called on nextTick, so that we don't
-// emit 'drain' before the write() consumer gets the 'false' return
-// value, and has a chance to attach a 'drain' listener.
-function onwriteDrain(stream, state) {
-  if (state.length === 0 && state.needDrain) {
-    state.needDrain = false;
-    stream.emit('drain');
-  }
-}
-
-
-// if there's something in the buffer waiting, then process it
-function clearBuffer(stream, state) {
-  state.bufferProcessing = true;
-
-  if (stream._writev && state.buffer.length > 1) {
-    // Fast case, write everything using _writev()
-    var cbs = [];
-    for (var c = 0; c < state.buffer.length; c++)
-      cbs.push(state.buffer[c].callback);
-
-    // count the one we are adding, as well.
-    // TODO(isaacs) clean this up
-    state.pendingcb++;
-    doWrite(stream, state, true, state.length, state.buffer, '', function(err) {
-      for (var i = 0; i < cbs.length; i++) {
-        state.pendingcb--;
-        cbs[i](err);
-      }
-    });
-
-    // Clear buffer
-    state.buffer = [];
-  } else {
-    // Slow case, write chunks one-by-one
-    for (var c = 0; c < state.buffer.length; c++) {
-      var entry = state.buffer[c];
-      var chunk = entry.chunk;
-      var encoding = entry.encoding;
-      var cb = entry.callback;
-      var len = state.objectMode ? 1 : chunk.length;
-
-      doWrite(stream, state, false, len, chunk, encoding, cb);
-
-      // if we didn't call the onwrite immediately, then
-      // it means that we need to wait until it does.
-      // also, that means that the chunk and cb are currently
-      // being processed, so move the buffer counter past them.
-      if (state.writing) {
-        c++;
-        break;
-      }
-    }
-
-    if (c < state.buffer.length)
-      state.buffer = state.buffer.slice(c);
-    else
-      state.buffer.length = 0;
-  }
-
-  state.bufferProcessing = false;
-}
-
-Writable.prototype._write = function(chunk, encoding, cb) {
-  cb(new Error('not implemented'));
-
-};
-
-Writable.prototype._writev = null;
-
-Writable.prototype.end = function(chunk, encoding, cb) {
-  var state = this._writableState;
-
-  if (util.isFunction(chunk)) {
-    cb = chunk;
-    chunk = null;
-    encoding = null;
-  } else if (util.isFunction(encoding)) {
-    cb = encoding;
-    encoding = null;
-  }
-
-  if (!util.isNullOrUndefined(chunk))
-    this.write(chunk, encoding);
-
-  // .end() fully uncorks
-  if (state.corked) {
-    state.corked = 1;
-    this.uncork();
-  }
-
-  // ignore unnecessary end() calls.
-  if (!state.ending && !state.finished)
-    endWritable(this, state, cb);
-};
-
-
-function needFinish(stream, state) {
-  return (state.ending &&
-          state.length === 0 &&
-          !state.finished &&
-          !state.writing);
-}
-
-function prefinish(stream, state) {
-  if (!state.prefinished) {
-    state.prefinished = true;
-    stream.emit('prefinish');
-  }
-}
-
-function finishMaybe(stream, state) {
-  var need = needFinish(stream, state);
-  if (need) {
-    if (state.pendingcb === 0) {
-      prefinish(stream, state);
-      state.finished = true;
-      stream.emit('finish');
-    } else
-      prefinish(stream, state);
-  }
-  return need;
-}
-
-function endWritable(stream, state, cb) {
-  state.ending = true;
-  finishMaybe(stream, state);
-  if (cb) {
-    if (state.finished)
-      process.nextTick(cb);
-    else
-      stream.once('finish', cb);
-  }
-  state.ended = true;
-}
-
-}).call(this,require('_process'))
-},{"./_stream_duplex":53,"_process":51,"buffer":43,"core-util-is":58,"inherits":48,"stream":63}],58:[function(require,module,exports){
 (function (Buffer){
 // Copyright Joyent, Inc. and other Node contributors.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a
 // copy of this software and associated documentation files (the
 // "Software"), to deal in the Software without restriction, including
 // without limitation the rights to use, copy, modify, merge, publish,
 // distribute, sublicense, and/or sell copies of the Software, and to permit
@@ -10511,18 +8601,22 @@ function endWritable(stream, state, cb) 
 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 // USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 // NOTE: These type checking functions intentionally don't use `instanceof`
 // because it is fragile and can be easily faked with `Object.create()`.
-function isArray(ar) {
-  return Array.isArray(ar);
+
+function isArray(arg) {
+  if (Array.isArray) {
+    return Array.isArray(arg);
+  }
+  return objectToString(arg) === '[object Array]';
 }
 exports.isArray = isArray;
 
 function isBoolean(arg) {
   return typeof arg === 'boolean';
 }
 exports.isBoolean = isBoolean;
 
@@ -10552,33 +8646,32 @@ function isSymbol(arg) {
 exports.isSymbol = isSymbol;
 
 function isUndefined(arg) {
   return arg === void 0;
 }
 exports.isUndefined = isUndefined;
 
 function isRegExp(re) {
-  return isObject(re) && objectToString(re) === '[object RegExp]';
+  return objectToString(re) === '[object RegExp]';
 }
 exports.isRegExp = isRegExp;
 
 function isObject(arg) {
   return typeof arg === 'object' && arg !== null;
 }
 exports.isObject = isObject;
 
 function isDate(d) {
-  return isObject(d) && objectToString(d) === '[object Date]';
+  return objectToString(d) === '[object Date]';
 }
 exports.isDate = isDate;
 
 function isError(e) {
-  return isObject(e) &&
-      (objectToString(e) === '[object Error]' || e instanceof Error);
+  return (objectToString(e) === '[object Error]' || e instanceof Error);
 }
 exports.isError = isError;
 
 function isFunction(arg) {
   return typeof arg === 'function';
 }
 exports.isFunction = isFunction;
 
@@ -10587,993 +8680,24 @@ function isPrimitive(arg) {
          typeof arg === 'boolean' ||
          typeof arg === 'number' ||
          typeof arg === 'string' ||
          typeof arg === 'symbol' ||  // ES6 symbol
          typeof arg === 'undefined';
 }
 exports.isPrimitive = isPrimitive;
 
-function isBuffer(arg) {
-  return Buffer.isBuffer(arg);
-}
-exports.isBuffer = isBuffer;
+exports.isBuffer = Buffer.isBuffer;
 
 function objectToString(o) {
   return Object.prototype.toString.call(o);
 }
-}).call(this,require("buffer").Buffer)
-},{"buffer":43}],59:[function(require,module,exports){
-module.exports = require("./lib/_stream_passthrough.js")
-
-},{"./lib/_stream_passthrough.js":54}],60:[function(require,module,exports){
-exports = module.exports = require('./lib/_stream_readable.js');
-exports.Stream = require('stream');
-exports.Readable = exports;
-exports.Writable = require('./lib/_stream_writable.js');
-exports.Duplex = require('./lib/_stream_duplex.js');
-exports.Transform = require('./lib/_stream_transform.js');
-exports.PassThrough = require('./lib/_stream_passthrough.js');
-
-},{"./lib/_stream_duplex.js":53,"./lib/_stream_passthrough.js":54,"./lib/_stream_readable.js":55,"./lib/_stream_transform.js":56,"./lib/_stream_writable.js":57,"stream":63}],61:[function(require,module,exports){
-module.exports = require("./lib/_stream_transform.js")
-
-},{"./lib/_stream_transform.js":56}],62:[function(require,module,exports){
-module.exports = require("./lib/_stream_writable.js")
-
-},{"./lib/_stream_writable.js":57}],63:[function(require,module,exports){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-module.exports = Stream;
-
-var EE = require('events').EventEmitter;
-var inherits = require('inherits');
-
-inherits(Stream, EE);
-Stream.Readable = require('readable-stream/readable.js');
-Stream.Writable = require('readable-stream/writable.js');
-Stream.Duplex = require('readable-stream/duplex.js');
-Stream.Transform = require('readable-stream/transform.js');
-Stream.PassThrough = require('readable-stream/passthrough.js');
-
-// Backwards-compat with node 0.4.x
-Stream.Stream = Stream;
-
-
-
-// old-style streams.  Note that the pipe method (the only relevant
-// part of this class) is overridden in the Readable class.
-
-function Stream() {
-  EE.call(this);
-}
-
-Stream.prototype.pipe = function(dest, options) {
-  var source = this;
-
-  function ondata(chunk) {
-    if (dest.writable) {
-      if (false === dest.write(chunk) && source.pause) {
-        source.pause();
-      }
-    }
-  }
-
-  source.on('data', ondata);
-
-  function ondrain() {
-    if (source.readable && source.resume) {
-      source.resume();
-    }
-  }
-
-  dest.on('drain', ondrain);
-
-  // If the 'end' option is not supplied, dest.end() will be called when
-  // source gets the 'end' or 'close' events.  Only dest.end() once.
-  if (!dest._isStdio && (!options || options.end !== false)) {
-    source.on('end', onend);
-    source.on('close', onclose);
-  }
-
-  var didOnEnd = false;
-  function onend() {
-    if (didOnEnd) return;
-    didOnEnd = true;
-
-    dest.end();
-  }
-
-
-  function onclose() {
-    if (didOnEnd) return;
-    didOnEnd = true;
-
-    if (typeof dest.destroy === 'function') dest.destroy();
-  }
-
-  // don't leave dangling pipes when there are errors.
-  function onerror(er) {
-    cleanup();
-    if (EE.listenerCount(this, 'error') === 0) {
-      throw er; // Unhandled stream error in pipe.
-    }
-  }
-
-  source.on('error', onerror);
-  dest.on('error', onerror);
-
-  // remove all the event listeners that were added.
-  function cleanup() {
-    source.removeListener('data', ondata);
-    dest.removeListener('drain', ondrain);
-
-    source.removeListener('end', onend);
-    source.removeListener('close', onclose);
-
-    source.removeListener('error', onerror);
-    dest.removeListener('error', onerror);
-
-    source.removeListener('end', cleanup);
-    source.removeListener('close', cleanup);
-
-    dest.removeListener('close', cleanup);
-  }
-
-  source.on('end', cleanup);
-  source.on('close', cleanup);
-
-  dest.on('close', cleanup);
-
-  dest.emit('pipe', source);
-
-  // Allow for unix-like usage: A.pipe(B).pipe(C)
-  return dest;
-};
-
-},{"events":47,"inherits":48,"readable-stream/duplex.js":52,"readable-stream/passthrough.js":59,"readable-stream/readable.js":60,"readable-stream/transform.js":61,"readable-stream/writable.js":62}],64:[function(require,module,exports){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-var Buffer = require('buffer').Buffer;
-
-var isBufferEncoding = Buffer.isEncoding
-  || function(encoding) {
-       switch (encoding && encoding.toLowerCase()) {
-         case 'hex': case 'utf8': case 'utf-8': case 'ascii': case 'binary': case 'base64': case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': case 'raw': return true;
-         default: return false;
-       }
-     }
-
-
-function assertEncoding(encoding) {
-  if (encoding && !isBufferEncoding(encoding)) {
-    throw new Error('Unknown encoding: ' + encoding);
-  }
-}
-
-// StringDecoder provides an interface for efficiently splitting a series of
-// buffers into a series of JS strings without breaking apart multi-byte
-// characters. CESU-8 is handled as part of the UTF-8 encoding.
-//
-// @TODO Handling all encodings inside a single object makes it very difficult
-// to reason about this code, so it should be split up in the future.
-// @TODO There should be a utf8-strict encoding that rejects invalid UTF-8 code
-// points as used by CESU-8.
-var StringDecoder = exports.StringDecoder = function(encoding) {
-  this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, '');
-  assertEncoding(encoding);
-  switch (this.encoding) {
-    case 'utf8':
-      // CESU-8 represents each of Surrogate Pair by 3-bytes
-      this.surrogateSize = 3;
-      break;
-    case 'ucs2':
-    case 'utf16le':
-      // UTF-16 represents each of Surrogate Pair by 2-bytes
-      this.surrogateSize = 2;
-      this.detectIncompleteChar = utf16DetectIncompleteChar;
-      break;
-    case 'base64':
-      // Base-64 stores 3 bytes in 4 chars, and pads the remainder.
-      this.surrogateSize = 3;
-      this.detectIncompleteChar = base64DetectIncompleteChar;
-      break;
-    default:
-      this.write = passThroughWrite;
-      return;
-  }
-
-  // Enough space to store all bytes of a single character. UTF-8 needs 4
-  // bytes, but CESU-8 may require up to 6 (3 bytes per surrogate).
-  this.charBuffer = new Buffer(6);
-  // Number of bytes received for the current incomplete multi-byte character.
-  this.charReceived = 0;
-  // Number of bytes expected for the current incomplete multi-byte character.
-  this.charLength = 0;
-};
-
-
-// write decodes the given buffer and returns it as JS string that is
-// guaranteed to not contain any partial multi-byte characters. Any partial
-// character found at the end of the buffer is buffered up, and will be
-// returned when calling write again with the remaining bytes.
-//
-// Note: Converting a Buffer containing an orphan surrogate to a String
-// currently works, but converting a String to a Buffer (via `new Buffer`, or
-// Buffer#write) will replace incomplete surrogates with the unicode
-// replacement character. See https://codereview.chromium.org/121173009/ .
-StringDecoder.prototype.write = function(buffer) {
-  var charStr = '';
-  // if our last write ended with an incomplete multibyte character
-  while (this.charLength) {
-    // determine how many remaining bytes this buffer has to offer for this char
-    var available = (buffer.length >= this.charLength - this.charReceived) ?
-        this.charLength - this.charReceived :
-        buffer.length;
-
-    // add the new bytes to the char buffer
-    buffer.copy(this.charBuffer, this.charReceived, 0, available);
-    this.charReceived += available;
-
-    if (this.charReceived < this.charLength) {
-      // still not enough chars in this buffer? wait for more ...
-      return '';
-    }
-
-    // remove bytes belonging to the current character from the buffer
-    buffer = buffer.slice(available, buffer.length);
-
-    // get the character that was split
-    charStr = this.charBuffer.slice(0, this.charLength).toString(this.encoding);
-
-    // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character
-    var charCode = charStr.charCodeAt(charStr.length - 1);
-    if (charCode >= 0xD800 && charCode <= 0xDBFF) {
-      this.charLength += this.surrogateSize;
-      charStr = '';
-      continue;
-    }
-    this.charReceived = this.charLength = 0;
-
-    // if there are no more bytes in this buffer, just emit our char
-    if (buffer.length === 0) {
-      return charStr;
-    }
-    break;
-  }
-
-  // determine and set charLength / charReceived
-  this.detectIncompleteChar(buffer);
-
-  var end = buffer.length;
-  if (this.charLength) {
-    // buffer the incomplete character bytes we got
-    buffer.copy(this.charBuffer, 0, buffer.length - this.charReceived, end);
-    end -= this.charReceived;
-  }
-
-  charStr += buffer.toString(this.encoding, 0, end);
-
-  var end = charStr.length - 1;
-  var charCode = charStr.charCodeAt(end);
-  // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character
-  if (charCode >= 0xD800 && charCode <= 0xDBFF) {
-    var size = this.surrogateSize;
-    this.charLength += size;
-    this.charReceived += size;
-    this.charBuffer.copy(this.charBuffer, size, 0, size);
-    buffer.copy(this.charBuffer, 0, 0, size);
-    return charStr.substring(0, end);
-  }
-
-  // or just emit the charStr
-  return charStr;
-};
-
-// detectIncompleteChar determines if there is an incomplete UTF-8 character at
-// the end of the given buffer. If so, it sets this.charLength to the byte
-// length that character, and sets this.charReceived to the number of bytes
-// that are available for this character.
-StringDecoder.prototype.detectIncompleteChar = function(buffer) {
-  // determine how many bytes we have to check at the end of this buffer
-  var i = (buffer.length >= 3) ? 3 : buffer.length;
-
-  // Figure out if one of the last i bytes of our buffer announces an
-  // incomplete char.
-  for (; i > 0; i--) {
-    var c = buffer[buffer.length - i];
-
-    // See http://en.wikipedia.org/wiki/UTF-8#Description
-
-    // 110XXXXX
-    if (i == 1 && c >> 5 == 0x06) {
-      this.charLength = 2;
-      break;
-    }
-
-    // 1110XXXX
-    if (i <= 2 && c >> 4 == 0x0E) {
-      this.charLength = 3;
-      break;
-    }
-
-    // 11110XXX
-    if (i <= 3 && c >> 3 == 0x1E) {
-      this.charLength = 4;
-      break;
-    }
-  }
-  this.charReceived = i;
-};
-
-StringDecoder.prototype.end = function(buffer) {
-  var res = '';
-  if (buffer && buffer.length)
-    res = this.write(buffer);
-
-  if (this.charReceived) {
-    var cr = this.charReceived;
-    var buf = this.charBuffer;
-    var enc = this.encoding;
-    res += buf.slice(0, cr).toString(enc);
-  }
-
-  return res;
-};
-
-function passThroughWrite(buffer) {
-  return buffer.toString(this.encoding);
-}
-
-function utf16DetectIncompleteChar(buffer) {
-  this.charReceived = buffer.length % 2;
-  this.charLength = this.charReceived ? 2 : 0;
-}
-
-function base64DetectIncompleteChar(buffer) {
-  this.charReceived = buffer.length % 3;
-  this.charLength = this.charReceived ? 3 : 0;
-}
-
-},{"buffer":43}],65:[function(require,module,exports){
-module.exports = function isBuffer(arg) {
-  return arg && typeof arg === 'object'
-    && typeof arg.copy === 'function'
-    && typeof arg.fill === 'function'
-    && typeof arg.readUInt8 === 'function';
-}
-},{}],66:[function(require,module,exports){
-(function (process,global){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-var formatRegExp = /%[sdj%]/g;
-exports.format = function(f) {
-  if (!isString(f)) {
-    var objects = [];
-    for (var i = 0; i < arguments.length; i++) {
-      objects.push(inspect(arguments[i]));
-    }
-    return objects.join(' ');
-  }
-
-  var i = 1;
-  var args = arguments;
-  var len = args.length;
-  var str = String(f).replace(formatRegExp, function(x) {
-    if (x === '%%') return '%';
-    if (i >= len) return x;
-    switch (x) {
-      case '%s': return String(args[i++]);
-      case '%d': return Number(args[i++]);
-      case '%j':
-        try {
-          return JSON.stringify(args[i++]);
-        } catch (_) {
-          return '[Circular]';
-        }
-      default:
-        return x;
-    }
-  });
-  for (var x = args[i]; i < len; x = args[++i]) {
-    if (isNull(x) || !isObject(x)) {
-      str += ' ' + x;
-    } else {
-      str += ' ' + inspect(x);
-    }
-  }
-  return str;
-};
-
-
-// Mark that a method should not be used.
-// Returns a modified function which warns once by default.
-// If --no-deprecation is set, then it is a no-op.
-exports.deprecate = function(fn, msg) {
-  // Allow for deprecating things in the process of starting up.
-  if (isUndefined(global.process)) {
-    return function() {
-      return exports.deprecate(fn, msg).apply(this, arguments);
-    };
-  }
-
-  if (process.noDeprecation === true) {
-    return fn;
-  }
-
-  var warned = false;
-  function deprecated() {
-    if (!warned) {
-      if (process.throwDeprecation) {
-        throw new Error(msg);
-      } else if (process.traceDeprecation) {
-        console.trace(msg);
-      } else {
-        console.error(msg);
-      }
-      warned = true;
-    }
-    return fn.apply(this, arguments);
-  }
-
-  return deprecated;
-};
-
-
-var debugs = {};
-var debugEnviron;
-exports.debuglog = function(set) {
-  if (isUndefined(debugEnviron))
-    debugEnviron = process.env.NODE_DEBUG || '';
-  set = set.toUpperCase();
-  if (!debugs[set]) {
-    if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) {
-      var pid = process.pid;
-      debugs[set] = function() {
-        var msg = exports.format.apply(exports, arguments);
-        console.error('%s %d: %s', set, pid, msg);
-      };
-    } else {
-      debugs[set] = function() {};
-    }
-  }
-  return debugs[set];
-};
-
-
-/**
- * Echos the value of a value. Trys to print the value out
- * in the best way possible given the different types.
- *
- * @param {Object} obj The object to print out.
- * @param {Object} opts Optional options object that alters the output.
- */
-/* legacy: obj, showHidden, depth, colors*/
-function inspect(obj, opts) {
-  // default options
-  var ctx = {
-    seen: [],
-    stylize: stylizeNoColor
-  };
-  // legacy...
-  if (arguments.length >= 3) ctx.depth = arguments[2];
-  if (arguments.length >= 4) ctx.colors = arguments[3];
-  if (isBoolean(opts)) {
-    // legacy...
-    ctx.showHidden = opts;
-  } else if (opts) {
-    // got an "options" object
-    exports._extend(ctx, opts);
-  }
-  // set default options
-  if (isUndefined(ctx.showHidden)) ctx.showHidden = false;
-  if (isUndefined(ctx.depth)) ctx.depth = 2;
-  if (isUndefined(ctx.colors)) ctx.colors = false;
-  if (isUndefined(ctx.customInspect)) ctx.customInspect = true;
-  if (ctx.colors) ctx.stylize = stylizeWithColor;
-  return formatValue(ctx, obj, ctx.depth);
-}
-exports.inspect = inspect;
-
-
-// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
-inspect.colors = {
-  'bold' : [1, 22],
-  'italic' : [3, 23],
-  'underline' : [4, 24],
-  'inverse' : [7, 27],
-  'white' : [37, 39],
-  'grey' : [90, 39],
-  'black' : [30, 39],
-  'blue' : [34, 39],
-  'cyan' : [36, 39],
-  'green' : [32, 39],
-  'magenta' : [35, 39],
-  'red' : [31, 39],
-  'yellow' : [33, 39]
-};
-
-// Don't use 'blue' not visible on cmd.exe
-inspect.styles = {
-  'special': 'cyan',
-  'number': 'yellow',
-  'boolean': 'yellow',
-  'undefined': 'grey',
-  'null': 'bold',
-  'string': 'green',
-  'date': 'magenta',
-  // "name": intentionally not styling
-  'regexp': 'red'
-};
-
-
-function stylizeWithColor(str, styleType) {
-  var style = inspect.styles[styleType];
-
-  if (style) {
-    return '\u001b[' + inspect.colors[style][0] + 'm' + str +
-           '\u001b[' + inspect.colors[style][1] + 'm';
-  } else {
-    return str;
-  }
-}
-
-
-function stylizeNoColor(str, styleType) {
-  return str;
-}
-
-
-function arrayToHash(array) {
-  var hash = {};
-
-  array.forEach(function(val, idx) {
-    hash[val] = true;
-  });
-
-  return hash;
-}
-
-
-function formatValue(ctx, value, recurseTimes) {
-  // Provide a hook for user-specified inspect functions.
-  // Check that value is an object with an inspect function on it
-  if (ctx.customInspect &&
-      value &&
-      isFunction(value.inspect) &&
-      // Filter out the util module, it's inspect function is special
-      value.inspect !== exports.inspect &&
-      // Also filter out any prototype objects using the circular check.
-      !(value.constructor && value.constructor.prototype === value)) {
-    var ret = value.inspect(recurseTimes, ctx);
-    if (!isString(ret)) {
-      ret = formatValue(ctx, ret, recurseTimes);
-    }
-    return ret;
-  }
-
-  // Primitive types cannot have properties
-  var primitive = formatPrimitive(ctx, value);
-  if (primitive) {
-    return primitive;
-  }
-
-  // Look up the keys of the object.
-  var keys = Object.keys(value);
-  var visibleKeys = arrayToHash(keys);
-
-  if (ctx.showHidden) {
-    keys = Object.getOwnPropertyNames(value);
-  }
-
-  // IE doesn't make error fields non-enumerable
-  // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx
-  if (isError(value)
-      && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) {
-    return formatError(value);
-  }
-
-  // Some type of object without properties can be shortcutted.
-  if (keys.length === 0) {
-    if (isFunction(value)) {
-      var name = value.name ? ': ' + value.name : '';
-      return ctx.stylize('[Function' + name + ']', 'special');
-    }
-    if (isRegExp(value)) {
-      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
-    }
-    if (isDate(value)) {
-      return ctx.stylize(Date.prototype.toString.call(value), 'date');
-    }
-    if (isError(value)) {
-      return formatError(value);
-    }
-  }
-
-  var base = '', array = false, braces = ['{', '}'];
-
-  // Make Array say that they are Array
-  if (isArray(value)) {
-    array = true;
-    braces = ['[', ']'];
-  }
-
-  // Make functions say that they are functions
-  if (isFunction(value)) {
-    var n = value.name ? ': ' + value.name : '';
-    base = ' [Function' + n + ']';
-  }
-
-  // Make RegExps say that they are RegExps
-  if (isRegExp(value)) {
-    base = ' ' + RegExp.prototype.toString.call(value);
-  }
-
-  // Make dates with properties first say the date
-  if (isDate(value)) {
-    base = ' ' + Date.prototype.toUTCString.call(value);
-  }
-
-  // Make error with message first say the error
-  if (isError(value)) {
-    base = ' ' + formatError(value);
-  }
-
-  if (keys.length === 0 && (!array || value.length == 0)) {
-    return braces[0] + base + braces[1];
-  }
-
-  if (recurseTimes < 0) {
-    if (isRegExp(value)) {
-      return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp');
-    } else {
-      return ctx.stylize('[Object]', 'special');
-    }
-  }
-
-  ctx.seen.push(value);
-
-  var output;
-  if (array) {
-    output = formatArray(ctx, value, recurseTimes, visibleKeys, keys);
-  } else {
-    output = keys.map(function(key) {
-      return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array);
-    });
-  }
-
-  ctx.seen.pop();
-
-  return reduceToSingleString(output, base, braces);
-}
-
-
-function formatPrimitive(ctx, value) {
-  if (isUndefined(value))
-    return ctx.stylize('undefined', 'undefined');
-  if (isString(value)) {
-    var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
-                                             .replace(/'/g, "\\'")
-                                             .replace(/\\"/g, '"') + '\'';
-    return ctx.stylize(simple, 'string');
-  }
-  if (isNumber(value))
-    return ctx.stylize('' + value, 'number');
-  if (isBoolean(value))
-    return ctx.stylize('' + value, 'boolean');
-  // For some reason typeof null is "object", so special case here.
-  if (isNull(value))
-    return ctx.stylize('null', 'null');
-}
-
-
-function formatError(value) {
-  return '[' + Error.prototype.toString.call(value) + ']';
-}
-
-
-function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
-  var output = [];
-  for (var i = 0, l = value.length; i < l; ++i) {
-    if (hasOwnProperty(value, String(i))) {
-      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
-          String(i), true));
-    } else {
-      output.push('');
-    }
-  }
-  keys.forEach(function(key) {
-    if (!key.match(/^\d+$/)) {
-      output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
-          key, true));
-    }
-  });
-  return output;
-}
-
-
-function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
-  var name, str, desc;
-  desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] };
-  if (desc.get) {
-    if (desc.set) {
-      str = ctx.stylize('[Getter/Setter]', 'special');
-    } else {
-      str = ctx.stylize('[Getter]', 'special');
-    }
-  } else {
-    if (desc.set) {
-      str = ctx.stylize('[Setter]', 'special');
-    }
-  }
-  if (!hasOwnProperty(visibleKeys, key)) {
-    name = '[' + key + ']';
-  }
-  if (!str) {
-    if (ctx.seen.indexOf(desc.value) < 0) {
-      if (isNull(recurseTimes)) {
-        str = formatValue(ctx, desc.value, null);
-      } else {
-        str = formatValue(ctx, desc.value, recurseTimes - 1);
-      }
-      if (str.indexOf('\n') > -1) {
-        if (array) {
-          str = str.split('\n').map(function(line) {
-            return '  ' + line;
-          }).join('\n').substr(2);
-        } else {
-          str = '\n' + str.split('\n').map(function(line) {
-            return '   ' + line;
-          }).join('\n');
-        }
-      }
-    } else {
-      str = ctx.stylize('[Circular]', 'special');
-    }
-  }
-  if (isUndefined(name)) {
-    if (array && key.match(/^\d+$/)) {
-      return str;
-    }
-    name = JSON.stringify('' + key);
-    if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) {
-      name = name.substr(1, name.length - 2);
-      name = ctx.stylize(name, 'name');
-    } else {
-      name = name.replace(/'/g, "\\'")
-                 .replace(/\\"/g, '"')
-                 .replace(/(^"|"$)/g, "'");
-      name = ctx.stylize(name, 'string');
-    }
-  }
-
-  return name + ': ' + str;
-}
-
-
-function reduceToSingleString(output, base, braces) {
-  var numLinesEst = 0;
-  var length = output.reduce(function(prev, cur) {
-    numLinesEst++;
-    if (cur.indexOf('\n') >= 0) numLinesEst++;
-    return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1;
-  }, 0);
-
-  if (length > 60) {
-    return braces[0] +
-           (base === '' ? '' : base + '\n ') +
-           ' ' +
-           output.join(',\n  ') +
-           ' ' +
-           braces[1];
-  }
-
-  return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1];
-}
-
-
-// NOTE: These type checking functions intentionally don't use `instanceof`
-// because it is fragile and can be easily faked with `Object.create()`.
-function isArray(ar) {
-  return Array.isArray(ar);
-}
-exports.isArray = isArray;
-
-function isBoolean(arg) {
-  return typeof arg === 'boolean';
-}
-exports.isBoolean = isBoolean;
-
-function isNull(arg) {
-  return arg === null;
-}
-exports.isNull = isNull;
-
-function isNullOrUndefined(arg) {
-  return arg == null;
-}
-exports.isNullOrUndefined = isNullOrUndefined;
-
-function isNumber(arg) {
-  return typeof arg === 'number';
-}
-exports.isNumber = isNumber;
-
-function isString(arg) {
-  return typeof arg === 'string';
-}
-exports.isString = isString;
-
-function isSymbol(arg) {
-  return typeof arg === 'symbol';
-}
-exports.isSymbol = isSymbol;
-
-function isUndefined(arg) {
-  return arg === void 0;
-}
-exports.isUndefined = isUndefined;
-
-function isRegExp(re) {
-  return isObject(re) && objectToString(re) === '[object RegExp]';
-}
-exports.isRegExp = isRegExp;
-
-function isObject(arg) {
-  return typeof arg === 'object' && arg !== null;
-}
-exports.isObject = isObject;
-
-function isDate(d) {
-  return isObject(d) && objectToString(d) === '[object Date]';
-}
-exports.isDate = isDate;
-
-function isError(e) {
-  return isObject(e) &&
-      (objectToString(e) === '[object Error]' || e instanceof Error);
-}
-exports.isError = isError;
-
-function isFunction(arg) {
-  return typeof arg === 'function';
-}
-exports.isFunction = isFunction;
-
-function isPrimitive(arg) {
-  return arg === null ||
-         typeof arg === 'boolean' ||
-         typeof arg === 'number' ||
-         typeof arg === 'string' ||
-         typeof arg === 'symbol' ||  // ES6 symbol
-         typeof arg === 'undefined';
-}
-exports.isPrimitive = isPrimitive;
-
-exports.isBuffer = require('./support/isBuffer');
-
-function objectToString(o) {
-  return Object.prototype.toString.call(o);
-}
-
-
-function pad(n) {
-  return n < 10 ? '0' + n.toString(10) : n.toString(10);
-}
-
-
-var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'