From 920436da656626386ee24a9dcf76db1a6830abb1 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 11 Jan 2026 02:45:37 +0100 Subject: [PATCH] fix(macos): add gateway discovery refresh --- .../Clawdbot/OnboardingView+Pages.swift | 5 ++ .../GatewayDiscoveryModel.swift | 55 ++++++++++++------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/apps/macos/Sources/Clawdbot/OnboardingView+Pages.swift b/apps/macos/Sources/Clawdbot/OnboardingView+Pages.swift index 55ef37f65..97f7144c1 100644 --- a/apps/macos/Sources/Clawdbot/OnboardingView+Pages.swift +++ b/apps/macos/Sources/Clawdbot/OnboardingView+Pages.swift @@ -116,6 +116,11 @@ extension OnboardingView { .foregroundStyle(.secondary) if self.gatewayDiscovery.gateways.isEmpty { ProgressView().controlSize(.small) + Button("Refresh") { + self.gatewayDiscovery.refreshWideAreaFallbackNow(timeoutSeconds: 5.0) + } + .buttonStyle(.link) + .help("Retry Tailscale discovery (DNS-SD).") } Spacer(minLength: 0) } diff --git a/apps/macos/Sources/ClawdbotDiscovery/GatewayDiscoveryModel.swift b/apps/macos/Sources/ClawdbotDiscovery/GatewayDiscoveryModel.swift index e4f99dce7..7547a6dba 100644 --- a/apps/macos/Sources/ClawdbotDiscovery/GatewayDiscoveryModel.swift +++ b/apps/macos/Sources/ClawdbotDiscovery/GatewayDiscoveryModel.swift @@ -112,6 +112,19 @@ public final class GatewayDiscoveryModel { self.scheduleWideAreaFallback() } + public func refreshWideAreaFallbackNow(timeoutSeconds: TimeInterval = 5.0) { + let domain = ClawdbotBonjour.wideAreaBridgeServiceDomain + Task.detached(priority: .utility) { [weak self] in + guard let self else { return } + let beacons = WideAreaGatewayDiscovery.discover(timeoutSeconds: timeoutSeconds) + await MainActor.run { [weak self] in + guard let self else { return } + self.wideAreaFallbackGateways = self.mapWideAreaBeacons(beacons, domain: domain) + self.recomputeGateways() + } + } + } + public func stop() { for browser in self.browsers.values { browser.cancel() @@ -130,6 +143,28 @@ public final class GatewayDiscoveryModel { self.statusText = "Stopped" } + private func mapWideAreaBeacons(_ beacons: [WideAreaGatewayBeacon], domain: String) -> [DiscoveredGateway] { + beacons.map { beacon in + let stableID = "wide-area|\(domain)|\(beacon.instanceName)" + let isLocal = Self.isLocalGateway( + lanHost: beacon.lanHost, + tailnetDns: beacon.tailnetDns, + displayName: beacon.displayName, + serviceName: beacon.instanceName, + local: self.localIdentity) + return DiscoveredGateway( + displayName: beacon.displayName, + lanHost: beacon.lanHost, + tailnetDns: beacon.tailnetDns, + sshPort: beacon.sshPort ?? 22, + gatewayPort: beacon.gatewayPort, + cliPath: beacon.cliPath, + stableID: stableID, + debugID: "\(beacon.instanceName)@\(beacon.host):\(beacon.port)", + isLocal: isLocal) + } + } + private func recomputeGateways() { var next = self.gatewaysByDomain.values .flatMap(\.self) @@ -231,25 +266,7 @@ public final class GatewayDiscoveryModel { if !beacons.isEmpty { await MainActor.run { [weak self] in guard let self else { return } - self.wideAreaFallbackGateways = beacons.map { beacon in - let stableID = "wide-area|\(domain)|\(beacon.instanceName)" - let isLocal = Self.isLocalGateway( - lanHost: beacon.lanHost, - tailnetDns: beacon.tailnetDns, - displayName: beacon.displayName, - serviceName: beacon.instanceName, - local: self.localIdentity) - return DiscoveredGateway( - displayName: beacon.displayName, - lanHost: beacon.lanHost, - tailnetDns: beacon.tailnetDns, - sshPort: beacon.sshPort ?? 22, - gatewayPort: beacon.gatewayPort, - cliPath: beacon.cliPath, - stableID: stableID, - debugID: "\(beacon.instanceName)@\(beacon.host):\(beacon.port)", - isLocal: isLocal) - } + self.wideAreaFallbackGateways = self.mapWideAreaBeacons(beacons, domain: domain) self.recomputeGateways() } return