|
@@ -1,72 +1,23 @@
|
|
var util = require('./util');
|
|
var util = require('./util');
|
|
-var restify = require('restify');
|
|
|
|
-var EventEmitter = require('events').EventEmitter;
|
|
|
|
|
|
+var bodyParser = require('body-parser');
|
|
var WebSocketServer = require('ws').Server;
|
|
var WebSocketServer = require('ws').Server;
|
|
var url = require('url');
|
|
var url = require('url');
|
|
|
|
|
|
-function PeerServer(options) {
|
|
|
|
- if (!(this instanceof PeerServer)) return new PeerServer(options);
|
|
|
|
- EventEmitter.call(this);
|
|
|
|
-
|
|
|
|
- this._options = util.extend({
|
|
|
|
- port: 80,
|
|
|
|
- debug: false,
|
|
|
|
- timeout: 5000,
|
|
|
|
- key: 'peerjs',
|
|
|
|
- ip_limit: 5000,
|
|
|
|
- concurrent_limit: 5000,
|
|
|
|
- ssl: {},
|
|
|
|
- path: '/',
|
|
|
|
- allow_discovery: false
|
|
|
|
- }, options);
|
|
|
|
-
|
|
|
|
- util.debug = this._options.debug;
|
|
|
|
-
|
|
|
|
- // Print warning if only one of the two is given.
|
|
|
|
- if (Object.keys(this._options.ssl).length === 1) {
|
|
|
|
- util.prettyError('Warning: PeerServer will not run on an HTTPS server'
|
|
|
|
- + ' because either the key or the certificate has not been provided.');
|
|
|
|
- }
|
|
|
|
|
|
+var app = exports = module.exports = {};
|
|
|
|
|
|
- this._options.ssl['name'] = 'PeerServer';
|
|
|
|
|
|
+/** Initialize WebSocket server. */
|
|
|
|
+app._initializeWSS = function(server) {
|
|
|
|
+ var self = this;
|
|
|
|
|
|
- if (this._options.path[0] !== '/') {
|
|
|
|
- this._options.path = '/' + this._options.path;
|
|
|
|
- }
|
|
|
|
- if (this._options.path[this._options.path.length - 1] !== '/') {
|
|
|
|
- this._options.path += '/';
|
|
|
|
|
|
+ if (this.mountpath instanceof Array) {
|
|
|
|
+ throw new Error("This app can only be mounted on a single path");
|
|
}
|
|
}
|
|
|
|
|
|
- this._app = restify.createServer(this._options.ssl);
|
|
|
|
-
|
|
|
|
- // Connected clients
|
|
|
|
- this._clients = {};
|
|
|
|
-
|
|
|
|
- // Messages waiting for another peer.
|
|
|
|
- this._outstanding = {};
|
|
|
|
-
|
|
|
|
- // Initailize WebSocket server handlers.
|
|
|
|
- this._initializeWSS();
|
|
|
|
-
|
|
|
|
- // Initialize HTTP routes. This is only used for the first few milliseconds
|
|
|
|
- // before a socket is opened for a Peer.
|
|
|
|
- this._initializeHTTP();
|
|
|
|
-
|
|
|
|
- // Mark concurrent users per ip
|
|
|
|
- this._ips = {};
|
|
|
|
-
|
|
|
|
- this._setCleanupIntervals();
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-util.inherits(PeerServer, EventEmitter);
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-/** Initialize WebSocket server. */
|
|
|
|
-PeerServer.prototype._initializeWSS = function() {
|
|
|
|
- var self = this;
|
|
|
|
|
|
+ var path = this.mountpath;
|
|
|
|
+ var path = path + (path[path.length - 1] != '/' ? '/' : '') + 'peerjs';
|
|
|
|
|
|
// Create WebSocket server as well.
|
|
// Create WebSocket server as well.
|
|
- this._wss = new WebSocketServer({ path: this._options.path + 'peerjs', server: this._app});
|
|
|
|
|
|
+ this._wss = new WebSocketServer({ path: path, server: server});
|
|
|
|
|
|
this._wss.on('connection', function(socket) {
|
|
this._wss.on('connection', function(socket) {
|
|
var query = url.parse(socket.upgradeReq.url, true).query;
|
|
var query = url.parse(socket.upgradeReq.url, true).query;
|
|
@@ -100,7 +51,8 @@ PeerServer.prototype._initializeWSS = function() {
|
|
});
|
|
});
|
|
};
|
|
};
|
|
|
|
|
|
-PeerServer.prototype._configureWS = function(socket, key, id, token) {
|
|
|
|
|
|
+app._configureWS = function(socket, key, id, token) {
|
|
|
|
+
|
|
var self = this;
|
|
var self = this;
|
|
var client = this._clients[key][id];
|
|
var client = this._clients[key][id];
|
|
|
|
|
|
@@ -122,7 +74,7 @@ PeerServer.prototype._configureWS = function(socket, key, id, token) {
|
|
|
|
|
|
// Cleanup after a socket closes.
|
|
// Cleanup after a socket closes.
|
|
socket.on('close', function() {
|
|
socket.on('close', function() {
|
|
- util.log('Socket closed:', id);
|
|
|
|
|
|
+ self._log('Socket closed:', id);
|
|
if (client.socket == socket) {
|
|
if (client.socket == socket) {
|
|
self._removePeer(key, id);
|
|
self._removePeer(key, id);
|
|
}
|
|
}
|
|
@@ -144,7 +96,7 @@ PeerServer.prototype._configureWS = function(socket, key, id, token) {
|
|
util.prettyError('Message unrecognized');
|
|
util.prettyError('Message unrecognized');
|
|
}
|
|
}
|
|
} catch(e) {
|
|
} catch(e) {
|
|
- util.log('Invalid message', data);
|
|
|
|
|
|
+ self._log('Invalid message', data);
|
|
throw e;
|
|
throw e;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
@@ -154,11 +106,11 @@ PeerServer.prototype._configureWS = function(socket, key, id, token) {
|
|
this.emit('connection', id);
|
|
this.emit('connection', id);
|
|
};
|
|
};
|
|
|
|
|
|
-PeerServer.prototype._checkAllowsDiscovery = function(key, cb) {
|
|
|
|
|
|
+app._checkAllowsDiscovery = function(key, cb) {
|
|
cb(this._options.allow_discovery);
|
|
cb(this._options.allow_discovery);
|
|
};
|
|
};
|
|
|
|
|
|
-PeerServer.prototype._checkKey = function(key, ip, cb) {
|
|
|
|
|
|
+app._checkKey = function(key, ip, cb) {
|
|
if (key == this._options.key) {
|
|
if (key == this._options.key) {
|
|
if (!this._clients[key]) {
|
|
if (!this._clients[key]) {
|
|
this._clients[key] = {};
|
|
this._clients[key] = {};
|
|
@@ -185,46 +137,23 @@ PeerServer.prototype._checkKey = function(key, ip, cb) {
|
|
};
|
|
};
|
|
|
|
|
|
/** Initialize HTTP server routes. */
|
|
/** Initialize HTTP server routes. */
|
|
-PeerServer.prototype._initializeHTTP = function() {
|
|
|
|
|
|
+app._initializeHTTP = function() {
|
|
var self = this;
|
|
var self = this;
|
|
|
|
|
|
- this._app.use(restify.bodyParser({ mapParams: false }));
|
|
|
|
- this._app.use(restify.queryParser());
|
|
|
|
- this._app.use(util.allowCrossDomain);
|
|
|
|
-
|
|
|
|
- /** Hack from https://github.com/mcavage/node-restify/issues/284, until we switch to express */
|
|
|
|
- function unknownMethodHandler(req, res) {
|
|
|
|
- if (req.method.toLowerCase() === 'options') {
|
|
|
|
- var allowHeaders = ['Accept', 'Accept-Version', 'Content-Type', 'Api-Version'];
|
|
|
|
-
|
|
|
|
- if (res.methods.indexOf('OPTIONS') === -1) res.methods.push('OPTIONS');
|
|
|
|
-
|
|
|
|
- res.header('Access-Control-Allow-Credentials', true);
|
|
|
|
- res.header('Access-Control-Allow-Headers', allowHeaders.join(', '));
|
|
|
|
- res.header('Access-Control-Allow-Methods', res.methods.join(', '));
|
|
|
|
- res.header('Access-Control-Allow-Origin', req.headers.origin);
|
|
|
|
-
|
|
|
|
- return res.send(204);
|
|
|
|
- } else {
|
|
|
|
- return res.send(new restify.MethodNotAllowedError());
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- this._app.on('MethodNotAllowed', unknownMethodHandler);
|
|
|
|
|
|
+ this.use(util.allowCrossDomain);
|
|
|
|
|
|
this._app.get(this._options.path, function(req, res, next) {
|
|
this._app.get(this._options.path, function(req, res, next) {
|
|
res.send(require('../app.json'));
|
|
res.send(require('../app.json'));
|
|
});
|
|
});
|
|
|
|
|
|
// Retrieve guaranteed random ID.
|
|
// Retrieve guaranteed random ID.
|
|
- this._app.get(this._options.path + ':key/id', function(req, res, next) {
|
|
|
|
|
|
+ this.get('/:key/id', function(req, res, next) {
|
|
res.contentType = 'text/html';
|
|
res.contentType = 'text/html';
|
|
res.send(self._generateClientId(req.params.key));
|
|
res.send(self._generateClientId(req.params.key));
|
|
- return next();
|
|
|
|
});
|
|
});
|
|
|
|
|
|
// Server sets up HTTP streaming when you get post an ID.
|
|
// Server sets up HTTP streaming when you get post an ID.
|
|
- this._app.post(this._options.path + ':key/:id/:token/id', function(req, res, next) {
|
|
|
|
|
|
+ this.post('/:key/:id/:token/id', function(req, res, next) {
|
|
var id = req.params.id;
|
|
var id = req.params.id;
|
|
var token = req.params.token;
|
|
var token = req.params.token;
|
|
var key = req.params.key;
|
|
var key = req.params.key;
|
|
@@ -243,11 +172,10 @@ PeerServer.prototype._initializeHTTP = function() {
|
|
} else {
|
|
} else {
|
|
self._startStreaming(res, key, id, token);
|
|
self._startStreaming(res, key, id, token);
|
|
}
|
|
}
|
|
- return next();
|
|
|
|
});
|
|
});
|
|
|
|
|
|
// Get a list of all peers for a key, enabled by the `allowDiscovery` flag.
|
|
// Get a list of all peers for a key, enabled by the `allowDiscovery` flag.
|
|
- this._app.get(this._options.path + ':key/peers', function(req, res, next) {
|
|
|
|
|
|
+ this.get('/:key/peers', function(req, res, next) {
|
|
var key = req.params.key;
|
|
var key = req.params.key;
|
|
if (self._clients[key]) {
|
|
if (self._clients[key]) {
|
|
self._checkAllowsDiscovery(key, function(isAllowed) {
|
|
self._checkAllowsDiscovery(key, function(isAllowed) {
|
|
@@ -256,11 +184,9 @@ PeerServer.prototype._initializeHTTP = function() {
|
|
} else {
|
|
} else {
|
|
res.send(401);
|
|
res.send(401);
|
|
}
|
|
}
|
|
- return next();
|
|
|
|
});
|
|
});
|
|
} else {
|
|
} else {
|
|
res.send(404);
|
|
res.send(404);
|
|
- return next();
|
|
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
@@ -272,7 +198,6 @@ PeerServer.prototype._initializeHTTP = function() {
|
|
if (!self._clients[key] || !(client = self._clients[key][id])) {
|
|
if (!self._clients[key] || !(client = self._clients[key][id])) {
|
|
if (req.params.retry) {
|
|
if (req.params.retry) {
|
|
res.send(401);
|
|
res.send(401);
|
|
- return next();
|
|
|
|
} else {
|
|
} else {
|
|
// Retry this request
|
|
// Retry this request
|
|
req.params.retry = true;
|
|
req.params.retry = true;
|
|
@@ -294,28 +219,21 @@ PeerServer.prototype._initializeHTTP = function() {
|
|
});
|
|
});
|
|
res.send(200);
|
|
res.send(200);
|
|
}
|
|
}
|
|
- return next();
|
|
|
|
};
|
|
};
|
|
|
|
|
|
- this._app.post(this._options.path + ':key/:id/:token/offer', handle);
|
|
|
|
|
|
+ var jsonParser = bodyParser.json();
|
|
|
|
|
|
- this._app.post(this._options.path + ':key/:id/:token/candidate', handle);
|
|
|
|
|
|
+ this.post('/:key/:id/:token/offer', jsonParser, handle);
|
|
|
|
|
|
- this._app.post(this._options.path + ':key/:id/:token/answer', handle);
|
|
|
|
|
|
+ this.post('/:key/:id/:token/candidate', jsonParser, handle);
|
|
|
|
|
|
- this._app.post(this._options.path + ':key/:id/:token/leave', handle);
|
|
|
|
-
|
|
|
|
- // Listen on user-specified port and IP address.
|
|
|
|
- if (this._options.ip) {
|
|
|
|
- this._app.listen(this._options.port, this._options.ip);
|
|
|
|
- } else {
|
|
|
|
- this._app.listen(this._options.port);
|
|
|
|
- }
|
|
|
|
|
|
+ this.post('/:key/:id/:token/answer', jsonParser, handle);
|
|
|
|
|
|
|
|
+ this.post('/:key/:id/:token/leave', jsonParser, handle);
|
|
};
|
|
};
|
|
|
|
|
|
/** Saves a streaming response and takes care of timeouts and headers. */
|
|
/** Saves a streaming response and takes care of timeouts and headers. */
|
|
-PeerServer.prototype._startStreaming = function(res, key, id, token, open) {
|
|
|
|
|
|
+app._startStreaming = function(res, key, id, token, open) {
|
|
var self = this;
|
|
var self = this;
|
|
|
|
|
|
res.writeHead(200, {'Content-Type': 'application/octet-stream'});
|
|
res.writeHead(200, {'Content-Type': 'application/octet-stream'});
|
|
@@ -352,7 +270,7 @@ PeerServer.prototype._startStreaming = function(res, key, id, token, open) {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
-PeerServer.prototype._pruneOutstanding = function() {
|
|
|
|
|
|
+app._pruneOutstanding = function() {
|
|
var keys = Object.keys(this._outstanding);
|
|
var keys = Object.keys(this._outstanding);
|
|
for (var k = 0, kk = keys.length; k < kk; k += 1) {
|
|
for (var k = 0, kk = keys.length; k < kk; k += 1) {
|
|
var key = keys[k];
|
|
var key = keys[k];
|
|
@@ -373,7 +291,7 @@ PeerServer.prototype._pruneOutstanding = function() {
|
|
};
|
|
};
|
|
|
|
|
|
/** Cleanup */
|
|
/** Cleanup */
|
|
-PeerServer.prototype._setCleanupIntervals = function() {
|
|
|
|
|
|
+app._setCleanupIntervals = function() {
|
|
var self = this;
|
|
var self = this;
|
|
|
|
|
|
// Clean up ips every 10 minutes
|
|
// Clean up ips every 10 minutes
|
|
@@ -394,7 +312,7 @@ PeerServer.prototype._setCleanupIntervals = function() {
|
|
};
|
|
};
|
|
|
|
|
|
/** Process outstanding peer offers. */
|
|
/** Process outstanding peer offers. */
|
|
-PeerServer.prototype._processOutstanding = function(key, id) {
|
|
|
|
|
|
+app._processOutstanding = function(key, id) {
|
|
var offers = this._outstanding[key][id];
|
|
var offers = this._outstanding[key][id];
|
|
if (!offers) {
|
|
if (!offers) {
|
|
return;
|
|
return;
|
|
@@ -405,7 +323,7 @@ PeerServer.prototype._processOutstanding = function(key, id) {
|
|
delete this._outstanding[key][id];
|
|
delete this._outstanding[key][id];
|
|
};
|
|
};
|
|
|
|
|
|
-PeerServer.prototype._removePeer = function(key, id) {
|
|
|
|
|
|
+app._removePeer = function(key, id) {
|
|
if (this._clients[key] && this._clients[key][id]) {
|
|
if (this._clients[key] && this._clients[key][id]) {
|
|
this._ips[this._clients[key][id].ip]--;
|
|
this._ips[this._clients[key][id].ip]--;
|
|
delete this._clients[key][id];
|
|
delete this._clients[key][id];
|
|
@@ -414,7 +332,7 @@ PeerServer.prototype._removePeer = function(key, id) {
|
|
};
|
|
};
|
|
|
|
|
|
/** Handles passing on a message. */
|
|
/** Handles passing on a message. */
|
|
-PeerServer.prototype._handleTransmission = function(key, message) {
|
|
|
|
|
|
+app._handleTransmission = function(key, message) {
|
|
var type = message.type;
|
|
var type = message.type;
|
|
var src = message.src;
|
|
var src = message.src;
|
|
var dst = message.dst;
|
|
var dst = message.dst;
|
|
@@ -425,7 +343,7 @@ PeerServer.prototype._handleTransmission = function(key, message) {
|
|
// User is connected!
|
|
// User is connected!
|
|
if (destination) {
|
|
if (destination) {
|
|
try {
|
|
try {
|
|
- util.log(type, 'from', src, 'to', dst);
|
|
|
|
|
|
+ this._log(type, 'from', src, 'to', dst);
|
|
if (destination.socket) {
|
|
if (destination.socket) {
|
|
destination.socket.send(data);
|
|
destination.socket.send(data);
|
|
} else if (destination.res) {
|
|
} else if (destination.res) {
|
|
@@ -438,7 +356,6 @@ PeerServer.prototype._handleTransmission = function(key, message) {
|
|
} catch (e) {
|
|
} catch (e) {
|
|
// This happens when a peer disconnects without closing connections and
|
|
// This happens when a peer disconnects without closing connections and
|
|
// the associated WebSocket has not closed.
|
|
// the associated WebSocket has not closed.
|
|
- util.prettyError(e);
|
|
|
|
// Tell other side to stop trying.
|
|
// Tell other side to stop trying.
|
|
this._removePeer(key, dst);
|
|
this._removePeer(key, dst);
|
|
this._handleTransmission(key, {
|
|
this._handleTransmission(key, {
|
|
@@ -465,7 +382,7 @@ PeerServer.prototype._handleTransmission = function(key, message) {
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
-PeerServer.prototype._generateClientId = function(key) {
|
|
|
|
|
|
+app._generateClientId = function(key) {
|
|
var clientId = util.randomId();
|
|
var clientId = util.randomId();
|
|
if (!this._clients[key]) {
|
|
if (!this._clients[key]) {
|
|
return clientId;
|
|
return clientId;
|
|
@@ -476,4 +393,8 @@ PeerServer.prototype._generateClientId = function(key) {
|
|
return clientId;
|
|
return clientId;
|
|
};
|
|
};
|
|
|
|
|
|
-exports.PeerServer = PeerServer;
|
|
|
|
|
|
+app._log = function() {
|
|
|
|
+ if (this._options.debug) {
|
|
|
|
+ console.log.apply(console, arguments);
|
|
|
|
+ }
|
|
|
|
+};
|