webp2p
Version:
The server-less signaling channel for WebRTC
2,114 lines (1,672 loc) • 428 kB
JavaScript
(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(require,module,exports){
(function (process){
var EventEmitter = require("events").EventEmitter;
var inherits = require('inherits');
var freeice = require('freeice');
var uuid = require('uuid');
var wrtc = require('wrtc');
var RTCPeerConnection = wrtc.RTCPeerConnection;
var RTCSessionDescription = wrtc.RTCSessionDescription;
require("process-events-shim");
var RpcBuilder = require('rpc-builder');
var HandshakeManager = require('./managers/HandshakeManager');
var PeersManager = require('./managers/PeersManager');
var packer = require('./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,require("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(require,module,exports){
var inherits = require("inherits");
var HandshakeConnector = require("./core/HandshakeConnector");
var PUBNUB = require("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(require,module,exports){
var inherits = require("inherits");
var EventEmitter = require("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(require,module,exports){
var inherits = require("inherits");
var Connector = require("./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(require,module,exports){
var inherits = require("inherits");
var Connector = require("./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(require,module,exports){
var inherits = require('inherits');
var HandshakeConnector = require("./core/HandshakeConnector");
var ortcNodeclient = require('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(require,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(require,module,exports){
/**
* New node file
*/
var HandshakeConnector = require('./connectors/core/HandshakeConnector');
var WebP2P = require('./WebP2P');
module.exports = WebP2P;
module.exports.HandshakeConnector = HandshakeConnector;
},{"./WebP2P":1,"./connectors/core/HandshakeConnector":5}],9:[function(require,module,exports){
var inherits = require('inherits');
var Manager = require('./Manager');
var errors = require('../errors');
var Connector_PubNub = require('../connectors/PubNub');
var Connector_xRTML = require('../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(require,module,exports){
var EventEmitter = require("events").EventEmitter;
var inherits = require('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(require,module,exports){
var inherits = require('inherits');
var Manager = require('./Manager');
var Connector_DataChannel = require('../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(require,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(require,module,exports){
},{}],14:[function(require,module,exports){
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
* @license MIT
*/
var base64 = require('base64-js')
var ieee754 = require('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) {
assert(offset !== undefined && offset !== null,
'missing offset')
assert(offset < this.length, 'Trying to read beyond buffer length')
}
if (offset >= this.length)
return
var neg = this[offset] & 0x80
if (neg)
return (0xff - this[offset] + 1) * -1
else
return this[offset]
}
functi