Skip to content

Commit 0ce60c6

Browse files
authored
fix: port for WDS should be optional (#623)
1 parent 0625d80 commit 0ce60c6

File tree

2 files changed

+88
-64
lines changed

2 files changed

+88
-64
lines changed

sockets/utils/getSocketUrlParts.js

Lines changed: 75 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const getCurrentScriptSource = require('./getCurrentScriptSource.js');
66
* @property {string} hostname
77
* @property {string} [protocol]
88
* @property {string} pathname
9-
* @property {string} port
9+
* @property {string} [port]
1010
*/
1111

1212
/**
@@ -21,92 +21,103 @@ function getSocketUrlParts(resourceQuery, metadata) {
2121
metadata = {};
2222
}
2323

24-
const scriptSource = getCurrentScriptSource();
25-
26-
let url = {};
27-
try {
28-
// The placeholder `baseURL` with `window.location.href`,
29-
// is to allow parsing of path-relative or protocol-relative URLs,
30-
// and will have no effect if `scriptSource` is a fully valid URL.
31-
url = new URL(scriptSource, window.location.href);
32-
} catch (e) {
33-
// URL parsing failed, do nothing.
34-
// We will still proceed to see if we can recover using `resourceQuery`
35-
}
36-
37-
/** @type {string | undefined} */
38-
let auth;
39-
/** @type {string | undefined} */
40-
let hostname = url.hostname;
41-
/** @type {string | undefined} */
42-
let protocol = url.protocol;
43-
/** @type {string | undefined} */
44-
let port = url.port;
45-
46-
// This is hard-coded in WDS v3
47-
let pathname = '/sockjs-node';
48-
if (metadata.version === 4) {
49-
// This is hard-coded in WDS v4
50-
pathname = '/ws';
51-
}
52-
53-
// Parse authentication credentials in case we need them
54-
if (url.username) {
55-
// Since HTTP basic authentication does not allow empty username,
56-
// we only include password if the username is not empty.
57-
// Result: <username> or <username>:<password>
58-
auth = url.username;
59-
if (url.password) {
60-
auth += ':' + url.password;
61-
}
62-
}
24+
/** @type {SocketUrlParts} */
25+
let urlParts = {};
6326

6427
// If the resource query is available,
65-
// parse it and overwrite everything we received from the script host.
66-
const parsedQuery = {};
28+
// parse it and ignore everything we received from the script host.
6729
if (resourceQuery) {
30+
const parsedQuery = {};
6831
const searchParams = new URLSearchParams(resourceQuery.slice(1));
6932
searchParams.forEach(function (value, key) {
7033
parsedQuery[key] = value;
7134
});
72-
}
7335

74-
hostname = parsedQuery.sockHost || hostname;
75-
pathname = parsedQuery.sockPath || pathname;
76-
port = parsedQuery.sockPort || port;
36+
urlParts.hostname = parsedQuery.sockHost;
37+
urlParts.pathname = parsedQuery.sockPath;
38+
urlParts.port = parsedQuery.sockPort;
39+
40+
// Make sure the protocol from resource query has a trailing colon
41+
if (parsedQuery.sockProtocol) {
42+
urlParts.protocol = parsedQuery.sockProtocol + ':';
43+
}
44+
} else {
45+
const scriptSource = getCurrentScriptSource();
46+
47+
let url = {};
48+
try {
49+
// The placeholder `baseURL` with `window.location.href`,
50+
// is to allow parsing of path-relative or protocol-relative URLs,
51+
// and will have no effect if `scriptSource` is a fully valid URL.
52+
url = new URL(scriptSource, window.location.href);
53+
} catch (e) {
54+
// URL parsing failed, do nothing.
55+
// We will still proceed to see if we can recover using `resourceQuery`
56+
}
7757

78-
// Make sure the protocol from resource query has a trailing colon
79-
if (parsedQuery.sockProtocol) {
80-
protocol = parsedQuery.sockProtocol + ':';
58+
// Parse authentication credentials in case we need them
59+
if (url.username) {
60+
// Since HTTP basic authentication does not allow empty username,
61+
// we only include password if the username is not empty.
62+
// Result: <username> or <username>:<password>
63+
urlParts.auth = url.username;
64+
if (url.password) {
65+
urlParts.auth += ':' + url.password;
66+
}
67+
}
68+
69+
// `file://` URLs has `'null'` origin
70+
if (url.origin !== 'null') {
71+
urlParts.hostname = url.hostname;
72+
}
73+
74+
urlParts.protocol = url.protocol;
75+
urlParts.port = url.port;
8176
}
8277

83-
// Check for IPv4 and IPv6 host addresses that corresponds to any/empty.
78+
if (!urlParts.pathname) {
79+
if (metadata.version === 4) {
80+
// This is hard-coded in WDS v4
81+
urlParts.pathname = '/ws';
82+
} else {
83+
// This is hard-coded in WDS v3
84+
urlParts.pathname = '/sockjs-node';
85+
}
86+
}
87+
88+
// Check for IPv4 and IPv6 host addresses that correspond to any/empty.
8489
// This is important because `hostname` can be empty for some hosts,
8590
// such as 'about:blank' or 'file://' URLs.
86-
const isEmptyHostname = hostname === '0.0.0.0' || hostname === '[::]' || !hostname;
91+
const isEmptyHostname =
92+
urlParts.hostname === '0.0.0.0' || urlParts.hostname === '[::]' || !urlParts.hostname;
8793
// We only re-assign the hostname if it is empty,
8894
// and if we are using HTTP/HTTPS protocols.
8995
if (
9096
isEmptyHostname &&
9197
window.location.hostname &&
92-
window.location.protocol.indexOf('http') !== -1
98+
window.location.protocol.indexOf('http') === 0
9399
) {
94-
hostname = window.location.hostname;
100+
urlParts.hostname = window.location.hostname;
95101
}
96102

97-
// We only re-assign `protocol` when `hostname` is available and is empty,
103+
// We only re-assign `protocol` when `protocol` is unavailable,
104+
// or if `hostname` is available and is empty,
98105
// since otherwise we risk creating an invalid URL.
99106
// We also do this when 'https' is used as it mandates the use of secure sockets.
100-
if (hostname && (isEmptyHostname || window.location.protocol === 'https:')) {
101-
protocol = window.location.protocol;
107+
if (
108+
!urlParts.protocol ||
109+
(urlParts.hostname && (isEmptyHostname || window.location.protocol === 'https:'))
110+
) {
111+
urlParts.protocol = window.location.protocol;
102112
}
103113

104114
// We only re-assign port when it is not available
105-
if (!port) {
106-
port = window.location.port;
115+
if (!urlParts.port) {
116+
urlParts.port = window.location.port;
107117
}
108118

109-
if (!hostname || !pathname || !port) {
119+
if (!urlParts.hostname || !urlParts.pathname) {
120+
console.log(urlParts);
110121
throw new Error(
111122
[
112123
'[React Refresh] Failed to get an URL for the socket connection.',
@@ -118,11 +129,11 @@ function getSocketUrlParts(resourceQuery, metadata) {
118129
}
119130

120131
return {
121-
auth: auth,
122-
hostname: hostname,
123-
pathname: pathname,
124-
protocol: protocol,
125-
port: port,
132+
auth: urlParts.auth,
133+
hostname: urlParts.hostname,
134+
pathname: urlParts.pathname,
135+
protocol: urlParts.protocol,
136+
port: urlParts.port || undefined,
126137
};
127138
}
128139

test/sockets/getSocketUrlParts.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,20 @@ describe('getSocketUrlParts', () => {
189189
});
190190
});
191191

192+
it('should work with incomplete resource query', () => {
193+
getCurrentScriptSource.mockImplementationOnce(() => 'http://localhost:8080');
194+
195+
expect(getSocketUrlParts('?sockHost=foo.com')).toStrictEqual({
196+
auth: undefined,
197+
hostname: 'foo.com',
198+
pathname: '/sockjs-node',
199+
port: undefined,
200+
protocol: 'http:',
201+
});
202+
});
203+
192204
it('should throw if script source and resource query are not defined', () => {
205+
mockLocation('file://test/');
193206
getCurrentScriptSource.mockImplementationOnce(() => null);
194207

195208
expect(() => getSocketUrlParts(null)).toThrow(

0 commit comments

Comments
 (0)