UNPKG

stompit

Version:

STOMP client library for node.js

283 lines (198 loc) 6.77 kB
/*jslint node: true, indent: 2, unused: true, maxlen: 80, camelcase: true, esversion: 9 */ const OutgoingFrameStream = require('./OutgoingFrameStream'); const IncomingFrameStream = require('./IncomingFrameStream'); const { EventEmitter } = require('events'); class Socket extends EventEmitter { constructor(transportSocket, options) { super(); this._commandHandlers = options.commandHandlers; this._unknownCommandHandler = options.unknownCommand || function() { this.destroy(); }; this._destroyed = false; this._destroy = this.destroy.bind(this); this._transportSocket = transportSocket; this._transportFinished = false; transportSocket.on('finish', () => { this._transportFinished = true; }); transportSocket.on('error', (error) => { const code = error.code || error.errno; if( code === 'ECONNRESET' && this.hasFinishedOutput()) { // Some servers may choose to send RST instead of FIN to prevent // connection lingering (per v1.2 recommendation) this._onInputEnd(); return; } this.destroy(this.createTransportError(error)); }); const incoming = new IncomingFrameStream(options); this._incoming = incoming; transportSocket.pipe(incoming); this._readingFrame = false; this._output = options.outgoingFrameStream || new OutgoingFrameStream(transportSocket); this._heartbeat = options.heartbeat || [0, 0]; this._heartbeatDelayMargin = options.heartbeatDelayMargin || 100; this._heartbeatOutputMargin = options.heartbeatOutputMargin || 0; const readFrameBody = (frame, callback) => { frame.on('end', callback); const handler = this._commandHandlers[frame.command]; const beforeSendResponseCb = (responseFrame, responseEndCallback) => { return this._beforeSendResponse(frame, responseFrame, responseEndCallback || function() {}); }; if (typeof handler === 'function') { handler.apply(this, [frame, beforeSendResponseCb]); } else { this._unknownCommandHandler.apply( this, [frame, beforeSendResponseCb] ); } }; const readIncomingFrame = () => { if (this._readingFrame) { return; } const frame = incoming.read(); if (!frame) { return; } this._readingFrame = true; readFrameBody(frame, (error) => { this._readingFrame = false; if (!error) { process.nextTick(readIncomingFrame); } }); }; incoming.on('readable', readIncomingFrame); incoming.on('error', (error) => { this.destroy(this.createProtocolError(error)); }); incoming.on('end', () => { this._onInputEnd(); }); readIncomingFrame(); } _onInputEnd() { this.destroy(); } destroy(exception) { if (!this._destroyed) { this._destroyed = true; this._transportSocket.destroy(exception); if (exception) { this.emit('error', exception); } else{ this.emit('end'); } if (typeof this._onDestroyed === 'function') { this._onDestroyed(exception); } } } _finishOutput() { if (!this.hasFinishedOutput()) { this._output.finish(); this.emit('finish'); } } hasFinishedOutput() { return this._output.hasFinished(); } setVersion(version) { this._incoming.setVersion(version); this._output.setVersion(version); } getTransportSocket() { return this._transportSocket; } setCommandHandler(command, handler) { this._commandHandlers[command] = handler; } setCommandHandlers(handlers) { this._commandHandlers = handlers; } setUnknownCommandHandler(handler) { this._unknownCommandHandler = handler; } sendFrame(command, headers, streamOptions) { const frame = this._output.frame(command, headers, streamOptions); frame.on('error', this._destroy); return frame; } getHeartbeat() { return this._heartbeat; } setHeartbeat(heartbeat) { this._heartbeat = heartbeat; } _runHeartbeat(input, output) { output = output === 0 || this._heartbeat[0] === 0 ? 0 : Math.max(output, this._heartbeat[0]) - this._heartbeatOutputMargin; input = input === 0 || this._heartbeat[1] === 0 ? 0 : Math.max(input, this._heartbeat[1]) + this._heartbeatDelayMargin; const self = this; const intervals = []; const stop = function() { for (let i = 0; i < intervals.length; i++) { clearInterval(intervals[i]); } }; const transportSocket = this._transportSocket; transportSocket.once('error', stop); transportSocket.once('end', stop); transportSocket.once('close', stop); if (output > 0) { if (this._transportSocket.setNoDelay) { this._transportSocket.setNoDelay(true); } intervals.push(setInterval(function() { self._output.heartbeat(); }, output)); } if (input > 0) { let lastBytesRead = 0; intervals.push(setInterval(function() { const bytesRead = self._transportSocket.bytesRead; if (bytesRead - lastBytesRead === 0) { self.destroy(self.createTransportError('connection timed out')); } lastBytesRead = bytesRead; }, input)); } } _createError(error, extensions) { if (!(error instanceof Error)) { error = new Error(error); } Object.assign(error, Object.assign({ isTransportError: () => false, isProtocolError: () => false, isApplicationError: () => false }, extensions)); return error; } createTransportError(message) { const error = this._createError(message, { isTransportError: () => true }); return error; } createProtocolError(message) { const error = this._createError(message, { isProtocolError: () => true }); return error; } createApplicationError(message) { const error = this._createError(message, { isApplicationError: () => true }); return error; } } module.exports = Socket;