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)
271 lines (270 loc) • 32.1 kB
JavaScript
;
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _async_to_generator(fn) {
return function() {
var self = this, args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
import fs from 'fs-extra';
import moment from 'moment';
import OptionsValidator from '../optionsValidator';
import LoggerManager from '../../logger';
let PacketLogger = class PacketLogger {
_ensurePreviousPriceObject(accountId) {
if (!this._previousPrices[accountId]) {
this._previousPrices[accountId] = {};
}
}
/**
* Processes packets and pushes them into save queue
* @param {Object} packet packet to log
*/ // eslint-disable-next-line complexity
logPacket(packet) {
const instanceIndex = packet.instanceIndex || 0;
if (!this._writeQueue[packet.accountId]) {
this._writeQueue[packet.accountId] = {
isWriting: false,
queue: []
};
}
if (packet.type === 'status') {
return;
}
if (!this._lastSNPacket[packet.accountId]) {
this._lastSNPacket[packet.accountId] = {};
}
if ([
'keepalive',
'noop'
].includes(packet.type)) {
this._lastSNPacket[packet.accountId][instanceIndex] = packet;
return;
}
const queue = this._writeQueue[packet.accountId].queue;
if (!this._previousPrices[packet.accountId]) {
this._previousPrices[packet.accountId] = {};
}
const prevPrice = this._previousPrices[packet.accountId][instanceIndex];
if (packet.type !== 'prices') {
if (prevPrice) {
this._recordPrices(packet.accountId, instanceIndex);
}
if (packet.type === 'specifications' && this._compressSpecifications) {
queue.push(JSON.stringify({
type: packet.type,
sequenceNumber: packet.sequenceNumber,
sequenceTimestamp: packet.sequenceTimestamp,
instanceIndex
}));
} else {
queue.push(JSON.stringify(packet));
}
} else {
if (!this._compressPrices) {
queue.push(JSON.stringify(packet));
} else {
if (prevPrice) {
const validSequenceNumbers = [
prevPrice.last.sequenceNumber,
prevPrice.last.sequenceNumber + 1
];
if (this._lastSNPacket[packet.accountId][instanceIndex]) {
validSequenceNumbers.push(this._lastSNPacket[packet.accountId][instanceIndex].sequenceNumber + 1);
}
if (!validSequenceNumbers.includes(packet.sequenceNumber)) {
this._recordPrices(packet.accountId, instanceIndex);
this._ensurePreviousPriceObject(packet.accountId);
this._previousPrices[packet.accountId][instanceIndex] = {
first: packet,
last: packet
};
queue.push(JSON.stringify(packet));
} else {
this._previousPrices[packet.accountId][instanceIndex].last = packet;
}
} else {
this._ensurePreviousPriceObject(packet.accountId);
this._previousPrices[packet.accountId][instanceIndex] = {
first: packet,
last: packet
};
queue.push(JSON.stringify(packet));
}
}
}
}
/**
* Returns log messages within date bounds as an array of objects
* @param {String} accountId account id
* @param {Date} dateAfter date to get logs after
* @param {Date} dateBefore date to get logs before
* @returns {Array<Object>} log messages
*/ readLogs(accountId, dateAfter, dateBefore) {
var _this = this;
return _async_to_generator(function*() {
const folders = yield fs.readdir(_this._root);
const packets = [];
for (let folder of folders){
const filePath = `${_this._root}/${folder}/${accountId}.log`;
if (yield fs.pathExists(filePath)) {
const contents = yield fs.readFile(filePath, 'utf8');
let messages = contents.split('\r\n').filter((message)=>message.length).map((message)=>{
return {
date: new Date(message.slice(1, 24)),
message: message.slice(26)
};
});
if (dateAfter) {
messages = messages.filter((message)=>message.date > dateAfter);
}
if (dateBefore) {
messages = messages.filter((message)=>message.date < dateBefore);
}
packets.push(...messages);
}
}
return packets;
})();
}
/**
* Returns path for account log file
* @param {String} accountId account id
* @returns {String} file path
*/ getFilePath(accountId) {
var _this = this;
return _async_to_generator(function*() {
const fileIndex = Math.floor(new Date().getHours() / _this._logFileSizeInHours);
const folderName = `${moment().format('YYYY-MM-DD')}-${fileIndex > 9 ? fileIndex : '0' + fileIndex}`;
yield fs.ensureDir(`${_this._root}/${folderName}`);
return `${_this._root}/${folderName}/${accountId}.log`;
})();
}
/**
* Initializes the packet logger
*/ start() {
this._previousPrices = {};
if (!this._recordInteval) {
this._recordInteval = setInterval(()=>this._appendLogs(), 1000);
this._deleteOldLogsInterval = setInterval(()=>this._deleteOldData(), 10000);
}
}
/**
* Deinitializes the packet logger
*/ stop() {
clearInterval(this._recordInteval);
clearInterval(this._deleteOldLogsInterval);
}
/**
* Records price packet messages to log files
* @param {String} accountId account id
*/ _recordPrices(accountId, instanceNumber) {
const prevPrice = this._previousPrices[accountId][instanceNumber] || {
first: {},
last: {}
};
const queue = this._writeQueue[accountId].queue;
delete this._previousPrices[accountId][instanceNumber];
if (!Object.keys(this._previousPrices[accountId]).length) {
delete this._previousPrices[accountId];
}
if (prevPrice.first.sequenceNumber !== prevPrice.last.sequenceNumber) {
queue.push(JSON.stringify(prevPrice.last));
queue.push(`Recorded price packets ${prevPrice.first.sequenceNumber}` + `-${prevPrice.last.sequenceNumber}, instanceIndex: ${instanceNumber}`);
}
}
/**
* Writes logs to files
*/ _appendLogs() {
var _this = this;
return _async_to_generator(function*() {
Object.keys(_this._writeQueue).forEach(function() {
var _ref = _async_to_generator(function*(accountId) {
const queue = _this._writeQueue[accountId];
if (!queue.isWriting && queue.queue.length) {
queue.isWriting = true;
try {
const filePath = yield _this.getFilePath(accountId);
const writeString = queue.queue.reduce((a, b)=>a + `[${moment().format('YYYY-MM-DD HH:mm:ss.SSS')}] ${b}\r\n`, '');
queue.queue = [];
yield fs.appendFile(filePath, writeString);
} catch (err) {
_this._logger.error(`${accountId}: Failed to record packet log`, err);
}
queue.isWriting = false;
}
});
return function(accountId) {
return _ref.apply(this, arguments);
};
}());
})();
}
/**
* Deletes folders when the folder limit is exceeded
*/ _deleteOldData() {
var _this = this;
return _async_to_generator(function*() {
const contents = yield fs.readdir(_this._root);
contents.reverse().slice(_this._fileNumberLimit).forEach(function() {
var _ref = _async_to_generator(function*(folderName) {
yield fs.remove(`${_this._root}/${folderName}`);
});
return function(folderName) {
return _ref.apply(this, arguments);
};
}());
})();
}
/**
* Constructs the class
* @param {PacketLoggerOpts} opts packet logger options
*/ constructor(opts){
const validator = new OptionsValidator();
opts = opts || {};
this._fileNumberLimit = validator.validateNonZero(opts.fileNumberLimit, 12, 'packetLogger.fileNumberLimit');
this._logFileSizeInHours = validator.validateNonZero(opts.logFileSizeInHours, 4, 'packetLogger.logFileSizeInHours');
this._compressSpecifications = validator.validateBoolean(opts.compressSpecifications, true, 'packetLogger.compressSpecifications');
this._compressPrices = validator.validateBoolean(opts.compressPrices, true, 'packetLogger.compressPrices');
this._previousPrices = {};
this._lastSNPacket = {};
this._writeQueue = {};
this._root = './.metaapi/logs';
this._logger = LoggerManager.getLogger('PacketLogger');
fs.ensureDir(this._root);
}
};
/**
* Packet logger options
* @typedef {Object} PacketLoggerOpts
* @property {Boolean} [enabled] whether packet logger is enabled
* @property {Number} [fileNumberLimit] maximum amount of files per account, default value is 12
* @property {Number} [logFileSizeInHours] amount of logged hours per account file, default value is 4
* @property {Boolean} [compressSpecifications] whether to compress specifications packets, default value is true
* @property {Boolean} [compressPrices] whether to compress specifications packets, default value is true
*/ /**
* A class which records packets into log files
*/ export { PacketLogger as default };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjxhbm9uPiJdLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XG5pbXBvcnQgZnMgZnJvbSAnZnMtZXh0cmEnO1xuaW1wb3J0IG1vbWVudCBmcm9tICdtb21lbnQnO1xuaW1wb3J0IE9wdGlvbnNWYWxpZGF0b3IgZnJvbSAnLi4vb3B0aW9uc1ZhbGlkYXRvcic7XG5pbXBvcnQgTG9nZ2VyTWFuYWdlciBmcm9tICcuLi8uLi9sb2dnZXInO1xuXG4vKipcbiAqIFBhY2tldCBsb2dnZXIgb3B0aW9uc1xuICogQHR5cGVkZWYge09iamVjdH0gUGFja2V0TG9nZ2VyT3B0c1xuICogQHByb3BlcnR5IHtCb29sZWFufSBbZW5hYmxlZF0gd2hldGhlciBwYWNrZXQgbG9nZ2VyIGlzIGVuYWJsZWRcbiAqIEBwcm9wZXJ0eSB7TnVtYmVyfSBbZmlsZU51bWJlckxpbWl0XSBtYXhpbXVtIGFtb3VudCBvZiBmaWxlcyBwZXIgYWNjb3VudCwgZGVmYXVsdCB2YWx1ZSBpcyAxMlxuICogQHByb3BlcnR5IHtOdW1iZXJ9IFtsb2dGaWxlU2l6ZUluSG91cnNdIGFtb3VudCBvZiBsb2dnZWQgaG91cnMgcGVyIGFjY291bnQgZmlsZSwgZGVmYXVsdCB2YWx1ZSBpcyA0XG4gKiBAcHJvcGVydHkge0Jvb2xlYW59IFtjb21wcmVzc1NwZWNpZmljYXRpb25zXSB3aGV0aGVyIHRvIGNvbXByZXNzIHNwZWNpZmljYXRpb25zIHBhY2tldHMsIGRlZmF1bHQgdmFsdWUgaXMgdHJ1ZVxuICogQHByb3BlcnR5IHtCb29sZWFufSBbY29tcHJlc3NQcmljZXNdIHdoZXRoZXIgdG8gY29tcHJlc3Mgc3BlY2lmaWNhdGlvbnMgcGFja2V0cywgZGVmYXVsdCB2YWx1ZSBpcyB0cnVlXG4gKi9cblxuLyoqXG4gKiBBIGNsYXNzIHdoaWNoIHJlY29yZHMgcGFja2V0cyBpbnRvIGxvZyBmaWxlc1xuICovXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBQYWNrZXRMb2dnZXIge1xuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3RzIHRoZSBjbGFzc1xuICAgKiBAcGFyYW0ge1BhY2tldExvZ2dlck9wdHN9IG9wdHMgcGFja2V0IGxvZ2dlciBvcHRpb25zXG4gICAqL1xuICBjb25zdHJ1Y3RvcihvcHRzKSB7XG4gICAgY29uc3QgdmFsaWRhdG9yID0gbmV3IE9wdGlvbnNWYWxpZGF0b3IoKTtcbiAgICBvcHRzID0gb3B0cyB8fCB7fTtcbiAgICB0aGlzLl9maWxlTnVtYmVyTGltaXQgPSB2YWxpZGF0b3IudmFsaWRhdGVOb25aZXJvKG9wdHMuZmlsZU51bWJlckxpbWl0LCAxMiwgJ3BhY2tldExvZ2dlci5maWxlTnVtYmVyTGltaXQnKTtcbiAgICB0aGlzLl9sb2dGaWxlU2l6ZUluSG91cnMgPSB2YWxpZGF0b3IudmFsaWRhdGVOb25aZXJvKG9wdHMubG9nRmlsZVNpemVJbkhvdXJzLCA0LFxuICAgICAgJ3BhY2tldExvZ2dlci5sb2dGaWxlU2l6ZUluSG91cnMnKTtcbiAgICB0aGlzLl9jb21wcmVzc1NwZWNpZmljYXRpb25zID0gdmFsaWRhdG9yLnZhbGlkYXRlQm9vbGVhbihvcHRzLmNvbXByZXNzU3BlY2lmaWNhdGlvbnMsIHRydWUsXG4gICAgICAncGFja2V0TG9nZ2VyLmNvbXByZXNzU3BlY2lmaWNhdGlvbnMnKTtcbiAgICB0aGlzLl9jb21wcmVzc1ByaWNlcyA9IHZhbGlkYXRvci52YWxpZGF0ZUJvb2xlYW4ob3B0cy5jb21wcmVzc1ByaWNlcywgdHJ1ZSwgJ3BhY2tldExvZ2dlci5jb21wcmVzc1ByaWNlcycpO1xuICAgIHRoaXMuX3ByZXZpb3VzUHJpY2VzID0ge307XG4gICAgdGhpcy5fbGFzdFNOUGFja2V0ID0ge307XG4gICAgdGhpcy5fd3JpdGVRdWV1ZSA9IHt9O1xuICAgIHRoaXMuX3Jvb3QgPSAnLi8ubWV0YWFwaS9sb2dzJztcbiAgICB0aGlzLl9sb2dnZXIgPSBMb2dnZXJNYW5hZ2VyLmdldExvZ2dlcignUGFja2V0TG9nZ2VyJyk7XG4gICAgZnMuZW5zdXJlRGlyKHRoaXMuX3Jvb3QpO1xuICB9XG5cbiAgX2Vuc3VyZVByZXZpb3VzUHJpY2VPYmplY3QoYWNjb3VudElkKSB7XG4gICAgaWYoIXRoaXMuX3ByZXZpb3VzUHJpY2VzW2FjY291bnRJZF0pIHtcbiAgICAgIHRoaXMuX3ByZXZpb3VzUHJpY2VzW2FjY291bnRJZF0gPSB7fTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogUHJvY2Vzc2VzIHBhY2tldHMgYW5kIHB1c2hlcyB0aGVtIGludG8gc2F2ZSBxdWV1ZVxuICAgKiBAcGFyYW0ge09iamVjdH0gcGFja2V0IHBhY2tldCB0byBsb2dcbiAgICovXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBjb21wbGV4aXR5XG4gIGxvZ1BhY2tldChwYWNrZXQpIHtcbiAgICBjb25zdCBpbnN0YW5jZUluZGV4ID0gcGFja2V0Lmluc3RhbmNlSW5kZXggfHwgMDtcbiAgICBpZighdGhpcy5fd3JpdGVRdWV1ZVtwYWNrZXQuYWNjb3VudElkXSkge1xuICAgICAgdGhpcy5fd3JpdGVRdWV1ZVtwYWNrZXQuYWNjb3VudElkXSA9IHtpc1dyaXRpbmc6IGZhbHNlLCBxdWV1ZTogW119O1xuICAgIH1cbiAgICBpZihwYWNrZXQudHlwZSA9PT0gJ3N0YXR1cycpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYoIXRoaXMuX2xhc3RTTlBhY2tldFtwYWNrZXQuYWNjb3VudElkXSkge1xuICAgICAgdGhpcy5fbGFzdFNOUGFja2V0W3BhY2tldC5hY2NvdW50SWRdID0ge307XG4gICAgfVxuICAgIGlmKFsna2VlcGFsaXZlJywgJ25vb3AnXS5pbmNsdWRlcyhwYWNrZXQudHlwZSkpIHtcbiAgICAgIHRoaXMuX2xhc3RTTlBhY2tldFtwYWNrZXQuYWNjb3VudElkXVtpbnN0YW5jZUluZGV4XSA9IHBhY2tldDtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3QgcXVldWUgPSB0aGlzLl93cml0ZVF1ZXVlW3BhY2tldC5hY2NvdW50SWRdLnF1ZXVlO1xuICAgIGlmKCF0aGlzLl9wcmV2aW91c1ByaWNlc1twYWNrZXQuYWNjb3VudElkXSkge1xuICAgICAgdGhpcy5fcHJldmlvdXNQcmljZXNbcGFja2V0LmFjY291bnRJZF0gPSB7fTtcbiAgICB9XG4gICAgXG4gICAgY29uc3QgcHJldlByaWNlID0gdGhpcy5fcHJldmlvdXNQcmljZXNbcGFja2V0LmFjY291bnRJZF1baW5zdGFuY2VJbmRleF07XG4gICAgXG4gICAgaWYocGFja2V0LnR5cGUgIT09ICdwcmljZXMnKSB7XG4gICAgICBpZihwcmV2UHJpY2UpIHtcbiAgICAgICAgdGhpcy5fcmVjb3JkUHJpY2VzKHBhY2tldC5hY2NvdW50SWQsIGluc3RhbmNlSW5kZXgpO1xuICAgICAgfVxuICAgICAgaWYocGFja2V0LnR5cGUgPT09ICdzcGVjaWZpY2F0aW9ucycgJiYgdGhpcy5fY29tcHJlc3NTcGVjaWZpY2F0aW9ucykge1xuICAgICAgICBxdWV1ZS5wdXNoKEpTT04uc3RyaW5naWZ5KHt0eXBlOiBwYWNrZXQudHlwZSwgc2VxdWVuY2VOdW1iZXI6IHBhY2tldC5zZXF1ZW5jZU51bWJlciwgXG4gICAgICAgICAgc2VxdWVuY2VUaW1lc3RhbXA6IHBhY2tldC5zZXF1ZW5jZVRpbWVzdGFtcCwgaW5zdGFuY2VJbmRleH0pKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHF1ZXVlLnB1c2goSlNPTi5zdHJpbmdpZnkocGFja2V0KSk7XG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIGlmKCF0aGlzLl9jb21wcmVzc1ByaWNlcykge1xuICAgICAgICBxdWV1ZS5wdXNoKEpTT04uc3RyaW5naWZ5KHBhY2tldCkpO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgaWYocHJldlByaWNlKSB7XG4gICAgICAgICAgY29uc3QgdmFsaWRTZXF1ZW5jZU51bWJlcnMgPSBbcHJldlByaWNlLmxhc3Quc2VxdWVuY2VOdW1iZXIsIHByZXZQcmljZS5sYXN0LnNlcXVlbmNlTnVtYmVyICsgMV07XG4gICAgICAgICAgaWYodGhpcy5fbGFzdFNOUGFja2V0W3BhY2tldC5hY2NvdW50SWRdW2luc3RhbmNlSW5kZXhdKSB7XG4gICAgICAgICAgICB2YWxpZFNlcXVlbmNlTnVtYmVycy5wdXNoKHRoaXMuX2xhc3RTTlBhY2tldFtwYWNrZXQuYWNjb3VudElkXVtpbnN0YW5jZUluZGV4XS5zZXF1ZW5jZU51bWJlciArIDEpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBpZighdmFsaWRTZXF1ZW5jZU51bWJlcnMuaW5jbHVkZXMocGFja2V0LnNlcXVlbmNlTnVtYmVyKSkge1xuICAgICAgICAgICAgdGhpcy5fcmVjb3JkUHJpY2VzKHBhY2tldC5hY2NvdW50SWQsIGluc3RhbmNlSW5kZXgpO1xuICAgICAgICAgICAgdGhpcy5fZW5zdXJlUHJldmlvdXNQcmljZU9iamVjdChwYWNrZXQuYWNjb3VudElkKTtcbiAgICAgICAgICAgIHRoaXMuX3ByZXZpb3VzUHJpY2VzW3BhY2tldC5hY2NvdW50SWRdW2luc3RhbmNlSW5kZXhdID0ge2ZpcnN0OiBwYWNrZXQsIGxhc3Q6IHBhY2tldH07XG4gICAgICAgICAgICBxdWV1ZS5wdXNoKEpTT04uc3RyaW5naWZ5KHBhY2tldCkpO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0aGlzLl9wcmV2aW91c1ByaWNlc1twYWNrZXQuYWNjb3VudElkXVtpbnN0YW5jZUluZGV4XS5sYXN0ID0gcGFja2V0O1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB0aGlzLl9lbnN1cmVQcmV2aW91c1ByaWNlT2JqZWN0KHBhY2tldC5hY2NvdW50SWQpO1xuICAgICAgICAgIHRoaXMuX3ByZXZpb3VzUHJpY2VzW3BhY2tldC5hY2NvdW50SWRdW2luc3RhbmNlSW5kZXhdID0ge2ZpcnN0OiBwYWNrZXQsIGxhc3Q6IHBhY2tldH07XG4gICAgICAgICAgcXVldWUucHVzaChKU09OLnN0cmluZ2lmeShwYWNrZXQpKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGxvZyBtZXNzYWdlcyB3aXRoaW4gZGF0ZSBib3VuZHMgYXMgYW4gYXJyYXkgb2Ygb2JqZWN0c1xuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWQgXG4gICAqIEBwYXJhbSB7RGF0ZX0gZGF0ZUFmdGVyIGRhdGUgdG8gZ2V0IGxvZ3MgYWZ0ZXJcbiAgICogQHBhcmFtIHtEYXRlfSBkYXRlQmVmb3JlIGRhdGUgdG8gZ2V0IGxvZ3MgYmVmb3JlXG4gICAqIEByZXR1cm5zIHtBcnJheTxPYmplY3Q+fSBsb2cgbWVzc2FnZXNcbiAgICovXG4gIGFzeW5jIHJlYWRMb2dzKGFjY291bnRJZCwgZGF0ZUFmdGVyLCBkYXRlQmVmb3JlKSB7XG4gICAgY29uc3QgZm9sZGVycyA9IGF3YWl0IGZzLnJlYWRkaXIodGhpcy5fcm9vdCk7XG4gICAgY29uc3QgcGFja2V0cyA9IFtdO1xuXG4gICAgZm9yIChsZXQgZm9sZGVyIG9mIGZvbGRlcnMpIHtcbiAgICAgIGNvbnN0IGZpbGVQYXRoID0gYCR7dGhpcy5fcm9vdH0vJHtmb2xkZXJ9LyR7YWNjb3VudElkfS5sb2dgO1xuICAgICAgaWYoYXdhaXQgZnMucGF0aEV4aXN0cyhmaWxlUGF0aCkpIHtcbiAgICAgICAgY29uc3QgY29udGVudHMgPSBhd2FpdCBmcy5yZWFkRmlsZShmaWxlUGF0aCwgJ3V0ZjgnKTtcbiAgICAgICAgbGV0IG1lc3NhZ2VzID0gY29udGVudHMuc3BsaXQoJ1xcclxcbicpLmZpbHRlcihtZXNzYWdlID0+IG1lc3NhZ2UubGVuZ3RoKS5tYXAobWVzc2FnZSA9PiB7XG4gICAgICAgICAgcmV0dXJuIHtkYXRlOiBuZXcgRGF0ZShtZXNzYWdlLnNsaWNlKDEsIDI0KSksIG1lc3NhZ2U6IG1lc3NhZ2Uuc2xpY2UoMjYpfTtcbiAgICAgICAgfSk7XG4gICAgICAgIGlmKGRhdGVBZnRlcikge1xuICAgICAgICAgIG1lc3NhZ2VzID0gbWVzc2FnZXMuZmlsdGVyKG1lc3NhZ2UgPT4gbWVzc2FnZS5kYXRlID4gZGF0ZUFmdGVyKTtcbiAgICAgICAgfVxuICAgICAgICBpZihkYXRlQmVmb3JlKSB7XG4gICAgICAgICAgbWVzc2FnZXMgPSBtZXNzYWdlcy5maWx0ZXIobWVzc2FnZSA9PiBtZXNzYWdlLmRhdGUgPCBkYXRlQmVmb3JlKTtcbiAgICAgICAgfVxuICAgICAgICBwYWNrZXRzLnB1c2goLi4ubWVzc2FnZXMpO1xuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBwYWNrZXRzO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgcGF0aCBmb3IgYWNjb3VudCBsb2cgZmlsZVxuICAgKiBAcGFyYW0ge1N0cmluZ30gYWNjb3VudElkIGFjY291bnQgaWRcbiAgICogQHJldHVybnMge1N0cmluZ30gZmlsZSBwYXRoXG4gICAqL1xuICBhc3luYyBnZXRGaWxlUGF0aChhY2NvdW50SWQpIHtcbiAgICBjb25zdCBmaWxlSW5kZXggPSBNYXRoLmZsb29yKG5ldyBEYXRlKCkuZ2V0SG91cnMoKSAvIHRoaXMuX2xvZ0ZpbGVTaXplSW5Ib3Vycyk7XG4gICAgY29uc3QgZm9sZGVyTmFtZSA9IGAke21vbWVudCgpLmZvcm1hdCgnWVlZWS1NTS1ERCcpfS0ke2ZpbGVJbmRleCA+IDkgPyBmaWxlSW5kZXggOiAnMCcgKyBmaWxlSW5kZXh9YDtcbiAgICBhd2FpdCBmcy5lbnN1cmVEaXIoYCR7dGhpcy5fcm9vdH0vJHtmb2xkZXJOYW1lfWApO1xuICAgIHJldHVybiBgJHt0aGlzLl9yb290fS8ke2ZvbGRlck5hbWV9LyR7YWNjb3VudElkfS5sb2dgO1xuICB9XG5cbiAgLyoqXG4gICAqIEluaXRpYWxpemVzIHRoZSBwYWNrZXQgbG9nZ2VyXG4gICAqL1xuICBzdGFydCgpIHtcbiAgICB0aGlzLl9wcmV2aW91c1ByaWNlcyA9IHt9O1xuICAgIGlmICghdGhpcy5fcmVjb3JkSW50ZXZhbCkge1xuICAgICAgdGhpcy5fcmVjb3JkSW50ZXZhbCA9IHNldEludGVydmFsKCgpID0+IHRoaXMuX2FwcGVuZExvZ3MoKSwgMTAwMCk7XG4gICAgICB0aGlzLl9kZWxldGVPbGRMb2dzSW50ZXJ2YWwgPSBzZXRJbnRlcnZhbCgoKSA9PiB0aGlzLl9kZWxldGVPbGREYXRhKCksIDEwMDAwKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRGVpbml0aWFsaXplcyB0aGUgcGFja2V0IGxvZ2dlclxuICAgKi9cbiAgc3RvcCgpIHtcbiAgICBjbGVhckludGVydmFsKHRoaXMuX3JlY29yZEludGV2YWwpO1xuICAgIGNsZWFySW50ZXJ2YWwodGhpcy5fZGVsZXRlT2xkTG9nc0ludGVydmFsKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZWNvcmRzIHByaWNlIHBhY2tldCBtZXNzYWdlcyB0byBsb2cgZmlsZXNcbiAgICogQHBhcmFtIHtTdHJpbmd9IGFjY291bnRJZCBhY2NvdW50IGlkXG4gICAqL1xuICBfcmVjb3JkUHJpY2VzKGFjY291bnRJZCwgaW5zdGFuY2VOdW1iZXIpIHtcbiAgICBjb25zdCBwcmV2UHJpY2UgPSB0aGlzLl9wcmV2aW91c1ByaWNlc1thY2NvdW50SWRdW2luc3RhbmNlTnVtYmVyXSB8fCB7Zmlyc3Q6IHt9LCBsYXN0Ont9fTtcbiAgICBjb25zdCBxdWV1ZSA9IHRoaXMuX3dyaXRlUXVldWVbYWNjb3VudElkXS5xdWV1ZTtcbiAgICBkZWxldGUgdGhpcy5fcHJldmlvdXNQcmljZXNbYWNjb3VudElkXVtpbnN0YW5jZU51bWJlcl07XG4gICAgaWYoIU9iamVjdC5rZXlzKHRoaXMuX3ByZXZpb3VzUHJpY2VzW2FjY291bnRJZF0pLmxlbmd0aCkge1xuICAgICAgZGVsZXRlIHRoaXMuX3ByZXZpb3VzUHJpY2VzW2FjY291bnRJZF07XG4gICAgfVxuICAgIGlmKHByZXZQcmljZS5maXJzdC5zZXF1ZW5jZU51bWJlciAhPT0gcHJldlByaWNlLmxhc3Quc2VxdWVuY2VOdW1iZXIpIHtcbiAgICAgIHF1ZXVlLnB1c2goSlNPTi5zdHJpbmdpZnkocHJldlByaWNlLmxhc3QpKTtcbiAgICAgIHF1ZXVlLnB1c2goYFJlY29yZGVkIHByaWNlIHBhY2tldHMgJHtwcmV2UHJpY2UuZmlyc3Quc2VxdWVuY2VOdW1iZXJ9YCArXG4gICAgICAgIGAtJHtwcmV2UHJpY2UubGFzdC5zZXF1ZW5jZU51bWJlcn0sIGluc3RhbmNlSW5kZXg6ICR7aW5zdGFuY2VOdW1iZXJ9YCk7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFdyaXRlcyBsb2dzIHRvIGZpbGVzXG4gICAqL1xuICBhc3luYyBfYXBwZW5kTG9ncygpIHtcbiAgICBPYmplY3Qua2V5cyh0aGlzLl93cml0ZVF1ZXVlKS5mb3JFYWNoKGFzeW5jIGFjY291bnRJZCA9PiB7XG4gICAgICBjb25zdCBxdWV1ZSA9IHRoaXMuX3dyaXRlUXVldWVbYWNjb3VudElkXTtcbiAgICAgIGlmICghcXVldWUuaXNXcml0aW5nICYmIHF1ZXVlLnF1ZXVlLmxlbmd0aCkge1xuICAgICAgICBxdWV1ZS5pc1dyaXRpbmcgPSB0cnVlO1xuICAgICAgICB0cnkge1xuICAgICAgICAgIGNvbnN0IGZpbGVQYXRoID0gYXdhaXQgdGhpcy5nZXRGaWxlUGF0aChhY2NvdW50SWQpO1xuICAgICAgICAgIGNvbnN0IHdyaXRlU3RyaW5nID0gcXVldWUucXVldWUucmVkdWNlKChhLGIpID0+IGEgKyBcbiAgICAgICAgICBgWyR7bW9tZW50KCkuZm9ybWF0KCdZWVlZLU1NLUREIEhIOm1tOnNzLlNTUycpfV0gJHtifVxcclxcbmAgLCcnKTtcbiAgICAgICAgICBxdWV1ZS5xdWV1ZSA9IFtdO1xuICAgICAgICAgIGF3YWl0IGZzLmFwcGVuZEZpbGUoZmlsZVBhdGgsIHdyaXRlU3RyaW5nKTsgIFxuICAgICAgICB9IGNhdGNoKGVycikge1xuICAgICAgICAgIHRoaXMuX2xvZ2dlci5lcnJvcihgJHthY2NvdW50SWR9OiBGYWlsZWQgdG8gcmVjb3JkIHBhY2tldCBsb2dgLCBlcnIpO1xuICAgICAgICB9XG4gICAgICAgIHF1ZXVlLmlzV3JpdGluZyA9IGZhbHNlO1xuICAgICAgfVxuICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIERlbGV0ZXMgZm9sZGVycyB3aGVuIHRoZSBmb2xkZXIgbGltaXQgaXMgZXhjZWVkZWRcbiAgICovXG4gIGFzeW5jIF9kZWxldGVPbGREYXRhKCkge1xuICAgIGNvbnN0IGNvbnRlbnRzID0gYXdhaXQgZnMucmVhZGRpcih0aGlzLl9yb290KTtcbiAgICBjb250ZW50cy5yZXZlcnNlKCkuc2xpY2UodGhpcy5fZmlsZU51bWJlckxpbWl0KS5mb3JFYWNoKGFzeW5jIGZvbGRlck5hbWUgPT4ge1xuICAgICAgYXdhaXQgZnMucmVtb3ZlKGAke3RoaXMuX3Jvb3R9LyR7Zm9sZGVyTmFtZX1gKTsgXG4gICAgfSk7XG4gIH1cblxufVxuIl0sIm5hbWVzIjpbImZzIiwibW9tZW50IiwiT3B0aW9uc1ZhbGlkYXRvciIsIkxvZ2dlck1hbmFnZXIiLCJQYWNrZXRMb2dnZXIiLCJfZW5zdXJlUHJldmlvdXNQcmljZU9iamVjdCIsImFjY291bnRJZCIsIl9wcmV2aW91c1ByaWNlcyIsImxvZ1BhY2tldCIsInBhY2tldCIsImluc3RhbmNlSW5kZXgiLCJfd3JpdGVRdWV1ZSIsImlzV3JpdGluZyIsInF1ZXVlIiwidHlwZSIsIl9sYXN0U05QYWNrZXQiLCJpbmNsdWRlcyIsInByZXZQcmljZSIsIl9yZWNvcmRQcmljZXMiLCJfY29tcHJlc3NTcGVjaWZpY2F0aW9ucyIsInB1c2giLCJKU09OIiwic3RyaW5naWZ5Iiwic2VxdWVuY2VOdW1iZXIiLCJzZXF1ZW5jZVRpbWVzdGFtcCIsIl9jb21wcmVzc1ByaWNlcyIsInZhbGlkU2VxdWVuY2VOdW1iZXJzIiwibGFzdCIsImZpcnN0IiwicmVhZExvZ3MiLCJkYXRlQWZ0ZXIiLCJkYXRlQmVmb3JlIiwiZm9sZGVycyIsInJlYWRkaXIiLCJfcm9vdCIsInBhY2tldHMiLCJmb2xkZXIiLCJmaWxlUGF0aCIsInBhdGhFeGlzdHMiLCJjb250ZW50cyIsInJlYWRGaWxlIiwibWVzc2FnZXMiLCJzcGxpdCIsImZpbHRlciIsIm1lc3NhZ2UiLCJsZW5ndGgiLCJtYXAiLCJkYXRlIiwiRGF0ZSIsInNsaWNlIiwiZ2V0RmlsZVBhdGgiLCJmaWxlSW5kZXgiLCJNYXRoIiwiZmxvb3IiLCJnZXRIb3VycyIsIl9sb2dGaWxlU2l6ZUluSG91cnMiLCJmb2xkZXJOYW1lIiwiZm9ybWF0IiwiZW5zdXJlRGlyIiwic3RhcnQiLCJfcmVjb3JkSW50ZXZhbCIsInNldEludGVydmFsIiwiX2FwcGVuZExvZ3MiLCJfZGVsZXRlT2xkTG9nc0ludGVydmFsIiwiX2RlbGV0ZU9sZERhdGEiLCJzdG9wIiwiY2xlYXJJbnRlcnZhbCIsImluc3RhbmNlTnVtYmVyIiwiT2JqZWN0Iiwia2V5cyIsImZvckVhY2giLCJ3cml0ZVN0cmluZyIsInJlZHVjZSIsImEiLCJiIiwiYXBwZW5kRmlsZSIsImVyciIsIl9sb2dnZXIiLCJlcnJvciIsInJldmVyc2UiLCJfZmlsZU51bWJlckxpbWl0IiwicmVtb3ZlIiwiY29uc3RydWN0b3IiLCJvcHRzIiwidmFsaWRhdG9yIiwidmFsaWRhdGVOb25aZXJvIiwiZmlsZU51bWJlckxpbWl0IiwibG9nRmlsZVNpemVJbkhvdXJzIiwidmFsaWRhdGVCb29sZWFuIiwiY29tcHJlc3NTcGVjaWZpY2F0aW9ucyIsImNvbXByZXNzUHJpY2VzIiwiZ2V0TG9nZ2VyIl0sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQ0EsT0FBT0EsUUFBUSxXQUFXO0FBQzFCLE9BQU9DLFlBQVksU0FBUztBQUM1QixPQUFPQyxzQkFBc0Isc0JBQXNCO0FBQ25ELE9BQU9DLG1CQUFtQixlQUFlO0FBZTFCLElBQUEsQUFBTUMsZUFBTixNQUFNQTtJQXVCbkJDLDJCQUEyQkMsU0FBUyxFQUFFO1FBQ3BDLElBQUcsQ0FBQyxJQUFJLENBQUNDLGVBQWUsQ0FBQ0QsVUFBVSxFQUFFO1lBQ25DLElBQUksQ0FBQ0MsZUFBZSxDQUFDRCxVQUFVLEdBQUcsQ0FBQztRQUNyQztJQUNGO0lBRUE7OztHQUdDLEdBQ0Qsc0NBQXNDO0lBQ3RDRSxVQUFVQyxNQUFNLEVBQUU7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