diff --git a/apps/ios/Sources/Model/NodeAppModel.swift b/apps/ios/Sources/Model/NodeAppModel.swift index 75950f55a4..5b59af1585 100644 --- a/apps/ios/Sources/Model/NodeAppModel.swift +++ b/apps/ios/Sources/Model/NodeAppModel.swift @@ -1626,6 +1626,10 @@ private extension NodeAppModel { try? await Task.sleep(nanoseconds: 1_000_000_000) continue } + if !self.gatewayAutoReconnectEnabled { + try? await Task.sleep(nanoseconds: 1_000_000_000) + continue + } if await self.isOperatorConnected() { try? await Task.sleep(nanoseconds: 1_000_000_000) continue @@ -1708,6 +1712,10 @@ private extension NodeAppModel { try? await Task.sleep(nanoseconds: 1_000_000_000) continue } + if !self.gatewayAutoReconnectEnabled { + try? await Task.sleep(nanoseconds: 1_000_000_000) + continue + } if await self.isGatewayConnected() { try? await Task.sleep(nanoseconds: 1_000_000_000) continue @@ -1795,10 +1803,18 @@ private extension NodeAppModel { } GatewayDiagnostics.log("gateway connect error: \(error.localizedDescription)") + // If auth is missing/rejected, pause reconnect churn until the user intervenes. + // Reconnect loops only spam the same failing handshake and make onboarding noisy. + let lower = error.localizedDescription.lowercased() + if lower.contains("unauthorized") || lower.contains("gateway token missing") { + await MainActor.run { + self.gatewayAutoReconnectEnabled = false + } + } + // If pairing is required, stop reconnect churn. The user must approve the request // on the gateway before another connect attempt will succeed, and retry loops can // generate multiple pending requests. - let lower = error.localizedDescription.lowercased() if lower.contains("not_paired") || lower.contains("pairing required") { let requestId: String? = { // GatewayResponseError for connect decorates the message with `(requestId: ...)`. diff --git a/apps/ios/Sources/Onboarding/OnboardingWizardView.swift b/apps/ios/Sources/Onboarding/OnboardingWizardView.swift index 7320099f19..cbe9db2575 100644 --- a/apps/ios/Sources/Onboarding/OnboardingWizardView.swift +++ b/apps/ios/Sources/Onboarding/OnboardingWizardView.swift @@ -818,7 +818,7 @@ struct OnboardingWizardView: View { private func retryLastAttempt() async { self.connectingGatewayID = "retry" - self.issue = .none + // Keep current auth/pairing issue sticky while retrying to avoid Step 3 UI flip-flop. self.connectMessage = "Retrying…" self.statusLine = "Retrying last connection…" defer { self.connectingGatewayID = nil }