iOS: centralize gateway connect config

This commit is contained in:
Mariano Belinky
2026-02-08 12:13:30 +01:00
parent 8ab192bc55
commit be8e23f013
3 changed files with 56 additions and 4 deletions

View File

@@ -0,0 +1,27 @@
import Foundation
import OpenClawKit
/// Single source of truth for "how we connect" to the current gateway.
///
/// The iOS app maintains two WebSocket sessions to the same gateway:
/// - a `role=node` session for device capabilities (`node.invoke.*`)
/// - a `role=operator` session for chat/talk/config (`chat.*`, `talk.*`, etc.)
///
/// Both sessions should derive all connection inputs from this config so we
/// don't accidentally persist gateway-scoped state under different keys.
struct GatewayConnectConfig: Sendable {
let url: URL
let stableID: String
let tls: GatewayTLSParams?
let token: String?
let password: String?
let nodeOptions: GatewayConnectOptions
/// Stable, non-empty identifier used for gateway-scoped persistence keys.
/// If the caller doesn't provide a stableID, fall back to URL identity.
var effectiveStableID: String {
let trimmed = self.stableID.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty { return self.url.absoluteString }
return trimmed
}
}

View File

@@ -291,7 +291,8 @@ final class GatewayConnectionController {
private func attemptAutoReconnectIfNeeded() {
guard let appModel = self.appModel else { return }
guard appModel.gatewayAutoReconnectEnabled else { return }
guard appModel.gatewayServerName == nil else { return }
// Avoid starting duplicate connect loops while a prior config is active.
guard appModel.activeGatewayConnectConfig == nil else { return }
guard UserDefaults.standard.bool(forKey: "gateway.autoconnect") else { return }
self.didAutoConnect = false
self.maybeAutoConnect()
@@ -327,13 +328,14 @@ final class GatewayConnectionController {
await MainActor.run {
appModel.gatewayStatusText = "Connecting…"
}
appModel.connectToGateway(
let cfg = GatewayConnectConfig(
url: url,
gatewayStableID: gatewayStableID,
stableID: gatewayStableID,
tls: tls,
token: token,
password: password,
connectOptions: connectOptions)
nodeOptions: connectOptions)
appModel.applyGatewayConnectConfig(cfg)
}
}

View File

@@ -116,6 +116,7 @@ final class NodeAppModel {
private var operatorConnected = false
var gatewaySession: GatewayNodeSession { self.nodeGateway }
var operatorSession: GatewayNodeSession { self.operatorGateway }
private(set) var activeGatewayConnectConfig: GatewayConnectConfig?
var cameraHUDText: String?
var cameraHUDKind: CameraHUDKind?
@@ -1465,6 +1466,13 @@ extension NodeAppModel {
let effectiveStableID = stableID.isEmpty ? url.absoluteString : stableID
let sessionBox = tls.map { WebSocketSessionBox(session: GatewayTLSPinningSession(params: $0)) }
self.activeGatewayConnectConfig = GatewayConnectConfig(
url: url,
stableID: stableID,
tls: tls,
token: token,
password: password,
nodeOptions: connectOptions)
self.prepareForGatewayConnect(url: url, stableID: effectiveStableID)
self.startOperatorGatewayLoop(
url: url,
@@ -1482,6 +1490,20 @@ extension NodeAppModel {
sessionBox: sessionBox)
}
/// Preferred entry-point: apply a single config object and start both sessions.
func applyGatewayConnectConfig(_ cfg: GatewayConnectConfig) {
self.activeGatewayConnectConfig = cfg
self.connectToGateway(
url: cfg.url,
// Preserve the caller-provided stableID (may be empty) and let connectToGateway
// derive the effective stable id consistently for persistence keys.
gatewayStableID: cfg.stableID,
tls: cfg.tls,
token: cfg.token,
password: cfg.password,
connectOptions: cfg.nodeOptions)
}
func disconnectGateway() {
self.gatewayAutoReconnectEnabled = false
self.nodeGatewayTask?.cancel()
@@ -1499,6 +1521,7 @@ extension NodeAppModel {
self.gatewayServerName = nil
self.gatewayRemoteAddress = nil
self.connectedGatewayID = nil
self.activeGatewayConnectConfig = nil
self.gatewayConnected = false
self.operatorConnected = false
self.talkMode.updateGatewayConnected(false)