浏览代码

Merge pull request #217 from peers/michelle-reconnect

Reconnection
Michelle Bu 11 年之前
父节点
当前提交
e34b89fee9
共有 8 个文件被更改,包括 224 次插入90 次删除
  1. 95 44
      dist/peer.js
  2. 0 0
      dist/peer.min.js
  3. 39 10
      docs/api.json
  4. 0 0
      docs/index.html
  5. 1 1
      examples/chat.html
  6. 5 5
      lib/negotiator.js
  7. 69 24
      lib/peer.js
  8. 15 6
      lib/socket.js

+ 95 - 44
dist/peer.js

@@ -1052,9 +1052,9 @@ Reliable.higherBandwidthSDP = function(sdp) {
 Reliable.prototype.onmessage = function(msg) {};
 
 exports.Reliable = Reliable;
-exports.RTCSessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
-exports.RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.RTCPeerConnection;
-exports.RTCIceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
+exports.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription;
+exports.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
+exports.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate;
 var defaultConfig = {'iceServers': [{ 'url': 'stun:stun.l.google.com:19302' }]};
 var dataCount = 1;
 
@@ -1390,6 +1390,7 @@ function Peer(id, options) {
     port: util.CLOUD_PORT,
     key: 'peerjs',
     path: '/',
+    token: util.randomToken(),
     config: util.defaultConfig
   }, options);
   this.options = options;
