socket.js 4.4 KB

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