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)

258 lines (257 loc) 38.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 _define_property(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 _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){ _define_property(this, "_maxConcurrentSynchronizations", void 0); _define_property(this, "_queueTimeoutInSeconds", void 0); _define_property(this, "_synchronizationTimeoutInSeconds", void 0); _define_property(this, "_client", void 0); _define_property(this, "_region", void 0); _define_property(this, "_socketInstanceIndex", void 0); _define_property(this, "_synchronizationIds", void 0); _define_property(this, "_accountsBySynchronizationIds", void 0); _define_property(this, "_synchronizationQueue", void 0); _define_property(this, "_removeOldSyncIdsInterval", void 0); _define_property(this, "_processQueueInterval", void 0); _define_property(this, "_instanceNumber", void 0); _define_property(this, "_logger", void 0); 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,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBUaW1lb3V0RXJyb3IgZnJvbSAnLi4vdGltZW91dEVycm9yJztcbmltcG9ydCBPcHRpb25zVmFsaWRhdG9yIGZyb20gJy4uL29wdGlvbnNWYWxpZGF0b3InO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIsIHtMb2dnZXJ9IGZyb20gJy4uLy4uL2xvZ2dlcic7XG5pbXBvcnQgTWV0YUFwaVdlYnNvY2tldENsaWVudCBmcm9tICcuL21ldGFBcGlXZWJzb2NrZXQuY2xpZW50JztcblxuLyoqXG4gKiBPcHRpb25zIGZvciBzeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyXG4gKiBAdHlwZWRlZiB7T2JqZWN0fSBTeW5jaHJvbml6YXRpb25UaHJvdHRsZXJPcHRzXG4gKiBAcHJvcGVydHkge051bWJlcn0gW21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zXSBhbW91bnQgb2YgbWF4aW11bSBhbGxvd2VkIGNvbmN1cnJlbnQgc3luY2hyb25pemF0aW9uc1xuICogQHByb3BlcnR5IHtOdW1iZXJ9IFtxdWV1ZVRpbWVvdXRJblNlY29uZHNdIGFsbG93ZWQgdGltZSBmb3IgYSBzeW5jaHJvbml6YXRpb24gaW4gcXVldWVcbiAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbc3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kc10gdGltZSBhZnRlciB3aGljaCBhIHN5bmNocm9uaXphdGlvbiBzbG90XG4gKiBpcyBmcmVlZCB0byBiZSB1c2VkIGJ5IGFub3RoZXIgc3luY2hyb25pemF0aW9uXG4gKi9cblxuLyoqXG4gKiBTeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyIHVzZWQgdG8gbGltaXQgdGhlIGFtb3VudCBvZiBjb25jdXJyZW50IHN5bmNocm9uaXphdGlvbnMgdG8gcHJldmVudCBhcHBsaWNhdGlvblxuICogZnJvbSBiZWluZyBvdmVybG9hZGVkIGR1ZSB0byBleGNlc3NpdmUgbnVtYmVyIG9mIHN5bmNocm9uaXNhdGlvbiByZXNwb25zZXMgYmVpbmcgc2VudC5cbiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgU3luY2hyb25pemF0aW9uVGhyb3R0bGVyIHtcbiAgXG4gIHByaXZhdGUgX21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zOiBhbnk7XG4gIHByaXZhdGUgX3F1ZXVlVGltZW91dEluU2Vjb25kczogYW55O1xuICBwcml2YXRlIF9zeW5jaHJvbml6YXRpb25UaW1lb3V0SW5TZWNvbmRzOiBhbnk7XG4gIHByaXZhdGUgX2NsaWVudDogTWV0YUFwaVdlYnNvY2tldENsaWVudDtcbiAgcHJpdmF0ZSBfcmVnaW9uOiBhbnk7XG4gIHByaXZhdGUgX3NvY2tldEluc3RhbmNlSW5kZXg6IGFueTtcbiAgcHJpdmF0ZSBfc3luY2hyb25pemF0aW9uSWRzOiB7fTtcbiAgcHJpdmF0ZSBfYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkczoge307XG4gIHByaXZhdGUgX3N5bmNocm9uaXphdGlvblF1ZXVlOiBhbnlbXTtcbiAgcHJpdmF0ZSBfcmVtb3ZlT2xkU3luY0lkc0ludGVydmFsOiBhbnk7XG4gIHByaXZhdGUgX3Byb2Nlc3NRdWV1ZUludGVydmFsOiBhbnk7XG4gIHByaXZhdGUgX2luc3RhbmNlTnVtYmVyOiBhbnk7XG4gIHByaXZhdGUgX2xvZ2dlcjogTG9nZ2VyO1xuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3RzIHRoZSBzeW5jaHJvbml6YXRpb24gdGhyb3R0bGVyXG4gICAqIEBwYXJhbSB7TWV0YUFwaVdlYnNvY2tldENsaWVudH0gY2xpZW50IE1ldGFBcGkgd2Vic29ja2V0IGNsaWVudFxuICAgKiBAcGFyYW0ge051bWJlcn0gc29ja2V0SW5zdGFuY2VJbmRleCBpbmRleCBvZiBzb2NrZXQgaW5zdGFuY2UgdGhhdCB1c2VzIHRoZSB0aHJvdHRsZXJcbiAgICogQHBhcmFtIHtOdW1iZXJ9IGluc3RhbmNlTnVtYmVyIGluc3RhbmNlIGluZGV4IG51bWJlclxuICAgKiBAcGFyYW0ge1N0cmluZ30gcmVnaW9uIHNlcnZlciByZWdpb25cbiAgICogQHBhcmFtIHtTeW5jaHJvbml6YXRpb25UaHJvdHRsZXJPcHRzfSBvcHRzIHN5bmNocm9uaXphdGlvbiB0aHJvdHRsZXIgb3B0aW9uc1xuICAgKi9cbiAgY29uc3RydWN0b3IoY2xpZW50LCBzb2NrZXRJbnN0YW5jZUluZGV4LCBpbnN0YW5jZU51bWJlciwgcmVnaW9uLCBvcHRzOiBTeW5jaHJvbml6YXRpb25UaHJvdHRsZXJPcHRzKSB7XG4gICAgY29uc3QgdmFsaWRhdG9yID0gbmV3IE9wdGlvbnNWYWxpZGF0b3IoKTtcbiAgICBvcHRzID0gb3B0cyB8fCB7fTtcbiAgICB0aGlzLl9tYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucyA9IHZhbGlkYXRvci52YWxpZGF0ZU5vblplcm8ob3B0cy5tYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucywgMTUsXG4gICAgICAnc3luY2hyb25pemF0aW9uVGhyb3R0bGVyLm1heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zJyk7XG4gICAgdGhpcy5fcXVldWVUaW1lb3V0SW5TZWNvbmRzID0gdmFsaWRhdG9yLnZhbGlkYXRlTm9uWmVybyhvcHRzLnF1ZXVlVGltZW91dEluU2Vjb25kcywgMzAwLFxuICAgICAgJ3N5bmNocm9uaXphdGlvblRocm90dGxlci5xdWV1ZVRpbWVvdXRJblNlY29uZHMnKTtcbiAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25UaW1lb3V0SW5TZWNvbmRzID0gdmFsaWRhdG9yLnZhbGlkYXRlTm9uWmVybyhvcHRzLnN5bmNocm9uaXphdGlvblRpbWVvdXRJblNlY29uZHMsIDEwLFxuICAgICAgJ3N5bmNocm9uaXphdGlvblRocm90dGxlci5zeW5jaHJvbml6YXRpb25UaW1lb3V0SW5TZWNvbmRzJyk7XG4gICAgdGhpcy5fY2xpZW50ID0gY2xpZW50O1xuICAgIHRoaXMuX3JlZ2lvbiA9IHJlZ2lvbjtcbiAgICB0aGlzLl9zb2NrZXRJbnN0YW5jZUluZGV4ID0gc29ja2V0SW5zdGFuY2VJbmRleDtcbiAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25JZHMgPSB7fTtcbiAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzID0ge307XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUgPSBbXTtcbiAgICB0aGlzLl9yZW1vdmVPbGRTeW5jSWRzSW50ZXJ2YWwgPSBudWxsO1xuICAgIHRoaXMuX3Byb2Nlc3NRdWV1ZUludGVydmFsID0gbnVsbDtcbiAgICB0aGlzLl9pbnN0YW5jZU51bWJlciA9IGluc3RhbmNlTnVtYmVyO1xuICAgIHRoaXMuX2xvZ2dlciA9IExvZ2dlck1hbmFnZXIuZ2V0TG9nZ2VyKCdTeW5jaHJvbml6YXRpb25UaHJvdHRsZXInKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbml0aWFsaXplcyB0aGUgc3luY2hyb25pemF0aW9uIHRocm90dGxlclxuICAgKi9cbiAgc3RhcnQoKSB7XG4gICAgaWYoIXRoaXMuX3JlbW92ZU9sZFN5bmNJZHNJbnRlcnZhbCkge1xuICAgICAgdGhpcy5fcmVtb3ZlT2xkU3luY0lkc0ludGVydmFsID0gc2V0SW50ZXJ2YWwoKCkgPT4gdGhpcy5fcmVtb3ZlT2xkU3luY0lkc0pvYigpLCAxMDAwKTtcbiAgICAgIHRoaXMuX3Byb2Nlc3NRdWV1ZUludGVydmFsID0gc2V0SW50ZXJ2YWwoKCkgPT4gdGhpcy5fcHJvY2Vzc1F1ZXVlSm9iKCksIDEwMDApO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBEZWluaXRpYWxpemVzIHRoZSB0aHJvdHRsZXJcbiAgICovXG4gIHN0b3AoKSB7XG4gICAgY2xlYXJJbnRlcnZhbCh0aGlzLl9yZW1vdmVPbGRTeW5jSWRzSW50ZXJ2YWwpO1xuICAgIHRoaXMuX3JlbW92ZU9sZFN5bmNJZHNJbnRlcnZhbCA9IG51bGw7XG4gICAgY2xlYXJJbnRlcnZhbCh0aGlzLl9wcm9jZXNzUXVldWVJbnRlcnZhbCk7XG4gICAgdGhpcy5fcHJvY2Vzc1F1ZXVlSW50ZXJ2YWwgPSBudWxsO1xuICB9XG5cbiAgYXN5bmMgX3JlbW92ZU9sZFN5bmNJZHNKb2IoKSB7XG4gICAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgICBmb3IgKGxldCBrZXkgb2YgT2JqZWN0LmtleXModGhpcy5fc3luY2hyb25pemF0aW9uSWRzKSkge1xuICAgICAgaWYgKChub3cgLSB0aGlzLl9zeW5jaHJvbml6YXRpb25JZHNba2V5XSkgPiB0aGlzLl9zeW5jaHJvbml6YXRpb25UaW1lb3V0SW5TZWNvbmRzICogMTAwMCkge1xuICAgICAgICBkZWxldGUgdGhpcy5fc3luY2hyb25pemF0aW9uSWRzW2tleV07XG4gICAgICB9XG4gICAgfVxuICAgIHdoaWxlICh0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5sZW5ndGggJiYgKERhdGUubm93KCkgLSB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZVswXS5xdWV1ZVRpbWUpID4gXG4gICAgICAgIHRoaXMuX3F1ZXVlVGltZW91dEluU2Vjb25kcyAqIDEwMDApIHtcbiAgICAgIHRoaXMuX3JlbW92ZUZyb21RdWV1ZSh0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZVswXS5zeW5jaHJvbml6YXRpb25JZCwgJ3RpbWVvdXQnKTtcbiAgICB9XG4gICAgdGhpcy5fYWR2YW5jZVF1ZXVlKCk7XG4gIH1cblxuICAvKipcbiAgICogRmlsbHMgYSBzeW5jaHJvbml6YXRpb24gc2xvdCB3aXRoIHN5bmNocm9uaXphdGlvbiBpZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gc3luY2hyb25pemF0aW9uSWQgc3luY2hyb25pemF0aW9uIGlkXG4gICAqL1xuICB1cGRhdGVTeW5jaHJvbml6YXRpb25JZChzeW5jaHJvbml6YXRpb25JZCkge1xuICAgIGlmKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdKSB7XG4gICAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdID0gRGF0ZS5ub3coKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgbGlzdCBvZiBjdXJyZW50bHkgc3luY2hyb25pemluZyBhY2NvdW50IGlkc1xuICAgKi9cbiAgZ2V0IHN5bmNocm9uaXppbmdBY2NvdW50cygpIHtcbiAgICBjb25zdCBzeW5jaHJvbml6aW5nQWNjb3VudHMgPSBbXTtcbiAgICBPYmplY3Qua2V5cyh0aGlzLl9zeW5jaHJvbml6YXRpb25JZHMpLmZvckVhY2goa2V5ID0+IHtcbiAgICAgIGNvbnN0IGFjY291bnREYXRhID0gdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldO1xuICAgICAgaWYoYWNjb3VudERhdGEgJiYgIXN5bmNocm9uaXppbmdBY2NvdW50cy5pbmNsdWRlcyhhY2NvdW50RGF0YS5hY2NvdW50SWQpKSB7XG4gICAgICAgIHN5bmNocm9uaXppbmdBY2NvdW50cy5wdXNoKGFjY291bnREYXRhLmFjY291bnRJZCk7XG4gICAgICB9XG4gICAgfSk7XG4gICAgcmV0dXJuIHN5bmNocm9uaXppbmdBY2NvdW50cztcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBsaXN0IG9mIGN1cnJlbmx5IGFjdGl2ZSBzeW5jaHJvbml6YXRpb24gaWRzXG4gICAqIEByZXR1cm4ge1N0cmluZ1tdfSBzeW5jaHJvbml6YXRpb24gaWRzXG4gICAqL1xuICBnZXQgYWN0aXZlU3luY2hyb25pemF0aW9uSWRzKCkge1xuICAgIHJldHVybiBPYmplY3Qua2V5cyh0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBhbW91bnQgb2YgbWF4aW11bSBhbGxvd2VkIGNvbmN1cnJlbnQgc3luY2hyb25pemF0aW9uc1xuICAgKiBAcmV0dXJuIHtudW1iZXJ9IG1heGltdW0gYWxsb3dlZCBjb25jdXJyZW50IHN5bmNocm9uaXphdGlvbnNcbiAgICovXG4gIGdldCBtYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucygpIHtcbiAgICBjb25zdCBjYWxjdWxhdGVkTWF4ID0gTWF0aC5tYXgoTWF0aC5jZWlsKFxuICAgICAgdGhpcy5fY2xpZW50LnN1YnNjcmliZWRBY2NvdW50SWRzKHRoaXMuX2luc3RhbmNlTnVtYmVyLCB0aGlzLl9zb2NrZXRJbnN0YW5jZUluZGV4LCB0aGlzLl9yZWdpb24pLmxlbmd0aCAvIDEwKSwgMSk7XG4gICAgcmV0dXJuIE1hdGgubWluKGNhbGN1bGF0ZWRNYXgsIHRoaXMuX21heENvbmN1cnJlbnRTeW5jaHJvbml6YXRpb25zKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGZsYWcgd2hldGhlciB0aGVyZSBhcmUgZnJlZSBzbG90cyBmb3Igc3luY2hyb25pemF0aW9uIHJlcXVlc3RzXG4gICAqIEByZXR1cm4ge0Jvb2xlYW59IGZsYWcgd2hldGhlciB0aGVyZSBhcmUgZnJlZSBzbG90cyBmb3Igc3luY2hyb25pemF0aW9uIHJlcXVlc3RzXG4gICAqL1xuICBnZXQgaXNTeW5jaHJvbml6YXRpb25BdmFpbGFibGUoKSB7XG4gICAgaWYgKHRoaXMuX2NsaWVudC5zb2NrZXRJbnN0YW5jZXNbdGhpcy5fcmVnaW9uXVt0aGlzLl9pbnN0YW5jZU51bWJlcl0ucmVkdWNlKChhY2MsIHNvY2tldEluc3RhbmNlKSA9PiBcbiAgICAgIGFjYyArIHNvY2tldEluc3RhbmNlLnN5bmNocm9uaXphdGlvblRocm90dGxlci5zeW5jaHJvbml6aW5nQWNjb3VudHMubGVuZ3RoLCAwKSA+PVxuICAgICAgdGhpcy5fbWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnMpIHtcbiAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMuc3luY2hyb25pemluZ0FjY291bnRzLmxlbmd0aCA8IHRoaXMubWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnM7XG4gIH1cblxuICAvKipcbiAgICogUmVtb3ZlcyBzeW5jaHJvbml6YXRpb25zIGZyb20gcXVldWUgYW5kIGZyb20gdGhlIGxpc3QgYnkgcGFyYW1ldGVyc1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHBhcmFtIHtOdW1iZXJ9IGluc3RhbmNlSW5kZXggYWNjb3VudCBpbnN0YW5jZSBpbmRleFxuICAgKiBAcGFyYW0ge1N0cmluZ30gaG9zdCBhY2NvdW50IGhvc3QgbmFtZVxuICAgKi9cbiAgcmVtb3ZlSWRCeVBhcmFtZXRlcnMoYWNjb3VudElkLCBpbnN0YW5jZUluZGV4LCBob3N0KSB7XG4gICAgZm9yIChsZXQga2V5IG9mIE9iamVjdC5rZXlzKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHMpKSB7XG4gICAgICBpZih0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uYWNjb3VudElkID09PSBhY2NvdW50SWQgJiZcbiAgICAgICAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uaW5zdGFuY2VJbmRleCA9PT0gaW5zdGFuY2VJbmRleCAmJlxuICAgICAgICAgIHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5ob3N0ID09PSBob3N0KSB7XG4gICAgICAgIHRoaXMucmVtb3ZlU3luY2hyb25pemF0aW9uSWQoa2V5KTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmVtb3ZlcyBzeW5jaHJvbml6YXRpb24gaWQgZnJvbSBzbG90cyBhbmQgcmVtb3ZlcyBpZHMgZm9yIHRoZSBzYW1lIGFjY291bnQgZnJvbSB0aGUgcXVldWVcbiAgICogQHBhcmFtIHtTdHJpbmd9IHN5bmNocm9uaXphdGlvbklkIHN5bmNocm9uaXphdGlvbiBpZFxuICAgKi9cbiAgcmVtb3ZlU3luY2hyb25pemF0aW9uSWQoc3luY2hyb25pemF0aW9uSWQpIHtcbiAgICBpZiAodGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1tzeW5jaHJvbml6YXRpb25JZF0pIHtcbiAgICAgIGNvbnN0IGFjY291bnRJZCA9IHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdLmFjY291bnRJZDtcbiAgICAgIGNvbnN0IGluc3RhbmNlSW5kZXggPSB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXS5pbnN0YW5jZUluZGV4O1xuICAgICAgY29uc3QgaG9zdCA9IHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdLmhvc3Q7XG4gICAgICBmb3IgKGxldCBrZXkgb2YgT2JqZWN0LmtleXModGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcykpIHtcbiAgICAgICAgaWYodGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldLmFjY291bnRJZCA9PT0gYWNjb3VudElkICYmIFxuICAgICAgICAgIHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5pbnN0YW5jZUluZGV4ID09PSBpbnN0YW5jZUluZGV4ICYmXG4gICAgICAgICAgdGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkc1trZXldLmhvc3QgPT09IGhvc3QpIHtcbiAgICAgICAgICB0aGlzLl9yZW1vdmVGcm9tUXVldWUoa2V5LCAnY2FuY2VsJyk7XG4gICAgICAgICAgZGVsZXRlIHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICBpZih0aGlzLl9zeW5jaHJvbml6YXRpb25JZHNbc3luY2hyb25pemF0aW9uSWRdKSB7XG4gICAgICBkZWxldGUgdGhpcy5fc3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXTtcbiAgICB9XG4gICAgdGhpcy5fYWR2YW5jZVF1ZXVlKCk7XG4gIH1cblxuICAvKipcbiAgICogQ2xlYXJzIHN5bmNocm9uaXphdGlvbiBpZHMgb24gZGlzY29ubmVjdFxuICAgKi9cbiAgb25EaXNjb25uZWN0KCkge1xuICAgIHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlLmZvckVhY2goc3luY2hyb25pemF0aW9uID0+IHtcbiAgICAgIHN5bmNocm9uaXphdGlvbi5yZXNvbHZlKCdjYW5jZWwnKTtcbiAgICB9KTtcbiAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25JZHMgPSB7fTtcbiAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzID0ge307XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUgPSBbXTtcbiAgICB0aGlzLnN0b3AoKTtcbiAgICB0aGlzLnN0YXJ0KCk7XG4gIH1cblxuICBfYWR2YW5jZVF1ZXVlKCkge1xuICAgIGxldCBpbmRleCA9IDA7XG4gICAgd2hpbGUodGhpcy5pc1N5bmNocm9uaXphdGlvbkF2YWlsYWJsZSAmJiB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5sZW5ndGggJiYgXG4gICAgICAgIGluZGV4IDwgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUubGVuZ3RoKSB7XG4gICAgICBjb25zdCBxdWV1ZUl0ZW0gPSB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZVtpbmRleF07XG4gICAgICBxdWV1ZUl0ZW0ucmVzb2x2ZSgnc3luY2hyb25pemUnKTtcbiAgICAgIHRoaXMudXBkYXRlU3luY2hyb25pemF0aW9uSWQocXVldWVJdGVtLnN5bmNocm9uaXphdGlvbklkKTtcbiAgICAgIGluZGV4Kys7XG4gICAgfVxuICB9XG5cbiAgX3JlbW92ZUZyb21RdWV1ZShzeW5jaHJvbml6YXRpb25JZCwgcmVzdWx0KSB7XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUuZm9yRWFjaCgoc3luY0l0ZW0sIGkpID0+IHtcbiAgICAgIGlmKHN5bmNJdGVtLnN5bmNocm9uaXphdGlvbklkID09PSBzeW5jaHJvbml6YXRpb25JZCkge1xuICAgICAgICBzeW5jSXRlbS5yZXNvbHZlKHJlc3VsdCk7XG4gICAgICB9XG4gICAgfSk7XG4gICAgdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWUgPSB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5maWx0ZXIoaXRlbSA9PiBcbiAgICAgIGl0ZW0uc3luY2hyb25pemF0aW9uSWQgIT09IHN5bmNocm9uaXphdGlvbklkKTtcbiAgfVxuXG4gIGFzeW5jIF9wcm9jZXNzUXVldWVKb2IoKSB7XG4gICAgdHJ5IHtcbiAgICAgIHdoaWxlICh0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5sZW5ndGgpIHtcbiAgICAgICAgY29uc3QgcXVldWVJdGVtID0gdGhpcy5fc3luY2hyb25pemF0aW9uUXVldWVbMF07XG4gICAgICAgIGF3YWl0IHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlWzBdLnByb21pc2U7XG4gICAgICAgIGlmKHRoaXMuX3N5bmNocm9uaXphdGlvblF1ZXVlLmxlbmd0aCAmJiB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZVswXS5zeW5jaHJvbml6YXRpb25JZCA9PT0gXG4gICAgICAgICAgICBxdWV1ZUl0ZW0uc3luY2hyb25pemF0aW9uSWQpIHtcbiAgICAgICAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5zaGlmdCgpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICB0aGlzLl9sb2dnZXIuZXJyb3IoJ0Vycm9yIHByb2Nlc3NpbmcgcXVldWUgam9iJywgZXJyKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogU2NoZWR1bGVzIHRvIHNlbmQgYSBzeW5jaHJvbml6YXRpb24gcmVxdWVzdCBmb3IgYWNjb3VudFxuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHBhcmFtIHtPYmplY3R9IHJlcXVlc3QgcmVxdWVzdCB0byBzZW5kXG4gICAqIEBwYXJhbSB7T2JqZWN0fSBoYXNoZXMgdGVybWluYWwgc3RhdGUgaGFzaGVzXG4gICAqL1xuICBhc3luYyBzY2hlZHVsZVN5bmNocm9uaXplKGFjY291bnRJZCwgcmVxdWVzdCwgaGFzaGVzKSB7XG4gICAgY29uc3Qgc3luY2hyb25pemF0aW9uSWQgPSByZXF1ZXN0LnJlcXVlc3RJZDtcbiAgICBmb3IgKGxldCBrZXkgb2YgT2JqZWN0LmtleXModGhpcy5fYWNjb3VudHNCeVN5bmNocm9uaXphdGlvbklkcykpIHtcbiAgICAgIGlmKHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5hY2NvdW50SWQgPT09IGFjY291bnRJZCAmJlxuICAgICAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW2tleV0uaW5zdGFuY2VJbmRleCA9PT0gcmVxdWVzdC5pbnN0YW5jZUluZGV4ICYmXG4gICAgICAgIHRoaXMuX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHNba2V5XS5ob3N0ID09PSByZXF1ZXN0Lmhvc3QpIHtcbiAgICAgICAgdGhpcy5yZW1vdmVTeW5jaHJvbml6YXRpb25JZChrZXkpO1xuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLl9hY2NvdW50c0J5U3luY2hyb25pemF0aW9uSWRzW3N5bmNocm9uaXphdGlvbklkXSA9IHthY2NvdW50SWQsIGluc3RhbmNlSW5kZXg6IHJlcXVlc3QuaW5zdGFuY2VJbmRleCxcbiAgICAgIGhvc3Q6IHJlcXVlc3QuaG9zdH07XG4gICAgaWYoIXRoaXMuaXNTeW5jaHJvbml6YXRpb25BdmFpbGFibGUpIHtcbiAgICAgIGxldCByZXNvbHZlO1xuICAgICAgbGV0IHJlcXVlc3RSZXNvbHZlID0gbmV3IFByb21pc2UoKHJlcykgPT4ge1xuICAgICAgICByZXNvbHZlID0gcmVzO1xuICAgICAgfSk7XG4gICAgICB0aGlzLl9zeW5jaHJvbml6YXRpb25RdWV1ZS5wdXNoKHtcbiAgICAgICAgc3luY2hyb25pemF0aW9uSWQ6IHN5bmNocm9uaXphdGlvbklkLFxuICAgICAgICBwcm9taXNlOiByZXF1ZXN0UmVzb2x2ZSxcbiAgICAgICAgcmVzb2x2ZSxcbiAgICAgICAgcXVldWVUaW1lOiBEYXRlLm5vdygpXG4gICAgICB9KTtcbiAgICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IHJlcXVlc3RSZXNvbHZlO1xuICAgICAgaWYocmVzdWx0ID09PSAnY2FuY2VsJykge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9IGVsc2UgaWYocmVzdWx0ID09PSAndGltZW91dCcpIHtcbiAgICAgICAgdGhyb3cgbmV3IFRpbWVvdXRFcnJvcihgQWNjb3VudCAke2FjY291bnRJZH0gc3luY2hyb25pemF0aW9uICR7c3luY2hyb25pemF0aW9uSWR9YCArXG4gICAgICAgICcgdGltZWQgb3V0IGluIHN5bmNocm9uaXphdGlvbiBxdWV1ZScpO1xuICAgICAgfVxuICAgIH1cbiAgICB0aGlzLnVwZGF0ZVN5bmNocm9uaXphdGlvbklkKHN5bmNocm9uaXphdGlvbklkKTtcbiAgICByZXF1ZXN0LnNwZWNpZmljYXRpb25zSGFzaGVzID0gaGFzaGVzLnNwZWNpZmljYXRpb25zSGFzaGVzO1xuICAgIHJlcXVlc3QucG9zaXRpb25zSGFzaGVzID0gaGFzaGVzLnBvc2l0aW9uc0hhc2hlcztcbiAgICByZXF1ZXN0Lm9yZGVyc0hhc2hlcyA9IGhhc2hlcy5vcmRlcnNIYXNoZXM7XG4gICAgYXdhaXQgdGhpcy5fY2xpZW50LnJwY1JlcXVlc3QoYWNjb3VudElkLCByZXF1ZXN0KTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuXG59XG5cbi8qKlxuICogT3B0aW9ucyBmb3Igc3luY2hyb25pemF0aW9uIHRocm90dGxlclxuICovXG5leHBvcnQgdHlwZSBTeW5jaHJvbml6YXRpb25UaHJvdHRsZXJPcHRzID0ge1xuXG4gIC8qKlxuICAgKiBhbW91bnQgb2YgbWF4aW11bSBhbGxvd2VkIGNvbmN1cnJlbnQgc3luY2hyb25pemF0aW9uc1xuICAgKi9cbiAgbWF4Q29uY3VycmVudFN5bmNocm9uaXphdGlvbnM/OiBudW1iZXIsXG5cbiAgLyoqXG4gICAqIGFsbG93ZWQgdGltZSBmb3IgYSBzeW5jaHJvbml6YXRpb24gaW4gcXVldWVcbiAgICovXG4gIHF1ZXVlVGltZW91dEluU2Vjb25kcz86IG51bWJlcixcblxuICAvKipcbiAgICogdGltZSBhZnRlciB3aGljaCBhIHN5bmNocm9uaXphdGlvbiBzbG90XG4gICAqIGlzIGZyZWVkIHRvIGJlIHVzZWQgYnkgYW5vdGhlciBzeW5jaHJvbml6YXRpb25cbiAgICovXG4gIHN5bmNocm9uaXphdGlvblRpbWVvdXRJblNlY29uZHM/OiBudW1iZXJcbn1cbiJdLCJuYW1lcyI6WyJTeW5jaHJvbml6YXRpb25UaHJvdHRsZXIiLCJzdGFydCIsIl9yZW1vdmVPbGRTeW5jSWRzSW50ZXJ2YWwiLCJzZXRJbnRlcnZhbCIsIl9yZW1vdmVPbGRTeW5jSWRzSm9iIiwiX3Byb2Nlc3NRdWV1ZUludGVydmFsIiwiX3Byb2Nlc3NRdWV1ZUpvYiIsInN0b3AiLCJjbGVhckludGVydmFsIiwibm93IiwiRGF0ZSIsImtleSIsIk9iamVjdCIsImtleXMiLCJfc3luY2hyb25pemF0aW9uSWRzIiwiX3N5bmNocm9uaXphdGlvblRpbWVvdXRJblNlY29uZHMiLCJfc3luY2hyb25pemF0aW9uUXVldWUiLCJsZW5ndGgiLCJxdWV1ZVRpbWUiLCJfcXVldWVUaW1lb3V0SW5TZWNvbmRzIiwiX3JlbW92ZUZyb21RdWV1ZSIsInN5bmNocm9uaXphdGlvbklkIiwiX2FkdmFuY2VRdWV1ZSIsInVwZGF0ZVN5bmNocm9uaXphdGlvbklkIiwiX2FjY291bnRzQnlTeW5jaHJvbml6YXRpb25JZHMiLCJzeW5jaHJvbml6aW5nQWNjb3VudHMiLCJmb3JFYWNoIiwiYWNjb3VudERhdGEiLCJpbmNsdWRlcyIsImFjY291bnRJZCIsInB1c2giLCJhY3RpdmVTeW5jaHJvbml6YXRpb25JZHMiLCJtYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucyIsImNhbGN1bGF0ZWRNYXgiLCJNYXRoIiwibWF4IiwiY2VpbCIsIl9jbGllbnQiLCJzdWJzY3JpYmVkQWNjb3VudElkcyIsIl9pbnN0YW5jZU51bWJlciIsIl9zb2NrZXRJbnN0YW5jZUluZGV4IiwiX3JlZ2lvbiIsIm1pbiIsIl9tYXhDb25jdXJyZW50U3luY2hyb25pemF0aW9ucyIsImlzU3luY2hyb25pemF0aW9uQXZhaWxhYmxlIiwic29ja2V0SW5zdGFuY2VzIiwicmVkdWNlIiwiYWNjIiwic29ja2V0SW5zdGFuY2UiLCJzeW5jaHJvbml6YXRpb25UaHJvdHRsZXIiLCJyZW1vdmVJZEJ5UGFyYW1ldGVycyIsImluc3RhbmNlSW5kZXgiLCJob3N0IiwicmVtb3ZlU3luY2hyb25pemF0aW9uSWQiLCJvbkRpc2Nvbm5lY3QiLCJzeW5jaHJvbml6YXRpb24iLCJyZXNvbHZlIiwiaW5kZXgiLCJxdWV1ZUl0ZW0iLCJyZXN1bHQiLCJzeW5jSXRlbSIsImkiLCJmaWx0ZXIiLCJpdGVtIiwicHJvbWlzZSIsInNoaWZ0IiwiZXJyIiwiX2xvZ2dlciIsImVycm9yIiwic2NoZWR1bGVTeW5jaHJvbml6ZSIsInJlcXVlc3QiLCJoYXNoZXMiLCJyZXF1ZXN0SWQiLCJyZXF1ZXN0UmVzb2x2ZSIsIlByb21pc2UiLCJyZXMiLCJUaW1lb3V0RXJyb3IiLCJzcGVjaWZpY2F0aW9uc0hhc2hlcyIsInBvc2l0aW9uc0hhc2hlcyIsIm9yZGVyc0hhc2hlcyIsInJwY1JlcXVlc3QiLCJjb25zdHJ1Y3RvciIsImNsaWVudCIsInNvY2tldEluc3RhbmNlSW5kZXgiLCJpbnN0YW5jZU51bWJlciIsInJlZ2lvbiIsIm9wdHMiLCJ2YWxpZGF0b3IiLCJPcHRpb25zVmFsaWRhdG9yIiwidmFsaWRhdGVOb25aZXJvIiwicXVldWVUaW1lb3V0SW5TZWNvbmRzIiwic3luY2hyb25pemF0aW9uVGltZW91dEluU2Vjb25kcyIsIkxvZ2dlck1hbmFnZXIiLCJnZXRMb2dnZXIiXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7O2VBb0JxQkE7OztxRUFsQkk7eUVBQ0k7K0RBQ087Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFnQnJCLElBQUEsQUFBTUEsMkJBQU4sTUFBTUE7SUE2Q25COztHQUVDLEdBQ0RDLFFBQVE7UUFDTixJQUFH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