How to send notification to multiple device token using firebase cloud functions - javascript

I am new in using Firebase Cloud Functions and JavaScript and I was able to send notification to a single device using Firebase Cloud Functions(JavaScript). Now I am trying to send push notification to multiple device token and I think I have a problem on it.
I want to send the notification to these device tokens in my firebase database:
/receivers
/event1
/uid1: device_token1
/uid2: device_token2
/uid3: device_token3
/uid4: device_token4
This is my code so far but it doesn't work..
exports.sendSOSNotif = functions.database.ref('/SOSNotifs/{sosId}').onWrite((data, context) => {
const eventId=data.after.child("eventId").val();
const uid=data.after.child("uid").val();
const displayName=data.after.child("displayName").val();
const photoUrl=data.after.child("photoUrl").val();
const status=data.after.child("status").val();
console.log("eventId:", eventId);
console.log("displayName:", displayName);
console.log("uid", uid);
const payload = {
notification: {
title: "SOS Alert",
body: displayName + " sent an alert!",
sound: "default"
},
data: {
eventId: eventId,
displayName: displayName
}
};
return Promise.all([admin.database().ref("/receivers/event1").once('value')]).then(results => {
const tokens = results[0];
if (!tokens.hasChildren()) return null;
const tokensList = Object.keys(tokens.val());
return admin.messaging().sendToDevice(tokensList, payload);
});
});

First of all, you shouldn't be adding tokens like below, if that's how you've organised your DB. There might be multiple token for a single uid
/receivers
/event1
/uid1: device_token1
/uid2: device_token2
/uid3: device_token3
/uid4: device_token4
And for sending notifications to multiple UIDs, I've written a script here
Also, update your question about what exactly the problem you are facing.

Related

Firebase Funtions push notifications

I'm new to firebase and there is something I can't do. I want to send a notification to the phone with firebase functions. I want to receive notifications on the phone when someone follows me. My Firebase collection is as in the photo. I want to access the Followers array and send its information with notification. The codes I could write are as follows. What do I need to add?
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.sendPushNotification = functions.firestore.document('/users/{uid}').onCreate((snap, context) => {
var values = snap.data();
var token = values.fcmTokens;
var payload = {
notification: {
title: values.title,
body: values.message
}
}
return admin.messaging().sendToDevice(token, payload);
});
First, onCreate() function is triggered when a document is created. I assume followers array will be updated everytime someone follows a user? In that case you should be using onUpdate() that'll trigger the function when the document is updated. You can just check if length of followers array has changed in the update, if yes then send the notification as shown below:
exports.sendPushNotification = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
const newValue = change.after.data();
const previousValue = change.before.data();
if (newValue.followers.length > previousValue.followers.length) {
// followers count increased, send notification
const token = newValue.fcmTokens;
const payload = {
notification: {
title: "New Follower",
body: "Someone followed you"
}
}
await admin.messaging().sendToDevice(token, payload);
}
return null;
});
Here, we send notification only if the followers field has changed since this function will trigger whenever any field in this user document is updated.
If you want to specify who followed the user, then you'll have to find the new UID added in followers array and query that user's data.
Firestore documents have a max size limit of 1 MB so if a user can have many followers then I'll recommend creating a followers sub-collection. Then you'll be able to use onCreate() on the sub-document path /users/{userId}/followers/{followerId}

Show user invoices for simultaneously logged in users using Expressjs

