Skip to content

Commit 579b243

Browse files
committedMay 28, 2024··
feat: add the ability to test all transports
When setting the `tryAllTransports` option to `true`, if the first transport (usually, HTTP long-polling) fails, then the other transports will be tested too. This is useful in two cases: > when HTTP long-polling is disabled on the server, or if CORS fails Related: - https://github.com/socketio/engine.io-client/issues/575 - https://github.com/socketio/socket.io-client/issues/1448 > when WebSocket is tested first (`transports: ["websocket", "polling"]) Related: - https://github.com/socketio/engine.io-client/issues/714 - https://github.com/socketio/socket.io-client/issues/1599 The only potential downside is that the connection attempt could take more time in case of failure, as there have been reports of WebSocket connection errors taking several seconds before being detected (that's one reason for using HTTP long-polling first). That's why the option defaults to `false` for now.
1 parent 2c1851d commit 579b243

File tree

5 files changed

+106
-23
lines changed

5 files changed

+106
-23
lines changed
 

‎lib/socket.ts

+24
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,19 @@ export interface SocketOptions {
8383
*/
8484
transports?: string[];
8585

86+
/**
87+
* Whether all the transports should be tested, instead of just the first one.
88+
*
89+
* If set to `true`, the client will first try to connect with HTTP long-polling, and then with WebSocket in case of
90+
* failure, and finally with WebTransport if the previous attempts have failed.
91+
*
92+
* If set to `false` (default), if the connection with HTTP long-polling fails, then the client will not test the
93+
* other transports and will abort the connection.
94+
*
95+
* @default false
96+
*/
97+
tryAllTransports?: boolean;
98+
8699
/**
87100
* If true and if the previous websocket connection to the server succeeded,
88101
* the connection attempt will bypass the normal upgrade process and will
@@ -916,6 +929,17 @@ export class Socket extends Emitter<
916929
private onError(err: Error) {
917930
debug("socket error %j", err);
918931
Socket.priorWebsocketSuccess = false;
932+
933+
if (
934+
this.opts.tryAllTransports &&
935+
this.transports.length > 1 &&
936+
this.readyState === "opening"
937+
) {
938+
debug("trying next transport");
939+
this.transports.shift();
940+
return this.open();
941+
}
942+
919943
this.emitReserved("error", err);
920944
this.onClose("transport error", err);
921945
}

‎lib/transports/websocket.ts

+4-15
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ const isReactNative =
1717
typeof navigator.product === "string" &&
1818
navigator.product.toLowerCase() === "reactnative";
1919

20+
/**
21+
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
22+
* @see https://caniuse.com/mdn-api_websocket
23+
*/
2024
export class WS extends Transport {
2125
private ws: any;
2226

@@ -37,11 +41,6 @@ export class WS extends Transport {
3741
}
3842

3943
override doOpen() {
40-
if (!this.check()) {
41-
// let probe timeout
42-
return;
43-
}
44-
4544
const uri = this.uri();
4645
const protocols = this.opts.protocols;
4746

@@ -189,14 +188,4 @@ export class WS extends Transport {
189188

190189
return this.createUri(schema, query);
191190
}
192-
193-
/**
194-
* Feature detection for WebSocket.
195-
*
196-
* @return {Boolean} whether this transport is available.
197-
* @private
198-
*/
199-
private check() {
200-
return !!WebSocket;
201-
}
202191
}

‎lib/transports/webtransport.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import debugModule from "debug"; // debug()
99

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

12+
/**
13+
* @see https://developer.mozilla.org/en-US/docs/Web/API/WebTransport
14+
* @see https://caniuse.com/webtransport
15+
*/
1216
export class WT extends Transport {
1317
private transport: any;
1418
private writer: any;
@@ -18,15 +22,15 @@ export class WT extends Transport {
1822
}
1923

2024
protected doOpen() {
21-
// @ts-ignore
22-
if (typeof WebTransport !== "function") {
23-
return;
25+
try {
26+
// @ts-ignore
27+
this.transport = new WebTransport(
28+
this.createUri("https"),
29+
this.opts.transportOptions[this.name]
30+
);
31+
} catch (err) {
32+
return this.emitReserved("error", err);
2433
}
25-
// @ts-ignore
26-
this.transport = new WebTransport(
27-
this.createUri("https"),
28-
this.opts.transportOptions[this.name]
29-
);
3034

3135
this.transport.closed
3236
.then(() => {

‎test/socket.js

+60
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,66 @@ describe("Socket", function () {
3737
});
3838
});
3939

40+
it("should connect with the 2nd transport if tryAllTransports is `true` (polling)", (done) => {
41+
const socket = new Socket({
42+
transports: ["websocket", "polling"],
43+
transportOptions: {
44+
websocket: {
45+
query: {
46+
deny: 1,
47+
},
48+
},
49+
},
50+
tryAllTransports: true,
51+
});
52+
53+
socket.on("open", () => {
54+
expect(socket.transport.name).to.eql("polling");
55+
socket.close();
56+
done();
57+
});
58+
});
59+
60+
it("should connect with the 2nd transport if tryAllTransports is `true` (websocket)", (done) => {
61+
const socket = new Socket({
62+
transports: ["polling", "websocket"],
63+
transportOptions: {
64+
polling: {
65+
query: {
66+
deny: 1,
67+
},
68+
},
69+
},
70+
tryAllTransports: true,
71+
});
72+
73+
socket.on("open", () => {
74+
expect(socket.transport.name).to.eql("websocket");
75+
socket.close();
76+
done();
77+
});
78+
});
79+
80+
it("should not connect with the 2nd transport if tryAllTransports is `false`", (done) => {
81+
const socket = new Socket({
82+
transports: ["polling", "websocket"],
83+
transportOptions: {
84+
polling: {
85+
query: {
86+
deny: 1,
87+
},
88+
},
89+
},
90+
});
91+
92+
socket.on("error", (err) => {
93+
expect(err.message).to.eql(
94+
useFetch ? "fetch read error" : "xhr poll error"
95+
);
96+
done();
97+
});
98+
});
99+
40100
describe("fake timers", function () {
41101
before(function () {
42102
if (isIE11 || isAndroid || isEdge || isIPad) {

‎test/support/hooks.js

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ exports.mochaHooks = {
1919
engine = attach(httpServer, {
2020
pingInterval: 500,
2121
maxHttpBufferSize: 100,
22+
allowRequest: (req, fn) => {
23+
const denyRequest = new URL(`http://${req.url}`).searchParams.has(
24+
"deny"
25+
);
26+
fn(null, !denyRequest);
27+
},
2228
});
2329

2430
rollup(rollupConfig).then(async (bundle) => {

0 commit comments

Comments
 (0)
Please sign in to comment.