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