ReactJS location access prompt on mobile device - reactjs

I am building an app in ReactJS that requires the user's location. In the browser (Chrome) on my PC I get a prompt once. If I decline, the prompt will not show again upon a page refresh. However, I will get the option to manually enable location.
When I open the same web page on my android phone in Chrome I neither get a prompt nor the option to enable location access manually. I have looked for solutions through Google but all of them rely on a prompt being automatically shown upon a navigator.geolocation.getCurrentPosition call, but it doesn't.
This is my code:
function getUserLocation() {
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(
function (position) {
setLocationDidMount(true);
console.log("got position");
},
function errorCallback(error) {
console.log(error);
navigator.permissions
.query({ name: "geolocation" })
.then(function (result) {
if (result.state === "granted") {
console.log(result.state);
//If granted then you can directly call your function here
} else if (result.state === "prompt") {
console.log(result.state);
} else if (result.state === "denied") {
console.log("location access denied by user");
//If denied then you have to show instructions to enable location
}
result.onchange = function () {
console.log(result.state);
};
});
},
{
timeout: 5000,
}
);
} else {
console.log("Not Available");
}
}
How to handle this problem? Btw, I'm not supposed to use React-Native.

As is pointed out by the article shared by Sergey, mobile browsers will only handle location properly when there is an SSL certificate in place.
While causing a warning, installing a local certificate using OpenSSL did fix the problem.

Related

How can I provide my react-native app with google sign in?

