Browse Source

big rewrite so that connections can be made before socket is connected

Michelle Bu 12 years ago
parent
commit
51469ad7ff
3 changed files with 143 additions and 49 deletions
  1. 128 47
      lib/server.js
  2. 13 1
      lib/util.js
  3. 2 1
      package.json

+ 128 - 47
lib/server.js

@@ -1,70 +1,81 @@
-var WebSocketServer = require('ws').Server;
 var util = require('./util');
+var express = require('express');
+var http = require('http');
 var EventEmitter = require('events').EventEmitter;
+var WebSocketServer = require('ws').Server;
+var url = require('url');
 
 
 function PeerServer(options) {
   if (!(this instanceof PeerServer)) return new PeerServer(options);
-  
   EventEmitter.call(this);
-  
-   
+
+  this._app = express();
+  this._httpServer = http.createServer(this._app);
+
   options = util.extend({
     port: 80
   }, options);
-  
-  var wss = new WebSocketServer({ port: options.port });
-  
-  
-  this.clients = {};
+
+  util.debug = options.debug;
+
+  // Listen on user-specified port and create WebSocket server as well.
+  this._httpServer.listen(options.port);
+  this._wss = new WebSocketServer({ path: '/ws', server: this._httpServer });
+
+  // WebSockets that are opened.
+  this._clients = {};
+  // Requests that are awaiting response.
+  this._requests = {};
+
+  // 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();
+};
+
+util.inherits(PeerServer, EventEmitter);
+
+/* Initialize WebSocket server. */
+PeerServer.prototype._initializeWSS = function() {
   var self = this;
+  this._wss.on('connection', function(socket) {
+    var id = url.parse(socket.upgradeReq.url, true).query.id;
 
-  // For connecting clients:
-  // Src will connect upon creating a link.
-  // Receivers will connect after clicking a button and entering an optional key.
-  wss.on('connection', function(socket) {
-    var clientId = util.randomId();
-    while (!!self.clients[clientId]) {
-      clientId = util.randomId();
-    }
-    self.clients[clientId] = socket;
+    // Save the socket for this id.
+    if (!!id && !self._clients[id]) {
+      self._clients[id] = socket;
+    } else if (!id) {
+      // TODO: error
+    };
 
     socket.on('message', function(data) {
       var message = JSON.parse(data);
-      if (options.debug) {
-        console.log('PeerServer: ', message);
+      var ice = false;
+      util.log(message);
+
+      // Save the socket.
+      if (!self._clients[message.src]) {
+        self._clients[message.src] = socket;
       }
 
       switch (message.type) {
-        // Source connected -- send back its ID.
-        case 'PEER':
-          socket.send(JSON.stringify({ type: 'ID', id: clientId }));
-          break;
-        case 'LEAVE':
-          if (!!self.clients[message.dst]) {
-            try {
-              self.clients[message.dst].send(data);
-            } catch (e) {
-              if (options.debug) {
-                console.log('Error', e);
-              }
-            }
-            delete self.clients[message.src];
-          }
-          break;
-        // Offer or answer from src to sink.
+        // ICE candidates
+        case 'CANDIDATE':
+          ice = true;
+        // Offer or answer between peers.
         case 'OFFER':
         case 'ANSWER':
-        case 'CANDIDATE':
+        // Firefoxism (connectDataConnection ports)
         case 'PORT':
-          if (!!self.clients[message.dst]) {
-            try {
-              self.clients[message.dst].send(data);
-            } catch (e) {
-              if (options.debug) {
-                console.log('Error', e);
-              }
-            }
+          self._handleTransmission(message.src, message.dst, data, ice);
+
+          // Clean up.
+          if (message.type === 'LEAVE') {
+            delete self._clients[message.src];
+            delete self._requests[message.src];
           }
           break;
         default:
@@ -72,10 +83,80 @@ function PeerServer(options) {
       }
     });
   });
+};
+
+/** Initialize HTTP server routes. */
+PeerServer.prototype._initializeHTTP = function() {
+  var self = this;
+
+  // Retrieve guaranteed random ID.
+  this._app.get('/id', function(req, res) {
+    var clientId = util.randomId();
+    while (!!self._clients[clientId] || !!self._requests[clientId]) {
+      clientId = util.randomId();
+    }
+    res.send(clientId);
+  });
+
+  this._app.post('/id', function(req, res) {
+    var id = req.body.id;
+    // Checked in with ID, now waiting for an offer.
+    self._requests[id, 'offer'] = res;
+  });
 
+  this._app.post('/offer', function(req, res) {
+    var data = req.body.data;
+    var src = data.src;
+    var dst = data.dst;
+    self._handleTransmission(src, dst, JSON.stringify(data));
+    // Now expecting ice from same dst.
+    self._requests[src, dst] = res;
+    self._ice[src, dst] = [];
+  });
+
+  this._app.post('/answer', function(req, res) {
+    var data = req.body.data;
+    var src = data.src;
+    var dst = data.dst;
+    self._handleTransmission(src, dst, JSON.stringify(data));
+    // Now expecting ice from same dst.
+    self._requests[src, dst] = res;
+    self._ice[src, dst] = [];
+  });
 };
 
+/** Handles passing on a message. */
+PeerServer.prototype._handleTransmission = function(src, dst, data, ice) {
+  var destination = this._clients[dst];
+  if (!destination) {
+    // For ICE, ports, and answers this should be here.
+    destination = this._requests[dst, src];
+    if (!destination) {
+      // Otherwise it's a new offer.
+      destination = this._requests[dst, 'offer'];
+    }
+  }
+
+  if (!!destination) {
+    if (!ice) {
+      try {
+        destination.send(data);
+      } catch (e) {
+        util.prettyError(e);
+      }
+    } else {
+      if (!!this._ice[dst, src]) {
+        // TODO: see if we can save less.
+        this._ice[dst, src].push(data);
+      }
+    }
+  } else {
+    // Place in queue for 10 seconds.
+  }
+}
+
 
-util.inherits(PeerServer, EventEmitter);
 
 exports.PeerServer = PeerServer;
+
+var ps = new PeerServer({ debug: true });

+ 13 - 1
lib/util.js

@@ -1,5 +1,6 @@
 
 var util = {
+  debug: false,
   inherits: function(ctor, superCtor) {
     ctor.super_ = superCtor;
     ctor.prototype = Object.create(superCtor.prototype, {
@@ -23,7 +24,18 @@ var util = {
     return Math.random().toString(36).substr(2);
   },
   prettyError: function (msg) {
-    console.log('PeerServer: ', msg);
+    if (util.debug) {
+      console.log('ERROR PeerServer: ', msg);
+    }
+  },
+  log: function() {
+    if (util.debug) {
+      var copy = [];
+      for (var i = 0; i < arguments.length; i += 1) {
+        copy[i] = arguments[i];
+      }
+      console.log.apply(console, copy);
+    }
   }
 };
 

+ 2 - 1
package.json

@@ -14,6 +14,7 @@
   "author": "Michelle Bu",
   "license": "BSD",
   "dependencies": {
-    "ws": "~0.4.25"
+    "ws": "~0.4.25",
+    "express": "~3.1.0"
   }
 }