200 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			200 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict';
 | 
						|
 | 
						|
var Stream      = require('stream').Stream,
 | 
						|
    util        = require('util'),
 | 
						|
    driver      = require('websocket-driver'),
 | 
						|
    EventTarget = require('./api/event_target'),
 | 
						|
    Event       = require('./api/event');
 | 
						|
 | 
						|
var API = function(options) {
 | 
						|
  options = options || {};
 | 
						|
  driver.validateOptions(options, ['headers', 'extensions', 'maxLength', 'ping', 'proxy', 'tls', 'ca']);
 | 
						|
 | 
						|
  this.readable = this.writable = true;
 | 
						|
 | 
						|
  var headers = options.headers;
 | 
						|
  if (headers) {
 | 
						|
    for (var name in headers) this._driver.setHeader(name, headers[name]);
 | 
						|
  }
 | 
						|
 | 
						|
  var extensions = options.extensions;
 | 
						|
  if (extensions) {
 | 
						|
    [].concat(extensions).forEach(this._driver.addExtension, this._driver);
 | 
						|
  }
 | 
						|
 | 
						|
  this._ping          = options.ping;
 | 
						|
  this._pingId        = 0;
 | 
						|
  this.readyState     = API.CONNECTING;
 | 
						|
  this.bufferedAmount = 0;
 | 
						|
  this.protocol       = '';
 | 
						|
  this.url            = this._driver.url;
 | 
						|
  this.version        = this._driver.version;
 | 
						|
 | 
						|
  var self = this;
 | 
						|
 | 
						|
  this._driver.on('open',    function(e) { self._open() });
 | 
						|
  this._driver.on('message', function(e) { self._receiveMessage(e.data) });
 | 
						|
  this._driver.on('close',   function(e) { self._beginClose(e.reason, e.code) });
 | 
						|
 | 
						|
  this._driver.on('error', function(error) {
 | 
						|
    self._emitError(error.message);
 | 
						|
  });
 | 
						|
  this.on('error', function() {});
 | 
						|
 | 
						|
  this._driver.messages.on('drain', function() {
 | 
						|
    self.emit('drain');
 | 
						|
  });
 | 
						|
 | 
						|
  if (this._ping)
 | 
						|
    this._pingTimer = setInterval(function() {
 | 
						|
      self._pingId += 1;
 | 
						|
      self.ping(self._pingId.toString());
 | 
						|
    }, this._ping * 1000);
 | 
						|
 | 
						|
  this._configureStream();
 | 
						|
 | 
						|
  if (!this._proxy) {
 | 
						|
    this._stream.pipe(this._driver.io);
 | 
						|
    this._driver.io.pipe(this._stream);
 | 
						|
  }
 | 
						|
};
 | 
						|
util.inherits(API, Stream);
 | 
						|
 | 
						|
API.CONNECTING = 0;
 | 
						|
API.OPEN       = 1;
 | 
						|
API.CLOSING    = 2;
 | 
						|
API.CLOSED     = 3;
 | 
						|
 | 
						|
API.CLOSE_TIMEOUT = 30000;
 | 
						|
 | 
						|
