diff --git a/web/projects/ui/src/app/services/config.service.ts b/web/projects/ui/src/app/services/config.service.ts index 7e21218b6..79455b411 100644 --- a/web/projects/ui/src/app/services/config.service.ts +++ b/web/projects/ui/src/app/services/config.service.ts @@ -43,7 +43,7 @@ export class ConfigService { isLocalhost(): boolean { return useMocks ? mocks.maskAs === 'localhost' - : this.hostname === 'localhost' + : this.hostname === 'localhost' || this.hostname === '127.0.0.1' } isIpv4(): boolean { @@ -52,6 +52,18 @@ export class ConfigService { : new RegExp(utils.Patterns.ipv4.regex).test(this.hostname) } + isLanIpv4(): boolean { + return useMocks + ? mocks.maskAs === 'ipv4' + : new RegExp(utils.Patterns.ipv4.regex).test(this.hostname) && + (this.hostname.startsWith('192.168.') || + this.hostname.startsWith('10.') || + (this.hostname.startsWith('172.') && + !![this.hostname.split('.').map(Number)[1]].filter( + n => n >= 16 && n < 32, + ).length)) + } + isIpv6(): boolean { return useMocks ? mocks.maskAs === 'ipv6' @@ -69,7 +81,7 @@ export class ConfigService { !this.isTor() && !this.isLocal() && !this.isLocalhost() && - !this.isIpv4() && + !this.isLanIpv4() && !this.isIpv6() } @@ -91,7 +103,7 @@ export class ConfigService { /** ${scheme}://${username}@${host}:${externalPort}${suffix} */ launchableAddress( interfaces: PackageDataEntry['serviceInterfaces'], - hosts: PackageDataEntry['hosts'], + hosts: T.Hosts, ): string { const ui = Object.values(interfaces).find( i => @@ -106,39 +118,117 @@ export class ConfigService { if (!host) return '' - const hostnameInfo = host.hostnameInfo[ui.addressInfo.internalPort] + let hostnameInfo = host.hostnameInfo[ui.addressInfo.internalPort] + hostnameInfo = hostnameInfo.filter( + h => + this.isLocalhost() || + h.kind !== 'ip' || + h.hostname.kind !== 'ipv6' || + !h.hostname.value.startsWith('fe80::'), + ) + if (this.isLocalhost()) { + const local = hostnameInfo.find( + h => h.kind === 'ip' && h.hostname.kind === 'local', + ) + if (local) { + hostnameInfo.unshift({ + kind: 'ip', + networkInterfaceId: 'lo', + public: false, + hostname: { + kind: 'local', + port: local.hostname.port, + sslPort: local.hostname.sslPort, + value: 'localhost', + }, + }) + } + } if (!hostnameInfo) return '' const addressInfo = ui.addressInfo - const scheme = this.isHttps() - ? ui.addressInfo.sslScheme === 'https' - ? 'https' - : 'http' - : ui.addressInfo.scheme === 'http' - ? 'http' - : 'https' const username = addressInfo.username ? addressInfo.username + '@' : '' const suffix = addressInfo.suffix || '' - const url = new URL(`${scheme}://${username}placeholder${suffix}`) + const url = new URL(`https://${username}placeholder${suffix}`) + const use = (hostname: { + value: string + port: number | null + sslPort: number | null + }) => { + url.hostname = hostname.value + const useSsl = + hostname.port && hostname.sslPort ? this.isHttps() : !!hostname.sslPort + url.protocol = useSsl + ? `${addressInfo.sslScheme || 'https'}:` + : `${addressInfo.scheme || 'http'}:` + const port = useSsl ? hostname.sslPort : hostname.port + const omitPort = useSsl + ? ui.addressInfo.sslScheme === 'https' && port === 443 + : ui.addressInfo.scheme === 'http' && port === 80 + if (!omitPort && port) url.port = String(port) + } + const useFirst = ( + hostnames: ( + | { + value: string + port: number | null + sslPort: number | null + } + | undefined + )[], + ) => { + const first = hostnames.find(h => h) + if (first) { + use(first) + } + return !!first + } const ipHostnames = hostnameInfo .filter(h => h.kind === 'ip') .map(h => h.hostname) as T.IpHostname[] - const domainHostname = ipHostnames.find(h => h.kind === 'domain') as { - kind: 'domain' - domain: string - subdomain: string | null - port: number | null - sslPort: number | null - } - const onionHostname = hostnameInfo.find(h => h.kind === 'onion') - ?.hostname as T.OnionHostname | undefined + const domainHostname = ipHostnames + .filter(h => h.kind === 'domain') + .map(h => h as T.IpHostname & { kind: 'domain' }) + .map(h => ({ + value: h.domain, + sslPort: h.sslPort, + port: h.port, + }))[0] + const wanIpHostname = hostnameInfo + .filter(h => h.kind === 'ip' && h.public && h.hostname.kind !== 'domain') + .map(h => h.hostname as Exclude) + .map(h => ({ + value: h.value, + sslPort: h.sslPort, + port: h.port, + }))[0] + const onionHostname = hostnameInfo + .filter(h => h.kind === 'onion') + .map(h => h as T.HostnameInfo & { kind: 'onion' }) + .map(h => ({ + value: h.hostname.value, + sslPort: h.hostname.sslPort, + port: h.hostname.port, + }))[0] + const localHostname = ipHostnames + .filter(h => h.kind === 'local') + .map(h => h as T.IpHostname & { kind: 'local' }) + .map(h => ({ value: h.value, sslPort: h.sslPort, port: h.port }))[0] - if (this.isClearnet() && domainHostname) { - url.hostname = domainHostname.domain - } else if (this.isTor() && onionHostname) { - url.hostname = onionHostname.value + if (this.isClearnet()) { + if ( + !useFirst([domainHostname, wanIpHostname, onionHostname, localHostname]) + ) { + return '' + } + } else if (this.isTor()) { + if ( + !useFirst([onionHostname, domainHostname, wanIpHostname, localHostname]) + ) { + return '' + } } else if (this.isIpv6()) { const ipv6Hostname = ipHostnames.find(h => h.kind === 'ipv6') as { kind: 'ipv6' @@ -148,18 +238,19 @@ export class ConfigService { sslPort: number | null } - if (!ipv6Hostname) return '' - - url.hostname = ipv6Hostname.value - url.port = String(ipv6Hostname.sslPort || ipv6Hostname.port) + if (!useFirst([ipv6Hostname, localHostname])) { + return '' + } } else { // ipv4 or .local or localhost - const localHostname = ipHostnames.find(h => h.kind === 'local') if (!localHostname) return '' - url.hostname = this.hostname - url.port = String(localHostname.sslPort || localHostname.port) + use({ + value: this.hostname, + port: localHostname.port, + sslPort: localHostname.sslPort, + }) } return url.href