Firebase Cloud Trigger Function - How can I send notifications to ALL users? - javascript

I am totally new to Firebase Cloud Functions (2 days exposure). I am trying to send notifications to ALL users of my app when Firebase Database detects that new data has been added. Here is what I have so far:
exports.sendNotification = functions.database.ref("/uploads/{pushId}").onCreate(event => {
const snapshot = event.data;
var str = snapshot.child("name").val();
console.log(str);
if (snapshot.previous.val()) {
return 0;
}
if (snapshot.val().name != "ADMIN") {
return 0;
}
const text = snapshot.val().text;
const payload = {
notification: {
title: snapshot.name,
body: ""
}
}
//return admin.messaging().sendToDevice(tokens, payload);
});
I know the code is in a state of mess right now, its due to a couple of copy and testing from various tutorial sites. I can succesfully get the data's name from console.log but am unable to send notification to ALL users.
I am aware that most use tokens and device IDs. But is there any easier way to send to each and every one of my users ? And do I need to add any java codes for my app for this notification to work ?
EDIT 1:
Following Peter's suggestions, I have updated my functions:
exports.sendNotification = functions.database.ref("/uploads/{pushId}").onCreate(event => {
const snapshot = event.data;
var str = snapshot.child("name").val();
console.log(str);
if (snapshot.previous.val()) {
console.log("RETURN 1");
return 0;
}
const payload = {
notification: {
title: str,
body: ""
}
}
return admin.messaging().sendToTopic("Users", payload)
.then(function(response){
console.log("Notification sent ", response);
})
.catch(function(error){
console.log("Error sending notification: ", error);
});
});
I have also added the following java to my code:
FirebaseMessaging.getInstance().subscribeToTopic("Users");
Problem I am having now is that on the Firebase console it says that the notification is being sent successfully, but on my phone I am not receiving anything. Is it a must to use the onMessageReceived method in my case ?
One thing I noticed is that the above statement is being called each time the app launches. Will this effect the result in any way ?

I think the easiest one is topics, you can subscribe all the users to a single topic and then send a notification to that topic. You have to change your return statement to this:
return admin.messaging().sendToTopic("Cat", payload);
So now all the users subscribed to the topic "Cat" will receive the notification. Of course you can change the topic also to anything you want..
To subscribe users to a topic, all you need to do is write this:
FirebaseMessaging.getInstance().subscribeToTopic("Cat"); //in java code
check this for more info topic messaging

For people seeking a more direct answer
It is not possible to send to all users from firebase functions unless you are sending to targetted users. These would be users who have subscribed to a certain topic The proceeding function would be
sendToTopic(topic, payload); //for topic
Alternatively, You can use the console GUI that will send to every user even if one is not subscribed to a topic provided you don't specify a topic or send to device option

Related

Issues connecting data stream to users responses in Google Voice App

