websocket.js 3.1 KB
// API and protocol references:
// 
// * http://dev.w3.org/html5/websockets/
// * http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-eventtarget
// * http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#interface-event
// * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
// * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
// * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17

var Draft75Parser = require('./websocket/draft75_parser'),
    Draft76Parser = require('./websocket/draft76_parser'),
    HybiParser    = require('./websocket/hybi_parser'),
    API           = require('./websocket/api'),
    Event         = require('./websocket/api/event');

var getParser = function(request) {
  var headers = request.headers;
  return headers['sec-websocket-version']
       ? HybiParser
       : (headers['sec-websocket-key1'] && headers['sec-websocket-key2'])
       ? Draft76Parser
       : Draft75Parser;
};

var isSecureConnection = function(request) {
  if (request.headers['x-forwarded-proto']) {
    return request.headers['x-forwarded-proto'] === 'https';
  } else {
    return (request.connection && request.connection.authorized !== undefined) ||
           (request.socket && request.socket.secure);
  }
};

var WebSocket = function(request, socket, head, supportedProtos, options) {
  this.request = request;
  this._stream = request.socket;
  this._ping   = options && options.ping;
  this._pingId = 0;

  var scheme = isSecureConnection(request) ? 'wss:' : 'ws:';
  this.url = scheme + '//' + request.headers.host + request.url;
  this.readyState = API.CONNECTING;
  this.bufferedAmount = 0;

  var Parser = getParser(request);
  this._parser = new Parser(this, {protocols: supportedProtos});

  var self = this;
  this._sendBuffer = [];
  process.nextTick(function() { self._open() });

  var handshake = this._parser.handshakeResponse(head);
  if (this._parser.isOpen()) this.readyState = API.OPEN;

  if (this._ping)
    this._pingLoop = setInterval(function() {
      self._pingId += 1;
      self.ping(self._pingId.toString());
    }, this._ping * 1000);

  this.protocol = this._parser.protocol || '';
  this.version = this._parser.getVersion();

  if (!this._stream || !this._stream.writable) return;

  this._stream.setTimeout(0);
  this._stream.setNoDelay(true);

  try { this._stream.write(handshake, 'binary') } catch (e) {}

  this._stream.addListener('data', function(data) {
    var response = self._parser.parse(data);
    if (!response) return;
    try { self._stream.write(response, 'binary') } catch (e) {}
    self._open();
  });
  ['close', 'end', 'error'].forEach(function(event) {
    self._stream.addListener(event, function() { self.close(1006, '', false) });
  });
};

WebSocket.prototype.ping = function(message, callback, context) {
  if (!this._parser.ping) return false;
  return this._parser.ping(message, callback, context);
};

for (var key in API) WebSocket.prototype[key] = API[key];

WebSocket.WebSocket   = WebSocket;
WebSocket.Client      = require('./websocket/client');
WebSocket.EventSource = require('./eventsource');
module.exports        = WebSocket;