| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 | 'use strict';var Buffer  = require('safe-buffer').Buffer,    Emitter = require('events').EventEmitter,    util    = require('util'),    streams = require('../streams'),    Headers = require('./headers'),    Reader  = require('./stream_reader');var Base = function(request, url, options) {  Emitter.call(this);  Base.validateOptions(options || {}, ['maxLength', 'masking', 'requireMasking', 'protocols']);  this._request   = request;  this._reader    = new Reader();  this._options   = options || {};  this._maxLength = this._options.maxLength || this.MAX_LENGTH;  this._headers   = new Headers();  this.__queue    = [];  this.readyState = 0;  this.url        = url;  this.io = new streams.IO(this);  this.messages = new streams.Messages(this);  this._bindEventListeners();};util.inherits(Base, Emitter);Base.isWebSocket = function(request) {  var connection = request.headers.connection || '',      upgrade    = request.headers.upgrade || '';  return request.method === 'GET' &&         connection.toLowerCase().split(/ *, */).indexOf('upgrade') >= 0 &&         upgrade.toLowerCase() === 'websocket';};Base.validateOptions = function(options, validKeys) {  for (var key in options) {    if (validKeys.indexOf(key) < 0)      throw new Error('Unrecognized option: ' + key);  }};var instance = {  // This is 64MB, small enough for an average VPS to handle without  // crashing from process out of memory  MAX_LENGTH: 0x3ffffff,  STATES: ['connecting', 'open', 'closing', 'closed'],  _bindEventListeners: function() {    var self = this;    // Protocol errors are informational and do not have to be handled    this.messages.on('error', function() {});    this.on('message', function(event) {      var messages = self.messages;      if (messages.readable) messages.emit('data', event.data);    });    this.on('error', function(error) {      var messages = self.messages;      if (messages.readable) messages.emit('error', error);    });    this.on('close', function() {      var messages = self.messages;      if (!messages.readable) return;      messages.readable = messages.writable = false;      messages.emit('end');    });  },  getState: function() {    return this.STATES[this.readyState] || null;  },  addExtension: function(extension) {    return false;  },  setHeader: function(name, value) {    if (this.readyState > 0) return false;    this._headers.set(name, value);    return true;  },  start: function() {    if (this.readyState !== 0) return false;    if (!Base.isWebSocket(this._request))      return this._failHandshake(new Error('Not a WebSocket request'));    var response;    try {      response = this._handshakeResponse();    } catch (error) {      return this._failHandshake(error);    }    this._write(response);    if (this._stage !== -1) this._open();    return true;  },  _failHandshake: function(error) {    var headers = new Headers();    headers.set('Content-Type', 'text/plain');    headers.set('Content-Length', Buffer.byteLength(error.message, 'utf8'));    headers = ['HTTP/1.1 400 Bad Request', headers.toString(), error.message];    this._write(Buffer.from(headers.join('\r\n'), 'utf8'));    this._fail('protocol_error', error.message);    return false;  },  text: function(message) {    return this.frame(message);  },  binary: function(message) {    return false;  },  ping: function() {    return false;  },  pong: function() {      return false;  },  close: function(reason, code) {    if (this.readyState !== 1) return false;    this.readyState = 3;    this.emit('close', new Base.CloseEvent(null, null));    return true;  },  _open: function() {    this.readyState = 1;    this.__queue.forEach(function(args) { this.frame.apply(this, args) }, this);    this.__queue = [];    this.emit('open', new Base.OpenEvent());  },  _queue: function(message) {    this.__queue.push(message);    return true;  },  _write: function(chunk) {    var io = this.io;    if (io.readable) io.emit('data', chunk);  },  _fail: function(type, message) {    this.readyState = 2;    this.emit('error', new Error(message));    this.close();  }};for (var key in instance)  Base.prototype[key] = instance[key];Base.ConnectEvent = function() {};Base.OpenEvent = function() {};Base.CloseEvent = function(code, reason) {  this.code   = code;  this.reason = reason;};Base.MessageEvent = function(data) {  this.data = data;};Base.PingEvent = function(data) {  this.data = data;};Base.PongEvent = function(data) {  this.data = data;};module.exports = Base;
 |