mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-01 15:48:59 +00:00
Summary: - Replace the legacy iOS shell with Pro Command, Chat, Agents, and Settings tabs. - Wire iOS chat/session/settings/diagnostics and realtime Talk flows through gateway-backed APIs. - Add gateway/session and shared chat coverage for the new iOS flow. Verification: - git diff --check - node scripts/run-vitest.mjs src/gateway/server.sessions.create.test.ts src/gateway/talk-realtime-relay.test.ts - swift test --filter ChatViewModelTests (apps/shared/OpenClawKit) - xcodebuild build for Nimrod's iPhone succeeded; install succeeded; launch was blocked because the phone was locked Known follow-up: - Preserve traceLevel in sessions.create parent runtime inheritance and keep the changelog credit in the follow-up patch.
78 lines
3.1 KiB
Swift
78 lines
3.1 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 should derive all connection inputs from this config so we
|
|
/// don't accidentally persist gateway-scoped state under different keys.
|
|
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 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
|
|
}
|
|
|
|
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.includeDeviceIdentity == rhs.includeDeviceIdentity &&
|
|
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()
|
|
}
|
|
}
|