UNPKG

metaapi.cloud-sdk

Version:

SDK for MetaApi, a professional cloud forex API which includes MetaTrader REST API and MetaTrader websocket API. Supports both MetaTrader 5 (MT5) and MetaTrader 4 (MT4). CopyFactory copy trading API included. (https://metaapi.cloud)

267 lines (266 loc) 37.1 kB
'use strict'; function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _async_to_generator(fn) { return function() { var self = this, args = arguments; return new Promise(function(resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } import TimeoutError from '../timeoutError'; import OptionsValidator from '../optionsValidator'; import LoggerManager from '../../logger'; let SynchronizationThrottler = class SynchronizationThrottler { /** * Initializes the synchronization throttler */ start() { if (!this._removeOldSyncIdsInterval) { this._removeOldSyncIdsInterval = setInterval(()=>this._removeOldSyncIdsJob(), 1000); this._processQueueInterval = setInterval(()=>this._processQueueJob(), 1000); } } /** * Deinitializes the throttler */ stop() { clearInterval(this._removeOldSyncIdsInterval); this._removeOldSyncIdsInterval = null; clearInterval(this._processQueueInterval); this._processQueueInterval = null; } _removeOldSyncIdsJob() { var _this = this; return _async_to_generator(function*() { const now = Date.now(); for (let key of Object.keys(_this._synchronizationIds)){ if (now - _this._synchronizationIds[key] > _this._synchronizationTimeoutInSeconds * 1000) { delete _this._synchronizationIds[key]; } } while(_this._synchronizationQueue.length && Date.now() - _this._synchronizationQueue[0].queueTime > _this._queueTimeoutInSeconds * 1000){ _this._removeFromQueue(_this._synchronizationQueue[0].synchronizationId, 'timeout'); } _this._advanceQueue(); })(); } /** * Fills a synchronization slot with synchronization id * @param {String} synchronizationId synchronization id */ updateSynchronizationId(synchronizationId) { if (this._accountsBySynchronizationIds[synchronizationId]) { this._synchronizationIds[synchronizationId] = Date.now(); } } /** * Returns the list of currently synchronizing account ids */ get synchronizingAccounts() { const synchronizingAccounts = []; Object.keys(this._synchronizationIds).forEach((key)=>{ const accountData = this._accountsBySynchronizationIds[key]; if (accountData && !synchronizingAccounts.includes(accountData.accountId)) { synchronizingAccounts.push(accountData.accountId); } }); return synchronizingAccounts; } /** * Returns the list of currenly active synchronization ids * @return {String[]} synchronization ids */ get activeSynchronizationIds() { return Object.keys(this._accountsBySynchronizationIds); } /** * Returns the amount of maximum allowed concurrent synchronizations * @return {number} maximum allowed concurrent synchronizations */ get maxConcurrentSynchronizations() { const calculatedMax = Math.max(Math.ceil(this._client.subscribedAccountIds(this._instanceNumber, this._socketInstanceIndex, this._region).length / 10), 1); return Math.min(calculatedMax, this._maxConcurrentSynchronizations); } /** * Returns flag whether there are free slots for synchronization requests * @return {Boolean} flag whether there are free slots for synchronization requests */ get isSynchronizationAvailable() { if (this._client.socketInstances[this._region][this._instanceNumber].reduce((acc, socketInstance)=>acc + socketInstance.synchronizationThrottler.synchronizingAccounts.length, 0) >= this._maxConcurrentSynchronizations) { return false; } return this.synchronizingAccounts.length < this.maxConcurrentSynchronizations; } /** * Removes synchronizations from queue and from the list by parameters * @param {String} accountId account id * @param {Number} instanceIndex account instance index * @param {String} host account host name */ removeIdByParameters(accountId, instanceIndex, host) { for (let key of Object.keys(this._accountsBySynchronizationIds)){ if (this._accountsBySynchronizationIds[key].accountId === accountId && this._accountsBySynchronizationIds[key].instanceIndex === instanceIndex && this._accountsBySynchronizationIds[key].host === host) { this.removeSynchronizationId(key); } } } /** * Removes synchronization id from slots and removes ids for the same account from the queue * @param {String} synchronizationId synchronization id */ removeSynchronizationId(synchronizationId) { if (this._accountsBySynchronizationIds[synchronizationId]) { const accountId = this._accountsBySynchronizationIds[synchronizationId].accountId; const instanceIndex = this._accountsBySynchronizationIds[synchronizationId].instanceIndex; const host = this._accountsBySynchronizationIds[synchronizationId].host; for (let key of Object.keys(this._accountsBySynchronizationIds)){ if (this._accountsBySynchronizationIds[key].accountId === accountId && this._accountsBySynchronizationIds[key].instanceIndex === instanceIndex && this._accountsBySynchronizationIds[key].host === host) { this._removeFromQueue(key, 'cancel'); delete this._accountsBySynchronizationIds[key]; } } } if (this._synchronizationIds[synchronizationId]) { delete this._synchronizationIds[synchronizationId]; } this._advanceQueue(); } /** * Clears synchronization ids on disconnect */ onDisconnect() { this._synchronizationQueue.forEach((synchronization)=>{ synchronization.resolve('cancel'); }); this._synchronizationIds = {}; this._accountsBySynchronizationIds = {}; this._synchronizationQueue = []; this.stop(); this.start(); } _advanceQueue() { let index = 0; while(this.isSynchronizationAvailable && this._synchronizationQueue.length && index < this._synchronizationQueue.length){ const queueItem = this._synchronizationQueue[index]; queueItem.resolve('synchronize'); this.updateSynchronizationId(queueItem.synchronizationId); index++; } } _removeFromQueue(synchronizationId, result) { this._synchronizationQueue.forEach((syncItem, i)=>{ if (syncItem.synchronizationId === synchronizationId) { syncItem.resolve(result); } }); this._synchronizationQueue = this._synchronizationQueue.filter((item)=>item.synchronizationId !== synchronizationId); } _processQueueJob() { var _this = this; return _async_to_generator(function*() { try { while(_this._synchronizationQueue.length){ const queueItem = _this._synchronizationQueue[0]; yield _this._synchronizationQueue[0].promise; if (_this._synchronizationQueue.length && _this._synchronizationQueue[0].synchronizationId === queueItem.synchronizationId) { _this._synchronizationQueue.shift(); } } } catch (err) { _this._logger.error('Error processing queue job', err); } })(); } /** * Schedules to send a synchronization request for account * @param {String} accountId account id * @param {Object} request request to send * @param {Object} hashes terminal state hashes */ scheduleSynchronize(accountId, request, hashes) { var _this = this; return _async_to_generator(function*() { const synchronizationId = request.requestId; for (let key of Object.keys(_this._accountsBySynchronizationIds)){ if (_this._accountsBySynchronizationIds[key].accountId === accountId && _this._accountsBySynchronizationIds[key].instanceIndex === request.instanceIndex && _this._accountsBySynchronizationIds[key].host === request.host) { _this.removeSynchronizationId(key); } } _this._accountsBySynchronizationIds[synchronizationId] = { accountId, instanceIndex: request.instanceIndex, host: request.host }; if (!_this.isSynchronizationAvailable) { let resolve; let requestResolve = new Promise((res)=>{ resolve = res; }); _this._synchronizationQueue.push({ synchronizationId: synchronizationId, promise: requestResolve, resolve, queueTime: Date.now() }); const result = yield requestResolve; if (result === 'cancel') { return false; } else if (result === 'timeout') { throw new TimeoutError(`Account ${accountId} synchronization ${synchronizationId}` + ' timed out in synchronization queue'); } } _this.updateSynchronizationId(synchronizationId); request.specificationsHashes = hashes.specificationsHashes; request.positionsHashes = hashes.positionsHashes; request.ordersHashes = hashes.ordersHashes; yield _this._client.rpcRequest(accountId, request); return true; })(); } /** * Constructs the synchronization throttler * @param {MetaApiWebsocketClient} client MetaApi websocket client * @param {Number} socketInstanceIndex index of socket instance that uses the throttler * @param {Number} instanceNumber instance index number * @param {String} region server region * @param {SynchronizationThrottlerOpts} opts synchronization throttler options */ constructor(client, socketInstanceIndex, instanceNumber, region, opts){ const validator = new OptionsValidator(); opts = opts || {}; this._maxConcurrentSynchronizations = validator.validateNonZero(opts.maxConcurrentSynchronizations, 15, 'synchronizationThrottler.maxConcurrentSynchronizations'); this._queueTimeoutInSeconds = validator.validateNonZero(opts.queueTimeoutInSeconds, 300, 'synchronizationThrottler.queueTimeoutInSeconds'); this._synchronizationTimeoutInSeconds = validator.validateNonZero(opts.synchronizationTimeoutInSeconds, 10, 'synchronizationThrottler.synchronizationTimeoutInSeconds'); this._client = client; this._region = region; this._socketInstanceIndex = socketInstanceIndex; this._synchronizationIds = {}; this._accountsBySynchronizationIds = {}; this._synchronizationQueue = []; this._removeOldSyncIdsInterval = null; this._processQueueInterval = null; this._instanceNumber = instanceNumber; this._logger = LoggerManager.getLogger('SynchronizationThrottler'); } }; /** * Options for synchronization throttler * @typedef {Object} SynchronizationThrottlerOpts * @property {Number} [maxConcurrentSynchronizations] amount of maximum allowed concurrent synchronizations * @property {Number} [queueTimeoutInSeconds] allowed time for a synchronization in queue * @property {Number} [synchronizationTimeoutInSeconds] time after which a synchronization slot * is freed to be used by another synchronization */ /** * Synchronization throttler used to limit the amount of concurrent synchronizations to prevent application * from being overloaded due to excessive number of synchronisation responses being sent. */ export { SynchronizationThrottler as default }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBUaW1lb3V0RXJyb3IgZnJvbSAnLi4vdGltZW91dEVycm9yJztcbmltcG9ydCBPcHRpb25zVmFsaWRhdG9yIGZyb20gJy4uL29wdGlvbnNWYWxpZGF0b3InO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIgZnJvbSAnLi4vLi4vbG9nZ2VyJztcblxuLyoqXG4gKiBPcHRpb25zIGZvciBzeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyXG4gKiBAdHlwZWRlZiB7T2JqZWN0fSBTeW5jaHJvbml6YXRpb25UaHJvdHRsZXJPcHRzXG4gKiBAcHJvcGVydHkge051bWJlcn0gW21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zXSBhbW91bnQgb2YgbWF4aW11bSBhbGxvd2VkIGNvbmN1cnJlbnQgc3luY2hyb25pemF0aW9uc1xuICogQHByb3BlcnR5IHtOdW1iZXJ9IFtxdWV1ZVRpbWVvdXRJblNlY29uZHNdIGFsbG93ZWQgdGltZSBmb3IgYSBzeW5jaHJvbml6YXRpb24gaW4gcXVldWVcbiAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kc10gdGltZSBhZnRlciB3aGljaCBhIHN5bmNocm9uaXphdGlvbiBzbG90XG4gKiBpcyBmcmVlZCB0byBiZSB1c2VkIGJ5IGFub3RoZXIgc3luY2hyb25pemF0aW9uXG4gKi9cblxuLyoqXG4gKiBTeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyIHVzZWQgdG8gbGltaXQgdGhlIGFtb3VudCBvZiBjb25jdXJyZW50IHN5bmNocm9uaXphdGlvbnMgdG8gcHJldmVudCBhcHBsaWNhdGlvblxuICogZnJvbSBiZWluZyBvdmVybG9hZGVkIGR1ZSB0byBleGNlc3NpdmUgbnVtYmVyIG9mIHN5bmNocm9uaXNhdGlvbiByZXNwb25zZXMgYmVpbmcgc2VudC5cbiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgU3luY2hyb25pemF0aW9uVGhyb3R0bGVyIHtcblxuICAvKipcbiAgICogQ29uc3RydWN0cyB0aGUgc3luY2hyb25pemF0aW9uIHRocm90dGxlclxuICAgKiBAcGFyYW0ge01ldGFBcGlXZWJzb2NrZXRDbGllbnR9IGNsaWVudCBNZXRhQXBpIHdlYnNvY2tldCBjbGllbnRcbiAgICogQHBhcmFtIHtOdW1iZXJ9IHNvY2tldEluc3RhbmNlSW5kZXggaW5kZXggb2Ygc29ja2V0IGluc3RhbmNlIHRoYXQgdXNlcyB0aGUgdGhyb3R0bGVyXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBpbnN0YW5jZU51bWJlciBpbnN0YW5jZSBpbmRleCBudW1iZXJcbiAgICogQHBhcmFtIHtTdHJpbmd9IHJlZ2lvbiBzZXJ2ZXIgcmVnaW9uXG4gICAqIEBwYXJhbSB7U3luY2hyb25pemF0aW9uVGhyb3R0bGVyT3B0c30gb3B0cyBzeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyIG9wdGlvbnNcbiAgICovXG4gIGNvbnN0cnVjdG9yKGNsaWVudCwgc29ja2V0SW5zdGFuY2VJbmRleCwgaW5zdGFuY2VOdW1iZXIsIHJlZ2lvbiwgb3B0cykge1xuICAgIGNvbnN0IHZhbGlkYXRvciA9IG5ldyBPcHRpb25zVmFsaWRhdG9yKCk7XG4gICAgb3B0cyA9IG9wdHMgfHwge307XG4gICAgdGhpcy5fbWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnMgPSB2YWxpZGF0b3IudmFsaWRhdGVOb25aZXJvKG9wdHMubWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnMsIDE1LFxuICAgICAgJ3N5bmNocm9uaXphdGlvblRocm90dGxlci5tYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucycpO1xuICAgIHRoaXMuX3F1ZXVlVGltZW91dEluU2Vjb25kcyA9IHZhbGlkYXRvci52YWxpZGF0ZU5vblplcm8ob3B0cy5xdWV1ZVRpbWVvdXRJblNlY29uZHMsIDMwMCxcbiAgICAgICdzeW5jaHJvbml6YXRpb25UaHJvdHRsZXIucXVldWVUaW1lb3V0SW5TZWNvbmRzJyk7XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kcyA9IHZhbGlkYXRvci52YWxpZGF0ZU5vblplcm8ob3B0cy5zeW5jaHJvbml6YXRpb25UaW1lb3V0SW5TZWNvbmRzLCAxMCxcbiAgICAgICdzeW5jaHJvbml6YXRpb25UaHJvdHRsZXIuc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kcycpO1xuICAgIHRoaXMuX2NsaWVudCA9IGNsaWVudDtcbiAgICB0aGlzLl9yZWdpb24gPSByZWdpb247XG4gICAgdGhpcy5fc29ja2V0SW5zdGFuY2VJbmRleCA9IHNvY2tldEluc3RhbmNlSW5kZXg7XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uSWRzID0ge307XG4gICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcyA9IHt9O1xuICAgIHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlID0gW107XG4gICAgdGhpcy5fcmVtb3ZlT2xkU3luY0lkc0ludGVydmFsID0gbnVsbDtcbiAgICB0aGlzLl9wcm9jZXNzUXVldWVJbnRlcnZhbCA9IG51bGw7XG4gICAgdGhpcy5faW5zdGFuY2VOdW1iZXIgPSBpbnN0YW5jZU51bWJlcjtcbiAgICB0aGlzLl9sb2dnZXIgPSBMb2dnZXJNYW5hZ2VyLmdldExvZ2dlcignU3luY2hyb25pemF0aW9uVGhyb3R0bGVyJyk7XG4gIH1cblxuICAvKipcbiAgICogSW5pdGlhbGl6ZXMgdGhlIHN5bmNocm9uaXphdGlvbiB0aHJvdHRsZXJcbiAgICovXG4gIHN0YXJ0KCkge1xuICAgIGlmKCF0aGlzLl9yZW1vdmVPbGRTeW5jSWRzSW50ZXJ2YWwpIHtcbiAgICAgIHRoaXMuX3JlbW92ZU9sZFN5bmNJZHNJbnRlcnZhbCA9IHNldEludGVydmFsKCgpID0+IHRoaXMuX3JlbW92ZU9sZFN5bmNJZHNKb2IoKSwgMTAwMCk7XG4gICAgICB0aGlzLl9wcm9jZXNzUXVldWVJbnRlcnZhbCA9IHNldEludGVydmFsKCgpID0+IHRoaXMuX3Byb2Nlc3NRdWV1ZUpvYigpLCAxMDAwKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRGVpbml0aWFsaXplcyB0aGUgdGhyb3R0bGVyXG4gICAqL1xuICBzdG9wKCkge1xuICAgIGNsZWFySW50ZXJ2YWwodGhpcy5fcmVtb3ZlT2xkU3luY0lkc0ludGVydmFsKTtcbiAgICB0aGlzLl9yZW1vdmVPbGRTeW5jSWRzSW50ZXJ2YWwgPSBudWxsO1xuICAgIGNsZWFySW50ZXJ2YWwodGhpcy5fcHJvY2Vzc1F1ZXVlSW50ZXJ2YWwpO1xuICAgIHRoaXMuX3Byb2Nlc3NRdWV1ZUludGVydmFsID0gbnVsbDtcbiAgfVxuXG4gIGFzeW5jIF9yZW1vdmVPbGRTeW5jSWRzSm9iKCkge1xuICAgIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG4gICAgZm9yIChsZXQga2V5IG9mIE9iamVjdC5rZXlzKHRoaXMuX3N5bmNocm9uaXphdGlvbklkcykpIHtcbiAgICAgIGlmICgobm93IC0gdGhpcy5fc3luY2hyb25pemF0aW9uSWRzW2tleV0pID4gdGhpcy5fc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kcyAqIDEwMDApIHtcbiAgICAgICAgZGVsZXRlIHRoaXMuX3N5bmNocm9uaXphdGlvbklkc1trZXldO1xuICAgICAgfVxuICAgIH1cbiAgICB3aGlsZSAodGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUubGVuZ3RoICYmIChEYXRlLm5vdygpIC0gdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWVbMF0ucXVldWVUaW1lKSA+IFxuICAgICAgICB0aGlzLl9xdWV1ZVRpbWVvdXRJblNlY29uZHMgKiAxMDAwKSB7XG4gICAgICB0aGlzLl9yZW1vdmVGcm9tUXVldWUodGhpcy5fc3luY2hyb25pemF0aW9uUXVldWVbMF0uc3luY2hyb25pemF0aW9uSWQsICd0aW1lb3V0Jyk7XG4gICAgfVxuICAgIHRoaXMuX2FkdmFuY2VRdWV1ZSgpO1xuICB9XG5cbiAgLyoqXG4gICAqIEZpbGxzIGEgc3luY2hyb25pemF0aW9uIHNsb3Qgd2l0aCBzeW5jaHJvbml6YXRpb24gaWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IHN5bmNocm9uaXphdGlvbklkIHN5bmNocm9uaXphdGlvbiBpZFxuICAgKi9cbiAgdXBkYXRlU3luY2hyb25pemF0aW9uSWQoc3luY2hyb25pemF0aW9uSWQpIHtcbiAgICBpZih0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXSkge1xuICAgICAgdGhpcy5fc3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXSA9IERhdGUubm93KCk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGxpc3Qgb2YgY3VycmVudGx5IHN5bmNocm9uaXppbmcgYWNjb3VudCBpZHNcbiAgICovXG4gIGdldCBzeW5jaHJvbml6aW5nQWNjb3VudHMoKSB7XG4gICAgY29uc3Qgc3luY2hyb25pemluZ0FjY291bnRzID0gW107XG4gICAgT2JqZWN0LmtleXModGhpcy5fc3luY2hyb25pemF0aW9uSWRzKS5mb3JFYWNoKGtleSA9PiB7XG4gICAgICBjb25zdCBhY2NvdW50RGF0YSA9IHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XTtcbiAgICAgIGlmKGFjY291bnREYXRhICYmICFzeW5jaHJvbml6aW5nQWNjb3VudHMuaW5jbHVkZXMoYWNjb3VudERhdGEuYWNjb3VudElkKSkge1xuICAgICAgICBzeW5jaHJvbml6aW5nQWNjb3VudHMucHVzaChhY2NvdW50RGF0YS5hY2NvdW50SWQpO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHJldHVybiBzeW5jaHJvbml6aW5nQWNjb3VudHM7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgbGlzdCBvZiBjdXJyZW5seSBhY3RpdmUgc3luY2hyb25pemF0aW9uIGlkc1xuICAgKiBAcmV0dXJuIHtTdHJpbmdbXX0gc3luY2hyb25pemF0aW9uIGlkc1xuICAgKi9cbiAgZ2V0IGFjdGl2ZVN5bmNocm9uaXphdGlvbklkcygpIHtcbiAgICByZXR1cm4gT2JqZWN0LmtleXModGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcyk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgYW1vdW50IG9mIG1heGltdW0gYWxsb3dlZCBjb25jdXJyZW50IHN5bmNocm9uaXphdGlvbnNcbiAgICogQHJldHVybiB7bnVtYmVyfSBtYXhpbXVtIGFsbG93ZWQgY29uY3VycmVudCBzeW5jaHJvbml6YXRpb25zXG4gICAqL1xuICBnZXQgbWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnMoKSB7XG4gICAgY29uc3QgY2FsY3VsYXRlZE1heCA9IE1hdGgubWF4KE1hdGguY2VpbChcbiAgICAgIHRoaXMuX2NsaWVudC5zdWJzY3JpYmVkQWNjb3VudElkcyh0aGlzLl9pbnN0YW5jZU51bWJlciwgdGhpcy5fc29ja2V0SW5zdGFuY2VJbmRleCwgdGhpcy5fcmVnaW9uKS5sZW5ndGggLyAxMCksIDEpO1xuICAgIHJldHVybiBNYXRoLm1pbihjYWxjdWxhdGVkTWF4LCB0aGlzLl9tYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucyk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBmbGFnIHdoZXRoZXIgdGhlcmUgYXJlIGZyZWUgc2xvdHMgZm9yIHN5bmNocm9uaXphdGlvbiByZXF1ZXN0c1xuICAgKiBAcmV0dXJuIHtCb29sZWFufSBmbGFnIHdoZXRoZXIgdGhlcmUgYXJlIGZyZWUgc2xvdHMgZm9yIHN5bmNocm9uaXphdGlvbiByZXF1ZXN0c1xuICAgKi9cbiAgZ2V0IGlzU3luY2hyb25pemF0aW9uQXZhaWxhYmxlKCkge1xuICAgIGlmICh0aGlzLl9jbGllbnQuc29ja2V0SW5zdGFuY2VzW3RoaXMuX3JlZ2lvbl1bdGhpcy5faW5zdGFuY2VOdW1iZXJdLnJlZHVjZSgoYWNjLCBzb2NrZXRJbnN0YW5jZSkgPT4gXG4gICAgICBhY2MgKyBzb2NrZXRJbnN0YW5jZS5zeW5jaHJvbml6YXRpb25UaHJvdHRsZXIuc3luY2hyb25pemluZ0FjY291bnRzLmxlbmd0aCwgMCkgPj1cbiAgICAgIHRoaXMuX21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLnN5bmNocm9uaXppbmdBY2NvdW50cy5sZW5ndGggPCB0aGlzLm1heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlbW92ZXMgc3luY2hyb25pemF0aW9ucyBmcm9tIHF1ZXVlIGFuZCBmcm9tIHRoZSBsaXN0IGJ5IHBhcmFtZXRlcnNcbiAgICogQHBhcmFtIHtTdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBpbnN0YW5jZUluZGV4IGFjY291bnQgaW5zdGFuY2UgaW5kZXhcbiAgICogQHBhcmFtIHtTdHJpbmd9IGhvc3QgYWNjb3VudCBob3N0IG5hbWVcbiAgICovXG4gIHJlbW92ZUlkQnlQYXJhbWV0ZXJzKGFjY291bnRJZCwgaW5zdGFuY2VJbmRleCwgaG9zdCkge1xuICAgIGZvciAobGV0IGtleSBvZiBPYmplY3Qua2V5cyh0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzKSkge1xuICAgICAgaWYodGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldLmFjY291bnRJZCA9PT0gYWNjb3VudElkICYmXG4gICAgICAgICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldLmluc3RhbmNlSW5kZXggPT09IGluc3RhbmNlSW5kZXggJiZcbiAgICAgICAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uaG9zdCA9PT0gaG9zdCkge1xuICAgICAgICB0aGlzLnJlbW92ZVN5bmNocm9uaXphdGlvbklkKGtleSk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJlbW92ZXMgc3luY2hyb25pemF0aW9uIGlkIGZyb20gc2xvdHMgYW5kIHJlbW92ZXMgaWRzIGZvciB0aGUgc2FtZSBhY2NvdW50IGZyb20gdGhlIHF1ZXVlXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBzeW5jaHJvbml6YXRpb25JZCBzeW5jaHJvbml6YXRpb24gaWRcbiAgICovXG4gIHJlbW92ZVN5bmNocm9uaXphdGlvbklkKHN5bmNocm9uaXphdGlvbklkKSB7XG4gICAgaWYgKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdKSB7XG4gICAgICBjb25zdCBhY2NvdW50SWQgPSB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXS5hY2NvdW50SWQ7XG4gICAgICBjb25zdCBpbnN0YW5jZUluZGV4ID0gdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1tzeW5jaHJvbml6YXRpb25JZF0uaW5zdGFuY2VJbmRleDtcbiAgICAgIGNvbnN0IGhvc3QgPSB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXS5ob3N0O1xuICAgICAgZm9yIChsZXQga2V5IG9mIE9iamVjdC5rZXlzKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHMpKSB7XG4gICAgICAgIGlmKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5hY2NvdW50SWQgPT09IGFjY291bnRJZCAmJiBcbiAgICAgICAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uaW5zdGFuY2VJbmRleCA9PT0gaW5zdGFuY2VJbmRleCAmJlxuICAgICAgICAgIHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5ob3N0ID09PSBob3N0KSB7XG4gICAgICAgICAgdGhpcy5fcmVtb3ZlRnJvbVF1ZXVlKGtleSwgJ2NhbmNlbCcpO1xuICAgICAgICAgIGRlbGV0ZSB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV07XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgaWYodGhpcy5fc3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXSkge1xuICAgICAgZGVsZXRlIHRoaXMuX3N5bmNocm9uaXphdGlvbklkc1tzeW5jaHJvbml6YXRpb25JZF07XG4gICAgfVxuICAgIHRoaXMuX2FkdmFuY2VRdWV1ZSgpO1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFycyBzeW5jaHJvbml6YXRpb24gaWRzIG9uIGRpc2Nvbm5lY3RcbiAgICovXG4gIG9uRGlzY29ubmVjdCgpIHtcbiAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5mb3JFYWNoKHN5bmNocm9uaXphdGlvbiA9PiB7XG4gICAgICBzeW5jaHJvbml6YXRpb24ucmVzb2x2ZSgnY2FuY2VsJyk7XG4gICAgfSk7XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uSWRzID0ge307XG4gICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcyA9IHt9O1xuICAgIHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlID0gW107XG4gICAgdGhpcy5zdG9wKCk7XG4gICAgdGhpcy5zdGFydCgpO1xuICB9XG5cbiAgX2FkdmFuY2VRdWV1ZSgpIHtcbiAgICBsZXQgaW5kZXggPSAwO1xuICAgIHdoaWxlKHRoaXMuaXNTeW5jaHJvbml6YXRpb25BdmFpbGFibGUgJiYgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUubGVuZ3RoICYmIFxuICAgICAgICBpbmRleCA8IHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlLmxlbmd0aCkge1xuICAgICAgY29uc3QgcXVldWVJdGVtID0gdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWVbaW5kZXhdO1xuICAgICAgcXVldWVJdGVtLnJlc29sdmUoJ3N5bmNocm9uaXplJyk7XG4gICAgICB0aGlzLnVwZGF0ZVN5bmNocm9uaXphdGlvbklkKHF1ZXVlSXRlbS5zeW5jaHJvbml6YXRpb25JZCk7XG4gICAgICBpbmRleCsrO1xuICAgIH1cbiAgfVxuXG4gIF9yZW1vdmVGcm9tUXVldWUoc3luY2hyb25pemF0aW9uSWQsIHJlc3VsdCkge1xuICAgIHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlLmZvckVhY2goKHN5bmNJdGVtLCBpKSA9PiB7XG4gICAgICBpZihzeW5jSXRlbS5zeW5jaHJvbml6YXRpb25JZCA9PT0gc3luY2hyb25pemF0aW9uSWQpIHtcbiAgICAgICAgc3luY0l0ZW0ucmVzb2x2ZShyZXN1bHQpO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlID0gdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUuZmlsdGVyKGl0ZW0gPT4gXG4gICAgICBpdGVtLnN5bmNocm9uaXphdGlvbklkICE9PSBzeW5jaHJvbml6YXRpb25JZCk7XG4gIH1cblxuICBhc3luYyBfcHJvY2Vzc1F1ZXVlSm9iKCkge1xuICAgIHRyeSB7XG4gICAgICB3aGlsZSAodGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUubGVuZ3RoKSB7XG4gICAgICAgIGNvbnN0IHF1ZXVlSXRlbSA9IHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlWzBdO1xuICAgICAgICBhd2FpdCB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZVswXS5wcm9taXNlO1xuICAgICAgICBpZih0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5sZW5ndGggJiYgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWVbMF0uc3luY2hyb25pemF0aW9uSWQgPT09IFxuICAgICAgICAgICAgcXVldWVJdGVtLnN5bmNocm9uaXphdGlvbklkKSB7XG4gICAgICAgICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUuc2hpZnQoKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgdGhpcy5fbG9nZ2VyLmVycm9yKCdFcnJvciBwcm9jZXNzaW5nIHF1ZXVlIGpvYicsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFNjaGVkdWxlcyB0byBzZW5kIGEgc3luY2hyb25pemF0aW9uIHJlcXVlc3QgZm9yIGFjY291bnRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkXG4gICAqIEBwYXJhbSB7T2JqZWN0fSByZXF1ZXN0IHJlcXVlc3QgdG8gc2VuZFxuICAgKiBAcGFyYW0ge09iamVjdH0gaGFzaGVzIHRlcm1pbmFsIHN0YXRlIGhhc2hlc1xuICAgKi9cbiAgYXN5bmMgc2NoZWR1bGVTeW5jaHJvbml6ZShhY2NvdW50SWQsIHJlcXVlc3QsIGhhc2hlcykge1xuICAgIGNvbnN0IHN5bmNocm9uaXphdGlvbklkID0gcmVxdWVzdC5yZXF1ZXN0SWQ7XG4gICAgZm9yIChsZXQga2V5IG9mIE9iamVjdC5rZXlzKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHMpKSB7XG4gICAgICBpZih0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uYWNjb3VudElkID09PSBhY2NvdW50SWQgJiZcbiAgICAgICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldLmluc3RhbmNlSW5kZXggPT09IHJlcXVlc3QuaW5zdGFuY2VJbmRleCAmJlxuICAgICAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uaG9zdCA9PT0gcmVxdWVzdC5ob3N0KSB7XG4gICAgICAgIHRoaXMucmVtb3ZlU3luY2hyb25pemF0aW9uSWQoa2V5KTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1tzeW5jaHJvbml6YXRpb25JZF0gPSB7YWNjb3VudElkLCBpbnN0YW5jZUluZGV4OiByZXF1ZXN0Lmluc3RhbmNlSW5kZXgsXG4gICAgICBob3N0OiByZXF1ZXN0Lmhvc3R9O1xuICAgIGlmKCF0aGlzLmlzU3luY2hyb25pemF0aW9uQXZhaWxhYmxlKSB7XG4gICAgICBsZXQgcmVzb2x2ZTtcbiAgICAgIGxldCByZXF1ZXN0UmVzb2x2ZSA9IG5ldyBQcm9taXNlKChyZXMpID0+IHtcbiAgICAgICAgcmVzb2x2ZSA9IHJlcztcbiAgICAgIH0pO1xuICAgICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUucHVzaCh7XG4gICAgICAgIHN5bmNocm9uaXphdGlvbklkOiBzeW5jaHJvbml6YXRpb25JZCxcbiAgICAgICAgcHJvbWlzZTogcmVxdWVzdFJlc29sdmUsXG4gICAgICAgIHJlc29sdmUsXG4gICAgICAgIHF1ZXVlVGltZTogRGF0ZS5ub3coKVxuICAgICAgfSk7XG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCByZXF1ZXN0UmVzb2x2ZTtcbiAgICAgIGlmKHJlc3VsdCA9PT0gJ2NhbmNlbCcpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfSBlbHNlIGlmKHJlc3VsdCA9PT0gJ3RpbWVvdXQnKSB7XG4gICAgICAgIHRocm93IG5ldyBUaW1lb3V0RXJyb3IoYEFjY291bnQgJHthY2NvdW50SWR9IHN5bmNocm9uaXphdGlvbiAke3N5bmNocm9uaXphdGlvbklkfWAgK1xuICAgICAgICAnIHRpbWVkIG91dCBpbiBzeW5jaHJvbml6YXRpb24gcXVldWUnKTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy51cGRhdGVTeW5jaHJvbml6YXRpb25JZChzeW5jaHJvbml6YXRpb25JZCk7XG4gICAgcmVxdWVzdC5zcGVjaWZpY2F0aW9uc0hhc2hlcyA9IGhhc2hlcy5zcGVjaWZpY2F0aW9uc0hhc2hlcztcbiAgICByZXF1ZXN0LnBvc2l0aW9uc0hhc2hlcyA9IGhhc2hlcy5wb3NpdGlvbnNIYXNoZXM7XG4gICAgcmVxdWVzdC5vcmRlcnNIYXNoZXMgPSBoYXNoZXMub3JkZXJzSGFzaGVzO1xuICAgIGF3YWl0IHRoaXMuX2NsaWVudC5ycGNSZXF1ZXN0KGFjY291bnRJZCwgcmVxdWVzdCk7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxufSJdLCJuYW1lcyI6WyJUaW1lb3V0RXJyb3IiLCJPcHRpb25zVmFsaWRhdG9yIiwiTG9nZ2VyTWFuYWdlciIsIlN5bmNocm9uaXphdGlvblRocm90dGxlciIsInN0YXJ0IiwiX3JlbW92ZU9sZFN5bmNJZHNJbnRlcnZhbCIsInNldEludGVydmFsIiwiX3JlbW92ZU9sZFN5bmNJZHNKb2IiLCJfcHJvY2Vzc1F1ZXVlSW50ZXJ2YWwiLCJfcHJvY2Vzc1F1ZXVlSm9iIiwic3RvcCIsImNsZWFySW50ZXJ2YWwiLCJub3ciLCJEYXRlIiwia2V5IiwiT2JqZWN0Iiwia2V5cyIsIl9zeW5jaHJvbml6YXRpb25JZHMiLCJfc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kcyIsIl9zeW5jaHJvbml6YXRpb25RdWV1ZSIsImxlbmd0aCIsInF1ZXVlVGltZSIsIl9xdWV1ZVRpbWVvdXRJblNlY29uZHMiLCJfcmVtb3ZlRnJvbVF1ZXVlIiwic3luY2hyb25pemF0aW9uSWQiLCJfYWR2YW5jZVF1ZXVlIiwidXBkYXRlU3luY2hyb25pemF0aW9uSWQiLCJfYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcyIsInN5bmNocm9uaXppbmdBY2NvdW50cyIsImZvckVhY2giLCJhY2NvdW50RGF0YSIsImluY2x1ZGVzIiwiYWNjb3VudElkIiwicHVzaCIsImFjdGl2ZVN5bmNocm9uaXphdGlvbklkcyIsIm1heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zIiwiY2FsY3VsYXRlZE1heCIsIk1hdGgiLCJtYXgiLCJjZWlsIiwiX2NsaWVudCIsInN1YnNjcmliZWRBY2NvdW50SWRzIiwiX2luc3RhbmNlTnVtYmVyIiwiX3NvY2tldEluc3RhbmNlSW5kZXgiLCJfcmVnaW9uIiwibWluIiwiX21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zIiwiaXNTeW5jaHJvbml6YXRpb25BdmFpbGFibGUiLCJzb2NrZXRJbnN0YW5jZXMiLCJyZWR1Y2UiLCJhY2MiLCJzb2NrZXRJbnN0YW5jZSIsInN5bmNocm9uaXphdGlvblRocm90dGxlciIsInJlbW92ZUlkQnlQYXJhbWV0ZXJzIiwiaW5zdGFuY2VJbmRleCIsImhvc3QiLCJyZW1vdmVTeW5jaHJvbml6YXRpb25JZCIsIm9uRGlzY29ubmVjdCIsInN5bmNocm9uaXphdGlvbiIsInJlc29sdmUiLCJpbmRleCIsInF1ZXVlSXRlbSIsInJlc3VsdCIsInN5bmNJdGVtIiwiaSIsImZpbHRlciIsIml0ZW0iLCJwcm9taXNlIiwic2hpZnQiLCJlcnIiLCJfbG9nZ2VyIiwiZXJyb3IiLCJzY2hlZHVsZVN5bmNocm9uaXplIiwicmVxdWVzdCIsImhhc2hlcyIsInJlcXVlc3RJZCIsInJlcXVlc3RSZXNvbHZlIiwiUHJvbWlzZSIsInJlcyIsInNwZWNpZmljYXRpb25zSGFzaGVzIiwicG9zaXRpb25zSGFzaGVzIiwib3JkZXJzSGFzaGVzIiwicnBjUmVxdWVzdCIsImNvbnN0cnVjdG9yIiwiY2xpZW50Iiwic29ja2V0SW5zdGFuY2VJbmRleCIsImluc3RhbmNlTnVtYmVyIiwicmVnaW9uIiwib3B0cyIsInZhbGlkYXRvciIsInZhbGlkYXRlTm9uWmVybyIsInF1ZXVlVGltZW91dEluU2Vjb25kcyIsInN5bmNocm9uaXphdGlvblRpbWVvdXRJblNlY29uZHMiLCJnZXRMb2dnZXIiXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFFQSxPQUFPQSxrQkFBa0Isa0JBQWtCO0FBQzNDLE9BQU9DLHNCQUFzQixzQkFBc0I7QUFDbkQsT0FBT0MsbUJBQW1CLGVBQWU7QUFlMUIsSUFBQSxBQUFNQywyQkFBTixNQUFNQTtJQStCbkI7O0dBRUMsR0FDREMsUUFBUTtRQUNOLElBQUcsQ0FBQyxJQUFJLENBQUNDLHlCQUF5QixFQUFFO1lBQ2xDLElBQUksQ0FBQ0EseUJBQXlCLEdBQUdDLFlBQVksSUFBTSxJQUFJLENBQUNDLG9CQUFvQixJQUFJO1lBQ2hGLElBQUksQ0FBQ0MscUJBQXFCLEdBQUdGLFlBQVksSUFBTSxJQUFJLENBQUNHLGdCQUFnQixJQUFJ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