Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ final class GeolocationCallbackManager {
private(set) var locationCallbacks: [CAPPluginCall]
private(set) var watchCallbacks: [String: CAPPluginCall]
private(set) var timeout: Int?
private(set) var maximumAge: Double?
private let capacitorBridge: CAPBridgeProtocol?

private var allCallbackGroups: [GeolocationCallbackGroup] {
Expand Down Expand Up @@ -55,6 +56,7 @@ final class GeolocationCallbackManager {
locationCallbacks.append(call)
let timeout = call.getInt(Constants.Arguments.timeout)
self.timeout = timeout
self.maximumAge = call.getDouble(Constants.Arguments.maximumAge) ?? 0
}

func addWatchCallback(_ watchId: String, capacitorCall call: CAPPluginCall) {
Expand Down Expand Up @@ -107,6 +109,11 @@ final class GeolocationCallbackManager {
createPluginResult(status: .success(position.toJSObject()))
}

func sendWatchSuccess(with position: IONGLOCPositionModel) {
guard let watchGroup = allCallbackGroups.first(where: { $0.type == .watch }) else { return }
send(.success(position.toJSObject()), to: watchGroup)
}

func sendError(_ call: CAPPluginCall, error: GeolocationError) {
let errorModel = error.toCodeMessagePair()
call.reject(errorModel.1, errorModel.0)
Expand Down
9 changes: 9 additions & 0 deletions ios/Sources/GeolocationPlugin/GeolocationConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@ enum Constants {
enum Arguments {
static let enableHighAccuracy = "enableHighAccuracy"
static let id = "id"
static let maximumAge = "maximumAge"
static let timeout = "timeout"
}

enum SingleLocationRequest {
/// A position younger than this is treated as a live fix rather than a cached one,
/// regardless of `maximumAge`. CLLocation timestamps lag delivery by the acquisition
/// time, so `maximumAge: 0` (the default, meaning "no cached positions") must still
/// accept freshly acquired fixes.
static let freshnessThresholdInMilliseconds: Double = 5000
}

enum AuthorisationStatus {
enum ResultKey {
static let location = "location"
Expand Down
36 changes: 35 additions & 1 deletion ios/Sources/GeolocationPlugin/GeolocationPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ private extension GeolocationPlugin {
}
}
.sink(receiveValue: { [weak self] position in
self?.callbackManager?.sendSuccess(with: position)
self?.handleReceivedPosition(position)
})

timeoutCancellable = locationService?.locationTimeoutPublisher
Expand All @@ -175,6 +175,40 @@ private extension GeolocationPlugin {
})
}

/// Routes a position received from the location service to the pending callbacks,
/// enforcing `maximumAge` for `getCurrentPosition` calls: a cached position older
/// than `maximumAge` (e.g. the system's last known location from before the device
/// moved) must not satisfy them, so they are kept pending while a fresh position
/// is acquired. Watch callbacks always receive the update.
func handleReceivedPosition(_ position: IONGLOCPositionModel) {
guard let callbackManager else { return }
let hasSingleLocationRequests = !callbackManager.locationCallbacks.isEmpty

if hasSingleLocationRequests, isStaleForSingleLocationRequests(position) {
callbackManager.sendWatchSuccess(with: position)
if callbackManager.watchCallbacks.isEmpty {
// No watch is keeping monitoring alive, so request continuous updates to
// obtain a fresh position; also re-arms the timeout timer, which was
// cancelled when the stale position was delivered.
locationService?.startMonitoringLocation(options: IONGLOCRequestOptionsModel(timeout: callbackManager.timeout))
}
return
}

callbackManager.sendSuccess(with: position)

if hasSingleLocationRequests, callbackManager.watchCallbacks.isEmpty {
// Stop any monitoring started above solely to satisfy the single requests.
locationService?.stopMonitoringLocation()
}
}

func isStaleForSingleLocationRequests(_ position: IONGLOCPositionModel) -> Bool {
let ageInMilliseconds = Date().timeIntervalSince1970 * 1000 - position.timestamp
let maximumAge = callbackManager?.maximumAge ?? 0
return ageInMilliseconds > max(maximumAge, Constants.SingleLocationRequest.freshnessThresholdInMilliseconds)
}

func requestLocationAuthorisation(type requestType: IONGLOCAuthorisationRequestType) {
DispatchQueue.global(qos: .background).async {
guard self.checkIfLocationServicesAreEnabled() else { return }
Expand Down