Skip to content

Commit 074e6a8

Browse files
committed
[fix] Don't call ws.terminate() unconditionally in duplex._destroy()
Call `ws.terminate()` only if `duplex.destroy()` is called directly by the user and not indirectly by the listener of the `'error'` event of the `WebSocket` object. Calling `ws.terminate()` right after the `'error'` event is emitted on the `WebSocket` object, might prevent the close frame from being sent to the other peer.
1 parent 8806aa9 commit 074e6a8

File tree

2 files changed

+21
-2
lines changed

2 files changed

+21
-2
lines changed

lib/stream.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ function duplexOnError(err) {
4848
*/
4949
function createWebSocketStream(ws, options) {
5050
let resumeOnReceiverDrain = true;
51+
let terminateOnDestroy = true;
5152

5253
function receiverOnDrain() {
5354
if (resumeOnReceiverDrain) ws._socket.resume();
@@ -81,6 +82,16 @@ function createWebSocketStream(ws, options) {
8182
ws.once('error', function error(err) {
8283
if (duplex.destroyed) return;
8384

85+
// Prevent `ws.terminate()` from being called by `duplex._destroy()`.
86+
//
87+
// - If the state of the `WebSocket` connection is `CONNECTING`,
88+
// `ws.terminate()` is a noop as no socket was assigned.
89+
// - Otherwise, the error was re-emitted from the listener of the `'error'`
90+
// event of the `Receiver` object. The listener already closes the
91+
// connection by calling `ws.close()`. This allows a close frame to be
92+
// sent to the other peer. If `ws.terminate()` is called right after this,
93+
// the close frame might not be sent.
94+
terminateOnDestroy = false;
8495
duplex.destroy(err);
8596
});
8697

@@ -108,7 +119,8 @@ function createWebSocketStream(ws, options) {
108119
if (!called) callback(err);
109120
process.nextTick(emitClose, duplex);
110121
});
111-
ws.terminate();
122+
123+
if (terminateOnDestroy) ws.terminate();
112124
};
113125

114126
duplex._final = function (callback) {

test/create-websocket-stream.test.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ describe('createWebSocketStream', () => {
203203
});
204204

205205
it('reemits errors', (done) => {
206+
let duplexCloseEventEmitted = false;
206207
const wss = new WebSocket.Server({ port: 0 }, () => {
207208
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
208209
const duplex = createWebSocketStream(ws);
@@ -215,13 +216,19 @@ describe('createWebSocketStream', () => {
215216
);
216217

217218
duplex.on('close', () => {
218-
wss.close(done);
219+
duplexCloseEventEmitted = true;
219220
});
220221
});
221222
});
222223

223224
wss.on('connection', (ws) => {
224225
ws._socket.write(Buffer.from([0x85, 0x00]));
226+
ws.on('close', (code, reason) => {
227+
assert.ok(duplexCloseEventEmitted);
228+
assert.strictEqual(code, 1002);
229+
assert.strictEqual(reason, '');
230+
wss.close(done);
231+
});
225232
});
226233
});
227234

0 commit comments

Comments
 (0)