"use strict";
/**
* Since only a single constructor is being exported as module.exports this comment isn't documented.
* The class and module are the same thing, the contructor comment takes precedence.
* @module request-response
*/
var EventEmitter = require('events').EventEmitter;
var util = require('util');
/**
* Creates a helper class used to send multiple messages back and forth across a read stream and a write stream
* Terminates each complete message with a [q|s] character to indicate request or response, a white space padded 4 character request ID, followed by an ASCII Group Separator
* @constructor
* @param inStream { external:Stream } - The readable stream used in the communication
* @param outStream { external:Stream } - The writtable stream used in the communication
* @example var requestHelper = new (require(request-response))(process.stdin, process.stdout);
*/
module.exports = function RequestHelper(inStream, outStream, generateEvenRequestIds) {
//Protect the constructor from being called as a normal method
if (!(this instanceof RequestHelper)) {
return new RequestHelper(inStream, outStream);
}
EventEmitter.call(this);
this.inStream = inStream;
this.outStream = outStream;
this.nextRequestId = 0;
this.currentInput = '';
var requestHelper = this;
inStream.on( 'data', function (chunk) {
//Just keep reading that stdin, the source is the trusted responsible one. We're expecting to load everything into memory
//TODO limit the amount of stdin which can be read
requestHelper.currentInput += chunk;
//Check if message is complete
if (requestHelper.currentInput.indexOf('\u0029', requestHelper.currentInput.length - 1) !== -1) {
//Message ended
//Parse the requestId, which is left padded with spaces to 4 characters
var requestId = parseInt(requestHelper.currentInput.substring(requestHelper.currentInput.length - 4, requestHelper.currentInput.length - 1));
//Strip the control character and the requestId
var msgBody = requestHelper.currentInput.substring(0, requestHelper.currentInput.length - 6);
var isResponse = requestHelper.currentInput.substring(requestHelper.currentInput.length - 6, requestHelper.currentInput.length - 5) === 's';
//emit the request to everyone listening for new incoming requests
if ( isResponse) {
//The requestId was generated by this helper (meaning it is a response)
requestHelper.emit('response'+requestId, msgBody);
} else {
//The requestId should have been generated by the oposite helper we're communicating with (meaning it is a new request)
requestHelper.emit('request', requestId, msgBody);
}
requestHelper.currentInput = '';
}
});
};
util.inherits(module.exports, EventEmitter);
/**
* The built in arguments object.
* @external Arguments
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments|Arguments}
*/
/**
* Request event
*
* @event request
* @type {external:Arguments}
* @property {integer} 0 - The requestId
* @property {string} 1 - The request body
*/
/**
* A Node.js stream
* @external Stream
* @see {@link https://nodejs.org/api/stream.html|Stream}
*/
/**
* A callback invoked with a response message
* @callback responseCallback
* @param {string} responseMessage
*/
/**
* Pipes a new request into our managed output stream. Will not close the managed stream. Writes a requestId and request delimiter to the managed stream once the pipe is complete.
* @method pipeRequest
* @param {external:Stream} stream - The stream to pipe and decorate with a requestId
* @param {module:request-response~responseCallback} [callback] - A callback to use if this instance receives an incoming response with a matching requestId on the managed input stream
*/
module.exports.prototype.pipeRequest = function(stream, callback) {
//TODO if there is a currently un-ended stream writing, queue this stream
var requestHelper = this;
//Pipe, but of course don't close our destination
stream.pipe(this.outStream, { end: false });
stream.on('end', function(){
//The stream ended, write out the next
requestHelper.outStream.write('q' + (" " + requestHelper.nextRequestId).slice(-4) + '\u0029');
if (!!callback) {
//callback provided, listen for a response
requestHelper.once('response' + requestHelper.nextRequestId, callback);
}
//increment the requestId to the next even or odd number
requestHelper.nextRequestId++;
//TODO dequeu any waiting streams
});
};
/**
* Writes a new request into our managed output stream. Writes a requestId and request delimiter to the managed stream along with the message.
* @method writeRequest
* @param {string} msgBody - The body of the request
* @param {module:request-response~responseCallback} [callback] - A callback to use if this instance receives a response with a matching requestId on the managed input stream
*/
module.exports.prototype.writeRequest = function(msgBody, callback) {
//TODO if there is a currently un-ended stream writing, queue this write
//Write the message body and requestId
this.outStream.write(msgBody + 'q' +(" " + this.nextRequestId).slice(-4) + '\u0029');
if (!!callback) {
//callback provided, listen for a response
this.once('response' + this.nextRequestId, callback);
}
//increment the requestId to the next even or odd number
this.nextRequestId++;
};
/**
* Writes a new response into our managed output stream using the provided requestId
* @method writeResponse
* @param {integer} requestId - The requestId being responded to
* @param {string} msgBody - The body of the response
*/
module.exports.prototype.writeResponse = function(requestId, msgBody) {
//TODO if there is a currently un-ended stream writing, queue this write
//Write the message body and requestId
this.outStream.write(msgBody + 's' + (" " + requestId).slice(-4) + '\u0029');
};