| import Foundation |
| import Network |
| import os |
|
|
| extension NodeAppModel { |
| func _test_resolveA2UIHostURL() async -> String? { |
| await self.resolveA2UIHostURL() |
| } |
|
|
| func resolveA2UIHostURL() async -> String? { |
| guard let raw = await self.gatewaySession.currentCanvasHostUrl() else { return nil } |
| let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines) |
| guard !trimmed.isEmpty, let base = URL(string: trimmed) else { return nil } |
| if let host = base.host, Self.isLoopbackHost(host) { |
| return nil |
| } |
| return base.appendingPathComponent("__openclaw__/a2ui/").absoluteString + "?platform=ios" |
| } |
|
|
| private static func isLoopbackHost(_ host: String) -> Bool { |
| let normalized = host.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() |
| if normalized.isEmpty { return true } |
| if normalized == "localhost" || normalized == "::1" || normalized == "0.0.0.0" { |
| return true |
| } |
| if normalized == "127.0.0.1" || normalized.hasPrefix("127.") { |
| return true |
| } |
| return false |
| } |
|
|
| func showA2UIOnConnectIfNeeded() async { |
| guard let a2uiUrl = await self.resolveA2UIHostURL() else { |
| await MainActor.run { |
| self.lastAutoA2uiURL = nil |
| self.screen.showDefaultCanvas() |
| } |
| return |
| } |
| let current = self.screen.urlString.trimmingCharacters(in: .whitespacesAndNewlines) |
| if current.isEmpty || current == self.lastAutoA2uiURL { |
| |
| |
| if let url = URL(string: a2uiUrl), |
| await Self.probeTCP(url: url, timeoutSeconds: 2.5) |
| { |
| self.screen.navigate(to: a2uiUrl) |
| self.lastAutoA2uiURL = a2uiUrl |
| } else { |
| self.lastAutoA2uiURL = nil |
| self.screen.showDefaultCanvas() |
| } |
| } |
| } |
|
|
| func showLocalCanvasOnDisconnect() { |
| self.lastAutoA2uiURL = nil |
| self.screen.showDefaultCanvas() |
| } |
|
|
| private static func probeTCP(url: URL, timeoutSeconds: Double) async -> Bool { |
| guard let host = url.host, !host.isEmpty else { return false } |
| let portInt = url.port ?? ((url.scheme ?? "").lowercased() == "wss" ? 443 : 80) |
| return await TCPProbe.probe( |
| host: host, |
| port: portInt, |
| timeoutSeconds: timeoutSeconds, |
| queueLabel: "a2ui.preflight") |
| } |
| } |
|
|