mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 20:46:09 +00:00
* fix(ios): defer QR pairing after scanner dismissal * fix(ios): process QR pairing after scanner dismissal * fix(ios): harden QR scanner handoff * fix(ios): give QR scanner dismissal more time * fix(ios): keep onboarding open for QR trust prompt * fix(ios): keep QR trust prompt owned by onboarding * fix(ios): recover operator pairing after QR bootstrap * fix(ios): cancel stale QR scanner handoffs Co-authored-by: PollyBot13 <pollybot13@gmail.com> * fix(ios): defer QR setup until onboarding closes * fix(ios): keep QR setup links with visible settings * fix(ios): consume setup links during onboarding * fix(ios): handle setup links during onboarding launch * fix(ios): route setup links through active onboarding * fix(ios): harden QR gateway handoff * fix(ios): cancel superseded gateway attempts * fix(ios): serialize scanner result delivery * fix(ios): prevent stale gateway reconnects * fix(ios): serialize gateway target handoff * fix(ios): disable stale gateway relaunch route * fix(ios): await staged bootstrap reset * test(ios): bound gateway reset handoff * fix(ios): preserve explicit gateway handoff * fix(ios): harden gateway lifecycle ownership * chore(ios): sync native i18n inventory * test(ios): align gateway ownership assertions * refactor(ios): remove superseded gateway helpers * fix(ios): keep gateway auth route scoped * fix(ios): restore gateway target review state * fix(protocol): refresh Swift plugin approval model * test(ios): isolate state directory overrides * fix(ios): preserve watch alerts across gateway switches * fix(ios): bind deferred work to gateway ownership * docs(changelog): credit iOS gateway handoff fix * chore(i18n): sync native app inventory * test(ios): remove unused Watch approval hooks --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
81 lines
3.3 KiB
Swift
81 lines
3.3 KiB
Swift
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 derive routing and authentication ownership from the route's
|
|
/// `stableID`. TLS certificate pins prove transport trust but are not gateway identity.
|
|
struct GatewayConnectConfig {
|
|
let url: URL
|
|
let stableID: String
|
|
let tls: GatewayTLSParams?
|
|
let token: String?
|
|
let bootstrapToken: String?
|
|
let password: String?
|
|
let nodeOptions: GatewayConnectOptions
|
|
|
|
/// Stable, non-empty route identifier used for UI/event ownership.
|
|
/// 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
|
|
}
|
|
|
|
func hasSameConnectionInputs(as other: GatewayConnectConfig) -> Bool {
|
|
self.url == other.url &&
|
|
self.stableID == other.stableID &&
|
|
Self.sameTLS(self.tls, other.tls) &&
|
|
self.token == other.token &&
|
|
self.bootstrapToken == other.bootstrapToken &&
|
|
self.password == other.password &&
|
|
Self.sameOptions(self.nodeOptions, other.nodeOptions)
|
|
}
|
|
|
|
private static func sameTLS(_ lhs: GatewayTLSParams?, _ rhs: GatewayTLSParams?) -> Bool {
|
|
switch (lhs, rhs) {
|
|
case (nil, nil):
|
|
true
|
|
case let (lhs?, rhs?):
|
|
lhs.required == rhs.required &&
|
|
lhs.expectedFingerprint == rhs.expectedFingerprint &&
|
|
lhs.allowTOFU == rhs.allowTOFU &&
|
|
lhs.storeKey == rhs.storeKey
|
|
default:
|
|
false
|
|
}
|
|
}
|
|
|
|
private static func sameOptions(_ lhs: GatewayConnectOptions, _ rhs: GatewayConnectOptions) -> Bool {
|
|
let lhsScopes = Self.normalizedValues(lhs.scopes)
|
|
let rhsScopes = Self.normalizedValues(rhs.scopes)
|
|
let lhsCaps = Self.normalizedValues(lhs.caps)
|
|
let rhsCaps = Self.normalizedValues(rhs.caps)
|
|
let lhsCommands = Self.normalizedValues(lhs.commands)
|
|
let rhsCommands = Self.normalizedValues(rhs.commands)
|
|
return lhs.role == rhs.role &&
|
|
lhs.scopesAreExplicit == rhs.scopesAreExplicit &&
|
|
lhs.clientId == rhs.clientId &&
|
|
lhs.clientMode == rhs.clientMode &&
|
|
lhs.clientDisplayName == rhs.clientDisplayName &&
|
|
lhs.deviceIdentityProfile == rhs.deviceIdentityProfile &&
|
|
lhs.includeDeviceIdentity == rhs.includeDeviceIdentity &&
|
|
lhs.allowStoredDeviceAuth == rhs.allowStoredDeviceAuth &&
|
|
lhs.deviceAuthGatewayID == rhs.deviceAuthGatewayID &&
|
|
lhsScopes == rhsScopes &&
|
|
lhsCaps == rhsCaps &&
|
|
lhsCommands == rhsCommands &&
|
|
lhs.permissions == rhs.permissions
|
|
}
|
|
|
|
private static func normalizedValues(_ values: [String]) -> [String] {
|
|
values.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
.filter { !$0.isEmpty }
|
|
.sorted()
|
|
}
|
|
}
|