فهرست منبع

socket abstraction

Michelle Bu 12 سال پیش
والد
کامیت
ec5ec82e8e
7فایلهای تغییر یافته به همراه762 افزوده شده و 554 حذف شده
  1. 1 0
      bin/build.js
  2. 256 187
      demo/static/peer.js
  3. 256 187
      dist/peer.js
  4. 0 0
      dist/peer.min.js
  5. 17 42
      lib/connection.js
  6. 30 138
      lib/peer.js
  7. 202 0
      lib/socket.js

+ 1 - 0
bin/build.js

@@ -45,6 +45,7 @@ var base = [
   , 'adapter.js' 
   , 'peer.js'
   , 'connection.js'
+  , 'socket.js'
 
 ];
 

+ 256 - 187
demo/static/peer.js

@@ -837,34 +837,36 @@ if (navigator.mozGetUserMedia) {
 
 exports.RTCPeerConnection = RTCPeerConnection;
 exports.getUserMedia = getUserMedia;
+/**
+ * A peer who can initiate connections with other peers.
+ */
 function Peer(options) {
   if (!(this instanceof Peer)) return new Peer(options);
   EventEmitter.call(this);
 
   options = util.extend({
     debug: false,
-    host: 'localhost',
+    host: '0.peerjs.com',
     config: { 'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }] },
     port: 80
   }, options);
-  this.options = options;
+  this._options = options;
   util.debug = options.debug;
 
   // TODO: default should be the cloud server.
   this._server = options.host + ':' + options.port;
-  this._httpUrl = 'http://' + this._server;
-  this._config = options.config;
 
   // Ensure alphanumeric_-
   if (options.id && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(options.id))
     throw new Error('Peer ID can only contain alphanumerics, "_", and "-".');
+  if (options.apikey && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(options.apikey))
+    throw new Error('API key can only contain alphanumerics, "_", and "-".');
 
   this._id = options.id;
   // Not used unless using cloud server.
   this._apikey = options.apikey;
 
-  // Check in with the server with ID or get an ID.
-  this._checkIn();
+  this._startSocket();
 
   // Connections for this peer.
   this.connections = {};
@@ -875,142 +877,31 @@ function Peer(options) {
 
 util.inherits(Peer, EventEmitter);
 
-/** Check in with ID or get one from server. */
-Peer.prototype._checkIn = function() {
-  // If no ID provided, get a unique ID from server.
-  var self = this;
-  if (!this._id) {
-    try {
-      var http = new XMLHttpRequest();
-      var url = this._httpUrl + '/id';
-      if (!!this._apikey)
-        url += '?key=' + this._apikey;
-
-      // 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 (!self._id && http.readyState > 2 && !!http.responseText) {
-          try {
-            var response = JSON.parse(http.responseText.split('\n').shift());
-            if (!!response.id) {
-              self._id = response.id;
-              self._socketInit();
-              self.emit('ready', self._id);
-              self._processQueue();
-            }
-          } catch (e) {
-            self._socketInit();
-          }
-        }
-        self._handleStream(http, true);
-      };
-      http.send(null);
-    } catch(e) {
-      util.log('XMLHttpRequest not available; defaulting to WebSockets');
-      this._socketInit();
-    }
-  } else {
-    this._startXhrStream();
-    this._socketInit();
-  }
-  // TODO: may need to setInterval in case handleStream is not being called
-  // enough.
-};
-
-Peer.prototype._startXhrStream = function() {
-  try {
-    var http = new XMLHttpRequest();
-    var self = this;
-    http.open('post', this._httpUrl + '/id', true);
-    http.setRequestHeader('Content-Type', 'application/json');
-    http.onreadystatechange = function() {
-      self._handleStream(http);
-    };
-    http.send(JSON.stringify({ id: this._id, key: this._apikey }));
-  } catch(e) {
-    util.log('XMLHttpRequest not available; defaulting to WebSockets');
-  }
-};
-
-/** Handles onreadystatechange response as a stream. */
-Peer.prototype._handleStream = function(http, pad) {
-  // 3 and 4 are loading/done state. All others are not relevant.
-  if (http.readyState < 3) {
-    return;
-  } else if (http.readyState == 3 && http.status != 200) {
-    return;
-  }
-
-  if (this._index === undefined)
-    this._index = pad ? 2 : 1;
-
-  if (http.responseText === null)
-    return;
-
-  // TODO: handle
-  var message = http.responseText.split('\n')[this._index];
-  if (!!message && http.readyState == 3) {
-    this._index += 1;
-    this._handleServerMessage(message);
-  } else if (http.readyState == 4) {
-    this._index = 1;
-  }
-
-};
-
-/** Start up websocket communications. */
-Peer.prototype._socketInit = function() {
-  if (!!this._socket)
-    return;
-
-  var wsurl = 'ws://' + this._server + '/ws';
-  if (!!this._id) {
-    wsurl += '?id=' + this._id;
-    if (!!this._apikey)
-      wsurl += '&key=' + this._apikey;
-  } else if (!!this._apikey) {
-    wsurl += '?key=' + this._apikey;
-  }
-  this._socket = new WebSocket(wsurl);
-
+Peer.prototype._startSocket = function() {
   var self = this;
-  this._socket.onmessage = function(event) {
-    self._handleServerMessage(event.data);
-  };
-
-  // Take care of the queue of connections if necessary and make sure Peer knows
-  // socket is open.
-  this._socket.onopen = function() {
-    util.log('Socket open');
-    self._socketOpen = true;
-    var ids = Object.keys(self.connections)
-    for (var i = 0, ii = ids.length; i < ii; i += 1) {
-      self.connections[ids[i]].setSocketOpen();
-    }
-    if (self._id)
-      self._processQueue();
-  };
-};
+  this._socket = new Socket(this._server, this._id, this._apikey);
+  this._socket.on('message', function(data) {
+    self._handleServerJSONMessage(data);
+  });
+  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();
+  });
+  this._socket.on('error', function(error) {
+    util.log(error);
+  });
+  this._socket.start();
+}
 
 
-Peer.prototype._handleServerMessage = function(message) {
-  message = JSON.parse(message);
+Peer.prototype._handleServerJSONMessage = function(message) {
   var peer = message.src;
   var connection = this.connections[peer];
   switch (message.type) {
-    // XHR stream closed by timeout.
-    case 'HTTP-END':
-      util.log('XHR stream timed out.');
-      if (!this._socketOpen)
-        this._startXhrStream();
-      break;
-    // XHR stream closed by socket connect.
-    case 'HTTP-SOCKET':
-        util.log('XHR stream closed, WebSocket connected.');
-        break;
-    case 'HTTP-ERROR':
-        util.log('Something went wrong.');
-        break;
     case 'ID':
       if (!this._id) {
         // If we're just now getting an ID then we may have a queue.
@@ -1028,7 +919,8 @@ Peer.prototype._handleServerMessage = function(message) {
         metadata: message.metadata,
         sdp: message.sdp,
         socketOpen: this._socketOpen,
-        config: this._config
+        config: this._options.config,
+        apikey: this._apikey
       };
       var self = this;
       var connection = new DataConnection(this._id, peer, this._socket, this._httpUrl, function(err, connection) {
@@ -1074,9 +966,7 @@ Peer.prototype._cleanup = function() {
       this.connections[peer].close();
     }
   }
-
-  if (this._socketOpen)
-    this._socket.close();
+  this._socket.close();
 };
 
 /** Listeners for DataConnection events. */
@@ -1104,7 +994,8 @@ Peer.prototype.connect = function(peer, metadata, cb) {
   var options = {
     metadata: metadata,
     socketOpen: this._socketOpen,
-    config: this._config
+    config: this._options.config,
+    apikey: this._apikey
   };
   var connection = new DataConnection(this._id, peer, this._socket, this._httpUrl, cb, options);
   this._attachConnectionListeners(connection);
@@ -1118,26 +1009,25 @@ Peer.prototype.destroy = function() {
 
 
 exports.Peer = Peer;
+/**
+ * A DataChannel PeerConnection between two Peers.
+ */
 function DataConnection(id, peer, socket, httpUrl, cb, options) {
   if (!(this instanceof DataConnection)) return new DataConnection(options);
   EventEmitter.call(this);
 
   options = util.extend({
     config: { 'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }] },
-    socketOpen: false
+    reliable: false
   }, options);
-  this.options = options;
+  this._options = options;
   
   this._id = id;
   this._peer = peer;
   this._originator = (options.sdp === undefined);
   this._cb = cb;
-  this._httpUrl = httpUrl;
-  this.metadata = options.metadata;
-  this._socketOpen = options.socketOpen;
-  this._config = options.config;
+  this._metadata = options.metadata;
 
-  // Set up socket handlers.
   this._socket = socket;
 
   // Firefoxism: connectDataConnection ports.
@@ -1188,7 +1078,7 @@ DataConnection.prototype._setupDataChannel = function() {
   var self = this;
   if (this._originator) {
     util.log('Creating data channel');
-    this._dc = this._pc.createDataChannel(this._peer, { reliable: false });
+    this._dc = this._pc.createDataChannel(this._peer, { reliable: this._options.reliable });
     this._configureDataChannel();
   } else {
     util.log('Listening for data channel');
@@ -1203,8 +1093,8 @@ DataConnection.prototype._setupDataChannel = function() {
 
 /** Starts a PeerConnection and sets up handlers. */
 DataConnection.prototype._startPeerConnection = function() {
-  util.log('Creating RTCPeerConnection: ', this._config);
-  this._pc = new RTCPeerConnection(this._config, { optional:[ { RtpDataChannels: true } ]});
+  util.log('Creating RTCPeerConnection');
+  this._pc = new RTCPeerConnection(this._options.config, { optional:[ { RtpDataChannels: true } ]});
 };
 
 
@@ -1215,35 +1105,16 @@ DataConnection.prototype._setupIce = function() {
   this._pc.onicecandidate = function(evt) {
     if (evt.candidate) {
       util.log('Received ICE candidates');
-      self._handleBroker('ice', JSON.stringify({
+      self._socket.send({
         type: 'CANDIDATE',
         candidate: evt.candidate,
         dst: self._peer,
         src: self._id
-      }));
+      });
     }
   };
 };
 
-DataConnection.prototype._handleBroker = function(type, data) {
-  if (this._socketOpen) {
-    this._socket.send(data);
-  } else {
-    var self = this;
-    var http = new XMLHttpRequest();
-    http.open('post', this._httpUrl + '/' + type, true);
-    http.setRequestHeader('Content-Type', 'application/json');
-    http.onload = function() {
-      // If destination peer is not available...
-      if (http.responseText != 'OK') {
-        util.log('Destination peer not available. Connection closing...');
-        self.close();
-      }
-    }
-    http.send(data);
-  }
-};
-
 // Awaiting update in Firefox spec ***
 /** Sets up DataChannel handlers. 
 DataConnection.prototype._setupDataChannel = function() {
@@ -1332,13 +1203,13 @@ DataConnection.prototype._makeOffer = function() {
     self._pc.setLocalDescription(offer, function() {
       util.log('Set localDescription to offer');
       //self._peerReady = false;
-      self._handleBroker('offer', JSON.stringify({
+      self._socket.send({
         type: 'OFFER',
         sdp: offer,
         dst: self._peer,
         src: self._id,
         metadata: self.metadata
-      }));
+      });
     }, function(err) {
       self._cb('Failed to setLocalDescription');
       util.log('Failed to setLocalDescription, ', err);
@@ -1353,12 +1224,12 @@ DataConnection.prototype._makeAnswer = function() {
     util.log('Created answer');
     self._pc.setLocalDescription(answer, function() {
       util.log('Set localDescription to answer');
-      self._handleBroker('answer', JSON.stringify({
+      self._socket.send({
         type: 'ANSWER',
         src: self._id,
         sdp: answer,
         dst: self._peer
-      }));
+      });
     }, function(err) {
       self._cb('Failed to setLocalDescription');
       util.log('Failed to setLocalDescription, ', err)
@@ -1411,11 +1282,11 @@ DataConnection.prototype._handleDataMessage = function(e) {
 DataConnection.prototype.close = function() {
   this._cleanup();
   var self = this;
-  this._handleBroker('leave', JSON.stringify({
+  this._socket.send({
     type: 'LEAVE',
     dst: self._peer,
     src: self._id,
-  }));
+  });
 };
 
 
@@ -1432,15 +1303,11 @@ DataConnection.prototype.send = function(data) {
   }
 };
 
-
-/**
- * Exposed functions for Peer.
- */
-
-DataConnection.prototype.setSocketOpen = function() {
-  this._socketOpen = true;
+DataConnection.prototype.getMetadata = function() {
+  return this._metadata;
 };
 
+
 DataConnection.prototype.handleSDP = function(message) {
   var sdp = message.sdp;
   if (util.browserisms != 'Firefox') {
@@ -1452,13 +1319,13 @@ DataConnection.prototype.handleSDP = function(message) {
     // Firefoxism
     if (message.type === 'ANSWER' && util.browserisms === 'Firefox') {
       self._pc.connectDataConnection(self.localPort, self.remotePort);
-      self._handleBroker('port', JSON.stringify({
+      self._socket.send({
         type: 'PORT',
         dst: self._peer,
         src: self._id,
         remote: self.localPort,
         local: self.remotePort
-      }));
+      });
     }
   }, function(err) {
     this._cb('Failed to setRemoteDescription');
@@ -1489,5 +1356,207 @@ DataConnection.prototype.handlePort = function(message) {
 };
 
 
+/**
+ * An abstraction on top of WebSockets and XHR streaming to provide fastest
+ * possible connection for peers.
+ */
+function Socket(server, id, key) {
+  if (!(this instanceof Socket)) return new Socket(server, id, key);
+  EventEmitter.call(this);
+
+  this._id = id;
+  this._server = server;
+  this._httpUrl = 'http://' + this._server;
+  this._key = key;
+};
+
+util.inherits(Socket, EventEmitter);
+
+/** Check in with ID or get one from server. */
+Socket.prototype._checkIn = function() {
+  // If no ID provided, get a unique ID from server.
+  var self = this;
+  if (!this._id) {
+    try {
+      var http = new XMLHttpRequest();
+      var url = this._httpUrl + '/id';
+      // Set API key if necessary.
+      if (!!this._key)
+        url += '/' + this._key;
+
+      // 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 (!self._id && http.readyState > 2 && !!http.responseText) {
+          try {
+            var response = JSON.parse(http.responseText.split('\n').shift());
+            if (!!response.id) {
+              self._id = response.id;
+              self._startWebSocket();
+              self.emit('message', { type: 'ID', id: self._id });
+            }
+          } catch (e) {
+            self._startWebSocket();
+          }
+        }
+        self._handleStream(http, true);
+      };
+      http.send(null);
+    } catch(e) {
+      util.log('XMLHttpRequest not available; defaulting to WebSockets');
+      this._startWebSocket();
+    }
+  } else {
+    this._startXhrStream();
+    this._startWebSocket();
+  }
+};
+
+
+/** Start up websocket communications. */
+Socket.prototype._startWebSocket = function() {
+  if (!!this._socket)
+    return;
+
+  var wsurl = 'ws://' + this._server + '/ws';
+  if (!!this._id) {
+    wsurl += '?id=' + this._id;
+    if (!!this._key)
+      wsurl += '&key=' + this._key;
+  } else if (!!this._key) {
+    wsurl += '?key=' + this._key;
+  }
+  this._socket = new WebSocket(wsurl);
+
+  var self = this;
+  this._socket.onmessage = function(event) {
+    try {
+      self.emit('message', JSON.parse(event.data));
+    } catch(e) {
+      util.log('Invalid server message');
+    }
+  };
+
+  // Take care of the queue of connections if necessary and make sure Peer knows
+  // socket is open.
+  this._socket.onopen = function() {
+    util.log('Socket open');
+    if (self._id)
+      self.emit('open');
+  };
+};
+
+
+/** Start XHR streaming. */
+Socket.prototype._startXhrStream = function() {
+  try {
+    var self = this;
+
+    var http = new XMLHttpRequest();
+    var url = this._httpUrl + '/id';
+    // Set API key if necessary.
+    if (!!this._key)
+      url += '/' + this._key;
+
+    http.open('post', url, true);
+    http.setRequestHeader('Content-Type', 'application/json');
+    http.onreadystatechange = function() {
+      self._handleStream(http);
+    };
+    http.send(JSON.stringify({ id: this._id }));
+  } catch(e) {
+    util.log('XMLHttpRequest not available; defaulting to WebSockets');
+  }
+};
+
+
+/** Handles onreadystatechange response as a stream. */
+Socket.prototype._handleStream = function(http, pad) {
+  // 3 and 4 are loading/done state. All others are not relevant.
+  if (http.readyState < 3) {
+    return;
+  } else if (http.readyState == 3 && http.status != 200) {
+    return;
+  }
+
+  if (this._index === undefined)
+    this._index = pad ? 2 : 1;
+
+  if (http.responseText === null)
+    return;
+
+  var message = http.responseText.split('\n')[this._index];
+  if (!!message && http.readyState == 3) {
+    this._index += 1;
+    try {
+      this._handleHTTPErrors(JSON.parse(message));
+    } catch(e) {
+      util.log('Invalid server message');
+    }
+  } else if (http.readyState == 4) {
+    this._index = 1;
+  }
+};
+
+
+Socket.prototype._handleHTTPErrors = function(message) {
+  switch (message.type) {
+    // XHR stream closed by timeout.
+    case 'HTTP-END':
+      util.log('XHR stream timed out.');
+      if (!!this._socket && this._socket.readyState != 1)
+        this._startXhrStream();
+      break;
+    // XHR stream closed by socket connect.
+    case 'HTTP-SOCKET':
+        util.log('XHR stream closed, WebSocket connected.');
+        break;
+    case 'HTTP-ERROR':
+        this.emit('error', 'Something went wrong.');
+        break;
+    default:
+        this.emit('message', message);
+  }
+};
+
+
+
+/** Exposed send for DC & Peer. */
+Socket.prototype.send = function(data) {
+  var type = data.type;
+  message = JSON.stringify(data);
+  if (!type)
+    this.emit('error', 'Invalid message');
+
+  if (!!this._socket && this._socket.readyState == 1) {
+    this._socket.send(message);
+  } else {
+    var self = this;
+    var http = new XMLHttpRequest();
+    var url = this._httpUrl + '/' + type.toLowerCase();
+    // Set API key if necessary.
+    if (!!this._key)
+      url += '/' + this._key;
+
+    http.open('post', url, true);
+    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);
+  }
+};
+
+Socket.prototype.close = function() {
+  if (!!this._socket && this._socket.readyState == 1)
+    this._socket.close();
+};
+
+Socket.prototype.start = function() {
+  this._checkIn();
+};
 
 })(this);

+ 256 - 187
dist/peer.js

@@ -837,34 +837,36 @@ if (navigator.mozGetUserMedia) {
 
 exports.RTCPeerConnection = RTCPeerConnection;
 exports.getUserMedia = getUserMedia;
+/**
+ * A peer who can initiate connections with other peers.
+ */
 function Peer(options) {
   if (!(this instanceof Peer)) return new Peer(options);
   EventEmitter.call(this);
 
   options = util.extend({
     debug: false,
-    host: 'localhost',
+    host: '0.peerjs.com',
     config: { 'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }] },
     port: 80
   }, options);
-  this.options = options;
+  this._options = options;
   util.debug = options.debug;
 
   // TODO: default should be the cloud server.
   this._server = options.host + ':' + options.port;
-  this._httpUrl = 'http://' + this._server;
-  this._config = options.config;
 
   // Ensure alphanumeric_-
   if (options.id && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(options.id))
     throw new Error('Peer ID can only contain alphanumerics, "_", and "-".');
+  if (options.apikey && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(options.apikey))
+    throw new Error('API key can only contain alphanumerics, "_", and "-".');
 
   this._id = options.id;
   // Not used unless using cloud server.
   this._apikey = options.apikey;
 
-  // Check in with the server with ID or get an ID.
-  this._checkIn();
+  this._startSocket();
 
   // Connections for this peer.
   this.connections = {};
@@ -875,142 +877,31 @@ function Peer(options) {
 
 util.inherits(Peer, EventEmitter);
 
-/** Check in with ID or get one from server. */
-Peer.prototype._checkIn = function() {
-  // If no ID provided, get a unique ID from server.
-  var self = this;
-  if (!this._id) {
-    try {
-      var http = new XMLHttpRequest();
-      var url = this._httpUrl + '/id';
-      if (!!this._apikey)
-        url += '?key=' + this._apikey;
-
-      // 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 (!self._id && http.readyState > 2 && !!http.responseText) {
-          try {
-            var response = JSON.parse(http.responseText.split('\n').shift());
-            if (!!response.id) {
-              self._id = response.id;
-              self._socketInit();
-              self.emit('ready', self._id);
-              self._processQueue();
-            }
-          } catch (e) {
-            self._socketInit();
-          }
-        }
-        self._handleStream(http, true);
-      };
-      http.send(null);
-    } catch(e) {
-      util.log('XMLHttpRequest not available; defaulting to WebSockets');
-      this._socketInit();
-    }
-  } else {
-    this._startXhrStream();
-    this._socketInit();
-  }
-  // TODO: may need to setInterval in case handleStream is not being called
-  // enough.
-};
-
-Peer.prototype._startXhrStream = function() {
-  try {
-    var http = new XMLHttpRequest();
-    var self = this;
-    http.open('post', this._httpUrl + '/id', true);
-    http.setRequestHeader('Content-Type', 'application/json');
-    http.onreadystatechange = function() {
-      self._handleStream(http);
-    };
-    http.send(JSON.stringify({ id: this._id, key: this._apikey }));
-  } catch(e) {
-    util.log('XMLHttpRequest not available; defaulting to WebSockets');
-  }
-};
-
-/** Handles onreadystatechange response as a stream. */
-Peer.prototype._handleStream = function(http, pad) {
-  // 3 and 4 are loading/done state. All others are not relevant.
-  if (http.readyState < 3) {
-    return;
-  } else if (http.readyState == 3 && http.status != 200) {
-    return;
-  }
-
-  if (this._index === undefined)
-    this._index = pad ? 2 : 1;
-
-  if (http.responseText === null)
-    return;
-
-  // TODO: handle
-  var message = http.responseText.split('\n')[this._index];
-  if (!!message && http.readyState == 3) {
-    this._index += 1;
-    this._handleServerMessage(message);
-  } else if (http.readyState == 4) {
-    this._index = 1;
-  }
-
-};
-
-/** Start up websocket communications. */
-Peer.prototype._socketInit = function() {
-  if (!!this._socket)
-    return;
-
-  var wsurl = 'ws://' + this._server + '/ws';
-  if (!!this._id) {
-    wsurl += '?id=' + this._id;
-    if (!!this._apikey)
-      wsurl += '&key=' + this._apikey;
-  } else if (!!this._apikey) {
-    wsurl += '?key=' + this._apikey;
-  }
-  this._socket = new WebSocket(wsurl);
-
+Peer.prototype._startSocket = function() {
   var self = this;
-  this._socket.onmessage = function(event) {
-    self._handleServerMessage(event.data);
-  };
-
-  // Take care of the queue of connections if necessary and make sure Peer knows
-  // socket is open.
-  this._socket.onopen = function() {
-    util.log('Socket open');
-    self._socketOpen = true;
-    var ids = Object.keys(self.connections)
-    for (var i = 0, ii = ids.length; i < ii; i += 1) {
-      self.connections[ids[i]].setSocketOpen();
-    }
-    if (self._id)
-      self._processQueue();
-  };
-};
+  this._socket = new Socket(this._server, this._id, this._apikey);
+  this._socket.on('message', function(data) {
+    self._handleServerJSONMessage(data);
+  });
+  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();
+  });
+  this._socket.on('error', function(error) {
+    util.log(error);
+  });
+  this._socket.start();
+}
 
 
-Peer.prototype._handleServerMessage = function(message) {
-  message = JSON.parse(message);
+Peer.prototype._handleServerJSONMessage = function(message) {
   var peer = message.src;
   var connection = this.connections[peer];
   switch (message.type) {
-    // XHR stream closed by timeout.
-    case 'HTTP-END':
-      util.log('XHR stream timed out.');
-      if (!this._socketOpen)
-        this._startXhrStream();
-      break;
-    // XHR stream closed by socket connect.
-    case 'HTTP-SOCKET':
-        util.log('XHR stream closed, WebSocket connected.');
-        break;
-    case 'HTTP-ERROR':
-        util.log('Something went wrong.');
-        break;
     case 'ID':
       if (!this._id) {
         // If we're just now getting an ID then we may have a queue.
@@ -1028,7 +919,8 @@ Peer.prototype._handleServerMessage = function(message) {
         metadata: message.metadata,
         sdp: message.sdp,
         socketOpen: this._socketOpen,
-        config: this._config
+        config: this._options.config,
+        apikey: this._apikey
       };
       var self = this;
       var connection = new DataConnection(this._id, peer, this._socket, this._httpUrl, function(err, connection) {
@@ -1074,9 +966,7 @@ Peer.prototype._cleanup = function() {
       this.connections[peer].close();
     }
   }
-
-  if (this._socketOpen)
-    this._socket.close();
+  this._socket.close();
 };
 
 /** Listeners for DataConnection events. */
@@ -1104,7 +994,8 @@ Peer.prototype.connect = function(peer, metadata, cb) {
   var options = {
     metadata: metadata,
     socketOpen: this._socketOpen,
-    config: this._config
+    config: this._options.config,
+    apikey: this._apikey
   };
   var connection = new DataConnection(this._id, peer, this._socket, this._httpUrl, cb, options);
   this._attachConnectionListeners(connection);
@@ -1118,26 +1009,25 @@ Peer.prototype.destroy = function() {
 
 
 exports.Peer = Peer;
+/**
+ * A DataChannel PeerConnection between two Peers.
+ */
 function DataConnection(id, peer, socket, httpUrl, cb, options) {
   if (!(this instanceof DataConnection)) return new DataConnection(options);
   EventEmitter.call(this);
 
   options = util.extend({
     config: { 'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }] },
-    socketOpen: false
+    reliable: false
   }, options);
-  this.options = options;
+  this._options = options;
   
   this._id = id;
   this._peer = peer;
   this._originator = (options.sdp === undefined);
   this._cb = cb;
-  this._httpUrl = httpUrl;
-  this.metadata = options.metadata;
-  this._socketOpen = options.socketOpen;
-  this._config = options.config;
+  this._metadata = options.metadata;
 
-  // Set up socket handlers.
   this._socket = socket;
 
   // Firefoxism: connectDataConnection ports.
@@ -1188,7 +1078,7 @@ DataConnection.prototype._setupDataChannel = function() {
   var self = this;
   if (this._originator) {
     util.log('Creating data channel');
-    this._dc = this._pc.createDataChannel(this._peer, { reliable: false });
+    this._dc = this._pc.createDataChannel(this._peer, { reliable: this._options.reliable });
     this._configureDataChannel();
   } else {
     util.log('Listening for data channel');
@@ -1203,8 +1093,8 @@ DataConnection.prototype._setupDataChannel = function() {
 
 /** Starts a PeerConnection and sets up handlers. */
 DataConnection.prototype._startPeerConnection = function() {
-  util.log('Creating RTCPeerConnection: ', this._config);
-  this._pc = new RTCPeerConnection(this._config, { optional:[ { RtpDataChannels: true } ]});
+  util.log('Creating RTCPeerConnection');
+  this._pc = new RTCPeerConnection(this._options.config, { optional:[ { RtpDataChannels: true } ]});
 };
 
 
@@ -1215,35 +1105,16 @@ DataConnection.prototype._setupIce = function() {
   this._pc.onicecandidate = function(evt) {
     if (evt.candidate) {
       util.log('Received ICE candidates');
-      self._handleBroker('ice', JSON.stringify({
+      self._socket.send({
         type: 'CANDIDATE',
         candidate: evt.candidate,
         dst: self._peer,
         src: self._id
-      }));
+      });
     }
   };
 };
 
-DataConnection.prototype._handleBroker = function(type, data) {
-  if (this._socketOpen) {
-    this._socket.send(data);
-  } else {
-    var self = this;
-    var http = new XMLHttpRequest();
-    http.open('post', this._httpUrl + '/' + type, true);
-    http.setRequestHeader('Content-Type', 'application/json');
-    http.onload = function() {
-      // If destination peer is not available...
-      if (http.responseText != 'OK') {
-        util.log('Destination peer not available. Connection closing...');
-        self.close();
-      }
-    }
-    http.send(data);
-  }
-};
-
 // Awaiting update in Firefox spec ***
 /** Sets up DataChannel handlers. 
 DataConnection.prototype._setupDataChannel = function() {
@@ -1332,13 +1203,13 @@ DataConnection.prototype._makeOffer = function() {
     self._pc.setLocalDescription(offer, function() {
       util.log('Set localDescription to offer');
       //self._peerReady = false;
-      self._handleBroker('offer', JSON.stringify({
+      self._socket.send({
         type: 'OFFER',
         sdp: offer,
         dst: self._peer,
         src: self._id,
         metadata: self.metadata
-      }));
+      });
     }, function(err) {
       self._cb('Failed to setLocalDescription');
       util.log('Failed to setLocalDescription, ', err);
@@ -1353,12 +1224,12 @@ DataConnection.prototype._makeAnswer = function() {
     util.log('Created answer');
     self._pc.setLocalDescription(answer, function() {
       util.log('Set localDescription to answer');
-      self._handleBroker('answer', JSON.stringify({
+      self._socket.send({
         type: 'ANSWER',
         src: self._id,
         sdp: answer,
         dst: self._peer
-      }));
+      });
     }, function(err) {
       self._cb('Failed to setLocalDescription');
       util.log('Failed to setLocalDescription, ', err)
@@ -1411,11 +1282,11 @@ DataConnection.prototype._handleDataMessage = function(e) {
 DataConnection.prototype.close = function() {
   this._cleanup();
   var self = this;
-  this._handleBroker('leave', JSON.stringify({
+  this._socket.send({
     type: 'LEAVE',
     dst: self._peer,
     src: self._id,
-  }));
+  });
 };
 
 
@@ -1432,15 +1303,11 @@ DataConnection.prototype.send = function(data) {
   }
 };
 
-
-/**
- * Exposed functions for Peer.
- */
-
-DataConnection.prototype.setSocketOpen = function() {
-  this._socketOpen = true;
+DataConnection.prototype.getMetadata = function() {
+  return this._metadata;
 };
 
+
 DataConnection.prototype.handleSDP = function(message) {
   var sdp = message.sdp;
   if (util.browserisms != 'Firefox') {
@@ -1452,13 +1319,13 @@ DataConnection.prototype.handleSDP = function(message) {
     // Firefoxism
     if (message.type === 'ANSWER' && util.browserisms === 'Firefox') {
       self._pc.connectDataConnection(self.localPort, self.remotePort);
-      self._handleBroker('port', JSON.stringify({
+      self._socket.send({
         type: 'PORT',
         dst: self._peer,
         src: self._id,
         remote: self.localPort,
         local: self.remotePort
-      }));
+      });
     }
   }, function(err) {
     this._cb('Failed to setRemoteDescription');
@@ -1489,5 +1356,207 @@ DataConnection.prototype.handlePort = function(message) {
 };
 
 
+/**
+ * An abstraction on top of WebSockets and XHR streaming to provide fastest
+ * possible connection for peers.
+ */
+function Socket(server, id, key) {
+  if (!(this instanceof Socket)) return new Socket(server, id, key);
+  EventEmitter.call(this);
+
+  this._id = id;
+  this._server = server;
+  this._httpUrl = 'http://' + this._server;
+  this._key = key;
+};
+
+util.inherits(Socket, EventEmitter);
+
+/** Check in with ID or get one from server. */
+Socket.prototype._checkIn = function() {
+  // If no ID provided, get a unique ID from server.
+  var self = this;
+  if (!this._id) {
+    try {
+      var http = new XMLHttpRequest();
+      var url = this._httpUrl + '/id';
+      // Set API key if necessary.
+      if (!!this._key)
+        url += '/' + this._key;
+
+      // 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 (!self._id && http.readyState > 2 && !!http.responseText) {
+          try {
+            var response = JSON.parse(http.responseText.split('\n').shift());
+            if (!!response.id) {
+              self._id = response.id;
+              self._startWebSocket();
+              self.emit('message', { type: 'ID', id: self._id });
+            }
+          } catch (e) {
+            self._startWebSocket();
+          }
+        }
+        self._handleStream(http, true);
+      };
+      http.send(null);
+    } catch(e) {
+      util.log('XMLHttpRequest not available; defaulting to WebSockets');
+      this._startWebSocket();
+    }
+  } else {
+    this._startXhrStream();
+    this._startWebSocket();
+  }
+};
+
+
+/** Start up websocket communications. */
+Socket.prototype._startWebSocket = function() {
+  if (!!this._socket)
+    return;
+
+  var wsurl = 'ws://' + this._server + '/ws';
+  if (!!this._id) {
+    wsurl += '?id=' + this._id;
+    if (!!this._key)
+      wsurl += '&key=' + this._key;
+  } else if (!!this._key) {
+    wsurl += '?key=' + this._key;
+  }
+  this._socket = new WebSocket(wsurl);
+
+  var self = this;
+  this._socket.onmessage = function(event) {
+    try {
+      self.emit('message', JSON.parse(event.data));
+    } catch(e) {
+      util.log('Invalid server message');
+    }
+  };
+
+  // Take care of the queue of connections if necessary and make sure Peer knows
+  // socket is open.
+  this._socket.onopen = function() {
+    util.log('Socket open');
+    if (self._id)
+      self.emit('open');
+  };
+};
+
+
+/** Start XHR streaming. */
+Socket.prototype._startXhrStream = function() {
+  try {
+    var self = this;
+
+    var http = new XMLHttpRequest();
+    var url = this._httpUrl + '/id';
+    // Set API key if necessary.
+    if (!!this._key)
+      url += '/' + this._key;
+
+    http.open('post', url, true);
+    http.setRequestHeader('Content-Type', 'application/json');
+    http.onreadystatechange = function() {
+      self._handleStream(http);
+    };
+    http.send(JSON.stringify({ id: this._id }));
+  } catch(e) {
+    util.log('XMLHttpRequest not available; defaulting to WebSockets');
+  }
+};
+
+
+/** Handles onreadystatechange response as a stream. */
+Socket.prototype._handleStream = function(http, pad) {
+  // 3 and 4 are loading/done state. All others are not relevant.
+  if (http.readyState < 3) {
+    return;
+  } else if (http.readyState == 3 && http.status != 200) {
+    return;
+  }
+
+  if (this._index === undefined)
+    this._index = pad ? 2 : 1;
+
+  if (http.responseText === null)
+    return;
+
+  var message = http.responseText.split('\n')[this._index];
+  if (!!message && http.readyState == 3) {
+    this._index += 1;
+    try {
+      this._handleHTTPErrors(JSON.parse(message));
+    } catch(e) {
+      util.log('Invalid server message');
+    }
+  } else if (http.readyState == 4) {
+    this._index = 1;
+  }
+};
+
+
+Socket.prototype._handleHTTPErrors = function(message) {
+  switch (message.type) {
+    // XHR stream closed by timeout.
+    case 'HTTP-END':
+      util.log('XHR stream timed out.');
+      if (!!this._socket && this._socket.readyState != 1)
+        this._startXhrStream();
+      break;
+    // XHR stream closed by socket connect.
+    case 'HTTP-SOCKET':
+        util.log('XHR stream closed, WebSocket connected.');
+        break;
+    case 'HTTP-ERROR':
+        this.emit('error', 'Something went wrong.');
+        break;
+    default:
+        this.emit('message', message);
+  }
+};
+
+
+
+/** Exposed send for DC & Peer. */
+Socket.prototype.send = function(data) {
+  var type = data.type;
+  message = JSON.stringify(data);
+  if (!type)
+    this.emit('error', 'Invalid message');
+
+  if (!!this._socket && this._socket.readyState == 1) {
+    this._socket.send(message);
+  } else {
+    var self = this;
+    var http = new XMLHttpRequest();
+    var url = this._httpUrl + '/' + type.toLowerCase();
+    // Set API key if necessary.
+    if (!!this._key)
+      url += '/' + this._key;
+
+    http.open('post', url, true);
+    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);
+  }
+};
+
+Socket.prototype.close = function() {
+  if (!!this._socket && this._socket.readyState == 1)
+    this._socket.close();
+};
+
+Socket.prototype.start = function() {
+  this._checkIn();
+};
 
 })(this);

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
dist/peer.min.js


+ 17 - 42
lib/connection.js

@@ -1,10 +1,12 @@
+/**
+ * A DataChannel PeerConnection between two Peers.
+ */
 function DataConnection(id, peer, socket, httpUrl, cb, options) {
   if (!(this instanceof DataConnection)) return new DataConnection(options);
   EventEmitter.call(this);
 
   options = util.extend({
     config: { 'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }] },
-    socketOpen: false,
     reliable: false
   }, options);
   this._options = options;
@@ -13,12 +15,8 @@ function DataConnection(id, peer, socket, httpUrl, cb, options) {
   this._peer = peer;
   this._originator = (options.sdp === undefined);
   this._cb = cb;
-  this._httpUrl = httpUrl;
-  this.metadata = options.metadata;
-  this._socketOpen = options.socketOpen;
-  
+  this._metadata = options.metadata;
 
-  // Set up socket handlers.
   this._socket = socket;
 
   // Firefoxism: connectDataConnection ports.
@@ -96,35 +94,16 @@ DataConnection.prototype._setupIce = function() {
   this._pc.onicecandidate = function(evt) {
     if (evt.candidate) {
       util.log('Received ICE candidates');
-      self._handleBroker('ice', JSON.stringify({
+      self._socket.send({
         type: 'CANDIDATE',
         candidate: evt.candidate,
         dst: self._peer,
         src: self._id
-      }));
+      });
     }
   };
 };
 
-DataConnection.prototype._handleBroker = function(type, data) {
-  if (this._socketOpen) {
-    this._socket.send(data);
-  } else {
-    var self = this;
-    var http = new XMLHttpRequest();
-    http.open('post', this._httpUrl + '/' + type, true);
-    http.setRequestHeader('Content-Type', 'application/json');
-    http.onload = function() {
-      // If destination peer is not available...
-      if (http.responseText != 'OK') {
-        util.log('Destination peer not available. Connection closing...');
-        self.close();
-      }
-    }
-    http.send(data);
-  }
-};
-
 // Awaiting update in Firefox spec ***
 /** Sets up DataChannel handlers. 
 DataConnection.prototype._setupDataChannel = function() {
@@ -213,13 +192,13 @@ DataConnection.prototype._makeOffer = function() {
     self._pc.setLocalDescription(offer, function() {
       util.log('Set localDescription to offer');
       //self._peerReady = false;
-      self._handleBroker('offer', JSON.stringify({
+      self._socket.send({
         type: 'OFFER',
         sdp: offer,
         dst: self._peer,
         src: self._id,
         metadata: self.metadata
-      }));
+      });
     }, function(err) {
       self._cb('Failed to setLocalDescription');
       util.log('Failed to setLocalDescription, ', err);
@@ -234,12 +213,12 @@ DataConnection.prototype._makeAnswer = function() {
     util.log('Created answer');
     self._pc.setLocalDescription(answer, function() {
       util.log('Set localDescription to answer');
-      self._handleBroker('answer', JSON.stringify({
+      self._socket.send({
         type: 'ANSWER',
         src: self._id,
         sdp: answer,
         dst: self._peer
-      }));
+      });
     }, function(err) {
       self._cb('Failed to setLocalDescription');
       util.log('Failed to setLocalDescription, ', err)
@@ -292,11 +271,11 @@ DataConnection.prototype._handleDataMessage = function(e) {
 DataConnection.prototype.close = function() {
   this._cleanup();
   var self = this;
-  this._handleBroker('leave', JSON.stringify({
+  this._socket.send({
     type: 'LEAVE',
     dst: self._peer,
     src: self._id,
-  }));
+  });
 };
 
 
@@ -313,15 +292,11 @@ DataConnection.prototype.send = function(data) {
   }
 };
 
-
-/**
- * Exposed functions for Peer.
- */
-
-DataConnection.prototype.setSocketOpen = function() {
-  this._socketOpen = true;
+DataConnection.prototype.getMetadata = function() {
+  return this._metadata;
 };
 
+
 DataConnection.prototype.handleSDP = function(message) {
   var sdp = message.sdp;
   if (util.browserisms != 'Firefox') {
@@ -333,13 +308,13 @@ DataConnection.prototype.handleSDP = function(message) {
     // Firefoxism
     if (message.type === 'ANSWER' && util.browserisms === 'Firefox') {
       self._pc.connectDataConnection(self.localPort, self.remotePort);
-      self._handleBroker('port', JSON.stringify({
+      self._socket.send({
         type: 'PORT',
         dst: self._peer,
         src: self._id,
         remote: self.localPort,
         local: self.remotePort
-      }));
+      });
     }
   }, function(err) {
     this._cb('Failed to setRemoteDescription');

+ 30 - 138
lib/peer.js

@@ -1,3 +1,6 @@
+/**
+ * A peer who can initiate connections with other peers.
+ */
 function Peer(options) {
   if (!(this instanceof Peer)) return new Peer(options);
   EventEmitter.call(this);
@@ -13,18 +16,18 @@ function Peer(options) {
 
   // TODO: default should be the cloud server.
   this._server = options.host + ':' + options.port;
-  this._httpUrl = 'http://' + this._server;
 
   // Ensure alphanumeric_-
   if (options.id && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(options.id))
     throw new Error('Peer ID can only contain alphanumerics, "_", and "-".');
+  if (options.apikey && !/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(options.apikey))
+    throw new Error('API key can only contain alphanumerics, "_", and "-".');
 
   this._id = options.id;
   // Not used unless using cloud server.
   this._apikey = options.apikey;
 
-  // Check in with the server with ID or get an ID.
-  this._checkIn();
+  this._startSocket();
 
   // Connections for this peer.
   this.connections = {};
@@ -35,142 +38,31 @@ function Peer(options) {
 
 util.inherits(Peer, EventEmitter);
 
-/** Check in with ID or get one from server. */
-Peer.prototype._checkIn = function() {
-  // If no ID provided, get a unique ID from server.
+Peer.prototype._startSocket = function() {
   var self = this;
-  if (!this._id) {
-    try {
-      var http = new XMLHttpRequest();
-      var url = this._httpUrl + '/id';
-      if (!!this._apikey)
-        url += '?key=' + this._apikey;
-
-      // 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 (!self._id && http.readyState > 2 && !!http.responseText) {
-          try {
-            var response = JSON.parse(http.responseText.split('\n').shift());
-            if (!!response.id) {
-              self._id = response.id;
-              self._socketInit();
-              self.emit('ready', self._id);
-              self._processQueue();
-            }
-          } catch (e) {
-            self._socketInit();
-          }
-        }
-        self._handleStream(http, true);
-      };
-      http.send(null);
-    } catch(e) {
-      util.log('XMLHttpRequest not available; defaulting to WebSockets');
-      this._socketInit();
-    }
-  } else {
-    this._startXhrStream();
-    this._socketInit();
-  }
-  // TODO: may need to setInterval in case handleStream is not being called
-  // enough.
-};
-
-Peer.prototype._startXhrStream = function() {
-  try {
-    var http = new XMLHttpRequest();
-    var self = this;
-    http.open('post', this._httpUrl + '/id', true);
-    http.setRequestHeader('Content-Type', 'application/json');
-    http.onreadystatechange = function() {
-      self._handleStream(http);
-    };
-    http.send(JSON.stringify({ id: this._id, key: this._apikey }));
-  } catch(e) {
-    util.log('XMLHttpRequest not available; defaulting to WebSockets');
-  }
-};
-
-/** Handles onreadystatechange response as a stream. */
-Peer.prototype._handleStream = function(http, pad) {
-  // 3 and 4 are loading/done state. All others are not relevant.
-  if (http.readyState < 3) {
-    return;
-  } else if (http.readyState == 3 && http.status != 200) {
-    return;
-  }
-
-  if (this._index === undefined)
-    this._index = pad ? 2 : 1;
-
-  if (http.responseText === null)
-    return;
-
-  // TODO: handle
-  var message = http.responseText.split('\n')[this._index];
-  if (!!message && http.readyState == 3) {
-    this._index += 1;
-    this._handleServerMessage(message);
-  } else if (http.readyState == 4) {
-    this._index = 1;
-  }
-
-};
-
-/** Start up websocket communications. */
-Peer.prototype._socketInit = function() {
-  if (!!this._socket)
-    return;
-
-  var wsurl = 'ws://' + this._server + '/ws';
-  if (!!this._id) {
-    wsurl += '?id=' + this._id;
-    if (!!this._apikey)
-      wsurl += '&key=' + this._apikey;
-  } else if (!!this._apikey) {
-    wsurl += '?key=' + this._apikey;
-  }
-  this._socket = new WebSocket(wsurl);
-
-  var self = this;
-  this._socket.onmessage = function(event) {
-    self._handleServerMessage(event.data);
-  };
-
-  // Take care of the queue of connections if necessary and make sure Peer knows
-  // socket is open.
-  this._socket.onopen = function() {
-    util.log('Socket open');
-    self._socketOpen = true;
-    var ids = Object.keys(self.connections)
-    for (var i = 0, ii = ids.length; i < ii; i += 1) {
-      self.connections[ids[i]].setSocketOpen();
-    }
-    if (self._id)
-      self._processQueue();
-  };
-};
+  this._socket = new Socket(this._server, this._id, this._apikey);
+  this._socket.on('message', function(data) {
+    self._handleServerJSONMessage(data);
+  });
+  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();
+  });
+  this._socket.on('error', function(error) {
+    util.log(error);
+  });
+  this._socket.start();
+}
 
 
-Peer.prototype._handleServerMessage = function(message) {
-  message = JSON.parse(message);
+Peer.prototype._handleServerJSONMessage = function(message) {
   var peer = message.src;
   var connection = this.connections[peer];
   switch (message.type) {
-    // XHR stream closed by timeout.
-    case 'HTTP-END':
-      util.log('XHR stream timed out.');
-      if (!this._socketOpen)
-        this._startXhrStream();
-      break;
-    // XHR stream closed by socket connect.
-    case 'HTTP-SOCKET':
-        util.log('XHR stream closed, WebSocket connected.');
-        break;
-    case 'HTTP-ERROR':
-        util.log('Something went wrong.');
-        break;
     case 'ID':
       if (!this._id) {
         // If we're just now getting an ID then we may have a queue.
@@ -188,7 +80,8 @@ Peer.prototype._handleServerMessage = function(message) {
         metadata: message.metadata,
         sdp: message.sdp,
         socketOpen: this._socketOpen,
-        config: this._options.config
+        config: this._options.config,
+        apikey: this._apikey
       };
       var self = this;
       var connection = new DataConnection(this._id, peer, this._socket, this._httpUrl, function(err, connection) {
@@ -234,9 +127,7 @@ Peer.prototype._cleanup = function() {
       this.connections[peer].close();
     }
   }
-
-  if (this._socketOpen)
-    this._socket.close();
+  this._socket.close();
 };
 
 /** Listeners for DataConnection events. */
@@ -264,7 +155,8 @@ Peer.prototype.connect = function(peer, metadata, cb) {
   var options = {
     metadata: metadata,
     socketOpen: this._socketOpen,
-    config: this._options.config
+    config: this._options.config,
+    apikey: this._apikey
   };
   var connection = new DataConnection(this._id, peer, this._socket, this._httpUrl, cb, options);
   this._attachConnectionListeners(connection);

+ 202 - 0
lib/socket.js

@@ -0,0 +1,202 @@
+/**
+ * An abstraction on top of WebSockets and XHR streaming to provide fastest
+ * possible connection for peers.
+ */
+function Socket(server, id, key) {
+  if (!(this instanceof Socket)) return new Socket(server, id, key);
+  EventEmitter.call(this);
+
+  this._id = id;
+  this._server = server;
+  this._httpUrl = 'http://' + this._server;
+  this._key = key;
+};
+
+util.inherits(Socket, EventEmitter);
+
+/** Check in with ID or get one from server. */
+Socket.prototype._checkIn = function() {
+  // If no ID provided, get a unique ID from server.
+  var self = this;
+  if (!this._id) {
+    try {
+      var http = new XMLHttpRequest();
+      var url = this._httpUrl + '/id';
+      // Set API key if necessary.
+      if (!!this._key)
+        url += '/' + this._key;
+
+      // 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 (!self._id && http.readyState > 2 && !!http.responseText) {
+          try {
+            var response = JSON.parse(http.responseText.split('\n').shift());
+            if (!!response.id) {
+              self._id = response.id;
+              self._startWebSocket();
+              self.emit('message', { type: 'ID', id: self._id });
+            }
+          } catch (e) {
+            self._startWebSocket();
+          }
+        }
+        self._handleStream(http, true);
+      };
+      http.send(null);
+    } catch(e) {
+      util.log('XMLHttpRequest not available; defaulting to WebSockets');
+      this._startWebSocket();
+    }
+  } else {
+    this._startXhrStream();
+    this._startWebSocket();
+  }
+};
+
+
+/** Start up websocket communications. */
+Socket.prototype._startWebSocket = function() {
+  if (!!this._socket)
+    return;
+
+  var wsurl = 'ws://' + this._server + '/ws';
+  if (!!this._id) {
+    wsurl += '?id=' + this._id;
+    if (!!this._key)
+      wsurl += '&key=' + this._key;
+  } else if (!!this._key) {
+    wsurl += '?key=' + this._key;
+  }
+  this._socket = new WebSocket(wsurl);
+
+  var self = this;
+  this._socket.onmessage = function(event) {
+    try {
+      self.emit('message', JSON.parse(event.data));
+    } catch(e) {
+      util.log('Invalid server message');
+    }
+  };
+
+  // Take care of the queue of connections if necessary and make sure Peer knows
+  // socket is open.
+  this._socket.onopen = function() {
+    util.log('Socket open');
+    if (self._id)
+      self.emit('open');
+  };
+};
+
+
+/** Start XHR streaming. */
+Socket.prototype._startXhrStream = function() {
+  try {
+    var self = this;
+
+    var http = new XMLHttpRequest();
+    var url = this._httpUrl + '/id';
+    // Set API key if necessary.
+    if (!!this._key)
+      url += '/' + this._key;
+
+    http.open('post', url, true);
+    http.setRequestHeader('Content-Type', 'application/json');
+    http.onreadystatechange = function() {
+      self._handleStream(http);
+    };
+    http.send(JSON.stringify({ id: this._id }));
+  } catch(e) {
+    util.log('XMLHttpRequest not available; defaulting to WebSockets');
+  }
+};
+
+
+/** Handles onreadystatechange response as a stream. */
+Socket.prototype._handleStream = function(http, pad) {
+  // 3 and 4 are loading/done state. All others are not relevant.
+  if (http.readyState < 3) {
+    return;
+  } else if (http.readyState == 3 && http.status != 200) {
+    return;
+  }
+
+  if (this._index === undefined)
+    this._index = pad ? 2 : 1;
+
+  if (http.responseText === null)
+    return;
+
+  var message = http.responseText.split('\n')[this._index];
+  if (!!message && http.readyState == 3) {
+    this._index += 1;
+    try {
+      this._handleHTTPErrors(JSON.parse(message));
+    } catch(e) {
+      util.log('Invalid server message');
+    }
+  } else if (http.readyState == 4) {
+    this._index = 1;
+  }
+};
+
+
+Socket.prototype._handleHTTPErrors = function(message) {
+  switch (message.type) {
+    // XHR stream closed by timeout.
+    case 'HTTP-END':
+      util.log('XHR stream timed out.');
+      if (!!this._socket && this._socket.readyState != 1)
+        this._startXhrStream();
+      break;
+    // XHR stream closed by socket connect.
+    case 'HTTP-SOCKET':
+        util.log('XHR stream closed, WebSocket connected.');
+        break;
+    case 'HTTP-ERROR':
+        this.emit('error', 'Something went wrong.');
+        break;
+    default:
+        this.emit('message', message);
+  }
+};
+
+
+
+/** Exposed send for DC & Peer. */
+Socket.prototype.send = function(data) {
+  var type = data.type;
+  message = JSON.stringify(data);
+  if (!type)
+    this.emit('error', 'Invalid message');
+
+  if (!!this._socket && this._socket.readyState == 1) {
+    this._socket.send(message);
+  } else {
+    var self = this;
+    var http = new XMLHttpRequest();
+    var url = this._httpUrl + '/' + type.toLowerCase();
+    // Set API key if necessary.
+    if (!!this._key)
+      url += '/' + this._key;
+
+    http.open('post', url, true);
+    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);
+  }
+};
+
+Socket.prototype.close = function() {
+  if (!!this._socket && this._socket.readyState == 1)
+    this._socket.close();
+};
+
+Socket.prototype.start = function() {
+  this._checkIn();
+};

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است