Skip to content

Commit

Permalink
fix: re-implement URL parsing with WHATWG URL API (#332)
Browse files Browse the repository at this point in the history
Co-authored-by: Nenad Ticaric <[email protected]>
  • Loading branch information
pmmmwh and nticaric authored Mar 23, 2021
1 parent 2689dd7 commit 652a31a
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 21 deletions.
37 changes: 19 additions & 18 deletions sockets/utils/getSocketUrlParts.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const url = require('native-url');
const getCurrentScriptSource = require('./getCurrentScriptSource');
const parseQuery = require('./parseQuery');

Expand All @@ -19,40 +18,42 @@ const parseQuery = require('./parseQuery');
*/
function getSocketUrlParts(resourceQuery) {
const scriptSource = getCurrentScriptSource();
const urlParts = url.parse(scriptSource || '');

let url = {};
try {
// The placeholder `baseURL` with `window.location.href`,
// is to allow parsing of path-relative or protocol-relative URLs,
// and will have no effect if `scriptSource` is a fully valid URL.
url = new URL(scriptSource, window.location.href);
} catch (e) {
// URL parsing failed, do nothing.
// We will still proceed to see if we can recover using `resourceQuery`
}

/** @type {string | undefined} */
let auth;
let hostname = urlParts.hostname;
let protocol = urlParts.protocol;
let hostname = url.hostname;
let protocol = url.protocol;
let pathname = '/sockjs-node'; // This is hard-coded in WDS
let port = urlParts.port;
let port = url.port;

// FIXME:
// This is a hack to work-around `native-url`'s parse method,
// which filters out falsy values when concatenating the `auth` string.
// In reality, we need to check for both values to correctly inject them.
// Ref: GoogleChromeLabs/native-url#32
// The placeholder `baseURL` is to allow parsing of relative paths,
// and will have no effect if `scriptSource` is a proper URL.
const authUrlParts = new URL(scriptSource, 'http://foo.bar');
// Parse authentication credentials in case we need them
if (authUrlParts.username) {
auth = authUrlParts.username;
if (url.username) {
auth = url.username;

// Since HTTP basic authentication does not allow empty username,
// we only include password if the username is not empty.
if (authUrlParts.password) {
if (url.password) {
// Result: <username>:<password>
auth = auth.concat(':', authUrlParts.password);
auth = auth.concat(':', url.password);
}
}

// Check for IPv4 and IPv6 host addresses that corresponds to `any`/`empty`.
// This is important because `hostname` can be empty for some hosts,
// such as `about:blank` or `file://` URLs.
const isEmptyHostname =
urlParts.hostname === '0.0.0.0' || urlParts.hostname === '::' || urlParts.hostname === null;
url.hostname === '0.0.0.0' || url.hostname === '[::]' || url.hostname === null;

// We only re-assign the hostname if we are using HTTP/HTTPS protocols
if (
Expand Down
4 changes: 1 addition & 3 deletions test/mocks/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ const location = (href) => {

cleanupHandlers.add(mockRestore);

return {
mockRestore,
};
return { mockRestore };
};

module.exports = location;
26 changes: 26 additions & 0 deletions test/unit/getSocketUrlParts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,30 @@ describe('getSocketUrlParts', () => {
'[React Refresh] Failed to get an URL for the socket connection.'
);
});

it('should work when script source has no protocol defined and location is https', () => {
mockLocation('https://localhost:8080');
getCurrentScriptSource.mockImplementationOnce(() => '//localhost:8080/index.js');

expect(getSocketUrlParts()).toStrictEqual({
auth: undefined,
hostname: 'localhost',
pathname: '/sockjs-node',
port: '8080',
protocol: 'https:',
});
});

it('should work when script source has no protocol defined and location is http', () => {
mockLocation('http://localhost:8080');
getCurrentScriptSource.mockImplementationOnce(() => '//localhost:8080/index.js');

expect(getSocketUrlParts()).toStrictEqual({
auth: undefined,
hostname: 'localhost',
pathname: '/sockjs-node',
port: '8080',
protocol: 'http:',
});
});
});

0 comments on commit 652a31a

Please sign in to comment.