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 = {}; } 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` } /** @type {string | undefined} */ let auth; /** @type {string | undefined} */ let hostname = url.hostname; /** @type {string | undefined} */ let protocol = url.protocol; /** @type {string | undefined} */ let port = url.port; // This is hard-coded in WDS v3 let pathname = '/sockjs-node'; if (metadata.version === 4) { // This is hard-coded in WDS v4 pathname = '/ws'; } // 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 : auth = url.username; if (url.password) { auth += ':' + url.password; } } // If the resource query is available, // parse it and overwrite everything we received from the script host. const parsedQuery = {}; if (resourceQuery) { const searchParams = new URLSearchParams(resourceQuery.slice(1)); searchParams.forEach(function (value, key) { parsedQuery[key] = value; }); } hostname = parsedQuery.sockHost || hostname; pathname = parsedQuery.sockPath || pathname; port = parsedQuery.sockPort || port; // Make sure the protocol from resource query has a trailing colon if (parsedQuery.sockProtocol) { protocol = parsedQuery.sockProtocol + ':'; } // 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 = hostname === '0.0.0.0' || hostname === '[::]' || !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') !== -1 ) { hostname = window.location.hostname; } // We only re-assign `protocol` when `hostname` is available and is empty, // since otherwise we risk creating an invalid URL. // We also do this when 'https' is used as it mandates the use of secure sockets. if (hostname && (isEmptyHostname || window.location.protocol === 'https:')) { protocol = window.location.protocol; } // We only re-assign port when it is not available if (!port) { port = window.location.port; } if (!hostname || !pathname || !port) { 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: auth, hostname: hostname, pathname: pathname, protocol: protocol, port: port, }; } module.exports = getSocketUrlParts;