UNPKG

node-routeros-altostrat

Version:

Mikrotik Routerboard RouterOS API for NodeJS

277 lines 17.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = require("events"); const Channel_1 = require("./Channel"); const RosException_1 = require("./RosException"); const timers_1 = require("timers"); const utils_1 = require("./utils"); /** * Stream class is responsible for handling * continuous data from some parts of the * routeros, like /ip/address/listen or * /tool/torch which keeps sending data endlessly. * It is also possible to pause/resume/stop generated * streams. */ class RStream extends events_1.EventEmitter { /** * Constructor, it also starts the streaming after construction * * @param {Channel} channel * @param {Array} params * @param {function} callback */ constructor(channel, params, callback) { super(); /** Flag for turning on empty data debouncing */ this.shouldDebounceEmptyData = false; /** * If is streaming flag */ this.streaming = true; /** * If is pausing flag */ this.pausing = false; /** * If is paused flag */ this.paused = false; /** * If is stopping flag */ this.stopping = false; /** * If is stopped flag */ this.stopped = false; /** * If got a trap error */ this.trapped = false; /** * Save the current section of the packet, if has any */ this.currentSection = null; this.forcelyStop = false; /** * Store the current section in a single * array before sending when another section comes */ this.currentSectionPacket = []; this.channel = channel; this.params = params; this.callback = callback; } /** * Function to receive the callback which * will receive data, if not provided over the * constructor or changed later after the streaming * have started. * * @param {function} callback */ data(callback) { this.callback = callback; } /** * Resume the paused stream, using the same channel * * @returns {Promise} */ resume() { if (this.stopped || this.stopping) return Promise.reject(new RosException_1.RosException('STREAMCLOSD')); if (!this.streaming) { this.pausing = false; this.start(); this.streaming = true; } return Promise.resolve(); } /** * Pause the stream, but don't destroy the channel * * @returns {Promise} */ pause() { if (this.stopped || this.stopping) return Promise.reject(new RosException_1.RosException('STREAMCLOSD')); if (this.pausing || this.paused) return Promise.resolve(); if (this.streaming) { this.pausing = true; return this.stop(true) .then(() => { this.pausing = false; this.paused = true; return Promise.resolve(); }) .catch((err) => { return Promise.reject(err); }); } return Promise.resolve(); } /** * Stop the stream entirely, can't re-stream after * this if called directly. * * @returns {Promise} */ stop(pausing = false) { if (this.stopped || this.stopping) return Promise.resolve(); if (!pausing) this.forcelyStop = true; if (this.paused) { this.streaming = false; this.stopping = false; this.stopped = true; if (this.channel) this.channel.close(true); return Promise.resolve(); } if (!this.pausing) this.stopping = true; let chann = new Channel_1.Channel(this.channel.Connector); chann.on('close', () => { chann = null; }); if (this.debounceSendingEmptyData) this.debounceSendingEmptyData.cancel(); return chann .write(['/cancel', '=tag=' + this.channel.Id]) .then(() => { this.streaming = false; if (!this.pausing) { this.stopping = false; this.stopped = true; } this.emit('stopped'); return Promise.resolve(); }) .catch((err) => { return Promise.reject(err); }); } /** * Alias for stop() */ close() { return this.stop(); } /** * Write over the connection and start the stream */ start() { if (!this.stopped && !this.stopping) { this.channel.on('close', () => { if (this.forcelyStop || (!this.pausing && !this.paused)) { if (!this.trapped) this.emit('done'); this.emit('close'); } this.stopped = false; }); this.channel.on('stream', (packet) => { if (this.debounceSendingEmptyData) this.debounceSendingEmptyData.run(); this.onStream(packet); }); this.channel.once('trap', this.onTrap.bind(this)); this.channel.once('done', this.onDone.bind(this)); this.channel.write(this.params.slice(), true, false); this.emit('started'); if (this.shouldDebounceEmptyData) this.prepareDebounceEmptyData(); } } prepareDebounceEmptyData() { this.shouldDebounceEmptyData = true; const intervalParam = this.params.find((param) => { return /=interval=/.test(param); }); let interval = 2000; if (intervalParam) { const val = intervalParam.split('=')[2]; interval = parseInt(val, null) * 1000; } this.debounceSendingEmptyData = utils_1.debounce(() => { if (!this.stopped || !this.stopping || !this.paused || !this.pausing) { this.onStream([]); this.debounceSendingEmptyData.run(); } }, interval + 300); } /** * When receiving the stream packet, give it to * the callback * * @returns {function} */ onStream(packet) { this.emit('data', packet); if (this.callback) { if (packet['.section']) { timers_1.clearTimeout(this.sectionPacketSendingTimeout); const sendData = () => { this.callback(null, this.currentSectionPacket.slice(), this); this.currentSectionPacket = []; }; this.sectionPacketSendingTimeout = timers_1.setTimeout(sendData.bind(this), 300); if (this.currentSectionPacket.length > 0 && packet['.section'] !== this.currentSection) { timers_1.clearTimeout(this.sectionPacketSendingTimeout); sendData(); } this.currentSection = packet['.section']; this.currentSectionPacket.push(packet); } else { this.callback(null, packet, this); } } } /** * When receiving a trap over the connection, * when pausing, will receive a 'interrupted' message, * this will not be considered as an error but a flag * for the pause and resume function * * @returns {function} */ onTrap(data) { if (data.message === 'interrupted') { this.streaming = false; } else { this.stopped = true; this.trapped = true; if (this.callback) { this.callback(new Error(data.message), null, this); } else { this.emit('error', data); } this.emit('trap', data); } } /** * When the channel stops sending data. * It will close the channel if the * intention was stopping it. * * @returns {function} */ onDone() { if (this.stopped && this.channel) { this.channel.close(true); } } } exports.RStream = RStream; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUlN0cmVhbS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9SU3RyZWFtLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsbUNBQXNDO0FBQ3RDLHVDQUFvQztBQUNwQyxpREFBOEM7QUFDOUMsbUNBQWtEO0FBQ2xELG1DQUFtQztBQUVuQzs7Ozs7OztHQU9HO0FBQ0gsTUFBYSxPQUFRLFNBQVEscUJBQVk7SUE2RXJDOzs7Ozs7T0FNRztJQUNILFlBQ0ksT0FBZ0IsRUFDaEIsTUFBZ0IsRUFDaEIsUUFBK0Q7UUFFL0QsS0FBSyxFQUFFLENBQUM7UUEvRFosZ0RBQWdEO1FBQ3hDLDRCQUF1QixHQUFZLEtBQUssQ0FBQztRQUVqRDs7V0FFRztRQUNLLGNBQVMsR0FBWSxJQUFJLENBQUM7UUFFbEM7O1dBRUc7UUFDSyxZQUFPLEdBQVksS0FBSyxDQUFDO1FBRWpDOztXQUVHO1FBQ0ssV0FBTSxHQUFZLEtBQUssQ0FBQztRQUVoQzs7V0FFRztRQUNLLGFBQVEsR0FBWSxLQUFLLENBQUM7UUFFbEM7O1dBRUc7UUFDSyxZQUFPLEdBQVksS0FBSyxDQUFDO1FBRWpDOztXQUVHO1FBQ0ssWUFBTyxHQUFZLEtBQUssQ0FBQztRQUVqQzs7V0FFRztRQUNLLG1CQUFjLEdBQVcsSUFBSSxDQUFDO1FBRTlCLGdCQUFXLEdBQVksS0FBSyxDQUFDO1FBRXJDOzs7V0FHRztRQUNLLHlCQUFvQixHQUFVLEVBQUUsQ0FBQztRQW9CckMsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7UUFDdkIsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7SUFDN0IsQ0FBQztJQUVEOzs7Ozs7O09BT0c7SUFDSSxJQUFJLENBQ1AsUUFBOEQ7UUFFOUQsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7SUFDN0IsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxNQUFNO1FBQ1QsSUFBSSxJQUFJ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