opal 1.6.1 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +17 -0
  3. data/CHANGELOG.md +35 -1
  4. data/Gemfile +1 -0
  5. data/HACKING.md +47 -26
  6. data/benchmark/benchmarks +415 -103
  7. data/benchmark/bm_call_overhead.yml +28 -0
  8. data/benchmark/run.rb +61 -40
  9. data/docs/cdp_common.json +3364 -0
  10. data/docs/cdp_common.md +18 -0
  11. data/docs/{headless_chrome.md → headless_browsers.md} +31 -12
  12. data/lib/opal/ast/builder.rb +1 -1
  13. data/lib/opal/builder.rb +6 -1
  14. data/lib/opal/builder_processors.rb +5 -3
  15. data/lib/opal/cache.rb +1 -7
  16. data/lib/opal/cli_options.rb +72 -58
  17. data/lib/opal/cli_runners/chrome.rb +47 -9
  18. data/lib/opal/cli_runners/chrome_cdp_interface.rb +238 -112
  19. data/lib/opal/cli_runners/compiler.rb +146 -13
  20. data/lib/opal/cli_runners/deno.rb +32 -0
  21. data/lib/opal/cli_runners/firefox.rb +350 -0
  22. data/lib/opal/cli_runners/firefox_cdp_interface.rb +212 -0
  23. data/lib/opal/cli_runners/node_modules/.bin/chrome-remote-interface.cmd +17 -0
  24. data/lib/opal/cli_runners/node_modules/.bin/chrome-remote-interface.ps1 +28 -0
  25. data/lib/opal/cli_runners/node_modules/.package-lock.json +41 -0
  26. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/LICENSE +1 -1
  27. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/README.md +322 -182
  28. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/bin/client.js +99 -114
  29. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/chrome-remote-interface.js +1 -11
  30. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/index.js +16 -11
  31. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/api.js +41 -33
  32. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/chrome.js +224 -214
  33. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/devtools.js +71 -191
  34. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/external-request.js +26 -6
  35. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/protocol.json +20788 -9049
  36. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/websocket-wrapper.js +10 -3
  37. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/package.json +59 -123
  38. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/webpack.config.js +25 -32
  39. data/lib/opal/cli_runners/node_modules/commander/History.md +298 -0
  40. data/lib/opal/cli_runners/node_modules/commander/LICENSE +22 -0
  41. data/lib/opal/cli_runners/node_modules/commander/Readme.md +217 -61
  42. data/lib/opal/cli_runners/node_modules/commander/index.js +431 -145
  43. data/lib/opal/cli_runners/node_modules/commander/package.json +16 -79
  44. data/lib/opal/cli_runners/node_modules/ws/README.md +334 -98
  45. data/lib/opal/cli_runners/node_modules/ws/browser.js +8 -0
  46. data/lib/opal/cli_runners/node_modules/ws/index.js +5 -10
  47. data/lib/opal/cli_runners/node_modules/ws/lib/buffer-util.js +129 -0
  48. data/lib/opal/cli_runners/node_modules/ws/lib/constants.js +10 -0
  49. data/lib/opal/cli_runners/node_modules/ws/lib/event-target.js +184 -0
  50. data/lib/opal/cli_runners/node_modules/ws/lib/extension.js +223 -0
  51. data/lib/opal/cli_runners/node_modules/ws/lib/limiter.js +55 -0
  52. data/lib/opal/cli_runners/node_modules/ws/lib/permessage-deflate.js +518 -0
  53. data/lib/opal/cli_runners/node_modules/ws/lib/receiver.js +607 -0
  54. data/lib/opal/cli_runners/node_modules/ws/lib/sender.js +409 -0
  55. data/lib/opal/cli_runners/node_modules/ws/lib/stream.js +180 -0
  56. data/lib/opal/cli_runners/node_modules/ws/lib/validation.js +104 -0
  57. data/lib/opal/cli_runners/node_modules/ws/lib/websocket-server.js +447 -0
  58. data/lib/opal/cli_runners/node_modules/ws/lib/websocket.js +1195 -0
  59. data/lib/opal/cli_runners/node_modules/ws/package.json +40 -106
  60. data/lib/opal/cli_runners/package-lock.json +62 -0
  61. data/lib/opal/cli_runners/package.json +1 -1
  62. data/lib/opal/cli_runners.rb +26 -4
  63. data/lib/opal/nodes/args/prepare_post_args.rb +2 -2
  64. data/lib/opal/nodes/def.rb +8 -8
  65. data/lib/opal/nodes/iter.rb +12 -12
  66. data/lib/opal/nodes/logic.rb +1 -1
  67. data/lib/opal/nodes/masgn.rb +2 -2
  68. data/lib/opal/parser/with_ruby_lexer.rb +1 -1
  69. data/lib/opal/paths.rb +14 -0
  70. data/lib/opal/rewriter.rb +2 -0
  71. data/lib/opal/rewriters/forward_args.rb +52 -4
  72. data/lib/opal/rewriters/targeted_patches.rb +94 -0
  73. data/lib/opal/version.rb +1 -1
  74. data/opal/corelib/basic_object.rb +1 -1
  75. data/opal/corelib/boolean.rb +2 -2
  76. data/opal/corelib/class.rb +11 -0
  77. data/opal/corelib/constants.rb +3 -3
  78. data/opal/corelib/enumerable.rb +4 -0
  79. data/opal/corelib/enumerator.rb +1 -1
  80. data/opal/corelib/hash.rb +2 -2
  81. data/opal/corelib/helpers.rb +1 -1
  82. data/opal/corelib/kernel.rb +3 -3
  83. data/opal/corelib/method.rb +1 -1
  84. data/opal/corelib/module.rb +29 -8
  85. data/opal/corelib/proc.rb +7 -5
  86. data/opal/corelib/runtime.js +141 -78
  87. data/opal/corelib/set.rb +252 -0
  88. data/opal/corelib/string.rb +2 -1
  89. data/opal/corelib/time.rb +2 -2
  90. data/opal/opal.rb +1 -0
  91. data/opal.gemspec +1 -0
  92. data/spec/filters/bugs/array.rb +22 -13
  93. data/spec/filters/bugs/base64.rb +5 -5
  94. data/spec/filters/bugs/basicobject.rb +16 -8
  95. data/spec/filters/bugs/bigdecimal.rb +161 -160
  96. data/spec/filters/bugs/binding.rb +10 -10
  97. data/spec/filters/bugs/class.rb +8 -8
  98. data/spec/filters/bugs/complex.rb +2 -1
  99. data/spec/filters/bugs/date.rb +79 -81
  100. data/spec/filters/bugs/datetime.rb +29 -29
  101. data/spec/filters/bugs/delegate.rb +1 -3
  102. data/spec/filters/bugs/encoding.rb +69 -69
  103. data/spec/filters/bugs/enumerable.rb +22 -20
  104. data/spec/filters/bugs/enumerator.rb +88 -85
  105. data/spec/filters/bugs/exception.rb +46 -40
  106. data/spec/filters/bugs/file.rb +32 -32
  107. data/spec/filters/bugs/float.rb +26 -21
  108. data/spec/filters/bugs/freeze.rb +88 -0
  109. data/spec/filters/bugs/hash.rb +39 -38
  110. data/spec/filters/bugs/integer.rb +57 -44
  111. data/spec/filters/bugs/io.rb +1 -1
  112. data/spec/filters/bugs/kernel.rb +349 -269
  113. data/spec/filters/bugs/language.rb +220 -188
  114. data/spec/filters/bugs/main.rb +5 -3
  115. data/spec/filters/bugs/marshal.rb +38 -38
  116. data/spec/filters/bugs/math.rb +2 -1
  117. data/spec/filters/bugs/method.rb +73 -62
  118. data/spec/filters/bugs/module.rb +163 -143
  119. data/spec/filters/bugs/numeric.rb +6 -6
  120. data/spec/filters/bugs/objectspace.rb +16 -16
  121. data/spec/filters/bugs/openstruct.rb +1 -1
  122. data/spec/filters/bugs/pack_unpack.rb +51 -51
  123. data/spec/filters/bugs/pathname.rb +7 -7
  124. data/spec/filters/bugs/proc.rb +63 -63
  125. data/spec/filters/bugs/random.rb +7 -6
  126. data/spec/filters/bugs/range.rb +12 -9
  127. data/spec/filters/bugs/rational.rb +8 -7
  128. data/spec/filters/bugs/regexp.rb +49 -48
  129. data/spec/filters/bugs/ruby-32.rb +56 -0
  130. data/spec/filters/bugs/set.rb +30 -30
  131. data/spec/filters/bugs/singleton.rb +4 -4
  132. data/spec/filters/bugs/string.rb +187 -99
  133. data/spec/filters/bugs/stringio.rb +7 -0
  134. data/spec/filters/bugs/stringscanner.rb +68 -68
  135. data/spec/filters/bugs/struct.rb +11 -9
  136. data/spec/filters/bugs/symbol.rb +1 -1
  137. data/spec/filters/bugs/time.rb +78 -63
  138. data/spec/filters/bugs/trace_point.rb +4 -4
  139. data/spec/filters/bugs/unboundmethod.rb +32 -17
  140. data/spec/filters/bugs/warnings.rb +8 -12
  141. data/spec/filters/unsupported/array.rb +24 -107
  142. data/spec/filters/unsupported/basicobject.rb +12 -12
  143. data/spec/filters/unsupported/bignum.rb +27 -52
  144. data/spec/filters/unsupported/class.rb +1 -2
  145. data/spec/filters/unsupported/delegator.rb +3 -3
  146. data/spec/filters/unsupported/enumerable.rb +2 -9
  147. data/spec/filters/unsupported/enumerator.rb +2 -11
  148. data/spec/filters/unsupported/file.rb +1 -1
  149. data/spec/filters/unsupported/float.rb +28 -47
  150. data/spec/filters/unsupported/hash.rb +8 -14
  151. data/spec/filters/unsupported/integer.rb +75 -91
  152. data/spec/filters/unsupported/kernel.rb +17 -35
  153. data/spec/filters/unsupported/language.rb +11 -19
  154. data/spec/filters/unsupported/marshal.rb +22 -41
  155. data/spec/filters/unsupported/matchdata.rb +28 -52
  156. data/spec/filters/unsupported/math.rb +1 -1
  157. data/spec/filters/unsupported/privacy.rb +229 -285
  158. data/spec/filters/unsupported/range.rb +1 -5
  159. data/spec/filters/unsupported/regexp.rb +40 -66
  160. data/spec/filters/unsupported/set.rb +2 -2
  161. data/spec/filters/unsupported/singleton.rb +4 -4
  162. data/spec/filters/unsupported/string.rb +305 -508
  163. data/spec/filters/unsupported/struct.rb +3 -4
  164. data/spec/filters/unsupported/symbol.rb +15 -18
  165. data/spec/filters/unsupported/thread.rb +1 -7
  166. data/spec/filters/unsupported/time.rb +159 -202
  167. data/spec/filters/unsupported/usage_of_files.rb +170 -259
  168. data/spec/lib/builder_spec.rb +4 -4
  169. data/spec/lib/rewriters/forward_args_spec.rb +32 -12
  170. data/spec/mspec-opal/runner.rb +2 -0
  171. data/spec/ruby_specs +4 -0
  172. data/stdlib/deno/base.rb +28 -0
  173. data/stdlib/deno/file.rb +340 -0
  174. data/stdlib/{headless_chrome.rb → headless_browser/base.rb} +1 -1
  175. data/stdlib/headless_browser/file.rb +15 -0
  176. data/stdlib/headless_browser.rb +4 -0
  177. data/stdlib/native.rb +1 -1
  178. data/stdlib/nodejs/file.rb +5 -0
  179. data/stdlib/opal/platform.rb +8 -6
  180. data/stdlib/opal-platform.rb +14 -8
  181. data/stdlib/set.rb +1 -258
  182. data/tasks/benchmarking.rake +62 -19
  183. data/tasks/performance.rake +1 -1
  184. data/tasks/testing.rake +5 -3
  185. data/test/nodejs/test_file.rb +29 -10
  186. data/test/opal/http_server.rb +28 -11
  187. data/test/opal/unsupported_and_bugs.rb +2 -1
  188. metadata +89 -50
  189. data/lib/opal/cli_runners/node_modules/ultron/LICENSE +0 -22
  190. data/lib/opal/cli_runners/node_modules/ultron/index.js +0 -138
  191. data/lib/opal/cli_runners/node_modules/ultron/package.json +0 -112
  192. data/lib/opal/cli_runners/node_modules/ws/SECURITY.md +0 -33
  193. data/lib/opal/cli_runners/node_modules/ws/lib/BufferUtil.fallback.js +0 -56
  194. data/lib/opal/cli_runners/node_modules/ws/lib/BufferUtil.js +0 -15
  195. data/lib/opal/cli_runners/node_modules/ws/lib/ErrorCodes.js +0 -28
  196. data/lib/opal/cli_runners/node_modules/ws/lib/EventTarget.js +0 -158
  197. data/lib/opal/cli_runners/node_modules/ws/lib/Extensions.js +0 -69
  198. data/lib/opal/cli_runners/node_modules/ws/lib/PerMessageDeflate.js +0 -339
  199. data/lib/opal/cli_runners/node_modules/ws/lib/Receiver.js +0 -520
  200. data/lib/opal/cli_runners/node_modules/ws/lib/Sender.js +0 -438
  201. data/lib/opal/cli_runners/node_modules/ws/lib/Validation.fallback.js +0 -9
  202. data/lib/opal/cli_runners/node_modules/ws/lib/Validation.js +0 -17
  203. data/lib/opal/cli_runners/node_modules/ws/lib/WebSocket.js +0 -705
  204. data/lib/opal/cli_runners/node_modules/ws/lib/WebSocketServer.js +0 -336
  205. data/spec/filters/bugs/boolean.rb +0 -3
  206. data/spec/filters/bugs/matrix.rb +0 -3
  207. data/spec/filters/unsupported/fixnum.rb +0 -15
  208. data/spec/filters/unsupported/freeze.rb +0 -102
  209. data/spec/filters/unsupported/pathname.rb +0 -4
  210. data/spec/filters/unsupported/proc.rb +0 -4
  211. data/spec/filters/unsupported/random.rb +0 -5
  212. data/spec/filters/unsupported/taint.rb +0 -162
