Cloud functions for Firebase FCM notifications to multiple users - node.js

I am using nodeJS with firebase for my flutter/firebase mobile app
I would like to send notifications to all users that have a certain query met. ie all users who have radiology as their specialty. So that they will be notified when a new article is added to the database
However I am unsure why my code (below) doesn't work to get notification tokens for all users with this query.
My database structure is users/notificationTokens/Ids of all tokens for that user stored in field 'token'
exports.sendToDevice5 = functions.firestore
.document('Articles/{paper}')
.onCreate(async (snapshot, context) => {
const paper = context.params.paper;
const item = snapshot.data();
if (item.subspecialty == "RadMSK" || item.subspecialty == "RadMS") {
const tokens = await admin.firestore().collection('users').where("specialty", "==", "RADIOLOGY").get().then(
snapshot.forEach((doc) => {
const docs = admin.firestore().collection('users').doc(doc.id).collection('notificationTokens').get();
return docs.data().token;
}));
const payload = {
notification: {
title: `${item.title}!`,
body: `New Journal`,
sound: "default",
},
data: {click_action: 'FLUTTER_NOTIFICATION_CLICK'},
};
return admin.messaging().sendToDevice(tokens, payload);
}
});

Related

I have the create and saving of the user's email, but I can't do an email update. Use firebase, firestore and react

This is my code:
exports.saveUserEmail = functions.region('europe-central2').auth.user().onCreate((user) => {
const email = user.email;
const uid = user.uid;
const dt = dateTime.create();
const formatted = dt.format("Y-m-d H:M:S");
return admin.firestore().collection('users').doc(uid).set({uid: uid, email: email, created_at: formatted});
});
and i tried do update like this:
exports.saveEditedEmail = functions.region('europe-central2').auth.user().updateUser((user, uid) => {
const email = user.email;
return admin.firestore().collection('users').doc(uid).set({uid: uid, email: email,});
})
Where is my mistake?
There isn't any onUpdate() auth trigger for Cloud functions. Instead your can create a callable function and call it directly from client side to update your user.
exports.addMessage = functions.https.onCall((data, context) => {
const { email } = data;
// update email in Firebase Auth and Firestore
});
Alternatively, you can directly update the document in Firestore if the user tries to update their own profile. You can setup the following security rules so a user can update their own profile only:
match /users/{userId} {
allow update: if request.auth.uid == userId;
}

Send Notification to device tokens

I'm using Cloud Functions with Cloud Messaging and I want to send a notification to all devices which have a specific userRole (see userRoleList).
Unfortunately, I have no idea how to do that.
For example, I just want to push the deviceTokens with userRole "Aktive" to the deviceTokens.
And here is my code for Cloud Functions so far:
exports.sendNotificationAusschuss = functions.firestore.document('news/{newsId}').onCreate(async snapshot => {
const news = snapshot.data();
console.log('Message received');
//var deviceTokens = ??
const payload = {
notification:{
title: 'Message received',
body: `${news.newsText}`,
sound: "default"
}
};
return admin.messaging().sendToDevice(deviceTokens, payload);
});
Thank you very much
You can query Firestore to retrieve the users with a given role.
const snap = await admin.firestore().collection('users')
.where('userRoleList', 'array-contains', 'Aktive')
.get();
const tokens = [];
snap.docs.forEach((doc) => {
tokens.push(doc.data().deviceToken);
});
Then split tokens into batches of 500, and:
await admin.messaging().sendMulticast({
tokens,
});

Firebase cloud function doesn't send push notification with async

