|
@@ -840,7 +840,11 @@ exports.getUserMedia = getUserMedia;
|
|
/**
|
|
/**
|
|
* A peer who can initiate connections with other peers.
|
|
* A peer who can initiate connections with other peers.
|
|
*/
|
|
*/
|
|
-function Peer(options) {
|
|
|
|
|
|
+function Peer(id, options) {
|
|
|
|
+ if (id.constructor == Object) {
|
|
|
|
+ options = id;
|
|
|
|
+ id = undefined;
|
|
|
|
+ }
|
|
if (!(this instanceof Peer)) return new Peer(options);
|
|
if (!(this instanceof Peer)) return new Peer(options);
|
|
EventEmitter.call(this);
|
|
EventEmitter.call(this);
|
|
|
|
|
|
@@ -856,12 +860,14 @@ function Peer(options) {
|
|
this._server = options.host + ':' + options.port;
|
|
this._server = options.host + ':' + options.port;
|
|
|
|
|
|
// Ensure alphanumeric_-
|
|
// Ensure alphanumeric_-
|
|
- if (options.id && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(options.id))
|
|
|
|
|
|
+ if (id && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(id)) {
|
|
throw new Error('Peer ID can only contain alphanumerics, "_", and "-".');
|
|
throw new Error('Peer ID can only contain alphanumerics, "_", and "-".');
|
|
- if (options.key && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(options.key))
|
|
|
|
|
|
+ }
|
|
|
|
+ if (options.key && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(options.key)) {
|
|
throw new Error('API key can only contain alphanumerics, "_", and "-".');
|
|
throw new Error('API key can only contain alphanumerics, "_", and "-".');
|
|
-
|
|
|
|
- this._id = options.id;
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.id = id;
|
|
// Not used unless using cloud server.
|
|
// Not used unless using cloud server.
|
|
this._key = options.key;
|
|
this._key = options.key;
|
|
|
|
|
|
@@ -878,20 +884,26 @@ util.inherits(Peer, EventEmitter);
|
|
|
|
|
|
Peer.prototype._startSocket = function() {
|
|
Peer.prototype._startSocket = function() {
|
|
var self = this;
|
|
var self = this;
|
|
- this._socket = new Socket(this._server, this._id, this._key);
|
|
|
|
|
|
+ this._socket = new Socket(this._server, this.id, this._key);
|
|
this._socket.on('message', function(data) {
|
|
this._socket.on('message', function(data) {
|
|
self._handleServerJSONMessage(data);
|
|
self._handleServerJSONMessage(data);
|
|
});
|
|
});
|
|
this._socket.on('open', function() {
|
|
this._socket.on('open', function() {
|
|
- self._processQueue();
|
|
|
|
- });
|
|
|
|
- this._socket.on('unavailable', function(peer) {
|
|
|
|
- util.log('Destination peer not available.', peer);
|
|
|
|
- if (self.connections[peer])
|
|
|
|
- self.connections[peer].close();
|
|
|
|
|
|
+ if (self.id) {
|
|
|
|
+ self.emit('open', self.id);
|
|
|
|
+ self._processQueue();
|
|
|
|
+ }
|
|
});
|
|
});
|
|
this._socket.on('error', function(error) {
|
|
this._socket.on('error', function(error) {
|
|
util.log(error);
|
|
util.log(error);
|
|
|
|
+ self.emit('error', error);
|
|
|
|
+ self.emit('close');
|
|
|
|
+ });
|
|
|
|
+ this._socket.on('close', function() {
|
|
|
|
+ var msg = 'Underlying socket has closed';
|
|
|
|
+ util.log('error', msg);
|
|
|
|
+ self.emit('error', msg);
|
|
|
|
+ self.emit('close');
|
|
});
|
|
});
|
|
this._socket.start();
|
|
this._socket.start();
|
|
}
|
|
}
|
|
@@ -902,10 +914,10 @@ Peer.prototype._handleServerJSONMessage = function(message) {
|
|
var connection = this.connections[peer];
|
|
var connection = this.connections[peer];
|
|
switch (message.type) {
|
|
switch (message.type) {
|
|
case 'ID':
|
|
case 'ID':
|
|
- if (!this._id) {
|
|
|
|
|
|
+ if (!this.id) {
|
|
// If we're just now getting an ID then we may have a queue.
|
|
// If we're just now getting an ID then we may have a queue.
|
|
- this._id = message.id;
|
|
|
|
- this.emit('ready', this._id);
|
|
|
|
|
|
+ this.id = message.id;
|
|
|
|
+ this.emit('open', this.id);
|
|
this._processQueue();
|
|
this._processQueue();
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
@@ -913,36 +925,55 @@ Peer.prototype._handleServerJSONMessage = function(message) {
|
|
this.emit('error', message.msg);
|
|
this.emit('error', message.msg);
|
|
util.log(message.msg);
|
|
util.log(message.msg);
|
|
break;
|
|
break;
|
|
|
|
+ case 'ID-TAKEN':
|
|
|
|
+ this.emit('error', 'ID `'+this.id+'` is taken');
|
|
|
|
+ this.destroy();
|
|
|
|
+ this.emit('close');
|
|
|
|
+ break;
|
|
case 'OFFER':
|
|
case 'OFFER':
|
|
var options = {
|
|
var options = {
|
|
metadata: message.metadata,
|
|
metadata: message.metadata,
|
|
sdp: message.sdp,
|
|
sdp: message.sdp,
|
|
config: this._options.config,
|
|
config: this._options.config,
|
|
};
|
|
};
|
|
- var self = this;
|
|
|
|
- var connection = new DataConnection(this._id, peer, this._socket, function(err, connection) {
|
|
|
|
- if (!err) {
|
|
|
|
- self.emit('connection', connection, message.metadata);
|
|
|
|
- }
|
|
|
|
- }, options);
|
|
|
|
|
|
+ var connection = new DataConnection(this.id, peer, this._socket, options);
|
|
this._attachConnectionListeners(connection);
|
|
this._attachConnectionListeners(connection);
|
|
this.connections[peer] = connection;
|
|
this.connections[peer] = connection;
|
|
|
|
+ this.emit('connection', connection, message.metadata);
|
|
|
|
+ break;
|
|
|
|
+ case 'EXPIRE':
|
|
|
|
+ connection = this.connections[message.expired];
|
|
|
|
+ if (connection) {
|
|
|
|
+ connection.close();
|
|
|
|
+ connection.emit('Could not connect to peer ' + connection.peer);
|
|
|
|
+ }
|
|
break;
|
|
break;
|
|
case 'ANSWER':
|
|
case 'ANSWER':
|
|
- if (connection) connection.handleSDP(message);
|
|
|
|
|
|
+ if (connection) {
|
|
|
|
+ connection.handleSDP(message);
|
|
|
|
+ }
|
|
break;
|
|
break;
|
|
case 'CANDIDATE':
|
|
case 'CANDIDATE':
|
|
- if (connection) connection.handleCandidate(message);
|
|
|
|
|
|
+ if (connection) {
|
|
|
|
+ connection.handleCandidate(message);
|
|
|
|
+ }
|
|
break;
|
|
break;
|
|
case 'LEAVE':
|
|
case 'LEAVE':
|
|
- if (connection) connection.handleLeave();
|
|
|
|
|
|
+ if (connection) {
|
|
|
|
+ connection.handleLeave();
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ case 'INVALID-KEY':
|
|
|
|
+ this.emit('error', 'API KEY "' + this._key + '" is invalid');
|
|
|
|
+ this.destroy();
|
|
|
|
+ this.emit('close');
|
|
break;
|
|
break;
|
|
case 'PORT':
|
|
case 'PORT':
|
|
- if (util.browserisms === 'Firefox') {
|
|
|
|
- connection.handlePort(message);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- case 'DEFAULT':
|
|
|
|
|
|
+ //if (util.browserisms === 'Firefox') {
|
|
|
|
+ // connection.handlePort(message);
|
|
|
|
+ // break;
|
|
|
|
+ //}
|
|
|
|
+ default:
|
|
util.log('Unrecognized message type:', message.type);
|
|
util.log('Unrecognized message type:', message.type);
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
@@ -951,26 +982,30 @@ Peer.prototype._handleServerJSONMessage = function(message) {
|
|
/** Process queued calls to connect. */
|
|
/** Process queued calls to connect. */
|
|
Peer.prototype._processQueue = function() {
|
|
Peer.prototype._processQueue = function() {
|
|
while (this._queued.length > 0) {
|
|
while (this._queued.length > 0) {
|
|
- var cdata = this._queued.pop();
|
|
|
|
- this.connect.apply(this, cdata);
|
|
|
|
|
|
+ var conn = this._queued.pop();
|
|
|
|
+ conn.initialize(this.id);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
Peer.prototype._cleanup = function() {
|
|
Peer.prototype._cleanup = function() {
|
|
- for (var peer in this.connections) {
|
|
|
|
- if (this.connections.hasOwnProperty(peer)) {
|
|
|
|
- this.connections[peer].close();
|
|
|
|
- }
|
|
|
|
|
|
+ var self = this;
|
|
|
|
+ var peers = Object.keys(this.connections);
|
|
|
|
+ for (var i = 0, ii = peers.length; i < ii; i++) {
|
|
|
|
+ this.connections[peers[i]].close();
|
|
}
|
|
}
|
|
- this._socket.close();
|
|
|
|
|
|
+ util.setZeroTimeout(function(){
|
|
|
|
+ self._socket.close();
|
|
|
|
+ });
|
|
};
|
|
};
|
|
|
|
|
|
/** Listeners for DataConnection events. */
|
|
/** Listeners for DataConnection events. */
|
|
Peer.prototype._attachConnectionListeners = function(connection) {
|
|
Peer.prototype._attachConnectionListeners = function(connection) {
|
|
var self = this;
|
|
var self = this;
|
|
connection.on('close', function(peer) {
|
|
connection.on('close', function(peer) {
|
|
- if (self.connections[peer]) delete self.connections[peer];
|
|
|
|
|
|
+ if (self.connections[peer]) {
|
|
|
|
+ delete self.connections[peer];
|
|
|
|
+ }
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
|
|
@@ -980,22 +1015,20 @@ Peer.prototype._attachConnectionListeners = function(connection) {
|
|
* is waiting for an ID. */
|
|
* is waiting for an ID. */
|
|
// TODO: pause XHR streaming when not in use and start again when this is
|
|
// TODO: pause XHR streaming when not in use and start again when this is
|
|
// called.
|
|
// called.
|
|
-Peer.prototype.connect = function(peer, metadata, cb) {
|
|
|
|
- if (typeof metadata === 'function' && !cb) cb = metadata; metadata = false;
|
|
|
|
-
|
|
|
|
- if (!this._id) {
|
|
|
|
- this._queued.push(Array.prototype.slice.apply(arguments));
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- var options = {
|
|
|
|
|
|
+Peer.prototype.connect = function(peer, metadata, options) {
|
|
|
|
+ options = util.extend({
|
|
metadata: metadata,
|
|
metadata: metadata,
|
|
config: this._options.config,
|
|
config: this._options.config,
|
|
- };
|
|
|
|
- var connection = new DataConnection(this._id, peer, this._socket, cb, options);
|
|
|
|
|
|
+ }, options);
|
|
|
|
+
|
|
|
|
+ var connection = new DataConnection(this.id, peer, this._socket, options);
|
|
this._attachConnectionListeners(connection);
|
|
this._attachConnectionListeners(connection);
|
|
|
|
|
|
this.connections[peer] = connection;
|
|
this.connections[peer] = connection;
|
|
|
|
+ if (!this.id) {
|
|
|
|
+ this._queued.push(connection);
|
|
|
|
+ }
|
|
|
|
+ return connection;
|
|
};
|
|
};
|
|
|
|
|
|
Peer.prototype.destroy = function() {
|
|
Peer.prototype.destroy = function() {
|
|
@@ -1007,7 +1040,7 @@ exports.Peer = Peer;
|
|
/**
|
|
/**
|
|
* A DataChannel PeerConnection between two Peers.
|
|
* A DataChannel PeerConnection between two Peers.
|
|
*/
|
|
*/
|
|
-function DataConnection(id, peer, socket, cb, options) {
|
|
|
|
|
|
+function DataConnection(id, peer, socket, options) {
|
|
if (!(this instanceof DataConnection)) return new DataConnection(options);
|
|
if (!(this instanceof DataConnection)) return new DataConnection(options);
|
|
EventEmitter.call(this);
|
|
EventEmitter.call(this);
|
|
|
|
|
|
@@ -1017,18 +1050,33 @@ function DataConnection(id, peer, socket, cb, options) {
|
|
}, options);
|
|
}, options);
|
|
this._options = options;
|
|
this._options = options;
|
|
|
|
|
|
- this._id = id;
|
|
|
|
- this._peer = peer;
|
|
|
|
- this._originator = (options.sdp === undefined);
|
|
|
|
- this._cb = cb;
|
|
|
|
- this._metadata = options.metadata;
|
|
|
|
|
|
+ // Connection is not open yet
|
|
|
|
+ this.open = false;
|
|
|
|
+
|
|
|
|
+ this.id = id;
|
|
|
|
+ this.peer = peer;
|
|
|
|
+ this.metadata = options.metadata;
|
|
|
|
|
|
|
|
+ this._originator = (options.sdp === undefined);
|
|
this._socket = socket;
|
|
this._socket = socket;
|
|
|
|
+ this._sdp = options.sdp;
|
|
|
|
|
|
|
|
+ // TODO: consider no-oping this method:
|
|
|
|
+ if (!!this.id) {
|
|
|
|
+ this.initialize();
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+util.inherits(DataConnection, EventEmitter);
|
|
|
|
+
|
|
|
|
+DataConnection.prototype.initialize = function(id) {
|
|
|
|
+ if (!!id) {
|
|
|
|
+ this.id = id;
|
|
|
|
+ }
|
|
// Firefoxism: connectDataConnection ports.
|
|
// Firefoxism: connectDataConnection ports.
|
|
- if (util.browserisms === 'Firefox') {
|
|
|
|
|
|
+ /*if (util.browserisms === 'Firefox') {
|
|
this._firefoxPortSetup();
|
|
this._firefoxPortSetup();
|
|
- }
|
|
|
|
|
|
+ }*/
|
|
|
|
|
|
// Set up PeerConnection.
|
|
// Set up PeerConnection.
|
|
this._startPeerConnection();
|
|
this._startPeerConnection();
|
|
@@ -1038,7 +1086,7 @@ function DataConnection(id, peer, socket, cb, options) {
|
|
|
|
|
|
// Listen for negotiation needed
|
|
// Listen for negotiation needed
|
|
// ** Chrome only.
|
|
// ** Chrome only.
|
|
- if (util.browserisms === 'Webkit') {
|
|
|
|
|
|
+ if (util.browserisms !== 'Firefox' && !!this.id) {
|
|
this._setupOffer();
|
|
this._setupOffer();
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1046,19 +1094,18 @@ function DataConnection(id, peer, socket, cb, options) {
|
|
this._setupDataChannel();
|
|
this._setupDataChannel();
|
|
|
|
|
|
var self = this;
|
|
var self = this;
|
|
- if (options.sdp) {
|
|
|
|
- this.handleSDP({ type: 'OFFER', sdp: options.sdp });
|
|
|
|
- if (util.browserisms !== 'Firefox') {
|
|
|
|
- this._makeAnswer();
|
|
|
|
- }
|
|
|
|
|
|
+ if (this._sdp) {
|
|
|
|
+ this.handleSDP({ type: 'OFFER', sdp: this._sdp });
|
|
}
|
|
}
|
|
|
|
|
|
- if (util.browserisms === 'Firefox') {
|
|
|
|
|
|
+ // Makes offer if Firefox
|
|
|
|
+ /*if (util.browserisms === 'Firefox') {
|
|
this._firefoxAdditional();
|
|
this._firefoxAdditional();
|
|
- }
|
|
|
|
-};
|
|
|
|
|
|
+ }*/
|
|
|
|
|
|
-util.inherits(DataConnection, EventEmitter);
|
|
|
|
|
|
+ // No-op this.
|
|
|
|
+ this.initialize = function() {};
|
|
|
|
+}
|
|
|
|
|
|
DataConnection.prototype._setupOffer = function() {
|
|
DataConnection.prototype._setupOffer = function() {
|
|
var self = this;
|
|
var self = this;
|
|
@@ -1073,7 +1120,7 @@ DataConnection.prototype._setupDataChannel = function() {
|
|
var self = this;
|
|
var self = this;
|
|
if (this._originator) {
|
|
if (this._originator) {
|
|
util.log('Creating data channel');
|
|
util.log('Creating data channel');
|
|
- this._dc = this._pc.createDataChannel(this._peer, { reliable: this._options.reliable });
|
|
|
|
|
|
+ this._dc = this._pc.createDataChannel(this.peer, { reliable: this._options.reliable });
|
|
this._configureDataChannel();
|
|
this._configureDataChannel();
|
|
} else {
|
|
} else {
|
|
util.log('Listening for data channel');
|
|
util.log('Listening for data channel');
|
|
@@ -1103,49 +1150,15 @@ DataConnection.prototype._setupIce = function() {
|
|
self._socket.send({
|
|
self._socket.send({
|
|
type: 'CANDIDATE',
|
|
type: 'CANDIDATE',
|
|
candidate: evt.candidate,
|
|
candidate: evt.candidate,
|
|
- dst: self._peer,
|
|
|
|
- src: self._id
|
|
|
|
|
|
+ dst: self.peer,
|
|
|
|
+ src: self.id
|
|
});
|
|
});
|
|
}
|
|
}
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
|
|
-// Awaiting update in Firefox spec ***
|
|
|
|
-/** Sets up DataChannel handlers.
|
|
|
|
-DataConnection.prototype._setupDataChannel = function() {
|
|
|
|
- var self = this;
|
|
|
|
- if (this._originator) {
|
|
|
|
-
|
|
|
|
- if (util.browserisms === 'Webkit') {
|
|
|
|
-
|
|
|
|
- // TODO: figure out the right thing to do with this.
|
|
|
|
- this._pc.onstatechange = function() {
|
|
|
|
- util.log('State Change: ', self._pc.readyState);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- } else {
|
|
|
|
- this._pc.onconnection = function() {
|
|
|
|
- util.log('ORIGINATOR: onconnection triggered');
|
|
|
|
-
|
|
|
|
- self._startDataChannel();
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
-
|
|
|
|
-
|
|
|
|
- this._pc.onconnection = function() {
|
|
|
|
- util.log('SINK: onconnection triggered');
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
|
|
|
|
- this._pc.onclosedconnection = function() {
|
|
|
|
- // Remove socket handlers perhaps.
|
|
|
|
- self.emit('close', self._peer);
|
|
|
|
- };
|
|
|
|
-};
|
|
|
|
-*/
|
|
|
|
-
|
|
|
|
-DataConnection.prototype._firefoxPortSetup = function() {
|
|
|
|
|
|
+/*DataConnection.prototype._firefoxPortSetup = function() {
|
|
if (!DataConnection.usedPorts) {
|
|
if (!DataConnection.usedPorts) {
|
|
DataConnection.usedPorts = [];
|
|
DataConnection.usedPorts = [];
|
|
}
|
|
}
|
|
@@ -1160,17 +1173,18 @@ DataConnection.prototype._firefoxPortSetup = function() {
|
|
}
|
|
}
|
|
DataConnection.usedPorts.push(this.remotePort);
|
|
DataConnection.usedPorts.push(this.remotePort);
|
|
DataConnection.usedPorts.push(this.localPort);
|
|
DataConnection.usedPorts.push(this.localPort);
|
|
-}
|
|
|
|
|
|
+}*/
|
|
|
|
|
|
DataConnection.prototype._configureDataChannel = function() {
|
|
DataConnection.prototype._configureDataChannel = function() {
|
|
var self = this;
|
|
var self = this;
|
|
|
|
|
|
- if (util.browserisms === 'Firefox') {
|
|
|
|
- this._dc.binaryType = 'blob';
|
|
|
|
|
|
+ if (util.browserisms !== 'Webkit') {
|
|
|
|
+ this._dc.binaryType = 'arraybuffer';
|
|
}
|
|
}
|
|
this._dc.onopen = function() {
|
|
this._dc.onopen = function() {
|
|
util.log('Data channel connection success');
|
|
util.log('Data channel connection success');
|
|
- self._cb(null, self);
|
|
|
|
|
|
+ self.open = true;
|
|
|
|
+ self.emit('open');
|
|
};
|
|
};
|
|
this._dc.onmessage = function(e) {
|
|
this._dc.onmessage = function(e) {
|
|
self._handleDataMessage(e);
|
|
self._handleDataMessage(e);
|
|
@@ -1179,17 +1193,15 @@ DataConnection.prototype._configureDataChannel = function() {
|
|
|
|
|
|
|
|
|
|
/** Decide whether to handle Firefoxisms. */
|
|
/** Decide whether to handle Firefoxisms. */
|
|
-DataConnection.prototype._firefoxAdditional = function() {
|
|
|
|
|
|
+/*DataConnection.prototype._firefoxAdditional = function() {
|
|
var self = this;
|
|
var self = this;
|
|
getUserMedia({ audio: true, fake: true }, function(s) {
|
|
getUserMedia({ audio: true, fake: true }, function(s) {
|
|
self._pc.addStream(s);
|
|
self._pc.addStream(s);
|
|
if (self._originator) {
|
|
if (self._originator) {
|
|
self._makeOffer();
|
|
self._makeOffer();
|
|
- } else {
|
|
|
|
- self._makeAnswer();
|
|
|
|
}
|
|
}
|
|
}, function(err) { util.log('Could not getUserMedia'); });
|
|
}, function(err) { util.log('Could not getUserMedia'); });
|
|
-}
|
|
|
|
|
|
+};*/
|
|
|
|
|
|
DataConnection.prototype._makeOffer = function() {
|
|
DataConnection.prototype._makeOffer = function() {
|
|
var self = this;
|
|
var self = this;
|
|
@@ -1197,16 +1209,15 @@ DataConnection.prototype._makeOffer = function() {
|
|
util.log('Created offer');
|
|
util.log('Created offer');
|
|
self._pc.setLocalDescription(offer, function() {
|
|
self._pc.setLocalDescription(offer, function() {
|
|
util.log('Set localDescription to offer');
|
|
util.log('Set localDescription to offer');
|
|
- //self._peerReady = false;
|
|
|
|
self._socket.send({
|
|
self._socket.send({
|
|
type: 'OFFER',
|
|
type: 'OFFER',
|
|
sdp: offer,
|
|
sdp: offer,
|
|
- dst: self._peer,
|
|
|
|
- src: self._id,
|
|
|
|
|
|
+ dst: self.peer,
|
|
|
|
+ src: self.id,
|
|
metadata: self.metadata
|
|
metadata: self.metadata
|
|
});
|
|
});
|
|
}, function(err) {
|
|
}, function(err) {
|
|
- self._cb('Failed to setLocalDescription');
|
|
|
|
|
|
+ self.emit('error', 'Failed to setLocalDescription');
|
|
util.log('Failed to setLocalDescription, ', err);
|
|
util.log('Failed to setLocalDescription, ', err);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
@@ -1221,35 +1232,33 @@ DataConnection.prototype._makeAnswer = function() {
|
|
util.log('Set localDescription to answer');
|
|
util.log('Set localDescription to answer');
|
|
self._socket.send({
|
|
self._socket.send({
|
|
type: 'ANSWER',
|
|
type: 'ANSWER',
|
|
- src: self._id,
|
|
|
|
|
|
+ src: self.id,
|
|
sdp: answer,
|
|
sdp: answer,
|
|
- dst: self._peer
|
|
|
|
|
|
+ dst: self.peer
|
|
});
|
|
});
|
|
}, function(err) {
|
|
}, function(err) {
|
|
- self._cb('Failed to setLocalDescription');
|
|
|
|
|
|
+ self.emit('error', 'Failed to setLocalDescription');
|
|
util.log('Failed to setLocalDescription, ', err)
|
|
util.log('Failed to setLocalDescription, ', err)
|
|
});
|
|
});
|
|
}, function(err) {
|
|
}, function(err) {
|
|
- self._cb('Failed to create answer');
|
|
|
|
|
|
+ self.emit('error', 'Failed to create answer');
|
|
util.log('Failed to create answer, ', err)
|
|
util.log('Failed to create answer, ', err)
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
DataConnection.prototype._cleanup = function() {
|
|
DataConnection.prototype._cleanup = function() {
|
|
- if (!!this._pc && this._pc.readyState != 'closed') {
|
|
|
|
- this._pc.close();
|
|
|
|
- this._pc = null;
|
|
|
|
- }
|
|
|
|
if (!!this._dc && this._dc.readyState != 'closed') {
|
|
if (!!this._dc && this._dc.readyState != 'closed') {
|
|
this._dc.close();
|
|
this._dc.close();
|
|
this._dc = null;
|
|
this._dc = null;
|
|
}
|
|
}
|
|
- this.emit('close', this._peer);
|
|
|
|
|
|
+ if (!!this._pc && this._pc.readyState != 'closed') {
|
|
|
|
+ this._pc.close();
|
|
|
|
+ this._pc = null;
|
|
|
|
+ }
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
// Handles a DataChannel message.
|
|
// Handles a DataChannel message.
|
|
DataConnection.prototype._handleDataMessage = function(e) {
|
|
DataConnection.prototype._handleDataMessage = function(e) {
|
|
var self = this;
|
|
var self = this;
|
|
@@ -1277,11 +1286,15 @@ DataConnection.prototype._handleDataMessage = function(e) {
|
|
DataConnection.prototype.close = function() {
|
|
DataConnection.prototype.close = function() {
|
|
this._cleanup();
|
|
this._cleanup();
|
|
var self = this;
|
|
var self = this;
|
|
- this._socket.send({
|
|
|
|
- type: 'LEAVE',
|
|
|
|
- dst: self._peer,
|
|
|
|
- src: self._id,
|
|
|
|
- });
|
|
|
|
|
|
+ if (this.open) {
|
|
|
|
+ this._socket.send({
|
|
|
|
+ type: 'LEAVE',
|
|
|
|
+ dst: self.peer,
|
|
|
|
+ src: self.id,
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ this.open = false;
|
|
|
|
+ this.emit('close', this.peer);
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -1298,11 +1311,6 @@ DataConnection.prototype.send = function(data) {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
-DataConnection.prototype.getMetadata = function() {
|
|
|
|
- return this._metadata;
|
|
|
|
-};
|
|
|
|
-
|
|
|
|
-
|
|
|
|
DataConnection.prototype.handleSDP = function(message) {
|
|
DataConnection.prototype.handleSDP = function(message) {
|
|
var sdp = message.sdp;
|
|
var sdp = message.sdp;
|
|
if (util.browserisms != 'Firefox') {
|
|
if (util.browserisms != 'Firefox') {
|
|
@@ -1316,14 +1324,16 @@ DataConnection.prototype.handleSDP = function(message) {
|
|
self._pc.connectDataConnection(self.localPort, self.remotePort);
|
|
self._pc.connectDataConnection(self.localPort, self.remotePort);
|
|
self._socket.send({
|
|
self._socket.send({
|
|
type: 'PORT',
|
|
type: 'PORT',
|
|
- dst: self._peer,
|
|
|
|
- src: self._id,
|
|
|
|
|
|
+ dst: self.peer,
|
|
|
|
+ src: self.id,
|
|
remote: self.localPort,
|
|
remote: self.localPort,
|
|
local: self.remotePort
|
|
local: self.remotePort
|
|
});
|
|
});
|
|
|
|
+ } else if (message.type === 'OFFER') {
|
|
|
|
+ self._makeAnswer();
|
|
}
|
|
}
|
|
}, function(err) {
|
|
}, function(err) {
|
|
- this._cb('Failed to setRemoteDescription');
|
|
|
|
|
|
+ self.emit('error', 'Failed to setRemoteDescription');
|
|
util.log('Failed to setRemoteDescription, ', err);
|
|
util.log('Failed to setRemoteDescription, ', err);
|
|
});
|
|
});
|
|
};
|
|
};
|
|
@@ -1337,10 +1347,11 @@ DataConnection.prototype.handleCandidate = function(message) {
|
|
|
|
|
|
|
|
|
|
DataConnection.prototype.handleLeave = function() {
|
|
DataConnection.prototype.handleLeave = function() {
|
|
- util.log('Peer ' + this._peer + ' disconnected');
|
|
|
|
- this._cleanup();
|
|
|
|
|
|
+ util.log('Peer ' + this.peer + ' disconnected');
|
|
|
|
+ this.close();
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+/*
|
|
DataConnection.prototype.handlePort = function(message) {
|
|
DataConnection.prototype.handlePort = function(message) {
|
|
if (!DataConnection.usedPorts) {
|
|
if (!DataConnection.usedPorts) {
|
|
DataConnection.usedPorts = [];
|
|
DataConnection.usedPorts = [];
|
|
@@ -1349,7 +1360,7 @@ DataConnection.prototype.handlePort = function(message) {
|
|
DataConnection.usedPorts.push(message.remote);
|
|
DataConnection.usedPorts.push(message.remote);
|
|
this._pc.connectDataConnection(message.local, message.remote);
|
|
this._pc.connectDataConnection(message.local, message.remote);
|
|
};
|
|
};
|
|
-
|
|
|
|
|
|
+*/
|
|
|
|
|
|
/**
|
|
/**
|
|
* An abstraction on top of WebSockets and XHR streaming to provide fastest
|
|
* An abstraction on top of WebSockets and XHR streaming to provide fastest
|
|
@@ -1374,10 +1385,12 @@ Socket.prototype._checkIn = function() {
|
|
if (!this._id) {
|
|
if (!this._id) {
|
|
try {
|
|
try {
|
|
var http = new XMLHttpRequest();
|
|
var http = new XMLHttpRequest();
|
|
- var url = this._httpUrl + '/id';
|
|
|
|
|
|
+ var url = this._httpUrl;
|
|
// Set API key if necessary.
|
|
// Set API key if necessary.
|
|
- if (!!this._key)
|
|
|
|
|
|
+ if (!!this._key) {
|
|
url += '/' + this._key;
|
|
url += '/' + this._key;
|
|
|
|
+ }
|
|
|
|
+ url += '/id';
|
|
|
|
|
|
// If there's no ID we need to wait for one before trying to init socket.
|
|
// If there's no ID we need to wait for one before trying to init socket.
|
|
http.open('get', url, true);
|
|
http.open('get', url, true);
|
|
@@ -1410,14 +1423,16 @@ Socket.prototype._checkIn = function() {
|
|
|
|
|
|
/** Start up websocket communications. */
|
|
/** Start up websocket communications. */
|
|
Socket.prototype._startWebSocket = function() {
|
|
Socket.prototype._startWebSocket = function() {
|
|
- if (!!this._socket)
|
|
|
|
|
|
+ if (!!this._socket) {
|
|
return;
|
|
return;
|
|
|
|
+ }
|
|
|
|
|
|
var wsurl = 'ws://' + this._server + '/ws';
|
|
var wsurl = 'ws://' + this._server + '/ws';
|
|
if (!!this._id) {
|
|
if (!!this._id) {
|
|
wsurl += '?id=' + this._id;
|
|
wsurl += '?id=' + this._id;
|
|
- if (!!this._key)
|
|
|
|
|
|
+ if (!!this._key) {
|
|
wsurl += '&key=' + this._key;
|
|
wsurl += '&key=' + this._key;
|
|
|
|
+ }
|
|
} else if (!!this._key) {
|
|
} else if (!!this._key) {
|
|
wsurl += '?key=' + this._key;
|
|
wsurl += '?key=' + this._key;
|
|
}
|
|
}
|
|
@@ -1425,10 +1440,16 @@ Socket.prototype._startWebSocket = function() {
|
|
|
|
|
|
var self = this;
|
|
var self = this;
|
|
this._socket.onmessage = function(event) {
|
|
this._socket.onmessage = function(event) {
|
|
|
|
+ var data;
|
|
try {
|
|
try {
|
|
- self.emit('message', JSON.parse(event.data));
|
|
|
|
|
|
+ data = JSON.parse(event.data);
|
|
} catch(e) {
|
|
} catch(e) {
|
|
- util.log('Invalid server message');
|
|
|
|
|
|
+ data = event.data;
|
|
|
|
+ }
|
|
|
|
+ if (data.constructor == Object) {
|
|
|
|
+ self.emit('message', data);
|
|
|
|
+ } else {
|
|
|
|
+ util.log('Invalid server message', event.data);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
@@ -1436,8 +1457,9 @@ Socket.prototype._startWebSocket = function() {
|
|
// socket is open.
|
|
// socket is open.
|
|
this._socket.onopen = function() {
|
|
this._socket.onopen = function() {
|
|
util.log('Socket open');
|
|
util.log('Socket open');
|
|
- if (self._id)
|
|
|
|
|
|
+ if (self._id) {
|
|
self.emit('open');
|
|
self.emit('open');
|
|
|
|
+ }
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
|
|
@@ -1448,11 +1470,12 @@ Socket.prototype._startXhrStream = function() {
|
|
var self = this;
|
|
var self = this;
|
|
|
|
|
|
var http = new XMLHttpRequest();
|
|
var http = new XMLHttpRequest();
|
|
- var url = this._httpUrl + '/id';
|
|
|
|
|
|
+ var url = this._httpUrl;
|
|
// Set API key if necessary.
|
|
// Set API key if necessary.
|
|
- if (!!this._key)
|
|
|
|
|
|
+ if (!!this._key) {
|
|
url += '/' + this._key;
|
|
url += '/' + this._key;
|
|
-
|
|
|
|
|
|
+ }
|
|
|
|
+ url += '/id';
|
|
http.open('post', url, true);
|
|
http.open('post', url, true);
|
|
http.setRequestHeader('Content-Type', 'application/json');
|
|
http.setRequestHeader('Content-Type', 'application/json');
|
|
http.onreadystatechange = function() {
|
|
http.onreadystatechange = function() {
|
|
@@ -1474,19 +1497,21 @@ Socket.prototype._handleStream = function(http, pad) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
- if (this._index === undefined)
|
|
|
|
|
|
+ if (this._index === undefined) {
|
|
this._index = pad ? 2 : 1;
|
|
this._index = pad ? 2 : 1;
|
|
-
|
|
|
|
- if (http.responseText === null)
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (http.responseText === null) {
|
|
return;
|
|
return;
|
|
-
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
var message = http.responseText.split('\n')[this._index];
|
|
var message = http.responseText.split('\n')[this._index];
|
|
if (!!message && http.readyState == 3) {
|
|
if (!!message && http.readyState == 3) {
|
|
this._index += 1;
|
|
this._index += 1;
|
|
try {
|
|
try {
|
|
this._handleHTTPErrors(JSON.parse(message));
|
|
this._handleHTTPErrors(JSON.parse(message));
|
|
} catch(e) {
|
|
} catch(e) {
|
|
- util.log('Invalid server message');
|
|
|
|
|
|
+ util.log('Invalid server message', message);
|
|
}
|
|
}
|
|
} else if (http.readyState == 4) {
|
|
} else if (http.readyState == 4) {
|
|
this._index = 1;
|
|
this._index = 1;
|
|
@@ -1499,18 +1524,20 @@ Socket.prototype._handleHTTPErrors = function(message) {
|
|
// XHR stream closed by timeout.
|
|
// XHR stream closed by timeout.
|
|
case 'HTTP-END':
|
|
case 'HTTP-END':
|
|
util.log('XHR stream timed out.');
|
|
util.log('XHR stream timed out.');
|
|
- if (!!this._socket && this._socket.readyState != 1)
|
|
|
|
|
|
+ if (!!this._socket && this._socket.readyState != 1) {
|
|
this._startXhrStream();
|
|
this._startXhrStream();
|
|
|
|
+ }
|
|
break;
|
|
break;
|
|
// XHR stream closed by socket connect.
|
|
// XHR stream closed by socket connect.
|
|
case 'HTTP-SOCKET':
|
|
case 'HTTP-SOCKET':
|
|
- util.log('XHR stream closed, WebSocket connected.');
|
|
|
|
- break;
|
|
|
|
|
|
+ util.log('XHR stream closed, WebSocket connected.');
|
|
|
|
+ break;
|
|
case 'HTTP-ERROR':
|
|
case 'HTTP-ERROR':
|
|
- this.emit('error', 'Something went wrong.');
|
|
|
|
- break;
|
|
|
|
|
|
+ // this.emit('error', 'Something went wrong.');
|
|
|
|
+ util.log('XHR ended in error or the websocket connected first.');
|
|
|
|
+ break;
|
|
default:
|
|
default:
|
|
- this.emit('message', message);
|
|
|
|
|
|
+ this.emit('message', message);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
@@ -1520,34 +1547,32 @@ Socket.prototype._handleHTTPErrors = function(message) {
|
|
Socket.prototype.send = function(data) {
|
|
Socket.prototype.send = function(data) {
|
|
var type = data.type;
|
|
var type = data.type;
|
|
message = JSON.stringify(data);
|
|
message = JSON.stringify(data);
|
|
- if (!type)
|
|
|
|
|
|
+ if (!type) {
|
|
this.emit('error', 'Invalid message');
|
|
this.emit('error', 'Invalid message');
|
|
|
|
+ }
|
|
|
|
|
|
if (!!this._socket && this._socket.readyState == 1) {
|
|
if (!!this._socket && this._socket.readyState == 1) {
|
|
this._socket.send(message);
|
|
this._socket.send(message);
|
|
} else {
|
|
} else {
|
|
var self = this;
|
|
var self = this;
|
|
var http = new XMLHttpRequest();
|
|
var http = new XMLHttpRequest();
|
|
- var url = this._httpUrl + '/' + type.toLowerCase();
|
|
|
|
|
|
+ var url = this._httpUrl;
|
|
// Set API key if necessary.
|
|
// Set API key if necessary.
|
|
- if (!!this._key)
|
|
|
|
|
|
+ if (!!this._key) {
|
|
url += '/' + this._key;
|
|
url += '/' + this._key;
|
|
-
|
|
|
|
|
|
+ }
|
|
|
|
+ url += '/' + type.toLowerCase();
|
|
|
|
+
|
|
http.open('post', url, true);
|
|
http.open('post', url, true);
|
|
http.setRequestHeader('Content-Type', 'application/json');
|
|
http.setRequestHeader('Content-Type', 'application/json');
|
|
- http.onload = function() {
|
|
|
|
- // This happens if destination peer is not available...
|
|
|
|
- if (http.responseText != 'OK') {
|
|
|
|
- self.emit('unavailable', data.dst)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
http.send(message);
|
|
http.send(message);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
Socket.prototype.close = function() {
|
|
Socket.prototype.close = function() {
|
|
- if (!!this._socket && this._socket.readyState == 1)
|
|
|
|
|
|
+ if (!!this._socket && this._socket.readyState == 1) {
|
|
this._socket.close();
|
|
this._socket.close();
|
|
|
|
+ }
|
|
};
|
|
};
|
|
|
|
|
|
Socket.prototype.start = function() {
|
|
Socket.prototype.start = function() {
|