You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

229 lines
4.2 KiB

  1. /**
  2. * Module dependencies.
  3. */
  4. var Transport = require('../transport');
  5. var parser = require('engine.io-parser');
  6. var parseqs = require('parseqs');
  7. var inherit = require('component-inherit');
  8. var debug = require('debug')('engine.io-client:websocket');
  9. /**
  10. * `ws` exposes a WebSocket-compatible interface in
  11. * Node, or the `WebSocket` or `MozWebSocket` globals
  12. * in the browser.
  13. */
  14. var WebSocket = require('ws');
  15. /**
  16. * Module exports.
  17. */
  18. module.exports = WS;
  19. /**
  20. * WebSocket transport constructor.
  21. *
  22. * @api {Object} connection options
  23. * @api public
  24. */
  25. function WS(opts){
  26. var forceBase64 = (opts && opts.forceBase64);
  27. if (forceBase64) {
  28. this.supportsBinary = false;
  29. }
  30. Transport.call(this, opts);
  31. }
  32. /**
  33. * Inherits from Transport.
  34. */
  35. inherit(WS, Transport);
  36. /**
  37. * Transport name.
  38. *
  39. * @api public
  40. */
  41. WS.prototype.name = 'websocket';
  42. /*
  43. * WebSockets support binary
  44. */
  45. WS.prototype.supportsBinary = true;
  46. /**
  47. * Opens socket.
  48. *
  49. * @api private
  50. */
  51. WS.prototype.doOpen = function(){
  52. if (!this.check()) {
  53. // let probe timeout
  54. return;
  55. }
  56. var self = this;
  57. var uri = this.uri();
  58. var protocols = void(0);
  59. var opts = { agent: this.agent };
  60. this.ws = new WebSocket(uri, protocols, opts);
  61. if (this.ws.binaryType === undefined) {
  62. this.supportsBinary = false;
  63. }
  64. this.ws.binaryType = 'arraybuffer';
  65. this.addEventListeners();
  66. };
  67. /**
  68. * Adds event listeners to the socket
  69. *
  70. * @api private
  71. */
  72. WS.prototype.addEventListeners = function(){
  73. var self = this;
  74. this.ws.onopen = function(){
  75. self.onOpen();
  76. };
  77. this.ws.onclose = function(){
  78. self.onClose();
  79. };
  80. this.ws.onmessage = function(ev){
  81. self.onData(ev.data);
  82. };
  83. this.ws.onerror = function(e){
  84. self.onError('websocket error', e);
  85. };
  86. };
  87. /**
  88. * Override `onData` to use a timer on iOS.
  89. * See: https://gist.github.com/mloughran/2052006
  90. *
  91. * @api private
  92. */
  93. if ('undefined' != typeof navigator
  94. && /iPad|iPhone|iPod/i.test(navigator.userAgent)) {
  95. WS.prototype.onData = function(data){
  96. var self = this;
  97. setTimeout(function(){
  98. Transport.prototype.onData.call(self, data);
  99. }, 0);
  100. };
  101. }
  102. /**
  103. * Writes data to socket.
  104. *
  105. * @param {Array} array of packets.
  106. * @api private
  107. */
  108. WS.prototype.write = function(packets){
  109. var self = this;
  110. this.writable = false;
  111. // encodePacket efficient as it uses WS framing
  112. // no need for encodePayload
  113. for (var i = 0, l = packets.length; i < l; i++) {
  114. parser.encodePacket(packets[i], this.supportsBinary, function(data) {
  115. //Sometimes the websocket has already been closed but the browser didn't
  116. //have a chance of informing us about it yet, in that case send will
  117. //throw an error
  118. try {
  119. self.ws.send(data);
  120. } catch (e){
  121. debug('websocket closed before onclose event');
  122. }
  123. });
  124. }
  125. function ondrain() {
  126. self.writable = true;
  127. self.emit('drain');
  128. }
  129. // fake drain
  130. // defer to next tick to allow Socket to clear writeBuffer
  131. setTimeout(ondrain, 0);
  132. };
  133. /**
  134. * Called upon close
  135. *
  136. * @api private
  137. */
  138. WS.prototype.onClose = function(){
  139. Transport.prototype.onClose.call(this);
  140. };
  141. /**
  142. * Closes socket.
  143. *
  144. * @api private
  145. */
  146. WS.prototype.doClose = function(){
  147. if (typeof this.ws !== 'undefined') {
  148. this.ws.close();
  149. }
  150. };
  151. /**
  152. * Generates uri for connection.
  153. *
  154. * @api private
  155. */
  156. WS.prototype.uri = function(){
  157. var query = this.query || {};
  158. var schema = this.secure ? 'wss' : 'ws';
  159. var port = '';
  160. // avoid port if default for schema
  161. if (this.port && (('wss' == schema && this.port != 443)
  162. || ('ws' == schema && this.port != 80))) {
  163. port = ':' + this.port;
  164. }
  165. // append timestamp to URI
  166. if (this.timestampRequests) {
  167. query[this.timestampParam] = +new Date;
  168. }
  169. // communicate binary support capabilities
  170. if (!this.supportsBinary) {
  171. query.b64 = 1;
  172. }
  173. query = parseqs.encode(query);
  174. // prepend ? to query
  175. if (query.length) {
  176. query = '?' + query;
  177. }
  178. return schema + '://' + this.hostname + port + this.path + query;
  179. };
  180. /**
  181. * Feature detection for WebSocket.
  182. *
  183. * @return {Boolean} whether this transport is available.
  184. * @api public
  185. */
  186. WS.prototype.check = function(){
  187. return !!WebSocket && !('__initialize' in WebSocket && this.name === WS.prototype.name);
  188. };