142 lines
4.7 KiB
JavaScript
142 lines
4.7 KiB
JavaScript
|
const getCurrentScriptSource = require('./getCurrentScriptSource.js');
|
||
|
|
||
|
/**
|
||
|
* @typedef {Object} SocketUrlParts
|
||
|
* @property {string} [auth]
|
||
|
* @property {string} hostname
|
||
|
* @property {string} [protocol]
|
||
|
* @property {string} pathname
|
||
|
* @property {string} [port]
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Parse current location and Webpack's `__resourceQuery` into parts that can create a valid socket URL.
|
||
|
* @param {string} [resourceQuery] The Webpack `__resourceQuery` string.
|
||
|
* @param {import('./getWDSMetadata').WDSMetaObj} [metadata] The parsed WDS metadata object.
|
||
|
* @returns {SocketUrlParts} The parsed URL parts.
|
||
|
* @see https://webpack.js.org/api/module-variables/#__resourcequery-webpack-specific
|
||
|
*/
|
||
|
function getSocketUrlParts(resourceQuery, metadata) {
|
||
|
if (typeof metadata === 'undefined') {
|
||
|
metadata = {};
|
||
|
}
|
||
|
|
||
|
/** @type {SocketUrlParts} */
|
||
|
let urlParts = {};
|
||
|
|
||
|
// If the resource query is available,
|
||
|
// parse it and ignore everything we received from the script host.
|
||
|
if (resourceQuery) {
|
||
|
const parsedQuery = {};
|
||
|
const searchParams = new URLSearchParams(resourceQuery.slice(1));
|
||
|
searchParams.forEach(function (value, key) {
|
||
|
parsedQuery[key] = value;
|
||
|
});
|
||
|
|
||
|
urlParts.hostname = parsedQuery.sockHost;
|
||
|
urlParts.pathname = parsedQuery.sockPath;
|
||
|
urlParts.port = parsedQuery.sockPort;
|
||
|
|
||
|
// Make sure the protocol from resource query has a trailing colon
|
||
|
if (parsedQuery.sockProtocol) {
|
||
|
urlParts.protocol = parsedQuery.sockProtocol + ':';
|
||
|
}
|
||
|
} else {
|
||
|
const scriptSource = getCurrentScriptSource();
|
||
|
|
||
|
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`
|
||
|
}
|
||
|
|
||
|
// Parse authentication credentials in case we need them
|
||
|
if (url.username) {
|
||
|
// Since HTTP basic authentication does not allow empty username,
|
||
|
// we only include password if the username is not empty.
|
||
|
// Result: <username> or <username>:<password>
|
||
|
urlParts.auth = url.username;
|
||
|
if (url.password) {
|
||
|
urlParts.auth += ':' + url.password;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `file://` URLs has `'null'` origin
|
||
|
if (url.origin !== 'null') {
|
||
|
urlParts.hostname = url.hostname;
|
||
|
}
|
||
|
|
||
|
urlParts.protocol = url.protocol;
|
||
|
urlParts.port = url.port;
|
||
|
}
|
||
|
|
||
|
if (!urlParts.pathname) {
|
||
|
if (metadata.version === 4) {
|
||
|
// This is hard-coded in WDS v4
|
||
|
urlParts.pathname = '/ws';
|
||
|
} else {
|
||
|
// This is hard-coded in WDS v3
|
||
|
urlParts.pathname = '/sockjs-node';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check for IPv4 and IPv6 host addresses that correspond 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;
|
||
|
// We only re-assign the hostname if it is empty,
|
||
|
// and if we are using HTTP/HTTPS protocols.
|
||
|
if (
|
||
|
isEmptyHostname &&
|
||
|
window.location.hostname &&
|
||
|
window.location.protocol.indexOf('http') === 0
|
||
|
) {
|
||
|
urlParts.hostname = window.location.hostname;
|
||
|
}
|
||
|
|
||
|
// We only re-assign `protocol` when `protocol` is unavailable,
|
||
|
// or if `hostname` is available and is empty,
|
||
|
// since otherwise we risk creating an invalid URL.
|
||
|
// We also do this when no sockProtocol was passed to the config and 'https' is used,
|
||
|
// as it mandates the use of secure sockets.
|
||
|
if (
|
||
|
!urlParts.protocol ||
|
||
|
(urlParts.hostname &&
|
||
|
(isEmptyHostname || (!resourceQuery && window.location.protocol === 'https:')))
|
||
|
) {
|
||
|
urlParts.protocol = window.location.protocol;
|
||
|
}
|
||
|
|
||
|
// We only re-assign port when it is not available
|
||
|
if (!urlParts.port) {
|
||
|
urlParts.port = window.location.port;
|
||
|
}
|
||
|
|
||
|
if (!urlParts.hostname || !urlParts.pathname) {
|
||
|
throw new Error(
|
||
|
[
|
||
|
'[React Refresh] Failed to get an URL for the socket connection.',
|
||
|
"This usually means that the current executed script doesn't have a `src` attribute set.",
|
||
|
'You should either specify the socket path parameters under the `devServer` key in your Webpack config, or use the `overlay` option.',
|
||
|
'https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/API.md#overlay',
|
||
|
].join('\n')
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
auth: urlParts.auth,
|
||
|
hostname: urlParts.hostname,
|
||
|
pathname: urlParts.pathname,
|
||
|
protocol: urlParts.protocol,
|
||
|
port: urlParts.port || undefined,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
module.exports = getSocketUrlParts;
|