Skip to content

Commit 8998d6b

Browse files
feat: implement the client.webSocketURL.protocol option (#3380)
1 parent 1bb9b84 commit 8998d6b

File tree

7 files changed

+164
-35
lines changed

7 files changed

+164
-35
lines changed

client-src/utils/createSocketURL.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ function createSocketURL(parsedURL) {
2424
hostname = self.location.hostname;
2525
}
2626

27+
if (protocol === 'auto:') {
28+
protocol = self.location.protocol;
29+
}
30+
2731
// `hostname` can be empty when the script path is relative. In that case, specifying a protocol would result in an invalid URL.
28-
// When https is used in the app, secure websockets are always necessary because the browser doesn't accept non-secure websockets.
32+
// When https is used in the app, secure web sockets are always necessary because the browser doesn't accept non-secure web sockets.
2933
if (hostname && isInAddrAny && self.location.protocol === 'https:') {
3034
protocol = self.location.protocol;
3135
}

lib/options.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,18 @@
290290
"type": "object",
291291
"additionalProperties": false,
292292
"properties": {
293+
"protocol": {
294+
"anyOf": [
295+
{
296+
"enum": ["auto"]
297+
},
298+
{
299+
"type": "string",
300+
"minLength": 1
301+
}
302+
],
303+
"description": "Tells clients connected to devServer to use the provided protocol."
304+
},
293305
"host": {
294306
"type": "string",
295307
"minLength": 1,

lib/utils/DevServerPlugin.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,15 @@ class DevServerPlugin {
2828
apply(compiler) {
2929
const { options } = this;
3030

31-
/** @type {"ws" | "wss"} */
32-
const protocol = options.https ? 'wss' : 'ws';
31+
/** @type {"ws:" | "wss:" | "http:" | "https:" | "auto:"} */
32+
let protocol;
33+
34+
// We are proxying dev server and need to specify custom `host`
35+
if (typeof options.client.webSocketURL.protocol !== 'undefined') {
36+
protocol = options.client.webSocketURL.protocol;
37+
} else {
38+
protocol = options.https ? 'wss:' : 'ws:';
39+
}
3340

3441
/** @type {string} */
3542
let host;
@@ -111,7 +118,7 @@ class DevServerPlugin {
111118

112119
const webSocketURL = encodeURIComponent(
113120
new URL(
114-
`${protocol}://${ipaddr.IPv6.isIPv6(host) ? `[${host}]` : host}${
121+
`${protocol}//${ipaddr.IPv6.isIPv6(host) ? `[${host}]` : host}${
115122
port ? `:${port}` : ''
116123
}${path || '/'}${
117124
Object.keys(searchParams).length > 0

lib/utils/normalizeOptions.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@ function normalizeOptions(compiler, options, logger) {
120120
const parsedURL = new URL(options.client.webSocketURL);
121121

122122
options.client.webSocketURL = {
123+
protocol: parsedURL.protocol,
123124
host: parsedURL.hostname,
124-
port: Number(parsedURL.port),
125+
port: parsedURL.port.length > 0 ? Number(parsedURL.port) : '',
125126
path: parsedURL.pathname,
126127
};
127128
} else if (typeof options.client.webSocketURL.port === 'string') {

test/__snapshots__/validate-options.test.js.snap.webpack4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ exports[`options validate should throw an error on the "client" option with '{"w
156156
exports[`options validate should throw an error on the "client" option with '{"webSocketURL":{"port":true}}' value 1`] = `
157157
"ValidationError: Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema.
158158
- configuration.client.webSocketURL should be one of these:
159-
non-empty string | object { host?, port?, path? }
159+
non-empty string | object { protocol?, host?, port?, path? }
160160
-> When using dev server and you're proxying dev-server, the client script does not always know where to connect to.
161161
Details:
162162
* configuration.client.webSocketURL.port should be one of these:

test/__snapshots__/validate-options.test.js.snap.webpack5

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ exports[`options validate should throw an error on the "client" option with '{"w
156156
exports[`options validate should throw an error on the "client" option with '{"webSocketURL":{"port":true}}' value 1`] = `
157157
"ValidationError: Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema.
158158
- configuration.client.webSocketURL should be one of these:
159-
non-empty string | object { host?, port?, path? }
159+
non-empty string | object { protocol?, host?, port?, path? }
160160
-> When using dev server and you're proxying dev-server, the client script does not always know where to connect to.
161161
Details:
162162
* configuration.client.webSocketURL.port should be one of these:

test/e2e/web-socket-server-and-url.test.js

Lines changed: 133 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -245,16 +245,15 @@ for (const webSocketServerType of webSocketServerTypes) {
245245
});
246246
});
247247

248-
describe('should work with custom client port and path', () => {
248+
describe('should work with the "client.webSocketURL.protocol" option', () => {
249249
beforeAll((done) => {
250250
const options = {
251251
webSocketServer: webSocketServerType,
252252
port: port2,
253253
host: '0.0.0.0',
254254
client: {
255255
webSocketURL: {
256-
path: '/foo/test/bar/',
257-
port: port3,
256+
protocol: 'ws:',
258257
},
259258
},
260259
};
@@ -265,11 +264,11 @@ for (const webSocketServerType of webSocketServerTypes) {
265264
afterAll(testServer.close);
266265

267266
describe('browser client', () => {
268-
it('uses correct port and path', (done) => {
267+
it('should work', (done) => {
269268
runBrowser().then(({ page, browser }) => {
270-
waitForTest(browser, page, /foo\/test\/bar/, (websocketUrl) => {
269+
waitForTest(browser, page, /ws/, (websocketUrl) => {
271270
expect(websocketUrl).toContain(
272-
`${websocketUrlProtocol}://localhost:${port3}/foo/test/bar`
271+
`${websocketUrlProtocol}://localhost:${port2}/ws`
273272
);
274273

275274
done();
@@ -281,15 +280,15 @@ for (const webSocketServerType of webSocketServerTypes) {
281280
});
282281
});
283282

284-
describe('should work with custom client port', () => {
283+
describe('should work with the "client.webSocketURL.protocol" option using "auto:" value', () => {
285284
beforeAll((done) => {
286285
const options = {
287286
webSocketServer: webSocketServerType,
288287
port: port2,
289288
host: '0.0.0.0',
290289
client: {
291290
webSocketURL: {
292-
port: port3,
291+
protocol: 'auto:',
293292
},
294293
},
295294
};
@@ -300,11 +299,11 @@ for (const webSocketServerType of webSocketServerTypes) {
300299
afterAll(testServer.close);
301300

302301
describe('browser client', () => {
303-
it('uses correct port and path', (done) => {
302+
it('should work', (done) => {
304303
runBrowser().then(({ page, browser }) => {
305304
waitForTest(browser, page, /ws/, (websocketUrl) => {
306305
expect(websocketUrl).toContain(
307-
`${websocketUrlProtocol}://localhost:${port3}/ws`
306+
`${websocketUrlProtocol}://localhost:${port2}/ws`
308307
);
309308

310309
done();
@@ -316,15 +315,15 @@ for (const webSocketServerType of webSocketServerTypes) {
316315
});
317316
});
318317

319-
describe('should work with custom client port as string', () => {
318+
describe('should work with the "client.webSocketURL.protocol" option using "http:" value and covert to "ws"', () => {
320319
beforeAll((done) => {
321320
const options = {
322321
webSocketServer: webSocketServerType,
323322
port: port2,
324323
host: '0.0.0.0',
325324
client: {
326325
webSocketURL: {
327-
port: `${port3}`,
326+
protocol: 'http:',
328327
},
329328
},
330329
};
@@ -335,11 +334,11 @@ for (const webSocketServerType of webSocketServerTypes) {
335334
afterAll(testServer.close);
336335

337336
describe('browser client', () => {
338-
it('uses correct port and path', (done) => {
337+
it('should work', (done) => {
339338
runBrowser().then(({ page, browser }) => {
340339
waitForTest(browser, page, /ws/, (websocketUrl) => {
341340
expect(websocketUrl).toContain(
342-
`${websocketUrlProtocol}://localhost:${port3}/ws`
341+
`${websocketUrlProtocol}://localhost:${port2}/ws`
343342
);
344343

345344
done();
@@ -351,16 +350,79 @@ for (const webSocketServerType of webSocketServerTypes) {
351350
});
352351
});
353352

354-
describe('should work with custom client.webSocketURL.port and webSocketServer.options.port both as string', () => {
353+
describe('should work with the "client.webSocketURL.host" option', () => {
355354
beforeAll((done) => {
356355
const options = {
357-
webSocketServer: {
358-
type: webSocketServerType,
359-
options: {
360-
host: '0.0.0.0',
361-
port: `${port2}`,
356+
webSocketServer: webSocketServerType,
357+
port: port2,
358+
host: '0.0.0.0',
359+
client: {
360+
webSocketURL: {
361+
host: 'myhost.test',
362362
},
363363
},
364+
};
365+
testServer.startAwaitingCompilation(config, options, done);
366+
});
367+
368+
afterAll(testServer.close);
369+
370+
describe('browser client', () => {
371+
it('should work', (done) => {
372+
runBrowser().then(({ page, browser }) => {
373+
waitForTest(browser, page, /ws/, (websocketUrl) => {
374+
expect(websocketUrl).toContain(
375+
`${websocketUrlProtocol}://myhost.test:${port2}/ws`
376+
);
377+
378+
done();
379+
});
380+
381+
page.goto(`http://localhost:${port2}/main`);
382+
});
383+
});
384+
});
385+
});
386+
387+
describe('should work with the "client.webSocketURL.port" option', () => {
388+
beforeAll((done) => {
389+
const options = {
390+
webSocketServer: webSocketServerType,
391+
port: port2,
392+
host: '0.0.0.0',
393+
client: {
394+
webSocketURL: {
395+
port: port3,
396+
},
397+
},
398+
};
399+
400+
testServer.startAwaitingCompilation(config, options, done);
401+
});
402+
403+
afterAll(testServer.close);
404+
405+
describe('browser client', () => {
406+
it('should work', (done) => {
407+
runBrowser().then(({ page, browser }) => {
408+
waitForTest(browser, page, /ws/, (websocketUrl) => {
409+
expect(websocketUrl).toContain(
410+
`${websocketUrlProtocol}://localhost:${port3}/ws`
411+
);
412+
413+
done();
414+
});
415+
416+
page.goto(`http://localhost:${port2}/main`);
417+
});
418+
});
419+
});
420+
});
421+
422+
describe('should work with the "client.webSocketURL.port" option as "string"', () => {
423+
beforeAll((done) => {
424+
const options = {
425+
webSocketServer: webSocketServerType,
364426
port: port2,
365427
host: '0.0.0.0',
366428
client: {
@@ -376,7 +438,7 @@ for (const webSocketServerType of webSocketServerTypes) {
376438
afterAll(testServer.close);
377439

378440
describe('browser client', () => {
379-
it('uses correct port and path', (done) => {
441+
it('should work', (done) => {
380442
runBrowser().then(({ page, browser }) => {
381443
waitForTest(browser, page, /ws/, (websocketUrl) => {
382444
expect(websocketUrl).toContain(
@@ -392,29 +454,72 @@ for (const webSocketServerType of webSocketServerTypes) {
392454
});
393455
});
394456

395-
describe('should work with custom client host', () => {
457+
describe('should work with "client.webSocketURL.port" and "client.webSocketURL.path" options', () => {
396458
beforeAll((done) => {
397459
const options = {
398460
webSocketServer: webSocketServerType,
399461
port: port2,
400462
host: '0.0.0.0',
401463
client: {
402464
webSocketURL: {
403-
host: 'myhost.test',
465+
path: '/foo/test/bar/',
466+
port: port3,
404467
},
405468
},
406469
};
470+
407471
testServer.startAwaitingCompilation(config, options, done);
408472
});
409473

410474
afterAll(testServer.close);
411475

412476
describe('browser client', () => {
413-
it('uses correct host', (done) => {
477+
it('should work', (done) => {
478+
runBrowser().then(({ page, browser }) => {
479+
waitForTest(browser, page, /foo\/test\/bar/, (websocketUrl) => {
480+
expect(websocketUrl).toContain(
481+
`${websocketUrlProtocol}://localhost:${port3}/foo/test/bar`
482+
);
483+
484+
done();
485+
});
486+
487+
page.goto(`http://localhost:${port2}/main`);
488+
});
489+
});
490+
});
491+
});
492+
493+
describe('should work with "client.webSocketURL.port" and "webSocketServer.options.port" options as string', () => {
494+
beforeAll((done) => {
495+
const options = {
496+
webSocketServer: {
497+
type: webSocketServerType,
498+
options: {
499+
host: '0.0.0.0',
500+
port: `${port2}`,
501+
},
502+
},
503+
port: port2,
504+
host: '0.0.0.0',
505+
client: {
506+
webSocketURL: {
507+
port: `${port3}`,
508+
},
509+
},
510+
};
511+
512+
testServer.startAwaitingCompilation(config, options, done);
513+
});
514+
515+
afterAll(testServer.close);
516+
517+
describe('browser client', () => {
518+
it('should work', (done) => {
414519
runBrowser().then(({ page, browser }) => {
415520
waitForTest(browser, page, /ws/, (websocketUrl) => {
416521
expect(websocketUrl).toContain(
417-
`${websocketUrlProtocol}://myhost.test:${port2}/ws`
522+
`${websocketUrlProtocol}://localhost:${port3}/ws`
418523
);
419524

420525
done();
@@ -426,7 +531,7 @@ for (const webSocketServerType of webSocketServerTypes) {
426531
});
427532
});
428533

429-
describe('should work with custom client host, port, and path', () => {
534+
describe('should work with "client.webSocketURL.host", "webSocketServer.options.port" and "webSocketServer.options.path" options', () => {
430535
beforeAll((done) => {
431536
const options = {
432537
webSocketServer: webSocketServerType,
@@ -463,7 +568,7 @@ for (const webSocketServerType of webSocketServerTypes) {
463568
});
464569
});
465570

466-
describe('should work with the "client.webSocketURL" option and custom client path', () => {
571+
describe('should work with the "client.webSocketURL" option as "string"', () => {
467572
beforeAll((done) => {
468573
const options = {
469574
webSocketServer: webSocketServerType,
@@ -480,7 +585,7 @@ for (const webSocketServerType of webSocketServerTypes) {
480585
afterAll(testServer.close);
481586

482587
describe('browser client', () => {
483-
it('uses the correct webSocketURL hostname and path', (done) => {
588+
it('should work', (done) => {
484589
runBrowser().then(({ page, browser }) => {
485590
waitForTest(browser, page, /foo\/test\/bar/, (websocketUrl) => {
486591
expect(websocketUrl).toContain(

0 commit comments

Comments
 (0)