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)
594 lines (593 loc) • 104 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "default", {
enumerable: true,
get: function() {
return StreamingMetaApiConnection;
}
});
const _terminalState = /*#__PURE__*/ _interop_require_default(require("./terminalState"));
const _memoryHistoryStorage = /*#__PURE__*/ _interop_require_default(require("./memoryHistoryStorage"));
const _timeoutError = /*#__PURE__*/ _interop_require_default(require("../clients/timeoutError"));
const _randomstring = /*#__PURE__*/ _interop_require_default(require("randomstring"));
const _connectionHealthMonitor = /*#__PURE__*/ _interop_require_default(require("./connectionHealthMonitor"));
const _errorHandler = require("../clients/errorHandler");
const _optionsValidator = /*#__PURE__*/ _interop_require_default(require("../clients/optionsValidator"));
const _logger = /*#__PURE__*/ _interop_require_default(require("../logger"));
const _metaApiConnection = /*#__PURE__*/ _interop_require_default(require("./metaApiConnection"));
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
let StreamingMetaApiConnection = class StreamingMetaApiConnection extends _metaApiConnection.default {
/**
* Opens the connection. Can only be called the first time, next calls will be ignored.
* @param {string} instanceId connection instance id
* @return {Promise} promise resolving when the connection is opened
*/ async connect(instanceId) {
if (!this._openedInstances.includes(instanceId)) {
this._openedInstances.push(instanceId);
}
if (!this._opened) {
this._logger.trace(`${this._account.id}: Opening connection`);
this._opened = true;
try {
await this.initialize();
await this.subscribe();
} catch (err) {
await this.close();
throw err;
}
}
}
/**
* Clears the order and transaction history of a specified application and removes application
* @return {Promise} promise resolving when the history is cleared and application is removed
*/ removeApplication() {
this._checkIsConnectionActive();
this._historyStorage.clear();
return this._websocketClient.removeApplication(this._account.id);
}
/**
* Requests the terminal to start synchronization process
* (see https://metaapi.cloud/docs/client/websocket/synchronizing/synchronize/)
* @param {String} instanceIndex instance index
* @returns {Promise} promise which resolves when synchronization started
*/ async synchronize(instanceIndex) {
this._checkIsConnectionActive();
const region = this.getRegion(instanceIndex);
const instance = this.getInstanceNumber(instanceIndex);
const host = this.getHostName(instanceIndex);
let startingHistoryOrderTime = new Date(Math.max((this._historyStartTime || new Date(0)).getTime(), (await this._historyStorage.lastHistoryOrderTime(instance)).getTime()));
let startingDealTime = new Date(Math.max((this._historyStartTime || new Date(0)).getTime(), (await this._historyStorage.lastDealTime(instance)).getTime()));
let synchronizationId = _randomstring.default.generate(32);
this._getState(instanceIndex).lastSynchronizationId = synchronizationId;
const accountId = this._account.accountRegions[region];
this._logger.debug(`${this._account.id}:${instanceIndex}: initiating synchronization ${synchronizationId}`);
return this._websocketClient.synchronize(accountId, instance, host, synchronizationId, startingHistoryOrderTime, startingDealTime, this.terminalState.getHashes());
}
/**
* Initializes meta api connection
* @return {Promise} promise which resolves when meta api connection is initialized
*/ async initialize() {
this._checkIsConnectionActive();
await this._historyStorage.initialize(this._account.id, this._connectionRegistry.application);
this._websocketClient.addAccountCache(this._account.id, this._account.accountRegions);
}
/**
* Initiates subscription to MetaTrader terminal
* @returns {Promise} promise which resolves when subscription is initiated
*/ async subscribe() {
this._checkIsConnectionActive();
const accountRegions = this._account.accountRegions;
Object.entries(accountRegions).forEach(([region, replicaId])=>{
if (!this._options.region || this._options.region === region) {
this._websocketClient.ensureSubscribe(replicaId, 0);
this._websocketClient.ensureSubscribe(replicaId, 1);
}
});
}
/**
* Subscribes on market data of specified symbol (see
* https://metaapi.cloud/docs/client/websocket/marketDataStreaming/subscribeToMarketData/).
* @param {String} symbol symbol (e.g. currency pair or an index)
* @param {Array<MarketDataSubscription>} subscriptions array of market data subscription to create or update. Please
* note that this feature is not fully implemented on server-side yet
* @param {number} [timeoutInSeconds] timeout to wait for prices in seconds, default is 30
* @param {boolean} [waitForQuote] if set to false, the method will resolve without waiting for the first quote to
* arrive. Default is to wait for quote if quotes subscription is requested.
* @returns {Promise} promise which resolves when subscription request was processed
*/ async subscribeToMarketData(symbol, subscriptions, timeoutInSeconds, waitForQuote = true) {
this._checkIsConnectionActive();
if (!this._terminalState.specification(symbol)) {
throw new _errorHandler.ValidationError(`${this._account.id}: Cannot subscribe to market data for symbol ${symbol} because ` + "symbol does not exist");
} else {
subscriptions = subscriptions || [
{
type: "quotes"
}
];
if (this._subscriptions[symbol]) {
const prevSubscriptions = this._subscriptions[symbol].subscriptions;
subscriptions.forEach((subscription)=>{
const index = subscription.type === "candles" ? prevSubscriptions.findIndex((item)=>item.type === subscription.type && item.timeframe === subscription.timeframe) : prevSubscriptions.findIndex((item)=>item.type === subscription.type);
if (index === -1) {
prevSubscriptions.push(subscription);
} else {
prevSubscriptions[index] = subscription;
}
});
} else {
this._subscriptions[symbol] = {
subscriptions
};
}
await this._websocketClient.subscribeToMarketData(this._account.id, symbol, subscriptions, this._account.reliability);
if (waitForQuote !== false && subscriptions.find((s)=>s.type === "quotes")) {
return this.terminalState.waitForPrice(symbol, timeoutInSeconds);
}
}
}
/**
* Unsubscribes from market data of specified symbol (see
* https://metaapi.cloud/docs/client/websocket/marketDataStreaming/unsubscribeFromMarketData/).
* @param {String} symbol symbol (e.g. currency pair or an index)
* @param {Array<MarketDataUnsubscription>} unsubscriptions array of subscriptions to cancel
* @returns {Promise} promise which resolves when unsubscription request was processed
*/ unsubscribeFromMarketData(symbol, unsubscriptions) {
this._checkIsConnectionActive();
if (!unsubscriptions) {
delete this._subscriptions[symbol];
} else if (this._subscriptions[symbol]) {
this._subscriptions[symbol].subscriptions = this._subscriptions[symbol].subscriptions.filter((subscription)=>{
return !unsubscriptions.find((unsubscription)=>subscription.type === unsubscription.type && (!unsubscription.timeframe || subscription.timeframe === unsubscription.timeframe));
});
if (!this._subscriptions[symbol].subscriptions.length) {
delete this._subscriptions[symbol];
}
}
return this._websocketClient.unsubscribeFromMarketData(this._account.id, symbol, unsubscriptions, this._account.reliability);
}
/**
* Invoked when subscription downgrade has occurred
* @param {String} instanceIndex index of an account instance connected
* @param {string} symbol symbol to update subscriptions for
* @param {Array<MarketDataSubscription>} updates array of market data subscription to update
* @param {Array<MarketDataUnsubscription>} unsubscriptions array of subscriptions to cancel
* @return {Promise} promise which resolves when the asynchronous event is processed
*/ // eslint-disable-next-line complexity
async onSubscriptionDowngraded(instanceIndex, symbol, updates, unsubscriptions) {
if (unsubscriptions === null || unsubscriptions === void 0 ? void 0 : unsubscriptions.length) {
this.unsubscribeFromMarketData(symbol, unsubscriptions).catch((err)=>{
let method = err.name !== "ValidationError" ? "error" : "trace";
this._logger[method](`${this._account.id}: failed do unsubscribe from market data on subscription downgraded`, err);
});
}
if (updates === null || updates === void 0 ? void 0 : updates.length) {
this.subscribeToMarketData(symbol, updates).catch((err)=>{
this._logger.error(`${this._account.id}: failed do subscribe from market data on subscription downgraded`, err);
});
}
}
/**
* Returns list of the symbols connection is subscribed to
* @returns {Array<String>} list of the symbols connection is subscribed to
*/ get subscribedSymbols() {
return Object.keys(this._subscriptions);
}
/**
* Returns subscriptions for a symbol
* @param {string} symbol symbol to retrieve subscriptions for
* @returns {Array<MarketDataSubscription>} list of market data subscriptions for the symbol
*/ subscriptions(symbol) {
this._checkIsConnectionActive();
return (this._subscriptions[symbol] || {}).subscriptions;
}
/**
* Returns local copy of terminal state
* @returns {TerminalState} local copy of terminal state
*/ get terminalState() {
return this._terminalState;
}
/**
* Returns local history storage
* @returns {HistoryStorage} local history storage
*/ get historyStorage() {
return this._historyStorage;
}
/**
* Invoked when connection to MetaTrader terminal established
* @param {String} instanceIndex index of an account instance connected
* @param {Number} replicas number of account replicas launched
* @return {Promise} promise which resolves when the asynchronous event is processed
*/ async onConnected(instanceIndex, replicas) {
let key = _randomstring.default.generate(32);
let state = this._getState(instanceIndex);
const region = this.getRegion(instanceIndex);
this.cancelRefresh(region);
await this._terminalHashManager.refreshIgnoredFieldLists(region);
state.shouldSynchronize = key;
state.synchronizationRetryIntervalInSeconds = 1;
state.synchronized = false;
this._ensureSynchronized(instanceIndex, key);
this._logger.debug(`${this._account.id}:${instanceIndex}: connected to broker`);
}
/**
* Invoked when connection to MetaTrader terminal terminated
* @param {String} instanceIndex index of an account instance connected
*/ async onDisconnected(instanceIndex) {
let state = this._getState(instanceIndex);
state.lastDisconnectedSynchronizationId = state.lastSynchronizationId;
state.lastSynchronizationId = undefined;
state.shouldSynchronize = undefined;
state.synchronized = false;
state.disconnected = true;
const instanceNumber = this.getInstanceNumber(instanceIndex);
const region = this.getRegion(instanceIndex);
const instance = `${region}:${instanceNumber}`;
delete this._refreshMarketDataSubscriptionSessions[instance];
clearTimeout(this._refreshMarketDataSubscriptionTimeouts[instance]);
delete this._refreshMarketDataSubscriptionTimeouts[instance];
clearTimeout(state.synchronizationTimeout);
delete state.synchronizationTimeout;
clearTimeout(state.ensureSynchronizeTimeout);
delete state.ensureSynchronizeTimeout;
this._logger.debug(`${this._account.id}:${instanceIndex}: disconnected from broker`);
}
/**
* Invoked when a symbol specifications were updated
* @param {String} instanceIndex index of account instance connected
* @param {Array<MetatraderSymbolSpecification>} specifications updated specifications
* @param {Array<String>} removedSymbols removed symbols
*/ onSymbolSpecificationsUpdated(instanceIndex, specifications, removedSymbols) {
this._scheduleSynchronizationTimeout(instanceIndex);
}
/**
* Invoked when position synchronization finished to indicate progress of an initial terminal state synchronization
* @param {string} instanceIndex index of an account instance connected
* @param {String} synchronizationId synchronization request id
*/ onPositionsSynchronized(instanceIndex, synchronizationId) {
this._scheduleSynchronizationTimeout(instanceIndex);
}
/**
* Invoked when pending order synchronization fnished to indicate progress of an initial terminal state
* synchronization
* @param {string} instanceIndex index of an account instance connected
* @param {String} synchronizationId synchronization request id
*/ onPendingOrdersSynchronized(instanceIndex, synchronizationId) {
this._scheduleSynchronizationTimeout(instanceIndex);
}
/**
* Invoked when a synchronization of history deals on a MetaTrader account have finished to indicate progress of an
* initial terminal state synchronization
* @param {String} instanceIndex index of an account instance connected
* @param {String} synchronizationId synchronization request id
* @return {Promise} promise which resolves when the asynchronous event is processed
*/ async onDealsSynchronized(instanceIndex, synchronizationId) {
let state = this._getState(instanceIndex);
state.dealsSynchronized[synchronizationId] = true;
this._scheduleSynchronizationTimeout(instanceIndex);
this._logger.debug(`${this._account.id}:${instanceIndex}: finished synchronization ${synchronizationId}`);
}
/**
* Invoked when a synchronization of history orders on a MetaTrader account have finished to indicate progress of an
* initial terminal state synchronization
* @param {String} instanceIndex index of an account instance connected
* @param {String} synchronizationId synchronization request id
* @return {Promise} promise which resolves when the asynchronous event is processed
*/ async onHistoryOrdersSynchronized(instanceIndex, synchronizationId) {
let state = this._getState(instanceIndex);
state.ordersSynchronized[synchronizationId] = true;
this._scheduleSynchronizationTimeout(instanceIndex);
}
/**
* Invoked when connection to MetaApi websocket API restored after a disconnect
* @param {String} region reconnected region
* @param {Number} instanceNumber reconnected instance number
* @return {Promise} promise which resolves when connection to MetaApi websocket API restored after a disconnect
*/ async onReconnected(region, instanceNumber) {
const instanceTemplate = `${region}:${instanceNumber}`;
Object.keys(this._stateByInstanceIndex).filter((key)=>key.startsWith(`${instanceTemplate}:`)).forEach((key)=>{
delete this._stateByInstanceIndex[key];
});
delete this._refreshMarketDataSubscriptionSessions[instanceTemplate];
clearTimeout(this._refreshMarketDataSubscriptionTimeouts[instanceTemplate]);
delete this._refreshMarketDataSubscriptionTimeouts[instanceTemplate];
}
/**
* Invoked when a stream for an instance index is closed
* @param {String} instanceIndex index of an account instance connected
* @return {Promise} promise which resolves when the asynchronous event is processed
*/ async onStreamClosed(instanceIndex) {
delete this._stateByInstanceIndex[instanceIndex];
}
/**
* Invoked when MetaTrader terminal state synchronization is started
* @param {string} instanceIndex index of an account instance connected
* @param {string} specificationsHash specifications hash
* @param {string} positionsHash positions hash
* @param {string} ordersHash orders hash
* @param {string} synchronizationId synchronization id
* @return {Promise} promise which resolves when the asynchronous event is processed
*/ async onSynchronizationStarted(instanceIndex, specificationsHash, positionsHash, ordersHash, synchronizationId) {
this._logger.debug(`${this._account.id}:${instanceIndex}: starting synchronization ${synchronizationId}`);
const instanceNumber = this.getInstanceNumber(instanceIndex);
const region = this.getRegion(instanceIndex);
const instance = `${region}:${instanceNumber}`;
const accountId = this._account.accountRegions[region];
delete this._refreshMarketDataSubscriptionSessions[instance];
let sessionId = _randomstring.default.generate(32);
this._refreshMarketDataSubscriptionSessions[instance] = sessionId;
clearTimeout(this._refreshMarketDataSubscriptionTimeouts[instance]);
delete this._refreshMarketDataSubscriptionTimeouts[instance];
await this._refreshMarketDataSubscriptions(accountId, instanceNumber, sessionId);
this._scheduleSynchronizationTimeout(instanceIndex);
let state = this._getState(instanceIndex);
if (state && !this._closed) {
state.lastSynchronizationId = synchronizationId;
}
}
/**
* Invoked when account region has been unsubscribed
* @param {String} region account region unsubscribed
* @return {Promise} promise which resolves when the asynchronous event is processed
*/ onUnsubscribeRegion(region) {
Object.keys(this._refreshMarketDataSubscriptionTimeouts).filter((instance)=>instance.startsWith(`${region}:`)).forEach((instance)=>{
clearTimeout(this._refreshMarketDataSubscriptionTimeouts[instance]);
delete this._refreshMarketDataSubscriptionTimeouts[instance];
delete this._refreshMarketDataSubscriptionSessions[instance];
});
Object.keys(this._stateByInstanceIndex).filter((instance)=>instance.startsWith(`${region}:`)).forEach((instance)=>delete this._stateByInstanceIndex[instance]);
}
/**
* Returns flag indicating status of state synchronization with MetaTrader terminal
* @param {String} instanceIndex index of an account instance connected
* @param {String} synchronizationId optional synchronization request id, last synchronization request id will be used
* by default
* @return {Promise<Boolean>} promise resolving with a flag indicating status of state synchronization with MetaTrader
* terminal
*/ async isSynchronized(instanceIndex, synchronizationId) {
return Object.values(this._stateByInstanceIndex).reduce((acc, s)=>{
if (instanceIndex !== undefined && s.instanceIndex !== instanceIndex) {
return acc;
}
const checkSynchronizationId = synchronizationId || s.lastSynchronizationId;
let synchronized = !!s.ordersSynchronized[checkSynchronizationId] && !!s.dealsSynchronized[checkSynchronizationId];
return acc || synchronized;
}, false);
}
/**
* @typedef {Object} SynchronizationOptions
* @property {String} [applicationPattern] application regular expression pattern, default is .*
* @property {String} [synchronizationId] synchronization id, last synchronization request id will be used by
* default
* @property {Number} [instanceIndex] index of an account instance to ensure synchronization on, default is to wait
* for the first instance to synchronize
* @property {Number} [timeoutInSeconds] wait timeout in seconds, default is 5m
* @property {Number} [intervalInMilliseconds] interval between account reloads while waiting for a change, default is 1s
*/ /**
* Waits until synchronization to MetaTrader terminal is completed
* @param {SynchronizationOptions} opts synchronization options
* @return {Promise} promise which resolves when synchronization to MetaTrader terminal is completed
* @throws {TimeoutError} if application failed to synchronize with the teminal within timeout allowed
*/ // eslint-disable-next-line complexity
async waitSynchronized(opts) {
this._checkIsConnectionActive();
opts = opts || {};
let instanceIndex = opts.instanceIndex;
let synchronizationId = opts.synchronizationId;
let timeoutInSeconds = opts.timeoutInSeconds || 300;
let intervalInMilliseconds = opts.intervalInMilliseconds || 1000;
let applicationPattern = opts.applicationPattern || (this._account.application === "CopyFactory" ? "CopyFactory.*|RPC" : "RPC");
let startTime = Date.now();
let synchronized;
while(!(synchronized = await this.isSynchronized(instanceIndex, synchronizationId)) && startTime + timeoutInSeconds * 1000 > Date.now()){
await new Promise((res)=>setTimeout(res, intervalInMilliseconds));
}
let state;
if (instanceIndex === undefined) {
for (let s of Object.values(this._stateByInstanceIndex)){
if (await this.isSynchronized(s.instanceIndex, synchronizationId)) {
state = s;
instanceIndex = s.instanceIndex;
}
}
} else {
state = Object.values(this._stateByInstanceIndex).find((s)=>s.instanceIndex === instanceIndex);
}
if (!synchronized) {
throw new _timeoutError.default("Timed out waiting for MetaApi to synchronize to MetaTrader account " + this._account.id + ", synchronization id " + (synchronizationId || state && state.lastSynchronizationId || state && state.lastDisconnectedSynchronizationId));
}
let timeLeftInSeconds = Math.max(0, timeoutInSeconds - (Date.now() - startTime) / 1000);
const region = this.getRegion(state.instanceIndex);
const accountId = this._account.accountRegions[region];
await this._websocketClient.waitSynchronized(accountId, this.getInstanceNumber(instanceIndex), applicationPattern, timeLeftInSeconds);
}
/**
* Closes the connection. The instance of the class should no longer be used after this method is invoked.
* @param {string} instanceId connection instance id
*/ async close(instanceId) {
if (this._opened) {
this._openedInstances = this._openedInstances.filter((id)=>id !== instanceId);
if (!this._openedInstances.length && !this._closed) {
clearInterval(this._refreshJob);
this._logger.debug(`${this._account.id}: Closing connection`);
Object.values(this._stateByInstanceIndex).forEach((state)=>clearTimeout(state.synchronizationTimeout));
this._stateByInstanceIndex = {};
await this._connectionRegistry.removeStreaming(this._account);
this._terminalState.close();
const accountRegions = this._account.accountRegions;
this._websocketClient.removeSynchronizationListener(this._account.id, this);
this._websocketClient.removeSynchronizationListener(this._account.id, this._terminalState);
this._websocketClient.removeSynchronizationListener(this._account.id, this._historyStorage);
this._websocketClient.removeSynchronizationListener(this._account.id, this._healthMonitor);
this._websocketClient.removeReconnectListener(this);
this._healthMonitor.stop();
this._refreshMarketDataSubscriptionSessions = {};
Object.values(this._refreshMarketDataSubscriptionTimeouts).forEach((timeout)=>clearTimeout(timeout));
this._refreshMarketDataSubscriptionTimeouts = {};
Object.values(accountRegions).forEach((replicaId)=>this._websocketClient.removeAccountCache(replicaId));
this._closed = true;
this._logger.trace(`${this._account.id}: Closed connection`);
}
}
}
/**
* Returns synchronization status
* @return {boolean} synchronization status
*/ get synchronized() {
return Object.values(this._stateByInstanceIndex).reduce((acc, s)=>acc || s.synchronized, false);
}
/**
* Returns MetaApi account
* @return {MetatraderAccount} MetaApi account
*/ get account() {
return this._account;
}
/**
* Returns connection health monitor instance
* @return {ConnectionHealthMonitor} connection health monitor instance
*/ get healthMonitor() {
return this._healthMonitor;
}
async _refreshMarketDataSubscriptions(accountId, instanceNumber, session) {
const region = this._websocketClient.getAccountRegion(accountId);
const instance = `${region}:${instanceNumber}`;
try {
if (this._refreshMarketDataSubscriptionSessions[instance] === session) {
const subscriptionsList = [];
Object.keys(this._subscriptions).forEach((key)=>{
const subscriptions = this.subscriptions(key);
const subscriptionsItem = {
symbol: key
};
if (subscriptions) {
subscriptionsItem.subscriptions = subscriptions;
}
subscriptionsList.push(subscriptionsItem);
});
await this._websocketClient.refreshMarketDataSubscriptions(accountId, instanceNumber, subscriptionsList);
}
} catch (err) {
this._logger.error(`Error refreshing market data subscriptions job for account ${this._account.id} ` + `${instanceNumber}`, err);
} finally{
if (this._refreshMarketDataSubscriptionSessions[instance] === session) {
let refreshInterval = (Math.random() * (this._maxSubscriptionRefreshInterval - this._minSubscriptionRefreshInterval) + this._minSubscriptionRefreshInterval) * 1000;
this._refreshMarketDataSubscriptionTimeouts[instance] = setTimeout(()=>this._refreshMarketDataSubscriptions(accountId, instanceNumber, session), refreshInterval);
}
}
}
_generateStopOptions(stopLoss, takeProfit) {
let trade = {};
if (typeof stopLoss === "number") {
trade.stopLoss = stopLoss;
} else if (stopLoss) {
trade.stopLoss = stopLoss.value;
trade.stopLossUnits = stopLoss.units;
}
if (typeof takeProfit === "number") {
trade.takeProfit = takeProfit;
} else if (takeProfit) {
trade.takeProfit = takeProfit.value;
trade.takeProfitUnits = takeProfit.units;
}
return trade;
}
async _ensureSynchronized(instanceIndex, key) {
let state = this._getState(instanceIndex);
if (state && state.shouldSynchronize && !this._closed) {
try {
const synchronizationResult = await this.synchronize(instanceIndex);
if (synchronizationResult) {
state.synchronized = true;
state.synchronizationRetryIntervalInSeconds = 1;
delete state.ensureSynchronizeTimeout;
}
this._scheduleSynchronizationTimeout(instanceIndex);
} catch (err) {
const level = this._latencyService.getSynchronizedAccountInstances(this._account.id).length ? "debug" : "error";
this._logger[level]("MetaApi websocket client for account " + this._account.id + ":" + instanceIndex + " failed to synchronize", err);
if (state.shouldSynchronize === key) {
clearTimeout(state.ensureSynchronizeTimeout);
state.ensureSynchronizeTimeout = setTimeout(this._ensureSynchronized.bind(this, instanceIndex, key), state.synchronizationRetryIntervalInSeconds * 1000);
state.synchronizationRetryIntervalInSeconds = Math.min(state.synchronizationRetryIntervalInSeconds * 2, 300);
}
}
}
}
_getState(instanceIndex) {
if (!this._stateByInstanceIndex["" + instanceIndex]) {
this._stateByInstanceIndex["" + instanceIndex] = {
instanceIndex,
ordersSynchronized: {},
dealsSynchronized: {},
shouldSynchronize: undefined,
synchronizationRetryIntervalInSeconds: 1,
synchronized: false,
lastDisconnectedSynchronizationId: undefined,
lastSynchronizationId: undefined,
disconnected: false
};
}
return this._stateByInstanceIndex["" + instanceIndex];
}
_scheduleSynchronizationTimeout(instanceIndex) {
let state = this._getState(instanceIndex);
if (state && !this._closed) {
clearTimeout(state.synchronizationTimeout);
state.synchronizationTimeout = setTimeout(()=>this._checkSynchronizationTimedOut(instanceIndex), 2 * 60 * 1000);
this._logger.debug(`${this._account.id}:${instanceIndex}: scheduled synchronization timeout`);
}
}
_checkSynchronizationTimedOut(instanceIndex) {
this._logger.debug(`${this._account.id}:${instanceIndex}: checking if synchronization timed out out`);
let state = this._getState(instanceIndex);
if (state && !this._closed) {
let synchronizationId = state.lastSynchronizationId;
let synchronized = !!state.dealsSynchronized[synchronizationId];
if (!synchronized && synchronizationId && state.shouldSynchronize) {
this._logger.warn(`${this._account.id}:${instanceIndex}: resynchronized since latest synchronization ` + `${synchronizationId} did not finish in time`);
this._ensureSynchronized(instanceIndex, state.shouldSynchronize);
}
}
}
/**
* Constructs MetaApi MetaTrader streaming Api connection
* @param {MetaApiOpts} options metaapi options
* @param {MetaApiWebsocketClient} websocketClient MetaApi websocket client
* @param {TerminalHashManager} terminalHashManager terminal hash manager
* @param {MetatraderAccount} account MetaTrader account id to connect to
* @param {HistoryStorage} historyStorage terminal history storage. By default an instance of MemoryHistoryStorage
* will be used.
* @param {ConnectionRegistry} connectionRegistry metatrader account connection registry
* @param {Date} [historyStartTime] history start sync time
* @param {RefreshSubscriptionsOpts} [refreshSubscriptionsOpts] subscriptions refresh options
*/ constructor(options, websocketClient, terminalHashManager, account, historyStorage, connectionRegistry, historyStartTime, refreshSubscriptionsOpts){
super(options, websocketClient, account);
refreshSubscriptionsOpts = refreshSubscriptionsOpts || {};
const validator = new _optionsValidator.default();
this._minSubscriptionRefreshInterval = validator.validateNonZero(refreshSubscriptionsOpts.minDelayInSeconds, 1, "refreshSubscriptionsOpts.minDelayInSeconds");
this._maxSubscriptionRefreshInterval = validator.validateNonZero(refreshSubscriptionsOpts.maxDelayInSeconds, 600, "refreshSubscriptionsOpts.maxDelayInSeconds");
this._connectionRegistry = connectionRegistry;
this._historyStartTime = historyStartTime;
this._terminalHashManager = terminalHashManager;
this._terminalState = new _terminalState.default(account, terminalHashManager, this._websocketClient);
this._historyStorage = historyStorage || new _memoryHistoryStorage.default();
this._healthMonitor = new _connectionHealthMonitor.default(this);
this._websocketClient.addSynchronizationListener(account.id, this);
this._websocketClient.addSynchronizationListener(account.id, this._terminalState);
this._websocketClient.addSynchronizationListener(account.id, this._historyStorage);
this._websocketClient.addSynchronizationListener(account.id, this._healthMonitor);
Object.values(account.accountRegions).forEach((replicaId)=>this._websocketClient.addReconnectListener(this, replicaId));
this._subscriptions = {};
this._stateByInstanceIndex = {};
this._refreshMarketDataSubscriptionSessions = {};
this._refreshMarketDataSubscriptionTimeouts = {};
this._openedInstances = [];
this._logger = _logger.default.getLogger("MetaApiConnection");
}
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBUZXJtaW5hbFN0YXRlIGZyb20gJy4vdGVybWluYWxTdGF0ZSc7XG5pbXBvcnQgTWVtb3J5SGlzdG9yeVN0b3JhZ2UgZnJvbSAnLi9tZW1vcnlIaXN0b3J5U3RvcmFnZSc7XG5pbXBvcnQgVGltZW91dEVycm9yIGZyb20gJy4uL2NsaWVudHMvdGltZW91dEVycm9yJztcbmltcG9ydCByYW5kb21zdHJpbmcgZnJvbSAncmFuZG9tc3RyaW5nJztcbmltcG9ydCBDb25uZWN0aW9uSGVhbHRoTW9uaXRvciBmcm9tICcuL2Nvbm5lY3Rpb25IZWFsdGhNb25pdG9yJztcbmltcG9ydCB7VmFsaWRhdGlvbkVycm9yfSBmcm9tICcuLi9jbGllbnRzL2Vycm9ySGFuZGxlcic7XG5pbXBvcnQgT3B0aW9uc1ZhbGlkYXRvciBmcm9tICcuLi9jbGllbnRzL29wdGlvbnNWYWxpZGF0b3InO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIgZnJvbSAnLi4vbG9nZ2VyJztcbmltcG9ydCBNZXRhQXBpQ29ubmVjdGlvbiBmcm9tICcuL21ldGFBcGlDb25uZWN0aW9uJztcblxuLyoqXG4gKiBFeHBvc2VzIE1ldGFBcGkgTWV0YVRyYWRlciBzdHJlYW1pbmcgQVBJIGNvbm5lY3Rpb24gdG8gY29uc3VtZXJzXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFN0cmVhbWluZ01ldGFBcGlDb25uZWN0aW9uIGV4dGVuZHMgTWV0YUFwaUNvbm5lY3Rpb24ge1xuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3RzIE1ldGFBcGkgTWV0YVRyYWRlciBzdHJlYW1pbmcgQXBpIGNvbm5lY3Rpb25cbiAgICogQHBhcmFtIHtNZXRhQXBpT3B0c30gb3B0aW9ucyBtZXRhYXBpIG9wdGlvbnNcbiAgICogQHBhcmFtIHtNZXRhQXBpV2Vic29ja2V0Q2xpZW50fSB3ZWJzb2NrZXRDbGllbnQgTWV0YUFwaSB3ZWJzb2NrZXQgY2xpZW50XG4gICAqIEBwYXJhbSB7VGVybWluYWxIYXNoTWFuYWdlcn0gdGVybWluYWxIYXNoTWFuYWdlciB0ZXJtaW5hbCBoYXNoIG1hbmFnZXJcbiAgICogQHBhcmFtIHtNZXRhdHJhZGVyQWNjb3VudH0gYWNjb3VudCBNZXRhVHJhZGVyIGFjY291bnQgaWQgdG8gY29ubmVjdCB0b1xuICAgKiBAcGFyYW0ge0hpc3RvcnlTdG9yYWdlfSBoaXN0b3J5U3RvcmFnZSB0ZXJtaW5hbCBoaXN0b3J5IHN0b3JhZ2UuIEJ5IGRlZmF1bHQgYW4gaW5zdGFuY2Ugb2YgTWVtb3J5SGlzdG9yeVN0b3JhZ2VcbiAgICogd2lsbCBiZSB1c2VkLlxuICAgKiBAcGFyYW0ge0Nvbm5lY3Rpb25SZWdpc3RyeX0gY29ubmVjdGlvblJlZ2lzdHJ5IG1ldGF0cmFkZXIgYWNjb3VudCBjb25uZWN0aW9uIHJlZ2lzdHJ5XG4gICAqIEBwYXJhbSB7RGF0ZX0gW2hpc3RvcnlTdGFydFRpbWVdIGhpc3Rvcnkgc3RhcnQgc3luYyB0aW1lXG4gICAqIEBwYXJhbSB7UmVmcmVzaFN1YnNjcmlwdGlvbnNPcHRzfSBbcmVmcmVzaFN1YnNjcmlwdGlvbnNPcHRzXSBzdWJzY3JpcHRpb25zIHJlZnJlc2ggb3B0aW9uc1xuICAgKi9cbiAgY29uc3RydWN0b3Iob3B0aW9ucywgd2Vic29ja2V0Q2xpZW50LCB0ZXJtaW5hbEhhc2hNYW5hZ2VyLCBhY2NvdW50LCBoaXN0b3J5U3RvcmFnZSwgY29ubmVjdGlvblJlZ2lzdHJ5LFxuICAgIGhpc3RvcnlTdGFydFRpbWUsIHJlZnJlc2hTdWJzY3JpcHRpb25zT3B0cykge1xuICAgIHN1cGVyKG9wdGlvbnMsIHdlYnNvY2tldENsaWVudCwgYWNjb3VudCk7XG4gICAgcmVmcmVzaFN1YnNjcmlwdGlvbnNPcHRzID0gcmVmcmVzaFN1YnNjcmlwdGlvbnNPcHRzIHx8IHt9O1xuICAgIGNvbnN0IHZhbGlkYXRvciA9IG5ldyBPcHRpb25zVmFsaWRhdG9yKCk7XG4gICAgdGhpcy5fbWluU3Vic2NyaXB0aW9uUmVmcmVzaEludGVydmFsID0gdmFsaWRhdG9yLnZhbGlkYXRlTm9uWmVybyhyZWZyZXNoU3Vic2NyaXB0aW9uc09wdHMubWluRGVsYXlJblNlY29uZHMsIDEsXG4gICAgICAncmVmcmVzaFN1YnNjcmlwdGlvbnNPcHRzLm1pbkRlbGF5SW5TZWNvbmRzJyk7XG4gICAgdGhpcy5fbWF4U3Vic2NyaXB0aW9uUmVmcmVzaEludGVydmFsID0gdmFsaWRhdG9yLnZhbGlkYXRlTm9uWmVybyhyZWZyZXNoU3Vic2NyaXB0aW9uc09wdHMubWF4RGVsYXlJblNlY29uZHMsIDYwMCxcbiAgICAgICdyZWZyZXNoU3Vic2NyaXB0aW9uc09wdHMubWF4RGVsYXlJblNlY29uZHMnKTtcbiAgICB0aGlzLl9jb25uZWN0aW9uUmVnaXN0cnkgPSBjb25uZWN0aW9uUmVnaXN0cnk7XG4gICAgdGhpcy5faGlzdG9yeVN0YXJ0VGltZSA9IGhpc3RvcnlTdGFydFRpbWU7XG4gICAgdGhpcy5fdGVybWluYWxIYXNoTWFuYWdlciA9IHRlcm1pbmFsSGFzaE1hbmFnZXI7XG4gICAgdGhpcy5fdGVybWluYWxTdGF0ZSA9IG5ldyBUZXJtaW5hbFN0YXRlKGFjY291bnQsIHRlcm1pbmFsSGFzaE1hbmFnZXIsIHRoaXMuX3dlYnNvY2tldENsaWVudCk7XG4gICAgdGhpcy5faGlzdG9yeVN0b3JhZ2UgPSBoaXN0b3J5U3RvcmFnZSB8fCBuZXcgTWVtb3J5SGlzdG9yeVN0b3JhZ2UoKTtcbiAgICB0aGlzLl9oZWFsdGhNb25pdG9yID0gbmV3IENvbm5lY3Rpb25IZWFsdGhNb25pdG9yKHRoaXMpO1xuICAgIHRoaXMuX3dlYnNvY2tldENsaWVudC5hZGRTeW5jaHJvbml6YXRpb25MaXN0ZW5lcihhY2NvdW50LmlkLCB0aGlzKTtcbiAgICB0aGlzLl93ZWJzb2NrZXRDbGllbnQuYWRkU3luY2hyb25pemF0aW9uTGlzdGVuZXIoYWNjb3VudC5pZCwgdGhpcy5fdGVybWluYWxTdGF0ZSk7XG4gICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LmFkZFN5bmNocm9uaXphdGlvbkxpc3RlbmVyKGFjY291bnQuaWQsIHRoaXMuX2hpc3RvcnlTdG9yYWdlKTtcbiAgICB0aGlzLl93ZWJzb2NrZXRDbGllbnQuYWRkU3luY2hyb25pemF0aW9uTGlzdGVuZXIoYWNjb3VudC5pZCwgdGhpcy5faGVhbHRoTW9uaXRvcik7XG4gICAgT2JqZWN0LnZhbHVlcyhhY2NvdW50LmFjY291bnRSZWdpb25zKVxuICAgICAgLmZvckVhY2gocmVwbGljYUlkID0+IHRoaXMuX3dlYnNvY2tldENsaWVudC5hZGRSZWNvbm5lY3RMaXN0ZW5lcih0aGlzLCByZXBsaWNhSWQpKTtcbiAgICB0aGlzLl9zdWJzY3JpcHRpb25zID0ge307XG4gICAgdGhpcy5fc3RhdGVCeUluc3RhbmNlSW5kZXggPSB7fTtcbiAgICB0aGlzLl9yZWZyZXNoTWFya2V0RGF0YVN1YnNjcmlwdGlvblNlc3Npb25zID0ge307XG4gICAgdGhpcy5fcmVmcmVzaE1hcmtldERhdGFTdWJzY3JpcHRpb25UaW1lb3V0cyA9IHt9O1xuICAgIHRoaXMuX29wZW5lZEluc3RhbmNlcyA9IFtdO1xuICAgIHRoaXMuX2xvZ2dlciA9IExvZ2dlck1hbmFnZXIuZ2V0TG9nZ2VyKCdNZXRhQXBpQ29ubmVjdGlvbicpO1xuICB9XG5cbiAgLyoqXG4gICAqIE9wZW5zIHRoZSBjb25uZWN0aW9uLiBDYW4gb25seSBiZSBjYWxsZWQgdGhlIGZpcnN0IHRpbWUsIG5leHQgY2FsbHMgd2lsbCBiZSBpZ25vcmVkLlxuICAgKiBAcGFyYW0ge3N0cmluZ30gaW5zdGFuY2VJZCBjb25uZWN0aW9uIGluc3RhbmNlIGlkXG4gICAqIEByZXR1cm4ge1Byb21pc2V9IHByb21pc2UgcmVzb2x2aW5nIHdoZW4gdGhlIGNvbm5lY3Rpb24gaXMgb3BlbmVkXG4gICAqL1xuICBhc3luYyBjb25uZWN0KGluc3RhbmNlSWQpIHtcbiAgICBpZiAoIXRoaXMuX29wZW5lZEluc3RhbmNlcy5pbmNsdWRlcyhpbnN0YW5jZUlkKSkge1xuICAgICAgdGhpcy5fb3BlbmVkSW5zdGFuY2VzLnB1c2goaW5zdGFuY2VJZCk7XG4gICAgfVxuICAgIGlmICghdGhpcy5fb3BlbmVkKSB7XG4gICAgICB0aGlzLl9sb2dnZXIudHJhY2UoYCR7dGhpcy5fYWNjb3VudC5pZH06IE9wZW5pbmcgY29ubmVjdGlvbmApO1xuICAgICAgdGhpcy5fb3BlbmVkID0gdHJ1ZTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IHRoaXMuaW5pdGlhbGl6ZSgpO1xuICAgICAgICBhd2FpdCB0aGlzLnN1YnNjcmliZSgpO1xuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIGF3YWl0IHRoaXMuY2xvc2UoKTtcbiAgICAgICAgdGhyb3cgZXJyO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDbGVhcnMgdGhlIG9yZGVyIGFuZCB0cmFuc2FjdGlvbiBoaXN0b3J5IG9mIGEgc3BlY2lmaWVkIGFwcGxpY2F0aW9uIGFuZCByZW1vdmVzIGFwcGxpY2F0aW9uXG4gICAqIEByZXR1cm4ge1Byb21pc2V9IHByb21pc2UgcmVzb2x2aW5nIHdoZW4gdGhlIGhpc3RvcnkgaXMgY2xlYXJlZCBhbmQgYXBwbGljYXRpb24gaXMgcmVtb3ZlZFxuICAgKi9cbiAgcmVtb3ZlQXBwbGljYXRpb24oKSB7XG4gICAgdGhpcy5fY2hlY2tJc0Nvbm5lY3Rpb25BY3RpdmUoKTtcbiAgICB0aGlzLl9oaXN0b3J5U3RvcmFnZS5jbGVhcigpO1xuICAgIHJldHVybiB0aGlzLl93ZWJzb2NrZXRDbGllbnQucmVtb3ZlQXBwbGljYXRpb24odGhpcy5fYWNjb3VudC5pZCk7XG4gIH1cblxuICAvKipcbiAgICogUmVxdWVzdHMgdGhlIHRlcm1pbmFsIHRvIHN0YXJ0IHN5bmNocm9uaXphdGlvbiBwcm9jZXNzXG4gICAqIChzZWUgaHR0cHM6Ly9tZXRhYXBpLmNsb3VkL2RvY3MvY2xpZW50L3dlYnNvY2tldC9zeW5jaHJvbml6aW5nL3N5bmNocm9uaXplLylcbiAgICogQHBhcmFtIHtTdHJpbmd9IGluc3RhbmNlSW5kZXggaW5zdGFuY2UgaW5kZXhcbiAgICogQHJldHVybnMge1Byb21pc2V9IHByb21pc2Ugd2hpY2ggcmVzb2x2ZXMgd2hlbiBzeW5jaHJvbml6YXRpb24gc3RhcnRlZFxuICAgKi9cbiAgYXN5bmMgc3luY2hyb25pemUoaW5zdGFuY2VJbmRleCkge1xuICAgIHRoaXMuX2NoZWNrSXNDb25uZWN0aW9uQWN0aXZlKCk7XG4gICAgY29uc3QgcmVnaW9uID0gdGhpcy5nZXRSZWdpb24oaW5zdGFuY2VJbmRleCk7XG4gICAgY29uc3QgaW5zdGFuY2UgPSB0aGlzLmdldEluc3RhbmNlTnVtYmVyKGluc3RhbmNlSW5kZXgpO1xuICAgIGNvbnN0IGhvc3QgPSB0aGlzLmdldEhvc3ROYW1lKGluc3RhbmNlSW5kZXgpO1xuICAgIGxldCBzdGFydGluZ0hpc3RvcnlPcmRlclRpbWUgPSBuZXcgRGF0ZShNYXRoLm1heChcbiAgICAgICh0aGlzLl9oaXN0b3J5U3RhcnRUaW1lIHx8IG5ldyBEYXRlKDApKS5nZXRUaW1lKCksXG4gICAgICAoYXdhaXQgdGhpcy5faGlzdG9yeVN0b3JhZ2UubGFzdEhpc3RvcnlPcmRlclRpbWUoaW5zdGFuY2UpKS5nZXRUaW1lKClcbiAgICApKTtcbiAgICBsZXQgc3RhcnRpbmdEZWFsVGltZSA9IG5ldyBEYXRlKE1hdGgubWF4KFxuICAgICAgKHRoaXMuX2hpc3RvcnlTdGFydFRpbWUgfHwgbmV3IERhdGUoMCkpLmdldFRpbWUoKSxcbiAgICAgIChhd2FpdCB0aGlzLl9oaXN0b3J5U3RvcmFnZS5sYXN0RGVhbFRpbWUoaW5zdGFuY2UpKS5nZXRUaW1lKClcbiAgICApKTtcbiAgICBsZXQgc3luY2hyb25pemF0aW9uSWQgPSByYW5kb21zdHJpbmcuZ2VuZXJhdGUoMzIpO1xuICAgIHRoaXMuX2dldFN0YXRlKGluc3RhbmNlSW5kZXgpLmxhc3RTeW5jaHJvbml6YXRpb25JZCA9IHN5bmNocm9uaXphdGlvbklkO1xuICAgIGNvbnN0IGFjY291bnRJZCA9IHRoaXMuX2FjY291bnQuYWNjb3VudFJlZ2lvbnNbcmVnaW9uXTtcbiAgICB0aGlzLl9sb2dnZXIuZGVidWcoYCR7dGhpcy5fYWNjb3VudC5pZH06JHtpbnN0YW5jZUluZGV4fTogaW5pdGlhdGluZyBzeW5jaHJvbml6YXRpb24gJHtzeW5jaHJvbml6YXRpb25JZH1gKTtcbiAgICByZXR1cm4gdGhpcy5fd2Vic29ja2V0Q2xpZW50LnN5bmNocm9uaXplKGFjY291bnRJZCwgaW5zdGFuY2UsIGhvc3QsIHN5bmNocm9uaXphdGlvbklkLFxuICAgICAgc3RhcnRpbmdIaXN0b3J5T3JkZXJUaW1lLCBzdGFydGluZ0RlYWxUaW1lLCB0aGlzLnRlcm1pbmFsU3RhdGUuZ2V0SGFzaGVzKCkpO1xuICB9XG5cbiAgLyoqXG4gICAqIEluaXRpYWxpemVzIG1ldGEgYXBpIGNvbm5lY3Rpb25cbiAgICogQHJldHVybiB7UHJvbWlzZX0gcHJvbWlzZSB3aGljaCByZXNvbHZlcyB3aGVuIG1ldGEgYXBpIGNvbm5lY3Rpb24gaXMgaW5pdGlhbGl6ZWRcbiAgICovXG4gIGFzeW5jIGluaXRpYWxpemUoKSB7XG4gICAgdGhpcy5fY2hlY2tJc0Nvbm5lY3Rpb25BY3RpdmUoKTtcbiAgICBhd2FpdCB0aGlzLl9oaXN0b3J5U3RvcmFnZS5pbml0aWFsaXplKHRoaXMuX2FjY291bnQuaWQsIHRoaXMuX2Nvbm5lY3Rpb25SZWdpc3RyeS5hcHBsaWNhdGlvbik7XG4gICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LmFkZEFjY291bnRDYWNoZSh0aGlzLl9hY2NvdW50LmlkLCB0aGlzLl9hY2NvdW50LmFjY291bnRSZWdpb25zKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbml0aWF0ZXMgc3Vic2NyaXB0aW9uIHRvIE1ldGFUcmFkZXIgdGVybWluYWxcbiAgICogQHJldHVybnMge1Byb21pc2V9IHByb21pc2Ugd2hpY2ggcmVzb2x2ZXMgd2hlbiBzdWJzY3JpcHRpb24gaXMgaW5pdGlhdGVkXG4gICAqL1xuICBhc3luYyBzdWJzY3JpYmUoKSB7XG4gICAgdGhpcy5fY2hlY2tJc0Nvbm5lY3Rpb25BY3RpdmUoKTtcbiAgICBjb25zdCBhY2NvdW50UmVnaW9ucyA9IHRoaXMuX2FjY291bnQuYWNjb3VudFJlZ2lvbnM7XG4gICAgT2JqZWN0LmVudHJpZXMoYWNjb3VudFJlZ2lvbnMpLmZvckVhY2goKFtyZWdpb24sIHJlcGxpY2FJZF0pID0+IHtcbiAgICAgIGlmICghdGhpcy5fb3B0aW9ucy5yZWdpb24gfHwgdGhpcy5fb3B0aW9ucy5yZWdpb24gPT09IHJlZ2lvbikge1xuICAgICAgICB0aGlzLl93ZWJzb2NrZXRDbGllbnQuZW5zdXJlU3Vic2NyaWJlKHJlcGxpY2FJZCwgMCk7XG4gICAgICAgIHRoaXMuX3dlYnNvY2tldENsaWVudC5lbnN1cmVTdWJzY3JpYmUocmVwbGljYUlkLCAxKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdWJzY3JpYmVzIG9uIG1hcmtldCBkYXRhIG9mIHNwZWNpZmllZCBzeW1ib2wgKHNlZVxuICAgKiBodHRwczovL21ldGFhcGkuY2xvdWQvZG9jcy9jbGllbnQvd2Vic29ja2V0L21hcmtldERhdGFTdHJlYW1pbmcvc3Vic2NyaWJlVG9NYXJrZXREYXRhLykuXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBzeW1ib2wgc3ltYm9sIChlLmcuIGN1cnJlbmN5IHBhaXIgb3IgYW4gaW5kZXgpXG4gICAqIEBwYXJhbSB7QXJyYXk8TWFya2V0RGF0YVN1YnNjcmlwdGlvbj59IHN1YnNjcmlwdGlvbnMgYXJyYXkgb2YgbWFya2V0IGRhdGEgc3Vic2NyaXB0aW9uIHRvIGNyZWF0ZSBvciB1cGRhdGUuIFBsZWFzZVxuICAgKiBub3RlIHRoYXQgdGhpcyBmZWF0dXJlIGlzIG5vdCBmdWxseSBpbXBsZW1lbnRlZCBvbiBzZXJ2ZXItc2lkZSB5ZXRcbiAgICogQHBhcmFtIHtudW1iZXJ9IFt0aW1lb3V0SW5TZWNvbmRzXSB0aW1lb3V0IHRvIHdhaXQgZm9yIHByaWNlcyBpbiBzZWNvbmRzLCBkZWZhdWx0IGlzIDMwXG4gICAqIEBwYXJhbSB7Ym9vbGVhbn0gW3dhaXRGb3JRdW90ZV0gaWYgc2V0IHRvIGZhbHNlLCB0aGUgbWV0aG9kIHdpbGwgcmVzb2x2ZSB3aXRob3V0IHdhaXRpbmcgZm9yIHRoZSBmaXJzdCBxdW90ZSB0b1xuICAgKiBhcnJpdmUuIERlZmF1bHQgaXMgdG8gd2FpdCBmb3IgcXVvdGUgaWYgcXVvdGVzIHN1YnNjcmlwdGlvbiBpcyByZXF1ZXN0ZWQuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBwcm9taXNlIHdoaWNoIHJlc29sdmVzIHdoZW4gc3Vic2NyaXB0aW9uIHJlcXVlc3Qgd2FzIHByb2Nlc3NlZFxuICAgKi9cbiAgYXN5bmMgc3Vic2NyaWJlVG9NYXJrZXREYXRhKHN5bWJvbCwgc3Vic2NyaXB0aW9ucywgdGltZW91dEluU2Vjb25kcywgd2FpdEZvclF1b3RlID0gdHJ1ZSkge1xuICAgIHRoaXMuX2NoZWNrSXNDb25uZWN0aW9uQWN0aXZlKCk7XG4gICAgaWYgKCF0aGlzLl90ZXJtaW5hbFN0YXRlLnNwZWNpZmljYXRpb24oc3ltYm9sKSkge1xuICAgICAgdGhyb3cgbmV3IFZhbGlkYXRpb25FcnJvcihgJHt0aGlzLl9hY2NvdW50LmlkfTogQ2Fubm90IHN1YnNjcmliZSB0byBtYXJrZXQgZGF0YSBmb3Igc3ltYm9sICR7c3ltYm9sfSBiZWNhdXNlIGAgK1xuICAgICAgICAnc3ltYm9sIGRvZXMgbm90IGV4aXN0Jyk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHN1YnNjcmlwdGlvbnMgPSBzdWJzY3JpcHRpb25zIHx8IFt7dHlwZTogJ3F1b3Rlcyd9XTtcbiAgICAgIGlmICh0aGlzLl9zdWJzY3JpcHRpb25zW3N5bWJvbF0pIHtcbiAgICAgICAgY29uc3QgcHJldlN1YnNjcmlwdGlvbnMgPSB0aGlzLl9zdWJzY3JpcHRpb25zW3N5bWJvbF0uc3Vic2NyaXB0aW9ucztcbiAgICAgICAgc3Vic2NyaXB0aW9ucy5mb3JFYWNoKHN1YnNjcmlwdGlvbiA9PiB7XG4gICAgICAgICAgY29uc3QgaW5kZXggPSBzdWJzY3JpcHRpb24udHlwZSA9PT0gJ2NhbmRsZXMnID8gXG4gICAgICAgICAgICBwcmV2U3Vic2NyaXB0aW9ucy5maW5kSW5kZXgoaXRlbSA9PiBpdGVtLnR5cGUgPT09IHN1YnNjcmlwdGlvbi50eXBlICYmIFxuICAgICAgICAgICAgICBpdGVtLnRpbWVmcmFtZSA9PT0gc3Vic2NyaXB0aW9uLnRpbWVmcmFtZSkgOlxuICAgICAgICAgICAgcHJldlN1YnNjcmlwdGlvbnMuZmluZEluZGV4KGl0ZW0gPT4gaXRlbS50eXBlID09PSBzdWJzY3JpcHRpb24udHlwZSk7XG4gICAgICAgICAgaWYgKGluZGV4ID09PSAtMSkge1xuICAgICAgICAgICAgcHJldlN1YnNjcmlwdGlvbnMucHVzaChzdWJzY3JpcHRpb24pO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBwcmV2U3Vic2NyaXB0aW9uc1tpbmRleF0gPSBzdWJzY3JpcHRpb247XG4gICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHRoaXMuX3N1YnNjcmlwdGlvbnNbc3ltYm9sXSA9IHtzdWJzY3JpcHRpb25zfTtcbiAgICAgIH1cbiAgICAgIGF3YWl0IHRoaXMuX3dlYnNvY2tldENsaWVudC5zdWJzY3JpYmVUb01hcmtldERhdGEodGhpcy5fYWNjb3VudC5pZCwgc3ltYm9sLCBzdWJzY3JpcHRpb25zLFxuICAgICAgICB0aGlzLl9hY2NvdW50LnJlbGlhYmlsaXR5KTtcbiAgICAgIGlmICh3YWl0Rm9yUXVvdGUgIT09IGZhbHNlICYmIHN1YnNjcmlwdGlvbnMuZmluZChzID0+IHMudHlwZSA9PT0gJ3F1b3RlcycpKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnRlcm1pbmFsU3RhdGUud2FpdEZvclByaWNlKHN5bWJvbCwgdGltZW91dEluU2Vjb25kcyk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFVuc3Vic2NyaWJlcyBmcm9tIG1hcmtldCBkYXRhIG9mIHNwZWNpZmllZCBzeW1ib2wgKHNlZVxuICAgKiBodHRwczovL21ldGFhcGkuY2xvdWQvZG9jcy9jbGllbnQvd2Vic29ja2V0L21hcmtldERhdGFTdHJlYW1pbmcvdW5zdWJzY3JpYmVGcm9tTWFya2V0RGF0YS8pLlxuICAgKiBAcGFyYW0ge1N0cmluZ30gc3ltYm9sIHN5bWJvbCAoZS5nLiBjdXJyZW5jeSBwYWlyIG9yIGFuIGluZGV4KVxuICAgKiBAcGFyYW0ge0FycmF5PE1hcmtldERhdGFVbnN1YnNjcmlwdGlvbj59IHVuc3Vic2NyaXB0aW9ucyBhcnJheSBvZiBzdWJzY3JpcHRpb25zIHRvIGNhbmNlbFxuICAgKiBAcmV0dXJucyB7UHJvbWlzZX0gcHJvbWlzZSB3aGljaCByZXNvbHZlcyB3aGVuIHVuc3Vic2NyaXB0aW9uIHJlcXVlc3Qgd2FzIHByb2Nlc3NlZFxuICAgKi9cbiAgdW5zdWJzY3JpYmVGcm9tTWFya2V0RGF0YShzeW1ib2wsIHVuc3Vic2NyaXB0aW9ucykge1xuICAgIHRoaXMuX2NoZWNrSXNDb25uZWN0aW9uQWN0aXZlKCk7XG4gICAgaWYgKCF1bnN1YnNjcmlwdGlvbnMpIHtcbiAgICAgIGRlbGV0ZSB0aGlzLl9zdWJzY3JpcHRpb25zW3N5bWJvbF07XG4gICAgfSBlbHNlIGlmICh0aGlzLl9zdWJzY3JpcHRpb25zW3N5bWJvbF0pIHtcbiAgICAgIHRoaXMuX3N1YnNjcmlwdGlvbnNbc3ltYm9sXS5zdWJzY3JpcHRpb25zID0gdGhpcy5fc3Vic2NyaXB0aW9uc1tzeW1ib2xdLnN1YnNjcmlwdGlvbnMuZmlsdGVyKHN1YnNjcmlwdGlvbiA9PiB7XG4gICAgICAgIHJldHVybiAhdW5zdWJzY3JpcHRpb25zLmZpbmQodW5zdWJzY3JpcHRpb24gPT4gc3Vic2NyaXB0aW9uLnR5cGUgPT09IHVuc3Vic2NyaXB0aW9uLnR5cGUgJiZcbiAgICAgICAgICAoIXVuc3Vic2NyaXB0aW9uLnRpbWVmcmFtZSB8fCBzdWJzY3JpcHRpb24udGltZWZyYW1lID09PSB1bnN1YnNjcmlwdGlvbi50aW1lZnJhbWUpKTtcbiAgICAgIH0pO1xuICAgICAgaWYgKCF0aGlzLl9zdWJzY3JpcHRpb25zW3N5bWJvbF0uc3Vic2NyaXB0aW9ucy5sZW5ndGgpIHtcbiAgICAgICAgZGVsZXRlIHRoaXMuX3N1YnNjcmlwdGlvbnNbc3ltYm9sXTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIHRoaXMuX3dlYnNvY2tldENsaWVudC51bnN1YnNjcmliZUZyb21NYXJrZXREYXRhKHRoaXMuX2FjY291bnQuaWQsIHN5bWJvbCwgdW5zdWJzY3JpcHRpb25zLFxuICAgICAgdGhpcy5fYWNjb3VudC5yZWxpYWJpbGl0eSk7XG4gIH1cblxuICAvKipcbiAgICogSW52b2tlZCB3aGVuIHN1YnNjcmlwdGlvbiBkb3duZ3JhZGUgaGFzIG9jY3VycmVkXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBpbnN0YW5jZUluZGV4IGluZGV4IG9mIGFuIGFjY291bnQgaW5zdGFuY2UgY29ubmVjdGVkXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBzeW1ib2wgc3ltYm9sIHRvIHVwZGF0ZSBzdWJzY3JpcHRpb25zIGZvclxuICAgKiBAcGFyYW0ge0FycmF5PE1hcmtldERhdGFTdWJzY3JpcHRpb24+fSB1cGRhdGVzIGFycmF5IG9mIG1hcmtldCBkYXRhIHN1YnNjcmlwdGlvbiB0byB1cGRhdGVcbiAgICogQHBhcmFtIHtBcnJheTxNYXJrZXREYXRhVW5zdWJzY3JpcHRpb24+fSB1bnN1YnNjcmlwdGlvbnMgYXJyYXkgb2Ygc3Vic2NyaXB0aW9ucyB0byBjYW5jZWxcbiAgICogQHJldHVybiB7UHJvbWlzZX0gcHJvbWlzZSB3aGljaCByZXNvbHZlcyB3aGVuIHRoZSBhc3luY2hyb25vdXMgZXZlbnQgaXMgcHJvY2Vzc2VkXG4gICAqL1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgY29tcGxleGl0eVxuICBhc3luYyBvblN1YnNjcmlwdGlvbkRvd25ncmFkZWQoaW5zdGFuY2VJbmRleCwgc3ltYm9sLCB1cGRhdGVzLCB1bnN1YnNjcmlwdGlvbnMpIHtcbiAgICBpZiAodW5zdWJzY3JpcHRpb25zPy5sZW5ndGgpIHtcbiAgICAgIHRoaXMudW5zdWJzY3JpYmVGcm9tTWFya2V0RGF0YShzeW1ib2wsIHVuc3Vic2NyaXB0aW9ucykuY2F0Y2goZXJyID0+IHtcbiAgICAgICAgbGV0IG1ldGhvZCA9IGVyci5uYW1lICE9PSAnVmFsaWRhdGlvbkVycm9yJyA/ICdlcnJvcicgOiAndHJhY2UnO1xuICAgICAgICB0aGlzLl9sb2dnZXJbbWV0aG9kXShgJHt0aGlzLl9hY2NvdW50LmlkfTogZmFpbGVkIGRvIHVuc3Vic2NyaWJlIGZyb20gbWFya2V0IGRhdGEgb24gc3Vic2NyaXB0aW9uIGRvd25ncmFkZWRgLFxuICAgICAgICAgIGVycik7XG4gICAgICB9KTtcbiAgICB9XG4gICAgaWYgKHVwZGF0ZXM/Lmxlbmd0aCkge1xuICAgICAgdGhpcy5zdWJzY3JpYmVUb01hcmtldERhdGEoc3ltYm9sLCB1cGRhdGVzKS5jYXRjaChlcnIgPT4ge1xuICAgICAgICB0aGlzLl9sb2dnZXIuZXJyb3IoYCR7dGhpcy5fYWNjb3VudC5pZH06IGZhaWxlZCBkbyBzdWJzY3JpYmUgZnJvbSBtYXJrZXQgZGF0YSBvbiBzdWJzY3JpcHRpb24gZG93bmdyYWRlZGAsIGVycik7XG4gICAgICB9KTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBsaXN0IG9mIHRoZSBzeW1ib2xzIGNvbm5lY3Rpb24gaXMgc3Vic2NyaWJlZCB0b1xuICAgKiBAcmV0dXJucyB7QXJyYXk8U3RyaW5nPn0gbGlzdCBvZiB0aGUgc3ltYm9scyBjb25uZWN0aW9uIGlzIHN1YnNjcmliZWQgdG9cbiAgICovXG4gIGdldCBzdWJzY3JpYmVkU3ltYm9scygpIHtcbiAgICByZXR1cm4gT2JqZWN0LmtleXModGhpcy5fc3Vic2NyaXB0aW9ucyk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBzdWJzY3JpcHRpb25zIGZvciBhIHN5bWJvbFxuICAgKiBAcGFyYW0ge3N0cmluZ30gc3ltYm9sIHN5bWJvbCB0byByZXRyaWV2ZSBzdWJzY3JpcHRpb25zIGZvclxuICAgKiBAcmV0dXJucyB7QXJyYXk8TWFya2V0RGF0YVN1YnNjcmlwdGlvbj59IGxpc3Qgb2YgbWFya2V0IGRhdGEgc3Vic2NyaXB0aW9ucyBmb3IgdGhlIHN5bWJvbFxuICAgKi9cbiAgc3Vic2NyaXB0aW9ucyhzeW1ib2wpIHtcbiAgICB0aGlzLl9jaGVja0lzQ29ubmVjdGlvbkFjdGl2ZSgpO1xuICAgIHJldHVybiAodGhpcy5fc3Vic2NyaXB0aW9uc1tzeW1ib2xdIHx8IHt9KS5zdWJzY3JpcHRpb25zO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgbG9jYWwgY29weSBvZiB0ZXJtaW5hbCBzdGF0ZVxuICAgKiBAcmV0dXJucyB7VGVybWluYWxTdGF0ZX0gbG9jYWwgY29weSBvZiB0ZXJtaW5hbCBzdGF0ZVxuICAgKi9cbiAgZ2V0IHRlcm1pbmFsU3RhdGUoKSB7XG4gICAgcmV0dXJuIHRoaXMuX3Rlcm1pbmFsU3RhdGU7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBsb2NhbCBoaXN0b3J5IHN0b3JhZ2VcbiAgICogQHJldHVybnMge0hpc3RvcnlTdG9yYWdlfSBsb2NhbCBoaXN0b3J5IHN0b3JhZ2VcbiAgICovXG4gIGdldCBoaXN0b3J5U3RvcmFnZSgpIHtcbiAgICByZXR1cm4gdGhpcy5faGlzdG9y