Callable cloud functions - handle error in android - node.js

im trying to delete an user from firestore and from auth.
I have this callable cloud function:
export const deleteUser = functions.https.onCall(async (data, context) => {
const userEmail = data.userEmail;
const collection = data.collection;
try {
deleteUserByEmail(userEmail, collection)
return "deleted!"
} catch (error) {
throw new functions.https.HttpsError('invalid-argument', 'there is no user with that email', error);
}
})
async function deleteUserByEmail(userEmail: string, collection: string) {
const auth = admin.auth();
const db = admin.firestore();
const { uid } = await auth.getUserByEmail(userEmail);
await db.collection(collection)
.doc(uid)
.delete();
await auth.deleteUser(uid);
return uid;
}
in android i have this:
fun deleteFromFirebase(){
val data = hashMapOf(
"userEmail" to user.email,
"collection" to "User"
)
functions // Optional region: .getInstance("europe-west1")
.getHttpsCallable("deleteUser")
.call(data)
.addOnCompleteListener() { task ->
if(!task.isSuccessful)
{
Log.d("User", "ERROR")
val e = task.exception
if (e != null) {
Log.d("Admin", e.message.toString())
}
}else{
Log.d("User", "Deleted")
//make something
}
}
}
If the user in auth and the document nin firestore exist, works great.
But i tryed to generate some error.
So I deleted the user from auth and ran the function. The Android log says D/User: User deleted
but in the console from google cloud:
Function execution took 1878 ms, finished with status code: 200
Exception from a finished function: Error: There is no user record corresponding to the provided identifier.
How can I handle the error and get correctly in android? Thanks!

The deleteUserByEmail function is async and returns a Promise. Your return statement runs before the promises is resolved. Try refactoring the code as shown below:
export const deleteUser = functions.https.onCall(async (data, context) => {
const userEmail = data.userEmail;
const collection = data.collection;
try {
// add await, continues after Promise is resolved
await deleteUserByEmail(userEmail, collection)
return "deleted!"
} catch (error) {
console.log(error) // <-- check for any errors
throw new functions.https.HttpsError('invalid-argument', 'there is no user with that email', error);
}
})
async function deleteUserByEmail(userEmail: string, collection: string) {
const auth = admin.auth();
const db = admin.firestore();
const { uid } = await auth.getUserByEmail(userEmail);
return await Promise.all([
db.collection(collection).doc(uid).delete(),
auth.deleteUser(uid)
])
}

Related

Async Firebase Cloud Function trigger - What to return on catch block?

I have written the trigger below and I'm not sure what I should return in case a catch block is called. I know that Firebase docs say that triggers should always return a Promise...
exports.sendPushNotificationForNewMessage = functions.firestore.document("messages/{messageId}").onCreate(async (snap, context) => {
const message = snap.data()
const chatRoomId = message.chatRoomId
const senderId = message.user.id
const senderUsername = message.user.username
try {
const chatRoom = await admin.firestore().collection("chatRooms").doc(chatRoomId).get()
const receiverId = chatRoom.data().userIds.find(userId => userId != senderId)
const receiver = await admin.firestore().collection("users").doc(receiverId).get()
const deviceToken = receiver.data().deviceToken
if (deviceToken) {
const payload = {
notification: {
title: "popster",
body: `New DM from ${senderUsername} 💬`,
badge: "1",
sound: "pop.m4a"
},
data: {
}
}
console.log(payload);
return admin.messaging().sendToDevice(deviceToken, payload)
} else {
return null
}
} catch (error) {
return null
}
})
The async function wraps your response in a Promise so here your return type is Promise<MessagingDevicesResponse | null> and that will terminate the Cloud Function.
I'm not sure what I should return in case a catch block is called.
Background functions do not return any value/error to client so you can just return null;.
Also checkout this Firecast for more information.

Nodejs exports returns undefined on mongoose Insertion

I have created nodejs application by organising as module structure , The problem I am facing is that a mongodb insertion return undefined value from one of my controller, The issue I found is that my async funtion doesn't wait to complete my mongodb operation But I could not find a solution for that, my route and controller code is given below
route.js
const {
createEvent, editEvent
} = require('./controller');
router.post("/event/create", validateEventManage, isRequestValidated, async(req, res) => {
let data = {};
data.body = req.body;
try{
let event = await createEvent(req.body);
console.log(event) // returned undefined
data.event = event;
res.status(200).json(data);
}catch(error){
console.log(error)
res.status(200).json({error:error});
}
});
controller.js
exports.createEvent = async(data) => {
// return "test" // This works correctly
const eventObj = {
name : data.name,
description : data.desc,
type : data.type,
startDate : new Date()
}
const event = await new Event(eventObj);
await event.save((error,event)=>{
if(error) {
return error;
}
if(event){
return event;
}
});
}
You should not await the new Event constructor.
Also, since you are using async - await you can
remove the callback from the save and try ... catch the error to handle it:
exports.createEvent = async (data) => {
// return "test" // This works correctly
const eventObj = {
name: data.name,
description: data.desc,
type: data.type,
startDate: new Date(),
};
try {
const event = new Event(eventObj);
await event.save();
return event;
} catch (error) {
return error;
}
};

Chat message notification in Firebase Cloud Functions. Can't get data from a promise when triggering a Cloud Function

I develop in iOS and this is the first time I'm coding in Typescript. I made my first Cloud Function that triggers when a new chat message is sent in RTDB and notifies the members that receive the message.
When I make the call to Firestore to get tokens of the user devices (userTokens) I get no errors and have the correct path, but the data returned from those promises don't show anything. When I log the "tokenList" it just says ["0"]. I think the error is when I push the promise to a list or resolve them, but I haven't managed to fix it.
The code:
import * as functions from "firebase-functions"
import * as admin from "firebase-admin"
admin.initializeApp()
export const newLastMessageDetected = functions.database
.ref('/ChatMessages/{chatID}/{messageID}/')
.onCreate(async (snap, context) => {
const values = snap.val()
const chatID = context.params.chatID
const messageID = context.params.messageID
const message = values.message
const fromID = values.fromID
const fromName = values.fromName
console.log( `LastMessage changed with chatID: ${chatID} and messageID ${messageID} `)
console.log( `Last message: ${message} by fromID: ${fromID} and by name ${fromName}`)
const payload = {
notification: {
title: fromName,
body: message
}
}
let membersSnapshotRef = admin.database().ref('/Members/' + chatID + '/')
return membersSnapshotRef.once('value')
.then(dataSnapshot => {
const promises = []
console.log('*** GOT SNAPSHOT ***')
dataSnapshot.forEach((element) => {
if (element.key != fromID && element.val() === true) {
const p = admin.firestore().collection('userTokens').doc(`${element.key}`).collection('devices').get()
console.log('*** GOT PROMISE ***')
console.log(`*** The recipientID: ${element.key} ***`)
console.log(`${p}`)
promises.push(p)
}
})
return Promise.all(promises).then(snapshot => {
console.log('*** GOT RETURNED PROMISES ***')
const tokenList = []
const data = snapshot.keys()
for (const token in data) {
console.log(`${token}`)
tokenList.push(token)
}
console.log(`${tokenList}`)
return admin.messaging().sendToDevice(tokenList, payload).then(result => {
console.log("Notification sent!");
return null;
})
})
.catch(error => {
console.log(`${error}`)
})
})
})
When you use Promise.all(), the result of the promise it returns is always going to be an array.
Promise.all(promises).then(snapshot => {
// snapshot is an array of results with one element for each of the promises
})
You need to iterate that array to find the results of all the promises you stored in the promises array. snapshot.keys() does not iterate that array - it is just giving you a list of numbers that are the indexes of those array. Try using snapshot.forEach() instead.
You might want to review some documentation for promise.all.
I actually really messed up because I tried to retrieve the data on the query;
didn't realize that the first loop was on the retrieved queries, so I had to do another on the documents retrieved. The device tokens are each of the documentIDs with the timestamp stored as the data.
The working code:
import * as functions from "firebase-functions"
import * as admin from "firebase-admin"
admin.initializeApp()
export const newLastMessageDetected = functions.database
.ref('/ChatMessages/{chatID}/{messageID}/')
.onCreate(async (snap, context) => {
const values = snap.val()
const chatID = context.params.chatID
const messageID = context.params.messageID
const message = values.message
const fromID = values.fromID
const fromName = values.fromName
console.log( `LastMessage changed with chatID: ${chatID} and messageID ${messageID} `)
console.log( `Last message: ${message} by fromID: ${fromID} and by name ${fromName}`)
const payload = {
notification: {
title: fromName,
body: message
}
}
let membersSnapshotRef = admin.database().ref('/Members/' + chatID + '/')
return membersSnapshotRef.once('value')
.then(dataSnapshot => {
const promises = []
// const docIDS = []
console.log('*** GOT SNAPSHOT ***')
dataSnapshot.forEach((element) => {
if (element.key != fromID && element.val() === true) {
const doc = admin.firestore().collection('userTokens').doc(`${element.key}`).collection('devices')
const p = doc.get()
// const docID = doc.id
console.log('*** GOT PROMISE ***')
console.log(`*** The recipientID: ${element.key} ***`)
// console.log(`*** The docID: ${docID} ***`)
promises.push(p)
// docIDS.push(docID)
}
})
return Promise.all(promises)
})
.then(async querySnapshot => {
console.log('*** GOT RETURNED PROMISES ***')
const tokenList = []
querySnapshot.forEach(snap => { // first here
console.log(`${snap.id} *** `)
console.log(`${snap} *** `)
snap.forEach(doc => { // then here
console.log(`${doc.id}`)
tokenList.push(doc.id)
})
})
await admin.messaging().sendToDevice(tokenList, payload)
console.log("Notification sent!")
return null
})
.catch(error => {
console.log(`${error}`)
})
})

