Skip to content

Commit beff620

Browse files
committed
[fix] Use status code from close frame if received
1 parent ae903b1 commit beff620

File tree

4 files changed

+97
-227
lines changed

4 files changed

+97
-227
lines changed

lib/EventTarget.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class CloseEvent extends Event {
5555
constructor (code, reason, target) {
5656
super('close', target);
5757

58-
this.wasClean = code === undefined || code === 1000 || (code >= 3000 && code <= 4999);
58+
this.wasClean = target._closeFrameReceived && target._closeFrameSent;
5959
this.reason = reason;
6060
this.code = code;
6161
}

lib/WebSocket.js

Lines changed: 44 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,12 @@ class WebSocket extends EventEmitter {
5555

5656
this._binaryType = constants.BINARY_TYPES[0];
5757
this._finalize = this.finalize.bind(this);
58+
this._closeFrameReceived = false;
5859
this._closeFrameSent = false;
59-
this._closeMessage = null;
60+
this._closeMessage = '';
6061
this._closeTimer = null;
6162
this._finalized = false;
62-
this._closeCode = null;
63+
this._closeCode = 1006;
6364
this._receiver = null;
6465
this._sender = null;
6566
this._socket = null;
@@ -126,36 +127,40 @@ class WebSocket extends EventEmitter {
126127
this._ultron = new Ultron(socket);
127128
this._socket = socket;
128129

129-
// socket cleanup handlers
130130
this._ultron.on('close', this._finalize);
131131
this._ultron.on('error', this._finalize);
132132
this._ultron.on('end', this._finalize);
133133

134-
// ensure that the head is added to the receiver
135134
if (head.length > 0) socket.unshift(head);
136135

137-
// subsequent packets are pushed to the receiver
138136
this._ultron.on('data', (data) => {
139137
this.bytesReceived += data.length;
140138
this._receiver.add(data);
141139
});
142140

143-
// receiver event handlers
144141
this._receiver.onmessage = (data) => this.emit('message', data);
145142
this._receiver.onping = (data) => {
146143
this.pong(data, !this._isServer, true);
147144
this.emit('ping', data);
148145
};
149146
this._receiver.onpong = (data) => this.emit('pong', data);
150147
this._receiver.onclose = (code, reason) => {
148+
this._closeFrameReceived = true;
151149
this._closeMessage = reason;
152150
this._closeCode = code;
153151
if (!this._finalized) this.close(code, reason);
154152
};
155153
this._receiver.onerror = (error, code) => {
156-
// close the connection when the receiver reports a HyBi error code
157-
this.close(code, '');
154+
this._closeMessage = '';
155+
this._closeCode = code;
156+
157+
//
158+
// Ensure that the error is emitted even if `WebSocket#finalize()` has
159+
// already been called.
160+
//
161+
this.readyState = WebSocket.CLOSING;
158162
this.emit('error', error);
163+
this.finalize(true);
159164
};
160165

161166
this.readyState = WebSocket.OPEN;
@@ -174,7 +179,8 @@ class WebSocket extends EventEmitter {
174179
this.readyState = WebSocket.CLOSING;
175180
this._finalized = true;
176181

177-
if (!this._socket) return this.emitClose(error);
182+
if (typeof error === 'object') this.emit('error', error);
183+
if (!this._socket) return this.emitClose();
178184

179185
clearTimeout(this._closeTimer);
180186
this._closeTimer = null;
@@ -190,29 +196,19 @@ class WebSocket extends EventEmitter {
190196
this._socket = null;
191197
this._sender = null;
192198

193-
this._receiver.cleanup(() => this.emitClose(error));
199+
this._receiver.cleanup(() => this.emitClose());
194200
this._receiver = null;
195201
}
196202

197203
/**
198204
* Emit the `close` event.
199205
*
200-
* @param {(Boolean|Error)} error Indicates whether or not an error occurred
201206
* @private
202207
*/
203-
emitClose (error) {
208+
emitClose () {
204209
this.readyState = WebSocket.CLOSED;
205210

206-
//
207-
// If the connection was closed abnormally (with an error), or if the close
208-
// control frame was not sent or received then the close code must be 1006.
209-
//
210-
if (error || !this._closeFrameSent) {
211-
this._closeMessage = '';
212-
this._closeCode = 1006;
213-
}
214-
215-
this.emit('close', this._closeCode || 1006, this._closeMessage || '');
211+
this.emit('close', this._closeCode, this._closeMessage);
216212

217213
if (this.extensions[PerMessageDeflate.extensionName]) {
218214
this.extensions[PerMessageDeflate.extensionName].cleanup();
@@ -271,37 +267,35 @@ class WebSocket extends EventEmitter {
271267
close (code, data) {
272268
if (this.readyState === WebSocket.CLOSED) return;
273269
if (this.readyState === WebSocket.CONNECTING) {
274-
if (this._req && !this._req.aborted) {
275-
this._req.abort();
276-
this.emit('error', new Error('closed before the connection is established'));
277-
this.finalize(true);
278-
}
270+
this._req.abort();
271+
this.finalize(new Error('closed before the connection is established'));
279272
return;
280273
}
281274

282275
if (this.readyState === WebSocket.CLOSING) {
283-
if (this._closeFrameSent && this._closeCode) this._socket.end();
276+
if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
284277
return;
285278
}
286279

287280
this.readyState = WebSocket.CLOSING;
288281
this._sender.close(code, data, !this._isServer, (err) => {
289-
if (this._finalized) return;
290-
291-
if (err) {
292-
this.emit('error', err);
293-
this.finalize(true);
294-
return;
295-
}
282+
//
283+
// This error is handled by the `'error'` listener on the socket. We only
284+
// want to know if the close frame has been sent here.
285+
//
286+
if (err) return;
296287

297-
if (this._closeCode) this._socket.end();
298288
this._closeFrameSent = true;
299289

300-
//
301-
// Ensure that the connection is cleaned up even when the closing
302-
// handshake fails.
303-
//
304-
this._closeTimer = setTimeout(this._finalize, closeTimeout, true);
290+
if (!this._finalized) {
291+
if (this._closeFrameReceived) this._socket.end();
292+
293+
//
294+
// Ensure that the connection is cleaned up even when the closing
295+
// handshake fails.
296+
//
297+
this._closeTimer = setTimeout(this._finalize, closeTimeout, true);
298+
}
305299
});
306300
}
307301

@@ -391,11 +385,8 @@ class WebSocket extends EventEmitter {
391385
terminate () {
392386
if (this.readyState === WebSocket.CLOSED) return;
393387
if (this.readyState === WebSocket.CONNECTING) {
394-
if (this._req && !this._req.aborted) {
395-
this._req.abort();
396-
this.emit('error', new Error('closed before the connection is established'));
397-
this.finalize(true);
398-
}
388+
this._req.abort();
389+
this.finalize(new Error('closed before the connection is established'));
399390
return;
400391
}
401392

@@ -645,24 +636,21 @@ function initAsClient (address, protocols, options) {
645636
if (options.handshakeTimeout) {
646637
this._req.setTimeout(options.handshakeTimeout, () => {
647638
this._req.abort();
648-
this.emit('error', new Error('opening handshake has timed out'));
649-
this.finalize(true);
639+
this.finalize(new Error('opening handshake has timed out'));
650640
});
651641
}
652642

653643
this._req.on('error', (error) => {
654644
if (this._req.aborted) return;
655645

656646
this._req = null;
657-
this.emit('error', error);
658-
this.finalize(true);
647+
this.finalize(error);
659648
});
660649

661650
this._req.on('response', (res) => {
662651
if (!this.emit('unexpected-response', this._req, res)) {
663652
this._req.abort();
664-
this.emit('error', new Error(`unexpected server response (${res.statusCode})`));
665-
this.finalize(true);
653+
this.finalize(new Error(`unexpected server response (${res.statusCode})`));
666654
}
667655
});
668656

@@ -683,8 +671,7 @@ function initAsClient (address, protocols, options) {
683671

684672
if (res.headers['sec-websocket-accept'] !== digest) {
685673
socket.destroy();
686-
this.emit('error', new Error('invalid server key'));
687-
return this.finalize(true);
674+
return this.finalize(new Error('invalid server key'));
688675
}
689676

690677
const serverProt = res.headers['sec-websocket-protocol'];
@@ -701,8 +688,7 @@ function initAsClient (address, protocols, options) {
701688

702689
if (protError) {
703690
socket.destroy();
704-
this.emit('error', new Error(protError));
705-
return this.finalize(true);
691+
return this.finalize(new Error(protError));
706692
}
707693

708694
if (serverProt) this.protocol = serverProt;
@@ -721,8 +707,8 @@ function initAsClient (address, protocols, options) {
721707
}
722708
} catch (err) {
723709
socket.destroy();
724-
this.emit('error', new Error('invalid Sec-WebSocket-Extensions header'));
725-
return this.finalize(true);
710+
this.finalize(new Error('invalid Sec-WebSocket-Extensions header'));
711+
return;
726712
}
727713
}
728714

0 commit comments

Comments
 (0)