Skip to content

bug(geolocation): iOS requestPermissions never settles after "Allow Once" lapses, and the plugin prompts unasked #2624

Open

Description

@beqa7137

Bug Report

Plugin(s)

@capacitor/geolocation 8.2.4 (iOS). The same code is in 9.0.0-next.3.

Capacitor Version

@capacitor/core and @capacitor/ios 8.5.3; iPhone 16 Pro simulator, iOS 18.3.

Platform(s)

iOS

Current Behavior

Two problems, both from how GeolocationPlugin.swift handles the .notDetermined status.

1. requestPermissions() (and getCurrentPosition()) never settle after "Allow Once" lapses.

  1. Call requestPermissions() and choose Allow Once, then getCurrentPosition(): works.
  2. Send the app to the background (for example, open Apple Maps). About 10 seconds later iOS ends the one-time grant and the status becomes .notDetermined.
  3. The plugin's authorisationStatusPublisher sink sees .notDetermined and calls requestLocationAuthorisation by itself. The app is not in use, so locationd drops it:
    locationd: {"msg":"#AuthPrompt #Warning Dropping authorization request placed while insufficiently in-use", "ClientKeyPath":"i<bundle id>:", "requestType":2, "inUseLevel":0}
    
  4. Back in the app, call checkPermissions() (returns prompt), then requestPermissions(). Because the status is already .notDetermined, requestPermissions only adds the call to requestPermissionsCallbacks and waits for the publisher. The publisher never fires again, since nothing changes, and no new authorisation request is made. The promise never resolves or rejects. getCurrentPosition parks the same way (default: break in handleLocationRequest), and its timeout does not help because the timer only starts in requestSingleLocation, after authorisation.

Every later call hangs too, until the app process is restarted.

2. A permission prompt appears without any call.

Once any plugin method has been called in the process, the sink stays subscribed. Whenever the status changes to .notDetermined while the app is active, the sink requests authorisation, and the system prompt appears over whatever screen is open, with no JS call behind it:

locationd: {"msg":"Showing #AuthPrompt", "requestType":2, "Client":"i<bundle id>:"}

Expected Behavior

  • requestPermissions() shows the prompt (or resolves) whenever the status is .notDetermined at the time of the call.
  • The plugin only asks for authorisation on behalf of a pending call.

Code Reproduction

await Geolocation.requestPermissions({ permissions: ['coarseLocation'] }); // choose "Allow Once"
await Geolocation.getCurrentPosition();                                    // works
// background the app for ~10 s, return
await Geolocation.checkPermissions();                                      // { coarseLocation: 'prompt' }
await Geolocation.requestPermissions({ permissions: ['coarseLocation'] }); // never settles

Additional Context

The patch we ship (patch-package), which fixes both cases in our testing:

@@ requestPermissions
         if locationService?.authorisationStatus == .notDetermined {
             shouldSetupBindings()
             callbackManager?.addRequestPermissionsCallback(capacitorCall: call)
+            requestLocationAuthorisation(type: .whenInUse)
         } else {
@@ bindAuthorisationStatusPublisher
                 case .notDetermined:
-                    self.requestLocationAuthorisation(type: .whenInUse)
+                    if self.hasPendingCallbacks() {
+                        self.requestLocationAuthorisation(type: .whenInUse)
+                    }
@@ handleLocationRequest
         case .restricted: callbackManager?.sendError(.permissionRestricted)
+        case .notDetermined: requestLocationAuthorisation(type: .whenInUse)
         default: break
@@
+    func hasPendingCallbacks() -> Bool {
+        guard let callbackManager else { return false }
+        return !callbackManager.requestPermissionsCallbacks.isEmpty
+            || !callbackManager.locationCallbacks.isEmpty
+            || !callbackManager.watchCallbacks.isEmpty
+    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions