|
@@ -5,86 +5,84 @@ function Peer(id, options) {
|
|
|
if (!(this instanceof Peer)) return new Peer(id, options);
|
|
|
EventEmitter.call(this);
|
|
|
|
|
|
+ // Deal with overloading
|
|
|
if (id && id.constructor == Object) {
|
|
|
options = id;
|
|
|
id = undefined;
|
|
|
+ } else {
|
|
|
+ // Ensure id is a string
|
|
|
+ id = id.toString();
|
|
|
}
|
|
|
-
|
|
|
+ //
|
|
|
+
|
|
|
+ // Configurize options
|
|
|
options = util.extend({
|
|
|
- debug: false,
|
|
|
+ debug: 0, // 1: Errors, 2: Warnings, 3: All logs
|
|
|
host: '0.peerjs.com',
|
|
|
port: 9000,
|
|
|
key: 'peerjs',
|
|
|
config: { 'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }] }
|
|
|
}, options);
|
|
|
- this._options = options;
|
|
|
- util.debug = options.debug;
|
|
|
-
|
|
|
- // First check if browser can use PeerConnection/DataChannels.
|
|
|
- // TODO: when media is supported, lower browser version limit and move DC
|
|
|
- // check to where `connect` is called.
|
|
|
- var self = this;
|
|
|
- if (!util.isBrowserCompatible()) {
|
|
|
- util.setZeroTimeout(function() {
|
|
|
- self._abort('browser-incompatible', 'The current browser does not support WebRTC DataChannels');
|
|
|
- });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
+ this.options = options;
|
|
|
// Detect relative URL host.
|
|
|
if (options.host === '/') {
|
|
|
options.host = window.location.hostname;
|
|
|
}
|
|
|
-
|
|
|
- // Ensure alphanumeric_-
|
|
|
- if (id && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(id)) {
|
|
|
- util.setZeroTimeout(function() {
|
|
|
- self._abort('invalid-id', 'ID "' + id + '" is invalid');
|
|
|
- });
|
|
|
+ // Set whether we use SSL to same as current host
|
|
|
+ if (options.secure === undefined) {
|
|
|
+ options.secure = util.isSecure();
|
|
|
+ }
|
|
|
+ // TODO: document this feature
|
|
|
+ // Set a custom log function if present
|
|
|
+ if (options.logFunction) {
|
|
|
+ util.setLogFunction(options.logFunction):
|
|
|
+ }
|
|
|
+ util.setLogLevel(options.debug);
|
|
|
+ //
|
|
|
+
|
|
|
+ // Sanity checks
|
|
|
+ // Ensure WebRTC supported
|
|
|
+ if (!util.supports.audioVideo && !util.supports.data ) {
|
|
|
+ this._delayedAbort('browser-incompatible', 'The current browser does not support WebRTC');
|
|
|
return;
|
|
|
}
|
|
|
- if (options.key && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(options.key)) {
|
|
|
- util.setZeroTimeout(function() {
|
|
|
- self._abort('invalid-key', 'API KEY "' + options.key + '" is invalid');
|
|
|
- });
|
|
|
+ // Ensure alphanumeric id
|
|
|
+ if (!util.validateId(id)) {
|
|
|
+ this._delayedAbort('invalid-id', 'ID "' + id + '" is invalid');
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- this._secure = util.isSecure();
|
|
|
- // Errors for now because no support for SSL on cloud server.
|
|
|
- if (this._secure && options.host === '0.peerjs.com') {
|
|
|
- util.setZeroTimeout(function() {
|
|
|
- self._abort('ssl-unavailable',
|
|
|
- 'The cloud server currently does not support HTTPS. Please run your own PeerServer to use HTTPS.');
|
|
|
- });
|
|
|
+ // Ensure valid key
|
|
|
+ if (!util.validateKey(options.key)) {
|
|
|
+ this._delayedAbort('invalid-key', 'API KEY "' + options.key + '" is invalid');
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+ // Ensure not using unsecure cloud server on SSL page
|
|
|
+ if (options.secure && options.host === '0.peerjs.com') {
|
|
|
+ this._delayedAbort('ssl-unavailable',
|
|
|
+ 'The cloud server currently does not support HTTPS. Please run your own PeerServer to use HTTPS.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ //
|
|
|
+
|
|
|
// States.
|
|
|
this.destroyed = false; // Connections have been killed
|
|
|
this.disconnected = false; // Connection to PeerServer killed but P2P connections still active
|
|
|
-
|
|
|
- // DataConnections for this peer.
|
|
|
- this.connections = {};
|
|
|
+ //
|
|
|
|
|
|
- // MediaConnections for this peer
|
|
|
- this.calls = {}
|
|
|
+ // References
|
|
|
+ this.connections = {}; // DataConnections for this peer.
|
|
|
+ this.calls = {}; // MediaConnections for this peer
|
|
|
+ //
|
|
|
|
|
|
- // Connection managers.
|
|
|
- // peer => {
|
|
|
- // nextId: unique number to use for next manager created
|
|
|
- // dataManager: the last created data manager, for multiplexing data connections
|
|
|
- // managers: { id: manager} }
|
|
|
- // }
|
|
|
- this._managers = {};
|
|
|
+ // Internal references
|
|
|
+ this._managers = {}; // Managers for peer connections
|
|
|
+ this._queued = []; // Queued connections to make.
|
|
|
+ //
|
|
|
|
|
|
- // Queued connections to make.
|
|
|
- this._queued = [];
|
|
|
|
|
|
- // Init immediately if ID is given, otherwise ask server for ID
|
|
|
- this.id = null;
|
|
|
+ // Start the connections
|
|
|
if (id) {
|
|
|
- this._initialize(id.toString());
|
|
|
+ this._initialize(id);
|
|
|
} else {
|
|
|
this._retrieveId();
|
|
|
}
|
|
@@ -94,57 +92,53 @@ util.inherits(Peer, EventEmitter);
|
|
|
|
|
|
Peer.prototype._retrieveId = function(cb) {
|
|
|
var self = this;
|
|
|
- try {
|
|
|
- var http = new XMLHttpRequest();
|
|
|
- var protocol = this._secure ? 'https://' : 'http://';
|
|
|
- var url = protocol + this._options.host + ':' + this._options.port + '/' + this._options.key + '/id';
|
|
|
- var queryString = '?ts=' + new Date().getTime() + '' + Math.random();
|
|
|
- url += queryString;
|
|
|
- // If there's no ID we need to wait for one before trying to init socket.
|
|
|
- http.open('get', url, true);
|
|
|
- http.onreadystatechange = function() {
|
|
|
- if (http.readyState === 4) {
|
|
|
- if (http.status !== 200) {
|
|
|
- throw 'Retrieve ID response not 200';
|
|
|
- return;
|
|
|
- }
|
|
|
- self.id = http.responseText;
|
|
|
- self._initialize(self.id);
|
|
|
- }
|
|
|
- };
|
|
|
- http.send(null);
|
|
|
- } catch(e) {
|
|
|
- this._abort('server-error', 'Could not get an ID from the server');
|
|
|
+ var http = new XMLHttpRequest();
|
|
|
+ var protocol = this.options.secure ? 'https://' : 'http://';
|
|
|
+ var url = protocol + this.options.host + ':' + this.options.port + '/' + this.options.key + '/id';
|
|
|
+ var queryString = '?ts=' + new Date().getTime() + '' + Math.random();
|
|
|
+ url += queryString;
|
|
|
+ // If there's no ID we need to wait for one before trying to init socket.
|
|
|
+ http.open('get', url, true);
|
|
|
+ http.onerror = function(e) {
|
|
|
+ util.error('Error retrieving ID', e);
|
|
|
+ self._abort('server-error', 'Could not get an ID from the server');
|
|
|
}
|
|
|
+ http.onreadystatechange = function() {
|
|
|
+ if (http.readyState !== 4) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (http.status !== 200) {
|
|
|
+ http.onerror();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ self._initialize(http.responseText);
|
|
|
+ };
|
|
|
+ http.send(null);
|
|
|
};
|
|
|
|
|
|
|
|
|
Peer.prototype._initialize = function(id) {
|
|
|
var self = this;
|
|
|
this.id = id;
|
|
|
- this._socket = new Socket(this._options.host, this._options.port, this._options.key, this.id);
|
|
|
- this._socket.on('message', function(data) {
|
|
|
- self._handleServerJSONMessage(data);
|
|
|
+ this.socket = new Socket(this.secure, this.options.host, this.options.port, this.options.key, this.id);
|
|
|
+ this.socket.on('message', function(data) {
|
|
|
+ self._dispatchMessage(data);
|
|
|
});
|
|
|
- this._socket.on('error', function(error) {
|
|
|
- util.log(error);
|
|
|
+ this.socket.on('error', function(error) {
|
|
|
self._abort('socket-error', error);
|
|
|
});
|
|
|
- this._socket.on('close', function() {
|
|
|
- var msg = 'Underlying socket has closed';
|
|
|
- util.log('error', msg);
|
|
|
- self._abort('socket-closed', msg);
|
|
|
+ this.socket.on('close', function() {
|
|
|
+ // TODO: What if we disconnected on purpose?
|
|
|
+ self._abort('socket-closed', 'Underlying socket has closed');
|
|
|
});
|
|
|
- this._socket.start();
|
|
|
+ this.socket.start();
|
|
|
}
|
|
|
|
|
|
|
|
|
-Peer.prototype._handleServerJSONMessage = function(message) {
|
|
|
- var peer = message.src;
|
|
|
- var managerId = message.manager;
|
|
|
- var manager = this._getManager(peer, managerId);
|
|
|
- var payload = message.payload;
|
|
|
- switch (message.type) {
|
|
|
+Peer.prototype._dispatchMessage = function(message) {
|
|
|
+ var type = message.type;
|
|
|
+ // Message types that don't involve a peer
|
|
|
+ switch (type) {
|
|
|
case 'OPEN':
|
|
|
this._processQueue();
|
|
|
this.emit('open', this.id);
|
|
@@ -155,11 +149,22 @@ Peer.prototype._handleServerJSONMessage = function(message) {
|
|
|
case 'ID-TAKEN':
|
|
|
this._abort('unavailable-id', 'ID `'+this.id+'` is taken');
|
|
|
break;
|
|
|
+ case 'INVALID-KEY':
|
|
|
+ this._abort('invalid-key', 'API KEY "' + this._key + '" is invalid');
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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
|
|
|
+ config: this.options.config
|
|
|
};
|
|
|
// Either forward to or create new manager
|
|
|
|
|
@@ -193,11 +198,8 @@ Peer.prototype._handleServerJSONMessage = function(message) {
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
- case 'INVALID-KEY':
|
|
|
- this._abort('invalid-key', 'API KEY "' + this._key + '" is invalid');
|
|
|
- break;
|
|
|
default:
|
|
|
- util.log('Unrecognized message type:', message.type);
|
|
|
+ util.warn('Unrecognized message type:', message.type);
|
|
|
break;
|
|
|
}
|
|
|
};
|
|
@@ -206,7 +208,7 @@ Peer.prototype._handleServerJSONMessage = function(message) {
|
|
|
Peer.prototype._processQueue = function() {
|
|
|
while (this._queued.length > 0) {
|
|
|
var manager = this._queued.pop();
|
|
|
- manager.initialize(this.id, this._socket);
|
|
|
+ manager.initialize(this.id, this.socket);
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -260,7 +262,7 @@ Peer.prototype._createManager = function(managerId, peer, options) {
|
|
|
}
|
|
|
|
|
|
options = util.extend({
|
|
|
- config: this._options.config
|
|
|
+ config: this.options.config
|
|
|
}, options);
|
|
|
|
|
|
if (!this._managers[peer]) {
|
|
@@ -270,8 +272,8 @@ Peer.prototype._createManager = function(managerId, peer, options) {
|
|
|
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);
|
|
|
+ if (!!this.id && !!this.socket) {
|
|
|
+ manager.initialize(this.id, this.socket);
|
|
|
} else {
|
|
|
this._queued.push(manager);
|
|
|
}
|
|
@@ -298,10 +300,16 @@ Peer.prototype.call = function(peer, stream, options) {
|
|
|
|
|
|
|
|
|
|
|
|
+Peer.prototype._delayedAbort = function(type, message) {
|
|
|
+ var self = this;
|
|
|
+ util.setZeroTimeout(function(){
|
|
|
+ self._abort(type, message);
|
|
|
+ });
|
|
|
+}
|
|
|
|
|
|
/** Destroys the Peer and emits an error message. */
|
|
|
Peer.prototype._abort = function(type, message) {
|
|
|
- util.log('Aborting. Error:', message);
|
|
|
+ util.error('Aborting. Error:', message);
|
|
|
var err = new Error(message);
|
|
|
err.type = type;
|
|
|
this.destroy();
|
|
@@ -348,8 +356,8 @@ Peer.prototype._cleanup = function() {
|
|
|
*/
|
|
|
Peer.prototype.disconnect = function() {
|
|
|
if (!this.disconnected) {
|
|
|
- if (!!this._socket) {
|
|
|
- this._socket.close();
|
|
|
+ if (!!this.socket) {
|
|
|
+ this.socket.close();
|
|
|
}
|
|
|
this.id = null;
|
|
|
this.disconnected = true;
|