Merge c4d727a2b810079c43cd3c72d243be94df63b86a into 9fb78453e088cd7b553d7779faa0de5c83708e70

This commit is contained in:
Eulices 2026-03-20 22:02:18 -07:00 committed by GitHub
commit fb8a5269b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -37,6 +37,9 @@ final class GatewayConnectionController {
private(set) var pendingTrustPrompt: TrustPrompt? private(set) var pendingTrustPrompt: TrustPrompt?
private let discovery = GatewayDiscoveryModel() private let discovery = GatewayDiscoveryModel()
/// Reused instance avoids creating CLLocationManager on the main thread
/// each time currentPermissions() is called, which triggers a UI-thread warning.
private let locationManager = CLLocationManager()
private weak var appModel: NodeAppModel? private weak var appModel: NodeAppModel?
private var didAutoConnect = false private var didAutoConnect = false
private var pendingServiceResolvers: [String: GatewayServiceResolver] = [:] private var pendingServiceResolvers: [String: GatewayServiceResolver] = [:]
@ -228,15 +231,25 @@ final class GatewayConnectionController {
guard let cfg = appModel.activeGatewayConnectConfig else { return } guard let cfg = appModel.activeGatewayConnectConfig else { return }
guard appModel.gatewayAutoReconnectEnabled else { return } guard appModel.gatewayAutoReconnectEnabled else { return }
let refreshedConfig = GatewayConnectConfig( Task { [weak self, weak appModel] in
url: cfg.url, guard let self, let appModel else { return }
stableID: cfg.stableID, let refreshedConfig = GatewayConnectConfig(
tls: cfg.tls, url: cfg.url,
token: cfg.token, stableID: cfg.stableID,
bootstrapToken: cfg.bootstrapToken, tls: cfg.tls,
password: cfg.password, token: cfg.token,
nodeOptions: self.makeConnectOptions(stableID: cfg.stableID)) bootstrapToken: cfg.bootstrapToken,
appModel.applyGatewayConnectConfig(refreshedConfig) password: cfg.password,
nodeOptions: await self.makeConnectOptions(stableID: cfg.stableID))
guard appModel.gatewayAutoReconnectEnabled,
let latestConfig = appModel.activeGatewayConnectConfig,
latestConfig.effectiveStableID == cfg.effectiveStableID,
latestConfig.url == cfg.url
else { return }
appModel.applyGatewayConnectConfig(refreshedConfig)
}
} }
func clearPendingTrustPrompt() { func clearPendingTrustPrompt() {
@ -462,10 +475,10 @@ final class GatewayConnectionController {
password: String?) password: String?)
{ {
guard let appModel else { return } guard let appModel else { return }
let connectOptions = self.makeConnectOptions(stableID: gatewayStableID)
Task { [weak appModel] in Task { [weak self, weak appModel] in
guard let appModel else { return } guard let self, let appModel else { return }
let connectOptions = await self.makeConnectOptions(stableID: gatewayStableID)
await MainActor.run { await MainActor.run {
appModel.gatewayStatusText = "Connecting…" appModel.gatewayStatusText = "Connecting…"
} }
@ -741,7 +754,7 @@ final class GatewayConnectionController {
"manual|\(host.lowercased())|\(port)" "manual|\(host.lowercased())|\(port)"
} }
private func makeConnectOptions(stableID: String?) -> GatewayConnectOptions { private func makeConnectOptions(stableID: String?) async -> GatewayConnectOptions {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
let displayName = self.resolvedDisplayName(defaults: defaults) let displayName = self.resolvedDisplayName(defaults: defaults)
let resolvedClientId = self.resolvedClientId(defaults: defaults, stableID: stableID) let resolvedClientId = self.resolvedClientId(defaults: defaults, stableID: stableID)
@ -751,7 +764,7 @@ final class GatewayConnectionController {
scopes: [], scopes: [],
caps: self.currentCaps(), caps: self.currentCaps(),
commands: self.currentCommands(), commands: self.currentCommands(),
permissions: self.currentPermissions(), permissions: await self.currentPermissions(),
clientId: resolvedClientId, clientId: resolvedClientId,
clientMode: "node", clientMode: "node",
clientDisplayName: displayName) clientDisplayName: displayName)
@ -887,13 +900,15 @@ final class GatewayConnectionController {
return commands return commands
} }
private func currentPermissions() -> [String: Bool] { private func currentPermissions() async -> [String: Bool] {
let speechRecognitionStatus = await Self.currentSpeechRecognitionStatus()
var permissions: [String: Bool] = [:] var permissions: [String: Bool] = [:]
permissions["camera"] = AVCaptureDevice.authorizationStatus(for: .video) == .authorized permissions["camera"] = AVCaptureDevice.authorizationStatus(for: .video) == .authorized
permissions["microphone"] = AVCaptureDevice.authorizationStatus(for: .audio) == .authorized permissions["microphone"] = AVCaptureDevice.authorizationStatus(for: .audio) == .authorized
permissions["speechRecognition"] = SFSpeechRecognizer.authorizationStatus() == .authorized permissions["speechRecognition"] = speechRecognitionStatus == .authorized
permissions["location"] = Self.isLocationAuthorized( permissions["location"] = Self.isLocationAuthorized(
status: CLLocationManager().authorizationStatus) status: self.locationManager.authorizationStatus)
&& CLLocationManager.locationServicesEnabled() && CLLocationManager.locationServicesEnabled()
permissions["screenRecording"] = RPScreenRecorder.shared().isAvailable permissions["screenRecording"] = RPScreenRecorder.shared().isAvailable
@ -921,6 +936,14 @@ final class GatewayConnectionController {
return permissions return permissions
} }
private nonisolated static func currentSpeechRecognitionStatus() async
-> SFSpeechRecognizerAuthorizationStatus
{
await Task.detached(priority: .utility) {
SFSpeechRecognizer.authorizationStatus()
}.value
}
private static func isLocationAuthorized(status: CLAuthorizationStatus) -> Bool { private static func isLocationAuthorized(status: CLAuthorizationStatus) -> Bool {
switch status { switch status {
case .authorizedAlways, .authorizedWhenInUse: case .authorizedAlways, .authorizedWhenInUse:
@ -953,8 +976,8 @@ extension GatewayConnectionController {
self.currentCommands() self.currentCommands()
} }
func _test_currentPermissions() -> [String: Bool] { func _test_currentPermissions() async -> [String: Bool] {
self.currentPermissions() await self.currentPermissions()
} }
func _test_platformString() -> String { func _test_platformString() -> String {