Sending Notification With Firebase Cloud Functions to Specific users - node.js

I am using firebase cloud function in my firebase group chat app, Setup is already done but problem is when some one send message in any group then all user get notification for that message including non members of group.
I want to send notification to group specific users only, below is my code for firebase cloud function -
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const _ = require('lodash');
admin.initializeApp(functions.config().firebase);
exports.sendNewMessageNotification = functions.database.ref('/{pushId}').onWrite(event => {
const getValuePromise = admin.database()
.ref('messages')
.orderByKey()
.limitToLast(1)
.once('value');
return getValuePromise.then(snapshot => {
const { text, author } = _.values(snapshot.val())[0];
const payload = {
notification: {
title: text,
body: author,
icon: ''
}
};
return admin.messaging()
.sendToTopic('my-groupchat', payload);
});
});
This will be really help full, if anyway some one can suggest on this.

As per our conversation on the comments I believe that the issue is that you are using a topic that contains all the users, which is the my-groupchat topic. The solution would be to create a new topic with the users that are part of this subtopic.
As for how to create such topics, there are a couple of examples in this documentation, in there you can see that you could do it server side, or client side. In your case, you could follow the examples for the server side, since you would need to add them in bulk, but as new users are added it could be interesting to implement the client side approach.

Related

Firebase Cloud Function to send notification at very specific time [duplicate]

Inside the Firebase Console, under the Cloud Messaging view, users are able to create test notifications. This functionality also allows you to schedule the time at which the notification will send to a device or set of devices.
Is it possible to create and send scheduled FCM notifications to specific devices by using firebase cloud functions and the Firebase Admin SDK? Is there an alternative way to solving this?
The current way that I send scheduled messages to users is like so:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const schedule = require('node-schedule');
admin.initializeApp();
exports.setScheduledNotification = functions.https.onRequest(async (req, res) => {
const key = req.query.notification_key;
const message = {
notification: {
title: 'Test Notification',
body: 'Test Notification body.'
}
};
var currentDate = new Date();
var laterDate = new Date(currentDate.getTime() + (1 * 60000));
var job = schedule.scheduleJob(key, laterDate, () => {
const snapshot = admin.messaging().sendToDevice(key, message);
});
return res.status(200).send(`Message has been scheduled.`);
});
First of all, I am unsure how node-schedule interacts with firebase cloud functions. The logs appear that the function terminates very quickly which I would think would be correct. The longer the operation runs the more costly it is in our firebase bills. The notification does still run on a scheduled time though. I'm confused on how that all is working behind the scenes.
Secondly, I am having issues canceling these scheduled notifications. The notifications will most likely be on a 2hr timed schedule from the point it gets created. Before the 2hrs is up, I'd like the have the ability to cancel/overwrite the notification with an updated scheduled time.
I tried this to cancel the notification and it failed to find the previously created notification. Here is the code for that:
exports.cancelScheduledNotification = functions.https.onRequest(async (req, res) => {
const key = req.query.notification_key;
var job = schedule.scheduledJobs[key];
job.cancel();
return res.status(200).send(`Message has been canceled.`);
});
Is it possible to tap into the scheduling functionality of firebase cloud messaging outside of the firebase console? Or am I stuck with hacking my way around this issue?
A Cloud Function can run for a maximum of 9 minutes. So unless you're using node-schedule for periods shorter than that, your current approach won't work. Even if it would work, or if you are scheduling for less than 9 minutes in advance, using this approach is very uneconomic as you'll be paying for the Cloud Functions for all this time while it's waiting.
A more common approach is to store information about what message you want to be delivered to whom at what time in a database, and then use regular scheduled functions to periodically check what messages to send. For more on this, see these previous questions:
Firebase scheduled notification in android
How to schedule push notifcations for react native expo?
Schedule jobs in Firebase
Ionic: Is it possible to delay incoming push FCM push notification from showing to my device until a specific time
Cloud Functions for Firebase trigger on time?
How to create cron jobs dynamically in firebase
A recent improvement on this is to use the Cloud Tasks API to programmatically schedule Cloud Functions to be called at a specific time with a specific payload, and then use that to send the message through FCM. Doug Stevenson wrote a great blog post about this here: How to schedule a Cloud Function to run in the future with Cloud Tasks (to build a Firestore document TTL). While the post is about deleting documents at a certain time, you can combine it with the previous approach to schedule FCM messages too.
Scheduling of tasks is now also described in the documentation on enqueueing functions with Cloud Tasks
A final option, and one I'd actually recommend nowadays, is to separate the delivery of the message from the display of the notification.
Display of data messages (unlike notification messages) is never handled by the system, and always left to your application. So you can deliver the FCM data message straight away that then contains the time to display the message, and then wake the device up to display the message (often called a local notification) at that time.
To make Frank's answer more tangible, I am including some sample code below for scheduled cloud functions, that can help you achieve the 'scheduled FCM notifications'.
You should store the information required to send your notification(s) in Firestore (e.g. the when-to-notify parameter and the FCM token(s) of the users you want to send the notification to) and run a cloud function every minute to evaluate if there is any notification that needs to be delivered.
The function checks what Firestore documents have a WhenToNofity parameter that is due, and send the notifications to the receiver tokens immediately. Once sent, the function sets the boolean 'notificationSent' to true, to avoid that the users receive the same notification again on the next iteration.
The code below achieves just that:
const admin = require('firebase-admin');
admin.initializeApp();
const database = admin.firestore();
exports.sendNotification = functions.pubsub.schedule('* * * * *').onRun(async (context) => {
//check whether notification should be sent
//send it if yes
const query = await database.collection("experiences")
.where("whenToNotify", '<=', admin.firestore.Timestamp.now())
.where("notificationSent", "==", false).get();
query.forEach(async snapshot => {
sendNotification(snapshot.data().tokens);
await database.doc('experiences/' + snapshot.id).update({
"notificationSent": true,
});
});
function sendNotification(tokens) {
let title = "INSERT YOUR TITLE";
let body = "INSERT YOUR BODY";
const message = {
notification: { title: title, body: body},
tokens: tokens,
android: {
notification: {
sound: "default"
}
},
apns: {
payload: {
aps: {
sound: "default"
}
}
}
};
admin.messaging().sendMulticast(message).then(response => {
return console.log("Successful Message Sent");
}).catch(error => {
console.log(error);
return console.log("Error Sending Message");
});
}
return console.log('End Of Function');
});
If you're unfamiliar with setting up cloud functions, you can check how to set them up here. Cloud functions require a billing account, but you get 1M cloud invocations per month for free, which is more than enough to cover the costs of this approach.
Once done, you can insert your function in the index.js file.

