/** * @param {{ protocol?: string, auth?: string, hostname?: string, port?: string, pathname?: string, search?: string, hash?: string, slashes?: boolean }} objURL * @returns {string} */ function format(objURL) { var protocol = objURL.protocol || ""; if (protocol && protocol.substr(-1) !== ":") { protocol += ":"; } var auth = objURL.auth || ""; if (auth) { auth = encodeURIComponent(auth); auth = auth.replace(/%3A/i, ":"); auth += "@"; } var host = ""; if (objURL.hostname) { host = auth + (objURL.hostname.indexOf(":") === -1 ? objURL.hostname : "[".concat(objURL.hostname, "]")); if (objURL.port) { host += ":".concat(objURL.port); } } var pathname = objURL.pathname || ""; if (objURL.slashes) { host = "//".concat(host || ""); if (pathname && pathname.charAt(0) !== "/") { pathname = "/".concat(pathname); } } else if (!host) { host = ""; } var search = objURL.search || ""; if (search && search.charAt(0) !== "?") { search = "?".concat(search); } var hash = objURL.hash || ""; if (hash && hash.charAt(0) !== "#") { hash = "#".concat(hash); } pathname = pathname.replace(/[?#]/g, /** * @param {string} match * @returns {string} */ function (match) { return encodeURIComponent(match); }); search = search.replace("#", "%23"); return "".concat(protocol).concat(host).concat(pathname).concat(search).concat(hash); } /** * @param {URL & { fromCurrentScript?: boolean }} parsedURL * @returns {string} */ function createSocketURL(parsedURL) { var hostname = parsedURL.hostname; // Node.js module parses it as `::` // `new URL(urlString, [baseURLString])` parses it as '[::]' var isInAddrAny = hostname === "0.0.0.0" || hostname === "::" || hostname === "[::]"; // why do we need this check? // hostname n/a for file protocol (example, when using electron, ionic) // see: https://github.com/webpack/webpack-dev-server/pull/384 if (isInAddrAny && self.location.hostname && self.location.protocol.indexOf("http") === 0) { hostname = self.location.hostname; } var socketURLProtocol = parsedURL.protocol || self.location.protocol; // When https is used in the app, secure web sockets are always necessary because the browser doesn't accept non-secure web sockets. if (socketURLProtocol === "auto:" || hostname && isInAddrAny && self.location.protocol === "https:") { socketURLProtocol = self.location.protocol; } socketURLProtocol = socketURLProtocol.replace(/^(?:http|.+-extension|file)/i, "ws"); var socketURLAuth = ""; // `new URL(urlString, [baseURLstring])` doesn't have `auth` property // Parse authentication credentials in case we need them if (parsedURL.username) { socketURLAuth = parsedURL.username; // Since HTTP basic authentication does not allow empty username, // we only include password if the username is not empty. if (parsedURL.password) { // Result: : socketURLAuth = socketURLAuth.concat(":", parsedURL.password); } } // In case the host is a raw IPv6 address, it can be enclosed in // the brackets as the brackets are needed in the final URL string. // Need to remove those as url.format blindly adds its own set of brackets // if the host string contains colons. That would lead to non-working // double brackets (e.g. [[::]]) host // // All of these web socket url params are optionally passed in through resourceQuery, // so we need to fall back to the default if they are not provided var socketURLHostname = (hostname || self.location.hostname || "localhost").replace(/^\[(.*)\]$/, "$1"); var socketURLPort = parsedURL.port; if (!socketURLPort || socketURLPort === "0") { socketURLPort = self.location.port; } // If path is provided it'll be passed in via the resourceQuery as a // query param so it has to be parsed out of the querystring in order for the // client to open the socket to the correct location. var socketURLPathname = "/ws"; if (parsedURL.pathname && !parsedURL.fromCurrentScript) { socketURLPathname = parsedURL.pathname; } return format({ protocol: socketURLProtocol, auth: socketURLAuth, hostname: socketURLHostname, port: socketURLPort, pathname: socketURLPathname, slashes: true }); } export default createSocketURL;