EVOLUTION-MANAGER
Edit File: nettest.js
/* 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/'; let output, lbls, net, tests, testName, testLabel, testNext, bulk = true; // Based on // https://github.com/nesk/network.js/blob/master/example/main.js function _init(_lbls) { lbls = _lbls; output = $('.nettest output'); $('.nettest button') .click(function() { const btn = $(this); btn.removeClass('complete').removeClass('not-started').addClass('started'); testLabel = btn.data('lbl'); testName = btn.data('measure'); tests[testName].start(); 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 _initTests() { tests = { ping: { start: function() { const t = net.latency; t.settings({ endpoint: URL + '?type=ping' , measures: PINGS , attempts: 1 }); t.off('end').on('end', _pingEnd); t.start(); t.trigger('start'); } } , jitter: { start: function() { const t = net.latency; t.settings({ endpoint: URL + '?type=jitter' , measures: 5 , attempts: 3 }); 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 , multiplier: 2 } }); t.start(); } } , download: { start: function() { const t = net.download; t.settings({ endpoint: URL + '?type=download' , delay: LIMIT , data: { size: 1 * 1024 * 1024 , multiplier: 2 } }); t.start(); } } }; } function __start(size, newSection) { const msg = $('<span></span>').append(lbls['report.start']); let upDown = false; 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('...'); _log(_delimiter(msg), newSection); } function _start(size) { __start(size, true); } function _jitterEnd(avg, _all) { const all = $('<span></span>').append('['); let delim = ''; let max = 0, min = Number.MAX_VALUE; for (let i = 0; i < _all.length; ++i) { const v = _all[i]; 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']))); _setResult($('<div></div>') .append($('<div class="line"></div>').append(lbls['jitter.avgAbbr'] + ' ').append(_value(avg, lbls['ms']))) .append($('<div class="line"></div>').append(lbls['jitter'] + ' ').append(_value(max - avg, lbls['ms'])))); } function _pingEnd(avg, _all) { _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, ''))); _setResult(_value(avg, lbls['ms'])); } function _restart(size) { __start(size, false); } function _mbps() { return lbls['mb'] + '/' + lbls['sec']; } function _btn() { return $('#test-' + testName + ' button.test-btn'); } function _setResult(val) { const btn = _btn(); btn.addClass('complete').removeClass('started'); btn.parent().find('.value').html(val); testNext = btn.data('next'); if (!testNext) { bulk = false; return; } if (bulk) { testName = testNext; _btn().click(); } } function _end(avg) { const val = _value(avg / 1024 / 1024, _mbps()) , msg = $('<span></span>') .append(lbls[testName === 'upload' ? 'upl.speed' : 'dwn.speed']) .append(val); _log(msg); _setResult(val); } function _delimiter(text) { return $('<span class="delim"></span>').html(text); } function _log(text, newSection) { output.append('<br/>'); if (newSection) { output.append('<br/>'); } output.append($('<span class="module"></span>').text(testLabel)).append(text); output.find('span').last()[0].scrollIntoView(false); } function _value(value, unit) { return $('<span class="value"></span>').append(value == null ? 'null' : value.toFixed(1) + ' ' + unit); } self.init = _init; return self; })(); /* 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)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ '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) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); 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) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); 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; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 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) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); 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; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 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) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); 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; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 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) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); 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) { for (var f = 0; f < decorators.length; f++) { var decorator = decorators[f]; if (typeof decorator === 'function') { descriptor = decorator(target, key, descriptor) || descriptor; } else { throw new TypeError('The decorator for method ' + descriptor.key + ' is of the invalid type ' + typeof decorator); } } if (descriptor.initializer !== undefined) { initializers[key] = descriptor; continue; } } Object.defineProperty(target, key, descriptor); } } return function (Constructor, protoProps, staticProps, protoInitializers, staticInitializers) { if (protoProps) defineProperties(Constructor.prototype, protoProps, protoInitializers); if (staticProps) defineProperties(Constructor, staticProps, staticInitializers); return Constructor; }; })(); 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) defineProperties(Constructor, staticProps); return Constructor; }; })(); 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) });