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)

232 lines (231 loc) 35.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "default", { enumerable: true, get: function() { return SynchronizationThrottler; } }); const _timeoutError = /*#__PURE__*/ _interop_require_default(require("../timeoutError")); const _optionsValidator = /*#__PURE__*/ _interop_require_default(require("../optionsValidator")); const _logger = /*#__PURE__*/ _interop_require_default(require("../../logger")); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 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; } async _removeOldSyncIdsJob() { 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); } async _processQueueJob() { try { while(this._synchronizationQueue.length){ const queueItem = this._synchronizationQueue[0]; await 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 */ async scheduleSynchronize(accountId, request, hashes) { 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 = await requestResolve; if (result === "cancel") { return false; } else if (result === "timeout") { throw new _timeoutError.default(`Account ${accountId} synchronization ${synchronizationId}` + " timed out in synchronization queue"); } } this.updateSynchronizationId(synchronizationId); request.specificationsHashes = hashes.specificationsHashes; request.positionsHashes = hashes.positionsHashes; request.ordersHashes = hashes.ordersHashes; await 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.default(); 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 = _logger.default.getLogger("SynchronizationThrottler"); } }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBUaW1lb3V0RXJyb3IgZnJvbSAnLi4vdGltZW91dEVycm9yJztcbmltcG9ydCBPcHRpb25zVmFsaWRhdG9yIGZyb20gJy4uL29wdGlvbnNWYWxpZGF0b3InO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIgZnJvbSAnLi4vLi4vbG9nZ2VyJztcblxuLyoqXG4gKiBPcHRpb25zIGZvciBzeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyXG4gKiBAdHlwZWRlZiB7T2JqZWN0fSBTeW5jaHJvbml6YXRpb25UaHJvdHRsZXJPcHRzXG4gKiBAcHJvcGVydHkge051bWJlcn0gW21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zXSBhbW91bnQgb2YgbWF4aW11bSBhbGxvd2VkIGNvbmN1cnJlbnQgc3luY2hyb25pemF0aW9uc1xuICogQHByb3BlcnR5IHtOdW1iZXJ9IFtxdWV1ZVRpbWVvdXRJblNlY29uZHNdIGFsbG93ZWQgdGltZSBmb3IgYSBzeW5jaHJvbml6YXRpb24gaW4gcXVldWVcbiAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kc10gdGltZSBhZnRlciB3aGljaCBhIHN5bmNocm9uaXphdGlvbiBzbG90XG4gKiBpcyBmcmVlZCB0byBiZSB1c2VkIGJ5IGFub3RoZXIgc3luY2hyb25pemF0aW9uXG4gKi9cblxuLyoqXG4gKiBTeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyIHVzZWQgdG8gbGltaXQgdGhlIGFtb3VudCBvZiBjb25jdXJyZW50IHN5bmNocm9uaXphdGlvbnMgdG8gcHJldmVudCBhcHBsaWNhdGlvblxuICogZnJvbSBiZWluZyBvdmVybG9hZGVkIGR1ZSB0byBleGNlc3NpdmUgbnVtYmVyIG9mIHN5bmNocm9uaXNhdGlvbiByZXNwb25zZXMgYmVpbmcgc2VudC5cbiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgU3luY2hyb25pemF0aW9uVGhyb3R0bGVyIHtcblxuICAvKipcbiAgICogQ29uc3RydWN0cyB0aGUgc3luY2hyb25pemF0aW9uIHRocm90dGxlclxuICAgKiBAcGFyYW0ge01ldGFBcGlXZWJzb2NrZXRDbGllbnR9IGNsaWVudCBNZXRhQXBpIHdlYnNvY2tldCBjbGllbnRcbiAgICogQHBhcmFtIHtOdW1iZXJ9IHNvY2tldEluc3RhbmNlSW5kZXggaW5kZXggb2Ygc29ja2V0IGluc3RhbmNlIHRoYXQgdXNlcyB0aGUgdGhyb3R0bGVyXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBpbnN0YW5jZU51bWJlciBpbnN0YW5jZSBpbmRleCBudW1iZXJcbiAgICogQHBhcmFtIHtTdHJpbmd9IHJlZ2lvbiBzZXJ2ZXIgcmVnaW9uXG4gICAqIEBwYXJhbSB7U3luY2hyb25pemF0aW9uVGhyb3R0bGVyT3B0c30gb3B0cyBzeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyIG9wdGlvbnNcbiAgICovXG4gIGNvbnN0cnVjdG9yKGNsaWVudCwgc29ja2V0SW5zdGFuY2VJbmRleCwgaW5zdGFuY2VOdW1iZXIsIHJlZ2lvbiwgb3B0cykge1xuICAgIGNvbnN0IHZhbGlkYXRvciA9IG5ldyBPcHRpb25zVmFsaWRhdG9yKCk7XG4gICAgb3B0cyA9IG9wdHMgfHwge307XG4gICAgdGhpcy5fbWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnMgPSB2YWxpZGF0b3IudmFsaWRhdGVOb25aZXJvKG9wdHMubWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnMsIDE1LFxuICAgICAgJ3N5bmNocm9uaXphdGlvblRocm90dGxlci5tYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucycpO1xuICAgIHRoaXMuX3F1ZXVlVGltZW91dEluU2Vjb25kcyA9IHZhbGlkYXRvci52YWxpZGF0ZU5vblplcm8ob3B0cy5xdWV1ZVRpbWVvdXRJblNlY29uZHMsIDMwMCxcbiAgICAgICdzeW5jaHJvbml6YXRpb25UaHJvdHRsZXIucXVldWVUaW1lb3V0SW5TZWNvbmRzJyk7XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kcyA9IHZhbGlkYXRvci52YWxpZGF0ZU5vblplcm8ob3B0cy5zeW5jaHJvbml6YXRpb25UaW1lb3V0SW5TZWNvbmRzLCAxMCxcbiAgICAgICdzeW5jaHJvbml6YXRpb25UaHJvdHRsZXIuc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kcycpO1xuICAgIHRoaXMuX2NsaWVudCA9IGNsaWVudDtcbiAgICB0aGlzLl9yZWdpb24gPSByZWdpb247XG4gICAgdGhpcy5fc29ja2V0SW5zdGFuY2VJbmRleCA9IHNvY2tldEluc3RhbmNlSW5kZXg7XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uSWRzID0ge307XG4gICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcyA9IHt9O1xuICAgIHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlID0gW107XG4gICAgdGhpcy5fcmVtb3ZlT2xkU3luY0lkc0ludGVydmFsID0gbnVsbDtcbiAgICB0aGlzLl9wcm9jZXNzUXVldWVJbnRlcnZhbCA9IG51bGw7XG4gICAgdGhpcy5faW5zdGFuY2VOdW1iZXIgPSBpbnN0YW5jZU51bWJlcjtcbiAgICB0aGlzLl9sb2dnZXIgPSBMb2dnZXJNYW5hZ2VyLmdldExvZ2dlcignU3luY2hyb25pemF0aW9uVGhyb3R0bGVyJyk7XG4gIH1cblxuICAvKipcbiAgICogSW5pdGlhbGl6ZXMgdGhlIHN5bmNocm9uaXphdGlvbiB0aHJvdHRsZXJcbiAgICovXG4gIHN0YXJ0KCkge1xuICAgIGlmKCF0aGlzLl9yZW1vdmVPbGRTeW5jSWRzSW50ZXJ2YWwpIHtcbiAgICAgIHRoaXMuX3JlbW92ZU9sZFN5bmNJZHNJbnRlcnZhbCA9IHNldEludGVydmFsKCgpID0+IHRoaXMuX3JlbW92ZU9sZFN5bmNJZHNKb2IoKSwgMTAwMCk7XG4gICAgICB0aGlzLl9wcm9jZXNzUXVldWVJbnRlcnZhbCA9IHNldEludGVydmFsKCgpID0+IHRoaXMuX3Byb2Nlc3NRdWV1ZUpvYigpLCAxMDAwKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRGVpbml0aWFsaXplcyB0aGUgdGhyb3R0bGVyXG4gICAqL1xuICBzdG9wKCkge1xuICAgIGNsZWFySW50ZXJ2YWwodGhpcy5fcmVtb3ZlT2xkU3luY0lkc0ludGVydmFsKTtcbiAgICB0aGlzLl9yZW1vdmVPbGRTeW5jSWRzSW50ZXJ2YWwgPSBudWxsO1xuICAgIGNsZWFySW50ZXJ2YWwodGhpcy5fcHJvY2Vzc1F1ZXVlSW50ZXJ2YWwpO1xuICAgIHRoaXMuX3Byb2Nlc3NRdWV1ZUludGVydmFsID0gbnVsbDtcbiAgfVxuXG4gIGFzeW5jIF9yZW1vdmVPbGRTeW5jSWRzSm9iKCkge1xuICAgIGNvbnN0IG5vdyA9IERhdGUubm93KCk7XG4gICAgZm9yIChsZXQga2V5IG9mIE9iamVjdC5rZXlzKHRoaXMuX3N5bmNocm9uaXphdGlvbklkcykpIHtcbiAgICAgIGlmICgobm93IC0gdGhpcy5fc3luY2hyb25pemF0aW9uSWRzW2tleV0pID4gdGhpcy5fc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kcyAqIDEwMDApIHtcbiAgICAgICAgZGVsZXRlIHRoaXMuX3N5bmNocm9uaXphdGlvbklkc1trZXldO1xuICAgICAgfVxuICAgIH1cbiAgICB3aGlsZSAodGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUubGVuZ3RoICYmIChEYXRlLm5vdygpIC0gdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWVbMF0ucXVldWVUaW1lKSA+IFxuICAgICAgICB0aGlzLl9xdWV1ZVRpbWVvdXRJblNlY29uZHMgKiAxMDAwKSB7XG4gICAgICB0aGlzLl9yZW1vdmVGcm9tUXVldWUodGhpcy5fc3luY2hyb25pemF0aW9uUXVldWVbMF0uc3luY2hyb25pemF0aW9uSWQsICd0aW1lb3V0Jyk7XG4gICAgfVxuICAgIHRoaXMuX2FkdmFuY2VRdWV1ZSgpO1xuICB9XG5cbiAgLyoqXG4gICAqIEZpbGxzIGEgc3luY2hyb25pemF0aW9uIHNsb3Qgd2l0aCBzeW5jaHJvbml6YXRpb24gaWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IHN5bmNocm9uaXphdGlvbklkIHN5bmNocm9uaXphdGlvbiBpZFxuICAgKi9cbiAgdXBkYXRlU3luY2hyb25pemF0aW9uSWQoc3luY2hyb25pemF0aW9uSWQpIHtcbiAgICBpZih0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXSkge1xuICAgICAgdGhpcy5fc3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXSA9IERhdGUubm93KCk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGxpc3Qgb2YgY3VycmVudGx5IHN5bmNocm9uaXppbmcgYWNjb3VudCBpZHNcbiAgICovXG4gIGdldCBzeW5jaHJvbml6aW5nQWNjb3VudHMoKSB7XG4gICAgY29uc3Qgc3luY2hyb25pemluZ0FjY291bnRzID0gW107XG4gICAgT2JqZWN0LmtleXModGhpcy5fc3luY2hyb25pemF0aW9uSWRzKS5mb3JFYWNoKGtleSA9PiB7XG4gICAgICBjb25zdCBhY2NvdW50RGF0YSA9IHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XTtcbiAgICAgIGlmKGFjY291bnREYXRhICYmICFzeW5jaHJvbml6aW5nQWNjb3VudHMuaW5jbHVkZXMoYWNjb3VudERhdGEuYWNjb3VudElkKSkge1xuICAgICAgICBzeW5jaHJvbml6aW5nQWNjb3VudHMucHVzaChhY2NvdW50RGF0YS5hY2NvdW50SWQpO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHJldHVybiBzeW5jaHJvbml6aW5nQWNjb3VudHM7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgbGlzdCBvZiBjdXJyZW5seSBhY3RpdmUgc3luY2hyb25pemF0aW9uIGlkc1xuICAgKiBAcmV0dXJuIHtTdHJpbmdbXX0gc3luY2hyb25pemF0aW9uIGlkc1xuICAgKi9cbiAgZ2V0IGFjdGl2ZVN5bmNocm9uaXphdGlvbklkcygpIHtcbiAgICByZXR1cm4gT2JqZWN0LmtleXModGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcyk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgYW1vdW50IG9mIG1heGltdW0gYWxsb3dlZCBjb25jdXJyZW50IHN5bmNocm9uaXphdGlvbnNcbiAgICogQHJldHVybiB7bnVtYmVyfSBtYXhpbXVtIGFsbG93ZWQgY29uY3VycmVudCBzeW5jaHJvbml6YXRpb25zXG4gICAqL1xuICBnZXQgbWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnMoKSB7XG4gICAgY29uc3QgY2FsY3VsYXRlZE1heCA9IE1hdGgubWF4KE1hdGguY2VpbChcbiAgICAgIHRoaXMuX2NsaWVudC5zdWJzY3JpYmVkQWNjb3VudElkcyh0aGlzLl9pbnN0YW5jZU51bWJlciwgdGhpcy5fc29ja2V0SW5zdGFuY2VJbmRleCwgdGhpcy5fcmVnaW9uKS5sZW5ndGggLyAxMCksIDEpO1xuICAgIHJldHVybiBNYXRoLm1pbihjYWxjdWxhdGVkTWF4LCB0aGlzLl9tYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucyk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBmbGFnIHdoZXRoZXIgdGhlcmUgYXJlIGZyZWUgc2xvdHMgZm9yIHN5bmNocm9uaXphdGlvbiByZXF1ZXN0c1xuICAgKiBAcmV0dXJuIHtCb29sZWFufSBmbGFnIHdoZXRoZXIgdGhlcmUgYXJlIGZyZWUgc2xvdHMgZm9yIHN5bmNocm9uaXphdGlvbiByZXF1ZXN0c1xuICAgKi9cbiAgZ2V0IGlzU3luY2hyb25pemF0aW9uQXZhaWxhYmxlKCkge1xuICAgIGlmICh0aGlzLl9jbGllbnQuc29ja2V0SW5zdGFuY2VzW3RoaXMuX3JlZ2lvbl1bdGhpcy5faW5zdGFuY2VOdW1iZXJdLnJlZHVjZSgoYWNjLCBzb2NrZXRJbnN0YW5jZSkgPT4gXG4gICAgICBhY2MgKyBzb2NrZXRJbnN0YW5jZS5zeW5jaHJvbml6YXRpb25UaHJvdHRsZXIuc3luY2hyb25pemluZ0FjY291bnRzLmxlbmd0aCwgMCkgPj1cbiAgICAgIHRoaXMuX21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLnN5bmNocm9uaXppbmdBY2NvdW50cy5sZW5ndGggPCB0aGlzLm1heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlbW92ZXMgc3luY2hyb25pemF0aW9ucyBmcm9tIHF1ZXVlIGFuZCBmcm9tIHRoZSBsaXN0IGJ5IHBhcmFtZXRlcnNcbiAgICogQHBhcmFtIHtTdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBpbnN0YW5jZUluZGV4IGFjY291bnQgaW5zdGFuY2UgaW5kZXhcbiAgICogQHBhcmFtIHtTdHJpbmd9IGhvc3QgYWNjb3VudCBob3N0IG5hbWVcbiAgICovXG4gIHJlbW92ZUlkQnlQYXJhbWV0ZXJzKGFjY291bnRJZCwgaW5zdGFuY2VJbmRleCwgaG9zdCkge1xuICAgIGZvciAobGV0IGtleSBvZiBPYmplY3Qua2V5cyh0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzKSkge1xuICAgICAgaWYodGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldLmFjY291bnRJZCA9PT0gYWNjb3VudElkICYmXG4gICAgICAgICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldLmluc3RhbmNlSW5kZXggPT09IGluc3RhbmNlSW5kZXggJiZcbiAgICAgICAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uaG9zdCA9PT0gaG9zdCkge1xuICAgICAgICB0aGlzLnJlbW92ZVN5bmNocm9uaXphdGlvbklkKGtleSk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJlbW92ZXMgc3luY2hyb25pemF0aW9uIGlkIGZyb20gc2xvdHMgYW5kIHJlbW92ZXMgaWRzIGZvciB0aGUgc2FtZSBhY2NvdW50IGZyb20gdGhlIHF1ZXVlXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBzeW5jaHJvbml6YXRpb25JZCBzeW5jaHJvbml6YXRpb24gaWRcbiAgICovXG4gIHJlbW92ZVN5bmNocm9uaXphdGlvbklkKHN5bmNocm9uaXphdGlvbklkKSB7XG4gICAgaWYgKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdKSB7XG4gICAgICBjb25zdCBhY2NvdW50SWQgPSB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXS5hY2NvdW50SWQ7XG4gICAgICBjb25zdCBpbnN0YW5jZUluZGV4ID0gdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1tzeW5jaHJvbml6YXRpb25JZF0uaW5zdGFuY2VJbmRleDtcbiAgICAgIGNvbnN0IGhvc3QgPSB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXS5ob3N0O1xuICAgICAgZm9yIChsZXQga2V5IG9mIE9iamVjdC5rZXlzKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHMpKSB7XG4gICAgICAgIGlmKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5hY2NvdW50SWQgPT09IGFjY291bnRJZCAmJiBcbiAgICAgICAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uaW5zdGFuY2VJbmRleCA9PT0gaW5zdGFuY2VJbmRleCAmJlxuICAgICAgICAgIHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5ob3N0ID09PSBob3N0KSB7XG4gICAgICAgICAgdGhpcy5fcmVtb3ZlRnJvbVF1ZXVlKGtleSwgJ2NhbmNlbCcpO1xuICAgICAgICAgIGRlbGV0ZSB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV07XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgaWYodGhpcy5fc3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXSkge1xuICAgICAgZGVsZXRlIHRoaXMuX3N5bmNocm9uaXphdGlvbklkc1tzeW5jaHJvbml6YXRpb25JZF07XG4gICAgfVxuICAgIHRoaXMuX2FkdmFuY2VRdWV1ZSgpO1xuICB9XG5cbiAgLyoqXG4gICAqIENsZWFycyBzeW5jaHJvbml6YXRpb24gaWRzIG9uIGRpc2Nvbm5lY3RcbiAgICovXG4gIG9uRGlzY29ubmVjdCgpIHtcbiAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5mb3JFYWNoKHN5bmNocm9uaXphdGlvbiA9PiB7XG4gICAgICBzeW5jaHJvbml6YXRpb24ucmVzb2x2ZSgnY2FuY2VsJyk7XG4gICAgfSk7XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uSWRzID0ge307XG4gICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcyA9IHt9O1xuICAgIHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlID0gW107XG4gICAgdGhpcy5zdG9wKCk7XG4gICAgdGhpcy5zdGFydCgpO1xuICB9XG5cbiAgX2FkdmFuY2VRdWV1ZSgpIHtcbiAgICBsZXQgaW5kZXggPSAwO1xuICAgIHdoaWxlKHRoaXMuaXNTeW5jaHJvbml6YXRpb25BdmFpbGFibGUgJiYgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUubGVuZ3RoICYmIFxuICAgICAgICBpbmRleCA8IHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlLmxlbmd0aCkge1xuICAgICAgY29uc3QgcXVldWVJdGVtID0gdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWVbaW5kZXhdO1xuICAgICAgcXVldWVJdGVtLnJlc29sdmUoJ3N5bmNocm9uaXplJyk7XG4gICAgICB0aGlzLnVwZGF0ZVN5bmNocm9uaXphdGlvbklkKHF1ZXVlSXRlbS5zeW5jaHJvbml6YXRpb25JZCk7XG4gICAgICBpbmRleCsrO1xuICAgIH1cbiAgfVxuXG4gIF9yZW1vdmVGcm9tUXVldWUoc3luY2hyb25pemF0aW9uSWQsIHJlc3VsdCkge1xuICAgIHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlLmZvckVhY2goKHN5bmNJdGVtLCBpKSA9PiB7XG4gICAgICBpZihzeW5jSXRlbS5zeW5jaHJvbml6YXRpb25JZCA9PT0gc3luY2hyb25pemF0aW9uSWQpIHtcbiAgICAgICAgc3luY0l0ZW0ucmVzb2x2ZShyZXN1bHQpO1xuICAgICAgfVxuICAgIH0pO1xuICAgIHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlID0gdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUuZmlsdGVyKGl0ZW0gPT4gXG4gICAgICBpdGVtLnN5bmNocm9uaXphdGlvbklkICE9PSBzeW5jaHJvbml6YXRpb25JZCk7XG4gIH1cblxuICBhc3luYyBfcHJvY2Vzc1F1ZXVlSm9iKCkge1xuICAgIHRyeSB7XG4gICAgICB3aGlsZSAodGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUubGVuZ3RoKSB7XG4gICAgICAgIGNvbnN0IHF1ZXVlSXRlbSA9IHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlWzBdO1xuICAgICAgICBhd2FpdCB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZVswXS5wcm9taXNlO1xuICAgICAgICBpZih0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5sZW5ndGggJiYgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWVbMF0uc3luY2hyb25pemF0aW9uSWQgPT09IFxuICAgICAgICAgICAgcXVldWVJdGVtLnN5bmNocm9uaXphdGlvbklkKSB7XG4gICAgICAgICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUuc2hpZnQoKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgdGhpcy5fbG9nZ2VyLmVycm9yKCdFcnJvciBwcm9jZXNzaW5nIHF1ZXVlIGpvYicsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFNjaGVkdWxlcyB0byBzZW5kIGEgc3luY2hyb25pemF0aW9uIHJlcXVlc3QgZm9yIGFjY291bnRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkXG4gICAqIEBwYXJhbSB7T2JqZWN0fSByZXF1ZXN0IHJlcXVlc3QgdG8gc2VuZFxuICAgKiBAcGFyYW0ge09iamVjdH0gaGFzaGVzIHRlcm1pbmFsIHN0YXRlIGhhc2hlc1xuICAgKi9cbiAgYXN5bmMgc2NoZWR1bGVTeW5jaHJvbml6ZShhY2NvdW50SWQsIHJlcXVlc3QsIGhhc2hlcykge1xuICAgIGNvbnN0IHN5bmNocm9uaXphdGlvbklkID0gcmVxdWVzdC5yZXF1ZXN0SWQ7XG4gICAgZm9yIChsZXQga2V5IG9mIE9iamVjdC5rZXlzKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHMpKSB7XG4gICAgICBpZih0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uYWNjb3VudElkID09PSBhY2NvdW50SWQgJiZcbiAgICAgICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldLmluc3RhbmNlSW5kZXggPT09IHJlcXVlc3QuaW5zdGFuY2VJbmRleCAmJlxuICAgICAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uaG9zdCA9PT0gcmVxdWVzdC5ob3N0KSB7XG4gICAgICAgIHRoaXMucmVtb3ZlU3luY2hyb25pemF0aW9uSWQoa2V5KTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1tzeW5jaHJvbml6YXRpb25JZF0gPSB7YWNjb3VudElkLCBpbnN0YW5jZUluZGV4OiByZXF1ZXN0Lmluc3RhbmNlSW5kZXgsXG4gICAgICBob3N0OiByZXF1ZXN0Lmhvc3R9O1xuICAgIGlmKCF0aGlzLmlzU3luY2hyb25pemF0aW9uQXZhaWxhYmxlKSB7XG4gICAgICBsZXQgcmVzb2x2ZTtcbiAgICAgIGxldCByZXF1ZXN0UmVzb2x2ZSA9IG5ldyBQcm9taXNlKChyZXMpID0+IHtcbiAgICAgICAgcmVzb2x2ZSA9IHJlcztcbiAgICAgIH0pO1xuICAgICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUucHVzaCh7XG4gICAgICAgIHN5bmNocm9uaXphdGlvbklkOiBzeW5jaHJvbml6YXRpb25JZCxcbiAgICAgICAgcHJvbWlzZTogcmVxdWVzdFJlc29sdmUsXG4gICAgICAgIHJlc29sdmUsXG4gICAgICAgIHF1ZXVlVGltZTogRGF0ZS5ub3coKVxuICAgICAgfSk7XG4gICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCByZXF1ZXN0UmVzb2x2ZTtcbiAgICAgIGlmKHJlc3VsdCA9PT0gJ2NhbmNlbCcpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfSBlbHNlIGlmKHJlc3VsdCA9PT0gJ3RpbWVvdXQnKSB7XG4gICAgICAgIHRocm93IG5ldyBUaW1lb3V0RXJyb3IoYEFjY291bnQgJHthY2NvdW50SWR9IHN5bmNocm9uaXphdGlvbiAke3N5bmNocm9uaXphdGlvbklkfWAgK1xuICAgICAgICAnIHRpbWVkIG91dCBpbiBzeW5jaHJvbml6YXRpb24gcXVldWUnKTtcbiAgICAgIH1cbiAgICB9XG4gICAgdGhpcy51cGRhdGVTeW5jaHJvbml6YXRpb25JZChzeW5jaHJvbml6YXRpb25JZCk7XG4gICAgcmVxdWVzdC5zcGVjaWZpY2F0aW9uc0hhc2hlcyA9IGhhc2hlcy5zcGVjaWZpY2F0aW9uc0hhc2hlcztcbiAgICByZXF1ZXN0LnBvc2l0aW9uc0hhc2hlcyA9IGhhc2hlcy5wb3NpdGlvbnNIYXNoZXM7XG4gICAgcmVxdWVzdC5vcmRlcnNIYXNoZXMgPSBoYXNoZXMub3JkZXJzSGFzaGVzO1xuICAgIGF3YWl0IHRoaXMuX2NsaWVudC5ycGNSZXF1ZXN0KGFjY291bnRJZCwgcmVxdWVzdCk7XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxufSJdLCJuYW1lcyI6WyJTeW5jaHJvbml6YXRpb25UaHJvdHRsZXIiLCJzdGFydCIsIl9yZW1vdmVPbGRTeW5jSWRzSW50ZXJ2YWwiLCJzZXRJbnRlcnZhbCIsIl9yZW1vdmVPbGRTeW5jSWRzSm9iIiwiX3Byb2Nlc3NRdWV1ZUludGVydmFsIiwiX3Byb2Nlc3NRdWV1ZUpvYiIsInN0b3AiLCJjbGVhckludGVydmFsIiwibm93IiwiRGF0ZSIsImtleSIsIk9iamVjdCIsImtleXMiLCJfc3luY2hyb25pemF0aW9uSWRzIiwiX3N5bmNocm9uaXphdGlvblRpbWVvdXRJblNlY29uZHMiLCJfc3luY2hyb25pemF0aW9uUXVldWUiLCJsZW5ndGgiLCJxdWV1ZVRpbWUiLCJfcXVldWVUaW1lb3V0SW5TZWNvbmRzIiwiX3JlbW92ZUZyb21RdWV1ZSIsInN5bmNocm9uaXphdGlvbklkIiwiX2FkdmFuY2VRdWV1ZSIsInVwZGF0ZVN5bmNocm9uaXphdGlvbklkIiwiX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHMiLCJzeW5jaHJvbml6aW5nQWNjb3VudHMiLCJmb3JFYWNoIiwiYWNjb3VudERhdGEiLCJpbmNsdWRlcyIsImFjY291bnRJZCIsInB1c2giLCJhY3RpdmVTeW5jaHJvbml6YXRpb25JZHMiLCJtYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucyIsImNhbGN1bGF0ZWRNYXgiLCJNYXRoIiwibWF4IiwiY2VpbCIsIl9jbGllbnQiLCJzdWJzY3JpYmVkQWNjb3VudElkcyIsIl9pbnN0YW5jZU51bWJlciIsIl9zb2NrZXRJbnN0YW5jZUluZGV4IiwiX3JlZ2lvbiIsIm1pbiIsIl9tYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucyIsImlzU3luY2hyb25pemF0aW9uQXZhaWxhYmxlIiwic29ja2V0SW5zdGFuY2VzIiwicmVkdWNlIiwiYWNjIiwic29ja2V0SW5zdGFuY2UiLCJzeW5jaHJvbml6YXRpb25UaHJvdHRsZXIiLCJyZW1vdmVJZEJ5UGFyYW1ldGVycyIsImluc3RhbmNlSW5kZXgiLCJob3N0IiwicmVtb3ZlU3luY2hyb25pemF0aW9uSWQiLCJvbkRpc2Nvbm5lY3QiLCJzeW5jaHJvbml6YXRpb24iLCJyZXNvbHZlIiwiaW5kZXgiLCJxdWV1ZUl0ZW0iLCJyZXN1bHQiLCJzeW5jSXRlbSIsImkiLCJmaWx0ZXIiLCJpdGVtIiwicHJvbWlzZSIsInNoaWZ0IiwiZXJyIiwiX2xvZ2dlciIsImVycm9yIiwic2NoZWR1bGVTeW5jaHJvbml6ZSIsInJlcXVlc3QiLCJoYXNoZXMiLCJyZXF1ZXN0SWQiLCJyZXF1ZXN0UmVzb2x2ZSIsIlByb21pc2UiLCJyZXMiLCJUaW1lb3V0RXJyb3IiLCJzcGVjaWZpY2F0aW9uc0hhc2hlcyIsInBvc2l0aW9uc0hhc2hlcyIsIm9yZGVyc0hhc2hlcyIsInJwY1JlcXVlc3QiLCJjb25zdHJ1Y3RvciIsImNsaWVudCIsInNvY2tldEluc3RhbmNlSW5kZXgiLCJpbnN0YW5jZU51bWJlciIsInJlZ2lvbiIsIm9wdHMiLCJ2YWxpZGF0b3IiLCJPcHRpb25zVmFsaWRhdG9yIiwidmFsaWRhdGVOb25aZXJvIiwicXVldWVUaW1lb3V0SW5TZWNvbmRzIiwic3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kcyIsIkxvZ2dlck1hbmFnZXIiLCJnZXRMb2dnZXIiXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7O2VBbUJxQkE7OztxRUFqQkk7eUVBQ0k7K0RBQ0g7Ozs7OztBQWVYLElBQUEsQUFBTUEsMkJBQU4sTUFBTUE7SUErQm5COztHQUVDLEdBQ0RDLFFBQVE7UUFDTixJQUFHLENBQUMsSUFBSSxDQUFDQyx5QkFBeUIsRUFBRTtZQUNsQyxJQUFJLENBQUNBLHlCQUF5QixHQUFHQyxZQUFZLElBQU0sSUFBSSxDQUFDQyxvQkFBb0IsSUFBSTtZQUNoRixJQUFJLENBQUNDLHFCQUFxQixHQUFHRixZQUFZLElBQU0sSUFBSSxDQUFDRyxnQkFBZ0IsSUFBSTtRQUMxRTtJQUNGO0lBRUE7O0dBRUMsR0FDREMsT0FBTztRQUNMQyxjQUFjLElBQUksQ0FBQ04seUJBQXlCO1FB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