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
text/typescript
'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];
}
}