Inconsistent results from Firestore Cloud Functions - javascript

I have a Cloud Function setup on Firebase that involved checking different parts of the Firestore Database and then sending a message via Cloud Messaging
Below is the JavaScript for the function in question:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().Firebase);
var db = admin.firestore();
exports.newMemberNotification = functions.firestore
.document('Teams/{teamId}/Waitlist/{userId}').onDelete((snap, context) => {
// get the user we want to send the message to
const newValue = snap.data();
const teamidno = context.params.teamId;
const useridno = newValue.userID;
//start retrieving Waitlist user's messaging token to send them a message
var tokenRef = db.collection('Users').doc(useridno);
tokenRef.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
} else {
const data = doc.data();
//get the messaging token
var token = data.messaging_token;
console.log("token: ", token);
//reference for the members collection
var memberRef = db.collection('Teams/'+teamidno+' /Members').doc(useridno);
memberRef.get()
.then(doc => {
if (!doc.exists){
console.log('user was not added to team. Informing them');
const negPayload = {
data: {
data_type:"team_rejection",
title:"Request denied",
message: "Your request to join the team has been denied",
}
};
return admin.messaging().sendToDevice(token, negPayload)
.then(function(response){
console.log("Successfully sent rejection message:", response);
return 0;
})
.catch(function(error){
console.log("Error sending rejection message: ", error);
});
} else {
console.log('user was added to the team. Informing them')
const payload = {
data: {
data_type: "team_accept",
title: "Request approved",
message: "You have been added to the team",
}
};
return admin.messaging().sendToDevice(token, payload)
.then(function(response){
console.log("Successfully sent accept message:", response);
return 0;
})
.catch(function(error){
console.log("Error sending accept message: ", error);
});
}
})
.catch(err => {
console.log('Error getting member', err);
});
}
return 0;
})
.catch(err => {
console.log('Error getting token', err);
});
return 0;
});
The issues I have with this are:
The code runs and only sometimes actually checks for the token or sends a message.
the logs show this error when the function runs: "Function returned undefined, expected Promise or value " but as per another Stack Oveflow posts, I have added return 0; everywhere a .then ends.
I am VERY new to node.js, javascript and Cloud Functions so I am unsure what is going wrong or if this is an issue on Firebase's end. Any help you can give will be greatly appreciated

As Doug said, you have to return a promise at each "step" and chain the steps:
the following code should work:
exports.newMemberNotification = functions.firestore
.document('Teams/{teamId}/Waitlist/{userId}').onDelete((snap, context) => {
// get the user we want to send the message to
const newValue = snap.data();
const teamidno = context.params.teamId;
const useridno = newValue.userID;
//start retrieving Waitlist user's messaging token to send them a message
var tokenRef = db.collection('Users').doc(useridno);
tokenRef.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
throw 'No such document!';
} else {
const data = doc.data();
//get the messaging token
var token = data.messaging_token;
console.log("token: ", token);
//reference for the members collection
var memberRef = db.collection('Teams/' + teamidno + '/Members').doc(useridno);
return memberRef.get()
}
})
.then(doc => {
let payload;
if (!doc.exists) {
console.log('user was not added to team. Informing them');
payload = {
data: {
data_type: "team_rejection",
title: "Request denied",
message: "Your request to join the team has been denied",
}
};
} else {
console.log('user was added to the team. Informing them')
payload = {
data: {
data_type: "team_accept",
title: "Request approved",
message: "You have been added to the team",
}
};
}
return admin.messaging().sendToDevice(token, payload);
})
.catch(err => {
console.log(err);
});
});

Related

Error "Function returned undefined, expected Promise or value" even after return in all places