@@ -0,0 +1,1195 @@
1
+ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Readable$" }] */
2
+
3
+ 'use strict';
4
+
5
+ const EventEmitter = require('events');
6
+ const https = require('https');
7
+ const http = require('http');
8
+ const net = require('net');
9
+ const tls = require('tls');
10
+ const { randomBytes, createHash } = require('crypto');
11
+ const { Readable } = require('stream');
12
+ const { URL } = require('url');
13
+
14
+ const PerMessageDeflate = require('./permessage-deflate');
15
+ const Receiver = require('./receiver');
16
+ const Sender = require('./sender');
17
+ const {
18
+ BINARY_TYPES,
19
+ EMPTY_BUFFER,
20
+ GUID,
21
+ kStatusCode,
22
+ kWebSocket,
23
+ NOOP
24
+ } = require('./constants');
25
+ const { addEventListener, removeEventListener } = require('./event-target');
26
+ const { format, parse } = require('./extension');
27
+ const { toBuffer } = require('./buffer-util');
28
+
29
+ const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
30
+ const protocolVersions = [8, 13];
31
+ const closeTimeout = 30 * 1000;
32
+
33
+ /**
34
+ * Class representing a WebSocket.
35
+ *
36
+ * @extends EventEmitter
37
+ */
38
+ class WebSocket extends EventEmitter {
39
+ /**
40
+ * Create a new `WebSocket`.
41
+ *
42
+ * @param {(String|URL)} address The URL to which to connect
43
+ * @param {(String|String[])} [protocols] The subprotocols
44
+ * @param {Object} [options] Connection options
45
+ */
46
+ constructor(address, protocols, options) {
47
+ super();
48
+
49
+ this._binaryType = BINARY_TYPES[0];
50
+ this._closeCode = 1006;
51
+ this._closeFrameReceived = false;
52
+ this._closeFrameSent = false;
53
+ this._closeMessage = '';
54
+ this._closeTimer = null;
55
+ this._extensions = {};
56
+ this._protocol = '';
57
+ this._readyState = WebSocket.CONNECTING;
58
+ this._receiver = null;
59
+ this._sender = null;
60
+ this._socket = null;
61
+
62
+ if (address !== null) {
63
+ this._bufferedAmount = 0;
64
+ this._isServer = false;
65
+ this._redirects = 0;
66
+
67
+ if (Array.isArray(protocols)) {
68
+ protocols = protocols.join(', ');
69
+ } else if (typeof protocols === 'object' && protocols !== null) {
70
+ options = protocols;
71
+ protocols = undefined;
72
+ }
73
+
74
+ initAsClient(this, address, protocols, options);
75
+ } else {
76
+ this._isServer = true;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * This deviates from the WHATWG interface since ws doesn't support the
82
+ * required default "blob" type (instead we define a custom "nodebuffer"
83
+ * type).
84
+ *
85
+ * @type {String}
86
+ */
87
+ get binaryType() {
88
+ return this._binaryType;
89
+ }
90
+
91
+ set binaryType(type) {
92
+ if (!BINARY_TYPES.includes(type)) return;
93
+
94
+ this._binaryType = type;
95
+
96
+ //
97
+ // Allow to change `binaryType` on the fly.
98
+ //
99
+ if (this._receiver) this._receiver._binaryType = type;
100
+ }
101
+
102
+ /**
103
+ * @type {Number}
104
+ */
105
+ get bufferedAmount() {
106
+ if (!this._socket) return this._bufferedAmount;
107
+
108
+ return this._socket._writableState.length + this._sender._bufferedBytes;
109
+ }
110
+
111
+ /**
112
+ * @type {String}
113
+ */
114
+ get extensions() {
115
+ return Object.keys(this._extensions).join();
116
+ }
117
+
118
+ /**
119
+ * @type {Function}
120
+ */
121
+ /* istanbul ignore next */
122
+ get onclose() {
123
+ return undefined;
124
+ }
125
+
126
+ /* istanbul ignore next */
127
+ set onclose(listener) {}
128
+
129
+ /**
130
+ * @type {Function}
131
+ */
132
+ /* istanbul ignore next */
133
+ get onerror() {
134
+ return undefined;
135
+ }
136
+
137
+ /* istanbul ignore next */
138
+ set onerror(listener) {}
139
+
140
+ /**
141
+ * @type {Function}
142
+ */
143
+ /* istanbul ignore next */
144
+ get onopen() {
145
+ return undefined;
146
+ }
147
+
148
+ /* istanbul ignore next */
149
+ set onopen(listener) {}
150
+
151
+ /**
152
+ * @type {Function}
153
+ */
154
+ /* istanbul ignore next */
155
+ get onmessage() {
156
+ return undefined;
157
+ }
158
+
159
+ /* istanbul ignore next */
160
+ set onmessage(listener) {}
161
+
162
+ /**
163
+ * @type {String}
164
+ */
165
+ get protocol() {
166
+ return this._protocol;
167
+ }
168
+
169
+ /**
170
+ * @type {Number}
171
+ */
172
+ get readyState() {
173
+ return this._readyState;
174
+ }
175
+
176
+ /**
177
+ * @type {String}
178
+ */
179
+ get url() {
180
+ return this._url;
181
+ }
182
+
183
+ /**
184
+ * Set up the socket and the internal resources.
185
+ *
186
+ * @param {(net.Socket|tls.Socket)} socket The network socket between the
187
+ * server and client
188
+ * @param {Buffer} head The first packet of the upgraded stream
189
+ * @param {Number} [maxPayload=0] The maximum allowed message size
190
+ * @private
191
+ */
192
+ setSocket(socket, head, maxPayload) {
193
+ const receiver = new Receiver(
194
+ this.binaryType,
195
+ this._extensions,
196
+ this._isServer,
197
+ maxPayload
198
+ );
199
+
200
+ this._sender = new Sender(socket, this._extensions);
201
+ this._receiver = receiver;
202
+ this._socket = socket;
203
+
204
+ receiver[kWebSocket] = this;
205
+ socket[kWebSocket] = this;
206
+
207
+ receiver.on('conclude', receiverOnConclude);
208
+ receiver.on('drain', receiverOnDrain);
209
+ receiver.on('error', receiverOnError);
210
+ receiver.on('message', receiverOnMessage);
211
+ receiver.on('ping', receiverOnPing);
212
+ receiver.on('pong', receiverOnPong);
213
+
214
+ socket.setTimeout(0);
215
+ socket.setNoDelay();
216
+
217
+ if (head.length > 0) socket.unshift(head);
218
+
219
+ socket.on('close', socketOnClose);
220
+ socket.on('data', socketOnData);
221
+ socket.on('end', socketOnEnd);
222
+ socket.on('error', socketOnError);
223
+
224
+ this._readyState = WebSocket.OPEN;
225
+ this.emit('open');
226
+ }
227
+
228
+ /**
229
+ * Emit the `'close'` event.
230
+ *
231
+ * @private
232
+ */
233
+ emitClose() {
234
+ if (!this._socket) {
235
+ this._readyState = WebSocket.CLOSED;
236
+ this.emit('close', this._closeCode, this._closeMessage);
237
+ return;
238
+ }
239
+
240
+ if (this._extensions[PerMessageDeflate.extensionName]) {
241
+ this._extensions[PerMessageDeflate.extensionName].cleanup();
242
+ }
243
+
244
+ this._receiver.removeAllListeners();
245
+ this._readyState = WebSocket.CLOSED;
246
+ this.emit('close', this._closeCode, this._closeMessage);
247
+ }
248
+
249
+ /**
250
+ * Start a closing handshake.
251
+ *
252
+ * +----------+ +-----------+ +----------+
253
+ * - - -|ws.close()|-->|close frame|-->|ws.close()|- - -
254
+ * | +----------+ +-----------+ +----------+ |
255
+ * +----------+ +-----------+ |
256
+ * CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING
257
+ * +----------+ +-----------+ |
258
+ * | | | +---+ |
259
+ * +------------------------+-->|fin| - - - -
260
+ * | +---+ | +---+
261
+ * - - - - -|fin|<---------------------+
262
+ * +---+
263
+ *
264
+ * @param {Number} [code] Status code explaining why the connection is closing
265
+ * @param {String} [data] A string explaining why the connection is closing
266
+ * @public
267
+ */
268
+ close(code, data) {
269
+ if (this.readyState === WebSocket.CLOSED) return;
270
+ if (this.readyState === WebSocket.CONNECTING) {
271
+ const msg = 'WebSocket was closed before the connection was established';
272
+ return abortHandshake(this, this._req, msg);
273
+ }
274
+
275
+ if (this.readyState === WebSocket.CLOSING) {
276
+ if (
277
+ this._closeFrameSent &&
278
+ (this._closeFrameReceived || this._receiver._writableState.errorEmitted)
279
+ ) {
280
+ this._socket.end();
281
+ }
282
+
283
+ return;
284
+ }
285
+
286
+ this._readyState = WebSocket.CLOSING;
287
+ this._sender.close(code, data, !this._isServer, (err) => {
288
+ //
289
+ // This error is handled by the `'error'` listener on the socket. We only
290
+ // want to know if the close frame has been sent here.
291
+ //
292
+ if (err) return;
293
+
294
+ this._closeFrameSent = true;
295
+
296
+ if (
297
+ this._closeFrameReceived ||
298
+ this._receiver._writableState.errorEmitted
299
+ ) {
300
+ this._socket.end();
301
+ }
302
+ });
303
+
304
+ //
305
+ // Specify a timeout for the closing handshake to complete.
306
+ //
307
+ this._closeTimer = setTimeout(
308
+ this._socket.destroy.bind(this._socket),
309
+ closeTimeout
310
+ );
311
+ }
312
+
313
+ /**
314
+ * Send a ping.
315
+ *
316
+ * @param {*} [data] The data to send
317
+ * @param {Boolean} [mask] Indicates whether or not to mask `data`
318
+ * @param {Function} [cb] Callback which is executed when the ping is sent
319
+ * @public
320
+ */
321
+ ping(data, mask, cb) {
322
+ if (this.readyState === WebSocket.CONNECTING) {
323
+ throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
324
+ }
325
+
326
+ if (typeof data === 'function') {
327
+ cb = data;
328
+ data = mask = undefined;
329
+ } else if (typeof mask === 'function') {
330
+ cb = mask;
331
+ mask = undefined;
332
+ }
333
+
334
+ if (typeof data === 'number') data = data.toString();
335
+
336
+ if (this.readyState !== WebSocket.OPEN) {
337
+ sendAfterClose(this, data, cb);
338
+ return;
339
+ }
340
+
341
+ if (mask === undefined) mask = !this._isServer;
342
+ this._sender.ping(data || EMPTY_BUFFER, mask, cb);
343
+ }
344
+
345
+ /**
346
+ * Send a pong.
347
+ *
348
+ * @param {*} [data] The data to send
349
+ * @param {Boolean} [mask] Indicates whether or not to mask `data`
350
+ * @param {Function} [cb] Callback which is executed when the pong is sent
351
+ * @public
352
+ */
353
+ pong(data, mask, cb) {
354
+ if (this.readyState === WebSocket.CONNECTING) {
355
+ throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
356
+ }
357
+
358
+ if (typeof data === 'function') {
359
+ cb = data;
360
+ data = mask = undefined;
361
+ } else if (typeof mask === 'function') {
362
+ cb = mask;
363
+ mask = undefined;
364
+ }
365
+
366
+ if (typeof data === 'number') data = data.toString();
367
+
368
+ if (this.readyState !== WebSocket.OPEN) {
369
+ sendAfterClose(this, data, cb);
370
+ return;
371
+ }
372
+
373
+ if (mask === undefined) mask = !this._isServer;
374
+ this._sender.pong(data || EMPTY_BUFFER, mask, cb);
375
+ }
376
+
377
+ /**
378
+ * Send a data message.
379
+ *
380
+ * @param {*} data The message to send
381
+ * @param {Object} [options] Options object
382
+ * @param {Boolean} [options.compress] Specifies whether or not to compress
383
+ * `data`
384
+ * @param {Boolean} [options.binary] Specifies whether `data` is binary or
385
+ * text
386
+ * @param {Boolean} [options.fin=true] Specifies whether the fragment is the
387
+ * last one
388
+ * @param {Boolean} [options.mask] Specifies whether or not to mask `data`
389
+ * @param {Function} [cb] Callback which is executed when data is written out
390
+ * @public
391
+ */
392
+ send(data, options, cb) {
393
+ if (this.readyState === WebSocket.CONNECTING) {
394
+ throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
395
+ }
396
+
397
+ if (typeof options === 'function') {
398
+ cb = options;
399
+ options = {};
400
+ }
401
+
402
+ if (typeof data === 'number') data = data.toString();
403
+
404
+ if (this.readyState !== WebSocket.OPEN) {
405
+ sendAfterClose(this, data, cb);
406
+ return;
407
+ }
408
+
409
+ const opts = {
410
+ binary: typeof data !== 'string',
411
+ mask: !this._isServer,
412
+ compress: true,
413
+ fin: true,
414
+ ...options
415
+ };
416
+
417
+ if (!this._extensions[PerMessageDeflate.extensionName]) {
418
+ opts.compress = false;
419
+ }
420
+
421
+ this._sender.send(data || EMPTY_BUFFER, opts, cb);
422
+ }
423
+
424
+ /**
425
+ * Forcibly close the connection.
426
+ *
427
+ * @public
428
+ */
429
+ terminate() {
430
+ if (this.readyState === WebSocket.CLOSED) return;
431
+ if (this.readyState === WebSocket.CONNECTING) {
432
+ const msg = 'WebSocket was closed before the connection was established';
433
+ return abortHandshake(this, this._req, msg);
434
+ }
435
+
436
+ if (this._socket) {
437
+ this._readyState = WebSocket.CLOSING;
438
+ this._socket.destroy();
439
+ }
440
+ }
441
+ }
442
+
443
+ /**
444
+ * @constant {Number} CONNECTING
445
+ * @memberof WebSocket
446
+ */
447
+ Object.defineProperty(WebSocket, 'CONNECTING', {
448
+ enumerable: true,
449
+ value: readyStates.indexOf('CONNECTING')
450
+ });
451
+
452
+ /**
453
+ * @constant {Number} CONNECTING
454
+ * @memberof WebSocket.prototype
455
+ */
456
+ Object.defineProperty(WebSocket.prototype, 'CONNECTING', {
457
+ enumerable: true,
458
+ value: readyStates.indexOf('CONNECTING')
459
+ });
460
+
461
+ /**
462
+ * @constant {Number} OPEN
463
+ * @memberof WebSocket
464
+ */
465
+ Object.defineProperty(WebSocket, 'OPEN', {
466
+ enumerable: true,
467
+ value: readyStates.indexOf('OPEN')
468
+ });
469
+
470
+ /**
471
+ * @constant {Number} OPEN
472
+ * @memberof WebSocket.prototype
473
+ */
474
+ Object.defineProperty(WebSocket.prototype, 'OPEN', {
475
+ enumerable: true,
476
+ value: readyStates.indexOf('OPEN')
477
+ });
478
+
479
+ /**
480
+ * @constant {Number} CLOSING
481
+ * @memberof WebSocket
482
+ */
483
+ Object.defineProperty(WebSocket, 'CLOSING', {
484
+ enumerable: true,
485
+ value: readyStates.indexOf('CLOSING')
486
+ });
487
+
488
+ /**
489
+ * @constant {Number} CLOSING
490
+ * @memberof WebSocket.prototype
491
+ */
492
+ Object.defineProperty(WebSocket.prototype, 'CLOSING', {
493
+ enumerable: true,
494
+ value: readyStates.indexOf('CLOSING')
495
+ });
496
+
497
+ /**
498
+ * @constant {Number} CLOSED
499
+ * @memberof WebSocket
500
+ */
501
+ Object.defineProperty(WebSocket, 'CLOSED', {
502
+ enumerable: true,
503
+ value: readyStates.indexOf('CLOSED')
504
+ });
505
+
506
+ /**
507
+ * @constant {Number} CLOSED
508
+ * @memberof WebSocket.prototype
509
+ */
510
+ Object.defineProperty(WebSocket.prototype, 'CLOSED', {
511
+ enumerable: true,
512
+ value: readyStates.indexOf('CLOSED')
513
+ });
514
+
515
+ [
516
+ 'binaryType',
517
+ 'bufferedAmount',
518
+ 'extensions',
519
+ 'protocol',
520
+ 'readyState',
521
+ 'url'
522
+ ].forEach((property) => {
523
+ Object.defineProperty(WebSocket.prototype, property, { enumerable: true });
524
+ });
525
+
526
+ //
527
+ // Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
528
+ // See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
529
+ //
530
+ ['open', 'error', 'close', 'message'].forEach((method) => {
531
+ Object.defineProperty(WebSocket.prototype, `on${method}`, {
532
+ enumerable: true,
533
+ get() {
534
+ const listeners = this.listeners(method);
535
+ for (let i = 0; i < listeners.length; i++) {
536
+ if (listeners[i]._listener) return listeners[i]._listener;
537
+ }
538
+
539
+ return undefined;
540
+ },
541
+ set(listener) {
542
+ const listeners = this.listeners(method);
543
+ for (let i = 0; i < listeners.length; i++) {
544
+ //
545
+ // Remove only the listeners added via `addEventListener`.
546
+ //
547
+ if (listeners[i]._listener) this.removeListener(method, listeners[i]);
548
+ }
549
+ this.addEventListener(method, listener);
550
+ }
551
+ });
552
+ });
553
+
554
+ WebSocket.prototype.addEventListener = addEventListener;
555
+ WebSocket.prototype.removeEventListener = removeEventListener;
556
+
557
+ module.exports = WebSocket;
558
+
559
+ /**
560
+ * Initialize a WebSocket client.
561
+ *
562
+ * @param {WebSocket} websocket The client to initialize
563
+ * @param {(String|URL)} address The URL to which to connect
564
+ * @param {String} [protocols] The subprotocols
565
+ * @param {Object} [options] Connection options
566
+ * @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable
567
+ * permessage-deflate
568
+ * @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
569
+ * handshake request
570
+ * @param {Number} [options.protocolVersion=13] Value of the
571
+ * `Sec-WebSocket-Version` header
572
+ * @param {String} [options.origin] Value of the `Origin` or
573
+ * `Sec-WebSocket-Origin` header
574
+ * @param {Number} [options.maxPayload=104857600] The maximum allowed message
575
+ * size
576
+ * @param {Boolean} [options.followRedirects=false] Whether or not to follow
577
+ * redirects
578
+ * @param {Number} [options.maxRedirects=10] The maximum number of redirects
579
+ * allowed
580
+ * @private
581
+ */
582
+ function initAsClient(websocket, address, protocols, options) {
583
+ const opts = {
584
+ protocolVersion: protocolVersions[1],
585
+ maxPayload: 100 * 1024 * 1024,
586
+ perMessageDeflate: true,
587
+ followRedirects: false,
588
+ maxRedirects: 10,
589
+ ...options,
590
+ createConnection: undefined,
591
+ socketPath: undefined,
592
+ hostname: undefined,
593
+ protocol: undefined,
594
+ timeout: undefined,
595
+ method: undefined,
596
+ host: undefined,
597
+ path: undefined,
598
+ port: undefined
599
+ };
600
+
601
+ if (!protocolVersions.includes(opts.protocolVersion)) {
602
+ throw new RangeError(
603
+ `Unsupported protocol version: ${opts.protocolVersion} ` +
604
+ `(supported versions: ${protocolVersions.join(', ')})`
605
+ );
606
+ }
607
+
608
+ let parsedUrl;
609
+
610
+ if (address instanceof URL) {
611
+ parsedUrl = address;
612
+ websocket._url = address.href;
613
+ } else {
614
+ parsedUrl = new URL(address);
615
+ websocket._url = address;
616
+ }
617
+
618
+ const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
619
+
620
+ if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) {
621
+ const err = new Error(`Invalid URL: ${websocket.url}`);
622
+
623
+ if (websocket._redirects === 0) {
624
+ throw err;
625
+ } else {
626
+ emitErrorAndClose(websocket, err);
627
+ return;
628
+ }
629
+ }
630
+
631
+ const isSecure =
632
+ parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:';
633
+ const defaultPort = isSecure ? 443 : 80;
634
+ const key = randomBytes(16).toString('base64');
635
+ const get = isSecure ? https.get : http.get;
636
+ let perMessageDeflate;
637
+
638
+ opts.createConnection = isSecure ? tlsConnect : netConnect;
639
+ opts.defaultPort = opts.defaultPort || defaultPort;
640
+ opts.port = parsedUrl.port || defaultPort;
641
+ opts.host = parsedUrl.hostname.startsWith('[')
642
+ ? parsedUrl.hostname.slice(1, -1)
643
+ : parsedUrl.hostname;
644
+ opts.headers = {
645
+ 'Sec-WebSocket-Version': opts.protocolVersion,
646
+ 'Sec-WebSocket-Key': key,
647
+ Connection: 'Upgrade',
648
+ Upgrade: 'websocket',
649
+ ...opts.headers
650
+ };
651
+ opts.path = parsedUrl.pathname + parsedUrl.search;
652
+ opts.timeout = opts.handshakeTimeout;
653
+
654
+ if (opts.perMessageDeflate) {
655
+ perMessageDeflate = new PerMessageDeflate(
656
+ opts.perMessageDeflate !== true ? opts.perMessageDeflate : {},
657
+ false,
658
+ opts.maxPayload
659
+ );
660
+ opts.headers['Sec-WebSocket-Extensions'] = format({
661
+ [PerMessageDeflate.extensionName]: perMessageDeflate.offer()
662
+ });
663
+ }
664
+ if (protocols) {
665
+ opts.headers['Sec-WebSocket-Protocol'] = protocols;
666
+ }
667
+ if (opts.origin) {
668
+ if (opts.protocolVersion < 13) {
669
+ opts.headers['Sec-WebSocket-Origin'] = opts.origin;
670
+ } else {
671
+ opts.headers.Origin = opts.origin;
672
+ }
673
+ }
674
+ if (parsedUrl.username || parsedUrl.password) {
675
+ opts.auth = `${parsedUrl.username}:${parsedUrl.password}`;
676
+ }
677
+
678
+ if (isUnixSocket) {
679
+ const parts = opts.path.split(':');
680
+
681
+ opts.socketPath = parts[0];
682
+ opts.path = parts[1];
683
+ }
684
+
685
+ if (opts.followRedirects) {
686
+ if (websocket._redirects === 0) {
687
+ websocket._originalUnixSocket = isUnixSocket;
688
+ websocket._originalSecure = isSecure;
689
+ websocket._originalHostOrSocketPath = isUnixSocket
690
+ ? opts.socketPath
691
+ : parsedUrl.host;
692
+
693
+ const headers = options && options.headers;
694
+
695
+ //
696
+ // Shallow copy the user provided options so that headers can be changed
697
+ // without mutating the original object.
698
+ //
699
+ options = { ...options, headers: {} };
700
+
701
+ if (headers) {
702
+ for (const [key, value] of Object.entries(headers)) {
703
+ options.headers[key.toLowerCase()] = value;
704
+ }
705
+ }
706
+ } else {
707
+ const isSameHost = isUnixSocket
708
+ ? websocket._originalUnixSocket
709
+ ? opts.socketPath === websocket._originalHostOrSocketPath
710
+ : false
711
+ : websocket._originalUnixSocket
712
+ ? false
713
+ : parsedUrl.host === websocket._originalHostOrSocketPath;
714
+
715
+ if (!isSameHost || (websocket._originalSecure && !isSecure)) {
716
+ //
717
+ // Match curl 7.77.0 behavior and drop the following headers. These
718
+ // headers are also dropped when following a redirect to a subdomain.
719
+ //
720
+ delete opts.headers.authorization;
721
+ delete opts.headers.cookie;
722
+
723
+ if (!isSameHost) delete opts.headers.host;
724
+
725
+ opts.auth = undefined;
726
+ }
727
+ }
728
+
729
+ //
730
+ // Match curl 7.77.0 behavior and make the first `Authorization` header win.
731
+ // If the `Authorization` header is set, then there is nothing to do as it
732
+ // will take precedence.
733
+ //
734
+ if (opts.auth && !options.headers.authorization) {
735
+ options.headers.authorization =
736
+ 'Basic ' + Buffer.from(opts.auth).toString('base64');
737
+ }
738
+ }
739
+
740
+ let req = (websocket._req = get(opts));
741
+
742
+ if (opts.timeout) {
743
+ req.on('timeout', () => {
744
+ abortHandshake(websocket, req, 'Opening handshake has timed out');
745
+ });
746
+ }
747
+
748
+ req.on('error', (err) => {
749
+ if (req === null || req.aborted) return;
750
+
751
+ req = websocket._req = null;
752
+ emitErrorAndClose(websocket, err);
753
+ });
754
+
755
+ req.on('response', (res) => {
756
+ const location = res.headers.location;
757
+ const statusCode = res.statusCode;
758
+
759
+ if (
760
+ location &&
761
+ opts.followRedirects &&
762
+ statusCode >= 300 &&
763
+ statusCode < 400
764
+ ) {
765
+ if (++websocket._redirects > opts.maxRedirects) {
766
+ abortHandshake(websocket, req, 'Maximum redirects exceeded');
767
+ return;
768
+ }
769
+
770
+ req.abort();
771
+
772
+ let addr;
773
+
774
+ try {
775
+ addr = new URL(location, address);
776
+ } catch (err) {
777
+ emitErrorAndClose(websocket, err);
778
+ return;
779
+ }
780
+
781
+ initAsClient(websocket, addr, protocols, options);
782
+ } else if (!websocket.emit('unexpected-response', req, res)) {
783
+ abortHandshake(
784
+ websocket,
785
+ req,
786
+ `Unexpected server response: ${res.statusCode}`
787
+ );
788
+ }
789
+ });
790
+
791
+ req.on('upgrade', (res, socket, head) => {
792
+ websocket.emit('upgrade', res);
793
+
794
+ //
795
+ // The user may have closed the connection from a listener of the `upgrade`
796
+ // event.
797
+ //
798
+ if (websocket.readyState !== WebSocket.CONNECTING) return;
799
+
800
+ req = websocket._req = null;
801
+
802
+ if (res.headers.upgrade.toLowerCase() !== 'websocket') {
803
+ abortHandshake(websocket, socket, 'Invalid Upgrade header');
804
+ return;
805
+ }
806
+
807
+ const digest = createHash('sha1')
808
+ .update(key + GUID)
809
+ .digest('base64');
810
+
811
+ if (res.headers['sec-websocket-accept'] !== digest) {
812
+ abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header');
813
+ return;
814
+ }
815
+
816
+ const serverProt = res.headers['sec-websocket-protocol'];
817
+ const protList = (protocols || '').split(/, */);
818
+ let protError;
819
+
820
+ if (!protocols && serverProt) {
821
+ protError = 'Server sent a subprotocol but none was requested';
822
+ } else if (protocols && !serverProt) {
823
+ protError = 'Server sent no subprotocol';
824
+ } else if (serverProt && !protList.includes(serverProt)) {
825
+ protError = 'Server sent an invalid subprotocol';
826
+ }
827
+
828
+ if (protError) {
829
+ abortHandshake(websocket, socket, protError);
830
+ return;
831
+ }
832
+
833
+ if (serverProt) websocket._protocol = serverProt;
834
+
835
+ const secWebSocketExtensions = res.headers['sec-websocket-extensions'];
836
+
837
+ if (secWebSocketExtensions !== undefined) {
838
+ if (!perMessageDeflate) {
839
+ const message =
840
+ 'Server sent a Sec-WebSocket-Extensions header but no extension ' +
841
+ 'was requested';
842
+ abortHandshake(websocket, socket, message);
843
+ return;
844
+ }
845
+
846
+ let extensions;
847
+
848
+ try {
849
+ extensions = parse(secWebSocketExtensions);
850
+ } catch (err) {
851
+ const message = 'Invalid Sec-WebSocket-Extensions header';
852
+ abortHandshake(websocket, socket, message);
853
+ return;
854
+ }
855
+
856
+ const extensionNames = Object.keys(extensions);
857
+
858
+ if (extensionNames.length) {
859
+ if (
860
+ extensionNames.length !== 1 ||
861
+ extensionNames[0] !== PerMessageDeflate.extensionName
862
+ ) {
863
+ const message =
864
+ 'Server indicated an extension that was not requested';
865
+ abortHandshake(websocket, socket, message);
866
+ return;
867
+ }
868
+
869
+ try {
870
+ perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
871
+ } catch (err) {
872
+ const message = 'Invalid Sec-WebSocket-Extensions header';
873
+ abortHandshake(websocket, socket, message);
874
+ return;
875
+ }
876
+
877
+ websocket._extensions[PerMessageDeflate.extensionName] =
878
+ perMessageDeflate;
879
+ }
880
+ }
881
+
882
+ websocket.setSocket(socket, head, opts.maxPayload);
883
+ });
884
+ }
885
+
886
+ /**
887
+ * Emit the `'error'` and `'close'` event.
888
+ *
889
+ * @param {WebSocket} websocket The WebSocket instance
890
+ * @param {Error} The error to emit
891
+ * @private
892
+ */
893
+ function emitErrorAndClose(websocket, err) {
894
+ websocket._readyState = WebSocket.CLOSING;
895
+ websocket.emit('error', err);
896
+ websocket.emitClose();
897
+ }
898
+
899
+ /**
900
+ * Create a `net.Socket` and initiate a connection.
901
+ *
902
+ * @param {Object} options Connection options
903
+ * @return {net.Socket} The newly created socket used to start the connection
904
+ * @private
905
+ */
906
+ function netConnect(options) {
907
+ options.path = options.socketPath;
908
+ return net.connect(options);
909
+ }
910
+
911
+ /**
912
+ * Create a `tls.TLSSocket` and initiate a connection.
913
+ *
914
+ * @param {Object} options Connection options
915
+ * @return {tls.TLSSocket} The newly created socket used to start the connection
916
+ * @private
917
+ */
918
+ function tlsConnect(options) {
919
+ options.path = undefined;
920
+
921
+ if (!options.servername && options.servername !== '') {
922
+ options.servername = net.isIP(options.host) ? '' : options.host;
923
+ }
924
+
925
+ return tls.connect(options);
926
+ }
927
+
928
+ /**
929
+ * Abort the handshake and emit an error.
930
+ *
931
+ * @param {WebSocket} websocket The WebSocket instance
932
+ * @param {(http.ClientRequest|net.Socket|tls.Socket)} stream The request to
933
+ * abort or the socket to destroy
934
+ * @param {String} message The error message
935
+ * @private
936
+ */
937
+ function abortHandshake(websocket, stream, message) {
938
+ websocket._readyState = WebSocket.CLOSING;
939
+
940
+ const err = new Error(message);
941
+ Error.captureStackTrace(err, abortHandshake);
942
+
943
+ if (stream.setHeader) {
944
+ stream.abort();
945
+
946
+ if (stream.socket && !stream.socket.destroyed) {
947
+ //
948
+ // On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if
949
+ // called after the request completed. See
950
+ // https://github.com/websockets/ws/issues/1869.
951
+ //
952
+ stream.socket.destroy();
953
+ }
954
+
955
+ stream.once('abort', websocket.emitClose.bind(websocket));
956
+ websocket.emit('error', err);
957
+ } else {
958
+ stream.destroy(err);
959
+ stream.once('error', websocket.emit.bind(websocket, 'error'));
960
+ stream.once('close', websocket.emitClose.bind(websocket));
961
+ }
962
+ }
963
+
964
+ /**
965
+ * Handle cases where the `ping()`, `pong()`, or `send()` methods are called
966
+ * when the `readyState` attribute is `CLOSING` or `CLOSED`.
967
+ *
968
+ * @param {WebSocket} websocket The WebSocket instance
969
+ * @param {*} [data] The data to send
970
+ * @param {Function} [cb] Callback
971
+ * @private
972
+ */
973
+ function sendAfterClose(websocket, data, cb) {
974
+ if (data) {
975
+ const length = toBuffer(data).length;
976
+
977
+ //
978
+ // The `_bufferedAmount` property is used only when the peer is a client and
979
+ // the opening handshake fails. Under these circumstances, in fact, the
980
+ // `setSocket()` method is not called, so the `_socket` and `_sender`
981
+ // properties are set to `null`.
982
+ //
983
+ if (websocket._socket) websocket._sender._bufferedBytes += length;
984
+ else websocket._bufferedAmount += length;
985
+ }
986
+
987
+ if (cb) {
988
+ const err = new Error(
989
+ `WebSocket is not open: readyState ${websocket.readyState} ` +
990
+ `(${readyStates[websocket.readyState]})`
991
+ );
992
+ cb(err);
993
+ }
994
+ }
995
+
996
+ /**
997
+ * The listener of the `Receiver` `'conclude'` event.
998
+ *
999
+ * @param {Number} code The status code
1000
+ * @param {String} reason The reason for closing
1001
+ * @private
1002
+ */
1003
+ function receiverOnConclude(code, reason) {
1004
+ const websocket = this[kWebSocket];
1005
+
1006
+ websocket._closeFrameReceived = true;
1007
+ websocket._closeMessage = reason;
1008
+ websocket._closeCode = code;
1009
+
1010
+ if (websocket._socket[kWebSocket] === undefined) return;
1011
+
1012
+ websocket._socket.removeListener('data', socketOnData);
1013
+ process.nextTick(resume, websocket._socket);
1014
+
1015
+ if (code === 1005) websocket.close();
1016
+ else websocket.close(code, reason);
1017
+ }
1018
+
1019
+ /**
1020
+ * The listener of the `Receiver` `'drain'` event.
1021
+ *
1022
+ * @private
1023
+ */
1024
+ function receiverOnDrain() {
1025
+ this[kWebSocket]._socket.resume();
1026
+ }
1027
+
1028
+ /**
1029
+ * The listener of the `Receiver` `'error'` event.
1030
+ *
1031
+ * @param {(RangeError|Error)} err The emitted error
1032
+ * @private
1033
+ */
1034
+ function receiverOnError(err) {
1035
+ const websocket = this[kWebSocket];
1036
+
1037
+ if (websocket._socket[kWebSocket] !== undefined) {
1038
+ websocket._socket.removeListener('data', socketOnData);
1039
+
1040
+ //
1041
+ // On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See
1042
+ // https://github.com/websockets/ws/issues/1940.
1043
+ //
1044
+ process.nextTick(resume, websocket._socket);
1045
+
1046
+ websocket.close(err[kStatusCode]);
1047
+ }
1048
+
1049
+ websocket.emit('error', err);
1050
+ }
1051
+
1052
+ /**
1053
+ * The listener of the `Receiver` `'finish'` event.
1054
+ *
1055
+ * @private
1056
+ */
1057
+ function receiverOnFinish() {
1058
+ this[kWebSocket].emitClose();
1059
+ }
1060
+
1061
+ /**
1062
+ * The listener of the `Receiver` `'message'` event.
1063
+ *
1064
+ * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The message
1065
+ * @private
1066
+ */
1067
+ function receiverOnMessage(data) {
1068
+ this[kWebSocket].emit('message', data);
1069
+ }
1070
+
1071
+ /**
1072
+ * The listener of the `Receiver` `'ping'` event.
1073
+ *
1074
+ * @param {Buffer} data The data included in the ping frame
1075
+ * @private
1076
+ */
1077
+ function receiverOnPing(data) {
1078
+ const websocket = this[kWebSocket];
1079
+
1080
+ websocket.pong(data, !websocket._isServer, NOOP);
1081
+ websocket.emit('ping', data);
1082
+ }
1083
+
1084
+ /**
1085
+ * The listener of the `Receiver` `'pong'` event.
1086
+ *
1087
+ * @param {Buffer} data The data included in the pong frame
1088
+ * @private
1089
+ */
1090
+ function receiverOnPong(data) {
1091
+ this[kWebSocket].emit('pong', data);
1092
+ }
1093
+
1094
+ /**
1095
+ * Resume a readable stream
1096
+ *
1097
+ * @param {Readable} stream The readable stream
1098
+ * @private
1099
+ */
1100
+ function resume(stream) {
1101
+ stream.resume();
1102
+ }
1103
+
1104
+ /**
1105
+ * The listener of the `net.Socket` `'close'` event.
1106
+ *
1107
+ * @private
1108
+ */
1109
+ function socketOnClose() {
1110
+ const websocket = this[kWebSocket];
1111
+
1112
+ this.removeListener('close', socketOnClose);
1113
+ this.removeListener('data', socketOnData);
1114
+ this.removeListener('end', socketOnEnd);
1115
+
1116
+ websocket._readyState = WebSocket.CLOSING;
1117
+
1118
+ let chunk;
1119
+
1120
+ //
1121
+ // The close frame might not have been received or the `'end'` event emitted,
1122
+ // for example, if the socket was destroyed due to an error. Ensure that the
1123
+ // `receiver` stream is closed after writing any remaining buffered data to
1124
+ // it. If the readable side of the socket is in flowing mode then there is no
1125
+ // buffered data as everything has been already written and `readable.read()`
1126
+ // will return `null`. If instead, the socket is paused, any possible buffered
1127
+ // data will be read as a single chunk.
1128
+ //
1129
+ if (
1130
+ !this._readableState.endEmitted &&
1131
+ !websocket._closeFrameReceived &&
1132
+ !websocket._receiver._writableState.errorEmitted &&
1133
+ (chunk = websocket._socket.read()) !== null
1134
+ ) {
1135
+ websocket._receiver.write(chunk);
1136
+ }
1137
+
1138
+ websocket._receiver.end();
1139
+
1140
+ this[kWebSocket] = undefined;
1141
+
1142
+ clearTimeout(websocket._closeTimer);
1143
+
1144
+ if (
1145
+ websocket._receiver._writableState.finished ||
1146
+ websocket._receiver._writableState.errorEmitted
1147
+ ) {
1148
+ websocket.emitClose();
1149
+ } else {
1150
+ websocket._receiver.on('error', receiverOnFinish);
1151
+ websocket._receiver.on('finish', receiverOnFinish);
1152
+ }
1153
+ }
1154
+
1155
+ /**
1156
+ * The listener of the `net.Socket` `'data'` event.
1157
+ *
1158
+ * @param {Buffer} chunk A chunk of data
1159
+ * @private
1160
+ */
1161
+ function socketOnData(chunk) {
1162
+ if (!this[kWebSocket]._receiver.write(chunk)) {
1163
+ this.pause();
1164
+ }
1165
+ }
1166
+
1167
+ /**
1168
+ * The listener of the `net.Socket` `'end'` event.
1169
+ *
1170
+ * @private
1171
+ */
1172
+ function socketOnEnd() {
1173
+ const websocket = this[kWebSocket];
1174
+
1175
+ websocket._readyState = WebSocket.CLOSING;
1176
+ websocket._receiver.end();
1177
+ this.end();
1178
+ }
1179
+
1180
+ /**
1181
+ * The listener of the `net.Socket` `'error'` event.
1182
+ *
1183
+ * @private
1184
+ */
1185
+ function socketOnError() {
1186
+ const websocket = this[kWebSocket];
1187
+
1188
+ this.removeListener('error', socketOnError);
1189
+ this.on('error', NOOP);
1190
+
1191
+ if (websocket) {
1192
+ websocket._readyState = WebSocket.CLOSING;
1193
+ this.destroy();
1194
+ }
1195
+ }