socket.js 5.5 KB

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