UNPKG

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
'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); }); }; } 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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