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

Viewed 1205

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.

2 Answers

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.

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>

Related