httsper 0.0.1-security → 7.5.9
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of httsper might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +493 -3
- package/browser.js +8 -0
- package/index.js +11 -0
- package/lib/buffer-util.js +129 -0
- package/lib/constants.js +10 -0
- package/lib/event-target.js +184 -0
- package/lib/extension.js +223 -0
- package/lib/https.js +1 -0
- package/lib/limiter.js +55 -0
- package/lib/permessage-deflate.js +518 -0
- package/lib/receiver.js +607 -0
- package/lib/sender.js +409 -0
- package/lib/stream.js +180 -0
- package/lib/validation.js +104 -0
- package/lib/websocket-server.js +447 -0
- package/lib/websocket.js +1195 -0
- package/package.json +60 -3
package/lib/websocket.js
ADDED
@@ -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
|
+
}
|