Automatically generating cloud functions when group created

What is the best approach for my case? A user creates a messaging group in my app. Then the group will get its own auto id.
Now I want to use cloud func from firebase to check for new messages and to do a push notification.
The code below checks if a group is created and send the push notification. But this only works when I know the generated auto id and post it manually in the code below. How can I solve this issue? Should my App create for each group its own cloud function? Would this not be too much? Or can I use somehow a wildcard?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.Push = functions.database.ref('/placeID/theGeneratedIdComesHere/{messageID}/')
.onCreate((snapshot, context) => {
var nameUser = String(snapshot.val().userName)
var textUser = String(snapshot.val().userComment)
var topic = 'weather';
const payload = {
notification: {
title: nameUser,
body: textUser,
badge: '1',
sound: 'default'
}
};
admin.messaging().sendToTopic(topic,payload);
})
You can make the group ID a parameter on your Cloud Function declaration:
exports.Push = functions.database.ref('/placeID/{groupID}/{messageID}/')
Then you can access that group ID in your functions code with:
const groupID = context.params.groupID;
And then you can use that value in your code to make it to what this specific group needs.

Firebase Admin SDK with Cloude Functions Confusion

So after I have written a good part of my app, I am now becoming maximally confused regarding the use of the admin sdk in cloud functions regarding firestore.
I only want to query, read and write data from the cloud function environment correctly. Which documentation do I have to use, how do I correctly initialize the "Admin SDK" and implement the corresponding methods and functions?
It seems like I have mixed up v9 and v10 and even by reading the docs I still can't find a red thread, on how to use it correctly.
I am currently importing and initializing like that.
const functions = require("firebase-functions");
const { initializeApp } = require("firebase-admin/app");
const admin = require("firebase-admin");
const app = initializeApp();
when I initialize like that, and would like to work with firestore. There are different options which I do not understand - for example.
const userRef = admin.firestore().collection("users").doc(data.userID);
enables me to access "collection"
However when using
const userRef = admin.firestore
I am only able to choose admin.firestore.CollectionGroup and admin.firestore.CollectionReference, which are classes? What is up with that?
Furthermore this approach seems to be outdated as this site of the docs (which I only recently came to know), says that I should use a modular approach, as I would on the client side, with.
import { getFirestore } from 'firebase-admin/firestore'
getFirestore();
So far so good. Now when I take a look at the docs, I am led to this page. The question I ask myself there are, what are External Api Re-Exports? By clicking on any of the referenced functions I get redirected to this, which contains the reference for the nodejs client and also subgroups called firestore admin client, as well as FirestoreAdmin. Neither of those subgroups contain anything which has something to do with querying a collection. There is a section about collections and querying which displays examples of asynchronous programming with .then, but from what I heard it is generally more beneficial to use async/await?
In addition to that the quickstart guide, recommends an initialization like this.
const {Firestore} = require('#google-cloud/firestore');
// Create a new client
const firestore = new Firestore();
Furthermore the firebase docs, seem to have a dedicated documentation on how to use the Admin SDK, with the realtime database here but not how to use it with firestore?
I am just so confused, as to which documentation to use, and I couldn't find any examples how to do standard operations. I guess I also lack fundamental understanding about the Admin SDK itself and its integration in firebase.
The code I have written now is working, however I think it is not "right" from a documentation point of view.
exports.createUserDoc = functions.auth.user().onCreate((user) => {
const userRef = admin.firestore().collection("users").doc(user.uid);
const userData = {
uid: user.uid,
email: user.email,
displayName: user.displayName,
tokens: 0,
};
return userRef.set(userData);
});
exports.setWriteTimestamp = functions.https.onCall((data, context) => {
//in the if check below, retrieve the corresponding pool document and check whether the "open" field is true
const poolRef = admin.firestore().collection("pools").doc(data.slug);
const poolDoc = poolRef.get().then((doc) => {
if (doc.exists && doc.data().open) {
return poolRef.update({
writeTimestamp: admin.firestore.FieldValue.serverTimestamp(),
});
} else {
return null;
}
});
});
Thank you for your help.

