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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. 'use strict';
  2. const assert = require('bsert');
  3. const EventEmitter = require('events');
  4. const UWS = require('uws');
  5. const UWSClient = UWS;
  6. const UWSServer = UWS.Server;
  7. const noop = () => {};
  8. let server = null;
  9. // Make UWS look like Faye.
  10. class API extends EventEmitter {
  11. constructor() {
  12. super();
  13. this.ws = null;
  14. this.readable = true;
  15. this.writable = true;
  16. this.url = '';
  17. this.binaryType = 'arraybuffer';
  18. this.version = 'hybi-13';
  19. this.protocol = '';
  20. this.extensions = '';
  21. this.bufferedAmount = 0;
  22. this.onopen = noop;
  23. this.onclose = noop;
  24. this.onerror = noop;
  25. this.onmessage = noop;
  26. this.on('error', noop);
  27. }
  28. _open(ws, outbound) {
  29. assert(ws);
  30. this.ws = ws;
  31. if (outbound) {
  32. ws.onopen = () => {
  33. this.onopen();
  34. this.emit('open');
  35. };
  36. }
  37. ws.onclose = ({code, reason}) => {
  38. const event = {
  39. code: code >>> 0,
  40. reason: String(reason)
  41. };
  42. this.onclose(event);
  43. this.emit('close', event);
  44. };
  45. ws.onerror = ({message}) => {
  46. if (message === 'uWs client connection error')
  47. message = `Network error: ${this.url}: connect ECONNREFUSED`;
  48. const event = {
  49. message: String(message)
  50. };
  51. this.onerror(event);
  52. this.emit('error', event);
  53. };
  54. ws.onmessage = ({data}) => {
  55. // UWS is zero copy.
  56. if (typeof data !== 'string') {
  57. assert(data instanceof ArrayBuffer);
  58. const ab = Buffer.from(data);
  59. const raw = Buffer.allocUnsafe(ab.length);
  60. ab.copy(raw, 0);
  61. data = raw;
  62. }
  63. const event = { data };
  64. this.onmessage(event);
  65. this.emit('message', event);
  66. };
  67. }
  68. write(data) {
  69. return this.send(data);
  70. }
  71. end(data) {
  72. if (data !== undefined)
  73. this.write(data);
  74. this.close();
  75. }
  76. pause() {
  77. ;
  78. }
  79. resume() {
  80. ;
  81. }
  82. send(data) {
  83. if (!this.ws)
  84. return true;
  85. this.ws.send(data);
  86. return true;
  87. }
  88. get readyState() {
  89. if (!this.ws)
  90. return API.CONNECTING;
  91. return this.ws.readyState;
  92. }
  93. ping(msg, callback) {
  94. if (!this.ws)
  95. return false;
  96. if (this.readyState > API.OPEN)
  97. return false;
  98. this.ws.ping(msg);
  99. if (callback)
  100. callback();
  101. return true;
  102. }
  103. close() {
  104. if (!this.ws)
  105. return;
  106. this.ws.close();
  107. }
  108. static isWebSocket(req, socket) {
  109. if (socket) {
  110. if (socket._isNative && (!server || server.serverGroup))
  111. return true;
  112. }
  113. if (req.method !== 'GET')
  114. return false;
  115. const connection = req.headers.connection;
  116. if (!connection)
  117. return false;
  118. const conn = connection.toLowerCase().split(/ *, */);
  119. if (conn.indexOf('upgrade') === -1)
  120. return false;
  121. const upgrade = req.headers.upgrade;
  122. if (!upgrade)
  123. return false;
  124. if (upgrade.toLowerCase() !== 'websocket')
  125. return false;
  126. const key = req.headers['sec-websocket-key'];
  127. if (!key)
  128. return false;
  129. if (key.length !== 24)
  130. return false;
  131. if (socket && (!socket.ssl || socket._parent)) {
  132. const {ssl, _handle, _parent} = socket;
  133. const handle = ssl ? _parent._handle : _handle;
  134. if (!handle)
  135. return false;
  136. }
  137. return true;
  138. }
  139. }
  140. API.CONNECTING = 0;
  141. API.OPEN = 1;
  142. API.CLOSING = 2;
  143. API.CLOSED = 3;
  144. API.CLOSE_TIMEOUT = 3000;
  145. class Client extends API {
  146. constructor(url) {
  147. super();
  148. assert(typeof url === 'string');
  149. url = url.replace(/^http:/, 'ws:');
  150. url = url.replace(/^https:/, 'wss:');
  151. if (url.indexOf('://') === -1)
  152. url = `ws://${url}`;
  153. url = url.replace('://localhost', '://127.0.0.1');
  154. this.url = url;
  155. this._open(new UWSClient(url), true);
  156. }
  157. }
  158. class WebSocket extends API {
  159. constructor(req, socket, body) {
  160. super();
  161. assert(req);
  162. this.url = req.url;
  163. if (!server)
  164. server = new UWSServer({ noServer: true });
  165. server.handleUpgrade(req, socket, body, (ws) => {
  166. setImmediate(() => {
  167. this._open(ws, false);
  168. this.onopen();
  169. this.emit('open');
  170. });
  171. });
  172. }
  173. }
  174. WebSocket.Client = Client;
  175. module.exports = WebSocket;