How do I use Javascript Promise Chaining with Firebase Authentication and Firebase? - javascript

I need to utilize a key stored in my firebase using the user's firebase user id.
The steps to my function are the following:
1) Get the Firebase User ID after authentication
2) Using the Firebase User ID, pull the stored API Key value (which I saved in a node: app/{Firebase User Id})
3) Using the stored API Key value, run my last function
After doing some research, I've come to the conclusion that I should use Javascript Promise Chaining to the below code, which I'm having a difficult time doing
firebase.initializeApp({databaseURL: "{}"});
var dbRef = firebase.database();
function pull_api_key_from_firebase_after_auth(func){
firebase.auth().onAuthStateChanged((user) => {
if (user) {
var app_ref = dbRef.ref('app').child(user.uid);
app_ref.on("child_added", function(snap) {
dictionary_object = snap.val()
api_key = dictionary_object.api_key
func(api_key)
})
}
});
}
function final_function(api_key){
console.log('processing final function')
}
pull_api_key_from_firebase_after_auth(final_function)
Alternatively, I'd like to make api_key a global variable as such:
function pull_api_key_from_firebase_after_auth(func){
firebase.auth().onAuthStateChanged((user) => {
if (user) {
var app_ref = dbRef.ref('app').child(user.uid);
app_ref.on("child_added", function(snap) {
dictionary_object = snap.val()
api_key = dictionary_object.api_key
localStorage.setItem('api_key',api_key)
})
}
});
}
api_key = localStorage.getItem('api_key')
final_function(api_key)
However I cant figure out how to make final_function as well as my other functions wait until api_key is defined

