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)
395 lines (394 loc) • 51.3 kB
JavaScript
'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);
});
};
}
import TrackerEventListenerManager from './trackerEventListenerManager';
import PeriodStatisticsStreamManager from './periodStatisticsStreamManager';
import EquityChartStreamManager from './equityChartStreamManager';
import EquityBalanceStreamManager from './equityBalanceStreamManager';
import moment from 'moment';
let EquityTrackingClient = class EquityTrackingClient {
/**
* Tracker configuration update
* @typedef {Object} TrackerUpdate
* @property {String} name unique tracker name
*/ /**
* Period length to track profit and drawdown for
* @typedef {'day' | 'date' | 'week' | 'week-to-date' | 'month' | 'month-to-date' | 'quarter' | 'quarter-to-date' |
* 'year' | 'year-to-date' | 'lifetime'} Period
*/ /**
* New tracker configuration
* @typedef {TrackerUpdate} NewTracker
* @property {String} [startBrokerTime] time to start tracking from in broker timezone, YYYY-MM-DD HH:mm:ss.SSS format
* @property {String} [endBrokerTime] time to end tracking at in broker timezone, YYYY-MM-DD HH:mm:ss.SSS format
* @property {Period} period period to track profit and drawdown for
* @property {Number} [relativeDrawdownThreshold] relative drawdown threshold after which tracker event is generated,
* a fraction of 1
* @property {Number} [absoluteDrawdownThreshold] absolute drawdown threshold after which tracker event is generated,
* should be greater than 0
* @property {Number} [relativeProfitThreshold] relative profit threshold after which tracker event is generated,
* a fraction of 1
* @property {Number} [absoluteProfitThreshold] absolute profit threshold after which tracker event is generated,
* should be greater than 0
*/ /**
* Tracker id
* @typedef {Object} TrackerId
* @property {String} id unique tracker id
*/ /**
* Creates a profit/drawdown tracker. See
* https://metaapi.cloud/docs/risk-management/restApi/api/createTracker/
* @param {String} accountId id of the MetaApi account
* @param {NewTracker} tracker profit/drawdown tracker
* @return {Promise<TrackerId>} promise resolving with profit/drawdown tracker id
*/ createTracker(accountId, tracker) {
return this._domainClient.requestApi({
url: `/users/current/accounts/${accountId}/trackers`,
headers: {
'auth-token': this._domainClient.token,
'api-version': '1'
},
method: 'POST',
data: tracker
});
}
/**
* Tracker configuration
* @typedef {NewTracker} Tracker
* @property {String} _id unique tracker id
*/ /**
* Returns trackers defined for an account. See
* https://metaapi.cloud/docs/risk-management/restApi/api/getTrackers/
* @param {String} accountId id of the MetaApi account
* @return {Promise<Tracker[]>} promise resolving with trackers
*/ getTrackers(accountId) {
return this._domainClient.requestApi({
url: `/users/current/accounts/${accountId}/trackers`,
headers: {
'auth-token': this._domainClient.token,
'api-version': '1'
},
method: 'GET'
});
}
/**
* Returns profit/drawdown tracker by account and id. See
* https://metaapi.cloud/docs/risk-management/restApi/api/getTracker/
* @param {string} accountId id of the MetaApi account
* @param {string} id tracker id
* @returns {Promise<Tracker>} promise resolving with profit/drawdown tracker found
*/ getTracker(accountId, id) {
return this._domainClient.requestApi({
url: `/users/current/accounts/${accountId}/trackers/${id}`,
headers: {
'auth-token': this._domainClient.token,
'api-version': '1'
},
method: 'GET'
});
}
/**
* Returns profit/drawdown tracker by account and name
* @param {string} accountId id of the MetaApi account
* @param {string} name tracker name
* @returns {Promise<Tracker>} promise resolving with profit/drawdown tracker found
*/ getTrackerByName(accountId, name) {
return this._domainClient.requestApi({
url: `/users/current/accounts/${accountId}/trackers/name/${encodeURIComponent(name)}`,
headers: {
'auth-token': this._domainClient.token,
'api-version': '1'
},
method: 'GET'
});
}
/**
* Updates profit/drawdown tracker. See
* https://metaapi.cloud/docs/risk-management/restApi/api/updateTracker/
* @param {String} accountId id of the MetaApi account
* @param {String} id id of the tracker
* @param {TrackerUpdate} update tracker update
* @return {Promise} promise resolving when profit/drawdown tracker updated
*/ updateTracker(accountId, id, update) {
return this._domainClient.requestApi({
url: `/users/current/accounts/${accountId}/trackers/${id}`,
method: 'PUT',
data: update
});
}
/**
* Removes profit/drawdown tracker. See
* https://metaapi.cloud/docs/risk-management/restApi/api/removeTracker/
* @param {String} accountId id of the MetaApi account
* @param {String} id id of the tracker
* @return {Promise} promise resolving when profit/drawdown tracker removed
*/ deleteTracker(accountId, id) {
return this._domainClient.requestApi({
url: `/users/current/accounts/${accountId}/trackers/${id}`,
method: 'DELETE'
});
}
/**
* Type of the exceeded threshold
* @typedef {'profit' | 'drawdown'} ExceededThresholdType
*/ /**
* Profit/drawdown threshold exceeded event model
* @typedef {Object} TrackerEvent
* @property {Number} sequenceNumber event unique sequence number
* @property {String} accountId MetaApi account id
* @property {String} trackerId profit/drawdown tracker id
* @property {String} startBrokerTime profit/drawdown tracking period start time in broker timezone,
* in YYYY-MM-DD HH:mm:ss.SSS format
* @property {String} [endBrokerTime] profit/drawdown tracking period end time in broker timezone,
* in YYYY-MM-DD HH:mm:ss.SSS format
* @property {Period} period profit/drawdown tracking period
* @property {String} brokerTime profit/drawdown threshold exceeded event time in broker timezone,
* in YYY-MM-DD HH:mm:ss.SSS format
* @property {Number} absoluteDrawdown absolute drawdown value which was observed when the profit or drawdown
* threshold was exceeded
* @property {Number} relativeDrawdown relative drawdown value which was observed when the profit or drawdown
* threshold was exceeded
* @property {Number} absoluteProfit absolute profit value which was observed when the profit or drawdown
* threshold was exceeded
* @property {Number} relativeProfit relative profit value which was observed when the profit or drawdown
* threshold was exceeded
* @property {ExceededThresholdType} exceededThresholdType type of the exceeded threshold
*/ /**
* Returns tracker events by broker time range. See
* https://metaapi.cloud/docs/risk-management/restApi/api/getTrackerEvents/
* @param {String} [startBrokerTime] value of the event time in broker timezone to start loading data from, inclusive,
* in 'YYYY-MM-DD HH:mm:ss.SSS format
* @param {String} [endBrokerTime] value of the event time in broker timezone to end loading data at, inclusive,
* in 'YYYY-MM-DD HH:mm:ss.SSS format
* @param {String} [accountId] id of the MetaApi account
* @param {String} [trackerId] id of the tracker
* @param {Number} [limit] pagination limit, default is 1000
* @return {Promise<TrackerEvent[]>} promise resolving with tracker events
*/ getTrackerEvents(startBrokerTime, endBrokerTime, accountId, trackerId, limit) {
return this._domainClient.requestApi({
url: '/users/current/tracker-events/by-broker-time',
params: {
startBrokerTime,
endBrokerTime,
accountId,
trackerId,
limit
},
method: 'GET'
});
}
/**
* Adds a tracker event listener and creates a job to make requests
* @param {TrackerEventListener} listener tracker event listener
* @param {String} [accountId] account id
* @param {String} [trackerId] tracker id
* @param {Number} [sequenceNumber] sequence number
* @return {String} listener id
*/ addTrackerEventListener(listener, accountId, trackerId, sequenceNumber) {
return this._trackerEventListenerManager.addTrackerEventListener(listener, accountId, trackerId, sequenceNumber);
}
/**
* Removes tracker event listener and cancels the event stream
* @param {String} listenerId tracker event listener id
*/ removeTrackerEventListener(listenerId) {
this._trackerEventListenerManager.removeTrackerEventListener(listenerId);
}
/**
* Period statistics
* @typedef {Object} PeriodStatistics
* @property {String} startBrokerTime period start time in broker timezone, in YYYY-MM-DD HH:mm:ss format
* @property {String} [endBrokerTime] period end time in broker timezone, in YYYY-MM-DD HH:mm:ss format
* @property {Period} period period length
* @property {Number} initialBalance balance at period start time
* @property {String} [maxDrawdownTime] time max drawdown was observed at in broker timezone,
* in YYYY-MM-DD HH:mm:ss format
* @property {Number} [maxAbsoluteDrawdown] the value of maximum absolute drawdown observed
* @property {Number} [maxRelativeDrawdown] the value of maximum relative drawdown observed
* @property {String} [maxProfitTime] time max profit was observed at in broker timezone,
* in YYYY-MM-DD HH:mm:ss format
* @property {Number} [maxAbsoluteProfit] the value of maximum absolute profit observed
* @property {Number} [maxRelativeProfit] the value of maximum relative profit observed
* @property {Boolean} thresholdExceeded the flag indicating that max allowed total drawdown was exceeded
* @property {ExceededThresholdType} exceededThresholdType type of the exceeded threshold
* @property {Number} [balanceAdjustment] balance adjustment applied to equity for tracking drawdown/profit
* @property {Number} [tradeDayCount] count of days when trades were performed during the period
*/ /**
* Returns account profit and drawdown tracking statistics by tracker id. See
* https://metaapi.cloud/docs/risk-management/restApi/api/getTrackingStats/
* @param {String} accountId id of MetaAPI account
* @param {String} trackerId id of the tracker
* @param {String} [startTime] time to start loading stats from, default is current time. Note that stats is loaded in
* backwards direction
* @param {Number} [limit] number of records to load, default is 1
* @param {Boolean} [realTime] if true, real-time data will be requested
* @return {Promise<PeriodStatistics[]>} promise resolving with profit and drawdown statistics
*/ getTrackingStatistics(accountId, trackerId, startTime, limit, realTime = false) {
return this._domainClient.requestApi({
url: `/users/current/accounts/${accountId}/trackers/${trackerId}/statistics`,
headers: {
'auth-token': this._domainClient.token,
'api-version': '1'
},
params: {
startTime,
limit,
realTime
},
method: 'GET'
});
}
/**
* Adds a period statistics event listener
* @param {PeriodStatisticsListener} listener period statistics event listener
* @param {String} accountId account id
* @param {String} trackerId tracker id
* @returns {Promise<String>} listener id
*/ addPeriodStatisticsListener(listener, accountId, trackerId) {
return this._periodStatisticsStreamManager.addPeriodStatisticsListener(listener, accountId, trackerId);
}
/**
* Removes period statistics event listener by id
* @param {String} listenerId listener id
*/ removePeriodStatisticsListener(listenerId) {
this._periodStatisticsStreamManager.removePeriodStatisticsListener(listenerId);
}
/**
* Equity chart item
* @typedef {Object} EquityChartItem
* @property {String} startBrokerTime start time of a chart item as per broker timezone, in YYYY-MM-DD HH:mm:ss format
* @property {String} endBrokerTime end time of a chart item as per broker timezone, in YYYY-MM-DD HH:mm:ss format
* @property {Number} averageBalance average balance value during the period
* @property {Number} minBalance minimum balance value during the period
* @property {Number} maxBalance maximum balance value during the period
* @property {Number} averageEquity average equity value during the period
* @property {Number} minEquity minimum equity value during the period
* @property {Number} maxEquity maximum equity value during the period
* @property {Number} startBalance starting balance value observed during the period
* @property {Number} startEquity starting equity value observed during the period
* @property {Number} lastBalance last balance value observed during the period
* @property {Number} lastEquity last equity value observed during the period
*/ /**
* Returns equity chart by account id. See
* https://metaapi.cloud/docs/risk-management/restApi/api/getEquityChart/
* @param {String} accountId metaApi account id
* @param {String} [startTime] starting broker time in YYYY-MM-DD HH:mm:ss format
* @param {String} [endTime] ending broker time in YYYY-MM-DD HH:mm:ss format
* @param {Boolean} [realTime] if true, real-time data will be requested
* @param {Boolean} [fillSkips] if true, skipped records will be automatically filled based on existing ones
* @return {Promise<EquityChartItem[]>} promise resolving with equity chart
*/ getEquityChart(accountId, startTime, endTime, realTime = false, fillSkips = false) {
var _this = this;
return _async_to_generator(function*() {
const records = yield _this._domainClient.requestApi({
url: `/users/current/accounts/${accountId}/equity-chart`,
headers: {
'auth-token': _this._domainClient.token,
'api-version': '1'
},
params: {
startTime,
endTime,
realTime
},
method: 'GET'
});
if (fillSkips) {
let i = 0;
while(i < records.length - 1){
const timeDiff = new Date(records[i + 1].startBrokerTime).getTime() - new Date(records[i].startBrokerTime).getTime();
if (timeDiff > 60 * 60 * 1000 && records[i].lastBalance !== undefined) {
const recordCopy = JSON.parse(JSON.stringify(records[i]));
recordCopy.minEquity = recordCopy.lastEquity;
recordCopy.maxEquity = recordCopy.lastEquity;
recordCopy.averageEquity = recordCopy.lastEquity;
recordCopy.minBalance = recordCopy.lastBalance;
recordCopy.maxBalance = recordCopy.lastBalance;
recordCopy.averageBalance = recordCopy.lastBalance;
const startBrokerTime = new Date(recordCopy.startBrokerTime);
startBrokerTime.setUTCHours(startBrokerTime.getUTCHours() + 1);
startBrokerTime.setUTCMinutes(0);
startBrokerTime.setUTCSeconds(0);
startBrokerTime.setUTCMilliseconds(0);
recordCopy.startBrokerTime = moment(startBrokerTime).format('YYYY-MM-DD HH:mm:ss.SSS');
startBrokerTime.setUTCHours(startBrokerTime.getUTCHours() + 1);
startBrokerTime.setUTCMilliseconds(-1);
recordCopy.endBrokerTime = moment(startBrokerTime).format('YYYY-MM-DD HH:mm:ss.SSS');
recordCopy.brokerTime = recordCopy.endBrokerTime;
records.splice(i + 1, 0, recordCopy);
}
i++;
}
}
return records;
})();
}
/**
* Adds an equity chart event listener
* @param {EquityChartListener} listener equity chart event listener
* @param {String} accountId account id
* @param {Date} [startTime] date to start tracking from
* @returns {Promise<string>} listener id
*/ addEquityChartListener(listener, accountId, startTime) {
return this._equityChartStreamManager.addEquityChartListener(listener, accountId, startTime);
}
/**
* Removes equity chart event listener by id
* @param {String} listenerId equity chart listener id
*/ removeEquityChartListener(listenerId) {
this._equityChartStreamManager.removeEquityChartListener(listenerId);
}
/**
* Adds an equity balance event listener
* @param {EquityBalanceListener} listener equity balance event listener
* @param {string} accountId account id
* @returns {Promise<string>} listener id
*/ addEquityBalanceListener(listener, accountId) {
return this._equityBalanceStreamManager.addEquityBalanceListener(listener, accountId);
}
/**
* Removes equity balance event listener by id
* @param {string} listenerId equity balance listener id
*/ removeEquityBalanceListener(listenerId) {
this._equityBalanceStreamManager.removeEquityBalanceListener(listenerId);
}
/**
* Constructs RiskManagement equity tracking API client instance
* @param {DomainClient} domainClient domain client
* @param {MetaApi} metaApi metaApi SDK instance
*/ constructor(domainClient, metaApi){
this._domainClient = domainClient;
this._trackerEventListenerManager = new TrackerEventListenerManager(domainClient);
this._equityBalanceStreamManager = new EquityBalanceStreamManager(domainClient, metaApi);
this._periodStatisticsStreamManager = new PeriodStatisticsStreamManager(domainClient, this, metaApi);
this._equityChartStreamManager = new EquityChartStreamManager(domainClient, this, metaApi);
}
};
/**
* metaapi.cloud RiskManagement equity tracking API client (see https://metaapi.cloud/docs/risk-management/)
*/ export { EquityTrackingClient as default };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBUcmFja2VyRXZlbnRMaXN0ZW5lck1hbmFnZXIgZnJvbSAnLi90cmFja2VyRXZlbnRMaXN0ZW5lck1hbmFnZXInO1xuaW1wb3J0IFBlcmlvZFN0YXRpc3RpY3NTdHJlYW1NYW5hZ2VyIGZyb20gJy4vcGVyaW9kU3RhdGlzdGljc1N0cmVhbU1hbmFnZXInO1xuaW1wb3J0IEVxdWl0eUNoYXJ0U3RyZWFtTWFuYWdlciBmcm9tICcuL2VxdWl0eUNoYXJ0U3RyZWFtTWFuYWdlcic7XG5pbXBvcnQgRXF1aXR5QmFsYW5jZVN0cmVhbU1hbmFnZXIgZnJvbSAnLi9lcXVpdHlCYWxhbmNlU3RyZWFtTWFuYWdlcic7XG5pbXBvcnQgbW9tZW50IGZyb20gJ21vbWVudCc7XG5cbi8qKlxuICogbWV0YWFwaS5jbG91ZCBSaXNrTWFuYWdlbWVudCBlcXVpdHkgdHJhY2tpbmcgQVBJIGNsaWVudCAoc2VlIGh0dHBzOi8vbWV0YWFwaS5jbG91ZC9kb2NzL3Jpc2stbWFuYWdlbWVudC8pXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEVxdWl0eVRyYWNraW5nQ2xpZW50IHtcblxuICAvKipcbiAgICogQ29uc3RydWN0cyBSaXNrTWFuYWdlbWVudCBlcXVpdHkgdHJhY2tpbmcgQVBJIGNsaWVudCBpbnN0YW5jZVxuICAgKiBAcGFyYW0ge0RvbWFpbkNsaWVudH0gZG9tYWluQ2xpZW50IGRvbWFpbiBjbGllbnRcbiAgICogQHBhcmFtIHtNZXRhQXBpfSBtZXRhQXBpIG1ldGFBcGkgU0RLIGluc3RhbmNlXG4gICAqL1xuICBjb25zdHJ1Y3Rvcihkb21haW5DbGllbnQsIG1ldGFBcGkpIHtcbiAgICB0aGlzLl9kb21haW5DbGllbnQgPSBkb21haW5DbGllbnQ7XG4gICAgdGhpcy5fdHJhY2tlckV2ZW50TGlzdGVuZXJNYW5hZ2VyID0gbmV3IFRyYWNrZXJFdmVudExpc3RlbmVyTWFuYWdlcihkb21haW5DbGllbnQpO1xuICAgIHRoaXMuX2VxdWl0eUJhbGFuY2VTdHJlYW1NYW5hZ2VyID0gbmV3IEVxdWl0eUJhbGFuY2VTdHJlYW1NYW5hZ2VyKGRvbWFpbkNsaWVudCwgbWV0YUFwaSk7XG4gICAgdGhpcy5fcGVyaW9kU3RhdGlzdGljc1N0cmVhbU1hbmFnZXIgPSBuZXcgUGVyaW9kU3RhdGlzdGljc1N0cmVhbU1hbmFnZXIoZG9tYWluQ2xpZW50LCB0aGlzLCBtZXRhQXBpKTtcbiAgICB0aGlzLl9lcXVpdHlDaGFydFN0cmVhbU1hbmFnZXIgPSBuZXcgRXF1aXR5Q2hhcnRTdHJlYW1NYW5hZ2VyKGRvbWFpbkNsaWVudCwgdGhpcywgbWV0YUFwaSk7IFxuICB9XG5cbiAgLyoqXG4gICAqIFRyYWNrZXIgY29uZmlndXJhdGlvbiB1cGRhdGVcbiAgICogQHR5cGVkZWYge09iamVjdH0gVHJhY2tlclVwZGF0ZVxuICAgKiBAcHJvcGVydHkge1N0cmluZ30gbmFtZSB1bmlxdWUgdHJhY2tlciBuYW1lXG4gICAqL1xuXG4gIC8qKlxuICAgKiBQZXJpb2QgbGVuZ3RoIHRvIHRyYWNrIHByb2ZpdCBhbmQgZHJhd2Rvd24gZm9yXG4gICAqIEB0eXBlZGVmIHsnZGF5JyB8ICdkYXRlJyB8ICd3ZWVrJyB8ICd3ZWVrLXRvLWRhdGUnIHwgJ21vbnRoJyB8ICdtb250aC10by1kYXRlJyB8ICdxdWFydGVyJyB8ICdxdWFydGVyLXRvLWRhdGUnIHxcbiAgICogJ3llYXInIHwgJ3llYXItdG8tZGF0ZScgfCAnbGlmZXRpbWUnfSBQZXJpb2RcbiAgICovXG5cbiAgLyoqXG4gICAqIE5ldyB0cmFja2VyIGNvbmZpZ3VyYXRpb25cbiAgICogQHR5cGVkZWYge1RyYWNrZXJVcGRhdGV9IE5ld1RyYWNrZXJcbiAgICogQHByb3BlcnR5IHtTdHJpbmd9IFtzdGFydEJyb2tlclRpbWVdIHRpbWUgdG8gc3RhcnQgdHJhY2tpbmcgZnJvbSBpbiBicm9rZXIgdGltZXpvbmUsIFlZWVktTU0tREQgSEg6bW06c3MuU1NTIGZvcm1hdFxuICAgKiBAcHJvcGVydHkge1N0cmluZ30gW2VuZEJyb2tlclRpbWVdIHRpbWUgdG8gZW5kIHRyYWNraW5nIGF0IGluIGJyb2tlciB0aW1lem9uZSwgWVlZWS1NTS1ERCBISDptbTpzcy5TU1MgZm9ybWF0XG4gICAqIEBwcm9wZXJ0eSB7UGVyaW9kfSBwZXJpb2QgcGVyaW9kIHRvIHRyYWNrIHByb2ZpdCBhbmQgZHJhd2Rvd24gZm9yXG4gICAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbcmVsYXRpdmVEcmF3ZG93blRocmVzaG9sZF0gcmVsYXRpdmUgZHJhd2Rvd24gdGhyZXNob2xkIGFmdGVyIHdoaWNoIHRyYWNrZXIgZXZlbnQgaXMgZ2VuZXJhdGVkLFxuICAgKiBhIGZyYWN0aW9uIG9mIDFcbiAgICogQHByb3BlcnR5IHtOdW1iZXJ9IFthYnNvbHV0ZURyYXdkb3duVGhyZXNob2xkXSBhYnNvbHV0ZSBkcmF3ZG93biB0aHJlc2hvbGQgYWZ0ZXIgd2hpY2ggdHJhY2tlciBldmVudCBpcyBnZW5lcmF0ZWQsXG4gICAqIHNob3VsZCBiZSBncmVhdGVyIHRoYW4gMFxuICAgKiBAcHJvcGVydHkge051bWJlcn0gW3JlbGF0aXZlUHJvZml0VGhyZXNob2xkXSByZWxhdGl2ZSBwcm9maXQgdGhyZXNob2xkIGFmdGVyIHdoaWNoIHRyYWNrZXIgZXZlbnQgaXMgZ2VuZXJhdGVkLFxuICAgKiBhIGZyYWN0aW9uIG9mIDFcbiAgICogQHByb3BlcnR5IHtOdW1iZXJ9IFthYnNvbHV0ZVByb2ZpdFRocmVzaG9sZF0gYWJzb2x1dGUgcHJvZml0IHRocmVzaG9sZCBhZnRlciB3aGljaCB0cmFja2VyIGV2ZW50IGlzIGdlbmVyYXRlZCxcbiAgICogc2hvdWxkIGJlIGdyZWF0ZXIgdGhhbiAwXG4gICAqL1xuXG4gIC8qKlxuICAgKiBUcmFja2VyIGlkXG4gICAqIEB0eXBlZGVmIHtPYmplY3R9IFRyYWNrZXJJZFxuICAgKiBAcHJvcGVydHkge1N0cmluZ30gaWQgdW5pcXVlIHRyYWNrZXIgaWRcbiAgICovXG5cbiAgLyoqXG4gICAqIENyZWF0ZXMgYSBwcm9maXQvZHJhd2Rvd24gdHJhY2tlci4gU2VlXG4gICAqIGh0dHBzOi8vbWV0YWFwaS5jbG91ZC9kb2NzL3Jpc2stbWFuYWdlbWVudC9yZXN0QXBpL2FwaS9jcmVhdGVUcmFja2VyL1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGlkIG9mIHRoZSBNZXRhQXBpIGFjY291bnRcbiAgICogQHBhcmFtIHtOZXdUcmFja2VyfSB0cmFja2VyIHByb2ZpdC9kcmF3ZG93biB0cmFja2VyXG4gICAqIEByZXR1cm4ge1Byb21pc2U8VHJhY2tlcklkPn0gcHJvbWlzZSByZXNvbHZpbmcgd2l0aCBwcm9maXQvZHJhd2Rvd24gdHJhY2tlciBpZFxuICAgKi9cbiAgY3JlYXRlVHJhY2tlcihhY2NvdW50SWQsIHRyYWNrZXIpIHtcbiAgICByZXR1cm4gdGhpcy5fZG9tYWluQ2xpZW50LnJlcXVlc3RBcGkoe1xuICAgICAgdXJsOiBgL3VzZXJzL2N1cnJlbnQvYWNjb3VudHMvJHthY2NvdW50SWR9L3RyYWNrZXJzYCxcbiAgICAgIGhlYWRlcnM6IHsnYXV0aC10b2tlbic6IHRoaXMuX2RvbWFpbkNsaWVudC50b2tlbiwgJ2FwaS12ZXJzaW9uJzogJzEnfSxcbiAgICAgIG1ldGhvZDogJ1BPU1QnLFxuICAgICAgZGF0YTogdHJhY2tlclxuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFRyYWNrZXIgY29uZmlndXJhdGlvblxuICAgKiBAdHlwZWRlZiB7TmV3VHJhY2tlcn0gVHJhY2tlclxuICAgKiBAcHJvcGVydHkge1N0cmluZ30gX2lkIHVuaXF1ZSB0cmFja2VyIGlkXG4gICAqL1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRyYWNrZXJzIGRlZmluZWQgZm9yIGFuIGFjY291bnQuIFNlZVxuICAgKiBodHRwczovL21ldGFhcGkuY2xvdWQvZG9jcy9yaXNrLW1hbmFnZW1lbnQvcmVzdEFwaS9hcGkvZ2V0VHJhY2tlcnMvXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBhY2NvdW50SWQgaWQgb2YgdGhlIE1ldGFBcGkgYWNjb3VudFxuICAgKiBAcmV0dXJuIHtQcm9taXNlPFRyYWNrZXJbXT59IHByb21pc2UgcmVzb2x2aW5nIHdpdGggdHJhY2tlcnNcbiAgICovXG4gIGdldFRyYWNrZXJzKGFjY291bnRJZCkge1xuICAgIHJldHVybiB0aGlzLl9kb21haW5DbGllbnQucmVxdWVzdEFwaSh7XG4gICAgICB1cmw6IGAvdXNlcnMvY3VycmVudC9hY2NvdW50cy8ke2FjY291bnRJZH0vdHJhY2tlcnNgLFxuICAgICAgaGVhZGVyczogeydhdXRoLXRva2VuJzogdGhpcy5fZG9tYWluQ2xpZW50LnRva2VuLCAnYXBpLXZlcnNpb24nOiAnMSd9LFxuICAgICAgbWV0aG9kOiAnR0VUJ1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgcHJvZml0L2RyYXdkb3duIHRyYWNrZXIgYnkgYWNjb3VudCBhbmQgaWQuIFNlZVxuICAgKiBodHRwczovL21ldGFhcGkuY2xvdWQvZG9jcy9yaXNrLW1hbmFnZW1lbnQvcmVzdEFwaS9hcGkvZ2V0VHJhY2tlci9cbiAgICogQHBhcmFtIHtzdHJpbmd9IGFjY291bnRJZCBpZCBvZiB0aGUgTWV0YUFwaSBhY2NvdW50IFxuICAgKiBAcGFyYW0ge3N0cmluZ30gaWQgdHJhY2tlciBpZCBcbiAgICogQHJldHVybnMge1Byb21pc2U8VHJhY2tlcj59IHByb21pc2UgcmVzb2x2aW5nIHdpdGggcHJvZml0L2RyYXdkb3duIHRyYWNrZXIgZm91bmRcbiAgICovXG4gIGdldFRyYWNrZXIoYWNjb3VudElkLCBpZCkge1xuICAgIHJldHVybiB0aGlzLl9kb21haW5DbGllbnQucmVxdWVzdEFwaSh7XG4gICAgICB1cmw6IGAvdXNlcnMvY3VycmVudC9hY2NvdW50cy8ke2FjY291bnRJZH0vdHJhY2tlcnMvJHtpZH1gLFxuICAgICAgaGVhZGVyczogeydhdXRoLXRva2VuJzogdGhpcy5fZG9tYWluQ2xpZW50LnRva2VuLCAnYXBpLXZlcnNpb24nOiAnMSd9LFxuICAgICAgbWV0aG9kOiAnR0VUJ1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgcHJvZml0L2RyYXdkb3duIHRyYWNrZXIgYnkgYWNjb3VudCBhbmQgbmFtZVxuICAgKiBAcGFyYW0ge3N0cmluZ30gYWNjb3VudElkIGlkIG9mIHRoZSBNZXRhQXBpIGFjY291bnQgXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBuYW1lIHRyYWNrZXIgbmFtZSBcbiAgICogQHJldHVybnMge1Byb21pc2U8VHJhY2tlcj59IHByb21pc2UgcmVzb2x2aW5nIHdpdGggcHJvZml0L2RyYXdkb3duIHRyYWNrZXIgZm91bmRcbiAgICovXG4gIGdldFRyYWNrZXJCeU5hbWUoYWNjb3VudElkLCBuYW1lKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RvbWFpbkNsaWVudC5yZXF1ZXN0QXBpKHtcbiAgICAgIHVybDogYC91c2Vycy9jdXJyZW50L2FjY291bnRzLyR7YWNjb3VudElkfS90cmFja2Vycy9uYW1lLyR7ZW5jb2RlVVJJQ29tcG9uZW50KG5hbWUpfWAsXG4gICAgICBoZWFkZXJzOiB7J2F1dGgtdG9rZW4nOiB0aGlzLl9kb21haW5DbGllbnQudG9rZW4sICdhcGktdmVyc2lvbic6ICcxJ30sXG4gICAgICBtZXRob2Q6ICdHRVQnXG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogVXBkYXRlcyBwcm9maXQvZHJhd2Rvd24gdHJhY2tlci4gU2VlXG4gICAqIGh0dHBzOi8vbWV0YWFwaS5jbG91ZC9kb2NzL3Jpc2stbWFuYWdlbWVudC9yZXN0QXBpL2FwaS91cGRhdGVUcmFja2VyL1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGlkIG9mIHRoZSBNZXRhQXBpIGFjY291bnRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGlkIGlkIG9mIHRoZSB0cmFja2VyXG4gICAqIEBwYXJhbSB7VHJhY2tlclVwZGF0ZX0gdXBkYXRlIHRyYWNrZXIgdXBkYXRlXG4gICAqIEByZXR1cm4ge1Byb21pc2V9IHByb21pc2UgcmVzb2x2aW5nIHdoZW4gcHJvZml0L2RyYXdkb3duIHRyYWNrZXIgdXBkYXRlZFxuICAgKi9cbiAgdXBkYXRlVHJhY2tlcihhY2NvdW50SWQsIGlkLCB1cGRhdGUpIHtcbiAgICByZXR1cm4gdGhpcy5fZG9tYWluQ2xpZW50LnJlcXVlc3RBcGkoe1xuICAgICAgdXJsOiBgL3VzZXJzL2N1cnJlbnQvYWNjb3VudHMvJHthY2NvdW50SWR9L3RyYWNrZXJzLyR7aWR9YCxcbiAgICAgIG1ldGhvZDogJ1BVVCcsXG4gICAgICBkYXRhOiB1cGRhdGVcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZW1vdmVzIHByb2ZpdC9kcmF3ZG93biB0cmFja2VyLiBTZWVcbiAgICogaHR0cHM6Ly9tZXRhYXBpLmNsb3VkL2RvY3Mvcmlzay1tYW5hZ2VtZW50L3Jlc3RBcGkvYXBpL3JlbW92ZVRyYWNrZXIvXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBhY2NvdW50SWQgaWQgb2YgdGhlIE1ldGFBcGkgYWNjb3VudFxuICAgKiBAcGFyYW0ge1N0cmluZ30gaWQgaWQgb2YgdGhlIHRyYWNrZXJcbiAgICogQHJldHVybiB7UHJvbWlzZX0gcHJvbWlzZSByZXNvbHZpbmcgd2hlbiBwcm9maXQvZHJhd2Rvd24gdHJhY2tlciByZW1vdmVkXG4gICAqL1xuICBkZWxldGVUcmFja2VyKGFjY291bnRJZCwgaWQpIHtcbiAgICByZXR1cm4gdGhpcy5fZG9tYWluQ2xpZW50LnJlcXVlc3RBcGkoe1xuICAgICAgdXJsOiBgL3VzZXJzL2N1cnJlbnQvYWNjb3VudHMvJHthY2NvdW50SWR9L3RyYWNrZXJzLyR7aWR9YCxcbiAgICAgIG1ldGhvZDogJ0RFTEVURSdcbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUeXBlIG9mIHRoZSBleGNlZWRlZCB0aHJlc2hvbGRcbiAgICogQHR5cGVkZWYgeydwcm9maXQnIHwgJ2RyYXdkb3duJ30gRXhjZWVkZWRUaHJlc2hvbGRUeXBlXG4gICAqL1xuXG4gIC8qKlxuICAgKiBQcm9maXQvZHJhd2Rvd24gdGhyZXNob2xkIGV4Y2VlZGVkIGV2ZW50IG1vZGVsXG4gICAqIEB0eXBlZGVmIHtPYmplY3R9IFRyYWNrZXJFdmVudFxuICAgKiBAcHJvcGVydHkge051bWJlcn0gc2VxdWVuY2VOdW1iZXIgZXZlbnQgdW5pcXVlIHNlcXVlbmNlIG51bWJlclxuICAgKiBAcHJvcGVydHkge1N0cmluZ30gYWNjb3VudElkIE1ldGFBcGkgYWNjb3VudCBpZFxuICAgKiBAcHJvcGVydHkge1N0cmluZ30gdHJhY2tlcklkIHByb2ZpdC9kcmF3ZG93biB0cmFja2VyIGlkXG4gICAqIEBwcm9wZXJ0eSB7U3RyaW5nfSBzdGFydEJyb2tlclRpbWUgcHJvZml0L2RyYXdkb3duIHRyYWNraW5nIHBlcmlvZCBzdGFydCB0aW1lIGluIGJyb2tlciB0aW1lem9uZSxcbiAgICogaW4gWVlZWS1NTS1ERCBISDptbTpzcy5TU1MgZm9ybWF0XG4gICAqIEBwcm9wZXJ0eSB7U3RyaW5nfSBbZW5kQnJva2VyVGltZV0gcHJvZml0L2RyYXdkb3duIHRyYWNraW5nIHBlcmlvZCBlbmQgdGltZSBpbiBicm9rZXIgdGltZXpvbmUsXG4gICAqIGluIFlZWVktTU0tREQgSEg6bW06c3MuU1NTIGZvcm1hdFxuICAgKiBAcHJvcGVydHkge1BlcmlvZH0gcGVyaW9kIHByb2ZpdC9kcmF3ZG93biB0cmFja2luZyBwZXJpb2RcbiAgICogQHByb3BlcnR5IHtTdHJpbmd9IGJyb2tlclRpbWUgcHJvZml0L2RyYXdkb3duIHRocmVzaG9sZCBleGNlZWRlZCBldmVudCB0aW1lIGluIGJyb2tlciB0aW1lem9uZSxcbiAgICogaW4gWVlZLU1NLUREIEhIOm1tOnNzLlNTUyBmb3JtYXRcbiAgICogQHByb3BlcnR5IHtOdW1iZXJ9IGFic29sdXRlRHJhd2Rvd24gYWJzb2x1dGUgZHJhd2Rvd24gdmFsdWUgd2hpY2ggd2FzIG9ic2VydmVkIHdoZW4gdGhlIHByb2ZpdCBvciBkcmF3ZG93blxuICAgKiB0aHJlc2hvbGQgd2FzIGV4Y2VlZGVkXG4gICAqIEBwcm9wZXJ0eSB7TnVtYmVyfSByZWxhdGl2ZURyYXdkb3duIHJlbGF0aXZlIGRyYXdkb3duIHZhbHVlIHdoaWNoIHdhcyBvYnNlcnZlZCB3aGVuIHRoZSBwcm9maXQgb3IgZHJhd2Rvd25cbiAgICogdGhyZXNob2xkIHdhcyBleGNlZWRlZFxuICAgKiBAcHJvcGVydHkge051bWJlcn0gYWJzb2x1dGVQcm9maXQgYWJzb2x1dGUgcHJvZml0IHZhbHVlIHdoaWNoIHdhcyBvYnNlcnZlZCB3aGVuIHRoZSBwcm9maXQgb3IgZHJhd2Rvd25cbiAgICogdGhyZXNob2xkIHdhcyBleGNlZWRlZFxuICAgKiBAcHJvcGVydHkge051bWJlcn0gcmVsYXRpdmVQcm9maXQgcmVsYXRpdmUgcHJvZml0IHZhbHVlIHdoaWNoIHdhcyBvYnNlcnZlZCB3aGVuIHRoZSBwcm9maXQgb3IgZHJhd2Rvd25cbiAgICogdGhyZXNob2xkIHdhcyBleGNlZWRlZFxuICAgKiBAcHJvcGVydHkge0V4Y2VlZGVkVGhyZXNob2xkVHlwZX0gZXhjZWVkZWRUaHJlc2hvbGRUeXBlIHR5cGUgb2YgdGhlIGV4Y2VlZGVkIHRocmVzaG9sZFxuICAgKi9cblxuICAvKipcbiAgICogUmV0dXJucyB0cmFja2VyIGV2ZW50cyBieSBicm9rZXIgdGltZSByYW5nZS4gU2VlXG4gICAqIGh0dHBzOi8vbWV0YWFwaS5jbG91ZC9kb2NzL3Jpc2stbWFuYWdlbWVudC9yZXN0QXBpL2FwaS9nZXRUcmFja2VyRXZlbnRzL1xuICAgKiBAcGFyYW0ge1N0cmluZ30gW3N0YXJ0QnJva2VyVGltZV0gdmFsdWUgb2YgdGhlIGV2ZW50IHRpbWUgaW4gYnJva2VyIHRpbWV6b25lIHRvIHN0YXJ0IGxvYWRpbmcgZGF0YSBmcm9tLCBpbmNsdXNpdmUsXG4gICAqIGluICdZWVlZLU1NLUREIEhIOm1tOnNzLlNTUyBmb3JtYXRcbiAgICogQHBhcmFtIHtTdHJpbmd9IFtlbmRCcm9rZXJUaW1lXSB2YWx1ZSBvZiB0aGUgZXZlbnQgdGltZSBpbiBicm9rZXIgdGltZXpvbmUgdG8gZW5kIGxvYWRpbmcgZGF0YSBhdCwgaW5jbHVzaXZlLFxuICAgKiBpbiAnWVlZWS1NTS1ERCBISDptbTpzcy5TU1MgZm9ybWF0XG4gICAqIEBwYXJhbSB7U3RyaW5nfSBbYWNjb3VudElkXSBpZCBvZiB0aGUgTWV0YUFwaSBhY2NvdW50XG4gICAqIEBwYXJhbSB7U3RyaW5nfSBbdHJhY2tlcklkXSBpZCBvZiB0aGUgdHJhY2tlclxuICAgKiBAcGFyYW0ge051bWJlcn0gW2xpbWl0XSBwYWdpbmF0aW9uIGxpbWl0LCBkZWZhdWx0IGlzIDEwMDBcbiAgICogQHJldHVybiB7UHJvbWlzZTxUcmFja2VyRXZlbnRbXT59IHByb21pc2UgcmVzb2x2aW5nIHdpdGggdHJhY2tlciBldmVudHNcbiAgICovXG4gIGdldFRyYWNrZXJFdmVudHMoc3RhcnRCcm9rZXJUaW1lLCBlbmRCcm9rZXJUaW1lLCBhY2NvdW50SWQsIHRyYWNrZXJJZCwgbGltaXQpIHtcbiAgICByZXR1cm4gdGhpcy5fZG9tYWluQ2xpZW50LnJlcXVlc3RBcGkoe1xuICAgICAgdXJsOiAnL3VzZXJzL2N1cnJlbnQvdHJhY2tlci1ldmVudHMvYnktYnJva2VyLXRpbWUnLFxuICAgICAgcGFyYW1zOiB7c3RhcnRCcm9rZXJUaW1lLCBlbmRCcm9rZXJUaW1lLCBhY2NvdW50SWQsIHRyYWNrZXJJZCwgbGltaXR9LFxuICAgICAgbWV0aG9kOiAnR0VUJ1xuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIEFkZHMgYSB0cmFja2VyIGV2ZW50IGxpc3RlbmVyIGFuZCBjcmVhdGVzIGEgam9iIHRvIG1ha2UgcmVxdWVzdHNcbiAgICogQHBhcmFtIHtUcmFja2VyRXZlbnRMaXN0ZW5lcn0gbGlzdGVuZXIgdHJhY2tlciBldmVudCBsaXN0ZW5lclxuICAgKiBAcGFyYW0ge1N0cmluZ30gW2FjY291bnRJZF0gYWNjb3VudCBpZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gW3RyYWNrZXJJZF0gdHJhY2tlciBpZFxuICAgKiBAcGFyYW0ge051bWJlcn0gW3NlcXVlbmNlTnVtYmVyXSBzZXF1ZW5jZSBudW1iZXJcbiAgICogQHJldHVybiB7U3RyaW5nfSBsaXN0ZW5lciBpZFxuICAgKi9cbiAgYWRkVHJhY2tlckV2ZW50TGlzdGVuZXIobGlzdGVuZXIsIGFjY291bnRJZCwgdHJhY2tlcklkLCBzZXF1ZW5jZU51bWJlcikge1xuICAgIHJldHVybiB0aGlzLl90cmFja2VyRXZlbnRMaXN0ZW5lck1hbmFnZXIuYWRkVHJhY2tlckV2ZW50TGlzdGVuZXIobGlzdGVuZXIsIGFjY291bnRJZCwgdHJhY2tlcklkLCBzZXF1ZW5jZU51bWJlcik7XG4gIH1cblxuICAvKipcbiAgICogUmVtb3ZlcyB0cmFja2VyIGV2ZW50IGxpc3RlbmVyIGFuZCBjYW5jZWxzIHRoZSBldmVudCBzdHJlYW1cbiAgICogQHBhcmFtIHtTdHJpbmd9IGxpc3RlbmVySWQgdHJhY2tlciBldmVudCBsaXN0ZW5lciBpZFxuICAgKi9cbiAgcmVtb3ZlVHJhY2tlckV2ZW50TGlzdGVuZXIobGlzdGVuZXJJZCkge1xuICAgIHRoaXMuX3RyYWNrZXJFdmVudExpc3RlbmVyTWFuYWdlci5yZW1vdmVUcmFja2VyRXZlbnRMaXN0ZW5lcihsaXN0ZW5lcklkKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBQZXJpb2Qgc3RhdGlzdGljc1xuICAgKiBAdHlwZWRlZiB7T2JqZWN0fSBQZXJpb2RTdGF0aXN0aWNzXG4gICAqIEBwcm9wZXJ0eSB7U3RyaW5nfSBzdGFydEJyb2tlclRpbWUgcGVyaW9kIHN0YXJ0IHRpbWUgaW4gYnJva2VyIHRpbWV6b25lLCBpbiBZWVlZLU1NLUREIEhIOm1tOnNzIGZvcm1hdFxuICAgKiBAcHJvcGVydHkge1N0cmluZ30gW2VuZEJyb2tlclRpbWVdIHBlcmlvZCBlbmQgdGltZSBpbiBicm9rZXIgdGltZXpvbmUsIGluIFlZWVktTU0tREQgSEg6bW06c3MgZm9ybWF0XG4gICAqIEBwcm9wZXJ0eSB7UGVyaW9kfSBwZXJpb2QgcGVyaW9kIGxlbmd0aFxuICAgKiBAcHJvcGVydHkge051bWJlcn0gaW5pdGlhbEJhbGFuY2UgYmFsYW5jZSBhdCBwZXJpb2Qgc3RhcnQgdGltZVxuICAgKiBAcHJvcGVydHkge1N0cmluZ30gW21heERyYXdkb3duVGltZV0gdGltZSBtYXggZHJhd2Rvd24gd2FzIG9ic2VydmVkIGF0IGluIGJyb2tlciB0aW1lem9uZSxcbiAgICogaW4gWVlZWS1NTS1ERCBISDptbTpzcyBmb3JtYXRcbiAgICogQHByb3BlcnR5IHtOdW1iZXJ9IFttYXhBYnNvbHV0ZURyYXdkb3duXSB0aGUgdmFsdWUgb2YgbWF4aW11bSBhYnNvbHV0ZSBkcmF3ZG93biBvYnNlcnZlZFxuICAgKiBAcHJvcGVydHkge051bWJlcn0gW21heFJlbGF0aXZlRHJhd2Rvd25dIHRoZSB2YWx1ZSBvZiBtYXhpbXVtIHJlbGF0aXZlIGRyYXdkb3duIG9ic2VydmVkXG4gICAqIEBwcm9wZXJ0eSB7U3RyaW5nfSBbbWF4UHJvZml0VGltZV0gdGltZSBtYXggcHJvZml0IHdhcyBvYnNlcnZlZCBhdCBpbiBicm9rZXIgdGltZXpvbmUsXG4gICAqIGluIFlZWVktTU0tREQgSEg6bW06c3MgZm9ybWF0XG4gICAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbbWF4QWJzb2x1dGVQcm9maXRdIHRoZSB2YWx1ZSBvZiBtYXhpbXVtIGFic29sdXRlIHByb2ZpdCBvYnNlcnZlZFxuICAgKiBAcHJvcGVydHkge051bWJlcn0gW21heFJlbGF0aXZlUHJvZml0XSB0aGUgdmFsdWUgb2YgbWF4aW11bSByZWxhdGl2ZSBwcm9maXQgb2JzZXJ2ZWRcbiAgICogQHByb3BlcnR5IHtCb29sZWFufSB0aHJlc2hvbGRFeGNlZWRlZCB0aGUgZmxhZyBpbmRpY2F0aW5nIHRoYXQgbWF4IGFsbG93ZWQgdG90YWwgZHJhd2Rvd24gd2FzIGV4Y2VlZGVkXG4gICAqIEBwcm9wZXJ0eSB7RXhjZWVkZWRUaHJlc2hvbGRUeXBlfSBleGNlZWRlZFRocmVzaG9sZFR5cGUgdHlwZSBvZiB0aGUgZXhjZWVkZWQgdGhyZXNob2xkXG4gICAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbYmFsYW5jZUFkanVzdG1lbnRdIGJhbGFuY2UgYWRqdXN0bWVudCBhcHBsaWVkIHRvIGVxdWl0eSBmb3IgdHJhY2tpbmcgZHJhd2Rvd24vcHJvZml0XG4gICAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbdHJhZGVEYXlDb3VudF0gY291bnQgb2YgZGF5cyB3aGVuIHRyYWRlcyB3ZXJlIHBlcmZvcm1lZCBkdXJpbmcgdGhlIHBlcmlvZFxuICAgKi9cblxuICAvKipcbiAgICogUmV0dXJucyBhY2NvdW50IHByb2ZpdCBhbmQgZHJhd2Rvd24gdHJhY2tpbmcgc3RhdGlzdGljcyBieSB0cmFja2VyIGlkLiBTZWVcbiAgICogaHR0cHM6Ly9tZXRhYXBpLmNsb3VkL2RvY3Mvcmlzay1tYW5hZ2VtZW50L3Jlc3RBcGkvYXBpL2dldFRyYWNraW5nU3RhdHMvXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBhY2NvdW50SWQgaWQgb2YgTWV0YUFQSSBhY2NvdW50XG4gICAqIEBwYXJhbSB7U3RyaW5nfSB0cmFja2VySWQgaWQgb2YgdGhlIHRyYWNrZXJcbiAgICogQHBhcmFtIHtTdHJpbmd9IFtzdGFydFRpbWVdIHRpbWUgdG8gc3RhcnQgbG9hZGluZyBzdGF0cyBmcm9tLCBkZWZhdWx0IGlzIGN1cnJlbnQgdGltZS4gTm90ZSB0aGF0IHN0YXRzIGlzIGxvYWRlZCBpblxuICAgKiBiYWNrd2FyZHMgZGlyZWN0aW9uXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBbbGltaXRdIG51bWJlciBvZiByZWNvcmRzIHRvIGxvYWQsIGRlZmF1bHQgaXMgMVxuICAgKiBAcGFyYW0ge0Jvb2xlYW59IFtyZWFsVGltZV0gaWYgdHJ1ZSwgcmVhbC10aW1lIGRhdGEgd2lsbCBiZSByZXF1ZXN0ZWRcbiAgICogQHJldHVybiB7UHJvbWlzZTxQZXJpb2RTdGF0aXN0aWNzW10+fSBwcm9taXNlIHJlc29sdmluZyB3aXRoIHByb2ZpdCBhbmQgZHJhd2Rvd24gc3RhdGlzdGljc1xuICAgKi9cbiAgZ2V0VHJhY2tpbmdTdGF0aXN0aWNzKGFjY291bnRJZCwgdHJhY2tlcklkLCBzdGFydFRpbWUsIGxpbWl0LCByZWFsVGltZSA9IGZhbHNlKSB7XG4gICAgcmV0dXJuIHRoaXMuX2RvbWFpbkNsaWVudC5yZXF1ZXN0QXBpKHtcbiAgICAgIHVybDogYC91c2Vycy9jdXJyZW50L2FjY291bnRzLyR7YWNjb3VudElkfS90cmFja2Vycy8ke3RyYWNrZXJJZH0vc3RhdGlzdGljc2AsXG4gICAgICBoZWFkZXJzOiB7J2F1dGgtdG9rZW4nOiB0aGlzLl9kb21haW5DbGllbnQudG9rZW4sICdhcGktdmVyc2lvbic6ICcxJ30sXG4gICAgICBwYXJhbXM6IHtzdGFydFRpbWUsIGxpbWl0LCByZWFsVGltZX0sXG4gICAgICBtZXRob2Q6ICdHRVQnXG4gICAgfSk7XG4gIH1cblxuICAvKipcbiAgICogQWRkcyBhIHBlcmlvZCBzdGF0aXN0aWNzIGV2ZW50IGxpc3RlbmVyXG4gICAqIEBwYXJhbSB7UGVyaW9kU3RhdGlzdGljc0xpc3RlbmVyfSBsaXN0ZW5lciBwZXJpb2Qgc3RhdGlzdGljcyBldmVudCBsaXN0ZW5lclxuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IHRyYWNrZXJJZCB0cmFja2VyIGlkXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPFN0cmluZz59IGxpc3RlbmVyIGlkXG4gICAqL1xuICBhZGRQZXJpb2RTdGF0aXN0aWNzTGlzdGVuZXIobGlzdGVuZXIsIGFjY291bnRJZCwgdHJhY2tlcklkKSB7XG4gICAgcmV0dXJuIHRoaXMuX3BlcmlvZFN0YXRpc3RpY3NTdHJlYW1NYW5hZ2VyLmFkZFBlcmlvZFN0YXRpc3RpY3NMaXN0ZW5lcihsaXN0ZW5lciwgYWNjb3VudElkLCB0cmFja2VySWQpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlbW92ZXMgcGVyaW9kIHN0YXRpc3RpY3MgZXZlbnQgbGlzdGVuZXIgYnkgaWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGxpc3RlbmVySWQgbGlzdGVuZXIgaWQgXG4gICAqL1xuICByZW1vdmVQZXJpb2RTdGF0aXN0aWNzTGlzdGVuZXIobGlzdGVuZXJJZCkge1xuICAgIHRoaXMuX3BlcmlvZFN0YXRpc3RpY3NTdHJlYW1NYW5hZ2VyLnJlbW92ZVBlcmlvZFN0YXRpc3RpY3NMaXN0ZW5lcihsaXN0ZW5lcklkKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBFcXVpdHkgY2hhcnQgaXRlbVxuICAgKiBAdHlwZWRlZiB7T2JqZWN0fSBFcXVpdHlDaGFydEl0ZW1cbiAgICogQHByb3BlcnR5IHtTdHJpbmd9IHN0YXJ0QnJva2VyVGltZSBzdGFydCB0aW1lIG9mIGEgY2hhcnQgaXRlbSBhcyBwZXIgYnJva2VyIHRpbWV6b25lLCBpbiBZWVlZLU1NLUREIEhIOm1tOnNzIGZvcm1hdFxuICAgKiBAcHJvcGVydHkge1N0cmluZ30gZW5kQnJva2VyVGltZSBlbmQgdGltZSBvZiBhIGNoYXJ0IGl0ZW0gYXMgcGVyIGJyb2tlciB0aW1lem9uZSwgaW4gWVlZWS1NTS1ERCBISDptbTpzcyBmb3JtYXRcbiAgICogQHByb3BlcnR5IHtOdW1iZXJ9IGF2ZXJhZ2VCYWxhbmNlIGF2ZXJhZ2UgYmFsYW5jZSB2YWx1ZSBkdXJpbmcgdGhlIHBlcmlvZFxuICAgKiBAcHJvcGVydHkge051bWJlcn0gbWluQmFsYW5jZSBtaW5pbXVtIGJhbGFuY2UgdmFsdWUgZHVyaW5nIHRoZSBwZXJpb2RcbiAgICogQHByb3BlcnR5IHtOdW1iZXJ9IG1heEJhbGFuY2UgbWF4aW11bSBiYWxhbmNlIHZhbHVlIGR1cmluZyB0aGUgcGVyaW9kXG4gICAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBhdmVyYWdlRXF1aXR5IGF2ZXJhZ2UgZXF1aXR5IHZhbHVlIGR1cmluZyB0aGUgcGVyaW9kXG4gICAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBtaW5FcXVpdHkgbWluaW11bSBlcXVpdHkgdmFsdWUgZHVyaW5nIHRoZSBwZXJpb2RcbiAgICogQHByb3BlcnR5IHtOdW1iZXJ9IG1heEVxdWl0eSBtYXhpbXVtIGVxdWl0eSB2YWx1ZSBkdXJpbmcgdGhlIHBlcmlvZFxuICAgKiBAcHJvcGVydHkge051bWJlcn0gc3RhcnRCYWxhbmNlIHN0YXJ0aW5nIGJhbGFuY2UgdmFsdWUgb2JzZXJ2ZWQgZHVyaW5nIHRoZSBwZXJpb2RcbiAgICogQHByb3BlcnR5IHtOdW1iZXJ9IHN0YXJ0RXF1aXR5IHN0YXJ0aW5nIGVxdWl0eSB2YWx1ZSBvYnNlcnZlZCBkdXJpbmcgdGhlIHBlcmlvZFxuICAgKiBAcHJvcGVydHkge051bWJlcn0gbGFzdEJhbGFuY2UgbGFzdCBiYWxhbmNlIHZhbHVlIG9ic2VydmVkIGR1cmluZyB0aGUgcGVyaW9kXG4gICAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBsYXN0RXF1aXR5IGxhc3QgZXF1aXR5IHZhbHVlIG9ic2VydmVkIGR1cmluZyB0aGUgcGVyaW9kXG4gICAqL1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGVxdWl0eSBjaGFydCBieSBhY2NvdW50IGlkLiBTZWVcbiAgICogaHR0cHM6Ly9tZXRhYXBpLmNsb3VkL2RvY3Mvcmlzay1tYW5hZ2VtZW50L3Jlc3RBcGkvYXBpL2dldEVxdWl0eUNoYXJ0L1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIG1ldGFBcGkgYWNjb3VudCBpZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gW3N0YXJ0VGltZV0gc3RhcnRpbmcgYnJva2VyIHRpbWUgaW4gWVlZWS1NTS1ERCBISDptbTpzcyBmb3JtYXRcbiAgICogQHBhcmFtIHtTdHJpbmd9IFtlbmRUaW1lXSBlbmRpbmcgYnJva2VyIHRpbWUgaW4gWVlZWS1NTS1ERCBISDptbTpzcyBmb3JtYXRcbiAgICogQHBhcmFtIHtCb29sZWFufSBbcmVhbFRpbWVdIGlmIHRydWUsIHJlYWwtdGltZSBkYXRhIHdpbGwgYmUgcmVxdWVzdGVkXG4gICAqIEBwYXJhbSB7Qm9vbGVhbn0gW2ZpbGxTa2lwc10gaWYgdHJ1ZSwgc2tpcHBlZCByZWNvcmRzIHdpbGwgYmUgYXV0b21hdGljYWxseSBmaWxsZWQgYmFzZWQgb24gZXhpc3Rpbmcgb25lc1xuICAgKiBAcmV0dXJuIHtQcm9taXNlPEVxdWl0eUNoYXJ0SXRlbVtdPn0gcHJvbWlzZSByZXNvbHZpbmcgd2l0aCBlcXVpdHkgY2hhcnRcbiAgICovXG4gIGFzeW5jIGdldEVxdWl0eUNoYXJ0KGFjY291bnRJZCwgc3RhcnRUaW1lLCBlbmRUaW1lLCByZWFsVGltZSA9IGZhbHNlLCBmaWxsU2tpcHMgPSBmYWxzZSkge1xuICAgIGNvbnN0IHJlY29yZHMgPSBhd2FpdCB0aGlzLl9kb21haW5DbGllbnQucmVxdWVzdEFwaSh7XG4gICAgICB1cmw6IGAvdXNlcnMvY3VycmVudC9hY2NvdW50cy8ke2FjY291bnRJZH0vZXF1aXR5LWNoYXJ0YCxcbiAgICAgIGhlYWRlcnM6IHsnYXV0aC10b2tlbic6IHRoaXMuX2RvbWFpbkNsaWVudC50b2tlbiwgJ2FwaS12ZXJzaW9uJzogJzEnfSxcbiAgICAgIHBhcmFtczoge3N0YXJ0VGltZSwgZW5kVGltZSwgcmVhbFRpbWV9LFxuICAgICAgbWV0aG9kOiAnR0VUJ1xuICAgIH0pO1xuICAgIGlmKGZpbGxTa2lwcyl7XG4gICAgICBsZXQgaSA9IDA7XG4gICAgICB3aGlsZShpIDwgcmVjb3Jkcy5sZW5ndGggLSAxKSB7XG4gICAgICAgIGNvbnN0IHRpbWVEaWZmID0gbmV3IERhdGUocmVjb3Jkc1tpICsgMV0uc3RhcnRCcm9rZXJUaW1lKS5nZXRUaW1lKCkgLSBcbiAgICAgICAgbmV3IERhdGUocmVjb3Jkc1tpXS5zdGFydEJyb2tlclRpbWUpLmdldFRpbWUoKTtcbiAgICAgICAgXG4gICAgICAgIGlmKHRpbWVEaWZmID4gNjAgKiA2MCAqIDEwMDAgJiYgcmVjb3Jkc1tpXS5sYXN0QmFsYW5jZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgY29uc3QgcmVjb3JkQ29weSA9IEpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkocmVjb3Jkc1tpXSkpO1xuICAgICAgICAgIHJlY29yZENvcHkubWluRXF1aXR5ID0gcmVjb3JkQ29weS5sYXN0RXF1aXR5O1xuICAgICAgICAgIHJlY29yZENvcHkubWF4RXF1aXR5ID0gcmVjb3JkQ29weS5sYXN0RXF1aXR5O1xuICAgICAgICAgIHJlY29yZENvcHkuYXZlcmFnZUVxdWl0eSA9IHJlY29yZENvcHkubGFzdEVxdWl0eTtcbiAgICAgICAgICByZWNvcmRDb3B5Lm1pbkJhbGFuY2UgPSByZWNvcmRDb3B5Lmxhc3RCYWxhbmNlO1xuICAgICAgICAgIHJlY29yZENvcHkubWF4QmFsYW5jZSA9IHJlY29yZENvcHkubGFzdEJhbGFuY2U7XG4gICAgICAgICAgcmVjb3JkQ29weS5hdmVyYWdlQmFsYW5jZSA9IHJlY29yZENvcHkubGFzdEJhbGFuY2U7XG4gICAgICAgICAgY29uc3Qgc3RhcnRCcm9rZXJUaW1lID0gbmV3IERhdGUocmVjb3JkQ29weS5zdGFydEJyb2tlclRpbWUpO1xuICAgICAgICAgIHN0YXJ0QnJva2VyVGltZS5zZXRVVENIb3VycyhzdGFydEJyb2tlclRpbWUuZ2V0VVRDSG91cnMoKSArIDEpO1xuICAgICAgICAgIHN0YXJ0QnJva2VyVGltZS5zZXRVVENNaW51dGVzKDApO1xuICAgICAgICAgIHN0YXJ0QnJva2VyVGltZS5zZXRVVENTZWNvbmRzKDApO1xuICAgICAgICAgIHN0YXJ0QnJva2VyVGltZS5zZXRVVENNaWxsaXNlY29uZHMoMCk7XG4gICAgICAgICAgcmVjb3JkQ29weS5zdGFydEJyb2tlclRpbWUgPSBtb21lbnQoc3RhcnRCcm9rZXJUaW1lKS5mb3JtYXQoJ1lZWVktTU0tREQgSEg6bW06c3MuU1NTJyk7XG4gICAgICAgICAgc3RhcnRCcm9rZXJUaW1lLnNldFVUQ0hvdXJzKHN0YXJ0QnJva2VyVGltZS5nZXRVVENIb3VycygpICsgMSk7XG4gICAgICAgICAgc3RhcnRCcm9rZXJUaW1lLnNldFVUQ01pbGxpc2Vjb25kcygtMSk7XG4gICAgICAgICAgcmVjb3JkQ29weS5lbmRCcm9rZXJUaW1lID0gbW9tZW50KHN0YXJ0QnJva2VyVGltZSkuZm9ybWF0KCdZWVlZLU1NLUREIEhIOm1tOnNzLlNTUycpO1xuICAgICAgICAgIHJlY29yZENvcHkuYnJva2VyVGltZSA9IHJlY29yZENvcHkuZW5kQnJva2VyVGltZTtcbiAgICAgICAgICByZWNvcmRzLnNwbGljZShpICsgMSwgMCwgcmVjb3JkQ29weSk7XG4gICAgICAgIH1cbiAgICAgICAgaSsrO1xuICAgICAgfSAgXG4gICAgfVxuICAgIHJldHVybiByZWNvcmRzO1xuICB9XG5cbiAgLyoqXG4gICAqIEFkZHMgYW4gZXF1aXR5IGNoYXJ0IGV2ZW50IGxpc3RlbmVyXG4gICAqIEBwYXJhbSB7RXF1aXR5Q2hhcnRMaXN0ZW5lcn0gbGlzdGVuZXIgZXF1aXR5IGNoYXJ0IGV2ZW50IGxpc3RlbmVyXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBhY2NvdW50SWQgYWNjb3VudCBpZFxuICAgKiBAcGFyYW0ge0RhdGV9IFtzdGFydFRpbWVdIGRhdGUgdG8gc3RhcnQgdHJhY2tpbmcgZnJvbVxuICAgKiBAcmV0dXJucyB7UHJvbWlzZTxzdHJpbmc+fSBsaXN0ZW5lciBpZFxuICAgKi9cbiAgYWRkRXF1aXR5Q2hhcnRMaXN0ZW5lcihsaXN0ZW5lciwgYWNjb3VudElkLCBzdGFydFRpbWUpIHtcbiAgICByZXR1cm4gdGhpcy5fZXF1aXR5Q2hhcnRTdHJlYW1NYW5hZ2VyLmFkZEVxdWl0eUNoYXJ0TGlzdGVuZXIobGlzdGVuZXIsIGFjY291bnRJZCwgc3RhcnRUaW1lKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZW1vdmVzIGVxdWl0eSBjaGFydCBldmVudCBsaXN0ZW5lciBieSBpZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gbGlzdGVuZXJJZCBlcXVpdHkgY2hhcnQgbGlzdGVuZXIgaWQgXG4gICAqL1xuICByZW1vdmVFcXVpdHlDaGFydExpc3RlbmVyKGxpc3RlbmVySWQpIHtcbiAgICB0aGlzLl9lcXVpdHlDaGFydFN0cmVhbU1hbmFnZXIucmVtb3ZlRXF1aXR5Q2hhcnRMaXN0ZW5lcihsaXN0ZW5lcklkKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBBZGRzIGFuIGVxdWl0eSBiYWxhbmNlIGV2ZW50IGxpc3RlbmVyXG4gICAqIEBwYXJhbSB7RXF1aXR5QmFsYW5jZUxpc3RlbmVyfSBsaXN0ZW5lciBlcXVpdHkgYmFsYW5jZSBldmVudCBsaXN0ZW5lclxuICAgKiBAcGFyYW0ge3N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHJldHVybnMge1Byb21pc2U8c3RyaW5nPn0gbGlzdGVuZXIgaWRcbiAgICovXG4gIGFkZEVxdWl0eUJhbGFuY2VMaXN0ZW5lcihsaXN0ZW5lciwgYWNjb3VudElkKSB7XG4gICAgcmV0dXJuIHRoaXMuX2VxdWl0eUJhbGFuY2VTdHJlYW1NYW5hZ2VyLmFkZEVxdWl0eUJhbGFuY2VMaXN0ZW5lcihsaXN0ZW5lciwgYWNjb3VudElkKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZW1vdmVzIGVxdWl0eSBiYWxhbmNlIGV2ZW50IGxpc3RlbmVyIGJ5IGlkXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBsaXN0ZW5lcklkIGVxdWl0eSBiYWxhbmNlIGxpc3RlbmVyIGlkIFxuICAgKi9cbiAgcmVtb3ZlRXF1aXR5QmFsYW5jZUxpc3RlbmVyKGxpc3RlbmVySWQpIHtcbiAgICB0aGlzLl9lcXVpdHlCYWxhbmNlU3RyZWFtTWFuYWdlci5yZW1vdmVFcXVpdHlCYWxhbmNlTGlzdGVuZXIobGlzdGVuZXJJZCk7XG4gIH1cblxufVxuIl0sIm5hbWVzIjpbIlRyYWNrZXJFdmVudExpc3RlbmVyTWFuYWdlciIsIlBlcmlvZFN0YXRpc3RpY3NTdHJlYW1NYW5hZ2VyIiwiRXF1aXR5Q2hhcnRTdHJlYW1NYW5hZ2VyIiwiRXF1aXR5QmFsYW5jZVN0cmVhbU1hbmFnZXIiLCJtb21lbnQiLCJFcXVpdHlUcmFja2luZ0NsaWVudCIsImNyZWF0ZVRyYWNrZXIiLCJhY2NvdW50SWQiLCJ0cmFja2VyIiwiX2RvbWFpbkNsaWVudCIsInJlcXVlc3RBcGkiLCJ1cmwiLCJoZWFkZXJzIiwidG9rZW4iLCJtZXRob2QiLCJkYXRhIiwiZ2V0VHJhY2tlcnMiLCJnZXRUcmFja2VyIiwiaWQiLCJnZXRUcmFja2VyQnlOYW1lIiwibmFtZSIsImVuY29kZVVSSUNvbXBvbmVudCIsInVwZGF0ZVRyYWNrZXIiLCJ1cGRhdGUiLCJkZWxldGVUcmFja2VyIiwiZ2V0VHJhY2tlckV2ZW50cyIsInN0YXJ0QnJva2VyVGltZSIsImVuZEJyb2tlclRpbWUiLCJ0cmFja2VySWQiLCJsaW1pdCIsInBhcmFtcyIsImFkZFRyYWNrZXJFdmVudExpc3RlbmVyIiwibGlzdGVuZXIiLCJzZXF1ZW5jZU51bWJlciIsIl90cmFja2VyRXZlbnRMaXN0ZW5lck1hbmFnZXIiLCJyZW1vdmVUcmFja2VyRXZlbnRMaXN0ZW5lciIsImxpc3RlbmVySWQiLCJnZXRUcmFja2luZ1N0YXRpc3RpY3MiLCJzdGFydFRpbWUiLCJyZWFsVGltZSIsImFkZFBlcmlvZFN0YXRpc3RpY3NMaXN0ZW5lciIsIl9wZXJpb2RTdGF0aXN0aWNzU3RyZWFtTWFuYWdlciIsInJlbW92ZVBlcmlvZFN0YXRpc3RpY3NMaXN0ZW5lciIsImdldEVxdWl0eUNoYXJ0IiwiZW5kVGltZSIsImZpbGxTa2lwcyIsInJlY29yZHMiLCJpIiwibGVuZ3RoIiwidGltZURpZmYiLCJEYXRlIiwiZ2V0VGltZSIsImxhc3RCYWxhbmNlIiwidW5kZWZpbmVkIiwicmVjb3JkQ29weSIsIkpTT04iLCJwYXJzZSIsInN0cmluZ2lmeSIsIm1pbkVxdWl0eSIsImxhc3RFcXVpdHkiLCJtYXhFcXVpdHkiLCJhdmVyYWdlRXF1aXR5IiwibWluQmFsYW5jZSIsIm1heEJhbGFuY2UiLCJhdmVyYWdlQmFsYW5jZSIsInNldFVUQ0hvdXJzIiwiZ2V0VVRDSG91cnMiLCJzZXRVVENNaW51dGVzIiwic2V0VVRDU2Vjb25kcyIsInNldFVUQ01pbGxpc2Vjb25kcyIsImZvcm1hdCIsImJyb2tlclRpbWUiLCJzcGxpY2UiLCJhZGRFcXVpdHlDaGFydExpc3RlbmVyIiwiX2VxdWl0eUNoYXJ0U3RyZWFtTWFuYWdlciIsInJlbW92ZUVxdWl0eUNoYXJ0TGlzdGVuZXIiLCJhZGRFcXVpdHlCYWxhbmNlTGlzdGVuZXIiLCJfZXF1aXR5QmFsYW5jZVN0cmVhbU1hbmFnZXIiLCJyZW1vdmVFcXVpdHlCYWxhbmNlTGlzdGVuZXIiLCJjb25zdHJ1Y3RvciIsImRvbWFpbkNsaWVudCIsIm1ldGFBcGkiXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFFQSxPQUFPQSxpQ0FBaUMsZ0NBQWdDO0FBQ3hFLE9BQU9DLG1DQUFtQyxrQ0FBa0M7QUFDNUUsT0FBT0MsOEJBQThCLDZCQUE2QjtBQUNsRSxPQUFPQyxnQ0FBZ0MsK0JBQStCO0FBQ3RFLE9BQU9DLFlBQVksU0FBUztBQUtiLElBQUEsQUFBTUMsdUJBQU4sTUFBTUE7SUFlbkI7Ozs7R0FJQyxHQUVEOzs7O0dBSUMsR0FFRDs7Ozs7Ozs7Ozs7Ozs7R0FjQyxHQUVEOzs7O0dBSUMsR0FFRDs7Ozs7O0dBTUMsR0FDREMsY0FBY0MsU0FBUyxFQUFFQyxPQUFPLEVBQUU7UUFDaEMsT0FBTyxJQUFJLENBQUNDLGFBQWEsQ0FBQ0MsVUFBVSxDQUFDO1lBQ25DQyxLQUFLLENBQUMsd0JBQXdCLEVBQUVKLFVBQVUsU0FBUyxDQUFDO1lBQ3BESyxTQUFTO2dCQUFDLGNBQWMsSUFBSSxDQUFDSCxhQUFhLENBQUNJLEtBQUs7Z0JBQUUsZUFBZTtZQUFHO1lBQ3BFQyxRQUFRO1lBQ1JDLE1BQU1QO1FBQ1I7SUFDRjtJQUVBOzs7O0dBSUMsR0FFRDs7Ozs7R0FLQyxHQUNEUSxZQUFZVCxTQUFTLEVBQUU7UUFDckIsT0FBTyxJQUFJLENBQUNFLGFBQWEsQ0FBQ0MsVUFBVSxDQUFDO1lBQ25DQyxLQUFLLENBQUMsd0JBQXdCLEVBQUVKLFVBQVUsU0FBUyxDQUFDO1lBQ3BESyxTQUFTO2dCQUFDLGNBQWMsSUFBSSxDQUFDSCxhQUFhLENBQUNJLEtBQUs7Z0JBQUUsZUFBZTtZQUFHO1lBQ3BFQyxRQUFRO1FBQ1Y7SUFDRjtJQUVBOzs7Ozs7R0FNQyxHQUNERyxXQUFXVixTQUFTLEVBQUVXLEVBQUUsRUFBRTtRQUN4QixPQUFP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