Using Firebase cloud functions callable - node.js

I am trying to use the admin sdk to check for a user phone number. when i check for a number in the database, it displays the result, but when i input a number not in the database, it throws an internal error.
below is a sample code of the functions index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.checkPhoneNumber = functions.https.onCall(async (data, context) => {
const phoneNumber = await admin.auth().getUserByPhoneNumber(data.phoneNumber);
return phoneNumber;
})
front-end.js
toPress = () => {
const getNumberInText = '+12321123232';
const checkPhone = Firebase.functions.httpsCallable('checkPhoneNumber');
checkPhone({ phoneNumber: getNumberInText }).then((result) => {
console.log(result);
}).catch((error) => {
console.log(error);
});
}
below is the error i am getting when i input a number thats not in the auth
- node_modules\#firebase\functions\dist\index.cjs.js:59:32 in HttpsErrorImpl
- node_modules\#firebase\functions\dist\index.cjs.js:155:30 in _errorForResponse
- ... 14 more stack frames from framework internals

As you will read in the documentation of Callable Cloud Functions:
The client receives an error if the server threw an error or if the resulting promise was rejected.
If the error returned by the function is of type function.https.HttpsError, then the client receives the error code, message, and details from the server error. Otherwise, the error contains the message INTERNAL and the code INTERNAL.
Since you don't specifically manage errors in your Callable Cloud Function, you receive an INTERNAL error.
So, if you want to get more details in your front-end, you need to handle the errors in your Cloud Function, as explained here in the doc.
For example, you could modify it as follows:
exports.checkPhoneNumber = functions.https.onCall(async (data, context) => {
try {
const phoneNumber = await admin.auth().getUserByPhoneNumber(data.phoneNumber);
return phoneNumber;
} catch (error) {
console.log(error.code);
if (error.code === 'auth/invalid-phone-number') {
throw new functions.https.HttpsError('not-found', 'No user found for this phone number');
}
}
})
We throw an error of type not-found (See all the possible Firebase Functions status codes here) in case the error code returned by the getUserByPhoneNumber() method is auth/invalid-phone-number (See all the possible error codes here).
You could refine this error handling code, by treating other errors returned by getUserByPhoneNumber() and sending to the client other specific status codes.

Here is a way I usually use to check if a field (ex. phone) exists in any of the documents in my collection.
For an example based on what you described here is a collection I have created:
The code to make a query for checking if a phone exists looks like this: (I'm using Node.Js)
let collref = db.collection('posts');
var phoneToCheck = '+123456789'
const phone1 = collref.where('phone', '==', phoneToCheck)
let query1 = phone1.get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
})
.catch(err => {
console.log('Error getting documents', err);
});
If a Document exists with that phone number the response is like this:
I no document has that phone number then the response is the following:

Related

TypeError: Cannot read property 'name' of undefined at Firestore.snapshot_

