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)

702 lines (701 loc) 108 kB
'use strict'; function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _async_to_generator(fn) { return function() { var self = this, args = arguments; return new Promise(function(resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function _define_property(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } import TerminalState from './terminalState'; import MemoryHistoryStorage from './memoryHistoryStorage'; import TimeoutError from '../clients/timeoutError'; import randomstring from 'randomstring'; import ConnectionHealthMonitor from './connectionHealthMonitor'; import { ValidationError } from '../clients/errorHandler'; import OptionsValidator from '../clients/optionsValidator'; import LoggerManager from '../logger'; import MetaApiConnection from './metaApiConnection'; let StreamingMetaApiConnection = class StreamingMetaApiConnection extends MetaApiConnection { /** * 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 */ connect(instanceId) { var _this = this; return _async_to_generator(function*() { if (!_this._openedInstances.includes(instanceId)) { _this._openedInstances.push(instanceId); } if (!_this._opened) { _this._logger.trace(`${_this._account.id}: Opening connection`); _this._opened = true; try { _this._healthMonitor.start(); yield _this.initialize(); yield _this.subscribe(); } catch (err) { yield _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 */ synchronize(instanceIndex) { var _this = this; return _async_to_generator(function*() { _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(), (yield _this._historyStorage.lastHistoryOrderTime(instance)).getTime())); let startingDealTime = new Date(Math.max((_this._historyStartTime || new Date(0)).getTime(), (yield _this._historyStorage.lastDealTime(instance)).getTime())); let synchronizationId = randomstring.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 */ initialize() { var _this = this; return _async_to_generator(function*() { _this._checkIsConnectionActive(); yield _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 */ subscribe() { var _this = this; return _async_to_generator(function*() { _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 */ subscribeToMarketData(symbol, subscriptions, timeoutInSeconds, waitForQuote = true) { var _this = this; return _async_to_generator(function*() { _this._checkIsConnectionActive(); if (!_this._terminalState.specification(symbol)) { throw new 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 }; } yield _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 onSubscriptionDowngraded(instanceIndex, symbol, updates, unsubscriptions) { var _this = this; return _async_to_generator(function*() { 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 */ onConnected(instanceIndex, replicas) { var _this = this; return _async_to_generator(function*() { let key = randomstring.generate(32); let state = _this._getState(instanceIndex); const region = _this.getRegion(instanceIndex); _this.cancelRefresh(region); yield _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 */ onDisconnected(instanceIndex) { var _this = this; return _async_to_generator(function*() { 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) { var _this = this; return _async_to_generator(function*() { _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) { var _this = this; return _async_to_generator(function*() { _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) { var _this = this; return _async_to_generator(function*() { _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 */ onDealsSynchronized(instanceIndex, synchronizationId) { var _this = this; return _async_to_generator(function*() { 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 */ onHistoryOrdersSynchronized(instanceIndex, synchronizationId) { var _this = this; return _async_to_generator(function*() { 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 */ onReconnected(region, instanceNumber) { var _this = this; return _async_to_generator(function*() { 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 */ onStreamClosed(instanceIndex) { var _this = this; return _async_to_generator(function*() { 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 */ onSynchronizationStarted(instanceIndex, specificationsHash, positionsHash, ordersHash, synchronizationId) { var _this = this; return _async_to_generator(function*() { _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.generate(32); _this._refreshMarketDataSubscriptionSessions[instance] = sessionId; clearTimeout(_this._refreshMarketDataSubscriptionTimeouts[instance]); delete _this._refreshMarketDataSubscriptionTimeouts[instance]; yield _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) { var _this = this; return _async_to_generator(function*() { 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 */ isSynchronized(instanceIndex, synchronizationId) { var _this = this; return _async_to_generator(function*() { 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 waitSynchronized(opts) { var _this = this; return _async_to_generator(function*() { _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 = yield _this.isSynchronized(instanceIndex, synchronizationId)) && startTime + timeoutInSeconds * 1000 > Date.now()){ yield new Promise((res)=>setTimeout(res, intervalInMilliseconds)); } let state; if (instanceIndex === undefined) { for (let s of Object.values(_this._stateByInstanceIndex)){ if (yield _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('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]; yield _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 */ close(instanceId) { var _this = this; return _async_to_generator(function*() { if (_this._opened) { _this._openedInstances = _this._openedInstances.filter((id)=>id !== instanceId); if (!_this._openedInstances.length && !_this._closed) { _this._logger.debug(`${_this._account.id}: Closing connection`); Object.values(_this._stateByInstanceIndex).forEach((state)=>clearTimeout(state.synchronizationTimeout)); _this._stateByInstanceIndex = {}; yield _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; } _refreshMarketDataSubscriptions(accountId, instanceNumber, session) { var _this = this; return _async_to_generator(function*() { 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); }); yield _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; } _ensureSynchronized(instanceIndex, key) { var _this = this; return _async_to_generator(function*() { let state = _this._getState(instanceIndex); if (state && state.shouldSynchronize && !_this._closed) { try { const synchronizationResult = yield _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); _define_property(this, "_minSubscriptionRefreshInterval", void 0); _define_property(this, "_maxSubscriptionRefreshInterval", void 0); _define_property(this, "_historyStartTime", void 0); _define_property(this, "_terminalHashManager", void 0); _define_property(this, "_terminalState", void 0); _define_property(this, "_historyStorage", void 0); _define_property(this, "_healthMonitor", void 0); _define_property(this, "_subscriptions", void 0); _define_property(this, "_refreshMarketDataSubscriptionSessions", void 0); _define_property(this, "_refreshMarketDataSubscriptionTimeouts", void 0); _define_property(this, "_openedInstances", void 0); refreshSubscriptionsOpts = refreshSubscriptionsOpts || {}; const validator = new OptionsValidator(); 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(account, terminalHashManager, this._websocketClient); this._historyStorage = historyStorage || new MemoryHistoryStorage(); this._healthMonitor = new ConnectionHealthMonitor(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 = LoggerManager.getLogger('MetaApiConnection'); } }; /** * Exposes MetaApi MetaTrader streaming API connection to consumers */ export { StreamingMetaApiConnection as default }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCBUZXJtaW5hbFN0YXRlIGZyb20gJy4vdGVybWluYWxTdGF0ZSc7XG5pbXBvcnQgTWVtb3J5SGlzdG9yeVN0b3JhZ2UgZnJvbSAnLi9tZW1vcnlIaXN0b3J5U3RvcmFnZSc7XG5pbXBvcnQgVGltZW91dEVycm9yIGZyb20gJy4uL2NsaWVudHMvdGltZW91dEVycm9yJztcbmltcG9ydCByYW5kb21zdHJpbmcgZnJvbSAncmFuZG9tc3RyaW5nJztcbmltcG9ydCBDb25uZWN0aW9uSGVhbHRoTW9uaXRvciBmcm9tICcuL2Nvbm5lY3Rpb25IZWFsdGhNb25pdG9yJztcbmltcG9ydCB7VmFsaWRhdGlvbkVycm9yfSBmcm9tICcuLi9jbGllbnRzL2Vycm9ySGFuZGxlcic7XG5pbXBvcnQgT3B0aW9uc1ZhbGlkYXRvciBmcm9tICcuLi9jbGllbnRzL29wdGlvbnNWYWxpZGF0b3InO1xuaW1wb3J0IExvZ2dlck1hbmFnZXIgZnJvbSAnLi4vbG9nZ2VyJztcbmltcG9ydCBNZXRhQXBpQ29ubmVjdGlvbiBmcm9tICcuL21ldGFBcGlDb25uZWN0aW9uJztcblxuLyoqXG4gKiBFeHBvc2VzIE1ldGFBcGkgTWV0YVRyYWRlciBzdHJlYW1pbmcgQVBJIGNvbm5lY3Rpb24gdG8gY29uc3VtZXJzXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFN0cmVhbWluZ01ldGFBcGlDb25uZWN0aW9uIGV4dGVuZHMgTWV0YUFwaUNvbm5lY3Rpb24ge1xuICBcbiAgcHJpdmF0ZSBfbWluU3Vic2NyaXB0aW9uUmVmcmVzaEludGVydmFsOiBhbnk7XG4gIHByaXZhdGUgX21heFN1YnNjcmlwdGlvblJlZnJlc2hJbnRlcnZhbDogYW55O1xuICBwcml2YXRlIF9oaXN0b3J5U3RhcnRUaW1lOiBhbnk7XG4gIHByaXZhdGUgX3Rlcm1pbmFsSGFzaE1hbmFnZXI6IGFueTtcbiAgcHJpdmF0ZSBfdGVybWluYWxTdGF0ZTogVGVybWluYWxTdGF0ZTtcbiAgcHJpdmF0ZSBfaGlzdG9yeVN0b3JhZ2U6IGFueTtcbiAgcHJpdmF0ZSBfaGVhbHRoTW9uaXRvcjogQ29ubmVjdGlvbkhlYWx0aE1vbml0b3I7XG4gIHByaXZhdGUgX3N1YnNjcmlwdGlvbnM6IHt9O1xuICBwcml2YXRlIF9yZWZyZXNoTWFya2V0RGF0YVN1YnNjcmlwdGlvblNlc3Npb25zOiB7fTtcbiAgcHJpdmF0ZSBfcmVmcmVzaE1hcmtldERhdGFTdWJzY3JpcHRpb25UaW1lb3V0czoge307XG4gIHByaXZhdGUgX29wZW5lZEluc3RhbmNlczogYW55W107XG5cbiAgLyoqXG4gICAqIENvbnN0cnVjdHMgTWV0YUFwaSBNZXRhVHJhZGVyIHN0cmVhbWluZyBBcGkgY29ubmVjdGlvblxuICAgKiBAcGFyYW0ge01ldGFBcGlPcHRzfSBvcHRpb25zIG1ldGFhcGkgb3B0aW9uc1xuICAgKiBAcGFyYW0ge01ldGFBcGlXZWJzb2NrZXRDbGllbnR9IHdlYnNvY2tldENsaWVudCBNZXRhQXBpIHdlYnNvY2tldCBjbGllbnRcbiAgICogQHBhcmFtIHtUZXJtaW5hbEhhc2hNYW5hZ2VyfSB0ZXJtaW5hbEhhc2hNYW5hZ2VyIHRlcm1pbmFsIGhhc2ggbWFuYWdlclxuICAgKiBAcGFyYW0ge01ldGF0cmFkZXJBY2NvdW50fSBhY2NvdW50IE1ldGFUcmFkZXIgYWNjb3VudCBpZCB0byBjb25uZWN0IHRvXG4gICAqIEBwYXJhbSB7SGlzdG9yeVN0b3JhZ2V9IGhpc3RvcnlTdG9yYWdlIHRlcm1pbmFsIGhpc3Rvcnkgc3RvcmFnZS4gQnkgZGVmYXVsdCBhbiBpbnN0YW5jZSBvZiBNZW1vcnlIaXN0b3J5U3RvcmFnZVxuICAgKiB3aWxsIGJlIHVzZWQuXG4gICAqIEBwYXJhbSB7Q29ubmVjdGlvblJlZ2lzdHJ5fSBjb25uZWN0aW9uUmVnaXN0cnkgbWV0YXRyYWRlciBhY2NvdW50IGNvbm5lY3Rpb24gcmVnaXN0cnlcbiAgICogQHBhcmFtIHtEYXRlfSBbaGlzdG9yeVN0YXJ0VGltZV0gaGlzdG9yeSBzdGFydCBzeW5jIHRpbWVcbiAgICogQHBhcmFtIHtSZWZyZXNoU3Vic2NyaXB0aW9uc09wdHN9IFtyZWZyZXNoU3Vic2NyaXB0aW9uc09wdHNdIHN1YnNjcmlwdGlvbnMgcmVmcmVzaCBvcHRpb25zXG4gICAqL1xuICBjb25zdHJ1Y3RvcihvcHRpb25zLCB3ZWJzb2NrZXRDbGllbnQsIHRlcm1pbmFsSGFzaE1hbmFnZXIsIGFjY291bnQsIGhpc3RvcnlTdG9yYWdlLCBjb25uZWN0aW9uUmVnaXN0cnksXG4gICAgaGlzdG9yeVN0YXJ0VGltZSwgcmVmcmVzaFN1YnNjcmlwdGlvbnNPcHRzKSB7XG4gICAgc3VwZXIob3B0aW9ucywgd2Vic29ja2V0Q2xpZW50LCBhY2NvdW50KTtcbiAgICByZWZyZXNoU3Vic2NyaXB0aW9uc09wdHMgPSByZWZyZXNoU3Vic2NyaXB0aW9uc09wdHMgfHwge307XG4gICAgY29uc3QgdmFsaWRhdG9yID0gbmV3IE9wdGlvbnNWYWxpZGF0b3IoKTtcbiAgICB0aGlzLl9taW5TdWJzY3JpcHRpb25SZWZyZXNoSW50ZXJ2YWwgPSB2YWxpZGF0b3IudmFsaWRhdGVOb25aZXJvKHJlZnJlc2hTdWJzY3JpcHRpb25zT3B0cy5taW5EZWxheUluU2Vjb25kcywgMSxcbiAgICAgICdyZWZyZXNoU3Vic2NyaXB0aW9uc09wdHMubWluRGVsYXlJblNlY29uZHMnKTtcbiAgICB0aGlzLl9tYXhTdWJzY3JpcHRpb25SZWZyZXNoSW50ZXJ2YWwgPSB2YWxpZGF0b3IudmFsaWRhdGVOb25aZXJvKHJlZnJlc2hTdWJzY3JpcHRpb25zT3B0cy5tYXhEZWxheUluU2Vjb25kcywgNjAwLFxuICAgICAgJ3JlZnJlc2hTdWJzY3JpcHRpb25zT3B0cy5tYXhEZWxheUluU2Vjb25kcycpO1xuICAgIHRoaXMuX2Nvbm5lY3Rpb25SZWdpc3RyeSA9IGNvbm5lY3Rpb25SZWdpc3RyeTtcbiAgICB0aGlzLl9oaXN0b3J5U3RhcnRUaW1lID0gaGlzdG9yeVN0YXJ0VGltZTtcbiAgICB0aGlzLl90ZXJtaW5hbEhhc2hNYW5hZ2VyID0gdGVybWluYWxIYXNoTWFuYWdlcjtcbiAgICB0aGlzLl90ZXJtaW5hbFN0YXRlID0gbmV3IFRlcm1pbmFsU3RhdGUoYWNjb3VudCwgdGVybWluYWxIYXNoTWFuYWdlciwgdGhpcy5fd2Vic29ja2V0Q2xpZW50KTtcbiAgICB0aGlzLl9oaXN0b3J5U3RvcmFnZSA9IGhpc3RvcnlTdG9yYWdlIHx8IG5ldyBNZW1vcnlIaXN0b3J5U3RvcmFnZSgpO1xuICAgIHRoaXMuX2hlYWx0aE1vbml0b3IgPSBuZXcgQ29ubmVjdGlvbkhlYWx0aE1vbml0b3IodGhpcyk7XG4gICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LmFkZFN5bmNocm9uaXphdGlvbkxpc3RlbmVyKGFjY291bnQuaWQsIHRoaXMpO1xuICAgIHRoaXMuX3dlYnNvY2tldENsaWVudC5hZGRTeW5jaHJvbml6YXRpb25MaXN0ZW5lcihhY2NvdW50LmlkLCB0aGlzLl90ZXJtaW5hbFN0YXRlKTtcbiAgICB0aGlzLl93ZWJzb2NrZXRDbGllbnQuYWRkU3luY2hyb25pemF0aW9uTGlzdGVuZXIoYWNjb3VudC5pZCwgdGhpcy5faGlzdG9yeVN0b3JhZ2UpO1xuICAgIHRoaXMuX3dlYnNvY2tldENsaWVudC5hZGRTeW5jaHJvbml6YXRpb25MaXN0ZW5lcihhY2NvdW50LmlkLCB0aGlzLl9oZWFsdGhNb25pdG9yKTtcbiAgICBPYmplY3QudmFsdWVzKGFjY291bnQuYWNjb3VudFJlZ2lvbnMpXG4gICAgICAuZm9yRWFjaChyZXBsaWNhSWQgPT4gdGhpcy5fd2Vic29ja2V0Q2xpZW50LmFkZFJlY29ubmVjdExpc3RlbmVyKHRoaXMsIHJlcGxpY2FJZCkpO1xuICAgIHRoaXMuX3N1YnNjcmlwdGlvbnMgPSB7fTtcbiAgICB0aGlzLl9zdGF0ZUJ5SW5zdGFuY2VJbmRleCA9IHt9O1xuICAgIHRoaXMuX3JlZnJlc2hNYXJrZXREYXRhU3Vic2NyaXB0aW9uU2Vzc2lvbnMgPSB7fTtcbiAgICB0aGlzLl9yZWZyZXNoTWFya2V0RGF0YVN1YnNjcmlwdGlvblRpbWVvdXRzID0ge307XG4gICAgdGhpcy5fb3BlbmVkSW5zdGFuY2VzID0gW107XG4gICAgdGhpcy5fbG9nZ2VyID0gTG9nZ2VyTWFuYWdlci5nZXRMb2dnZXIoJ01ldGFBcGlDb25uZWN0aW9uJyk7XG4gIH1cblxuICAvKipcbiAgICogT3BlbnMgdGhlIGNvbm5lY3Rpb24uIENhbiBvbmx5IGJlIGNhbGxlZCB0aGUgZmlyc3QgdGltZSwgbmV4dCBjYWxscyB3aWxsIGJlIGlnbm9yZWQuXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBpbnN0YW5jZUlkIGNvbm5lY3Rpb24gaW5zdGFuY2UgaWRcbiAgICogQHJldHVybiB7UHJvbWlzZX0gcHJvbWlzZSByZXNvbHZpbmcgd2hlbiB0aGUgY29ubmVjdGlvbiBpcyBvcGVuZWRcbiAgICovXG4gIGFzeW5jIGNvbm5lY3QoaW5zdGFuY2VJZCkge1xuICAgIGlmICghdGhpcy5fb3BlbmVkSW5zdGFuY2VzLmluY2x1ZGVzKGluc3RhbmNlSWQpKSB7XG4gICAgICB0aGlzLl9vcGVuZWRJbnN0YW5jZXMucHVzaChpbnN0YW5jZUlkKTtcbiAgICB9XG4gICAgaWYgKCF0aGlzLl9vcGVuZWQpIHtcbiAgICAgIHRoaXMuX2xvZ2dlci50cmFjZShgJHt0aGlzLl9hY2NvdW50LmlkfTogT3BlbmluZyBjb25uZWN0aW9uYCk7XG4gICAgICB0aGlzLl9vcGVuZWQgPSB0cnVlO1xuICAgICAgdHJ5IHtcbiAgICAgICAgdGhpcy5faGVhbHRoTW9uaXRvci5zdGFydCgpO1xuICAgICAgICBhd2FpdCB0aGlzLmluaXRpYWxpemUoKTtcbiAgICAgICAgYXdhaXQgdGhpcy5zdWJzY3JpYmUoKTtcbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBhd2FpdCB0aGlzLmNsb3NlKCk7XG4gICAgICAgIHRocm93IGVycjtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogQ2xlYXJzIHRoZSBvcmRlciBhbmQgdHJhbnNhY3Rpb24gaGlzdG9yeSBvZiBhIHNwZWNpZmllZCBhcHBsaWNhdGlvbiBhbmQgcmVtb3ZlcyBhcHBsaWNhdGlvblxuICAgKiBAcmV0dXJuIHtQcm9taXNlfSBwcm9taXNlIHJlc29sdmluZyB3aGVuIHRoZSBoaXN0b3J5IGlzIGNsZWFyZWQgYW5kIGFwcGxpY2F0aW9uIGlzIHJlbW92ZWRcbiAgICovXG4gIHJlbW92ZUFwcGxpY2F0aW9uKCkge1xuICAgIHRoaXMuX2NoZWNrSXNDb25uZWN0aW9uQWN0aXZlKCk7XG4gICAgdGhpcy5faGlzdG9yeVN0b3JhZ2UuY2xlYXIoKTtcbiAgICByZXR1cm4gdGhpcy5fd2Vic29ja2V0Q2xpZW50LnJlbW92ZUFwcGxpY2F0aW9uKHRoaXMuX2FjY291bnQuaWQpO1xuICB9XG5cbiAgLyoqXG4gICAqIFJlcXVlc3RzIHRoZSB0ZXJtaW5hbCB0byBzdGFydCBzeW5jaHJvbml6YXRpb24gcHJvY2Vzc1xuICAgKiAoc2VlIGh0dHBzOi8vbWV0YWFwaS5jbG91ZC9kb2NzL2NsaWVudC93ZWJzb2NrZXQvc3luY2hyb25pemluZy9zeW5jaHJvbml6ZS8pXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBpbnN0YW5jZUluZGV4IGluc3RhbmNlIGluZGV4XG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBwcm9taXNlIHdoaWNoIHJlc29sdmVzIHdoZW4gc3luY2hyb25pemF0aW9uIHN0YXJ0ZWRcbiAgICovXG4gIGFzeW5jIHN5bmNocm9uaXplKGluc3RhbmNlSW5kZXgpIHtcbiAgICB0aGlzLl9jaGVja0lzQ29ubmVjdGlvbkFjdGl2ZSgpO1xuICAgIGNvbnN0IHJlZ2lvbiA9IHRoaXMuZ2V0UmVnaW9uKGluc3RhbmNlSW5kZXgpO1xuICAgIGNvbnN0IGluc3RhbmNlID0gdGhpcy5nZXRJbnN0YW5jZU51bWJlcihpbnN0YW5jZUluZGV4KTtcbiAgICBjb25zdCBob3N0ID0gdGhpcy5nZXRIb3N0TmFtZShpbnN0YW5jZUluZGV4KTtcbiAgICBsZXQgc3RhcnRpbmdIaXN0b3J5T3JkZXJUaW1lID0gbmV3IERhdGUoTWF0aC5tYXgoXG4gICAgICAodGhpcy5faGlzdG9yeVN0YXJ0VGltZSB8fCBuZXcgRGF0ZSgwKSkuZ2V0VGltZSgpLFxuICAgICAgKGF3YWl0IHRoaXMuX2hpc3RvcnlTdG9yYWdlLmxhc3RIaXN0b3J5T3JkZXJUaW1lKGluc3RhbmNlKSkuZ2V0VGltZSgpXG4gICAgKSk7XG4gICAgbGV0IHN0YXJ0aW5nRGVhbFRpbWUgPSBuZXcgRGF0ZShNYXRoLm1heChcbiAgICAgICh0aGlzLl9oaXN0b3J5U3RhcnRUaW1lIHx8IG5ldyBEYXRlKDApKS5nZXRUaW1lKCksXG4gICAgICAoYXdhaXQgdGhpcy5faGlzdG9yeVN0b3JhZ2UubGFzdERlYWxUaW1lKGluc3RhbmNlKSkuZ2V0VGltZSgpXG4gICAgKSk7XG4gICAgbGV0IHN5bmNocm9uaXphdGlvbklkID0gcmFuZG9tc3RyaW5nLmdlbmVyYXRlKDMyKTtcbiAgICB0aGlzLl9nZXRTdGF0ZShpbnN0YW5jZUluZGV4KS5sYXN0U3luY2hyb25pemF0aW9uSWQgPSBzeW5jaHJvbml6YXRpb25JZDtcbiAgICBjb25zdCBhY2NvdW50SWQgPSB0aGlzLl9hY2NvdW50LmFjY291bnRSZWdpb25zW3JlZ2lvbl07XG4gICAgdGhpcy5fbG9nZ2VyLmRlYnVnKGAke3RoaXMuX2FjY291bnQuaWR9OiR7aW5zdGFuY2VJbmRleH06IGluaXRpYXRpbmcgc3luY2hyb25pemF0aW9uICR7c3luY2hyb25pemF0aW9uSWR9YCk7XG4gICAgcmV0dXJuIHRoaXMuX3dlYnNvY2tldENsaWVudC5zeW5jaHJvbml6ZShhY2NvdW50SWQsIGluc3RhbmNlLCBob3N0LCBzeW5jaHJvbml6YXRpb25JZCxcbiAgICAgIHN0YXJ0aW5nSGlzdG9yeU9yZGVyVGltZSwgc3RhcnRpbmdEZWFsVGltZSwgdGhpcy50ZXJtaW5hbFN0YXRlLmdldEhhc2hlcygpKTtcbiAgfVxuICBcbiAgLyoqXG4gICAqIEluaXRpYWxpemVzIG1ldGEgYXBpIGNvbm5lY3Rpb25cbiAgICogQHJldHVybiB7UHJvbWlzZX0gcHJvbWlzZSB3aGljaCByZXNvbHZlcyB3aGVuIG1ldGEgYXBpIGNvbm5lY3Rpb24gaXMgaW5pdGlhbGl6ZWRcbiAgICovXG4gIGFzeW5jIGluaXRpYWxpemUoKSB7XG4gICAgdGhpcy5fY2hlY2tJc0Nvbm5lY3Rpb25BY3RpdmUoKTtcbiAgICBhd2FpdCB0aGlzLl9oaXN0b3J5U3RvcmFnZS5pbml0aWFsaXplKHRoaXMuX2FjY291bnQuaWQsIHRoaXMuX2Nvbm5lY3Rpb25SZWdpc3RyeS5hcHBsaWNhdGlvbik7XG4gICAgdGhpcy5fd2Vic29ja2V0Q2xpZW50LmFkZEFjY291bnRDYWNoZSh0aGlzLl9hY2NvdW50LmlkLCB0aGlzLl9hY2NvdW50LmFjY291bnRSZWdpb25zKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJbml0aWF0ZXMgc3Vic2NyaXB0aW9uIHRvIE1ldGFUcmFkZXIgdGVybWluYWxcbiAgICogQHJldHVybnMge1Byb21pc2V9IHByb21pc2Ugd2hpY2ggcmVzb2x2ZXMgd2hlbiBzdWJzY3JpcHRpb24gaXMgaW5pdGlhdGVkXG4gICAqL1xuICBhc3luYyBzdWJzY3JpYmUoKSB7XG4gICAgdGhpcy5fY2hlY2tJc0Nvbm5lY3Rpb25BY3RpdmUoKTtcbiAgICBjb25zdCBhY2NvdW50UmVnaW9ucyA9IHRoaXMuX2FjY291bnQuYWNjb3VudFJlZ2lvbnM7XG4gICAgT2JqZWN0LmVudHJpZXMoYWNjb3VudFJlZ2lvbnMpLmZvckVhY2goKFtyZWdpb24sIHJlcGxpY2FJZF0pID0+IHtcbiAgICAgIGlmICghdGhpcy5fb3B0aW9ucy5yZWdpb24gfHwgdGhpcy5fb3B0aW9ucy5yZWdpb24gPT09IHJlZ2lvbikge1xuICAgICAgICB0aGlzLl93ZWJzb2NrZXRDbGllbnQuZW5zdXJlU3Vic2NyaWJlKHJlcGxpY2FJZCwgMCk7XG4gICAgICAgIHRoaXMuX3dlYnNvY2tldENsaWVudC5lbnN1cmVTdWJzY3JpYmUocmVwbGljYUlkLCAxKTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdWJzY3JpYmVzIG9uIG1hcmtldCBkYXRhIG9mIHNwZWNpZmllZCBzeW1ib2wgKHNlZVxuICAgKiBodHRwczovL21ldGFhcGkuY2xvdWQvZG9jcy9jbGllbnQvd2Vic29ja2V0L21hcmtldERhdGFTdHJlYW1pbmcvc3Vic2NyaWJlVG9NYXJrZXREYXRhLykuXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBzeW1ib2wgc3ltYm9sIChlLmcuIGN1cnJlbmN5IHBhaXIgb3IgYW4gaW5kZXgpXG4gICAqIEBwYXJhbSB7QXJyYXk8TWFya2V0RGF0YVN1YnNjcmlwdGlvbj59IHN1YnNjcmlwdGlvbnMgYXJyYXkgb2YgbWFya2V0IGRhdGEgc3Vic2NyaXB0aW9uIHRvIGNyZWF0ZSBvciB1cGRhdGUuIFBsZWFzZVxuICAgKiBub3RlIHRoYXQgdGhpcyBmZWF0dXJlIGlzIG5vdCBmdWxseSBpbXBsZW1lbnRlZCBvbiBzZXJ2ZXItc2lkZSB5ZXRcbiAgICogQHBhcmFtIHtudW1iZXJ9IFt0aW1lb3V0SW5TZWNvbmRzXSB0aW1lb3V0IHRvIHdhaXQgZm9yIHByaWNlcyBpbiBzZWNvbmRzLCBkZWZhdWx0IGlzIDMwXG4gICAqIEBwYXJhbSB7Ym9vbGVhbn0gW3dhaXRGb3JRdW90ZV0gaWYgc2V0IHRvIGZhbHNlLCB0aGUgbWV0aG9kIHdpbGwgcmVzb2x2ZSB3aXRob3V0IHdhaXRpbmcgZm9yIHRoZSBmaXJzdCBxdW90ZSB0b1xuICAgKiBhcnJpdmUuIERlZmF1bHQgaXMgdG8gd2FpdCBmb3IgcXVvdGUgaWYgcXVvdGVzIHN1YnNjcmlwdGlvbiBpcyByZXF1ZXN0ZWQuXG4gICAqIEByZXR1cm5zIHtQcm9taXNlfSBwcm9taXNlIHdoaWNoIHJlc29sdmVzIHdoZW4gc3Vic2NyaXB0aW9uIHJlcXVlc3Qgd2FzIHByb2Nlc3NlZFxuICAgKi9cbiAgYXN5bmMgc3Vic2NyaWJlVG9NYXJrZXREYXRhKHN5bWJvbCwgc3Vic2NyaXB0aW9ucywgdGltZW91dEluU2Vjb25kcz8sIHdhaXRGb3JRdW90ZSA9IHRydWUpIHtcbiAgICB0aGlzLl9jaGVja0lzQ29ubmVjdGlvbkFjdGl2ZSgpO1xuICAgIGlmICghdGhpcy5fdGVybWluYWxTdGF0ZS5zcGVjaWZpY2F0aW9uKHN5bWJvbCkpIHtcbiAgICAgIHRocm93IG5ldyBWYWxpZGF0aW9uRXJyb3IoYCR7dGhpcy5fYWNjb3VudC5pZH06IENhbm5vdCBzdWJzY3JpYmUgdG8gbWFya2V0IGRhdGEgZm9yIHN5bWJvbCAke3N5bWJvbH0gYmVjYXVzZSBgICtcbiAgICAgICAgJ3N5bWJvbCBkb2VzIG5vdCBleGlzdCcpO1xuICAgIH0gZWxzZSB7XG4gICAgICBzdWJzY3JpcHRpb25zID0gc3Vic2NyaXB0aW9ucyB8fCBbe3R5cGU6ICdxdW90ZXMnfV07XG4gICAgICBpZiAodGhpcy5fc3Vic2NyaXB0aW9uc1tzeW1ib2xdKSB7XG4gICAgICAgIGNvbnN0IHByZXZTdWJzY3JpcHRpb25zID0gdGhpcy5fc3Vic2NyaXB0aW9uc1tzeW1ib2xdLnN1YnNjcmlwdGlvbnM7XG4gICAgICAgIHN1YnNjcmlwdGlvbnMuZm9yRWFjaChzdWJzY3JpcHRpb24gPT4ge1xuICAgICAgICAgIGNvbnN0IGluZGV4ID0gc3Vic2NyaXB0aW9uLnR5cGUgPT09ICdjYW5kbGVzJyA/IFxuICAgICAgICAgICAgcHJldlN1YnNjcmlwdGlvbnMuZmluZEluZGV4KGl0ZW0gPT4gaXRlbS50eXBlID09PSBzdWJzY3JpcHRpb24udHlwZSAmJiBcbiAgICAgICAgICAgICAgaXRlbS50aW1lZnJhbWUgPT09IHN1YnNjcmlwdGlvbi50aW1lZnJhbWUpIDpcbiAgICAgICAgICAgIHByZXZTdWJzY3JpcHRpb25zLmZpbmRJbmRleChpdGVtID0+IGl0ZW0udHlwZSA9PT0gc3Vic2NyaXB0aW9uLnR5cGUpO1xuICAgICAgICAgIGlmIChpbmRleCA9PT0gLTEpIHtcbiAgICAgICAgICAgIHByZXZTdWJzY3JpcHRpb25zLnB1c2goc3Vic2NyaXB0aW9uKTtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgcHJldlN1YnNjcmlwdGlvbnNbaW5kZXhdID0gc3Vic2NyaXB0aW9uO1xuICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICB0aGlzLl9zdWJzY3JpcHRpb25zW3N5