How to program Firebase Cloud Functions to iterate through array of references? - javascript

I'm pretty new to Cloud Functions on Firebase and I'm struggling to program some code to iterate through an array of document references that have been downloaded from the Firestore.
The array is stored in my Firestore and contains references to each admin user in my users collection. Each of these users has a field in their document with their messaging token, which I need to send the message. I've manage to get the code to send a notification to a token that I define as a constant in the code however haven't had any luck sending to the tokens stored in the database.
Here is my code so far;
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// // 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.notifyNewReport = functions.firestore
.document('admin/reportsToReview')
.onUpdate((change, context) => {
console.log('Change to doc function registered');
// Get an object representing the document
const newValueReports = change.after.data().reports;
// ...or the previous value before this update
const previousValueReports = change.before.data().reports;
if (newValueReports.length > previousValueReports.length) {
console.log('Report added to review list');
var adminsArray = ""
admin.firestore()
.collection('admin')
.doc('admins')
.get()
.then(doc => {
adminsArray = doc.data().admins
return console.log('Found admin UID: ' + adminsArray);
})
.catch(error => {
console.error(error);
res.error(500);
});
//Code to get send notification to each device
console.log("Construct the notification message.");
var message = {
notification: {
body: 'There are new reports to review!',
},
token: token
};
admin.messaging().send(message)
}
});
If anyone can point me in the right direction that would be greatly appreciated! :)

Related

Cloud functions with Firebase Realtime Database not triggering onCreate and not sending FCM notification