I am using firebase for my cloud functions in my flutter app.
For some reason, my cloud function is not working. This is the console error:
TypeError: Cannot read property 'name' of undefined at Firestore.snapshot_ (/workspace/node_modules/#google-cloud/firestore/build/src/index.js:709:128) at beforeSnapshotConstructor (/workspace/node_modules/firebase-functions/lib/providers/firestore.js:146:30) at changeConstructor (/workspace/node_modules/firebase-functions/lib/providers/firestore.js:150:49) at cloudFunction (/workspace/node_modules/firebase-functions/lib/cloud-functions.js:134:34)
This is my cloud function code:
exports.onSendingNotificationsReceived3 = functions.firestore.document('/orders/{documentId}')
.onCreate(async (snap, context) => {
// Grab the current value of what was written to Firestore
snap.data()['items'].forEach((result,index) =>{
functions.logger.log('Token loop entered');
//Getting list of shopIds from the items list in order
if(listOfFCMToks.includes(result['fcmToken'])==true){
functions.logger.log('Token already exists');
}else{
listOfFCMToks.push(result['fcmToken']);
functions.logger.log('Token of shop is added');
}
});
/* snap.data()['shipperTokens'].forEach((result,index) =>{
functions.logger.log('ShipperToken loop entered');
//Getting list of shopIds from the items list in order
if(listOfFCMToks.includes(result)==true){
functions.logger.log('Token already exists');
}else{
listOfFCMToks.push(result);
functions.logger.log('ShipperToken loop entered'+index);
}
});*/
functions.logger.log('3');/*
const shopRef = admin.firestore().collection("shops");
const querySnapshot = await shopRef.get();
querySnapshot.forEach(async (doc) => {
functions.logger.log('4');
listOfShopIds.forEach(async (item) =>{
if(item==doc.data()['sid']){
functions.logger.log('55');
allChecksids.push(doc.data()['sid']);
const tokenRef = admin.firestore().collection("users").doc(doc.data()['sid']).collection('tokens').doc('fcmToken');
const tokenCurrent = await tokenRef.get();
functions.logger.log('6');
functions.logger.log('88');
listOfUserTokensForFCM.push(doc.data()['token']);
listOfUserTokensForFCM.push('DataIsFound');
}
});
});*/
functions.logger.log('66');
await admin.messaging().sendMulticast({
tokens : listOfFCMToks,
notification: {
title: "A NewOrder is Placed",
body: "Please check your dashboard in Telloo App",
imageUrl: "https://firebasestorTage.googleapis.com/v0/b/learning-firebase-2d636.appspot.com/o/newOrder.jpg?alt=media&token=71ad7cc8-6be6-4069-b097-75cc4f437978g",
},
}) .then((response) => {
console.log(
"Notifications are sent"
);});
return snap.ref.set({listOfFCMToks}, {merge: true});
});
Each order contains different items from multiple shop. I'm accessing fcmToken and trying to save them in a list in the same order with the cloud function use this code:
snap.data()['items'].forEach()
The function was working properly. But now I'm facing the error given above in the console.
To check if this isn't the problem with the syntax of function, I created another function and tested it:
exports.getOrderId = functions.firestore
.document('/orders/{documentId}')
.onCreate((snap, context) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const newValue = snap.data();
// access a particular field as you would any JS property
const name = newValue.orderId;
functions.logger.log($name);
// perform desired operations ...
});
But upon testing this function, I'm getting the same error:
getOrderIdxtsp49f33azi ReferenceError: $name is not defined at exports.getOrderId.functions.firestore.document.onCreate (/workspace/index.js:147:60) at cloudFunction (/workspace/node_modules/firebase-functions/lib/cloud-functions.js:135:23) at Promise.resolve.then (/layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/function_wrappers.js:140:25) at process._tickCallback (internal/process/next_tick.js:68:7)
What solution I found from internet:
Soution: Change node version to 8.
But from now we can't use functions under node 10.
Help!

retrieve Firestore document from onCreate trigger with Cloud Functions

