HTML5 Geolocation API quietly fails when Mac settings (not browser settings) deny location sharing
Asked Answered
O

3

5

I ran into an issue where if my Mac denied location sharing, then nothing happens in JS code... this is dangerous, anyway to get around this? If system denies location sharing, I would expect an exception to be thrown

Running macOS Mojave 10.14.6 & tested in Chrome 87.0.4280.88.

In System Preferences > Security & Privacy, you can check Enable Location Services and then check Apps that are allowed. If I EITHER uncheck Enable Location Services entirely OR keep it checked but uncheck Google Chrome, then the code quietly fails*, by which I mean nothing is logged to console in the sample code below.

Code:

$("#btn").click(function(e){
  e.preventDefault();
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
      # success
      function(location) {
        console.log("GPS coordinates retrieved")
      }, 
      # failure
      function(error) {
        console.log(error.message)
      }
    )
  } else {
    console.log("GPS not supported by browser")
  }
})

Is there a meta way to catch this (e.g., determine whether location has been enabled or not in system preferences from JS) so that something gets logged and I can move onto next steps in code?

*The code quietly fails, but technically the browser has ways of alerting you, but only the FIRST time. So basically, assuming your browser has not blocked the URL and you're on HTTPS (e.g., all the browser conditions are met), the FIRST time you click #btn, you'll get the native pop up that navigator.geolocation.getCurrentPosition triggers asking you for permission. Now, even if you click allow, because system preferences disallows it, it won't share, the code doesn't log anything. But at least this first time you get a visual of something happening. On subsequent clicks, truly nothing will happen either on browser or in code. Again I'm more concerned with code, just pointing out that I acknowledge browser shows you things.

Oxazine answered 2/1, 2021 at 20:27 Comment(2)
I am exactly on the specified version of the OS and browser, and i ran your code, but its working fine. I am getting an error message on the right part of browser URL "Location Services is turned off in mac system preferences.Cantabile
@Cantabile but in the console, nothing is logged right? the code just failsOxazine
E
7

On some browsers you can check the permission status without prompting the user like so:

const permissionStatus = await navigator.permissions.query({ name: 'geolocation' })

Compatibility: http://caniuse.com/#feat=permissions-api

Although note that this only indicates the permissions at browser level. Even when this permission is granted, another layer like the OS can still deny it later in the chain.

Given that navigator.geolocation.getCurrentPosition() always immediately returns with undefined no matter what, the best you can do to handle these cases is having a timeout forcing the error callback to be called after a given duration if no response is provided to the browser. Luckily you can do so easily by passing options as a third argument to the function.

const options = { timeout: 5000 }
navigator.geolocation.getCurrentPosition(successCb, errorCb, options)

In your case:

$("#btn").click(function(e){
  e.preventDefault()

  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
      // success
      function(location) {
        console.log("GPS coordinates retrieved")
      }, 
      // failure
      function(error) {
        console.log(error.message)
      },
      {
        timeout: 5000 // ms
      }
    )
  } else {
    console.log("GPS not supported by browser")
  }
})

Given that the W3C specification details that the browsers must call the callback function on success/error, it is unlikely to be silenced by the browser itself. It is more likely that the OS decides to never respond instead of refusing the access, leaving the function in a pending state indefinitely if no timeout is provided.

Estelleesten answered 13/1, 2021 at 2:37 Comment(0)
P
1

Try this:

// pseudo
set location to unknown (yet)
set waiting time
try retrieve location
  set location when receive, call as resolved (success)
  set error when failure, call resolved (failure)

cancel waiting when receive (location / a sure failure)
resolveLocation after timeout, success or failure

$("#btn").click(function(e){
  e.preventDefault()
  function resolveLocation(){
    if(location){
      console.log("Location detected",location)
    }else{
      console.log("Cannot detect location")
    }
  }
  var location = null;
  var wait = setTimeout(resolveLocation, 3000)
  if(navigator.geolocation){
    navigator.geolocation.getCurrentPosition(
      // success
      function(_location){
        clearTimeout(wait)
        console.log("GPS coordinates retrieved")
        location = _location;
        resolveLocation()
      },
      // error
      function(error){
        clearTimeout(wait)
        console.log("GPS error")
        resolveLocation()
      }
    )
  }else{
    clearTimeout(wait)
    console.log("GPS not supported by browser")
    resolveLocation()
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="btn">Check</button>
Peak answered 13/1, 2021 at 14:6 Comment(2)
The same behavior can be achieved by simply passing timeout as an option to navigator.geolocation.getCurrentPosition. See previous answer for explanation and example.Estelleesten
side note: setTimeout is highly inaccurate as it is impacted by the logic that is running in parallel synchronously, or other operations also queued asynchronously. For these situations where timing is important, setInterval should be used instead.Estelleesten
P
0

According to MDN there's error code for each geolocation API error, you can read details here.

So applying the error code respectively on your code should like this.

$("#btn").click(function(e) {

  e.preventDefault();

  if (navigator.geolocation) {

    navigator.geolocation.getCurrentPosition(#success
      function(location) {
        console.log("GPS coordinates retrieved")
      }, #failure
      function(error) {
        //when location request is rejected is error code = 1
        console.log(error.code)

        //when POSITION is UNAVAILABLE is error code = 2
        console.log(error.code)

        //when there is a timeout error code = 3
        console.log(error.code)
      }
    )
  } else {
    console.log("GPS not supported by browser")
  }
})   
Paraffin answered 22/11, 2022 at 19:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.