socket.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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 + '/'
  14. + (path ? path + '/' : '') + key;
  15. this._wsUrl = wsProtocol + host + ':' + port + '/'
  16. + (path ? 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) {
  21. this.id = id;
  22. var token = util.randomToken();
  23. this._httpUrl += '/' + id + '/' + token;
  24. this._wsUrl += '&id='+id+'&token='+token;
  25. this._startXhrStream();
  26. this._startWebSocket();
  27. }
  28. /** Start up websocket communications. */
  29. Socket.prototype._startWebSocket = function(id) {
  30. var self = this;
  31. if (this._socket) {
  32. return;
  33. }
  34. this._socket = new WebSocket(this._wsUrl);
  35. this._socket.onmessage = function(event) {
  36. var data;
  37. try {
  38. data = JSON.parse(event.data);
  39. } catch(e) {
  40. util.log('Invalid server message', event.data);
  41. return;
  42. }
  43. self.emit('message', data);
  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. }
  72. if (this.readyState > 2 && this.status == 200 && this.responseText) {
  73. self._handleStream(this);
  74. }
  75. };
  76. this._http.send(null);
  77. this._setHTTPTimeout();
  78. } catch(e) {
  79. util.log('XMLHttpRequest not available; defaulting to WebSockets');
  80. }
  81. }
  82. /** Handles onreadystatechange response as a stream. */
  83. Socket.prototype._handleStream = function(http) {
  84. // 3 and 4 are loading/done state. All others are not relevant.
  85. var messages = http.responseText.split('\n');
  86. // Check to see if anything needs to be processed on buffer.
  87. if (http._buffer) {
  88. while (http._buffer.length > 0) {
  89. var index = http._buffer.shift();
  90. var bufferedMessage = messages[index];
  91. try {
  92. bufferedMessage = JSON.parse(bufferedMessage);
  93. } catch(e) {
  94. http._buffer.shift(index);
  95. break;
  96. }
  97. this.emit('message', bufferedMessage);
  98. }
  99. }
  100. var message = messages[http._index];
  101. if (message) {
  102. http._index += 1;
  103. // Buffering--this message is incomplete and we'll get to it next time.
  104. // This checks if the httpResponse ended in a `\n`, in which case the last
  105. // element of messages should be the empty string.
  106. if (http._index === messages.length) {
  107. if (!http._buffer) {
  108. http._buffer = [];
  109. }
  110. http._buffer.push(http._index - 1);
  111. } else {
  112. try {
  113. message = JSON.parse(message);
  114. } catch(e) {
  115. util.log('Invalid server message', message);
  116. return;
  117. }
  118. this.emit('message', message);
  119. }
  120. }
  121. }
  122. Socket.prototype._setHTTPTimeout = function() {
  123. var self = this;
  124. this._timeout = setTimeout(function() {
  125. var old = self._http;
  126. if (!self._wsOpen()) {
  127. self._startXhrStream(old._streamIndex + 1);
  128. self._http.old = old;
  129. } else {
  130. old.abort();
  131. }
  132. }, 25000);
  133. }
  134. /** Is the websocket currently open? */
  135. Socket.prototype._wsOpen = function() {
  136. return this._socket && this._socket.readyState == 1;
  137. }
  138. /** Send queued messages. */
  139. Socket.prototype._sendQueuedMessages = function() {
  140. for (var i = 0, ii = this._queue.length; i < ii; i += 1) {
  141. this.send(this._queue[i]);
  142. }
  143. }
  144. /** Exposed send for DC & Peer. */
  145. Socket.prototype.send = function(data) {
  146. if (this.disconnected) {
  147. return;
  148. }
  149. // If we didn't get an ID yet, we can't yet send anything so we should queue
  150. // up these messages.
  151. if (!this.id) {
  152. this._queue.push(data);
  153. return;
  154. }
  155. if (!data.type) {
  156. this.emit('error', 'Invalid message');
  157. return;
  158. }
  159. var message = JSON.stringify(data);
  160. if (this._wsOpen()) {
  161. this._socket.send(message);
  162. } else {
  163. var http = new XMLHttpRequest();
  164. var url = this._httpUrl + '/' + data.type.toLowerCase();
  165. http.open('post', url, true);
  166. http.setRequestHeader('Content-Type', 'application/json');
  167. http.send(message);
  168. }
  169. }
  170. Socket.prototype.close = function() {
  171. if (!this.disconnected && this._wsOpen()) {
  172. this._socket.close();
  173. this.disconnected = true;
  174. }
  175. }