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)

420 lines (419 loc) 72.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "default", { enumerable: true, get: function() { return PeriodStatisticsStreamManager; } }); const _randomstring = /*#__PURE__*/ _interop_require_default(require("randomstring")); const _synchronizationListener = /*#__PURE__*/ _interop_require_default(require("../../../clients/metaApi/synchronizationListener")); const _logger = /*#__PURE__*/ _interop_require_default(require("../../../logger")); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } let PeriodStatisticsStreamManager = class PeriodStatisticsStreamManager { /** * Returns listeners for a tracker * @param {string} accountId account id to return listeners for * @param {string} trackerId tracker id to return listeners for * @returns {{[listenerId: string]: PeriodStatisticsListener}} dictionary of period statistics listeners */ getTrackerListeners(accountId, trackerId) { if (!this._periodStatisticsListeners[accountId] || !this._periodStatisticsListeners[accountId][trackerId]) { return {}; } else { return this._periodStatisticsListeners[accountId][trackerId]; } } /** * Adds a period statistics event listener * @param {PeriodStatisticsListener} listener period statistics event listener * @param {String} accountId account id * @param {String} trackerId tracker id * @returns {String} listener id */ // eslint-disable-next-line complexity, max-statements async addPeriodStatisticsListener(listener, accountId, trackerId) { let newTracker = false; if (!this._periodStatisticsCaches[accountId]) { this._periodStatisticsCaches[accountId] = {}; } if (!this._periodStatisticsCaches[accountId][trackerId]) { newTracker = true; this._periodStatisticsCaches[accountId][trackerId] = { trackerData: {}, record: {}, lastPeriod: {}, equityAdjustments: {} }; } const cache = this._periodStatisticsCaches[accountId][trackerId]; let connection = null; let retryIntervalInSeconds = this._retryIntervalInSeconds; const equityTrackingClient = this._equityTrackingClient; const listenerId = _randomstring.default.generate(10); const removePeriodStatisticsListener = this.removePeriodStatisticsListener; const getTrackerListeners = ()=>this.getTrackerListeners(accountId, trackerId); const pendingInitalizationResolves = this._pendingInitalizationResolves; const synchronizationFlags = this._accountSynchronizationFlags; let PeriodStatisticsStreamListener = class PeriodStatisticsStreamListener extends _synchronizationListener.default { async onDealsSynchronized(instanceIndex, synchronizationId) { try { if (!synchronizationFlags[accountId]) { synchronizationFlags[accountId] = true; Object.values(getTrackerListeners()).forEach((accountListener)=>{ accountListener.onConnected(); }); if (pendingInitalizationResolves[accountId]) { pendingInitalizationResolves[accountId].forEach((resolve)=>resolve()); delete pendingInitalizationResolves[accountId]; } } } catch (err) { listener.onError(err); this._logger.error("Error processing onDealsSynchronized event for " + `equity chart listener for account ${accountId}`, err); } } async onDisconnected(instanceIndex) { try { if (synchronizationFlags[accountId] && !connection.healthMonitor.healthStatus.synchronized) { synchronizationFlags[accountId] = false; Object.values(getTrackerListeners()).forEach((trackerListener)=>{ trackerListener.onDisconnected(); }); } } catch (err) { Object.values(getTrackerListeners()).forEach((trackerListener)=>{ trackerListener.onError(err); }); this._logger.error("Error processing onDisconnected event for " + `equity chart listener for account ${accountId}`, err); } } // eslint-disable-next-line complexity, max-statements async onSymbolPriceUpdated(instanceIndex, price) { try { if (pendingInitalizationResolves[accountId]) { pendingInitalizationResolves[accountId].forEach((resolve)=>resolve()); delete pendingInitalizationResolves[accountId]; } if (!cache.lastPeriod) { return; } /** * Process brokerTime: * - smaller than tracker startBrokerTime -> ignore * - bigger than tracker endBrokerTime -> send onTrackerCompleted, close connection * - bigger than period endBrokerTime -> send onPeriodStatisticsCompleted * - normal -> compare to previous data, if different -> send onPeriodStatisticsUpdated */ const equity = price.equity - Object.values(cache.equityAdjustments).reduce((a, b)=>a + b, 0); const brokerTime = price.brokerTime; if (brokerTime > cache.lastPeriod.endBrokerTime) { Object.values(getTrackerListeners()).forEach((trackerListener)=>{ trackerListener.onPeriodStatisticsCompleted(); }); cache.equityAdjustments = {}; const startBrokerTime = cache.lastPeriod.startBrokerTime; cache.lastPeriod = null; // eslint-disable-next-line no-constant-condition while(true){ let periods = await equityTrackingClient.getTrackingStatistics(accountId, trackerId, undefined, 2, true); if (periods[0].startBrokerTime === startBrokerTime) { await new Promise((res)=>setTimeout(res, 10000)); } else { cache.lastPeriod = periods[0]; periods.reverse(); Object.values(getTrackerListeners()).forEach((trackerListener)=>{ trackerListener.onPeriodStatisticsUpdated(periods); }); break; } } } else { if (cache.trackerData.startBrokerTime && brokerTime < cache.trackerData.startBrokerTime) { return; } if (cache.trackerData.endBrokerTime && brokerTime > cache.trackerData.endBrokerTime) { Object.values(getTrackerListeners()).forEach((trackerListener)=>{ trackerListener.onTrackerCompleted(); }); cache.equityAdjustments = {}; Object.keys(getTrackerListeners()).forEach((trackerListenerId)=>{ removePeriodStatisticsListener(trackerListenerId); }); } let absoluteDrawdown = Math.max(0, cache.lastPeriod.initialBalance - equity); let relativeDrawdown = absoluteDrawdown / cache.lastPeriod.initialBalance; let absoluteProfit = Math.max(0, equity - cache.lastPeriod.initialBalance); let relativeProfit = absoluteProfit / cache.lastPeriod.initialBalance; const previousRecord = JSON.stringify(cache.record); if (!cache.record.thresholdExceeded) { if (cache.record.maxAbsoluteDrawdown < absoluteDrawdown) { cache.record.maxAbsoluteDrawdown = absoluteDrawdown; cache.record.maxRelativeDrawdown = relativeDrawdown; cache.record.maxDrawdownTime = brokerTime; if (cache.trackerData.relativeDrawdownThreshold && cache.trackerData.relativeDrawdownThreshold < relativeDrawdown || cache.trackerData.absoluteDrawdownThreshold && cache.trackerData.absoluteDrawdownThreshold < absoluteDrawdown) { cache.record.thresholdExceeded = true; cache.record.exceededThresholdType = "drawdown"; } } if (cache.record.maxAbsoluteProfit < absoluteProfit) { cache.record.maxAbsoluteProfit = absoluteProfit; cache.record.maxRelativeProfit = relativeProfit; cache.record.maxProfitTime = brokerTime; if (cache.trackerData.relativeProfitThreshold && cache.trackerData.relativeProfitThreshold < relativeProfit || cache.trackerData.absoluteProfitThreshold && cache.trackerData.absoluteProfitThreshold < absoluteProfit) { cache.record.thresholdExceeded = true; cache.record.exceededThresholdType = "profit"; } } if (JSON.stringify(cache.record) !== previousRecord) { Object.values(getTrackerListeners()).forEach((trackerListener)=>{ trackerListener.onPeriodStatisticsUpdated([ { startBrokerTime: cache.lastPeriod.startBrokerTime, endBrokerTime: cache.lastPeriod.endBrokerTime, initialBalance: cache.lastPeriod.initialBalance, maxAbsoluteDrawdown: cache.record.maxAbsoluteDrawdown, maxAbsoluteProfit: cache.record.maxAbsoluteProfit, maxDrawdownTime: cache.record.maxDrawdownTime, maxProfitTime: cache.record.maxProfitTime, maxRelativeDrawdown: cache.record.maxRelativeDrawdown, maxRelativeProfit: cache.record.maxRelativeProfit, period: cache.lastPeriod.period, exceededThresholdType: cache.record.exceededThresholdType, thresholdExceeded: cache.record.thresholdExceeded, tradeDayCount: cache.record.tradeDayCount } ]); }); } } } } catch (err) { Object.values(getTrackerListeners()).forEach((trackerListener)=>{ trackerListener.onError(err); }); this._logger.error("Error processing onSymbolPriceUpdated event for " + `period statistics listener for account ${accountId}`, err); } } async onDealAdded(instanceIndex, deal) { try { if (!cache.lastPeriod || !Object.keys(cache.lastPeriod).length) { return; } if (deal.type === "DEAL_TYPE_BALANCE") { cache.equityAdjustments[deal.id] = deal.profit; } const ignoredDealTypes = [ "DEAL_TYPE_BALANCE", "DEAL_TYPE_CREDIT" ]; if (!ignoredDealTypes.includes(deal.type)) { const timeDiff = new Date(deal.time).getTime() - new Date(deal.brokerTime).getTime(); const startSearchDate = new Date(new Date(cache.lastPeriod.startBrokerTime).getTime() + timeDiff); const deals = connection.historyStorage.getDealsByTimeRange(startSearchDate, new Date(8640000000000000)).filter((dealItem)=>!ignoredDealTypes.includes(dealItem.type)); deals.push(deal); const tradedDays = {}; deals.forEach((dealItem)=>{ tradedDays[dealItem.brokerTime.slice(0, 10)] = true; }); const tradeDayCount = Object.keys(tradedDays).length; if (cache.record.tradeDayCount !== tradeDayCount) { cache.record.tradeDayCount = tradeDayCount; Object.values(getTrackerListeners()).forEach((trackerListener)=>{ trackerListener.onPeriodStatisticsUpdated([ { startBrokerTime: cache.lastPeriod.startBrokerTime, endBrokerTime: cache.lastPeriod.endBrokerTime, initialBalance: cache.lastPeriod.initialBalance, maxAbsoluteDrawdown: cache.record.maxAbsoluteDrawdown, maxAbsoluteProfit: cache.record.maxAbsoluteProfit, maxDrawdownTime: cache.record.maxDrawdownTime, maxProfitTime: cache.record.maxProfitTime, maxRelativeDrawdown: cache.record.maxRelativeDrawdown, maxRelativeProfit: cache.record.maxRelativeProfit, period: cache.lastPeriod.period, exceededThresholdType: cache.record.exceededThresholdType, thresholdExceeded: cache.record.thresholdExceeded, tradeDayCount: cache.record.tradeDayCount } ]); }); } } } catch (err) { Object.values(getTrackerListeners()).forEach((trackerListener)=>{ trackerListener.onError(err); }); this._logger.error("Error processing onDealAdded event for " + `period statistics listener for account ${accountId}`, err); } } }; const account = await this._metaApi.metatraderAccountApi.getAccount(accountId); const tracker = await equityTrackingClient.getTracker(accountId, trackerId); cache.trackerData = tracker; if (!this._periodStatisticsListeners[accountId]) { this._periodStatisticsListeners[accountId] = {}; } if (!this._periodStatisticsListeners[accountId][trackerId]) { this._periodStatisticsListeners[accountId][trackerId] = {}; } const accountListeners = this._periodStatisticsListeners[accountId][trackerId]; accountListeners[listenerId] = listener; this._accountsByListenerId[listenerId] = accountId; this._trackersByListenerId[listenerId] = trackerId; let isDeployed = false; while(!isDeployed){ try { await account.waitDeployed(); isDeployed = true; } catch (err) { listener.onError(err); this._logger.error(`Error wait for account ${accountId} to deploy, retrying`, err); await new Promise((res)=>setTimeout(res, retryIntervalInSeconds * 1000)); retryIntervalInSeconds = Math.min(retryIntervalInSeconds * 2, 300); } } if (!this._periodStatisticsConnections[accountId]) { retryIntervalInSeconds = this._retryIntervalInSeconds; connection = account.getStreamingConnection(); const syncListener = new PeriodStatisticsStreamListener(); connection.addSynchronizationListener(syncListener); this._periodStatisticsConnections[accountId] = connection; this._syncListeners[trackerId] = syncListener; let isSynchronized = false; while(!isSynchronized){ try { await connection.connect(); await connection.waitSynchronized(); isSynchronized = true; } catch (err) { listener.onError(err); this._logger.error("Error configuring period statistics stream listener for " + `account ${accountId}, retrying`, err); await new Promise((res)=>setTimeout(res, retryIntervalInSeconds * 1000)); retryIntervalInSeconds = Math.min(retryIntervalInSeconds * 2, 300); } } retryIntervalInSeconds = this._retryIntervalInSeconds; } else { connection = this._periodStatisticsConnections[accountId]; if (newTracker) { const syncListener = new PeriodStatisticsStreamListener(); connection.addSynchronizationListener(syncListener); this._syncListeners[trackerId] = syncListener; } if (!connection.healthMonitor.healthStatus.synchronized) { if (!this._pendingInitalizationResolves[accountId]) { this._pendingInitalizationResolves[accountId] = []; } let resolveInitialize; let initializePromise = new Promise((res, rej)=>{ resolveInitialize = res; }); this._pendingInitalizationResolves[accountId].push(resolveInitialize); await initializePromise; } } let initialData = []; const fetchInitialData = async ()=>{ try { initialData = await equityTrackingClient.getTrackingStatistics(accountId, trackerId, undefined, undefined, true); if (initialData.length) { const lastItem = initialData[0]; if (this._fetchInitialDataIntervalId[listenerId]) { clearInterval(this._fetchInitialDataIntervalId[listenerId]); delete this._fetchInitialDataIntervalId[listenerId]; } listener.onPeriodStatisticsUpdated(initialData); cache.lastPeriod = { startBrokerTime: lastItem.startBrokerTime, endBrokerTime: lastItem.endBrokerTime, period: lastItem.period, initialBalance: lastItem.initialBalance, maxDrawdownTime: lastItem.maxDrawdownTime, maxAbsoluteDrawdown: lastItem.maxAbsoluteDrawdown, maxRelativeDrawdown: lastItem.maxRelativeDrawdown, maxProfitTime: lastItem.maxProfitTime, maxAbsoluteProfit: lastItem.maxAbsoluteProfit, maxRelativeProfit: lastItem.maxRelativeProfit, thresholdExceeded: lastItem.thresholdExceeded, exceededThresholdType: lastItem.exceededThresholdType, tradeDayCount: lastItem.tradeDayCount }; cache.record = cache.lastPeriod; } } catch (err) { listener.onError(err); this._logger.error(`Failed to initialize tracking statistics data for account ${accountId}`, err); await new Promise((res)=>setTimeout(res, retryIntervalInSeconds * 1000)); retryIntervalInSeconds = Math.min(retryIntervalInSeconds * 2, 300); } }; retryIntervalInSeconds = this._retryIntervalInSeconds; this._fetchInitialDataIntervalId[listenerId] = setInterval(fetchInitialData, retryIntervalInSeconds * 1000 * 2 * 60); fetchInitialData(); return listenerId; } /** * Removes period statistics event listener by id * @param {String} listenerId listener id */ // eslint-disable-next-line complexity removePeriodStatisticsListener(listenerId) { if (this._accountsByListenerId[listenerId] && this._trackersByListenerId[listenerId]) { if (this._fetchInitialDataIntervalId[listenerId]) { clearInterval(this._fetchInitialDataIntervalId[listenerId]); delete this._fetchInitialDataIntervalId[listenerId]; } const accountId = this._accountsByListenerId[listenerId]; const trackerId = this._trackersByListenerId[listenerId]; delete this._accountsByListenerId[listenerId]; delete this._trackersByListenerId[listenerId]; if (this._periodStatisticsListeners[accountId]) { if (this._periodStatisticsListeners[accountId][trackerId]) { delete this._periodStatisticsListeners[accountId][trackerId][listenerId]; if (!Object.keys(this._periodStatisticsListeners[accountId][trackerId]).length) { delete this._periodStatisticsListeners[accountId][trackerId]; if (this._periodStatisticsConnections[accountId] && this._syncListeners[trackerId]) { this._periodStatisticsConnections[accountId].removeSynchronizationListener(this._syncListeners[trackerId]); delete this._syncListeners[trackerId]; } } } if (!Object.keys(this._periodStatisticsListeners[accountId]).length) { delete this._periodStatisticsListeners[accountId]; } } if (this._periodStatisticsConnections[accountId] && !this._periodStatisticsListeners[accountId]) { delete this._accountSynchronizationFlags[accountId]; this._periodStatisticsConnections[accountId].close(); delete this._periodStatisticsConnections[accountId]; } } } /** * Constructs period statistics event listener manager instance * @param {DomainClient} domainClient domain client * @param {EquityTrackingClient} equityTrackingClient equity tracking client * @param {MetaApi} metaApi metaApi SDK instance */ constructor(domainClient, equityTrackingClient, metaApi){ this._domainClient = domainClient; this._equityTrackingClient = equityTrackingClient; this._metaApi = metaApi; this._periodStatisticsListeners = {}; this._accountsByListenerId = {}; this._trackersByListenerId = {}; this._trackerSyncListeners = {}; this._periodStatisticsConnections = {}; this._periodStatisticsCaches = {}; this._accountSynchronizationFlags = {}; this._pendingInitalizationResolves = {}; this._syncListeners = {}; this._retryIntervalInSeconds = 1; this._fetchInitialDataIntervalId = {}; this.removePeriodStatisticsListener = this.removePeriodStatisticsListener.bind(this); this._logger = _logger.default.getLogger("PeriodStatisticsStreamManager"); } }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCByYW5kb21zdHJpbmcgZnJvbSAncmFuZG9tc3RyaW5nJztcbmltcG9ydCBTeW5jaHJvbml6YXRpb25MaXN0ZW5lciBmcm9tICcuLi8uLi8uLi9jbGllbnRzL21ldGFBcGkvc3luY2hyb25pemF0aW9uTGlzdGVuZXInO1xuLy8gaW1wb3J0IHtOb3RGb3VuZEVycm9yfSBmcm9tICcuLi8uLi8uLi9jbGllbnRzL2Vycm9ySGFuZGxlcic7XG5pbXBvcnQgTG9nZ2VyTWFuYWdlciBmcm9tICcuLi8uLi8uLi9sb2dnZXInO1xuXG4vKipcbiAqIE1hbmFnZXIgZm9yIGhhbmRsaW5nIHBlcmlvZCBzdGF0aXN0aWNzIGV2ZW50IGxpc3RlbmVyc1xuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBQZXJpb2RTdGF0aXN0aWNzU3RyZWFtTWFuYWdlciB7XG5cbiAgLyoqXG4gICAqIENvbnN0cnVjdHMgcGVyaW9kIHN0YXRpc3RpY3MgZXZlbnQgbGlzdGVuZXIgbWFuYWdlciBpbnN0YW5jZVxuICAgKiBAcGFyYW0ge0RvbWFpbkNsaWVudH0gZG9tYWluQ2xpZW50IGRvbWFpbiBjbGllbnRcbiAgICogQHBhcmFtIHtFcXVpdHlUcmFja2luZ0NsaWVudH0gZXF1aXR5VHJhY2tpbmdDbGllbnQgZXF1aXR5IHRyYWNraW5nIGNsaWVudFxuICAgKiBAcGFyYW0ge01ldGFBcGl9IG1ldGFBcGkgbWV0YUFwaSBTREsgaW5zdGFuY2VcbiAgICovXG4gIGNvbnN0cnVjdG9yKGRvbWFpbkNsaWVudCwgZXF1aXR5VHJhY2tpbmdDbGllbnQsIG1ldGFBcGkpIHtcbiAgICB0aGlzLl9kb21haW5DbGllbnQgPSBkb21haW5DbGllbnQ7XG4gICAgdGhpcy5fZXF1aXR5VHJhY2tpbmdDbGllbnQgPSBlcXVpdHlUcmFja2luZ0NsaWVudDtcbiAgICB0aGlzLl9tZXRhQXBpID0gbWV0YUFwaTtcbiAgICB0aGlzLl9wZXJpb2RTdGF0aXN0aWNzTGlzdGVuZXJzID0ge307XG4gICAgdGhpcy5fYWNjb3VudHNCeUxpc3RlbmVySWQgPSB7fTtcbiAgICB0aGlzLl90cmFja2Vyc0J5TGlzdGVuZXJJZCA9IHt9O1xuICAgIHRoaXMuX3RyYWNrZXJTeW5jTGlzdGVuZXJzID0ge307XG4gICAgdGhpcy5fcGVyaW9kU3RhdGlzdGljc0Nvbm5lY3Rpb25zID0ge307XG4gICAgdGhpcy5fcGVyaW9kU3RhdGlzdGljc0NhY2hlcyA9IHt9O1xuICAgIHRoaXMuX2FjY291bnRTeW5jaHJvbml6YXRpb25GbGFncyA9IHt9O1xuICAgIHRoaXMuX3BlbmRpbmdJbml0YWxpemF0aW9uUmVzb2x2ZXMgPSB7fTtcbiAgICB0aGlzLl9zeW5jTGlzdGVuZXJzID0ge307XG4gICAgdGhpcy5fcmV0cnlJbnRlcnZhbEluU2Vjb25kcyA9IDE7XG4gICAgdGhpcy5fZmV0Y2hJbml0aWFsRGF0YUludGVydmFsSWQgPSB7fTtcbiAgICB0aGlzLnJlbW92ZVBlcmlvZFN0YXRpc3RpY3NMaXN0ZW5lciA9IHRoaXMucmVtb3ZlUGVyaW9kU3RhdGlzdGljc0xpc3RlbmVyLmJpbmQodGhpcyk7XG4gICAgdGhpcy5fbG9nZ2VyID0gTG9nZ2VyTWFuYWdlci5nZXRMb2dnZXIoJ1BlcmlvZFN0YXRpc3RpY3NTdHJlYW1NYW5hZ2VyJyk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBsaXN0ZW5lcnMgZm9yIGEgdHJhY2tlclxuICAgKiBAcGFyYW0ge3N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWQgdG8gcmV0dXJuIGxpc3RlbmVycyBmb3JcbiAgICogQHBhcmFtIHtzdHJpbmd9IHRyYWNrZXJJZCB0cmFja2VyIGlkIHRvIHJldHVybiBsaXN0ZW5lcnMgZm9yXG4gICAqIEByZXR1cm5zIHt7W2xpc3RlbmVySWQ6IHN0cmluZ106IFBlcmlvZFN0YXRpc3RpY3NMaXN0ZW5lcn19IGRpY3Rpb25hcnkgb2YgcGVyaW9kIHN0YXRpc3RpY3MgbGlzdGVuZXJzXG4gICAqL1xuICBnZXRUcmFja2VyTGlzdGVuZXJzKGFjY291bnRJZCwgdHJhY2tlcklkKSB7XG4gICAgaWYoIXRoaXMuX3BlcmlvZFN0YXRpc3RpY3NMaXN0ZW5lcnNbYWNjb3VudElkXSB8fCAhdGhpcy5fcGVyaW9kU3RhdGlzdGljc0xpc3RlbmVyc1thY2NvdW50SWRdW3RyYWNrZXJJZF0pIHtcbiAgICAgIHJldHVybiB7fTtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIHRoaXMuX3BlcmlvZFN0YXRpc3RpY3NMaXN0ZW5lcnNbYWNjb3VudElkXVt0cmFja2VySWRdO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBBZGRzIGEgcGVyaW9kIHN0YXRpc3RpY3MgZXZlbnQgbGlzdGVuZXJcbiAgICogQHBhcmFtIHtQZXJpb2RTdGF0aXN0aWNzTGlzdGVuZXJ9IGxpc3RlbmVyIHBlcmlvZCBzdGF0aXN0aWNzIGV2ZW50IGxpc3RlbmVyXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBhY2NvdW50SWQgYWNjb3VudCBpZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gdHJhY2tlcklkIHRyYWNrZXIgaWRcbiAgICogQHJldHVybnMge1N0cmluZ30gbGlzdGVuZXIgaWRcbiAgICovXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBjb21wbGV4aXR5LCBtYXgtc3RhdGVtZW50c1xuICBhc3luYyBhZGRQZXJpb2RTdGF0aXN0aWNzTGlzdGVuZXIobGlzdGVuZXIsIGFjY291bnRJZCwgdHJhY2tlcklkKSB7XG4gICAgbGV0IG5ld1RyYWNrZXIgPSBmYWxzZTtcbiAgICBpZighdGhpcy5fcGVyaW9kU3RhdGlzdGljc0NhY2hlc1thY2NvdW50SWRdKSB7XG4gICAgICB0aGlzLl9wZXJpb2RTdGF0aXN0aWNzQ2FjaGVzW2FjY291bnRJZF0gPSB7fTtcbiAgICB9XG4gICAgaWYoIXRoaXMuX3BlcmlvZFN0YXRpc3RpY3NDYWNoZXNbYWNjb3VudElkXVt0cmFja2VySWRdKSB7XG4gICAgICBuZXdUcmFja2VyID0gdHJ1ZTtcbiAgICAgIHRoaXMuX3BlcmlvZFN0YXRpc3RpY3NDYWNoZXNbYWNjb3VudElkXVt0cmFja2VySWRdID0ge1xuICAgICAgICB0cmFja2VyRGF0YToge30sXG4gICAgICAgIHJlY29yZDoge30sXG4gICAgICAgIGxhc3RQZXJpb2Q6IHt9LFxuICAgICAgICBlcXVpdHlBZGp1c3RtZW50czoge31cbiAgICAgIH07XG4gICAgfVxuICAgIGNvbnN0IGNhY2hlID0gdGhpcy5fcGVyaW9kU3RhdGlzdGljc0NhY2hlc1thY2NvdW50SWRdW3RyYWNrZXJJZF07XG4gICAgbGV0IGNvbm5lY3Rpb24gPSBudWxsO1xuICAgIGxldCByZXRyeUludGVydmFsSW5TZWNvbmRzID0gdGhpcy5fcmV0cnlJbnRlcnZhbEluU2Vjb25kcztcbiAgICBjb25zdCBlcXVpdHlUcmFja2luZ0NsaWVudCA9IHRoaXMuX2VxdWl0eVRyYWNraW5nQ2xpZW50O1xuICAgIGNvbnN0IGxpc3RlbmVySWQgPSByYW5kb21zdHJpbmcuZ2VuZXJhdGUoMTApO1xuICAgIGNvbnN0IHJlbW92ZVBlcmlvZFN0YXRpc3RpY3NMaXN0ZW5lciA9IHRoaXMucmVtb3ZlUGVyaW9kU3RhdGlzdGljc0xpc3RlbmVyO1xuICAgIGNvbnN0IGdldFRyYWNrZXJMaXN0ZW5lcnMgPSAoKSA9PiB0aGlzLmdldFRyYWNrZXJMaXN0ZW5lcnMoYWNjb3VudElkLCB0cmFja2VySWQpO1xuICAgIGNvbnN0IHBlbmRpbmdJbml0YWxpemF0aW9uUmVzb2x2ZXMgPSB0aGlzLl9wZW5kaW5nSW5pdGFsaXphdGlvblJlc29sdmVzO1xuICAgIGNvbnN0IHN5bmNocm9uaXphdGlvbkZsYWdzID0gdGhpcy5fYWNjb3VudFN5bmNocm9uaXphdGlvbkZsYWdzO1xuXG4gICAgY2xhc3MgUGVyaW9kU3RhdGlzdGljc1N0cmVhbUxpc3RlbmVyIGV4dGVuZHMgU3luY2hyb25pemF0aW9uTGlzdGVuZXIge1xuXG4gICAgICBhc3luYyBvbkRlYWxzU3luY2hyb25pemVkKGluc3RhbmNlSW5kZXgsIHN5bmNocm9uaXphdGlvbklkKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgaWYoIXN5bmNocm9uaXphdGlvbkZsYWdzW2FjY291bnRJZF0pIHtcbiAgICAgICAgICAgIHN5bmNocm9uaXphdGlvbkZsYWdzW2FjY291bnRJZF0gPSB0cnVlO1xuICAgICAgICAgICAgT2JqZWN0LnZhbHVlcyhnZXRUcmFja2VyTGlzdGVuZXJzKCkpLmZvckVhY2goYWNjb3VudExpc3RlbmVyID0+IHtcbiAgICAgICAgICAgICAgYWNjb3VudExpc3RlbmVyLm9uQ29ubmVjdGVkKCk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIGlmKHBlbmRpbmdJbml0YWxpemF0aW9uUmVzb2x2ZXNbYWNjb3VudElkXSkge1xuICAgICAgICAgICAgICBwZW5kaW5nSW5pdGFsaXphdGlvblJlc29sdmVzW2FjY291bnRJZF0uZm9yRWFjaChyZXNvbHZlID0+IHJlc29sdmUoKSk7XG4gICAgICAgICAgICAgIGRlbGV0ZSBwZW5kaW5nSW5pdGFsaXphdGlvblJlc29sdmVzW2FjY291bnRJZF07XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICBsaXN0ZW5lci5vbkVycm9yKGVycik7XG4gICAgICAgICAgdGhpcy5fbG9nZ2VyLmVycm9yKCdFcnJvciBwcm9jZXNzaW5nIG9uRGVhbHNTeW5jaHJvbml6ZWQgZXZlbnQgZm9yICcgK1xuICAgICAgICAgIGBlcXVpdHkgY2hhcnQgbGlzdGVuZXIgZm9yIGFjY291bnQgJHthY2NvdW50SWR9YCwgZXJyKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBhc3luYyBvbkRpc2Nvbm5lY3RlZChpbnN0YW5jZUluZGV4KSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgaWYoc3luY2hyb25pemF0aW9uRmxhZ3NbYWNjb3VudElkXSAmJiAhY29ubmVjdGlvbi5oZWFsdGhNb25pdG9yLmhlYWx0aFN0YXR1cy5zeW5jaHJvbml6ZWQpIHtcbiAgICAgICAgICAgIHN5bmNocm9uaXphdGlvbkZsYWdzW2FjY291bnRJZF0gPSBmYWxzZTtcbiAgICAgICAgICAgIE9iamVjdC52YWx1ZXMoZ2V0VHJhY2tlckxpc3RlbmVycygpKS5mb3JFYWNoKHRyYWNrZXJMaXN0ZW5lciA9PiB7XG4gICAgICAgICAgICAgIHRyYWNrZXJMaXN0ZW5lci5vbkRpc2Nvbm5lY3RlZCgpO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgfVxuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICBPYmplY3QudmFsdWVzKGdldFRyYWNrZXJMaXN0ZW5lcnMoKSkuZm9yRWFjaCh0cmFja2VyTGlzdGVuZXIgPT4ge1xuICAgICAgICAgICAgdHJhY2tlckxpc3RlbmVyLm9uRXJyb3IoZXJyKTtcbiAgICAgICAgICB9KTtcbiAgICAgICAgICB0aGlzLl9sb2dnZXIuZXJyb3IoJ0Vycm9yIHByb2Nlc3Npbmcgb25EaXNjb25uZWN0ZWQgZXZlbnQgZm9yICcgK1xuICAgICAgICAgIGBlcXVpdHkgY2hhcnQgbGlzdGVuZXIgZm9yIGFjY291bnQgJHthY2NvdW50SWR9YCwgZXJyKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgY29tcGxleGl0eSwgbWF4LXN0YXRlbWVudHNcbiAgICAgIGFzeW5jIG9uU3ltYm9sUHJpY2VVcGRhdGVkKGluc3RhbmNlSW5kZXgsIHByaWNlKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgaWYocGVuZGluZ0luaXRhbGl6YXRpb25SZXNvbHZlc1thY2NvdW50SWRdKSB7XG4gICAgICAgICAgICBwZW5kaW5nSW5pdGFsaXphdGlvblJlc29sdmVzW2FjY291bnRJZF0uZm9yRWFjaChyZXNvbHZlID0+IHJlc29sdmUoKSk7XG4gICAgICAgICAgICBkZWxldGUgcGVuZGluZ0luaXRhbGl6YXRpb25SZXNvbHZlc1thY2NvdW50SWRdO1xuICAgICAgICAgIH1cbiAgXG4gICAgICAgICAgaWYoIWNhY2hlLmxhc3RQZXJpb2QpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gIFxuICAgICAgICAgIC8qKlxuICAgICAgICAgICAqIFByb2Nlc3MgYnJva2VyVGltZTpcbiAgICAgICAgICAgKiAtIHNtYWxsZXIgdGhhbiB0cmFja2VyIHN0YXJ0QnJva2VyVGltZSAtPiBpZ25vcmVcbiAgICAgICAgICAgKiAtIGJpZ2dlciB0aGFuIHRyYWNrZXIgZW5kQnJva2VyVGltZSAtPiBzZW5kIG9uVHJhY2tlckNvbXBsZXRlZCwgY2xvc2UgY29ubmVjdGlvblxuICAgICAgICAgICAqIC0gYmlnZ2VyIHRoYW4gcGVyaW9kIGVuZEJyb2tlclRpbWUgLT4gc2VuZCBvblBlcmlvZFN0YXRpc3RpY3NDb21wbGV0ZWRcbiAgICAgICAgICAgKiAtIG5vcm1hbCAtPiBjb21wYXJlIHRvIHByZXZpb3VzIGRhdGEsIGlmIGRpZmZlcmVudCAtPiBzZW5kIG9uUGVyaW9kU3RhdGlzdGljc1VwZGF0ZWRcbiAgICAgICAgICAgKi9cbiAgICAgICAgICBjb25zdCBlcXVpdHkgPSBwcmljZS5lcXVpdHkgLSBPYmplY3QudmFsdWVzKGNhY2hlLmVxdWl0eUFkanVzdG1lbnRzKVxuICAgICAgICAgICAgLnJlZHVjZSgoYSwgYikgPT4gYSArIGIsIDApO1xuICAgICAgICAgIGNvbnN0IGJyb2tlclRpbWUgPSBwcmljZS5icm9rZXJUaW1lO1xuICAgICAgICAgIGlmKGJyb2tlclRpbWUgPiBjYWNoZS5sYXN0UGVyaW9kLmVuZEJyb2tlclRpbWUpIHtcbiAgICAgICAgICAgIE9iamVjdC52YWx1ZXMoZ2V0VHJhY2tlckxpc3RlbmVycygpKS5mb3JFYWNoKHRyYWNrZXJMaXN0ZW5lciA9PiB7XG4gICAgICAgICAgICAgIHRyYWNrZXJMaXN0ZW5lci5vblBlcmlvZFN0YXRpc3RpY3NDb21wbGV0ZWQoKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgY2FjaGUuZXF1aXR5QWRqdXN0bWVudHMgPSB7fTtcbiAgICAgICAgICAgIGNvbnN0IHN0YXJ0QnJva2VyVGltZSA9IGNhY2hlLmxhc3RQZXJpb2Quc3RhcnRCcm9rZXJUaW1lO1xuICAgICAgICAgICAgY2FjaGUubGFzdFBlcmlvZCA9IG51bGw7XG4gICAgICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc3RhbnQtY29uZGl0aW9uXG4gICAgICAgICAgICB3aGlsZSh0cnVlKSB7XG4gICAgICAgICAgICAgIGxldCBwZXJpb2RzID0gYXdhaXQgZXF1aXR5VHJhY2tpbmdDbGllbnQuZ2V0VHJhY2tpbmdTdGF0aXN0aWNzKGFjY291bnRJZCwgdHJhY2tlcklkLCB1bmRlZmluZWQsIDIsIHRydWUpO1xuICAgICAgICAgICAgICBpZihwZXJpb2RzWzBdLnN0YXJ0QnJva2VyVGltZSA9PT0gc3RhcnRCcm9rZXJUaW1lKSB7XG4gICAgICAgICAgICAgICAgYXdhaXQgbmV3IFByb21pc2UocmVzID0+IHNldFRpbWVvdXQocmVzLCAxMDAwMCkpO1xuICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIGNhY2hlLmxhc3RQZXJpb2QgPSBwZXJpb2RzWzBdO1xuICAgICAgICAgICAgICAgIHBlcmlvZHMucmV2ZXJzZSgpO1xuICAgICAgICAgICAgICAgIE9iamVjdC52YWx1ZXMoZ2V0VHJhY2tlckxpc3RlbmVycygpKS5mb3JFYWNoKHRyYWNrZXJMaXN0ZW5lciA9PiB7XG4gICAgICAgICAgICAgICAgICB0cmFja2VyTGlzdGVuZXIub25QZXJpb2RTdGF0aXN0aWNzVXBkYXRlZChwZXJpb2RzKTtcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBpZihjYWNoZS50cmFja2VyRGF0YS5zdGFydEJyb2tlclRpbWUgJiYgYnJva2VyVGltZSA8IGNhY2hlLnRyYWNrZXJEYXRhLnN0YXJ0QnJva2VyVGltZSkge1xuICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZihjYWNoZS50cmFja2VyRGF0YS5lbmRCcm9rZXJUaW1lICYmIGJyb2tlclRpbWUgPiBjYWNoZS50cmFja2VyRGF0YS5lbmRCcm9rZXJUaW1lKSB7XG4gICAgICAgICAgICAgIE9iamVjdC52YWx1ZXMoZ2V0VHJhY2tlckxpc3RlbmVycygpKS5mb3JFYWNoKHRyYWNrZXJMaXN0ZW5lciA9PiB7XG4gICAgICAgICAgICAgICAgdHJhY2tlckxpc3RlbmVyLm9uVHJhY2tlckNvbXBsZXRlZCgpO1xuICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgY2FjaGUuZXF1aXR5QWRqdXN0bWVudHMgPSB7fTtcbiAgICAgICAgICAgICAgT2JqZWN0LmtleXMoZ2V0VHJhY2tlckxpc3RlbmVycygpKS5mb3JFYWNoKHRyYWNrZXJMaXN0ZW5lcklkID0+IHtcbiAgICAgICAgICAgICAgICByZW1vdmVQZXJpb2RTdGF0aXN0aWNzTGlzdGVuZXIodHJhY2tlckxpc3RlbmVySWQpO1xuICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIFxuICAgICAgICAgICAgbGV0IGFic29sdXRlRHJhd2Rvd24gPSBNYXRoLm1heCgwLCBjYWNoZS5sYXN0UGVyaW9kLmluaXRpYWxCYWxhbmNlIC0gZXF1aXR5KTtcbiAgICAgICAgICAgIGxldCByZWxhdGl2ZURyYXdkb3duID0gYWJzb2x1dGVEcmF3ZG93biAvIGNhY2hlLmxhc3RQZXJpb2QuaW5pdGlhbEJhbGFuY2U7XG4gICAgICAgICAgICBsZXQgYWJzb2x1dGVQcm9maXQgPSBNYXRoLm1heCgwLCBlcXVpdHkgLSBjYWNoZS5sYXN0UGVyaW9kLmluaXRpYWxCYWxhbmNlKTtcbiAgICAgICAgICAgIGxldCByZWxhdGl2ZVByb2ZpdCA9IGFic29sdXRlUHJvZml0IC8gY2FjaGUubGFzdFBlcmlvZC5pbml0aWFsQmFsYW5jZTtcbiAgICAgICAgICAgIGNvbnN0IHByZXZpb3VzUmVjb3JkID0gSlNPTi5zdHJpbmdpZnkoY2FjaGUucmVjb3JkKTtcbiAgICAgICAgICAgIGlmKCFjYWNoZS5yZWNvcmQudGhyZXNob2xkRXhjZWVkZWQpIHtcbiAgICAgICAgICAgICAgaWYoY2FjaGUucmVjb3JkLm1heEFic29sdXRlRHJhd2Rvd24gPCBhYnNvbHV0ZURyYXdkb3duKSB7XG4gICAgICAgICAgICAgICAgY2FjaGUucmVjb3JkLm1heEFic29sdXRlRHJhd2Rvd24gPSBhYnNvbHV0ZURyYXdkb3duO1xuICAgICAgICAgICAgICAgIGNhY2hlLnJlY29yZC5tYXhSZWxhdGl2ZURyYXdkb3duID0gcmVsYXRpdmVEcmF3ZG93bjtcbiAgICAgICAgICAgICAgICBjYWNoZS5yZWNvcmQubWF4RHJhd2Rvd25UaW1lID0gYnJva2VyVGltZTtcbiAgICAgICAgICAgICAgICBpZigoY2FjaGUudHJhY2tlckRhdGEucmVsYXRpdmVEcmF3ZG93blRocmVzaG9sZCAmJiBcbiAgICAgICAgICAgICAgICAgIGNhY2hlLnRyYWNrZXJEYXRhLnJlbGF0aXZlRHJhd2Rvd25UaHJlc2hvbGQgPCByZWxhdGl2ZURyYXdkb3duKSB8fCBcbiAgICAgICAgICAgICAgICAgIChjYWNoZS50cmFja2VyRGF0YS5hYnNvbHV0ZURyYXdkb3duVGhyZXNob2xkICYmXG4gICAgICAgICAgICAgICAgICAgIGNhY2hlLnRyYWNrZXJEYXRhLmFic29sdXRlRHJhd2Rvd25UaHJlc2hvbGQgPCBhYnNvbHV0ZURyYXdkb3duKSkge1xuICAgICAgICAgICAgICAgICAgY2FjaGUucmVjb3JkLnRocmVzaG9sZEV4Y2VlZGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICAgIGNhY2hlLnJlY29yZC5leGNlZWRlZFRocmVzaG9sZFR5cGUgPSAnZHJhd2Rvd24nO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBpZihjYWNoZS5yZWNvcmQubWF4QWJzb2x1dGVQcm9maXQgPCBhYnNvbHV0ZVByb2ZpdCkge1xuICAgICAgICAgICAgICAgIGNhY2hlLnJlY29yZC5tYXhBYnNvbHV0ZVByb2ZpdCA9IGFic29sdXRlUHJvZml0O1xuICAgICAgICAgICAgICAgIGNhY2hlLnJlY29yZC5tYXhSZWxhdGl2ZVByb2ZpdCA9IHJlbGF0aXZlUHJvZml0O1xuICAgICAgICAgICAgICAgIGNhY2hlLnJlY29yZC5tYXhQcm9maXRUaW1lID0gYnJva2VyVGltZTtcbiAgICAgICAgICAgICAgICBpZigoY2FjaGUudHJhY2tlckRhdGEucmVsYXRpdmVQcm9maXRUaHJlc2hvbGQgJiYgXG4gICAgICAgICAgICAgICAgICBjYWNoZS50cmFja2VyRGF0YS5yZWxhdGl2ZVByb2ZpdFRocmVzaG9sZCA8IHJlbGF0aXZlUHJvZml0KSB8fFxuICAgICAgICAgICAgICAgICAgKGNhY2hlLnRyYWNrZXJEYXRhLmFic29sdXRlUHJvZml0VGhyZXNob2xkICYmXG4gICAgICAgICAgICAgICAgICAgIGNhY2hlLnRyYWNrZXJEYXRhLmFic29sdXRlUHJvZml0VGhyZXNob2xkIDwgYWJzb2x1dGVQcm9maXQpKSB7XG4gICAgICAgICAgICAgICAgICBjYWNoZS5yZWNvcmQudGhyZXNob2xkRXhjZWVkZWQgPSB0cnVlO1xuICAgICAgICAgICAgICAgICAgY2FjaGUucmVjb3JkLmV4Y2VlZGVkVGhyZXNob2xkVHlwZSA9ICdwcm9maXQnO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBpZihKU09OLnN0cmluZ2lmeShjYWNoZS5yZWNvcmQpICE9PSBwcmV2aW91c1JlY29yZCkge1xuICAgICAgICAgICAgICAgIE9iamVjdC52YWx1ZXMoZ2V0VHJhY2tlckxpc3RlbmVycygpKS5mb3JFYWNoKHRyYWNrZXJMaXN0ZW5lciA9PiB7XG4gICAgICAgICAgICAgICAgICB0cmFja2VyTGlzdGVuZXIub25QZXJpb2RTdGF0aXN0aWNzVXBkYXRlZChbe1xuICAgICAgICAgICAgICAgICAgICBzdGFydEJyb2tlclRpbWU6IGNhY2hlLmxhc3RQZXJpb2Quc3RhcnRCcm9rZXJUaW1lLFxuICAgICAgICAgICAgICAgICAgICBlbmRCcm9rZXJUaW1lOiBjYWNoZS5sYXN0UGVyaW9kLmVuZEJyb2tlclRpbWUsXG4gICAgICAgICAgICAgICAgICAgIGluaXRpYWxCYWxhbmNlOiBjYWNoZS5sYXN0UGVyaW9kLmluaXRpYWxCYWxhbmNlLFxuICAgICAgICAgICAgICAgICAgICBtYXhBYnNvbHV0ZURyYXdkb3duOiBjYWNoZS5yZWNvcmQubWF4QWJzb2x1dGVEcmF3ZG93bixcbiAgICAgICAgICAgICAgICAgICAgbWF4QWJzb2x1dGVQcm9maXQ6IGNhY2hlLnJlY29yZC5tYXhBYnNvbHV0ZVByb2ZpdCxcbiAgICAgICAgICAgICAgICAgICAgbWF4RHJhd2Rvd25UaW1lOiBjYWNoZS5yZWNvcmQubWF4RHJhd2Rvd25UaW1lLFxuICAgICAgICAgICAgICAgICAgICBtYXhQcm9maXRUaW1lOiBjYWNoZS5yZWNvcmQubWF4UHJvZml0VGltZSxcbiAgICAgICAgICAgICAgICAgICAgbWF4UmVsYXRpdmVEcmF3ZG93bjogY2FjaGUucmVjb3JkLm1heFJlbGF0aXZlRHJhd2Rvd24sXG4gICAgICAgICAgICAgICAgICAgIG1heFJlbGF0aXZlUHJvZml0OiBjYWNoZS5yZWNvcmQubWF4UmVsYXRpdmVQcm9maXQsXG4gICAgICAgICAgICAgICAgICAgIHBlcmlvZDogY2FjaGUubGFzdFBlcmlvZC5wZXJpb2QsXG4gICAgICAgICAgICAgICAgICAgIGV4Y2VlZGVkVGhyZXNob2xkVHlwZTogY2FjaGUucmVjb3JkLmV4Y2VlZGVkVGhyZXNob2xkVHlwZSxcbiAgICAgICAgICAgICAgICAgICAgdGhyZXNob2xkRXhjZWVkZWQ6IGNhY2hlLnJlY29yZC50aHJlc2hvbGRFeGNlZWRlZCxcbiAgICAgICAgICAgICAgICAgICAgdHJhZGVEYXlDb3VudDogY2FjaGUucmVjb3JkLnRyYWRlRGF5Q291bnRcbiAgICAgICAgICAgICAgICAgIH1dKTtcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgT2JqZWN0LnZhbHVlcyhnZXRUcmFja2VyTGlzdGVuZXJzKCkpLmZvckVhY2godHJhY2tlckxpc3RlbmVyID0+IHtcbiAgICAgICAgICAgIHRyYWNrZXJMaXN0ZW5lci5vbkVycm9yKGVycik7XG4gICAgICAgICAgfSk7XG4gICAgICAgICAgdGhpcy5fbG9nZ2VyLmVycm9yKCdFcnJvciBwcm9jZXNzaW5nIG9uU3ltYm9sUHJpY2VVcGRhdGVkIGV2ZW50IGZvciAnICtcbiAgICAgICAgICBgcGVyaW9kIHN0YXRpc3RpY3MgbGlzdGVuZXIgZm9yIGFjY291bnQgJHthY2NvdW50SWR9YCwgZXJyKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBhc3luYyBvbkRlYWxBZGRlZChpbnN0YW5jZUluZGV4LCBkZWFsKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgaWYoIWNhY2hlLmxhc3RQZXJpb2QgfHwgIU9iamVjdC5rZXlzKGNhY2hlLmxhc3RQZXJpb2QpLmxlbmd0aCkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgIH1cbiAgICAgICAgICBpZihkZWFsLnR5cGUgPT09ICdERUFMX1RZUEVfQkFMQU5DRScpIHtcbiAgICAgICAgICAgIGNhY2hlLmVxdWl0eUFkanVzdG1lbnRzW2RlYWwuaWRdID0gZGVhbC5wcm9maXQ7XG4gICAgICAgICAgfVxuICAgICAgICAgIGNvbnN0IGlnbm9yZWREZWFsVHlwZXMgPSBbJ0RFQUxfVFlQRV9CQUxBTkNFJywgJ0RFQUxfVFlQRV9DUkVESVQnXTtcbiAgICAgICAgICBpZighaWdub3JlZERlYWxUeXBlcy5pbmNsdWRlcyhkZWFsLnR5cGUpKSB7XG4gICAgICAgICAgICBjb25zdCB0aW1lRGlmZiA9IG5ldyBEYXRlKGRlYWwudGltZSkuZ2V0VGltZSgpIC0gbmV3IERhdGUoZGVhbC5icm9rZXJUaW1lKS5nZXRUaW1lKCk7XG4gICAgICAgICAgICBjb25zdCBzdGFydFNlYXJjaERhdGUgPSBuZXcgRGF0ZShuZXcgRGF0ZShjYWNoZS5sYXN0UGVyaW9kLnN0YXJ0QnJva2VyVGltZSkuZ2V0VGltZSgpICsgdGltZURpZmYpO1xuICAgICAgICAgICAgY29uc3QgZGVhbHMgPSBjb25uZWN0aW9uLmhpc3RvcnlTdG9yYWdlLmdldERlYWxzQnlUaW1lUmFuZ2Uoc3RhcnRTZWFyY2hEYXRlLCBuZXcgRGF0ZSg4NjQwMDAwMDAwMDAwMDAwKSlcbiAgICAgICAgICAgICAgLmZpbHRlcihkZWFsSXRlbSA9PiAhaWdub3JlZERlYWxUeXBlcy5pbmNsdWRlcyhkZWFsSXRlbS50eXBlKSk7XG4gICAgICAgICAgICBkZWFscy5wdXNoKGRlYWwpO1xuICAgICAgICAgICAgY29uc3QgdHJhZGVkRGF5cyA9IHt9O1xuICAgICAgICAgICAgZGVhbHMuZm9yRWFjaChkZWFsSXRlbSA9PiB7XG4gICAgICAgICAgICAgIHRyYWRlZERheXNbZGVhbEl0ZW0uYnJva2VyVGltZS5zbGljZSgwLCAxMCldID0gdHJ1ZTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgY29uc3QgdHJhZGVEYXlDb3VudCA9IE9iamVjdC5rZXlzKHRyYWRlZERheXMpLmxlbmd0aDtcbiAgICAgICAgICAgIGlmKGNhY2hlLnJlY29yZC50cmFkZURheUNvdW50ICE9PSB0cmFkZURheUNvdW50KSB7XG4gICAgICAgICAgICAgIGNhY2hlLnJlY29yZC50cmFkZURheUNvdW50ID0gdHJhZGVEYXlDb3VudDtcbiAgICAgICAgICAgICAgT2JqZWN0LnZhbHVlcyhnZXRUcmFja2VyTGlzdGVuZXJzKCkpLmZvckVhY2godHJhY2tlckxpc3RlbmVyID0+IHtcbiAgICAgICAgICAgICAgICB0cmFja2VyTGlzdGVuZXIub25QZXJpb2RTdGF0aXN0aWNzVXBkYXRlZChbe1xuICAgICAgICAgICAgICAgICAgc3RhcnRCcm9rZXJUaW1lOiBjYWNoZS5sYXN0UGVyaW9kLnN0YXJ0QnJva2VyVGltZSxcbiAgICAgICAgICAgICAgICAgIGVuZEJyb2tlclRpbWU6IGNhY2hlLmxhc3RQZXJpb2QuZW5kQnJva2VyVGltZSxcbiAgICAgICAgICAgICAgICAgIGluaXRpYWxCYWxhbmNlOiBjYWNoZS5sYXN0UGVyaW9kLmluaXRpYWxCYWxhbmNlLFxuICAgICAgICAgICAgICAgICAgbWF4QWJzb2x1dGVEcmF3ZG93bjogY2FjaGUucmVjb3JkLm1heEFic29sdXRlRHJhd2Rvd24sXG4gICAgICAgICAgICAgICAgICBtYXhBYnNvbHV0ZVByb2ZpdDogY2FjaGUucmVjb3JkLm1heEFic29sdXRlUHJvZml0LFxuICAgICAgICAgICAgICAgICAgbWF4RHJhd2Rvd25UaW1lOiBjYWNoZS5yZWNvcmQubWF4RHJhd2Rvd25UaW1lLFxuICAgICAgICAgICAgICAgICAgbWF4UHJvZml0VGltZTogY2FjaGUucmVjb3JkLm1heFByb2ZpdFRpbWUsXG4gICAgICAgICAgICAgICAgICBtYXhSZWxhdGl2ZURyYXdkb3duOiBjYWNoZS5yZWNvcmQubWF4UmVsYXRpdmVEcmF3ZG93bixcbiAgICAgICAgICAgICAgICAgIG1heFJlbGF0aXZlUHJvZml0OiBjYWNoZS5yZWNvcmQubWF4UmVsYXRpdmVQcm9maXQsXG4gICAgICAgICAgICAgICAgICBwZXJpb2Q6IGNhY2hlLmxhc3RQZXJpb2QucGVyaW9kLFxuICAgICAgICAgICAgICAgICAgZXhjZWVkZWRUaHJlc2hvbGRUeXBlOiBjYWNoZS5yZWNvcmQuZXhjZWVkZWRUaHJlc2hvbGRUeXBlLFxuICAgICAgICAgICAgICAgICAgdGhyZXNob2xkRXhjZWVkZWQ6IGNhY2hlLnJlY29yZC50aHJlc2hvbGRFeGNlZWRlZCxcbiAgICAgICAgICAgICAgICAgIHRyYWRlRGF5Q291bnQ6IGNhY2hlLnJlY29yZC50cmFkZURheUNvdW50XG4gICAgICAgICAgICAgICAgfV0pO1xuICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgIE9iamVjdC52YWx1ZXMoZ2V0VHJhY2tlckxpc3RlbmVycygpKS5mb3JFYWNoKHRyYWNrZXJMaXN0ZW5lciA9PiB7XG4gICAgICAgICAgICB0cmFja2VyTGlzdGVuZXIub25FcnJvcihlcnIpO1xuICAgICAgICAgIH0pO1xuICAgICAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcignRXJyb3IgcHJvY2Vzc2luZyBvbkRlYWxBZGRlZCBldmVudCBmb3IgJyArXG4gICAgICAgICAgYHBlcmlvZCBzdGF0aXN0aWNzIGxpc3RlbmVyIGZvciBhY2NvdW50ICR7YWNjb3VudElkfWAsIGVycik7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICBjb25zdCBhY2NvdW50ID0gYXdhaXQgdGhpcy5fbWV0YUFwaS5tZXRhdHJhZGVyQWNjb3VudEFwaS5nZXRBY2NvdW50KGFjY291bnRJZCk7XG4gICAgY29uc3QgdHJhY2tlciA9IGF3YWl0IGVxdWl0eVRyYWNraW5nQ2xpZW50LmdldFRyYWNrZXIoYWNjb3VudElkLCB0cmFja2VySWQpO1xuICAgIGNhY2hlLnRyYWNrZXJEYXRhID0gdHJhY2tlcjtcbiAgICBpZighdGhpcy5fcGVyaW9kU3RhdGlzdGljc0xpc3RlbmVyc1thY2NvdW50SWRdKSB7XG4gICAgICB0aGlzLl9wZXJpb2RTdGF0aXN0aWNzTGlzdGVuZXJzW2FjY291bnRJZF0gPSB7fTtcbiAgICB9XG4gICAgaWYoIXRoaXMuX3BlcmlvZFN0YXRpc3RpY3NMaXN0ZW5lcnNbYWNjb3VudElkXVt0cmFja2VySWRdKSB7XG4gICAgICB0aGlzLl9wZXJpb2RTdGF0aXN0aWNzTGlzdGVuZXJzW2FjY291bnRJZF1bdHJhY2tlcklkXSA9IHt9O1xuICAgIH1cbiAgICBjb25zdCBhY2NvdW50TGlzdGVuZXJzID0gdGhpcy5fcGVyaW9kU3RhdGlzdGljc0xpc3RlbmVyc1thY2NvdW50SWRdW3RyYWNrZXJJZF07XG4gICAgYWNjb3VudExpc3RlbmVyc1tsaXN0ZW5lcklkXSA9IGxpc3RlbmVyO1xuICAgIHRoaXMuX2FjY291bnRzQnlMaXN0ZW5lcklkW2xpc3RlbmVySWRdID0gYWNjb3VudElkO1xuICAgIHRoaXMuX3RyYWNrZXJzQnlMaXN0ZW5lcklkW2xpc3RlbmVySWRdID0gdHJhY2tlcklkO1xuICAgIGxldCBpc0RlcGxveWVkID0gZmFsc2U7XG4gICAgd2hpbGUoIWlzRGVwbG95ZWQpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IGFjY291bnQud2FpdERlcGxveWVkKCk7XG4gICAgICAgIGlzRGVwbG95ZWQgPSB0cnVlOyAgXG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgbGlzdGVuZXIub25FcnJvcihlcnIpO1xuICAgICAgICB0aGlzLl9sb2dnZXIuZXJyb3IoYEVycm9yIHdhaXQgZm9yIGFjY291bnQgJHthY2NvdW50SWR9IHRvIGRlcGxveSwgcmV0cnlpbmdgLCBlcnIpO1xuICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZShyZXMgPT4gc2V0VGltZW91dChyZXMsIHJldHJ5SW50ZXJ2YWxJblNlY29uZHMgKiAxMDAwKSk7IFxuICAgICAgICByZXRyeUludGVydmFsSW5TZWNvbmRzID0gTWF0aC5taW4ocmV0cnlJbnRlcnZhbEluU2Vjb25kcyAqIDIsIDMwMCk7XG4gICAgICB9XG4gICAgfVxuICAgIGlmKCF0aGlzLl9wZXJpb2RTdGF0aXN0aWNzQ29ubmVjdGlvbnNbYWNjb3VudElkXSkge1xuICAgICAgcmV0cnlJbnRlcnZhbEluU2Vjb25kcyA9IHRoaXMuX3JldHJ5SW50ZXJ2YWxJblNlY29uZHM7XG4gICAgICBjb25uZWN0aW9uID0gYWNjb3VudC5nZXRTdHJlYW1pbmdDb25uZWN0aW9uKCk7XG4gICAgICBjb25zdCBzeW5jTGlzdGVuZXIgPSBuZXcgUGVyaW9kU3RhdGlzdGljc1N0cmVhbUxpc3RlbmVyKCk7XG4gICAgICBjb25uZWN0aW9uLmFkZFN5bmNocm9uaXphdGlvbkxpc3RlbmVyKHN5bmNMaXN0ZW5lcik7XG4gICAgICB0aGlzLl9wZXJpb2RTdGF0aXN0aWNzQ29ubmVjdGlvbnNbYWNjb3VudElkXSA9IGNvbm5lY3Rpb247XG4gICAgICB0aGlzLl9zeW5jTGlzdGVuZXJzW3RyYWNrZXJJZF0gPSBzeW5jTGlzdGVuZXI7XG4gICAgICBcbiAgICAgIGxldCBpc1N5bmNocm9uaXplZCA9IGZhbHNlO1xuICAgICAgd2hpbGUoIWlzU3luY2hyb25pemVkKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgYXdhaXQgY29ubmVjdGlvbi5jb25uZWN0KCk7XG4gICAgICAgICAgYXdhaXQgY29ubmVjdGlvbi53YWl0U3luY2hyb25pemVkKCk7XG4gICAgICAgICAgaXNTeW5jaHJvbml6ZWQgPSB0cnVlO1xuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICBsaXN0ZW5lci5vbkVycm9yKGVycik7XG4gICAgICAgICAgdGhpcy5fbG9nZ2VyLmVycm9yKCdFcnJvciBjb25maWd1cmluZyBwZXJpb2Qgc3RhdGlzdGljcyBzdHJlYW0gbGlzdGVuZXIgZm9yICcgK1xuICAgICAgICAgIGBhY2NvdW50ICR7YWNjb3VudElkfSwgcmV0cnlpbmdgLCBlcnIpO1xuICAgICAgICAgIGF3YWl0IG5ldyBQcm9taXNlKHJlcyA9PiBzZXRUaW1lb3V0KHJlcywgcmV0cnlJbnRlcnZhbEluU2Vjb25kcyAqIDEwMDApKTsgXG4gICAgICAgICAgcmV0cnlJbnRlcnZhbEluU2Vjb25kcyA9IE1hdGgubWluKHJldHJ5SW50ZXJ2YWxJblNlY29uZHMgKiAyLCAzMDApO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICByZXRyeUludGVydmFsSW5TZWNvbmRzID0gdGhpcy5fcmV0cnlJbnRlcnZhbEluU2Vjb25kcztcbiAgICB9IGVsc2Uge1xuICAgICAgY29ubmVjdGlvbiA9IHRoaXMuX3BlcmlvZFN0YXRpc3RpY3NDb25uZWN0aW9uc1thY2NvdW50SWRdO1xuICAgICAgaWYobmV3VHJhY2tlcikge1xuICAgICAgICBjb25zdCBzeW5jTGlzdGVuZXIgPSBuZXcgUGVyaW9kU3RhdGlzdGljc1N0cmVhbUxpc3RlbmVyKCk7XG4gICAgICAgIGNvbm5lY3Rpb24uYWRkU3luY2hyb25pemF0aW9uTGlzdGVuZXIoc3luY0xpc3RlbmVyKTtcbiAgICAgICAgdGhpcy5fc3luY0xpc3RlbmVyc1t0cmFja2VySWRdID0gc3luY0xpc3RlbmVyO1xuICAgICAgfVxuICAgICAgaWYoIWNvbm5lY3Rpb24uaGVhbHRoTW9uaXRvci5oZWFsdGhTdGF0dXMuc3luY2hyb25pemVkKSB7XG4gICAgICAgIGlmKCF0aGlzLl9wZW5kaW5nSW5pdGFsaXphdGlvblJlc29sdmVzW2FjY291bnRJZF0pIHtcbiAgICAgICAgICB0aGlzLl9wZW5kaW5nSW5pdGFsaXphdGlvblJlc29sdmVzW2FjY291bnRJZF0gPSBbXTtcbiAgICAgICAgfVxuICAgICAgICBsZXQgcmVzb2x2ZUluaXRpYWxpemU7XG4gICAgICAgIGxldCBpbml0aWFsaXplUHJvbWlzZSA9IG5ldyBQcm9taXNlKChyZXMsIHJlaikgPT4ge1xuICAgICAgICAgIHJlc29sdmVJbml0aWFsaXplID0gcmVzO1xuICAgICAgICB9KTtcbiAgICAgICAgdGhpcy5fcGVuZGluZ0luaXRhbGl6YXRpb25SZXNvbHZlc1thY2NvdW50SWRdLnB1c2gocmVzb2x2ZUluaXRpYWxpemUpO1xuICAgICAgICBhd2FpdCBpbml0aWFsaXplUHJvbWlzZTtcbiAgICAgIH1cbiAgICB9XG4gICAgXG4gICAgbGV0IGluaXRpYWxEYXRhID0gW107XG4gICAgY29uc3QgZmV0Y2hJbml0aWFsRGF0YSA9IGFzeW5jICgpID0+IHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGluaXRpYWxEYXRhID0gYXdhaXQgZXF1aXR5VHJhY2tpbmdDbGllbnQuZ2V0VHJhY2tpbmdTdGF0aXN0aWNzKGFjY291bnRJZCwgdHJhY2tlcklkLFxuICAgICAgICAgIHVuZGVmaW5lZCwgdW5kZWZpbmVkLCB0cnVlKTtcbiAgICAgICAgaWYoaW5pdGlhbERhdGEubGVuZ3RoKSB7XG4gICAgICAgICAgY29uc3QgbGFzdEl0ZW0gPSBpbml0aWFsRGF0YVswXTtcbiAgICAgICAgICBpZih0aGlzLl9mZXRjaEluaXRpYWxEYXRhSW50ZXJ2YWxJZFtsaXN0ZW5lcklkXSkge1xuICAgICAgICAgICAgY2xlYXJJbnRlcnZhbCh0aGlzLl9mZXRjaEluaXRpYWxEYXRhSW50ZXJ2YWxJZFtsaXN0ZW5lcklkXSk7XG4gICAgICAgICAgICBkZWxldGUgdGhpcy5fZmV0Y2hJbml0aWFsRGF0YUludGVydmFsSWRbbGlzdGVuZXJJZF07XG4gICAgICAgICAgfVxuICAgICAgICAgIGxpc3RlbmVyLm9uUGVyaW9kU3RhdGlzdGljc1VwZGF0ZWQoaW5pdGlhbERhdGEpO1xuICAgICAgICAgIGNhY2hlLmxhc3RQZXJpb2QgPSB7XG4gICAgICAgICAgICBzdGFydEJyb2tlclRpbWU6IGxhc3RJdGVtLnN0YXJ0QnJva2VyVGltZSxcbiAgICAgICAgICAgIGVuZEJyb2tlclRpbWU6IGxhc3RJdGVtLmVuZEJyb2tlclRpbWUsXG4gICAgICAgICAgICBwZXJpb2Q6IGxhc3RJdGVtLnBlcmlvZCxcbiAgICAgICAgICAgIGluaXRpYWxCYWxhbmNlOiBsYXN0SXRlbS5pbml0aWFsQmFsYW5jZSxcbiAgICAgICAgICAgIG1heERyYXdkb3duVGltZTogbGFzdEl0ZW0ubWF4RHJhd2Rvd25UaW1lLFxuICAgICAgICAgICAgbWF4QWJzb2x1dGVEcmF3ZG93bjogbGFzdEl0ZW0ubWF4QWJzb2x1dGVEcmF3ZG93bixcbiAgICAgICAgICAgIG1heFJlbGF0aXZlRHJhd2Rvd246IGxhc3RJdGVtLm1heFJlbGF0aXZlRHJhd2Rvd24sXG4gICAgICAgICAgICBtYXhQcm9maXRUaW1lOiBsYXN0SXRlbS5tYXhQcm9maXRUaW1lLFxuICAgICAgICAgICAgbWF4QWJzb2x1dGVQcm9maXQ6IGxhc3RJdGVtLm1heEFic29sdXRlUHJvZml0LFxuICAgICAgICAgICAgbWF4UmVsYXRpdmVQcm9maXQ6IGxhc3RJdGVtLm1heFJlbGF0aXZlUHJvZml0LFxuICAgICAgICAgICAgdGhyZXNob2xkRXhjZWVkZWQ6IGxhc3RJdGVtLnRocmVzaG9sZEV4Y2VlZGVkLFxuICAgICAgICAgICAgZXhjZWVkZWRUaHJlc2hvbGRUeXBlOiBsYXN0SXRlbS5leGNlZWRlZFRocmVzaG9sZFR5cGUsXG4gICAgICAgICAgICB0cmFkZURheUNvdW50OiBsYXN0SXRlbS50cmFkZURheUNvdW50XG4gICAgICAgICAgfTtcbiAgICAgICAgICBjYWNoZS5yZWNvcmQgPSBjYWNoZS5sYXN0UGVyaW9kO1xuICAgICAgICB9XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgbGlzdGVuZXIub25FcnJvcihlcnIpO1xuICAgICAgICB0aGlzLl9sb2dnZXIuZXJyb3IoYEZhaWxlZCB0byBpbml0aWFsaXplIHRyYWNraW5nIHN0YXRpc3RpY3MgZGF0YSBmb3IgYWNjb3VudCAke2FjY291bnRJZH1gLCBlcnIpO1xuICAgICAgICBhd2FpdCBuZXcgUHJvbWlzZShyZXMgPT4gc2V0VGltZW91dChyZXMsIHJldHJ5SW50ZXJ2YWxJblNlY29uZHMgKiAxMDAwKSk7IFxuICAgICAgICByZXRyeUludGVydmFsSW5TZWNvbmRzID0gTWF0aC5taW4ocmV0cnlJbnRlcnZhbEluU2Vjb25kcyAqIDIsIDMwMCk7XG4gICAgICB9XG4gICAgfTtcbiAgICByZXRyeUludGVydmFsSW5TZWNvbmRzID0gdGhpcy5fcmV0cnlJbnRlcnZhbEluU2Vjb25kcztcbiAgICB0aGlzLl9mZXRjaEluaXRpYWxEYXRhSW50ZXJ2YWxJZFtsaXN0ZW5lcklkXSA9IFxuICAgICAgc2V0SW50ZXJ2YWwoZmV0Y2hJbml0aWFsRGF0YSwgcmV0cnlJbnRlcnZhbEluU2Vjb25kcyAqIDEwMDAgKiAyICogNjApO1xuICAgIGZldGNoSW5pdGlhbERhdGEoKTtcblxuICAgIHJldHVybiBsaXN0ZW5lcklkO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlbW92ZXMgcGVyaW9kIHN0YXRpc3RpY3MgZXZlbnQgbGlzdGVuZXIgYnkgaWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGxpc3RlbmVySWQgbGlzdGVuZXIgaWQgXG4gICAqL1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgY29tcGxleGl0eVxuICByZW1vdmVQZXJpb2RTdGF0aXN0aWNzTGlzdGVuZXIobGlzdGVuZXJJZCkge1xuICAgIGlmKHRoaXMuX2FjY291bnRzQnlMaXN0ZW5lcklkW2xpc3Rl