I am trying to use Firebase .on() method in order to check whenever a new account has been made in my realtime Firebase database. Now since I am encrypting this data in the Firebase Database, I would like to the first time the data from the firebase is loaded to decrypt it using a function that I made. Is it possible I could make something like a promise that will allow me to do this once the firebase loaded all the data in my variable?
var users2 = database.ref("users/").on("value", (snapshot) => {
const usersLst = snapshot.val();
users = usersLst
});
I would like to add something like this:
var users2 = database.ref("users/").on("value", (snapshot) => {
const usersLst = snapshot.val();
users = usersLst
}).then(() => {
console.log(decryptAccounts(users))
});
One important thing that I want to keep is the .on() function since that will allow me to constantly check whether someone has created a new account in the database. Does anyone have any ideas what is another approach I could use to achieve the same thing?
You can store the ref in a variable to reuse it like the docs suggest.
const databaseRef = database.ref("users/");
// use databaseRef.get() with the correct path to your attribute you want to get
// Runs only once
databaseRef.get().then((snapshot) => {
// Decrypt function
});
// Listens to changes
var users2 = databaseRef .on("value", (snapshot) => {
const usersLst = snapshot.val();
users = usersLst
});
This first gets the values once and then you can decrypt them, when the values change the .on function will be fired again.
You can just add the decryptAccounts() functions inside on() so everytime an update is received, you can run that function on that data.
var users2 = database.ref("users/").on("value", (snapshot) => {
const usersLst = snapshot.val();
// runs on new value fetched after every update
decryptAccounts(usersLst)
})
The answer that I found with the help of Dharmaraj was that you can just do this:
database.ref("users/").on("value", (snapshot) => {
decryptAccounts(snapshot.val());
});
This would get the values from the database every time and decrypt them.
Related
Greetings of the day to everyone,
So I'm having a really hard time with Firebase cause there's just so many versions and things going on. Its extremely complicated. I wanted to achieve some functionality which is not available through the client modular web 9 version.
So I have been trying to use the Cloud functions to just get the list of all the collections in a document.
My cloud function looks like this so far -
const functions = require("firebase-functions");
const admin = require('firebase-admin');
const { object } = require("firebase-functions/v1/storage");
admin.initializeApp();
const db = admin.firestore();
exports.addMessages = functions.https.onCall(async (data, context) => {
// Grab the text parameter.
const original = data.text;
var test;
const writeResult = admin.firestore().collection('users').doc(original)
const collections = await writeResult.listCollections();
collections.forEach(collection => {
console.log('Found subcollection with id:', collection.id);
test = collection.id;
});
// Send back a message that we've successfully written the message
return { id: test }
});
I call this function on my front end simply like this --
const functions = getFunctions();
const addMessage = httpsCallable(functions, 'addMessages');
const Cloud = () => {
addMessage({ docPath: `users/${userProfile.uid}` })
.then(function (result) {
var collections = result.data.collections;
console.log(collections);
})
.catch(function (error) {
// Getting the Error details.
var code = error.code;
var message = error.message;
var details = error.details;
// ...
});
}
However, I get a 500 ERROR. I have checked the payload and everything, the data is being passed and the function is being called but there is some other kind of bug that I seem to be missing.
If anyone has any ideas please do let me know.
First off, you're calling addMessage with an Object parameter and then trying to access it's payload by the text field of data, which is undefined. You could either pass users/${userProfile.uid} as a string parameter or assign data.docPath to original.
Also, test will only contain the last collection id, since it's being overwritten forEach of the collections. So, may I suggest you make test a string array and push() each collection id to it?
Finally, on the callback of addMessage, there is no data field to access in result. In the case you decide to use an array, result will simply be the array you returned from the cloud function.
P.S. I'm not sure I see a reason why this wouldn't work with the V9 modular SDK.
I have two calls to Firebase: one to get the existing data and one to listen for updates in the data. When those updates happen, instead of replacing the existing data for some reason I see to be adding the two datasets together. Can't figure out why as I'm directly updating state with new data in my second function.
Here are the functions called on mounted():
mounted() {
this.getImages();
this.refreshImages();
},
And the two functions in question:
async getImages() {
let snapshot = await db
.collection("Maria")
.orderBy("timestamp", "desc")
.get();
snapshot.forEach((doc) => {
let appData = doc.data();
appData.id = doc.id;
this.picturesData.push(appData);
});
this.dataLoaded = true;
},
async refreshImages() {
await db
.collection("Maria")
.orderBy("timestamp", "desc")
.onSnapshot((snapshot) => {
let newPicturesData = [];
snapshot.forEach((doc) => {
let newPictureData = doc.data();
newPictureData.id = doc.id;
newPicturesData.push(newPictureData);
});
this.picturesData = newPicturesData; // this should overwrite the data in my state, right? But instead it's appending.
});
},
It's difficult to tell you exactly what's happening without thoroughly testing your code but you have to note that the two calls (to getImages() and refreshImages()) may not be done in the order you expect.
Since in getImages() you push the data to picturesData and in refreshImages() you replace picturesData, I suspect that the listener set through refreshImages() returns data before you get the result of the query triggered by getImages().
Actually, since onSnapshot() triggers an initial call that returns the entire result of the query, you only need to call refreshImages() (you don't need the initial call to getImages()).
Note that onSnapshot() is not an asynchronous method like get(), so you don't need to make refreshImages() async.
I want to take wipe all of the values of a particular userID connected to many different post keys in my database by turning the userID to null. The userIDs are attached to post keys in the path: posts/ivies/userIDs in my database. Here is how the database looks:
So I decided to run the following for loop to filter for the userID and turn it to null:
exports.wipeData = functions.https.onRequest(async (req, res) => {
const original = 'ppPXA8MvaSRVbmksof0ByOzTxJ92';
const snapshot = await admin.database().ref('/posts/ivies/userIDs/');
console.log((snapshot));
for (let value in snapshot.val) {
if (value == original) {
snapshot.val.set("null")
}
else {
console.log(value)
}
}
res.redirect(303, snapshot.ref.toString());
// [END adminSdkPush]
});
Although this function deploys and runs, it does not turn 'ppPXA8MvaSRVbmksof0ByOzTxJ92' to 'null' as anticipated. Thank you for your help.
Your general approach seems fine, but you have a few bugs in there.
This should work better:
exports.wipeData = functions.https.onRequest(async (req, res) => {
const original = 'ppPXA8MvaSRVbmksof0ByOzTxJ92';
const ref = admin.database().ref('/posts/ivies/userIDs/');
const query = ref.orderByValue().equalTo(original);
const results = await query.once('value');
const updates = {};
results.forEach((snapshot) => {
updates[snapshot.key] = null;
});
await ref.update(updates);
res.status(200).send(JSON.stringify(updates));
})
The main changes:
Your snapshot variable doesn't contain any data yet, as you're not reading from the database. The once('value') in my code performs that read.
This code uses a query to select only the nodes that have the right value. When your number of users grows, this significantly reduces the database load (and cost).
This code first gathers all updates into a single object, and then sends them to the database as one call.
The await in await ref.update(updates) is probably the main fix, as it ensures the redirect is only executed once the database writes has been completed.
I am not familiar with firebase cloud functions, but in regular client-side firebase code val needs to be called as a function, and you have to wait for the value from a reference. You could try:
exports.wipeData = functions.https.onRequest(async (req, res) => {
const original = 'ppPXA8MvaSRVbmksof0ByOzTxJ92';
const userIDs = await admin.database().ref('/posts/ivies/userIDs/');
userIDs.once("value", snapshot => {
var lookup = snapshot.val();
for (let key in lookup) {
var value = lookup[key];
if (key == value) {
userIDs.child(key).set(null);
}
}
res.redirect(303, userIDs.ref.toString());
});
});
app.get('/zones/:id/experiences', function(req,res) {
var zone_key = req.params.id;
var recent = [];
var ref = firebase.database().ref('participants/'+zone_key+'/experiences');
ref.on("value", function(snapshot) {
snapshot.forEach((snap) => {
firebase.database().ref('experiences').child(snap.val()).once("value").then((usersnap) => {
recent.push(usersnap.val());
});
});
console.log(recent);
});
res.render('experiences',{key: zone_key, list: recent});
});
In the above code, I am querying a reference point to get a set of "keys". Then for each key, I am querying another reference point to get the object associated to that key. Then for each object returned for those keys, I simply want to push the objects into a list. I then want to pass in this list to the client site to do stuff with the data using the render.
For some reason, the recent [] never gets populated. It remains empty. Is this an issue with my variables not being in scope? I console logged to check what the data the reference points are returning and its all good, I get the data that I want.
P.S is nesting queries like this ok? For loop within another query
As cartant commented: the data is loaded from Firebase asynchronously; by the time you call res.render, the list will still be empty.
An easy way to see this is the 1-2-3 test:
app.get('/zones/:id/experiences', function(req,res) {
var zone_key = req.params.id;
var recent = [];
var ref = firebase.database().ref('participants/'+zone_key+'/experiences');
console.log("1");
ref.on("value", function(snapshot) {
console.log("2");
});
console.log("3");
});
When you run this code, it prints:
1
3
2
You probably expected it to print 1,2,3, but since the on("value" loads data asynchronously, that is not the case.
The solution is to move the code that needs access to the data into the callback, where the data is available. In your code you need both the original value and the joined usersnap values, so it requires a bit of work.
app.get('/zones/:id/experiences', function(req,res) {
var zone_key = req.params.id;
var recent = [];
var ref = firebase.database().ref('participants/'+zone_key+'/experiences');
ref.on("value", function(snapshot) {
var promises = [];
snapshot.forEach((snap) => {
promises.push(firebase.database().ref('experiences').child(snap.val()).once("value"));
Promise.all(promises).then((snapshots) => {
snapshots.forEach((usersnap) => {
recent.push(usersnap.val());
});
res.render('experiences',{key: zone_key, list: recent});
});
});
});
});
In this snippet we use Promise.all to wait for all usersnaps to load.
Can anyone help me determine why firebase isn't returning the value from a cloud function?
It seems like reading from on database change is simple, but when doing a http on request, firebase functions just hangs and finally times out.
exports.getTotalPrice = functions.https.onRequest((req, res) => {
var data = "";
req.on('data', function(chunk){ data += chunk})
req.on('end', function(){
req.rawBody = data;
req.jsonBody = JSON.parse(data);
res.end(next(req.jsonBody));
})
});
function next(json) {
var total = 0;
for(var i = 0; i < json.products.length; i++) {
//Get product by UPC
console.log(order.products[i].upc);
codeRef.orderByKey()
.equalTo(order.products[i].upc)
.once('value', function(snap) {
console.log(snap.val()); // returns `null`
return (snap.val()[0].msrp);
})
//Multiply the price of the product by the quantity
console.log(order.products[i].quantity);
//add it to the total price
}
}
You are running several asynchronous functions but you aren't informing your function when they are done. The function needs to return a promise to succeed here.
Note also that if the database call fails, it's going to do so silently here. So you should capture errors and report those as well.
Note also that you're storing your distributed JSON data as arrays, which you probably shouldn't.
Note also that you're using .orderByKey().equalTo() when you could just be using .child(upc).
So what you've essentially got here is a mess. You need to spend some time in the guide and samples--you're going to spend a lot of time thrashing like this if you don't.
For a starting point, reduce your code set to the simplest use case runs as expected:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.testDbOp = functions.https.onRequest((req, res) => {
return admin.database()
.ref('/foo')
.once('value')
.then(snap => res.send(JSON.stringify(snap.val()))
.catch(e => console.error(e));
});
Once you have that working, if you want to fetch several values asynchronously, you can do that with Promise.all(), something like this:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.testDbOp = functions.https.onRequest((req, res) => {
const promises = [];
const output = {};
const jsonData = {....};
jsonData.products.forEach(product => {
promises.push( codeRef.child(product.upc).once('value')
.then(snap => output[product.upc] = snap.val()[0].msrp);
});
return Promise.all(promises).then(() => res.send(JSON.stringify(output));
});
It doesn't seem like you are calling the database at all. I only see functions.https.onRequest() which is a http trigger https://firebase.google.com/docs/functions/http-events.
If you wanted to call the database it would have to be something more like functions.database.ref('/').onWrite(event => {}) so that it references the database https://firebase.google.com/docs/functions/database-events.
onWrite refers to any kind of change at that point in the database, not just writing to the database. https://firebase.google.com/docs/functions/database-events#reading_the_previous_value