I am new to Node.js and I am struggling with Promises even after reading the tutorials provided by other stackflow users. I have already spent a whole evening on this and I am looking for help. I get the following error " Function returned undefined, expected Promise or value". My code is below. What am I doing wrong? I also have a suspicion that I have to use await/async because it looks like my code is running through without waiting for the first get to complete.
const admin = require('firebase-admin');
const functions = require('firebase-functions');
var db = admin.firestore();
exports.declinedRequest = functions.firestore
.document('requests/{requestId}')
.onUpdate((change, context) => {
const newValue = change.after.data();
const status = newValue.status;
const request = context.params.requestId;
var registrationToken;
var message;
if(status=="created") {
console.log('Checkpoint1 ',context.params.requestId);
newValue.friends.forEach(doc => {
console.log('Checkpoint 2: ', doc);
var usersRef = db.collection('users');
var query = usersRef.where('mobile', '==', doc).get()
.then(snapshotFriend => {
if (snapshotFriend.empty) {
console.log('Checkpoint3.');
return;
}
snapshotFriend.forEach(mobile => {
registrationToken = mobile.data().fcmToken;
console.log('FCM token =>', registrationToken);
if (!registrationToken) {
console.log('No fcmToken available');
return;
}
message = {
notification: {
body: "Request still available from " + newValue.requesterName,
sound: "default",
badge: 1
},
data: {
requestId: `${request}`
}
};
console.log('FCM token message created');
})
})
})
} else {
return;
}
return admin.messaging().sendToDevice(registrationToken, message)
.then(function (response) {
console.log("Successfully sent message:", response)
})
.catch(function (error) {
console.log("Error sending message:", error);
})
})
Try the code below hope this will work.
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const Promise = require('bluebird');
const _ = require('lodash');
let db = admin.firestore();
exports.declinedRequest = functions.firestore
.document('requests/{requestId}')
.onUpdate((change, context) => {
const newValue = change.after.data();
const status = newValue.status;
const request = context.params.requestId;
if (status == "created") {
console.log('Checkpoint1 ', context.params.requestId);
allPromises = [];
newValue.friends.forEach(doc => {
console.log('Checkpoint 2: ', doc);
const usersRef = db.collection('users');
// query for each document return promise.
allPromises.push(queryForEachDocument(doc,request,usersRef));
});
return Promise.all(allPromises);
} else {
return Promise.reject / resolve('Whatever you want.');
}
})
function queryForEachDocument(doc,request,usersRef) {
let promiseInvoices = []
let registrationToken;
let message;
return usersRef.where('mobile', '==', doc).get().then((snapshotFriend) => {
if (_.isEmpty(snapshotFriend)) {
console.log('Checkpoint3.');
return Promise.reject(new Error('Your error'));
}
snapshotFriend.forEach(mobile => {
registrationToken = mobile.data().fcmToken;
console.log('FCM token =>', registrationToken);
if (!registrationToken) {
console.log('No fcmToken available for', newValue.requesterName);
// Do anything you want to change here.
return Promise.reject(new Error('No fcmToken available for',newValue.requesterName));
}
message = {
notification: {
body: "Request still available from " + newValue.requesterName,
sound: "default",
badge: 1
},
data: {
requestId: request
}
};
console.log('FCM token message created');
// send invoice for each registrationToken
promiseInvoices.push(sendInvoice(registrationToken, message))
});
}).then(() => {
return Promise.all(promiseInvoices);
})
}
function sendInvoice(registrationToken, message) {
return admin.messaging().sendToDevice(registrationToken, message)
.then(function (response) {
console.log("Successfully sent message:", response)
})
.catch(function (error) {
console.log("Error sending message:", error);
})
}

Javascript if conditional variable problem

I have two receiver type. 0 for guests 1 for users. I'm trying to push notification from firebase cloud functions. I can send notification user to guest succesfully. But can not send notification guest to user. Is there any problem with my if condition?
var usersRef = null;
if (receiverType === 0) {
usersRef = admin.firestore().collection('Guests').doc(receiverId);
} else {
usersRef = admin.firestore().collection('Users').doc(receiverId);
}
I want to change var usersRef variable based on receiverType. I managed to change it to Guest path but on the else part its path must change again to Users. But not changing. There is a problem in my if else statement.
var getDoc = usersRef.get()
.then(doc => {
if (!doc.exists) {
return null;
} else {
const token = doc.data().token;
const payload = {
notification: {
title: "New Message",
body: chatMessage,
}
};
admin.messaging().sendToDevice(token, payload)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
return null;
})
.catch((error) => {
console.log('Error sending message:', error);
return null;
});
}
return null;
})
.catch(err => {
console.log('Error getting document', err);
return null;
});
return null;
});

High Delay in FCM when sent through Firebase Functions

