UNPKG

@fraserdarwent/xapi-node

Version:

This project is made possible to get data from Forex market, execute market or limit order with NodeJS/JS through WebSocket connection

245 lines 10.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Queue = void 0; const Listener_1 = require("../modules/Listener"); const __1 = require(".."); const Log_1 = require("../utils/Log"); const Enum_1 = require("../enum/Enum"); class Queue extends Listener_1.Listener { constructor(rateLimit, type) { super(); this._status = Enum_1.ConnectionStatus.DISCONNECTED; this.transactions = {}; this.lastReceivedMessage = null; this.messageQueues = { urgent: [], normal: [] }; this._transactionIdIncrement = 0; this.messagesElapsedTime = []; this.messageSender = new __1.Timer(); this.openTimeout = new __1.Timer(); this.rateLimit = rateLimit; this.type = type; } get status() { return this._status; } set status(status) { if (this._status !== status) { this._status = status; this.callListener(Enum_1.Listeners.xapi_onConnectionChange, [status]); } } get queueSize() { return this.messageQueues.urgent.length + this.messageQueues.normal.length; } stopTimer() { this.openTimeout.clear(); } addQueu(transaction) { const { urgent, transactionId } = transaction; if (this.queueSize >= 150) { this.rejectTransaction({ code: Enum_1.errorCode.XAPINODE_2, explain: 'messageQueues exceeded 150 size limit' }, transaction); } else { if (urgent) { this.messageQueues.urgent.push({ transactionId }); } else { this.messageQueues.normal.push({ transactionId }); } Log_1.Log.hidden((this.type === Enum_1.TransactionType.STREAM ? ' Stream' : 'Socket') + ' (' + transaction.transactionId + '): ' + transaction.command + ' added to queue (messages in queue = ' + this.queueSize + ')', 'INFO'); } } addElapsedTime(time) { if (this.messagesElapsedTime.length > 4) { this.messagesElapsedTime = [...this.messagesElapsedTime.slice(1, 5), time]; } else { this.messagesElapsedTime.push(time); } } isRateLimitReached() { return this.messagesElapsedTime.length < 4 ? false : this.messagesElapsedTime[this.messagesElapsedTime.length - 4].elapsedMs() < this.rateLimit; } resetMessageTube() { if (this.queueSize > 0) { Log_1.Log.info((this.type === Enum_1.TransactionType.STREAM ? ' Stream' : 'Socket') + ' Message queue reseted, deleted = ' + this.queueSize); } this.messageQueues = { urgent: [], normal: [] }; this.messagesElapsedTime = []; this.messageSender.clear(); } createTransactionId() { this._transactionIdIncrement += 1; if (this._transactionIdIncrement > 9999) { this._transactionIdIncrement = 0; } return __1.Utils.getUTCTimestampString() + __1.Utils.formatNumber(this._transactionIdIncrement, 4) + (this.type === Enum_1.TransactionType.SOCKET ? '0' : '1'); } addTransaction(newTransaction) { return this.transactions[newTransaction.transactionId] = { command: newTransaction.command, type: this.type, request: { json: newTransaction.json, arguments: newTransaction.args, sent: null }, response: { json: null, received: null, status: null }, transactionId: newTransaction.transactionId, createdAt: new __1.Time(), status: Enum_1.TransactionStatus.waiting, transactionPromise: { resolve: newTransaction.resolve, reject: newTransaction.reject }, urgent: newTransaction.urgent }; } rejectOldTransactions() { Object.values(this.transactions) .filter(t => t.transactionPromise.reject !== null && t.createdAt.elapsedMs() > 60000) .forEach(transaction => { this.rejectTransaction({ code: Enum_1.errorCode.XAPINODE_3, explain: 'Timeout' }, transaction); }); } removeOldTransactions() { let removed = 0; Object.values(this.transactions) .filter(t => t.transactionPromise.reject === null && t.transactionPromise.resolve === null && t.createdAt.elapsedMs() > 86400000) .forEach(transaction => { delete this.transactions[transaction.transactionId]; removed += 1; }); return { removed }; } sendJSON(json) { try { const time = new __1.Time(); this.WebSocket.send(json); return time; } catch (e) { Log_1.Log.error(e.toString()); return null; } } resolveTransaction(returnData, time, transaction) { if (this.type === Enum_1.TransactionType.SOCKET) { transaction.response = { status: true, received: time, json: returnData }; } transaction.status = Enum_1.TransactionStatus.successful; const { resolve } = transaction.transactionPromise; if (resolve !== null) { transaction.transactionPromise = { resolve: null, reject: null }; if (transaction.type === Enum_1.TransactionType.STREAM) { Log_1.Log.hidden(' Stream (' + transaction.transactionId + '): ' + transaction.command + ', ' + JSON.stringify(transaction.request.arguments), 'INFO'); resolve({ transaction }); } else if (transaction.request.sent !== null) { const elapsedMs = transaction.response.received !== null && transaction.response.received.getDifference(transaction.request.sent); Log_1.Log.hidden('Socket (' + transaction.transactionId + '): ' + transaction.command + ', ' + (transaction.command === 'login' ? '(arguments contains secret information)' : JSON.stringify(transaction.request.arguments)) + ', (' + elapsedMs + 'ms)', 'INFO'); resolve({ returnData, time, transaction }); } } if (transaction.command !== 'ping') { Log_1.Log.hidden('Transaction archived:\n' + __1.Utils.transactionToJSONString(transaction), 'INFO', 'Transactions'); } } rejectTransaction(json, transaction, interrupted = false, received = new __1.Time()) { transaction.status = interrupted ? Enum_1.TransactionStatus.interrupted : Enum_1.TransactionStatus.timeout; transaction.response = { status: false, received, json }; Log_1.Log.hidden(transaction.type + ' message rejected (' + transaction.transactionId + '): ' + transaction.command + ', ' + (transaction.command === 'login' ? '(arguments contains secret information)' : JSON.stringify(transaction.request.arguments)) + '\nReason:\n' + JSON.stringify(json, null, '\t'), 'ERROR'); const { reject } = transaction.transactionPromise; if (reject !== null) { transaction.transactionPromise = { resolve: null, reject: null }; reject({ reason: json, transaction: transaction.command === 'login' ? __1.Utils.hideSecretInfo(transaction) : transaction }); } Log_1.Log.hidden('Transaction archived:\n' + __1.Utils.transactionToJSONString(transaction), 'INFO', 'Transactions'); } sendMessage(transaction, addQueu) { if (!this.isRateLimitReached()) { if (this.queueSize === 0 || !addQueu) { const sentTime = this.sendJSON(transaction.request.json); if (sentTime !== null) { this.addElapsedTime(sentTime); transaction.request.sent = sentTime; transaction.status = (transaction.type === Enum_1.TransactionType.STREAM) ? Enum_1.TransactionStatus.successful : Enum_1.TransactionStatus.sent; if (transaction.type === Enum_1.TransactionType.STREAM) { this.resolveTransaction(null, new __1.Time(), transaction); } return true; } } } if (addQueu) { this.addQueu(transaction); } if (this.queueSize > 0 && this.messageSender.isNull()) { this.callCleanQueuTimeout(); } return false; } callCleanQueuTimeout() { if (this.messagesElapsedTime.length <= 3) { this.tryCleanQueue(); } else { const elapsedMs = this.messagesElapsedTime[this.messagesElapsedTime.length - 4].elapsedMs(); const timeoutMs = Math.max(this.rateLimit - elapsedMs, 0); this.messageSender.setTimeout(() => { this.tryCleanQueue(); }, timeoutMs); } } tryCleanQueue() { while (this.queueSize > 0) { const urgent = this.messageQueues.urgent.length > 0; const { transactionId } = urgent ? this.messageQueues.urgent[0] : this.messageQueues.normal[0]; if (this.transactions[transactionId].status === Enum_1.TransactionStatus.waiting) { const isSent = this.sendMessage(this.transactions[transactionId], false); if (!isSent) { return; } } if (urgent) { this.messageQueues.urgent.shift(); } else { this.messageQueues.normal.shift(); } } } isQueueContains(command) { return this.messageQueues.urgent.some(id => { return this.transactions[id.transactionId].command === command; }) || this.messageQueues.normal.some(id => { return this.transactions[id.transactionId].command === command; }); } } exports.Queue = Queue; //# sourceMappingURL=Queue.js.map