github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/themes/wind/static/libs/sockjs-client-1.1.0/lib/main.js (about)

     1  'use strict';
     2  
     3  require('./shims');
     4  
     5  var URL = require('url-parse')
     6    , inherits = require('inherits')
     7    , JSON3 = require('json3')
     8    , random = require('./utils/random')
     9    , escape = require('./utils/escape')
    10    , urlUtils = require('./utils/url')
    11    , eventUtils = require('./utils/event')
    12    , transport = require('./utils/transport')
    13    , objectUtils = require('./utils/object')
    14    , browser = require('./utils/browser')
    15    , log = require('./utils/log')
    16    , Event = require('./event/event')
    17    , EventTarget = require('./event/eventtarget')
    18    , loc = require('./location')
    19    , CloseEvent = require('./event/close')
    20    , TransportMessageEvent = require('./event/trans-message')
    21    , InfoReceiver = require('./info-receiver')
    22    ;
    23  
    24  var debug = function() {};
    25  if (process.env.NODE_ENV !== 'production') {
    26    debug = require('debug')('sockjs-client:main');
    27  }
    28  
    29  var transports;
    30  
    31  // follow constructor steps defined at http://dev.w3.org/html5/websockets/#the-websocket-interface
    32  function SockJS(url, protocols, options) {
    33    if (!(this instanceof SockJS)) {
    34      return new SockJS(url, protocols, options);
    35    }
    36    if (arguments.length < 1) {
    37      throw new TypeError("Failed to construct 'SockJS: 1 argument required, but only 0 present");
    38    }
    39    EventTarget.call(this);
    40  
    41    this.readyState = SockJS.CONNECTING;
    42    this.extensions = '';
    43    this.protocol = '';
    44  
    45    // non-standard extension
    46    options = options || {};
    47    if (options.protocols_whitelist) {
    48      log.warn("'protocols_whitelist' is DEPRECATED. Use 'transports' instead.");
    49    }
    50    this._transportsWhitelist = options.transports;
    51    this._transportOptions = options.transportOptions || {};
    52  
    53    var sessionId = options.sessionId || 8;
    54    if (typeof sessionId === 'function') {
    55      this._generateSessionId = sessionId;
    56    } else if (typeof sessionId === 'number') {
    57      this._generateSessionId = function() {
    58        return random.string(sessionId);
    59      };
    60    } else {
    61      throw new TypeError('If sessionId is used in the options, it needs to be a number or a function.');
    62    }
    63  
    64    this._server = options.server || random.numberString(1000);
    65  
    66    // Step 1 of WS spec - parse and validate the url. Issue #8
    67    var parsedUrl = new URL(url);
    68    if (!parsedUrl.host || !parsedUrl.protocol) {
    69      throw new SyntaxError("The URL '" + url + "' is invalid");
    70    } else if (parsedUrl.hash) {
    71      throw new SyntaxError('The URL must not contain a fragment');
    72    } else if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
    73      throw new SyntaxError("The URL's scheme must be either 'http:' or 'https:'. '" + parsedUrl.protocol + "' is not allowed.");
    74    }
    75  
    76    var secure = parsedUrl.protocol === 'https:';
    77    // Step 2 - don't allow secure origin with an insecure protocol
    78    if (loc.protocol === 'https' && !secure) {
    79      throw new Error('SecurityError: An insecure SockJS connection may not be initiated from a page loaded over HTTPS');
    80    }
    81  
    82    // Step 3 - check port access - no need here
    83    // Step 4 - parse protocols argument
    84    if (!protocols) {
    85      protocols = [];
    86    } else if (!Array.isArray(protocols)) {
    87      protocols = [protocols];
    88    }
    89  
    90    // Step 5 - check protocols argument
    91    var sortedProtocols = protocols.sort();
    92    sortedProtocols.forEach(function(proto, i) {
    93      if (!proto) {
    94        throw new SyntaxError("The protocols entry '" + proto + "' is invalid.");
    95      }
    96      if (i < (sortedProtocols.length - 1) && proto === sortedProtocols[i + 1]) {
    97        throw new SyntaxError("The protocols entry '" + proto + "' is duplicated.");
    98      }
    99    });
   100  
   101    // Step 6 - convert origin
   102    var o = urlUtils.getOrigin(loc.href);
   103    this._origin = o ? o.toLowerCase() : null;
   104  
   105    // remove the trailing slash
   106    parsedUrl.set('pathname', parsedUrl.pathname.replace(/\/+$/, ''));
   107  
   108    // store the sanitized url
   109    this.url = parsedUrl.href;
   110    debug('using url', this.url);
   111  
   112    // Step 7 - start connection in background
   113    // obtain server info
   114    // http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-26
   115    this._urlInfo = {
   116      nullOrigin: !browser.hasDomain()
   117    , sameOrigin: urlUtils.isOriginEqual(this.url, loc.href)
   118    , sameScheme: urlUtils.isSchemeEqual(this.url, loc.href)
   119    };
   120  
   121    this._ir = new InfoReceiver(this.url, this._urlInfo);
   122    this._ir.once('finish', this._receiveInfo.bind(this));
   123  }
   124  
   125  inherits(SockJS, EventTarget);
   126  
   127  function userSetCode(code) {
   128    return code === 1000 || (code >= 3000 && code <= 4999);
   129  }
   130  
   131  SockJS.prototype.close = function(code, reason) {
   132    // Step 1
   133    if (code && !userSetCode(code)) {
   134      throw new Error('InvalidAccessError: Invalid code');
   135    }
   136    // Step 2.4 states the max is 123 bytes, but we are just checking length
   137    if (reason && reason.length > 123) {
   138      throw new SyntaxError('reason argument has an invalid length');
   139    }
   140  
   141    // Step 3.1
   142    if (this.readyState === SockJS.CLOSING || this.readyState === SockJS.CLOSED) {
   143      return;
   144    }
   145  
   146    // TODO look at docs to determine how to set this
   147    var wasClean = true;
   148    this._close(code || 1000, reason || 'Normal closure', wasClean);
   149  };
   150  
   151  SockJS.prototype.send = function(data) {
   152    // #13 - convert anything non-string to string
   153    // TODO this currently turns objects into [object Object]
   154    if (typeof data !== 'string') {
   155      data = '' + data;
   156    }
   157    if (this.readyState === SockJS.CONNECTING) {
   158      throw new Error('InvalidStateError: The connection has not been established yet');
   159    }
   160    if (this.readyState !== SockJS.OPEN) {
   161      return;
   162    }
   163    this._transport.send(escape.quote(data));
   164  };
   165  
   166  SockJS.version = require('./version');
   167  
   168  SockJS.CONNECTING = 0;
   169  SockJS.OPEN = 1;
   170  SockJS.CLOSING = 2;
   171  SockJS.CLOSED = 3;
   172  
   173  SockJS.prototype._receiveInfo = function(info, rtt) {
   174    debug('_receiveInfo', rtt);
   175    this._ir = null;
   176    if (!info) {
   177      this._close(1002, 'Cannot connect to server');
   178      return;
   179    }
   180  
   181    // establish a round-trip timeout (RTO) based on the
   182    // round-trip time (RTT)
   183    this._rto = this.countRTO(rtt);
   184    // allow server to override url used for the actual transport
   185    this._transUrl = info.base_url ? info.base_url : this.url;
   186    info = objectUtils.extend(info, this._urlInfo);
   187    debug('info', info);
   188    // determine list of desired and supported transports
   189    var enabledTransports = transports.filterToEnabled(this._transportsWhitelist, info);
   190    this._transports = enabledTransports.main;
   191    debug(this._transports.length + ' enabled transports');
   192  
   193    this._connect();
   194  };
   195  
   196  SockJS.prototype._connect = function() {
   197    for (var Transport = this._transports.shift(); Transport; Transport = this._transports.shift()) {
   198      debug('attempt', Transport.transportName);
   199      if (Transport.needBody) {
   200        if (!global.document.body ||
   201            (typeof global.document.readyState !== 'undefined' &&
   202              global.document.readyState !== 'complete' &&
   203              global.document.readyState !== 'interactive')) {
   204          debug('waiting for body');
   205          this._transports.unshift(Transport);
   206          eventUtils.attachEvent('load', this._connect.bind(this));
   207          return;
   208        }
   209      }
   210  
   211      // calculate timeout based on RTO and round trips. Default to 5s
   212      var timeoutMs = (this._rto * Transport.roundTrips) || 5000;
   213      this._transportTimeoutId = setTimeout(this._transportTimeout.bind(this), timeoutMs);
   214      debug('using timeout', timeoutMs);
   215  
   216      var transportUrl = urlUtils.addPath(this._transUrl, '/' + this._server + '/' + this._generateSessionId());
   217      var options = this._transportOptions[Transport.transportName];
   218      debug('transport url', transportUrl);
   219      var transportObj = new Transport(transportUrl, this._transUrl, options);
   220      transportObj.on('message', this._transportMessage.bind(this));
   221      transportObj.once('close', this._transportClose.bind(this));
   222      transportObj.transportName = Transport.transportName;
   223      this._transport = transportObj;
   224  
   225      return;
   226    }
   227    this._close(2000, 'All transports failed', false);
   228  };
   229  
   230  SockJS.prototype._transportTimeout = function() {
   231    debug('_transportTimeout');
   232    if (this.readyState === SockJS.CONNECTING) {
   233      this._transportClose(2007, 'Transport timed out');
   234    }
   235  };
   236  
   237  SockJS.prototype._transportMessage = function(msg) {
   238    debug('_transportMessage', msg);
   239    var self = this
   240      , type = msg.slice(0, 1)
   241      , content = msg.slice(1)
   242      , payload
   243      ;
   244  
   245    // first check for messages that don't need a payload
   246    switch (type) {
   247      case 'o':
   248        this._open();
   249        return;
   250      case 'h':
   251        this.dispatchEvent(new Event('heartbeat'));
   252        debug('heartbeat', this.transport);
   253        return;
   254    }
   255  
   256    if (content) {
   257      try {
   258        payload = JSON3.parse(content);
   259      } catch (e) {
   260        debug('bad json', content);
   261      }
   262    }
   263  
   264    if (typeof payload === 'undefined') {
   265      debug('empty payload', content);
   266      return;
   267    }
   268  
   269    switch (type) {
   270      case 'a':
   271        if (Array.isArray(payload)) {
   272          payload.forEach(function(p) {
   273            debug('message', self.transport, p);
   274            self.dispatchEvent(new TransportMessageEvent(p));
   275          });
   276        }
   277        break;
   278      case 'm':
   279        debug('message', this.transport, payload);
   280        this.dispatchEvent(new TransportMessageEvent(payload));
   281        break;
   282      case 'c':
   283        if (Array.isArray(payload) && payload.length === 2) {
   284          this._close(payload[0], payload[1], true);
   285        }
   286        break;
   287    }
   288  };
   289  
   290  SockJS.prototype._transportClose = function(code, reason) {
   291    debug('_transportClose', this.transport, code, reason);
   292    if (this._transport) {
   293      this._transport.removeAllListeners();
   294      this._transport = null;
   295      this.transport = null;
   296    }
   297  
   298    if (!userSetCode(code) && code !== 2000 && this.readyState === SockJS.CONNECTING) {
   299      this._connect();
   300      return;
   301    }
   302  
   303    this._close(code, reason);
   304  };
   305  
   306  SockJS.prototype._open = function() {
   307    debug('_open', this._transport.transportName, this.readyState);
   308    if (this.readyState === SockJS.CONNECTING) {
   309      if (this._transportTimeoutId) {
   310        clearTimeout(this._transportTimeoutId);
   311        this._transportTimeoutId = null;
   312      }
   313      this.readyState = SockJS.OPEN;
   314      this.transport = this._transport.transportName;
   315      this.dispatchEvent(new Event('open'));
   316      debug('connected', this.transport);
   317    } else {
   318      // The server might have been restarted, and lost track of our
   319      // connection.
   320      this._close(1006, 'Server lost session');
   321    }
   322  };
   323  
   324  SockJS.prototype._close = function(code, reason, wasClean) {
   325    debug('_close', this.transport, code, reason, wasClean, this.readyState);
   326    var forceFail = false;
   327  
   328    if (this._ir) {
   329      forceFail = true;
   330      this._ir.close();
   331      this._ir = null;
   332    }
   333    if (this._transport) {
   334      this._transport.close();
   335      this._transport = null;
   336      this.transport = null;
   337    }
   338  
   339    if (this.readyState === SockJS.CLOSED) {
   340      throw new Error('InvalidStateError: SockJS has already been closed');
   341    }
   342  
   343    this.readyState = SockJS.CLOSING;
   344    setTimeout(function() {
   345      this.readyState = SockJS.CLOSED;
   346  
   347      if (forceFail) {
   348        this.dispatchEvent(new Event('error'));
   349      }
   350  
   351      var e = new CloseEvent('close');
   352      e.wasClean = wasClean || false;
   353      e.code = code || 1000;
   354      e.reason = reason;
   355  
   356      this.dispatchEvent(e);
   357      this.onmessage = this.onclose = this.onerror = null;
   358      debug('disconnected');
   359    }.bind(this), 0);
   360  };
   361  
   362  // See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/
   363  // and RFC 2988.
   364  SockJS.prototype.countRTO = function(rtt) {
   365    // In a local environment, when using IE8/9 and the `jsonp-polling`
   366    // transport the time needed to establish a connection (the time that pass
   367    // from the opening of the transport to the call of `_dispatchOpen`) is
   368    // around 200msec (the lower bound used in the article above) and this
   369    // causes spurious timeouts. For this reason we calculate a value slightly
   370    // larger than that used in the article.
   371    if (rtt > 100) {
   372      return 4 * rtt; // rto > 400msec
   373    }
   374    return 300 + rtt; // 300msec < rto <= 400msec
   375  };
   376  
   377  module.exports = function(availableTransports) {
   378    transports = transport(availableTransports);
   379    require('./iframe-bootstrap')(SockJS, availableTransports);
   380    return SockJS;
   381  };