I am fetching a user's location from Expo. While catch block always executes first and later location is still fetched correctly. Though, catch should happen only if the location was not fetched.
PS: It happens only when I am testing the app on real device under a Tunnel connection. However, works fine on the Emulator.
I don't see anything wrong with the Code though.
Please comment.
useEffect(() => {
const verifyPermissions = async () => {
const result = await Permissions.askAsync(Permissions.LOCATION);
if (result.status !== 'granted') {
Alert.alert(
'Insufficient permissions!',
'You need to grant location permissions to use this app.',
[{ text: 'Okay' }]
);
return false;
}
return true;
};
(async () => {
const hasPermission = await verifyPermissions();
if (!hasPermission) {
return;
}
try {
const location = await Location.getCurrentPositionAsync({});
setPickedLocation({
latitude: location.coords.latitude,
longitude: location.coords.longitude,
});
} catch (err) {
Alert.alert('Could not fetch location!', 'Please try again.', [
{ text: 'Okay' },
]);
}
})();
}, []);
Related
I was coding a leveling system for my discord bot, but encountered a mongoerror on the way, does anyone know why this is occuring? (The addXp function runs whenever someone chatted, the error is logged in the removeTimeout function.) I have used mongoose in many more ways in my code but this hasn't yet occured before, I also did find others' issues with this error but found no fitting solution there.
async function removeTimeout(leveldataid) {
console.log("removetimeout")
await mongo().then(async (mongoose) => {
try {
await levelSchema.findByIdAndUpdate(
leveldataid,
{
_id: leveldataid,
oncooldown: false
},
{upsert: true}
)
} catch(err) {
console.log(err)
} finally {
mongoose.connection.close()
}
})
}
async function addXP(member, guild) {
let data = undefined
let changedleveldata = false
await mongo().then(async (mongoose) => {
try {
data = await levelenabledSchema.findById(guild.id)
if (data) {
if (data.enabled == true) {
let extraxp = Math.floor(Math.random() *20) + 15
let leveldata = await levelSchema.findById(`guild${guild.id}member${member.id}`)
if (!leveldata) {
await levelSchema.findByIdAndUpdate(
`guild${guild.id}member${member.id}`,
{
_id: `guild${guild.id}member${member.id}`,
guild: guild.id,
level: 1,
maxXp: 113,
xp: extraxp,
oncooldown: true
},
{upsert: true}
)
} else {
if (leveldata.oncooldown == false) {
let newlevel = leveldata.level
let newMaxXp = leveldata.maxXp
newMaxXp = Math.floor(newMaxXp)
let xp = leveldata.xp
xp +=extraxp
if (xp > leveldata.maxXp) {
xp -=Math.floor(100*1.135**newlevel)
newlevel++
newMaxXp = 100*1.13**newlevel
}
await levelSchema.findByIdAndUpdate(
`guild${guild.id}member${member.id}`,
{
level: newlevel,
maxXp: newMaxXp,
xp: xp,
oncooldown: true
},
{upsert: true}
)
}
}
}
}
changedleveldata = true
} catch(err) {
console.log(err)
} finally {
mongoose.connection.close()
}
if (changedleveldata == true) {
setTimeout(remoteTimeout(`guild${guild.id}member${member.id}`), 60000)
}
})
}
This basically means the connection between node server and mongoDB was interrupted while writing data.
The solution to your problem would be to remove the finally block from your code because it interrupts the connection when the bot is being used by a lot of users and the bot tries to write data while a previous connection is being closed.
await mongo().then(async (mongoose) => {
try { // code here
} catch(e) { console.log(e); }
});
Please help me figure out the difference in return behaviour between the onCall and onRequest google functions below.
onCall, the problem: returns null on all returns, except at the first return (as commented below). The db entries and rest of the code works fine. Just no returns problem.
onRequest, returns perfectly fine on every return. The db entries and rest of the code also works fine.
Both as you will see compare the same, but I just can't seem to get it to work at all. Any advice on how to get my returns to work for the onCall (and structure it better) would be much appreciated.
I am keen on sticking with async await (as opposed to a promise). Using Node.js 12. I am calling the onCall in Flutter, don't know if that is relevant for the question.
The onCall:
exports.applyUserDiscount = functions.https.onCall(async (data, context) => {
if (!context.auth) return {message: "Authentication Required!", code: 401};
const uid = context.auth.uid;
const discountCode = data["discountCode"];
const cartTotal = data["cartTotal"];
try {
return await db.collection("discountCodes").where("itemID", "==", discountCode).limit(1).get()
.then(async (snapshot) => {
if (snapshot.empty) {
return "doesNotExist"; // The only return that works.
} else { // Everything else from here onwards returns null.
snapshot.forEach(async (doc) => {
if (doc.data().redeemed == true) {
return "codeUsed";
} else {
const newCartTotal = cartTotal - doc.data().discountAmount;
if (newCartTotal < 0) {
return "lessThanTotal";
} else {
doc.ref.update({
redeemed: true,
uid: uid,
redeemDate: fireDateTimeNow,
});
await db.collection("userdata").doc(uid).set({
cartDiscount: admin.firestore.FieldValue.increment(-doc.data().discountAmount),
}, {merge: true});
return doc.data().discountAmount.toString();
}
}
});
}
});
} catch (error) {
console.log("Error:" + error);
return "error";
}
});
The onRequest:
exports.applyUserDiscount = functions.https.onRequest(async (req, res) => {
const uid = req.body.uid;
const discountCode = req.body.discountCode;
const cartTotal = req.body.cartTotal;
try {
return await db.collection("discountCodes").where("itemID", "==", discountCode).limit(1).get()
.then(async (snapshot) => {
if (snapshot.isempty) {
res.send("doesNotExist");
} else {
snapshot.forEach(async (doc) => {
if (doc.data().redeemed == true) {
res.send("codeUsed");
} else {
const newCartTotal = cartTotal - doc.data().discountAmount;
if (newCartTotal < 0) {
res.send("lessThanTotal");
} else {
doc.ref.update({
redeemed: true,
uid: uid,
redeemDate: fireDateTimeNow,
});
await db.collection("userdata").doc(uid).set({
cartDiscount: admin.firestore.FieldValue.increment(-doc.data().discountAmount),
}, {merge: true});
res.send(doc.data().discountAmount.toString());
}
}
});
}
});
} catch (error) {
console.log(error);
res.send("error");
}
});
There are several points to be noted when looking at your code(s):
You should not use async/await within a forEach loop. The problem is that the callback passed to forEach() is not being awaited, see more explanations here or here. HOWEVER, in your case you don't need to loop over the QuerySnapshot since it contains only one doc. You can use the docs property which return an array of all the documents in the QuerySnapshot and take the first (and unique) element.
You mix-up then() with async/await, which is not recommended.
I would advise to throw exceptions for the "error" cases, like doesNotExist, codeUsed or lessThanTotal but it's up to you to choose. The fact that, for example, the lessThanTotal case is an error or a standard business case is debatable... So if you prefer to send a "text" response, I would advise to encapsulate this response in a Object with one property: in your front-end the response will always have the same format.
So, the following should do the trick. Note that I send back on object with a response element, including for the cases that could be considered as errors. As said above you could throw an exception in these cases.
exports.applyUserDiscount = functions.https.onCall(async (data, context) => {
if (!context.auth) ... //See https://firebase.google.com/docs/functions/callable#handle_errors
const uid = context.auth.uid;
const discountCode = data["discountCode"];
const cartTotal = data["cartTotal"];
try {
const snapshot = await db.collection("discountCodes").where("itemID", "==", discountCode).limit(1).get();
if (snapshot.empty) {
//See https://firebase.google.com/docs/functions/callable#handle_errors
} else {
const uniqueDoc = snapshot.docs[0];
if (uniqueDoc.data().redeemed == true) {
return { response: "codeUsed" };
} else {
const newCartTotal = cartTotal - uniqueDoc.data().discountAmount;
if (newCartTotal < 0) {
return { response: "lessThanTotal" };
} else {
await uniqueDoc.ref.update({ // See await here!!
redeemed: true,
uid: uid,
redeemDate: fireDateTimeNow,
});
await db.collection("userdata").doc(uid).set({
cartDiscount: admin.firestore.FieldValue.increment(-uniqueDoc.data().discountAmount),
}, { merge: true });
return {
response: uniqueDoc.data().discountAmount.toString()
}
}
}
}
} catch (error) {
console.log("Error:" + error);
return "error";
}
});
I am referencing this tutorial for Firestore security rules. I have extracted the code from the repository and it matches that of the video.
I changed the setup code to run the firestore.rules instead of firestore-test.rules, and tried running firebase emulators:start and jest ./spec following the same directory structure, I fail the tests of "should allow delete when user is admin" and "should not allow delete for normal user" and the reason it is failing is due to the write rule in the wildcard. Does anyone know what is wrong?
collections.spec.js
const { setup, teardown } = require("./helpers");
describe("General Safety Rules", () => {
afterEach(async () => {
await teardown();
});
test("should deny a read to the posts collection", async () => {
const db = await setup();
const postsRef = db.collection("posts");
await expect(postsRef.get()).toDeny();
});
test("should deny a write to users even when logged in", async () => {
const db = await setup({
uid: "danefilled"
});
const usersRef = db.collection("users");
await expect(usersRef.add({ data: "something" })).toDeny();
});
});
describe("Posts Rules", () => {
afterEach(async () => {
await teardown();
});
test("should allow update when user owns post", async () => {
const mockData = {
"posts/id1": {
userId: "danefilled"
},
"posts/id2": {
userId: "not_filledstacks"
}
};
const mockUser = {
uid: "danefilled"
};
const db = await setup(mockUser, mockData);
const postsRef = db.collection("posts");
await expect(
postsRef.doc("id1").update({ updated: "new_value" })
).toAllow();
await expect(postsRef.doc("id2").update({ updated: "new_value" })).toDeny();
});
test("should allow delete when user owns post", async () => {
const mockData = {
"posts/id1": {
userId: "danefilled"
},
"posts/id2": {
userId: "not_filledstacks"
}
};
const mockUser = {
uid: "danefilled"
};
const db = await setup(mockUser, mockData);
const postsRef = db.collection("posts");
await expect(postsRef.doc("id1").delete()).toAllow();
await expect(postsRef.doc("id2").delete()).toDeny();
});
test("should allow delete when user is admin", async () => {
const mockData = {
"users/filledstacks": {
userRole: "Admin"
},
"posts/id1": {
userId: "not_matching1"
},
"posts/id2": {
userId: "not_matching2"
}
};
const mockUser = {
uid: "filledstacks"
};
const db = await setup(mockUser, mockData);
const postsRef = db.collection("posts");
await expect(postsRef.doc("id1").delete()).toAllow();
});
test("should not allow delete for normal user", async () => {
const mockData = {
"users/filledstacks": {
userRole: "User"
},
"posts/id1": {
userId: "not_matching1"
},
"posts/id2": {
userId: "not_matching2"
}
};
const mockUser = {
uid: "filledstacks"
};
const db = await setup(mockUser, mockData);
const postsRef = db.collection("posts");
await expect(postsRef.doc("id1").delete()).toDeny();
});
test("should allow adding a post when logged in", async () => {
const db = await setup({
uid: "userId"
});
const postsRef = db.collection("posts");
await expect(postsRef.add({ title: "new_post" })).toAllow();
});
test("should deny adding a post when not logged in", async () => {
const db = await setup();
const postsRef = db.collection("posts");
await expect(postsRef.add({ title: "new post" })).toDeny();
});
});
firestore.rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// lock down the db
match /{document=**} {
allow read: if false;
allow write: if false;
}
match /posts/{postId} {
allow update: if userOwnsPost();
allow delete: if userOwnsPost() || userIsAdmin();
allow create: if loggedIn();
}
function loggedIn() {
return request.auth.uid != null;
}
function userIsAdmin() {
return getUserData().userRole == 'Admin';
}
function getUserData() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data
}
function userOwnsPost() {
return resource.data.userId == request.auth.uid;
}
}
}
Error trace from terminal
FirebaseError: 7 PERMISSION_DENIED:
false for 'create' # L10
● Posts Rules › should not allow delete for normal user
FirebaseError: 7 PERMISSION_DENIED:
false for 'create' # L10
at new FirestoreError (/Users/../../../../../../../../../Resources/rules/node_modules/#firebase/firestore/src/util/error.ts:166:5)
at ClientDuplexStream.<anonymous> (/Users/../../../../../../../../../Resources/rules/node_modules/#firebase/firestore/src/platform_node/grpc_connection.ts:240:13)
at ClientDuplexStream._emitStatusIfDone (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client.js:234:12)
at ClientDuplexStream._receiveStatus (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client.js:211:8)
at Object.onReceiveStatus (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:1311:15)
at InterceptingListener._callNext (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:568:42)
at InterceptingListener.onReceiveStatus (/Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:618:8)
at /Users/../../../../../../../../../Resources/rules/node_modules/grpc/src/client_interceptors.js:1127:18
I actually followed the same tutorial to get started with the firebase emulator and got the same kind of error messages. The problem for me was that when you start the simulator it automatically looks for your firestore.rules file and loads the rules. So, when you then add your mockData the rules already apply.
In order to make your test code work either change the setting for your firestore rules file in your firebase.json to a non-existing file (or rules file that allows all read/write) or add the mockData as an admin in your setup function, e.g.:
module.exports.setup = async (auth, data) => {
const projectId = `rules-spec-${Date.now()}`;
const app = firebase.initializeTestApp({
projectId,
auth
});
const db = app.firestore();
// Initialize admin app
const adminApp = firebase.initializeAdminApp({
projectId
});
const adminDB = adminApp.firestore();
// Write mock documents before rules using adminApp
if (data) {
for (const key in data) {
const ref = adminDB.doc(key);
await ref.set(data[key]);
}
}
// Apply rules
await firebase.loadFirestoreRules({
projectId,
rules: fs.readFileSync('firestore.rules', 'utf8')
});
return db;
};
Hope this helps.
Also see this question
For those that are currently having this issue firestore 8.6.1 (or equivalent), there is a bug discussed here:
https://github.com/firebase/firebase-tools/issues/3258#issuecomment-814402977
The fix is to downgrade to firestore 8.3.1, or if you are reading this in the future and firestore >= 9.9.0 has been released, upgrade to that version.
I am getting this error when trying to access my website on an iPhone 7, with a white bank screen (the main screen loads fine, but then I get this at the net screen after I click something.
I assume this is what it's talking about:
useEffect(() => {
navigator.permissions
.query({ name: "microphone" })
.then((permissionStatus) => {
setMicrophonePermissionGranted(permissionStatus.state === "granted");
permissionStatus.onchange = function () {
setMicrophonePermissionGranted(this.state === "granted");
};
});
navigator.permissions.query({ name: "camera" }).then((permissionStatus) => {
setCameraPermissionGranted(permissionStatus.state === "granted");
permissionStatus.onchange = function () {
setCameraPermissionGranted(this.state === "granted");
};
});
}, []);
How do I fix this?
You need to check permission APIs availability and then if not available - query standard APIs.
Here is the location example:
Permissions API
Navigation API
if ( navigator.permissions && navigator.permissions.query) {
//try permissions APIs first
navigator.permissions.query({ name: 'geolocation' }).then(function(result) {
// Will return ['granted', 'prompt', 'denied']
const permission = result.state;
if ( permission === 'granted' || permission === 'prompt' ) {
_onGetCurrentLocation();
}
});
} else if (navigator.geolocation) {
//then Navigation APIs
_onGetCurrentLocation();
}
function _onGetCurrentLocation () {
navigator.geolocation.getCurrentPosition(function(position) {
//imitate map latlng construct
const marker = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
})
}
Permissions.query() is marked as an experimental feature as of June 2021 https://developer.mozilla.org/en-US/docs/Web/API/Permissions/query.
As of today, that traduces into that you'll need to implement two UIs / flows; one capable of supporting fancy flows to tell the user how to proceed, and the other one more standard, using try / catch blocks. Something like:
useEffect(() => {
requestPermissions();
}, []);
const requestPermissions = async () => {
try {
handlePermissionsGranted();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
startRecording();
} catch {
...
}
};
const handlePermissionsGranted = async () => {
if (navigator.permissions && navigator.permissions.query) {
const permissions = await navigator.permissions.query({name: 'microphone'});
permissions.onchange = () => {
setMicrophonePermissionGranted(permissions === 'granted');
};
}
};
const startRecording = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
...
} catch {
... << if you reach this catch means that either the browser does not support webrtc or that the user didn't grant permissions
}
};
I was trying to check for the mic and camera permissions from iOs devices and through the Facebook browser, which I guess makes the whole thing fail, as these don't exist in those environments.
Once I've moved that query to the component that only loads if it is not a mobile device, my error fixed.
I'm having a problem with an async function not returning when running on android whereas it returns normally when run on iOS.
This is the function:
_getLocationAsync = async () => {
let {status} = await Permissions.askAsync(Permissions.LOCATION);
if (status !== 'granted') {
this.setState({
errorMessage: 'Permission to access location was denied',
});
}
let location = await Location.getCurrentPositionAsync({});
this.setState({location});
return location;
};
and I'm using it in another function here:
async fetchMarkers(settings ){
console.log("fetchMarkers");
// console.log(settings);
this.setState({listLoading: true});
let location = await this._getLocationAsync();
console.log("location is ", location);
....
....
}
This line is not returning in android, but it returns in ios. In android I tried logging the value of location just before returning it in _getLocationAsync and it logs a defined and correct object, I'm wondering why it's failing to return it then:
let location = await Location.getCurrentPositionAsync({});
I'm using React Native 0.53
I think there are some reasons that Android can't get location.
I'm using this location option, anh it
works well on android
// how accuracy should get location be
const GET_LOCATION_OPTIONS = {
enableHighAccuracy: false,
timeout: 20000,
maximumAge: 1000,
};
navigator.geolocation.getCurrentPosition(
(position) => {
const location = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
};
if (callback) { callback(location); }
},
(err) => {
if (callback) { callback(null); }
},
GET_LOCATION_OPTIONS,
);
Ref: https://facebook.github.io/react-native/docs/geolocation.html
maybe it's a permission problem,
check whether the app does apply the position permisson