UNPKG

happn-primus

Version:

Primus is a simple abstraction around real-time frameworks. It allows you to easily switch between different frameworks without any code changes.

2,209 lines (1,828 loc) 96.4 kB
(function(f){var g;if(typeof window!=='undefined'){g=window}else if(typeof self!=='undefined'){g=self}g.eio=f()})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ module.exports = _dereq_('./socket'); /** * Exports parser * * @api public * */ module.exports.parser = _dereq_('engine.io-parser'); },{"./socket":2,"engine.io-parser":18}],2:[function(_dereq_,module,exports){ (function (global){ /** * Module dependencies. */ var transports = _dereq_('./transports'); var Emitter = _dereq_('component-emitter'); var debug = _dereq_('debug')('engine.io-client:socket'); var index = _dereq_('indexof'); var parser = _dereq_('engine.io-parser'); var parseuri = _dereq_('parseuri'); var parsejson = _dereq_('parsejson'); var parseqs = _dereq_('parseqs'); /** * Module exports. */ module.exports = Socket; /** * Noop function. * * @api private */ function noop(){} /** * Socket constructor. * * @param {String|Object} uri or options * @param {Object} options * @api public */ function Socket(uri, opts){ if (!(this instanceof Socket)) return new Socket(uri, opts); opts = opts || {}; if (uri && 'object' == typeof uri) { opts = uri; uri = null; } if (uri) { uri = parseuri(uri); opts.hostname = uri.host; opts.secure = uri.protocol == 'https' || uri.protocol == 'wss'; opts.port = uri.port; if (uri.query) opts.query = uri.query; } else if (opts.host) { opts.hostname = parseuri(opts.host).host; } this.secure = null != opts.secure ? opts.secure : (global.location && 'https:' == location.protocol); if (opts.hostname && !opts.port) { // if no port is specified manually, use the protocol default opts.port = this.secure ? '443' : '80'; } this.agent = opts.agent || false; this.hostname = opts.hostname || (global.location ? location.hostname : 'localhost'); this.port = opts.port || (global.location && location.port ? location.port : (this.secure ? 443 : 80)); this.query = opts.query || {}; if ('string' == typeof this.query) this.query = parseqs.decode(this.query); this.upgrade = false !== opts.upgrade; this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/'; this.forceJSONP = !!opts.forceJSONP; this.jsonp = false !== opts.jsonp; this.forceBase64 = !!opts.forceBase64; this.enablesXDR = !!opts.enablesXDR; this.timestampParam = opts.timestampParam || 't'; this.timestampRequests = opts.timestampRequests; this.transports = opts.transports || ['polling', 'websocket']; this.readyState = ''; this.writeBuffer = []; this.policyPort = opts.policyPort || 843; this.rememberUpgrade = opts.rememberUpgrade || false; this.binaryType = null; this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades; this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || {}) : false; if (true === this.perMessageDeflate) this.perMessageDeflate = {}; if (this.perMessageDeflate && null == this.perMessageDeflate.threshold) { this.perMessageDeflate.threshold = 1024; } // SSL options for Node.js client this.pfx = opts.pfx || null; this.key = opts.key || null; this.passphrase = opts.passphrase || null; this.cert = opts.cert || null; this.ca = opts.ca || null; this.ciphers = opts.ciphers || null; this.rejectUnauthorized = opts.rejectUnauthorized === undefined ? true : opts.rejectUnauthorized; // other options for Node.js client var freeGlobal = typeof global == 'object' && global; if (freeGlobal.global === freeGlobal) { if (opts.extraHeaders && Object.keys(opts.extraHeaders).length > 0) { this.extraHeaders = opts.extraHeaders; } } this.open(); } Socket.priorWebsocketSuccess = false; /** * Mix in `Emitter`. */ Emitter(Socket.prototype); /** * Protocol version. * * @api public */ Socket.protocol = parser.protocol; // this is an int /** * Expose deps for legacy compatibility * and standalone browser access. */ Socket.Socket = Socket; Socket.Transport = _dereq_('./transport'); Socket.transports = _dereq_('./transports'); Socket.parser = _dereq_('engine.io-parser'); /** * Creates transport of the given type. * * @param {String} transport name * @return {Transport} * @api private */ Socket.prototype.createTransport = function (name) { debug('creating transport "%s"', name); var query = clone(this.query); // append engine.io protocol identifier query.EIO = parser.protocol; // transport name query.transport = name; // session id if we already have one if (this.id) query.sid = this.id; var transport = new transports[name]({ agent: this.agent, hostname: this.hostname, port: this.port, secure: this.secure, path: this.path, query: query, forceJSONP: this.forceJSONP, jsonp: this.jsonp, forceBase64: this.forceBase64, enablesXDR: this.enablesXDR, timestampRequests: this.timestampRequests, timestampParam: this.timestampParam, policyPort: this.policyPort, socket: this, pfx: this.pfx, key: this.key, passphrase: this.passphrase, cert: this.cert, ca: this.ca, ciphers: this.ciphers, rejectUnauthorized: this.rejectUnauthorized, perMessageDeflate: this.perMessageDeflate, extraHeaders: this.extraHeaders }); return transport; }; function clone (obj) { var o = {}; for (var i in obj) { if (obj.hasOwnProperty(i)) { o[i] = obj[i]; } } return o; } /** * Initializes transport to use and starts probe. * * @api private */ Socket.prototype.open = function () { var transport; if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') != -1) { transport = 'websocket'; } else if (0 === this.transports.length) { // Emit error on next tick so it can be listened to var self = this; setTimeout(function() { self.emit('error', 'No transports available'); }, 0); return; } else { transport = this.transports[0]; } this.readyState = 'opening'; // Retry with the next transport if the transport is disabled (jsonp: false) try { transport = this.createTransport(transport); } catch (e) { this.transports.shift(); this.open(); return; } transport.open(); this.setTransport(transport); }; /** * Sets the current transport. Disables the existing one (if any). * * @api private */ Socket.prototype.setTransport = function(transport){ debug('setting transport %s', transport.name); var self = this; if (this.transport) { debug('clearing existing transport %s', this.transport.name); this.transport.removeAllListeners(); } // set up transport this.transport = transport; // set up transport listeners transport .on('drain', function(){ self.onDrain(); }) .on('packet', function(packet){ self.onPacket(packet); }) .on('error', function(e){ self.onError(e); }) .on('close', function(){ self.onClose('transport close'); }); }; /** * Probes a transport. * * @param {String} transport name * @api private */ Socket.prototype.probe = function (name) { debug('probing transport "%s"', name); var transport = this.createTransport(name, { probe: 1 }) , failed = false , self = this; Socket.priorWebsocketSuccess = false; function onTransportOpen(){ if (self.onlyBinaryUpgrades) { var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary; failed = failed || upgradeLosesBinary; } if (failed) return; debug('probe transport "%s" opened', name); transport.send([{ type: 'ping', data: 'probe' }]); transport.once('packet', function (msg) { if (failed) return; if ('pong' == msg.type && 'probe' == msg.data) { debug('probe transport "%s" pong', name); self.upgrading = true; self.emit('upgrading', transport); if (!transport) return; Socket.priorWebsocketSuccess = 'websocket' == transport.name; debug('pausing current transport "%s"', self.transport.name); self.transport.pause(function () { if (failed) return; if ('closed' == self.readyState) return; debug('changing transport and sending upgrade packet'); cleanup(); self.setTransport(transport); transport.send([{ type: 'upgrade' }]); self.emit('upgrade', transport); transport = null; self.upgrading = false; self.flush(); }); } else { debug('probe transport "%s" failed', name); var err = new Error('probe error'); err.transport = transport.name; self.emit('upgradeError', err); } }); } function freezeTransport() { if (failed) return; // Any callback called by transport should be ignored since now failed = true; cleanup(); transport.close(); transport = null; } //Handle any error that happens while probing function onerror(err) { var error = new Error('probe error: ' + err); error.transport = transport.name; freezeTransport(); debug('probe transport "%s" failed because of error: %s', name, err); self.emit('upgradeError', error); } function onTransportClose(){ onerror("transport closed"); } //When the socket is closed while we're probing function onclose(){ onerror("socket closed"); } //When the socket is upgraded while we're probing function onupgrade(to){ if (transport && to.name != transport.name) { debug('"%s" works - aborting "%s"', to.name, transport.name); freezeTransport(); } } //Remove all listeners on the transport and on self function cleanup(){ transport.removeListener('open', onTransportOpen); transport.removeListener('error', onerror); transport.removeListener('close', onTransportClose); self.removeListener('close', onclose); self.removeListener('upgrading', onupgrade); } transport.once('open', onTransportOpen); transport.once('error', onerror); transport.once('close', onTransportClose); this.once('close', onclose); this.once('upgrading', onupgrade); transport.open(); }; /** * Called when connection is deemed open. * * @api public */ Socket.prototype.onOpen = function () { debug('socket open'); this.readyState = 'open'; Socket.priorWebsocketSuccess = 'websocket' == this.transport.name; this.emit('open'); this.flush(); // we check for `readyState` in case an `open` // listener already closed the socket if ('open' == this.readyState && this.upgrade && this.transport.pause) { debug('starting upgrade probes'); for (var i = 0, l = this.upgrades.length; i < l; i++) { this.probe(this.upgrades[i]); } } }; /** * Handles a packet. * * @api private */ Socket.prototype.onPacket = function (packet) { if ('opening' == this.readyState || 'open' == this.readyState) { debug('socket receive: type "%s", data "%s"', packet.type, packet.data); this.emit('packet', packet); // Socket is live - any packet counts this.emit('heartbeat'); switch (packet.type) { case 'open': this.onHandshake(parsejson(packet.data)); break; case 'pong': this.setPing(); this.emit('pong'); break; case 'error': var err = new Error('server error'); err.code = packet.data; this.onError(err); break; case 'message': this.emit('data', packet.data); this.emit('message', packet.data); break; } } else { debug('packet received with socket readyState "%s"', this.readyState); } }; /** * Called upon handshake completion. * * @param {Object} handshake obj * @api private */ Socket.prototype.onHandshake = function (data) { this.emit('handshake', data); this.id = data.sid; this.transport.query.sid = data.sid; this.upgrades = this.filterUpgrades(data.upgrades); this.pingInterval = data.pingInterval; this.pingTimeout = data.pingTimeout; this.onOpen(); // In case open handler closes socket if ('closed' == this.readyState) return; this.setPing(); // Prolong liveness of socket on heartbeat this.removeListener('heartbeat', this.onHeartbeat); this.on('heartbeat', this.onHeartbeat); }; /** * Resets ping timeout. * * @api private */ Socket.prototype.onHeartbeat = function (timeout) { clearTimeout(this.pingTimeoutTimer); var self = this; self.pingTimeoutTimer = setTimeout(function () { if ('closed' == self.readyState) return; self.onClose('ping timeout'); }, timeout || (self.pingInterval + self.pingTimeout)); }; /** * Pings server every `this.pingInterval` and expects response * within `this.pingTimeout` or closes connection. * * @api private */ Socket.prototype.setPing = function () { var self = this; clearTimeout(self.pingIntervalTimer); self.pingIntervalTimer = setTimeout(function () { debug('writing ping packet - expecting pong within %sms', self.pingTimeout); self.ping(); self.onHeartbeat(self.pingTimeout); }, self.pingInterval); }; /** * Sends a ping packet. * * @api private */ Socket.prototype.ping = function () { var self = this; this.sendPacket('ping', function(){ self.emit('ping'); }); }; /** * Called on `drain` event * * @api private */ Socket.prototype.onDrain = function() { this.writeBuffer.splice(0, this.prevBufferLen); // setting prevBufferLen = 0 is very important // for example, when upgrading, upgrade packet is sent over, // and a nonzero prevBufferLen could cause problems on `drain` this.prevBufferLen = 0; if (0 === this.writeBuffer.length) { this.emit('drain'); } else { this.flush(); } }; /** * Flush write buffers. * * @api private */ Socket.prototype.flush = function () { if ('closed' != this.readyState && this.transport.writable && !this.upgrading && this.writeBuffer.length) { debug('flushing %d packets in socket', this.writeBuffer.length); this.transport.send(this.writeBuffer); // keep track of current length of writeBuffer // splice writeBuffer and callbackBuffer on `drain` this.prevBufferLen = this.writeBuffer.length; this.emit('flush'); } }; /** * Sends a message. * * @param {String} message. * @param {Function} callback function. * @param {Object} options. * @return {Socket} for chaining. * @api public */ Socket.prototype.write = Socket.prototype.send = function (msg, options, fn) { this.sendPacket('message', msg, options, fn); return this; }; /** * Sends a packet. * * @param {String} packet type. * @param {String} data. * @param {Object} options. * @param {Function} callback function. * @api private */ Socket.prototype.sendPacket = function (type, data, options, fn) { if('function' == typeof data) { fn = data; data = undefined; } if ('function' == typeof options) { fn = options; options = null; } if ('closing' == this.readyState || 'closed' == this.readyState) { return; } options = options || {}; options.compress = false !== options.compress; var packet = { type: type, data: data, options: options }; this.emit('packetCreate', packet); this.writeBuffer.push(packet); if (fn) this.once('flush', fn); this.flush(); }; /** * Closes the connection. * * @api private */ Socket.prototype.close = function () { if ('opening' == this.readyState || 'open' == this.readyState) { this.readyState = 'closing'; var self = this; if (this.writeBuffer.length) { this.once('drain', function() { if (this.upgrading) { waitForUpgrade(); } else { close(); } }); } else if (this.upgrading) { waitForUpgrade(); } else { close(); } } function close() { self.onClose('forced close'); debug('socket closing - telling transport to close'); self.transport.close(); } function cleanupAndClose() { self.removeListener('upgrade', cleanupAndClose); self.removeListener('upgradeError', cleanupAndClose); close(); } function waitForUpgrade() { // wait for upgrade to finish since we can't send packets while pausing a transport self.once('upgrade', cleanupAndClose); self.once('upgradeError', cleanupAndClose); } return this; }; /** * Called upon transport error * * @api private */ Socket.prototype.onError = function (err) { debug('socket error %j', err); Socket.priorWebsocketSuccess = false; this.emit('error', err); this.onClose('transport error', err); }; /** * Called upon transport close. * * @api private */ Socket.prototype.onClose = function (reason, desc) { if ('opening' == this.readyState || 'open' == this.readyState || 'closing' == this.readyState) { debug('socket close with reason: "%s"', reason); var self = this; // clear timers clearTimeout(this.pingIntervalTimer); clearTimeout(this.pingTimeoutTimer); // stop event from firing again for transport this.transport.removeAllListeners('close'); // ensure transport won't stay open this.transport.close(); // ignore further transport communication this.transport.removeAllListeners(); // set ready state this.readyState = 'closed'; // clear session id this.id = null; // emit close event this.emit('close', reason, desc); // clean buffers after, so users can still // grab the buffers on `close` event self.writeBuffer = []; self.prevBufferLen = 0; } }; /** * Filters upgrades, returning only those matching client transports. * * @param {Array} server upgrades * @api private * */ Socket.prototype.filterUpgrades = function (upgrades) { var filteredUpgrades = []; for (var i = 0, j = upgrades.length; i<j; i++) { if (~index(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]); } return filteredUpgrades; }; }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./transport":3,"./transports":4,"component-emitter":14,"debug":16,"engine.io-parser":18,"indexof":22,"parsejson":25,"parseqs":26,"parseuri":27}],3:[function(_dereq_,module,exports){ /** * Module dependencies. */ var parser = _dereq_('engine.io-parser'); var Emitter = _dereq_('component-emitter'); /** * Module exports. */ module.exports = Transport; /** * Transport abstract constructor. * * @param {Object} options. * @api private */ function Transport (opts) { this.path = opts.path; this.hostname = opts.hostname; this.port = opts.port; this.secure = opts.secure; this.query = opts.query; this.timestampParam = opts.timestampParam; this.timestampRequests = opts.timestampRequests; this.readyState = ''; this.agent = opts.agent || false; this.socket = opts.socket; this.enablesXDR = opts.enablesXDR; // SSL options for Node.js client this.pfx = opts.pfx; this.key = opts.key; this.passphrase = opts.passphrase; this.cert = opts.cert; this.ca = opts.ca; this.ciphers = opts.ciphers; this.rejectUnauthorized = opts.rejectUnauthorized; // other options for Node.js client this.extraHeaders = opts.extraHeaders; } /** * Mix in `Emitter`. */ Emitter(Transport.prototype); /** * Emits an error. * * @param {String} str * @return {Transport} for chaining * @api public */ Transport.prototype.onError = function (msg, desc) { var err = new Error(msg); err.type = 'TransportError'; err.description = desc; this.emit('error', err); return this; }; /** * Opens the transport. * * @api public */ Transport.prototype.open = function () { if ('closed' == this.readyState || '' == this.readyState) { this.readyState = 'opening'; this.doOpen(); } return this; }; /** * Closes the transport. * * @api private */ Transport.prototype.close = function () { if ('opening' == this.readyState || 'open' == this.readyState) { this.doClose(); this.onClose(); } return this; }; /** * Sends multiple packets. * * @param {Array} packets * @api private */ Transport.prototype.send = function(packets){ if ('open' == this.readyState) { this.write(packets); } else { throw new Error('Transport not open'); } }; /** * Called upon open * * @api private */ Transport.prototype.onOpen = function () { this.readyState = 'open'; this.writable = true; this.emit('open'); }; /** * Called with data. * * @param {String} data * @api private */ Transport.prototype.onData = function(data){ var packet = parser.decodePacket(data, this.socket.binaryType); this.onPacket(packet); }; /** * Called with a decoded packet. */ Transport.prototype.onPacket = function (packet) { this.emit('packet', packet); }; /** * Called upon close. * * @api private */ Transport.prototype.onClose = function () { this.readyState = 'closed'; this.emit('close'); }; },{"component-emitter":14,"engine.io-parser":18}],4:[function(_dereq_,module,exports){ (function (global){ /** * Module dependencies */ var XMLHttpRequest = _dereq_('xmlhttprequest-ssl'); var XHR = _dereq_('./polling-xhr'); var JSONP = _dereq_('./polling-jsonp'); var websocket = _dereq_('./websocket'); /** * Export transports. */ exports.polling = polling; exports.websocket = websocket; /** * Polling transport polymorphic constructor. * Decides on xhr vs jsonp based on feature detection. * * @api private */ function polling(opts){ var xhr; var xd = false; var xs = false; var jsonp = false !== opts.jsonp; if (global.location) { var isSSL = 'https:' == location.protocol; var port = location.port; // some user agents have empty `location.port` if (!port) { port = isSSL ? 443 : 80; } xd = opts.hostname != location.hostname || port != opts.port; xs = opts.secure != isSSL; } opts.xdomain = xd; opts.xscheme = xs; xhr = new XMLHttpRequest(opts); if ('open' in xhr && !opts.forceJSONP) { return new XHR(opts); } else { if (!jsonp) throw new Error('JSONP disabled'); return new JSONP(opts); } } }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./polling-jsonp":5,"./polling-xhr":6,"./websocket":8,"xmlhttprequest-ssl":9}],5:[function(_dereq_,module,exports){ (function (global){ /** * Module requirements. */ var Polling = _dereq_('./polling'); var inherit = _dereq_('component-inherit'); /** * Module exports. */ module.exports = JSONPPolling; /** * Cached regular expressions. */ var rNewline = /\n/g; var rEscapedNewline = /\\n/g; /** * Global JSONP callbacks. */ var callbacks; /** * Callbacks count. */ var index = 0; /** * Noop. */ function empty () { } /** * JSONP Polling constructor. * * @param {Object} opts. * @api public */ function JSONPPolling (opts) { Polling.call(this, opts); this.query = this.query || {}; // define global callbacks array if not present // we do this here (lazily) to avoid unneeded global pollution if (!callbacks) { // we need to consider multiple engines in the same page if (!global.___eio) global.___eio = []; callbacks = global.___eio; } // callback identifier this.index = callbacks.length; // add callback to jsonp global var self = this; callbacks.push(function (msg) { self.onData(msg); }); // append to query string this.query.j = this.index; // prevent spurious errors from being emitted when the window is unloaded if (global.document && global.addEventListener) { global.addEventListener('beforeunload', function () { if (self.script) self.script.onerror = empty; }, false); } } /** * Inherits from Polling. */ inherit(JSONPPolling, Polling); /* * JSONP only supports binary as base64 encoded strings */ JSONPPolling.prototype.supportsBinary = false; /** * Closes the socket. * * @api private */ JSONPPolling.prototype.doClose = function () { if (this.script) { this.script.parentNode.removeChild(this.script); this.script = null; } if (this.form) { this.form.parentNode.removeChild(this.form); this.form = null; this.iframe = null; } Polling.prototype.doClose.call(this); }; /** * Starts a poll cycle. * * @api private */ JSONPPolling.prototype.doPoll = function () { var self = this; var script = document.createElement('script'); if (this.script) { this.script.parentNode.removeChild(this.script); this.script = null; } script.async = true; script.src = this.uri(); script.onerror = function(e){ self.onError('jsonp poll error',e); }; var insertAt = document.getElementsByTagName('script')[0]; if (insertAt) { insertAt.parentNode.insertBefore(script, insertAt); } else { (document.head || document.body).appendChild(script); } this.script = script; var isUAgecko = 'undefined' != typeof navigator && /gecko/i.test(navigator.userAgent); if (isUAgecko) { setTimeout(function () { var iframe = document.createElement('iframe'); document.body.appendChild(iframe); document.body.removeChild(iframe); }, 100); } }; /** * Writes with a hidden iframe. * * @param {String} data to send * @param {Function} called upon flush. * @api private */ JSONPPolling.prototype.doWrite = function (data, fn) { var self = this; if (!this.form) { var form = document.createElement('form'); var area = document.createElement('textarea'); var id = this.iframeId = 'eio_iframe_' + this.index; var iframe; form.className = 'socketio'; form.style.position = 'absolute'; form.style.top = '-1000px'; form.style.left = '-1000px'; form.target = id; form.method = 'POST'; form.setAttribute('accept-charset', 'utf-8'); area.name = 'd'; form.appendChild(area); document.body.appendChild(form); this.form = form; this.area = area; } this.form.action = this.uri(); function complete () { initIframe(); fn(); } function initIframe () { if (self.iframe) { try { self.form.removeChild(self.iframe); } catch (e) { self.onError('jsonp polling iframe removal error', e); } } try { // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) var html = '<iframe src="javascript:0" name="'+ self.iframeId +'">'; iframe = document.createElement(html); } catch (e) { iframe = document.createElement('iframe'); iframe.name = self.iframeId; iframe.src = 'javascript:0'; } iframe.id = self.iframeId; self.form.appendChild(iframe); self.iframe = iframe; } initIframe(); // escape \n to prevent it from being converted into \r\n by some UAs // double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side data = data.replace(rEscapedNewline, '\\\n'); this.area.value = data.replace(rNewline, '\\n'); try { this.form.submit(); } catch(e) {} if (this.iframe.attachEvent) { this.iframe.onreadystatechange = function(){ if (self.iframe.readyState == 'complete') { complete(); } }; } else { this.iframe.onload = complete; } }; }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./polling":7,"component-inherit":15}],6:[function(_dereq_,module,exports){ (function (global){ /** * Module requirements. */ var XMLHttpRequest = _dereq_('xmlhttprequest-ssl'); var Polling = _dereq_('./polling'); var Emitter = _dereq_('component-emitter'); var inherit = _dereq_('component-inherit'); var debug = _dereq_('debug')('engine.io-client:polling-xhr'); /** * Module exports. */ module.exports = XHR; module.exports.Request = Request; /** * Empty function */ function empty(){} /** * XHR Polling constructor. * * @param {Object} opts * @api public */ function XHR(opts){ Polling.call(this, opts); if (global.location) { var isSSL = 'https:' == location.protocol; var port = location.port; // some user agents have empty `location.port` if (!port) { port = isSSL ? 443 : 80; } this.xd = opts.hostname != global.location.hostname || port != opts.port; this.xs = opts.secure != isSSL; } else { this.extraHeaders = opts.extraHeaders; } } /** * Inherits from Polling. */ inherit(XHR, Polling); /** * XHR supports binary */ XHR.prototype.supportsBinary = true; /** * Creates a request. * * @param {String} method * @api private */ XHR.prototype.request = function(opts){ opts = opts || {}; opts.uri = this.uri(); opts.xd = this.xd; opts.xs = this.xs; opts.agent = this.agent || false; opts.supportsBinary = this.supportsBinary; opts.enablesXDR = this.enablesXDR; // SSL options for Node.js client opts.pfx = this.pfx; opts.key = this.key; opts.passphrase = this.passphrase; opts.cert = this.cert; opts.ca = this.ca; opts.ciphers = this.ciphers; opts.rejectUnauthorized = this.rejectUnauthorized; // other options for Node.js client opts.extraHeaders = this.extraHeaders; return new Request(opts); }; /** * Sends data. * * @param {String} data to send. * @param {Function} called upon flush. * @api private */ XHR.prototype.doWrite = function(data, fn){ var isBinary = typeof data !== 'string' && data !== undefined; var req = this.request({ method: 'POST', data: data, isBinary: isBinary }); var self = this; req.on('success', fn); req.on('error', function(err){ self.onError('xhr post error', err); }); this.sendXhr = req; }; /** * Starts a poll cycle. * * @api private */ XHR.prototype.doPoll = function(){ debug('xhr poll'); var req = this.request(); var self = this; req.on('data', function(data){ self.onData(data); }); req.on('error', function(err){ self.onError('xhr poll error', err); }); this.pollXhr = req; }; /** * Request constructor * * @param {Object} options * @api public */ function Request(opts){ this.method = opts.method || 'GET'; this.uri = opts.uri; this.xd = !!opts.xd; this.xs = !!opts.xs; this.async = false !== opts.async; this.data = undefined != opts.data ? opts.data : null; this.agent = opts.agent; this.isBinary = opts.isBinary; this.supportsBinary = opts.supportsBinary; this.enablesXDR = opts.enablesXDR; // SSL options for Node.js client this.pfx = opts.pfx; this.key = opts.key; this.passphrase = opts.passphrase; this.cert = opts.cert; this.ca = opts.ca; this.ciphers = opts.ciphers; this.rejectUnauthorized = opts.rejectUnauthorized; // other options for Node.js client this.extraHeaders = opts.extraHeaders; this.create(); } /** * Mix in `Emitter`. */ Emitter(Request.prototype); /** * Creates the XHR object and sends the request. * * @api private */ Request.prototype.create = function(){ var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR }; // SSL options for Node.js client opts.pfx = this.pfx; opts.key = this.key; opts.passphrase = this.passphrase; opts.cert = this.cert; opts.ca = this.ca; opts.ciphers = this.ciphers; opts.rejectUnauthorized = this.rejectUnauthorized; var xhr = this.xhr = new XMLHttpRequest(opts); var self = this; try { debug('xhr open %s: %s', this.method, this.uri); xhr.open(this.method, this.uri, this.async); try { if (this.extraHeaders) { xhr.setDisableHeaderCheck(true); for (var i in this.extraHeaders) { if (this.extraHeaders.hasOwnProperty(i)) { xhr.setRequestHeader(i, this.extraHeaders[i]); } } } } catch (e) {} if (this.supportsBinary) { // This has to be done after open because Firefox is stupid // http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension xhr.responseType = 'arraybuffer'; } if ('POST' == this.method) { try { if (this.isBinary) { xhr.setRequestHeader('Content-type', 'application/octet-stream'); } else { xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8'); } } catch (e) {} } // ie6 check if ('withCredentials' in xhr) { xhr.withCredentials = true; } if (this.hasXDR()) { xhr.onload = function(){ self.onLoad(); }; xhr.onerror = function(){ self.onError(xhr.responseText); }; } else { xhr.onreadystatechange = function(){ if (4 != xhr.readyState) return; if (200 == xhr.status || 1223 == xhr.status) { self.onLoad(); } else { // make sure the `error` event handler that's user-set // does not throw in the same tick and gets caught here setTimeout(function(){ self.onError(xhr.status); }, 0); } }; } debug('xhr data %s', this.data); xhr.send(this.data); } catch (e) { // Need to defer since .create() is called directly fhrom the constructor // and thus the 'error' event can only be only bound *after* this exception // occurs. Therefore, also, we cannot throw here at all. setTimeout(function() { self.onError(e); }, 0); return; } if (global.document) { this.index = Request.requestsCount++; Request.requests[this.index] = this; } }; /** * Called upon successful response. * * @api private */ Request.prototype.onSuccess = function(){ this.emit('success'); this.cleanup(); }; /** * Called if we have data. * * @api private */ Request.prototype.onData = function(data){ this.emit('data', data); this.onSuccess(); }; /** * Called upon error. * * @api private */ Request.prototype.onError = function(err){ this.emit('error', err); this.cleanup(true); }; /** * Cleans up house. * * @api private */ Request.prototype.cleanup = function(fromError){ if ('undefined' == typeof this.xhr || null === this.xhr) { return; } // xmlhttprequest if (this.hasXDR()) { this.xhr.onload = this.xhr.onerror = empty; } else { this.xhr.onreadystatechange = empty; } if (fromError) { try { this.xhr.abort(); } catch(e) {} } if (global.document) { delete Request.requests[this.index]; } this.xhr = null; }; /** * Called upon load. * * @api private */ Request.prototype.onLoad = function(){ var data; try { var contentType; try { contentType = this.xhr.getResponseHeader('Content-Type').split(';')[0]; } catch (e) {} if (contentType === 'application/octet-stream') { data = this.xhr.response; } else { if (!this.supportsBinary) { data = this.xhr.responseText; } else { try { data = String.fromCharCode.apply(null, new Uint8Array(this.xhr.response)); } catch (e) { var ui8Arr = new Uint8Array(this.xhr.response); var dataArray = []; for (var idx = 0, length = ui8Arr.length; idx < length; idx++) { dataArray.push(ui8Arr[idx]); } data = String.fromCharCode.apply(null, dataArray); } } } } catch (e) { this.onError(e); } if (null != data) { this.onData(data); } }; /** * Check if it has XDomainRequest. * * @api private */ Request.prototype.hasXDR = function(){ return 'undefined' !== typeof global.XDomainRequest && !this.xs && this.enablesXDR; }; /** * Aborts the request. * * @api public */ Request.prototype.abort = function(){ this.cleanup(); }; /** * Aborts pending requests when unloading the window. This is needed to prevent * memory leaks (e.g. when using IE) and to ensure that no spurious error is * emitted. */ if (global.document) { Request.requestsCount = 0; Request.requests = {}; if (global.attachEvent) { global.attachEvent('onunload', unloadHandler); } else if (global.addEventListener) { global.addEventListener('beforeunload', unloadHandler, false); } } function unloadHandler() { for (var i in Request.requests) { if (Request.requests.hasOwnProperty(i)) { Request.requests[i].abort(); } } } }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./polling":7,"component-emitter":14,"component-inherit":15,"debug":16,"xmlhttprequest-ssl":9}],7:[function(_dereq_,module,exports){ /** * Module dependencies. */ var Transport = _dereq_('../transport'); var parseqs = _dereq_('parseqs'); var parser = _dereq_('engine.io-parser'); var inherit = _dereq_('component-inherit'); var yeast = _dereq_('yeast'); var debug = _dereq_('debug')('engine.io-client:polling'); /** * Module exports. */ module.exports = Polling; /** * Is XHR2 supported? */ var hasXHR2 = (function() { var XMLHttpRequest = _dereq_('xmlhttprequest-ssl'); var xhr = new XMLHttpRequest({ xdomain: false }); return null != xhr.responseType; })(); /** * Polling interface. * * @param {Object} opts * @api private */ function Polling(opts){ var forceBase64 = (opts && opts.forceBase64); if (!hasXHR2 || forceBase64) { this.supportsBinary = false; } Transport.call(this, opts); } /** * Inherits from Transport. */ inherit(Polling, Transport); /** * Transport name. */ Polling.prototype.name = 'polling'; /** * Opens the socket (triggers polling). We write a PING message to determine * when the transport is open. * * @api private */ Polling.prototype.doOpen = function(){ this.poll(); }; /** * Pauses polling. * * @param {Function} callback upon buffers are flushed and transport is paused * @api private */ Polling.prototype.pause = function(onPause){ var pending = 0; var self = this; this.readyState = 'pausing'; function pause(){ debug('paused'); self.readyState = 'paused'; onPause(); } if (this.polling || !this.writable) { var total = 0; if (this.polling) { debug('we are currently polling - waiting to pause'); total++; this.once('pollComplete', function(){ debug('pre-pause polling complete'); --total || pause(); }); } if (!this.writable) { debug('we are currently writing - waiting to pause'); total++; this.once('drain', function(){ debug('pre-pause writing complete'); --total || pause(); }); } } else { pause(); } }; /** * Starts polling cycle. * * @api public */ Polling.prototype.poll = function(){ debug('polling'); this.polling = true; this.doPoll(); this.emit('poll'); }; /** * Overloads onData to detect payloads. * * @api private */ Polling.prototype.onData = function(data){ var self = this; debug('polling got data %s', data); var callback = function(packet, index, total) { // if its the first message we consider the transport open if ('opening' == self.readyState) { self.onOpen(); } // if its a close packet, we close the ongoing requests if ('close' == packet.type) { self.onClose(); return false; } // otherwise bypass onData and handle the message self.onPacket(packet); }; // decode payload parser.decodePayload(data, this.socket.binaryType, callback); // if an event did not trigger closing if ('closed' != this.readyState) { // if we got data we're not polling this.polling = false; this.emit('pollComplete'); if ('open' == this.readyState) { this.poll(); } else { debug('ignoring poll - transport state "%s"', this.readyState); } } }; /** * For polling, send a close packet. * * @api private */ Polling.prototype.doClose = function(){ var self = this; function close(){ debug('writing close packet'); self.write([{ type: 'close' }]); } if ('open' == this.readyState) { debug('transport open - closing'); close(); } else { // in case we're trying to close while // handshaking is in progress (GH-164) debug('transport not open - deferring close'); this.once('open', close); } }; /** * Writes a packets payload. * * @param {Array} data packets * @param {Function} drain callback * @api private */ Polling.prototype.write = function(packets){ var self = this; this.writable = false; var callbackfn = function() { self.writable = true; self.emit('drain'); }; var self = this; parser.encodePayload(packets, this.supportsBinary, function(data) { self.doWrite(data, callbackfn); }); }; /** * Generates uri for connection. * * @api private */ Polling.prototype.uri = function(){ var query = this.query || {}; var schema = this.secure ? 'https' : 'http'; var port = ''; // cache busting is forced if (false !== this.timestampRequests) { query[this.timestampParam] = yeast(); } if (!this.supportsBinary && !query.sid) { query.b64 = 1; } query = parseqs.encode(query); // avoid port if default for schema if (this.port && (('https' == schema && this.port != 443) || ('http' == schema && this.port != 80))) { port = ':' + this.port; } // prepend ? to query if (query.length) { query = '?' + query; } var ipv6 = this.hostname.indexOf(':') !== -1; return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query; }; },{"../transport":3,"component-inherit":15,"debug":16,"engine.io-parser":18,"parseqs":26,"xmlhttprequest-ssl":9,"yeast":29}],8:[function(_dereq_,module,exports){ (function (global){ /** * Module dependencies. */ var Transport = _dereq_('../transport'); var parser = _dereq_('engine.io-parser'); var parseqs = _dereq_('parseqs'); var inherit = _dereq_('component-inherit'); var yeast = _dereq_('yeast'); var debug = _dereq_('debug')('engine.io-client:websocket'); var BrowserWebSocket = global.WebSocket || global.MozWebSocket; /** * Get either the `WebSocket` or `MozWebSocket` globals * in the browser or try to resolve WebSocket-compatible * interface exposed by `ws` for Node-like environment. */ var WebSocket = BrowserWebSocket; if (!WebSocket && typeof window === 'undefined') { try { WebSocket = _dereq_('ws'); } catch (e) { } } /** * Module exports. */ module.exports = WS; /** * WebSocket transport constructor. * * @api {Object} connection options * @api public */ function WS(opts){ var forceBase64 = (opts && opts.forceBase64); if (forceBase64) { this.supportsBinary = false; } this.perMessageDeflate = opts.perMessageDeflate; Transport.call(this, opts); } /** * Inherits from Transport. */ inherit(WS, Transport); /** * Transport name. * * @api public */ WS.prototype.name = 'websocket'; /* * WebSockets support binary */ WS.prototype.supportsBinary = true; /** * Opens socket. * * @api private */ WS.prototype.doOpen = function(){ if (!this.check()) { // let probe timeout return; } var self = this; var uri = this.uri(); var protocols = void(0); var opts = { agent: this.agent, perMessageDeflate: this.perMessageDeflate }; // SSL options for Node.js client opts.pfx = this.pfx; opts.key = this.key; opts.passphrase = this.passphrase; opts.cert = this.cert; opts.ca = this.ca; opts.ciphers = this.ciphers; opts.rejectUnauthorized = this.rejectUnauthorized; if (this.extraHeaders) { opts.headers = this.extraHeaders; } this.ws = BrowserWebSocket ? new WebSocket(uri) : new WebSocket(uri, protocols, opts); if (this.ws.binaryType === undefined) { this.supportsBinary = false; } if (this.ws.supports && this.ws.supports.binary) { this.supportsBinary = true; this.ws.binaryType = 'buffer'; } else { this.ws.binaryType = 'arraybuffer'; } this.addEventListeners(); }; /** * Adds event listeners to the socket * * @api private */ WS.prototype.addEventListeners = function(){ var self = this; this.ws.onopen = function(){ self.onOpen(); }; this.ws.onclose = function(){ self.onClose(); }; this.ws.onmessage = function(ev){ self.onData(ev.data); }; this.ws.onerror = function(e){ self.onError('websocket error', e); }; }; /** * Writes data to socket. * * @param {Array} array of packets. * @api private */ WS.prototype.write = function(packets){ var self = this; this.writable = false; // encodePacket efficient as it uses WS framing // no need for encodePayload var total = packets.length; for (var i = 0, l = total; i < l; i++) { (function(packet) { parser.encodePacket(packet, self.supportsBinary, function(data) { if (!BrowserWebSocket) { // always create a new object (GH-437) var opts = {}; if (packet.options) { opts.compress = packet.options.compress; } if (self.perMessageDeflate) { var len = 'string' == typeof data ? global.Buffer.byteLength(data) : data.length; if (len < self.perMessageDeflate.threshold) { opts.compress = false; } } } //Sometimes the websocket has already been closed but the browser didn't //have a chance of informing us about it yet, in that case send will //throw an error try { if (BrowserWebSocket) { // TypeError is thrown when passing the second argument on Safari self.ws.send(data); } else { self.ws.send(data, opts); } } catch (e){ debug('websocket closed before onclose event'); } --total || done(); }); })(packets[i]); } function done(){ self.emit('flush'); // fake drain // defer to next tick to allow Socket to clear writeBuffer setTimeout(function(){ self.writable = true; self.emit('drain'); }, 0); } }; /** * Called upon close * * @api private */ WS.prototype.onClose = function(){ Transport.prototype.onClose.call(this); }; /** * Closes socket. * * @api private */ WS.prototype.doClose = function(){ if (typeof this.ws !== 'undefined') { this.ws.close(); } }; /** * Generates uri for connection. * * @api private */ WS.prototype.uri = function(){ var query = this.query || {}; var schema = this.secure ? 'wss' : 'ws'; var port = ''; // avoid port if default for schema if (this.port && (('wss' == schema && this.port != 443) || ('ws' == schema && this.port != 80))) { port = ':' + this.port; } // append timestamp to URI if (this.timestampRequests) { query[this.timestampParam] = yeast(); } // communicate binary support capabilities if (!this.supportsBinary) { query.b64 = 1; } query = parseqs.encode(query); // prepend ? to query if (query.length) { query = '?' + query; } var ipv6 = this.hostname.indexOf(':') !== -1; return schema + '://' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query; }; /** * Feature detection for WebSocket. * * @return {Boolean} whether this transport is available. * @api public */ WS.prototype.check = function(){ return !!WebSocket && !('__initialize' in WebSocket && this.name === WS.prototype.name); }; }).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"../transport":3,"component-inherit":15,"debug":16,"engine.io-parser":18,"parseqs":26,"ws":undefined,"yeast":29}],9:[function(_dereq_,module,exports){ // browser shim for xmlhttprequest module var hasCORS = _dereq_('has-cors'); module.exports = function(opts) { var xdomain = opts.xdomain; // scheme must be same when usign XDomainRequest // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx var xscheme = opts.xscheme; // XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default. // https://github.com/Automattic/engine.io-client/pull/217 var enablesXDR = opts.enablesXDR; // XMLHttpRequest can be disabled on IE try { if ('undefined' != typeof XMLHttpRequest && (!xdomain || hasCORS)) { return new XMLHttpRequest(); } } catch (e) { } // Use XDomainRequest for IE8 if enablesXDR is true // because loading bar keeps flashing when using jsonp-polling // https://github.com/yujiosaka/socke.io-ie8-loading-example try { if ('undefined' != typeof XDomainRequest && !xscheme && enablesXDR) { return new XDomainRequest(); } } catch (e) { } if (!xdomain) { try { return new window[(['Active'].concat('Object').join('X'))]('Microsoft.XMLHTTP'); } catch(e) { } } } },{"has-cors":21}],10:[function(_dereq_,module,exports){ module.exports = after function after(count, callback, err_cb) { var bail = false err_cb = err_cb || noop proxy.count = count return (count === 0) ? callback() : proxy function proxy(err, result) { if (proxy.count <= 0) { throw new Error('after called too many times') } --proxy.count // after first error, rest are passed to err_cb if (err) { bail = true callback(err) // future error callbacks will go to error handler callback = err_cb } else if (proxy.count === 0 && !bail) { callback(null, result) } } } function noop() {} },