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)

286 lines (285 loc) 39.2 kB
"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+IHtcbiAgICAgIGNvbnN0IGFjY291bnRSZWdpb25zID0gdGhpcy5fZ2V0QWNjb3VudFJlZ2lvbnMoYWNjb3VudElkKTtcbiAgICAgIGNvbnN0IGFjdGl2ZUluc3RhbmNlcyA9IHRoaXMuZ2V0QWN0aXZlQWNjb3VudEluc3RhbmNlcyhhY2NvdW50SWQpO1xuICAgICAgaWYgKGFjdGl2ZUluc3RhbmNlcy5sZW5ndGggPT09IDEpIHtcbiAgICAgICAgY29uc3QgYWN0aXZlSW5zdGFuY2UgPSBhY3RpdmVJbnN0YW5jZXNbMF07XG4gICAgICAgIGNvbnN0IGFjdGl2ZVJlZ2lvbiA9IHRoaXMuX2dldFJlZ2lvbkZyb21JbnN0YW5jZShhY3RpdmVJbnN0YW5jZSk7XG4gICAgICAgIGNvbnN0IGFjY291bnRCZXN0UmVnaW9ucyA9IHNvcnRlZFJlZ2lvbnMuZmlsdGVyKHJlZ2lvbiA9PiBhY2NvdW50UmVnaW9ucy5pbmNsdWRlcyhyZWdpb24pKTtcbiAgICAgICAgaWYgKGFjY291bnRCZXN0UmVnaW9uc1swXSAhPT0gYWN0aXZlUmVnaW9uKSB7XG4gICAgICAgICAgdGhpcy5fc3Vic2NyaWJlQWNjb3VudFJlcGxpY2EoYWNjb3VudElkLCBhY2NvdW50QmVzdFJlZ2lvbnNbMF0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICBhc3luYyBfcmVmcmVzaExhdGVuY3kocmVnaW9uKSB7XG4gICAgaWYgKHRoaXMuX3JlZnJlc2hQcm9taXNlc0J5UmVnaW9uW3JlZ2lvbl0pIHtcbiAgICAgIHJldHVybiBhd2FpdCB0aGlzLl9yZWZyZXNoUHJvbWlzZXNCeVJlZ2lvbltyZWdpb25dO1xuICAgIH1cbiAgICBsZXQgcmVzb2x2ZTtcbiAgICB0aGlzLl9yZWZyZXNoUHJvbWlzZXNCeVJlZ2lvbltyZWdpb25dID0gbmV3IFByb21pc2UoKHJlcywgcmVqKSA9PiB7XG4gICAgICByZXNvbHZlID0gcmVzO1xuICAgIH0pO1xuICAgIGNvbnN0IHNlcnZlclVybCA9IGF3YWl0IHRoaXMuX3dlYnNvY2tldENsaWVudC5nZXRVcmxTZXR0aW5ncygwLCByZWdpb24pO1xuICAgIGNvbnN0IHN0YXJ0RGF0ZSA9IERhdGUubm93KCk7XG4gIFxuICAgIGNvbnN0IHNvY2tldEluc3RhbmNlID0gc29ja2V0SU8oc2VydmVyVXJsLnVybCwge1xuICAgICAgcGF0aDogJy93cycsXG4gICAgICByZWNvbm5lY3Rpb246IHRydWUsXG4gICAgICByZWNvbm5lY3Rpb25EZWxheTogMTAwMCxcbiAgICAgIHJlY29ubmVjdGlvbkRlbGF5TWF4OiA1MDAwLFxuICAgICAgcmVjb25uZWN0aW9uQXR0ZW1wdHM6IEluZmluaXR5LFxuICAgICAgdGltZW91dDogdGhpcy5fY29ubmVjdFRpbWVvdXQsXG4gICAgICBxdWVyeToge1xuICAgICAgICAnYXV0aC10b2tlbic6IHRoaXMuX3Rva2VuLFxuICAgICAgICBwcm90b2NvbDogM1xuICAgICAgfVxuICAgIH0pO1xuICAgIHNvY2tldEluc3RhbmNlLm9uKCdjb25uZWN0JywgYXN5bmMgKCkgPT4ge1xuICAgICAgcmVzb2x2ZSgpO1xuICAgICAgY29uc3QgbGF0ZW5jeSA9IERhdGUubm93KCkgLSBzdGFydERhdGU7XG4gICAgICB0aGlzLl9sYXRlbmN5Q2FjaGVbcmVnaW9uXSA9IGxhdGVuY3k7XG4gICAgICBzb2NrZXRJbnN0YW5jZS5jbG9zZSgpO1xuICAgIH0pO1xuICAgIGF3YWl0IHRoaXMuX3JlZnJlc2hQcm9taXNlc0J5UmVnaW9uW3JlZ2lvbl07XG4gICAgZGVsZXRlIHRoaXMuX3JlZnJlc2hQcm9taXNlc0J5UmVnaW9uW3JlZ2lvbl07XG4gIH1cblxufVxuIl0sIm5hbWVzIjpbIkxhdGVuY3lTZXJ2aWNlIiwic3RvcCIsImNsZWFySW50ZXJ2YWwiLCJfcmVmcmVzaFJlZ2lvbkxhdGVuY3lJbnRlcnZhbCIsInJlZ2lvbnNTb3J0ZWRCeUxhdGVuY3kiLCJyZWdpb25zIiwiT2JqZWN0Iiwia2V5cyIsIl9sYXRlbmN5Q2FjaGUiLCJzb3J0IiwiYSIsImIiLCJvbkRpc2Nvbm5lY3RlZCIsImluc3RhbmNlSWQiLCJhY2NvdW50SWQiLCJfZ2V0QWNjb3VudElkRnJvbUluc3RhbmNlIiwiZGlzY29ubmVjdGVkUmVnaW9uIiwiX2dldFJlZ2lvbkZyb21JbnN0YW5jZSIsIl9kaXNjb25uZWN0SW5zdGFuY2UiLCJpbnN0YW5jZXMiLCJfZ2V0QWNjb3VudEluc3RhbmNlcyIsIm1hcCIsImluc3RhbmNlIiwiX2Nvbm5lY3RlZEluc3RhbmNlc0NhY2hlIiwiaW5jbHVkZXMiLCJfZ2V0QWNjb3VudFJlZ2lvbnMiLCJmaWx0ZXIiLCJyZWdpb24iLCJmb3JFYWNoIiwiX3N1YnNjcmliZUFjY291bnRSZXBsaWNhIiwiZXJyIiwiX2xvZ2dlciIsImVycm9yIiwib25VbnN1YnNjcmliZSIsIl93ZWJzb2NrZXRDbGllbnQiLCJnZXRBY2NvdW50UmVnaW9uIiwicHJpbWFyeUFjY291bnRJZCIsImFjY291bnRzQnlSZXBsaWNhSWQiLCJzdGFydHNXaXRoIiwib25Db25uZWN0ZWQiLCJfcmVmcmVzaExhdGVuY3kiLCJnZXRBY3RpdmVBY2NvdW50SW5zdGFuY2VzIiwic3luY2hyb25pemVkSW5zdGFuY2VzIiwiZ2V0U3luY2hyb25pemVkQWNjb3VudEluc3RhbmNlcyIsImxlbmd0aCIsInJlZ2lvbnNUb0Rpc2Nvbm5lY3QiLCJzb3J0ZWRSZWdpb24iLCJzbGljZSIsInJlZ2lvbkl0ZW0iLCJ1bnN1YnNjcmliZSIsImFjY291bnRSZXBsaWNhcyIsInVuc3Vic2NyaWJlQWNjb3VudFJlZ2lvbiIsIl93YWl0Q29ubmVjdFByb21pc2VzIiwicmVzb2x2ZSIsIm9uRGVhbHNTeW5jaHJvbml6ZWQiLCJfc3luY2hyb25pemVkSW5zdGFuY2VzQ2FjaGUiLCJTZXQiLCJ3YWl0Q29ubmVjdGVkSW5zdGFuY2UiLCJwcm9taXNlIiwiUHJvbWlzZSIsInJlcyIsInJlaiIsInB1c2giLCJzcGxpdCIsImVuc3VyZVN1YnNjcmliZSIsIl9yZWZyZXNoUmVnaW9uTGF0ZW5jeUpvYiIsImFjY291bnRJZHMiLCJzb3J0ZWRSZWdpb25zIiwiYWNjb3VudFJlZ2lvbnMiLCJhY3RpdmVJbnN0YW5jZXMiLCJhY3RpdmVJbnN0YW5jZSIsImFjdGl2ZVJlZ2lvbiIsImFjY291bnRCZXN0UmVnaW9ucyIsIl9yZWZyZXNoUHJvbWlzZXNCeVJlZ2lvbiIsInNlcnZlclVybCIsImdldFVybFNldHRpbmdzIiwic3RhcnREYXRlIiwiRGF0ZSIsIm5vdyIsInNvY2tldEluc3RhbmNlIiwic29ja2V0SU8iLCJ1cmwiLCJwYXRoIiwicmVjb25uZWN0aW9uIiwicmVjb25uZWN0aW9uRGVsYXkiLCJyZWNvbm5lY3Rpb25EZWxheU1heCIsInJlY29ubmVjdGlvbkF0dGVtcHRzIiwiSW5maW5pdHkiLCJ0aW1lb3V0IiwiX2Nvbm5lY3RUaW1lb3V0IiwicXVlcnkiLCJfdG9rZW4iLCJwcm90b2NvbCIsIm9uIiwibGF0ZW5jeSIsImNsb3NlIiwiY29uc3RydWN0b3IiLCJ3ZWJzb2NrZXRDbGllbnQiLCJ0b2tlbiIsImNvbm5lY3RUaW1lb3V0IiwiTG9nZ2VyTWFuYWdlciIsImdldExvZ2dlciIsImJpbmQiLCJzZXRJbnRlcnZhbCJdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7ZUFTcUJBOzs7dUVBUEE7K0RBQ2U7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFNckIsSUFBQSxBQUFNQSxpQkFBTixNQUFNQTtJQWlDbkI7O0dBRUMsR0FDREMsT0FBTztRQUNMQyxjQUFjLElBQUksQ0FBQ0MsNkJBQTZCO0lBQ2xEO0lBRUE7OztHQUdDLEdBQ0QsSUFBSUMseUJBQXlCO1FBQzNCLE1BQU1DLFVBQVVDLE9BQU9DLElBQUksQ0FBQyxJQUFJLENBQUNDLGFBQWE7UUFDOUNILFFBQVFJLElBQUksQ0FBQyxDQUFDQyxHQUFHQyxJQUFNLElBQUksQ0FBQ0gsYUFBYSxDQUFDRSxFQUFFLEdBQUcsSUFBSSxDQUFDRixhQUFhLENBQUNHLEVBQUU7UUFDcEUsT0FBT047SUFDVDtJQUVBOzs7R0FHQyxHQUNETyxlQUFlQyxVQUFVLEVBQUU7UUFDekIsSUFBSTtZQUNGLE1BQU1DLFlBQVksSUFBSSxDQUFDQyx5QkFBeUIsQ0FBQ0Y7WUFDakQsTUFBTUcscUJBQXFCLElBQUksQ0FBQ0Msc0JBQXNCLENBQUNKO1lBQ3ZELElBQUksQ0FBQ0ssbUJBQW1CLENBQUNMO1lBQ3pCLE1BQU1NLFlBQVksSUFBSSxDQUFDQyxvQkFBb0IsQ0FBQ047WUFDNUMsSUFBSSxDQUFDSyxVQUFVRSxHQUFHLENBQUNDLENBQUFBLFdBQVksSUFBSSxDQUFDQyx3QkFBd0IsQ0FBQ0QsU0FBUyxFQUFFRSxRQUFRLENBQUMsT0FBTztnQkFDdEYsTUFBTW5CLFVBQVUsSUFBSSxDQUFDb0Isa0JBQWtCLENBQUNYO2dCQUN4Q1QsUUFBUXFCLE1BQU0sQ0FBQ0MsQ0FBQUEsU0FBVUEsV0FBV1gsb0JBQ2pDWSxPQUFPLENBQUNELENBQUFBLFNBQVUsSUFBSSxDQUFDRSx3QkFBd0IsQ0FBQ2YsV0FBV2E7WUFDaEU7UUFDRixFQUFFLE9BQU9HLEtBQUs7WUFDWixJQUFJLENBQUNDLE9BQU8sQ0FBQ0MsS0FBSyxDQUFDLENBQUMsb0RBQW9ELEVBQUVuQixXQUFXLENBQUMsRUFBRWlCO1FBQzFGO0lBQ0Y7SUFFQTs7O0dBR0MsR0FDREcsY0FBY25CLFNBQVMsRUFBRTtRQUN2QixJQUFJO1lBQ0YsTUFBTWEsU0FBUyxJQUFJLENBQUNPLGdCQUFnQixDQUFDQyxnQkFBZ0IsQ0FBQ3JCO1lBQ3RELE1BQU1zQixtQkFBbUIsSUFBSSxDQUFDRixnQkFBZ0IsQ0FBQ0csbUJBQW1CLENBQUN2QixVQUFVO1lBQzdFLE1BQU1LLFlBQVksSUFBSSxDQUFDQyxvQkFBb0IsQ0FBQ2dCO1lBQzVDakIsVUFBVU8sTUFBTSxDQUFDYixDQUFBQSxhQUFjQSxXQUFXeUIsVUFBVSxDQUFDLENBQUMsRUFBRUYsaUJBQWlCLENBQUMsRUFBRVQsT0FBTyxDQUFDLENBQUMsR0FDbEZDLE9BQU8sQ0FBQ2YsQ0FBQUEsYUFBYyxJQUFJLENBQUNLLG1CQUFtQixDQUFDTDtRQUNwRCxFQUFFLE9BQU9pQixLQUFLO1lBQ1osSUFBSSxDQUFDQyxPQUFPLENBQUNDLEtBQUssQ0FBQyxDQUFDLGtEQUFrRCxFQUFFbEIsVUFBVSxDQUFDLEVBQUVnQjtRQUN2RjtJQUNGO0lBRUE7OztHQUdDLEdBQ0QsTUFBTVMsWUFBWTFCLFVBQVUsRUFBRTtRQUM1QixJQUFJO1lBQ0YsSUFBSSxDQUFDVSx3QkFBd0IsQ0FBQ1YsV0FBVyxHQUFHO1lBQzVDLE1BQU1DLFlBQVksSUFBSSxDQUFDQyx5QkFBeUIsQ0FBQ0Y7WUFDakQsTUFBTWMsU0FBUyxJQUFJLENBQUNWLHNCQUFzQixDQUFDSjtZQUMzQyxJQUFJLENBQUMsSUFBSSxDQUFDTCxhQUFhLENBQUNtQixPQUFPLEVBQUU7Z0JBQy9CLE1BQU0sSUFBSSxDQUFDYSxlQUFlLENBQUNiO1lBQzdCO1lBQ0EsTUFBTVIsWUFBWSxJQUFJLENBQUNzQix5QkFBeUIsQ0FBQzNCO1lBQ2pELE1BQU00Qix3QkFBd0IsSUFBSSxDQUFDQywrQkFBK0IsQ0FBQzdCO1lBQ25FLE1BQU1ULFVBQVVjLFVBQVVFLEdBQUcsQ0FBQ0MsQ0FBQUEsV0FBWSxJQUFJLENBQUNMLHNCQUFzQixDQUFDSztZQUN0RSxJQUFJSCxVQUFVeUIsTUFBTSxHQUFHLEtBQUssQ0FBQ0Ysc0JBQXNCRSxNQUFNLEVBQUU7Z0JBQ3pELE1BQU1DLHNCQUFzQixJQUFJLENBQUN6QyxzQkFBc0IsQ0FDcERzQixNQUFNLENBQUNvQixDQUFBQSxlQUFnQnpDLFFBQVFtQixRQUFRLENBQUNzQixlQUFlQyxLQUFLLENBQUM7Z0JBQ2hFRixvQkFBb0JqQixPQUFPLENBQUNvQixDQUFBQTtvQkFDMUIsSUFBSSxDQUFDZCxnQkFBZ0IsQ0FBQ2UsV0FBVyxDQUFDLElBQUksQ0FBQ2YsZ0JBQWdCLENBQUNnQixlQUFlLENBQUNwQyxVQUFVLENBQUNrQyxXQUFXO29CQUM5RixJQUFJLENBQUNkLGdCQUFnQixDQUFDaUIsd0JBQXdCLENBQUNyQyxXQUFXa0M7Z0JBQzVEO1lBQ0Y7WUFDQSxJQUFJLElBQUksQ0FBQ0ksb0JBQW9CLENBQUN0QyxVQUFVLEVBQUU7Z0JBQ3hDLElBQUksQ0FBQ3NDLG9CQUFvQixDQUFDdEMsVUFBVSxDQUFDdUMsT0FBTztnQkFDNUMsT0FBTyxJQUFJLENBQUNELG9CQUFvQixDQUFDdEMsVUFBVTtZQUM3QztRQUNGLEVBQUUsT0FBT2dCLEtBQUs7WUFDWixJQUFJLENBQUNDLE9BQU8sQ0FBQ0MsS0FBSyxDQUFDLENBQUMsaURBQWlELEVBQUVuQixXQUFXLENBQUMsRUFBRWlCO1FBQ3ZGO0lBQ0Y7SUFFQTs7O0dBR0MsR0FDRCxNQUFNd0Isb0JBQW9CekMsVUFBVSxFQUFFO1FBQ3BDLElBQUk7WUFDRixJQUFJLENBQUMwQywyQkFBMkIsQ0FBQzFDLFdBQVcsR0FBRztZQUMvQyxNQUFNQyxZQUFZLElBQUksQ0FBQ0MseUJBQXlCLENBQUNGO1lBQ2pELE1BQU1jLFNBQVMsSUFBSSxDQUFDVixzQkFBc0IsQ0FBQ0o7WUFDM0MsSUFBSSxDQUFDLElBQUksQ0FBQ0wsYUFBYSxDQUFDbUIsT0FBTyxFQUFFO2dCQUMvQixNQUFNLElBQUksQ0FBQ2EsZUFBZSxDQUFDYjtZQUM3QjtZQUNBLE1BQU1SLFlBQVksSUFBSSxDQUFDd0IsK0JBQStCLENBQUM3QjtZQUN2RCxNQUFNVCxVQUFVO21CQUFJLElBQUltRCxJQUFJckMsVUFBVUUsR0FBRyxDQUFDQyxDQUFBQSxXQUFZLElBQUksQ0FBQ0wsc0JBQXNCLENBQUNLO2FBQVk7WUFDOUYsSUFBSUgsVUFBVXlCLE1BQU0sR0FBRyxHQUFHO2dCQUN4QixNQUFNQyxzQkFBc0IsSUFBSSxDQUFDekMsc0JBQXNCLENBQ3BEc0IsTUFBTSxDQUFDb0IsQ0FBQUEsZUFBZ0J6QyxRQUFRbUIsUUFBUSxDQUFDc0IsZUFBZUMsS0FBSyxDQUFDO2dCQUNoRUYsb0JBQW9CakIsT0FBTyxDQUFDb0IsQ0FBQUE7b0JBQzFCLElBQUksQ0FBQ2QsZ0JBQWdCLENBQUNlLFdBQVcsQ0FBQyxJQUFJLENBQUNmLGdCQUFnQixDQUFDZ0IsZUFBZSxDQUFDcEMsVUFBVSxDQUFDa0MsV0FBVztvQkFDOUYsSUFBSSxDQUFDZCxnQkFBZ0IsQ0FBQ2lCLHdCQUF3QixDQUFDckMsV0FBV2tDO2dCQUM1RDtZQUNGO1FBQ0YsRUFBRSxPQUFPbEIsS0FBSztZQUNaLElBQUksQ0FBQ0MsT0FBTyxDQUFDQyxLQUFLLENBQUMsQ0FBQyx5REFBeUQsRUFBRW5CLFdBQVcsQ0FBQyxFQUFFaUI7UUFDL0Y7SUFDRjtJQUVBOzs7O0dBSUMsR0FDRFcsMEJBQTBCM0IsU0FBUyxFQUFFO1FBQ25DLE9BQU8sSUFBSSxDQUFDTSxvQkFBb0IsQ0FBQ04sV0FBV1ksTUFBTSxDQUFDSixDQUFBQSxXQUFZLElBQUksQ0FBQ0Msd0JBQXdCLENBQUNELFNBQVM7SUFDeEc7SUFFQTs7OztHQUlDLEdBQ0RxQixnQ0FBZ0M3QixTQUFTLEVBQUU7UUFDekMsT0FBTyxJQUFJLENBQUNNLG9CQUFvQixDQUFDTixXQUFXWSxNQUFNLENBQUNKLENBQUFBLFdBQVksSUFBSSxDQUFDaUMsMkJBQTJCLENBQUNqQyxTQUFTO0lBQzNHO0lBRUE7Ozs7R0FJQyxHQUNELE1BQU1tQyxzQkFBc0IzQyxTQUFTLEVBQUU7UUFDckMsSUFBSUssWUFBWSxJQUFJLENBQUNzQix5QkFBeUIsQ0FBQzNCO1FBQy9DLElBQUksQ0FBQ0ssVUFBVXlCLE1BQU0sRUFBRTtZQUNyQixJQUFJLENBQUMsSUFBSSxDQUFDUSxvQkFBb0IsQ0FBQ3RDLFVBQVUsRUFBRTtnQkFDekMsSUFBSXVDO2dCQUNKLElBQUlLLFVBQVUsSUFBSUMsUUFBUSxDQUFDQyxLQUFLQztvQkFDOUJSLFVBQVVPO2dCQUNaO2dCQUNBLElBQUksQ0FBQ1Isb0JBQW9CLENBQUN0QyxVQUFVLEdBQUc7b0JBQUM0QztvQkFBU0w7Z0JBQU87WUFDMUQ7WUFDQSxNQUFNLElBQUksQ0FBQ0Qsb0JBQW9CLENBQUN0QyxVQUFVLENBQUM0QyxPQUFPO1lBQ2xEdkMsWUFBWSxJQUFJLENBQUNzQix5QkFBeUIsQ0FBQzNCO1FBQzdDO1FBQ0EsT0FBT0ssU0FBUyxDQUFDLEVBQUU7SUFDckI7SUFFQUMscUJBQXFCTixTQUFTLEVBQUU7UUFDOUIsT0FBT1IsT0FBT0MsSUFBSSxDQUFDLElBQUksQ0FBQ2dCLHdCQUF3QixFQUFFRyxNQUFNLENBQUNiLENBQUFBLGFBQWNBLFdBQVd5QixVQUFVLENBQUMsQ0FBQyxFQUFFeEIsVUFBVSxDQUFDLENBQUM7SUFDOUc7SUFFQVcsbUJBQW1CWCxTQUFTLEVBQUU7UUFDNUIsTUFBTVQsVUFBVSxFQUFFO1FBQ2xCLE1BQU1jLFlBQVksSUFBSSxDQUFDQyxvQkFBb0IsQ0FBQ047UUFDNUNLLFVBQVVTLE9BQU8sQ0FBQ04sQ0FBQUE7WUFDaEIsTUFBTUssU0FBUyxJQUFJLENBQUNWLHNCQUFzQixDQUFDSztZQUMzQyxJQUFJLENBQUNqQixRQUFRbUIsUUFBUSxDQUFDRyxTQUFTO2dCQUM3QnRCLFFBQVF5RCxJQUFJLENBQUNuQztZQUNmO1FBQ0Y7UUFDQSxPQUFPdEI7SUFDVDtJQUVBVSwwQkFBMEJGLFVBQVUsRUFBRTtRQUNwQyxPQUFPQSxXQUFXa0QsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFO0lBQ2pDO0lBRUE5Qyx1QkFBdUJKLFVBQVUsRUFBRTtRQUNqQyxPQUFPQSxXQUFXa0QsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFO0lBQ2pDO0lBRUE3QyxvQkFBb0JMLFVBQVUsRUFBRTtRQUM5QixJQUFJLENBQUNVLHdCQUF3QixDQUFDVixXQUFXLEdBQUc7UUFDNUMsSUFBSSxJQUFJLENBQUMwQywyQkFBMkIsQ0FBQzFDLFdBQVcsRUFBRTtZQUNoRCxJQUFJLENBQUMwQywyQkFBMkIsQ0FBQzFDLFdBQVcsR0FBRztRQUNqRDtJQUNGO0lBRUFnQix5QkFBeUJmLFNBQVMsRUFBRWEsTUFBTSxFQUFFO1FBQzFDLE1BQU1kLGFBQWEsSUFBSSxDQUFDcUIsZ0JBQWdCLENBQUNnQixlQUFlLENBQUNwQyxVQUFVLENBQUNhLE9BQU87UUFDM0UsSUFBSWQsWUFBWTtZQUNkLElBQUksQ0FBQ3FCLGdCQUFnQixDQUFDOEIsZUFBZSxDQUFDbkQsWUFBWTtZQUNsRCxJQUFJLENBQUNxQixnQkFBZ0IsQ0FBQzhCLGVBQWUsQ0FBQ25ELFlBQVk7UUFDcEQ7SUFDRjtJQUVBLE1BQU1vRCwyQkFBMkI7UUFDL0IsS0FBSSxJQUFJdEMsVUFBVXJCLE9BQU9DLElBQUksQ0FBQyxJQUFJLENBQUNDLGFBQWEsRUFBRztZQUNqRCxNQUFNLElBQUksQ0FBQ2dDLGVBQWUsQ0FBQ2I7UUFDN0I7UUFFQSw4REFBOEQ7UUFDOUQsTUFBTXVDLGFBQWEsRUFBRTtRQUNyQjVELE9BQU9DLElBQUksQ0FBQyxJQUFJLENBQUNnQix3QkFBd0IsRUFDdENHLE1BQU0sQ0FBQ2IsQ0FBQUEsYUFBYyxJQUFJLENBQUNVLHdCQUF3QixDQUFDVixXQUFXLEVBQzlEZSxPQUFPLENBQUNmLENBQUFBO1lBQ1AsTUFBTUMsWUFBWSxJQUFJLENBQUNDLHlCQUF5QixDQUFDRjtZQUNqRCxJQUFJLENBQUNxRCxXQUFXMUMsUUFBUSxDQUFDVixZQUFZO2dCQUNuQ29ELFdBQVdKLElBQUksQ0FBQ2hEO1lBQ2xCO1FBQ0Y7UUFFRixNQUFNcUQsZ0JBQWdCLElBQUksQ0FBQy9ELHNCQUFzQjtRQUVqRDhELFdBQVd0QyxPQUFPLENBQUNkLENBQUFBO1lBQ2pCLE1BQU1zRCxpQkFBaUIsSUFBSSxDQUFDM0Msa0JBQWtCLENBQUNYO1lBQy9DLE1BQU11RCxrQkFBa0IsSUFBSSxDQUFDNUIseUJBQXlCLENBQUMzQjtZQUN2RCxJQUFJdUQsZ0JBQWdCekIsTUFBTSxLQUFLLEdBQUc7Z0JBQ2hDLE1BQU0wQixpQkFBaUJELGVBQWUsQ0FBQyxFQUFFO2dCQUN6QyxNQUFNRSxlQUFlLElBQUksQ0FBQ3RELHNCQUFzQixDQUFDcUQ7Z0JBQ2pELE1BQU1FLHFCQUFxQkwsY0FBY3pDLE1BQU0sQ0FBQ0MsQ0FBQUEsU0FBVXlDLGVBQWU1QyxRQUFRLENBQUNHO2dCQUNsRixJQUFJNkMsa0JBQWtCLENBQUMsRUFBRSxLQUFLRCxjQUFjO29CQUMxQyxJQUFJLENBQUMxQyx3QkFBd0IsQ0FBQ2YsV0FBVzBELGtCQUFrQixDQUFDLEVBQUU7Z0JBQ2hFO1lBQ0Y7UUFDRjtJQUNGO0lBRUEsTUFBTWhDLGdCQUFnQmIsTUFBTSxFQUFFO1FBQzVCLElBQUksSUFBSSxDQUFDOEMsd0JBQXdCLENBQUM5QyxPQUFPLEVBQUU7WUFDekMsT0FBTyxNQUFNLElBQUksQ0FBQzhDLHdCQUF3QixDQUFDOUMsT0FBTztRQUNwRDtRQUNBLElBQUkwQjtRQUNKLElBQUksQ0FBQ29CLHdCQUF3QixDQUFDOUMsT0FBTyxHQUFHLElBQUlnQyxRQUFRLENBQUNDLEtBQUtDO1lBQ3hEUixVQUFVTztRQUNaO1FBQ0EsTUFBTWMsWUFBWSxNQUFNLElBQUksQ0FBQ3hDLGdCQUFnQixDQUFDeUMsY0FBYyxDQUFDLEdBQUdoRDtRQUNoRSxNQUFNaUQsWUFBWUMsS0FBS0MsR0FBRztRQUUxQixNQUFNQyxpQkFBaUJDLElBQUFBLHVCQUFRLEVBQUNOLFVBQVVPLEdBQUcsRUFBRTtZQUM3Q0MsTUFBTTtZQUNOQyxjQUFjO1lBQ2RDLG1CQUFtQjtZQUNuQkMsc0JBQXNCO1lBQ3RCQyxzQkFBc0JDO1lBQ3RCQyxTQUFTLElBQUksQ0FBQ0MsZUFBZTtZQUM3QkMsT0FBTztnQkFDTCxjQUFjLElBQUksQ0FBQ0MsTUFBTTtnQkFDekJDLFVBQVU7WUFDWjtRQUNGO1FBQ0FiLGVBQWVjLEVBQUUsQ0FBQyxXQUFXO1lBQzNCeEM7WUFDQSxNQUFNeUMsVUFBVWpCLEtBQUtDLEdBQUcsS0FBS0Y7WUFDN0IsSUFBSSxDQUFDcEUsYUFBYSxDQUFDbUIsT0FBTyxHQUFHbUU7WUFDN0JmLGVBQWVnQixLQUFLO1FBQ3RCO1FBQ0EsTUFBTSxJQUFJLENBQUN0Qix3QkFBd0IsQ0FBQzlDLE9BQU87UUFDM0MsT0FBTyxJQUFJLENBQUM4Qyx3QkFBd0IsQ0FBQzlDLE9BQU87SUFDOUM7SUFqUkE7Ozs7O0dBS0MsR0FDRHFFLFlBQVlDLGVBQWUsRUFBRUMsS0FBSyxFQUFFQyxjQUFjLENBQUU7UUFqQnBELHVCQUFRakUsb0JBQVIsS0FBQTtRQUNBLHVCQUFReUQsVUFBUixLQUFBO1FBQ0EsdUJBQVFGLG1CQUFSLEtBQUE7UUFDQSx1QkFBUWpGLGlCQUFSLEtBQUE7UUFDQSx1QkFBUWUsNEJBQVIsS0FBQTtRQUNBLHVCQUFRZ0MsK0JBQVIsS0FBQTtRQUNBLHVCQUFRa0IsNEJBQVIsS0FBQTtRQUNBLHVCQUFRckIsd0JBQVIsS0FBQTtRQUNBLHVCQUFRckIsV0FBUixLQUFBO1FBQ0EsdUJBQVE1QixpQ0FBUixLQUFBO1FBU0UsSUFBSSxDQUFDK0IsZ0JBQWdCLEdBQUcrRDtRQUN4QixJQUFJLENBQUNOLE1BQU0sR0FBR087UUFDZCxJQUFJLENBQUNULGVBQWUsR0FBR1U7UUFDdkIsSUFBSSxDQUFDM0YsYUFBYSxHQUFHLENBQUM7UUFDdEIsSUFBSSxDQUFDZSx3QkFBd0IsR0FBRyxDQUFDO1FBQ2pDLElBQUksQ0FBQ2dDLDJCQUEyQixHQUFHLENBQUM7UUFDcEMsSUFBSSxDQUFDa0Isd0JBQXdCLEdBQUcsQ0FBQztRQUNqQyxJQUFJLENBQUNyQixvQkFBb0IsR0FBRyxDQUFDO1FBQzdCLElBQUksQ0FBQ3JCLE9BQU8sR0FBR3FFLGVBQWEsQ0FBQ0MsU0FBUyxDQUFDO1FBQ3ZDLElBQUksQ0FBQ3BDLHdCQUF3QixHQUFHLElBQUksQ0FBQ0Esd0JBQXdCLENBQUNxQyxJQUFJLENBQUMsSUFBSTtRQUN2RSxJQUFJLENBQUNuRyw2QkFBNkIsR0FBR29HLFlBQVksSUFBSSxDQUFDdEMsd0JBQXdCLEVBQUUsS0FBSyxLQUFLO0lBQzVGO0FBaVFGIn0=