I need to retrieve information from a Firestore Document when another document is created. When I try to do this I get hit with an error about the function not being async. It has been so long since I used javascript I am basically a novice again and have no idea how to fix this.
ok, so I am using Firebase Cloud Functions and the function in question is a Firestore .onCreate() trigger.
When the function is triggered I set a sender variable (which is the document ID from a different collection that I need to retrieve)
then I try to get the document as per the documentation.
The function ends up like this:
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate((snap, context) => {
// when friend request is created
data = doc.data()//get request data
sender = data["sender"]//get request sender from data
const requestRef = db.collection('User').doc(sender);
const doc = await requestRef.get();//get user data of sender
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
});
when I run this in the emulator I get this error:
const doc = await requestRef.get();//get user data of sender
^^^^^
SyntaxError: await is only valid in async functions and the top level bodies of modules
I have absolutely no idea where to go from here.
Can anyone help me with this?
Thanks
The await keyword is valid only in an async function.
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate(async (snap, context) => {
// ^^^^^
})
If you are (or need to) use synchronous function then you would have to use promise chaining.
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate((snap, context) => {
return requestRef.get().then((snapshot) => {
if (snapshot.exists) { ... }
})
})
Apart from that, the order of variables/statements looks incorrect. With the current code (as in original question), you may end up getting an error: "Cannot access 'doc' before initialization" Try refactoring it like this:
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}')
.onCreate(async (snap, context) => {
// accessing data from newly created doc
const newDocData = snap.data()
// const sender = "" // ??
const requestRef = db.collection('User').doc(sender);
const doc = await requestRef.get();//get user data of sender
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
})
Where is the sender coming from? I've just commented it above but if the sender is present in new document then you can access it by: const sender = newDocData.sender
If your using await you have to specify that function is asynchronous. Otherwise it will throw error.
exports.pushFriendRequestNotification = functions.firestore.document('friends/{friendID}').onCreate(async (snap, context) => {
// when friend request is created
data = doc.data()//get request data
sender = data["sender"]//get request sender from data
const requestRef = db.collection('User').doc(sender);
const doc = await requestRef.get();//get user data of sender
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
});
Yet some of your references is unknown to us. Maybe this code is not completed.
The main point is you need to understand when you can access async/await or Promise
All await methods must be inside an async block or be handled in an async manor using .then() promises
in this case, the parent function is on this line .onCreate((snap, context) => {
simply inserting an async at the start of the variables will upgrade the arrow function to an async arrow function
.onCreate(async (snap, context) => {

Unhandled Rejection (FirebaseError): No document to update

I'm still very new to coding so bear with me! I have followed a youtube course to build a note app and get a base to work from, but I'm now getting this error at random times when deleting the notes in firebase, hoping someone might be able to spot what's cooking here!
"Unhandled Rejection (FirebaseError): No document to update: projects/speakle-dc94b/databases/(default)/documents/notes/GdWPrQNxR3Z9TFMWmqOZ"
And it's referencing the node modules like so:
screenshot of the error in chrome
The code I have that interacts with firebase looks like this:
componentDidMount = () => {
firebase
.firestore()
.collection('notes')
.onSnapshot(serverUpdate => {
const notes = serverUpdate.docs.map(_doc => {
const data = _doc.data();
data['id'] = _doc.id;
return data;
});
console.log(notes);
this.setState({ notes: notes });
});
}
selectNote = (note, index) => this.setState({ selectedNoteIndex: index, selectedNote: note });
noteUpdate = (id, noteObj) => {
firebase
.firestore()
.collection('notes')
.doc(id)
.update({
title: noteObj.title,
body: noteObj.body,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
});
}
newNote = async (title) => {
const note = {
title: title,
body: ''
};
const newFromDB = await firebase
.firestore()
.collection('notes')
.add({
title: note.title,
body: note.body,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
});
const newID = newFromDB.id;
await this.setState({ notes: [...this.state.notes, note] });
const newNoteIndex = this.state.notes.indexOf(this.state.notes.filter(_note => _note.id === newID)[0]);
this.setState({ selectedNote: this.state.notes[newNoteIndex], selectedNoteIndex: newNoteIndex });
}
deleteNote = async (note) => {
const noteIndex = this.state.notes.indexOf(note);
await this.setState({ notes: this.state.notes.filter(_note => _note !== note) })
if(this.state.selectedNoteIndex === noteIndex) {
this.setState({ selectedNoteIndex: null, selectedNote: null});
} else {
this.state.notes.lenght > 1 ?
this.selectNote(this.state.notes[this.state.selectedNoteIndex - 1], this.state.selectedNoteIndex - 1) :
this.setState({ selectedNoteIndex: null, selectedNote: null });
}
firebase
.firestore()
.collection('notes')
.doc(note.id)
.delete()
.then(function() {
console.log("Document successfully deleted!");
}).catch(function(error) {
console.error("Error removing document: ", error);
});
}
}
It simply means that there is no document of that name to be uploaded.
you could either use set() or add() to add the document because it is not present.
noteUpdate = (id, noteObj) => {
firebase
.firestore()
.collection('notes')
.doc(id)
.update({
title: noteObj.title,
body: noteObj.body,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
});
}
replace the above code with this
noteUpdate = (id, noteObj) => {
firebase
.firestore()
.collection('notes')
.doc(id)
.add({
title: noteObj.title,
body: noteObj.body,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
});
}
or
noteUpdate = (id, noteObj) => {
firebase
.firestore()
.collection('notes')
.doc(id)
.set({
title: noteObj.title,
body: noteObj.body,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
});
}
I was working with something like this only especially in Cloud Functions and while writing an endpoint for doing some operation I came across the below-quoted error.
I was trying to read a document in a collection and if it existed I was trying to write a new doc into another collection. So it was kind of a nested code.
A piece of my code.
const firstDocRef = db.collection('myFirstCollection').doc('myDocName');
const existDoc = firstDocRef.get()
.then((resDoc)=>{
if(resDoc.exists)
{
db.collection('mySecondCollection').add({
.
.
.
.
.
orderCreatedAt:Firestore.FieldValue.serverTimestamp()
})
.then((new_doc)=>{
return res.status(200);
// return 200 ok what we were trying to achieve has completed.
})
.catch(()=>{
console.log("Log, as write failed somehow");
return res.status(500);
// return a 500 internal server error occurred
});
}
else
{
console.log("My first condition wasn't met, so logged it");
return res.end();
// properly terminate the processing of request
}
});
/*.catch((err)=>{
console.log("Our first doc presence check couldn't complete and hence I arrived here, log it");
res.writeHead(500);
return res.end();
// again give back 500 to client
});*/
UnhandledPromiseRejectionWarning: ReferenceError: Firestore is not defined
UnhandledPromiseRejectionWarning: Unhandled promise rejection.
This error originated either by throwing inside of an async function without a catch block
Now I am also new to Firebase but I came across this and somehow solved it.
So I was not getting the above error if I was putting in a catch block in get() document.
Strange huh!
Removed the catch block by commenting it. Got this error.
Now, this is a haywire error, it says the catch is not there, but we did it on purpose.
So I began searching, came across this question here on stack overflow and saw it's still unanswered. Searched and read the documentation myself.
I would like to tell you that this isn't because of any Firestore Security Rules, or anything else. Because I came across some guesses around these notions too while searching for an answer.
The common thing we all are doing here is that we are trying to achieve the ServerTimeStamp at FireStore
I would like to bring your notice to my imports in my node cloud function code.
const functions = require('firebase-functions');
const express = require('express');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
So you see, I am using the new way of getting the permission to use Firestore because I am trying to establish a cloud function.
Now this is the documentation reference provided by Google: Click here!
The right syntax proposed by above API reference is
Firestore.FieldValue.serverTimestamp()
It is the culprit, it isn't providing me any timestamp and if there is not a catch block unhandled promise error is occuring and no error is being shown while debugging, it just doesn't work.
I did this, Solution part:
Even after those imports in my Node Program, I imported this:
const {Firestore} = require('#google-cloud/firestore');
Now all I did was that I used the statement in the timestamp field as
Firestore.FieldValue.serverTimestamp()
Exactly as mentioned and even used a catch block just in case any other problem occurs while at production. That is using db constant to do all the Database Transactional Stuff and just for serverTimeStamp I have to bring in new import.
And it worked, I guess require('#google-cloud/firestore') statement imported as {FireStore} brings in all the things that are required for the FieldValue thing to use as a reference.
I hope it helps any new person looking for it and saves a lot of time which I wasted searching for a solution.
I have verified it by running the cloud function on firebase emulator.
You could simply do it like this
You
Get it
If it exsits: you update it
If it doesn't exist, you set it.
const docRef = this.db.collection("users_holdings").doc(userId);
docRef.get().subscribe((doc) => {
if (doc.exists) {
docRef.update({
stockHoldings: stockHoldings,
});
} else {
docRef.set({
stockHoldings: stockHoldings,
});
}
});

How perform a query in Cloud Function for Firebase and retrieve a specific attribute

I'm new to Cloud Functions and I'm trying to retrieve the attribute 'name' performing a query using its id, but I don't know how to handle the object returned by the query.
Here is the code I'm using:
// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp()
exports.testNotification = functions.firestore
.document('messages/{email}/{groupChat}/{groupId1}/{groupId2}/{message}')
.onCreate((snap, context) => {
console.log('----------------start function--------------------')
const doc = snap.data()
console.log(doc)
const idFrom = doc.idFrom
const idTo = doc.idTo
console.log(idTo)
const contentMessage = doc.content
/*[...] Some awesome code here not correlated to the question */
admin
.firestore()
.collection('settings')
.doc('table')
.collection('room')
.where('id', '==', idTo)
.get()
.then(querySnapshot =>{
console.log('querySnapshot: ' + querySnapshot)
return null
})
.catch(error => {
console.log('Error sending message:', error)
})
return null
});
I went for the trial & error solution, but the only syntax I tried that returns something rather than 'undefined' or exception, is 'querySnapshot', that logs 'querySnapshot: [object Object]'.
Other useful info: I know that the const 'idTo' is correct and the element searched into the db has both attributes 'id' and 'name'.
What am I doing wrong? Do you have an useful and complete documentation to link to?
Thanks
I just needed to use
querySnapshot.forEach(doc => {
console.log(doc.data().name)
})
So the final code is:
admin
.firestore()
.collection('settings')
.doc('table')
.collection('room')
.where('id', '==', idTo)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log(doc.data().name)
});
return null
})
.catch(error => {
console.log('Error sending message:', error)
})
return null
For more info, check out this documentation.

