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)
319 lines (318 loc) • 40.6 kB
JavaScript
;
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _async_to_generator(fn) {
return function() {
var self = this, args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _define_property(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
import socketIO from 'socket.io-client';
import LoggerManager from '../../logger';
let LatencyService = class LatencyService {
/**
* Stops the service
*/ stop() {
clearInterval(this._refreshRegionLatencyInterval);
}
/**
* Returns the list of regions sorted by latency
* @returns {String[]} list of regions sorted by latency
*/ get regionsSortedByLatency() {
const regions = Object.keys(this._latencyCache);
regions.sort((a, b)=>this._latencyCache[a] - this._latencyCache[b]);
return regions;
}
/**
* Invoked when an instance has been disconnected
* @param {String} instanceId instance id
*/ onDisconnected(instanceId) {
try {
const accountId = this._getAccountIdFromInstance(instanceId);
const disconnectedRegion = this._getRegionFromInstance(instanceId);
this._disconnectInstance(instanceId);
const instances = this._getAccountInstances(accountId);
if (!instances.map((instance)=>this._connectedInstancesCache[instance]).includes(true)) {
const regions = this._getAccountRegions(accountId);
regions.filter((region)=>region !== disconnectedRegion).forEach((region)=>this._subscribeAccountReplica(accountId, region));
}
} catch (err) {
this._logger.error(`Failed to process onDisconnected event for instance ${instanceId}`, err);
}
}
/**
* Invoked when an account has been unsubscribed
* @param {String} accountId account id
*/ onUnsubscribe(accountId) {
try {
const region = this._websocketClient.getAccountRegion(accountId);
const primaryAccountId = this._websocketClient.accountsByReplicaId[accountId];
const instances = this._getAccountInstances(primaryAccountId);
instances.filter((instanceId)=>instanceId.startsWith(`${primaryAccountId}:${region}:`)).forEach((instanceId)=>this._disconnectInstance(instanceId));
} catch (err) {
this._logger.error(`Failed to process onUnsubscribe event for account ${accountId}`, err);
}
}
/**
* Invoked when an instance has been connected
* @param {String} instanceId instance id
*/ onConnected(instanceId) {
var _this = this;
return _async_to_generator(function*() {
try {
_this._connectedInstancesCache[instanceId] = true;
const accountId = _this._getAccountIdFromInstance(instanceId);
const region = _this._getRegionFromInstance(instanceId);
if (!_this._latencyCache[region]) {
yield _this._refreshLatency(region);
}
const instances = _this.getActiveAccountInstances(accountId);
const synchronizedInstances = _this.getSynchronizedAccountInstances(accountId);
const regions = instances.map((instance)=>_this._getRegionFromInstance(instance));
if (instances.length > 1 && !synchronizedInstances.length) {
const regionsToDisconnect = _this.regionsSortedByLatency.filter((sortedRegion)=>regions.includes(sortedRegion)).slice(1);
regionsToDisconnect.forEach((regionItem)=>{
_this._websocketClient.unsubscribe(_this._websocketClient.accountReplicas[accountId][regionItem]);
_this._websocketClient.unsubscribeAccountRegion(accountId, regionItem);
});
}
if (_this._waitConnectPromises[accountId]) {
_this._waitConnectPromises[accountId].resolve();
delete _this._waitConnectPromises[accountId];
}
} catch (err) {
_this._logger.error(`Failed to process onConnected event for instance ${instanceId}`, err);
}
})();
}
/**
* Invoked when an instance has been synchronized
* @param {String} instanceId instance id
*/ onDealsSynchronized(instanceId) {
var _this = this;
return _async_to_generator(function*() {
try {
_this._synchronizedInstancesCache[instanceId] = true;
const accountId = _this._getAccountIdFromInstance(instanceId);
const region = _this._getRegionFromInstance(instanceId);
if (!_this._latencyCache[region]) {
yield _this._refreshLatency(region);
}
const instances = _this.getSynchronizedAccountInstances(accountId);
const regions = [
...new Set(instances.map((instance)=>_this._getRegionFromInstance(instance)))
];
if (instances.length > 1) {
const regionsToDisconnect = _this.regionsSortedByLatency.filter((sortedRegion)=>regions.includes(sortedRegion)).slice(1);
regionsToDisconnect.forEach((regionItem)=>{
_this._websocketClient.unsubscribe(_this._websocketClient.accountReplicas[accountId][regionItem]);
_this._websocketClient.unsubscribeAccountRegion(accountId, regionItem);
});
}
} catch (err) {
_this._logger.error(`Failed to process onDealsSynchronized event for instance ${instanceId}`, err);
}
})();
}
/**
* Returns the list of currently connected account instances
* @param {String} accountId account id
* @returns {String[]} list of connected account instances
*/ getActiveAccountInstances(accountId) {
return this._getAccountInstances(accountId).filter((instance)=>this._connectedInstancesCache[instance]);
}
/**
* Returns the list of currently synchronized account instances
* @param {String} accountId account id
* @returns {String[]} list of synchronized account instances
*/ getSynchronizedAccountInstances(accountId) {
return this._getAccountInstances(accountId).filter((instance)=>this._synchronizedInstancesCache[instance]);
}
/**
* Waits for connected instance
* @param {String} accountId account id
* @returns {String} instance id
*/ waitConnectedInstance(accountId) {
var _this = this;
return _async_to_generator(function*() {
let instances = _this.getActiveAccountInstances(accountId);
if (!instances.length) {
if (!_this._waitConnectPromises[accountId]) {
let resolve;
let promise = new Promise((res, rej)=>{
resolve = res;
});
_this._waitConnectPromises[accountId] = {
promise,
resolve
};
}
yield _this._waitConnectPromises[accountId].promise;
instances = _this.getActiveAccountInstances(accountId);
}
return instances[0];
})();
}
_getAccountInstances(accountId) {
return Object.keys(this._connectedInstancesCache).filter((instanceId)=>instanceId.startsWith(`${accountId}:`));
}
_getAccountRegions(accountId) {
const regions = [];
const instances = this._getAccountInstances(accountId);
instances.forEach((instance)=>{
const region = this._getRegionFromInstance(instance);
if (!regions.includes(region)) {
regions.push(region);
}
});
return regions;
}
_getAccountIdFromInstance(instanceId) {
return instanceId.split(':')[0];
}
_getRegionFromInstance(instanceId) {
return instanceId.split(':')[1];
}
_disconnectInstance(instanceId) {
this._connectedInstancesCache[instanceId] = false;
if (this._synchronizedInstancesCache[instanceId]) {
this._synchronizedInstancesCache[instanceId] = false;
}
}
_subscribeAccountReplica(accountId, region) {
const instanceId = this._websocketClient.accountReplicas[accountId][region];
if (instanceId) {
this._websocketClient.ensureSubscribe(instanceId, 0);
this._websocketClient.ensureSubscribe(instanceId, 1);
}
}
_refreshRegionLatencyJob() {
var _this = this;
return _async_to_generator(function*() {
for (let region of Object.keys(_this._latencyCache)){
yield _this._refreshLatency(region);
}
// For every account, switch to a better region if such exists
const accountIds = [];
Object.keys(_this._connectedInstancesCache).filter((instanceId)=>_this._connectedInstancesCache[instanceId]).forEach((instanceId)=>{
const accountId = _this._getAccountIdFromInstance(instanceId);
if (!accountIds.includes(accountId)) {
accountIds.push(accountId);
}
});
const sortedRegions = _this.regionsSortedByLatency;
accountIds.forEach((accountId)=>{
const accountRegions = _this._getAccountRegions(accountId);
const activeInstances = _this.getActiveAccountInstances(accountId);
if (activeInstances.length === 1) {
const activeInstance = activeInstances[0];
const activeRegion = _this._getRegionFromInstance(activeInstance);
const accountBestRegions = sortedRegions.filter((region)=>accountRegions.includes(region));
if (accountBestRegions[0] !== activeRegion) {
_this._subscribeAccountReplica(accountId, accountBestRegions[0]);
}
}
});
})();
}
_refreshLatency(region) {
var _this = this;
return _async_to_generator(function*() {
if (_this._refreshPromisesByRegion[region]) {
return yield _this._refreshPromisesByRegion[region];
}
let resolve;
_this._refreshPromisesByRegion[region] = new Promise((res, rej)=>{
resolve = res;
});
const serverUrl = yield _this._websocketClient.getUrlSettings(0, region);
const startDate = Date.now();
const socketInstance = socketIO(serverUrl.url, {
path: '/ws',
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
reconnectionAttempts: Infinity,
timeout: _this._connectTimeout,
query: {
'auth-token': _this._token,
protocol: 3
}
});
socketInstance.on('connect', /*#__PURE__*/ _async_to_generator(function*() {
resolve();
const latency = Date.now() - startDate;
_this._latencyCache[region] = latency;
socketInstance.close();
}));
yield _this._refreshPromisesByRegion[region];
delete _this._refreshPromisesByRegion[region];
})();
}
/**
* Constructs latency service instance
* @param {MetaApiWebsocketClient} websocketClient MetaApi websocket client
* @param {String} token authorization token
* @param {Number} connectTimeout websocket connect timeout in seconds
*/ constructor(websocketClient, token, connectTimeout){
_define_property(this, "_websocketClient", void 0);
_define_property(this, "_token", void 0);
_define_property(this, "_connectTimeout", void 0);
_define_property(this, "_latencyCache", void 0);
_define_property(this, "_connectedInstancesCache", void 0);
_define_property(this, "_synchronizedInstancesCache", void 0);
_define_property(this, "_refreshPromisesByRegion", void 0);
_define_property(this, "_waitConnectPromises", void 0);
_define_property(this, "_logger", void 0);
_define_property(this, "_refreshRegionLatencyInterval", void 0);
this._websocketClient = websocketClient;
this._token = token;
this._connectTimeout = connectTimeout;
this._latencyCache = {};
this._connectedInstancesCache = {};
this._synchronizedInstancesCache = {};
this._refreshPromisesByRegion = {};
this._waitConnectPromises = {};
this._logger = LoggerManager.getLogger('LatencyService');
this._refreshRegionLatencyJob = this._refreshRegionLatencyJob.bind(this);
this._refreshRegionLatencyInterval = setInterval(this._refreshRegionLatencyJob, 15 * 60 * 1000);
}
};
/**
* Service for managing account replicas based on region latency
*/ export { LatencyService as default };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBzb2NrZXRJTyBmcm9tICdzb2NrZXQuaW8tY2xpZW50JztcbmltcG9ydCBMb2dnZXJNYW5hZ2VyLCB7TG9nZ2VyfSBmcm9tICcuLi8uLi9sb2dnZXInO1xuaW1wb3J0IHR5cGUgTWV0YUFwaVdlYnNvY2tldENsaWVudCBmcm9tICcuL21ldGFBcGlXZWJzb2NrZXQuY2xpZW50JztcblxuLyoqXG4gKiBTZXJ2aWNlIGZvciBtYW5hZ2luZyBhY2NvdW50IHJlcGxpY2FzIGJhc2VkIG9uIHJlZ2lvbiBsYXRlbmN5XG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIExhdGVuY3lTZXJ2aWNlIHtcbiAgXG4gIHByaXZhdGUgX3dlYnNvY2tldENsaWVudDogTWV0YUFwaVdlYnNvY2tldENsaWVudDtcbiAgcHJpdmF0ZSBfdG9rZW46IGFueTtcbiAgcHJpdmF0ZSBfY29ubmVjdFRpbWVvdXQ6IGFueTtcbiAgcHJpdmF0ZSBfbGF0ZW5jeUNhY2hlOiB7fTtcbiAgcHJpdmF0ZSBfY29ubmVjdGVkSW5zdGFuY2VzQ2FjaGU6IHt9O1xuICBwcml2YXRlIF9zeW5jaHJvbml6ZWRJbnN0YW5jZXNDYWNoZToge307XG4gIHByaXZhdGUgX3JlZnJlc2hQcm9taXNlc0J5UmVnaW9uOiB7fTtcbiAgcHJpdmF0ZSBfd2FpdENvbm5lY3RQcm9taXNlczoge307XG4gIHByaXZhdGUgX2xvZ2dlcjogTG9nZ2VyO1xuICBwcml2YXRlIF9yZWZyZXNoUmVnaW9uTGF0ZW5jeUludGVydmFsOiBOb2RlSlMuVGltZW91dDtcblxuICAvKipcbiAgICogQ29uc3RydWN0cyBsYXRlbmN5IHNlcnZpY2UgaW5zdGFuY2VcbiAgICogQHBhcmFtIHtNZXRhQXBpV2Vic29ja2V0Q2xpZW50fSB3ZWJzb2NrZXRDbGllbnQgTWV0YUFwaSB3ZWJzb2NrZXQgY2xpZW50XG4gICAqIEBwYXJhbSB7U3RyaW5nfSB0b2tlbiBhdXRob3JpemF0aW9uIHRva2VuXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBjb25uZWN0VGltZW91dCB3ZWJzb2NrZXQgY29ubmVjdCB0aW1lb3V0IGluIHNlY29uZHNcbiAgICovXG4gIGNvbnN0cnVjdG9yKHdlYnNvY2tldENsaWVudCwgdG9rZW4sIGNvbm5lY3RUaW1lb3V0KSB7XG4gICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50ID0gd2Vic29ja2V0Q2xpZW50O1xuICAgIHRoaXMuX3Rva2VuID0gdG9rZW47XG4gICAgdGhpcy5fY29ubmVjdFRpbWVvdXQgPSBjb25uZWN0VGltZW91dDtcbiAgICB0aGlzLl9sYXRlbmN5Q2FjaGUgPSB7fTtcbiAgICB0aGlzLl9jb25uZWN0ZWRJbnN0YW5jZXNDYWNoZSA9IHt9O1xuICAgIHRoaXMuX3N5bmNocm9uaXplZEluc3RhbmNlc0NhY2hlID0ge307XG4gICAgdGhpcy5fcmVmcmVzaFByb21pc2VzQnlSZWdpb24gPSB7fTtcbiAgICB0aGlzLl93YWl0Q29ubmVjdFByb21pc2VzID0ge307XG4gICAgdGhpcy5fbG9nZ2VyID0gTG9nZ2VyTWFuYWdlci5nZXRMb2dnZXIoJ0xhdGVuY3lTZXJ2aWNlJyk7XG4gICAgdGhpcy5fcmVmcmVzaFJlZ2lvbkxhdGVuY3lKb2IgPSB0aGlzLl9yZWZyZXNoUmVnaW9uTGF0ZW5jeUpvYi5iaW5kKHRoaXMpO1xuICAgIHRoaXMuX3JlZnJlc2hSZWdpb25MYXRlbmN5SW50ZXJ2YWwgPSBzZXRJbnRlcnZhbCh0aGlzLl9yZWZyZXNoUmVnaW9uTGF0ZW5jeUpvYiwgMTUgKiA2MCAqIDEwMDApO1xuICB9XG5cbiAgLyoqXG4gICAqIFN0b3BzIHRoZSBzZXJ2aWNlXG4gICAqL1xuICBzdG9wKCkge1xuICAgIGNsZWFySW50ZXJ2YWwodGhpcy5fcmVmcmVzaFJlZ2lvbkxhdGVuY3lJbnRlcnZhbCk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgbGlzdCBvZiByZWdpb25zIHNvcnRlZCBieSBsYXRlbmN5XG4gICAqIEByZXR1cm5zIHtTdHJpbmdbXX0gbGlzdCBvZiByZWdpb25zIHNvcnRlZCBieSBsYXRlbmN5XG4gICAqL1xuICBnZXQgcmVnaW9uc1NvcnRlZEJ5TGF0ZW5jeSgpIHtcbiAgICBjb25zdCByZWdpb25zID0gT2JqZWN0LmtleXModGhpcy5fbGF0ZW5jeUNhY2hlKTtcbiAgICByZWdpb25zLnNvcnQoKGEsIGIpID0+IHRoaXMuX2xhdGVuY3lDYWNoZVthXSAtIHRoaXMuX2xhdGVuY3lDYWNoZVtiXSk7XG4gICAgcmV0dXJuIHJlZ2lvbnM7XG4gIH1cblxuICAvKipcbiAgICogSW52b2tlZCB3aGVuIGFuIGluc3RhbmNlIGhhcyBiZWVuIGRpc2Nvbm5lY3RlZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gaW5zdGFuY2VJZCBpbnN0YW5jZSBpZFxuICAgKi9cbiAgb25EaXNjb25uZWN0ZWQoaW5zdGFuY2VJZCkge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBhY2NvdW50SWQgPSB0aGlzLl9nZXRBY2NvdW50SWRGcm9tSW5zdGFuY2UoaW5zdGFuY2VJZCk7XG4gICAgICBjb25zdCBkaXNjb25uZWN0ZWRSZWdpb24gPSB0aGlzLl9nZXRSZWdpb25Gcm9tSW5zdGFuY2UoaW5zdGFuY2VJZCk7XG4gICAgICB0aGlzLl9kaXNjb25uZWN0SW5zdGFuY2UoaW5zdGFuY2VJZCk7XG4gICAgICBjb25zdCBpbnN0YW5jZXMgPSB0aGlzLl9nZXRBY2NvdW50SW5zdGFuY2VzKGFjY291bnRJZCk7XG4gICAgICBpZiAoIWluc3RhbmNlcy5tYXAoaW5zdGFuY2UgPT4gdGhpcy5fY29ubmVjdGVkSW5zdGFuY2VzQ2FjaGVbaW5zdGFuY2VdKS5pbmNsdWRlcyh0cnVlKSkge1xuICAgICAgICBjb25zdCByZWdpb25zID0gdGhpcy5fZ2V0QWNjb3VudFJlZ2lvbnMoYWNjb3VudElkKTtcbiAgICAgICAgcmVnaW9ucy5maWx0ZXIocmVnaW9uID0+IHJlZ2lvbiAhPT0gZGlzY29ubmVjdGVkUmVnaW9uKVxuICAgICAgICAgIC5mb3JFYWNoKHJlZ2lvbiA9PiB0aGlzLl9zdWJzY3JpYmVBY2NvdW50UmVwbGljYShhY2NvdW50SWQsIHJlZ2lvbikpO1xuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgdGhpcy5fbG9nZ2VyLmVycm9yKGBGYWlsZWQgdG8gcHJvY2VzcyBvbkRpc2Nvbm5lY3RlZCBldmVudCBmb3IgaW5zdGFuY2UgJHtpbnN0YW5jZUlkfWAsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBhbiBhY2NvdW50IGhhcyBiZWVuIHVuc3Vic2NyaWJlZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICovXG4gIG9uVW5zdWJzY3JpYmUoYWNjb3VudElkKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlZ2lvbiA9IHRoaXMuX3dlYnNvY2tldENsaWVudC5nZXRBY2NvdW50UmVnaW9uKGFjY291bnRJZCk7XG4gICAgICBjb25zdCBwcmltYXJ5QWNjb3VudElkID0gdGhpcy5fd2Vic29ja2V0Q2xpZW50LmFjY291bnRzQnlSZXBsaWNhSWRbYWNjb3VudElkXTtcbiAgICAgIGNvbnN0IGluc3RhbmNlcyA9IHRoaXMuX2dldEFjY291bnRJbnN0YW5jZXMocHJpbWFyeUFjY291bnRJZCk7XG4gICAgICBpbnN0YW5jZXMuZmlsdGVyKGluc3RhbmNlSWQgPT4gaW5zdGFuY2VJZC5zdGFydHNXaXRoKGAke3ByaW1hcnlBY2NvdW50SWR9OiR7cmVnaW9ufTpgKSlcbiAgICAgICAgLmZvckVhY2goaW5zdGFuY2VJZCA9PiB0aGlzLl9kaXNjb25uZWN0SW5zdGFuY2UoaW5zdGFuY2VJZCkpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgdGhpcy5fbG9nZ2VyLmVycm9yKGBGYWlsZWQgdG8gcHJvY2VzcyBvblVuc3Vic2NyaWJlIGV2ZW50IGZvciBhY2NvdW50ICR7YWNjb3VudElkfWAsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBhbiBpbnN0YW5jZSBoYXMgYmVlbiBjb25uZWN0ZWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGluc3RhbmNlSWQgaW5zdGFuY2UgaWRcbiAgICovXG4gIGFzeW5jIG9uQ29ubmVjdGVkKGluc3RhbmNlSWQpIHtcbiAgICB0cnkge1xuICAgICAgdGhpcy5fY29ubmVjdGVkSW5zdGFuY2VzQ2FjaGVbaW5zdGFuY2VJZF0gPSB0cnVlO1xuICAgICAgY29uc3QgYWNjb3VudElkID0gdGhpcy5fZ2V0QWNjb3VudElkRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpO1xuICAgICAgY29uc3QgcmVnaW9uID0gdGhpcy5fZ2V0UmVnaW9uRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpO1xuICAgICAgaWYgKCF0aGlzLl9sYXRlbmN5Q2FjaGVbcmVnaW9uXSkge1xuICAgICAgICBhd2FpdCB0aGlzLl9yZWZyZXNoTGF0ZW5jeShyZWdpb24pO1xuICAgICAgfVxuICAgICAgY29uc3QgaW5zdGFuY2VzID0gdGhpcy5nZXRBY3RpdmVBY2NvdW50SW5zdGFuY2VzKGFjY291bnRJZCk7XG4gICAgICBjb25zdCBzeW5jaHJvbml6ZWRJbnN0YW5jZXMgPSB0aGlzLmdldFN5bmNocm9uaXplZEFjY291bnRJbnN0YW5jZXMoYWNjb3VudElkKTtcbiAgICAgIGNvbnN0IHJlZ2lvbnMgPSBpbnN0YW5jZXMubWFwKGluc3RhbmNlID0+IHRoaXMuX2dldFJlZ2lvbkZyb21JbnN0YW5jZShpbnN0YW5jZSkpO1xuICAgICAgaWYgKGluc3RhbmNlcy5sZW5ndGggPiAxICYmICFzeW5jaHJvbml6ZWRJbnN0YW5jZXMubGVuZ3RoKSB7XG4gICAgICAgIGNvbnN0IHJlZ2lvbnNUb0Rpc2Nvbm5lY3QgPSB0aGlzLnJlZ2lvbnNTb3J0ZWRCeUxhdGVuY3lcbiAgICAgICAgICAuZmlsdGVyKHNvcnRlZFJlZ2lvbiA9PiByZWdpb25zLmluY2x1ZGVzKHNvcnRlZFJlZ2lvbikpLnNsaWNlKDEpO1xuICAgICAgICByZWdpb25zVG9EaXNjb25uZWN0LmZvckVhY2gocmVnaW9uSXRlbSA9PiB7XG4gICAgICAgICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LnVuc3Vic2NyaWJlKHRoaXMuX3dlYnNvY2tldENsaWVudC5hY2NvdW50UmVwbGljYXNbYWNjb3VudElkXVtyZWdpb25JdGVtXSk7XG4gICAgICAgICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LnVuc3Vic2NyaWJlQWNjb3VudFJlZ2lvbihhY2NvdW50SWQsIHJlZ2lvbkl0ZW0pO1xuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICAgIGlmICh0aGlzLl93YWl0Q29ubmVjdFByb21pc2VzW2FjY291bnRJZF0pIHtcbiAgICAgICAgdGhpcy5fd2FpdENvbm5lY3RQcm9taXNlc1thY2NvdW50SWRdLnJlc29sdmUoKTtcbiAgICAgICAgZGVsZXRlIHRoaXMuX3dhaXRDb25uZWN0UHJvbWlzZXNbYWNjb3VudElkXTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcihgRmFpbGVkIHRvIHByb2Nlc3Mgb25Db25uZWN0ZWQgZXZlbnQgZm9yIGluc3RhbmNlICR7aW5zdGFuY2VJZH1gLCBlcnIpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBJbnZva2VkIHdoZW4gYW4gaW5zdGFuY2UgaGFzIGJlZW4gc3luY2hyb25pemVkXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBpbnN0YW5jZUlkIGluc3RhbmNlIGlkXG4gICAqL1xuICBhc3luYyBvbkRlYWxzU3luY2hyb25pemVkKGluc3RhbmNlSWQpIHtcbiAgICB0cnkge1xuICAgICAgdGhpcy5fc3luY2hyb25pemVkSW5zdGFuY2VzQ2FjaGVbaW5zdGFuY2VJZF0gPSB0cnVlO1xuICAgICAgY29uc3QgYWNjb3VudElkID0gdGhpcy5fZ2V0QWNjb3VudElkRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpO1xuICAgICAgY29uc3QgcmVnaW9uID0gdGhpcy5fZ2V0UmVnaW9uRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpO1xuICAgICAgaWYgKCF0aGlzLl9sYXRlbmN5Q2FjaGVbcmVnaW9uXSkge1xuICAgICAgICBhd2FpdCB0aGlzLl9yZWZyZXNoTGF0ZW5jeShyZWdpb24pO1xuICAgICAgfVxuICAgICAgY29uc3QgaW5zdGFuY2VzID0gdGhpcy5nZXRTeW5jaHJvbml6ZWRBY2NvdW50SW5zdGFuY2VzKGFjY291bnRJZCk7XG4gICAgICBjb25zdCByZWdpb25zID0gWy4uLm5ldyBTZXQoaW5zdGFuY2VzLm1hcChpbnN0YW5jZSA9PiB0aGlzLl9nZXRSZWdpb25Gcm9tSW5zdGFuY2UoaW5zdGFuY2UpKSldO1xuICAgICAgaWYgKGluc3RhbmNlcy5sZW5ndGggPiAxKSB7XG4gICAgICAgIGNvbnN0IHJlZ2lvbnNUb0Rpc2Nvbm5lY3QgPSB0aGlzLnJlZ2lvbnNTb3J0ZWRCeUxhdGVuY3lcbiAgICAgICAgICAuZmlsdGVyKHNvcnRlZFJlZ2lvbiA9PiByZWdpb25zLmluY2x1ZGVzKHNvcnRlZFJlZ2lvbikpLnNsaWNlKDEpO1xuICAgICAgICByZWdpb25zVG9EaXNjb25uZWN0LmZvckVhY2gocmVnaW9uSXRlbSA9PiB7XG4gICAgICAgICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LnVuc3Vic2NyaWJlKHRoaXMuX3dlYnNvY2tldENsaWVudC5hY2NvdW50UmVwbGljYXNbYWNjb3VudElkXVtyZWdpb25JdGVtXSk7XG4gICAgICAgICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LnVuc3Vic2NyaWJlQWNjb3VudFJlZ2lvbihhY2NvdW50SWQsIHJlZ2lvbkl0ZW0pO1xuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcihgRmFpbGVkIHRvIHByb2Nlc3Mgb25EZWFsc1N5bmNocm9uaXplZCBldmVudCBmb3IgaW5zdGFuY2UgJHtpbnN0YW5jZUlkfWAsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGxpc3Qgb2YgY3VycmVudGx5IGNvbm5lY3RlZCBhY2NvdW50IGluc3RhbmNlc1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHJldHVybnMge1N0cmluZ1tdfSBsaXN0IG9mIGNvbm5lY3RlZCBhY2NvdW50IGluc3RhbmNlc1xuICAgKi9cbiAgZ2V0QWN0aXZlQWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpIHtcbiAgICByZXR1cm4gdGhpcy5fZ2V0QWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpLmZpbHRlcihpbnN0YW5jZSA9PiB0aGlzLl9jb25uZWN0ZWRJbnN0YW5jZXNDYWNoZVtpbnN0YW5jZV0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGxpc3Qgb2YgY3VycmVudGx5IHN5bmNocm9uaXplZCBhY2NvdW50IGluc3RhbmNlc1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHJldHVybnMge1N0cmluZ1tdfSBsaXN0IG9mIHN5bmNocm9uaXplZCBhY2NvdW50IGluc3RhbmNlc1xuICAgKi9cbiAgZ2V0U3luY2hyb25pemVkQWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpIHtcbiAgICByZXR1cm4gdGhpcy5fZ2V0QWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpLmZpbHRlcihpbnN0YW5jZSA9PiB0aGlzLl9zeW5jaHJvbml6ZWRJbnN0YW5jZXNDYWNoZVtpbnN0YW5jZV0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFdhaXRzIGZvciBjb25uZWN0ZWQgaW5zdGFuY2VcbiAgICogQHBhcmFtIHtTdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkIFxuICAgKiBAcmV0dXJucyB7U3RyaW5nfSBpbnN0YW5jZSBpZFxuICAgKi9cbiAgYXN5bmMgd2FpdENvbm5lY3RlZEluc3RhbmNlKGFjY291bnRJZCkge1xuICAgIGxldCBpbnN0YW5jZXMgPSB0aGlzLmdldEFjdGl2ZUFjY291bnRJbnN0YW5jZXMoYWNjb3VudElkKTtcbiAgICBpZiAoIWluc3RhbmNlcy5sZW5ndGgpIHtcbiAgICAgIGlmICghdGhpcy5fd2FpdENvbm5lY3RQcm9taXNlc1thY2NvdW50SWRdKSB7XG4gICAgICAgIGxldCByZXNvbHZlO1xuICAgICAgICBsZXQgcHJvbWlzZSA9IG5ldyBQcm9taXNlKChyZXMsIHJlaikgPT4ge1xuICAgICAgICAgIHJlc29sdmUgPSByZXM7XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLl93YWl0Q29ubmVjdFByb21pc2VzW2FjY291bnRJZF0gPSB7cHJvbWlzZSwgcmVzb2x2ZX07XG4gICAgICB9XG4gICAgICBhd2FpdCB0aGlzLl93YWl0Q29ubmVjdFByb21pc2VzW2FjY291bnRJZF0ucHJvbWlzZTtcbiAgICAgIGluc3RhbmNlcyA9IHRoaXMuZ2V0QWN0aXZlQWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpO1xuICAgIH1cbiAgICByZXR1cm4gaW5zdGFuY2VzWzBdO1xuICB9XG5cbiAgX2dldEFjY291bnRJbnN0YW5jZXMoYWNjb3VudElkKSB7XG4gICAgcmV0dXJuIE9iamVjdC5rZXlzKHRoaXMuX2Nvbm5lY3RlZEluc3RhbmNlc0NhY2hlKS5maWx0ZXIoaW5zdGFuY2VJZCA9PiBpbnN0YW5jZUlkLnN0YXJ0c1dpdGgoYCR7YWNjb3VudElkfTpgKSk7XG4gIH1cblxuICBfZ2V0QWNjb3VudFJlZ2lvbnMoYWNjb3VudElkKSB7XG4gICAgY29uc3QgcmVnaW9ucyA9IFtdO1xuICAgIGNvbnN0IGluc3RhbmNlcyA9IHRoaXMuX2dldEFjY291bnRJbnN0YW5jZXMoYWNjb3VudElkKTtcbiAgICBpbnN0YW5jZXMuZm9yRWFjaChpbnN0YW5jZSA9PiB7XG4gICAgICBjb25zdCByZWdpb24gPSB0aGlzLl9nZXRSZWdpb25Gcm9tSW5zdGFuY2UoaW5zdGFuY2UpO1xuICAgICAgaWYgKCFyZWdpb25zLmluY2x1ZGVzKHJlZ2lvbikpIHtcbiAgICAgICAgcmVnaW9ucy5wdXNoKHJlZ2lvbik7XG4gICAgICB9XG4gICAgfSk7XG4gICAgcmV0dXJuIHJlZ2lvbnM7XG4gIH1cblxuICBfZ2V0QWNjb3VudElkRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpIHtcbiAgICByZXR1cm4gaW5zdGFuY2VJZC5zcGxpdCgnOicpWzBdO1xuICB9XG5cbiAgX2dldFJlZ2lvbkZyb21JbnN0YW5jZShpbnN0YW5jZUlkKSB7XG4gICAgcmV0dXJuIGluc3RhbmNlSWQuc3BsaXQoJzonKVsxXTtcbiAgfVxuXG4gIF9kaXNjb25uZWN0SW5zdGFuY2UoaW5zdGFuY2VJZCkge1xuICAgIHRoaXMuX2Nvbm5lY3RlZEluc3RhbmNlc0NhY2hlW2luc3RhbmNlSWRdID0gZmFsc2U7XG4gICAgaWYgKHRoaXMuX3N5bmNocm9uaXplZEluc3RhbmNlc0NhY2hlW2luc3RhbmNlSWRdKSB7XG4gICAgICB0aGlzLl9zeW5jaHJvbml6ZWRJbnN0YW5jZXNDYWNoZVtpbnN0YW5jZUlkXSA9IGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIF9zdWJzY3JpYmVBY2NvdW50UmVwbGljYShhY2NvdW50SWQsIHJlZ2lvbikge1xuICAgIGNvbnN0IGluc3RhbmNlSWQgPSB0aGlzLl93ZWJzb2NrZXRDbGllbnQuYWNjb3VudFJlcGxpY2FzW2FjY291bnRJZF1bcmVnaW9uXTtcbiAgICBpZiAoaW5zdGFuY2VJZCkge1xuICAgICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LmVuc3VyZVN1YnNjcmliZShpbnN0YW5jZUlkLCAwKTtcbiAgICAgIHRoaXMuX3dlYnNvY2tldENsaWVudC5lbnN1cmVTdWJzY3JpYmUoaW5zdGFuY2VJZCwgMSk7XG4gICAgfVxuICB9XG5cbiAgYXN5bmMgX3JlZnJlc2hSZWdpb25MYXRlbmN5Sm9iKCkge1xuICAgIGZvcihsZXQgcmVnaW9uIG9mIE9iamVjdC5rZXlzKHRoaXMuX2xhdGVuY3lDYWNoZSkpIHtcbiAgICAgIGF3YWl0IHRoaXMuX3JlZnJlc2hMYXRlbmN5KHJlZ2lvbik7XG4gICAgfVxuXG4gICAgLy8gRm9yIGV2ZXJ5IGFjY291bnQsIHN3aXRjaCB0byBhIGJldHRlciByZWdpb24gaWYgc3VjaCBleGlzdHNcbiAgICBjb25zdCBhY2NvdW50SWRzID0gW107XG4gICAgT2JqZWN0LmtleXModGhpcy5fY29ubmVjdGVkSW5zdGFuY2VzQ2FjaGUpXG4gICAgICAuZmlsdGVyKGluc3RhbmNlSWQgPT4gdGhpcy5fY29ubmVjdGVkSW5zdGFuY2VzQ2FjaGVbaW5zdGFuY2VJZF0pXG4gICAgICAuZm9yRWFjaChpbnN0YW5jZUlkID0+IHtcbiAgICAgICAgY29uc3QgYWNjb3VudElkID0gdGhpcy5fZ2V0QWNjb3VudElkRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpO1xuICAgICAgICBpZiAoIWFjY291bnRJZHMuaW5jbHVkZXMoYWNjb3VudElkKSkge1xuICAgICAgICAgIGFjY291bnRJZHMucHVzaChhY2NvdW50SWQpO1xuICAgICAgICB9XG4gICAgICB9KTtcblxuICAgIGNvbnN0IHNvcnRlZFJlZ2lvbnMgPSB0aGlzLnJlZ2lvbnNTb3J0ZWRCeUxhdGVuY3k7XG5cbiAgICBhY2NvdW50SWRzLmZvckVhY2goYWNjb3VudElkID0+IHtcbiAgICAgIGNvbnN0IGFjY291bnRSZWdpb25zID0gdGhpcy5fZ2V0QWNjb3VudFJlZ2lvbnMoYWNjb3VudElkKTtcbiAgICAgIGNvbnN0IGFjdGl2ZUluc3RhbmNlcyA9IHRoaXMuZ2V0QWN0aXZlQWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpO1xuICAgICAgaWYgKGFjdGl2ZUluc3RhbmNlcy5sZW5ndGggPT09IDEpIHtcbiAgICAgICAgY29uc3QgYWN0aXZlSW5zdGFuY2UgPSBhY3RpdmVJbnN0YW5jZXNbMF07XG4gICAgICAgIGNvbnN0IGFjdGl2ZVJlZ2lvbiA9IHRoaXMuX2dldFJlZ2lvbkZyb21JbnN0YW5jZShhY3RpdmVJbnN0YW5jZSk7XG4gICAgICAgIGNvbnN0IGFjY291bnRCZXN0UmVnaW9ucyA9IHNvcnRlZFJlZ2lvbnMuZmlsdGVyKHJlZ2lvbiA9PiBhY2NvdW50UmVnaW9ucy5pbmNsdWRlcyhyZWdpb24pKTtcbiAgICAgICAgaWYgKGFjY291bnRCZXN0UmVnaW9uc1swXSAhPT0gYWN0aXZlUmVnaW9uKSB7XG4gICAgICAgICAgdGhpcy5fc3Vic2NyaWJlQWNjb3VudFJlcGxpY2EoYWNjb3VudElkLCBhY2NvdW50QmVzdFJlZ2lvbnNbMF0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICBhc3luYyBfcmVmcmVzaExhdGVuY3kocmVnaW9uKSB7XG4gICAgaWYgKHRoaXMuX3JlZnJlc2hQcm9taXNlc0J5UmVnaW9uW3JlZ2lvbl0pIHtcbiAgICAgIHJldHVybiBhd2FpdCB0aGlzLl9yZWZyZXNoUHJvbWlzZXNCeVJlZ2lvbltyZWdpb25dO1xuICAgIH1cbiAgICBsZXQgcmVzb2x2ZTtcbiAgICB0aGlzLl9yZWZyZXNoUHJvbWlzZXNCeVJlZ2lvbltyZWdpb25dID0gbmV3IFByb21pc2UoKHJlcywgcmVqKSA9PiB7XG4gICAgICByZXNvbHZlID0gcmVzO1xuICAgIH0pO1xuICAgIGNvbnN0IHNlcnZlclVybCA9IGF3YWl0IHRoaXMuX3dlYnNvY2tldENsaWVudC5nZXRVcmxTZXR0aW5ncygwLCByZWdpb24pO1xuICAgIGNvbnN0IHN0YXJ0RGF0ZSA9IERhdGUubm93KCk7XG4gIFxuICAgIGNvbnN0IHNvY2tldEluc3RhbmNlID0gc29ja2V0SU8oc2VydmVyVXJsLnVybCwge1xuICAgICAgcGF0aDogJy93cycsXG4gICAgICByZWNvbm5lY3Rpb246IHRydWUsXG4gICAgICByZWNvbm5lY3Rpb25EZWxheTogMTAwMCxcbiAgICAgIHJlY29ubmVjdGlvbkRlbGF5TWF4OiA1MDAwLFxuICAgICAgcmVjb25uZWN0aW9uQXR0ZW1wdHM6IEluZmluaXR5LFxuICAgICAgdGltZW91dDogdGhpcy5fY29ubmVjdFRpbWVvdXQsXG4gICAgICBxdWVyeToge1xuICAgICAgICAnYXV0aC10b2tlbic6IHRoaXMuX3Rva2VuLFxuICAgICAgICBwcm90b2NvbDogM1xuICAgICAgfVxuICAgIH0pO1xuICAgIHNvY2tldEluc3RhbmNlLm9uKCdjb25uZWN0JywgYXN5bmMgKCkgPT4ge1xuICAgICAgcmVzb2x2ZSgpO1xuICAgICAgY29uc3QgbGF0ZW5jeSA9IERhdGUubm93KCkgLSBzdGFydERhdGU7XG4gICAgICB0aGlzLl9sYXRlbmN5Q2FjaGVbcmVnaW9uXSA9IGxhdGVuY3k7XG4gICAgICBzb2NrZXRJbnN0YW5jZS5jbG9zZSgpO1xuICAgIH0pO1xuICAgIGF3YWl0IHRoaXMuX3JlZnJlc2hQcm9taXNlc0J5UmVnaW9uW3JlZ2lvbl07XG4gICAgZGVsZXRlIHRoaXMuX3JlZnJlc2hQcm9taXNlc0J5UmVnaW9uW3JlZ2lvbl07XG4gIH1cblxufVxuIl0sIm5hbWVzIjpbInNvY2tldElPIiwiTG9nZ2VyTWFuYWdlciIsIkxhdGVuY3lTZXJ2aWNlIiwic3RvcCIsImNsZWFySW50ZXJ2YWwiLCJfcmVmcmVzaFJlZ2lvbkxhdGVuY3lJbnRlcnZhbCIsInJlZ2lvbnNTb3J0ZWRCeUxhdGVuY3kiLCJyZWdpb25zIiwiT2JqZWN0Iiwia2V5cyIsIl9sYXRlbmN5Q2FjaGUiLCJzb3J0IiwiYSIsImIiLCJvbkRpc2Nvbm5lY3RlZCIsImluc3RhbmNlSWQiLCJhY2NvdW50SWQiLCJfZ2V0QWNjb3VudElkRnJvbUluc3RhbmNlIiwiZGlzY29ubmVjdGVkUmVnaW9uIiwiX2dldFJlZ2lvbkZyb21JbnN0YW5jZSIsIl9kaXNjb25uZWN0SW5zdGFuY2UiLCJpbnN0YW5jZXMiLCJfZ2V0QWNjb3VudEluc3RhbmNlcyIsIm1hcCIsImluc3RhbmNlIiwiX2Nvbm5lY3RlZEluc3RhbmNlc0NhY2hlIiwiaW5jbHVkZXMiLCJfZ2V0QWNjb3VudFJlZ2lvbnMiLCJmaWx0ZXIiLCJyZWdpb24iLCJmb3JFYWNoIiwiX3N1YnNjcmliZUFjY291bnRSZXBsaWNhIiwiZXJyIiwiX2xvZ2dlciIsImVycm9yIiwib25VbnN1YnNjcmliZSIsIl93ZWJzb2NrZXRDbGllbnQiLCJnZXRBY2NvdW50UmVnaW9uIiwicHJpbWFyeUFjY291bnRJZCIsImFjY291bnRzQnlSZXBsaWNhSWQiLCJzdGFydHNXaXRoIiwib25Db25uZWN0ZWQiLCJfcmVmcmVzaExhdGVuY3kiLCJnZXRBY3RpdmVBY2NvdW50SW5zdGFuY2VzIiwic3luY2hyb25pemVkSW5zdGFuY2VzIiwiZ2V0U3luY2hyb25pemVkQWNjb3VudEluc3RhbmNlcyIsImxlbmd0aCIsInJlZ2lvbnNUb0Rpc2Nvbm5lY3QiLCJzb3J0ZWRSZWdpb24iLCJzbGljZSIsInJlZ2lvbkl0ZW0iLCJ1bnN1YnNjcmliZSIsImFjY291bnRSZXBsaWNhcyIsInVuc3Vic2NyaWJlQWNjb3VudFJlZ2lvbiIsIl93YWl0Q29ubmVjdFByb21pc2VzIiwicmVzb2x2ZSIsIm9uRGVhbHNTeW5jaHJvbml6ZWQiLCJfc3luY2hyb25pemVkSW5zdGFuY2VzQ2FjaGUiLCJTZXQiLCJ3YWl0Q29ubmVjdGVkSW5zdGFuY2UiLCJwcm9taXNlIiwiUHJvbWlzZSIsInJlcyIsInJlaiIsInB1c2giLCJzcGxpdCIsImVuc3VyZVN1YnNjcmliZSIsIl9yZWZyZXNoUmVnaW9uTGF0ZW5jeUpvYiIsImFjY291bnRJZHMiLCJzb3J0ZWRSZWdpb25zIiwiYWNjb3VudFJlZ2lvbnMiLCJhY3RpdmVJbnN0YW5jZXMiLCJhY3RpdmVJbnN0YW5jZSIsImFjdGl2ZVJlZ2lvbiIsImFjY291bnRCZXN0UmVnaW9ucyIsIl9yZWZyZXNoUHJvbWlzZXNCeVJlZ2lvbiIsInNlcnZlclVybCIsImdldFVybFNldHRpbmdzIiwic3RhcnREYXRlIiwiRGF0ZSIsIm5vdyIsInNvY2tldEluc3RhbmNlIiwidXJsIiwicGF0aCIsInJlY29ubmVjdGlvbiIsInJlY29ubmVjdGlvbkRlbGF5IiwicmVjb25uZWN0aW9uRGVsYXlNYXgiLCJyZWNvbm5lY3Rpb25BdHRlbXB0cyIsIkluZmluaXR5IiwidGltZW91dCIsIl9jb25uZWN0VGltZW91dCIsInF1ZXJ5IiwiX3Rva2VuIiwicHJvdG9jb2wiLCJvbiIsImxhdGVuY3kiLCJjbG9zZSIsImNvbnN0cnVjdG9yIiwid2Vic29ja2V0Q2xpZW50IiwidG9rZW4iLCJjb25uZWN0VGltZW91dCIsImdldExvZ2dlciIsImJpbmQiLCJzZXRJbnRlcnZhbCJdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFFQSxPQUFPQSxjQUFjLG1CQUFtQjtBQUN4QyxPQUFPQyxtQkFBNkIsZUFBZTtBQU1wQyxJQUFBLEFBQU1DLGlCQUFOLE1BQU1BO0lBaUNuQjs7R0FFQyxHQUNEQyxPQUFPO1FBQ0xDLGNBQWMsSUFBSSxDQUFDQyw2QkFBNkI7SUFDbEQ7SUFFQTs7O0dBR0MsR0FDRCxJQUFJQyx5QkFBeUI7UUFDM0IsTUFBTUMsVUFBVUMsT0FBT0MsSUFBSSxDQUFDLElBQUksQ0FBQ0MsYUFBYTtRQUM5Q0gsUUFBUUksSUFBSSxDQUFDLENBQUNDLEdBQUdDLElBQU0sSUFBSSxDQUFDSCxhQUFhLENBQUNFLEVBQUUsR0FBRyxJQUFJLENBQUNGLGFBQWEsQ0FBQ0csRUFBRTtRQUNwRSxPQUFPTjtJQUNUO0lBRUE7OztHQUdDLEdBQ0RPLGVBQWVDLFVBQVUsRUFBRTtRQUN6QixJQUFJO1lBQ0YsTUFBTUMsWUFBWSxJQUFJLENBQUNDLHlCQUF5QixDQUFDRjtZQUNqRCxNQUFNRyxxQkFBcUIsSUFBSSxDQUFDQyxzQkFBc0IsQ0FBQ0o7WUFDdkQsSUFBSSxDQUFDSyxtQkFBbUIsQ0FBQ0w7WUFDekIsTUFBTU0sWUFBWSxJQUFJLENBQUNDLG9CQUFvQixDQUFDTjtZQUM1QyxJQUFJLENBQUNLLFVBQVVFLEdBQUcsQ0FBQ0MsQ0FBQUEsV0FBWSxJQUFJLENBQUNDLHdCQUF3QixDQUFDRCxTQUFTLEVBQUVFLFFBQVEsQ0FBQyxPQUFPO2dCQUN0RixNQUFNbkIsVUFBVSxJQUFJLENBQUNvQixrQkFBa0IsQ0FBQ1g7Z0JBQ3hDVCxRQUFRcUIsTUFBTSxDQUFDQyxDQUFBQSxTQUFVQSxXQUFXWCxvQkFDakNZLE9BQU8sQ0FBQ0QsQ0FBQUEsU0FBVSxJQUFJLENBQUNFLHdCQUF3QixDQUFDZixX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