api.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. 'use strict';
  2. var Stream = require('stream').Stream,
  3. util = require('util'),
  4. driver = require('websocket-driver'),
  5. EventTarget = require('./api/event_target'),
  6. Event = require('./api/event');
  7. var API = function(options) {
  8. options = options || {};
  9. driver.validateOptions(options, ['headers', 'extensions', 'maxLength', 'ping', 'proxy', 'tls', 'ca']);
  10. this.readable = this.writable = true;
  11. var headers = options.headers;
  12. if (headers) {
  13. for (var name in headers) this._driver.setHeader(name, headers[name]);
  14. }
  15. var extensions = options.extensions;
  16. if (extensions) {
  17. [].concat(extensions).forEach(this._driver.addExtension, this._driver);
  18. }
  19. this._ping = options.ping;
  20. this._pingId = 0;
  21. this.readyState = API.CONNECTING;
  22. this.bufferedAmount = 0;
  23. this.protocol = '';
  24. this.url = this._driver.url;
  25. this.version = this._driver.version;
  26. var self = this;
  27. this._driver.on('open', function(e) { self._open() });
  28. this._driver.on('message', function(e) { self._receiveMessage(e.data) });
  29. this._driver.on('close', function(e) { self._beginClose(e.reason, e.code) });
  30. this._driver.on('error', function(error) {
  31. self._emitError(error.message);
  32. });
  33. this.on('error', function() {});
  34. this._driver.messages.on('drain', function() {
  35. self.emit('drain');
  36. });
  37. if (this._ping)
  38. this._pingTimer = setInterval(function() {
  39. self._pingId += 1;
  40. self.ping(self._pingId.toString());
  41. }, this._ping * 1000);
  42. this._configureStream();
  43. if (!this._proxy) {
  44. this._stream.pipe(this._driver.io);
  45. this._driver.io.pipe(this._stream);
  46. }
  47. };
  48. util.inherits(API, Stream);
  49. API.CONNECTING = 0;
  50. API.OPEN = 1;
  51. API.CLOSING = 2;
  52. API.CLOSED = 3;
  53. API.CLOSE_TIMEOUT = 30000;
  54. var instance = {
  55. write: function(data) {
  56. return this.send(data);
  57. },
  58. end: function(data) {
  59. if (data !== undefined) this.send(data);
  60. this.close();
  61. },
  62. pause: function() {
  63. return this._driver.messages.pause();
  64. },
  65. resume: function() {
  66. return this._driver.messages.resume();
  67. },
  68. send: function(data) {
  69. if (this.readyState > API.OPEN) return false;
  70. if (!(data instanceof Buffer)) data = String(data);
  71. return this._driver.messages.write(data);
  72. },
  73. ping: function(message, callback) {
  74. if (this.readyState > API.OPEN) return false;
  75. return this._driver.ping(message, callback);
  76. },
  77. close: function(code, reason) {
  78. if (code === undefined) code = 1000;
  79. if (reason === undefined) reason = '';
  80. if (code !== 1000 && (code < 3000 || code > 4999))
  81. throw new Error("Failed to execute 'close' on WebSocket: " +
  82. "The code must be either 1000, or between 3000 and 4999. " +
  83. code + " is neither.");
  84. if (this.readyState < API.CLOSING) {
  85. var self = this;
  86. this._closeTimer = setTimeout(function() {
  87. self._beginClose('', 1006);
  88. }, API.CLOSE_TIMEOUT);
  89. }
  90. if (this.readyState !== API.CLOSED) this.readyState = API.CLOSING;
  91. this._driver.close(reason, code);
  92. },
  93. _configureStream: function() {
  94. var self = this;
  95. this._stream.setTimeout(0);
  96. this._stream.setNoDelay(true);
  97. ['close', 'end'].forEach(function(event) {
  98. this._stream.on(event, function() { self._finalizeClose() });
  99. }, this);
  100. this._stream.on('error', function(error) {
  101. self._emitError('Network error: ' + self.url + ': ' + error.message);
  102. self._finalizeClose();
  103. });
  104. },
  105. _open: function() {
  106. if (this.readyState !== API.CONNECTING) return;
  107. this.readyState = API.OPEN;
  108. this.protocol = this._driver.protocol || '';
  109. var event = new Event('open');
  110. event.initEvent('open', false, false);
  111. this.dispatchEvent(event);
  112. },
  113. _receiveMessage: function(data) {
  114. if (this.readyState > API.OPEN) return false;
  115. if (this.readable) this.emit('data', data);
  116. var event = new Event('message', { data: data });
  117. event.initEvent('message', false, false);
  118. this.dispatchEvent(event);
  119. },
  120. _emitError: function(message) {
  121. if (this.readyState >= API.CLOSING) return;
  122. var event = new Event('error', { message: message });
  123. event.initEvent('error', false, false);
  124. this.dispatchEvent(event);
  125. },
  126. _beginClose: function(reason, code) {
  127. if (this.readyState === API.CLOSED) return;
  128. this.readyState = API.CLOSING;
  129. this._closeParams = [reason, code];
  130. if (this._stream) {
  131. this._stream.destroy();
  132. if (!this._stream.readable) this._finalizeClose();
  133. }
  134. },
  135. _finalizeClose: function() {
  136. if (this.readyState === API.CLOSED) return;
  137. this.readyState = API.CLOSED;
  138. if (this._closeTimer) clearTimeout(this._closeTimer);
  139. if (this._pingTimer) clearInterval(this._pingTimer);
  140. if (this._stream) this._stream.end();
  141. if (this.readable) this.emit('end');
  142. this.readable = this.writable = false;
  143. var reason = this._closeParams ? this._closeParams[0] : '',
  144. code = this._closeParams ? this._closeParams[1] : 1006;
  145. var event = new Event('close', { code: code, reason: reason });
  146. event.initEvent('close', false, false);
  147. this.dispatchEvent(event);
  148. }
  149. };
  150. for (var method in instance) API.prototype[method] = instance[method];
  151. for (var key in EventTarget) API.prototype[key] = EventTarget[key];
  152. module.exports = API;