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