socket.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /**
  2. * An abstraction on top of WebSockets and XHR streaming to provide fastest
  3. * possible connection for peers.
  4. */
  5. function Socket(server, id, key) {
  6. if (!(this instanceof Socket)) return new Socket(server, id, key);
  7. EventEmitter.call(this);
  8. this._id = id;
  9. this._server = server;
  10. this._httpUrl = 'http://' + this._server;
  11. this._key = key;
  12. this._token = util.randomToken();
  13. };
  14. util.inherits(Socket, EventEmitter);
  15. /** Check in with ID or get one from server. */
  16. Socket.prototype._checkIn = function() {
  17. // If no ID provided, get a unique ID from server.
  18. var self = this;
  19. if (!this._id) {
  20. try {
  21. this._http = new XMLHttpRequest();
  22. var url = this._httpUrl;
  23. // Set API key if necessary.
  24. if (!!this._key) {
  25. url += '/' + this._key;
  26. }
  27. url += '/id?token=' + this._token;
  28. // If there's no ID we need to wait for one before trying to init socket.
  29. this._http.open('get', url, true);
  30. this._http.onreadystatechange = function() {
  31. if (!self._id && self._http.readyState > 2 && !!self._http.responseText) {
  32. try {
  33. var response = JSON.parse(self._http.responseText.split('\n').shift());
  34. if (!!response.id) {
  35. self._id = response.id;
  36. self._startWebSocket();
  37. self.emit('message', { type: 'OPEN', id: self._id });
  38. }
  39. } catch (e) {
  40. self._startWebSocket();
  41. }
  42. }
  43. self._handleStream(true);
  44. };
  45. this._http.send(null);
  46. this._setHTTPTimeout();
  47. } catch(e) {
  48. util.log('XMLHttpRequest not available; defaulting to WebSockets');
  49. this._startWebSocket();
  50. }
  51. } else {
  52. this._startXhrStream();
  53. this._startWebSocket();
  54. }
  55. };
  56. /** Start up websocket communications. */
  57. Socket.prototype._startWebSocket = function() {
  58. if (!!this._socket) {
  59. return;
  60. }
  61. var wsurl = 'ws://' + this._server + '/ws?';
  62. var query = ['token=' + this._token];
  63. if (!!this._id) {
  64. query.push('id=' + this._id);
  65. }
  66. if (!!this._key) {
  67. query.push('key=' + this._key);
  68. }
  69. wsurl += query.join('&');
  70. this._socket = new WebSocket(wsurl);
  71. var self = this;
  72. this._socket.onmessage = function(event) {
  73. var data;
  74. try {
  75. data = JSON.parse(event.data);
  76. } catch(e) {
  77. data = event.data;
  78. }
  79. if (data.constructor == Object) {
  80. self.emit('message', data);
  81. } else {
  82. util.log('Invalid server message', event.data);
  83. }
  84. };
  85. // Take care of the queue of connections if necessary and make sure Peer knows
  86. // socket is open.
  87. this._socket.onopen = function() {
  88. if (!!self._timeout) {
  89. clearTimeout(self._timeout);
  90. self._http.abort();
  91. self._http = null;
  92. }
  93. util.log('Socket open');
  94. if (self._id) {
  95. self.emit('open');
  96. }
  97. };
  98. };
  99. /** Start XHR streaming. */
  100. Socket.prototype._startXhrStream = function() {
  101. try {
  102. var self = this;
  103. this._http = new XMLHttpRequest();
  104. var url = this._httpUrl;
  105. // Set API key if necessary.
  106. if (!!this._key) {
  107. url += '/' + this._key;
  108. }
  109. url += '/id';
  110. this._http.open('post', url, true);
  111. this._http.setRequestHeader('Content-Type', 'application/json');
  112. this._http.onreadystatechange = function() {
  113. self._handleStream();
  114. };
  115. this._http.send(JSON.stringify({ id: this._id, token: this._token }));
  116. this._setHTTPTimeout();
  117. } catch(e) {
  118. util.log('XMLHttpRequest not available; defaulting to WebSockets');
  119. }
  120. };
  121. /** Handles onreadystatechange response as a stream. */
  122. Socket.prototype._handleStream = function(pad) {
  123. // 3 and 4 are loading/done state. All others are not relevant.
  124. if (this._http.readyState < 3) {
  125. return;
  126. } else if (this._http.readyState == 3 && this._http.status != 200) {
  127. return;
  128. }
  129. if (this._index === undefined) {
  130. this._index = pad ? 2 : 1;
  131. }
  132. if (this._http.responseText === null) {
  133. return;
  134. }
  135. var message = this._http.responseText.split('\n')[this._index];
  136. if (!!message && this._http.readyState == 3) {
  137. this._index += 1;
  138. try {
  139. this._handleHTTPErrors(JSON.parse(message));
  140. } catch(e) {
  141. util.log('Invalid server message', message);
  142. }
  143. } else if (this._http.readyState == 4) {
  144. this._index = 1;
  145. }
  146. };
  147. Socket.prototype._setHTTPTimeout = function() {
  148. this._timeout = setTimeout(function() {
  149. var temp_http = self._http;
  150. if (!self._wsOpen()) {
  151. self._startXhrStream();
  152. }
  153. temp_http.abort();
  154. }, 30000);
  155. };
  156. Socket.prototype._handleHTTPErrors = function(message) {
  157. switch (message.type) {
  158. case 'HTTP-ERROR':
  159. util.log('XHR ended in error.');
  160. break;
  161. default:
  162. this.emit('message', message);
  163. }
  164. };
  165. /** Exposed send for DC & Peer. */
  166. Socket.prototype.send = function(data) {
  167. var type = data.type;
  168. var message;
  169. if (!type) {
  170. this.emit('error', 'Invalid message');
  171. }
  172. if (this._wsOpen()) {
  173. message = JSON.stringify(data);
  174. this._socket.send(message);
  175. } else {
  176. data['token'] = this._token;
  177. message = JSON.stringify(data);
  178. var self = this;
  179. var http = new XMLHttpRequest();
  180. var url = this._httpUrl;
  181. // Set API key if necessary.
  182. if (!!this._key) {
  183. url += '/' + this._key;
  184. }
  185. url += '/' + type.toLowerCase();
  186. http.open('post', url, true);
  187. http.setRequestHeader('Content-Type', 'application/json');
  188. http.send(message);
  189. }
  190. };
  191. Socket.prototype.close = function() {
  192. if (!!this._wsOpen()) {
  193. this._socket.close();
  194. }
  195. };
  196. Socket.prototype._wsOpen = function() {
  197. return !!this._socket && this._socket.readyState == 1;
  198. };
  199. Socket.prototype.start = function() {
  200. this._checkIn();
  201. };