You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openmeetings.apache.org by so...@apache.org on 2020/09/12 07:59:59 UTC

[openmeetings] branch master updated: [OPENMEETINGS-2432] rate limit is being checked

This is an automated email from the ASF dual-hosted git repository.

solomax pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openmeetings.git


The following commit(s) were added to refs/heads/master by this push:
     new 6992946  [OPENMEETINGS-2432] rate limit is being checked
6992946 is described below

commit 69929466818fe291e450f9854a8a3627a83b6325
Author: Maxim Solodovnik <so...@gmail.com>
AuthorDate: Sat Sep 12 14:59:44 2020 +0700

    [OPENMEETINGS-2432] rate limit is being checked
---
 openmeetings-web/pom.xml                           |    1 -
 .../org/apache/openmeetings/web/room/network.js    | 2171 --------------------
 .../apache/openmeetings/web/room/raw-nettest.js    |  213 +-
 .../main/webapp/WEB-INF/classes/cxf-servlet.xml    |    9 +-
 .../src/main/webapp/css/raw-nettest.css            |    3 +
 .../openmeetings/webservice/NetTestWebService.java |    8 +-
 .../webservice/util/RateLimitRequestFilter.java    |   63 +
 .../openmeetings/webservice/util/RateLimited.java  |   34 +
 8 files changed, 250 insertions(+), 2252 deletions(-)

diff --git a/openmeetings-web/pom.xml b/openmeetings-web/pom.xml
index 47e665d..4f297e1 100644
--- a/openmeetings-web/pom.xml
+++ b/openmeetings-web/pom.xml
@@ -242,7 +242,6 @@
 							<jsSourceDir>../java/org/apache/openmeetings/web/room</jsSourceDir>
 							<jsSourceFiles>
 								<jsSourceFile>raw-nettest.js</jsSourceFile>
