| [9af201e] | 1 | const getCurrentScriptSource = require('./getCurrentScriptSource.js');
|
|---|
| 2 |
|
|---|
| 3 | /**
|
|---|
| 4 | * @typedef {Object} SocketUrlParts
|
|---|
| 5 | * @property {string} [auth]
|
|---|
| 6 | * @property {string} hostname
|
|---|
| 7 | * @property {string} [protocol]
|
|---|
| 8 | * @property {string} pathname
|
|---|
| 9 | * @property {string} [port]
|
|---|
| 10 | */
|
|---|
| 11 |
|
|---|
| 12 | /**
|
|---|
| 13 | * Parse current location and Webpack's `__resourceQuery` into parts that can create a valid socket URL.
|
|---|
| 14 | * @param {string} [resourceQuery] The Webpack `__resourceQuery` string.
|
|---|
| 15 | * @param {import('./getWDSMetadata').WDSMetaObj} [metadata] The parsed WDS metadata object.
|
|---|
| 16 | * @returns {SocketUrlParts} The parsed URL parts.
|
|---|
| 17 | * @see https://webpack.js.org/api/module-variables/#__resourcequery-webpack-specific
|
|---|
| 18 | */
|
|---|
| 19 | function getSocketUrlParts(resourceQuery, metadata) {
|
|---|
| 20 | if (typeof metadata === 'undefined') {
|
|---|
| 21 | metadata = {};
|
|---|
| 22 | }
|
|---|
| 23 |
|
|---|
| 24 | /** @type {SocketUrlParts} */
|
|---|
| 25 | let urlParts = {};
|
|---|
| 26 |
|
|---|
| 27 | // If the resource query is available,
|
|---|
| 28 | // parse it and ignore everything we received from the script host.
|
|---|
| 29 | if (resourceQuery) {
|
|---|
| 30 | const parsedQuery = {};
|
|---|
| 31 | const searchParams = new URLSearchParams(resourceQuery.slice(1));
|
|---|
| 32 | searchParams.forEach(function (value, key) {
|
|---|
| 33 | parsedQuery[key] = value;
|
|---|
| 34 | });
|
|---|
| 35 |
|
|---|
| 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 | }
|
|---|
| 57 |
|
|---|
| 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;
|
|---|
| 76 | }
|
|---|
| 77 |
|
|---|
| 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.
|
|---|
| 89 | // This is important because `hostname` can be empty for some hosts,
|
|---|
| 90 | // such as 'about:blank' or 'file://' URLs.
|
|---|
| 91 | const isEmptyHostname =
|
|---|
| 92 | urlParts.hostname === '0.0.0.0' || urlParts.hostname === '[::]' || !urlParts.hostname;
|
|---|
| 93 | // We only re-assign the hostname if it is empty,
|
|---|
| 94 | // and if we are using HTTP/HTTPS protocols.
|
|---|
| 95 | if (
|
|---|
| 96 | isEmptyHostname &&
|
|---|
| 97 | window.location.hostname &&
|
|---|
| 98 | window.location.protocol.indexOf('http') === 0
|
|---|
| 99 | ) {
|
|---|
| 100 | urlParts.hostname = window.location.hostname;
|
|---|
| 101 | }
|
|---|
| 102 |
|
|---|
| 103 | // We only re-assign `protocol` when `protocol` is unavailable,
|
|---|
| 104 | // or if `hostname` is available and is empty,
|
|---|
| 105 | // since otherwise we risk creating an invalid URL.
|
|---|
| 106 | // We also do this when no sockProtocol was passed to the config and 'https' is used,
|
|---|
| 107 | // as it mandates the use of secure sockets.
|
|---|
| 108 | if (
|
|---|
| 109 | !urlParts.protocol ||
|
|---|
| 110 | (urlParts.hostname &&
|
|---|
| 111 | (isEmptyHostname || (!resourceQuery && window.location.protocol === 'https:')))
|
|---|
| 112 | ) {
|
|---|
| 113 | urlParts.protocol = window.location.protocol;
|
|---|
| 114 | }
|
|---|
| 115 |
|
|---|
| 116 | // We only re-assign port when it is not available
|
|---|
| 117 | if (!urlParts.port) {
|
|---|
| 118 | urlParts.port = window.location.port;
|
|---|
| 119 | }
|
|---|
| 120 |
|
|---|
| 121 | if (!urlParts.hostname || !urlParts.pathname) {
|
|---|
| 122 | throw new Error(
|
|---|
| 123 | [
|
|---|
| 124 | '[React Refresh] Failed to get an URL for the socket connection.',
|
|---|
| 125 | "This usually means that the current executed script doesn't have a `src` attribute set.",
|
|---|
| 126 | 'You should either specify the socket path parameters under the `devServer` key in your Webpack config, or use the `overlay` option.',
|
|---|
| 127 | 'https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/API.md#overlay',
|
|---|
| 128 | ].join('\n')
|
|---|
| 129 | );
|
|---|
| 130 | }
|
|---|
| 131 |
|
|---|
| 132 | return {
|
|---|
| 133 | auth: urlParts.auth,
|
|---|
| 134 | hostname: urlParts.hostname,
|
|---|
| 135 | pathname: urlParts.pathname,
|
|---|
| 136 | protocol: urlParts.protocol,
|
|---|
| 137 | port: urlParts.port || undefined,
|
|---|
| 138 | };
|
|---|
| 139 | }
|
|---|
| 140 |
|
|---|
| 141 | module.exports = getSocketUrlParts;
|
|---|