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)

360 lines (359 loc) 48.5 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); }); }; } 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; } import LoggerManager from '../../logger'; let SubscriptionManager = class SubscriptionManager { /** * Returns whether an account is currently subscribing * @param {String} accountId account id * @param {Number} instanceNumber instance index number * @returns {Boolean} whether an account is currently subscribing */ isAccountSubscribing(accountId, instanceNumber) { if (instanceNumber !== undefined) { return Object.keys(this._subscriptions).includes(accountId + ':' + instanceNumber); } else { for (let key of Object.keys(this._subscriptions)){ if (key.startsWith(accountId)) { return true; } } return false; } } /** * Returns whether an instance is in disconnected retry mode * @param {String} accountId account id * @param {Number} instanceNumber instance index number * @returns {Boolean} whether an account is currently subscribing */ isDisconnectedRetryMode(accountId, instanceNumber) { let instanceId = accountId + ':' + (instanceNumber || 0); return this._subscriptions[instanceId] ? this._subscriptions[instanceId].isDisconnectedRetryMode : false; } /** * Returns whether an account subscription is active * @param {String} accountId account id * @returns {Boolean} instance actual subscribe state */ isSubscriptionActive(accountId) { return !!this._subscriptionState[accountId]; } /** * Subscribes to the Metatrader terminal events * @param {String} accountId id of the MetaTrader account to subscribe to * @param {Number} instanceNumber instance index number * @returns {Promise} promise which resolves when subscription started */ subscribe(accountId, instanceNumber) { this._subscriptionState[accountId] = true; return this._websocketClient.rpcRequest(accountId, { type: 'subscribe', instanceIndex: instanceNumber }); } /** * Schedules to send subscribe requests to an account until cancelled * @param {String} accountId id of the MetaTrader account * @param {Number} instanceNumber instance index number * @param {Boolean} isDisconnectedRetryMode whether to start subscription in disconnected retry * mode. Subscription task in disconnected mode will be immediately replaced when the status packet is received */ scheduleSubscribe(accountId, instanceNumber, isDisconnectedRetryMode = false) { var _this = this; return _async_to_generator(function*() { const client = _this._websocketClient; let instanceId = accountId + ':' + (instanceNumber || 0); if (!_this._subscriptions[instanceId]) { _this._subscriptions[instanceId] = { shouldRetry: true, task: null, waitTask: null, future: null, isDisconnectedRetryMode }; let subscribeRetryIntervalInSeconds = 3; while(_this._subscriptions[instanceId].shouldRetry){ let resolveSubscribe; _this._subscriptions[instanceId].task = { promise: new Promise((res)=>{ resolveSubscribe = res; }) }; _this._subscriptions[instanceId].task.resolve = resolveSubscribe; // eslint-disable-next-line no-inner-declarations, complexity let subscribeTask = function() { var _ref = _async_to_generator(function*() { try { _this._logger.debug(`${accountId}:${instanceNumber}: running subscribe task`); yield _this.subscribe(accountId, instanceNumber); } catch (err) { if (err.name === 'TooManyRequestsError') { const socketInstanceIndex = client.socketInstancesByAccounts[instanceNumber][accountId]; if (err.metadata.type === 'LIMIT_ACCOUNT_SUBSCRIPTIONS_PER_USER') { _this._logSubscriptionError(accountId, `${instanceId}: Failed to subscribe`, err); } if ([ 'LIMIT_ACCOUNT_SUBSCRIPTIONS_PER_USER', 'LIMIT_ACCOUNT_SUBSCRIPTIONS_PER_SERVER', 'LIMIT_ACCOUNT_SUBSCRIPTIONS_PER_USER_PER_SERVER' ].includes(err.metadata.type)) { delete client.socketInstancesByAccounts[instanceNumber][accountId]; client.lockSocketInstance(instanceNumber, socketInstanceIndex, _this._websocketClient.getAccountRegion(accountId), err.metadata); } else { const retryTime = new Date(err.metadata.recommendedRetryTime).getTime(); if (Date.now() + subscribeRetryIntervalInSeconds * 1000 < retryTime) { yield new Promise((res)=>setTimeout(res, retryTime - Date.now() - subscribeRetryIntervalInSeconds * 1000)); } } } else { _this._logSubscriptionError(accountId, `${instanceId}: Failed to subscribe`, err); if (err.name === 'NotFoundError') { _this.refreshAccount(accountId); } if (err.name === 'TimeoutError') { const mainAccountId = _this._websocketClient.accountsByReplicaId[accountId]; if (mainAccountId) { const region = _this._websocketClient.getAccountRegion(accountId); const connectedInstances = _this._latencyService.getActiveAccountInstances(mainAccountId); // eslint-disable-next-line max-depth if (!connectedInstances.some((instance)=>instance.startsWith(`${mainAccountId}:${region}`))) { _this._timeoutErrorCounter[accountId] = _this._timeoutErrorCounter[accountId] || 0; _this._timeoutErrorCounter[accountId]++; // eslint-disable-next-line max-depth if (_this._timeoutErrorCounter[accountId] > 4) { _this._timeoutErrorCounter[accountId] = 0; _this.refreshAccount(accountId); } } } } } } resolveSubscribe(); }); return function subscribeTask() { return _ref.apply(this, arguments); }; }(); subscribeTask(); yield _this._subscriptions[instanceId].task.promise; if (!_this._subscriptions[instanceId].shouldRetry) { break; } const retryInterval = subscribeRetryIntervalInSeconds; subscribeRetryIntervalInSeconds = Math.min(subscribeRetryIntervalInSeconds * 2, 300); let resolve; let subscribePromise = new Promise((res)=>{ resolve = res; }); _this._subscriptions[instanceId].waitTask = setTimeout(()=>{ resolve(true); }, retryInterval * 1000); _this._subscriptions[instanceId].future = { resolve, promise: subscribePromise }; const result = yield _this._subscriptions[instanceId].future.promise; _this._subscriptions[instanceId].future = null; if (!result) { break; } } delete _this._subscriptions[instanceId]; } })(); } /** * Unsubscribe from account * @param {String} accountId id of the MetaTrader account to unsubscribe * @param {Number} instanceNumber instance index number * @returns {Promise} promise which resolves when socket unsubscribed */ unsubscribe(accountId, instanceNumber) { var _this = this; return _async_to_generator(function*() { _this.cancelAccount(accountId); delete _this._subscriptionState[accountId]; return _this._websocketClient.rpcRequest(accountId, { type: 'unsubscribe', instanceIndex: instanceNumber }); })(); } /** * Cancels active subscription tasks for an instance id * @param {String} instanceId instance id to cancel subscription task for */ cancelSubscribe(instanceId) { if (this._subscriptions[instanceId]) { const subscription = this._subscriptions[instanceId]; if (subscription.future) { subscription.future.resolve(false); clearTimeout(subscription.waitTask); } if (subscription.task) { subscription.task.resolve(false); } subscription.shouldRetry = false; } } /** * Cancels active subscription tasks for an account * @param {String} accountId account id to cancel subscription tasks for */ cancelAccount(accountId) { for (let instanceId of Object.keys(this._subscriptions).filter((key)=>key.startsWith(accountId))){ this.cancelSubscribe(instanceId); } Object.keys(this._awaitingResubscribe).forEach((instanceNumber)=>delete this._awaitingResubscribe[instanceNumber][accountId]); delete this._timeoutErrorCounter[accountId]; } /** * Invoked on account timeout. * @param {String} accountId id of the MetaTrader account * @param {Number} instanceNumber instance index number */ onTimeout(accountId, instanceNumber) { const region = this._websocketClient.getAccountRegion(accountId); if (this._websocketClient.socketInstancesByAccounts[instanceNumber][accountId] !== undefined && this._websocketClient.connected(instanceNumber, this._websocketClient.socketInstancesByAccounts[instanceNumber][accountId], region)) { this._logger.debug(`${accountId}:${instanceNumber}: scheduling subscribe because of account timeout`); this.scheduleSubscribe(accountId, instanceNumber, true); } } /** * Invoked when connection to MetaTrader terminal terminated * @param {String} accountId id of the MetaTrader account * @param {Number} instanceNumber instance index number */ onDisconnected(accountId, instanceNumber) { var _this = this; return _async_to_generator(function*() { yield new Promise((res)=>setTimeout(res, Math.max(Math.random() * 5, 1) * 1000)); if (_this._websocketClient.socketInstancesByAccounts[instanceNumber][accountId] !== undefined) { _this._logger.debug(`${accountId}:${instanceNumber}: scheduling subscribe because account disconnected`); _this.scheduleSubscribe(accountId, instanceNumber, true); } })(); } /** * Invoked when connection to MetaApi websocket API restored after a disconnect. * @param {Number} instanceNumber instance index number * @param {Number} socketInstanceIndex socket instance index * @param {String[]} reconnectAccountIds account ids to reconnect */ onReconnected(instanceNumber, socketInstanceIndex, reconnectAccountIds) { if (!this._awaitingResubscribe[instanceNumber]) { this._awaitingResubscribe[instanceNumber] = {}; } const socketInstancesByAccounts = this._websocketClient.socketInstancesByAccounts[instanceNumber]; for (let instanceId of Object.keys(this._subscriptions)){ const accountId = instanceId.split(':')[0]; if (socketInstancesByAccounts[accountId] === socketInstanceIndex) { this.cancelSubscribe(instanceId); } } var _this = this; reconnectAccountIds.forEach(function() { var _ref = _async_to_generator(function*(accountId) { if (!_this._awaitingResubscribe[instanceNumber][accountId]) { _this._awaitingResubscribe[instanceNumber][accountId] = true; while(_this.isAccountSubscribing(accountId, instanceNumber)){ yield new Promise((res)=>setTimeout(res, 1000)); } yield new Promise((res)=>setTimeout(res, Math.random() * 5000)); if (_this._awaitingResubscribe[instanceNumber][accountId]) { delete _this._awaitingResubscribe[instanceNumber][accountId]; _this._logger.debug(`${accountId}:${instanceNumber}: scheduling subscribe because account reconnected`); _this.scheduleSubscribe(accountId, instanceNumber); } } }); return function(accountId) { return _ref.apply(this, arguments); }; }()); } /** * Schedules a task to refresh the account data * @param {string} accountId account id */ refreshAccount(accountId) { const mainAccountId = this._websocketClient.accountsByReplicaId[accountId]; if (mainAccountId) { const registry = this._metaApi._connectionRegistry; const rpcConnection = registry.rpcConnections[mainAccountId]; const region = this._websocketClient.getAccountRegion(accountId); if (region) { if (rpcConnection) { rpcConnection.scheduleRefresh(region); } const streamingConnection = registry.streamingConnections[mainAccountId]; if (streamingConnection) { streamingConnection.scheduleRefresh(region); } } } } _logSubscriptionError(accountId, message, error) { const primaryAccountId = this._websocketClient.accountsByReplicaId[accountId]; const method = this._latencyService.getSynchronizedAccountInstances(primaryAccountId).length ? 'debug' : 'error'; this._logger[method](message, error); } /** * Constructs the subscription manager * @param {MetaApiWebsocketClient} websocketClient websocket client to use for sending requests * @param {MetaApi} metaApi metaApi instance */ constructor(websocketClient, metaApi){ _define_property(this, "_websocketClient", void 0); _define_property(this, "_latencyService", void 0); _define_property(this, "_metaApi", void 0); _define_property(this, "_subscriptions", void 0); _define_property(this, "_awaitingResubscribe", void 0); _define_property(this, "_subscriptionState", void 0); _define_property(this, "_logger", void 0); _define_property(this, "_timeoutErrorCounter", void 0); _define_property(this, "_recentlyDeletedAccounts", void 0); this._websocketClient = websocketClient; this._latencyService = websocketClient.latencyService; this._metaApi = metaApi; this._subscriptions = {}; this._awaitingResubscribe = {}; this._subscriptionState = {}; this._logger = LoggerManager.getLogger('SubscriptionManager'); this._timeoutErrorCounter = {}; this._recentlyDeletedAccounts = {}; } }; /** * Subscription manager to handle account subscription logic */ export { SubscriptionManager as default }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBMb2dnZXJNYW5hZ2VyIGZyb20gJy4uLy4uL2xvZ2dlcic7XG5pbXBvcnQgTWV0YUFwaVdlYnNvY2tldENsaWVudCBmcm9tICcuL21ldGFBcGlXZWJzb2NrZXQuY2xpZW50JztcblxuLyoqXG4gKiBTdWJzY3JpcHRpb24gbWFuYWdlciB0byBoYW5kbGUgYWNjb3VudCBzdWJzY3JpcHRpb24gbG9naWNcbiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgU3Vic2NyaXB0aW9uTWFuYWdlciB7XG4gIFxuICBwcml2YXRlIF93ZWJzb2NrZXRDbGllbnQ6IE1ldGFBcGlXZWJzb2NrZXRDbGllbnQ7XG4gIHByaXZhdGUgX2xhdGVuY3lTZXJ2aWNlOiBhbnk7XG4gIHByaXZhdGUgX21ldGFBcGk6IGFueTtcbiAgcHJpdmF0ZSBfc3Vic2NyaXB0aW9uczoge307XG4gIHByaXZhdGUgX2F3YWl0aW5nUmVzdWJzY3JpYmU6IHt9O1xuICBwcml2YXRlIF9zdWJzY3JpcHRpb25TdGF0ZToge307XG4gIHByaXZhdGUgX2xvZ2dlcjogYW55O1xuICBwcml2YXRlIF90aW1lb3V0RXJyb3JDb3VudGVyOiB7fTtcbiAgcHJpdmF0ZSBfcmVjZW50bHlEZWxldGVkQWNjb3VudHM6IHt9O1xuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3RzIHRoZSBzdWJzY3JpcHRpb24gbWFuYWdlclxuICAgKiBAcGFyYW0ge01ldGFBcGlXZWJzb2NrZXRDbGllbnR9IHdlYnNvY2tldENsaWVudCB3ZWJzb2NrZXQgY2xpZW50IHRvIHVzZSBmb3Igc2VuZGluZyByZXF1ZXN0c1xuICAgKiBAcGFyYW0ge01ldGFBcGl9IG1ldGFBcGkgbWV0YUFwaSBpbnN0YW5jZVxuICAgKi9cbiAgY29uc3RydWN0b3Iod2Vic29ja2V0Q2xpZW50LCBtZXRhQXBpKSB7XG4gICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50ID0gd2Vic29ja2V0Q2xpZW50O1xuICAgIHRoaXMuX2xhdGVuY3lTZXJ2aWNlID0gd2Vic29ja2V0Q2xpZW50LmxhdGVuY3lTZXJ2aWNlO1xuICAgIHRoaXMuX21ldGFBcGkgPSBtZXRhQXBpO1xuICAgIHRoaXMuX3N1YnNjcmlwdGlvbnMgPSB7fTtcbiAgICB0aGlzLl9hd2FpdGluZ1Jlc3Vic2NyaWJlID0ge307XG4gICAgdGhpcy5fc3Vic2NyaXB0aW9uU3RhdGUgPSB7fTtcbiAgICB0aGlzLl9sb2dnZXIgPSBMb2dnZXJNYW5hZ2VyLmdldExvZ2dlcignU3Vic2NyaXB0aW9uTWFuYWdlcicpO1xuICAgIHRoaXMuX3RpbWVvdXRFcnJvckNvdW50ZXIgPSB7fTtcbiAgICB0aGlzLl9yZWNlbnRseURlbGV0ZWRBY2NvdW50cyA9IHt9O1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgd2hldGhlciBhbiBhY2NvdW50IGlzIGN1cnJlbnRseSBzdWJzY3JpYmluZ1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHBhcmFtIHtOdW1iZXJ9IGluc3RhbmNlTnVtYmVyIGluc3RhbmNlIGluZGV4IG51bWJlclxuICAgKiBAcmV0dXJucyB7Qm9vbGVhbn0gd2hldGhlciBhbiBhY2NvdW50IGlzIGN1cnJlbnRseSBzdWJzY3JpYmluZ1xuICAgKi9cbiAgaXNBY2NvdW50U3Vic2NyaWJpbmcoYWNjb3VudElkLCBpbnN0YW5jZU51bWJlcikge1xuICAgIGlmIChpbnN0YW5jZU51bWJlciAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICByZXR1cm4gT2JqZWN0LmtleXModGhpcy5fc3Vic2NyaXB0aW9ucykuaW5jbHVkZXMoYWNjb3VudElkICsgJzonICsgaW5zdGFuY2VOdW1iZXIpO1xuICAgIH0gZWxzZSB7XG4gICAgICBmb3IgKGxldCBrZXkgb2YgT2JqZWN0LmtleXModGhpcy5fc3Vic2NyaXB0aW9ucykpIHtcbiAgICAgICAgaWYgKGtleS5zdGFydHNXaXRoKGFjY291bnRJZCkpIHtcbiAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHdoZXRoZXIgYW4gaW5zdGFuY2UgaXMgaW4gZGlzY29ubmVjdGVkIHJldHJ5IG1vZGVcbiAgICogQHBhcmFtIHtTdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBpbnN0YW5jZU51bWJlciBpbnN0YW5jZSBpbmRleCBudW1iZXJcbiAgICogQHJldHVybnMge0Jvb2xlYW59IHdoZXRoZXIgYW4gYWNjb3VudCBpcyBjdXJyZW50bHkgc3Vic2NyaWJpbmdcbiAgICovXG4gIGlzRGlzY29ubmVjdGVkUmV0cnlNb2RlKGFjY291bnRJZCwgaW5zdGFuY2VOdW1iZXIpIHtcbiAgICBsZXQgaW5zdGFuY2VJZCA9IGFjY291bnRJZCArICc6JyArIChpbnN0YW5jZU51bWJlciB8fCAwKTtcbiAgICByZXR1cm4gdGhpcy5fc3Vic2NyaXB0aW9uc1tpbnN0YW5jZUlkXSA/IHRoaXMuX3N1YnNjcmlwdGlvbnNbaW5zdGFuY2VJZF0uaXNEaXNjb25uZWN0ZWRSZXRyeU1vZGUgOiBmYWxzZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHdoZXRoZXIgYW4gYWNjb3VudCBzdWJzY3JpcHRpb24gaXMgYWN0aXZlXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBhY2NvdW50SWQgYWNjb3VudCBpZFxuICAgKiBAcmV0dXJucyB7Qm9vbGVhbn0gaW5zdGFuY2UgYWN0dWFsIHN1YnNjcmliZSBzdGF0ZVxuICAgKi9cbiAgaXNTdWJzY3JpcHRpb25BY3RpdmUoYWNjb3VudElkKSB7XG4gICAgcmV0dXJuICEhdGhpcy5fc3Vic2NyaXB0aW9uU3RhdGVbYWNjb3VudElkXTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdWJzY3JpYmVzIHRvIHRoZSBNZXRhdHJhZGVyIHRlcm1pbmFsIGV2ZW50c1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGlkIG9mIHRoZSBNZXRhVHJhZGVyIGFjY291bnQgdG8gc3Vic2NyaWJlIHRvXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBpbnN0YW5jZU51bWJlciBpbnN0YW5jZSBpbmRleCBudW1iZXJcbiAgICogQHJldHVybnMge1Byb21pc2V9IHByb21pc2Ugd2hpY2ggcmVzb2x2ZXMgd2hlbiBzdWJzY3JpcHRpb24gc3RhcnRlZFxuICAgKi9cbiAgc3Vic2NyaWJlKGFjY291bnRJZCwgaW5zdGFuY2VOdW1iZXIpIHtcbiAgICB0aGlzLl9zdWJzY3JpcHRpb25TdGF0ZVthY2NvdW50SWRdID0gdHJ1ZTtcbiAgICByZXR1cm4gdGhpcy5fd2Vic29ja2V0Q2xpZW50LnJwY1JlcXVlc3QoYWNjb3VudElkLCB7dHlwZTogJ3N1YnNjcmliZScsIGluc3RhbmNlSW5kZXg6IGluc3RhbmNlTnVtYmVyfSk7XG4gIH1cblxuICAvKipcbiAgICogU2NoZWR1bGVzIHRvIHNlbmQgc3Vic2NyaWJlIHJlcXVlc3RzIHRvIGFuIGFjY291bnQgdW50aWwgY2FuY2VsbGVkXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBhY2NvdW50SWQgaWQgb2YgdGhlIE1ldGFUcmFkZXIgYWNjb3VudFxuICAgKiBAcGFyYW0ge051bWJlcn0gaW5zdGFuY2VOdW1iZXIgaW5zdGFuY2UgaW5kZXggbnVtYmVyXG4gICAqIEBwYXJhbSB7Qm9vbGVhbn0gaXNEaXNjb25uZWN0ZWRSZXRyeU1vZGUgd2hldGhlciB0byBzdGFydCBzdWJzY3JpcHRpb24gaW4gZGlzY29ubmVjdGVkIHJldHJ5XG4gICAqIG1vZGUuIFN1YnNjcmlwdGlvbiB0YXNrIGluIGRpc2Nvbm5lY3RlZCBtb2RlIHdpbGwgYmUgaW1tZWRpYXRlbHkgcmVwbGFjZWQgd2hlbiB0aGUgc3RhdHVzIHBhY2tldCBpcyByZWNlaXZlZFxuICAgKi9cbiAgYXN5bmMgc2NoZWR1bGVTdWJzY3JpYmUoYWNjb3VudElkLCBpbnN0YW5jZU51bWJlciwgaXNEaXNjb25uZWN0ZWRSZXRyeU1vZGUgPSBmYWxzZSkge1xuICAgIGNvbnN0IGNsaWVudCA9IHRoaXMuX3dlYnNvY2tldENsaWVudDtcbiAgICBsZXQgaW5zdGFuY2VJZCA9IGFjY291bnRJZCArICc6JyArIChpbnN0YW5jZU51bWJlciB8fCAwKTtcbiAgICBpZiAoIXRoaXMuX3N1YnNjcmlwdGlvbnNbaW5zdGFuY2VJZF0pIHtcbiAgICAgIHRoaXMuX3N1YnNjcmlwdGlvbnNbaW5zdGFuY2VJZF0gPSB7XG4gICAgICAgIHNob3VsZFJldHJ5OiB0cnVlLFxuICAgICAgICB0YXNrOiBudWxsLFxuICAgICAgICB3YWl0VGFzazogbnVsbCxcbiAgICAgICAgZnV0dXJlOiBudWxsLFxuICAgICAgICBpc0Rpc2Nvbm5lY3RlZFJldHJ5TW9kZVxuICAgICAgfTtcbiAgICAgIGxldCBzdWJzY3JpYmVSZXRyeUludGVydmFsSW5TZWNvbmRzID0gMztcbiAgICAgIHdoaWxlICh0aGlzLl9zdWJzY3JpcHRpb25zW2luc3RhbmNlSWRdLnNob3VsZFJldHJ5KSB7XG4gICAgICAgIGxldCByZXNvbHZlU3Vic2NyaWJlO1xuICAgICAgICB0aGlzLl9zdWJzY3JpcHRpb25zW2luc3RhbmNlSWRdLnRhc2sgPSB7cHJvbWlzZTogbmV3IFByb21pc2UoKHJlcykgPT4ge1xuICAgICAgICAgIHJlc29sdmVTdWJzY3JpYmUgPSByZXM7XG4gICAgICAgIH0pfTtcbiAgICAgICAgdGhpcy5fc3Vic2NyaXB0aW9uc1tpbnN0YW5jZUlkXS50YXNrLnJlc29sdmUgPSByZXNvbHZlU3Vic2NyaWJlO1xuICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8taW5uZXItZGVjbGFyYXRpb25zLCBjb21wbGV4aXR5XG4gICAgICAgIGxldCBzdWJzY3JpYmVUYXNrID0gYXN5bmMgKCkgPT4ge1xuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICB0aGlzLl9sb2dnZXIuZGVidWcoYCR7YWNjb3VudElkfToke2luc3RhbmNlTnVtYmVyfTogcnVubmluZyBzdWJzY3JpYmUgdGFza2ApO1xuICAgICAgICAgICAgYXdhaXQgdGhpcy5zdWJzY3JpYmUoYWNjb3VudElkLCBpbnN0YW5jZU51bWJlcik7XG4gICAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICBpZiAoZXJyLm5hbWUgPT09ICdUb29NYW55UmVxdWVzdHNFcnJvcicpIHtcbiAgICAgICAgICAgICAgY29uc3Qgc29ja2V0SW5zdGFuY2VJbmRleCA9IGNsaWVudC5zb2NrZXRJbnN0YW5jZXNCeUFjY291bnRzW2luc3RhbmNlTnVtYmVyXVthY2NvdW50SWRdO1xuICAgICAgICAgICAgICBpZiAoZXJyLm1ldGFkYXRhLnR5cGUgPT09ICdMSU1JVF9BQ0NPVU5UX1NVQlNDUklQVElPTlNfUEVSX1VTRVInKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5fbG9nU3Vic2NyaXB0aW9uRXJyb3IoYWNjb3VudElkLCBgJHtpbnN0YW5jZUlkfTogRmFpbGVkIHRvIHN1YnNjcmliZWAsIGVycik7XG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgaWYgKFsnTElNSVRfQUNDT1VOVF9TVUJTQ1JJUFRJT05TX1BFUl9VU0VSJywgJ0xJTUlUX0FDQ09VTlRfU1VCU0NSSVBUSU9OU19QRVJfU0VSVkVSJywgXG4gICAgICAgICAgICAgICAgJ0xJTUlUX0FDQ09VTlRfU1VCU0NSSVBUSU9OU19QRVJfVVNFUl9QRVJfU0VSVkVSJ10uaW5jbHVkZXMoZXJyLm1ldGFkYXRhLnR5cGUpKSB7XG4gICAgICAgICAgICAgICAgZGVsZXRlIGNsaWVudC5zb2NrZXRJbnN0YW5jZXNCeUFjY291bnRzW2luc3RhbmNlTnVtYmVyXVthY2NvdW50SWRdO1xuICAgICAgICAgICAgICAgIGNsaWVudC5sb2NrU29ja2V0SW5zdGFuY2UoaW5zdGFuY2VOdW1iZXIsIHNvY2tldEluc3RhbmNlSW5kZXgsIFxuICAgICAgICAgICAgICAgICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LmdldEFjY291bnRSZWdpb24oYWNjb3VudElkKSwgZXJyLm1ldGFkYXRhKTtcbiAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBjb25zdCByZXRyeVRpbWUgPSBuZXcgRGF0ZShlcnIubWV0YWRhdGEucmVjb21tZW5kZWRSZXRyeVRpbWUpLmdldFRpbWUoKTtcbiAgICAgICAgICAgICAgICBpZiAoRGF0ZS5ub3coKSArIHN1YnNjcmliZVJldHJ5SW50ZXJ2YWxJblNlY29uZHMgKiAxMDAwIDwgcmV0cnlUaW1lKSB7XG4gICAgICAgICAgICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZShyZXMgPT4gc2V0VGltZW91dChyZXMsIHJldHJ5VGltZSAtIERhdGUubm93KCkgLVxuICAgICAgICAgICAgICAgICAgICBzdWJzY3JpYmVSZXRyeUludGVydmFsSW5TZWNvbmRzICogMTAwMCkpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgdGhpcy5fbG9nU3Vic2NyaXB0aW9uRXJyb3IoYWNjb3VudElkLCBgJHtpbnN0YW5jZUlkfTogRmFpbGVkIHRvIHN1YnNjcmliZWAsIGVycik7XG4gICAgICAgICAgICAgIGlmIChlcnIubmFtZSA9PT0gJ05vdEZvdW5kRXJyb3InKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5yZWZyZXNoQWNjb3VudChhY2NvdW50SWQpO1xuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGlmIChlcnIubmFtZSA9PT0gJ1RpbWVvdXRFcnJvcicpIHtcbiAgICAgICAgICAgICAgICBjb25zdCBtYWluQWNjb3VudElkID0gdGhpcy5fd2Vic29ja2V0Q2xpZW50LmFjY291bnRzQnlSZXBsaWNhSWRbYWNjb3VudElkXTtcbiAgICAgICAgICAgICAgICBpZiAobWFpbkFjY291bnRJZCkge1xuICAgICAgICAgICAgICAgICAgY29uc3QgcmVnaW9uID0gdGhpcy5fd2Vic29ja2V0Q2xpZW50LmdldEFjY291bnRSZWdpb24oYWNjb3VudElkKTtcbiAgICAgICAgICAgICAgICAgIGNvbnN0IGNvbm5lY3RlZEluc3RhbmNlcyA9IHRoaXMuX2xhdGVuY3lTZXJ2aWNlLmdldEFjdGl2ZUFjY291bnRJbnN0YW5jZXMobWFpbkFjY291bnRJZCk7XG4gICAgICAgICAgICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbWF4LWRlcHRoXG4gICAgICAgICAgICAgICAgICBpZiAoIWNvbm5lY3RlZEluc3RhbmNlcy5zb21lKGluc3RhbmNlID0+IGluc3RhbmNlLnN0YXJ0c1dpdGgoYCR7bWFpbkFjY291bnRJZH06JHtyZWdpb259YCkpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuX3RpbWVvdXRFcnJvckNvdW50ZXJbYWNjb3VudElkXSA9IHRoaXMuX3RpbWVvdXRFcnJvckNvdW50ZXJbYWNjb3VudElkXSB8fCAwO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLl90aW1lb3V0RXJyb3JDb3VudGVyW2FjY291bnRJZF0rKztcbiAgICAgICAgICAgICAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG1heC1kZXB0aFxuICAgICAgICAgICAgICAgICAgICBpZiAodGhpcy5fdGltZW91dEVycm9yQ291bnRlclthY2NvdW50SWRdID4gNCkge1xuICAgICAgICAgICAgICAgICAgICAgIHRoaXMuX3RpbWVvdXRFcnJvckNvdW50ZXJbYWNjb3VudElkXSA9IDA7XG4gICAgICAgICAgICAgICAgICAgICAgdGhpcy5yZWZyZXNoQWNjb3VudChhY2NvdW50SWQpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIHJlc29sdmVTdWJzY3JpYmUoKTtcbiAgICAgICAgfTtcbiAgICAgICAgc3Vic2NyaWJlVGFzaygpO1xuICAgICAgICBhd2FpdCB0aGlzLl9zdWJzY3JpcHRpb25zW2luc3RhbmNlSWRdLnRhc2sucHJvbWlzZTtcbiAgICAgICAgaWYgKCF0aGlzLl9zdWJzY3JpcHRpb25zW2luc3RhbmNlSWRdLnNob3VsZFJldHJ5KSB7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgcmV0cnlJbnRlcnZhbCA9IHN1YnNjcmliZVJldHJ5SW50ZXJ2YWxJblNlY29uZHM7XG4gICAgICAgIHN1YnNjcmliZVJldHJ5SW50ZXJ2YWxJblNlY29uZHMgPSBNYXRoLm1pbihzdWJzY3JpYmVSZXRyeUludGVydmFsSW5TZWNvbmRzICogMiwgMzAwKTtcbiAgICAgICAgbGV0IHJlc29sdmU7XG4gICAgICAgIGxldCBzdWJzY3JpYmVQcm9taXNlID0gbmV3IFByb21pc2UoKHJlcykgPT4ge1xuICAgICAgICAgIHJlc29sdmUgPSByZXM7XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLl9zdWJzY3JpcHRpb25zW2luc3RhbmNlSWRdLndhaXRUYXNrID0gc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgcmVzb2x2ZSh0cnVlKTtcbiAgICAgICAgfSwgcmV0cnlJbnRlcnZhbCAqIDEwMDApO1xuICAgICAgICB0aGlzLl9zdWJzY3JpcHRpb25zW2luc3RhbmNlSWRdLmZ1dHVyZSA9IHtyZXNvbHZlLCBwcm9taXNlOiBzdWJzY3JpYmVQcm9taXNlfTtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdGhpcy5fc3Vic2NyaXB0aW9uc1tpbnN0YW5jZUlkXS5mdXR1cmUucHJvbWlzZTtcbiAgICAgICAgdGhpcy5fc3Vic2NyaXB0aW9uc1tpbnN0YW5jZUlkXS5mdXR1cmUgPSBudWxsO1xuICAgICAgICBpZiAoIXJlc3VsdCkge1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBkZWxldGUgdGhpcy5fc3Vic2NyaXB0aW9uc1tpbnN0YW5jZUlkXTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogVW5zdWJzY3JpYmUgZnJvbSBhY2NvdW50XG4gICAqIEBwYXJhbSB7U3RyaW5nfSBhY2NvdW50SWQgaWQgb2YgdGhlIE1ldGFUcmFkZXIgYWNjb3VudCB0byB1bnN1YnNjcmliZVxuICAgKiBAcGFyYW0ge051bWJlcn0gaW5zdGFuY2VOdW1iZXIgaW5zdGFuY2UgaW5kZXggbnVtYmVyXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBwcm9taXNlIHdoaWNoIHJlc29sdmVzIHdoZW4gc29ja2V0IHVuc3Vic2NyaWJlZFxuICAgKi9cbiAgYXN5bmMgdW5zdWJzY3JpYmUoYWNjb3VudElkLCBpbnN0YW5jZU51bWJlcikge1xuICAgIHRoaXMuY2FuY2VsQWNjb3VudChhY2NvdW50SWQpO1xuICAgIGRlbGV0ZSB0aGlzLl9zdWJzY3JpcHRpb25TdGF0ZVthY2NvdW50SWRdO1xuICAgIHJldHVybiB0aGlzLl93ZWJzb2NrZXRDbGllbnQucnBjUmVxdWVzdChhY2NvdW50SWQsIHt0eXBlOiAndW5zdWJzY3JpYmUnLCBpbnN0YW5jZUluZGV4OiBpbnN0YW5jZU51bWJlcn0pO1xuICB9XG5cbiAgLyoqXG4gICAqIENhbmNlbHMgYWN0aXZlIHN1YnNjcmlwdGlvbiB0YXNrcyBmb3IgYW4gaW5zdGFuY2UgaWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGluc3RhbmNlSWQgaW5zdGFuY2UgaWQgdG8gY2FuY2VsIHN1YnNjcmlwdGlvbiB0YXNrIGZvclxuICAgKi9cbiAgY2FuY2VsU3Vic2NyaWJlKGluc3RhbmNlSWQpIHtcbiAgICBpZiAodGhpcy5fc3Vic2NyaXB0aW9uc1tpbnN0YW5jZUlkXSkge1xuICAgICAgY29uc3Qgc3Vic2NyaXB0aW9uID0gdGhpcy5fc3Vic2NyaXB0aW9uc1tpbnN0YW5jZUlkXTtcbiAgICAgIGlmIChzdWJzY3JpcHRpb24uZnV0dXJlKSB7XG4gICAgICAgIHN1YnNjcmlwdGlvbi5mdXR1cmUucmVzb2x2ZShmYWxzZSk7XG4gICAgICAgIGNsZWFyVGltZW91dChzdWJzY3JpcHRpb24ud2FpdFRhc2spO1xuICAgICAgfVxuICAgICAgaWYgKHN1YnNjcmlwdGlvbi50YXNrKSB7XG4gICAgICAgIHN1YnNjcmlwdGlvbi50YXNrLnJlc29sdmUoZmFsc2UpO1xuICAgICAgfVxuICAgICAgc3Vic2NyaXB0aW9uLnNob3VsZFJldHJ5ID0gZmFsc2U7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIENhbmNlbHMgYWN0aXZlIHN1YnNjcmlwdGlvbiB0YXNrcyBmb3IgYW4gYWNjb3VudFxuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWQgdG8gY2FuY2VsIHN1YnNjcmlwdGlvbiB0YXNrcyBmb3JcbiAgICovXG4gIGNhbmNlbEFjY291bnQoYWNjb3VudElkKSB7XG4gICAgZm9yIChsZXQgaW5zdGFuY2VJZCBvZiBPYmplY3Qua2V5cyh0aGlzLl9zdWJzY3JpcHRpb25zKS5maWx0ZXIoa2V5ID0+IGtleS5zdGFydHNXaXRoKGFjY291bnRJZCkpKSB7XG4gICAgICB0aGlzLmNhbmNlbFN1YnNjcmliZShpbnN0YW5jZUlkKTtcbiAgICB9XG4gICAgT2JqZWN0LmtleXModGhpcy5fYXdhaXRpbmdSZXN1YnNjcmliZSkuZm9yRWFjaChpbnN0YW5jZU51bWJlciA9PiBcbiAgICAgIGRlbGV0ZSB0aGlzLl9hd2FpdGluZ1Jlc3Vic2NyaWJlW2luc3RhbmNlTnVtYmVyXVthY2NvdW50SWRdKTtcbiAgICBkZWxldGUgdGhpcy5fdGltZW91dEVycm9yQ291bnRlclthY2NvdW50SWRdO1xuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgb24gYWNjb3VudCB0aW1lb3V0LlxuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGlkIG9mIHRoZSBNZXRhVHJhZGVyIGFjY291bnRcbiAgICogQHBhcmFtIHtOdW1iZXJ9IGluc3RhbmNlTnVtYmVyIGluc3RhbmNlIGluZGV4IG51bWJlclxuICAgKi9cbiAgb25UaW1lb3V0KGFjY291bnRJZCwgaW5zdGFuY2VOdW1iZXIpIHtcbiAgICBjb25zdCByZWdpb24gPSB0aGlzLl93ZWJzb2NrZXRDbGllbnQuZ2V0QWNjb3VudFJlZ2lvbihhY2NvdW50SWQpO1xuICAgIGlmIChcbiAgICAgIHRoaXMuX3dlYnNvY2tldENsaWVudC5zb2NrZXRJbnN0YW5jZXNCeUFjY291bnRzW2luc3RhbmNlTnVtYmVyXVthY2NvdW50SWRdICE9PSB1bmRlZmluZWQgJiYgXG4gICAgICB0aGlzLl93ZWJzb2NrZXRDbGllbnQuY29ubmVjdGVkKFxuICAgICAgICBpbnN0YW5jZU51bWJlciwgdGhpcy5fd2Vic29ja2V0Q2xpZW50LnNvY2tldEluc3RhbmNlc0J5QWNjb3VudHNbaW5zdGFuY2VOdW1iZXJdW2FjY291bnRJZF0sIHJlZ2lvblxuICAgICAgKVxuICAgICkge1xuICAgICAgdGhpcy5fbG9nZ2VyLmRlYnVnKGAke2FjY291bnRJZH06JHtpbnN0YW5jZU51bWJlcn06IHNjaGVkdWxpbmcgc3Vic2NyaWJlIGJlY2F1c2Ugb2YgYWNjb3VudCB0aW1lb3V0YCk7XG4gICAgICB0aGlzLnNjaGVkdWxlU3Vic2NyaWJlKGFjY291bnRJZCwgaW5zdGFuY2VOdW1iZXIsIHRydWUpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBJbnZva2VkIHdoZW4gY29ubmVjdGlvbiB0byBNZXRhVHJhZGVyIHRlcm1pbmFsIHRlcm1pbmF0ZWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGFjY291bnRJZCBpZCBvZiB0aGUgTWV0YVRyYWRlciBhY2NvdW50XG4gICAqIEBwYXJhbSB7TnVtYmVyfSBpbnN0YW5jZU51bWJlciBpbnN0YW5jZSBpbmRleCBudW1iZXJcbiAgICovXG4gIGFzeW5jIG9uRGlzY29ubmVjdGVkKGFjY291bnRJZCwgaW5zdGFuY2VOdW1iZXIpIHtcbiAgICBhd2FpdCBuZXcgUHJvbWlzZShyZXMgPT4gc2V0VGltZW91dChyZXMsIE1hdGgubWF4KE1hdGgucmFuZG9tKCkgKiA1LCAxKSAqIDEwMDApKTtcbiAgICBpZiAodGhpcy5fd2Vic29ja2V0Q2xpZW50LnNvY2tldEluc3RhbmNlc0J5QWNjb3VudHNbaW5zdGFuY2VOdW1iZXJdW2FjY291bnRJZF0gIT09IHVuZGVmaW5lZCkge1xuICAgICAgdGhpcy5fbG9nZ2VyLmRlYnVnKGAke2FjY291bnRJZH06JHtpbnN0YW5jZU51bWJlcn06IHNjaGVkdWxpbmcgc3Vic2NyaWJlIGJlY2F1c2UgYWNjb3VudCBkaXNjb25uZWN0ZWRgKTtcbiAgICAgIHRoaXMuc2NoZWR1bGVTdWJzY3JpYmUoYWNjb3VudElkLCBpbnN0YW5jZU51bWJlciwgdHJ1ZSk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBjb25uZWN0aW9uIHRvIE1ldGFBcGkgd2Vic29ja2V0IEFQSSByZXN0b3JlZCBhZnRlciBhIGRpc2Nvbm5lY3QuXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBpbnN0YW5jZU51bWJlciBpbnN0YW5jZSBpbmRleCBudW1iZXJcbiAgICogQHBhcmFtIHtOdW1iZXJ9IHNvY2tldEluc3RhbmNlSW5kZXggc29ja2V0IGluc3RhbmNlIGluZGV4XG4gICAqIEBwYXJhbSB7U3RyaW5nW119IHJlY29ubmVjdEFjY291bnRJZHMgYWNjb3VudCBpZHMgdG8gcmVjb25uZWN0XG4gICAqL1xuICBvblJlY29ubmVjdGVkKGluc3RhbmNlTnVtYmVyLCBzb2NrZXRJbnN0YW5jZUluZGV4LCByZWNvbm5lY3RBY2NvdW50SWRzKSB7XG4gICAgaWYgKCF0aGlzLl9hd2FpdGluZ1Jlc3Vic2NyaWJlW2luc3RhbmNlTnVtYmVyXSkge1xuICAgICAgdGhpcy5fYXdhaXRpbmdSZXN1YnNjcmliZVtpbnN0YW5jZU51bWJlcl0gPSB7fTtcbiAgICB9XG4gICAgY29uc3Qgc29ja2V0SW5zdGFuY2VzQnlBY2NvdW50cyA9IHRoaXMuX3dlYnNvY2tldENsaWVudC5zb2NrZXRJbnN0YW5jZXNCeUFjY291bnRzW2luc3RhbmNlTnVtYmVyXTtcbiAgICBmb3IobGV0IGluc3RhbmNlSWQgb2YgT2JqZWN0LmtleXModGhpcy5fc3Vic2NyaXB0aW9ucykpe1xuICAgICAgY29uc3QgYWNjb3VudElkID0gaW5zdGFuY2VJZC5zcGxpdCgnOicpWzBdO1xuICAgICAgaWYgKHNvY2tldEluc3RhbmNlc0J5QWNjb3VudHNbYWNjb3VudElkXSA9PT0gc29ja2V0SW5zdGFuY2VJbmRleCkge1xuICAgICAgICB0aGlzLmNhbmNlbFN1YnNjcmliZShpbnN0YW5jZUlkKTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmVjb25uZWN0QWNjb3VudElkcy5mb3JFYWNoKGFzeW5jIGFjY291bnRJZCA9PiB7XG4gICAgICBpZiAoIXRoaXMuX2F3YWl0aW5nUmVzdWJzY3JpYmVbaW5zdGFuY2VOdW1iZXJdW2FjY291bnRJZF0pIHtcbiAgICAgICAgdGhpcy5fYXdhaXRpbmdSZXN1YnNjcmliZVtpbnN0YW5jZU51bWJlcl1bYWNjb3VudElkXSA9IHRydWU7XG4gICAgICAgIHdoaWxlICh0aGlzLmlzQWNjb3VudFN1YnNjcmliaW5nKGFjY291bnRJZCwgaW5zdGFuY2VOdW1iZXIpKSB7XG4gICAgICAgICAgYXdhaXQgbmV3IFByb21pc2UocmVzID0+IHNldFRpbWVvdXQocmVzLCAxMDAwKSk7XG4gICAgICAgIH1cbiAgICAgICAgYXdhaXQgbmV3IFByb21pc2UocmVzID0+IHNldFRpbWVvdXQocmVzLCBNYXRoLnJhbmRvbSgpICogNTAwMCkpO1xuICAgICAgICBpZiAodGhpcy5fYXdhaXRpbmdSZXN1YnNjcmliZVtpbnN0YW5jZU51bWJlcl1bYWNjb3VudElkXSkge1xuICAgICAgICAgIGRlbGV0ZSB0aGlzLl9hd2FpdGluZ1Jlc3Vic2NyaWJlW2luc3RhbmNlTnVtYmVyXVthY2NvdW50SWRdO1xuICAgICAgICAgIHRoaXMuX2xvZ2dlci5kZWJ1ZyhgJHthY2NvdW50SWR9OiR7aW5zdGFuY2VOdW1iZXJ9OiBzY2hlZHVsaW5nIHN1YnNjcmliZSBiZWNhdXNlIGFjY291bnQgcmVjb25uZWN0ZWRgKTtcbiAgICAgICAgICB0aGlzLnNjaGVkdWxlU3Vic2NyaWJlKGFjY291bnRJZCwgaW5zdGFuY2VOdW1iZXIpO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogU2NoZWR1bGVzIGEgdGFzayB0byByZWZyZXNoIHRoZSBhY2NvdW50IGRhdGFcbiAgICogQHBhcmFtIHtzdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkXG4gICAqL1xuICByZWZyZXNoQWNjb3VudChhY2NvdW50SWQpIHtcbiAgICBjb25zdCBtYWluQWNjb3VudElkID0gdGhpcy5fd2Vic29ja2V0Q2xpZW50LmFjY291bnRzQnlSZXBsaWNhSWRbYWNjb3VudElkXTtcbiAgICBpZiAobWFpbkFjY291bnRJZCkge1xuICAgICAgY29uc3QgcmVnaXN0cnkgPSB0aGlzLl9tZXRhQXBpLl9jb25uZWN0aW9uUmVnaXN0cnk7XG4gICAgICBjb25zdCBycGNDb25uZWN0aW9uID0gcmVnaXN0cnkucnBjQ29ubmVjdGlvbnNbbWFpbkFjY291bnRJZF07XG4gICAgICBjb25zdCByZWdpb24gPSB0aGlzLl93ZWJzb2NrZXRDbGllbnQuZ2V0QWNjb3VudFJlZ2lvbihhY2NvdW50SWQpO1xuICAgICAgaWYgKHJlZ2lvbikge1xuICAgICAgICBpZiAocnBjQ29ubmVjdGlvbikge1xuICAgICAgICAgIHJwY0Nvbm5lY3Rpb24uc2NoZWR1bGVSZWZyZXNoKHJlZ2lvbik7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3Qgc3RyZWFtaW5nQ29ubmVjdGlvbiA9IHJlZ2lzdHJ5LnN0cmVhbWluZ0Nvbm5lY3Rpb25zW21haW5BY2NvdW50SWRdO1xuICAgICAgICBpZiAoc3RyZWFtaW5nQ29ubmVjdGlvbikge1xuICAgICAgICAgIHN0cmVhbWluZ0Nvbm5lY3Rpb24uc2NoZWR1bGVSZWZyZXNoKHJlZ2lvbik7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICBfbG9nU3Vic2NyaXB0aW9uRXJyb3IoYWNjb3VudElkLCBtZXNzYWdlLCBlcnJvcikge1xuICAgIGNvbnN0IHByaW1hcnlBY2NvdW50SWQgPSB0aGlzLl93ZWJzb2NrZXRDbGllbnQuYWNjb3VudHNCeVJlcGxpY2FJZFthY2NvdW50SWRdO1xuICAgIGNvbnN0IG1ldGhvZCA9IHRoaXMuX2xhdGVuY3lTZXJ2aWNlLmdldFN5bmNocm9uaXplZEFjY291bnRJbnN0YW5jZXMocHJpbWFyeUFjY291bnRJZCkubGVuZ3RoID8gJ2RlYnVnJyA6ICdlcnJvcic7XG4gICAgdGhpcy5fbG9nZ2VyW21ldGhvZF0obWVzc2FnZSwgZXJyb3IpO1xuICB9XG59Il0sIm5hbWVzIjpbIkxvZ2dlck1hbmFnZXIiLCJTdWJzY3JpcHRpb25NYW5hZ2VyIiwiaXNBY2NvdW50U3Vic2NyaWJpbmciLCJhY2NvdW50SWQiLCJpbnN0YW5jZU51bWJlciIsInVuZGVmaW5lZCIsIk9iamVjdCIsImtleXMiLCJfc3Vic2NyaXB0aW9ucyIsImluY2x1ZGVzIiwia2V5Iiwic3RhcnRzV2l0aCIsImlzRGlzY29ubmVjdGVkUmV0cnlNb2RlIiwiaW5zdGFuY2VJZCIsImlzU3Vic2NyaXB0aW9uQWN0aXZlIiwiX3N1YnNjcmlwdGlvblN0YXRlIiwic3Vic2NyaWJlIiwiX3dlYnNvY2tldENsaWVudCIsInJwY1JlcXVlc3QiLCJ0eXBlIiwiaW5zdGFuY2VJbmRleCIsInNjaGVkdWxlU3Vic2NyaWJlIiwiY2xpZW50Iiwic2hvdWxkUmV0cnkiLCJ0YXNrIiwid2FpdFRhc2siLCJmdXR1cmUiLCJzdWJzY3JpYmVSZXRyeUludGVydmFsSW5TZWNvbmRzIiwicmVzb2x2ZVN1YnNjcmliZSIsInByb21pc2UiLCJQcm9taXNlIiwicmVzIiwicmVzb2x2ZSIsInN1YnNjcmliZVRhc2siLCJfbG9nZ2VyIiwiZGVidWciLCJlcnIiLCJuYW1lIiwic29ja2V0SW5zdGFuY2VJbmRleCIsInNvY2tldEluc3RhbmNlc0J5QWNjb3VudHMiLCJtZXRhZGF0YSIsIl9sb2dTdWJzY3JpcHRpb25FcnJvciIsImxvY2tTb2NrZXRJbnN0YW5jZSIsImdldEFjY291bnRSZWdpb24iLCJyZXRyeVRpbWUiLCJEYXRlIiwicmVjb21tZW5kZWRSZXRyeVRpbWUiLCJnZXRUaW1lIiwibm93Iiwic2V0VGltZW91dCIsInJlZnJlc2hBY2NvdW50IiwibWFpbkFjY291bnRJZCIsImFjY291bnRzQnlSZXBsaWNhSWQiLCJyZWdpb24iLCJjb25uZWN0ZWRJbnN0YW5jZXMiLCJfbGF0ZW5jeVNlcnZpY2UiLCJnZXRBY3RpdmVBY2NvdW50SW5zdGFuY2VzIiwic29tZSIsImluc3RhbmNlIiwiX3RpbWVvdXRFcnJvckNvdW50ZXIiLCJyZXRyeUludGVydmFsIiwiTWF0aCIsIm1pbiIsInN1YnNjcmliZVByb21pc2UiLCJyZXN1bHQiLCJ1bnN1YnNjcmliZSIsImNhbmNlbEFjY291bnQiLCJjYW5jZWxTdWJzY3JpYmUiLCJzdWJzY3JpcHRpb24iLCJjbGVhclRpbWVvdXQiLCJmaWx0ZXIiLCJfYXdhaXRpbmdSZXN1YnNjcmliZSIsImZvckVhY2giLCJvblRpbWVvdXQiLCJjb25uZWN0ZWQiLCJvbkRpc2Nvbm5lY3RlZCIsIm1heCIsInJhbmRvbSIsIm9uUmVjb25uZWN0ZWQiLCJyZWNvbm5lY3RBY2NvdW50SWRzIiwic3BsaXQiLCJyZWdpc3RyeSIsIl9tZXRhQXBpIiwiX2Nvbm5lY3Rpb25SZWdpc3RyeSIsInJwY0Nvbm5lY3Rpb24iLCJycGNDb25uZWN0aW9ucyIsInNjaGVkdWxlUmVmcmVzaCIsInN0cmVhbWluZ0Nvbm5lY3Rpb24iLCJzdHJlYW1pbmdDb25uZWN0aW9ucyIsIm1lc3NhZ2UiLCJlcnJvciIsInByaW1hcnlBY2NvdW50SWQiLCJtZXRob2QiLCJnZXRTeW5jaHJvbml6ZWRBY2NvdW50SW5zdGFuY2VzIiwibGVuZ3RoIiwiY29uc3RydWN0b3IiLCJ3ZWJzb2NrZXRDbGllbnQiLCJtZXRhQXBpIiwiX3JlY2VudGx5RGVsZXRlZEFjY291bnRzIiwibGF0ZW5jeVNlcnZpY2UiLCJnZXRMb2dnZXIiXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBRUEsT0FBT0EsbUJB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