fix(ios): probe speech permission off the main actor
Follow up on PR review by making the permission snapshot path async so the speech recognition authorization probe runs in a detached utility-priority task instead of on the main actor.\n\nAlso rebase the branch onto current origin/main and update the connect-options call sites to await the refreshed permission snapshot.
This commit is contained in:
parent
0bcff4baf5
commit
300e494653
@ -40,11 +40,6 @@ final class GatewayConnectionController {
|
||||
/// Reused instance — avoids creating CLLocationManager on the main thread
|
||||
/// each time currentPermissions() is called, which triggers a UI-thread warning.
|
||||
private let locationManager = CLLocationManager()
|
||||
/// Cached off the main thread to avoid the UI-responsiveness warning from
|
||||
/// calling SFSpeechRecognizer.authorizationStatus() on the main actor.
|
||||
private nonisolated var speechRecognitionStatus: SFSpeechRecognizerAuthorizationStatus {
|
||||
SFSpeechRecognizer.authorizationStatus()
|
||||
}
|
||||
private weak var appModel: NodeAppModel?
|
||||
private var didAutoConnect = false
|
||||
private var pendingServiceResolvers: [String: GatewayServiceResolver] = [:]
|
||||
@ -236,15 +231,18 @@ final class GatewayConnectionController {
|
||||
guard let cfg = appModel.activeGatewayConnectConfig else { return }
|
||||
guard appModel.gatewayAutoReconnectEnabled else { return }
|
||||
|
||||
let refreshedConfig = GatewayConnectConfig(
|
||||
url: cfg.url,
|
||||
stableID: cfg.stableID,
|
||||
tls: cfg.tls,
|
||||
token: cfg.token,
|
||||
bootstrapToken: cfg.bootstrapToken,
|
||||
password: cfg.password,
|
||||
nodeOptions: self.makeConnectOptions(stableID: cfg.stableID))
|
||||
appModel.applyGatewayConnectConfig(refreshedConfig)
|
||||
Task { [weak self, weak appModel] in
|
||||
guard let self, let appModel else { return }
|
||||
let refreshedConfig = GatewayConnectConfig(
|
||||
url: cfg.url,
|
||||
stableID: cfg.stableID,
|
||||
tls: cfg.tls,
|
||||
token: cfg.token,
|
||||
bootstrapToken: cfg.bootstrapToken,
|
||||
password: cfg.password,
|
||||
nodeOptions: await self.makeConnectOptions(stableID: cfg.stableID))
|
||||
appModel.applyGatewayConnectConfig(refreshedConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func clearPendingTrustPrompt() {
|
||||
@ -470,10 +468,10 @@ final class GatewayConnectionController {
|
||||
password: String?)
|
||||
{
|
||||
guard let appModel else { return }
|
||||
let connectOptions = self.makeConnectOptions(stableID: gatewayStableID)
|
||||
|
||||
Task { [weak appModel] in
|
||||
guard let appModel else { return }
|
||||
Task { [weak self, weak appModel] in
|
||||
guard let self, let appModel else { return }
|
||||
let connectOptions = await self.makeConnectOptions(stableID: gatewayStableID)
|
||||
await MainActor.run {
|
||||
appModel.gatewayStatusText = "Connecting…"
|
||||
}
|
||||
@ -749,7 +747,7 @@ final class GatewayConnectionController {
|
||||
"manual|\(host.lowercased())|\(port)"
|
||||
}
|
||||
|
||||
private func makeConnectOptions(stableID: String?) -> GatewayConnectOptions {
|
||||
private func makeConnectOptions(stableID: String?) async -> GatewayConnectOptions {
|
||||
let defaults = UserDefaults.standard
|
||||
let displayName = self.resolvedDisplayName(defaults: defaults)
|
||||
let resolvedClientId = self.resolvedClientId(defaults: defaults, stableID: stableID)
|
||||
@ -759,7 +757,7 @@ final class GatewayConnectionController {
|
||||
scopes: [],
|
||||
caps: self.currentCaps(),
|
||||
commands: self.currentCommands(),
|
||||
permissions: self.currentPermissions(),
|
||||
permissions: await self.currentPermissions(),
|
||||
clientId: resolvedClientId,
|
||||
clientMode: "node",
|
||||
clientDisplayName: displayName)
|
||||
@ -895,11 +893,13 @@ final class GatewayConnectionController {
|
||||
return commands
|
||||
}
|
||||
|
||||
private func currentPermissions() -> [String: Bool] {
|
||||
private func currentPermissions() async -> [String: Bool] {
|
||||
let speechRecognitionStatus = await Self.currentSpeechRecognitionStatus()
|
||||
|
||||
var permissions: [String: Bool] = [:]
|
||||
permissions["camera"] = AVCaptureDevice.authorizationStatus(for: .video) == .authorized
|
||||
permissions["microphone"] = AVCaptureDevice.authorizationStatus(for: .audio) == .authorized
|
||||
permissions["speechRecognition"] = self.speechRecognitionStatus == .authorized
|
||||
permissions["speechRecognition"] = speechRecognitionStatus == .authorized
|
||||
permissions["location"] = Self.isLocationAuthorized(
|
||||
status: self.locationManager.authorizationStatus)
|
||||
&& CLLocationManager.locationServicesEnabled()
|
||||
@ -929,6 +929,14 @@ final class GatewayConnectionController {
|
||||
return permissions
|
||||
}
|
||||
|
||||
private nonisolated static func currentSpeechRecognitionStatus() async
|
||||
-> SFSpeechRecognizerAuthorizationStatus
|
||||
{
|
||||
await Task.detached(priority: .utility) {
|
||||
SFSpeechRecognizer.authorizationStatus()
|
||||
}.value
|
||||
}
|
||||
|
||||
private static func isLocationAuthorized(status: CLAuthorizationStatus) -> Bool {
|
||||
switch status {
|
||||
case .authorizedAlways, .authorizedWhenInUse:
|
||||
@ -961,8 +969,8 @@ extension GatewayConnectionController {
|
||||
self.currentCommands()
|
||||
}
|
||||
|
||||
func _test_currentPermissions() -> [String: Bool] {
|
||||
self.currentPermissions()
|
||||
func _test_currentPermissions() async -> [String: Bool] {
|
||||
await self.currentPermissions()
|
||||
}
|
||||
|
||||
func _test_platformString() -> String {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user