I'm trying to automatically send notification per onCreate event of RealtimeDatabase, however the function never triggers and never logs anything.
code:
// The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
exports.notificationOnArticleCreate = functions.database.ref().onCreate((snapshot, context) => {
const payload = {
notification: {
title: 'cloud function demo',
body: 'this is a test notification'
}
};
admin.messaging().send(payload).then((response) => {
console.log('Successfully sent message:', response);
return {success: true};
}).catch((error) => {
return {error: error.code};
});
console.log('test test');
})
I'm surprised this even compiles/deployed: functions.database.ref().onCreate.
You'll want to indicate the path where the node is created, e.g.
functions.database.ref("/messages/{message}").onCreate(...

Retrieve an array from a Firestore document and store it to Node.Js then use it as tokens to send notifications

I've been trying to figure this out for hours and I just can't. I'm still a beginner with Node.js and Firebase. I need your help to be able to retrieve the tokens array in my "userdata" collection to Node.js and be able to use it to send notifications in the Cloud Function. So far this is what I've been working on. Here is what my database looks like:
The receiverId is gathered from when I have an onCreate function whenever a user sends a new message. Then I used it to access the userdata of a specific user which uses the receiverId as their uid.
In the cloud function, I was able to start the function and retrieve the receiverId and print the userToken[key]. However, when I try to push the token it doesnt go through and it results in an error that says that the token is empty. See the image:
Your help would mean a lot. Thank you!
newData = snapshot.data();
console.log("Retrieving Receiver Id");
console.log(newData.receiverId); //uid of the user
const tokens = [];
const docRef = db.collection('userdata').doc(newData.receiverId);
docRef.get().then((doc) => {
if (doc.exists) {
console.log("DocRef exist");
const userToken = doc.data().tokens;
for(var key in userToken){
console.log(userToken[key]);
tokens.push(userToken[key]);
}
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
//Notification Payload
var payload = {
notification: {
title: newData.sendBy,
body: 'Sent you a message',
sound: 'default',
},
data: {
click_action : 'FLUTTER_NOTIFICATION_CLICK',
route: '/telconsultinbox',
}
};
console.log("Sending Notification now.");
console.log(tokens);
try{
//send to device
const response = await admin.messaging().sendToDevice(tokens, payload);
console.log('Notification sent successfully');
console.log(newData.sendBy);
}catch(err){
console.log(err);
}
I think you should avoid using for..in to iterate through an array (you can read more about it in this answer). Try one of these 2 options:
You could use forEach(), which is more elegant:
userToken.forEach((token) => {
console.log(token);
tokens.push(token);
});
for-of statement:
for(const token of userToken){
console.log(token);
tokens.push(token);
}
Also, I would consider renaming userToken to userTokens, since it should contain multiple values. Makes the code a bit more readable.

how to trigger a firestore collection via cloud function

I want to trigger a new collection (timeline collection) from the existing collection of followers collection and videos collection whenever I clicked the following button in my app.
Now the problem is that, the Cloud Function is created from the view log but the new collection (timeline collection) won't be created.
Below is the code for the Cloud Function where I target the followers collection and the videos collection to create a new timeline collection. I anticipate for your help.
Videos collection
Followers collection
Cloud function view logs
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
// // 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.onCreateFollower = functions.firestore
.document("/followers/{userId}/userFollowers/{userfollowerId}")
.onCreate(async (snapshot, context) => {
console.log("The Event has Created The Follower", snapshot.id);
const userId = context.params.userId;
const userfollowerId = context.params.userfollowerId;
// 1) Create followed users posts ref
const followedUserVideosCollection = admin
.firestore()
.collection("videos")
.doc(userId)
.collection("userVideos");
// 2) Create following user's timeline ref
const timelineVideosCollection = admin
.firestore()
.collection("timeline")
.doc(userfollowerId)
.collection("timelinePosts");
// 3) Get followed users posts
const querySnapshot = await followedUserVideosCollection.get();
// 4) Add each user post to following user's timeline
querySnapshot.forEach(doc => {
if (doc.exists) {
const videoId = doc.id;
const videoData = doc.data();
timelineVideosCollection.doc(videoId).set(videoData);
}
});
});
I figured out what causes the error "querySnapshot.forEach isn't a function". According to this answer, you need to query the collection first because get() returns a document instead of a snapshot. Here's a sample code (see step 3):
// 1) Create followed users posts ref
const followedUserVideosCollection = admin
.firestore()
.collection("videos")
.doc("Videos 1") // I changed the value with your sample for test purposes and also because I'm not sure how you fill up this doc.
.collection("userVideos");
// 2) Create following user's timeline ref
const timelineVideosCollection = admin
.firestore()
.collection("timeline")
.doc(userfollowerId)
.collection("timelinePosts");
// 3) Get followed users posts & Add each user post to following user's timeline
await followedUserVideosCollection.where('id', '==', 0).get().then((querySnapshot) => {
if (querySnapshot) {
querySnapshot.forEach(doc => {
if (doc) {
const videoId = doc.id;
const videoData = doc.data();
timelineVideosCollection.doc(videoId).set(videoData);
}
});
}else {
console.log("Document not found");
}
}).catch((error) => {
console.log(error);
});
A solution is to create a filter, and make sure that the document you're looking for matches the filter. For example, a document inside the subcollection userVideos should have a field called id with value of 0.
You may have to remodel your DB to fix the line where I put a comment but this code should write the timeline collection.

Notification not being sent via firebase functions. "undefined" logged in console

I am trying to send a sample notification to all devices according to their token, however the token is being logged as "undefined" and the notification subsequently fails to deliver
The following lines from my code successfully show me the data from the database:
const notificationSnapshot = change.after.val(); //get new value
console.info(notificationSnapshot);
However, the following gives "undefined", despite the above retrieving the data successfully.
const userToken = notificationSnapshot.token;
console.info(userToken);
Is this not the correct way to retrieve the token to send the notification to all the registered devices in my firebase database?
my whole function (index.js)
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendSampleNotification = functions.database.ref('/User')
.onWrite((change, context) => {
const notificationSnapshot = change.after.val(); //get new value
const userToken = notificationSnapshot.token;
const name = notificationSnapshot.name;
const surname = notificationSnapshot.surname;
console.info(notificationSnapshot);
console.info(userToken);
var message = {
notification: {
title: 'test title',
body: 'test message'
},
token: userToken
};
admin.messaging().send(message).then((response) => {
console.log("Message sent successfully:", response);
return response;
})
.catch((error) => {
console.log("Error sending message: ", error);
});
});
I would say that your issue is very similar to this one since you are having a missing token (showed as undefined) due to the executions times, more or less what Doug was pointing out.
Note that the solution relies on considering the execution times and I’ve seen also that the implementation differs in some method executions but I would say the generals point in the same direction.

How do you access a DocumentReference object stored in Firestore from a Cloud Function?

In my Firestore database I store DocumentReferences to users so that I am always using up-to-date user data such as username, profile pictures, and auth tokens.
I am also implementing Cloud Functions to listen for database triggers so that I can send notifications to those specific users about activity related to their posts.
This is where I run into trouble, because I do not know how to use the stored reference object properly inside the Node.js function when I access it like all other database information.
The following is my function code:
exports.countNameChanges = functions.firestore
.document('posts/{postId}')
.onUpdate((change, context) => {
// Retrieve the current and previous value
const data = change.after.data();
const previousData = change.before.data();
var registrationToken = '';
var notification = '';
var postTitle = data.statement;
var userRef = data.userRef; //This is my `DocumentReference` object
if (data.interactionCount > previousData.interactionCount && data.postTypeId == 2131165321) notification = 'You recieved a new comment!';
if (data.interactionCount > previousData.interactionCount && data.postTypeId == 2131165335) notification = 'You recieved a new vote!';
if (data.likes > previousData.likes) notification = 'You have a new post like!' ;
if (data.dislikes > previousData.dislikes) notification = 'You have a new post dislike!' ;
admin.firestore()
.doc(userRef) //This is my `DocumentReference` object
.get()
.then(doc => {
registrationToken = doc.data().token;
var payload = {
data: {
title: postTitle,
body: notification
},
token: registrationToken
};
admin.messaging().send(payload)
.then((response) => {
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
})
});
});
});
My Function Log
I would assume that the DocumentReference object would be easy to work with
inside a Cloud Function since the object is supported for direct storage into Firestore, but I can't figure it out.
If userRef is a DocumentReference type object, then just call get() on it directly. Don't pass it to doc(). You're only supposed to pass string type objects to doc().
userRef.get().then(...)

Categories