-								<jsSourceFile>network.js</jsSourceFile>
 							</jsSourceFiles>
 							<jsFinalFile>nettest.js</jsFinalFile>
 							<jsEngine>CLOSURE</jsEngine>
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/network.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/network.js
deleted file mode 100644
index 5f34968..0000000
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/network.js
+++ /dev/null
@@ -1,2171 +0,0 @@
-/* Licensed MIT https://github.com/nesk/network.js/blob/master/LICENSE */
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Network = f()}})(function(){var define,module,exports;return (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 [...]
-'use strict';
-
-Object.defineProperty(exports, '__esModule', {
-    value: true
-});
-
-var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) {  [...]
-
-function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
-
-function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer.call(target); Object.defineProperty(target, key, descriptor); }
-
-var _utilsDecorators = require('../utils/decorators');
-
-/**
- * A callback used as an event handler.
- * @public
- * @callback EventDispatcher~eventHandler
- * @param {...*} args The extra parameters provided to the `trigger` method.
- * @returns {?boolean} If `false` is explicitly returned, the `trigger` method will return `false`.
- */
-
-/**
- * @class EventDispatcher
- */
-
-var EventDispatcher = (function () {
-    var _instanceInitializers = {};
-
-    function EventDispatcher() {
-        _classCallCheck(this, EventDispatcher);
-
-        _defineDecoratedPropertyDescriptor(this, '_eventCallbacks', _instanceInitializers);
-    }
-
-    _createDecoratedClass(EventDispatcher, [{
-        key: 'on',
-
-        /**
-         * Attach a callback to one or more events.
-         * @public
-         * @method EventDispatcher#on
-         * @param {string|string[]} events One or multiple event names.
-         * @param {EventDispatcher~eventHandler} callback An event handler.
-         * @returns {EventDispatcher}
-         */
-        value: function on(events, callback) {
-            var _this = this;
-
-            events = Array.isArray(events) ? events : [events];
-
-            events.forEach(function (event) {
-                var eventCallbacks = _this._eventCallbacks[event] = _this._eventCallbacks[event] || [];
-
-                // If the callback isn't already registered, store it.
-                if (! ~eventCallbacks.indexOf(callback)) {
-                    eventCallbacks.push(callback);
-                }
-            });
-
-            return this;
-        }
-
-        /**
-         * Detach a callback from one or more events.
-         * @public
-         * @method EventDispatcher#off
-         * @param {string|string[]} events One or multiple event names.
-         * @param {EventDispatcher~eventHandler} [callback=null] An event handler.
-         * @returns {EventDispatcher}
-         */
-    }, {
-        key: 'off',
-        value: function off(events) {
-            var _this2 = this;
-
-            var callback = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1];
-
-            events = Array.isArray(events) ? events : [events];
-
-            events.forEach(function (event) {
-                var eventCallbacks = _this2._eventCallbacks[event];
-
-                // If there is no specified callback, simply delete all the callbacks binded to the provided event.
-                if (!callback && eventCallbacks) {
-                    delete _this2._eventCallbacks[event];
-                } else {
-                    var callbackIndex = eventCallbacks ? eventCallbacks.indexOf(callback) : -1;
-
-                    // If the callback is registered, remove it from the array.
-                    if (callbackIndex != -1) {
-                        eventCallbacks.splice(callbackIndex, 1);
-                    }
-                }
-            });
-
-            return this;
-        }
-
-        /**
-         * Trigger an event.
-         * @public
-         * @method EventDispatcher#trigger
-         * @param {string} event An event name.
-         * @param {...*} extraParameters Some extra parameters to pass to the event handlers.
-         * @returns {boolean} Returns `false` if one of the event handlers explicitly returned `false`.
-         */
-    }, {
-        key: 'trigger',
-        value: function trigger(event) {
-            for (var _len = arguments.length, extraParameters = Array(_len > 1 ? _len - 1 : 0), _key2 = 1; _key2 < _len; _key2++) {
-                extraParameters[_key2 - 1] = arguments[_key2];
-            }
-
-            var eventCallbacks = this._eventCallbacks[event] || [];
-
-            // A callback can return a boolean value which will be logically compared to the other callbacks values before
-            // being returned by the trigger() method. This allows a callback to send a "signal" to the caller, like
-            // cancelling an action.
-            var returnValue = true;
-
-            eventCallbacks.forEach(function (eventCallback) {
-                // A callback must explicitly return false if it wants the trigger() method to return false, undefined will
-                // not work. This avoids crappy callbacks to mess up with the triggering system.
-                var value = eventCallback.apply(undefined, extraParameters);
-                value = value !== false ? true : false;
-
-                returnValue = returnValue && value; // Compare the result of the callback to the actual return value
-            });
-
-            return returnValue;
-        }
-    }, {
-        key: '_eventCallbacks',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return {};
-        },
-        enumerable: true
-    }], null, _instanceInitializers);
-
-    return EventDispatcher;
-})();
-
-exports['default'] = EventDispatcher;
-module.exports = exports['default'];
-
-/**
- * All the registered event callbacks, organized by events.
- * @private
- * @member {Object} EventDispatcher#_eventCallbacks
- */
-
-},{"../utils/decorators":7}],2:[function(require,module,exports){
-'use strict';
-
-Object.defineProperty(exports, '__esModule', {
-    value: true
-});
-
-var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) {  [...]
-
-var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; continue  [...]
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
-
-function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
-
-function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer.call(target); Object.defineProperty(target, key, descriptor); }
-
-var _HttpModule2 = require('./HttpModule');
-
-var _HttpModule3 = _interopRequireDefault(_HttpModule2);
-
-var _Timing = require('../Timing');
-
-var _Timing2 = _interopRequireDefault(_Timing);
-
-var _utilsHelpers = require('../../utils/helpers');
-
-var _utilsDecorators = require('../../utils/decorators');
-
-/**
- * @public
- * @typedef {Object} BandwidthModule~settingsObject
- * @extends HttpModule~settingsObject
- * @property {Object} data
- * @property {number} data.size The amount of data to initially use.
- * @property {number} [data.multiplier=2] If the measure period can't reach the delay defined in the settings, the data amount is multiplied by the following value.
- */
-
-/**
- * Apply a new set of custom settings.
- * @public
- * @method BandwidthModule#settings
- * @param {BandwidthModule~settingsObject} settings A set of custom settings.
- * @returns {BandwidthModule}
- */
-/**
- * Return the current set of settings.
- * @public
- * @method BandwidthModule#settings^2
- * @returns {BandwidthModule~settingsObject}
- */
-
-/**
- * @class BandwidthModule
- * @extends HttpModule
- * @param {string} loadingType The loading type, `upload` or `download`.
- * @param {BandwidthModule~settingsObject} [settings={}] A set of custom settings.
- */
-
-var BandwidthModule = (function (_HttpModule) {
-    var _instanceInitializers = {};
-    var _instanceInitializers = {};
-
-    _inherits(BandwidthModule, _HttpModule);
-
-    _createDecoratedClass(BandwidthModule, [{
-        key: '_loadingType',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return undefined;
-        },
-
-        /**
-         *
-         * @private
-         * @member {boolean} BandwidthModule#_intendedEnd
-         */
-        enumerable: true
-    }, {
-        key: '_intendedEnd',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return false;
-        },
-
-        /**
-         *
-         * @private
-         * @member {boolean} BandwidthModule#_isRestarting
-         */
-        enumerable: true
-    }, {
-        key: '_isRestarting',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return false;
-        },
-
-        /**
-         * Tracks the value of the `loaded` property for each progress event.
-         * @private
-         * @member {?number} BandwidthModule#_lastLoadedValue
-         */
-        enumerable: true
-    }, {
-        key: '_lastLoadedValue',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return null;
-        },
-
-        /**
-         * The recorded measures of speed.
-         * @private
-         * @member {number[]} BandwidthModule#_speedRecords
-         */
-        enumerable: true
-    }, {
-        key: '_speedRecords',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return [];
-        },
-
-        /**
-         * The average speed.
-         * @private
-         * @member {number} BandwidthModule#_avgSpeed
-         */
-        enumerable: true
-    }, {
-        key: '_avgSpeed',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return undefined;
-        },
-
-        /**
-         * The ID of the current request.
-         * @private
-         * @member {number} BandwidthModule#_requestID
-         */
-        enumerable: true
-    }, {
-        key: '_requestID',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return 0;
-        },
-
-        /**
-         * The ID of the current progress event.
-         * @private
-         * @member {number} BandwidthModule#_progressID
-         */
-        enumerable: true
-    }, {
-        key: '_progressID',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return 0;
-        },
-
-        /**
-         * Defines if measures have started.
-         * @private
-         * @member {boolean} BandwidthModule#_started
-         */
-        enumerable: true
-    }, {
-        key: '_started',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return false;
-        },
-
-        /**
-         * Defines if the current progress event is the first one triggered for the current request.
-         * @private
-         * @member {boolean} BandwidthModule#_firstProgress
-         */
-        enumerable: true
-    }, {
-        key: '_firstProgress',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return true;
-        },
-
-        /**
-         * @private
-         * @member {Defer} BandwidthModule#_deferredProgress
-         */
-        enumerable: true
-    }, {
-        key: '_deferredProgress',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return undefined;
-        },
-
-        /**
-         * Unique labels for each request, exclusively used to make measures.
-         * @private
-         * @member {Object} BandwidthModule#_timingLabels
-         * @property {?string} start
-         * @property {?string} progress
-         * @property {?string} end
-         * @property {?string} measure
-         */
-        enumerable: true
-    }, {
-        key: '_timingLabels',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return {
-                start: null,
-                progress: null,
-                end: null,
-                measure: null
-            };
-        },
-        enumerable: true
-    }], null, _instanceInitializers);
-
-    function BandwidthModule(loadingType) {
-        var _this = this;
-
-        var settings = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
-
-        _classCallCheck(this, BandwidthModule);
-
-        loadingType = ~['upload', 'download'].indexOf(loadingType) ? loadingType : 'download';
-
-        _get(Object.getPrototypeOf(BandwidthModule.prototype), 'constructor', this).call(this, loadingType);
-
-        _defineDecoratedPropertyDescriptor(this, '_loadingType', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_intendedEnd', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_isRestarting', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_lastLoadedValue', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_speedRecords', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_avgSpeed', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_requestID', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_progressID', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_started', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_firstProgress', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_deferredProgress', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_timingLabels', _instanceInitializers);
-
-        this._extendDefaultSettings({
-            data: {
-                // 2 MB for upload, 10 MB for download
-                size: loadingType == 'upload' ? 2 * 1024 * 1024 : 10 * 1024 * 1024,
-                multiplier: 2
-            }
-        }).settings(settings);
-
-        this._loadingType = loadingType;
-
-        // Bind to XHR events
-        this.on('xhr-upload-loadstart', function () {
-            return _Timing2['default'].mark(_this._timingLabels.start);
-        });
-        this.on('xhr-readystatechange', function (xhr) {
-            if (!_this._started && xhr.readyState == XMLHttpRequest.LOADING) {
-                _Timing2['default'].mark(_this._timingLabels.start);
-                _this._started = true;
-            }
-        });
-
-        var eventsPrefix = loadingType == 'upload' ? 'xhr-upload' : 'xhr';
-
-        this.on(eventsPrefix + '-progress', function (xhr, event) {
-            return _this._progress(event);
-        });
-        this.on(eventsPrefix + '-timeout', function () {
-            return _this._timeout();
-        });
-        this.on(eventsPrefix + '-loadend', function () {
-            return _this._end();
-        });
-    }
-
-    /**
-     * Start requesting the server to make measures.
-     * @public
-     * @method BandwidthModule#start
-     * @returns {BandwidthModule}
-     */
-
-    _createDecoratedClass(BandwidthModule, [{
-        key: 'start',
-        value: function start() {
-            var loadingType = this._loadingType,
-                dataSettings = this.settings().data,
-                reqID = this._requestID++;
-
-            this._intendedEnd = false;
-            this._lastLoadedValue = null;
-            this._speedRecords = [];
-            this._started = false;
-            this._firstProgress = true;
-            this._deferredProgress = (0, _utilsHelpers.defer)();
-
-            // Trigger the start event
-            if (!this._isRestarting) {
-                this.trigger('start', dataSettings.size);
-            }
-
-            // Create unique timing labels for the new request
-            var labels = this._timingLabels;
-            labels.start = loadingType + '-' + reqID + '-start';
-            labels.progress = loadingType + '-' + reqID + '-progress';
-            labels.end = loadingType + '-' + reqID + '-end';
-            labels.measure = loadingType + '-' + reqID + '-measure';
-
-            // Generate some random data to upload to the server. Here we're using a Blob instead of an ArrayBuffer because
-            // of a bug in Chrome (tested in v33.0.1750.146), causing a freeze of the page while trying to directly upload
-            // an ArrayBuffer (through an ArrayBufferView). The freeze lasts nearly 4.5s for 10MB of data. Using a Blob
-            // seems to solve the problem.
-            var blob = loadingType == 'upload' ? new Blob([new ArrayBuffer(dataSettings.size)]) : null;
-
-            var type = loadingType == 'download' ? 'GET' : 'POST';
-
-            // Initiate and send a new request
-            this._newRequest(type, {
-                size: dataSettings.size
-            })._sendRequest(blob);
-
-            return this;
-        }
-
-        /**
-         * Abort the measures.
-         * @public
-         * @method BandwidthModule#abort
-         * @returns {BandwidthModule}
-         */
-    }, {
-        key: 'abort',
-        value: function abort() {
-            this._intendedEnd = true;
-            return this._abort();
-        }
-
-        /**
-         * Make bandwidth measures for the current request.
-         * @private
-         * @method BandwidthModule#_progress
-         * @param {ProgressEvent} event The event associated with the progress event of the current request.
-         * @returns {BandwidthModule}
-         */
-    }, {
-        key: '_progress',
-        value: function _progress(event) {
-            var _this2 = this;
-
-            // Ignore the first progress event, it generally contributes to get incoherent values.
-            if (this._firstProgress) return this._firstProgress = false;
-
-            // Execute the previous progress trigger
-            this._deferredProgress.run();
-
-            var labels = this._timingLabels,
-                progressID = this._progressID++,
-                markLabel = labels.progress + '-' + progressID,
-                loaded = event.loaded;
-
-            _Timing2['default'].mark(markLabel);
-
-            // Measure the average speed (B/s) since the request started
-            var avgMeasure = _Timing2['default'].measure(labels.measure + '-avg-' + progressID, labels.start, markLabel),
-                avgSpeed = loaded / avgMeasure * 1000;
-
-            var instantSpeed;
-
-            if (this._lastLoadedValue === null) {
-                // We are executing the first progress event of the current request
-                instantSpeed = avgSpeed; // The instant speed of the first progress event is equal to the average one
-            } else {
-                    // Measure the instant speed (B/s), which defines the speed between two progress events.
-                    var instantMeasure = _Timing2['default'].measure(labels.measure + '-instant-' + progressID,
-                    // Set the mark of the previous progress event as the starting point
-                    labels.progress + '-' + (progressID - 1), markLabel);
-                    instantSpeed = (loaded - this._lastLoadedValue) / instantMeasure * 1000;
-                }
-
-            // Save the `loaded` property of the event for the next progress event
-            this._lastLoadedValue = loaded;
-
-            // Defer measures saving and event triggering, this allows to cancel the last progress event, which can generate
-            // incoherent values.
-            this._deferredProgress = (0, _utilsHelpers.defer)(function () {
-                _this2._avgSpeed = avgSpeed;
-                _this2._speedRecords.push(instantSpeed);
-
-                _this2.trigger('progress', avgSpeed, instantSpeed);
-            });
-
-            return this;
-        }
-
-        /**
-         * Mark the current request as entirely finished (this means it ended after a time out).
-         * @private
-         * @method BandwidthModule#_timeout
-         * @returns {BandwidthModule}
-         */
-    }, {
-        key: '_timeout',
-        value: function _timeout() {
-            this._intendedEnd = true;
-            return this;
-        }
-
-        /**
-         * End the measures.
-         * @private
-         * @method BandwidthModule#_end
-         * @returns {BandwidthModule}
-         */
-    }, {
-        key: '_end',
-        value: function _end() {
-            // A timeout or an abort occured, bypass the further requests and trigger the "end" event.
-            if (this._intendedEnd) {
-                this._isRestarting = false;
-                this.trigger('end', this._avgSpeed, this._speedRecords);
-            }
-
-            // The request ended to early, restart it with an increased data size.
-            else {
-                    var dataSettings = this.settings().data,
-                        size = dataSettings.size * dataSettings.multiplier;
-
-                    this.settings({ data: { size: size } });
-                    this.trigger('restart', size);
-
-                    this._isRestarting = true;
-                    this.start();
-                }
-
-            return this;
-        }
-    }], null, _instanceInitializers);
-
-    return BandwidthModule;
-})(_HttpModule3['default']);
-
-exports['default'] = BandwidthModule;
-module.exports = exports['default'];
-
-/**
- *
- * @private
- * @member {string} BandwidthModule#_loadingType
- */
-
-},{"../../utils/decorators":7,"../../utils/helpers":8,"../Timing":6,"./HttpModule":3}],3:[function(require,module,exports){
-'use strict';
-
-Object.defineProperty(exports, '__esModule', {
-    value: true
-});
-
-var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) {  [...]
-
-var _get = function get(_x4, _x5, _x6) { var _again = true; _function: while (_again) { var object = _x4, property = _x5, receiver = _x6; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x4 = parent; _x5 = property; _x6 = receiver; _again = true; continue  [...]
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
-
-function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
-
-function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer.call(target); Object.defineProperty(target, key, descriptor); }
-
-var _EventDispatcher2 = require('../EventDispatcher');
-
-var _EventDispatcher3 = _interopRequireDefault(_EventDispatcher2);
-
-var _utilsHelpers = require('../../utils/helpers');
-
-var _utilsDecorators = require('../../utils/decorators');
-
-/**
- * @public
- * @typedef {Object} HttpModule~settingsObject
- * @property {string} [endpoint=./network.php] Where is located your `network.php` file.
- * @property {number} [delay=8000] The delay while you want to take measures.
- */
-
-/**
- * @class HttpModule
- * @extends EventDispatcher
- * @param {string} moduleName The name of the instanciated module.
- * @param {HttpModule~settingsObject} [settings={}] A set of custom settings.
- */
-
-var HttpModule = (function (_EventDispatcher) {
-    var _instanceInitializers = {};
-    var _instanceInitializers = {};
-
-    _inherits(HttpModule, _EventDispatcher);
-
-    _createDecoratedClass(HttpModule, [{
-        key: '_defaultSettings',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return null;
-        },
-
-        /**
-         * The current settings.
-         * @private
-         * @member {?Object} HttpModule#_settings
-         */
-        enumerable: true
-    }, {
-        key: '_settings',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return null;
-        },
-
-        /**
-         * The module name, will be send to the server.
-         * @private
-         * @member {string} HttpModule#_moduleName
-         */
-        enumerable: true
-    }, {
-        key: '_moduleName',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return undefined;
-        },
-
-        /**
-         * The current XMLHttpRequest object.
-         * @private
-         * @member {?XMLHttpRequest} HttpModule#_xhr
-         */
-        enumerable: true
-    }, {
-        key: '_xhr',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return null;
-        },
-
-        /**
-         * An URL token to avoid any caching issues. Also allows to identify the request in the Resource Timing entries.
-         * @private
-         * @member {?string} HttpModule#_lastURLToken
-         */
-        enumerable: true
-    }, {
-        key: '_lastURLToken',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return null;
-        },
-
-        /**
-         * Defines if the module is currently running an HTTP request.
-         * @private
-         * @member {boolean} HttpModule#_requesting
-         */
-        enumerable: true
-    }, {
-        key: '_requesting',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return false;
-        },
-
-        /**
-         * Defines if the requesting status has been overridden by the `_setRequesting` method.
-         * @private
-         * @member {boolean} HttpModule#_requestingOverridden
-         */
-        enumerable: true
-    }, {
-        key: '_requestingOverridden',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return false;
-        },
-        enumerable: true
-    }], null, _instanceInitializers);
-
-    function HttpModule(moduleName) {
-        var _this = this;
-
-        var settings = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
-
-        _classCallCheck(this, HttpModule);
-
-        _get(Object.getPrototypeOf(HttpModule.prototype), 'constructor', this).call(this);
-
-        _defineDecoratedPropertyDescriptor(this, '_defaultSettings', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_settings', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_moduleName', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_xhr', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_lastURLToken', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_requesting', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_requestingOverridden', _instanceInitializers);
-
-        this._extendDefaultSettings({
-            endpoint: './network.php',
-            delay: 8000
-        });
-
-        this.settings(settings);
-
-        this._moduleName = moduleName;
-
-        // Each time a request starts or ends, set the requesting value unless it has been overridden with the
-        // _setRequesting() method.
-        this.on(['xhr-loadstart', 'xhr-upload-loadstart'], function () {
-            if (!_this._requestingOverridden) {
-                _this._requesting = true;
-            }
-        });
-
-        this.on(['xhr-loadend', 'xhr-upload-loadend'], function () {
-            if (!_this._requestingOverridden) {
-                _this._requesting = false;
-            }
-        });
-    }
-
-    /**
-     * Apply a new set of custom settings.
-     * @public
-     * @method HttpModule#settings
-     * @param {HttpModule~settingsObject} settings A set of custom settings.
-     * @returns {HttpModule}
-     */
-    /**
-     * Return the current set of settings.
-     * @public
-     * @method HttpModule#settings^2
-     * @returns {HttpModule~settingsObject}
-     */
-
-    _createDecoratedClass(HttpModule, [{
-        key: 'settings',
-        value: function settings() {
-            var _settings = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
-
-            if ((0, _utilsHelpers.isObject)(_settings)) {
-                this._settings = (0, _utilsHelpers.assignStrict)(this._defaultSettings || {}, this._settings || {}, _settings);
-                return this;
-            } else {
-                return (0, _utilsHelpers.copy)(this._settings || this._defaultSettings || {});
-            }
-        }
-
-        /**
-         * Return if the module is currently making a request.
-         * @public
-         * @method HttpModule#isRequesting
-         * @returns {boolean} `true` if the module is requesting, otherwise `false`.
-         */
-    }, {
-        key: 'isRequesting',
-        value: function isRequesting() {
-            return this._requesting;
-        }
-
-        /**
-         * Extend the set of default settings.
-         * @protected
-         * @method HttpModule#_extendDefaultSettings
-         * @param {Object} settings The new properties to add to the default settings.
-         * @returns {HttpModule}
-         */
-    }, {
-        key: '_extendDefaultSettings',
-        value: function _extendDefaultSettings(settings) {
-            this._defaultSettings = (0, _utilsHelpers.assign)(this._defaultSettings || {}, settings);
-            return this;
-        }
-
-        /**
-         * Create a new XHR request.
-         * @protected
-         * @method HttpModule#_newRequest
-         * @param {string} httpMethod The HTTP method to use with the request, GET or POST.
-         * @param {Object} queryParams The query parameters to use with the request.
-         * @returns {HttpModule}
-         */
-    }, {
-        key: '_newRequest',
-        value: function _newRequest(httpMethod, queryParams) {
-            var _this2 = this;
-
-            // Check if a callback binded to the "_newRequest" event returns false, if it's the case, cancel the request
-            // creation. If the requesting status has been overridden, there's no need to cancel the request since the user
-            // should know what he's doing.
-            if (!this.trigger('_newRequest') && !this._requestingOverridden) {
-                console.warn('To ensure accurate measures, you can only make one request at a time.');
-                return this;
-            }
-
-            var settings = this.settings(),
-                xhr = new XMLHttpRequest(),
-                validHttpMethods = ['GET', 'POST'];
-
-            // Prepare the new request.
-            if (! ~validHttpMethods.indexOf(httpMethod)) {
-                console.warn('The HTTP method must be GET or POST.');
-                return this;
-            }
-
-            queryParams = queryParams || {};
-
-            var tokenSuffix = new Date().getTime();
-            this._lastURLToken = 'network-' + tokenSuffix;
-
-            // Append the query parameters
-            var url = settings.endpoint;
-            url += ~url.indexOf('?') ? '&' : '?';
-            url += 'module=' + this._moduleName;
-
-            Object.keys(queryParams).forEach(function (param) {
-                var value = encodeURIComponent(queryParams[param]);
-                url += '&' + param + '=' + value;
-            });
-
-            url += '&' + this._lastURLToken;
-
-            xhr.open(httpMethod, url);
-
-            // Abort the previous request if it hasn't been sent
-            if (this._xhr && this._xhr.readyState == XMLHttpRequest.OPENED) {
-                this._xhr.abort();
-            }
-
-            // Replace the old request by the new one
-            this._xhr = xhr;
-
-            // Bind all the XHR events
-            var events = ['loadstart', 'progress', 'abort', 'error', 'load', 'timeout', 'loadend', 'readystatechange'];
-
-            events.forEach(function (eventType) {
-                xhr.addEventListener(eventType, function () {
-                    for (var _len = arguments.length, args = Array(_len), _key2 = 0; _key2 < _len; _key2++) {
-                        args[_key2] = arguments[_key2];
-                    }
-
-                    // A last progress event can be triggered once a request has timed out, ignore it.
-                    if (eventType == 'progress' && !_this2._requesting) return;
-
-                    _this2.trigger.apply(_this2, ['xhr-' + eventType, xhr].concat(args));
-                });
-
-                // The XMLHttpRequestUpload interface supports all the above event types except the "readystatechange" one
-                if (eventType != 'readystatechange') {
-                    xhr.upload.addEventListener(eventType, function () {
-                        for (var _len2 = arguments.length, args = Array(_len2), _key3 = 0; _key3 < _len2; _key3++) {
-                            args[_key3] = arguments[_key3];
-                        }
-
-                        _this2.trigger.apply(_this2, ['xhr-upload-' + eventType, xhr].concat(args));
-                    });
-                }
-            });
-
-            // Define the timeout of the request. We don't use the native `timeout` property since it can distort the
-            // measures.
-            // See: https://github.com/nesk/network.js/issues/26
-            var startTimeout = function startTimeout(xhr) {
-                setTimeout(function () {
-                    if (xhr.readyState != XMLHttpRequest.UNSENT && xhr.readyState != XMLHttpRequest.DONE) {
-                        _this2.trigger('xhr-timeout');
-                        _this2.trigger('xhr-upload-timeout');
-                        xhr.abort();
-                    }
-                }, settings.delay);
-            };
-
-            this.on('xhr-upload-loadstart', startTimeout).on('xhr-readystatechange', (function (timeoutStarted) {
-                return function (xhr) {
-                    if (!timeoutStarted && xhr.readyState == XMLHttpRequest.LOADING) {
-                        timeoutStarted = true;
-                        startTimeout(xhr);
-                    }
-                };
-            })(false));
-
-            return this;
-        }
-
-        /**
-         * Send a newly created XHR request.
-         * @protected
-         * @method HttpModule#_sendRequest
-         * @param {?*} [data=null] The data to send with the request.
-         * @returns {HttpModule}
-         */
-    }, {
-        key: '_sendRequest',
-        value: function _sendRequest() {
-            var data = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
-
-            if (this._xhr && this._xhr.readyState == XMLHttpRequest.OPENED) {
-                this._xhr.send(data);
-            } else {
-                console.warn('A request must have been created before sending any data.');
-            }
-
-            return this;
-        }
-
-        /**
-         * Abort the current request.
-         * @protected
-         * @method HttpModule#_abort
-         * @returns {HttpModule}
-         */
-    }, {
-        key: '_abort',
-        value: function _abort() {
-            if (this._xhr) {
-                this._xhr.abort();
-            }
-
-            return this;
-        }
-
-        /**
-         * Get the Resource Timing entry associated to the current request.
-         * @protected
-         * @method HttpModule#_getTimingEntry
-         * @param {HttpModule~timingCallback} callback A callback used to send back the timing entry.
-         * @returns {HttpModule}
-         */
-    }, {
-        key: '_getTimingEntry',
-        value: function _getTimingEntry(callback) {
-            // The Resource Timing entries aren't immediately available once the 'load' event is triggered by an
-            // XMLHttpRequest, we must wait for another process tick to check for a refreshed list.
-            setTimeout((function (lastURLToken) {
-                return function () {
-                    // Filter the timing entries to return only the one concerned by the last request made
-                    var entries = performance.getEntriesByType('resource').filter(function (entry) {
-                        return ~entry.name.indexOf(lastURLToken);
-                    });
-
-                    /**
-                     * A callback used to send back the timing entry.
-                     * @private
-                     * @callback HttpModule~timingCallback
-                     * @param {PerformanceResourceTiming} entry The Resource Timing entry associated to the current request.
-                     */
-                    callback(entries.length ? entries[0] : null);
-                };
-            })(this._lastURLToken), 0);
-
-            return this;
-        }
-
-        /**
-         * Override the requesting status of the module.
-         * @protected
-         * @method HttpModule#_setRequesting
-         * @param {boolean} isRequesting The requesting status.
-         * @returns {HttpModule}
-         */
-    }, {
-        key: '_setRequesting',
-        value: function _setRequesting(isRequesting) {
-            this._requestingOverridden = true;
-            this._requesting = isRequesting;
-            return this;
-        }
-    }], null, _instanceInitializers);
-
-    return HttpModule;
-})(_EventDispatcher3['default']);
-
-exports['default'] = HttpModule;
-module.exports = exports['default'];
-
-/**
- * The default settings.
- * @private
- * @member {?Object} HttpModule#_defaultSettings
- */
-
-},{"../../utils/decorators":7,"../../utils/helpers":8,"../EventDispatcher":1}],4:[function(require,module,exports){
-'use strict';
-
-Object.defineProperty(exports, '__esModule', {
-    value: true
-});
-
-var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) {  [...]
-
-var _get = function get(_x5, _x6, _x7) { var _again = true; _function: while (_again) { var object = _x5, property = _x6, receiver = _x7; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x5 = parent; _x6 = property; _x7 = receiver; _again = true; continue  [...]
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
-
-function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
-
-function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer.call(target); Object.defineProperty(target, key, descriptor); }
-
-var _HttpModule2 = require('./HttpModule');
-
-var _HttpModule3 = _interopRequireDefault(_HttpModule2);
-
-var _Timing = require('../Timing');
-
-var _Timing2 = _interopRequireDefault(_Timing);
-
-var _utilsHelpers = require('../../utils/helpers');
-
-var _utilsDecorators = require('../../utils/decorators');
-
-/**
- * @public
- * @typedef {Object} LatencyModule~settingsObject
- * @property {string} [endpoint=./network.php] Where is located your `network.php` file.
- * @property {number} [measures=5] How many measures should be returned.
- * @property {number} [attempts=3] How much attempts to get a valid value should be done for each measure.
- */
-
-/**
- * @class LatencyModule
- * @extends HttpModule
- * @param {LatencyModule~settingsObject} [settings={}] A set of custom settings.
- */
-
-var LatencyModule = (function (_HttpModule) {
-    var _instanceInitializers = {};
-    var _instanceInitializers = {};
-
-    _inherits(LatencyModule, _HttpModule);
-
-    _createDecoratedClass(LatencyModule, [{
-        key: '_supportsResourceTiming',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return undefined;
-        },
-
-        /**
-         * The total number of requests left.
-         * @private
-         * @member {number} LatencyModule#_requestsLeft
-         */
-        enumerable: true
-    }, {
-        key: '_requestsLeft',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return undefined;
-        },
-
-        /**
-         * The total number of attempts left.
-         * @private
-         * @member {number} LatencyModule#_attemptsLeft
-         */
-        enumerable: true
-    }, {
-        key: '_attemptsLeft',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return undefined;
-        },
-
-        /**
-         * The measured latencies.
-         * @private
-         * @member {number[]} LatencyModule#_latencies
-         */
-        enumerable: true
-    }, {
-        key: '_latencies',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return undefined;
-        },
-
-        /**
-         * The ID of the current request.
-         * @private
-         * @member {number} LatencyModule#_requestID
-         */
-        enumerable: true
-    }, {
-        key: '_requestID',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return 0;
-        },
-
-        /**
-         * Unique labels for each request, exclusively used to make measures.
-         * @private
-         * @member {Object} LatencyModule#_requestID
-         * @property {?string} start
-         * @property {?string} end
-         * @property {?string} measure
-         */
-        enumerable: true
-    }, {
-        key: '_timingLabels',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return {
-                start: null,
-                end: null,
-                measure: null
-            };
-        },
-        enumerable: true
-    }], null, _instanceInitializers);
-
-    function LatencyModule() {
-        var settings = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
-
-        _classCallCheck(this, LatencyModule);
-
-        _get(Object.getPrototypeOf(LatencyModule.prototype), 'constructor', this).call(this, 'latency');
-
-        _defineDecoratedPropertyDescriptor(this, '_supportsResourceTiming', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_requestsLeft', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_attemptsLeft', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_latencies', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_requestID', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_timingLabels', _instanceInitializers);
-
-        this._extendDefaultSettings({
-            measures: 5,
-            attempts: 3
-        }).settings(settings);
-
-        this._defineResourceTimingSupport();
-    }
-
-    /**
-     * Apply a new set of custom settings.
-     * @public
-     * @method LatencyModule#settings
-     * @param {LatencyModule~settingsObject} settings A set of custom settings.
-     * @returns {LatencyModule}
-     */
-    /**
-     * Return the current set of settings.
-     * @public
-     * @method LatencyModule#settings^2
-     * @returns {LatencyModule~settingsObject}
-     */
-
-    _createDecoratedClass(LatencyModule, [{
-        key: 'settings',
-        value: function settings() {
-            var _settings = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
-
-            if ((0, _utilsHelpers.isObject)(_settings)) {
-                return _get(Object.getPrototypeOf(LatencyModule.prototype), 'settings', this).call(this, (0, _utilsHelpers.assignStrict)(_settings, {
-                    delay: 0 // We dont want any timeout during a latency calculation
-                }));
-            } else {
-                    return (0, _utilsHelpers.except)(_get(Object.getPrototypeOf(LatencyModule.prototype), 'settings', this).call(this), ['delay']);
-                }
-        }
-
-        /**
-         * Start requesting the server to make measures.
-         * @public
-         * @method LatencyModule#start
-         * @returns {LatencyModule}
-         */
-    }, {
-        key: 'start',
-        value: function start() {
-            var _settings2 = this.settings();
-
-            // Set the number of requests required to establish the network latency.
-            var measures = _settings2.measures;
-            var attempts = _settings2.attempts;
-            this._requestsLeft = measures;
-            this._attemptsLeft = attempts * measures;
-
-            // If the browser doesn't support the Resource Timing API, add a request that will be ignored to avoid a longer
-            // request due to a possible DNS/whatever fetch.
-            if (!this._supportsResourceTiming) {
-                this._requestsLeft++;
-                this._attemptsLeft++;
-            }
-
-            // Override the requesting value since a complete latency request consists off multiple ones
-            this._setRequesting(true);
-
-            this._latencies = [];
-            this._nextRequest();
-
-            return this;
-        }
-
-        /**
-         * Define if the module should support the Resource Timing API.
-         * @private
-         * @method LatencyModule#_defineResourceTimingSupport
-         * @param {boolean} supportsResourceTiming If `undefined`, the support will be determined by feature detection.
-         * @returns {LatencyModule}
-         */
-    }, {
-        key: '_defineResourceTimingSupport',
-        value: function _defineResourceTimingSupport(supportsResourceTiming) {
-            var _this = this;
-
-            if (typeof supportsResourceTiming !== 'boolean') supportsResourceTiming = _Timing2['default'].supportsResourceTiming;
-            this._supportsResourceTiming = supportsResourceTiming;
-
-            // Unregisters all the previously registered events, since this method can be called multiple times.
-            this.off(['xhr-load', 'xhr-loadstart', 'xhr-readystatechange']);
-
-            // Measure the latency with the Resource Timing API once the request is finished
-            if (supportsResourceTiming) {
-                this.on('xhr-load', function () {
-                    return _this._measure();
-                });
-            }
-
-            // If the browser doesn't support the Resource Timing API, we fallback on a Datetime solution.
-            else {
-                    // Set a mark when the request starts
-                    this.on('xhr-loadstart', function () {
-                        return _Timing2['default'].mark(_this._timingLabels.start);
-                    });
-
-                    // Then make a measure with the previous mark
-                    this.on('xhr-readystatechange', function (xhr) {
-                        return _this._measure(xhr);
-                    });
-                }
-        }
-
-        /**
-         * Initiate the next request used for latency measures.
-         * @private
-         * @method LatencyModule#_nextRequest
-         * @param {boolean} [retry=false] Defines if the next request is a retry due to a failing request or not.
-         * @returns {LatencyModule}
-         */
-    }, {
-        key: '_nextRequest',
-        value: function _nextRequest() {
-            var _this2 = this;
-
-            var retry = arguments.length <= 0 || arguments[0] === undefined ? false : arguments[0];
-
-            var reqID = this._requestID++;
-            var requestsLeft = retry ? this._requestsLeft : this._requestsLeft--;
-
-            if (this._attemptsLeft-- && (requestsLeft || retry)) {
-                // Create unique timing labels for the new request
-                var labels = this._timingLabels;
-                labels.start = 'latency-' + reqID + '-start';
-                labels.end = 'latency-' + reqID + '-end';
-                labels.measure = 'latency-' + reqID + '-measure';
-
-                // Create the new request and send it
-                this._newRequest('GET')._sendRequest();
-            } else {
-                // All the requests are finished, set the requesting status to false.
-                this._setRequesting(false);
-
-                // If all the requests have been executed, calculate the average latency. Since the _getTimingEntry() method
-                // is asynchronous, wait for the next process tick to execute the _end() method, to be sure that all the
-                // latencies have been retrieved.
-                setTimeout(function () {
-                    return _this2._end();
-                }, 0);
-            }
-
-            return this;
-        }
-
-        /**
-         * Make latency measures for the last request.
-         * @private
-         * @method LatencyModule#_measure
-         * @param {?XMLHttpRequest} [xhr=null] The concerned XMLHttpRequest if the browser doesn't support the Resource Timing API.
-         * @returns {LatencyModule}
-         */
-    }, {
-        key: '_measure',
-        value: function _measure() {
-            var _this3 = this;
-
-            var xhr = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
-
-            // With Resource Timing API
-            if (!xhr) {
-                this._getTimingEntry(function (entry) {
-                    // The latency calculation differs between an HTTP and an HTTPS connection
-                    // See: http://www.w3.org/TR/resource-timing/#processing-model
-                    var latency = !entry.secureConnectionStart ? entry.connectEnd - entry.connectStart : entry.secureConnectionStart - entry.connectStart;
-
-                    if (latency) _this3._latencies.push(latency);
-                    _this3._nextRequest(!latency);
-                });
-            }
-
-            // Without Resource Timing API
-            else if (this._requestsLeft < this.settings().measures) {
-
-                    // Measure and save the latency if the headers have been received
-                    if (xhr.readyState == XMLHttpRequest.HEADERS_RECEIVED) {
-                        var labels = this._timingLabels;
-
-                        _Timing2['default'].mark(labels.end);
-                        var latency = _Timing2['default'].measure(labels.measure, labels.start, labels.end);
-
-                        if (latency) this._latencies.push(latency);
-
-                        // Abort the current request before we run a new one
-                        this._abort();
-                        this._nextRequest(!latency);
-                    }
-                }
-
-                // Ignore the first request when using the XHR states. See the comments in the start() method for explanations.
-                else {
-                        this._nextRequest();
-                    }
-
-            return this;
-        }
-
-        /**
-         * End the measures.
-         * @private
-         * @method LatencyModule#_end
-         * @returns {LatencyModule}
-         */
-    }, {
-        key: '_end',
-        value: function _end() {
-            var latencies = this._latencies;
-
-            // Get the average latency
-            var avgLatency = latencies.reduce(function (a, b) {
-                return a + b;
-            }, 0) / (latencies.length || 1);
-            avgLatency = avgLatency || null;
-
-            // If there is no measures, restart with the polyfill.
-            if (!latencies.length) {
-                this._defineResourceTimingSupport(false);
-                this.start();
-                return this;
-            }
-
-            // If there is not enough measures, display a warning.
-            if (latencies.length < this.settings().measures) {
-                var _settings3 = this.settings();
-
-                var measures = _settings3.measures;
-                var attempts = _settings3.attempts;
-
-                console.warn('\n                An insufficient number of measures have been processed, this could be due to your web server using\n                persistant connections or to your client settings (measures: ' + measures + ', attempts: ' + attempts + ').\n            ');
-            }
-
-            // Trigger the "end" event with the average latency and the latency list as parameters
-            this.trigger('end', avgLatency, latencies);
-
-            return this;
-        }
-    }], null, _instanceInitializers);
-
-    return LatencyModule;
-})(_HttpModule3['default']);
-
-exports['default'] = LatencyModule;
-module.exports = exports['default'];
-
-/**
- * Defines if the module supports the Resource Timing API.
- * @private
- * @member {number} LatencyModule#_requestsLeft
- */
-
-},{"../../utils/decorators":7,"../../utils/helpers":8,"../Timing":6,"./HttpModule":3}],5:[function(require,module,exports){
-'use strict';
-
-Object.defineProperty(exports, '__esModule', {
-    value: true
-});
-
-var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) {  [...]
-
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
-
-function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
-function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
-
-function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer.call(target); Object.defineProperty(target, key, descriptor); }
-
-var _EventDispatcher = require('./EventDispatcher');
-
-var _EventDispatcher2 = _interopRequireDefault(_EventDispatcher);
-
-var _HttpHttpModule = require('./Http/HttpModule');
-
-var _HttpHttpModule2 = _interopRequireDefault(_HttpHttpModule);
-
-var _HttpLatencyModule = require('./Http/LatencyModule');
-
-var _HttpLatencyModule2 = _interopRequireDefault(_HttpLatencyModule);
-
-var _HttpBandwidthModule = require('./Http/BandwidthModule');
-
-var _HttpBandwidthModule2 = _interopRequireDefault(_HttpBandwidthModule);
-
-var _Timing = require('./Timing');
-
-var _Timing2 = _interopRequireDefault(_Timing);
-
-var _utilsHelpers = require('../utils/helpers');
-
-var _utilsDecorators = require('../utils/decorators');
-
-/**
- * @public
- * @typedef {Object} Network~settingsObject
- * @property {LatencyModule~settingsObject} latency
- * @property {BandwidthModule~settingsObject} upload
- * @property {BandwidthModule~settingsObject} download
- * @example
- * {
- *     // Top-level properties are applied to all the modules
- *     endpoint: './my-new-endpoint/',
- *
- *     // Top-level properties will be overridden by the ones specified in each module
- *     latency: {
- *         endpoint: './my-new-latency-endpoint/'
- *     }
- * }
- */
-
-/**
- * @class Network
- * @param {Network~settingsObject} [settings={}] A set of custom settings.
- * @member {LatencyModule} latency The latency module.
- * @member {BandwidthModule} upload The upload module.
- * @member {BandwidthModule} download The download module.
- */
-
-var Network = (function () {
-    var _instanceInitializers = {};
-    var _instanceInitializers = {};
-
-    _createDecoratedClass(Network, [{
-        key: '_modules',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return {};
-        },
-
-        /**
-         * Defines if the registered modules have been initialized.
-         * @private
-         * @member {boolean} Network#_modulesInitialized
-         */
-        enumerable: true
-    }, {
-        key: '_modulesInitialized',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return false;
-        },
-
-        /**
-         * The settings defined via the constructor, they will be applied once the modules are initialized.
-         * @private
-         * @member {Network~settingsObject} Network#_pendingSettings
-         */
-        enumerable: true
-    }, {
-        key: '_pendingSettings',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return {};
-        },
-
-        /**
-         * Expose all the internal classes to the global scope. Only for testing purposes!
-         * @private
-         * @method Network._exposeInternalClasses
-         * @returns {Network}
-         */
-        enumerable: true
-    }], [{
-        key: '_exposeInternalClasses',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        value: function _exposeInternalClasses() {
-            var global = (0, _utilsHelpers.getGlobalObject)(),
-                classes = { EventDispatcher: _EventDispatcher2['default'], HttpModule: _HttpHttpModule2['default'], LatencyModule: _HttpLatencyModule2['default'], BandwidthModule: _HttpBandwidthModule2['default'], Timing: _Timing2['default'] };
-
-            Object.keys(classes).forEach(function (name) {
-                global[name] = classes[name];
-            });
-
-            return this;
-        }
-    }, {
-        key: 'supportsResourceTiming',
-
-        /**
-         * Defines if the current browser supports the Resource Timing API.
-         * @public
-         * @readonly
-         * @member {boolean} Network#supportsResourceTiming
-         */
-        get: function get() {
-            return _Timing2['default'].supportsResourceTiming;
-        }
-
-        /**
-         * The registered modules.
-         * @private
-         * @member {Object} Network#_modules
-         */
-    }], _instanceInitializers);
-
-    function Network() {
-        var settings = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
-
-        _classCallCheck(this, Network);
-
-        _defineDecoratedPropertyDescriptor(this, '_modules', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_modulesInitialized', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_pendingSettings', _instanceInitializers);
-
-        this._registerModule('latency', function (settings) {
-            return new _HttpLatencyModule2['default'](settings);
-        })._registerModule('upload', function (settings) {
-            return new _HttpBandwidthModule2['default']('upload', settings);
-        })._registerModule('download', function (settings) {
-            return new _HttpBandwidthModule2['default']('download', settings);
-        });
-
-        this._initModules(this.settings(settings));
-    }
-
-    /**
-     * Apply a new set of custom settings.
-     * @public
-     * @method Network#settings
-     * @param {Network~settingsObject} settings A set of custom settings.
-     * @returns {Network}
-     */
-    /**
-     * Return the current set of settings.
-     * @public
-     * @method Network#settings^2
-     * @returns {Network~settingsObject}
-     */
-
-    _createDecoratedClass(Network, [{
-        key: 'settings',
-        value: function settings() {
-            var _this = this;
-
-            var _settings = arguments.length <= 0 || arguments[0] === undefined ? null : arguments[0];
-
-            var moduleNames = Object.keys(this._modules);
-
-            if ((0, _utilsHelpers.isObject)(_settings)) {
-                var _ret = (function () {
-                    // Extract the global settings
-                    var globalSettings = (0, _utilsHelpers.except)(_settings, moduleNames);
-
-                    // Extract the local settings
-                    var localSettings = (0, _utilsHelpers.except)(_settings, Object.keys(globalSettings));
-
-                    // Create new settings with the global ones nested in the local ones
-                    _settings = moduleNames.reduce(function (settings, moduleName) {
-                        return (0, _utilsHelpers.assign)(settings, _defineProperty({}, moduleName, globalSettings));
-                    }, {});
-
-                    // Apply the local settings to the new settings
-                    _settings = (0, _utilsHelpers.assign)(_settings, localSettings);
-
-                    // Apply the settings to the modules
-                    if (_this._modulesInitialized) {
-                        Object.keys(_this._modules).forEach(function (name) {
-                            _this._modules[name].settings(_settings[name]);
-                        });
-                    }
-
-                    // If the modules aren't instanciated, store the settings.
-                    else {
-                            _this._pendingSettings = _settings;
-                        }
-
-                    return {
-                        v: _this
-                    };
-                })();
-
-                if (typeof _ret === 'object') return _ret.v;
-            } else {
-                return moduleNames.reduce(function (settings, moduleName) {
-                    return (0, _utilsHelpers.assign)(settings, _defineProperty({}, moduleName, _this._modules[moduleName].settings()));
-                }, {});
-            }
-        }
-
-        /**
-         * Return if a module is currently making a request.
-         * @public
-         * @method Network#isRequesting
-         * @returns {boolean} `true` if a module is requesting, otherwise `false`.
-         */
-    }, {
-        key: 'isRequesting',
-        value: function isRequesting() {
-            var requesting = false;
-
-            for (var _name in this._modules) {
-                if (this._modules.hasOwnProperty(_name)) {
-                    requesting = requesting || this._modules[_name].isRequesting();
-                }
-            }
-
-            return requesting;
-        }
-
-        /**
-         * Register a new module for the current `Network` instance.
-         * @private
-         * @method Network#registerModule
-         * @param {string} name The name of the module. Will be used to create the property `Network.<name>`.
-         * @param {Network~moduleCallback} moduleCallback A callback used to initialize a module with a set of settings.
-         * @returns {Network}
-         */
-    }, {
-        key: '_registerModule',
-        value: function _registerModule(name, moduleCallback) {
-            /**
-             * A callback used to initialize a module with a set of settings.
-             * @private
-             * @callback Network~moduleCallback
-             * @param {Object} settings A set of custom settings.
-             * @returns {HttpModule} An instanciated subclass of `HttpModule`.
-             */
-            this._modules[name] = moduleCallback;
-            return this;
-        }
-
-        /**
-         * Initialize all the registered modules with the settings passed to the constructor.
-         * @private
-         * @method Network#_initModules
-         * @returns {Network}
-         */
-    }, {
-        key: '_initModules',
-        value: function _initModules() {
-            var _this2 = this;
-
-            if (!this._modulesInitialized) {
-                // Initialize the modules with their respective settings
-                Object.keys(this._modules).forEach(function (name) {
-                    _this2._modules[name] = _this2._modules[name](_this2._pendingSettings[name]).on('_newRequest', function () {
-                        return !_this2.isRequesting();
-                    });
-
-                    _this2[name] = _this2._modules[name];
-                });
-
-                this._modulesInitialized = true;
-            }
-
-            return this;
-        }
-    }], null, _instanceInitializers);
-
-    return Network;
-})();
-
-exports['default'] = Network;
-module.exports = exports['default'];
-
-},{"../utils/decorators":7,"../utils/helpers":8,"./EventDispatcher":1,"./Http/BandwidthModule":2,"./Http/HttpModule":3,"./Http/LatencyModule":4,"./Timing":6}],6:[function(require,module,exports){
-'use strict';
-
-Object.defineProperty(exports, '__esModule', {
-    value: true
-});
-
-var _createDecoratedClass = (function () { function defineProperties(target, descriptors, initializers) { for (var i = 0; i < descriptors.length; i++) { var descriptor = descriptors[i]; var decorators = descriptor.decorators; var key = descriptor.key; delete descriptor.key; delete descriptor.decorators; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor || descriptor.initializer) descriptor.writable = true; if (decorators) {  [...]
-
-function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
-
-function _defineDecoratedPropertyDescriptor(target, key, descriptors) { var _descriptor = descriptors[key]; if (!_descriptor) return; var descriptor = {}; for (var _key in _descriptor) descriptor[_key] = _descriptor[_key]; descriptor.value = descriptor.initializer.call(target); Object.defineProperty(target, key, descriptor); }
-
-var _utilsHelpers = require('../utils/helpers');
-
-var _utilsDecorators = require('../utils/decorators');
-
-/**
- * @private
- * @class Timing
- */
-
-var Timing = (function () {
-    var _instanceInitializers = {};
-    var _instanceInitializers = {};
-
-    _createDecoratedClass(Timing, [{
-        key: 'supportsResourceTiming',
-
-        /**
-         * Defines if the current browser supports the Resource Timing API.
-         * @public
-         * @readonly
-         * @member {boolean} Timing#supportsResourceTiming
-         */
-        get: function get() {
-            return Boolean(this._support.resourceTiming);
-        }
-
-        /**
-         * Defines if the current browser supports some specific Timing APIs.
-         * @private
-         * @member {Object} Timing#_support
-         * @property {boolean} performance `true` if the Performance API is available.
-         * @property {boolean} userTiming `true` if the User Timing API is available.
-         * @property {boolean} resourceTiming `true` if the Resource Timing API is available.
-         */
-    }, {
-        key: '_support',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return {};
-        },
-
-        /**
-         * All the marks created by the `mark` method.
-         * @private
-         * @member {Object} Timing#_marks
-         */
-        enumerable: true
-    }, {
-        key: '_marks',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return {};
-        },
-
-        /**
-         * All the measures created by the `measure` method.
-         * @private
-         * @member {Object} Timing#_measures
-         */
-        enumerable: true
-    }, {
-        key: '_measures',
-        decorators: [(0, _utilsDecorators.enumerable)(false)],
-        initializer: function initializer() {
-            return {};
-        },
-        enumerable: true
-    }], null, _instanceInitializers);
-
-    function Timing() {
-        _classCallCheck(this, Timing);
-
-        _defineDecoratedPropertyDescriptor(this, '_support', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_marks', _instanceInitializers);
-
-        _defineDecoratedPropertyDescriptor(this, '_measures', _instanceInitializers);
-
-        var global = (0, _utilsHelpers.getGlobalObject)();
-
-        this._support = {
-            performance: !!global.performance,
-            userTiming: global.performance && performance.mark,
-            resourceTiming: global.performance && typeof performance.getEntriesByType == "function" && performance.timing
-        };
-    }
-
-    /**
-     * Create a new timing mark.
-     * @public
-     * @method Timing#mark
-     * @param {string} label A label associated to the mark.
-     * @returns {Timing}
-     */
-
-    _createDecoratedClass(Timing, [{
-        key: 'mark',
-        value: function mark(label) {
-            var support = this._support,
-                marks = this._marks;
-
-            if (support.userTiming) {
-                performance.mark(label);
-            }
-
-            if (support.performance) {
-                marks[label] = performance.now();
-            } else {
-                marks[label] = new Date().getTime();
-            }
-
-            return this;
-        }
-
-        /**
-         * Measure the delay between two marks.
-         * @public
-         * @method Timing#measure
-         * @param {string} measureLabel A label associated to the measure.
-         * @param {string} markLabelA The label of the first mark.
-         * @param {string} markLabelB The label of the second mark.
-         * @returns {number} The measured value.
-         */
-    }, {
-        key: 'measure',
-        value: function measure(measureLabel, markLabelA, markLabelB) {
-            var support = this._support,
-                marks = this._marks,
-                measures = this._measures;
-
-            if (typeof measures[measureLabel] == 'undefined') {
-                var measureWithoutUserTiming = marks[markLabelB] - marks[markLabelA];
-
-                if (support.userTiming) {
-                    performance.measure(measureLabel, markLabelA, markLabelB);
-                    var entriesByName = performance.getEntriesByName(measureLabel);
-
-                    // The performance API could return no corresponding entries in Firefox so we must use a fallback.
-                    // See: https://github.com/nesk/network.js/issues/32#issuecomment-118434305
-                    measures[measureLabel] = entriesByName.length ? entriesByName[0].duration : measureWithoutUserTiming;
-                } else {
-                    measures[measureLabel] = measureWithoutUserTiming;
-                }
-            }
-
-            return measures[measureLabel];
-        }
-    }], null, _instanceInitializers);
-
-    return Timing;
-})();
-
-exports['default'] = new Timing();
-module.exports = exports['default'];
-
-},{"../utils/decorators":7,"../utils/helpers":8}],7:[function(require,module,exports){
-/**
- * @callback propertyDecorator
- * @param target
- * @param key
- * @param descriptor
- */
-
-/**
- * Set the enumerability of a property.
- * @private
- * @function enumerable
- * @param {boolean} isEnumerable Whether the property should be enumerable or not.
- * @returns {propertyDecorator}
- */
-"use strict";
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.enumerable = enumerable;
-
-function enumerable(isEnumerable) {
-  return function (target, key, descriptor) {
-    descriptor.enumerable = isEnumerable;
-    return descriptor;
-  };
-}
-
-},{}],8:[function(require,module,exports){
-(function (global){
-/**
- * Return the global object.
- * @private
- * @function getGlobalObject
- * @return {Object}
- * @see https://gist.github.com/rauschma/1bff02da66472f555c75
- */
-'use strict';
-
-Object.defineProperty(exports, '__esModule', {
-    value: true
-});
-
-var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps [...]
-
-exports.getGlobalObject = getGlobalObject;
-exports.isObject = isObject;
-exports.copy = copy;
-exports.assign = assign;
-exports.assignStrict = assignStrict;
-exports.except = except;
-exports.defer = defer;
-
-function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
-
-function getGlobalObject() {
-    // Workers don’t have `window`, only `self`.
-    if (typeof self !== 'undefined') {
-        return self;
-    }
-
-    if (typeof global !== 'undefined') {
-        return global;
-    }
-
-    // Not all environments allow `eval` and `Function`, use only as a last resort.
-    return new Function('return this')();
-}
-
-/**
- * Determine if the provided value is an object.
- * @private
- * @function isObject
- * @param {*} obj The value to check.
- * @returns {boolean} `true` if the value is an object, otherwise `false`.
- */
-
-function isObject(obj) {
-    return obj != undefined && obj != null && typeof obj.valueOf() == 'object';
-}
-
-/**
- * Make a deep copy of any value.
- * @private
- * @function copy
- * @param {*} value The value to copy.
- * @returns {*} The copied value.
- */
-
-function copy(value) {
-    return JSON.parse(JSON.stringify(value));
-}
-
-/**
- * Copy the properties in the source objects over to the destination object.
- * @private
- * @function _assign
- * @param {boolean} strict Given `true`, new properties will not be copied.
- * @param {Object} [target={}] The destination object.
- * @param {...Object} sources The source objects.
- * @returns {Object} The destination object once the properties are copied.
- */
-function _assign(strict) {
-    var target = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
-
-    target = copy(target);
-
-    for (var _len = arguments.length, sources = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
-        sources[_key - 2] = arguments[_key];
-    }
-
-    sources.forEach(function (source) {
-        Object.keys(source).forEach(function (key) {
-            if (!strict || target.hasOwnProperty(key)) {
-                var value = source[key];
-                target[key] = isObject(value) ? _assign(strict, target[key], value) : value;
-            }
-        });
-    });
-
-    return target;
-}
-
-/**
- * Copy all the properties in the source objects over to the destination object.
- * @private
- * @function assign
- * @param {Object} [target={}] The destination object.
- * @param {...Object} sources The source objects.
- * @returns {Object} The destination object once the properties are copied.
- */
-
-function assign() {
-    var target = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
-
-    for (var _len2 = arguments.length, sources = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
-        sources[_key2 - 1] = arguments[_key2];
-    }
-
-    return _assign.apply(undefined, [false, target].concat(sources));
-}
-
-/**
- * Copy the properties (but no new ones) in the source objects over to the destination object.
- * @private
- * @function assignStrict
- * @param {Object} [target={}] The destination object.
- * @param {...Object} sources The source objects.
- * @returns {Object} The destination object once the properties are copied.
- */
-
-function assignStrict() {
-    var target = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
-
-    for (var _len3 = arguments.length, sources = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
-        sources[_key3 - 1] = arguments[_key3];
-    }
-
-    return _assign.apply(undefined, [true, target].concat(sources));
-}
-
-/**
- * Get a copy of an object without some of its properties.
- * @private
- * @function except
- * @param {Object} obj The original object.
- * @param {string[]} properties The properties to exclude from the copied object.
- * @returns {Object} The copied object without the specified properties.
- */
-
-function except(obj, properties) {
-    var objCopy = copy(obj);
-
-    properties.forEach(function (index) {
-        return delete objCopy[index];
-    });
-
-    return objCopy;
-}
-
-/**
- * Defer the execution of a function.
- * @private
- * @function defer
- * @param {Function} func The function to defer.
- * @returns {Defer} The Defer object used to execute the function when needed.
- */
-
-function defer() {
-    var func = arguments.length <= 0 || arguments[0] === undefined ? function () {} : arguments[0];
-
-    /**
-     * @private
-     * @class Defer
-     */
-    return new ((function () {
-        function _class() {
-            _classCallCheck(this, _class);
-
-            this.func = func;
-        }
-
-        /**
-         * Execute the deferred function.
-         * @public
-         * @method Defer#run
-         */
-
-        _createClass(_class, [{
-            key: 'run',
-            value: function run() {
-                if (this.func) this.func();
-                delete this.func;
-            }
-        }]);
-
-        return _class;
-    })())();
-}
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{}]},{},[5])(5)
-});
diff --git a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-nettest.js b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-nettest.js
index fccd633..23c3757 100644
--- a/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-nettest.js
+++ b/openmeetings-web/src/main/java/org/apache/openmeetings/web/room/raw-nettest.js
@@ -1,10 +1,11 @@
 /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */
 var NetTest = (function() {
-	const self = {}, PINGS = 10, LIMIT = 2000, URL = './services/networktest/';
+	const self = {}, PINGS = 5, LIMIT = 2000, URL = './services/networktest/'
+		, DELAY = 3000, KB = 1024, MB = KB * KB;
 	let output, lbls, net, tests, testName, testLabel, testNext, bulk = true;
 
-	// Based on
-	// https://github.com/nesk/network.js/blob/master/example/main.js
+	const average = (array) => array.reduce((a, b) => a + b) / array.length;
+
 	function _init(_lbls) {
 		lbls = _lbls;
 		output = $('.nettest output');
@@ -18,129 +19,194 @@ var NetTest = (function() {
 				btn.parent().find('.value').html('');
 			});
 
-		net = new Network();
 		_initTests();
-		// progress can be added
-		net.upload
-			.on('start', _start)
-			.on('restart', _restart)
-			.on('end', _end);
-		net.download
-			.on('start', _start)
-			.on('restart', _restart)
-			.on('end', _end);
-		net.latency.on('start', _start);
 		$('.nettest button[data-start="true"]').click()
 	}
+	function __repeat(options) {
+		if (isNaN(options.attempts)) {
+			options.attempts = 0;
+		}
+		if (isNaN(options.step)) {
+			options.step = options.measures;
+			options.astep = options.attempts;
+		}
+		if (!Array.isArray(options.results)) {
+			options.results = [];
+			options.lresults = [];
+		}
+		if (options.step < 0) {
+			return; //might happen in case of error
+		}
+		if (options.step === 0) {
+			if (--options.astep > 0) {
+				options.step = options.measures;
+				options.results.push(options.lresults);
+				options.lresults = [];
+				__repeat(options);
+			} else {
+				if (options.attempts > 0) {
+					options.results.push(options.lresults);
+				} else {
+					options.results = options.lresults;
+				}
+				options.onend(options.results);
+			}
+		} else {
+			options.action(options.params, res => {
+				if (res.ok) {
+					let val = res.time;
+					if (options.params.size) {
+						_logSize(options.params.curSize);
+						val = 1000 * options.params.curSize / res.time;
+					}
+					options.lresults.push(val);
+				}
+				if (options.maxTime && res.time > options.maxTime) {
+					options.step = 0;
+				} else {
+					options.step--;
+				}
+				__repeat(options);
+			});
+		}
+	}
+	function __netTest(params, callback) {
+		let tail = '';
+		if (params.size) {
+			params.curSize = params.multiplier * (params.curSize ? params.curSize : params.size);
+			tail = '&size=' + params.curSize;
+		}
+		setTimeout(() => {
+			const fopts = {cache: 'no-cache'};
+			if (params.mode === 'upload') {
+				fopts.method = 'POST'
+				const arr = Uint8Array.from({length: params.curSize}, () => Math.floor(Math.random() * 255));
+				fopts.body = new Blob([arr.buffer], {type: 'application/octet-stream'});
+			}
+			let t = Date.now();
+			fetch(URL + params.url + tail, fopts)
+				.then(resp => resp.ok ? resp.arrayBuffer() : Promise.resolve(null))
+				.then(buf => {
+					callback({ok: !!buf, time: Date.now() - t});
+				})
+				.catch(err => {
+					OmUtil.log(err);
+					callback({ok: false, time: -1});
+				});
+		}, params.delay || 0);
+	}
 	function _initTests() {
 		tests = {
 			ping: {
-				start: function() {
-					const t = net.latency;
-					t.settings({
-						endpoint: URL + '?type=ping'
+				start: () => {
+					_start();
+					__repeat({
+						action: __netTest
+						, params: {
+							url: '?type=ping'
+						}
 						, measures: PINGS
-						, attempts: 1
+						, onend: _pingEnd
 					});
-					t.off('end').on('end', _pingEnd);
-					t.start();
-					t.trigger('start');
 				}
 			}
 			, jitter: {
-				start: function() {
-					const t = net.latency;
-					t.settings({
-						endpoint: URL + '?type=jitter'
+				start: () => {
+					_start();
+					__repeat({
+						action: __netTest
+						, params: {
+							url: '?type=jitter'
+						}
 						, measures: 5
 						, attempts: 3
+						, onend: _jitterEnd
 					});
-					t.off('end').on('end', _jitterEnd);
-					t.start();
-					t.trigger('start');
 				}
 			}
 			, upload: {
 				start: function() {
-					const t = net.upload;
-					t.settings({
-						endpoint: URL + '?type=upload'
-						, delay: LIMIT
-						, data: {
-							size: 1 * 1024 * 1024
+					_start();
+					__repeat({
+						action: __netTest
+						, params: {
+							url: '?type=upload'
+							, mode: 'upload'
+							, size: 512 * KB
 							, multiplier: 2
+							, delay: DELAY
 						}
+						, maxTime: LIMIT
+						, measures: 5
+						, onend: _end
 					});
-					t.start();
 				}
 			}
 			, download: {
-				start: function() {
-					const t = net.download;
-					t.settings({
-						endpoint: URL + '?type=download'
-						, delay: LIMIT
-						, data: {
-							size: 1 * 1024 * 1024
+				start: () => {
+					_start();
+					__repeat({
+						action: __netTest
+						, params: {
+							url: '?type=download'
+							, size: 2 * MB
 							, multiplier: 2
+							, delay: DELAY
 						}
+						, maxTime: LIMIT
+						, measures: 5
+						, onend: _end
 					});
-					t.start();
 				}
 			}
 		};
 	}
-	function __start(size, newSection) {
+	function _start() {
 		const msg = $('<span></span>').append(lbls['report.start']);
-		let upDown = false;
+		_log(_delimiter(msg), true);
+	}
+	function _logSize(size) {
+		const msg = $('<span></span>');
 		if (testName === 'upload') {
 			msg.append(lbls['upl.bytes']);
-			upDown = true;
 		} else if (testName === 'download') {
 			msg.append(lbls['dwn.bytes']);
-			upDown = true;
-		}
-		if (upDown) {
-			msg.append(_value(size / 1024 / 1024, lbls['mb']));
 		}
+		msg.append(_value(size / MB, lbls['mb']));
 		msg.append('...');
-		_log(_delimiter(msg), newSection);
+		_log(_delimiter(msg));
 	}
-	function _start(size) {
-		__start(size, true);
-	}
-	function _jitterEnd(avg, _all) {
-		const all = $('<span></span>').append('[');
+	function _jitterEnd(times) {
+		const all = $('<span></span>').append('[')
+			, atimes = times.map(arr => average(arr))
+			, avg = average(atimes);
 		let delim = '';
 		let max = 0, min = Number.MAX_VALUE;
-		for (let i = 0; i < _all.length; ++i) {
-			const v = _all[i];
+		atimes.forEach(v => {
 			max = Math.max(max, v);
 			min = Math.min(min, v);
 			all.append(delim).append(_value(v, lbls['ms']));
 			delim = ',';
-		}
+		});
 		all.append(']');
 		_log(all);
 		_log($('<span></span>').append(lbls['jitter.avg']).append(_value(avg, lbls['ms'])));
 		_log($('<span></span>').append(lbls['jitter.min']).append(_value(min, lbls['ms'])));
 		_log($('<span></span>').append(lbls['jitter.max']).append(_value(max, lbls['ms'])));
 		_log($('<span></span>').append(lbls['jitter'])
-				.append(':').append(_value(max - avg, lbls['ms']))
-				.append(';').append(_value(min - avg, lbls['ms'])));
+			.append(':').append(_value(max - avg, lbls['ms']))
+			.append(';').append(_value(min - avg, lbls['ms'])));
 		_setResult($('<div></div>')
-				.append($('<div class="line"></div>').append(lbls['jitter.avgAbbr'] + '&nbsp;').append(_value(avg, lbls['ms'])))
-				.append($('<div class="line"></div>').append(lbls['jitter'] + '&nbsp;').append(_value(max - avg, lbls['ms']))));
+			.append($('<div class="line"></div>').append(lbls['jitter.avgAbbr'] + '&nbsp;').append(_value(avg, lbls['ms'])))
+			.append($('<div class="line"></div>').append(lbls['jitter'] + '&nbsp;').append(_value(max - avg, lbls['ms']))));
 	}
-	function _pingEnd(avg, _all) {
+	function _pingEnd(times) {
+		const avg = average(times);
 		_log($('<span></span>').append(lbls['ping.avg']).append(_value(avg, lbls['ms'])));
-		_log($('<span></span>').append(lbls['ping.rcv']).append(_value(_all.length, '')));
-		_log($('<span></span>').append(lbls['ping.lost']).append(_value(PINGS - _all.length, '')));
+		_log($('<span></span>').append(lbls['ping.rcv']).append(_value(times.length, '')));
+		_log($('<span></span>').append(lbls['ping.lost']).append(_value(PINGS - times.length, '')));
 		_setResult(_value(avg, lbls['ms']));
 	}
-	function _restart(size) {
-		__start(size, false);
-	}
 	function _mbps() {
 		return lbls['mb'] + '/' + lbls['sec'];
 	}
@@ -161,13 +227,14 @@ var NetTest = (function() {
 			_btn().click();
 		}
 	}
-	function _end(avg) {
-		const val = _value(avg / 1024 / 1024, _mbps())
+	function _end(speeds) {
+		const avg = average(speeds)
+			, val = _value(avg / MB, _mbps())
 			, msg = $('<span></span>')
 				.append(lbls[testName === 'upload' ? 'upl.speed' : 'dwn.speed'])
 				.append(val);
 		_log(msg);
-		_setResult(val);
+		_setResult(val.clone());
 	}
 	function _delimiter(text) {
 		return $('<span class="delim"></span>').html(text);
diff --git a/openmeetings-web/src/main/webapp/WEB-INF/classes/cxf-servlet.xml b/openmeetings-web/src/main/webapp/WEB-INF/classes/cxf-servlet.xml
index 3ee2449..38d3d60 100644
--- a/openmeetings-web/src/main/webapp/WEB-INF/classes/cxf-servlet.xml
+++ b/openmeetings-web/src/main/webapp/WEB-INF/classes/cxf-servlet.xml
@@ -51,10 +51,11 @@
 			<ref bean="wbWebService"/>
 		</jaxrs:serviceBeans>
 		<jaxrs:providers>
-			<bean id="appDtoMessageBodyWriter" class="org.apache.openmeetings.webservice.util.AppointmentMessageBodyWriter" />
-			<bean id="appDtoListMessageBodyWriter" class="org.apache.openmeetings.webservice.util.AppointmentListMessageBodyWriter" />
-			<bean id="userDtoMessageBodyWriter" class="org.apache.openmeetings.webservice.util.UserMessageBodyWriter" />
-			<bean id="omParamProvider" class="org.apache.openmeetings.webservice.util.OmParamConverterProvider"/>
+			<bean class="org.apache.openmeetings.webservice.util.RateLimitRequestFilter"/>
+			<bean class="org.apache.openmeetings.webservice.util.AppointmentMessageBodyWriter"/>
+			<bean class="org.apache.openmeetings.webservice.util.AppointmentListMessageBodyWriter"/>
+			<bean class="org.apache.openmeetings.webservice.util.UserMessageBodyWriter"/>
+			<bean class="org.apache.openmeetings.webservice.util.OmParamConverterProvider"/>
 		</jaxrs:providers>
 		<jaxrs:outInterceptors>
 			<bean id="allowOriginProvider" class="org.apache.openmeetings.webservice.util.AllowOriginProvider"/>
diff --git a/openmeetings-web/src/main/webapp/css/raw-nettest.css b/openmeetings-web/src/main/webapp/css/raw-nettest.css
index e6212f0..20c119a 100644
--- a/openmeetings-web/src/main/webapp/css/raw-nettest.css
+++ b/openmeetings-web/src/main/webapp/css/raw-nettest.css
@@ -37,6 +37,9 @@
 .nettest .test-container button.not-started i::before {
 	content: '\f04b';
 }
+.nettest .test-container button.started i {
+	animation: fa-spin 2s linear infinite;
+}
 .nettest .test-container button.started i::before {
 	content: '\f110';
 }
diff --git a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/NetTestWebService.java b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/NetTestWebService.java
index 5c61c7c..709cb95 100644
--- a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/NetTestWebService.java
+++ b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/NetTestWebService.java
@@ -33,6 +33,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.ResponseBuilder;
 
+import org.apache.openmeetings.webservice.util.RateLimited;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -40,8 +41,8 @@ import org.springframework.stereotype.Service;
 @Service("netTestWebService")
 @Path("/networktest")
 public class NetTestWebService {
-	private static final Logger log = LoggerFactory.getLogger(UserWebService.class);
-	enum TestType {
+	private static final Logger log = LoggerFactory.getLogger(NetTestWebService.class);
+	public enum TestType {
 		UNKNOWN,
 		PING,
 		JITTER,
@@ -53,6 +54,7 @@ public class NetTestWebService {
 	private static final int JITTER_PACKET_SIZE = 1024;
 	private static final int MAX_UPLOAD_SIZE = 16 * 1024 * 1024;
 
+	@RateLimited
 	@GET
 	@Produces(MediaType.APPLICATION_OCTET_STREAM)
 	@Path("/")
@@ -109,7 +111,7 @@ public class NetTestWebService {
 		log.debug("Total bytes read {}", totalCount);
 	}
 
-	private static TestType getTypeByString(String typeString) {
+	public static TestType getTypeByString(String typeString) {
 		if ("ping".equals(typeString)) {
 			return TestType.PING;
 		} else if ("jitter".equals(typeString)) {
diff --git a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/util/RateLimitRequestFilter.java b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/util/RateLimitRequestFilter.java
new file mode 100644
index 0000000..023af45
--- /dev/null
+++ b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/util/RateLimitRequestFilter.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") +  you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.webservice.util;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.cxf.interceptor.Fault;
+import org.apache.openmeetings.webservice.NetTestWebService;
+import org.apache.openmeetings.webservice.NetTestWebService.TestType;
+
+@RateLimited
+public class RateLimitRequestFilter implements ContainerRequestFilter {
+	private final static String ATTR_LAST_ACCESS_TIME = "LAST_ACCESS_TIME";
+	private final static long ALLOWED_TIME = 3000;
+
+	@Context
+	private HttpServletRequest request;
+
+	@Override
+	public void filter(ContainerRequestContext context) throws Fault {
+		HttpSession session = request.getSession(false);
+		if (session == null) {
+			context.abortWith(Response.status(Status.FORBIDDEN).build());
+			return;
+		}
+		List<String> typeList = context.getUriInfo().getQueryParameters().get("type");
+		if (typeList != null && !typeList.isEmpty()) {
+			TestType type = NetTestWebService.getTypeByString(typeList.get(0));
+			if (TestType.PING == type || TestType.JITTER == type) {
+				return;
+			}
+		}
+		Long lastAccessed = (Long)session.getAttribute(ATTR_LAST_ACCESS_TIME);
+		session.setAttribute(ATTR_LAST_ACCESS_TIME, System.currentTimeMillis());
+		if (lastAccessed != null && System.currentTimeMillis() - lastAccessed.longValue() < ALLOWED_TIME) {
+			context.abortWith(Response.status(Status.TOO_MANY_REQUESTS).build());
+		}
+	}
+}
diff --git a/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/util/RateLimited.java b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/util/RateLimited.java
new file mode 100644
index 0000000..86834f8
--- /dev/null
+++ b/openmeetings-webservice/src/main/java/org/apache/openmeetings/webservice/util/RateLimited.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License") +  you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.openmeetings.webservice.util;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import javax.ws.rs.NameBinding;
+
+@Retention(RUNTIME)
+@Target({TYPE, METHOD})
+@NameBinding
+public @interface RateLimited {
+}