Reading from firebase inside a cloud function - javascript

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

Related

Firebase cloud functions errors

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.

Set all instances of a value to null in Realtime Database: Firebase Cloud Functions

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());
});
});

Firebase Functions: Why do they sometimes fail? Why do they often complete without error but don't fulfill all tasks?

This perplexes me. I'm six months into a firebase project and have been using Javascript for firebase-functions. I've learned a lot along the way by adding transactions, promises, batch writes and neat tricks. However, it seems like complete luck for a function to execute correctly. More often than not, the functions do execute correctly, but there are strange periods when bursts of consecutive function calls where functions half complete with no errors in the logs.
For example. I have a function for when a new user joins my app. It does a little bit of server data construction and also notifies the two admins that a new user has joined. Last night I did a test run with two new users and got no notification, but their user profiles constructed correctly on the server database. I checked the function logs and there were no errors.
Am I not handling Promises in the correct way? If a firebase function hangs, does it mess up the next few function calls?
exports.onNewUser = functions.firestore
.document('/users/{userId}')
.onCreate(async (snapshot, context) => {
user = snapshot.data().username;
//Notification payload
const payload = {
notification: {
title: `New user!`,
body: `${user} has joined [AppName]`
}
};
var promises = [];
//Check if usename unique
var passed = true;
promises.push(db.runTransaction(async t => {
const docRef = db.collection('users').doc('index');
const doc = await t.get(docRef);
var newIndex = doc.data().usernames;
if (newIndex[user.toUpperCase()] == true) {
t.delete(snapshot.ref);
passed = false;
return null;
} else {
newIndex[user.toUpperCase()] = true;
t.set(docRef, { 'usernames': newIndex });
}
}));
if (!passed) return Promise.all(promises);
//add new user to Algolia database
const algoliasearch = require('algoliasearch');
const algoliaClient = algoliasearch(functions.config().algolia.appid, functions.config().algolia.apikey);
const collectionIndex = algoliaClient.initIndex(collectionIndexName);
await saveDocumentInAlgolia(snapshot, collectionIndex);
//Notify Admins
db.collection('notificationTokens')
.doc(admin1)
.get().then((doc) => {
if (doc.exists && doc.data().notificationToken != null)
promises.push(pushNotification(doc.data().notificationToken, payload));
});
db.collection('notificationTokens')
.doc(admin2)
.get().then((doc) => {
if (doc.exists && doc.data().notificationToken != null)
promises.push(pushNotification(doc.data().notificationToken, payload));
});
return Promise.all(promises);
});
Just change
return Promise.all(promises);
to
return await Promise.all(promises);
You have to wait till the promises resolve before you return the function, as that would stop the instance of the cloud function.

Cloud function -> comparing timestamps

I'm trying to make a cloud function where the function deletes every item in the "links" collection where the "eindTijd" (endtime) timestamp is older than now.
The function executes on every DB write, and gives me no errors at all, but just doesn't do what I intend to do. It's driving me nuts!
I suspect the error is in the oldItemsQuery, but cannot find out what goes wrong. Any help would be really appreciated!
The "eindTijd" field is generated with a Date function and is recognized to be a valid timestamp in Firestore.
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const firestore = admin.firestore();
const settings = {timestampsInSnapshots: true};
firestore.settings(settings);
exports.verwijderOudeNodes = functions.firestore
.document('/links/{pushId}')
.onWrite(() => {
const now = Date.now();
let oldItemsQuery = firestore.collection('links').where('eindTijd', '<=', now);
return oldItemsQuery.get().then((snapshot) => {
// create a map with all children that need to be removed
let batch = firestore.batch();
snapshot.forEach(child => {
batch.delete(child.ref);
});
// execute all updates in one go and return the result to end the function
return batch.commit();
});
});
I'm not sure if the way you're passing now is correct. Based on the Firebase documentation on writing its various data types, I'd expect:
const now = admin.firestore.Timestamp.fromDate(new Date())

firebase functions endpoint cold starts

Firebase REST endpoint. I am getting a lot of NULL returns especially at startup. Looking through other issues, I think it is a Coldstart. I believe the issue is that I am using callbacks which is returning before firebase has a chance to return a dataset. I read a comment about callabcks from #puf - frank-van-puffelen
sugesting a Cold Start. So I'm trying to re-write as a promise. This code works, usually, but still get the cold start NULL data sets. How would I do this as a promise?
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
//=================================================================================================
// KeysFromAccountGet01
//=================================================================================================
// This function is not working correctly because it often returns a NULL set. probably
// because I am using callbacks instead of promises, and the callback returns before firebase
// can return a query. Usually it works.
// But I am fairly sure that I should be using PROMICES so as to wait for the data to arrive.
// that said, I can not figure out how to do a promise. Everythign I have tried returns nothing.
// some sugestions on how to do promises for this would be appreciated.
//curl 'https://us-central1-test.cloudfunctions.net/KeysFromAccountGet01?account=dporras8'
//curl 'https://us-central1-test.cloudfunctions.net/KeysFromAccountGet01?account='
//firebase deploy --only functions:KeysFromAccountGet01
exports.KeysFromAccountGet01 = functions.https.onRequest((req, res) =>{
var arr =[];
arr.push("1====+++starting");
arr.push("acount = "+ req.query.account);
admin.database().ref('/newacctok/'+req.query.account+'/tok3/').on('value', function(snapshot){
snapshot.forEach(function(miniSnapShot){
var tt = miniSnapShot.val();
var json = ({
"key":miniSnapShot.key,
"account":req.query.account,
"uuid":tt.uuid,
"ts2":tt.ts2,
"token":tt.token
});
arr.push(json);
})
.then(res.status(200).send(arr));
});
//===================================
I'm not sure these changes will help with your Null returns. Note that I changed on(), which leaves the listener attached, to once(). Also, I've seen answers from Frank van Puffelen cautioning against performing asynchronous processing in HTTPS request functions. I'll try to find his answers/comments and add them.
exports.KeysFromAccountGet01 = functions.https.onRequest((req, res) => {
var arr =[];
arr.push("1====+++starting");
arr.push("acount = "+ req.query.account);
// note change from on() to once()
admin.database().ref('/newacctok/'+req.query.account+'/tok3/').once('value')
.then(snapshot => {
snapshot.forEach(miniSnapShot => {
var tt = miniSnapShot.val();
var json = ({
"key":miniSnapShot.key,
"account":req.query.account,
"uuid":tt.uuid,
"ts2":tt.ts2,
"token":tt.token
});
arr.push(json);
});
res.status(200).send(arr)
});
});

Categories

Resources