From 8204074bdfbdedff39de1dcb82f9f7a86925a267 Mon Sep 17 00:00:00 2001 From: Aiden McClelland Date: Tue, 10 Feb 2026 13:38:12 -0700 Subject: [PATCH] chore: flatten HostnameInfo from enum to struct HostnameInfo only had one variant (Ip) after removing Tor. Flatten it into a plain struct with fields gateway, public, hostname. Remove all kind === 'ip' type guards and narrowing across SDK, frontend, and container runtime. Update DB migration to strip the kind field. --- .../DockerProcedureContainer.ts | 2 +- .../Systems/SystemForEmbassy/index.ts | 8 +--- core/src/net/net_controller.rs | 12 +++--- core/src/net/service_interface.rs | 16 +++----- core/src/version/v0_4_0_alpha_20.rs | 13 +++++-- sdk/base/lib/osBindings/HostnameInfo.ts | 1 - sdk/base/lib/util/getServiceInterface.ts | 37 ++++++++----------- .../interfaces/interface.service.ts | 22 ++++------- 8 files changed, 47 insertions(+), 64 deletions(-) diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts index 029483212..b6fd6f033 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/DockerProcedureContainer.ts @@ -93,7 +93,7 @@ export class DockerProcedureContainer extends Drop { )?.hostnameInfo || {}, ) .flatMap((h) => h) - .flatMap((h) => (h.kind === "onion" ? [h.hostname.value] : [])), + .map((h) => h.hostname.value), ).values(), ] const certChain = await effects.getSslCertificate({ diff --git a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts index c2f84c19f..82dc037f9 100644 --- a/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts +++ b/container-runtime/src/Adapters/Systems/SystemForEmbassy/index.ts @@ -1244,12 +1244,8 @@ async function updateConfig( ? "" : catchFn( () => - (specValue.target === "lan-address" - ? filled.addressInfo!.filter({ kind: "mdns" }) || - filled.addressInfo!.onion - : filled.addressInfo!.onion || - filled.addressInfo!.filter({ kind: "mdns" }) - ).hostnames[0].hostname.value, + filled.addressInfo!.filter({ kind: "mdns" })! + .hostnames[0].hostname.value, ) || "" mutConfigValue[key] = url } diff --git a/core/src/net/net_controller.rs b/core/src/net/net_controller.rs index b1c6e0a6a..06cc087b5 100644 --- a/core/src/net/net_controller.rs +++ b/core/src/net/net_controller.rs @@ -481,7 +481,7 @@ impl NetServiceData { i.device_type != Some(NetworkInterfaceType::Wireguard) }) { - bind_hostname_info.push(HostnameInfo::Ip { + bind_hostname_info.push(HostnameInfo { gateway: gateway.clone(), public: false, hostname: IpHostname::Local { @@ -514,7 +514,7 @@ impl NetServiceData { .as_ref() .map_or(false, |ssl| ssl.preferred_external_port == 443) { - bind_hostname_info.push(HostnameInfo::Ip { + bind_hostname_info.push(HostnameInfo { gateway: gateway.clone(), public, hostname: IpHostname::Domain { @@ -524,7 +524,7 @@ impl NetServiceData { }, }); } else { - bind_hostname_info.push(HostnameInfo::Ip { + bind_hostname_info.push(HostnameInfo { gateway: gateway.clone(), public, hostname: IpHostname::Domain { @@ -540,7 +540,7 @@ impl NetServiceData { if let Some(ip_info) = &info.ip_info { let public = info.public(); if let Some(wan_ip) = ip_info.wan_ip { - bind_hostname_info.push(HostnameInfo::Ip { + bind_hostname_info.push(HostnameInfo { gateway: gateway.clone(), public: true, hostname: IpHostname::Ipv4 { @@ -554,7 +554,7 @@ impl NetServiceData { match ipnet { IpNet::V4(net) => { if !public { - bind_hostname_info.push(HostnameInfo::Ip { + bind_hostname_info.push(HostnameInfo { gateway: gateway.clone(), public, hostname: IpHostname::Ipv4 { @@ -566,7 +566,7 @@ impl NetServiceData { } } IpNet::V6(net) => { - bind_hostname_info.push(HostnameInfo::Ip { + bind_hostname_info.push(HostnameInfo { gateway: gateway.clone(), public: public && !ipv6_is_local(net.addr()), hostname: IpHostname::Ipv6 { diff --git a/core/src/net/service_interface.rs b/core/src/net/service_interface.rs index ca960e569..6d04ae891 100644 --- a/core/src/net/service_interface.rs +++ b/core/src/net/service_interface.rs @@ -9,20 +9,14 @@ use crate::{GatewayId, HostId, ServiceInterfaceId}; #[derive(Clone, Debug, Deserialize, Serialize, TS)] #[ts(export)] #[serde(rename_all = "camelCase")] -#[serde(rename_all_fields = "camelCase")] -#[serde(tag = "kind")] -pub enum HostnameInfo { - Ip { - gateway: GatewayInfo, - public: bool, - hostname: IpHostname, - }, +pub struct HostnameInfo { + pub gateway: GatewayInfo, + pub public: bool, + pub hostname: IpHostname, } impl HostnameInfo { pub fn to_san_hostname(&self) -> InternedString { - match self { - Self::Ip { hostname, .. } => hostname.to_san_hostname(), - } + self.hostname.to_san_hostname() } } diff --git a/core/src/version/v0_4_0_alpha_20.rs b/core/src/version/v0_4_0_alpha_20.rs index 6cb902bde..3320af322 100644 --- a/core/src/version/v0_4_0_alpha_20.rs +++ b/core/src/version/v0_4_0_alpha_20.rs @@ -58,7 +58,7 @@ impl VersionT for Version { } // Remove onion entries from hostnameInfo in server host - remove_onion_hostname_info( + migrate_hostname_info( db.get_mut("public") .and_then(|p| p.get_mut("serverInfo")) .and_then(|s| s.get_mut("network")) @@ -74,7 +74,7 @@ impl VersionT for Version { for (_, package) in packages.iter_mut() { if let Some(hosts) = package.get_mut("hosts").and_then(|h| h.as_object_mut()) { for (_, host) in hosts.iter_mut() { - remove_onion_hostname_info(Some(host)); + migrate_hostname_info(Some(host)); } } } @@ -96,14 +96,21 @@ impl VersionT for Version { } } -fn remove_onion_hostname_info(host: Option<&mut Value>) { +fn migrate_hostname_info(host: Option<&mut Value>) { if let Some(hostname_info) = host .and_then(|h| h.get_mut("hostnameInfo")) .and_then(|h| h.as_object_mut()) { for (_, infos) in hostname_info.iter_mut() { if let Some(arr) = infos.as_array_mut() { + // Remove onion entries arr.retain(|info| info.get("kind").and_then(|k| k.as_str()) != Some("onion")); + // Strip "kind" field from remaining entries (HostnameInfo flattened from enum to struct) + for info in arr.iter_mut() { + if let Some(obj) = info.as_object_mut() { + obj.remove("kind"); + } + } } } } diff --git a/sdk/base/lib/osBindings/HostnameInfo.ts b/sdk/base/lib/osBindings/HostnameInfo.ts index 5865c6381..4d80dd43f 100644 --- a/sdk/base/lib/osBindings/HostnameInfo.ts +++ b/sdk/base/lib/osBindings/HostnameInfo.ts @@ -3,7 +3,6 @@ import type { GatewayInfo } from './GatewayInfo' import type { IpHostname } from './IpHostname' export type HostnameInfo = { - kind: 'ip' gateway: GatewayInfo public: boolean hostname: IpHostname diff --git a/sdk/base/lib/util/getServiceInterface.ts b/sdk/base/lib/util/getServiceInterface.ts index d0f5fe6d5..cc80e3aec 100644 --- a/sdk/base/lib/util/getServiceInterface.ts +++ b/sdk/base/lib/util/getServiceInterface.ts @@ -43,19 +43,19 @@ type VisibilityFilter = V extends 'public' : never type KindFilter = K extends 'mdns' ? - | (HostnameInfo & { kind: 'ip'; hostname: { kind: 'local' } }) + | (HostnameInfo & { hostname: { kind: 'local' } }) | KindFilter> : K extends 'domain' ? - | (HostnameInfo & { kind: 'ip'; hostname: { kind: 'domain' } }) + | (HostnameInfo & { hostname: { kind: 'domain' } }) | KindFilter> : K extends 'ipv4' ? - | (HostnameInfo & { kind: 'ip'; hostname: { kind: 'ipv4' } }) + | (HostnameInfo & { hostname: { kind: 'ipv4' } }) | KindFilter> : K extends 'ipv6' ? - | (HostnameInfo & { kind: 'ip'; hostname: { kind: 'ipv6' } }) + | (HostnameInfo & { hostname: { kind: 'ipv6' } }) | KindFilter> : K extends 'ip' ? KindFilter | 'ipv4' | 'ipv6'> @@ -154,16 +154,14 @@ export const addressHostToUrl = ( scheme in knownProtocols && port === knownProtocols[scheme as keyof typeof knownProtocols].defaultPort let hostname - if (host.kind === 'ip') { - if (host.hostname.kind === 'domain') { - hostname = host.hostname.value - } else if (host.hostname.kind === 'ipv6') { - hostname = IPV6_LINK_LOCAL.contains(host.hostname.value) - ? `[${host.hostname.value}%${host.hostname.scopeId}]` - : `[${host.hostname.value}]` - } else { - hostname = host.hostname.value - } + if (host.hostname.kind === 'domain') { + hostname = host.hostname.value + } else if (host.hostname.kind === 'ipv6') { + hostname = IPV6_LINK_LOCAL.contains(host.hostname.value) + ? `[${host.hostname.value}%${host.hostname.scopeId}]` + : `[${host.hostname.value}]` + } else { + hostname = host.hostname.value } return `${scheme ? `${scheme}://` : ''}${ username ? `${username}@` : '' @@ -205,16 +203,13 @@ function filterRec( hostnames = hostnames.filter( (h) => invert !== - ((kind.has('mdns') && h.kind === 'ip' && h.hostname.kind === 'local') || - (kind.has('domain') && - h.kind === 'ip' && - h.hostname.kind === 'domain') || - (kind.has('ipv4') && h.kind === 'ip' && h.hostname.kind === 'ipv4') || - (kind.has('ipv6') && h.kind === 'ip' && h.hostname.kind === 'ipv6') || + ((kind.has('mdns') && h.hostname.kind === 'local') || + (kind.has('domain') && h.hostname.kind === 'domain') || + (kind.has('ipv4') && h.hostname.kind === 'ipv4') || + (kind.has('ipv6') && h.hostname.kind === 'ipv6') || (kind.has('localhost') && ['localhost', '127.0.0.1', '::1'].includes(h.hostname.value)) || (kind.has('link-local') && - h.kind === 'ip' && h.hostname.kind === 'ipv6' && IPV6_LINK_LOCAL.contains(IpAddress.parse(h.hostname.value)))), ) diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.service.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.service.ts index 218e4d7f3..21bc013b7 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.service.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.service.ts @@ -27,9 +27,9 @@ function cmpWithRankedPredicates( return 0 } -type LanAddress = AddressWithInfo & { info: { kind: 'ip'; public: false } } +type LanAddress = AddressWithInfo & { info: { public: false } } function filterLan(a: AddressWithInfo): a is LanAddress { - return a.info.kind === 'ip' && !a.info.public + return !a.info.public } function cmpLan(host: T.Host, a: LanAddress, b: LanAddress): -1 | 0 | 1 { return cmpWithRankedPredicates(a, b, [ @@ -45,15 +45,12 @@ function cmpLan(host: T.Host, a: LanAddress, b: LanAddress): -1 | 0 | 1 { type VpnAddress = AddressWithInfo & { info: { - kind: 'ip' public: false hostname: { kind: 'ipv4' | 'ipv6' | 'domain' } } } function filterVpn(a: AddressWithInfo): a is VpnAddress { - return ( - a.info.kind === 'ip' && !a.info.public && a.info.hostname.kind !== 'local' - ) + return !a.info.public && a.info.hostname.kind !== 'local' } function cmpVpn(host: T.Host, a: VpnAddress, b: VpnAddress): -1 | 0 | 1 { return cmpWithRankedPredicates(a, b, [ @@ -68,13 +65,12 @@ function cmpVpn(host: T.Host, a: VpnAddress, b: VpnAddress): -1 | 0 | 1 { type ClearnetAddress = AddressWithInfo & { info: { - kind: 'ip' public: true hostname: { kind: 'ipv4' | 'ipv6' | 'domain' } } } function filterClearnet(a: AddressWithInfo): a is ClearnetAddress { - return a.info.kind === 'ip' && a.info.public + return a.info.public } function cmpClearnet( host: T.Host, @@ -134,10 +130,7 @@ export class InterfaceService { h, ) const info = h - const gateway = - h.kind === 'ip' - ? gateways.find(g => h.gateway.id === g.id) - : undefined + const gateway = gateways.find(g => h.gateway.id === g.id) const res = [] if (url) { res.push({ @@ -266,10 +259,9 @@ export class InterfaceService { h => this.config.accessType === 'localhost' || !( - h.kind === 'ip' && - ((h.hostname.kind === 'ipv6' && + (h.hostname.kind === 'ipv6' && utils.IPV6_LINK_LOCAL.contains(h.hostname.value)) || - h.gateway.id === 'lo') + h.gateway.id === 'lo' ), ) || [] )