You must use firebase.auth().onAuthStateChanged to get the uid bcs .auth.currentUser.uid will NOT be available on page load. It shows up a microsecond after (it's asynchronous).
To do what you want simply build out a promise function and call it within .onAuthStateChanged and .then do your other function. You custom promise might look like:
function fetchUserData(){
return new Promise(function(resolve, reject){
var db = firebase.firestore(); // OR realtime db
db.collection("users").get().then(function(snap) {
resolve (snap);
}).catch(function(error) {
reject (error);
});
});
}
Within .onAuthStateChange just do something like:
fetchUserData().then((snap) => {
// do your thing
})
.catch((error) => {
console.log(error);
});

Related

How can make my Program wait for Firebase's data before executing a function?

I'm a beginner in Next.js and Firebase. I was trying to make a log in system that has roles. I used Firebase Authentication and stored the account's roles on firestore. I connected the authentication account and firestore data by using the UID (from authentication) as the Firestore Document ID.
Ps. I'm not sure if this is the best way to do this but I am able to fetch data from a field by looking for the Document ID fetched using the UID.
What I need to happen is to get the role of the Account from the Firestore and use that for a function to push the correct page for that account type. The problem is, my pushToPage() fires first before my getData() gets the information it asks for from the firestore.
This is the LogIn function. I used auth.onAuthStateChanged to get the UID of the user.
var firebaseDocument = ' ';
var accountType = '';
var databaseRef = '';
function LogIn() {
signInWithEmailAndPassword(auth, email, password)
.then((response) => {
sessionStorage.setItem('Token', response.user.accessToken);
auth.onAuthStateChanged((user) => {
if (user) {
firebaseDocument = user.uid;
databaseRef = doc(database, 'users', firebaseDocument);
} else {
console.log('user logged out');
}
});
})
.catch((err) => {
console.log(err.code);
});
accountType = getData();
pushToPage(accountType);
}
This is the getData function where it fetches the account role
const getData = async () => {
try {
const docSnap = await getDoc(databaseRef);
if (docSnap.exists()) {
return docSnap.data().account_type;
} else {
console.log('Document does not exist');
}
} catch (e) {
console.log(e);
}
};
this is the pushToPage function that reads the var accountType value to decide on what page to go.
const pushToPage = (accountType) => {
if (accountType == 'management') {
router.push('/accounts/management');
} else if (accountType == 'dean') {
router.push('/accounts/deans');
} else {
router.push('/accounts/faculty');
}
};
Running this code does not only make my Program wait for the firebase response but also displays this error
Unhandled Runtime Error
FirebaseError: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore
I didn't use collection() though.
Also, I searched for similar problems and I think I need to place a .then() function somewhere or somekind of Asynchronous Programming technique. Unfortunately, I struggle in understanding these.
I hope I can find help here. Thanks guys.
I don't think I need the .then() function anymore. I just called the pushToPage() inside the getData.
const getData = async () => {
try {
const docSnap = await getDoc(databaseRef);
if (docSnap.exists()) {
accountType = docSnap.data().account_type;
pushToPage(accountType);
} else {
console.log('Document does not exist');
}
} catch (e) {
console.log(e);
}
};

Not getting data from firebase on opening the app

I am trying to get data from firebase but it returns empty value when the app loads, but if I edit something on that file even the commented line, then the data loads and app runs, I want when the app opens all data should be there from firebase to run app. and also how to arrange "grabbedData" in reverse order tried grabbedData.reverse() but doent work.
const Getdata = async () => {
let grabbedData = [];
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
// console.log("snapshot....", snapshot);
grabbedData.push(snapshot.val());
});
setUserdata(grabbedData);
console.log("grabbedData", grabbedData); // empty value here :(
if (grabbedData) {
let getfranchiseuid = "";
Object.keys(grabbedData).map(function (key) {
let y = grabbedData[key];
Object.keys(y).map(function (key2) {
let x = y[key2];
if (key2 === uid) {
getfranchiseuid = x.franchiseuid;
}
});
});
if (getfranchiseuid) {
let customerList1 = [];
firebase
.database()
.ref(`/serviceProvider/${getfranchiseuid}/franchise/customers`)
.orderByKey()
.on("value", (snapshot) => {
customerList1.push(snapshot.val());
});
setCustomerList(customerList1);
console.log("customerList1customerList1", customerList1);
}
}
};
useEffect(() => {
var unsubscribe = firebase.auth().onAuthStateChanged(function (user) {
if (user) {
storeUser({ user });
setUser(user);
setEmail(user.email);
setUid(user.uid);
} else {
// No user is signed in.
}
});
unsubscribe();
Getdata();
}, []);
Data is loaded from Firebase asynchronously. Since this may take some time, your main JavaScript code will continue to run, so that the user can continue to use the app while the data is loading. Then when the data is available, your callback is invoked with that data.
What this means in your code is that (for example) right now your setUserdata is called before the grabbedData.push(snapshot.val()) has run, so you're setting any empty user data. You can most easily see this by setting some breakpoints on the code and running it in a debugger, or by adding logging and checking the order of its output.
console.log("1");
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
console.log("2");
});
console.log("3");
When you run this code, the output will be:
1
3
2
This is probably not what you expected, but it is exactly correct and does explain your problems.
The solution for this is always the same: any code that needs the data from the database must be inside the callback, or be called from there.
So for example:
await firebase
.database()
.ref(`/users/`)
.orderByKey()
.on("value", (snapshot, key) => {
grabbedData.push(snapshot.val());
setUserdata(grabbedData);
});
this will ensure that setUserdata is called whenever you updated the grabbedData.
Since you have much more code that depends on grabbedData, that will also have to be inside the callback. So the entire if (grabbedData) { block will need to be moved, and probably others. If you keep applying the solution above, the code will start working.
This is a very common problem for developers that are new to calling asynchronous cloud APIs, so I highly recommend reading some of these other answers:
Why Does Firebase Lose Reference outside the once() Function?
Best way to retrieve Firebase data and return it, or an alternative way
How do I return the response from an asynchronous call? (this one is not specific to Firebase, as the problem is not specific to Firebase)

Retrieve multiple users info from firebase auth using Node js

I am using Firebase authentication to store users. I have two types of users: Manager and Employee. I am storing the manager's UID in Firestore employee along with the employee's UID. The structure is shown below.
Firestore structure
Company
|
> Document's ID
|
> mng_uid: Manager's UID
> emp_uid: Employee's UID
Now I want to perform a query like "Retrieve employees' info which is under the specific manager." To do that I tried to run the below code.
module.exports = {
get_users: async (mng_uid, emp_uid) => {
return await db.collection("Company").where("manager_uid", "==", mng_uid).get().then(snaps => {
if (!snaps.empty) {
let resp = {};
let i = 0;
snaps.forEach(async (snap) => {
resp[i] = await admin.auth().getUser(emp_uid).then(userRecord => {
return userRecord;
}).catch(err => {
return err;
});
i++;
});
return resp;
}
else return "Oops! Not found.";
}).catch(() => {
return "Error in retrieving employees.";
});
}
}
Above code returns {}. I tried to debug by returning data from specific lines. I got to know that the issue is in retrieving the user's info using firebase auth function which I used in forEach loop. But it is not returning any error.
Thank you.
There are several points to be corrected in your code:
You use async/await with then() which is not recommended. Only use one of these approaches.
If I understand correctly your goal ("Retrieve employees' info which is under the specific manager"), you do not need to pass a emp_uid parameter to your function, but for each snap you need to read the value of the emp_uid field with snap.data().emp_uid
Finally, you need to use Promise.all() to execute all the asynchronous getUser() method calls in parallel.
So the following should do the trick:
module.exports = {
get_users: async (mng_uid) => {
try {
const snaps = await db
.collection('Company')
.where('manager_uid', '==', mng_uid)
.get();
if (!snaps.empty) {
const promises = [];
snaps.forEach(snap => {
promises.push(admin.auth().getUser(snap.data().emp_uid));
});
return Promise.all(promises); //This will return an Array of UserRecords
} else return 'Oops! Not found.';
} catch (error) {
//...
}
},
};

Set on firebase and then set firebase claims

So i working with firebase auth and database in order to set new user to data base, if set successful i want to set claims for that user.
So it means i have a promise within a promise:
function setUser(user){
// no need for the database code before this, but userRef is set properly
return userRef.set(user)
.then(succ => {
return firebase.firebase.auth().setCustomUserClaims(user.key, {admin: true})
.then(() => {
console.log("setting claims")
return true;
});
})
.catch(err => {
return err
})
}
calling function:
app.post("/register_user",jsonParser,async (req, res) => {
var user = req.body.user;
let result = await fireBase.setUser(user);
res.send(result);
})
What happens is that i get the set on the database but claims are not set nor i can i see the log. I know its a js question and not firebase one. I tried many different ways (with await) but non worked.
firebase.firebase does not seem correct. You need to be using the admin object which can be initialised using const admin = require('firebase-admin'); This is not part of the firebase db sdk, but the admin one. You can also use the userRef.uid as that gives you the id of the document of the user, if that is what you want, else use your user.key
return admin.auth().setCustomUserClaims(userRef.uid, {
admin: true
}).then(() => {
//on success
});

How to fix Cloud Function error admin.database.ref is not a function at exports

I'm currently trying to modify my Cloud Functions and move in under https.onRequest so that i can call use it to schedule a cron job. How it i'm getting the following error in the logs.
TypeError: admin.database.ref is not a function
at exports.scheduleSendNotificationMessageJob.functions.https.onRequest (/user_code/index.js:30:20)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:57:9)
exports.scheduleSendNotificationMessageJob = functions.https.onRequest((req, res) => {
admin.database.ref('/notifications/{studentId}/notifications/{notificationCode}')
.onCreate((dataSnapshot, context) => {
const dbPath = '/notifications/' + context.params.pHumanId + '/fcmCode';
const promise = admin.database().ref(dbPath).once('value').then(function(tokenSnapshot) {
const theToken = tokenSnapshot.val();
res.status(200).send(theToken);
const notificationCode = context.params.pNotificationCode;
const messageData = {notificationCode: notificationCode};
const theMessage = { data: messageData,
notification: { title: 'You have a new job reminder' }
};
const options = { contentAvailable: true,
collapseKey: notificationCode };
const notificationPath = '/notifications/' + context.params.pHumanId + '/notifications/' + notificationCode;
admin.database().ref(notificationPath).remove();
return admin.messaging().sendToDevice(theToken, theMessage, options);
});
return null;
});
});
You cannot use the definition of an onCreate() Realtime Database trigger within the definition of an HTTP Cloud Function.
If you switch to an HTTP Cloud Function "so that (you) can call use it to schedule a cron job" it means the trigger will be the call to the HTTP Cloud Function. In other words you will not be anymore able to trigger an action (or the Cloud Function) when new data is created in the Realtime Database.
What you can very well do is to read the data of the Realtime Database, as follows, for example (simplified scenario of sending a notification):
exports.scheduleSendNotificationMessageJob = functions.https.onRequest((req, res) => {
//get the desired values from the request
const studentId = req.body.studentId;
const notificationCode = req.body.notificationCode;
//Read data with the once() method
admin.database.ref('/notifications/' + studentId + '/notifications/' + notificationCode)
.once('value')
.then(snapshot => {
//Here just an example on how you would get the desired values
//for your notification
const theToken = snapshot.val();
const theMessage = ....
//......
// return the promise returned by the sendToDevice() asynchronous task
return admin.messaging().sendToDevice(theToken, theMessage, options)
})
.then(() => {
//And then send back the result (see video referred to below)
res.send("{ result : 'message sent'}") ;
})
.catch(err => {
//........
});
});
You may watch the following official Firebase video about HTTP Cloud Functions: https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3. It shows how to read data from Firestore but the concept of reading and sending back the response (or an error) is the same for the Realtime Database. Together with the 2 other videos of the series (https://firebase.google.com/docs/functions/video-series/?authuser=0), it also explains how it is important to correctly chain promises and to indicate to the platform that the work of the Cloud Function is finished.
For me, this error happened when writing admin.database instead of admin.database().

Categories

Resources