Firebase Cloud Function no console.log when admin.firestore() is called

I have a webhook to recieve facebook messenger events in a cloud function like so:
export const facebookMessengerHook = functions.https.onRequest(async (req: express.Request, res: express.Response) => {
console.log(req);
console.log(req.method);
console.log(req.body);
if (req.method == "POST") {
const body = req.body;
console.log(body);
// Checks this is an event from a page subscription
if (body.object === 'page') {
res.status(200).send('EVENT_RECEIVED');
// Iterates over each entry - there may be multiple if batched
for (const entry of body.entry) {
// will only ever contain one message, so we get index 0
const webhook_data = entry.messaging[0];
console.log(webhook_data);
try {
// v THAT PART HERE v
const user = await admin.firestore().collection('users')
.where('facebookMessengerId', '==', webhook_data.sender.id)
.get();
// ^ THAT PART HERE ^
console.log(user);
} catch (e) {
console.log('No user');
}
}
}
else {
// Returns a '404 Not Found' if event is not from a page subscription
res.sendStatus(404);
}
}
});
It does not log anything, unless I comment out the marked part in the snippet.
Can someone please explain to me why and how to fix this, because I need to make a call to firestore and I also need the console.log for debug purposes?
Thanks for any help!
The problem most probably comes from the fact that by doing
res.status(200).send('EVENT_RECEIVED');
you actually indicate to the Cloud Function platform that the Cloud Function can be terminated before the rest of the asynchronous work (the set of calls to the get() method) is done. See the following official video form more detail. In other words, the Cloud Function is terminated before the promises returned by the get() method are resolved.
So you should modify your code as follows:
//....
if (body.object === 'page') {
// Iterates over each entry - there may be multiple if batched
for (const entry of body.entry) {
// will only ever contain one message, so we get index 0
const webhook_data = entry.messaging[0];
console.log(webhook_data);
try {
const user = await admin.firestore().collection('users')
.where('facebookMessengerId', '==', webhook_data.sender.id)
.get();
console.log(user);
} catch (e) {
console.log('No user');
//Here throw an error to be catched at an upper level
}
}
res.status(200).send('EVENT_RECEIVED');
}
//....
Note that you may use Promise.all() since you issue a series of fetch to the database. But with your code it is impossible to confirm that, because it does not show the exact use of these fetches.

Resources