@@ -1442,7 +1443,7 @@ function Peer(id, options) {
 
   // States.
   this.destroyed = false; // Connections have been killed
-  this.disconnected = false; // Connection to PeerServer killed manually but P2P connections still active
+  this.disconnected = false; // Connection to PeerServer killed but P2P connections still active
   this.open = false; // Sockets and such are not yet open.
   //
 
@@ -1451,8 +1452,21 @@ function Peer(id, options) {
   this._lostMessages = {}; // src => [list of messages]
   //
 
-  // Initialize the 'socket' (which is actually a mix of XHR streaming and
-  // websockets.)
+  // Start the server connection
+  this._initializeServerConnection();
+  if (id) {
+    this._initialize(id);
+  } else {
+    this._retrieveId();
+  }
+  //
+};
+
+util.inherits(Peer, EventEmitter);
+
+// Initialize the 'socket' (which is actually a mix of XHR streaming and
+// websockets.)
+Peer.prototype._initializeServerConnection = function() {
   var self = this;
   this.socket = new Socket(this.options.secure, this.options.host, this.options.port, this.options.path, this.options.key);
   this.socket.on('message', function(data) {
@@ -1461,24 +1475,21 @@ function Peer(id, options) {
   this.socket.on('error', function(error) {
     self._abort('socket-error', error);
   });
+  this.socket.on('disconnected', function() {
+    // If we haven't explicitly disconnected, emit error and disconnect.
+    if (!self.disconnected) {
+      self.emitError('network', 'Lost connection to server.')
+      self.disconnect();
+    }
+  });
   this.socket.on('close', function() {
-    if (!self.disconnected) { // If we haven't explicitly disconnected, emit error.
+    // If we haven't explicitly disconnected, emit error.
+    if (!self.disconnected) {
       self._abort('socket-closed', 'Underlying socket is already closed.');
     }
   });
-  //
-
-  // Start the connections
-  if (id) {
-    this._initialize(id);
-  } else {
-    this._retrieveId();
-  }
-  //
 };
 
-util.inherits(Peer, EventEmitter);
-
 /** Get a unique ID from the server via XHR. */
 Peer.prototype._retrieveId = function(cb) {
   var self = this;
@@ -1516,9 +1527,8 @@ Peer.prototype._retrieveId = function(cb) {
 
 /** Initialize a connection with the server. */
 Peer.prototype._initialize = function(id) {
-  var self = this;
   this.id = id;
-  this.socket.start(this.id);
+  this.socket.start(this.id, this.options.token);
 }
 
 /** Handles messages from the server. */
@@ -1549,7 +1559,7 @@ Peer.prototype._handleMessage = function(message) {
       break;
 
     case 'EXPIRE': // The offer sent to a peer has expired without response.
-      this.emit('error', new Error('Could not connect to peer ' + peer));
+      this.emitError('peer-unavailable', 'Could not connect to peer ' + peer);
       break;
     case 'OFFER': // we should consider switching this to CALL/CONNECT, but this is the least breaking option.
       var connectionId = payload.connectionId;
@@ -1639,8 +1649,9 @@ Peer.prototype.connect = function(peer, options) {
   if (this.disconnected) {
     util.warn('You cannot connect to a new Peer because you called '
         + '.disconnect() on this Peer and ended your connection with the'
-        + ' server. You can create a new Peer to reconnect.');
-    this.emit('error', new Error('Cannot connect to new Peer after disconnecting from server.'));
+        + ' server. You can create a new Peer to reconnect, or call reconnect'
+        + ' on this peer if you believe its ID to still be available.');
+    this.emitError('disconnected', 'Cannot connect to new Peer after disconnecting from server.');
     return;
   }
   var connection = new DataConnection(peer, this, options);
@@ -1657,7 +1668,7 @@ Peer.prototype.call = function(peer, stream, options) {
     util.warn('You cannot connect to a new Peer because you called '
         + '.disconnect() on this Peer and ended your connection with the'
         + ' server. You can create a new Peer to reconnect.');
-    this.emit('error', new Error('Cannot connect to new Peer after disconnecting from server.'));
+    this.emitError('disconnected', 'Cannot connect to new Peer after disconnecting from server.');
     return;
   }
   if (!stream) {
@@ -1700,12 +1711,26 @@ Peer.prototype._delayedAbort = function(type, message) {
   });
 }
 
-/** Destroys the Peer and emits an error message. */
+/**
+ * Destroys the Peer and emits an error message.
+ * The Peer is not destroyed if it's in a disconnected state, in which case
+ * it retains its disconnected state and its existing connections.
+ */
 Peer.prototype._abort = function(type, message) {
-  util.error('Aborting. Error:', message);
-  var err = new Error(message);
+  util.error('Aborting!');
+  if (!this.disconnected) {
+    this.destroy();
+  }
+  this.emitError(type, message);
+};
+
+/** Emits a typed error message. */
+Peer.prototype.emitError = function(type, err) {
+  util.error('Error:', err);
+  if (typeof err === 'string') {
+    err = new Error(err);
+  }
   err.type = type;
-  this.destroy();
   this.emit('error', err);
 };
 
@@ -1758,11 +1783,29 @@ Peer.prototype.disconnect = function() {
       if (self.socket) {
         self.socket.close();
       }
+      self.emit('disconnected', self.id);
+      self._lastServerId = self.id;
       self.id = null;
     }
   });
 }
 
+/** Attempts to reconnect with the same ID. */
+Peer.prototype.reconnect = function() {
+  if (this.disconnected && !this.destroyed) {
+    util.log('Attempting reconnection to server with ID ' + this._lastServerId);
+    this._initializeServerConnection();
+    this._initialize(this._lastServerId);
+  } else if (this.destroyed) {
+    throw new Error('This peer cannot reconnect to the server. It has already been destroyed.');
+  } else if (!this.disconnected && !this.open) {
+    // Do nothing. We're still connecting the first time.
+    util.error('In a hurry? We\'re still trying to make the initial connection!');
+  } else {
+    throw new Error('Peer ' + this.id + ' cannot reconnect because it is not disconnected from the server!');
+  }
+};
+
 /**
  * Get a list of available peer IDs. If you're running your own server, you'll
  * want to set allow_discovery: true in the PeerServer options. If you're using
@@ -1930,12 +1973,12 @@ DataConnection.prototype._handleDataMessage = function(e) {
     chunkInfo.count += 1;
 
     if (chunkInfo.total === chunkInfo.count) {
+      // Clean up before making the recursive call to `_handleDataMessage`.
+      delete this._chunkedData[id];
+
       // We've received all the chunks--time to construct the complete data.
       data = new Blob(chunkInfo.data);
       this._handleDataMessage({data: data});
-
-      // We can also just delete the chunks now.
-      delete this._chunkedData[id];
     }
 
     this._chunkedData[id] = chunkInfo;
@@ -2384,11 +2427,11 @@ Negotiator._makeOffer = function(connection) {
         dst: connection.peer
       });
     }, function(err) {
-      connection.provider.emit('error', err);
+      connection.provider.emitError('webrtc', err);
       util.log('Failed to setLocalDescription, ', err);
     });
   }, function(err) {
-    connection.provider.emit('error', err);
+    connection.provider.emitError('webrtc', err);
     util.log('Failed to createOffer, ', err);
   }, connection.options.constraints);
 }
@@ -2416,11 +2459,11 @@ Negotiator._makeAnswer = function(connection) {
         dst: connection.peer
       });
     }, function(err) {
-      connection.provider.emit('error', err);
+      connection.provider.emitError('webrtc', err);
       util.log('Failed to setLocalDescription, ', err);
     });
   }, function(err) {
-    connection.provider.emit('error', err);
+    connection.provider.emitError('webrtc', err);
     util.log('Failed to create answer, ', err);
   });
 }
@@ -2438,7 +2481,7 @@ Negotiator.handleSDP = function(type, connection, sdp) {
       Negotiator._makeAnswer(connection);
     }
   }, function(err) {
-    connection.provider.emit('error', err);
+    connection.provider.emitError('webrtc', err);
     util.log('Failed to setRemoteDescription, ', err);
   });
 }
@@ -2476,12 +2519,11 @@ util.inherits(Socket, EventEmitter);
 
 
 /** Check in with ID or get one from server. */
-Socket.prototype.start = function(id) {  
+Socket.prototype.start = function(id, token) {
   this.id = id;
 
-  var token = util.randomToken();
   this._httpUrl += '/' + id + '/' + token;
-  this._wsUrl += '&id='+id+'&token='+token;
+  this._wsUrl += '&id=' + id + '&token=' + token;
 
   this._startXhrStream();
   this._startWebSocket();
@@ -2499,14 +2541,19 @@ Socket.prototype._startWebSocket = function(id) {
   this._socket = new WebSocket(this._wsUrl);
 
   this._socket.onmessage = function(event) {
-    var data;
     try {
-      data = JSON.parse(event.data);
+      var data = JSON.parse(event.data);
+      self.emit('message', data);
     } catch(e) {
       util.log('Invalid server message', event.data);
       return;
     }
-    self.emit('message', data);
+  };
+
+  this._socket.onclose = function(event) {
+    util.log('Socket closed.');
+    self.disconnected = true;
+    self.emit('disconnected');
   };
 
   // Take care of the queue of connections if necessary and make sure Peer knows
@@ -2536,9 +2583,13 @@ Socket.prototype._startXhrStream = function(n) {
       if (this.readyState == 2 && this.old) {
         this.old.abort();
         delete this.old;
-      }
-      if (this.readyState > 2 && this.status == 200 && this.responseText) {
+      } else if (this.readyState > 2 && this.status === 200 && this.responseText) {
         self._handleStream(this);
+      } else if (this.status !== 200) {
+        // If we get a different status code, likely something went wrong.
+        // Stop streaming.
+        clearTimeout(self._timeout);
+        self.emit('disconnected');
       }
     };
     this._http.send(null);

文件差异内容过多而无法显示
+ 0 - 0
dist/peer.min.js


+ 39 - 10
docs/api.json

@@ -176,7 +176,13 @@
         "name": "'close'",
         "type": "event",
         "snippet": "peer.on('close', function() { ... });",
-        "description": "Emitted when the peer is <a href='#peerdestroy'>destroyed</a>.<span class='tip'>To be extra certain that peers clean up correctly, we recommend calling <code>peer.destroy()</code> on a peer when it is no longer needed.</span>"
+        "description": "Emitted when the peer is <a href='#peerdestroy'>destroyed</a> and can no longer accept or create any new connections. At this time, the peer's connections will all be closed. <span class='tip'>To be extra certain that peers clean up correctly, we recommend calling <code>peer.destroy()</code> on a peer when it is no longer needed.</span>"
+      },
+      {
+        "name": "'disconnected'",
+        "type": "event",
+        "snippet": "peer.on('disconnected', function() { ... });",
+        "description": "Emitted when the peer is disconnected from the signalling server, either <a href='#peerdisconnect'>manually</a> or because the connection to the signalling server was lost. When a peer is disconnected, its existing connections will stay alive, but the peer cannot accept or create any new connections. You can reconnect to the server by calling <a href='#peerreconnect'><code>peer.reconnect()</code></a>."
       },
       {
         "name": "'error'",
@@ -190,6 +196,11 @@
             "tags": ["fatal"],
             "description": "The client's browser does not support some or all WebRTC features that you are trying to use."
           },
+          {
+            "name": "'disconnected'",
+            "type": "Error",
+            "description": "You've already disconnected this peer from the server and can no longer make any new connections on it."
+          },
           {
             "name": "'invalid-id'",
             "type": "Error",
@@ -203,21 +214,20 @@
             "description": "The API key passed into the Peer constructor contains illegal characters or is not in the system (cloud server only)."
           },
           {
-            "name": "'unavailable-id'",
+            "name": "'network'",
             "type": "Error",
-            "tags": ["fatal"],
-            "description": "The ID passed into the Peer constructor is already taken."
+            "description": "Lost or cannot establish a connection to the signalling server."
           },
           {
-            "name": "'ssl-unavailable'",
+            "name": "'peer-unavailable'",
             "type": "Error",
-            "tags": ["fatal"],
-            "description": "PeerJS is being used securely, but the cloud server does not support SSL. Use a custom PeerServer."
+            "description": "The peer you're trying to connect to does not exist."
           },
           {
-            "name": "'server-disconnected'",
+            "name": "'ssl-unavailable'",
             "type": "Error",
-            "description": "You've already disconnected this peer and can no longer make any new connections on it."
+            "tags": ["fatal"],
+            "description": "PeerJS is being used securely, but the cloud server does not support SSL. Use a custom PeerServer."
           },
           {
             "name": "'server-error'",
@@ -236,6 +246,17 @@
             "type": "Error",
             "tags": ["fatal"],
             "description": "The underlying socket closed unexpectedly."
+          },
+          {
+            "name": "'unavailable-id'",
+            "type": "Error",
+            "tags": ["sometimes fatal"],
+            "description": "The ID passed into the Peer constructor is already taken.<span class='warn'>This error is not fatal if your peer has open peer-to-peer connections. This can happen if you attempt to <a href='#peerreconnect'>reconnect</a> a peer that has been <a href='#peerdisconnect'>disconnected from the server</a>, but its old ID has now been taken.</span>"
+          },
+          {
+            "name": "'webrtc'",
+            "type": "Error",
+            "description": "Native WebRTC errors."
           }
         ]
       }
@@ -246,9 +267,17 @@
     "name": "peer.disconnect",
     "type": "method",
     "snippet": "peer.disconnect();",
-    "description": "Close the connection to the server, leaving all existing data and media connections intact. <a href='#peerdisconnected'><code>peer.disconnected</code></a> will be set to <code>true</code>.<span class='warn'>This cannot be undone; the respective peer object will no longer be able to create or receive any connections and its ID will be forfeited on the (cloud) server.</span>"
+    "description": "Close the connection to the server, leaving all existing data and media connections intact. <a href='#peerdisconnected'><code>peer.disconnected</code></a> will be set to <code>true</code> and the <a href='#peeron-disconnected'><code>disconnected</code></a> event will fire.<span class='warn'>This cannot be undone; the respective peer object will no longer be able to create or receive any connections and its ID will be forfeited on the (cloud) server.</span>"
+  },
+
+  {
+    "name": "peer.reconnect",
+    "type": "method",
+    "snippet": "peer.reconnect();",
+    "description": "Attempt to reconnect to the server with the peer's old ID. Only <a href='#peerdisconnect'>disconnected peers</a> can be reconnected. Destroyed peers cannot be reconnected. If the connection fails (as an example, if the peer's old ID is now taken), the peer's existing connections will not close, but any associated errors events will fire."
   },
 
+
   {
     "name": "peer.destroy",
     "type": "method",

文件差异内容过多而无法显示
+ 0 - 0
docs/index.html


+ 1 - 1
examples/chat.html

@@ -8,7 +8,7 @@
 <link href="fancy.css" rel="stylesheet" type="text/css">
 
 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8/jquery.min.js"></script>
-<script type="text/javascript" src="../dist/peer.min.js"></script>
+<script type="text/javascript" src="../dist/peer.js"></script>
 <script>
 // Connect to PeerJS, have server assign an ID instead of providing one
 // Showing off some of the configs available with PeerJS :).

+ 5 - 5
lib/negotiator.js

@@ -223,11 +223,11 @@ Negotiator._makeOffer = function(connection) {
         dst: connection.peer
       });
     }, function(err) {
-      connection.provider.emit('error', err);
+      connection.provider.emitError('webrtc', err);
       util.log('Failed to setLocalDescription, ', err);
     });
   }, function(err) {
-    connection.provider.emit('error', err);
+    connection.provider.emitError('webrtc', err);
     util.log('Failed to createOffer, ', err);
   }, connection.options.constraints);
 }
