socket.js 4.6 KB

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