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)

207 lines (206 loc) 28.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "default", { enumerable: true, get: function() { return ConnectionHealthMonitor; } }); const _synchronizationListener = /*#__PURE__*/ _interop_require_default(require("../clients/metaApi/synchronizationListener")); const _moment = /*#__PURE__*/ _interop_require_default(require("moment")); const _reservoir = /*#__PURE__*/ _interop_require_default(require("./reservoir/reservoir")); const _logger = /*#__PURE__*/ _interop_require_default(require("../logger")); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } let ConnectionHealthMonitor = class ConnectionHealthMonitor extends _synchronizationListener.default { /** * Starts health monitor */ start() { const updateQuoteHealthStatusInterval = ()=>{ this._updateQuoteHealthStatus(); this._updateQuoteHealthStatusInterval = setTimeout(updateQuoteHealthStatusInterval.bind(this), this._getRandomTimeout()); }; this._updateQuoteHealthStatusInterval = setTimeout(updateQuoteHealthStatusInterval.bind(this), this._getRandomTimeout()); const measureUptimeInterval = ()=>{ this._measureUptime(); this._measureUptimeInterval = setTimeout(measureUptimeInterval.bind(this), this._getRandomTimeout()); }; this._measureUptimeInterval = setTimeout(measureUptimeInterval.bind(this), this._getRandomTimeout()); } /** * Stops health monitor */ stop() { this._logger.debug(`${this._connection.account.id}: Stopping the monitor`); clearTimeout(this._updateQuoteHealthStatusInterval); clearTimeout(this._measureUptimeInterval); } /** * Invoked when a symbol price was updated * @param {String} instanceIndex index of an account instance connected * @param {MetatraderSymbolPrice} price updated MetaTrader symbol price */ onSymbolPriceUpdated(instanceIndex, price) { try { let brokerTimestamp = (0, _moment.default)(price.brokerTime).toDate().getTime(); this._priceUpdatedAt = new Date(); this._offset = this._priceUpdatedAt.getTime() - brokerTimestamp; } catch (err) { // eslint-disable-next-line no-console this._logger.error(`${this._connection.account.id}: Failed to update quote ` + "streaming health status on price update", err); } } /** * Invoked when a server-side application health status is received from MetaApi * @param {String} instanceIndex index of an account instance connected * @param {HealthStatus} status server-side application health status * @return {Promise} promise which resolves when the asynchronous event is processed */ onHealthStatus(instanceIndex, status) { this._serverHealthStatus["" + instanceIndex] = status; } /** * Invoked when connection to MetaTrader terminal terminated * @param {String} instanceIndex index of an account instance connected * @return {Promise} promise which resolves when the asynchronous event is processed */ onDisconnected(instanceIndex) { delete this._serverHealthStatus["" + instanceIndex]; } /** * Returns server-side application health status * @return {HealthStatus} server-side application health status */ get serverHealthStatus() { let result; for (let s of Object.values(this._serverHealthStatus)){ if (!result) { result = s; } else { for (let field of Object.keys(s)){ result[field] = result[field] || s[field]; } } } return result || {}; } /** * Connection health status * @typedef {Object} ConnectionHealthStatus * @property {Boolean} connected flag indicating successful connection to API server * @property {Boolean} connectedToBroker flag indicating successful connection to broker * @property {Boolean} quoteStreamingHealthy flag indicating that quotes are being streamed successfully from the * broker * @property {Boolean} synchronized flag indicating a successful synchronization * @property {Boolean} healthy flag indicating overall connection health status * @property {String} message health status message */ /** * Returns health status * @returns {ConnectionHealthStatus} connection health status */ // eslint-disable-next-line complexity get healthStatus() { let status = { connected: this._connection.terminalState.connected, connectedToBroker: this._connection.terminalState.connectedToBroker, quoteStreamingHealthy: this._quotesHealthy, synchronized: this._connection.synchronized }; status.healthy = status.connected && status.connectedToBroker && status.quoteStreamingHealthy && status.synchronized; let message; if (status.healthy) { message = "Connection to broker is stable. No health issues detected."; } else { message = "Connection is not healthy because "; let reasons = []; if (!status.connected) { reasons.push("connection to API server is not established or lost"); } if (!status.connectedToBroker) { reasons.push("connection to broker is not established or lost"); } if (!status.synchronized) { reasons.push("local terminal state is not synchronized to broker"); } if (!status.quoteStreamingHealthy) { reasons.push("quotes are not streamed from the broker properly"); } message = message + reasons.join(" and ") + "."; } status.message = message; return status; } /** * Returns uptime in percents measured over specific periods of time * @returns {Object} uptime in percents measured over specific periods of time */ get uptime() { let uptime = {}; for (let e of Object.entries(this._uptimeReservoirs)){ uptime[e[0]] = e[1].getStatistics().average; } return uptime; } _measureUptime() { try { Object.values(this._uptimeReservoirs).forEach((r)=>r.pushMeasurement(this._connection.terminalState.connected && this._connection.terminalState.connectedToBroker && this._connection.synchronized && this._quotesHealthy ? 100 : 0)); } catch (err) { // eslint-disable-next-line no-console this._logger.error("failed to measure uptime for account " + this._connection.account.id, err); } } // eslint-disable-next-line complexity _updateQuoteHealthStatus() { try { let serverDateTime = (0, _moment.default)(new Date(Date.now() - this._offset)); let serverTime = serverDateTime.format("HH:mm:ss.SSS"); let dayOfWeek = serverDateTime.day(); let daysOfWeek = { 0: "SUNDAY", 1: "MONDAY", 2: "TUESDAY", 3: "WEDNESDAY", 4: "THURSDAY", 5: "FRIDAY", 6: "SATURDAY" }; let inQuoteSession = false; if (!this._priceUpdatedAt) { this._priceUpdatedAt = new Date(); } if (!(this._connection.subscribedSymbols || []).length) { this._priceUpdatedAt = new Date(); } for (let symbol of this._connection.subscribedSymbols || []){ let specification = this._connection.terminalState.specification(symbol) || {}; let quoteSessions = (specification.quoteSessions || [])[daysOfWeek[dayOfWeek]] || []; for (let session of quoteSessions){ if (session.from <= serverTime && session.to >= serverTime) { inQuoteSession = true; } } } this._quotesHealthy = !this._connection.subscribedSymbols.length || !inQuoteSession || Date.now() - this._priceUpdatedAt.getTime() < this._minQuoteInterval; } catch (err) { this._logger.error("failed to update quote streaming health status for account " + this._connection.account.id, err); } } _getRandomTimeout() { return (Math.random() * 59 + 1) * 1000; } /** * Constructs the listener * @param {StreamingMetaApiConnection} connection MetaApi connection instance */ constructor(connection){ super(); this._connection = connection; this._minQuoteInterval = 60000; this._serverHealthStatus = {}; this._uptimeReservoirs = { "5m": new _reservoir.default(300, 5 * 60 * 1000), "1h": new _reservoir.default(600, 60 * 60 * 1000), "1d": new _reservoir.default(24 * 60, 24 * 60 * 60 * 1000), "1w": new _reservoir.default(24 * 7, 7 * 24 * 60 * 60 * 1000) }; this._logger = _logger.default.getLogger("ConnectionHealthMonitor"); } }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBTeW5jaHJvbml6YXRpb25MaXN0ZW5lciBmcm9tICcuLi9jbGllbnRzL21ldGFBcGkvc3luY2hyb25pemF0aW9uTGlzdGVuZXInO1xuaW1wb3J0IG1vbWVudCBmcm9tICdtb21lbnQnO1xuaW1wb3J0IFJlc2Vydm9pciBmcm9tICcuL3Jlc2Vydm9pci9yZXNlcnZvaXInO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIgZnJvbSAnLi4vbG9nZ2VyJztcblxuLyoqXG4gKiBUcmFja3MgY29ubmVjdGlvbiBoZWFsdGggc3RhdHVzXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIENvbm5lY3Rpb25IZWFsdGhNb25pdG9yIGV4dGVuZHMgU3luY2hyb25pemF0aW9uTGlzdGVuZXIge1xuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3RzIHRoZSBsaXN0ZW5lclxuICAgKiBAcGFyYW0ge1N0cmVhbWluZ01ldGFBcGlDb25uZWN0aW9ufSBjb25uZWN0aW9uIE1ldGFBcGkgY29ubmVjdGlvbiBpbnN0YW5jZVxuICAgKi9cbiAgY29uc3RydWN0b3IoY29ubmVjdGlvbikge1xuICAgIHN1cGVyKCk7XG4gICAgdGhpcy5fY29ubmVjdGlvbiA9IGNvbm5lY3Rpb247XG4gICAgdGhpcy5fbWluUXVvdGVJbnRlcnZhbCA9IDYwMDAwO1xuICAgIHRoaXMuX3NlcnZlckhlYWx0aFN0YXR1cyA9IHt9O1xuICAgIHRoaXMuX3VwdGltZVJlc2Vydm9pcnMgPSB7XG4gICAgICAnNW0nOiBuZXcgUmVzZXJ2b2lyKDMwMCwgNSAqIDYwICogMTAwMCksXG4gICAgICAnMWgnOiBuZXcgUmVzZXJ2b2lyKDYwMCwgNjAgKiA2MCAqIDEwMDApLFxuICAgICAgJzFkJzogbmV3IFJlc2Vydm9pcigyNCAqIDYwLCAyNCAqIDYwICogNjAgKiAxMDAwKSxcbiAgICAgICcxdyc6IG5ldyBSZXNlcnZvaXIoMjQgKiA3LCA3ICogMjQgKiA2MCAqIDYwICogMTAwMCksXG4gICAgfTtcbiAgICB0aGlzLl9sb2dnZXIgPSBMb2dnZXJNYW5hZ2VyLmdldExvZ2dlcignQ29ubmVjdGlvbkhlYWx0aE1vbml0b3InKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdGFydHMgaGVhbHRoIG1vbml0b3JcbiAgICovXG4gIHN0YXJ0KCkge1xuICAgIGNvbnN0IHVwZGF0ZVF1b3RlSGVhbHRoU3RhdHVzSW50ZXJ2YWwgPSAoKSA9PiB7XG4gICAgICB0aGlzLl91cGRhdGVRdW90ZUhlYWx0aFN0YXR1cygpO1xuICAgICAgdGhpcy5fdXBkYXRlUXVvdGVIZWFsdGhTdGF0dXNJbnRlcnZhbCA9IHNldFRpbWVvdXQoXG4gICAgICAgIHVwZGF0ZVF1b3RlSGVhbHRoU3RhdHVzSW50ZXJ2YWwuYmluZCh0aGlzKSxcbiAgICAgICAgdGhpcy5fZ2V0UmFuZG9tVGltZW91dCgpXG4gICAgICApO1xuICAgIH07XG4gICAgdGhpcy5fdXBkYXRlUXVvdGVIZWFsdGhTdGF0dXNJbnRlcnZhbCA9IHNldFRpbWVvdXQoXG4gICAgICB1cGRhdGVRdW90ZUhlYWx0aFN0YXR1c0ludGVydmFsLmJpbmQodGhpcyksXG4gICAgICB0aGlzLl9nZXRSYW5kb21UaW1lb3V0KClcbiAgICApO1xuICAgIGNvbnN0IG1lYXN1cmVVcHRpbWVJbnRlcnZhbCA9ICgpID0+IHtcbiAgICAgIHRoaXMuX21lYXN1cmVVcHRpbWUoKTtcbiAgICAgIHRoaXMuX21lYXN1cmVVcHRpbWVJbnRlcnZhbCA9IHNldFRpbWVvdXQobWVhc3VyZVVwdGltZUludGVydmFsLmJpbmQodGhpcyksIHRoaXMuX2dldFJhbmRvbVRpbWVvdXQoKSk7XG4gICAgfTtcbiAgICB0aGlzLl9tZWFzdXJlVXB0aW1lSW50ZXJ2YWwgPSBzZXRUaW1lb3V0KG1lYXN1cmVVcHRpbWVJbnRlcnZhbC5iaW5kKHRoaXMpLCB0aGlzLl9nZXRSYW5kb21UaW1lb3V0KCkpO1xuICB9XG5cbiAgLyoqXG4gICAqIFN0b3BzIGhlYWx0aCBtb25pdG9yXG4gICAqL1xuICBzdG9wKCkge1xuICAgIHRoaXMuX2xvZ2dlci5kZWJ1ZyhgJHt0aGlzLl9jb25uZWN0aW9uLmFjY291bnQuaWR9OiBTdG9wcGluZyB0aGUgbW9uaXRvcmApO1xuICAgIGNsZWFyVGltZW91dCh0aGlzLl91cGRhdGVRdW90ZUhlYWx0aFN0YXR1c0ludGVydmFsKTtcbiAgICBjbGVhclRpbWVvdXQodGhpcy5fbWVhc3VyZVVwdGltZUludGVydmFsKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbnZva2VkIHdoZW4gYSBzeW1ib2wgcHJpY2Ugd2FzIHVwZGF0ZWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGluc3RhbmNlSW5kZXggaW5kZXggb2YgYW4gYWNjb3VudCBpbnN0YW5jZSBjb25uZWN0ZWRcbiAgICogQHBhcmFtIHtNZXRhdHJhZGVyU3ltYm9sUHJpY2V9IHByaWNlIHVwZGF0ZWQgTWV0YVRyYWRlciBzeW1ib2wgcHJpY2VcbiAgICovXG4gIG9uU3ltYm9sUHJpY2VVcGRhdGVkKGluc3RhbmNlSW5kZXgsIHByaWNlKSB7XG4gICAgdHJ5IHtcbiAgICAgIGxldCBicm9rZXJUaW1lc3RhbXAgPSBtb21lbnQocHJpY2UuYnJva2VyVGltZSkudG9EYXRlKCkuZ2V0VGltZSgpO1xuICAgICAgdGhpcy5fcHJpY2VVcGRhdGVkQXQgPSBuZXcgRGF0ZSgpO1xuICAgICAgdGhpcy5fb2Zmc2V0ID0gdGhpcy5fcHJpY2VVcGRhdGVkQXQuZ2V0VGltZSgpIC0gYnJva2VyVGltZXN0YW1wO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGVcbiAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcihgJHt0aGlzLl9jb25uZWN0aW9uLmFjY291bnQuaWR9OiBGYWlsZWQgdG8gdXBkYXRlIHF1b3RlIGAgKyBcbiAgICAgICAgJ3N0cmVhbWluZyBoZWFsdGggc3RhdHVzIG9uIHByaWNlIHVwZGF0ZScsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBhIHNlcnZlci1zaWRlIGFwcGxpY2F0aW9uIGhlYWx0aCBzdGF0dXMgaXMgcmVjZWl2ZWQgZnJvbSBNZXRhQXBpXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBpbnN0YW5jZUluZGV4IGluZGV4IG9mIGFuIGFjY291bnQgaW5zdGFuY2UgY29ubmVjdGVkXG4gICAqIEBwYXJhbSB7SGVhbHRoU3RhdHVzfSBzdGF0dXMgc2VydmVyLXNpZGUgYXBwbGljYXRpb24gaGVhbHRoIHN0YXR1c1xuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBwcm9taXNlIHdoaWNoIHJlc29sdmVzIHdoZW4gdGhlIGFzeW5jaHJvbm91cyBldmVudCBpcyBwcm9jZXNzZWRcbiAgICovXG4gIG9uSGVhbHRoU3RhdHVzKGluc3RhbmNlSW5kZXgsIHN0YXR1cykge1xuICAgIHRoaXMuX3NlcnZlckhlYWx0aFN0YXR1c1snJyArIGluc3RhbmNlSW5kZXhdID0gc3RhdHVzO1xuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBjb25uZWN0aW9uIHRvIE1ldGFUcmFkZXIgdGVybWluYWwgdGVybWluYXRlZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gaW5zdGFuY2VJbmRleCBpbmRleCBvZiBhbiBhY2NvdW50IGluc3RhbmNlIGNvbm5lY3RlZFxuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBwcm9taXNlIHdoaWNoIHJlc29sdmVzIHdoZW4gdGhlIGFzeW5jaHJvbm91cyBldmVudCBpcyBwcm9jZXNzZWRcbiAgICovXG4gIG9uRGlzY29ubmVjdGVkKGluc3RhbmNlSW5kZXgpIHtcbiAgICBkZWxldGUgdGhpcy5fc2VydmVySGVhbHRoU3RhdHVzWycnICsgaW5zdGFuY2VJbmRleF07XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBzZXJ2ZXItc2lkZSBhcHBsaWNhdGlvbiBoZWFsdGggc3RhdHVzXG4gICAqIEByZXR1cm4ge0hlYWx0aFN0YXR1c30gc2VydmVyLXNpZGUgYXBwbGljYXRpb24gaGVhbHRoIHN0YXR1c1xuICAgKi9cbiAgZ2V0IHNlcnZlckhlYWx0aFN0YXR1cygpIHtcbiAgICBsZXQgcmVzdWx0O1xuICAgIGZvciAobGV0IHMgb2YgT2JqZWN0LnZhbHVlcyh0aGlzLl9zZXJ2ZXJIZWFsdGhTdGF0dXMpKSB7XG4gICAgICBpZiAoIXJlc3VsdCkge1xuICAgICAgICByZXN1bHQgPSBzO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZm9yIChsZXQgZmllbGQgb2YgT2JqZWN0LmtleXMocykpIHtcbiAgICAgICAgICByZXN1bHRbZmllbGRdID0gcmVzdWx0W2ZpZWxkXSB8fCBzW2ZpZWxkXTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0IHx8IHt9O1xuICB9XG5cbiAgLyoqXG4gICAqIENvbm5lY3Rpb24gaGVhbHRoIHN0YXR1c1xuICAgKiBAdHlwZWRlZiB7T2JqZWN0fSBDb25uZWN0aW9uSGVhbHRoU3RhdHVzXG4gICAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gY29ubmVjdGVkIGZsYWcgaW5kaWNhdGluZyBzdWNjZXNzZnVsIGNvbm5lY3Rpb24gdG8gQVBJIHNlcnZlclxuICAgKiBAcHJvcGVydHkge0Jvb2xlYW59IGNvbm5lY3RlZFRvQnJva2VyIGZsYWcgaW5kaWNhdGluZyBzdWNjZXNzZnVsIGNvbm5lY3Rpb24gdG8gYnJva2VyXG4gICAqIEBwcm9wZXJ0eSB7Qm9vbGVhbn0gcXVvdGVTdHJlYW1pbmdIZWFsdGh5IGZsYWcgaW5kaWNhdGluZyB0aGF0IHF1b3RlcyBhcmUgYmVpbmcgc3RyZWFtZWQgc3VjY2Vzc2Z1bGx5IGZyb20gdGhlXG4gICAqIGJyb2tlclxuICAgKiBAcHJvcGVydHkge0Jvb2xlYW59IHN5bmNocm9uaXplZCBmbGFnIGluZGljYXRpbmcgYSBzdWNjZXNzZnVsIHN5bmNocm9uaXphdGlvblxuICAgKiBAcHJvcGVydHkge0Jvb2xlYW59IGhlYWx0aHkgZmxhZyBpbmRpY2F0aW5nIG92ZXJhbGwgY29ubmVjdGlvbiBoZWFsdGggc3RhdHVzXG4gICAqIEBwcm9wZXJ0eSB7U3RyaW5nfSBtZXNzYWdlIGhlYWx0aCBzdGF0dXMgbWVzc2FnZVxuICAgKi9cblxuICAvKipcbiAgICogUmV0dXJucyBoZWFsdGggc3RhdHVzXG4gICAqIEByZXR1cm5zIHtDb25uZWN0aW9uSGVhbHRoU3RhdHVzfSBjb25uZWN0aW9uIGhlYWx0aCBzdGF0dXNcbiAgICovXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBjb21wbGV4aXR5XG4gIGdldCBoZWFsdGhTdGF0dXMoKSB7XG4gICAgbGV0IHN0YXR1cyA9IHtcbiAgICAgIGNvbm5lY3RlZDogdGhpcy5fY29ubmVjdGlvbi50ZXJtaW5hbFN0YXRlLmNvbm5lY3RlZCxcbiAgICAgIGNvbm5lY3RlZFRvQnJva2VyOiB0aGlzLl9jb25uZWN0aW9uLnRlcm1pbmFsU3RhdGUuY29ubmVjdGVkVG9Ccm9rZXIsXG4gICAgICBxdW90ZVN0cmVhbWluZ0hlYWx0aHk6IHRoaXMuX3F1b3Rlc0hlYWx0aHksXG4gICAgICBzeW5jaHJvbml6ZWQ6IHRoaXMuX2Nvbm5lY3Rpb24uc3luY2hyb25pemVkXG4gICAgfTtcbiAgICBzdGF0dXMuaGVhbHRoeSA9IHN0YXR1cy5jb25uZWN0ZWQgJiYgc3RhdHVzLmNvbm5lY3RlZFRvQnJva2VyICYmIHN0YXR1cy5xdW90ZVN0cmVhbWluZ0hlYWx0aHkgJiZcbiAgICAgIHN0YXR1cy5zeW5jaHJvbml6ZWQ7XG4gICAgbGV0IG1lc3NhZ2U7XG4gICAgaWYgKHN0YXR1cy5oZWFsdGh5KSB7XG4gICAgICBtZXNzYWdlID0gJ0Nvbm5lY3Rpb24gdG8gYnJva2VyIGlzIHN0YWJsZS4gTm8gaGVhbHRoIGlzc3VlcyBkZXRlY3RlZC4nO1xuICAgIH0gZWxzZSB7XG4gICAgICBtZXNzYWdlID0gJ0Nvbm5lY3Rpb24gaXMgbm90IGhlYWx0aHkgYmVjYXVzZSAnO1xuICAgICAgbGV0IHJlYXNvbnMgPSBbXTtcbiAgICAgIGlmICghc3RhdHVzLmNvbm5lY3RlZCkge1xuICAgICAgICByZWFzb25zLnB1c2goJ2Nvbm5lY3Rpb24gdG8gQVBJIHNlcnZlciBpcyBub3QgZXN0YWJsaXNoZWQgb3IgbG9zdCcpO1xuICAgICAgfVxuICAgICAgaWYgKCFzdGF0dXMuY29ubmVjdGVkVG9Ccm9rZXIpIHtcbiAgICAgICAgcmVhc29ucy5wdXNoKCdjb25uZWN0aW9uIHRvIGJyb2tlciBpcyBub3QgZXN0YWJsaXNoZWQgb3IgbG9zdCcpO1xuICAgICAgfVxuICAgICAgaWYgKCFzdGF0dXMuc3luY2hyb25pemVkKSB7XG4gICAgICAgIHJlYXNvbnMucHVzaCgnbG9jYWwgdGVybWluYWwgc3RhdGUgaXMgbm90IHN5bmNocm9uaXplZCB0byBicm9rZXInKTtcbiAgICAgIH1cbiAgICAgIGlmICghc3RhdHVzLnF1b3RlU3RyZWFtaW5nSGVhbHRoeSkge1xuICAgICAgICByZWFzb25zLnB1c2goJ3F1b3RlcyBhcmUgbm90IHN0cmVhbWVkIGZyb20gdGhlIGJyb2tlciBwcm9wZXJseScpO1xuICAgICAgfVxuICAgICAgbWVzc2FnZSA9IG1lc3NhZ2UgKyByZWFzb25zLmpvaW4oJyBhbmQgJykgKyAnLic7XG4gICAgfVxuICAgIHN0YXR1cy5tZXNzYWdlID0gbWVzc2FnZTtcbiAgICByZXR1cm4gc3RhdHVzO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdXB0aW1lIGluIHBlcmNlbnRzIG1lYXN1cmVkIG92ZXIgc3BlY2lmaWMgcGVyaW9kcyBvZiB0aW1lXG4gICAqIEByZXR1cm5zIHtPYmplY3R9IHVwdGltZSBpbiBwZXJjZW50cyBtZWFzdXJlZCBvdmVyIHNwZWNpZmljIHBlcmlvZHMgb2YgdGltZVxuICAgKi9cbiAgZ2V0IHVwdGltZSgpIHtcbiAgICBsZXQgdXB0aW1lID0ge307XG4gICAgZm9yIChsZXQgZSBvZiBPYmplY3QuZW50cmllcyh0aGlzLl91cHRpbWVSZXNlcnZvaXJzKSkge1xuICAgICAgdXB0aW1lW2VbMF1dID0gZVsxXS5nZXRTdGF0aXN0aWNzKCkuYXZlcmFnZTtcbiAgICB9XG4gICAgcmV0dXJuIHVwdGltZTtcbiAgfVxuXG4gIF9tZWFzdXJlVXB0aW1lKCkge1xuICAgIHRyeSB7XG4gICAgICBPYmplY3QudmFsdWVzKHRoaXMuX3VwdGltZVJlc2Vydm9pcnMpLmZvckVhY2gociA9PiByLnB1c2hNZWFzdXJlbWVudCh0aGlzLl9jb25uZWN0aW9uLnRlcm1pbmFsU3RhdGUuY29ubmVjdGVkICYmXG4gICAgICAgIHRoaXMuX2Nvbm5lY3Rpb24udGVybWluYWxTdGF0ZS5jb25uZWN0ZWRUb0Jyb2tlciAmJiB0aGlzLl9jb25uZWN0aW9uLnN5bmNocm9uaXplZCAmJlxuICAgICAgICB0aGlzLl9xdW90ZXNIZWFsdGh5ID8gMTAwIDogMCkpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWNvbnNvbGVcbiAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcignZmFpbGVkIHRvIG1lYXN1cmUgdXB0aW1lIGZvciBhY2NvdW50ICcgK1xuICAgICAgICB0aGlzLl9jb25uZWN0aW9uLmFjY291bnQuaWQsIGVycik7XG4gICAgfVxuICB9XG5cbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGNvbXBsZXhpdHlcbiAgX3VwZGF0ZVF1b3RlSGVhbHRoU3RhdHVzKCkge1xuICAgIHRyeSB7XG4gICAgICBsZXQgc2VydmVyRGF0ZVRpbWUgPSBtb21lbnQobmV3IERhdGUoRGF0ZS5ub3coKSAtIHRoaXMuX29mZnNldCkpO1xuICAgICAgbGV0IHNlcnZlclRpbWUgPSBzZXJ2ZXJEYXRlVGltZS5mb3JtYXQoJ0hIOm1tOnNzLlNTUycpO1xuICAgICAgbGV0IGRheU9mV2VlayA9IHNlcnZlckRhdGVUaW1lLmRheSgpO1xuICAgICAgbGV0IGRheXNPZldlZWsgPSB7XG4gICAgICAgIDA6ICdTVU5EQVknLFxuICAgICAgICAxOiAnTU9OREFZJyxcbiAgICAgICAgMjogJ1RVRVNEQVknLFxuICAgICAgICAzOiAnV0VETkVTREFZJyxcbiAgICAgICAgNDogJ1RIVVJTREFZJyxcbiAgICAgICAgNTogJ0ZSSURBWScsXG4gICAgICAgIDY6ICdTQVRVUkRBWSdcbiAgICAgIH07XG4gICAgICBsZXQgaW5RdW90ZVNlc3Npb24gPSBmYWxzZTtcbiAgICAgIGlmICghdGhpcy5fcHJpY2VVcGRhdGVkQXQpIHtcbiAgICAgICAgdGhpcy5fcHJpY2VVcGRhdGVkQXQgPSBuZXcgRGF0ZSgpO1xuICAgICAgfVxuICAgICAgaWYgKCEodGhpcy5fY29ubmVjdGlvbi5zdWJzY3JpYmVkU3ltYm9scyB8fCBbXSkubGVuZ3RoKSB7XG4gICAgICAgIHRoaXMuX3ByaWNlVXBkYXRlZEF0ID0gbmV3IERhdGUoKTtcbiAgICAgIH1cbiAgICAgIGZvciAobGV0IHN5bWJvbCBvZiB0aGlzLl9jb25uZWN0aW9uLnN1YnNjcmliZWRTeW1ib2xzIHx8IFtdKSB7XG4gICAgICAgIGxldCBzcGVjaWZpY2F0aW9uID0gdGhpcy5fY29ubmVjdGlvbi50ZXJtaW5hbFN0YXRlLnNwZWNpZmljYXRpb24oc3ltYm9sKSB8fCB7fTtcbiAgICAgICAgbGV0IHF1b3RlU2Vzc2lvbnMgPSAoc3BlY2lmaWNhdGlvbi5xdW90ZVNlc3Npb25zIHx8IFtdKVtkYXlzT2ZXZWVrW2RheU9mV2Vla11dIHx8IFtdO1xuICAgICAgICBmb3IgKGxldCBzZXNzaW9uIG9mIHF1b3RlU2Vzc2lvbnMpIHtcbiAgICAgICAgICBpZiAoc2Vzc2lvbi5mcm9tIDw9IHNlcnZlclRpbWUgJiYgc2Vzc2lvbi50byA+PSBzZXJ2ZXJUaW1lKSB7XG4gICAgICAgICAgICBpblF1b3RlU2Vzc2lvbiA9IHRydWU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgICB0aGlzLl9xdW90ZXNIZWFsdGh5ID0gIXRoaXMuX2Nvbm5lY3Rpb24uc3Vic2NyaWJlZFN5bWJvbHMubGVuZ3RoIHx8ICFpblF1b3RlU2Vzc2lvbiB8fFxuICAgICAgICAoRGF0ZS5ub3coKSAtIHRoaXMuX3ByaWNlVXBkYXRlZEF0LmdldFRpbWUoKSA8IHRoaXMuX21pblF1b3RlSW50ZXJ2YWwpO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgdGhpcy5fbG9nZ2VyLmVycm9yKCdmYWlsZWQgdG8gdXBkYXRlIHF1b3RlIHN0cmVhbWluZyBoZWFsdGggc3RhdHVzIGZvciBhY2NvdW50ICcgK1xuICAgICAgICB0aGlzLl9jb25uZWN0aW9uLmFjY291bnQuaWQsIGVycik7XG4gICAgfVxuICB9XG5cbiAgX2dldFJhbmRvbVRpbWVvdXQoKSB7XG4gICAgcmV0dXJuIChNYXRoLnJhbmRvbSgpICogNTkgKyAxKSAqIDEwMDA7XG4gIH1cblxufVxuIl0sIm5hbWVzIjpbIkNvbm5lY3Rpb25IZWFsdGhNb25pdG9yIiwiU3luY2hyb25pemF0aW9uTGlzdGVuZXIiLCJzdGFydCIsInVwZGF0ZVF1b3RlSGVhbHRoU3RhdHVzSW50ZXJ2YWwiLCJfdXBkYXRlUXVvdGVIZWFsdGhTdGF0dXMiLCJfdXBkYXRlUXVvdGVIZWFsdGhTdGF0dXNJbnRlcnZhbCIsInNldFRpbWVvdXQiLCJiaW5kIiwiX2dldFJhbmRvbVRpbWVvdXQiLCJtZWFzdXJlVXB0aW1lSW50ZXJ2YWwiLCJfbWVhc3VyZVVwdGltZSIsIl9tZWFzdXJlVXB0aW1lSW50ZXJ2YWwiLCJzdG9wIiwiX2xvZ2dlciIsImRlYnVnIiwiX2Nvbm5lY3Rpb24iLCJhY2NvdW50IiwiaWQiLCJjbGVhclRpbWVvdXQiLCJvblN5bWJvbFByaWNlVXBkYXRlZCIsImluc3RhbmNlSW5kZXgiLCJwcmljZSIsImJyb2tlclRpbWVzdGFtcCIsIm1vbWVudCIsImJyb2tlclRpbWUiLCJ0b0RhdGUiLCJnZXRUaW1lIiwiX3ByaWNlVXBkYXRlZEF0IiwiRGF0ZSIsIl9vZmZzZXQiLCJlcnIiLCJlcnJvciIsIm9uSGVhbHRoU3RhdHVzIiwic3RhdHVzIiwiX3NlcnZlckhlYWx0aFN0YXR1cyIsIm9uRGlzY29ubmVjdGVkIiwic2VydmVySGVhbHRoU3RhdHVzIiwicmVzdWx0IiwicyIsIk9iamVjdCIsInZhbHVlcyIsImZpZWxkIiwia2V5cyIsImhlYWx0aFN0YXR1cyIsImNvbm5lY3RlZCIsInRlcm1pbmFsU3RhdGUiLCJjb25uZWN0ZWRUb0Jyb2tlciIsInF1b3RlU3RyZWFtaW5nSGVhbHRoeSIsIl9xdW90ZXNIZWFsdGh5Iiwic3luY2hyb25pemVkIiwiaGVhbHRoeSIsIm1lc3NhZ2UiLCJyZWFzb25zIiwicHVzaCIsImpvaW4iLCJ1cHRpbWUiLCJlIiwiZW50cmllcyIsIl91cHRpbWVSZXNlcnZvaXJzIiwiZ2V0U3RhdGlzdGljcyIsImF2ZXJhZ2UiLCJmb3JFYWNoIiwiciIsInB1c2hNZWFzdXJlbWVudCIsInNlcnZlckRhdGVUaW1lIiwibm93Iiwic2VydmVyVGltZSIsImZvcm1hdCIsImRheU9mV2VlayIsImRheSIsImRheXNPZldlZWsiLCJpblF1b3RlU2Vzc2lvbiIsInN1YnNjcmliZWRTeW1ib2xzIiwibGVuZ3RoIiwic3ltYm9sIiwic3BlY2lmaWNhdGlvbiIsInF1b3RlU2Vzc2lvbnMiLCJzZXNzaW9uIiwiZnJvbSIsInRvIiwiX21pblF1b3RlSW50ZXJ2YWwiLCJNYXRoIiwicmFuZG9tIiwiY29uc3RydWN0b3IiLCJjb25uZWN0aW9uIiwiUmVzZXJ2b2lyIiwiTG9nZ2VyTWFuYWdlciIsImdldExvZ2dlciJdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7ZUFVcUJBOzs7Z0ZBUmU7K0RBQ2pCO2tFQUNHOytEQUNJOzs7Ozs7QUFLWCxJQUFBLEFBQU1BLDBCQUFOLE1BQU1BLGdDQUFnQ0MsZ0NBQXVCO0lBb0IxRTs7R0FFQyxHQUNEQyxRQUFRO1FBQ04sTUFBTUMsa0NBQWtDO1lBQ3RDLElBQUksQ0FBQ0Msd0JBQXdCO1lBQzdCLElBQUksQ0FBQ0MsZ0NBQWdDLEdBQUdDLFdBQ3RDSCxnQ0FBZ0NJLElBQUksQ0FBQyxJQUFJLEdBQ3pDLElBQUksQ0FBQ0MsaUJBQWlCO1FBRTFCO1FBQ0EsSUFBSSxDQUFDSCxnQ0FBZ0MsR0FBR0MsV0FDdENILGdDQUFnQ0ksSUFBSSxDQUFDLElBQUksR0FDekMsSUFBSSxD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