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