Is there any reason why I'm being signed out before my firebase actions are done finishing?
What Should Happen:
I first make a post request to my server to update values in my db.
Then I update the users firebase email if its changed.
Then I update the email if its changed as well.
Then finally I want to sign the user out.
What Happens:
I instantly gets signed out and then get errors in my console because the other actions couldn't be completed.
I have tried to trail the .then() after my axios post as well but I still had the same issue of being instantly signed out.
export const updateUserData = (userData) => {
return (dispatch, getState, {getFirebase}) => {
const state = getState();
const firebase = getFirebase()
let user = firebase.auth().currentUser;
let cred = firebase.auth.EmailAuthProvider.credential(user.email, userData.oldPassword);
user.reauthenticateWithCredential(cred).then(() => {
axios.post('/updateUserData', {
uid: state.firebase.auth.uid,
email: userData.email,
firstName: userData.firstName,
lastName: userData.lastName,
devices: userData.devices,
}, {
headers: {
"Authorization": `${state.firebase.auth.stsTokenManager.accessToken}`,
'Content-Type': 'application/json',
},
withCredentials: true
}).catch(err => {
console.log("Failed Email Change: " + err)
});
}).then(() => {
if (state.firebase.auth.email !== userData.email) {
firebase.auth().currentUser.updateEmail(userData.email).then(() => {
console.log("Email Changed")
}).catch(err => {
console.log("Failed Email Change: " + err)
});
}
}).then(() => {
if (userData.newPassword.length !== 0) {
firebase.auth().currentUser.updatePassword(userData.newPassword).then(() => {
console.log("Successful Password Change")
}).catch(err => {
console.log("Failed Password Change: " + err)
});
}
}).then(() => {
firebase.auth().signOut().then(null)
})
}
}
You aren't returning values from your promise chains. If you want an async action to take place after another one when using Promises, you need to return them:
// don't do this
doThing().then(() => {
doSomethingElse().then(() => { /* ... */ });
}).then(() => {
// this will happen before doSomethingElse is finished
finallyDoThing();
});
// instead do this
doThing().then(() => {
return doSomethingElse();
}).then(() => {
return finallyDoThing();
});
Related
I have a Cloud Function deployed to Firebase, and my iOS and Android apps use it fine, all works good. Below is the function deployed.
const admin = require('firebase-admin');
const firebase_tools = require('firebase-tools');
const functions = require('firebase-functions');
admin.initializeApp();
exports.deleteUser = functions
.runWith({
timeoutSeconds: 540,
memory: '2GB'
})
.https.onCall((data, context) => {
const userId = context.auth.uid;
var promises = [];
// DELETE DATA
var paths = ['users/' + userId, 'messages/' + userId, 'chat/' + userId, 'like/' + userId];
paths.forEach((path) => {
promises.push(
recursiveDelete(path).then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE FILES
const bucket = admin.storage().bucket();
var image_paths = ["avatar/" + userId, "avatar2/" + userId, "avatar3/" + userId];
image_paths.forEach((path) => {
promises.push(
bucket.file(path).delete().then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE USER
promises.push(
admin.auth().deleteUser(userId)
.then( () => {
console.log('Successfully deleted user');
return true;
})
.catch((error) => {
console.log('Error deleting user:', error);
})
);
return Promise.all(promises).then(() => {
return true;
}).catch(er => {
console.error('...', er);
});
});
function recursiveDelete(path, context) {
return firebase_tools.firestore
.delete(path, {
project: process.env.GCLOUD_PROJECT,
recursive: true,
yes: true,
token: functions.config().fb.token
})
.then(() => {
return {
path: path
}
}).catch( (error) => {
console.log('error: ', error);
return error;
});
}
// [END recursive_delete_function]
How can I execute this script with a button in javascript? A standard .js file locally? I also need to be able to pass in a userId manually.
In my react native app I call it like:
const deleteUser = async () => {
functions().httpsCallable('deleteUser')()
signOut();
}
But in my javascript file (nothing to do with my react native app), I need to pass in a userId and call that same function to delete the user.
There are a number of ways to go about executing a cloud function within your client side application.
Depending on how you have the function setup, you can either pass in a parameter or data via the body in the request.
For example, using express (similar to other frameworks):
// fetch(‘api.com/user/foo’, {method: ‘DELETE’} )
app.delete(‘/user/:uid’, (req, res) => {
const uid = req.params.uid;
// execute function
})
// fetch(‘api.com/user’, {method: ‘DELETE’, body: { uid: foo } } )
app.delete(‘/user’, (req, res) => {
const uid = req.body.uid;
// execute function
})
// fetch(‘api.com/user?uid=foo’, {method: ‘DELETE’} )
app.delete(‘/user’, (req, res) => {
const uid = req.query.uid;
// execute function
})
Full Example:
<button onclick=“deleteUser(uid)”>Delete Me</button>
<script>
function deleteUser(uid) {
fetch(`api.com/user/${uid}`, { method: ‘DELETE’});
// rest of function
}
</script>
Was able to call my firebase function with the following:
userId was accessible like so const { userId } = data; from my function script
async function deleteAccount(userId) {
const deleteUser = firebase.functions().httpsCallable("deleteUser");
deleteUser({ userId }).then((result) => {
console.log(result.data);
});
}
i have a function called login that redirects the user to the main page if everything was ok. Then, on the main page, i want to fetch some user info with useEffect using the token the was stored when the user logged in, but nothing happens. Only when i refresh the page i get the data.
login function
export const login = ({ email, password, history }) => {
return async (dispatch) => {
try {
const response = await fetch("http://localhost:5000/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email,
password,
}),
});
const data = await response.json();
if (data.status === 200) {
localStorage.setItem("userToken", data.user);
history.push("/");
} else {
dispatch(
setNotification({
variant: "error",
message: data.message,
})
);
}
} catch (e) {
console.log(e.message);
}
};
};
fetch user funtion
export const fetchUser = () => {
return async (dispatch) => {
try {
const response = await fetch("http://localhost:5000/userInfo", {
headers: {
"x-access-token": localStorage.getItem("userToken"),
},
});
const data = await response.json();
dispatch(setUser({
id: data.id,
fullname: data.fullname,
email: data.email
}))
} catch (error) {}
};
};
useEffect on my main page
useEffect(() => {
dispatch(fetchUser());
}, []);
backend function
module.exports.getCurrentUser = async (req, res) => {
const token = req.headers["x-access-token"];
try {
const verifyToken = jwt.verify(token, "123");
const user = await User.findOne({ email: verifyToken.email });
return res.json({
id: user._id,
fullname: user.fullname,
email: user.email
})
} catch (error) {}
};
The 2nd parameter to useEffect tells it when it needs to run. It only runs if one of the values in the array has changed. Since you pass an empty array, none of the values in it have changed.
This is presuming your app probably starts at '/', then detects there is no user so switches to the login screen. When it goes back to the root, it only executes useEffect if something in the array has changed from the previous render.
As it is, the isMounted doesn't make much sense. This could simply be:
useEffect(() => {
dispatch(fetchUser());
});
You're calling setUser, but what is calling your login function?
I am receiving the following error when triggering this cloud function: "Error unauthenticated".
I do not wish to allow unauthenticated calls to this cloud function.
The workflow is as follows:
User registers in app via firebase password authentication
Firebase Auth Credentials are created (firebase signs in user upon success)
Once the credentials have been created, the cloud function is triggered in the firebase auth callback.
At this point, the call should be authenticated, given it's being triggered in the firebase auth response.
However, it keeps erroring with
Error: unauthenticated
The user is authenticated at this point.
Any suggestions?
CLIENT CODE ->
const onRegisterPress = () => {
if (password !== confirmPassword) {
alert("Passwords don't match.")
return
}
setLoading(true);
//CREATE'S USER'S AUTH CREDENTIALS
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((response) => {
const data = {
...
}
//console.log(response);
return new Promise(async function(resolve, reject) {
await firebase.functions().httpsCallable('writeAccountUser')({
data
}).then((response) => {
console.log("Write Account User Response: ", response);
resolve(setLoading(false));
}).catch((error) => {
console.error("Cloud Function Error: ", error);
setLoading(false);
reject(error)
})
});
})
.catch((error) => {
alert(error)
});
}
CLOUD FUNCTION ->
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const firestore = admin.firestore();
exports.writeAccountUser = functions.https.onCall((data, context) => {
console.log("Incoming Data: ", data);
console.log("Incoming Context: ", context);
const clientData = data.data;
console.log("Client Data: ", clientData);
console.log("Account ID: ", clientData.accountID);
return new Promise(async function (resolve, reject) {
const accountsRef = firestore.collection('Accounts');
const usersRef = firestore.collection('users');
const now = new Date();
if (clientData.accountExists === true) {
console.log("Account Exists");
await accountsRef
.doc(clientData.accountID)
.update({
users: admin.firestore.FieldValue.arrayUnion(clientData.uid)
}).catch((error) => { console.error(error); reject(false) });
}
else {
console.log("Account Does Not Exist!");
const account_data = clientData.accountData;
const product_lines = clientData.productLines;
await accountsRef
.doc(clientData.accountID)
.set({
account_data,
product_lines,
users: [clientData.uid],
dateCreated: {
date: now,
timestamp: now.getTime()
}
}).catch((error) => { console.error(error); reject(false)});
};
const email = clientData.email;
const fullName = clientData.fullName;
const acceptTerms = clientData.acceptTerms;
const userData = {
id: clientData.uid,
email,
fullName,
accountID: clientData.accountID,
dateCreated: {
date: now,
timestamp: now.getTime()
},
lastUpdateFetch: {
date: now,
timestamp: now.getTime()
},
termsConditionsAccepted: acceptTerms
};
await usersRef
.doc(clientData.uid)
.set(userData)
.catch((error) => { console.error(error); reject(false) });
resolve(true);
});
});
Error ->
[Unhandled promise rejection: Error: unauthenticated]
at node_modules/#firebase/firestore/dist/rn/prebuilt.rn-f9cd27ba.js:12199:33 in
at http:///node_modules/expo/AppEntry.bundle?platform=ios&dev=true&hot=false&minify=false:169743:29 in _errorForResponse
at node_modules/#firebase/firestore/dist/rn/prebuilt.rn-f9cd27ba.js:12747:31 in yu
at node_modules/tslib/tslib.js:77:12 in
at http://REDACTED/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&hot=false&minify=false:120748:21 in
at http://REDACTED/node_modules/expo/AppEntry.bundle?platform=ios&dev=true&hot=false&minify=false:120702:31 in fulfilled
You can try refactoring the function as shown below:
const onRegisterPress = async () => {
if (password !== confirmPassword) {
alert("Passwords don't match.")
return
}
setLoading(true);
const response = await firebase.auth().createUserWithEmailAndPassword(email, password)
const data = {...}
const fnResponse = await firebase.functions().httpsCallable('writeAccountUser')({data})
console.log("Write Account User Response: ", response);
}
You can also create the account using the Admin SDK in the same function and log the user on your web app after the response. That'll ensure the Cloud function's action has been executed as well (just in case the function is not called after user sign up for any reason).
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
Hi everyone I have such a problem,
After a user signs up for my site, for the first time, I want to log in with the user one more time.
I want that after he registers, connect again.
I tried to do it asynchronously, but it does not always work, sometimes I try to log in before the user is registers, I do not know why it does not work.
I want there to be a login only after registration, to force it.
handleSubmit = async () => {
const newUserData = {
email: 'test#mail.com',
password: '123456',
confirmPassword: '123456',
handle: 'test'
};
await signupUser(newUserData);
await signinChat(newUserData);
}
export const signupUser = (newUserData) => (dispatch) => {
axios
.post('/signup', newUserData)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
};
//basically call to this function to signup
exports.signup = (req, res) => {
const newUser = {
email: req.body.email,
password: req.body.password,
confirmPassword: req.body.confirmPassword,
handle: req.body.handle,
};
db.doc(`/users/${newUser.handle}`)
.get()
.then((doc) => {
if (doc.exists) {
return res.status(400).json({ handle: "this handle is already taken" });
} else {
return firebase
.auth()
.createUserWithEmailAndPassword(newUser.email, newUser.password);
}
})
.then((data) => {
userId = data.user.uid;
return data.user.getIdToken();
})
.then((idToken) => {
token = idToken;
const userCredentials = {
handle: newUser.handle,
email: newUser.email,
};
const userPreferences = {
handle: newUser.handle
};
return db.doc(`/users/${newUser.handle}`).set(userCredentials);
})
.then(() => {
return res.status(201).json({ token });
})
.catch((err) => {
console.error(err);
if (err.code === "auth/email-already-in-use") {
return res.status(400).json({ email: "Email is already is use" });
} else {
return res
.status(500)
.json({ general: "Something went wrong, please try again" });
}
});
};
export const signinChat = (user) => {
return async (dispatch) => {
const db = firebase.firestore();
firebase.auth()
.signInWithEmailAndPassword(user.email, user.password)
.then(data => {
console.log(data);
const currentUser = firebase.auth().currentUser;
const name = `${user.handle}`;
currentUser.updateProfile({
displayName: name,
})
.then(() => {
db.collection('users')
.doc(data.user.displayName)
.update({
isOnline: true,
})
.then(() => {
const loggedInUser = {
handle: user.handle,
uid: data.user.uid,
email: user.email
}
localStorage.setItem('user', JSON.stringify(loggedInUser));
console.log('User logged in successfully...!');
})
.catch(error => {
console.log(error);
});
});
})
.catch(error => {
console.log(error);
})
}
}
When I try to connect a second time, sometimes it does not work, for example:
image to show the error:
Its possible for me to send a notification to the reciever: idTo which is a string in the database. However, is it possible to use the array-field instead?: participants and send a notification to everyone in the array?
I store my users with their respective tokens at this path in firebase: Users->{userId}:
I've tried changing:
const idTo = doc.idTo
admin.firestore().collection('users').where('uid', '==', idTo).get().then(querySnapshot => {
to:
const participants = doc.participants
admin.firestore().collection('users').where('uid', 'arrayContains', participants).get().then(querySnapshot => {
Full code:
exports.sendNotification = functions.firestore
.document('messages/{roomId1}/room/{message}/message/{messageId}')
.onCreate((snap, context) => {
console.log('----------------start function--------------------')
const doc = snap.data()
console.log(doc)
const idFrom = doc.idFrom
const idTo = doc.idTo
const contentMessage = doc.message
// Get push token user to (receive)
admin.firestore().collection('users').where('uid', '==', idTo).get().then(querySnapshot => {
querySnapshot.forEach(userTo => {
console.log(`Found user to: ${userTo.data().uid}`)
if (userTo.data().pushToken) {
// Get info user from (sent)
admin.firestore().collection('users').where('uid', '==', idFrom).get().then(querySnapshot2 => {
querySnapshot2.forEach(userFrom => {
console.log(`Found user from: ${userFrom.data().uid}`)
const payload = {
notification: {
title: `${userFrom.data().name}`,
body: contentMessage,
badge: '1',
sound: 'default',
clickAction: 'FLUTTER_NOTIFICATION_CLICK',
// badge: '1'
},
data: {
title: '',
content: '',
image: '',
uploader: '',
type: 'chat',
},
}
// Let push to the target device
admin.messaging().sendToDevice(userTo.data().pushToken, payload).then(response => {
return console.log('Successfully sent message:', response)
}).catch(error => {
console.log('Error sending message:', error)
})
})
return console.log('failed')
}).catch(error => {
console.log('Error sending message:', error)
})
} else {
console.log('Can not find pushToken target user')
}
})
return console.log('error: invalid path')
}).catch(error => {
console.log('Error sending message:', error)
})
return null
})
I'm thinking maybe I need to loop over the array for each of the users and somehow execute the push notification. Any ideas are welcome
var messageToSend=req.body.messageToSend;
var message = { //this may vary according to the message type (single recipient, multicast, topic, et cetera)
registration_ids:regTokens , // Id List if more then one recipent
//to : regTokens, //use if One recipient
data: { //This is only optional, you can send any data
score: '',
time: ''
},
notification:{
body : messageToSend
}
};
console.log(message);
fcm.send(message, function(err, response){
if (err) {
console.log("Something has gone wrong!",err);
} else {
console.log("Successfully sent with response: ", response);
}
});