diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..09d3ef3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js +node_js: + - 0.8 + - 0.10 diff --git a/README.md b/README.md index 9fc4515..8568436 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# PeerJS Server: Server component for PeerJS # +[![Build Status](https://travis-ci.org/peers/peerjs-server.png?branch=master)](https://travis-ci.org/peers/peerjs-server) + +# PeerServer: A server for PeerJS # PeerServer helps broker connections between PeerJS clients. Data is not proxied through the server. @@ -12,32 +14,46 @@ PeerServer helps broker connections between PeerJS clients. Data is not proxied Install the library: - npm install peer +```bash +$> npm install peer +``` Run the server: - var PeerServer = require('peer').PeerServer; - var server = new PeerServer({ port: 9000 }); +```bash +$> peerjs --port 9000 --key peerjs +``` + +Or, create a custom server: + +```javascript +var PeerServer = require('peer').PeerServer; +var server = new PeerServer({ port: 9000 }); +``` Connecting to the server from PeerJS: - +```html + +``` Using HTTPS: Simply pass in PEM-encoded certificate and key. - var fs = require('fs'); - var PeerServer = require('peer').PeerServer; - - var server = new PeerServer({ - port: 9000, - ssl: { - key: fs.readFileSync('/path/to/your/ssl/key/here.key'), - certificate: fs.readFileSync('/path/to/your/ssl/certificate/here.crt') - } - }); +```javascript +var fs = require('fs'); +var PeerServer = require('peer').PeerServer; + +var server = new PeerServer({ + port: 9000, + ssl: { + key: fs.readFileSync('/path/to/your/ssl/key/here.key'), + certificate: fs.readFileSync('/path/to/your/ssl/certificate/here.crt') + } +}); +``` ## Problems? diff --git a/bin/peerjs b/bin/peerjs new file mode 100755 index 0000000..83ba95a --- /dev/null +++ b/bin/peerjs @@ -0,0 +1,71 @@ +#!/usr/bin/env node + +var path = require('path') + , pkg = require('../package.json') + , fs = require('fs') + , version = pkg.version + , PeerServer = require('../lib/server').PeerServer + , opts = require('optimist') + .usage('Usage: $0') + .options({ + debug: { + demand: false, + alias: 'd', + description: 'debug', + default: false + }, + timeout: { + demand: false, + alias: 't', + description: 'timeout (milliseconds)', + default: 5000 + }, + ip_limit: { + demand: false, + alias: 'i', + description: 'IP limit', + default: 5000 + }, + concurrent_limit: { + demand: false, + alias: 'c', + description: 'concurrent limit', + default: 5000 + }, + key: { + demand: false, + alias: 'k', + description: 'connection key', + default: 'peerjs' + }, + sslkey: { + demand: false, + description: 'path to SSL key' + }, + sslcert: { + demand: false, + description: 'path to SSL certificate' + }, + port: { + demand: true, + alias: 'p', + description: 'port', + } + }).argv; + +opts.version = version; + +if (opts.sslkey && opts.sslcert) { + opts['ssl'] = {}; + opts.ssl['key'] = fs.readFileSync(path.resolve(opts.sslkey)); + opts.ssl['certificate'] = fs.readFileSync(path.resolve(opts.sslcert)); +} + +process.on('uncaughtException', function(e) { + console.error('Error: ' + e); +}); + +var server = new PeerServer(opts); +console.log( + "Started PeerServer, port: " + opts.port + (" (v. %s)"), version +); diff --git a/lib/server.js b/lib/server.js index 5cf2572..0e5d087 100644 --- a/lib/server.js +++ b/lib/server.js @@ -21,8 +21,6 @@ function PeerServer(options) { util.debug = this._options.debug; - // Set up HTTPS server if key and certificate are provided. - var secure = this._options.ssl.key && this._options.ssl.certificate; // 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' @@ -49,7 +47,7 @@ function PeerServer(options) { this._ips = {}; this._setCleanupIntervals(); -}; +} util.inherits(PeerServer, EventEmitter); @@ -73,7 +71,7 @@ PeerServer.prototype._initializeWSS = function() { socket.close(); return; } - + if (!self._clients[key] || !self._clients[key][id]) { self._checkKey(key, ip, function(err) { if (!err) { @@ -126,35 +124,22 @@ PeerServer.prototype._configureWS = function(socket, key, id, token) { try { var message = JSON.parse(data); - switch (message.type) { - case 'LEAVE': - // Clean up if a Peer sends a LEAVE. - if (!message.dst) { - self._removePeer(key, id); - break; - } - // ICE candidates - case 'CANDIDATE': - // Offer or answer between peers. - case 'OFFER': - case 'ANSWER': - // Use the ID we know to be correct to prevent spoofing. - self._handleTransmission(key, { - type: message.type, - src: id, - dst: message.dst, - payload: message.payload - }); - break; - default: - util.prettyError('Message unrecognized'); + if (['LEAVE', 'CANDIDATE', 'OFFER', 'ANSWER'].indexOf(message.type) !== -1) { + self._handleTransmission(key, { + type: message.type, + src: id, + dst: message.dst, + payload: message.payload + }); + } else { + util.prettyError('Message unrecognized'); } } catch(e) { - throw e; util.log('Invalid message', data); + throw e; } }); -} +}; PeerServer.prototype._checkKey = function(key, ip, cb) { @@ -181,14 +166,14 @@ PeerServer.prototype._checkKey = function(key, ip, cb) { } else { cb('Invalid key provided'); } -} +}; /** Initialize HTTP server routes. */ PeerServer.prototype._initializeHTTP = function() { var self = this; this._app.use(restify.bodyParser({ mapParams: false })); - this._app.use(restify.queryParser()) + this._app.use(restify.queryParser()); this._app.use(util.allowCrossDomain); // Retrieve guaranteed random ID. @@ -203,7 +188,7 @@ PeerServer.prototype._initializeHTTP = function() { var id = req.params.id; var token = req.params.token; var key = req.params.key; - var ip = req.ip; + var ip = req.connection.remoteAddress; if (!self._clients[key] || !self._clients[key][id]) { self._checkKey(key, ip, function(err) { @@ -307,7 +292,7 @@ PeerServer.prototype._pruneOutstanding = function() { var keys = Object.keys(this._outstanding); for (var k = 0, kk = keys.length; k < kk; k += 1) { var key = keys[k]; - var dsts = Object.keys(this._outstanding[key]); + var dsts = Object.keys(this._outstanding[key]); for (var i = 0, ii = dsts.length; i < ii; i += 1) { var offers = this._outstanding[key][dsts[i]]; var seen = {}; @@ -332,7 +317,7 @@ PeerServer.prototype._setCleanupIntervals = function() { var keys = Object.keys(self._ips); for (var i = 0, ii = keys.length; i < ii; i += 1) { var key = keys[i]; - if (self._ips[key] == 0) { + if (self._ips[key] === 0) { delete self._ips[key]; } } @@ -383,7 +368,7 @@ PeerServer.prototype._handleTransmission = function(key, message) { destination.res.write(data); } else { // Neither socket no res available. Peer dead? - throw "Peer dead" + throw "Peer dead"; } } catch (e) { // This happens when a peer disconnects without closing connections and diff --git a/lib/util.js b/lib/util.js index 01b0e89..8a81cf1 100644 --- a/lib/util.js +++ b/lib/util.js @@ -13,6 +13,7 @@ var util = { }); }, extend: function(dest, source) { + source = source || {}; for(var key in source) { if(source.hasOwnProperty(key)) { dest[key] = source[key]; diff --git a/package.json b/package.json index 2da52db..a4240f6 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "peer", - "version": "0.1.6", + "version": "0.2.0", "description": "Peer-to-peer data in browsers", "main": "lib/server.js", + "bin": { "peerjs": "./bin/peerjs" }, "repository": { "type": "git", "url": "git://github.com/peers/peerjs-server.git" @@ -11,6 +12,18 @@ "license": "MIT", "dependencies": { "restify": "~2.3.5", - "ws": "~0.4.25" + "ws": "~0.4.25", + "optimist": "*" + }, + "devDependencies": { + "expect.js": "*", + "sinon": "*", + "mocha": "*" + }, + "engines": { + "node": ">=0.8" + }, + "scripts": { + "test": "mocha test" } } diff --git a/test/server.js b/test/server.js new file mode 100644 index 0000000..8a04bc5 --- /dev/null +++ b/test/server.js @@ -0,0 +1,113 @@ +var PeerServer = require('../').PeerServer; +var expect = require('expect.js'); +var sinon = require('sinon'); + +describe('PeerServer', function() { + describe('constructor', function() { + before(function() { + PeerServer.prototype._initializeWSS = sinon.stub(); + PeerServer.prototype._initializeHTTP = sinon.stub(); + }); + + it('should be able to be created without the `new` keyword', function() { + var p = PeerServer(); + expect(p.constructor).to.be(PeerServer); + }); + + it('should default to port 80, key `peerjs`', function() { + var p = new PeerServer(); + expect(p._options.key).to.be('peerjs'); + expect(p._options.port).to.be(80); + }); + + it('should accept a custom port', function() { + var p = new PeerServer({ port: 8000 }); + expect(p._options.port).to.be(8000); + }); + }); + + describe('#_initializeWSS', function() { + WebSocketServer = sinon.stub(); + + }); + + describe('#_configureWS', function() { + + }); + + describe('#_checkKey', function() { + var p; + before(function() { + PeerServer.prototype._initializeHTTP = sinon.stub(); + p = new PeerServer({ port: 8000 }); + p._checkKey('peerjs', 'myip', function() {}); + }); + + it('should reject keys that are not the default', function(done) { + p._checkKey('bad key', null, function(response) { + expect(response).to.be('Invalid key provided'); + done(); + }); + }); + + it('should accept valid key/ip pairs', function(done) { + p._checkKey('peerjs', 'myip', function(response) { + expect(response).to.be(null); + done(); + }); + }); + + it('should reject ips that are at their limit', function(done) { + p._options.ip_limit = 0; + p._checkKey('peerjs', 'myip', function(response) { + expect(response).to.be('myip has reached its concurrent user limit'); + done(); + }); + }); + + it('should reject when the server is at its limit', function(done) { + p._options.concurrent_limit = 0; + p._checkKey('peerjs', 'myip', function(response) { + expect(response).to.be('Server has reached its concurrent user limit'); + done(); + }); + }); + + }); + + describe('#_initializeHTTP', function() { + + }); + + describe('#_startStreaming', function() { + + }); + + describe('#_pruneOutstanding', function() { + + }); + + describe('#_processOutstanding', function() { + + }); + + describe('#_removePeer', function() { + + }); + + describe('#_handleTransmission', function() { + // TODO: this is probably the most important method to test. + }); + + describe('#_generateClientId', function() { + var p; + before(function() { + PeerServer.prototype._initializeHTTP = sinon.stub(); + p = new PeerServer({ port: 8000 }); + }); + + it('should generate a 16-character ID', function() { + expect(p._generateClientId('anykey').length).to.be(16); + }); + }); +});