I'm trying to send FCM using Firebase Functions, This is the code I'm using
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
admin.firestore().settings({
timestampsInSnapshots: true
});
var token = 'token_value';
var sender = 'sender_value';
var reciever = 'reciever_value';
var message = 'message_value';
var payload ='payload_value';
var db = admin.firestore();
exports.sendFollowerNotification =
functions.database.ref('/m/{messageid}')
.onCreate((snapshot, context) => {
console.log('v21');
message = context.params.messageid;
console.log('Message Id:', message);
reciever = snapshot.val().r;
console.log('Message reciever: ', reciever);
sender = snapshot.val().s;
console.log('Message sender: ', sender);
payload = {
data: {
id: `${message}`,
sender: `${sender}`
}
};
console.log('Payload Created');
var tokenRef = db.collection(reciever).doc('t');
console.log('Fetching Token');
tokenRef.get()
.then(doc => {
console.log('Fetching Token started');
if (!doc.exists) {
console.log('Token doesnt exist ');
} else {
token = doc.data().v;
console.log('Token data:', token);
}
console.log('End Then');
return token;
})
.catch(err => {
console.log('Error getting token', err);
}).then((token) => {
console.log('Sending FCM now');
admin.messaging().sendToDevice(token,payload);
return console.log('Successfully sent message:');
})
.catch((error) => {
console.log('Error sending message:', error);
});
})
The problem is that FCM is received with huge delay(about 40s), however fcm sent from Firebase Console is received almost immediately (about 2-3s).
Since I am an android developer and have no experience of Node.js, I believe that something is wrong with JS code. Help me by telling whats wrong or possible workaround.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
admin.firestore().settings({
timestampsInSnapshots: true
});
var db = admin.firestore();
function debugLogger(msg) {
// comment out to disable logging
console.log(msg);
}
function errLogger(err) {
console.log(err);
}
exports.sendFollowerNotification =
functions.database.ref('/m/{messageid}')
.onCreate((snapshot, context) => {
debugLogger('v23_stackoverflow');
const message = context.params.messageid;
debugLogger(`Message Id: ${message}`);
const reciever = snapshot.val().r;
debugLogger(`Message reciever: ${reciever}`);
const sender = snapshot.val().s;
debugLogger(`Message sender: ${sender}`);
const payload = {
data: {
id: `${message}`,
sender: `${sender}`
}
};
debugLogger('Payload Created');
let tokenRef = db.collection(reciever).doc('t');
debugLogger('Fetching Token');
return tokenRef.get()
.then(doc => {
debugLogger('Fetching Token started');
if (!doc.exists)
throw new Error("Token doesnt exist");
let token = doc.data().v;
debugLogger(`Token data: ${token}`);
return token;
})
.then(token => {
debugLogger('Sending FCM now');
return admin.messaging().sendToDevice(token, payload);
})
.then(() => {
debugLogger('Successfully sent message!');
return null;
})
.catch(err => {
errLogger(err);
})
})

Firebase function to fetch data from Firebase DB to make Push notification

