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)

169 lines (154 loc) 6.42 kB
'use strict'; import LoggerManager from '../logger'; import MetaApiConnection from './metaApiConnection'; import TimeoutError from '../clients/timeoutError'; /** * Exposes MetaApi MetaTrader RPC API connection to consumers */ export default class RpcMetaApiConnection extends MetaApiConnection { private _openedInstances: any[]; /** * Constructs MetaApi MetaTrader RPC Api connection * @param {MetaApiOpts} options MetaApi options * @param {MetaApiWebsocketClient} websocketClient MetaApi websocket client * @param {MetatraderAccount} account MetaTrader account id to connect to * @param {ConnectionRegistry} connectionRegistry metatrader account connection registry */ constructor(options, websocketClient, account, connectionRegistry) { super(options, websocketClient, account, 'RPC'); this._connectionRegistry = connectionRegistry; this._websocketClient.addSynchronizationListener(account.id, this); this._stateByInstanceIndex = {}; this._openedInstances = []; Object.values(account.accountRegions) .forEach(replicaId => this._websocketClient.addReconnectListener(this, replicaId)); this._logger = LoggerManager.getLogger('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 */ async connect(instanceId) { if (!this._openedInstances.includes(instanceId)) { this._openedInstances.push(instanceId); } if (!this._opened) { this._opened = true; const accountRegions = this._account.accountRegions; this._websocketClient.addAccountCache(this._account.id, accountRegions); Object.keys(accountRegions).forEach(region => { if (!this._options.region || this._options.region === region) { this._websocketClient.ensureSubscribe(accountRegions[region], 0); this._websocketClient.ensureSubscribe(accountRegions[region], 1); } }); } } /** * Closes the connection. The instance of the class should no longer be used after this method is invoked. * @param {string} instanceId connection instance id */ async close(instanceId) { if (this._opened) { this._openedInstances = this._openedInstances.filter(id => id !== instanceId); if (!this._openedInstances.length && !this._closed) { await this._connectionRegistry.removeRpc(this.account); this._websocketClient.removeSynchronizationListener(this.account.id, this); this._websocketClient.removeAccountCache(this.account.id); this._websocketClient.removeReconnectListener(this); this._closed = true; } } } /** * Invoked when connection to MetaTrader terminal established * @param {String} instanceIndex index of an account instance connected * @param {Number} replicas number of account replicas launched * @return {Promise} promise which resolves when the asynchronous event is processed */ async onConnected(instanceIndex, replicas) { const state = this._getState(instanceIndex); state.synchronized = true; const region = this.getRegion(instanceIndex); this.cancelRefresh(region); } /** * Invoked when connection to MetaTrader terminal terminated * @param {String} instanceIndex index of an account instance connected * @return {Promise} promise which resolves when the asynchronous event is processed */ async onDisconnected(instanceIndex) { const state = this._getState(instanceIndex); state.synchronized = false; this._logger.debug(`${this._account.id}:${instanceIndex}: disconnected from broker`); } /** * Invoked when a stream for an instance index is closed * @param {String} instanceIndex index of an account instance connected */ async onStreamClosed(instanceIndex) { delete this._stateByInstanceIndex[instanceIndex]; } /** * Returns flag indicating status of state synchronization with MetaTrader terminal * @returns {Boolean} a flag indicating status of state synchronization with MetaTrader terminal */ isSynchronized() { return Object.values<any>(this._stateByInstanceIndex) .map(instance => instance.synchronized) .includes(true); } /** * Waits until synchronization to RPC application is completed * @param {Number} timeoutInSeconds synchronization timeout in seconds. Defaults to 5 minutes * @return {Promise} promise which resolves when synchronization to RPC application is completed * @throws {TimeoutError} if application failed to synchronize with the teminal within timeout allowed */ async waitSynchronized(timeoutInSeconds=300) { this._checkIsConnectionActive(); const startTime = Date.now(); let synchronized = this.isSynchronized(); while (!synchronized && startTime + timeoutInSeconds * 1000 > Date.now()) { await new Promise(res => setTimeout(res, 1000)); synchronized = this.isSynchronized(); } if (!synchronized) { throw new TimeoutError('Timed out waiting for MetaApi to synchronize to MetaTrader account ' + this._account.id); } // eslint-disable-next-line while (true) { try { await this._websocketClient.waitSynchronized(this._account.id, undefined, 'RPC', 5, 'RPC'); break; } catch (err) { if (Date.now() > startTime + timeoutInSeconds * 1000) { throw err; } } } } /** * Invoked when connection to MetaApi websocket API restored after a disconnect * @param {String} region reconnected region * @param {Number} instanceNumber reconnected instance number * @return {Promise} promise which resolves when connection to MetaApi websocket API restored after a disconnect */ async onReconnected(region, instanceNumber) { const instanceTemplate = `${region}:${instanceNumber}`; Object.keys(this._stateByInstanceIndex) .filter(key => key.startsWith(`${instanceTemplate}:`)).forEach(key => { delete this._stateByInstanceIndex[key]; }); } _getState(instanceIndex) { if (!this._stateByInstanceIndex[instanceIndex]) { this._stateByInstanceIndex[instanceIndex] = { instanceIndex, synchronized: false, }; } return this._stateByInstanceIndex[instanceIndex]; } }