Skip to content

Commit d55c39e

Browse files
fix(webtransport): add proper framing
WebTransport being a stream-based protocol, the chunking boundaries are not necessarily preserved. That's why we need a header indicating the type of the payload (plain text or binary) and its length. See also: socketio/engine.io@a306db0
1 parent 500085d commit d55c39e

File tree

4 files changed

+46
-63
lines changed

4 files changed

+46
-63
lines changed

Diff for: lib/transports/webtransport.ts

+23-42
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,14 @@
11
import { Transport } from "../transport.js";
22
import { nextTick } from "./websocket-constructor.js";
33
import {
4-
encodePacketToBinary,
5-
decodePacketFromBinary,
64
Packet,
5+
createPacketDecoderStream,
6+
createPacketEncoderStream,
77
} from "engine.io-parser";
88
import debugModule from "debug"; // debug()
99

1010
const debug = debugModule("engine.io-client:webtransport"); // debug()
1111

12-
function shouldIncludeBinaryHeader(packet, encoded) {
13-
// 48 === "0".charCodeAt(0) (OPEN packet type)
14-
// 54 === "6".charCodeAt(0) (NOOP packet type)
15-
return (
16-
packet.type === "message" &&
17-
typeof packet.data !== "string" &&
18-
encoded[0] >= 48 &&
19-
encoded[0] <= 54
20-
);
21-
}
22-
2312
export class WT extends Transport {
2413
private transport: any;
2514
private writer: any;
@@ -52,10 +41,16 @@ export class WT extends Transport {
5241
// note: we could have used async/await, but that would require some additional polyfills
5342
this.transport.ready.then(() => {
5443
this.transport.createBidirectionalStream().then((stream) => {
55-
const reader = stream.readable.getReader();
56-
this.writer = stream.writable.getWriter();
44+
const decoderStream = createPacketDecoderStream(
45+
Number.MAX_SAFE_INTEGER,
46+
// TODO expose binarytype
47+
"arraybuffer"
48+
);
49+
const reader = stream.readable.pipeThrough(decoderStream).getReader();
5750

58-
let binaryFlag;
51+
const encoderStream = createPacketEncoderStream();
52+
encoderStream.readable.pipeTo(stream.writable);
53+
this.writer = encoderStream.writable.getWriter();
5954

6055
const read = () => {
6156
reader
@@ -66,15 +61,7 @@ export class WT extends Transport {
6661
return;
6762
}
6863
debug("received chunk: %o", value);
69-
if (!binaryFlag && value.byteLength === 1 && value[0] === 54) {
70-
binaryFlag = true;
71-
} else {
72-
// TODO expose binarytype
73-
this.onPacket(
74-
decodePacketFromBinary(value, binaryFlag, "arraybuffer")
75-
);
76-
binaryFlag = false;
77-
}
64+
this.onPacket(value);
7865
read();
7966
})
8067
.catch((err) => {
@@ -84,10 +71,11 @@ export class WT extends Transport {
8471

8572
read();
8673

87-
const handshake = this.query.sid ? `0{"sid":"${this.query.sid}"}` : "0";
88-
this.writer
89-
.write(new TextEncoder().encode(handshake))
90-
.then(() => this.onOpen());
74+
const packet: Packet = { type: "open" };
75+
if (this.query.sid) {
76+
packet.data = `{"sid":"${this.query.sid}"}`;
77+
}
78+
this.writer.write(packet).then(() => this.onOpen());
9179
});
9280
});
9381
}
@@ -99,20 +87,13 @@ export class WT extends Transport {
9987
const packet = packets[i];
10088
const lastPacket = i === packets.length - 1;
10189

102-
encodePacketToBinary(packet, (data) => {
103-
if (shouldIncludeBinaryHeader(packet, data)) {
104-
debug("writing binary header");
105-
this.writer.write(Uint8Array.of(54));
90+
this.writer.write(packet).then(() => {
91+
if (lastPacket) {
92+
nextTick(() => {
93+
this.writable = true;
94+
this.emitReserved("drain");
95+
}, this.setTimeoutFn);
10696
}
107-
debug("writing chunk: %o", data);
108-
this.writer.write(data).then(() => {
109-
if (lastPacket) {
110-
nextTick(() => {
111-
this.writable = true;
112-
this.emitReserved("drain");
113-
}, this.setTimeoutFn);
114-
}
115-
});
11697
});
11798
}
11899
}

Diff for: package-lock.json

+19-19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"dependencies": {
4646
"@socket.io/component-emitter": "~3.1.0",
4747
"debug": "~4.3.1",
48-
"engine.io-parser": "~5.1.0",
48+
"engine.io-parser": "~5.2.1",
4949
"ws": "~8.11.0",
5050
"xmlhttprequest-ssl": "~2.0.0"
5151
},
@@ -63,7 +63,7 @@
6363
"@types/sinonjs__fake-timers": "^6.0.3",
6464
"babel-loader": "^8.2.2",
6565
"blob": "0.0.5",
66-
"engine.io": "^6.5.0-alpha.1",
66+
"engine.io": "^6.5.2-alpha.1",
6767
"expect.js": "^0.3.1",
6868
"express": "^4.17.1",
6969
"mocha": "^10.2.0",

Diff for: test/webtransport.mjs

+2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ import { Server } from "engine.io";
55
import { Socket } from "../build/esm-debug/index.js";
66
import { generateWebTransportCertificate } from "./util-wt.mjs";
77
import { createServer } from "http";
8+
import { TransformStream } from "stream/web";
89

910
if (typeof window === "undefined") {
1011
global.WebTransport = WebTransport;
12+
global.TransformStream = TransformStream;
1113
}
1214

1315
async function setup(opts, cb) {

0 commit comments

Comments
 (0)