I have created a simple invoice application using the MERN stack. The application is great at handling data for the logged in user as long as one user is logged in, but if another user logs in then the invoices for the user that first logged in is shown.
I am currently using app.set() and app.get() to pass data between endpoints and send to my frontend client. Auth0 handles the authentication layer, but would express-session solve this issue? And if it is how would I go about implementing this? Or is there a better solution?
Below is the code that sends the invoices currently to the frontend:
var express = require('express');
var app = express();
var userInvoices = express.Router();
const axios = require('axios');
const InvoiceModel = require('../models/Invoice');
const UserModel = require('../models/User');
//Functions//
async function clientCall() {
const url = `${process.env.REACT_APP_SAVE_USER}`;
const axiosConfig = {
method: 'get',
url
};
await axios(axiosConfig).catch((e) => {console.log(e)});
};
async function fetchUsersFromDB() {
const usersFromDB = await UserModel.find().catch((e) => {console.log(e)});
return usersFromDB;
};
async function saveUser(User) {
const condition = {email: User.email};
const query = {
nickname: User.nickname,
name: User.name,
picture: User.picture,
email: User.email,
email_verified: User.email_verified,
sub: User.sub,
};
const options = { upsert: true };
const update = await UserModel.updateMany(condition, query, options).catch((e) => {console.log(e)});
// Log the amount of documents that where updated in the DB.
if(update.nModified > 0 ) {
console.log('Number of Users added or updated to DB:', update.nModified)
}
};
function findCommonUser(dbUser, User) {
if(dbUser.length <= 0) {
UserModel.create(User, () => {console.log('Users saved to database')});
console.log('An Error has occured with Database')
} else {
dbUser.forEach((DBElement) => {
if(User.email !== DBElement.email) {
saveUser(User);
}
})
}
console.log(' Users match')
};
function matchUserAndInvoice(dbInvoices, loggedInUser) {
let newArray = [];
dbInvoices.forEach(async (DBElement) => {
if(DBElement.customer_email === loggedInUser.email){
newArray.push(DBElement);
app.set('userInvoices', newArray);
}
})
}
// prevents user from having to refresh to get data.
clientCall();
userInvoices.post('/saveUser', async (req, res) => {
try {
const User = req.body;
const usersFromDB = await fetchUsersFromDB().catch((e) => {console.log(e)});
findCommonUser(usersFromDB, User);
app.set('Users', User)
} catch (error) {
console.log(error)
}
})
userInvoices.get('/fetchUserInvoices', async (req,res) => {
try {
const invoices = await InvoiceModel.find().catch((e) => {console.log(e)});
const user = await app.get('Users');
await matchUserAndInvoice(invoices,user);
const userInvoices = await app.get('userInvoices')
res.json(userInvoices);
} catch (error) {
console.log(error)
}
});
;
module.exports = userInvoices;
app.get() is essentially global to your server instance so putting data there to use between requests for individual users will (as you have discovered) get data confused between different users as all users are trying to store data in the same place.
The usual way to solve a problem like this is to use express-session. This cookies the end-user's connection the first time they connect to your server and then creates a server-side object that is automatically associated with that cookie. Then, inside of any request handler, you can read or set data in req.session and that data will uniquely belong to just that user.
If the user changes devices or clears their cookies, then the association with the session object for that user will be lost (creating a new session object for them upon their next connection). But, if you have a persistent data store and you use some sort of login that allows you to populate the session from the user's persistent store, you can even make the session persistent across devices for the same user (though often times this is not required).
In the specific application you describe in your question (tracking invoices), it seems like your invoice data should be tagged with a specific user when it is stored in your database and then any future requests to display invoices should query based on the particular user that is making the request. This requires a login and login cookie so that you can uniquely identify each user and thus show them only the invoices that pertain to them.
The above-described session object should only be used for temporal session state, not for persistent storage such as invoices. If your server implementation is truly RESTFUL, you may not even need any data stored in the session object other than user's logged in userID.

Error: Registration token(s) provided to sendToDevice()

Now im working for my final project. I try to send notification using firebase cloud function when its trigger the onUpdate but i got an error. I have follow tutorial on youtube and website but i dont get it. By the way, im new to firebase. below Here is my index.js code :-
const functions = require('firebase-functions');
//Firebase function and handling notification logic
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.pushNotification = functions.database.ref('/Sensor').onWrite(( change,context) => {
const sensor = change.after.val();
const payload = {
notification: {
Title: "Alert",
Body: "Open pipe detect !",
icon: "default"
}
};
return admin.messaging().sendToDevice(sensor.token, payload)
.then((response)=> {
return console.log("Successfully sent message:", response);
});
});
the project structure is like this:
**water-system**
+--Sensor
+---Pipe
+---pipeName
+---solenoid
+---status // trigger on this update
+---User
+---Id1
+---email
+---name
+---token // token store by this user
+---Id2
+---Id3
+---token // also store token
So when the child node of Sensor have been update it will send notification to User who have store the token(user id1 and id3). Glad if anyone could help me to solve this problem
Try storing the tokens in this format:
"tokens" : {
"cXyVF6oUGuo:APA91bHTSUPy31JjMVTYK" : true,
"deL50wnXUZ0:APA91bGAF-kWMNxyP6LGH" : true,
"dknxCjdSQ1M:APA91bGFkKeQxB8KPHz4o" : true,
"eZunoQspodk:APA91bGzG4J302zS7sfUW" : true
}
Whenever you want to write a new token just do a set:
firebase.app().database().ref(`/user/${uid}/tokens/${token}`).set(true);
And to create an array for sendToDevice:
const tokensList = Object.keys(tokens.val());
return admin.messaging().sendToDevice(tokensList, payload);

How to scale push notifications in Firebase Cloud functions for this use case?

