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
JavaScript
"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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