iOS: align node permissions and notifications

This commit is contained in:
Mariano Belinky
2026-01-31 02:27:51 +01:00
parent 65dedef65b
commit b17e6fdd07
5 changed files with 290 additions and 8 deletions

View File

@@ -1,14 +1,39 @@
import OpenClawKit
import AVFoundation
import CoreLocation
import Darwin
import Foundation
import Network
import Observation
import ReplayKit
import SwiftUI
import UIKit
@MainActor
@Observable
final class GatewayConnectionController {
struct PermissionStatusProvider: Sendable {
var cameraStatus: @Sendable () -> AVAuthorizationStatus
var microphoneStatus: @Sendable () -> AVAuthorizationStatus
var locationStatus: @Sendable () -> CLAuthorizationStatus
var locationServicesEnabled: @Sendable () -> Bool
var screenRecordingAvailable: @Sendable () -> Bool
static func live() -> PermissionStatusProvider {
PermissionStatusProvider(
cameraStatus: { AVCaptureDevice.authorizationStatus(for: .video) },
microphoneStatus: { AVCaptureDevice.authorizationStatus(for: .audio) },
locationStatus: {
if #available(iOS 14.0, *) {
return CLLocationManager.authorizationStatus()
}
return CLLocationManager().authorizationStatus
},
locationServicesEnabled: { CLLocationManager.locationServicesEnabled() },
screenRecordingAvailable: { RPScreenRecorder.shared().isAvailable })
}
}
private(set) var gateways: [GatewayDiscoveryModel.DiscoveredGateway] = []
private(set) var discoveryStatusText: String = "Idle"
private(set) var discoveryDebugLog: [GatewayDiscoveryModel.DebugLogEntry] = []
@@ -16,9 +41,15 @@ final class GatewayConnectionController {
private let discovery = GatewayDiscoveryModel()
private weak var appModel: NodeAppModel?
private var didAutoConnect = false
private let permissionProvider: PermissionStatusProvider
init(appModel: NodeAppModel, startDiscovery: Bool = true) {
init(
appModel: NodeAppModel,
startDiscovery: Bool = true,
permissionProvider: PermissionStatusProvider = PermissionStatusProvider.live())
{
self.appModel = appModel
self.permissionProvider = permissionProvider
GatewaySettingsStore.bootstrapPersistence()
let defaults = UserDefaults.standard
@@ -282,7 +313,7 @@ final class GatewayConnectionController {
scopes: [],
caps: self.currentCaps(),
commands: self.currentCommands(),
permissions: [:],
permissions: self.currentPermissions(),
clientId: "openclaw-ios",
clientMode: "node",
clientDisplayName: displayName)
@@ -335,10 +366,6 @@ final class GatewayConnectionController {
OpenClawCanvasA2UICommand.reset.rawValue,
OpenClawScreenCommand.record.rawValue,
OpenClawSystemCommand.notify.rawValue,
OpenClawSystemCommand.which.rawValue,
OpenClawSystemCommand.run.rawValue,
OpenClawSystemCommand.execApprovalsGet.rawValue,
OpenClawSystemCommand.execApprovalsSet.rawValue,
]
let caps = Set(self.currentCaps())
@@ -354,6 +381,32 @@ final class GatewayConnectionController {
return commands
}
private func currentPermissions() -> [String: Bool] {
let camera = self.permissionProvider.cameraStatus()
let microphone = self.permissionProvider.microphoneStatus()
let locationStatus = self.permissionProvider.locationStatus()
let locationEnabled = self.permissionProvider.locationServicesEnabled()
let screenRecordingAvailable = self.permissionProvider.screenRecordingAvailable()
return [
"camera": camera == .authorized,
"microphone": microphone == .authorized,
"location": locationEnabled && Self.isLocationAuthorized(status: locationStatus),
"screenRecording": screenRecordingAvailable,
]
}
private static func isLocationAuthorized(status: CLAuthorizationStatus) -> Bool {
switch status {
case .authorizedAlways, .authorizedWhenInUse:
return true
case .authorized:
return true
default:
return false
}
}
private func platformString() -> String {
let v = ProcessInfo.processInfo.operatingSystemVersion
let name = switch UIDevice.current.userInterfaceIdiom {
@@ -407,6 +460,10 @@ extension GatewayConnectionController {
self.currentCommands()
}
func _test_currentPermissions() -> [String: Bool] {
self.currentPermissions()
}
func _test_platformString() -> String {
self.platformString()
}