I am currently developing a voice agent to be used in a smart speaker where users will ask about some items that are being stored in a data stream. The ultimate goal is that users ask about items' names in the stream and google actions through voice will tell them the details about those items as presented in another column in the stream.
To do this, I linked a spreadsheet to Axios to stream the content of the spreadsheet as data to be read in a webhook in google actions. The link to the data stream is HERE.
Honestly, I am new to developing apps for google actions and new to javascript overall so I might be doing silly mistakes.
In the graphical interface for google actions, I am setting a type for the items I want the user to ask about.
Then, I set an intent to recognize the item as a data type and be able to send this to the webhook.
The cloud function in the webhook is as follows:
const { conversation } = require('#assistant/conversation');
const functions = require('firebase-functions');
require('firebase-functions/lib/logger/compat'); // console.log compact
const axios = require('axios');
const app = conversation({debug: true});
app.handle('getItem', async conv => {
const data = await getItem();
const itemParam = app.types.Item;
// conv.add("This test to see if we are accessing the webhook for ${itemParam}");
data.map(item => {
if (item.Name === itemParam)
agent.add('These are the datails for ${itemParam}. It is located in zone
${item.Zone}, at level ${item.Level}');
});
});
async function getItem() {
const res = await axios.get('https://sheetdb.io/api/v1/n3ol4hwmfsmqd');
console.log(res.data);
return res.data; // To use in your Action's response
}
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);
What the webhook is doing is getting the stream with the getItem function and then mapping the data to find the Name in the stream to match the item parameter (ItemParam) as identified by the user.
However, one of the main problems I have is that when trying to access the item from the user, I am using app.types.Item, but this does not work as when testing I get an error saying: "error": "Cannot read property 'Item' of undefined". I think what is happening is that I am not using the correct way to call the Item in the conversation app.
Also, I am not sure exactly how the linking to the database will work. In other works, I am not sure if
data.map(item => {
if (item.Name === itemParam)
agent.add('These are the datails for ${itemParam}. It is located in zone
${item.Zone}, at level ${item.Level}');
will work.
I have tried multiple things to solve but I am really struggling so any help with this would be really appreciated. Also, I know that I rushed to explain things, so please let me know if you need me to explain better or clarify anything.
Thank you
There are three points I am seeing that won't work.
First, app.types.Item is not the way to get this parameter. You should instead use conv.intent.params['Item'].resolved to get the user's spoken name.
Second, you are trying to use agent.add to include text, but there is no agent in your environment. You should instead be using conv.add.
Third, the text you are sending is not properly escaped between backticks ``. It is the backtick that allows you to use template literals.
Altogether your code can be rewritten as:
const { conversation } = require('#assistant/conversation');
const functions = require('firebase-functions');
require('firebase-functions/lib/logger/compat'); // console.log compact
const axios = require('axios');
const app = conversation({debug: true});
app.handle('getItem', async conv => {
const data = await getItem();
const itemParam = conv.intent.params['Item'].resolved;
data.map(item => {
if (item.Name === itemParam)
conv.add(`These are the datails for ${itemParam}. It is located in zone
${item.Zone}, at level ${item.Level}`);
});
});
async function getItem() {
const res = await axios.get('https://sheetdb.io/api/v1/n3ol4hwmfsmqd');
console.log(res.data);
return res.data; // To use in your Action's response
}
exports.ActionsOnGoogleFulfillment = functions.https.onRequest(app);

How to notify the front end that it needs to refresh after setting a custom claim with Firebase cloud functions onCreate listener

I'm trying to initialize a user upon registration with a isUSer role using custom claims and the onCreate listener. I've got it to set the correct custom claim but the front end is aware of it only after a full page refresh.
I've been following this article, https://firebase.google.com/docs/auth/admin/custom-claims?authuser=0#logic, to notify the front end that it needs to refresh the token in order to get the latest changes on the custom claims object, but to be honest I don't quite fully understand what's going on in the article.
Would someone be able to help me successfully do this with the firestore database ?
This is my current cloud function:
exports.initializeUserRole = functions.auth.user().onCreate(user => {
return admin.auth().setCustomUserClaims(user.uid, {
isUser: true
}).then(() => {
return null;
});
});
I've tried adapting the real-time database example provided in the article above to the firestore database but I've been unsuccessful.
exports.initializeUserRole = functions.auth.user().onCreate(user => {
return admin.auth().setCustomUserClaims(user.uid, {
isUser: true
}).then(() => {
// get the user with the updated claims
return admin.auth().getUser(user.uid);
}).then(user => {
user.metadata.set({
refreshTime: new Date().getTime()
});
return null;
})
});
I thought I could simply set refreshTime on the user metadata but there's no such property on the metadata object.
In the linked article, does the metadataRef example provided not actually live on the user object but instead somewhere else in the database ?
const metadataRef = admin.database().ref("metadata/" + user.uid);
If anyone could at least point me in the right direction on how to adapt the real-time database example in the article to work with the firestore database that would be of immense help.
If my description doesn't make sense or is missing vital information let me know and I'll amend it.
Thanks.
The example is using data stored in the Realtime Database at a path of the form metadata/[userID]/refreshTime.
To do the same thing in Firestore you will need to create a Collection named metadata and add a Document for each user. The Document ID will be the value of user.uid. Those documents will need a timestamp field named refreshTime.
After that, all you need to do is update that field on the corresponding Document after the custom claim has been set for the user. On the client side, you will subscribe to changes for the user's metadata Document and update in response to that.
Here is an example of how I did it in one of my projects. My equivalent of the metadata collection is named userTokens. I use a transaction to prevent partial database changes in the case that any of the steps fail.
Note: My function uses some modern JavaScript syntax that is being transpiled with Babel before uploading.
exports.initializeUserData = functions.auth.user().onCreate(async user => {
await firestore.collection('userTokens').doc(user.uid).set({ accountStatus: 'pending' })
const tokenRef = firestore.collection('userTokens').doc(user.uid)
const userRef = firestore.collection('users').doc(user.uid)
const permissionsRef = firestore.collection('userPermissions').doc(user.email)
await firestore.runTransaction(async transaction => {
const permissionsDoc = await transaction.get(permissionsRef)
const permissions = permissionsDoc.data();
const customClaims = {
admin: permissions ? permissions.admin : false,
hasAccess: permissions ? permissions.hasAccess : false,
};
transaction.set(userRef, { name: user.displayName, email: user.email, getEmails: customClaims.hasAccess })
await admin.auth().setCustomUserClaims(user.uid, customClaims)
transaction.update(tokenRef, { accountStatus: 'ready', refreshTime: admin.firestore.FieldValue.serverTimestamp() })
});
})

Getting id of the current user from Cloud Functions and Firebase

I am using google cloud functions to register push notifications through firebase. In my app, i have a notifications reference that changes for a current user whenever they get a new follower or like, etc. As of right now I am able to send the notification to the phone whenever that whole reference child changes
For example, if any single post is liked, then it will send a notification. What I need to do is observe the current user to only send the notification that single person.
Here is my JavaScript file
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendPushNotification = functions.database.ref('/notification/{id}').onWrite(event => {
const payload = {
notification: {
title: 'New message arrived',
body: 'come check it',
badge: '1',
sound: 'default',
}
};
return admin.database().ref('fcmToken').once('value').then(allToken => {
if (allToken.val()) {
const token = Object.keys(allToken.val());
return admin.messaging().sendToDevice(token, payload).then(response => {
});
}
});
});
I would like to replace this line:
functions.database.ref('/notification/{id}').onWrite(event => {
With this:
functions.database.ref('/notification/{id}').(The current user ID).onWrite(event => {
How do I get the current users id?
You seem very new to JavaScript (calling it JSON is sort-of a give-away for that). Cloud Functions for Firebase is not the best way to learn JavaScript. I recommend first reading the Firebase documentation for Web developers and/or taking the Firebase codelab for Web developer. They cover many basic JavaScript, Web and Firebase interactions. After those you'll be much better equipped to write code for Cloud Functions too.
Now back to your question: there is no concept of a "current user" in Cloud Functions. Your JavaScript code runs on a server, and all users can trigger the code by writing to the database.
You can figure out what user triggered the function, but that too isn't what you want here. The user who triggered the notification is not the one who needs to receive the message. What you want instead is to read the user who is the target of the notification.
One way to do this is to read it from the database path that triggered the function. If you keep the notifications per user in the database like this:
user_notifications
$uid
notification1: ...
notification2: ...
You can trigger the Cloud Function like this:
exports.sendPushNotification = functions.database.ref('/user_notification/{uid}/{id}').onWrite(event => {
And then in the code of that function, get the UID of the user with:
var uid = event.params.uid;
For Swift 3.0 - 4.0
You can do this:
import Firebase
import FirebaseAuth
class YourClass {
let user = Auth.auth().currentUser
let userID = user.uid
// user userID anywhere
}

Send FCM to all Android devices using Cloud Functions

Just trying to understand the process for sending a Firebase Cloud Message using Cloud Functions to notify all users who have my app installed on their phone. This would fire whenever a new event has been added at a particular branch, as follows:
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const payload = {
notification: {
title: 'New event added'
}
};
exports.bookingsChanged = functions.database.ref("/Events")
.onWrite(event => {
return admin.messaging().sendToDeviceGroup("latest_events", payload);
});
The above function I've uploaded doesn't appear to send the message to the Android device I'm using at all, despite setting up and testing FCM using the Firebase Console option to send messages. I've noticed there is little documentation for this at the moment, so any help would be greatly appreciated!
EDIT
I may've missed this, but I've replaced the string 'latest_events' with my Android application package name that I assume is required, as per the console to target a 'User Segment'.
Ended up solving this by waiting for a topic I had set up to appear in the Firebase Notifications dashboard. I then changed the following code to send to this topic directly:
return admin.messaging().sendToTopic("latest_events", payload);
I also found out that you have to provide a token when using 'sendToDevicegroup' after coming across the API documentation. Therefore, topics are more effective in my use case as I do not wish to obtain tokens to send to specific user devices.
Hope this helps someone who experiences a similar problem!
ADDITIONAL EDIT
If like me, you would like to alert users only of new events that have been added to a specific branch, typically including a push id, I've created the following code to implement this.
With a little help from the examples in the documentation, this will evaluate the number of records at the location compared to the previous location. Thus, this will only alert users of new child records that are added, rather than every time a record is edited and deleted.
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.bookingsChanged = functions.database.ref("/Bookings").onWrite(event
=> {
var payload = {
notification: {
title: "A new event has been added!"
}
};
if (event.data.previous.exists()) {
if (event.data.previous.numChildren() < event.data.numChildren()) {
return admin.messaging().sendToTopic("latest_events", payload);
} else {
return;
}
}
if (!event.data.exists()) {
return;
}
return admin.messaging().sendToTopic("latest_events", payload);
});

How to send user specific notification via Web push

I'm implementing web push on chrome and everything works well except that I don't know how to get user specific notification from the server. On send, everybody gets d same thing.
Is there a way I can pass endpoint ID to the latest notification request from service worker? Or how else can I do this?
Thanks.
There should be a data storage implemented of all subscribed clients on the server endpoint.txt that can be read and then message can be delivered to a specific user.
More on that part here: developer.mozilla.org/en/docs/Web/API/Push_API
Part of the function that sends broadcast message that you can modify to pick one client not all of them in for-loop: https://github.com/chrisdavidmills/push-api-demo/blob/gh-pages/server.js
if(obj.statusType === 'chatMsg') {
fs.readFile("endpoint.txt", function (err, buffer) {
var string = buffer.toString();
var array = string.split('\n');
for(i = 0; i < (array.length-1); i++) {
var subscriber = array[i].split(',');
webPush.sendNotification(subscriber[2], 200, obj.key, JSON.stringify({
action: 'chatMsg',
name: obj.name,
msg: obj.msg
}));
};
});
You can send data with a message to a user but you need to encrypt the payload data. This has been in Firefox for a while in a few versions and is in a few versions of Chrome.
Check out libraries like the following:
https://github.com/marco-c/web-push
https://github.com/GoogleChrome/web-push-encryption/
https://github.com/Minishlink/web-push

Categories