|
@@ -63,22 +63,16 @@ function Peer(id, options) {
|
|
|
return;
|
|
|
}
|
|
|
//
|
|
|
-
|
|
|
+
|
|
|
// States.
|
|
|
this.destroyed = false; // Connections have been killed
|
|
|
this.disconnected = false; // Connection to PeerServer killed but P2P connections still active
|
|
|
//
|
|
|
-
|
|
|
+
|
|
|
// References
|
|
|
this.connections = {}; // DataConnections for this peer.
|
|
|
this.calls = {}; // MediaConnections for this peer
|
|
|
//
|
|
|
-
|
|
|
- // Internal references
|
|
|
- this._managers = {}; // Managers for peer connections
|
|
|
- this._queued = []; // Queued connections to make.
|
|
|
- //
|
|
|
-
|
|
|
|
|
|
// Start the connections
|
|
|
if (id) {
|
|
@@ -122,8 +116,8 @@ Peer.prototype._initialize = function(id) {
|
|
|
var self = this;
|
|
|
this.id = id;
|
|
|
this.socket = new Socket(this.options.secure, this.options.host, this.options.port, this.options.key, this.id);
|
|
|
- this.socket.on('message', function(data) {
|
|
|
- self._dispatchMessage(data);
|
|
|
+ this.socket.on('server-message', function(data) {
|
|
|
+ self._handleMessage(data);
|
|
|
});
|
|
|
this.socket.on('error', function(error) {
|
|
|
self._abort('socket-error', error);
|
|
@@ -135,172 +129,106 @@ Peer.prototype._initialize = function(id) {
|
|
|
this.socket.start();
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-Peer.prototype._dispatchMessage = function(message) {
|
|
|
+/** Handles messages from the server. */
|
|
|
+Peer.prototype._handleMessage = function(message) {
|
|
|
var type = message.type;
|
|
|
- // Message types that don't involve a peer
|
|
|
+ var payload = message.payload
|
|
|
switch (type) {
|
|
|
case 'OPEN':
|
|
|
this._processQueue();
|
|
|
this.emit('open', this.id);
|
|
|
- return;
|
|
|
+ break;
|
|
|
case 'ERROR':
|
|
|
this._abort('server-error', payload.msg);
|
|
|
- return;
|
|
|
+ break;
|
|
|
case 'ID-TAKEN':
|
|
|
- this._abort('unavailable-id', 'ID `'+this.id+'` is taken');
|
|
|
- return;
|
|
|
+ this._abort('unavailable-id', 'ID `' + this.id + '` is taken');
|
|
|
+ break;
|
|
|
case 'INVALID-KEY':
|
|
|
this._abort('invalid-key', 'API KEY "' + this._key + '" is invalid');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- var peer = message.src;
|
|
|
- var managerId = message.manager;
|
|
|
- var manager = this._getManager(peer, managerId);
|
|
|
- var payload = message.payload;
|
|
|
- switch (message.type) {
|
|
|
-
|
|
|
- case 'OFFER':
|
|
|
- var options = {
|
|
|
- sdp: payload.sdp,
|
|
|
- labels: payload.labels,
|
|
|
- config: this.options.config
|
|
|
- };
|
|
|
- // Either forward to or create new manager
|
|
|
-
|
|
|
- if (!manager) {
|
|
|
- manager = this._createManager(managerId, peer, options);
|
|
|
- }
|
|
|
- manager.handleUpdate(options.labels);
|
|
|
- manager.handleSDP(payload.sdp, message.type, payload.call);
|
|
|
- break;
|
|
|
- case 'EXPIRE':
|
|
|
- peer.emit('error', new Error('Could not connect to peer ' + manager.peer));
|
|
|
- break;
|
|
|
- case 'ANSWER':
|
|
|
- // Forward to specific manager
|
|
|
- if (manager) {
|
|
|
- manager.handleSDP(payload.sdp, message.type);
|
|
|
- }
|
|
|
break;
|
|
|
- case 'CANDIDATE':
|
|
|
- // Forward to specific manager
|
|
|
- if (manager) {
|
|
|
- manager.handleCandidate(payload);
|
|
|
- }
|
|
|
- break;
|
|
|
- case 'LEAVE':
|
|
|
- // Leave on all managers for a user
|
|
|
- if (this._managers[peer]) {
|
|
|
- var ids = Object.keys(this._managers[peer].managers);
|
|
|
- for (var i = 0; i < ids.length; i++) {
|
|
|
- this._managers[peer].managers[ids[i]].handleLeave();
|
|
|
+ case 'OFFER': // we should consider switching this to CALL/CONNECT, but this is the least breaking option.
|
|
|
+ var peer = message.src;
|
|
|
+ var id = message.id;
|
|
|
+ var connection = this._getConnection(peer, id);
|
|
|
+
|
|
|
+ if (connection) {
|
|
|
+ // Pass it on
|
|
|
+ connection.handleMessage(message);
|
|
|
+ } else {
|
|
|
+ // Create a new connection.
|
|
|
+ if (payload.type === 'call') {
|
|
|
+ var call = new MediaConnection(peer, {
|
|
|
+ id: id,
|
|
|
+ offer: offer,
|
|
|
+ sdp: payload.sdp
|
|
|
+ });
|
|
|
+ this._addConnection(peer, call);
|
|
|
+ this.emit('call', call);
|
|
|
+ } else if (payload.type === 'connect') {
|
|
|
+ var connection = new DataConnection(peer, {
|
|
|
+ id: id,
|
|
|
+ offer: offer,
|
|
|
+ sdp: payload.sdp
|
|
|
+ });
|
|
|
+ this._addConnection(peer, connection);
|
|
|
+ this.emit('connection', connection);
|
|
|
+ } else {
|
|
|
+ util.warn('Received malformed connection type.');
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
default:
|
|
|
- util.warn('Unrecognized message type:', message.type);
|
|
|
- break;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-/** Process queued calls to connect. */
|
|
|
-Peer.prototype._processQueue = function() {
|
|
|
- while (this._queued.length > 0) {
|
|
|
- var manager = this._queued.pop();
|
|
|
- manager.initialize(this.id, this.socket);
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-/** Listeners for manager. */
|
|
|
-Peer.prototype._attachManagerListeners = function(manager) {
|
|
|
- var self = this;
|
|
|
- // Handle receiving a connection.
|
|
|
- manager.on('connection', function(connection) {
|
|
|
- self._managers[manager.peer].dataManager = manager;
|
|
|
- self.connections[connection.label] = connection;
|
|
|
- self.emit('connection', connection);
|
|
|
- });
|
|
|
- // Handle receiving a call
|
|
|
- manager.on('call', function(call) {
|
|
|
- self.calls[call.label] = call;
|
|
|
- self.emit('call', call);
|
|
|
- });
|
|
|
- // Handle a connection closing.
|
|
|
- manager.on('close', function() {
|
|
|
- if (!!self._managers[manager.peer]) {
|
|
|
- delete self._managers[manager.peer];
|
|
|
- // TODO: delete relevant calls and connections
|
|
|
- }
|
|
|
- });
|
|
|
- manager.on('error', function(err) {
|
|
|
- self.emit('error', err);
|
|
|
- });
|
|
|
-};
|
|
|
-
|
|
|
+ var peer = message.src;
|
|
|
+ var id = message.id;
|
|
|
+ var connection = this._getConnection(peer, id);
|
|
|
|
|
|
-Peer.prototype._getManager = function(peer, managerId) {
|
|
|
- if (this._managers[peer]) {
|
|
|
- return this._managers[peer].managers[managerId];
|
|
|
+ if (connection) {
|
|
|
+ connection.handleMessage(message);
|
|
|
+ } else {
|
|
|
+ util.warn('You aborted your connection to ' + peer + ' before it opened.');
|
|
|
+ }
|
|
|
+ break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-Peer.prototype._getDataManager = function(peer) {
|
|
|
- if (this._managers[peer]) {
|
|
|
- return this._managers[peer].dataManager;
|
|
|
- }
|
|
|
+Peer.prototype.connect = function(peer, options) {
|
|
|
+ var connection = new DataConnection(peer, options);
|
|
|
+ this._addConnection(peer, connection);
|
|
|
+ return connection;
|
|
|
}
|
|
|
|
|
|
-/** Exposed connect function for users. Will try to connect later if user
|
|
|
- * is waiting for an ID. */
|
|
|
-Peer.prototype._createManager = function(managerId, peer, options) {
|
|
|
- if (this.disconnected) {
|
|
|
- var err = new Error('This Peer has been disconnected from the server and can no longer make connections.');
|
|
|
- err.type = 'server-disconnected';
|
|
|
- this.emit('error', err);
|
|
|
+Peer.prototype.call = function(peer, stream, options) {
|
|
|
+ if (!stream) {
|
|
|
+ util.error('To call a peer, you must provide a stream from your browser\'s `getUserMedia`.');
|
|
|
return;
|
|
|
}
|
|
|
+ options.stream = stream;
|
|
|
+ var call = new MediaConnection(peer, options);
|
|
|
+ this._addConnection(peer, call);
|
|
|
+ return call;
|
|
|
+}
|
|
|
|
|
|
- options = util.extend({
|
|
|
- config: this.options.config
|
|
|
- }, options);
|
|
|
-
|
|
|
- if (!this._managers[peer]) {
|
|
|
- this._managers[peer] = {nextId: 0, managers: {}};
|
|
|
- }
|
|
|
-
|
|
|
- managerId = managerId || peer + this._managers[peer].nextId++;
|
|
|
-
|
|
|
- var manager = new ConnectionManager(managerId, peer, options);
|
|
|
- if (!!this.id && !!this.socket) {
|
|
|
- manager.initialize(this.id, this.socket);
|
|
|
- } else {
|
|
|
- this._queued.push(manager);
|
|
|
- }
|
|
|
- this._attachManagerListeners(manager);
|
|
|
- this._managers[peer].managers[manager._managerId] = manager;
|
|
|
-
|
|
|
- return manager;
|
|
|
-};
|
|
|
-
|
|
|
-Peer.prototype.connect = function(peer, options) {
|
|
|
- var manager = this._getDataManager(peer);
|
|
|
- if (!manager) {
|
|
|
- manager = this._createManager(false, peer, options);
|
|
|
+Peer.prototype._addConnection = function(peer, connection) {
|
|
|
+ if (!this.connections[peer]) {
|
|
|
+ this.connections[peer] = [];
|
|
|
}
|
|
|
- var connection = manager.connect(options);
|
|
|
- return connection;
|
|
|
+ this.connections[peer].push(connection);
|
|
|
}
|
|
|
|
|
|
-Peer.prototype.call = function(peer, stream, options) {
|
|
|
- var manager = this._createManager(false, peer, options);
|
|
|
- var connection = manager.call(stream, options);
|
|
|
- return connection;
|
|
|
+Peer.prototype._getConnection = function(peer, id) {
|
|
|
+ var connections = this.connections[peer];
|
|
|
+ if (!connections) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ for (var i = 0, ii = connections.length; i < ii; i++) {
|
|
|
+ if (connections[i].id === id) {
|
|
|
+ return connections[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
Peer.prototype._delayedAbort = function(type, message) {
|
|
|
var self = this;
|
|
|
util.setZeroTimeout(function(){
|
|
@@ -326,28 +254,23 @@ Peer.prototype._abort = function(type, message) {
|
|
|
Peer.prototype.destroy = function() {
|
|
|
if (!this.destroyed) {
|
|
|
this._cleanup();
|
|
|
+ this.disconnect();
|
|
|
this.destroyed = true;
|
|
|
}
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
|
|
|
-// TODO: UPDATE
|
|
|
+/* Disconnects every connection on this peer. */
|
|
|
Peer.prototype._cleanup = function() {
|
|
|
- var self = this;
|
|
|
- if (!!this._managers) {
|
|
|
- var peers = Object.keys(this._managers);
|
|
|
- for (var i = 0, ii = peers.length; i < ii; i++) {
|
|
|
- var ids = Object.keys(this._managers[peers[i]]);
|
|
|
- for (var j = 0, jj = peers.length; j < jj; j++) {
|
|
|
- this._managers[peers[i]][ids[j]].close();
|
|
|
- }
|
|
|
+ var peers = Object.keys(this.connections);
|
|
|
+ for (var i = 0, ii = peers.length; i < ii; i++) {
|
|
|
+ var connections = this.connections[peers[i]];
|
|
|
+ for (var j = 0, jj = connections; j < jj; j++) {
|
|
|
+ connections[j].close();
|
|
|
}
|
|
|
}
|
|
|
- util.setZeroTimeout(function(){
|
|
|
- self.disconnect();
|
|
|
- });
|
|
|
this.emit('close');
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
/**
|
|
|
* Disconnects the Peer's connection to the PeerServer. Does not close any
|
|
@@ -356,17 +279,20 @@ Peer.prototype._cleanup = function() {
|
|
|
* disconnected. It also cannot reconnect to the server.
|
|
|
*/
|
|
|
Peer.prototype.disconnect = function() {
|
|
|
- if (!this.disconnected) {
|
|
|
- if (!!this.socket) {
|
|
|
- this.socket.close();
|
|
|
+ var self = this;
|
|
|
+ util.setZeroTimeout(function(){
|
|
|
+ if (!self.disconnected) {
|
|
|
+ if (self.socket) {
|
|
|
+ self.socket.close();
|
|
|
+ }
|
|
|
+ self.id = null;
|
|
|
+ self.disconnected = true;
|
|
|
}
|
|
|
- this.id = null;
|
|
|
- this.disconnected = true;
|
|
|
- }
|
|
|
-};
|
|
|
+ });
|
|
|
+}
|
|
|
|
|
|
/** The current browser. */
|
|
|
+// TODO: maybe expose util.supports
|
|
|
Peer.browser = util.browserisms;
|
|
|
|
|
|
-
|
|
|
exports.Peer = Peer;
|