UNPKG

routeros-api

Version:

Mikrotik Routerboard RouterOS API for NodeJS

278 lines 16.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RStream = void 0; 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUlN0cmVhbS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9SU3RyZWFtLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLG1DQUFzQztBQUN0Qyx1Q0FBb0M7QUFDcEMsaURBQThDO0FBQzlDLG1DQUFrRDtBQUNsRCxtQ0FBbUM7QUFFbkM7Ozs7Ozs7R0FPRztBQUNILE1BQWEsT0FBUSxTQUFRLHFCQUFZO0lBNkVyQzs7Ozs7O09BTUc7SUFDSCxZQUNJLE9BQWdCLEVBQ2hCLE1BQWdCLEVBQ2hCLFFBQStEO1FBRS9ELEtBQUssRUFBRSxDQUFDO1FBL0RaLGdEQUFnRDtRQUN4Qyw0QkFBdUIsR0FBWSxLQUFLLENBQUM7UUFFakQ7O1dBRUc7UUFDSyxjQUFTLEdBQVksSUFBSSxDQUFDO1FBRWxDOztXQUVHO1FBQ0ssWUFBTyxHQUFZLEtBQUssQ0FBQztRQUVqQzs7V0FFRztRQUNLLFdBQU0sR0FBWSxLQUFLLENBQUM7UUFFaEM7O1dBRUc7UUFDSyxhQUFRLEdBQVksS0FBSyxDQUFDO1FBRWxDOztXQUVHO1FBQ0ssWUFBTyxHQUFZLEtBQUssQ0FBQztRQUVqQzs7V0FFRztRQUNLLFlBQU8sR0FBWSxLQUFLLENBQUM7UUFFakM7O1dBRUc7UUFDSyxtQkFBYyxHQUFXLElBQUksQ0FBQztRQUU5QixnQkFBVyxHQUFZLEtBQUssQ0FBQztRQUVyQzs7O1dBR0c7UUFDSyx5QkFBb0IsR0FBVSxFQUFFLENBQUM7UUFvQnJDLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO1FBQ3JCLElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO0lBQzdCLENBQUM7SUFFRDs7Ozs7OztPQU9HO0lBQ0ksSUFBSSxDQUNQLFFBQThEO1FBRTlELElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO0lBQzdCLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksTUFBTTtRQUNULElBQUksSUFB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