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)
286 lines (285 loc) • 39.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return LatencyService;
}
});
const _socketioclient = /*#__PURE__*/ _interop_require_default(require("socket.io-client"));
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 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
*/ async onConnected(instanceId) {
try {
this._connectedInstancesCache[instanceId] = true;
const accountId = this._getAccountIdFromInstance(instanceId);
const region = this._getRegionFromInstance(instanceId);
if (!this._latencyCache[region]) {
await 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
*/ async onDealsSynchronized(instanceId) {
try {
this._synchronizedInstancesCache[instanceId] = true;
const accountId = this._getAccountIdFromInstance(instanceId);
const region = this._getRegionFromInstance(instanceId);
if (!this._latencyCache[region]) {
await 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
*/ async waitConnectedInstance(accountId) {
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
};
}
await 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);
}
}
async _refreshRegionLatencyJob() {
for (let region of Object.keys(this._latencyCache)){
await 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]);
}
}
});
}
async _refreshLatency(region) {
if (this._refreshPromisesByRegion[region]) {
return await this._refreshPromisesByRegion[region];
}
let resolve;
this._refreshPromisesByRegion[region] = new Promise((res, rej)=>{
resolve = res;
});
const serverUrl = await this._websocketClient.getUrlSettings(0, region);
const startDate = Date.now();
const socketInstance = (0, _socketioclient.default)(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", async ()=>{
resolve();
const latency = Date.now() - startDate;
this._latencyCache[region] = latency;
socketInstance.close();
});
await 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 = _logger.default.getLogger("LatencyService");
this._refreshRegionLatencyJob = this._refreshRegionLatencyJob.bind(this);
this._refreshRegionLatencyInterval = setInterval(this._refreshRegionLatencyJob, 15 * 60 * 1000);
}
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBzb2NrZXRJTyBmcm9tICdzb2NrZXQuaW8tY2xpZW50JztcbmltcG9ydCBMb2dnZXJNYW5hZ2VyLCB7TG9nZ2VyfSBmcm9tICcuLi8uLi9sb2dnZXInO1xuaW1wb3J0IHR5cGUgTWV0YUFwaVdlYnNvY2tldENsaWVudCBmcm9tICcuL21ldGFBcGlXZWJzb2NrZXQuY2xpZW50JztcblxuLyoqXG4gKiBTZXJ2aWNlIGZvciBtYW5hZ2luZyBhY2NvdW50IHJlcGxpY2FzIGJhc2VkIG9uIHJlZ2lvbiBsYXRlbmN5XG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIExhdGVuY3lTZXJ2aWNlIHtcbiAgXG4gIHByaXZhdGUgX3dlYnNvY2tldENsaWVudDogTWV0YUFwaVdlYnNvY2tldENsaWVudDtcbiAgcHJpdmF0ZSBfdG9rZW46IGFueTtcbiAgcHJpdmF0ZSBfY29ubmVjdFRpbWVvdXQ6IGFueTtcbiAgcHJpdmF0ZSBfbGF0ZW5jeUNhY2hlOiB7fTtcbiAgcHJpdmF0ZSBfY29ubmVjdGVkSW5zdGFuY2VzQ2FjaGU6IHt9O1xuICBwcml2YXRlIF9zeW5jaHJvbml6ZWRJbnN0YW5jZXNDYWNoZToge307XG4gIHByaXZhdGUgX3JlZnJlc2hQcm9taXNlc0J5UmVnaW9uOiB7fTtcbiAgcHJpdmF0ZSBfd2FpdENvbm5lY3RQcm9taXNlczoge307XG4gIHByaXZhdGUgX2xvZ2dlcjogTG9nZ2VyO1xuICBwcml2YXRlIF9yZWZyZXNoUmVnaW9uTGF0ZW5jeUludGVydmFsOiBOb2RlSlMuVGltZW91dDtcblxuICAvKipcbiAgICogQ29uc3RydWN0cyBsYXRlbmN5IHNlcnZpY2UgaW5zdGFuY2VcbiAgICogQHBhcmFtIHtNZXRhQXBpV2Vic29ja2V0Q2xpZW50fSB3ZWJzb2NrZXRDbGllbnQgTWV0YUFwaSB3ZWJzb2NrZXQgY2xpZW50XG4gICAqIEBwYXJhbSB7U3RyaW5nfSB0b2tlbiBhdXRob3JpemF0aW9uIHRva2VuXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBjb25uZWN0VGltZW91dCB3ZWJzb2NrZXQgY29ubmVjdCB0aW1lb3V0IGluIHNlY29uZHNcbiAgICovXG4gIGNvbnN0cnVjdG9yKHdlYnNvY2tldENsaWVudCwgdG9rZW4sIGNvbm5lY3RUaW1lb3V0KSB7XG4gICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50ID0gd2Vic29ja2V0Q2xpZW50O1xuICAgIHRoaXMuX3Rva2VuID0gdG9rZW47XG4gICAgdGhpcy5fY29ubmVjdFRpbWVvdXQgPSBjb25uZWN0VGltZW91dDtcbiAgICB0aGlzLl9sYXRlbmN5Q2FjaGUgPSB7fTtcbiAgICB0aGlzLl9jb25uZWN0ZWRJbnN0YW5jZXNDYWNoZSA9IHt9O1xuICAgIHRoaXMuX3N5bmNocm9uaXplZEluc3RhbmNlc0NhY2hlID0ge307XG4gICAgdGhpcy5fcmVmcmVzaFByb21pc2VzQnlSZWdpb24gPSB7fTtcbiAgICB0aGlzLl93YWl0Q29ubmVjdFByb21pc2VzID0ge307XG4gICAgdGhpcy5fbG9nZ2VyID0gTG9nZ2VyTWFuYWdlci5nZXRMb2dnZXIoJ0xhdGVuY3lTZXJ2aWNlJyk7XG4gICAgdGhpcy5fcmVmcmVzaFJlZ2lvbkxhdGVuY3lKb2IgPSB0aGlzLl9yZWZyZXNoUmVnaW9uTGF0ZW5jeUpvYi5iaW5kKHRoaXMpO1xuICAgIHRoaXMuX3JlZnJlc2hSZWdpb25MYXRlbmN5SW50ZXJ2YWwgPSBzZXRJbnRlcnZhbCh0aGlzLl9yZWZyZXNoUmVnaW9uTGF0ZW5jeUpvYiwgMTUgKiA2MCAqIDEwMDApO1xuICB9XG5cbiAgLyoqXG4gICAqIFN0b3BzIHRoZSBzZXJ2aWNlXG4gICAqL1xuICBzdG9wKCkge1xuICAgIGNsZWFySW50ZXJ2YWwodGhpcy5fcmVmcmVzaFJlZ2lvbkxhdGVuY3lJbnRlcnZhbCk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgbGlzdCBvZiByZWdpb25zIHNvcnRlZCBieSBsYXRlbmN5XG4gICAqIEByZXR1cm5zIHtTdHJpbmdbXX0gbGlzdCBvZiByZWdpb25zIHNvcnRlZCBieSBsYXRlbmN5XG4gICAqL1xuICBnZXQgcmVnaW9uc1NvcnRlZEJ5TGF0ZW5jeSgpIHtcbiAgICBjb25zdCByZWdpb25zID0gT2JqZWN0LmtleXModGhpcy5fbGF0ZW5jeUNhY2hlKTtcbiAgICByZWdpb25zLnNvcnQoKGEsIGIpID0+IHRoaXMuX2xhdGVuY3lDYWNoZVthXSAtIHRoaXMuX2xhdGVuY3lDYWNoZVtiXSk7XG4gICAgcmV0dXJuIHJlZ2lvbnM7XG4gIH1cblxuICAvKipcbiAgICogSW52b2tlZCB3aGVuIGFuIGluc3RhbmNlIGhhcyBiZWVuIGRpc2Nvbm5lY3RlZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gaW5zdGFuY2VJZCBpbnN0YW5jZSBpZFxuICAgKi9cbiAgb25EaXNjb25uZWN0ZWQoaW5zdGFuY2VJZCkge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBhY2NvdW50SWQgPSB0aGlzLl9nZXRBY2NvdW50SWRGcm9tSW5zdGFuY2UoaW5zdGFuY2VJZCk7XG4gICAgICBjb25zdCBkaXNjb25uZWN0ZWRSZWdpb24gPSB0aGlzLl9nZXRSZWdpb25Gcm9tSW5zdGFuY2UoaW5zdGFuY2VJZCk7XG4gICAgICB0aGlzLl9kaXNjb25uZWN0SW5zdGFuY2UoaW5zdGFuY2VJZCk7XG4gICAgICBjb25zdCBpbnN0YW5jZXMgPSB0aGlzLl9nZXRBY2NvdW50SW5zdGFuY2VzKGFjY291bnRJZCk7XG4gICAgICBpZiAoIWluc3RhbmNlcy5tYXAoaW5zdGFuY2UgPT4gdGhpcy5fY29ubmVjdGVkSW5zdGFuY2VzQ2FjaGVbaW5zdGFuY2VdKS5pbmNsdWRlcyh0cnVlKSkge1xuICAgICAgICBjb25zdCByZWdpb25zID0gdGhpcy5fZ2V0QWNjb3VudFJlZ2lvbnMoYWNjb3VudElkKTtcbiAgICAgICAgcmVnaW9ucy5maWx0ZXIocmVnaW9uID0+IHJlZ2lvbiAhPT0gZGlzY29ubmVjdGVkUmVnaW9uKVxuICAgICAgICAgIC5mb3JFYWNoKHJlZ2lvbiA9PiB0aGlzLl9zdWJzY3JpYmVBY2NvdW50UmVwbGljYShhY2NvdW50SWQsIHJlZ2lvbikpO1xuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgdGhpcy5fbG9nZ2VyLmVycm9yKGBGYWlsZWQgdG8gcHJvY2VzcyBvbkRpc2Nvbm5lY3RlZCBldmVudCBmb3IgaW5zdGFuY2UgJHtpbnN0YW5jZUlkfWAsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBhbiBhY2NvdW50IGhhcyBiZWVuIHVuc3Vic2NyaWJlZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICovXG4gIG9uVW5zdWJzY3JpYmUoYWNjb3VudElkKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlZ2lvbiA9IHRoaXMuX3dlYnNvY2tldENsaWVudC5nZXRBY2NvdW50UmVnaW9uKGFjY291bnRJZCk7XG4gICAgICBjb25zdCBwcmltYXJ5QWNjb3VudElkID0gdGhpcy5fd2Vic29ja2V0Q2xpZW50LmFjY291bnRzQnlSZXBsaWNhSWRbYWNjb3VudElkXTtcbiAgICAgIGNvbnN0IGluc3RhbmNlcyA9IHRoaXMuX2dldEFjY291bnRJbnN0YW5jZXMocHJpbWFyeUFjY291bnRJZCk7XG4gICAgICBpbnN0YW5jZXMuZmlsdGVyKGluc3RhbmNlSWQgPT4gaW5zdGFuY2VJZC5zdGFydHNXaXRoKGAke3ByaW1hcnlBY2NvdW50SWR9OiR7cmVnaW9ufTpgKSlcbiAgICAgICAgLmZvckVhY2goaW5zdGFuY2VJZCA9PiB0aGlzLl9kaXNjb25uZWN0SW5zdGFuY2UoaW5zdGFuY2VJZCkpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgdGhpcy5fbG9nZ2VyLmVycm9yKGBGYWlsZWQgdG8gcHJvY2VzcyBvblVuc3Vic2NyaWJlIGV2ZW50IGZvciBhY2NvdW50ICR7YWNjb3VudElkfWAsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBhbiBpbnN0YW5jZSBoYXMgYmVlbiBjb25uZWN0ZWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGluc3RhbmNlSWQgaW5zdGFuY2UgaWRcbiAgICovXG4gIGFzeW5jIG9uQ29ubmVjdGVkKGluc3RhbmNlSWQpIHtcbiAgICB0cnkge1xuICAgICAgdGhpcy5fY29ubmVjdGVkSW5zdGFuY2VzQ2FjaGVbaW5zdGFuY2VJZF0gPSB0cnVlO1xuICAgICAgY29uc3QgYWNjb3VudElkID0gdGhpcy5fZ2V0QWNjb3VudElkRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpO1xuICAgICAgY29uc3QgcmVnaW9uID0gdGhpcy5fZ2V0UmVnaW9uRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpO1xuICAgICAgaWYgKCF0aGlzLl9sYXRlbmN5Q2FjaGVbcmVnaW9uXSkge1xuICAgICAgICBhd2FpdCB0aGlzLl9yZWZyZXNoTGF0ZW5jeShyZWdpb24pO1xuICAgICAgfVxuICAgICAgY29uc3QgaW5zdGFuY2VzID0gdGhpcy5nZXRBY3RpdmVBY2NvdW50SW5zdGFuY2VzKGFjY291bnRJZCk7XG4gICAgICBjb25zdCBzeW5jaHJvbml6ZWRJbnN0YW5jZXMgPSB0aGlzLmdldFN5bmNocm9uaXplZEFjY291bnRJbnN0YW5jZXMoYWNjb3VudElkKTtcbiAgICAgIGNvbnN0IHJlZ2lvbnMgPSBpbnN0YW5jZXMubWFwKGluc3RhbmNlID0+IHRoaXMuX2dldFJlZ2lvbkZyb21JbnN0YW5jZShpbnN0YW5jZSkpO1xuICAgICAgaWYgKGluc3RhbmNlcy5sZW5ndGggPiAxICYmICFzeW5jaHJvbml6ZWRJbnN0YW5jZXMubGVuZ3RoKSB7XG4gICAgICAgIGNvbnN0IHJlZ2lvbnNUb0Rpc2Nvbm5lY3QgPSB0aGlzLnJlZ2lvbnNTb3J0ZWRCeUxhdGVuY3lcbiAgICAgICAgICAuZmlsdGVyKHNvcnRlZFJlZ2lvbiA9PiByZWdpb25zLmluY2x1ZGVzKHNvcnRlZFJlZ2lvbikpLnNsaWNlKDEpO1xuICAgICAgICByZWdpb25zVG9EaXNjb25uZWN0LmZvckVhY2gocmVnaW9uSXRlbSA9PiB7XG4gICAgICAgICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LnVuc3Vic2NyaWJlKHRoaXMuX3dlYnNvY2tldENsaWVudC5hY2NvdW50UmVwbGljYXNbYWNjb3VudElkXVtyZWdpb25JdGVtXSk7XG4gICAgICAgICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LnVuc3Vic2NyaWJlQWNjb3VudFJlZ2lvbihhY2NvdW50SWQsIHJlZ2lvbkl0ZW0pO1xuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICAgIGlmICh0aGlzLl93YWl0Q29ubmVjdFByb21pc2VzW2FjY291bnRJZF0pIHtcbiAgICAgICAgdGhpcy5fd2FpdENvbm5lY3RQcm9taXNlc1thY2NvdW50SWRdLnJlc29sdmUoKTtcbiAgICAgICAgZGVsZXRlIHRoaXMuX3dhaXRDb25uZWN0UHJvbWlzZXNbYWNjb3VudElkXTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcihgRmFpbGVkIHRvIHByb2Nlc3Mgb25Db25uZWN0ZWQgZXZlbnQgZm9yIGluc3RhbmNlICR7aW5zdGFuY2VJZH1gLCBlcnIpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBJbnZva2VkIHdoZW4gYW4gaW5zdGFuY2UgaGFzIGJlZW4gc3luY2hyb25pemVkXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBpbnN0YW5jZUlkIGluc3RhbmNlIGlkXG4gICAqL1xuICBhc3luYyBvbkRlYWxzU3luY2hyb25pemVkKGluc3RhbmNlSWQpIHtcbiAgICB0cnkge1xuICAgICAgdGhpcy5fc3luY2hyb25pemVkSW5zdGFuY2VzQ2FjaGVbaW5zdGFuY2VJZF0gPSB0cnVlO1xuICAgICAgY29uc3QgYWNjb3VudElkID0gdGhpcy5fZ2V0QWNjb3VudElkRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpO1xuICAgICAgY29uc3QgcmVnaW9uID0gdGhpcy5fZ2V0UmVnaW9uRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpO1xuICAgICAgaWYgKCF0aGlzLl9sYXRlbmN5Q2FjaGVbcmVnaW9uXSkge1xuICAgICAgICBhd2FpdCB0aGlzLl9yZWZyZXNoTGF0ZW5jeShyZWdpb24pO1xuICAgICAgfVxuICAgICAgY29uc3QgaW5zdGFuY2VzID0gdGhpcy5nZXRTeW5jaHJvbml6ZWRBY2NvdW50SW5zdGFuY2VzKGFjY291bnRJZCk7XG4gICAgICBjb25zdCByZWdpb25zID0gWy4uLm5ldyBTZXQoaW5zdGFuY2VzLm1hcChpbnN0YW5jZSA9PiB0aGlzLl9nZXRSZWdpb25Gcm9tSW5zdGFuY2UoaW5zdGFuY2UpKSldO1xuICAgICAgaWYgKGluc3RhbmNlcy5sZW5ndGggPiAxKSB7XG4gICAgICAgIGNvbnN0IHJlZ2lvbnNUb0Rpc2Nvbm5lY3QgPSB0aGlzLnJlZ2lvbnNTb3J0ZWRCeUxhdGVuY3lcbiAgICAgICAgICAuZmlsdGVyKHNvcnRlZFJlZ2lvbiA9PiByZWdpb25zLmluY2x1ZGVzKHNvcnRlZFJlZ2lvbikpLnNsaWNlKDEpO1xuICAgICAgICByZWdpb25zVG9EaXNjb25uZWN0LmZvckVhY2gocmVnaW9uSXRlbSA9PiB7XG4gICAgICAgICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LnVuc3Vic2NyaWJlKHRoaXMuX3dlYnNvY2tldENsaWVudC5hY2NvdW50UmVwbGljYXNbYWNjb3VudElkXVtyZWdpb25JdGVtXSk7XG4gICAgICAgICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LnVuc3Vic2NyaWJlQWNjb3VudFJlZ2lvbihhY2NvdW50SWQsIHJlZ2lvbkl0ZW0pO1xuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcihgRmFpbGVkIHRvIHByb2Nlc3Mgb25EZWFsc1N5bmNocm9uaXplZCBldmVudCBmb3IgaW5zdGFuY2UgJHtpbnN0YW5jZUlkfWAsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGxpc3Qgb2YgY3VycmVudGx5IGNvbm5lY3RlZCBhY2NvdW50IGluc3RhbmNlc1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHJldHVybnMge1N0cmluZ1tdfSBsaXN0IG9mIGNvbm5lY3RlZCBhY2NvdW50IGluc3RhbmNlc1xuICAgKi9cbiAgZ2V0QWN0aXZlQWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpIHtcbiAgICByZXR1cm4gdGhpcy5fZ2V0QWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpLmZpbHRlcihpbnN0YW5jZSA9PiB0aGlzLl9jb25uZWN0ZWRJbnN0YW5jZXNDYWNoZVtpbnN0YW5jZV0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGxpc3Qgb2YgY3VycmVudGx5IHN5bmNocm9uaXplZCBhY2NvdW50IGluc3RhbmNlc1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHJldHVybnMge1N0cmluZ1tdfSBsaXN0IG9mIHN5bmNocm9uaXplZCBhY2NvdW50IGluc3RhbmNlc1xuICAgKi9cbiAgZ2V0U3luY2hyb25pemVkQWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpIHtcbiAgICByZXR1cm4gdGhpcy5fZ2V0QWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpLmZpbHRlcihpbnN0YW5jZSA9PiB0aGlzLl9zeW5jaHJvbml6ZWRJbnN0YW5jZXNDYWNoZVtpbnN0YW5jZV0pO1xuICB9XG5cbiAgLyoqXG4gICAqIFdhaXRzIGZvciBjb25uZWN0ZWQgaW5zdGFuY2VcbiAgICogQHBhcmFtIHtTdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkIFxuICAgKiBAcmV0dXJucyB7U3RyaW5nfSBpbnN0YW5jZSBpZFxuICAgKi9cbiAgYXN5bmMgd2FpdENvbm5lY3RlZEluc3RhbmNlKGFjY291bnRJZCkge1xuICAgIGxldCBpbnN0YW5jZXMgPSB0aGlzLmdldEFjdGl2ZUFjY291bnRJbnN0YW5jZXMoYWNjb3VudElkKTtcbiAgICBpZiAoIWluc3RhbmNlcy5sZW5ndGgpIHtcbiAgICAgIGlmICghdGhpcy5fd2FpdENvbm5lY3RQcm9taXNlc1thY2NvdW50SWRdKSB7XG4gICAgICAgIGxldCByZXNvbHZlO1xuICAgICAgICBsZXQgcHJvbWlzZSA9IG5ldyBQcm9taXNlKChyZXMsIHJlaikgPT4ge1xuICAgICAgICAgIHJlc29sdmUgPSByZXM7XG4gICAgICAgIH0pO1xuICAgICAgICB0aGlzLl93YWl0Q29ubmVjdFByb21pc2VzW2FjY291bnRJZF0gPSB7cHJvbWlzZSwgcmVzb2x2ZX07XG4gICAgICB9XG4gICAgICBhd2FpdCB0aGlzLl93YWl0Q29ubmVjdFByb21pc2VzW2FjY291bnRJZF0ucHJvbWlzZTtcbiAgICAgIGluc3RhbmNlcyA9IHRoaXMuZ2V0QWN0aXZlQWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpO1xuICAgIH1cbiAgICByZXR1cm4gaW5zdGFuY2VzWzBdO1xuICB9XG5cbiAgX2dldEFjY291bnRJbnN0YW5jZXMoYWNjb3VudElkKSB7XG4gICAgcmV0dXJuIE9iamVjdC5rZXlzKHRoaXMuX2Nvbm5lY3RlZEluc3RhbmNlc0NhY2hlKS5maWx0ZXIoaW5zdGFuY2VJZCA9PiBpbnN0YW5jZUlkLnN0YXJ0c1dpdGgoYCR7YWNjb3VudElkfTpgKSk7XG4gIH1cblxuICBfZ2V0QWNjb3VudFJlZ2lvbnMoYWNjb3VudElkKSB7XG4gICAgY29uc3QgcmVnaW9ucyA9IFtdO1xuICAgIGNvbnN0IGluc3RhbmNlcyA9IHRoaXMuX2dldEFjY291bnRJbnN0YW5jZXMoYWNjb3VudElkKTtcbiAgICBpbnN0YW5jZXMuZm9yRWFjaChpbnN0YW5jZSA9PiB7XG4gICAgICBjb25zdCByZWdpb24gPSB0aGlzLl9nZXRSZWdpb25Gcm9tSW5zdGFuY2UoaW5zdGFuY2UpO1xuICAgICAgaWYgKCFyZWdpb25zLmluY2x1ZGVzKHJlZ2lvbikpIHtcbiAgICAgICAgcmVnaW9ucy5wdXNoKHJlZ2lvbik7XG4gICAgICB9XG4gICAgfSk7XG4gICAgcmV0dXJuIHJlZ2lvbnM7XG4gIH1cblxuICBfZ2V0QWNjb3VudElkRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpIHtcbiAgICByZXR1cm4gaW5zdGFuY2VJZC5zcGxpdCgnOicpWzBdO1xuICB9XG5cbiAgX2dldFJlZ2lvbkZyb21JbnN0YW5jZShpbnN0YW5jZUlkKSB7XG4gICAgcmV0dXJuIGluc3RhbmNlSWQuc3BsaXQoJzonKVsxXTtcbiAgfVxuXG4gIF9kaXNjb25uZWN0SW5zdGFuY2UoaW5zdGFuY2VJZCkge1xuICAgIHRoaXMuX2Nvbm5lY3RlZEluc3RhbmNlc0NhY2hlW2luc3RhbmNlSWRdID0gZmFsc2U7XG4gICAgaWYgKHRoaXMuX3N5bmNocm9uaXplZEluc3RhbmNlc0NhY2hlW2luc3RhbmNlSWRdKSB7XG4gICAgICB0aGlzLl9zeW5jaHJvbml6ZWRJbnN0YW5jZXNDYWNoZVtpbnN0YW5jZUlkXSA9IGZhbHNlO1xuICAgIH1cbiAgfVxuXG4gIF9zdWJzY3JpYmVBY2NvdW50UmVwbGljYShhY2NvdW50SWQsIHJlZ2lvbikge1xuICAgIGNvbnN0IGluc3RhbmNlSWQgPSB0aGlzLl93ZWJzb2NrZXRDbGllbnQuYWNjb3VudFJlcGxpY2FzW2FjY291bnRJZF1bcmVnaW9uXTtcbiAgICBpZiAoaW5zdGFuY2VJZCkge1xuICAgICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LmVuc3VyZVN1YnNjcmliZShpbnN0YW5jZUlkLCAwKTtcbiAgICAgIHRoaXMuX3dlYnNvY2tldENsaWVudC5lbnN1cmVTdWJzY3JpYmUoaW5zdGFuY2VJZCwgMSk7XG4gICAgfVxuICB9XG5cbiAgYXN5bmMgX3JlZnJlc2hSZWdpb25MYXRlbmN5Sm9iKCkge1xuICAgIGZvcihsZXQgcmVnaW9uIG9mIE9iamVjdC5rZXlzKHRoaXMuX2xhdGVuY3lDYWNoZSkpIHtcbiAgICAgIGF3YWl0IHRoaXMuX3JlZnJlc2hMYXRlbmN5KHJlZ2lvbik7XG4gICAgfVxuXG4gICAgLy8gRm9yIGV2ZXJ5IGFjY291bnQsIHN3aXRjaCB0byBhIGJldHRlciByZWdpb24gaWYgc3VjaCBleGlzdHNcbiAgICBjb25zdCBhY2NvdW50SWRzID0gW107XG4gICAgT2JqZWN0LmtleXModGhpcy5fY29ubmVjdGVkSW5zdGFuY2VzQ2FjaGUpXG4gICAgICAuZmlsdGVyKGluc3RhbmNlSWQgPT4gdGhpcy5fY29ubmVjdGVkSW5zdGFuY2VzQ2FjaGVbaW5zdGFuY2VJZF0pXG4gICAgICAuZm9yRWFjaChpbnN0YW5jZUlkID0+IHtcbiAgICAgICAgY29uc3QgYWNjb3VudElkID0gdGhpcy5fZ2V0QWNjb3VudElkRnJvbUluc3RhbmNlKGluc3RhbmNlSWQpO1xuICAgICAgICBpZiAoIWFjY291bnRJZHMuaW5jbHVkZXMoYWNjb3VudElkKSkge1xuICAgICAgICAgIGFjY291bnRJZHMucHVzaChhY2NvdW50SWQpO1xuICAgICAgICB9XG4gICAgICB9KTtcblxuICAgIGNvbnN0IHNvcnRlZFJlZ2lvbnMgPSB0aGlzLnJlZ2lvbnNTb3J0ZWRCeUxhdGVuY3k7XG5cbiAgICBhY2NvdW50SWRzLmZvckVhY2goYWNjb3VudElkID0+IHtcbiAgICAgIGNvbnN0IGFjY291bnRSZWdpb25zID0gdGhpcy5fZ2V0QWNjb3VudFJlZ2lvbnMoYWNjb3VudElkKTtcbiAgICAgIGNvbnN0IGFjdGl2ZUluc3RhbmNlcyA9IHRoaXMuZ2V0QWN0aXZlQWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpO1xuICAgICAgaWYgKGFjdGl2ZUluc3RhbmNlcy5sZW5ndGggPT09IDEpIHtcbiAgICAgICAgY29uc3QgYWN0aXZlSW5zdGFuY2UgPSBhY3RpdmVJbnN0YW5jZXNbMF07XG4gICAgICAgIGNvbnN0IGFjdGl2ZVJlZ2lvbiA9IHRoaXMuX2dldFJlZ2lvbkZyb21JbnN0YW5jZShhY3RpdmVJbnN0YW5jZSk7XG4gICAgICAgIGNvbnN0IGFjY291bnRCZXN0UmVnaW9ucyA9IHNvcnRlZFJlZ2lvbnMuZmlsdGVyKHJlZ2lvbiA9PiBhY2NvdW50UmVnaW9ucy5pbmNsdWRlcyhyZWdpb24pKTtcbiAgICAgICAgaWYgKGFjY291bnRCZXN0UmVnaW9uc1swXSAhPT0gYWN0aXZlUmVnaW9uKSB7XG4gICAgICAgICAgdGhpcy5fc3Vic2NyaWJlQWNjb3VudFJlcGxpY2EoYWNjb3VudElkLCBhY2NvdW50QmVzdFJlZ2lvbnNbMF0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICBhc3luYyBfcmVmcmVzaExhdGVuY3kocmVnaW9uKSB7XG4gICAgaWYgKHRoaXMuX3JlZnJlc2hQcm9taXNlc0J5UmVnaW9uW3JlZ2lvbl0pIHtcbiAgICAgIHJldHVybiBhd2FpdCB0aGlzLl9yZWZyZXNoUHJvbWlzZXNCeVJlZ2lvbltyZWdpb25dO1xuICAgIH1cbiAgICBsZXQgcmVzb2x2ZTtcbiAgICB0aGlzLl9yZWZyZXNoUHJvbWlzZXNCeVJlZ2lvbltyZWdpb25dID0gbmV3IFByb21pc2UoKHJlcywgcmVqKSA9PiB7XG4gICAgICByZXNvbHZlID0gcmVzO1xuICAgIH0pO1xuICAgIGNvbnN0IHNlcnZlclVybCA9IGF3YWl0IHRoaXMuX3dlYnNvY2tldENsaWVudC5nZXRVcmxTZXR0aW5ncygwLCByZWdpb24pO1xuICAgIGNvbnN0IHN0YXJ0RGF0ZSA9IERhdGUubm93KCk7XG4gIFxuICAgIGNvbnN0IHNvY2tldEluc3RhbmNlID0gc29ja2V0SU8oc2VydmVyVXJsLnVybCwge1xuICAgICAgcGF0aDogJy93cycsXG4gICAgICByZWNvbm5lY3Rpb246IHRydWUsXG4gICAgICByZWNvbm5lY3Rpb25EZWxheTogMTAwMCxcbiAgICAgIHJlY29ubmVjdGlvbkRlbGF5TWF4OiA1MDAwLFxuICAgICAgcmVjb25uZWN0aW9uQXR0ZW1wdHM6IEluZmluaXR5LFxuICAgICAgdGltZW91dDogdGhpcy5fY29ubmVjdFRpbWVvdXQsXG4gICAgICBxdWVyeToge1xuICAgICAgICAnYXV0aC10b2tlbic6IHRoaXMuX3Rva2VuLFxuICAgICAgICBwcm90b2NvbDogM1xuICAgICAgfVxuICAgIH0pO1xuICAgIHNvY2tldEluc3RhbmNlLm9uKCdjb25uZWN0JywgYXN5bmMgKCkgPT4ge1xuICAgICAgcmVzb2x2ZSgpO1xuICAgICAgY29uc3QgbGF0ZW5jeSA9IERhdGUubm93KCkgLSBzdGFydERhdGU7XG4gICAgICB0aGlzLl9sYXRlbmN5Q2FjaGVbcmVnaW9uXSA9IGxhdGVuY3k7XG4gICAgICBzb2NrZXRJbnN0YW5jZS5jbG9zZSgpO1xuICAgIH0pO1xuICAgIGF3YWl0IHRoaXMuX3JlZnJlc2hQcm9taXNlc0J5UmVnaW9uW3JlZ2lvbl07XG4gICAgZGVsZXRlIHRoaXMuX3JlZnJlc2hQcm9taXNlc0J5UmVnaW9uW3JlZ2lvbl07XG4gIH1cblxufVxuIl0sIm5hbWVzIjpbIkxhdGVuY3lTZXJ2aWNlIiwic3RvcCIsImNsZWFySW50ZXJ2YWwiLCJfcmVmcmVzaFJlZ2lvbkxhdGVuY3lJbnRlcnZhbCIsInJlZ2lvbnNTb3J0ZWRCeUxhdGVuY3kiLCJyZWdpb25zIiwiT2JqZWN0Iiwia2V5cyIsIl9sYXRlbmN5Q2FjaGUiLCJzb3J0IiwiYSIsImIiLCJvbkRpc2Nvbm5lY3RlZCIsImluc3RhbmNlSWQiLCJhY2NvdW50SWQiLCJfZ2V0QWNjb3VudElkRnJvbUluc3RhbmNlIiwiZGlzY29ubmVjdGVkUmVnaW9uIiwiX2dldFJlZ2lvbkZyb21JbnN0YW5jZSIsIl9kaXNjb25uZWN0SW5zdGFuY2UiLCJpbnN0YW5jZXMiLCJfZ2V0QWNjb3VudEluc3RhbmNlcyIsIm1hcCIsImluc3RhbmNlIiwiX2Nvbm5lY3RlZEluc3RhbmNlc0NhY2hlIiwiaW5jbHVkZXMiLCJfZ2V0QWNjb3VudFJlZ2lvbnMiLCJmaWx0ZXIiLCJyZWdpb24iLCJmb3JFYWNoIiwiX3N1YnNjcmliZUFjY291bnRSZXBsaWNhIiwiZXJyIiwiX2xvZ2dlciIsImVycm9yIiwib25VbnN1YnNjcmliZSIsIl93ZWJzb2NrZXRDbGllbnQiLCJnZXRBY2NvdW50UmVnaW9uIiwicHJpbWFyeUFjY291bnRJZCIsImFjY291bnRzQnlSZXBsaWNhSWQiLCJzdGFydHNXaXRoIiwib25Db25uZWN0ZWQiLCJfcmVmcmVzaExhdGVuY3kiLCJnZXRBY3RpdmVBY2NvdW50SW5zdGFuY2VzIiwic3luY2hyb25pemVkSW5zdGFuY2VzIiwiZ2V0U3luY2hyb25pemVkQWNjb3VudEluc3RhbmNlcyIsImxlbmd0aCIsInJlZ2lvbnNUb0Rpc2Nvbm5lY3QiLCJzb3J0ZWRSZWdpb24iLCJzbGljZSIsInJlZ2lvbkl0ZW0iLCJ1bnN1YnNjcmliZSIsImFjY291bnRSZXBsaWNhcyIsInVuc3Vic2NyaWJlQWNjb3VudFJlZ2lvbiIsIl93YWl0Q29ubmVjdFByb21pc2VzIiwicmVzb2x2ZSIsIm9uRGVhbHNTeW5jaHJvbml6ZWQiLCJfc3luY2hyb25pemVkSW5zdGFuY2VzQ2FjaGUiLCJTZXQiLCJ3YWl0Q29ubmVjdGVkSW5zdGFuY2UiLCJwcm9taXNlIiwiUHJvbWlzZSIsInJlcyIsInJlaiIsInB1c2giLCJzcGxpdCIsImVuc3VyZVN1YnNjcmliZSIsIl9yZWZyZXNoUmVnaW9uTGF0ZW5jeUpvYiIsImFjY291bnRJZHMiLCJzb3J0ZWRSZWdpb25zIiwiYWNjb3VudFJlZ2lvbnMiLCJhY3RpdmVJbnN0YW5jZXMiLCJhY3RpdmVJbnN0YW5jZSIsImFjdGl2ZVJlZ2lvbiIsImFjY291bnRCZXN0UmVnaW9ucyIsIl9yZWZyZXNoUHJvbWlzZXNCeVJlZ2lvbiIsInNlcnZlclVybCIsImdldFVybFNldHRpbmdzIiwic3RhcnREYXRlIiwiRGF0ZSIsIm5vdyIsInNvY2tldEluc3RhbmNlIiwic29ja2V0SU8iLCJ1cmwiLCJwYXRoIiwicmVjb25uZWN0aW9uIiwicmVjb25uZWN0aW9uRGVsYXkiLCJyZWNvbm5lY3Rpb25EZWxheU1heCIsInJlY29ubmVjdGlvbkF0dGVtcHRzIiwiSW5maW5pdHkiLCJ0aW1lb3V0IiwiX2Nvbm5lY3RUaW1lb3V0IiwicXVlcnkiLCJfdG9rZW4iLCJwcm90b2NvbCIsIm9uIiwibGF0ZW5jeSIsImNsb3NlIiwiY29uc3RydWN0b3IiLCJ3ZWJzb2NrZXRDbGllbnQiLCJ0b2tlbiIsImNvbm5lY3RUaW1lb3V0IiwiTG9nZ2VyTWFuYWdlciIsImdldExvZ2dlciIsImJpbmQiLCJzZXRJbnRlcnZhbCJdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7ZUFTcUJBOzs7dUVBUEE7K0RBQ2U7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFNckIsSUFBQSxBQUFNQSxpQkFBTixNQUFNQTtJQWlDbkI7O0dBRUMsR0FDREMsT0FBTztRQUNMQyxjQUFjLElBQUksQ0FBQ0MsNkJBQTZCO0lBQ2xEO0lBRUE7OztHQUdDLEdBQ0QsSUFBSUMseUJBQXlCO1FBQzNCLE1BQU1DLFVBQVVDLE9BQU9DLElBQUksQ0FBQyxJQUFJLENBQUNDLGFBQWE7UUFDOUNILFFBQVFJLElBQUksQ0FBQyxDQUFDQyxHQUFHQyxJQUFNLElBQUksQ0FBQ0gsYUFBYSxDQUFDRSxFQUFFLEdBQUcsSUFBSSxDQUFDRixhQUFhLENBQUNHLEVBQUU7UUFDcEUsT0FBT047SUFDVDtJQUVBOzs7R0FHQyxHQUNETyxlQUFlQyxVQUFVLEVBQUU7UUFDekIsSUFBSTtZQUNGLE1BQU1DLFlBQVksSUFBSSxDQUFDQyx5QkFBeUIsQ0FBQ0Y7WUFDakQsTUFBTUcscUJBQXFCLElBQUksQ0FBQ0Msc0JBQXNCLENBQUNKO1lBQ3ZELElBQUksQ0FBQ0ssbUJBQW1CLENBQUNMO1lBQ3pCLE1BQU1NLFlBQVksSUFBSSxDQUFDQyxvQkFBb0IsQ0FBQ047WUFDNUMsSUFBSSxDQUFDSyxVQUFVRSxHQUFHLENBQUNDLENBQUFBLFdBQVksSUFBSSxDQUFDQyx3QkFBd0IsQ0FBQ0QsU0FBUyxFQUFFRSxRQUFRLENBQUMsT0FBTztnQkFDdEYsTUFBTW5CLFVBQVUsSUFBSSxDQUFDb0Isa0JBQWtCLENBQUNYO2dCQUN4Q1QsUUFBUXFCLE1BQU0sQ0FBQ0MsQ0FBQUEsU0FBVUEsV0FBV1gsb0JBQ2pDWSxPQUFPLENBQUNELENBQUFBLFNBQVUsSUFBSSxDQUFDRSx3QkFBd0IsQ0FBQ2YsV0FBV2E7WUFDaEU7UUFDRixFQUFFLE9BQU9HLEtBQUs7WUFDWixJQUFJLENB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