socket.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. /**
  2. * An abstraction on top of WebSockets and XHR streaming to provide fastest
  3. * possible connection for peers.
  4. */
  5. function Socket(secure, host, port, path, key) {
  6. if (!(this instanceof Socket)) return new Socket(secure, host, port, path, key);
  7. EventEmitter.call(this);
  8. // Disconnected manually.
  9. this.disconnected = false;
  10. this._queue = [];
  11. var httpProtocol = secure ? 'https://' : 'http://';
  12. var wsProtocol = secure ? 'wss://' : 'ws://';
  13. this._httpUrl = httpProtocol + host + ':' + port + path + key;
  14. this._wsUrl = wsProtocol + host + ':' + port + path + 'peerjs?key=' + key;
  15. }
  16. util.inherits(Socket, EventEmitter);
  17. /** Check in with ID or get one from server. */
  18. Socket.prototype.start = function(id, token) {
  19. this.id = id;
  20. this._httpUrl += '/' + id + '/' + token;
  21. this._wsUrl += '&id=' + id + '&token=' + token;
  22. this._startXhrStream();
  23. this._startWebSocket();
  24. }
  25. /** Start up websocket communications. */
  26. Socket.prototype._startWebSocket = function(id) {
  27. var self = this;
  28. if (this._socket) {
  29. return;
  30. }
  31. this._socket = new WebSocket(this._wsUrl);
  32. this._socket.onmessage = function(event) {
  33. try {
  34. var data = JSON.parse(event.data);
  35. } catch(e) {
  36. util.log('Invalid server message', event.data);
  37. return;
  38. }
  39. self.emit('message', data);
  40. };
  41. this._socket.onclose = function(event) {
  42. util.log('Socket closed.');
  43. self.disconnected = true;
  44. self.emit('disconnected');
  45. };
  46. // Take care of the queue of connections if necessary and make sure Peer knows
  47. // socket is open.
  48. this._socket.onopen = function() {
  49. if (self._timeout) {
  50. clearTimeout(self._timeout);
  51. setTimeout(function(){
  52. self._http.abort();
  53. self._http = null;
  54. }, 5000);
  55. }
  56. self._sendQueuedMessages();
  57. util.log('Socket open');
  58. };
  59. }
  60. /** Start XHR streaming. */
  61. Socket.prototype._startXhrStream = function(n) {
  62. try {
  63. var self = this;
  64. this._http = new XMLHttpRequest();
  65. this._http._index = 1;
  66. this._http._streamIndex = n || 0;
  67. this._http.open('post', this._httpUrl + '/id?i=' + this._http._streamIndex, true);
  68. this._http.onerror = function() {
  69. // If we get an error, likely something went wrong.
  70. // Stop streaming.
  71. clearTimeout(self._timeout);
  72. self.emit('disconnected');
  73. }
  74. this._http.onreadystatechange = function() {
  75. if (this.readyState == 2 && this.old) {
  76. this.old.abort();
  77. delete this.old;
  78. } else if (this.readyState > 2 && this.status === 200 && this.responseText) {
  79. self._handleStream(this);
  80. }
  81. };
  82. this._http.send(null);
  83. this._setHTTPTimeout();
  84. } catch(e) {
  85. util.log('XMLHttpRequest not available; defaulting to WebSockets');
  86. }
  87. }
  88. /** Handles onreadystatechange response as a stream. */
  89. Socket.prototype._handleStream = function(http) {
  90. // 3 and 4 are loading/done state. All others are not relevant.
  91. var messages = http.responseText.split('\n');
  92. // Check to see if anything needs to be processed on buffer.
  93. if (http._buffer) {
  94. while (http._buffer.length > 0) {
  95. var index = http._buffer.shift();
  96. var bufferedMessage = messages[index];
  97. try {
  98. bufferedMessage = JSON.parse(bufferedMessage);
  99. } catch(e) {
  100. http._buffer.shift(index);
  101. break;
  102. }
  103. this.emit('message', bufferedMessage);
  104. }
  105. }
  106. var message = messages[http._index];
  107. if (message) {
  108. http._index += 1;
  109. // Buffering--this message is incomplete and we'll get to it next time.
  110. // This checks if the httpResponse ended in a `\n`, in which case the last
  111. // element of messages should be the empty string.
  112. if (http._index === messages.length) {
  113. if (!http._buffer) {
  114. http._buffer = [];
  115. }
  116. http._buffer.push(http._index - 1);
  117. } else {
  118. try {
  119. message = JSON.parse(message);
  120. } catch(e) {
  121. util.log('Invalid server message', message);
  122. return;
  123. }
  124. this.emit('message', message);
  125. }
  126. }
  127. }
  128. Socket.prototype._setHTTPTimeout = function() {
  129. var self = this;
  130. this._timeout = setTimeout(function() {
  131. var old = self._http;
  132. if (!self._wsOpen()) {
  133. self._startXhrStream(old._streamIndex + 1);
  134. self._http.old = old;
  135. } else {
  136. old.abort();
  137. }
  138. }, 25000);
  139. }
  140. /** Is the websocket currently open? */
  141. Socket.prototype._wsOpen = function() {
  142. return this._socket && this._socket.readyState == 1;
  143. }
  144. /** Send queued messages. */
  145. Socket.prototype._sendQueuedMessages = function() {
  146. for (var i = 0, ii = this._queue.length; i < ii; i += 1) {
  147. this.send(this._queue[i]);
  148. }
  149. }
  150. /** Exposed send for DC & Peer. */
  151. Socket.prototype.send = function(data) {
  152. if (this.disconnected) {
  153. return;
  154. }
  155. // If we didn't get an ID yet, we can't yet send anything so we should queue
  156. // up these messages.
  157. if (!this.id) {
  158. this._queue.push(data);
  159. return;
  160. }
  161. if (!data.type) {
  162. this.emit('error', 'Invalid message');
  163. return;
  164. }
  165. var message = JSON.stringify(data);
  166. if (this._wsOpen()) {
  167. this._socket.send(message);
  168. } else {
  169. var http = new XMLHttpRequest();
  170. var url = this._httpUrl + '/' + data.type.toLowerCase();
  171. http.open('post', url, true);
  172. http.setRequestHeader('Content-Type', 'application/json');
  173. http.send(message);
  174. }
  175. }
  176. Socket.prototype.close = function() {
  177. if (!this.disconnected && this._wsOpen()) {
  178. this._socket.close();
  179. this.disconnected = true;
  180. }
  181. }