My goal is to send a push notification when a user sends a message. I am trying to do this by retrieving all of the push tokens from the firestore database, and sending a multicast message using these tokens each time a new message is added to the realtime database.
Works
This first example works. There is no token retrieval, the tokens are hardcoded. I do receive the notifications.
exports.notifyUsers = functions.database.ref('/messages/{messageId}').onCreate((liveSnapshot, context) => {
const name = context.params.messageId;
const message = liveSnapshot.val().toString();
const tokens = [
"e6erA_qM...",
"ePU9p_CI...",
];
const payload = {
notification: {
title: `New message from ${name}`,
body: message,
badge: '1',
sound: 'default'
},
tokens: tokens,
}
const res = admin.messaging().sendMulticast(payload);
console.log(`response: ${res}`);
Doesn't work
This doesn't work, I don't receive any notifications.
exports.notifyUsers = functions.database.ref('/messages/{messageId}').onCreate(async (liveSnapshot, context) => {
const name = context.params.messageId;
const message = liveSnapshot.val().toString();
const snapshot = await admin.firestore().collection('users').get();
const tokens = snapshot.docs.map(doc => doc.data().token);
const payload = {
notification: {
title: `New message from ${name}`,
body: message,
badge: '1',
sound: 'default'
},
tokens: tokens,
}
const res = await admin.messaging().sendMulticast(payload);
console.log(`response: ${res}`);
I have verified that the tokens retrieved from the database are the same as the hardcoded ones with the following code.
exports.notifyUsers = functions.database.ref('/messages/{messageId}').onCreate(async (liveSnapshot, context) => {
const hardcodedTokens = [
"e6erA_qM...",
"ePU9p_CI...",
];
const snapshot = await admin.firestore().collection('users').get();
const tokens = snapshot.docs.map(doc => doc.data().token);
let same = true;
hardcodedTokens.forEach(el => {
if (!tokens.includes(el)) {
same = false;
}
});
console.log(same);
})
This logs true in the firebase cloud functions console.
The function uses Node 12.
I experienced a similar problem recently, and solved it by breaking out Android and iOS specific fields according to the Firebase docs :
const message = {
"notification": {
"title": `New message from ${name}`,
"body": message,
},
'apns': {
'payload': {
'aps': {
'badge': 1,
},
},
},
'android':{
'notification':{
'notificationCount': 1,
},
},
"tokens": tokens,
}
The following code works.
async function getTokens() {
const snapshot = await admin.firestore().collection('users').get();
return snapshot.docs.map(doc => doc.data().token);
}
exports.notifyUsers = functions.database.ref('/messages/{messageId}').onCreate(async (snapshot, context) => {
const name = context.params.messageId;
const message = snapshot.val().toString();
const tokens = await getTokens();
const payload = {
notification: {
title: `New message from ${name}`,
body: message,
},
tokens: tokens,
};
await admin.messaging().sendMulticast(payload);
})
I logged my response like below:
const res = await admin.messaging().sendMulticast(payload);
console.log('response:', JSON.stringify(res));
This logged the following:
response: {"responses":[{"success":false,"error":{"code":"messaging/invalid-argument","message":"Invalid JSON payload received. Unknown name \"sound\" at 'message.notification': Cannot find field."}},{"success":false,"error":{"code":"messaging/invalid-argument","message":"Invalid JSON payload received. Unknown name \"sound\" at 'message.notification': Cannot find field."}}],"successCount":0,"failureCount":2}
Based on this, I believe the problem was the sound argument in the notification part of the payload. It works after removing it.

How to send emails that loop through arrays from firestore database

I'm trying to send a user receipt from an eCommerce store. How do I loop through the data to send
I have tried using [] on the dynamic data.
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp();
const db = admin.firestore();
// Sendgrid Config
import * as sgMail from "#sendgrid/mail";
const API_KEY = functions.config().sendgrid.key;
const TEMPLATE_ID = functions.config().sendgrid.template;
sgMail.setApiKey(API_KEY);
//FUNCTIONS
export const newOrder = functions.firestore
.document("checkout/{checkoutId}/products/{productId}")
.onCreate(async (change, context) => {
// Read booking document
const postSnap = await db
.collection("checkout/{checkoutId}/products")
.doc(context.params.productId)
.get();
const booking = postSnap.data() || {};
//Email
const msg = {
to: "wilmutsami#gmail.com",
from: "test#example.com",
templateId: TEMPLATE_ID,
dynamic_template_data: {
subject: "Hey there, thank you for your order!",
name: booking.name,
amount: booking.amount
}
};
//Send it
return sgMail.send(msg);
});
Expected results are an email to the user displays a table of items that you ordered
If you want to get the data of the document that triggered the Cloud Function at checkout/{checkoutId}/products/{productId} you don't need to do
await db
.collection("checkout/{checkoutId}/products")
.doc(context.params.productId)
.get();
As explained in the doc:
When a function is triggered, it provides a snapshot of the data
related to the event. You can use this snapshot to read from or write
to the document that triggered the event, or use the Firebase Admin
SDK to access other parts of your database.
You can easily get the values of the document fields through the snap DocumentSnapshot as follows:
export const newOrder = functions.firestore
.document("checkout/{checkoutId}/products/{productId}")
.onCreate(async (snap, context) => {
const docData = snap.data();
const name = docData.name;
const amount = docData.amount;
// You can then use those values in the rest of your code
const msg = {
to: "wilmutsami#gmail.com",
from: "test#example.com",
templateId: TEMPLATE_ID,
dynamic_template_data: {
subject: "Hey there, thank you for your order!",
name: name,
amount: amount
}
};
return sgMail.send(msg);
});

Cloud Functions Error Firebase

I am trying to do push notification through Functions in Firebase.
Here is my code in Node.JS
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendPushNotification = functions.database.ref('Received Downs/{owner}/{propID}')
.onCreate(event => {
// get the owner name and propID
var owner = event.params.owner;
var propID = event.params.propID;
// Log it
console.log('Owner: ' + owner + ' Property ID: ' + propID);
// Get the list of device notification tokens.
return admin.database().ref(`/users/${owner}`).once('value', snapshot => {
var ownerID = snapshot.val();
// This will find requester ID
return admin.database().ref(`/Received Downs/${owner}/${propID}`).once('value', snapshot => {
// First will find the property the requester downed
var property = snapshot.val();
// Find the requester's name
return admin.database().ref('/Users Info/' + property.downedBy).once('value', snapshot => {
// Requester's ID
var downedBy = snapshot.val();
// Notification details.
const payload = {
notification: {
title: 'You have a new request!',
body: `${downedBy.name} is now following you.`,
sound: 'default'
}
};
// Listing all tokens. (the function save the keys of given variable)
// const tokens = Object.keys(getDeviceTokens.val());
// var fcmToken = "dzJLM-JdIt8:APA91bHBJJP6t3Z0_T7kEFDrLLsu5T_NpYsR6QmJz2EJhpK88SV1ZfemoyCtC_6hl3_0sCPdzkvlQFoAFhlWn4xTQOY3k5P8JMvdYFyeNBN1lHceQtytE0y-9oTP6qgKspi9p9E8V9dB";
// Send to all tokens of a device
admin.messaging().sendToDevice(ownerID.token, payload)
.then(response => {
console.log("Successfully sent message:", response);
}).catch(function(error) {
console.log("Error sending message:", error);
});
})
})
})
})
And here is what I got in LOGS at Firebase Functions
When I used a variable that has fem token , typed, it works fine, but not when i fetched from Firebase Realtime Database. Anyone could tell me why?
The problem I had the wrong path return admin.database().ref(/users/${owner})

Resources