I have chat app with firebase database and Firebase cloud messaging. I can send firebase notification via console but in real scenario it should be automatic. To make automatic notification,My friend wrote Index.js (Added in cloud functions) file for me but its not sending notifications.
As per our logic function should trigger whenever there is any new entries (in any node or in any room) and fetch these values by firebase function and make post request to FCM server to make notification to receiver device (get value of receiver device from token_To).
Message
Message_From
Time
Type
token_To
Index.js
var functions = require('firebase-functions');
var admin = require('firebase-admin');
var serviceAccount = require('./demofcm-78aad-firebase-adminsdk-4v1ot-2764e7b580.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://demofcm-78aad.firebaseio.com/"
})
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
exports.setUserNode = functions.auth.user().onCreate(event => {
// ...
});
exports.notifyMsg = functions.database.ref('/{chatroom}/{mid}/')
.onWrite(event => {
if (!event.data.val()) {
return console.log('Message Deleted');
}
const getDeviceTokensPromise = admin.database().ref('/{chatroom}/{mid}/token_to').once('value');
return Promise.all([getDeviceTokensPromise]).then(results => {
const tokensSnapshot = results[0];
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
const payload = {
notification: {
title: 'You have a new Message!',
body: event.data.val().Message
}
};
const tokens = Object.keys(tokensSnapshot.val());
return admin.messaging().sendToDevice(tokens, payload).then(response => {
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
});
});
Firebase function Log
How can i fetch above mentioned values of any newly added node in same room(9810012321-9810012347) or any other room(9810012321-9810012325) from database and send it to FCM to make notification
Thanks in Advance.
What i did is created a Message node and I believe doing this by users key. ie, having the receiver(toId) and sender (fromId) key to send the notification.
Hope it helps.
exports.sendMessageNotification = functions.database.ref('/messages/{pushId}')
.onWrite(event => {
let message = event.data.current.val();
console.log('Fetched message', event.data.current.val());
let senderUid = message.fromId;
let receiverUid = message.toId;
let promises = [];
console.log('message fromId', receiverUid);
console.log('catch me', admin.database().ref(`/users/${receiverUid}`).once('value'));
if (senderUid == receiverUid) {
//if sender is receiver, don't send notification
//promises.push(event.data.current.ref.remove());
return Promise.all(promises);
}
let messageStats = message.messageStatus;
console.log('message Status', messageStats);
if (messageStats == "read") {
return Promise.all(promises);
}
let getInstanceIdPromise = admin.database().ref(`/users/${receiverUid}/pushToken`).once('value');
let getSenderUidPromise = admin.auth().getUser(senderUid);
return Promise.all([getInstanceIdPromise, getSenderUidPromise]).then(results => {
let instanceId = results[0].val();
let sender = results[1];
console.log('notifying ' + receiverUid + ' about ' + message.text + ' from ' + senderUid);
console.log('Sender ', sender);
var badgeCount = 1;
let payload = {
notification: {
uid: sender.uid,
title: 'New message from' + ' ' + sender.displayName,
body: message.text,
sound: 'default',
badge: badgeCount.toString()
},
'data': {
'notificationType': "messaging",
'uid': sender.uid
}
};
badgeCount++;
admin.messaging().sendToDevice(instanceId, payload)
.then(function (response) {
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
});
});
const getDeviceTokensPromise = event.data.child('token_To');
should be there instated of getting data from database reference.
or
with fixed path without wildcard like below
const getDeviceTokensPromise = admin.database().ref('/${chatroom}/${mid}/token_to').once('value');
where chatroom and mid is variable which contain value
Second thing:
if (!tokensSnapshot.exists()) {
should in replace of
if (!tokensSnapshot.hasChildren()) {
third thing:
I am not sure about push notification tokenId but
is it required to do?
const tokens = Object.keys(tokensSnapshot.val());
may be we can use directly like below to send push notification
const tokens = tokensSnapshot.val();
You could store all device tokens in a node called tokens like in my example. Tokens could be an array if you would like one user to be able to get notifications on multiple devices. Anyway, store them by their UID.
This works for both Andriod and iOS.
Here is my code:
function loadUsers() {
let dbRef = admin.database().ref('/tokens/' + recieveId);
console.log(recieveId)
let defer = new Promise((resolve, reject) => {
dbRef.once('value', (snap) => {
let data = snap.val();
console.log("token: " + data.token)
//userToken = data.token
resolve(data.token);
}, (err) => {
reject(err);
});
});
return defer;
}
Next we create the notification. I created a lastMessage node to capture just the last message sent in the chat. It is just updated every time a new message is sent in a chat between two users. Makes it easy to get the value. Also makes it easy to show the message on the Conversations screen where there is a list of users who are in a conversation with the current user.
exports.newMessagePush =
functions.database.ref('/lastMessages/{rcId}/{sendId}').onWrite(event => {
if (!event.data.exists()) {
console.log("deleted message")
return;
}
recieveId = event.params.rcId
//let path = event.data.adminRef.toString();
// let recieveId = path.slice(53, 81);
return loadUsers().then(user => {
console.log("Event " + event.data.child("text").val());
let payload = {
notification: {
title: event.data.child("name").val(),
body: event.data.child("text").val(),
sound: 'default',
priority: "10",
}
};
return admin.messaging().sendToDevice(user , payload);
});
});
To implement this logic on your current data structure, just change this line:
let dbRef = admin.database().ref('/tokens/' + recieveId);
and this line:
exports.newMessagePush =
functions.database.ref('/lastMessages/{rcId}/{sendId}').onWrite(event
=> {
to your token location:
let dbRef =
admin.database().ref('/${chatroom}/${mid}/token_to');
and your conversation location:
exports.notifyMsg = functions.database.ref('/{chatroom}/{mid}/')
.onWrite(event => {
Then just change the notification payload be the message you want to display and throw in your error handling on the end of the sendToDevice function, as you did in your code.
Hopefully you figured all this out already but if not maybe this will help you or others trying to use Cloud Functions for notifications.
let payload = {
notification: {
uid: sender.uid,
title: 'New message from' + ' ' + sender.displayName,
body: message.text,
sound: 'default',
badge: badgeCount.toString()
},
'data': {
'notificationType': "messaging",
'uid': sender.uid
}
};
There are two types of FCMs.
1) Data
2) Notification
For detailed overview : FCM Reference
You have to fix your payload for both FCMS. And for Data FCM you have to extract Data in your FCM Service (Client) and generate a push notification according to your need.

I'm trying to give notification by Firebase cloud function, but my code doesn't give any notification

I have done authentication trigger, it's working fine. If someone delete their account I need to send to notification "this user deleted his account (email)" like that. Here is my code
const functions = require('firebase-functions')
//initialize the app
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
const ref = admin.database().ref()
//create user account function
exports.createUserAccount = functions.auth.user().onCreate(event => {
const uid = event.data.uid
const email = event.data.email
const newUserRef = ref.child(`/UserNotify/${uid}`)
return newUserRef.set({
email: email
})
})
//delete user account function
exports.cleanupUserData = functions.auth.user().onDelete(event => {
const uid = event.data.uid
const userRef = ref.child(`/UserNotify/${uid}`)
return userRef.update({isDeleted: true})
})
function sendNotification() {
console.log("Successfully sent");
var payload = {
notification: {
title: "User get deleted",
body: "sample#gmail.com"
}
};
admin.messaging().sendToDeveice(payload)
.then(function (response) {
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
})
}
You may have a typing error
admin.messaging().sendToDevice() and not sendToDeveice
check: https://firebase.google.com/docs/cloud-messaging/admin/send-messages

Categories

Resources