hose.js 2.2 KB
'use strict';

var util = require('util');
var EventEmitter = require('events').EventEmitter;

function Hose(socket, options, filter) {
  EventEmitter.call(this);

  if (typeof options === 'function') {
    filter = options;
    options = {};
  }

  this.socket = socket;
  this.options = options;
  this.filter = filter;

  this.buffer = null;

  var self = this;
  this.listeners = {
    error: function(err) {
      return self.onError(err);
    },
    data: function(chunk) {
      return self.onData(chunk);
    },
    end: function() {
      return self.onEnd();
    }
  };

  this.socket.on('error', this.listeners.error);
  this.socket.on('data', this.listeners.data);
  this.socket.on('end', this.listeners.end);
}
util.inherits(Hose, EventEmitter);
module.exports = Hose;

Hose.create = function create(socket, options, filter) {
  return new Hose(socket, options, filter);
};

Hose.prototype.detach = function detach() {
  // Stop the flow
  this.socket.pause();

  this.socket.removeListener('error', this.listeners.error);
  this.socket.removeListener('data', this.listeners.data);
  this.socket.removeListener('end', this.listeners.end);
};

Hose.prototype.reemit = function reemit() {
  var buffer = this.buffer;
  this.buffer = null;

  // Modern age
  if (this.socket.unshift) {
    this.socket.unshift(buffer);
    if (this.socket.listeners('data').length > 0)
      this.socket.resume();
    return;
  }

  // Rusty node v0.8
  if (this.socket.ondata)
    this.socket.ondata(buffer, 0, buffer.length);
  this.socket.emit('data', buffer);
  this.socket.resume();
};

Hose.prototype.onError = function onError(err) {
  this.detach();
  this.emit('error', err);
};

Hose.prototype.onData = function onData(chunk) {
  if (this.buffer)
    this.buffer = Buffer.concat([ this.buffer, chunk ]);
  else
    this.buffer = chunk;

  var self = this;
  this.filter(this.buffer, function(err, protocol) {
    if (err)
      return self.onError(err);

    // No protocol selected yet
    if (!protocol)
      return;

    self.detach();
    self.emit('select', protocol, self.socket);
    self.reemit();
  });
};

Hose.prototype.onEnd = function onEnd() {
  this.detach();
  this.emit('error', new Error('Not enough data to recognize protocol'));
};