I`ve tried to register my app as Web application, generate the user id and implement it in my code but get an error when I press my button for log in with google:
[Unhandled promise rejection: Error: Please provide the appropriate client ID.
enter image description here
If you're using expo, you have to configure the google sign-in like this. This is my configuration. You have to create androidClientId and iosClientId from your account and use it here.
Disclaimer: This is a standalone function only for signingin/signingup a Google user and fetching details. To configure it with firebase you have to add other functions too.
Also, make sure that you're importing this package. I faced a similar problem when I used another package.
import * as Google from 'expo-google-app-auth'
Additionally, are you using the latest version of expo SDK?
async signInWithGoogleAsync() {
try {
const result = await Google.logInAsync({
androidClientId:
'your-id',
iosClientId:
'your-id',
scopes: ['profile', 'email'],
permissions: ['public_profile', 'email', 'gender', 'location']
})
if (result.type === 'success') {
/*put your logic here, I set some states and navigate to home screen
after successful signin.*/
const googleUser = result.user
this.setState({
email: googleUser.email,
name: googleUser.name,
})
this.navigateToLoadingScreen()
return result.accessToken
} else {
return { cancelled: true }
}
} catch (e) {
return { error: true }
}
}

How to activate a react route and pass data from the service worker?

I have a SPA PWA React app.
It is installed and running in standalone mode on the mobile device (Android+Chrome).
Let's say the app lists people and then when you click on a person it diplays details using /person route.
Now, I'm sending push notifications from the server and receiving them in the service worker attached to the app. The notification is about a person and I want to open that person's details when the user clicks on the notification.
The question is:
how do I activate the /person route on my app from the service worker
and pass data (e.g. person id, or person object)
without reloading the app
From what I understand, from the service worker notificationclick event handler I can:
focus on the app (but how do I pass data and activate a route)
open an url (but /person is not a physical route, and either way - I want avoid refreshing the page)
You can listen for click event for the Notification which you show to the user. And in the handler, you can open the URL for the corresponding person which comes from your server with push event.
notification.onclick = function(event) {
event.preventDefault();
// suppose you have an url property in the data
if (event.notification.data.url) {
self.clients.openWindow(event.notification.data.url);
}
}
Check these links:
https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/notificationclick_event
https://developer.mozilla.org/en-US/docs/Web/API/Clients/openWindow
To answer my own question: I've used IndexedDB (can't use localStorage as it is synchronous) to communicate between SW and PWA, though I'm not too happy about it.
This is roughly how my service worker code looks (I'm using idb library):
self.addEventListener('notificationclick', function(event) {
const notif = event.notification;
notif.close();
if (notif.data) {
let db;
let p = idb.openDB('my-store', 1, {
upgrade(db) {
db.createObjectStore(OBJSTORENAME, {
keyPath: 'id'
});
}
}).then(function(idb) {
db = idb;
return db.clear(OBJSTORENAME);
}).then(function(rv) {
return db.put(OBJSTORENAME, notif.data);
}).then(function(res) {
clients.openWindow('/');
}).catch(function(err) {
console.log("Error spawning notif", err);
});
event.waitUntil(p);
}
});
and then, in the root of my react app ie in my AppNavBar component I always check if there is something to show:
componentWillMount() {
let self = this;
let db;
idb.openDB('my-store', 1)
.then(function (idb) {
db = idb;
return db.getAll(OBJSTORENAME);
}).then(function (items) {
if (items && items.length) {
axios.get(`/some-additional-info-optional/${items[0].id}`).then(res => {
if (res.data && res.data.success) {
self.props.history.push({
pathname: '/details',
state: {
selectedObject: res.data.data[0]
}
});
}
});
db.clear(OBJSTORENAME)
.then()
.catch(err => {
console.log("error clearing ", OBJSTORENAME);
});
}
}).catch(function (err) {
console.log("Error", err);
});
}
Have been toying with clients.openWindow('/?id=123'); and clients.openWindow('/#123'); but that was behaving strangely, sometimes the app would stall, so I reverted to the IndexedDB approach.
(clients.postMessage could also be the way to go though I'm not sure how to plug that into the react framework)
HTH someone else, and I'm still looking for a better solution.
I had a similar need in my project. Using your's postMessage tip, I was able to get an event on my component every time a user clicks on service worker notification, and then route the user to the desired path.
service-worker.js
self.addEventListener("notificationclick", async event => {
const notification = event.notification;
notification.close();
event.waitUntil(
self.clients.matchAll({ type: "window" }).then(clientsArr => {
if (clientsArr[0]) {
clientsArr[0].focus();
clientsArr[0].postMessage({
type: "NOTIFICATION_CLICK",
ticketId: notification.tag,
});
}
})
);
});
On your react component, add a new listener:
useEffect(() => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.addEventListener("message", message => {
if (message.data.type === "NOTIFICATION_CLICK") {
history.push(`/tickets/${message.data.ticketId}`);
}
});
}
}, [history]);

navigator.geolocation.getCurrentPosition() is not getting a response from googleapi

I am using react to get geolocation on localhost:3000. I have seen another person get the geolocation coordinates on their localhost, but I am unable to do so even with allow location access enabled on Chrome.
I have tried using both the hooks and class syntax in react. I have enabled allow access. I eventually used an ip address api to get a general location, but since the geolocation is supposed to work(at least that is what I have been told) I would at least like to see it work so I can implement it with https in the future. The error log does not even get fired, whereas the first three logs are getting fired when the component is mounted. Here is the code I have tried, I have made it as simple as possible:
const App = props => {
useEffect(() => {
console.log('hello')
console.log(navigator)
console.log(navigator.geolocation)
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
console.log(position)
}, (error) => {
console.log(error)
})
} else {
console.log('error')
}
}, [])
return (
<div>
<h3>Please Login.</h3>
</div>
)
}
export default App
I expect to receive a response from googleapi.
Edit:
I added the error callback and it printed:
message: "Network location provider at 'https://www.googleapis.com/' : No response received."
add the optional error callback to handle the error (if user declines location permission)
navigator.geolocation.getCurrentPosition(success[, error[, [options]])
you are checking only if it is in navigator or not !!!
if user declines location permission then error callback will handle it...
possible options are (reference taken from mdn)
{
enableHighAccuracy: true,
maximumAge : 30000,
timeout : 27000
}

react native geolocation not asking user permission on android emulator

Hi Im using the below code to successfully get user location, but in the android emulator it is not asking user if they want to share their location (even after I reinstall the app).
The code works which is great....but I really would like the standard alert to appear which says "are you sure you want to share your location with the app"
Any guidance would be appreciated
navigator.geolocation.getCurrentPosition(
(pos) => {
console.log(pos.coords.longitude, pos.coords.latitude);
I think location permission not show again event you reinstall app because phone still remember permissions granted for app. You can check app have permission granted in Android Setting with App (https://www.howtogeek.com/230683/how-to-manage-app-permissions-on-android-6.0/).
Maybe this code can help you
import { PermissionsAndroid } from 'react-native';
async function getLocation() {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
'title': 'App',
'message': 'are you sure you want to share your location with the app ?'
}
)
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
// permission granted
// do code get location here.
navigator.geolocation.getCurrentPosition(
(pos) => {
console.log(pos.coords.longitude, pos.coords.latitude);
})
} else {
// permission denied
console.log("GPS permission denied")
}
} catch (err) {
console.warn(err)
}
}

Cordova wait until user gave premission to use location services

I have to check if the user disabled the location services i do this like so:
cordova.plugins.diagnostic.isLocationEnabled(function(enabled) {
if (enabled === false) {
$state.go("errorTwo");
}
}, function(error) {
alert("The following error occurred: " + error);
});
}
But the problem is, that if the user starts the app the first time, the error already runs. The function should wait until the user gave premission to use the location services. How can I do this?
Any help much appreciated!
The documentation states for isLocationEnabled():
Returns true if the device setting for location is on. On Android this returns true if Location Mode is switched on. On iOS this returns true if Location Services is switched on.
i.e. this only checks if the device setting is enabled.
However, you can either use isLocationAvailable()
On iOS and Windows 10 Mobile this returns true if both the device setting is enabled AND the application is authorized to use location. When location is enabled, the locations returned are by a mixture GPS hardware, network triangulation and Wifi network IDs.
On Android, this returns true if Location mode is enabled and any mode is selected (e.g. Battery saving, Device only, High accuracy) AND if the app is authorised to use location. When location is enabled, the locations returned are dependent on the location mode
This checks "if both the device setting is enabled AND the application is authorized to use location":
cordova.plugins.diagnostic.isLocationAvailable(
function(enabled) {
if (enabled === false) {
$state.go("errorTwo");
},
}, function(error) {
alert("The following error occurred: " + error);
}
});
Or, you can call isLocationAuthorized() to check if the app is authorized to use location, before checking the device setting:
cordova.plugins.diagnostic.isLocationAuthorized(function(authorized){
console.log("Location is " + (authorized ? "authorized" : "unauthorized"));
if(authorized){
cordova.plugins.diagnostic.isLocationEnabled(
function(enabled) {
if (enabled === false) {
$state.go("errorTwo");
},
}, function(error) {
alert("The following error occurred: " + error);
}
});
}
}, function(error){
console.error("The following error occurred: "+error);
});
To "wait" until the app has permission to use location before checking, you must take control of the request for location authorization:
function checkLocationSetting(){
cordova.plugins.diagnostic.isLocationEnabled(
function(enabled) {
if (enabled === false) {
$state.go("errorTwo");
},
}, function(error) {
alert("The following error occurred: " + error);
}
});
}
cordova.plugins.diagnostic.isLocationAuthorized(function(authorized){
console.log("Location is " + (authorized ? "authorized" : "unauthorized"));
if(authorized){
checkLocationSetting();
}else{
cordova.plugins.diagnostic.requestLocationAuthorization(function(status){
if(status == cordova.plugins.diagnostic.permissionStatus.GRANTED){
checkLocationSetting();
}else{
console.warn("Permission denied to use location");
}
}, function(error){
console.error(error);
});
}
}, function(error){
console.error("The following error occurred: "+error);
});
You must determine if this is th initial run of your application. To do this you can add this code and adjust to your needs.
//service
.factory('Application', function ($window) {
return {
setInitialRun = function (initial) {
$window.localStorage["initialRun"] = (initial ? "true" : "false");
},
isInitialRun = function () {
var value = $window.localStorage["initialRun"] || "true";
return value == "true";
}
};
});
in your app.js
.run(function ($ionicPlatform, $state, Application) {
var state = "mainPage"; // whatever, the main page of your app
if (Application.isInitialRun()) {
Application.setInitialRun(false);
state = "intro";
}
$state.go(state);
});

Resources