Skip to content

Commit 79dab96

Browse files
authored
[fix] Emit at most one event per microtask (#2160)
To improve compatibility with the WHATWG standard, emit at most one of `'message'`, `'ping'`, and `'pong'` events per tick. Fixes #2159
1 parent 67007fc commit 79dab96

File tree

3 files changed

+130
-14
lines changed

3 files changed

+130
-14
lines changed

lib/receiver.js

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ const { concat, toArrayBuffer, unmask } = require('./buffer-util');
1313
const { isValidStatusCode, isValidUTF8 } = require('./validation');
1414

1515
const FastBuffer = Buffer[Symbol.species];
16+
const promise = Promise.resolve();
17+
1618
const GET_INFO = 0;
1719
const GET_PAYLOAD_LENGTH_16 = 1;
1820
const GET_PAYLOAD_LENGTH_64 = 2;
1921
const GET_MASK = 3;
2022
const GET_DATA = 4;
2123
const INFLATING = 5;
24+
const WAIT_MICROTASK = 6;
2225

2326
/**
2427
* HyBi Receiver implementation.
@@ -157,9 +160,23 @@ class Receiver extends Writable {
157160
case GET_DATA:
158161
err = this.getData(cb);
159162
break;
163+
case INFLATING:
164+
this._loop = false;
165+
return;
160166
default:
161-
// `INFLATING`
167+
//
168+
// `WAIT_MICROTASK`.
169+
//
162170
this._loop = false;
171+
172+
//
173+
// `queueMicrotask()` is not available in Node.js < 11 and is no
174+
// better anyway.
175+
//
176+
promise.then(() => {
177+
this._state = GET_INFO;
178+
this.startLoop(cb);
179+
});
163180
return;
164181
}
165182
} while (this._loop);
@@ -542,7 +559,7 @@ class Receiver extends Writable {
542559
}
543560
}
544561

545-
this._state = GET_INFO;
562+
this._state = WAIT_MICROTASK;
546563
}
547564

548565
/**
@@ -559,6 +576,8 @@ class Receiver extends Writable {
559576
if (data.length === 0) {
560577
this.emit('conclude', 1005, EMPTY_BUFFER);
561578
this.end();
579+
580+
this._state = GET_INFO;
562581
} else {
563582
const code = data.readUInt16BE(0);
564583

@@ -590,14 +609,16 @@ class Receiver extends Writable {
590609

591610
this.emit('conclude', code, buf);
592611
this.end();
612+
613+
this._state = GET_INFO;
593614
}
594615
} else if (this._opcode === 0x09) {
595616
this.emit('ping', data);
617+
this._state = WAIT_MICROTASK;
596618
} else {
597619
this.emit('pong', data);
620+
this._state = WAIT_MICROTASK;
598621
}
599-
600-
this._state = GET_INFO;
601622
}
602623
}
603624

test/receiver.test.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,4 +1083,96 @@ describe('Receiver', () => {
10831083

10841084
receiver.write(Buffer.from([0x88, 0x03, 0x03, 0xe8, 0xf8]));
10851085
});
1086+
1087+
it("waits a microtask after each 'message' event", (done) => {
1088+
const messages = [];
1089+
const receiver = new Receiver();
1090+
1091+
receiver.on('message', (data, isBinary) => {
1092+
assert.ok(!isBinary);
1093+
1094+
const message = data.toString();
1095+
messages.push(message);
1096+
1097+
// `queueMicrotask()` is not available in Node.js < 11.
1098+
Promise.resolve().then(() => {
1099+
messages.push(`microtask ${message}`);
1100+
1101+
if (messages.length === 6) {
1102+
assert.deepStrictEqual(messages, [
1103+
'1',
1104+
'microtask 1',
1105+
'2',
1106+
'microtask 2',
1107+
'3',
1108+
'microtask 3'
1109+
]);
1110+
1111+
done();
1112+
}
1113+
});
1114+
});
1115+
1116+
receiver.write(Buffer.from('810131810132810133', 'hex'));
1117+
});
1118+
1119+
it("waits a microtask after each 'ping' event", (done) => {
1120+
const actual = [];
1121+
const receiver = new Receiver();
1122+
1123+
receiver.on('ping', (data) => {
1124+
const message = data.toString();
1125+
actual.push(message);
1126+
1127+
// `queueMicrotask()` is not available in Node.js < 11.
1128+
Promise.resolve().then(() => {
1129+
actual.push(`microtask ${message}`);
1130+
1131+
if (actual.length === 6) {
1132+
assert.deepStrictEqual(actual, [
1133+
'1',
1134+
'microtask 1',
1135+
'2',
1136+
'microtask 2',
1137+
'3',
1138+
'microtask 3'
1139+
]);
1140+
1141+
done();
1142+
}
1143+
});
1144+
});
1145+
1146+
receiver.write(Buffer.from('890131890132890133', 'hex'));
1147+
});
1148+
1149+
it("waits a microtask after each 'pong' event", (done) => {
1150+
const actual = [];
1151+
const receiver = new Receiver();
1152+
1153+
receiver.on('pong', (data) => {
1154+
const message = data.toString();
1155+
actual.push(message);
1156+
1157+
// `queueMicrotask()` is not available in Node.js < 11.
1158+
Promise.resolve().then(() => {
1159+
actual.push(`microtask ${message}`);
1160+
1161+
if (actual.length === 6) {
1162+
assert.deepStrictEqual(actual, [
1163+
'1',
1164+
'microtask 1',
1165+
'2',
1166+
'microtask 2',
1167+
'3',
1168+
'microtask 3'
1169+
]);
1170+
1171+
done();
1172+
}
1173+
});
1174+
});
1175+
1176+
receiver.write(Buffer.from('8A01318A01328A0133', 'hex'));
1177+
});
10861178
});

test/websocket.test.js

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4075,18 +4075,18 @@ describe('WebSocket', () => {
40754075
const messages = [];
40764076
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
40774077

4078-
ws.on('open', () => {
4079-
ws._socket.on('end', () => {
4080-
assert.strictEqual(ws._receiver._state, 5);
4081-
});
4082-
});
4083-
40844078
ws.on('message', (message, isBinary) => {
40854079
assert.ok(!isBinary);
40864080

40874081
if (messages.push(message.toString()) > 1) return;
40884082

4089-
ws.close(1000);
4083+
// `queueMicrotask()` is not available in Node.js < 11.
4084+
Promise.resolve().then(() => {
4085+
process.nextTick(() => {
4086+
assert.strictEqual(ws._receiver._state, 5);
4087+
ws.close(1000);
4088+
});
4089+
});
40904090
});
40914091

40924092
ws.on('close', (code, reason) => {
@@ -4331,9 +4331,12 @@ describe('WebSocket', () => {
43314331

43324332
if (messages.push(message.toString()) > 1) return;
43334333

4334-
process.nextTick(() => {
4335-
assert.strictEqual(ws._receiver._state, 5);
4336-
ws.terminate();
4334+
// `queueMicrotask()` is not available in Node.js < 11.
4335+
Promise.resolve().then(() => {
4336+
process.nextTick(() => {
4337+
assert.strictEqual(ws._receiver._state, 5);
4338+
ws.terminate();
4339+
});
43374340
});
43384341
});
43394342

0 commit comments

Comments
 (0)