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: or : 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;