Firebase cloud functions errors - javascript

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.

Related

How do you make a promise with firebase .on() function?

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.

Google cloud function onCreate not writing to the Database

I have created a google function for firebase that when A new conversation is added the function attaches it to the Users table under a new collection for each user in the conversation but when the function gets triggered nothing happens in the database, So far I have console logged the values tp make sure they ware being set right and they ware I have also tried looking at the google function logs and there are no errors according to the logs the script ran with no errors
Here is the code for the function
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
export const onConversationCreated = functions.firestore.document("Conversations/{conversationID}").onCreate((snapshot, context) => {
let data = snapshot.data();
let conversationID = context.params.conversationID;
if(data){
let members = data.members;
for(let index = 0; index < members.length; index++){
let currentUserID = members[index];
let remainingUserIDs = members.filter((u: string) => u !== currentUserID)
remainingUserIDs.forEach((m: string) => {
return admin.firestore().collection("Users").doc(m).get().then((_doc) => {
let userData = _doc.data();
if(userData){
return admin.firestore().collection("Users").doc(currentUserID).collection("Conversations").doc(m).create({
"conversationID": conversationID,
"image": userData.image,
"unseenCount": 1,
});
}
return null;
}).catch(() => {return null})
});
}
}
return null;
});
Can someone tell me if there is something wrong with my code or do I have to give functions permission to write to the cloud firestore database?
You are not dealing with promises correctly. Your function needs to return a promise that resolves with all of the asynchronous work is complete. Right now, it's just returning null, and not waiting for any work to complete.
All Firestore operations are asynchronous and return a promise. You will need to use these promises to build a single promise to return from the function. That means each time you query and write a document, that's generating another promise to handle.
Also, you should know that there is no method create() that you're trying to use to add a document. Perhaps you meant to use set() instead. The code will crash if it tries to execute create().

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

Async Function in AWS Lambda Alexa Intent with Yummly API

I'm trying to retrieve data from the Yummly API through Amazon Alexa using ws-yummly in Node.js deployed on AWS Lambda. I'm fairly new to all aspects of this, but new to javascript in particular (Python is my 'native' language).
Here is what I have for my recommendation intent:
"RecommendationIntent": function () {
// delegate to Alexa to collect all the required slots
let filledSlots = delegateSlotCollection.call(this);
if (!filledSlots) {
return;
}
console.log("filled slots: " + JSON.stringify(filledSlots));
// at this point, we know that all required slots are filled.
let slotValues = getSlotValues(filledSlots);
console.log(JSON.stringify(slotValues));
const mainIngredientQuery = slotValues.mainIngredient.resolved;
async function main (queryWord) {
const resp = await Yummly.query(queryWord)
.maxTotalTimeInSeconds(1400)
.maxResults(20)
.minRating(3)
.get();
const names = resp.matches.map(recipe => recipe.recipeName);
const speechOutput = String(names[0]);
this.response.speak(speechOutput);
this.emit(":responseReady");
}
main(mainIngredientQuery).catch(error => console.error(error))
},
This is in the index.js file deployed on AWS lambda. I have isolated the problem to the async function. I have tested the function locally and it returns to console.log a list of recipe names. I want to have Alexa say these names. Or at least one name.
If I put the speechOutput assignment inside (as it is now), then I get an error that the 'Speechlet Response is set to null'.
If I tell it to 'return names' and set the external assignment to names or names[0] I get object promise or undefined (respectively).
Everything else in my program works fine and test these two bits apart they work, but putting them together doesn't work. I think that this is a syntax or placement error, but I don't understand the structure or formatting well enough yet (still learning) to understand what to try next.
How about using Promise.then like this:
async function main (queryWord) {
const resp = await Yummly.query(queryWord)
.maxTotalTimeInSeconds(1400)
.maxResults(20)
.minRating(3)
.get();
const names = resp.matches.map(recipe => recipe.recipeName);
return String(names[0]);
}
main(mainIngredientQuery)
.catch( error => console.error(error) )
.then( data => {
this.response.speak( data );
this.emit(":responseReady");
});
I'm updating this in case anyone else has the same problem.
If you notice, in my original code, I had an async function inside the intent. That didnt work because the intent itself was/is a function. By making the intent function an async function instead, I was able to solve the problem.
The following is working code for an async/await Alexa Intent.
The full index.js is available on my github if you want to take a look, but that will be a more advanced final version. The code below immediately follows up on the original question.
"RecommendationIntent": async function main () {
// delegate to Alexa to collect all the required slots
let filledSlots = delegateSlotCollection.call(this);
if (!filledSlots) {
return;
}
console.log("filled slots: " + JSON.stringify(filledSlots));
// at this point, we know that all required slots are filled.
let slotValues = getSlotValues(filledSlots);
console.log(JSON.stringify(slotValues));
const mainIngredientQuery = slotValues.mainIngredient.resolved;
const resp = await Yummly.query('chicken')
.maxTotalTimeInSeconds(1400)
.maxResults(20)
.minRating(3)
.get();
const names = resp.matches.map(recipe => recipe.recipeName);
console.log(names);
const speechOutput = names[0];
this.response.speak(speechOutput);
this.emit(":responseReady");
},

Reading from firebase inside a cloud function

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

Categories

Resources