UNPKG

webp2p

Version:

The server-less signaling channel for WebRTC

2,098 lines (1,659 loc) 429 kB
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.WebP2P=e()}}(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);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.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){ (function (process){ var EventEmitter = _dereq_("events").EventEmitter; var inherits = _dereq_('inherits'); var freeice = _dereq_('freeice'); var uuid = _dereq_('uuid'); var wrtc = _dereq_('wrtc'); var RTCPeerConnection = wrtc.RTCPeerConnection; var RTCSessionDescription = wrtc.RTCSessionDescription; _dereq_("process-events-shim"); var RpcBuilder = _dereq_('rpc-builder'); var HandshakeManager = _dereq_('./managers/HandshakeManager'); var PeersManager = _dereq_('./managers/PeersManager'); var packer = _dereq_('./packer'); const MAX_TTL_DEFAULT = 5; function noop(error) { if(error) console.error(error); }; /** * @classdesc Init and connect to the WebP2P.io network * * @constructor */ function WebP2P(options) { var self = this; options = options || {}; // Internal options var commonLabels = options.commonLabels || []; var handshake_servers = options.handshake_servers; var iceServers = freeice(options.iceServers); // Read-only options Object.defineProperty(this, "routingLabel", { value: options.routingLabel || "webp2p" }); Object.defineProperty(this, "sessionID", { value: options.sessionID || uuid.v4() }); var options = { max_retries: 3, peerID: this.sessionID }; var rpcBuilder = new RpcBuilder(packer, options); var handshakeManager = new HandshakeManager(rpcBuilder, handshake_servers); var peersManager = new PeersManager(rpcBuilder, self.routingLabel); this.__defineGetter__('status', function() { if(peersManager.status == 'connected') return 'connected'; return handshakeManager.status; }); this.__defineGetter__("peers", function() { return peersManager.peers; }); function onerror(error) { self.emit('error', new Error(error)); }; var offers = {}; /** * Create a new RTCPeerConnection * @param {UUID} sessionID Identifier of the other peer so later can be accessed. * * @return {RTCPeerConnection} */ function createPeerConnection(sessionID, callbackType, callback) { var pc = new RTCPeerConnection ( {iceServers: iceServers}, {optional: [{DtlsSrtpKeyAgreement: true}]} ); pc.addEventListener('icecandidate', function(event) { // There's a candidate, ignore it if(event.candidate) return; // There's no candidate, send the full SDP var type = this.localDescription.type; var sdp = this.localDescription.sdp; if(type == callbackType) callback(sdp) else console.error(type+" SDP type is not equal to "+callbackType+" callback type"); }); pc.addEventListener('signalingstatechange', function(event) { // Add PeerConnection object to available ones when gets open if(pc.signalingState == 'stable') { var channels = pc._channels || []; peersManager.add(sessionID, pc, channels); var offer = offers[sessionID]; if(offer) { delete offers[sessionID]; offer.callback(null, pc, channels); }; self.emit('peerconnection', pc, channels); }; }); // Set ID of the PeerConnection Object.defineProperty(pc, "sessionID", {value : sessionID}); return pc; }; // // Connection methods // /** * Callback to send the offer. If not defined send it to all connected peers. * * @callback WebP2P~ConnectToCallback * @param {Error} error * @param {RTCPeerConnection} peer - The (newly created) peer */ /** * Connects to another peer based on its UID. If we are already connected, * it does nothing. * * @param {UUID} sessionID - Identifier of the other peer to be connected. * @param {string[]} [labels] - Per-connection labels * @param {WebP2P~ConnectToCallback} callback */ this.connectTo = function(dest, labels, callback) { if(labels instanceof Function) { if(callback) throw new SyntaxError("Nothing can be defined after the callback"); callback = labels; labels = undefined; }; // Don't connect to ourself if(dest == self.sessionID) return callback(new Error("Connecting to ourself")); // Check if we are already connected to the requested peer var pc = peersManager.get(dest); if(pc) { // We are already connected to that peer, ignore the request var message = "["+self.sessionID+"] Already connected to "+dest; console.log(message); // [ToDo] AddChannels(pc, labels); var channels = pc._channels || []; callback(null, pc, channels); return; }; // Check if we are already trying to connect to that peer var offer = offers[dest]; if(offer) { // We are already trying to connect to that peer, ignore the request var message = "["+self.sessionID+"] Already requested connection to "+dest; console.log(message); var pc = offer.peerConnection; // [ToDo] AddChannels(pc, labels); pc.addEventListener('signalingstatechange', function(event) { if(pc.signalingState == 'stable') { var channels = pc._channels || []; callback(null, pc, channels); } }); return; }; // Request to connect to a new peer, generate the offer generateOffer(dest, [handshakeManager, peersManager], labels, callback); }; // // Managers // function forward(message, connector) { var from = message.from; var dest = message.dest; // Message was for us, raise error if(dest == self.sessionID) return console.error('Ignored message send to us', message); // Don't forward the message if TTL has been achieved if(--message.ttl <= 0) return console.warn("TTL achieved:",message); var dest = message.dest; message = message.pack(); // Search the peer between the ones currently connected so we can send the // message directly to it for(var i=0, peer; peer=peersManager._connectors[i]; i++) if(peer.sessionID == dest) return peer.send(message); // Peer was not found, forward message to all the connectors except the one // where we received it peersManager.send(message, connector); handshakeManager.send(message, connector); }; handshakeManager.on('connected', function() { self.emit('handshakeManager.connected', handshakeManager); }); handshakeManager.on('disconnected', function() { self.emit('handshakeManager.disconnected', handshakeManager); }); peersManager.on('connected', function() { self.emit('peersManager.connected', peersManager); }); peersManager.on('disconnected', function() { self.emit('peersManager.disconnected', peersManager); }); /** * Check if it's a request from a peer we are already connected or trying to * connect to, so we can prevent to do crossed connections * * @param from - ID of the other peer * * @returns {Boolean} - If we are already connected or trying to connect */ function connectionProcessed(from) { // Check if we are already connected to the requester peer var peer = peersManager.get(from); if(peer) { // [ToDo] AddChannels(peer, labels); // We are already connected to that peer, send an error to the other end var message = "["+self.sessionID+"] Already connected to "+from; console.log(message); var error = { code: -1, message: message, ttl: MAX_TTL_DEFAULT }; return error; }; // Check if we are already trying to connect to that peer var offer = offers[from]; if(offer) { // [ToDo] AddChannels(offer.peerConnection, labels); // We are already trying to connect to that peer, ignore the request var message = "["+self.sessionID+"] Already requested connection to "+from; console.log(message); // We have higher precedence, send an error to the other end if(self.sessionID < from) { console.log("["+self.sessionID+"] Higher precedence than "+from+", sending error"); var error = { code: -2, message: message, ttl: MAX_TTL_DEFAULT }; return error; } // We have less precedence, cancel our connection request and prepare to use the // incoming one with the previous callback delete offers[from]; rpcBuilder.cancel(offer.message); offer.peerConnection.close(); console.debug("["+self.sessionID+"] Lower precedence than "+from+", request canceled"); return offer.callback; }; // Connection request is a genuinely new one, process it as usual }; // // Protocol messages // function onpresence(from, connector) { // Check if we are already connected to the new peer var peer = peersManager.get(from); if(peer) return; // Check if we are already trying to connect to the new peer var offer = offers[from]; if(offer) return; var pc = generateOffer(from, connector); pc.addEventListener('signalingstatechange', function(event) { // If PeerConnection object gets open, increase number of connections // fetch over this connector so it can decide if it could close if(pc.signalingState == 'stable') connector.increaseConnections(); }); }; function onoffer(request, connector) { var from = request.from; var dest = request.dest; // Message is for us if(dest == self.sessionID) { console.log("["+dest+"] Received connection request from "+from); // If connection is already processed, send an error to the other end var processed = connectionProcessed(from); if(processed && processed.code) return request.reply(processed); // Search the peer between the list of currently connected ones, // or create it if it's not connected var pc = createPeerConnection(from, "answer", function(answer) { var response = { sdp: answer, ttl: MAX_TTL_DEFAULT }; // Send back the connection request over the same connector, since // this should be the shortest path to connect both peers request.reply(null, response); }); // Process offer & generate answer pc.setRemoteDescription(new RTCSessionDescription( { sdp: request.sdp, type: 'offer' }), function() { pc.createAnswer(function(answer) { pc.setLocalDescription(answer, null, onerror); }, onerror) }, onerror); if(processed) pc.addEventListener('signalingstatechange', function(event) { if(pc.signalingState == 'stable') { var channels = pc._channels || []; processed(null, pc, channels); } }); pc.addEventListener('datachannel', function(event) { var channel = event.channel; var channels = pc._channels || []; channels.push(channel); pc._channels = channels; }); } // Forward message else forward(request, connector); }; function generateOffer(dest, connector, labels, callback) { var pc = createPeerConnection(dest, "offer", function(offer) { var message = { sdp: offer, ttl: callback ? MAX_TTL_DEFAULT : 1 }; callback = callback || noop; var request = rpcBuilder.encode('offer', message, dest, function(error, result) { if(error) return callback(error); processAnswer(result); }); offers[dest] = { callback: callback, message: request, peerConnection: pc }; if(connector instanceof Array) connector.forEach(function(c) { c.send(request); }); // Send back the connection request over the same connector, since this // should be the shortest path to connect both peers else connector.send(request); }); // // Init PeerConnection // // Set channels for this PeerConnection object labels = [self.routingLabel].concat(commonLabels, labels); var channels = pc._channels = []; for(var i=0, label; label=labels[i]; i++) { var channel = pc.createDataChannel(label); channels.push(channel); channel.addEventListener('close', function(event) { channels.splice(channels.indexOf(channel), 1); }); }; // Send offer var mediaConstraints = { mandatory: { OfferToReceiveAudio: false, OfferToReceiveVideo: false } }; pc.createOffer(function(offer) { pc.setLocalDescription(offer, null, onerror); }, onerror, mediaConstraints); return pc; }; function processAnswer(response) { var from = response.from; console.debug('['+self.sessionID+'] Received answer from '+from); var offer = offers[from]; if(offer) offer.peerConnection.setRemoteDescription(new RTCSessionDescription( { sdp: response.sdp, type: 'answer' }), null, onerror); else console.warn("["+self.sessionID+"] Connection with peer '" + from + "' was not previously requested"); }; // // Manager events // function initManagerEvents(manager) { manager.on('connected', function() { if(handshakeManager.status != peersManager.status) self.emit('connected'); }); manager.on('disconnected', function() { if(handshakeManager.status == peersManager.status) self.emit('disconnected'); }); // manager.on('error', onerror); manager.on('offer', onoffer); manager.on('answer', forward); manager.on('error', forward); }; // Init managers events initManagerEvents(handshakeManager); initManagerEvents(peersManager); handshakeManager.on('presence', onpresence); // // Clossing functions // this.close = function() { handshakeManager.close(); peersManager.close(); rpcBuilder.close(); }; // Close all connections when user goes out of the page or app is clossed process.on('exit', function(code) { self.close(); }); }; inherits(WebP2P, EventEmitter); module.exports = WebP2P; }).call(this,_dereq_("FWaASH")) },{"./managers/HandshakeManager":9,"./managers/PeersManager":11,"./packer":12,"FWaASH":24,"events":17,"freeice":46,"inherits":56,"process-events-shim":57,"rpc-builder":59,"uuid":65,"wrtc":66}],2:[function(_dereq_,module,exports){ var inherits = _dereq_("inherits"); var HandshakeConnector = _dereq_("./core/HandshakeConnector"); var PUBNUB = _dereq_("pubnub"); /** * Handshake connector for PubNub * @param {Object} configuration Configuration object. */ function Connector_PubNub(config_init, config_mess, max_connections) { HandshakeConnector.call(this, max_connections); var self = this; var channel = config_mess.channel; // Connect a handshake connector to the PubNub server var pubnub = PUBNUB.init(config_init); // Configure handshake connector pubnub.subscribe( { channel: channel, restore: false, backfill: false, connect: self._open, message: self._message, disconnect: self._close, error: function(response) { self._error(new Error(response ? response.error : 'Undefined error')) } }); // Define methods /** * Close the connection with this handshake server */ this.close = function() { pubnub.unsubscribe( { channel: channel }); // This shouldn't be necesary, but seems 'disconnect' event is not being // dispatched by the PubNub channel, so we close the connector explicitly self._close(); } /** * Send the message */ this.send = function(message) { pubnub.publish( { channel: channel, message: message }); }; } inherits(Connector_PubNub, HandshakeConnector); // Class constants Connector_PubNub.prototype.max_connections = 50; Object.defineProperty(Connector_PubNub.prototype, 'max_chars', {value: 1800}); module.exports = Connector_PubNub; },{"./core/HandshakeConnector":5,"inherits":56,"pubnub":67}],3:[function(_dereq_,module,exports){ var inherits = _dereq_("inherits"); var EventEmitter = _dereq_("events").EventEmitter; function Connector() { EventEmitter.call(this); var self = this; /** * Notify that the connection to this handshake server is open */ this._open = function() { self.emit('open'); }; this._message = function(message) { self.emit("message", message); }; this._close = function() { self.emit('close'); }; this._error = function(error) { self.emit('error', error); }; }; inherits(Connector, EventEmitter); Connector.prototype.close = function() { throw new TypeError("Connector.close should be defined in a child class"); }; Connector.prototype.send = function(message) { throw new TypeError("Connector.send should be defined in a child class"); }; module.exports = Connector; },{"events":17,"inherits":56}],4:[function(_dereq_,module,exports){ var inherits = _dereq_("inherits"); var Connector = _dereq_("./Connector"); function Connector_DataChannel(datachannel) { Connector.call(this); var self = this; datachannel.addEventListener('open', self._open); datachannel.addEventListener('message', function(event) { self._message(event.data); }); datachannel.addEventListener('close', self._close); datachannel.addEventListener('error', self._error); // Define methods /** * Close the connection with the peer */ this.close = function() { datachannel.close(); }; /** * Send the message */ this.send = function(message) { datachannel.send(message); }; }; inherits(Connector_DataChannel, Connector); module.exports = Connector_DataChannel; },{"./Connector":3,"inherits":56}],5:[function(_dereq_,module,exports){ var inherits = _dereq_("inherits"); var Connector = _dereq_("./Connector"); function HandshakeConnector(max_connections) { Connector.call(this); var self = this; if(max_connections != undefined) this.max_connections = Math.min(max_connections, this.max_connections); /** * Check if we should connect this new peer or ignore it to increase entropy * in the network mesh * * @returns {Boolean} */ this.shouldConnect = function() { return true; }; // Count the maximum number of pending connections allowed to be // done with this handshake server (undefined == unlimited) var connections = 0; this.increaseConnections = function() { // Increase the number of connections reached throught // this handshake server connections++; // Close connection with handshake server if we got its quota of peers if(connections >= self.max_connections) self.close(); }; }; inherits(HandshakeConnector, Connector); // Class constants HandshakeConnector.prototype.max_connections = Number.POSITIVE_INFINITY; module.exports = HandshakeConnector; },{"./Connector":3,"inherits":56}],6:[function(_dereq_,module,exports){ var inherits = _dereq_('inherits'); var HandshakeConnector = _dereq_("./core/HandshakeConnector"); var ortcNodeclient = _dereq_('ibtrealtimesjnode').IbtRealTimeSJNode; /** * Handshake connector for xRTML * @param {Object} configuration Configuration object */ function Connector_xRTML(config_init, config_mess, max_connections) { HandshakeConnector.call(this, max_connections); var self = this; var channel = config_mess.channel; // Create ORTC client var ortcClient = new ortcNodeclient(); ortcClient.setClusterUrl('http://ortc-developers.realtime.co/server/2.1/'); ortcClient.onConnected = function(ortc) { ortcClient.subscribe(channel, true, function(ortc, channel, message) { self._message(message); }); }; ortcClient.onSubscribed = this._open.bind(this); ortcClient.onUnsubscribed = this._close.bind(this); ortcClient.onException = function(ortc, exception) { self._error(exception); }; // Connect a handshake connector to the xRTML server ortcClient.connect(config_init.application_key, config_init.authentication_token); // Define methods /** * Close the connection with this handshake server */ this.close = function() { ortcClient.unsubscribe(channel); }; /** * Send the message */ this.send = function(message) { ortcClient.send(channel, message); }; } inherits(Connector_xRTML, HandshakeConnector); module.exports = Connector_xRTML; },{"./core/HandshakeConnector":5,"ibtrealtimesjnode":51,"inherits":56}],7:[function(_dereq_,module,exports){ const ERROR_NETWORK_UNKNOWN = {id: 0, msg: 'Unable to fetch handshake servers configuration'}; const ERROR_NETWORK_OFFLINE = {id: 1, msg: "There's no available network"}; const ERROR_REQUEST_FAILURE = {id: 2, msg: 'Unable to fetch handshake servers configuration'}; const ERROR_REQUEST_EMPTY = {id: 3, msg: 'Handshake servers configuration is empty'}; const ERROR_NO_PEERS = {id: 4, msg: 'Not connected to any peer'}; exports.ERROR_NETWORK_UNKNOWN = ERROR_NETWORK_UNKNOWN; exports.ERROR_NETWORK_OFFLINE = ERROR_NETWORK_OFFLINE; exports.ERROR_REQUEST_FAILURE = ERROR_REQUEST_FAILURE; exports.ERROR_REQUEST_EMPTY = ERROR_REQUEST_EMPTY; exports.ERROR_NO_PEERS = ERROR_NO_PEERS; },{}],8:[function(_dereq_,module,exports){ /** * New node file */ var HandshakeConnector = _dereq_('./connectors/core/HandshakeConnector'); var WebP2P = _dereq_('./WebP2P'); module.exports = WebP2P; module.exports.HandshakeConnector = HandshakeConnector; },{"./WebP2P":1,"./connectors/core/HandshakeConnector":5}],9:[function(_dereq_,module,exports){ var inherits = _dereq_('inherits'); var Manager = _dereq_('./Manager'); var errors = _dereq_('../errors'); var Connector_PubNub = _dereq_('../connectors/PubNub'); var Connector_xRTML = _dereq_('../connectors/xRTML'); /** * Manage the handshake connectors using several servers * * @constructor * @param {String} json_uri URI of the handshake servers configuration. */ function HandshakeManager(rpcBuilder, handshake_servers) { Manager.call(this, rpcBuilder); var self = this; var handshakeConnectorConstructors = {}; this.registerConnectorConstructor = function(type, constructor) { handshakeConnectorConstructors[type] = constructor }; // Default handshake connectors this.registerConnectorConstructor('PubNub', Connector_PubNub); this.registerConnectorConstructor('xRTML', Connector_xRTML); function createConnector(config) { var type = config.type; var config_init = config.config_init; var config_mess = config.config_mess; var max_connections = config.max_connections; // Check if connector constructor is from a valid handshake server var connectorConstructor = handshakeConnectorConstructors[type]; if(!connectorConstructor) throw Error("Invalid handshake server type '" + type + "'"); var connector = new connectorConstructor(config_init, config_mess, max_connections); self._initConnector(connector); connector.on('open', function() { // Notify our presence to the other peers connector.send(rpcBuilder.encode('presence')); }); var _messageUnpacked = connector._messageUnpacked; connector._messageUnpacked = function(message) { if(message.method == 'presence') { if(connector.shouldConnect()) self.emit("presence", message.params.from, connector); } else _messageUnpacked.call(connector, message); }; return connector; }; var configs = []; var index = 0; var configs_infinity = []; /** * Get a random handshake connector or test for the next one * @param {Object} configuration Handshake servers configuration. */ function handshake() { if(!configs.length) throw Error('No handshake servers defined') for(; index < configs.length; index++) { var connector = createConnector(configs[index]); connector.on('close', function() { // Handshake connector has been closed, try to get an alternative one index++; handshake(); }); // Connector successfully created return; }; // All configured handshake servers has been consumed // Get ready to start again from beginning of handshake servers list index = 0; }; this.addConfigs_byObject = function(config) { // Check if connector constructor is from a valid handshake server var connectorConstructor = handshakeConnectorConstructors[config.type]; if(!connectorConstructor) return console.error("Invalid handshake server config: ", config); if(connectorConstructor.prototype.max_connections == Number.POSITIVE_INFINITY && !config.max_connections) { configs_infinity.push(config) // Start handshaking createConnector(config); } else { configs.push(config); // Start handshaking if(self.status == 'disconnected') handshake(); } }; // Add handshake servers configuration if(handshake_servers) this.addConfigs(handshake_servers) }; inherits(HandshakeManager, Manager); HandshakeManager.prototype.addConfigs = function(configuration) { if(typeof configuration == 'string') return this.addConfigs_byUri(configuration); if(configuration instanceof Array) return this.addConfigs_byArray(configuration); this.addConfigs_byObject(configuration); }; HandshakeManager.prototype.addConfigs_byArray = function(configuration) { for(var i=0, config; config=configuration[i]; i++) this.addConfigs_byObject(config); }; HandshakeManager.prototype.addConfigs_byUri = function(json_uri) { var self = this; function dispatchError(error) { self.emit('error', error); }; // Request the handshake servers configuration file var http_request = new XMLHttpRequest(); http_request.open('GET', json_uri); http_request.onload = function(event) { if(this.status == 200) { var configuration = JSON.parse(http_request.response); // We got some config entries if(configuration.length) self.addConfigs_byArray(configuration) // Config was empty else dispatchError(errors.ERROR_REQUEST_EMPTY) } // Request returned an error else dispatchError(errors.ERROR_REQUEST_FAILURE) }; // Connection error http_request.onerror = function(event) { dispatchError(navigator.onLine ? errors.ERROR_NETWORK_UNKNOWN : errors.ERROR_NETWORK_OFFLINE) }; http_request.send(); }; module.exports = HandshakeManager; },{"../connectors/PubNub":2,"../connectors/xRTML":6,"../errors":7,"./Manager":10,"inherits":56}],10:[function(_dereq_,module,exports){ var EventEmitter = _dereq_("events").EventEmitter; var inherits = _dereq_('inherits'); function Manager(rpcBuilder) { EventEmitter.call(this); var self = this; this._connectors = []; this.__defineGetter__("status", function() { return this._connectors.length ? 'connected' : 'disconnected'; }); this._initConnector = function(connector) { connector.on('open', function() { // for(var i=0, conn; conn=self._connectors[i]; i++) // if(conn===connector) // console.error('Duplicate connector',connector); self._connectors.push(connector); if(self._connectors.length == 1) self.emit('connected'); }); connector.on('close', function() { self._connectors.splice(self._connectors.indexOf(connector), 1); if(self._connectors.length == 0) self.emit('disconnected'); }); connector.on('error', function(error) { connector.close(); self.emit('error', error); }); connector._messageUnpacked = function(message) { var method = message.method; var params = message.params; switch(method) { case 'offer': params.reply = function(error, result) { return message.reply(error, result, connector); }; case 'answer': case 'error': params.pack = function() { return message.pack(); }; self.emit(method, params, connector); break; default: // This should never be reached console.error("Unknown message method '"+method+"'", message); }; }; connector.on('message', function(message) { message = rpcBuilder.decode(message); if(message) { // Response was previously stored, send it directly if(message.stored) connector.send(message) // Normal message, process it else connector._messageUnpacked(message); }; }); }; }; inherits(Manager, EventEmitter); /** * Close all the connections */ Manager.prototype.close = function() { for(var i=0, connector; connector=this._connectors[i]; i++) connector.close(); }; /** * {Object} message - Message to be send * {Connector} [incomingConnector] - {Connector} to don't send the message */ Manager.prototype.send = function(message, incomingConnector) { for(var i=0, connector; connector=this._connectors[i]; i++) { // Don't send the message to the same connector where we received it if(connector === incomingConnector) continue; connector.send(message); }; }; module.exports = Manager; },{"events":17,"inherits":56}],11:[function(_dereq_,module,exports){ var inherits = _dereq_('inherits'); var Manager = _dereq_('./Manager'); var Connector_DataChannel = _dereq_('../connectors/core/DataChannel'); /** * @classdesc Manager of the communications with the other peers * * @constructor */ function PeersManager(rpcBuilder, routingLabel) { Manager.call(this, rpcBuilder); var self = this; function createConnector(channel) { var connector = new Connector_DataChannel(channel); self._initConnector(connector); return connector; }; var peers = {}; this.__defineGetter__("peers", function() { return peers; }); function initDataChannel(channel, sessionID) { if(channel.label == routingLabel) { var connector = createConnector(channel); connector.sessionID = sessionID; return connector; }; }; this.add = function(sessionID, peerConnection, channels) { peerConnection.addEventListener('signalingstatechange', function(event) { // Remove the peer from the list of peers when gets closed if(peerConnection.signalingState == 'closed') delete peers[sessionID]; }); // Connection initiator if(channels.length) { // Only init the routing functionality on the routing channel for(var i=0, channel; channel=channels[i]; i++) if(initDataChannel(channel, sessionID)) break; } // Connection receiver else { function initDataChannel_listener(event) { var channel = event.channel; var connector = initDataChannel(channel, sessionID); if(connector) event.target.removeEventListener('datachannel', initDataChannel_listener); }; peerConnection.addEventListener('datachannel', initDataChannel_listener); }; // Add PeerConnection to the list of enabled ones peers[sessionID] = peerConnection; }; this.get = function(sessionID) { return peers[sessionID]; }; var _close = this.close; this.close = function() { for(var id in peers) peers[id].close(); _close.call(this); }; }; inherits(PeersManager, Manager); module.exports = PeersManager; },{"../connectors/core/DataChannel":4,"./Manager":10,"inherits":56}],12:[function(_dereq_,module,exports){ const ERROR = 0; const PRESENCE = 1; const OFFER = 2; const ANSWER = 3; function pack(message, id) { var result = new Array(6); var params = message.params || {}; // Method var method = message.method; switch(method) { case "error": result[0] = ERROR; break; case "presence": result[0] = PRESENCE; break; case "offer": result[0] = OFFER; break; case "answer": result[0] = ANSWER; break; default: { var error = new Error("Unknown message method '"+method+"'"); error.message = message; throw error; }; }; // From result[1] = params.from; // Offer & Answer if(method != 'presence') { result[2] = params.dest; result[3] = id; result[4] = params.ttl; result[5] = params.sdp || params.message; }; return JSON.stringify(result); }; function unpack(message) { var result = {}; var params = {}; message = JSON.parse(message); // Method var method = message[0]; switch(method) { case ERROR: result.method = "error"; break; case PRESENCE: result.method = "presence"; break; case OFFER: result.method = "offer"; break; case ANSWER: result.method = "answer"; break; default: { var error = new Error("Unknown message method '"+method+"'"); error.message = message; throw error; }; }; // From params.from = message[1]; // Offer & Answer if(method != PRESENCE) { params.dest = message[2]; if(method == ERROR || method == ANSWER) result.ack = message[3]; else result.id = message[3]; params.ttl = message[4]; if(method == ERROR) params.message = message[5]; else params.sdp = message[5]; }; // Return unpacked message result.params = params; return result; }; var responseMethods = { 'presence': 'offer', 'offer': { error: 'error', response: 'answer' } }; exports.pack = pack; exports.unpack = unpack; exports.responseMethods = responseMethods; },{}],13:[function(_dereq_,module,exports){ },{}],14:[function(_dereq_,module,exports){ /*! * The buffer module from node.js, for the browser. * * @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org> * @license MIT */ var base64 = _dereq_('base64-js') var ieee754 = _dereq_('ieee754') exports.Buffer = Buffer exports.SlowBuffer = Buffer exports.INSPECT_MAX_BYTES = 50 Buffer.poolSize = 8192 /** * If `Buffer._useTypedArrays`: * === true Use Uint8Array implementation (fastest) * === false Use Object implementation (compatible down to IE6) */ Buffer._useTypedArrays = (function () { // Detect if browser supports Typed Arrays. Supported browsers are IE 10+, Firefox 4+, // Chrome 7+, Safari 5.1+, Opera 11.6+, iOS 4.2+. If the browser does not support adding // properties to `Uint8Array` instances, then that's the same as no `Uint8Array` support // because we need to be able to add all the node Buffer API methods. This is an issue // in Firefox 4-29. Now fixed: https://bugzilla.mozilla.org/show_bug.cgi?id=695438 try { var buf = new ArrayBuffer(0) var arr = new Uint8Array(buf) arr.foo = function () { return 42 } return 42 === arr.foo() && typeof arr.subarray === 'function' // Chrome 9-10 lack `subarray` } catch (e) { return false } })() /** * Class: Buffer * ============= * * The Buffer constructor returns instances of `Uint8Array` that are augmented * with function properties for all the node `Buffer` API functions. We use * `Uint8Array` so that square bracket notation works as expected -- it returns * a single octet. * * By augmenting the instances, we can avoid modifying the `Uint8Array` * prototype. */ function Buffer (subject, encoding, noZero) { if (!(this instanceof Buffer)) return new Buffer(subject, encoding, noZero) var type = typeof subject // Workaround: node's base64 implementation allows for non-padded strings // while base64-js does not. if (encoding === 'base64' && type === 'string') { subject = stringtrim(subject) while (subject.length % 4 !== 0) { subject = subject + '=' } } // Find the length var length if (type === 'number') length = coerce(subject) else if (type === 'string') length = Buffer.byteLength(subject, encoding) else if (type === 'object') length = coerce(subject.length) // assume that object is array-like else throw new Error('First argument needs to be a number, array or string.') var buf if (Buffer._useTypedArrays) { // Preferred: Return an augmented `Uint8Array` instance for best performance buf = Buffer._augment(new Uint8Array(length)) } else { // Fallback: Return THIS instance of Buffer (created by `new`) buf = this buf.length = length buf._isBuffer = true } var i if (Buffer._useTypedArrays && typeof subject.byteLength === 'number') { // Speed optimization -- use set if we're copying from a typed array buf._set(subject) } else if (isArrayish(subject)) { // Treat array-ish objects as a byte array for (i = 0; i < length; i++) { if (Buffer.isBuffer(subject)) buf[i] = subject.readUInt8(i) else buf.writeInt8(subject[i], i) } } else if (type === 'string') { buf.write(subject, 0, encoding) } else if (type === 'number' && !Buffer._useTypedArrays && !noZero) { for (i = 0; i < length; i++) { buf[i] = 0 } } return buf } // STATIC METHODS // ============== Buffer.isEncoding = function (encoding) { switch (String(encoding).toLowerCase()) { case 'hex': case 'utf8': case 'utf-8': case 'ascii': case 'binary': case 'base64': case 'raw': case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': return true default: return false } } Buffer.isBuffer = function (b) { return !!(b !== null && b !== undefined && b._isBuffer) } Buffer.byteLength = function (str, encoding) { var ret str = str.toString() switch (encoding || 'utf8') { case 'hex': ret = str.length / 2 break case 'utf8': case 'utf-8': ret = utf8ToBytes(str).length break case 'ascii': case 'binary': case 'raw': ret = str.length break case 'base64': ret = base64ToBytes(str).length break case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': ret = str.length * 2 break default: throw new Error('Unknown encoding') } return ret } Buffer.concat = function (list, totalLength) { assert(isArray(list), 'Usage: Buffer.concat(list[, length])') if (list.length === 0) { return new Buffer(0) } else if (list.length === 1) { return list[0] } var i if (totalLength === undefined) { totalLength = 0 for (i = 0; i < list.length; i++) { totalLength += list[i].length } } var buf = new Buffer(totalLength) var pos = 0 for (i = 0; i < list.length; i++) { var item = list[i] item.copy(buf, pos) pos += item.length } return buf } Buffer.compare = function (a, b) { assert(Buffer.isBuffer(a) && Buffer.isBuffer(b), 'Arguments must be Buffers') var x = a.length var y = b.length for (var i = 0, len = Math.min(x, y); i < len && a[i] === b[i]; i++) {} if (i !== len) { x = a[i] y = b[i] } if (x < y) { return -1 } if (y < x) { return 1 } return 0 } // BUFFER INSTANCE METHODS // ======================= function hexWrite (buf, string, offset, length) { offset = Number(offset) || 0 var remaining = buf.length - offset if (!length) { length = remaining } else { length = Number(length) if (length > remaining) { length = remaining } } // must be an even number of digits var strLen = string.length assert(strLen % 2 === 0, 'Invalid hex string') if (length > strLen / 2) { length = strLen / 2 } for (var i = 0; i < length; i++) { var byte = parseInt(string.substr(i * 2, 2), 16) assert(!isNaN(byte), 'Invalid hex string') buf[offset + i] = byte } return i } function utf8Write (buf, string, offset, length) { var charsWritten = blitBuffer(utf8ToBytes(string), buf, offset, length) return charsWritten } function asciiWrite (buf, string, offset, length) { var charsWritten = blitBuffer(asciiToBytes(string), buf, offset, length) return charsWritten } function binaryWrite (buf, string, offset, length) { return asciiWrite(buf, string, offset, length) } function base64Write (buf, string, offset, length) { var charsWritten = blitBuffer(base64ToBytes(string), buf, offset, length) return charsWritten } function utf16leWrite (buf, string, offset, length) { var charsWritten = blitBuffer(utf16leToBytes(string), buf, offset, length) return charsWritten } Buffer.prototype.write = function (string, offset, length, encoding) { // Support both (string, offset, length, encoding) // and the legacy (string, encoding, offset, length) if (isFinite(offset)) { if (!isFinite(length)) { encoding = length length = undefined } } else { // legacy var swap = encoding encoding = offset offset = length length = swap } offset = Number(offset) || 0 var remaining = this.length - offset if (!length) { length = remaining } else { length = Number(length) if (length > remaining) { length = remaining } } encoding = String(encoding || 'utf8').toLowerCase() var ret switch (encoding) { case 'hex': ret = hexWrite(this, string, offset, length) break case 'utf8': case 'utf-8': ret = utf8Write(this, string, offset, length) break case 'ascii': ret = asciiWrite(this, string, offset, length) break case 'binary': ret = binaryWrite(this, string, offset, length) break case 'base64': ret = base64Write(this, string, offset, length) break case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': ret = utf16leWrite(this, string, offset, length) break default: throw new Error('Unknown encoding') } return ret } Buffer.prototype.toString = function (encoding, start, end) { var self = this encoding = String(encoding || 'utf8').toLowerCase() start = Number(start) || 0 end = (end === undefined) ? self.length : Number(end) // Fastpath empty strings if (end === start) return '' var ret switch (encoding) { case 'hex': ret = hexSlice(self, start, end) break case 'utf8': case 'utf-8': ret = utf8Slice(self, start, end) break case 'ascii': ret = asciiSlice(self, start, end) break case 'binary': ret = binarySlice(self, start, end) break case 'base64': ret = base64Slice(self, start, end) break case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': ret = utf16leSlice(self, start, end) break default: throw new Error('Unknown encoding') } return ret } Buffer.prototype.toJSON = function () { return { type: 'Buffer', data: Array.prototype.slice.call(this._arr || this, 0) } } Buffer.prototype.equals = function (b) { assert(Buffer.isBuffer(b), 'Argument must be a Buffer') return Buffer.compare(this, b) === 0 } Buffer.prototype.compare = function (b) { assert(Buffer.isBuffer(b), 'Argument must be a Buffer') return Buffer.compare(this, b) } // copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) Buffer.prototype.copy = function (target, target_start, start, end) { var source = this if (!start) start = 0 if (!end && end !== 0) end = this.length if (!target_start) target_start = 0 // Copy 0 bytes; we're done if (end === start) return if (target.length === 0 || source.length === 0) return // Fatal error conditions assert(end >= start, 'sourceEnd < sourceStart') assert(target_start >= 0 && target_start < target.length, 'targetStart out of bounds') assert(start >= 0 && start < source.length, 'sourceStart out of bounds') assert(end >= 0 && end <= source.length, 'sourceEnd out of bounds') // Are we oob? if (end > this.length) end = this.length if (target.length - target_start < end - start) end = target.length - target_start + start var len = end - start if (len < 100 || !Buffer._useTypedArrays) { for (var i = 0; i < len; i++) { target[i + target_start] = this[i + start] } } else { target._set(this.subarray(start, start + len), target_start) } } function base64Slice (buf, start, end) { if (start === 0 && end === buf.length) { return base64.fromByteArray(buf) } else { return base64.fromByteArray(buf.slice(start, end)) } } function utf8Slice (buf, start, end) { var res = '' var tmp = '' end = Math.min(buf.length, end) for (var i = start; i < end; i++) { if (buf[i] <= 0x7F) { res += decodeUtf8Char(tmp) + String.fromCharCode(buf[i]) tmp = '' } else { tmp += '%' + buf[i].toString(16) } } return res + decodeUtf8Char(tmp) } function asciiSlice (buf, start, end) { var ret = '' end = Math.min(buf.length, end) for (var i = start; i < end; i++) { ret += String.fromCharCode(buf[i]) } return ret } function binarySlice (buf, start, end) { return asciiSlice(buf, start, end) } function hexSlice (buf, start, end) { var len = buf.length if (!start || start < 0) start = 0 if (!end || end < 0 || end > len) end = len var out = '' for (var i = start; i < end; i++) { out += toHex(buf[i]) } return out } function utf16leSlice (buf, start, end) { var bytes = buf.slice(start, end) var res = '' for (var i = 0; i < bytes.length; i += 2) { res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256) } return res } Buffer.prototype.slice = function (start, end) { var len = this.length start = clamp(start, len, 0) end = clamp(end, len, len) if (Buffer._useTypedArrays) { return Buffer._augment(this.subarray(start, end)) } else { var sliceLen = end - start var newBuf = new Buffer(sliceLen, undefined, true) for (var i = 0; i < sliceLen; i++) { newBuf[i] = this[i + start] } return newBuf } } // `get` will be removed in Node 0.13+ Buffer.prototype.get = function (offset) { console.log('.get() is deprecated. Access using array indexes instead.') return this.readUInt8(offset) } // `set` will be removed in Node 0.13+ Buffer.prototype.set = function (v, offset) { console.log('.set() is deprecated. Access using array indexes instead.') return this.writeUInt8(v, offset) } Buffer.prototype.readUInt8 = function (offset, noAssert) { if (!noAssert) { assert(offset !== undefined && offset !== null, 'missing offset') assert(offset < this.length, 'Trying to read beyond buffer length') } if (offset >= this.length) return return this[offset] } function readUInt16 (buf, offset, littleEndian, noAssert) { if (!noAssert) { assert(typeof littleEndian === 'boolean', 'missing or invalid endian') assert(offset !== undefined && offset !== null, 'missing offset') assert(offset + 1 < buf.length, 'Trying to read beyond buffer length') } var len = buf.length if (offset >= len) return var val if (littleEndian) { val = buf[offset] if (offset + 1 < len) val |= buf[offset + 1] << 8 } else { val = buf[offset] << 8 if (offset + 1 < len) val |= buf[offset + 1] } return val } Buffer.prototype.readUInt16LE = function (offset, noAssert) { return readUInt16(this, offset, true, noAssert) } Buffer.prototype.readUInt16BE = function (offset, noAssert) { return readUInt16(this, offset, false, noAssert) } function readUInt32 (buf, offset, littleEndian, noAssert) { if (!noAssert) { assert(typeof littleEndian === 'boolean', 'missing or invalid endian') assert(offset !== undefined && offset !== null, 'missing offset') assert(offset + 3 < buf.length, 'Trying to read beyond buffer length') } var len = buf.length if (offset >= len) return var val if (littleEndian) { if (offset + 2 < len) val = buf[offset + 2] << 16 if (offset + 1 < len) val |= buf[offset + 1] << 8 val |= buf[offset] if (offset + 3 < len) val = val + (buf[offset + 3] << 24 >>> 0) } else { if (offset + 1 < len) val = buf[offset + 1] << 16 if (offset + 2 < len) val |= buf[offset + 2] << 8 if (offset + 3 < len) val |= buf[offset + 3] val = val + (buf[offset] << 24 >>> 0) } return val } Buffer.prototype.readUInt32LE = function (offset, noAssert) { return readUInt32(this, offset, true, noAssert) } Buffer.prototype.readUInt32BE = function (offset, noAssert) { return readUInt32(this, offset, false, noAssert) } Buffer.prototype.readInt8 = function (offset, noAssert) { if (!noAssert)