socket.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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(server, id, key);
  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://' + this._server + '/ws?key='+key+'id='+id+'token='+token;
  12. this._index = 1;
  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(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. self._http.abort();
  43. self._http = null;
  44. }
  45. util.log('Socket open');
  46. };
  47. };
  48. /** Start XHR streaming. */
  49. Socket.prototype._startXhrStream = function() {
  50. try {
  51. var self = this;
  52. this._http = new XMLHttpRequest();
  53. this._http.open('post', this._httpUrl + '/id', true);
  54. this._http.onreadystatechange = function() {
  55. if (!!self._http.old) {
  56. self._http.old.abort();
  57. delete self._http.old;
  58. }
  59. if (self._http.readyState > 2 && self._http.status == 200 && !!self._http.responseText) {
  60. self._handleStream();
  61. }
  62. };
  63. this._http.send(null);
  64. this._setHTTPTimeout();
  65. } catch(e) {
  66. util.log('XMLHttpRequest not available; defaulting to WebSockets');
  67. }
  68. };
  69. /** Handles onreadystatechange response as a stream. */
  70. Socket.prototype._handleStream = function() {
  71. var self = this;
  72. // 3 and 4 are loading/done state. All others are not relevant.
  73. var message = this._http.responseText.split('\n')[this._index];
  74. if (!!message) {
  75. this._index += 1;
  76. try {
  77. message = JSON.parse(message);
  78. } catch(e) {
  79. util.log('Invalid server message', message);
  80. return;
  81. }
  82. self.emit('message', message);
  83. }
  84. };
  85. Socket.prototype._setHTTPTimeout = function() {
  86. var self = this;
  87. this._timeout = setTimeout(function() {
  88. var old = self._http;
  89. if (!self._wsOpen()) {
  90. self._index = 1;
  91. self._startXhrStream();
  92. self._http.old = old;
  93. } else {
  94. old.abort();
  95. }
  96. }, 30000);
  97. };
  98. /** Exposed send for DC & Peer. */
  99. Socket.prototype.send = function(data) {
  100. if (!data.type) {
  101. this.emit('error', 'Invalid message');
  102. }
  103. message = JSON.stringify(data);
  104. if (this._wsOpen()) {
  105. this._socket.send(message);
  106. } else {
  107. var http = new XMLHttpRequest();
  108. var url = this._httpUrl + '/' + type.toLowerCase();
  109. http.open('post', url, true);
  110. http.setRequestHeader('Content-Type', 'application/json');
  111. http.send(message);
  112. }
  113. };
  114. Socket.prototype.close = function() {
  115. if (!!this._wsOpen()) {
  116. this._socket.close();
  117. }
  118. };
  119. Socket.prototype._wsOpen = function() {
  120. return !!this._socket && this._socket.readyState == 1;
  121. };