socket.js 4.6 KB

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