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 <solomax...@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'] + ' ').append(_value(avg, lbls['ms']))) - .append($('<div class="line"></div>').append(lbls['jitter'] + ' ').append(_value(max - avg, lbls['ms'])))); + .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) { + 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 { +}