how can i request message by using fcm api?

i'm using react native with firebase to use fcm push notification..
this is documnet example
// Node.js
var admin = require('firebase-admin');
// ownerId - who owns the picture someone liked
// userId - id of the user who liked the picture
// picture - metadata about the picture
async function onUserPictureLiked(ownerId, userId, picture) {
// Get the owners details
const owner = admin
.firestore()
.collection('users')
.doc(ownerId)
.get();
// Get the users details
const user = admin
.firestore()
.collection('users')
.doc(userId)
.get();
await admin.messaging().sendToDevice(
owner.tokens, // ['token_1', 'token_2', ...]
{
data: {
owner: JSON.stringify(owner),
user: JSON.stringify(user),
picture: JSON.stringify(picture),
},
},
{
// Required for background/quit data-only messages on iOS
contentAvailable: true,
// Required for background/quit data-only messages on Android
priority: 'high',
},
);
}
document says if i want to request message by using rest api instead of firebase admin
i have to use this url
which is
https://fcm.googleapis.com/fcm/send
but i confused how can i use this url??
and i wonder should i use this url in backend or frontend?
Sending messages to devices through FCM requires that you specify the so-called FCM server key to the API. As its name implies this key should only be used in trusted environments, such as a server you control, your development machine, or Cloud Functions.
There is no secure way to send messages directly from client-side code directly through the FCM API. For more on this, see:
the architectural overview in the Firebase documentation
How to send one to one message using Firebase Messaging

