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)

684 lines (683 loc) 119 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "default", { enumerable: true, get: function() { return TerminalState; } }); const _randomstring = /*#__PURE__*/ _interop_require_default(require("randomstring")); const _synchronizationListener = /*#__PURE__*/ _interop_require_default(require("../clients/metaApi/synchronizationListener")); const _logger = /*#__PURE__*/ _interop_require_default(require("../logger")); const _promises = require("../helpers/promises"); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } let TerminalState = class TerminalState extends _synchronizationListener.default { get id() { return this._id; } /** * Returns true if MetaApi have connected to MetaTrader terminal * @return {Boolean} true if MetaApi have connected to MetaTrader terminal */ get connected() { return Object.values(this._stateByInstanceIndex).reduce((acc, s)=>acc || s.connected, false); } /** * Returns true if MetaApi have connected to MetaTrader terminal and MetaTrader terminal is connected to broker * @return {Boolean} true if MetaApi have connected to MetaTrader terminal and MetaTrader terminal is connected to * broker */ get connectedToBroker() { return Object.values(this._stateByInstanceIndex).reduce((acc, s)=>acc || s.connectedToBroker, false); } /** * Returns a local copy of account information * @returns {MetatraderAccountInformation} local copy of account information */ get accountInformation() { return this._combinedState.accountInformation; } /** * Returns a local copy of MetaTrader positions opened * @returns {Array<MetatraderPosition>} a local copy of MetaTrader positions opened */ get positions() { const hash = this._combinedState.positionsHash; return hash ? Object.values(this._terminalHashManager.getPositionsByHash(hash) || {}) : []; } /** * Returns a local copy of MetaTrader orders opened * @returns {Array<MetatraderOrder>} a local copy of MetaTrader orders opened */ get orders() { const hash = this._combinedState.ordersHash; return hash ? Object.values(this._terminalHashManager.getOrdersByHash(hash) || {}) : []; } /** * Returns a local copy of symbol specifications available in MetaTrader trading terminal * @returns {Array<MetatraderSymbolSpecification>} a local copy of symbol specifications available in MetaTrader * trading terminal */ get specifications() { const hash = this._combinedState.specificationsHash; return hash ? Object.values(this._terminalHashManager.getSpecificationsByHash(this._combinedState.specificationsHash) || {}) : []; } /** * Returns hashes of terminal state data for incremental synchronization * @returns {Promise<Object>} promise resolving with hashes of terminal state data */ // eslint-disable-next-line complexity getHashes() { const specificationsHashes = this._terminalHashManager.getLastUsedSpecificationHashes(this._account.server); const positionsHashes = this._terminalHashManager.getLastUsedPositionHashes(this._account.id); const ordersHashes = this._terminalHashManager.getLastUsedOrderHashes(this._account.id); return { specificationsHashes: specificationsHashes, positionsHashes: positionsHashes, ordersHashes: ordersHashes }; } /** * Returns MetaTrader symbol specification by symbol * @param {String} symbol symbol (e.g. currency pair or an index) * @return {MetatraderSymbolSpecification} MetatraderSymbolSpecification found or undefined if specification for a * symbol is not found */ specification(symbol) { if (this._combinedState.specificationsHash) { const state = this._terminalHashManager.getSpecificationsByHash(this._combinedState.specificationsHash); return state[symbol]; } else { return null; } } /** * Returns MetaTrader symbol price by symbol * @param {String} symbol symbol (e.g. currency pair or an index) * @return {MetatraderSymbolPrice} MetatraderSymbolPrice found or undefined if price for a symbol is not found */ price(symbol) { return this._combinedState.pricesBySymbol[symbol]; } /** * Quote time * @typdef {Object} QuoteTime * @property {Date} time quote time * @property {String} brokerTime quote time in broker timezone, YYYY-MM-DD HH:mm:ss.SSS format */ /** * Returns time of the last received quote * @return {QuoteTime} time of the last received quote */ get lastQuoteTime() { if (this._combinedState.lastQuoteTime) { return { time: this._combinedState.lastQuoteTime, brokerTime: this._combinedState.lastQuoteBrokerTime }; } else { return undefined; } } /** * Waits for price to be received * @param {string} symbol symbol (e.g. currency pair or an index) * @param {number} [timeoutInSeconds] timeout in seconds, default is 30 * @return {Promise<MetatraderSymbolPrice>} promise resolving with price or undefined if price has not been received */ async waitForPrice(symbol, timeoutInSeconds = 30) { this._waitForPriceResolves[symbol] = this._waitForPriceResolves[symbol] || []; if (!this.price(symbol)) { await Promise.race([ new Promise((res)=>this._waitForPriceResolves[symbol].push(res)), new Promise((res)=>setTimeout(res, timeoutInSeconds * 1000)) ]); } return this.price(symbol); } /** * Invoked when connection to MetaTrader terminal established * @param {String} instanceIndex index of an account instance connected */ onConnected(instanceIndex) { this._getState(instanceIndex).connected = true; } /** * Invoked when connection to MetaTrader terminal terminated * @param {String} instanceIndex index of an account instance connected */ onDisconnected(instanceIndex) { let state = this._getState(instanceIndex); state.connected = false; state.connectedToBroker = false; } /** * Invoked when broker connection status have changed * @param {String} instanceIndex index of an account instance connected * @param {Boolean} connected is MetaTrader terminal is connected to broker */ onBrokerConnectionStatusChanged(instanceIndex, connected) { this._combinedState.lastStatusTime = Date.now(); this._getState(instanceIndex).connectedToBroker = connected; } /** * 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) { const unsynchronizedStates = this._getStateIndicesOfSameInstanceNumber(instanceIndex).filter((stateIndex)=>!this._stateByInstanceIndex[stateIndex].ordersInitialized); unsynchronizedStates.sort((a, b)=>b.lastSyncUpdateTime - a.lastSyncUpdateTime); unsynchronizedStates.slice(1).forEach((stateIndex)=>this._removeState(stateIndex)); let state = this._getState(instanceIndex); state.isSpecificationsExpected = !specificationsHash; state.isPositionsExpected = !positionsHash; state.isOrdersExpected = !ordersHash; state.lastSyncUpdateTime = Date.now(); state.accountInformation = undefined; state.pricesBySymbol = {}; state.positions = []; if (!positionsHash) { state.positionsInitialized = false; state.positionsHash = null; } else { state.positionsHash = positionsHash; } state.orders = []; if (!ordersHash) { state.ordersInitialized = false; state.ordersHash = null; } else { state.ordersHash = ordersHash; } state.specificationsBySymbol = {}; if (!specificationsHash) { this._logger.trace(()=>`${this._account.id}:${instanceIndex}:${synchronizationId}: cleared specifications ` + "on synchronization start"); state.specificationsHash = null; } else { this._logger.trace(()=>`${this._account.id}:${instanceIndex}:${synchronizationId}: no need to clear ` + `specifications on synchronization start, ${Object.keys(state.specificationsBySymbol || {}).length} ` + "specifications reused"); state.specificationsHash = specificationsHash; } } /** * Invoked when MetaTrader account information is updated * @param {String} instanceIndex index of an account instance connected * @param {MetatraderAccountInformation} accountInformation updated MetaTrader account information */ onAccountInformationUpdated(instanceIndex, accountInformation) { let state = this._getState(instanceIndex); this._refreshStateUpdateTime(instanceIndex); state.accountInformation = accountInformation; if (accountInformation) { this._combinedState.accountInformation = Object.assign({}, accountInformation); } } /** * Invoked when the positions are replaced as a result of initial terminal state synchronization * @param {String} instanceIndex index of an account instance connected * @param {Array<MetatraderPosition>} positions updated array of positions * @return {Promise} promise which resolves when the asynchronous event is processed */ onPositionsReplaced(instanceIndex, positions) { let state = this._getState(instanceIndex); this._refreshStateUpdateTime(instanceIndex); if (state.isPositionsExpected) { state.positions = positions; } } /** * Invoked when position 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 * @return {Promise} promise which resolves when the asynchronous event is processed */ onPositionsSynchronized(instanceIndex, synchronizationId) { let state = this._getState(instanceIndex); state.positionsInitialized = true; } /** * Invoked when MetaTrader positions are updated * @param {string} instanceIndex index of an account instance connected * @param {MetatraderPosition[]} positions updated MetaTrader positions * @param {string[]} removedPositionIds removed position ids * @return {Promise} promise which resolves when the asynchronous event is processed */ async onPositionsUpdated(instanceIndex, positions, removedPositionIds) { let instanceState = this._getState(instanceIndex); this._refreshStateUpdateTime(instanceIndex); const date = Date.now(); removedPositionIds.forEach((id)=>this._combinedState.removedPositions[id] = date); positions = this._filterRemovedPositions(positions); Object.keys(this._combinedState.removedPositions).forEach((id)=>{ if (this._combinedState.removedPositions[id] < date - 24 * 60 * 60 * 1000) { delete this._combinedState.removedPositions[id]; } }); if (instanceState.ordersInitialized) { const updatePositions = async (state, instance)=>{ const hash = await this._terminalHashManager.updatePositions(this._account.id, this._account.type, this._id, instance, positions, removedPositionIds, state.positionsHash); state.positionsHash = hash; }; await updatePositions(instanceState, instanceIndex); await updatePositions(this._combinedState, this._combinedInstanceIndex); } else { instanceState.positions = instanceState.positions.filter((position)=>!removedPositionIds.includes(position.id)); positions.forEach((position)=>{ let index = instanceState.positions.findIndex((p)=>p.id === position.id); if (index !== -1) { instanceState.positions[index] = position; } else { instanceState.positions.push(position); } }); } } /** * Invoked when the orders are replaced as a result of initial terminal state synchronization * @param {String} instanceIndex index of an account instance connected * @param {Array<MetatraderOrder>} orders updated array of pending orders * @return {Promise} promise which resolves when the asynchronous event is processed */ onPendingOrdersReplaced(instanceIndex, orders) { let state = this._getState(instanceIndex); this._refreshStateUpdateTime(instanceIndex); if (state.isOrdersExpected) { state.orders = orders; } } /** * 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 * @return {Promise} promise which resolves when the asynchronous event is processed */ // eslint-disable-next-line complexity, max-statements async onPendingOrdersSynchronized(instanceIndex, synchronizationId) { let state = this._getState(instanceIndex); state.positionsInitialized = true; state.ordersInitialized = true; this._combinedState.accountInformation = state.accountInformation ? Object.assign({}, state.accountInformation) : undefined; state.positions = this._filterRemovedPositions(state.positions); if (state.positions.length) { const hash = this._terminalHashManager.recordPositions(this._account.id, this._account.type, this._id, instanceIndex, state.positions); state.positionsHash = hash; this._combinedState.positions = (state.positions || []).map((p)=>Object.assign({}, p)); this._combinedState.positionsHash = hash; } else if (state.positionsHash) { this._terminalHashManager.removePositionReference(this.id, instanceIndex); this._terminalHashManager.addPositionReference(state.positionsHash, this.id, instanceIndex); this._combinedState.positionsHash = state.positionsHash; this._terminalHashManager.removePositionReference(this.id, this._combinedInstanceIndex); this._terminalHashManager.addPositionReference(state.positionsHash, this.id, this._combinedInstanceIndex); } state.orders = this._filterRemovedOrders(state.orders); if (state.orders.length) { const hash = this._terminalHashManager.recordOrders(this._account.id, this._account.type, this._id, instanceIndex, state.orders); state.ordersHash = hash; this._combinedState.orders = (state.orders || []).map((o)=>Object.assign({}, o)); this._combinedState.ordersHash = hash; } else if (state.ordersHash) { this._terminalHashManager.removeOrderReference(this.id, instanceIndex); this._terminalHashManager.addOrderReference(state.ordersHash, this.id, instanceIndex); this._combinedState.ordersHash = state.ordersHash; this._terminalHashManager.removeOrderReference(this.id, this._combinedInstanceIndex); this._terminalHashManager.addOrderReference(state.ordersHash, this.id, this._combinedInstanceIndex); } this._logger.trace(()=>`${this._account.id}:${instanceIndex}:${synchronizationId}: assigned specifications to ` + "combined state from " + `${instanceIndex}, ${Object.keys(state.specificationsBySymbol || {}).length} specifications assigned`); this._combinedState.positionsInitialized = true; this._combinedState.ordersInitialized = true; if (Object.keys(state.specificationsBySymbol || {}).length) { if (state.isSpecificationsExpected) { const hash = await this._terminalHashManager.recordSpecifications(this._account.server, this._account.type, this._id, instanceIndex, Object.values(state.specificationsBySymbol)); this._combinedState.specificationsHash = hash; state.specificationsHash = hash; state.specificationsBySymbol = null; } else if (state.specificationsHash) { const hash = await this._terminalHashManager.updateSpecifications(this._account.server, this._account.type, this._id, instanceIndex, Object.values(state.specificationsBySymbol), [], state.specificationsHash); state.specificationsHash = hash; } } else if (state.specificationsHash) { this._terminalHashManager.removeSpecificationReference(this.id, instanceIndex); this._terminalHashManager.addSpecificationReference(state.specificationsHash, this.id, instanceIndex); this._combinedState.specificationsHash = state.specificationsHash; this._terminalHashManager.removeSpecificationReference(this.id, this._combinedInstanceIndex); this._terminalHashManager.addSpecificationReference(state.specificationsHash, this.id, this._combinedInstanceIndex); } for (let stateIndex of this._getStateIndicesOfSameInstanceNumber(instanceIndex)){ if (!this._stateByInstanceIndex[stateIndex].connected) { this._removeState(stateIndex); } } } /** * Invoked when MetaTrader pending orders are updated or completed * @param {string} instanceIndex index of an account instance connected * @param {MetatraderOrder[]} orders updated MetaTrader pending orders * @param {string[]} completedOrderIds completed MetaTrader pending order ids * @return {Promise} promise which resolves when the asynchronous event is processed */ async onPendingOrdersUpdated(instanceIndex, orders, completedOrderIds) { let instanceState = this._getState(instanceIndex); this._refreshStateUpdateTime(instanceIndex); const date = Date.now(); completedOrderIds.forEach((id)=>this._combinedState.completedOrders[id] = date); orders = this._filterRemovedOrders(orders); Object.keys(this._combinedState.completedOrders).forEach((id)=>{ if (this._combinedState.completedOrders[id] < date - 24 * 60 * 60 * 1000) { delete this._combinedState.completedOrders[id]; } }); if (instanceState.ordersInitialized) { const updatePendingOrders = async (state, instance)=>{ const hash = await this._terminalHashManager.updateOrders(this._account.id, this._account.type, this._id, instance, orders, completedOrderIds, state.ordersHash); state.ordersHash = hash; }; await updatePendingOrders(instanceState, instanceIndex); await updatePendingOrders(this._combinedState, this._combinedInstanceIndex); } else { instanceState.orders = instanceState.orders.filter((order)=>!completedOrderIds.includes(order.id)); orders.forEach((order)=>{ let index = instanceState.orders.findIndex((o)=>o.id === order.id); if (index !== -1) { instanceState.orders[index] = order; } else { instanceState.orders.push(order); } }); } } /** * Invoked when a symbol specification was updated * @param {String} instanceIndex index of account instance connected * @param {Array<MetatraderSymbolSpecification>} specifications updated specifications * @param {Array<String>} removedSymbols removed symbols */ async onSymbolSpecificationsUpdated(instanceIndex, specifications, removedSymbols) { let instanceState = this._getState(instanceIndex); this._refreshStateUpdateTime(instanceIndex); if (!instanceState.ordersInitialized) { for (let specification of specifications){ instanceState.specificationsBySymbol[specification.symbol] = specification; } } else { const hash = this._terminalHashManager.updateSpecifications(this._account.server, this._account.type, this._id, instanceIndex, specifications, removedSymbols, instanceState.specificationsHash); instanceState.specificationsHash = hash; const combinedHash = this._terminalHashManager.updateSpecifications(this._account.server, this._account.type, this._id, this._combinedInstanceIndex, specifications, removedSymbols, this._combinedState.specificationsHash); this._combinedState.specificationsHash = combinedHash; } this._logger.trace(()=>`${this._account.id}:${instanceIndex}: updated ${specifications.length} specifications, ` + `removed ${removedSymbols.length} specifications. There are ` + `${Object.keys(instanceState.specificationsBySymbol || {}).length} specifications after update`); } /** * Invoked when prices for several symbols were updated * @param {String} instanceIndex index of an account instance connected * @param {Array<MetatraderSymbolPrice>} prices updated MetaTrader symbol prices * @param {Number} equity account liquidation value * @param {Number} margin margin used * @param {Number} freeMargin free margin * @param {Number} marginLevel margin level calculated as % of equity/margin */ // eslint-disable-next-line complexity onSymbolPricesUpdated(instanceIndex, prices, equity, margin, freeMargin, marginLevel) { let instanceState = this._getState(instanceIndex); this._refreshStateUpdateTime(instanceIndex); // eslint-disable-next-line complexity,max-statements const updateSymbolPrices = (state)=>{ let pricesInitialized = false; let priceUpdated = false; for (let price of prices || []){ let currentPrice = state.pricesBySymbol[price.symbol]; if (currentPrice && currentPrice.time.getTime() > price.time.getTime()) { continue; } else { priceUpdated = true; } if (!state.lastQuoteTime || state.lastQuoteTime.getTime() < price.time.getTime()) { state.lastQuoteTime = price.time; state.lastQuoteBrokerTime = price.brokerTime; } state.pricesBySymbol[price.symbol] = price; const allPositions = Object.values(this._terminalHashManager.getPositionsByHash(state.positionsHash) || {}); const allOrders = Object.values(this._terminalHashManager.getOrdersByHash(state.ordersHash) || {}); let positions = allPositions.filter((p)=>p.symbol === price.symbol); let otherPositions = allPositions.filter((p)=>p.symbol !== price.symbol); let orders = allOrders.filter((o)=>o.symbol === price.symbol); pricesInitialized = true; for (let position of otherPositions){ let p = state.pricesBySymbol[position.symbol]; if (p) { if (position.unrealizedProfit === undefined) { this._updatePositionProfits(position, p); } } else { pricesInitialized = false; } } for (let position of positions){ this._updatePositionProfits(position, price); } for (let order of orders){ order.currentPrice = order.type === "ORDER_TYPE_BUY" || order.type === "ORDER_TYPE_BUY_LIMIT" || order.type === "ORDER_TYPE_BUY_STOP" || order.type === "ORDER_TYPE_BUY_STOP_LIMIT" ? price.ask : price.bid; } let priceResolves = this._waitForPriceResolves[price.symbol] || []; if (priceResolves.length) { for (let resolve of priceResolves){ resolve(); } delete this._waitForPriceResolves[price.symbol]; } } if (priceUpdated && state.accountInformation) { const positions = Object.values(this._terminalHashManager.getPositionsByHash(state.positionsHash) || {}); if (state.positionsInitialized && pricesInitialized) { if (state.accountInformation.platform === "mt5") { state.accountInformation.equity = equity !== undefined ? equity : state.accountInformation.balance + positions.reduce((acc, p)=>acc + Math.round((p.unrealizedProfit || 0) * 100) / 100 + Math.round((p.swap || 0) * 100) / 100, 0); } else { state.accountInformation.equity = equity !== undefined ? equity : state.accountInformation.balance + positions.reduce((acc, p)=>acc + Math.round((p.swap || 0) * 100) / 100 + Math.round((p.commission || 0) * 100) / 100 + Math.round((p.unrealizedProfit || 0) * 100) / 100, 0); } state.accountInformation.equity = Math.round(state.accountInformation.equity * 100) / 100; } else { state.accountInformation.equity = equity !== undefined ? equity : state.accountInformation.equity; } var _prices__accountCurrencyExchangeRate; state.accountInformation.accountCurrencyExchangeRate = (_prices__accountCurrencyExchangeRate = prices[0].accountCurrencyExchangeRate) !== null && _prices__accountCurrencyExchangeRate !== void 0 ? _prices__accountCurrencyExchangeRate : state.accountInformation.accountCurrencyExchangeRate; state.accountInformation.margin = margin !== undefined ? margin : state.accountInformation.margin; state.accountInformation.freeMargin = freeMargin !== undefined ? freeMargin : state.accountInformation.freeMargin; state.accountInformation.marginLevel = freeMargin !== undefined ? marginLevel : state.accountInformation.marginLevel; } }; updateSymbolPrices(instanceState); updateSymbolPrices(this._combinedState); for (let price of prices){ for (let call of Object.values(this._processThrottledQuotesCalls)){ var _call_expectedSymbols; this._logger.trace(`${this._account.id}:${instanceIndex}: refreshed ${price.symbol} price`); (_call_expectedSymbols = call.expectedSymbols) === null || _call_expectedSymbols === void 0 ? void 0 : _call_expectedSymbols.delete(price.symbol); call.receivedSymbols.add(price.symbol); call.promise.check(); } } } /** * 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) { if (this._stateByInstanceIndex[instanceIndex]) { for (let stateIndex of this._getStateIndicesOfSameInstanceNumber(instanceIndex)){ const instanceState = this._stateByInstanceIndex[stateIndex]; if (!this._stateByInstanceIndex[instanceIndex].ordersInitialized && this._stateByInstanceIndex[instanceIndex].lastSyncUpdateTime <= instanceState.lastSyncUpdateTime) { this._removeState(instanceIndex); break; } if (instanceState.connected && instanceState.ordersInitialized) { this._removeState(instanceIndex); break; } } } } /** * Forces refresh of most recent quote updates for symbols subscribed to by the terminal, and waits for them all to * be processed by this terminal state. This method does not waits for all other listeners to receive and process the * quote updates * @param {RefreshTerminalStateOptions} [options] additional options * @returns {Promise} promise resolving when the terminal state received and processed the latest quotes */ async refreshTerminalState(options) { let callData = { receivedSymbols: new Set() }; let callId = _randomstring.default.generate(8); this._processThrottledQuotesCalls[callId] = callData; callData.promise = new _promises.ConditionPromise(()=>callData.expectedSymbols && !callData.expectedSymbols.size); var _options_timeoutInSeconds; callData.promise.timeout(1000 * ((_options_timeoutInSeconds = options === null || options === void 0 ? void 0 : options.timeoutInSeconds) !== null && _options_timeoutInSeconds !== void 0 ? _options_timeoutInSeconds : 10), "refreshing terminal state timed out"); try { let symbols = await Promise.race([ this._websocketClient.refreshTerminalState(this._account.id), callData.promise // will only throw timeout error at this point ]); this._logger.debug(`${this._account.id}: expecting for ${symbols.length ? symbols : 0} symbols to refresh`); let expectedSymbols = new Set(); for (let symbol of symbols){ if (!callData.receivedSymbols.has(symbol)) { expectedSymbols.add(symbol); } } callData.expectedSymbols = expectedSymbols; callData.promise.check(); await callData.promise; } finally{ delete this._processThrottledQuotesCalls[callId]; } } /** * Removes connection related data from terminal hash manager */ close() { clearInterval(this._checkCombinedStateActivityJobInterval); Object.keys(this._stateByInstanceIndex).forEach((instanceIndex)=>{ this._removeFromHashManager(instanceIndex); }); this._removeFromHashManager(this._combinedInstanceIndex); } // resets combined state and removes from hash manager if has been disconnected for a long time _checkCombinedStateActivityJob() { if (!this.connectedToBroker && this._combinedState.lastStatusTime < Date.now() - 30 * 60 * 1000) { this._removeFromHashManager(this._combinedInstanceIndex); this._combinedState.accountInformation = undefined; this._combinedState.specificationsBySymbol = null; this._combinedState.pricesBySymbol = {}; this._combinedState.specificationsHash = null; this._combinedState.orders = []; this._combinedState.ordersHash = null; this._combinedState.positions = []; this._combinedState.positionsHash = null; this._combinedState.ordersInitialized = false; this._combinedState.positionsInitialized = false; this._combinedState.lastStatusTime = 0; this._combinedState.lastQuoteTime = undefined; this._combinedState.lastQuoteBrokerTime = undefined; } } _removeState(instanceIndex) { delete this._stateByInstanceIndex[instanceIndex]; this._removeFromHashManager(instanceIndex); } _removeFromHashManager(instanceIndex) { this._terminalHashManager.removeConnectionReferences(this._account.server, this._account.id, this._id, instanceIndex); } _refreshStateUpdateTime(instanceIndex) { const state = this._stateByInstanceIndex[instanceIndex]; if (state && state.ordersInitialized) { state.lastSyncUpdateTime = Date.now(); } } _getStateIndicesOfSameInstanceNumber(instanceIndex) { const region = instanceIndex.split(":")[0]; const instanceNumber = instanceIndex.split(":")[1]; return Object.keys(this._stateByInstanceIndex).filter((stateInstanceIndex)=>stateInstanceIndex.startsWith(`${region}:${instanceNumber}:`) && instanceIndex !== stateInstanceIndex); } // eslint-disable-next-line complexity _updatePositionProfits(position, price) { let specification = this.specification(position.symbol); if (specification) { let multiplier = Math.pow(10, specification.digits); if (position.profit !== undefined) { position.profit = Math.round(position.profit * multiplier) / multiplier; } if (position.unrealizedProfit === undefined || position.realizedProfit === undefined) { position.unrealizedProfit = (position.type === "POSITION_TYPE_BUY" ? 1 : -1) * (position.currentPrice - position.openPrice) * position.currentTickValue * position.volume / specification.tickSize; position.unrealizedProfit = Math.round(position.unrealizedProfit * multiplier) / multiplier; position.realizedProfit = position.profit - position.unrealizedProfit; } let newPositionPrice = position.type === "POSITION_TYPE_BUY" ? price.bid : price.ask; let isProfitable = (position.type === "POSITION_TYPE_BUY" ? 1 : -1) * (newPositionPrice - position.openPrice); let currentTickValue = isProfitable > 0 ? price.profitTickValue : price.lossTickValue; let unrealizedProfit = (position.type === "POSITION_TYPE_BUY" ? 1 : -1) * (newPositionPrice - position.openPrice) * currentTickValue * position.volume / specification.tickSize; unrealizedProfit = Math.round(unrealizedProfit * multiplier) / multiplier; position.unrealizedProfit = unrealizedProfit; position.profit = position.unrealizedProfit + position.realizedProfit; position.profit = Math.round(position.profit * multiplier) / multiplier; position.currentPrice = newPositionPrice; position.currentTickValue = currentTickValue; } } _filterRemovedPositions(positions) { return positions.filter((position)=>!this._combinedState.removedPositions[position.id]); } _filterRemovedOrders(orders) { return orders.filter((order)=>!this._combinedState.completedOrders[order.id]); } _getState(instanceIndex) { if (!this._stateByInstanceIndex["" + instanceIndex]) { this._logger.trace(`${this._account.id}:${instanceIndex}: constructed new state`); this._stateByInstanceIndex["" + instanceIndex] = this._constructTerminalState(instanceIndex); } return this._stateByInstanceIndex["" + instanceIndex]; } _constructTerminalState(instanceIndex) { return { instanceIndex, connected: false, connectedToBroker: false, accountInformation: undefined, positions: [], orders: [], specificationsBySymbol: {}, pricesBySymbol: {}, ordersInitialized: false, positionsInitialized: false, lastSyncUpdateTime: 0, positionsHash: null, ordersHash: null, specificationsHash: null, isSpecificationsExpected: true, isPositionsExpected: true, isOrdersExpected: true, lastQuoteTime: undefined, lastQuoteBrokerTime: undefined }; } /** * Constructs the instance of terminal state class * @param {MetatraderAccount} account mt account * @param {TerminalHashManager} terminalHashManager terminal hash manager * @param {MetaApiWebsocketClient} websocketClient websocket client */ constructor(account, terminalHashManager, websocketClient){ super(); this._id = _randomstring.default.generate(32); this._account = account; this._terminalHashManager = terminalHashManager; this._websocketClient = websocketClient; this._stateByInstanceIndex = {}; this._waitForPriceResolves = {}; this._combinedInstanceIndex = "combined"; this._combinedState = { accountInformation: undefined, positions: [], orders: [], specificationsBySymbol: null, pricesBySymbol: {}, removedPositions: {}, completedOrders: {}, specificationsHash: null, positionsHash: null, ordersHash: null, ordersInitialized: false, positionsInitialized: false, lastStatusTime: 0, lastQuoteTime: undefined, lastQuoteBrokerTime: undefined }; this._processThrottledQuotesCalls = {}; this._logger = _logger.default.getLogger("TerminalState"); this._checkCombinedStateActivityJob = this._checkCombinedStateActivityJob.bind(this); this._checkCombinedStateActivityJobInterval = setInterval(this._checkCombinedStateActivityJob, 5 * 60 * 1000); } }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5cbmltcG9ydCByYW5kb21zdHJpbmcgZnJvbSAncmFuZG9tc3RyaW5nJztcbmltcG9ydCBTeW5jaHJvbml6YXRpb25MaXN0ZW5lciBmcm9tICcuLi9jbGllbnRzL21ldGFBcGkvc3luY2hyb25pemF0aW9uTGlzdGVuZXInO1xuaW1wb3J0IE1ldGFBcGlXZWJzb2NrZXRDbGllbnQgZnJvbSAnLi4vY2xpZW50cy9tZXRhQXBpL21ldGFBcGlXZWJzb2NrZXQuY2xpZW50JztcbmltcG9ydCBMb2dnZXJNYW5hZ2VyIGZyb20gJy4uL2xvZ2dlcic7XG5pbXBvcnQgVGVybWluYWxIYXNoTWFuYWdlciBmcm9tICcuL3Rlcm1pbmFsSGFzaE1hbmFnZXInO1xuaW1wb3J0IE1ldGF0cmFkZXJBY2NvdW50IGZyb20gJy4vbWV0YXRyYWRlckFjY291bnQnO1xuaW1wb3J0IHtDb25kaXRpb25Qcm9taXNlfSBmcm9tICcuLi9oZWxwZXJzL3Byb21pc2VzJztcblxuLyoqXG4gKiBSZXNwb25zaWJsZSBmb3Igc3RvcmluZyBhIGxvY2FsIGNvcHkgb2YgcmVtb3RlIHRlcm1pbmFsIHN0YXRlXG4gKi9cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFRlcm1pbmFsU3RhdGUgZXh0ZW5kcyBTeW5jaHJvbml6YXRpb25MaXN0ZW5lciB7XG5cbiAgLyoqXG4gICAqIENvbnN0cnVjdHMgdGhlIGluc3RhbmNlIG9mIHRlcm1pbmFsIHN0YXRlIGNsYXNzXG4gICAqIEBwYXJhbSB7TWV0YXRyYWRlckFjY291bnR9IGFjY291bnQgbXQgYWNjb3VudFxuICAgKiBAcGFyYW0ge1Rlcm1pbmFsSGFzaE1hbmFnZXJ9IHRlcm1pbmFsSGFzaE1hbmFnZXIgdGVybWluYWwgaGFzaCBtYW5hZ2VyXG4gICAqIEBwYXJhbSB7TWV0YUFwaVdlYnNvY2tldENsaWVudH0gd2Vic29ja2V0Q2xpZW50IHdlYnNvY2tldCBjbGllbnRcbiAgICovXG4gIGNvbnN0cnVjdG9yKGFjY291bnQsIHRlcm1pbmFsSGFzaE1hbmFnZXIsIHdlYnNvY2tldENsaWVudCkge1xuICAgIHN1cGVyKCk7XG4gICAgdGhpcy5faWQgPSByYW5kb21zdHJpbmcuZ2VuZXJhdGUoMzIpO1xuICAgIHRoaXMuX2FjY291bnQgPSBhY2NvdW50O1xuICAgIHRoaXMuX3Rlcm1pbmFsSGFzaE1hbmFnZXIgPSB0ZXJtaW5hbEhhc2hNYW5hZ2VyO1xuICAgIHRoaXMuX3dlYnNvY2tldENsaWVudCA9IHdlYnNvY2tldENsaWVudDtcbiAgICB0aGlzLl9zdGF0ZUJ5SW5zdGFuY2VJbmRleCA9IHt9O1xuICAgIHRoaXMuX3dhaXRGb3JQcmljZVJlc29sdmVzID0ge307XG4gICAgdGhpcy5fY29tYmluZWRJbnN0YW5jZUluZGV4ID0gJ2NvbWJpbmVkJztcbiAgICB0aGlzLl9jb21iaW5lZFN0YXRlID0ge1xuICAgICAgYWNjb3VudEluZm9ybWF0aW9uOiB1bmRlZmluZWQsXG4gICAgICBwb3NpdGlvbnM6IFtdLFxuICAgICAgb3JkZXJzOiBbXSxcbiAgICAgIHNwZWNpZmljYXRpb25zQnlTeW1ib2w6IG51bGwsXG4gICAgICBwcmljZXNCeVN5bWJvbDoge30sXG4gICAgICByZW1vdmVkUG9zaXRpb25zOiB7fSxcbiAgICAgIGNvbXBsZXRlZE9yZGVyczoge30sXG4gICAgICBzcGVjaWZpY2F0aW9uc0hhc2g6IG51bGwsXG4gICAgICBwb3NpdGlvbnNIYXNoOiBudWxsLFxuICAgICAgb3JkZXJzSGFzaDogbnVsbCxcbiAgICAgIG9yZGVyc0luaXRpYWxpemVkOiBmYWxzZSxcbiAgICAgIHBvc2l0aW9uc0luaXRpYWxpemVkOiBmYWxzZSxcbiAgICAgIGxhc3RTdGF0dXNUaW1lOiAwLFxuICAgICAgbGFzdFF1b3RlVGltZTogdW5kZWZpbmVkLFxuICAgICAgbGFzdFF1b3RlQnJva2VyVGltZTogdW5kZWZpbmVkXG4gICAgfTtcbiAgICB0aGlzLl9wcm9jZXNzVGhyb3R0bGVkUXVvdGVzQ2FsbHMgPSB7fTtcbiAgICB0aGlzLl9sb2dnZXIgPSBMb2dnZXJNYW5hZ2VyLmdldExvZ2dlcignVGVybWluYWxTdGF0ZScpO1xuICAgIHRoaXMuX2NoZWNrQ29tYmluZWRTdGF0ZUFjdGl2aXR5Sm9iID0gdGhpcy5fY2hlY2tDb21iaW5lZFN0YXRlQWN0aXZpdHlKb2IuYmluZCh0aGlzKTtcbiAgICB0aGlzLl9jaGVja0NvbWJpbmVkU3RhdGVBY3Rpdml0eUpvYkludGVydmFsID0gc2V0SW50ZXJ2YWwodGhpcy5fY2hlY2tDb21iaW5lZFN0YXRlQWN0aXZpdHlKb2IsIDUgKiA2MCAqIDEwMDApO1xuICB9XG5cbiAgZ2V0IGlkKCkge1xuICAgIHJldHVybiB0aGlzLl9pZDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRydWUgaWYgTWV0YUFwaSBoYXZlIGNvbm5lY3RlZCB0byBNZXRhVHJhZGVyIHRlcm1pbmFsXG4gICAqIEByZXR1cm4ge0Jvb2xlYW59IHRydWUgaWYgTWV0YUFwaSBoYXZlIGNvbm5lY3RlZCB0byBNZXRhVHJhZGVyIHRlcm1pbmFsXG4gICAqL1xuICBnZXQgY29ubmVjdGVkKCkge1xuICAgIHJldHVybiBPYmplY3QudmFsdWVzKHRoaXMuX3N0YXRlQnlJbnN0YW5jZUluZGV4KS5yZWR1Y2UoKGFjYywgcykgPT4gYWNjIHx8IHMuY29ubmVjdGVkLCBmYWxzZSk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0cnVlIGlmIE1ldGFBcGkgaGF2ZSBjb25uZWN0ZWQgdG8gTWV0YVRyYWRlciB0ZXJtaW5hbCBhbmQgTWV0YVRyYWRlciB0ZXJtaW5hbCBpcyBjb25uZWN0ZWQgdG8gYnJva2VyXG4gICAqIEByZXR1cm4ge0Jvb2xlYW59IHRydWUgaWYgTWV0YUFwaSBoYXZlIGNvbm5lY3RlZCB0byBNZXRhVHJhZGVyIHRlcm1pbmFsIGFuZCBNZXRhVHJhZGVyIHRlcm1pbmFsIGlzIGNvbm5lY3RlZCB0b1xuICAgKiBicm9rZXJcbiAgICovXG4gIGdldCBjb25uZWN0ZWRUb0Jyb2tlcigpIHtcbiAgICByZXR1cm4gT2JqZWN0LnZhbHVlcyh0aGlzLl9zdGF0ZUJ5SW5zdGFuY2VJbmRleCkucmVkdWNlKChhY2MsIHMpID0+IGFjYyB8fCBzLmNvbm5lY3RlZFRvQnJva2VyLCBmYWxzZSk7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBhIGxvY2FsIGNvcHkgb2YgYWNjb3VudCBpbmZvcm1hdGlvblxuICAgKiBAcmV0dXJucyB7TWV0YXRyYWRlckFjY291bnRJbmZvcm1hdGlvbn0gbG9jYWwgY29weSBvZiBhY2NvdW50IGluZm9ybWF0aW9uXG4gICAqL1xuICBnZXQgYWNjb3VudEluZm9ybWF0aW9uKCkge1xuICAgIHJldHVybiB0aGlzLl9jb21iaW5lZFN0YXRlLmFjY291bnRJbmZvcm1hdGlvbjtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGEgbG9jYWwgY29weSBvZiBNZXRhVHJhZGVyIHBvc2l0aW9ucyBvcGVuZWRcbiAgICogQHJldHVybnMge0FycmF5PE1ldGF0cmFkZXJQb3NpdGlvbj59IGEgbG9jYWwgY29weSBvZiBNZXRhVHJhZGVyIHBvc2l0aW9ucyBvcGVuZWRcbiAgICovXG4gIGdldCBwb3NpdGlvbnMoKSB7XG4gICAgY29uc3QgaGFzaCA9IHRoaXMuX2NvbWJpbmVkU3RhdGUucG9zaXRpb25zSGFzaDtcbiAgICByZXR1cm4gaGFzaCA/IE9iamVjdC52YWx1ZXModGhpcy5fdGVybWluYWxIYXNoTWFuYWdlci5nZXRQb3NpdGlvbnNCeUhhc2goaGFzaCkgfHwge30pIDogW107XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBhIGxvY2FsIGNvcHkgb2YgTWV0YVRyYWRlciBvcmRlcnMgb3BlbmVkXG4gICAqIEByZXR1cm5zIHtBcnJheTxNZXRhdHJhZGVyT3JkZXI+fSBhIGxvY2FsIGNvcHkgb2YgTWV0YVRyYWRlciBvcmRlcnMgb3BlbmVkXG4gICAqL1xuICBnZXQgb3JkZXJzKCkge1xuICAgIGNvbnN0IGhhc2ggPSB0aGlzLl9jb21iaW5lZFN0YXRlLm9yZGVyc0hhc2g7XG4gICAgcmV0dXJuIGhhc2ggPyBPYmplY3QudmFsdWVzKHRoaXMuX3Rlcm1pbmFsSGFzaE1hbmFnZXIuZ2V0T3JkZXJzQnlIYXNoKGhhc2gpIHx8IHt9KSA6IFtdO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgYSBsb2NhbCBjb3B5IG9mIHN5bWJvbCBzcGVjaWZpY2F0aW9ucyBhdmFpbGFibGUgaW4gTWV0YVRyYWRlciB0cmFkaW5nIHRlcm1pbmFsXG4gICAqIEByZXR1cm5zIHtBcnJheTxNZXRhdHJhZGVyU3ltYm9sU3BlY2lmaWNhdGlvbj59IGEgbG9jYWwgY29weSBvZiBzeW1ib2wgc3BlY2lmaWNhdGlvbnMgYXZhaWxhYmxlIGluIE1ldGFUcmFkZXJcbiAgICogdHJhZGluZyB0ZXJtaW5hbFxuICAgKi9cbiAgZ2V0IHNwZWNpZmljYXRpb25zKCkge1xuICAgIGNvbnN0IGhhc2ggPSB0aGlzLl9jb21iaW5lZFN0YXRlLnNwZWNpZmljYXRpb25zSGFzaDtcbiAgICByZXR1cm4gaGFzaCA/IE9iamVjdC52YWx1ZXModGhpcy5fdGVybWluYWxIYXNoTWFuYWdlci5nZXRTcGVjaWZpY2F0aW9uc0J5SGFzaChcbiAgICAgIHRoaXMuX2NvbWJpbmVkU3RhdGUuc3BlY2lmaWNhdGlvbnNIYXNoKSB8fCB7fSkgOiBbXTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGhhc2hlcyBvZiB0ZXJtaW5hbCBzdGF0ZSBkYXRhIGZvciBpbmNyZW1lbnRhbCBzeW5jaHJvbml6YXRpb25cbiAgICogQHJldHVybnMge1Byb21pc2U8T2JqZWN0Pn0gcHJvbWlzZSByZXNvbHZpbmcgd2l0aCBoYXNoZXMgb2YgdGVybWluYWwgc3RhdGUgZGF0YVxuICAgKi9cbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGNvbXBsZXhpdHlcbiAgZ2V0SGFzaGVzKCkge1xuICAgIGNvbnN0IHNwZWNpZmljYXRpb25zSGFzaGVzID0gdGhpcy5fdGVybWluYWxIYXNoTWFuYWdlci5nZXRMYXN0VXNlZFNwZWNpZmljYXRpb25IYXNoZXModGhpcy5fYWNjb3VudC5zZXJ2ZXIpO1xuICAgIGNvbnN0IHBvc2l0aW9uc0hhc2hlcyA9IHRoaXMuX3Rlcm1pbmFsSGFzaE1hbmFnZXIuZ2V0TGFzdFVzZWRQb3NpdGlvbkhhc2hlcyh0aGlzLl9hY2NvdW50LmlkKTtcbiAgICBjb25zdCBvcmRlcnNIYXNoZXMgPSB0aGlzLl90ZXJtaW5hbEhhc2hNYW5hZ2VyLmdldExhc3RVc2VkT3JkZXJIYXNoZXModGhpcy5fYWNjb3VudC5pZCk7XG5cbiAgICByZXR1cm4ge1xuICAgICAgc3BlY2lmaWNhdGlvbnNIYXNoZXM6IHNwZWNpZmljYXRpb25zSGFzaGVzLFxuICAgICAgcG9zaXRpb25zSGFzaGVzOiBwb3NpdGlvbnNIYXNoZXMsXG4gICAgICBvcmRlcnNIYXNoZXM6IG9yZGVyc0hhc2hlc1xuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBNZXRhVHJhZGVyIHN5bWJvbCBzcGVjaWZpY2F0aW9uIGJ5IHN5bWJvbFxuICAgKiBAcGFyYW0ge1N0cmluZ30gc3ltYm9sIHN5bWJvbCAoZS5nLiBjdXJyZW5jeSBwYWlyIG9yIGFuIGluZGV4KVxuICAgKiBAcmV0dXJuIHtNZXRhdHJhZGVyU3ltYm9sU3BlY2lmaWNhdGlvbn0gTWV0YXRyYWRlclN5bWJvbFNwZWNpZmljYXRpb24gZm91bmQgb3IgdW5kZWZpbmVkIGlmIHNwZWNpZmljYXRpb24gZm9yIGFcbiAgICogc3ltYm9sIGlzIG5vdCBmb3VuZFxuICAgKi9cbiAgc3BlY2lmaWNhdGlvbihzeW1ib2wpIHtcbiAgICBpZih0aGlzLl9jb21iaW5lZFN0YXRlLnNwZWNpZmljYXRpb25zSGFzaCkge1xuICAgICAgY29uc3Qgc3RhdGUgPSB0aGlzLl90ZXJtaW5hbEhhc2hNYW5hZ2VyLmdldFNwZWNpZmljYXRpb25zQnlIYXNoKFxuICAgICAgICB0aGlzLl9jb21iaW5lZFN0YXRlLnNwZWNpZmljYXRpb25zSGFzaCk7XG4gICAgICByZXR1cm4gc3RhdGVbc3ltYm9sXTtcbiAgICB9IGVsc2Uge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgTWV0YVRyYWRlciBzeW1ib2wgcHJpY2UgYnkgc3ltYm9sXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBzeW1ib2wgc3ltYm9sIChlLmcuIGN1cnJlbmN5IHBhaXIgb3IgYW4gaW5kZXgpXG4gICAqIEByZXR1cm4ge01ldGF0cmFkZXJTeW1ib2xQcmljZX0gTWV0YXRyYWRlclN5bWJvbFByaWNlIGZvdW5kIG9yIHVuZGVmaW5lZCBpZiBwcmljZSBmb3IgYSBzeW1ib2wgaXMgbm90IGZvdW5kXG4gICAqL1xuICBwcmljZShzeW1ib2wpIHtcbiAgICByZXR1cm4gdGhpcy5fY29tYmluZWRTdGF0ZS5wcmljZXNCeVN5bWJvbFtzeW1ib2xdO1xuICB9XG5cbiAgLyoqXG4gICAqIFF1b3RlIHRpbWVcbiAgICogQHR5cGRlZiB7T2JqZWN0fSBRdW90ZVRpbWVcbiAgICogQHByb3BlcnR5IHtEYXRlfSB0aW1lIHF1b3RlIHRpbWVcbiAgICogQHByb3BlcnR5IHtTdHJpbmd9IGJyb2tlclRpbWUgcXVvdGUgdGltZSBpbiBicm9rZXIgdGltZXpvbmUsIFlZWVktTU0tREQgSEg6bW06c3MuU1NTIGZvcm1hdFxuICAgKi9cblxuICAvKipcbiAgICogUmV0dXJucyB0aW1lIG9mIHRoZSBsYXN0IHJlY2VpdmVkIHF1b3RlXG4gICAqIEByZXR1cm4ge1F1b3RlVGltZX0gdGltZSBvZiB0aGUgbGFzdCByZWNlaXZlZCBxdW90ZVxuICAgKi9cbiAgZ2V0IGxhc3RRdW90ZVRpbWUoKSB7XG4gICAgaWYgKHRoaXMuX2NvbWJpbmVkU3RhdGUubGFzdFF1b3RlVGltZSkge1xuICAgICAgcmV0dXJuIHtcbiAgICAgICAgdGltZTogdGhpcy5fY29tYmluZWRTdGF0ZS5sYXN0UXVvdGVUaW1lLFxuICAgICAgICBicm9rZXJUaW1lOiB0aGlzLl9jb21iaW5lZFN0YXRlLmxhc3RRdW90ZUJyb2tlclRpbWUsXG4gICAgICB9O1xuICAgIH0gZWxzZSB7XG4gICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBXYWl0cyBmb3IgcHJpY2UgdG8gYmUgcmVjZWl2ZWRcbiAgICogQHBhcmFtIHtzdHJpbmd9IHN5bWJvbCBzeW1ib2wgKGUuZy4gY3VycmVuY3kgcGFpciBvciBhbiBpbmRleClcbiAgICogQHBhcmFtIHtudW1iZXJ9IFt0aW1lb3V0SW5TZWNvbmRzXSB0aW1lb3V0IGluIHNlY29uZHMsIGRlZmF1bHQgaXMgMzBcbiAgICogQHJldHVybiB7UHJvbWlzZTxNZXRhdHJhZGVyU3ltYm9sUHJpY2U+fSBwcm9taXNlIHJlc29sdmluZyB3aXRoIHByaWNlIG9yIHVuZGVmaW5lZCBpZiBwcmljZSBoYXMgbm90IGJlZW4gcmVjZWl2ZWRcbiAgICovXG4gIGFzeW5jIHdhaXRGb3JQcmljZShzeW1ib2wsIHRpbWVvdXRJblNlY29uZHMgPSAzMCkge1xuICAgIHRoaXMuX3dhaXRGb3JQcmljZVJlc29sdmVzW3N5bWJvbF0gPSB0aGlzLl93YWl0Rm9yUHJpY2VSZXNvbHZlc1tzeW1ib2xdIHx8IFtdO1xuICAgIGlmICghdGhpcy5wcmljZShzeW1ib2wpKSB7XG4gICAgICBhd2FpdCBQcm9taXNlLnJhY2UoW1xuICAgICAgICBuZXcgUHJvbWlzZShyZXMgPT4gdGhpcy5fd2FpdEZvclByaWNlUmVzb2x2ZXNbc3ltYm9sXS5wdXNoKHJlcykpLFxuICAgICAgICBuZXcgUHJvbWlzZShyZXMgPT4gc2V0VGltZW91dChyZXMsIHRpbWVvdXRJblNlY29uZHMgKiAxMDAwKSlcbiAgICAgIF0pO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5wcmljZShzeW1ib2wpO1xuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBjb25uZWN0aW9uIHRvIE1ldGFUcmFkZXIgdGVybWluYWwgZXN0YWJsaXNoZWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGluc3RhbmNlSW5kZXggaW5kZXggb2YgYW4gYWNjb3VudCBpbnN0YW5jZSBjb25uZWN0ZWRcbiAgICovXG4gIG9uQ29ubmVjdGVkKGluc3RhbmNlSW5kZXgpIHtcbiAgICB0aGlzLl9nZXRTdGF0ZShpbnN0YW5jZUluZGV4KS5jb25uZWN0ZWQgPSB0cnVlO1xuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBjb25uZWN0aW9uIHRvIE1ldGFUcmFkZXIgdGVybWluYWwgdGVybWluYXRlZFxuICAgKiBAcGFyYW0ge1N0cmluZ30gaW5zdGFuY2VJbmRleCBpbmRleCBvZiBhbiBhY2NvdW50IGluc3RhbmNlIGNvbm5lY3RlZFxuICAgKi9cbiAgb25EaXNjb25uZWN0ZWQoaW5zdGFuY2VJbmRleCkge1xuICAgIGxldCBzdGF0ZSA9IHRoaXMuX2dldFN0YXRlKGluc3RhbmNlSW5kZXgpO1xuICAgIHN0YXRlLmNvbm5lY3RlZCA9IGZhbHNlO1xuICAgIHN0YXRlLmNvbm5lY3RlZFRvQnJva2VyID0gZmFsc2U7XG4gIH1cblxuICAvKipcbiAgICogSW52b2tlZCB3aGVuIGJyb2tlciBjb25uZWN0aW9uIHN0YXR1cyBoYXZlIGNoYW5nZWRcbiAgICogQHBhcmFtIHtTdHJpbmd9IGluc3RhbmNlSW5kZXggaW5kZXggb2YgYW4gYWNjb3VudCBpbnN0YW5jZSBjb25uZWN0ZWRcbiAgICogQHBhcmFtIHtCb29sZWFufSBjb25uZWN0ZWQgaXMgTWV0YVRyYWRlciB0ZXJtaW5hbCBpcyBjb25uZWN0ZWQgdG8gYnJva2VyXG4gICAqL1xuICBvbkJyb2tlckNvbm5lY3Rpb25TdGF0dXNDaGFuZ2VkKGluc3RhbmNlSW5kZXgsIGNvbm5lY3RlZCkge1xuICAgIHRoaXMuX2NvbWJpbmVkU3RhdGUubGFzdFN0YXR1c1RpbWUgPSBEYXRlLm5vdygpO1xuICAgIHRoaXMuX2dldFN0YXRlKGluc3RhbmNlSW5kZXgpLmNvbm5lY3RlZFRvQnJva2VyID0gY29ubmVjdGVkO1xuICB9XG5cbiAgLyoqXG4gICAqIEludm9rZWQgd2hlbiBNZXRhVHJhZGVyIHRlcm1pbmFsIHN0YXRlIHN5bmNocm9uaXphdGlvbiBpcyBzdGFydGVkXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBpbnN0YW5jZUluZGV4IGluZGV4IG9mIGFuIGFjY291bnQgaW5zdGFuY2UgY29ubmVjdGVkXG4gICAqIEBwYXJhbSB7c3RyaW5nfSBzcGVjaWZpY2F0aW9uc0hhc2ggc3BlY2lmaWNhdGlvbnMgaGFzaFxuICAgKiBAcGFyYW0ge3N0cmluZ30gcG9zaXRpb25zSGFzaCBwb3NpdGlvbnMgaGFzaFxuICAgKiBAcGFyYW0ge3N0cmluZ30gb3JkZXJzSGFzaCBvcmRlcnMgaGFzaFxuICAgKiBAcGFyYW0ge3N0cmluZ30gc3luY2hyb25pemF0aW9uSWQgc3luY2hyb25pemF0aW9uIGlkXG4gICAqIEByZXR1cm4ge1Byb21pc2V9IHByb21pc2Ugd2hpY2ggcmVzb2x2ZXMgd2hlbiB0aGUgYXN5bmNocm9ub3VzIGV2ZW50IGlzIHByb2Nlc3NlZFxuICAgKi9cbiAgb25TeW5jaHJvbml6YXRpb25TdGFydGVkKGluc3RhbmNlSW5kZXgsIHNwZWNpZmljYXRpb25zSGFzaCwgcG9zaXRpb25zSGFzaCwgb3JkZXJzSGFzaCwgc3luY2hyb25pemF0aW9uSWQpIHtcbiAgICBjb25zdCB1bnN5bmNocm9uaXplZFN0YXRlcyA9IHRoaXMuX2dldFN0YXRlSW5kaWNlc09mU2FtZUluc3RhbmNlTnVtYmVyKGluc3RhbmNlSW5kZXgpXG4gICAgICAuZmlsdGVyKHN0YXRlSW5kZXggPT4gIXRoaXMuX3N0YXRlQnlJbnN0YW5jZUluZGV4W3N0YXRlSW5kZXhdLm9yZGVyc0luaXRpYWxpemVkKTtcbiAgICB1bnN5bmNocm9uaXplZFN0YXRlcy5zb3J0KChhLGIpID0+IGIubGFzdFN5bmNVcGRhdGVUaW1lIC0gYS5sYXN0U3luY1VwZGF0ZVRpbWUpO1xuICAgIHVuc3luY2hyb25pemVkU3RhdGVzLnNsaWNlKDEpLmZvckVhY2goc3RhdGVJbmRleCA9PiB0aGlzLl9yZW1vdmVTdGF0ZShzdGF0ZUluZGV4KSk7XG5cbiAgICBsZXQgc3RhdGUgPSB0aGlzLl9nZXRTdGF0ZShpbnN0YW5jZUluZGV4KTtcbiAgICBzdGF0ZS5pc1NwZWNpZmljYXRpb25zRXhwZWN0ZWQgPSAhc3BlY2lmaWNhdGlvbnNIYXNoO1xuICAgIHN0YXRlLmlzUG9zaXRpb25zRXhwZWN0ZWQgPSAhcG9zaXRpb25zSGFzaDtcbiAgICBzdGF0ZS5pc09yZGVyc0V4cGVjdGVkID0gIW9yZGVyc0hhc2g7XG4gICAgc3RhdGUubGFzdFN5bmNVcGRhdGVUaW1lID0gRGF0ZS5ub3coKTtcbiAgICBzdGF0ZS5hY2NvdW50SW5mb3JtYXRpb24gPSB1bmRlZmluZWQ7XG4g