I have this Vuex action that should change a users status on logout but since false values aren't save in Firestore, it isn't working
logoutUser({commit, state}) {
let user = firebase.auth().currentUser;
db
.collection('users')
.where('user_id', '==', user.uid)
.get()
.then(snapshot => {
snapshot.forEach((doc) => {
db
.collection('users')
.doc(doc.id)
.update({
is_online: false
});
localStorage.setItem('firebase_user', null);
firebase
.auth()
.signOut()
.then(() => {
commit('SET_AUTHENTICATED', false);
commit('SET_CURRENT_USER', null);
});
});
});
}
I am just starting with Firestore so I would appreciate any assistance or recommendations ... Thanks!!!
The following should work (not tested). You need to chain the promises returned by the asynchronous methods (update() and signOut()).
I make the assumption that there is only one user with user.uid in the users collection, therefore I use snapshot.docs[0] to get the unique user document, instead of using forEach()
logoutUser({ commit, state }) {
const user = firebase.auth().currentUser;
db.collection('users')
.where('user_id', '==', user.uid)
.get()
.then(snapshot => {
const userDocSnapshot = snapshot.docs[0];
return userDocSnapshot.ref.update({
is_online: false
});
})
.then(() => {
return firebase.auth().signOut();
})
.then(() => {
localStorage.setItem('firebase_user', null);
commit('SET_AUTHENTICATED', false);
commit('SET_CURRENT_USER', null);
});
}
Related
I started this tutorial (https://www.freecodecamp.org/news/react-native-firebase-tutorial/) on Firebase and React Native. Everything is working well overall.
But I have this error: “User does not exist anymore.” for the Login.
However, users are well rooted in Firebase.
const onLoginPress = () => {
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((response) => {
const uid = response.user.uid
const usersRef = firebase.firestore().collection('users')
usersRef
.doc(uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
alert("User does not exist anymore.")
return;
}
const user = firestoreDocument.data()
navigation.navigate('Home', {user})
})
.catch(error => {
alert(error)
});
})
.catch(error => {
alert(error)
})
}
With
const usersRef = firebase.firestore().collection('users')
usersRef
.doc(uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
alert("User does not exist anymore.")
return;
}
const user = firestoreDocument.data()
navigation.navigate('Home', {user})
})
you actually query the user document with the id corresponding to the user's uid in the users collection.
This document is normally created by the onRegisterPress() function in the tutorial. If you get the "User does not exist anymore." message, it means that the user document is not present in the collection.
So you need to check why this is the case: the onRegisterPress() function was not called? The doc was deleted? There are security rules that prevent creating the document? etc...
I have login code in react native using firebase and google signin auth.
So when new user sign in using google account, I set new data. And if user has signed in before, user go to main page.
My problem is when new user sign in > code start to get signInWithCredential > set new data user, before set data finish, onAuthStateChanged was detect there is change in auth and start to get user document / data. But because it's not finish yet, it throw error 'Can Not Get UID / Undefined UID'.
This is my login page code:
const _signIn = async () => {
setInitializing(true);
try {
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
const credential = auth.GoogleAuthProvider.credential(
userInfo.idToken,
userInfo.accessToken,
);
await auth()
.signInWithCredential(credential)
.then(response => {
const uid = response.user.uid;
const data = {
uid: uid,
email: userInfo.user.email,
fullname: userInfo.user.name,
bio: 'Halo!! ..',
username: uid.substring(0, 8),
};
const usersRef = firestore().collection('users');
usersRef
.doc(uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
usersRef
.doc(data.uid)
.set(data)
.then(() => {
setInitializing(false); return;
})
.catch(error => {
setInitializing(false);
Alert.alert(JSON.stringify(error.message));
});
} else {
setInitializing(false);
return;
}
})
.catch(error => {
Alert.alert(JSON.stringify(error.message));
console.log('Error getting document:', error);
return;
});
});
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
setInitializing(false);
Alert.alert('Sign in canceled');
} else if (error.code === statusCodes.IN_PROGRESS) {
setInitializing(false);
Alert.alert('Signin in progress');
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
setInitializing(false);
Alert.alert('PLAY_SERVICES_NOT_AVAILABLE');
} else {
setInitializing(false);
Alert.alert(JSON.stringify(error.message));
}
}};
And this is my index page code to check auth user:
useEffect(() => {
try {
NetInfo.fetch().then(state => {
if(state.isConnected === false){
Alert.alert('No Internet Connection Detected');
setInitializing(false);
return;
}
});
setInitializing(true);
await auth().onAuthStateChanged(user => {
if (user) {
const usersRef = firestore().collection('users');
usersRef
.doc(user.uid)
.get()
.then(document => {
const userData = document.data().uid;
setisLogin(userData);
})
.then(() => {
setInitializing(false);
})
.catch(error => {
setInitializing(false);
Alert.alert(JSON.stringify(error.message));
});
} else {
setInitializing(false);
}
});
} catch (error) {
Alert.alert(error);
} }, []);
How to wait auth().signInWithCredential finish? Thankyou.
If you need to perform more actions such read data from database or so after the user logs in, you should ideally unsubscribe from onAuthStateChanged. Essentially it won't trigger when the auth state changes (i.e. user logs in) and let you do your own custom actions. Once your processing is done, then you manually redirect the user to where the onAuthStateChange would have redirected is the user wa s logged in.
const authStateListenter = await auth().onAuthStateChanged(user => {
//...
})
// Unsubscribe auth state observer when _signIn function runs
const _signIn = async () => {
setInitializing(true);
authStateListenter()
}
Calling authStateListener will disable the auth state observer. It's similar to detaching Firestore's listeners.
I deployed a HTTP cloud function for firebase. However after deploy, the very first invocation returns a timeout error code. I'm using volley library for Android to invoke the HTTP function.
Subsequent invocations seem to work perfectly.
What I've done: I felt the issue maybe a cold start time problem however I tried increasing the timeout duration from code to 180secs. Yet I still get the same issue
Code: Shows the complete cloud function with the issue
const runtimeOptsActivateApp = {
timeoutSeconds: 180,
memory: '128MB'
}
exports.activateApp = functions.runWith(runtimeOptsActivateApp).https.onRequest((req, res) => {
const pin = req.query.pin;
const id_token = req.query.auth;
let docId;
let uid;
let email;
// idToken comes from the client app
admin.auth().verifyIdToken(id_token)
.then(decodedToken => {
uid = decodedToken.uid;
email = decodedToken.email;
return activationPinsRef
.where("pin", "==", pin)
.get()
})
.then(querySnapshot=>{
if(querySnapshot.empty){
return Promise.reject(new Error("Activation pin does not exist..."))
}
return activationPinsRef
.where("pin", "==", pin)
.where("is_blocked", "==", false)
.get()
})
.then(querySnapshot=>{
if(querySnapshot.empty){
return Promise.reject(new Error("User has been blocked..."))
}
return activationPinsRef
.where("pin", "==", pin)
.where("is_blocked", "==", false)
.where("has_activated", "==", false)
.get()
})
.then(querySnapshot=>{
if(querySnapshot.empty){
console.log("User has activated, He may be trying to reactivate, verify his activation status in users document!")
return usersRef
.doc(uid)
.get()
}
console.log("User is a trying to activate for the first time!")
querySnapshot.forEach(documentSnapshot => {
docId = documentSnapshot.id;
})
return activationPinsRef
.doc(docId)
.update({
"has_activated": true,
"uid_activated": uid,
"time_activated": Date.now()
})
})
.then(result => {
if(result instanceof admin.firestore.DocumentSnapshot){
var is_activated = result.data().is_activated;
if(is_activated){
console.log('Reactivation successful!!!');
return res.status(200).send({
"status": "success",
"is_reactivation": true
})
}
return Promise.reject(new Error("User is_activated field is false!"))
}
usersRef
.doc(uid)
.update({
is_activated:true
})
return res.status(200).send({
"status": "success",
"is_reactivation": false
})
})
.catch(error => {
console.log(error)
return res.status(401).end()
})
});
I have an issue and I need help since I'm learning.
I have a flutter app that saves on Firebase/Firestore data, when a user requests a friendship I add it on both sender and target user, changing the IDs of sendTo and sentBy.
My problem is that CloudFunctions detect well that 2 collections from different users have been changed and notify me 2x (target user). So code is fine but should only notify once/target user
I'm using FCM to send local notifications.
exports.sendRequestNotification = functions.firestore
.document('users/{userId}/requests/{requestId}')
.onCreate((snap, context) => {
const docReq = snap.data()
/*console.log(docReq)*/
const sentBy = docReq.sentBy
const sentTo = docReq.sentTo
const contentRequest = docReq.code
if(contentRequest !== null){
// Get push token user to (receive)
admin
.firestore()
.collection('users')
.where('userId', '==', sentTo)
.get()
.then(querySnapshot => {
querySnapshot.forEach(userTo => {
/*console.log(`Found request user to: ${userTo.data().userId}`)*/
if (userTo.data().pushToken) {
// Get info user from (sent)
admin
.firestore()
.collection('users')
.where('userId', '==', sentBy)
.get()
.then(querySnapshot2 => {
querySnapshot2.forEach(userFrom => {
/*console.log(`Found request user from: ${userFrom.data().userId}`)*/
const payload = {
notification: {
title: `${userFrom.data().nickname}`,
body: contentRequest,
badge: '1',
sound: 'default'
}
}
// Let push to the target device
admin
.messaging()
.sendToDevice(userTo.data().pushToken, payload)
.then(response => {
/*console.log('Successfully sent request:', response)*/
})
.catch(error => {
console.log('Error sending request:', error)
})
})
})
} else {
console.log('User request or token not found')
}
})
})
return null
}
})
It is not very clear from your code why it would send the notification twice (since you check that userTo.data().userId !== sentBy). But what is sure is that you are not returning a Promise that resolves when all the asynchronous operations (get() and sendToDevice()) are completed.
I would suggest you watch the official Video Series (https://firebase.google.com/docs/functions/video-series/) which explain very well this point about returning Promises for background functions (in particular the ones titled "Learn JavaScript Promises").
In particular, you will see in the videos that if you don't return a Promise, the Cloud Function may terminate before asynchronous operations are completed, potentially resulting in some inconsistent (not logical) results .
So, you should give a try with the following adapted code, which returns the promises chain:
exports.sendRequestNotification = functions.firestore
.document('users/{userId}/requests/{requestId}')
.onCreate((snap, context) => {
const db = admin.firestore();
const docReq = snap.data();
/*console.log(docReq)*/
const sentBy = docReq.sentBy;
const sentTo = docReq.sentTo;
// Get push token user to (receive)
return db.collection('users')
.where('userId', '==', sentTo)
.get()
.then(querySnapshot => {
//We know there is only one document (i.e. one user with this Id), so lets use the docs property
//See https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot.html#docs
const userTo = querySnapshot.docs[0];
if (userTo.data().pushToken && userTo.data().userId !== sentBy) {
// Get info user from (sent)
return db.collection('users')
.where('userId', '==', sentBy)
.get();
} else {
console.log('User request or token not found')
throw new Error('User request or token not found');
}
})
.then(querySnapshot => {
const userFrom = querySnapshot.docs[0];
const payload = {
notification: {
title: `${userFrom.data().nickname}`,
body: `requestNotify`,
badge: '1',
sound: 'default'
}
}
return admin
.messaging()
.sendToDevice(userTo.data().pushToken, payload);
})
.catch(error => {
console.log('Error:', error);
return false;
})
})
I'm building a simple single page web app using Vue JS (Vue Cli 3) with Firebase's Firestore as the back-end database. I've managed to add, and delete records with ease. I'm running into an issue when trying to 'update' a user's details.
My code for this function is as follows:
saveEditUser() {
db.collection('users')
.where('email', '==', this.form.email)
.get()
.then(snap => {
snap.forEach(doc => {
doc.ref.update({
email: this.form.email
})
})
})
.then(() => {
console.log('Successfully updated the record')
})
.catch(error => {
console.error('There was an error editing the record: ' + error)
})
}
Some things that I've discovered during my attempts at debugging this:
This is not a scope issue where 'this' in the this.form.email is not available inside the forEach loop.
I thought this could be the case and so I declared a 'const vm = this' before the loop and tried to use vm.form.email, but no dice.
Also, when I try to update the email field to a simple string like 'abc' instead of a dynamic value such as this.form.email, it works!
After several spent hours on this ridiculous problem, I am officially stumped folks. Please send help!
Thank you.
TL;DR: the OP was updating a record with the same value, hence nothing appeared to change in the Firestore DB. However, in his code, there was the need to return the promise returned by the single asynchronous operation (or by the set of asynchronous operations)
Since your are potentially going to execute several asynchronous operations to the database in parallel (using the update() method, which return a promise, see doc) , you need to use Promise.all(), as follows.
saveEditUser() {
const email = this.form.email;
const= promises = [];
db.collection('users')
.where('email', '==', email )
.get()
.then(snap => {
snap.forEach(doc => {
promises.push(
doc.ref.update({
email: email //Actually the problems comes from here, see below
})
);
return Promise.all(promises);
})
})
.then(() => {
console.log('Successfully updated the record')
})
.catch(error => {
console.error('There was an error editing the record: ' + error)
})
}
If you are 100% sure your query will return only one doc you could update the doc directly, but then you have to return the promise returned by update(), as follows:
saveEditUser() {
const email = this.form.email;
db.collection('users')
.where('email', '==', email)
.get()
.then(snap => {
return snap.docs[0].ref.update({
email: email
});
})
.then(() => {
console.log('Successfully updated the record')
})
.catch(error => {
console.error('There was an error editing the record: ' + error)
})
}
Note: by declaring the email const at the beginning of the function, you should not encounter any problem of context anymore.
Update following our comments and discussion:
Actually you are updating with the SAME value of email. So it is normal you don't see any result. Just try to update with another value, like in the following code:
saveEditUser() {
const email = this.form.email;
db.collection('users')
.where('email', '==', email)
.get()
.then(snap => {
return snap.docs[0].ref.update({
email: 'john.doe#gmail.com'
});
})
.then(() => {
console.log('Successfully updated the record')
})
.catch(error => {
console.error('There was an error editing the record: ' + error)
})
}
If you want to test with a value from your form, just use two fields: one with the value to query and one with the new value, like:
<input v-model="form.mail" placeholder="mail to search for">
<input v-model="form.newMail" placeholder="new email">
.....
saveEditUser() {
const emailToQuery = this.form.email;
const newEmail = this.form.newMail;
db.collection('users')
.where('email', '==', emailToQuery )
.get()
.then(snap => {
return snap.docs[0].ref.update({
email: newEmail
});
})
.then(() => {
console.log('Successfully updated the record')
})
.catch(error => {
console.error('There was an error editing the record: ' + error)
})
}