socket.js 5.1 KB

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