text: module.exports = Socket;
  
  var BCSocket = require('browserchannel/dist/bcsocket-uncompressed').BCSocket;
  
  function Socket(options) {
    this._options = options;
    this._messageQueue = [];
    this._connectedOnce = false;
    this._attemptNum = 0;
    this._url = getWebSocketURL(options);
  
    if (supportWebSockets() && !options.browserChannelOnly) {
      this._createWebSocket();
    } else {
      this._createBrowserChannel();
    }
  }
  
  Socket.prototype._createWebSocket = function() {
  
    this._type = 'websocket';
    this._socket = new WebSocket(this._url);
  
    this.open = this._createWebSocket.bind(this);
    this._syncState();
  
    this._socket.onmessage = this._ws_onmessage.bind(this);
    this._socket.onopen = this._ws_onopen.bind(this);
    this._socket.onclose = this._ws_onclose.bind(this);
  
  };
  
  Socket.prototype._createBrowserChannel = function() {
    this._type = 'browserchannel';
    this._socket = BCSocket(this._options.base, this._options);
  
    this.open = this._createBrowserChannel.bind(this);
    this._syncState();
  
    this._socket.onmessage = this._bc_onmessage.bind(this);
    this._socket.onopen = this._bc_onopen.bind(this);
    this._socket.onclose = this._bc_onclose.bind(this);
  };
  
  Socket.prototype._ws_onmessage = function(message) {
    this._syncState();
    message.data = JSON.parse(message.data);
    this.onmessage && this.onmessage(message);
  };
  
  Socket.prototype._ws_onopen = function(event) {
    this._attemptNum = 0;
    this._connectedOnce = true;
  
    this._syncState();
    this._flushQueue();
  
    this.onopen && this.onopen(event);
  };
  
  Socket.prototype._ws_onclose = function(event) {
    this._syncState();
  
    this.onclose && this.onclose(event);
  
    if (!this._connectedOnce) {
      return this._createBrowserChannel();
    }
  
    var socket = this;
  
    if (this._options.reconnect) {
      setTimeout(function(){
        if (socket.readyState === socket.CLOSED) {
          socket._createWebSocket();
        }
      }, this._getTimeout(event.wasClean));
    }
    this._attemptNum++;
  };
  
  Socket.prototype._getTimeout = function(wasClean) {
    if (wasClean && this._attemptNum < 2) {
      return this._options.timeout / 10
    }
    else {
      var base = this._options.timeout;
      var increment = this._options.timeoutIncrement * this._attemptNum;
      var maxTimeout = base + increment;
      return getRandom(maxTimeout / 3, maxTimeout);
    }
  };
  
  Socket.prototype._bc_onmessage = function(data) {
    this._syncState();
    this.onmessage && this.onmessage(data);
  };
  
  Socket.prototype._bc_onopen = function(event) {
    this._syncState();
    this.onopen && this.onopen(event);
  };
  
  Socket.prototype._bc_onclose = function(event) {
    this._syncState();
    this.onclose && this.onclose(event);
  };
  
  Socket.prototype._flushQueue = function(){
    while (this._messageQueue.length !== 0) {
      var data = this._messageQueue.shift();
      this._send(data);
    }
  };
  
  Socket.prototype._send = function(data){
    if (this._type === 'websocket' && (typeof data !== 'string')) data = JSON.stringify(data);
  
    this._socket.send(data);
  };
  
  Socket.prototype.send = function(data){
    if (this._type === 'websocket') {
      if (this._socket.readyState === WebSocket.OPEN && this._messageQueue.length === 0) {
        this._send(data);
      } else {
        this._messageQueue.push(data);
      }
    } else {
      this._send(data);
    }
  };
  
  Socket.prototype.close = function(){
    this._socket.close();
  };
  
  Socket.prototype._syncState = function(){
    this.readyState = this._socket.readyState;
  };
  
  Socket.prototype.reconnect = function(){
    if (this._type === 'websocket' && this.readyState === this.CLOSED){
      this._createWebSocket();
    }
  };
  
  // ShareJS constants
  Socket.prototype.canSendWhileConnecting = true;
  Socket.prototype.canSendJSON = true;
  
  // WebSocket constants
  Socket.prototype.CONNECTING = 0;
  Socket.prototype.OPEN = 1;
  Socket.prototype.CLOSING = 2;
  Socket.prototype.CLOSED = 3;
  
  function getRandom(min, max){
    return Math.random() * (max - min) + min;
  }
  
  function supportWebSockets(){
    // The condition is from Modernizr
    // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/websockets.js#L28
    return 'WebSocket' in window && window.WebSocket.CLOSING === 2;
  }
  
  function getWebSocketURL(options){
    var port;
  
    if (window.location.port !== '') {
      port = ":" + window.location.port;
    }
  
    var srvPort = options.srvPort;
    var srvSecurePort = options.srvSecurePort;
  
    var srvHost = options.srvHost || window.location.hostname;
    var srvProtocol = options.srvProtocol || window.location.protocol;
  
    var protocol = (srvProtocol === 'https:' ? 'wss:' : 'ws:');
  
    if (protocol === 'ws:' && srvPort) {
      port = ":" + srvPort;
    } else if (protocol === 'wss:' && srvSecurePort) {
      port = ":" + srvSecurePort;
    }
    return protocol + '//' + srvHost + (port || "") + options.base;
  }
  
  // Maybe need to use reconnection timing algorithm from
  // http://blog.johnryding.com/post/78544969349/how-to-reconnect-web-sockets-in-a-realtime-web-app
  