Skip to content

Commit 46b1161

Browse files
committed
fix(ios): suggest onboarding reset for stale gateway token
1 parent f079b8a commit 46b1161

7 files changed

Lines changed: 83 additions & 14 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Foundation
2+
import OpenClawKit
3+
4+
enum GatewayOnboardingReset {
5+
static func reset(
6+
appModel: NodeAppModel,
7+
instanceId: String,
8+
defaults: UserDefaults = .standard)
9+
{
10+
appModel.disconnectGateway()
11+
12+
let trimmedInstanceId = instanceId.trimmingCharacters(in: .whitespacesAndNewlines)
13+
if !trimmedInstanceId.isEmpty {
14+
GatewaySettingsStore.deleteGatewayCredentials(instanceId: trimmedInstanceId)
15+
}
16+
17+
GatewaySettingsStore.clearLastGatewayConnection()
18+
GatewaySettingsStore.clearPreferredGatewayStableID()
19+
GatewaySettingsStore.clearLastDiscoveredGatewayStableID()
20+
GatewayTLSStore.clearAllFingerprints()
21+
OnboardingStateStore.reset(defaults: defaults)
22+
23+
defaults.set(false, forKey: "gateway.onboardingComplete")
24+
defaults.set(false, forKey: "gateway.hasConnectedOnce")
25+
defaults.set(false, forKey: "gateway.manual.enabled")
26+
defaults.set("", forKey: "gateway.manual.host")
27+
defaults.set("", forKey: "gateway.setupCode")
28+
defaults.set(defaults.integer(forKey: "onboarding.requestID") + 1, forKey: "onboarding.requestID")
29+
}
30+
}

apps/ios/Sources/Onboarding/OnboardingWizardView.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,10 +1016,24 @@ struct OnboardingWizardView: View {
10161016
}
10171017

10181018
private func gatewayProblemPrimaryActionTitle(_ problem: GatewayConnectionProblem) -> String {
1019+
if problem.suggestsOnboardingReset { return "Scan QR again" }
10191020
problem.canTrustRotatedCertificate ? "Trust certificate" : "Retry connection"
10201021
}
10211022