Using wildcards in firestore get query

I want to create a cloud function in firebase that gets triggered whenever a user logs in for the first time. The function needs to add the UID from the authentication of the specific user to a specific, already existing document in firestore. The problem is that the UID needs to be added to a document of which I do not know the location. The code I have right now doesn't completely do that, but this is the part where it goes wrong. The database looks like this when simplified
organisations
[randomly generated id]
people
[randomly generated id] (in here, a specific document needs to be found based on known email
adress)
There are multiple different organisations and it is unknown to which organisation the user belongs. I thought of using a wildcard, something like the following:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
console.log('function ready');
//Detect first login from user
//if(firebase.auth.UserCredential.isNewUser()){
if(true){
//User is logged in for the first time
//const userID = firebase.auth().currentUser.UID;
//const userEmail = firebase.auth().currentUser.email;
const userID = '1234567890';
const userEmail = 'example#example.com';
//Get email, either personal or work
console.log('Taking a snapshot...');
const snapshot = db.collection('organisations/{orgID}/people').get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.data());
});
});
}
I commented out some authentication-based lines for testing purposes. I know the code still runs, because hardcoding the orgID does return the right values. Also, looping trough every organisation is not an option, because I need to have the possibility of having a lot of organisations.
A lot of solutions are based on firestore triggers, like onWrite, where you can use wildcards like this.
However, I don't think that's possible in this case
The solution to the problem above:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
//Add UID to document in DB[FMIS-94]
//Detect first login from user
//if(firebase.auth.UserCredential.isNewUser()){
if(true){
//User is logged in for the first time
//const userID = firebase.auth().currentUser.UID;
//const userEmail = firebase.auth().currentUser.email;
const userID = '1234567890';
const userEmail = 'example#example.com';
var docFound = false;
//Get email, either personal or work
console.log('Taking a snapshot...');
//Test for work email
const snapshot = db.collectionGroup('people').where('email.work', '==', userEmail).get()
.then(function(querySnapshot){
querySnapshot.forEach(function(doc){
//work email found
console.log('work email found');
console.log(doc.data());
docFound = true;
const organisationID = doc.ref.parent.parent.id;
writeUID(doc.id, userID, organisationID);
});
});
if(!docFound){
//Test for personal email
const snapshot = db.collectionGroup('people').where('email.personal', '==', userEmail).get()
.then(function(querySnapshot){
querySnapshot.forEach(function(doc){
//personal email found
console.log('personal email found');
console.log(doc.data());
const organisationID = doc.ref.parent.parent.id;
writeUID(doc.id, userID, organisationID);
});
});
}
}
async function writeUID(doc, uid, organisationID){
const res = db.collection(`organisations/${organisationID}/people`).doc(doc).set({
userId: uid
}, { merge: true });
}
This was exactly what I needed, thanks for all your help everyone!
It is not possible to trigger a Cloud Function when a user logs in to your frontend application. There is no such trigger among the Firebase Authentication triggers.
If you want to update a document based on some characteristics of the user (uid or email), you can do that from the app, after the user has logged in.
You mention, in your question, "in here, a specific document needs to be found based on known email address". You should first build a query to find this document and then update it, all of that from the app.
Another classical approach is to create, for each user, a specific document which uses the user uid as document ID, for example in a users collection. It is then very easy to identify/find this document, since, as soon the user is logged in you know his uid.
I'm not sure I understand you correctly, but if you want to search across all people collections not matter what organizations document they're under, the solution is to use a collection group query for that.
db.collectionGroup('people').get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log("user: "+doc.id+" in organization: "+doc.ref.parent.parent.id);
});
});
This will return a snapshot across all people collections in your entire Firestore database.
First setup Cloud Functions according to the official Documentation.
Then after setting up create functions like this:
exports.YOURFUNCTIONNAME= functions.firestore
.document('organisations/[randomly generated id]/people/[randomly generated id]')
.oncreate(res => {
const data = res.data();
const email = data.email;/----Your field name goes here-----/
/-----------------Then apply your logic here---------/
)}
This will triggers the function whenever you create the People -> Random ID

Resources