socket.js 5.4 KB

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