Source: index.js

"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');
};