3

My Setup:

I have got some very simple code:

const onSuccess = () => {
    console.log('success');
}

const onError = () => {
    console.log('error');
}

navigator.geolocation.getCurrentPosition(onSuccess, onError);

My actual code contains a bit more logic. But for simplicity, I have posted only the required code.

  • I am asked for the permission when I first visit the page.
  • Let us assume that, I grant the access to use my geolocation.
  • success is printed to the console.

So far so good...

Actual Problem:

  • Everytime I refresh the page, I get success logged in the console.
  • Actually I want something different. I want to call my function only once, when user grants access to use geolocation.

Now, if user blocks the geolocation and if he again allows the browser to use geolocation, at that time also, success should be logged. But on every refresh, it should not log success.

So, I believe, there should be some kind of eventlistener on allow button, but I don't see it mentioned anywhere.

My research:

I am not sure if I am going in the right direction, but...

On MDN docs here, I can see that there is something called PermissionStatus.onchange.

But it is experimental and not supported on Internet Explorer, Safari and Safari iOS.

If any one of you have used any alternatives, then I would love to check your ideas.

Actual use case

I am developing a weather application. The requirement is:

  • When user lands on the site, he should be asked for geolocation permission (if not already granted)

  • Once user grants permission, He should be taken to the page where he can see weather details of his city.

  • Current problem is that everytime, I refresh the page, it detects the location of the user and takes the user to his city details page. This is bad. This should only happen once, only when user grants access to geolocation.

My actual tech stack

  • React
  • Typescript

Update:


I am getting some suggestions to store the user's permission status to local-storage.

But if I store the permission in local-storage then I have some problems.

Let me explain in detail by taking an example.

Part till when everything works fine:

  • If user grants access to read his location.
  • Then I take him to city details page.
  • Then I save in local-storage that user has granted the permission.
  • Now if user refreshes the page, then everything seems to work fine.

When I get problems:

  • Now user goes to chrome settings and delete the location permissions for my site.
  • Then user refreshes my site, he will see a popup asking him for location permission.
  • If he then allows the permission, then I won't have any way to know that user has again allowed the permission because in my local-storage, I already have location granted stored. So, I won't be able to take him to city details page.
Vishal
  • 6,238
  • 10
  • 82
  • 158
  • I don't know much about permissions, but it seems like you should persist the users location in like local storage or something and when React renders it should check to see if it already knows the users location before asking again? Because that React code is gonna run every time you refresh. – Zeke Hernandez Dec 11 '20 at 20:13
  • @ZekeHernandez Thanks for trying to suggest a different approach. In this case, I only get 1 issue. If user goes to browser settings and deletes the geolocation permissions for my app. Now, when you refresh the page, user should be asked again to have access to their location and then once he allows, I need to take him to his city details page again. But if my data is stored in local-storage, then I won't be able to do that. – Vishal Dec 11 '20 at 20:53
  • @Vishal, did you find any better solution? I'm also curious if there is an alternative way to fix your issue. – Honey Dec 15 '20 at 08:14

3 Answers3

3

After waiting for sometime, I did not get any solutions other than using local-storage. So, I took a mixed approach. In my approach, I use navigator.permissions on whichever browser has implemented it and for other browsers, I use localstorage.

I am using react, so my solution includes react code, but still the idea can be used for pure javascript or for any javascript framework.

I have created a custom hook for that:

useGeoLocation.ts

import { useState, useEffect } from 'react';

interface Coordinates {
  latitude: number;
  longitude: number;
};

const useGeoLocation = (localStorageKey = 'is-location-granted') => {
  const [shouldRequestLocation, setShouldRequestLocation] = useState(false);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<GeolocationPositionError>();
  const [coords, setCoords] = useState<Coordinates>();

  const onSuccess = (location: GeolocationPosition) => {
    setLoading(false);
    setCoords({
      latitude: location.coords.latitude,
      longitude: location.coords.longitude,
    });
    if (!('permissions' in navigator)) {
      localStorage.setItem(localStorageKey, JSON.stringify(true));
    }
  }

  const onError = (error: GeolocationPositionError) => {
    setLoading(false);
    setError(error);
  }

  useEffect(() => {
    if ('permissions' in navigator) {
      navigator.permissions.query({ name: 'geolocation' }).then((permissionStatus) => {
        if (permissionStatus.state !== 'granted') {
          setShouldRequestLocation(true);
        }
      });
    } else {
      const isLocationGranted = JSON.parse(localStorage.getItem(localStorageKey) || 'false');
      if (!isLocationGranted) {
        setShouldRequestLocation(true);
      }
    }
  }, []);

  useEffect(() => {
    if(!('geolocation' in navigator)) {
      const geoError = new GeolocationPositionError();
      setError({
        ...geoError,
        message: 'Geolocation not supported'
      });
    } else if (shouldRequestLocation) {
      setLoading(true);
      navigator.geolocation.getCurrentPosition(
        onSuccess,
        onError,
        { enableHighAccuracy: false, timeout: 10000, maximumAge: 18000000 },
      );
    }
  }, [shouldRequestLocation]);

  return { loading, error, coords };
};

export default useGeoLocation;

The code is preety straight forward, but if anyone finds any difficulties, please let me know and I will update my answer accordingly.

Vishal
  • 6,238
  • 10
  • 82
  • 158
0

You can save the Permission Status to local storage and check at every page refresh if permissions have changed:

const onSuccess = () => {
console.log('success');
    if (localStorage.getItem("permission_status") !== "ok")  {
        // fire permission onchange event
        // update local storage
    }
}

const onError = () => {
    console.log('error');
    if (localStorage.getItem("permission_status") === "ok")  {
        // fire permission onchange event
        // update local storage
    }
}


navigator.geolocation.getCurrentPosition(onSuccess, onError);
agssl
  • 16
  • 3
  • Thank you for trying to answer my question. I have added a new section in my question. The title of section is `update`, which is the last section as of now. I have added it to the question as I wanted to explain you the problems that I face with local storage. – Vishal Dec 12 '20 at 12:43
0

My suggestion is rather than saving the user's permission status, save the user's GeolocationPosition object in the success callback. So you can compare the object instead of comparing permission status. Since you can't use navigator.permissions because of macOS browsers' don't support it, the only solution is saving the coordination data itself for comparation.

So your code will look like this:

const onSuccess = (pos) => {
  if (localStorage.getItem("latitude") !== pos.coords.latitude || 
      localStorage.getItem("longitude") !== pos.coords.longitude)
  {
    // fire permission onchange event
    // update local storage
    console.log('success');
  }
}

const onError = () => {
    console.log('error');
}

navigator.geolocation.getCurrentPosition(onSuccess, onError);

EDIT Just saw your comment with @Zeke Hernandez. This is permission independent solution, so I think it should work for you. I don't know how you are rendering the user's city detail page, but I believe you will use the user's location data for rendering the page, and we can use the data that we are saving to local storage in the success callback. I don't see other possibilities or alternative.s

Honey
  • 2,208
  • 11
  • 21
  • I am working on a mixed approach with local-storage and permissions API. Once I finish that, I will post it here and will let you know. – Vishal Dec 15 '20 at 09:20
  • Hi, I have posted an answer using mixed approach. Thank you for trying to help me. In my particular case, I did not require to fire geolocation API when location changes, so I simply save true or false to local-storage. – Vishal Dec 17 '20 at 19:43