IOS Companion for @LimesKey/Serviceberry (on github)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fixes?

+77 -10
+1 -1
serviceberry/App/Constants.swift
··· 9 9 10 10 // MARK: - mDNS 11 11 static let bonjourServiceType = "_serviceberry._tcp" 12 - static let bonjourDomain = "local." 12 + static let bonjourDomain = "local." // trailing dot is standard DNS format 13 13 14 14 // MARK: - Server 15 15 static let serverPort: UInt16 = 8080
+45 -9
serviceberry/Services/MDNSDiscovery.swift
··· 11 11 @Published var discoveredServers: [ServerInfo] = [] 12 12 @Published var isSearching = false 13 13 @Published var lastError: Error? 14 + @Published var debugState: String = "idle" 14 15 15 16 /// Start browsing for Serviceberry servers 16 17 func startBrowsing() { 17 18 stopBrowsing() 18 19 discoveredServers = [] 19 20 isSearching = true 21 + debugState = "starting" 20 22 23 + // Use "local." domain with trailing dot (standard DNS format) 21 24 let descriptor = NWBrowser.Descriptor.bonjour( 22 25 type: Constants.bonjourServiceType, 23 26 domain: Constants.bonjourDomain 24 27 ) 25 28 29 + // Configure parameters for local network discovery 26 30 let parameters = NWParameters() 27 - parameters.includePeerToPeer = true 31 + parameters.allowLocalEndpointReuse = true 32 + parameters.acceptLocalOnly = true 33 + parameters.allowFastOpen = true 28 34 35 + print("[mDNS] Creating browser for type: \(Constants.bonjourServiceType)") 29 36 browser = NWBrowser(for: descriptor, using: parameters) 30 37 31 38 browser?.stateUpdateHandler = { [weak self] state in ··· 33 40 guard let self else { return } 34 41 switch state { 35 42 case .ready: 36 - break 43 + print("[mDNS] ✅ Browser ready, searching for \(Constants.bonjourServiceType)") 44 + self.debugState = "ready" 37 45 case .failed(let error): 46 + print("[mDNS] ❌ Browser failed: \(error)") 47 + print("[mDNS] ❌ Error code: \(error.debugDescription)") 48 + self.debugState = "failed: \(error.localizedDescription)" 38 49 self.lastError = error 39 50 self.isSearching = false 40 51 case .cancelled: 52 + print("[mDNS] Browser cancelled") 53 + self.debugState = "cancelled" 41 54 self.isSearching = false 42 - default: 43 - break 55 + case .waiting(let error): 56 + print("[mDNS] ⏳ Browser waiting: \(error)") 57 + self.debugState = "waiting: \(error.localizedDescription)" 58 + case .setup: 59 + print("[mDNS] Browser setup...") 60 + self.debugState = "setup" 61 + @unknown default: 62 + print("[mDNS] Browser unknown state: \(state)") 63 + self.debugState = "unknown" 44 64 } 45 65 } 46 66 } ··· 48 68 browser?.browseResultsChangedHandler = { [weak self] results, _ in 49 69 Task { @MainActor [weak self] in 50 70 guard let self else { return } 71 + print("[mDNS] Found \(results.count) service(s)") 51 72 self.processBrowseResults(results) 52 73 } 53 74 } ··· 64 85 } 65 86 connections = [] 66 87 isSearching = false 88 + debugState = "stopped" 67 89 } 68 90 69 91 private func processBrowseResults(_ results: Set<NWBrowser.Result>) { 70 92 for result in results { 71 93 guard case .service(let name, let type, let domain, _) = result.endpoint else { continue } 94 + print("[mDNS] Service: '\(name)' type: '\(type)' domain: '\(domain)'") 72 95 73 96 // Extract TXT records from metadata 74 97 var version = "unknown" ··· 159 182 // Use the resolved hostname (e.g., "turtle.local") 160 183 host = hostname 161 184 case .ipv4(let addr): 162 - // Use IPv4 address 163 - host = "\(addr)" 185 + // Use IPv4 address, strip any interface suffix (e.g., %en0) 186 + var ipStr = "\(addr)" 187 + if let percentIndex = ipStr.firstIndex(of: "%") { 188 + ipStr = String(ipStr[..<percentIndex]) 189 + } 190 + host = ipStr 164 191 case .ipv6(let addr): 165 - // Use IPv6 address 166 - host = "[\(addr)]" 192 + // Use IPv6 address, strip any interface suffix 193 + var ipStr = "\(addr)" 194 + if let percentIndex = ipStr.firstIndex(of: "%") { 195 + ipStr = String(ipStr[..<percentIndex]) 196 + } 197 + host = "[\(ipStr)]" 167 198 @unknown default: 168 199 break 169 200 } ··· 172 203 break 173 204 } 174 205 175 - guard !host.isEmpty else { return } 206 + guard !host.isEmpty else { 207 + print("[mDNS] Failed to resolve host for '\(serviceName)'") 208 + return 209 + } 210 + 211 + print("[mDNS] Resolved '\(serviceName)' -> \(host):\(port)") 176 212 177 213 let serverInfo = ServerInfo( 178 214 name: serviceName,
+31
serviceberry/Views/Onboarding/LANSetupView.swift
··· 10 10 @State private var showManualEntry = false 11 11 @State private var manualHost = "" 12 12 @State private var manualFingerprint = "" 13 + @State private var showDebugInfo = false 13 14 14 15 var body: some View { 15 16 VStack(spacing: 24) { ··· 62 63 showManualEntry = true 63 64 } 64 65 .buttonStyle(.bordered) 66 + 67 + Button(showDebugInfo ? "Hide Debug" : "Show Debug") { 68 + showDebugInfo.toggle() 69 + } 70 + .font(.caption) 71 + .foregroundStyle(.secondary) 72 + .padding(.top, 8) 73 + } 74 + 75 + if showDebugInfo { 76 + VStack(alignment: .leading, spacing: 4) { 77 + Text("Debug Info") 78 + .font(.caption.bold()) 79 + Text("Service type: \(Constants.bonjourServiceType)") 80 + .font(.caption2) 81 + Text("Browser state: \(discovery.debugState)") 82 + .font(.caption2) 83 + Text("Searching: \(discovery.isSearching ? "Yes" : "No")") 84 + .font(.caption2) 85 + Text("Servers found: \(discovery.discoveredServers.count)") 86 + .font(.caption2) 87 + if let error = discovery.lastError { 88 + Text("Error: \(error.localizedDescription)") 89 + .font(.caption2) 90 + .foregroundStyle(.red) 91 + } 92 + } 93 + .padding() 94 + .background(Color(.systemGray6)) 95 + .cornerRadius(8) 65 96 } 66 97 } 67 98