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
JavaScript
"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