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.
This commit is contained in:
Aiden McClelland
2026-02-10 13:38:12 -07:00
parent 2ee403e7de
commit 8204074bdf
8 changed files with 47 additions and 64 deletions

View File

@@ -93,7 +93,7 @@ export class DockerProcedureContainer extends Drop {
)?.hostnameInfo || {}, )?.hostnameInfo || {},
) )
.flatMap((h) => h) .flatMap((h) => h)
.flatMap((h) => (h.kind === "onion" ? [h.hostname.value] : [])), .map((h) => h.hostname.value),
).values(), ).values(),
] ]
const certChain = await effects.getSslCertificate({ const certChain = await effects.getSslCertificate({

View File

@@ -1244,12 +1244,8 @@ async function updateConfig(
? "" ? ""
: catchFn( : catchFn(
() => () =>
(specValue.target === "lan-address" filled.addressInfo!.filter({ kind: "mdns" })!
? filled.addressInfo!.filter({ kind: "mdns" }) || .hostnames[0].hostname.value,
filled.addressInfo!.onion
: filled.addressInfo!.onion ||
filled.addressInfo!.filter({ kind: "mdns" })
).hostnames[0].hostname.value,
) || "" ) || ""
mutConfigValue[key] = url mutConfigValue[key] = url
} }

View File

@@ -481,7 +481,7 @@ impl NetServiceData {
i.device_type != Some(NetworkInterfaceType::Wireguard) i.device_type != Some(NetworkInterfaceType::Wireguard)
}) })
{ {
bind_hostname_info.push(HostnameInfo::Ip { bind_hostname_info.push(HostnameInfo {
gateway: gateway.clone(), gateway: gateway.clone(),
public: false, public: false,
hostname: IpHostname::Local { hostname: IpHostname::Local {
@@ -514,7 +514,7 @@ impl NetServiceData {
.as_ref() .as_ref()
.map_or(false, |ssl| ssl.preferred_external_port == 443) .map_or(false, |ssl| ssl.preferred_external_port == 443)
{ {
bind_hostname_info.push(HostnameInfo::Ip { bind_hostname_info.push(HostnameInfo {
gateway: gateway.clone(), gateway: gateway.clone(),
public, public,
hostname: IpHostname::Domain { hostname: IpHostname::Domain {
@@ -524,7 +524,7 @@ impl NetServiceData {
}, },
}); });
} else { } else {
bind_hostname_info.push(HostnameInfo::Ip { bind_hostname_info.push(HostnameInfo {
gateway: gateway.clone(), gateway: gateway.clone(),
public, public,
hostname: IpHostname::Domain { hostname: IpHostname::Domain {
@@ -540,7 +540,7 @@ impl NetServiceData {
if let Some(ip_info) = &info.ip_info { if let Some(ip_info) = &info.ip_info {
let public = info.public(); let public = info.public();
if let Some(wan_ip) = ip_info.wan_ip { if let Some(wan_ip) = ip_info.wan_ip {
bind_hostname_info.push(HostnameInfo::Ip { bind_hostname_info.push(HostnameInfo {
gateway: gateway.clone(), gateway: gateway.clone(),
public: true, public: true,
hostname: IpHostname::Ipv4 { hostname: IpHostname::Ipv4 {
@@ -554,7 +554,7 @@ impl NetServiceData {
match ipnet { match ipnet {
IpNet::V4(net) => { IpNet::V4(net) => {
if !public { if !public {
bind_hostname_info.push(HostnameInfo::Ip { bind_hostname_info.push(HostnameInfo {
gateway: gateway.clone(), gateway: gateway.clone(),
public, public,
hostname: IpHostname::Ipv4 { hostname: IpHostname::Ipv4 {
@@ -566,7 +566,7 @@ impl NetServiceData {
} }
} }
IpNet::V6(net) => { IpNet::V6(net) => {
bind_hostname_info.push(HostnameInfo::Ip { bind_hostname_info.push(HostnameInfo {
gateway: gateway.clone(), gateway: gateway.clone(),
public: public && !ipv6_is_local(net.addr()), public: public && !ipv6_is_local(net.addr()),
hostname: IpHostname::Ipv6 { hostname: IpHostname::Ipv6 {

View File

@@ -9,20 +9,14 @@ use crate::{GatewayId, HostId, ServiceInterfaceId};
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")] pub struct HostnameInfo {
#[serde(tag = "kind")] pub gateway: GatewayInfo,
pub enum HostnameInfo { pub public: bool,
Ip { pub hostname: IpHostname,
gateway: GatewayInfo,
public: bool,
hostname: IpHostname,
},
} }
impl HostnameInfo { impl HostnameInfo {
pub fn to_san_hostname(&self) -> InternedString { pub fn to_san_hostname(&self) -> InternedString {
match self { self.hostname.to_san_hostname()
Self::Ip { hostname, .. } => hostname.to_san_hostname(),
}
} }
} }

View File

@@ -58,7 +58,7 @@ impl VersionT for Version {
} }
// Remove onion entries from hostnameInfo in server host // Remove onion entries from hostnameInfo in server host
remove_onion_hostname_info( migrate_hostname_info(
db.get_mut("public") db.get_mut("public")
.and_then(|p| p.get_mut("serverInfo")) .and_then(|p| p.get_mut("serverInfo"))
.and_then(|s| s.get_mut("network")) .and_then(|s| s.get_mut("network"))
@@ -74,7 +74,7 @@ impl VersionT for Version {
for (_, package) in packages.iter_mut() { for (_, package) in packages.iter_mut() {
if let Some(hosts) = package.get_mut("hosts").and_then(|h| h.as_object_mut()) { if let Some(hosts) = package.get_mut("hosts").and_then(|h| h.as_object_mut()) {
for (_, host) in hosts.iter_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 if let Some(hostname_info) = host
.and_then(|h| h.get_mut("hostnameInfo")) .and_then(|h| h.get_mut("hostnameInfo"))
.and_then(|h| h.as_object_mut()) .and_then(|h| h.as_object_mut())
{ {
for (_, infos) in hostname_info.iter_mut() { for (_, infos) in hostname_info.iter_mut() {
if let Some(arr) = infos.as_array_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")); 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");
}
}
} }
} }
} }

View File

@@ -3,7 +3,6 @@ import type { GatewayInfo } from './GatewayInfo'
import type { IpHostname } from './IpHostname' import type { IpHostname } from './IpHostname'
export type HostnameInfo = { export type HostnameInfo = {
kind: 'ip'
gateway: GatewayInfo gateway: GatewayInfo
public: boolean public: boolean
hostname: IpHostname hostname: IpHostname

View File

@@ -43,19 +43,19 @@ type VisibilityFilter<V extends 'public' | 'private'> = V extends 'public'
: never : never
type KindFilter<K extends FilterKinds> = K extends 'mdns' type KindFilter<K extends FilterKinds> = K extends 'mdns'
? ?
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'local' } }) | (HostnameInfo & { hostname: { kind: 'local' } })
| KindFilter<Exclude<K, 'mdns'>> | KindFilter<Exclude<K, 'mdns'>>
: K extends 'domain' : K extends 'domain'
? ?
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'domain' } }) | (HostnameInfo & { hostname: { kind: 'domain' } })
| KindFilter<Exclude<K, 'domain'>> | KindFilter<Exclude<K, 'domain'>>
: K extends 'ipv4' : K extends 'ipv4'
? ?
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'ipv4' } }) | (HostnameInfo & { hostname: { kind: 'ipv4' } })
| KindFilter<Exclude<K, 'ipv4'>> | KindFilter<Exclude<K, 'ipv4'>>
: K extends 'ipv6' : K extends 'ipv6'
? ?
| (HostnameInfo & { kind: 'ip'; hostname: { kind: 'ipv6' } }) | (HostnameInfo & { hostname: { kind: 'ipv6' } })
| KindFilter<Exclude<K, 'ipv6'>> | KindFilter<Exclude<K, 'ipv6'>>
: K extends 'ip' : K extends 'ip'
? KindFilter<Exclude<K, 'ip'> | 'ipv4' | 'ipv6'> ? KindFilter<Exclude<K, 'ip'> | 'ipv4' | 'ipv6'>
@@ -154,16 +154,14 @@ export const addressHostToUrl = (
scheme in knownProtocols && scheme in knownProtocols &&
port === knownProtocols[scheme as keyof typeof knownProtocols].defaultPort port === knownProtocols[scheme as keyof typeof knownProtocols].defaultPort
let hostname let hostname
if (host.kind === 'ip') { if (host.hostname.kind === 'domain') {
if (host.hostname.kind === 'domain') { hostname = host.hostname.value
hostname = host.hostname.value } else if (host.hostname.kind === 'ipv6') {
} else if (host.hostname.kind === 'ipv6') { hostname = IPV6_LINK_LOCAL.contains(host.hostname.value)
hostname = IPV6_LINK_LOCAL.contains(host.hostname.value) ? `[${host.hostname.value}%${host.hostname.scopeId}]`
? `[${host.hostname.value}%${host.hostname.scopeId}]` : `[${host.hostname.value}]`
: `[${host.hostname.value}]` } else {
} else { hostname = host.hostname.value
hostname = host.hostname.value
}
} }
return `${scheme ? `${scheme}://` : ''}${ return `${scheme ? `${scheme}://` : ''}${
username ? `${username}@` : '' username ? `${username}@` : ''
@@ -205,16 +203,13 @@ function filterRec(
hostnames = hostnames.filter( hostnames = hostnames.filter(
(h) => (h) =>
invert !== invert !==
((kind.has('mdns') && h.kind === 'ip' && h.hostname.kind === 'local') || ((kind.has('mdns') && h.hostname.kind === 'local') ||
(kind.has('domain') && (kind.has('domain') && h.hostname.kind === 'domain') ||
h.kind === 'ip' && (kind.has('ipv4') && h.hostname.kind === 'ipv4') ||
h.hostname.kind === 'domain') || (kind.has('ipv6') && h.hostname.kind === 'ipv6') ||
(kind.has('ipv4') && h.kind === 'ip' && h.hostname.kind === 'ipv4') ||
(kind.has('ipv6') && h.kind === 'ip' && h.hostname.kind === 'ipv6') ||
(kind.has('localhost') && (kind.has('localhost') &&
['localhost', '127.0.0.1', '::1'].includes(h.hostname.value)) || ['localhost', '127.0.0.1', '::1'].includes(h.hostname.value)) ||
(kind.has('link-local') && (kind.has('link-local') &&
h.kind === 'ip' &&
h.hostname.kind === 'ipv6' && h.hostname.kind === 'ipv6' &&
IPV6_LINK_LOCAL.contains(IpAddress.parse(h.hostname.value)))), IPV6_LINK_LOCAL.contains(IpAddress.parse(h.hostname.value)))),
) )

View File

@@ -27,9 +27,9 @@ function cmpWithRankedPredicates<T extends AddressWithInfo>(
return 0 return 0
} }
type LanAddress = AddressWithInfo & { info: { kind: 'ip'; public: false } } type LanAddress = AddressWithInfo & { info: { public: false } }
function filterLan(a: AddressWithInfo): a is LanAddress { 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 { function cmpLan(host: T.Host, a: LanAddress, b: LanAddress): -1 | 0 | 1 {
return cmpWithRankedPredicates(a, b, [ return cmpWithRankedPredicates(a, b, [
@@ -45,15 +45,12 @@ function cmpLan(host: T.Host, a: LanAddress, b: LanAddress): -1 | 0 | 1 {
type VpnAddress = AddressWithInfo & { type VpnAddress = AddressWithInfo & {
info: { info: {
kind: 'ip'
public: false public: false
hostname: { kind: 'ipv4' | 'ipv6' | 'domain' } hostname: { kind: 'ipv4' | 'ipv6' | 'domain' }
} }
} }
function filterVpn(a: AddressWithInfo): a is VpnAddress { function filterVpn(a: AddressWithInfo): a is VpnAddress {
return ( return !a.info.public && a.info.hostname.kind !== 'local'
a.info.kind === 'ip' && !a.info.public && a.info.hostname.kind !== 'local'
)
} }
function cmpVpn(host: T.Host, a: VpnAddress, b: VpnAddress): -1 | 0 | 1 { function cmpVpn(host: T.Host, a: VpnAddress, b: VpnAddress): -1 | 0 | 1 {
return cmpWithRankedPredicates(a, b, [ return cmpWithRankedPredicates(a, b, [
@@ -68,13 +65,12 @@ function cmpVpn(host: T.Host, a: VpnAddress, b: VpnAddress): -1 | 0 | 1 {
type ClearnetAddress = AddressWithInfo & { type ClearnetAddress = AddressWithInfo & {
info: { info: {
kind: 'ip'
public: true public: true
hostname: { kind: 'ipv4' | 'ipv6' | 'domain' } hostname: { kind: 'ipv4' | 'ipv6' | 'domain' }
} }
} }
function filterClearnet(a: AddressWithInfo): a is ClearnetAddress { function filterClearnet(a: AddressWithInfo): a is ClearnetAddress {
return a.info.kind === 'ip' && a.info.public return a.info.public
} }
function cmpClearnet( function cmpClearnet(
host: T.Host, host: T.Host,
@@ -134,10 +130,7 @@ export class InterfaceService {
h, h,
) )
const info = h const info = h
const gateway = const gateway = gateways.find(g => h.gateway.id === g.id)
h.kind === 'ip'
? gateways.find(g => h.gateway.id === g.id)
: undefined
const res = [] const res = []
if (url) { if (url) {
res.push({ res.push({
@@ -266,10 +259,9 @@ export class InterfaceService {
h => h =>
this.config.accessType === 'localhost' || this.config.accessType === 'localhost' ||
!( !(
h.kind === 'ip' && (h.hostname.kind === 'ipv6' &&
((h.hostname.kind === 'ipv6' &&
utils.IPV6_LINK_LOCAL.contains(h.hostname.value)) || utils.IPV6_LINK_LOCAL.contains(h.hostname.value)) ||
h.gateway.id === 'lo') h.gateway.id === 'lo'
), ),
) || [] ) || []
) )