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 };