@@ -255,11 +255,11 @@ Negotiator._makeAnswer = function(connection) {
         dst: connection.peer
       });
     }, function(err) {
-      connection.provider.emit('error', err);
+      connection.provider.emitError('webrtc', err);
       util.log('Failed to setLocalDescription, ', err);
     });
   }, function(err) {
-    connection.provider.emit('error', err);
+    connection.provider.emitError('webrtc', err);
     util.log('Failed to create answer, ', err);
   });
 }
@@ -277,7 +277,7 @@ Negotiator.handleSDP = function(type, connection, sdp) {
       Negotiator._makeAnswer(connection);
     }
   }, function(err) {
-    connection.provider.emit('error', err);
+    connection.provider.emitError('webrtc', err);
     util.log('Failed to setRemoteDescription, ', err);
   });
 }

+ 69 - 24
lib/peer.js

@@ -75,7 +75,7 @@ function Peer(id, options) {
 
   // States.
   this.destroyed = false; // Connections have been killed
-  this.disconnected = false; // Connection to PeerServer killed manually but P2P connections still active
+  this.disconnected = false; // Connection to PeerServer killed but P2P connections still active
   this.open = false; // Sockets and such are not yet open.
   //
 
@@ -84,8 +84,21 @@ function Peer(id, options) {
   this._lostMessages = {}; // src => [list of messages]
   //
 
-  // Initialize the 'socket' (which is actually a mix of XHR streaming and
-  // websockets.)
+  // Start the server connection
+  this._initializeServerConnection();
+  if (id) {
+    this._initialize(id);
+  } else {
+    this._retrieveId();
+  }
+  //
+};
+
+util.inherits(Peer, EventEmitter);
+
+// Initialize the 'socket' (which is actually a mix of XHR streaming and
+// websockets.)
+Peer.prototype._initializeServerConnection = function() {
   var self = this;
   this.socket = new Socket(this.options.secure, this.options.host, this.options.port, this.options.path, this.options.key);
   this.socket.on('message', function(data) {
@@ -94,24 +107,21 @@ function Peer(id, options) {
   this.socket.on('error', function(error) {
     self._abort('socket-error', error);
   });
+  this.socket.on('disconnected', function() {
+    // If we haven't explicitly disconnected, emit error and disconnect.
+    if (!self.disconnected) {
+      self.emitError('network', 'Lost connection to server.')
+      self.disconnect();
+    }
+  });
   this.socket.on('close', function() {
-    if (!self.disconnected) { // If we haven't explicitly disconnected, emit error.
+    // If we haven't explicitly disconnected, emit error.
+    if (!self.disconnected) {
       self._abort('socket-closed', 'Underlying socket is already closed.');
     }
   });
-  //
-
-  // Start the connections
-  if (id) {
-    this._initialize(id);
-  } else {
-    this._retrieveId();
-  }
-  //
 };
 
-util.inherits(Peer, EventEmitter);
-
 /** Get a unique ID from the server via XHR. */
 Peer.prototype._retrieveId = function(cb) {
   var self = this;
@@ -149,7 +159,6 @@ Peer.prototype._retrieveId = function(cb) {
 
 /** Initialize a connection with the server. */
 Peer.prototype._initialize = function(id) {
-  var self = this;
   this.id = id;
   this.socket.start(this.id, this.options.token);
 }
@@ -182,7 +191,7 @@ Peer.prototype._handleMessage = function(message) {
       break;
 
     case 'EXPIRE': // The offer sent to a peer has expired without response.
-      this.emit('error', new Error('Could not connect to peer ' + peer));
+      this.emitError('peer-unavailable', 'Could not connect to peer ' + peer);
       break;
     case 'OFFER': // we should consider switching this to CALL/CONNECT, but this is the least breaking option.
       var connectionId = payload.connectionId;
@@ -272,8 +281,9 @@ Peer.prototype.connect = function(peer, options) {
   if (this.disconnected) {
     util.warn('You cannot connect to a new Peer because you called '
         + '.disconnect() on this Peer and ended your connection with the'
-        + ' server. You can create a new Peer to reconnect.');
-    this.emit('error', new Error('Cannot connect to new Peer after disconnecting from server.'));
+        + ' server. You can create a new Peer to reconnect, or call reconnect'
+        + ' on this peer if you believe its ID to still be available.');
+    this.emitError('disconnected', 'Cannot connect to new Peer after disconnecting from server.');
     return;
   }
   var connection = new DataConnection(peer, this, options);
@@ -290,7 +300,7 @@ Peer.prototype.call = function(peer, stream, options) {
     util.warn('You cannot connect to a new Peer because you called '
         + '.disconnect() on this Peer and ended your connection with the'
         + ' server. You can create a new Peer to reconnect.');
-    this.emit('error', new Error('Cannot connect to new Peer after disconnecting from server.'));
+    this.emitError('disconnected', 'Cannot connect to new Peer after disconnecting from server.');
     return;
   }
   if (!stream) {
@@ -333,12 +343,28 @@ Peer.prototype._delayedAbort = function(type, message) {
   });
 }
 
-/** Destroys the Peer and emits an error message. */
+/**
+ * Destroys the Peer and emits an error message.
+ * The Peer is not destroyed if it's in a disconnected state, in which case
+ * it retains its disconnected state and its existing connections.
+ */
 Peer.prototype._abort = function(type, message) {
-  util.error('Aborting. Error:', message);
-  var err = new Error(message);
+  util.error('Aborting!');
+  if (!this._lastServerId) {
+    this.destroy();
+  } else {
+    this.disconnect();
+  }
+  this.emitError(type, message);
+};
+
+/** Emits a typed error message. */
+Peer.prototype.emitError = function(type, err) {
+  util.error('Error:', err);
+  if (typeof err === 'string') {
+    err = new Error(err);
+  }
   err.type = type;
-  this.destroy();
   this.emit('error', err);
 };
 
@@ -391,11 +417,30 @@ Peer.prototype.disconnect = function() {
       if (self.socket) {
         self.socket.close();
       }
+      self.emit('disconnected', self.id);
+      self._lastServerId = self.id;
       self.id = null;
     }
   });
 }
 
+/** Attempts to reconnect with the same ID. */
+Peer.prototype.reconnect = function() {
+  if (this.disconnected && !this.destroyed) {
+    util.log('Attempting reconnection to server with ID ' + this._lastServerId);
+    this.disconnected = false;
+    this._initializeServerConnection();
+    this._initialize(this._lastServerId);
+  } else if (this.destroyed) {
+    throw new Error('This peer cannot reconnect to the server. It has already been destroyed.');
+  } else if (!this.disconnected && !this.open) {
+    // Do nothing. We're still connecting the first time.
+    util.error('In a hurry? We\'re still trying to make the initial connection!');
+  } else {
+    throw new Error('Peer ' + this.id + ' cannot reconnect because it is not disconnected from the server!');
+  }
+};
+
 /**
  * Get a list of available peer IDs. If you're running your own server, you'll
  * want to set allow_discovery: true in the PeerServer options. If you're using

+ 15 - 6
lib/socket.js

@@ -25,7 +25,7 @@ Socket.prototype.start = function(id, token) {
   this.id = id;
 
   this._httpUrl += '/' + id + '/' + token;
-  this._wsUrl += '&id='+id+'&token='+token;
+  this._wsUrl += '&id=' + id + '&token=' + token;
 
   this._startXhrStream();
   this._startWebSocket();
@@ -43,14 +43,19 @@ Socket.prototype._startWebSocket = function(id) {
   this._socket = new WebSocket(this._wsUrl);
 
   this._socket.onmessage = function(event) {
-    var data;
     try {
-      data = JSON.parse(event.data);
+      var data = JSON.parse(event.data);
+      self.emit('message', data);
     } catch(e) {
       util.log('Invalid server message', event.data);
       return;
     }
-    self.emit('message', data);
+  };
+
+  this._socket.onclose = function(event) {
+    util.log('Socket closed.');
+    self.disconnected = true;
+    self.emit('disconnected');
   };
 
   // Take care of the queue of connections if necessary and make sure Peer knows
@@ -80,9 +85,13 @@ Socket.prototype._startXhrStream = function(n) {
       if (this.readyState == 2 && this.old) {
         this.old.abort();
         delete this.old;
-      }
-      if (this.readyState > 2 && this.status == 200 && this.responseText) {
+      } else if (this.readyState > 2 && this.status === 200 && this.responseText) {
         self._handleStream(this);
+      } else if (this.status !== 200) {
+        // If we get a different status code, likely something went wrong.
+        // Stop streaming.
+        clearTimeout(self._timeout);
+        self.emit('disconnected');
       }
     };
     this._http.send(null);

部分文件因为文件数量过多而无法显示