var instance = {
 | 
						|
  write: function(data) {
 | 
						|
    return this.send(data);
 | 
						|
  },
 | 
						|
 | 
						|
  end: function(data) {
 | 
						|
    if (data !== undefined) this.send(data);
 | 
						|
    this.close();
 | 
						|
  },
 | 
						|
 | 
						|
  pause: function() {
 | 
						|
    return this._driver.messages.pause();
 | 
						|
  },
 | 
						|
 | 
						|
  resume: function() {
 | 
						|
    return this._driver.messages.resume();
 | 
						|
  },
 | 
						|
 | 
						|
  send: function(data) {
 | 
						|
    if (this.readyState > API.OPEN) return false;
 | 
						|
    if (!(data instanceof Buffer)) data = String(data);
 | 
						|
    return this._driver.messages.write(data);
 | 
						|
  },
 | 
						|
 | 
						|
  ping: function(message, callback) {
 | 
						|
    if (this.readyState > API.OPEN) return false;
 | 
						|
    return this._driver.ping(message, callback);
 | 
						|
  },
 | 
						|
 | 
						|
  close: function(code, reason) {
 | 
						|
    if (code === undefined) code = 1000;
 | 
						|
    if (reason === undefined) reason = '';
 | 
						|
 | 
						|
    if (code !== 1000 && (code < 3000 || code > 4999))
 | 
						|
      throw new Error("Failed to execute 'close' on WebSocket: " +
 | 
						|
                      "The code must be either 1000, or between 3000 and 4999. " +
 | 
						|
                      code + " is neither.");
 | 
						|
 | 
						|
    if (this.readyState < API.CLOSING) {
 | 
						|
      var self = this;
 | 
						|
      this._closeTimer = setTimeout(function() {
 | 
						|
        self._beginClose('', 1006);
 | 
						|
      }, API.CLOSE_TIMEOUT);
 | 
						|
    }
 | 
						|
 | 
						|
    if (this.readyState !== API.CLOSED) this.readyState = API.CLOSING;
 | 
						|
 | 
						|
    this._driver.close(reason, code);
 | 
						|
  },
 | 
						|
 | 
						|
  _configureStream: function() {
 | 
						|
    var self = this;
 | 
						|
 | 
						|
    this._stream.setTimeout(0);
 | 
						|
    this._stream.setNoDelay(true);
 | 
						|
 | 
						|
    ['close', 'end'].forEach(function(event) {
 | 
						|
      this._stream.on(event, function() { self._finalizeClose() });
 | 
						|
    }, this);
 | 
						|
 | 
						|
    this._stream.on('error', function(error) {
 | 
						|
      self._emitError('Network error: ' + self.url + ': ' + error.message);
 | 
						|
      self._finalizeClose();
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  _open: function() {
 | 
						|
    if (this.readyState !== API.CONNECTING) return;
 | 
						|
 | 
						|
    this.readyState = API.OPEN;
 | 
						|
    this.protocol = this._driver.protocol || '';
 | 
						|
 | 
						|
    var event = new Event('open');
 | 
						|
    event.initEvent('open', false, false);
 | 
						|
    this.dispatchEvent(event);
 | 
						|
  },
 | 
						|
 | 
						|
  _receiveMessage: function(data) {
 | 
						|
    if (this.readyState > API.OPEN) return false;
 | 
						|
 | 
						|
    if (this.readable) this.emit('data', data);
 | 
						|
 | 
						|
    var event = new Event('message', { data: data });
 | 
						|
    event.initEvent('message', false, false);
 | 
						|
    this.dispatchEvent(event);
 | 
						|
  },
 | 
						|
 | 
						|
  _emitError: function(message) {
 | 
						|
    if (this.readyState >= API.CLOSING) return;
 | 
						|
 | 
						|
    var event = new Event('error', { message: message });
 | 
						|
    event.initEvent('error', false, false);
 | 
						|
    this.dispatchEvent(event);
 | 
						|
  },
 | 
						|
 | 
						|
  _beginClose: function(reason, code) {
 | 
						|
    if (this.readyState === API.CLOSED) return;
 | 
						|
    this.readyState = API.CLOSING;
 | 
						|
    this._closeParams = [reason, code];
 | 
						|
 | 
						|
    if (this._stream) {
 | 
						|
      this._stream.destroy();
 | 
						|
      if (!this._stream.readable) this._finalizeClose();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _finalizeClose: function() {
 | 
						|
    if (this.readyState === API.CLOSED) return;
 | 
						|
    this.readyState = API.CLOSED;
 | 
						|
 | 
						|
    if (this._closeTimer) clearTimeout(this._closeTimer);
 | 
						|
    if (this._pingTimer) clearInterval(this._pingTimer);
 | 
						|
    if (this._stream) this._stream.end();
 | 
						|
 | 
						|
    if (this.readable) this.emit('end');
 | 
						|
    this.readable = this.writable = false;
 | 
						|
 | 
						|
    var reason = this._closeParams ? this._closeParams[0] : '',
 | 
						|
        code   = this._closeParams ? this._closeParams[1] : 1006;
 | 
						|
 | 
						|
    var event = new Event('close', { code: code, reason: reason });
 | 
						|
    event.initEvent('close', false, false);
 | 
						|
    this.dispatchEvent(event);
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
for (var method in instance) API.prototype[method] = instance[method];
 | 
						|
for (var key in EventTarget) API.prototype[key] = EventTarget[key];
 | 
						|
 | 
						|
module.exports = API;
 |