10221023
private func handleGatewayProblemPrimaryAction(_ problem: GatewayConnectionProblem) async {
1024+
if problem.suggestsOnboardingReset {
1025+
GatewayOnboardingReset.reset(appModel: self.appModel, instanceId: self.instanceId)
1026+
self.gatewayToken = ""
1027+
self.gatewayPassword = ""
1028+
self.connectingGatewayID = nil
1029+
self.connectMessage = nil
1030+
self.issue = .none
1031+
self.pairingRequestId = nil
1032+
self.statusLine = "Scan a fresh setup QR code from this gateway."
1033+
self.step = .connect
1034+
self.showQRScanner = true
1035+
return
1036+
}
10231037
if problem.canTrustRotatedCertificate {
10241038
self.connectingGatewayID = "trust-certificate"
10251039
self.connectMessage = "Updating gateway certificate…"

apps/ios/Sources/RootCanvas.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct RootCanvas: View {
1515
@AppStorage("onboarding.requestID") private var onboardingRequestID: Int = 0
1616
@AppStorage("gateway.onboardingComplete") private var onboardingComplete: Bool = false
1717
@AppStorage("gateway.hasConnectedOnce") private var hasConnectedOnce: Bool = false
18+
@AppStorage("node.instanceId") private var instanceId: String = UUID().uuidString
1819
@AppStorage("gateway.preferredStableID") private var preferredGatewayStableID: String = ""
1920
@AppStorage("gateway.manual.enabled") private var manualGatewayEnabled: Bool = false
2021
@AppStorage("gateway.manual.host") private var manualGatewayHost: String = ""
@@ -102,6 +103,9 @@ struct RootCanvas: View {
102103
},
103104
retryGatewayConnection: {
104105
Task { await self.gatewayController.connectLastKnown() }
106+
},
107+
resetOnboarding: {
108+
self.resetOnboardingFromGatewayProblem()
105109
})
106110
.preferredColorScheme(.dark)
107111

@@ -429,6 +433,13 @@ struct RootCanvas: View {
429433
guard shouldPresent else { return }
430434
self.presentedSheet = .quickSetup
431435
}
436+
437+
private func resetOnboardingFromGatewayProblem() {
438+
GatewayOnboardingReset.reset(appModel: self.appModel, instanceId: self.instanceId)
439+
self.presentedSheet = nil
440+
self.onboardingAllowSkip = false
441+
self.showOnboarding = true
442+
}
432443
}
433444

434445
private struct HomeCanvasPayload: Codable {
@@ -469,6 +480,7 @@ private struct CanvasContent: View {
469480
var openChat: () -> Void
470481
var openSettings: () -> Void
471482
var retryGatewayConnection: () -> Void
483+
var resetOnboarding: () -> Void
472484

473485
private var brightenButtons: Bool {
474486
self.systemColorScheme == .light
@@ -578,12 +590,15 @@ private struct CanvasContent: View {
578590

579591
private func gatewayProblemPrimaryActionTitle(_ problem: GatewayConnectionProblem) -> String {
580592
if problem.canTrustRotatedCertificate { return "Trust certificate" }
593+
if problem.suggestsOnboardingReset { return "Reset onboarding" }
581594
return problem.retryable ? "Retry" : "Open Settings"
582595
}
583596

584597
private func handleGatewayProblemPrimaryAction(_ problem: GatewayConnectionProblem) {
585598
if problem.canTrustRotatedCertificate {
586599
Task { await self.gatewayController.trustRotatedGatewayCertificate(from: problem) }
600+
} else if problem.suggestsOnboardingReset {
601+
self.resetOnboarding()
587602
} else if problem.retryable {
588603
self.retryGatewayConnection()
589604
} else {

apps/ios/Sources/Settings/SettingsTab.swift

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,10 +1057,15 @@ struct SettingsTab: View {
10571057
}
10581058

10591059
private func gatewayProblemPrimaryActionTitle(_ problem: GatewayConnectionProblem) -> String {
1060+
if problem.suggestsOnboardingReset { return "Reset onboarding" }
10601061
problem.canTrustRotatedCertificate ? "Trust certificate" : "Retry connection"
10611062
}
10621063

10631064
private func handleGatewayProblemPrimaryAction(_ problem: GatewayConnectionProblem) async {
1065+
if problem.suggestsOnboardingReset {
1066+
self.resetOnboarding()
1067+
return
1068+
}
10641069
if problem.canTrustRotatedCertificate {
10651070
_ = await self.gatewayController.trustRotatedGatewayCertificate(from: problem)
10661071
return
@@ -1070,7 +1075,6 @@ struct SettingsTab: View {
10701075

10711076
private func resetOnboarding() {
10721077
// Disconnect first so RootCanvas doesn't instantly mark onboarding complete again.
1073-
self.appModel.disconnectGateway()
10741078
self.connectingGatewayID = nil
10751079
self.setupStatusText = nil
10761080
self.setupCode = ""
@@ -1082,19 +1086,7 @@ struct SettingsTab: View {
10821086
self.gatewayToken = ""
10831087
self.gatewayPassword = ""
10841088

1085-
let trimmedInstanceId = self.instanceId.trimmingCharacters(in: .whitespacesAndNewlines)
1086-
if !trimmedInstanceId.isEmpty {
1087-
GatewaySettingsStore.deleteGatewayCredentials(instanceId: trimmedInstanceId)
1088-
}
1089-
1090-
// Reset onboarding state + clear saved gateway connection (the two things RootCanvas checks).
1091-
GatewaySettingsStore.clearLastGatewayConnection()
1092-
GatewaySettingsStore.clearPreferredGatewayStableID()
1093-
GatewaySettingsStore.clearLastDiscoveredGatewayStableID()
1094-
// Resetting onboarding should also forget trusted gateway TLS fingerprints.
1095-
// Otherwise a restarted dev gateway can stay stuck in a local TLS cancel loop.
1096-
GatewayTLSStore.clearAllFingerprints()
1097-
OnboardingStateStore.reset()
1089+
GatewayOnboardingReset.reset(appModel: self.appModel, instanceId: self.instanceId)
10981090

10991091
// RootCanvas also short-circuits onboarding when these are true.
11001092
self.onboardingComplete = false

apps/ios/SwiftSources.input.xcfilelist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Sources/Model/NodeAppModel+WatchNotifyNormalization.swift
3535
Sources/Model/NodeAppModel.swift
3636
Sources/Model/WatchReplyCoordinator.swift
3737
Sources/Motion/MotionService.swift
38+
Sources/Onboarding/GatewayOnboardingReset.swift
3839
Sources/Onboarding/GatewayOnboardingView.swift
3940
Sources/Onboarding/OnboardingStateStore.swift
4041
Sources/Onboarding/OnboardingWizardView.swift

apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayConnectionProblem.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ public struct GatewayConnectionProblem: Equatable, Sendable {
121121
}
122122
}
123123

124+
public var suggestsOnboardingReset: Bool {
125+
self.kind == .gatewayAuthTokenMismatch
126+
}
127+
124128
public var statusText: String {
125129
switch self.kind {
126130
case .pairingRequired, .pairingRoleUpgradeRequired, .pairingScopeUpgradeRequired,

apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayErrorsTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,19 @@ import Testing
7070
#expect(problem?.needsCredentialUpdate == false)
7171
}
7272

73+
@Test func tokenMismatchSuggestsOnboardingReset() {
74+
let error = GatewayConnectAuthError(
75+
message: "token mismatch",
76+
detailCode: GatewayConnectAuthDetailCode.authTokenMismatch.rawValue,
77+
canRetryWithDeviceToken: false)
78+
79+
let problem = GatewayConnectionProblemMapper.map(error: error)
80+
81+
#expect(problem?.kind == .gatewayAuthTokenMismatch)
82+
#expect(problem?.suggestsOnboardingReset == true)
83+
#expect(problem?.needsCredentialUpdate == true)
84+
}
85+
7386
@Test func cancelledTransportDoesNotReplaceStructuredPairingProblem() {
7487
let pairing = GatewayConnectAuthError(
7588
message: "pairing required",

0 commit comments

Comments
 (0)