PageRenderTime 44ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/socket.js

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