mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-15 10:16:07 +00:00
* fix(ios): return promptly from noncooperative timeouts * fix(apple): harden async timeout cancellation Co-authored-by: NianJiuZst <180004567+NianJiuZst@users.noreply.github.com> * fix(apple): preserve legacy location requests * style(apple): format location continuation code --------- Co-authored-by: NianJiuZst <180004567+NianJiuZst@users.noreply.github.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
96 lines
3.4 KiB
Swift
96 lines
3.4 KiB
Swift
import CoreLocation
|
|
import Foundation
|
|
import OpenClawKit
|
|
|
|
@MainActor
|
|
final class MacNodeLocationService: NSObject, CLLocationManagerDelegate, ConcurrentLocationServiceCommon {
|
|
enum Error: Swift.Error {
|
|
case timeout
|
|
case unavailable
|
|
}
|
|
|
|
private let manager = CLLocationManager()
|
|
private var locationContinuation: CheckedContinuation<CLLocation, Swift.Error>?
|
|
var locationRequestContinuations: [UUID: CheckedContinuation<CLLocation, Swift.Error>] = [:]
|
|
|
|
var locationManager: CLLocationManager {
|
|
self.manager
|
|
}
|
|
|
|
/// Compatibility witness for the shipped single-waiter protocol; app calls use the
|
|
/// concurrent extension and its per-request continuation dictionary.
|
|
var locationRequestContinuation: CheckedContinuation<CLLocation, Swift.Error>? {
|
|
get { self.locationContinuation }
|
|
set { self.locationContinuation = newValue }
|
|
}
|
|
|
|
override init() {
|
|
super.init()
|
|
self.configureLocationManager()
|
|
}
|
|
|
|
func currentLocation(
|
|
desiredAccuracy: OpenClawLocationAccuracy,
|
|
maxAgeMs: Int?,
|
|
timeoutMs: Int?) async throws -> CLLocation
|
|
{
|
|
guard CLLocationManager.locationServicesEnabled() else {
|
|
throw Error.unavailable
|
|
}
|
|
return try await LocationCurrentRequest.resolve(
|
|
manager: self.manager,
|
|
desiredAccuracy: desiredAccuracy,
|
|
maxAgeMs: maxAgeMs,
|
|
timeoutMs: timeoutMs,
|
|
request: { try await self.requestLocationOnce() },
|
|
withTimeout: { timeoutMs, operation in
|
|
try await self.withTimeout(timeoutMs: timeoutMs) {
|
|
try await operation()
|
|
}
|
|
})
|
|
}
|
|
|
|
private func withTimeout<T: Sendable>(
|
|
timeoutMs: Int,
|
|
operation: @escaping @Sendable () async throws -> T) async throws -> T
|
|
{
|
|
try await AsyncTimeout.withTimeoutMs(
|
|
timeoutMs: timeoutMs,
|
|
onTimeout: { Error.timeout },
|
|
operation: operation)
|
|
}
|
|
|
|
// MARK: - CLLocationManagerDelegate (nonisolated for Swift 6 compatibility)
|
|
|
|
nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
|
Task { @MainActor in
|
|
let continuations = Array(self.locationRequestContinuations.values) + [self.locationContinuation]
|
|
.compactMap(\.self)
|
|
self.locationRequestContinuations.removeAll()
|
|
self.locationContinuation = nil
|
|
if let latest = locations.last {
|
|
for continuation in continuations {
|
|
continuation.resume(returning: latest)
|
|
}
|
|
} else {
|
|
for continuation in continuations {
|
|
continuation.resume(throwing: Error.unavailable)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.Error) {
|
|
let errorCopy = error // Capture error for Sendable compliance
|
|
Task { @MainActor in
|
|
let continuations = Array(self.locationRequestContinuations.values) + [self.locationContinuation]
|
|
.compactMap(\.self)
|
|
self.locationRequestContinuations.removeAll()
|
|
self.locationContinuation = nil
|
|
for continuation in continuations {
|
|
continuation.resume(throwing: errorCopy)
|
|
}
|
|
}
|
|
}
|
|
}
|