UNPKG

socket-signaler-client

Version:

Client for SocketIO WebRTC streaming webcam chatrooms

2,157 lines (1,771 loc) 208 kB
(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(require,module,exports){ /* globals define, module, require */ /** * Socket Signaler Client * Version: 0.0.1 * GitHub: https://github.com/mcmouse/socketio-signaler-client **/ (function (root, factory) { 'use strict'; if (typeof define === 'function' && define.amd) { define(['https://cdnjs.cloudflare.com/ajax/libs/EventEmitter/4.2.11/EventEmitter.min.js', 'https://cdn.socket.io/socket.io-1.3.3.js'], function (EventEmitter, io) { return factory(window, EventEmitter, io); }); } else if (typeof exports === 'object') { module.exports = factory(window, require('wolfy87-eventemitter'), require('socket.io-client')); } else { root.PeerConnectionClient = factory(window, root.EventEmitter, root.io); } }(this, function factory(window, EventEmitter, io) { 'use strict'; function PeerConnectionClient(options) { this.peerConnections = []; if (navigator.mozGetUserMedia) { //Set constraints to properly negotiate connection this.constraints = { offerToReceiveAudio: true, offerToReceiveVideo: true }; } else { //Set constraints to properly negotiate connection this.constraints = { mandatory: { OfferToReceiveAudio: true, OfferToReceiveVideo: true }, }; } //Declare our public STUN server this.iceServers = { 'iceServers': [{ 'url': 'stun:stun.services.mozilla.com' }, { 'url': 'stun:stun.l.google.com:19302' }] }; //Set up our prefixed defaults this.setupRTCObjects = function () { //PeerConnection this.PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; //SessionDescription this.SessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription; //GetUserMedia navigator.getUserMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); //RTCIceCandidate this.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate; }; //Generate our option defaults this.generateDefaults = function (options) { options = options || {}; options.server = options.server || 'http://' + window.location.host + '/'; options.room = options.room || 'default'; options.debug = options.debug || false; return options; }; //Set up our event handlers this.bindEvents = function () { this.socket.on('initialized', this.getPeerList.bind(this)); this.socket.on('list', this.generateConnections.bind(this)); this.socket.on('offer', this.createAnswer.bind(this)); this.socket.on('answer', this.handleAnswer.bind(this)); this.socket.on('icecandidate', this.receiveIceCandidate.bind(this)); this.socket.on('newconnection', this.addPeer.bind(this)); this.socket.on('disconnect', this.disconnectConnection.bind(this)); this.socket.on('peerconnected', this.peerConnected.bind(this)); this.socket.on('streamremoved', this.removeRemoteStream.bind(this)); }; //Request our list of peers - all other users connected to the socket.io room this.getPeerList = function () { this.socket.emit('list'); }; //Generate our PeerConnections for each user received by getPeerList this.generateConnections = function (peers) { if (options.debug) console.log('Received list, creating connection(s) for ' + peers.length + ' peers'); //Create a new connection for each peer peers.forEach(this.addPeer.bind(this)); }; //Add a peer to our connections, initializing the PeerConnection object //and sending an offer if we are currently broadcasting this.addPeer = function (id, suppress) { if (!this.hasPeer(id)) { console.log('Adding peer: ' + id); //Create new peer var peer = { connection: new this.PeerConnection(this.iceServers), id: id, }; //Bind events peer = this.bindConnectionEvents(peer, suppress); //Add peer to array of connections this.peerConnections.push(peer); //If we are currently broadcasting, broadcast our stream to the newly added peer if (this.localStream) { this.addStreamToPeer(this.localStream, peer); } return peer; } }; //Bind our PeerConnection events //Not using "onremovestream" because it seems to be in a buggy state - //returning nonexistant remote connections on renegotiation this.bindConnectionEvents = function (peer, suppress) { if (options.debug) console.log('Binding connection events for peer: ' + peer.id); //Send ice candidate to peer peer.connection.onicecandidate = function (event) { if (options.debug && options.debug === 'verbose') console.log('Sending ICE candidate to ' + peer.id); if (options.debug && options.debug === 'verbose') console.log('Full event ', event); if (options.debug && options.debug === 'verbose') console.log('Stringified event ', JSON.stringify(event)); console.log(Object.keys(event)); if (event.candidate) { this.socket.emit('icecandidate', { target: peer.id, candidate: event.candidate }); } }.bind(this); //When connection succeeds after stream is added, proxy event peer.connection.onaddstream = function (event) { //Checking suppress because if a connection is renegotiated and the remote maintains a //stream, onaddstream will be fired twice if (options.debug) console.log('Remote stream added from ' + peer.id); if (!suppress) { this.emit('remoteStreamAdded', event.stream, peer.id); } else { if (options.debug) console.log('onaddstream event suppressed'); } }.bind(this); //Untested peer.connection.ondatachannel = function (event) { if (options.debug) console.log('Data channel added from ' + peer.id); this.emit('dataChannelAdded', event.channel, peer.id); }.bind(this); //Don't add unnecessary ICE candidates. //On Ice error, close the connection peer.connection.oniceconnectionstatechange = function () { switch (peer.connection.iceConnectionState) { case 'disconnected': case 'failed': this.logError('iceConnectionState is disconnected, closing connections to ' + peer.id); peer.connection.close(); break; case 'completed': peer.connection.onicecandidate = function () {}; break; } }.bind(this); return peer; }; //Create our offer and set our local session description this.createOffer = function (peer) { if (options.debug) console.log('Creating offer for: ' + peer.id); //Create our offer peer.connection.createOffer(function (offer) { //Set local description from offer peer.connection.setLocalDescription(new this.SessionDescription(offer), //Send offer this.sendOffer({ target: peer.id, offer: offer }), this.logError); //Pass constraints }.bind(this), this.logError, this.constraints); }; //Send offer to server this.sendOffer = function (offer) { if (options.debug) console.log('Sending offer to ' + offer.target); this.socket.emit('offer', offer); }; //Set remote and local session description this.createAnswer = function (data) { //Retreive peer from this.peerConnections and set offer var peer = this.getPeer(data.sender), offer = data.offer; if (options.debug) console.log('Creating answer for ' + peer.id); //Set remote description from offer peer.connection.setRemoteDescription(new this.SessionDescription(offer), function () { //Callback after remote description set peer.connection.createAnswer(function (answer) { //Callback after answer created to set the local description peer.connection.setLocalDescription(new this.SessionDescription(answer), //Send answer back to peer this.sendAnswer({ target: peer.id, answer: answer }), this.logError); //Set constraints for answer }.bind(this), this.logError, this.constraints); //Error logging for setRemoteDescription }.bind(this), this.logError); }; //Send answer to server this.sendAnswer = function (answer) { if (options.debug) console.log('Sent answer to peer: ' + answer.target); this.socket.emit('answer', answer); }; //Handle the answer received from peer this.handleAnswer = function (data) { var peer = this.getPeer(data.sender), answer = data.answer; if (options.debug) console.log('Handling answer from: ' + peer.id); //Set remote description peer.connection.setRemoteDescription(new this.SessionDescription(answer), //Trigger peer connected and emit to let peer know that we're good function () { this.peerConnected(peer.id); this.socket.emit('peerconnected', peer.id); }.bind(this), this.logError); }; //Event fired when our peer is connected this.peerConnected = function (id) { this.emit('peerconnected', id); if (options.debug) console.log('Signaling with peer: ' + id); }; //Disconnect a peer this.disconnectConnection = function (id) { //Get our peer var peer = this.getPeer(id); //Close connection peer.connection.close(); //Proxy events out this.emit('peerDisconnected', id); this.emit('remoteStreamRemoved', id); if (options.debug) console.log('Disconnected with peer: ' + id); //Remove peer from internal array this.peerConnections.forEach(function (peer, index, peers) { if (peer.id === id) { peers.splice(index, 1); } }); }; //Find a peer by ID this.getPeer = function (id) { var foundPeer; //Match based on ID this.peerConnections.forEach(function (peer) { if (peer.id === id) { foundPeer = peer; } }); //Create peer if it doesn't exist if (!foundPeer) { foundPeer = this.addPeer(id); } return foundPeer; }; //Check if a peer exists based on ID this.hasPeer = function (id) { var foundPeer = false; this.peerConnections.forEach(function (peer) { if (peer.id === id) { foundPeer = true; } }); return foundPeer; }; //Log errors this.logError = function (error) { if (options.debug) console.error(error); else console.error('There was an error with the signaller'); }; //Add stream to all connections this.addStream = function (stream) { this.peerConnections.forEach(function (peer) { this.addStreamToPeer(stream, peer); }.bind(this)); }; //Add stream to single connection this.addStreamToPeer = function (stream, peer) { if (options.debug) console.log('Stream added to ' + peer.id); peer.connection.addStream(stream); //Need to renegotiate after a stream is added this.createOffer(peer); }; //Add local webcam and/or microphone stream //Toggle video with opts.video and audio with opts.audio this.addLocalStream = function (opts) { opts = opts || {}; //Request access navigator.getUserMedia({ audio: opts.audio || true, video: opts.video || true, //Add stream to PeerConnection }, function (stream) { //Reference to our stream this.localStream = stream; //Add stream to all peers this.addStream(stream); //Proxy stream added event this.emit('localStreamAdded', stream); if (options.debug) console.log('Local stream added'); }.bind(this), this.logError); }; //Remove stream from all connections this.removeLocalStream = function () { //Stop recording this.localStream.stop(); //Remove our local stream entirely this.localStream = undefined; this.peerConnections.forEach(function (peer) { if (options.debug) console.log('Local stream removed from ' + peer.id); this.socket.emit('streamremoved', peer.id); //Demolish stream this.regenStream(peer, true); }.bind(this)); //Pass events through this.emit('localStreamRemoved'); }; //Remove stream this.removeRemoteStream = function (id) { this.emit('remoteStreamRemoved', id); if (options.debug) console.log('Removing stream from: ' + id); //Need to regenerate stream to deal with "phantom" remote MediaStream bug(?) this.regenStream(this.getPeer(id)); }; //Regenerate a stream by re-creating its PeerConnection and re-binding all events this.regenStream = function (peer, suppress) { if (options.debug) console.log('Regenerating PeerConnection for ' + peer.id); //If we are regenerating because we're removing a local connection and there is a remote stream suppress = suppress && peer.connection.getRemoteStreams().length; //Replace PeerConnection with new connection peer.connection = new this.PeerConnection(this.iceServers); //Bind connection events this.bindConnectionEvents(peer, suppress); //Add local stream if necessary if (this.localStream) { this.addStreamToPeer(this.localStream, peer); } }; //Process ice candidate this.receiveIceCandidate = function (data) { if (options.debug && options.debug === 'verbose') console.log('Received candidate', data.candidate); //This is horrible, but that's the way the data is packaged if (data.candidate.candidate) { //Unpackage data var peer = this.getPeer(data.sender), candidate = data.candidate.candidate, line = data.candidate.sdpMLineIndex; if (options.debug && options.debug === 'verbose') console.log('Added ICE candidate from ' + peer.id); //Suppress error message if we're debugging after unnecessary ice candidates are sent - causes like 30 errors if you pause during connection peer.connection.addIceCandidate(new this.RTCIceCandidate({ candidate: candidate, sdpMLineIndex: line, })); } }; //Get our prefixed RTC objects this.setupRTCObjects(); //Options defaults options = this.generateDefaults(options); //Set up our socket connection this.socket = io(options.server + options.room); //Bind our events this.bindEvents(); } PeerConnectionClient.prototype = new EventEmitter(); return PeerConnectionClient; })); },{"socket.io-client":2,"wolfy87-eventemitter":52}],2:[function(require,module,exports){ module.exports = require('./lib/'); },{"./lib/":3}],3:[function(require,module,exports){ /** * Module dependencies. */ var url = require('./url'); var parser = require('socket.io-parser'); var Manager = require('./manager'); var debug = require('debug')('socket.io-client'); /** * Module exports. */ module.exports = exports = lookup; /** * Managers cache. */ var cache = exports.managers = {}; /** * Looks up an existing `Manager` for multiplexing. * If the user summons: * * `io('http://localhost/a');` * `io('http://localhost/b');` * * We reuse the existing instance based on same scheme/port/host, * and we initialize sockets for each namespace. * * @api public */ function lookup(uri, opts) { if (typeof uri == 'object') { opts = uri; uri = undefined; } opts = opts || {}; var parsed = url(uri); var source = parsed.source; var id = parsed.id; var io; if (opts.forceNew || opts['force new connection'] || false === opts.multiplex) { debug('ignoring socket cache for %s', source); io = Manager(source, opts); } else { if (!cache[id]) { debug('new io instance for %s', source); cache[id] = Manager(source, opts); } io = cache[id]; } return io.socket(parsed.path); } /** * Protocol version. * * @api public */ exports.protocol = parser.protocol; /** * `connect`. * * @param {String} uri * @api public */ exports.connect = lookup; /** * Expose constructors for standalone build. * * @api public */ exports.Manager = require('./manager'); exports.Socket = require('./socket'); },{"./manager":4,"./socket":6,"./url":7,"debug":11,"socket.io-parser":47}],4:[function(require,module,exports){ /** * Module dependencies. */ var url = require('./url'); var eio = require('engine.io-client'); var Socket = require('./socket'); var Emitter = require('component-emitter'); var parser = require('socket.io-parser'); var on = require('./on'); var bind = require('component-bind'); var object = require('object-component'); var debug = require('debug')('socket.io-client:manager'); var indexOf = require('indexof'); var Backoff = require('backo2'); /** * Module exports */ module.exports = Manager; /** * `Manager` constructor. * * @param {String} engine instance or engine uri/opts * @param {Object} options * @api public */ function Manager(uri, opts){ if (!(this instanceof Manager)) return new Manager(uri, opts); if (uri && ('object' == typeof uri)) { opts = uri; uri = undefined; } opts = opts || {}; opts.path = opts.path || '/socket.io'; this.nsps = {}; this.subs = []; this.opts = opts; this.reconnection(opts.reconnection !== false); this.reconnectionAttempts(opts.reconnectionAttempts || Infinity); this.reconnectionDelay(opts.reconnectionDelay || 1000); this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000); this.randomizationFactor(opts.randomizationFactor || 0.5); this.backoff = new Backoff({ min: this.reconnectionDelay(), max: this.reconnectionDelayMax(), jitter: this.randomizationFactor() }); this.timeout(null == opts.timeout ? 20000 : opts.timeout); this.readyState = 'closed'; this.uri = uri; this.connected = []; this.encoding = false; this.packetBuffer = []; this.encoder = new parser.Encoder(); this.decoder = new parser.Decoder(); this.autoConnect = opts.autoConnect !== false; if (this.autoConnect) this.open(); } /** * Propagate given event to sockets and emit on `this` * * @api private */ Manager.prototype.emitAll = function() { this.emit.apply(this, arguments); for (var nsp in this.nsps) { this.nsps[nsp].emit.apply(this.nsps[nsp], arguments); } }; /** * Update `socket.id` of all sockets * * @api private */ Manager.prototype.updateSocketIds = function(){ for (var nsp in this.nsps) { this.nsps[nsp].id = this.engine.id; } }; /** * Mix in `Emitter`. */ Emitter(Manager.prototype); /** * Sets the `reconnection` config. * * @param {Boolean} true/false if it should automatically reconnect * @return {Manager} self or value * @api public */ Manager.prototype.reconnection = function(v){ if (!arguments.length) return this._reconnection; this._reconnection = !!v; return this; }; /** * Sets the reconnection attempts config. * * @param {Number} max reconnection attempts before giving up * @return {Manager} self or value * @api public */ Manager.prototype.reconnectionAttempts = function(v){ if (!arguments.length) return this._reconnectionAttempts; this._reconnectionAttempts = v; return this; }; /** * Sets the delay between reconnections. * * @param {Number} delay * @return {Manager} self or value * @api public */ Manager.prototype.reconnectionDelay = function(v){ if (!arguments.length) return this._reconnectionDelay; this._reconnectionDelay = v; this.backoff && this.backoff.setMin(v); return this; }; Manager.prototype.randomizationFactor = function(v){ if (!arguments.length) return this._randomizationFactor; this._randomizationFactor = v; this.backoff && this.backoff.setJitter(v); return this; }; /** * Sets the maximum delay between reconnections. * * @param {Number} delay * @return {Manager} self or value * @api public */ Manager.prototype.reconnectionDelayMax = function(v){ if (!arguments.length) return this._reconnectionDelayMax; this._reconnectionDelayMax = v; this.backoff && this.backoff.setMax(v); return this; }; /** * Sets the connection timeout. `false` to disable * * @return {Manager} self or value * @api public */ Manager.prototype.timeout = function(v){ if (!arguments.length) return this._timeout; this._timeout = v; return this; }; /** * Starts trying to reconnect if reconnection is enabled and we have not * started reconnecting yet * * @api private */ Manager.prototype.maybeReconnectOnOpen = function() { // Only try to reconnect if it's the first time we're connecting if (!this.reconnecting && this._reconnection && this.backoff.attempts === 0) { // keeps reconnection from firing twice for the same reconnection loop this.reconnect(); } }; /** * Sets the current transport `socket`. * * @param {Function} optional, callback * @return {Manager} self * @api public */ Manager.prototype.open = Manager.prototype.connect = function(fn){ debug('readyState %s', this.readyState); if (~this.readyState.indexOf('open')) return this; debug('opening %s', this.uri); this.engine = eio(this.uri, this.opts); var socket = this.engine; var self = this; this.readyState = 'opening'; this.skipReconnect = false; // emit `open` var openSub = on(socket, 'open', function() { self.onopen(); fn && fn(); }); // emit `connect_error` var errorSub = on(socket, 'error', function(data){ debug('connect_error'); self.cleanup(); self.readyState = 'closed'; self.emitAll('connect_error', data); if (fn) { var err = new Error('Connection error'); err.data = data; fn(err); } else { // Only do this if there is no fn to handle the error self.maybeReconnectOnOpen(); } }); // emit `connect_timeout` if (false !== this._timeout) { var timeout = this._timeout; debug('connect attempt will timeout after %d', timeout); // set timer var timer = setTimeout(function(){ debug('connect attempt timed out after %d', timeout); openSub.destroy(); socket.close(); socket.emit('error', 'timeout'); self.emitAll('connect_timeout', timeout); }, timeout); this.subs.push({ destroy: function(){ clearTimeout(timer); } }); } this.subs.push(openSub); this.subs.push(errorSub); return this; }; /** * Called upon transport open. * * @api private */ Manager.prototype.onopen = function(){ debug('open'); // clear old subs this.cleanup(); // mark as open this.readyState = 'open'; this.emit('open'); // add new subs var socket = this.engine; this.subs.push(on(socket, 'data', bind(this, 'ondata'))); this.subs.push(on(this.decoder, 'decoded', bind(this, 'ondecoded'))); this.subs.push(on(socket, 'error', bind(this, 'onerror'))); this.subs.push(on(socket, 'close', bind(this, 'onclose'))); }; /** * Called with data. * * @api private */ Manager.prototype.ondata = function(data){ this.decoder.add(data); }; /** * Called when parser fully decodes a packet. * * @api private */ Manager.prototype.ondecoded = function(packet) { this.emit('packet', packet); }; /** * Called upon socket error. * * @api private */ Manager.prototype.onerror = function(err){ debug('error', err); this.emitAll('error', err); }; /** * Creates a new socket for the given `nsp`. * * @return {Socket} * @api public */ Manager.prototype.socket = function(nsp){ var socket = this.nsps[nsp]; if (!socket) { socket = new Socket(this, nsp); this.nsps[nsp] = socket; var self = this; socket.on('connect', function(){ socket.id = self.engine.id; if (!~indexOf(self.connected, socket)) { self.connected.push(socket); } }); } return socket; }; /** * Called upon a socket close. * * @param {Socket} socket */ Manager.prototype.destroy = function(socket){ var index = indexOf(this.connected, socket); if (~index) this.connected.splice(index, 1); if (this.connected.length) return; this.close(); }; /** * Writes a packet. * * @param {Object} packet * @api private */ Manager.prototype.packet = function(packet){ debug('writing packet %j', packet); var self = this; if (!self.encoding) { // encode, then write to engine with result self.encoding = true; this.encoder.encode(packet, function(encodedPackets) { for (var i = 0; i < encodedPackets.length; i++) { self.engine.write(encodedPackets[i]); } self.encoding = false; self.processPacketQueue(); }); } else { // add packet to the queue self.packetBuffer.push(packet); } }; /** * If packet buffer is non-empty, begins encoding the * next packet in line. * * @api private */ Manager.prototype.processPacketQueue = function() { if (this.packetBuffer.length > 0 && !this.encoding) { var pack = this.packetBuffer.shift(); this.packet(pack); } }; /** * Clean up transport subscriptions and packet buffer. * * @api private */ Manager.prototype.cleanup = function(){ var sub; while (sub = this.subs.shift()) sub.destroy(); this.packetBuffer = []; this.encoding = false; this.decoder.destroy(); }; /** * Close the current socket. * * @api private */ Manager.prototype.close = Manager.prototype.disconnect = function(){ this.skipReconnect = true; this.backoff.reset(); this.readyState = 'closed'; this.engine && this.engine.close(); }; /** * Called upon engine close. * * @api private */ Manager.prototype.onclose = function(reason){ debug('close'); this.cleanup(); this.backoff.reset(); this.readyState = 'closed'; this.emit('close', reason); if (this._reconnection && !this.skipReconnect) { this.reconnect(); } }; /** * Attempt a reconnection. * * @api private */ Manager.prototype.reconnect = function(){ if (this.reconnecting || this.skipReconnect) return this; var self = this; if (this.backoff.attempts >= this._reconnectionAttempts) { debug('reconnect failed'); this.backoff.reset(); this.emitAll('reconnect_failed'); this.reconnecting = false; } else { var delay = this.backoff.duration(); debug('will wait %dms before reconnect attempt', delay); this.reconnecting = true; var timer = setTimeout(function(){ if (self.skipReconnect) return; debug('attempting reconnect'); self.emitAll('reconnect_attempt', self.backoff.attempts); self.emitAll('reconnecting', self.backoff.attempts); // check again for the case socket closed in above events if (self.skipReconnect) return; self.open(function(err){ if (err) { debug('reconnect attempt error'); self.reconnecting = false; self.reconnect(); self.emitAll('reconnect_error', err.data); } else { debug('reconnect success'); self.onreconnect(); } }); }, delay); this.subs.push({ destroy: function(){ clearTimeout(timer); } }); } }; /** * Called upon successful reconnect. * * @api private */ Manager.prototype.onreconnect = function(){ var attempt = this.backoff.attempts; this.reconnecting = false; this.backoff.reset(); this.updateSocketIds(); this.emitAll('reconnect', attempt); }; },{"./on":5,"./socket":6,"./url":7,"backo2":8,"component-bind":9,"component-emitter":10,"debug":11,"engine.io-client":12,"indexof":43,"object-component":44,"socket.io-parser":47}],5:[function(require,module,exports){ /** * Module exports. */ module.exports = on; /** * Helper for subscriptions. * * @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter` * @param {String} event name * @param {Function} callback * @api public */ function on(obj, ev, fn) { obj.on(ev, fn); return { destroy: function(){ obj.removeListener(ev, fn); } }; } },{}],6:[function(require,module,exports){ /** * Module dependencies. */ var parser = require('socket.io-parser'); var Emitter = require('component-emitter'); var toArray = require('to-array'); var on = require('./on'); var bind = require('component-bind'); var debug = require('debug')('socket.io-client:socket'); var hasBin = require('has-binary'); /** * Module exports. */ module.exports = exports = Socket; /** * Internal events (blacklisted). * These events can't be emitted by the user. * * @api private */ var events = { connect: 1, connect_error: 1, connect_timeout: 1, disconnect: 1, error: 1, reconnect: 1, reconnect_attempt: 1, reconnect_failed: 1, reconnect_error: 1, reconnecting: 1 }; /** * Shortcut to `Emitter#emit`. */ var emit = Emitter.prototype.emit; /** * `Socket` constructor. * * @api public */ function Socket(io, nsp){ this.io = io; this.nsp = nsp; this.json = this; // compat this.ids = 0; this.acks = {}; if (this.io.autoConnect) this.open(); this.receiveBuffer = []; this.sendBuffer = []; this.connected = false; this.disconnected = true; } /** * Mix in `Emitter`. */ Emitter(Socket.prototype); /** * Subscribe to open, close and packet events * * @api private */ Socket.prototype.subEvents = function() { if (this.subs) return; var io = this.io; this.subs = [ on(io, 'open', bind(this, 'onopen')), on(io, 'packet', bind(this, 'onpacket')), on(io, 'close', bind(this, 'onclose')) ]; }; /** * "Opens" the socket. * * @api public */ Socket.prototype.open = Socket.prototype.connect = function(){ if (this.connected) return this; this.subEvents(); this.io.open(); // ensure open if ('open' == this.io.readyState) this.onopen(); return this; }; /** * Sends a `message` event. * * @return {Socket} self * @api public */ Socket.prototype.send = function(){ var args = toArray(arguments); args.unshift('message'); this.emit.apply(this, args); return this; }; /** * Override `emit`. * If the event is in `events`, it's emitted normally. * * @param {String} event name * @return {Socket} self * @api public */ Socket.prototype.emit = function(ev){ if (events.hasOwnProperty(ev)) { emit.apply(this, arguments); return this; } var args = toArray(arguments); var parserType = parser.EVENT; // default if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary var packet = { type: parserType, data: args }; // event ack callback if ('function' == typeof args[args.length - 1]) { debug('emitting packet with ack id %d', this.ids); this.acks[this.ids] = args.pop(); packet.id = this.ids++; } if (this.connected) { this.packet(packet); } else { this.sendBuffer.push(packet); } return this; }; /** * Sends a packet. * * @param {Object} packet * @api private */ Socket.prototype.packet = function(packet){ packet.nsp = this.nsp; this.io.packet(packet); }; /** * Called upon engine `open`. * * @api private */ Socket.prototype.onopen = function(){ debug('transport is open - connecting'); // write connect packet if necessary if ('/' != this.nsp) { this.packet({ type: parser.CONNECT }); } }; /** * Called upon engine `close`. * * @param {String} reason * @api private */ Socket.prototype.onclose = function(reason){ debug('close (%s)', reason); this.connected = false; this.disconnected = true; delete this.id; this.emit('disconnect', reason); }; /** * Called with socket packet. * * @param {Object} packet * @api private */ Socket.prototype.onpacket = function(packet){ if (packet.nsp != this.nsp) return; switch (packet.type) { case parser.CONNECT: this.onconnect(); break; case parser.EVENT: this.onevent(packet); break; case parser.BINARY_EVENT: this.onevent(packet); break; case parser.ACK: this.onack(packet); break; case parser.BINARY_ACK: this.onack(packet); break; case parser.DISCONNECT: this.ondisconnect(); break; case parser.ERROR: this.emit('error', packet.data); break; } }; /** * Called upon a server event. * * @param {Object} packet * @api private */ Socket.prototype.onevent = function(packet){ var args = packet.data || []; debug('emitting event %j', args); if (null != packet.id) { debug('attaching ack callback to event'); args.push(this.ack(packet.id)); } if (this.connected) { emit.apply(this, args); } else { this.receiveBuffer.push(args); } }; /** * Produces an ack callback to emit with an event. * * @api private */ Socket.prototype.ack = function(id){ var self = this; var sent = false; return function(){ // prevent double callbacks if (sent) return; sent = true; var args = toArray(arguments); debug('sending ack %j', args); var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK; self.packet({ type: type, id: id, data: args }); }; }; /** * Called upon a server acknowlegement. * * @param {Object} packet * @api private */ Socket.prototype.onack = function(packet){ debug('calling ack %s with %j', packet.id, packet.data); var fn = this.acks[packet.id]; fn.apply(this, packet.data); delete this.acks[packet.id]; }; /** * Called upon server connect. * * @api private */ Socket.prototype.onconnect = function(){ this.connected = true; this.disconnected = false; this.emit('connect'); this.emitBuffered(); }; /** * Emit buffered events (received and emitted). * * @api private */ Socket.prototype.emitBuffered = function(){ var i; for (i = 0; i < this.receiveBuffer.length; i++) { emit.apply(this, this.receiveBuffer[i]); } this.receiveBuffer = []; for (i = 0; i < this.sendBuffer.length; i++) { this.packet(this.sendBuffer[i]); } this.sendBuffer = []; }; /** * Called upon server disconnect. * * @api private */ Socket.prototype.ondisconnect = function(){ debug('server disconnect (%s)', this.nsp); this.destroy(); this.onclose('io server disconnect'); }; /** * Called upon forced client/server side disconnections, * this method ensures the manager stops tracking us and * that reconnections don't get triggered for this. * * @api private. */ Socket.prototype.destroy = function(){ if (this.subs) { // clean subscriptions to avoid reconnections for (var i = 0; i < this.subs.length; i++) { this.subs[i].destroy(); } this.subs = null; } this.io.destroy(this); }; /** * Disconnects the socket manually. * * @return {Socket} self * @api public */ Socket.prototype.close = Socket.prototype.disconnect = function(){ if (this.connected) { debug('performing disconnect (%s)', this.nsp); this.packet({ type: parser.DISCONNECT }); } // remove socket from pool this.destroy(); if (this.connected) { // fire events this.onclose('io client disconnect'); } return this; }; },{"./on":5,"component-bind":9,"component-emitter":10,"debug":11,"has-binary":41,"socket.io-parser":47,"to-array":51}],7:[function(require,module,exports){ (function (global){ /** * Module dependencies. */ var parseuri = require('parseuri'); var debug = require('debug')('socket.io-client:url'); /** * Module exports. */ module.exports = url; /** * URL parser. * * @param {String} url * @param {Object} An object meant to mimic window.location. * Defaults to window.location. * @api public */ function url(uri, loc){ var obj = uri; // default to window.location var loc = loc || global.location; if (null == uri) uri = loc.protocol + '//' + loc.host; // relative path support if ('string' == typeof uri) { if ('/' == uri.charAt(0)) { if ('/' == uri.charAt(1)) { uri = loc.protocol + uri; } else { uri = loc.hostname + uri; } } if (!/^(https?|wss?):\/\//.test(uri)) { debug('protocol-less url %s', uri); if ('undefined' != typeof loc) { uri = loc.protocol + '//' + uri; } else { uri = 'https://' + uri; } } // parse debug('parse %s', uri); obj = parseuri(uri); } // make sure we treat `localhost:80` and `localhost` equally if (!obj.port) { if (/^(http|ws)$/.test(obj.protocol)) { obj.port = '80'; } else if (/^(http|ws)s$/.test(obj.protocol)) { obj.port = '443'; } } obj.path = obj.path || '/'; // define unique id obj.id = obj.protocol + '://' + obj.host + ':' + obj.port; // define href obj.href = obj.protocol + '://' + obj.host + (loc && loc.port == obj.port ? '' : (':' + obj.port)); return obj; } }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"debug":11,"parseuri":45}],8:[function(require,module,exports){ /** * Expose `Backoff`. */ module.exports = Backoff; /** * Initialize backoff timer with `opts`. * * - `min` initial timeout in milliseconds [100] * - `max` max timeout [10000] * - `jitter` [0] * - `factor` [2] * * @param {Object} opts * @api public */ function Backoff(opts) { opts = opts || {}; this.ms = opts.min || 100; this.max = opts.max || 10000; this.factor = opts.factor || 2; this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0; this.attempts = 0; } /** * Return the backoff duration. * * @return {Number} * @api public */ Backoff.prototype.duration = function(){ var ms = this.ms * Math.pow(this.factor, this.attempts++); if (this.jitter) { var rand = Math.random(); var deviation = Math.floor(rand * this.jitter * ms); ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation; } return Math.min(ms, this.max) | 0; }; /** * Reset the number of attempts. * * @api public */ Backoff.prototype.reset = function(){ this.attempts = 0; }; /** * Set the minimum duration * * @api public */ Backoff.prototype.setMin = function(min){ this.ms = min; }; /** * Set the maximum duration * * @api public */ Backoff.prototype.setMax = function(max){ this.max = max; }; /** * Set the jitter * * @api public */ Backoff.prototype.setJitter = function(jitter){ this.jitter = jitter; }; },{}],9:[function(require,module,exports){ /** * Slice reference. */ var slice = [].slice; /** * Bind `obj` to `fn`. * * @param {Object} obj * @param {Function|String} fn or string * @return {Function} * @api public */ module.exports = function(obj, fn){ if ('string' == typeof fn) fn = obj[fn]; if ('function' != typeof fn) throw new Error('bind() requires a function'); var args = slice.call(arguments, 2); return function(){ return fn.apply(obj, args.concat(slice.call(arguments))); } }; },{}],10:[function(require,module,exports){ /** * Expose `Emitter`. */ module.exports = Emitter; /** * Initialize a new `Emitter`. * * @api public */ function Emitter(obj) { if (obj) return mixin(obj); }; /** * Mixin the emitter properties. * * @param {Object} obj * @return {Object} * @api private */ function mixin(obj) { for (var key in Emitter.prototype) { obj[key] = Emitter.prototype[key]; } return obj; } /** * Listen on the given `event` with `fn`. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.on = Emitter.prototype.addEventListener = function(event, fn){ this._callbacks = this._callbacks || {}; (this._callbacks[event] = this._callbacks[event] || []) .push(fn); return this; }; /** * Adds an `event` listener that will be invoked a single * time then automatically removed. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.once = function(event, fn){ var self = this; this._callbacks = this._callbacks || {}; function on() { self.off(event, on); fn.apply(this, arguments); } on.fn = fn; this.on(event, on); return this; }; /** * Remove the given callback for `event` or all * registered callbacks. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.off = Emitter.prototype.removeListener = Emitter.prototype.removeAllListeners = Emitter.prototype.removeEventListener = function(event, fn){ this._callbacks = this._callbacks || {}; // all if (0 == arguments.length) { this._callbacks = {}; return this; } // specific event var callbacks = this._callbacks[event]; if (!callbacks) return this; // remove all handlers if (1 == arguments.length) { delete this._callbacks[event]; return this; } // remove specific handler var cb; for (var i = 0; i < callbacks.length; i++) { cb = callbacks[i]; if (cb === fn || cb.fn === fn) { callbacks.splice(i, 1); break; } } return this; }; /** * Emit `event` with the given args. * * @param {String} event * @param {Mixed} ... * @return {Emitter} */ Emitter.prototype.emit = function(event){ this._callbacks = this._callbacks || {}; var args = [].slice.call(arguments, 1) , callbacks = this._callbacks[event]; if (callbacks) { callbacks = callbacks.slice(0); for (var i = 0, len = callbacks.length; i < len; ++i) { callbacks[i].apply(this, args); } } return this; }; /** * Return array of callbacks for `event`. * * @param {String} event * @return {Array} * @api public */ Emitter.prototype.listeners = function(event){ this._callbacks = this._callbacks || {}; return this._callbacks[event] || []; }; /** * Check if this emitter has `event` handlers. * * @param {String} event * @return {Boolean} * @api public */ Emitter.prototype.hasListeners = function(event){ return !! this.listeners(event).length; }; },{}],11:[function(require,module,exports){ /** * Expose `debug()` as the module. */ module.exports = debug; /** * Create a debugger with the given `name`. * * @param {String} name * @return {Type} * @api public */ function debug(name) { if (!debug.enabled(name)) return function(){}; return function(fmt){ fmt = coerce(fmt); var curr = new Date; var ms = curr - (debug[name] || curr); debug[name] = curr; fmt = name + ' ' + fmt + ' +' + debug.humanize(ms); // This hackery is required for IE8 // where `console.log` doesn't have 'apply' window.console && console.log && Function.prototype.apply.call(console.log, console, arguments); } } /** * The currently active debug mode names. */ debug.names = []; debug.skips = []; /** * Enables a debug mode by name. This can include modes * separated by a colon and wildcards. * * @param {String} name * @api public */ debug.enable = function(name) { try { localStorage.debug = name; } catch(e){} var split = (name || '').split(/[\s,]+/) , len = split.length; for (var i = 0; i < len; i++) { name = split[i].replace('*', '.*?'); if (name[0] === '-') { debug.skips.push(new RegExp('^' + name.substr(1) + '$')); } else { debug.names.push(new RegExp('^' + name + '$')); } } }; /** * Disable debug output. * * @api public */ debug.disable = function(){ debug.enable(''); }; /** * Humanize the given `ms`. * * @param {Number} m * @return {String} * @api private */ debug.humanize = function(ms) { var sec = 1000 , min = 60 * 1000 , hour = 60 * min; if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; if (ms >= min) return (ms / min).toFixed(1) + 'm'; if (ms >= sec) return (ms / sec | 0) + 's'; return ms + 'ms'; }; /** * Returns true if the given mode name is enabled, false otherwise. * * @param {String} name * @return {Boolean} * @api public */ debug.enabled = function(name) { for (var i = 0, len = debug.skips.length; i < len; i++) { if (debug.skips[i].test(name)) { return false; } } for (var i = 0, len = debug.names.length; i < len; i++) { if (debug.names[i].test(name)) { return true; } } return false; }; /** * Coerce `val`. */ function coerce(val) { if (val instanceof Error) return val.stack || val.message; return val; } // persist try { if (window.localStorage) debug.enable(localStorage.debug); } catch(e){} },{}],12:[function(require,module,exports){ module.exports = require('./lib/'); },{"./lib/":13}],13:[function(require,module,exports){ module.exports = require('./socket'); /** * Exports parser * * @api public * */ module.exports.parser = require('engine.io-parser'); },{"./socket":14,"engine.io-parser":26}],14:[function(require,module,exports){ (function (global){ /** * Module dependencies. */ var transports = require('./transports'); var Emitter = require('component-emitter'); var debug = require('debug')('engine.io-client:socket'); var index = require('indexof'); var parser = require('engine.io-parser'); var parseuri = require('parseuri'); var parsejson = require('parsejson'); var parseqs = require('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.host = uri.host; opts.secure = uri.protocol == 'https' || uri.protocol == 'wss'; opts.port = uri.port; if (uri.query) opts.query = uri.query; } this.secure = null != opts.secure ? opts.secure : (global.location && 'https:' == location.protocol); if (opts.host) { var pieces = opts.host.split(':'); opts.hostname = pieces.shift(); if (pieces.length) { opts.port = pieces.pop(); } else if (!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.callbackBuffer = []; this.policyPort = opts.policyPort || 843; this.rememberUpgrade = opts.rememberUpgrade || false; this.binaryType = null; this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades; // 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 || null; 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 = require('./transport'); Socket.transports = require('./transports'); Socket.parser = require('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