Ignoring exception from a finished function

I keep getting this error when this function executes.
Ignoring exception from a finished function
What I'm I missing?
exports = module.exports = functions.database.ref('/cards/{userId}/{id}')
.onCreate((snap, context) => {
const token = snap.val().token;
const userId = context.params.userId;
const stripeRef = admin.database().ref('/stripe').child(userId);
return stripeRef.once('value').then(function(snapshot) {
let accountId = snapshot.val().accountId;
return stripe.accounts.createExternalAccount(
accountId,
{ external_account: token },
function(err, card) {
snap.ref.child('cardId').set(card.id);
});
});
});
use try and catch to log the errors manually, like
try {
//code goes here
}
catch(error) {
console.log(error)
}

Use Dynasty DynamoDB Query in Alexa Response

I am using Dynasty in my Nodejs Alexa Skill Service, run on AWS Lambda, to query DynamoDB. Due to the structure of the API, I am unable to use a query's result in my Alexa response. In the code below, the callback passed to 'then' is run after the handler returns, so 'name' is never assigned. How can I use information obtained in the query callback in my response?
const dynasty = require('dynasty')(credentials);
const myIntentHandler = {
canHandle(input) {
return input.requestEnvelope.request.intent.name === 'MyIntent';
},
handle(input) {
const userId = input.requestEnvelope.session.user.userId;
const users = dynasty.table('user');
var name;
users.find(userId).then(function(user) {
if(user) {
name = user.name;
} else {
...
}
});
return input.responseBuilder.speak('Hello ' + name).getResponse();
}
};
The Alexa SDK for NodeJS v2 supports promises in handlers.
So, from you handler you return a promise, chained off of the Dynasty query promise.
const dynasty = require('dynasty')(credentials);
const myIntentHandler = {
canHandle(input) {
return input.requestEnvelope.request.intent.name === 'MyIntent';
},
handle(input) {
const userId = input.requestEnvelope.session.user.userId;
const users = dynasty.table('user');
var name;
return new Promise((resolve, reject) => {
users.find(userId).then(function(user) {
if(user) {
name = user.name;
let response
= input.responseBuilder
.speak('Hello ' + name)
.getResponse();
resolve(response);
} else {
...
// handle this case
// and resolve(..) or reject(..)
}
});
});
}
};

Resources