In my app, I am sending push notifications to the followers of a user when that user creates a new post. As you can see in the code below, I have some additional settings that I need to query from each follower's profile to get their push token and check for some additional notification settings. I am afraid that this query of each user's profile might become a bottleneck if a user has a large number of followers i.e. 1000.
What is the best way to approach this?
// The cloud function to trigger when a post is created
exports.newPost = functions.database.ref('/posts/{postId}').onCreate(event => {
const postId = event.params.postId;
const post = event.data.val();
const userId = post.author;
let tokens = [];
let promises = [];
return admin.database().ref(`/followers/${userId}`).once('value', (followers) => {
followers.forEach((f) => {
let follower = f.key;
promises.push(
admin.database().ref(`users/${follower}`).once('value')
);
});
})
.then(() => {
return Promise.all(promises).then((users) => {
users.forEach((user) => {
const userDetails = user.val();
if (userDetails.post_notifications) {
if(userDetails.push_id != null) {
tokens.push(userDetails.push_id);
}
}
})
})
})
.then(() => {
if (tokens.length > 0) {
const payload = {
notification: {
title: 'New Post!',
body: 'A new post has been created'
}
};
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload);
}
});
})
EDIT:
We have thought about using topics. But we are not sure how we can still have our customized notifications settings working with topics. Here's our dilemma.
We have multiple actions that can create notifications and we provide individual switches for each type of notification in the app so a user can select which type of notifications they want to switch off.
Let's say when User A follows User B. We can subscribe User A to "User B's topic" so whenever User B performs an action which sends out notifications to his/her followers, I can send send out notifications to users subscribed to "User B topic".
Because we have multiple notification switches in the app and when user A changes his/her settings that they do not want notifications for new posts but still want other types of notifications from the users he/she follows, we have not been able to figure out how we can use topics in this case.
Instead of using tokens, you can use topics for this. So lets say a user started following someone, then he will register to that topic.
Lets say he followed someone called "Peter", then you can execute this:
FirebaseMessaging.getInstance().subscribeToTopic("Peter");
Now if you have this database:
posts
postid
postdetails: detailshere
author: Peter
then using onCreate():
exports.newPost = functions.database.ref('/posts/{postId}').onCreate(event => {
const postId = event.params.postId;
const post = event.data.val();
const authorname = post.author;
const details=post.postdetails;
const payload = {
data: {
title:userId,
body: details,
sound: "default"
},
};
const options = {
priority: "high",
timeToLive: 60 * 60 * 24
};
return admin.messaging().sendToTopic(authorname, payload, options);
});
You can use this, everytime the author creates a new post, onCreate() is triggered then you can add the details of the post and the author name in the notification (if you want) and sendToTopic() will send it to all users subscribed to the topic that is the authorname (ex: Peter)
After your edit, I think you want the user to be unsubscribed from a topic, but stay subscribed to other topics, then you have to use the admin sdk for this:
https://firebase.google.com/docs/cloud-messaging/admin/manage-topic-subscriptions
Using the admin sdk you can unsubscribe the user also from a topic, a simple example:
// These registration tokens come from the client FCM SDKs.
var registrationTokens = [
'YOUR_REGISTRATION_TOKEN_1',
// ...
'YOUR_REGISTRATION_TOKEN_n'
];
// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
admin.messaging().unsubscribeFromTopic(registrationTokens, topic)
.then(function(response) {
// See the MessagingTopicManagementResponse reference documentation
// for the contents of response.
console.log('Successfully unsubscribed from topic:', response);
})
.catch(function(error) {
console.log('Error unsubscribing from topic:', error);
});

Firebase functions FCM

I'm building an app with Firebase and as they added functions I wanted to try this out but ran into a few errors as I am unfamiliar with this language... I'm trying to send an FCM to every user of a group (when a new one is added to the database) and I used the example I found online but still ran into some trouble.
exports.sendPush = functions.database.ref('/groups/{groupId}').onWrite(event => {
const groupId = event.params.groupId;
... // defining constants like msg
const participators = admin.database().ref('/groups/' + groupId + '/users').once('value');
let getDeviceTokensPromise = []
for (let part in participators) {
getDeviceTokensPromise.push(admin.database().ref('/users/' + part + '/notificationtoken')).once('value');
}
return Promise.all([getDeviceTokensPromise, participators]).then(results => {
const tokensSnapshot = results[0];
const follower = results[1];
// Check if there are any device tokens.
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
console.log('There are', tokensSnapshot.numChildren(), 'tokens to send notifications to.');
console.log('Fetched follower profile', follower);
// Notification details.
const payload = {
notification: {
title: 'New meeting!',
body: msg
}
};
// Listing all tokens.
const tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
// For each message check if there was an error.
...
So I guess my mistake must be in the first few lines as all the rest follows this code (I left out the unimportant bits)... Here is my firebase architecture:
The groups branch of the firebase database
One user under the branch users
Regards
Your code is fine. Just change the following
const participators = admin.database().ref('/groups/' + groupId + '/users').once('value');
and
getDeviceTokensPromise.push(admin.database().ref('/users/' + part + '/notificationtoken')).once('value');
to these :-
const participators = admin.database().ref(`/groups/${groupId}/users`).once('value');
and
getDeviceTokensPromise.push(admin.database().ref(`/users/${part}/notificationtoken`)).once('value');
Also, make sure that you use `` and not ' ' inside the ref part.

Categories

Resources