I have chat app with firebase database and Firebase cloud messaging. I can send firebase notification via console but in real scenario it should be automatic. To make automatic notification,My friend wrote Index.js (Added in cloud functions) file for me but its not sending notifications.
As per our logic function should trigger whenever there is any new entries (in any node or in any room) and fetch these values by firebase function and make post request to FCM server to make notification to receiver device (get value of receiver device from token_To).
Message
Message_From
Time
Type
token_To
Index.js
var functions = require('firebase-functions');
var admin = require('firebase-admin');
var serviceAccount = require('./demofcm-78aad-firebase-adminsdk-4v1ot-2764e7b580.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://demofcm-78aad.firebaseio.com/"
})
// // 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.setUserNode = functions.auth.user().onCreate(event => {
// ...
});
exports.notifyMsg = functions.database.ref('/{chatroom}/{mid}/')
.onWrite(event => {
if (!event.data.val()) {
return console.log('Message Deleted');
}
const getDeviceTokensPromise = admin.database().ref('/{chatroom}/{mid}/token_to').once('value');
return Promise.all([getDeviceTokensPromise]).then(results => {
const tokensSnapshot = results[0];
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
const payload = {
notification: {
title: 'You have a new Message!',
body: event.data.val().Message
}
};
const tokens = Object.keys(tokensSnapshot.val());
return admin.messaging().sendToDevice(tokens, payload).then(response => {
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
});
});
Firebase function Log
How can i fetch above mentioned values of any newly added node in same room(9810012321-9810012347) or any other room(9810012321-9810012325) from database and send it to FCM to make notification
Thanks in Advance.
What i did is created a Message node and I believe doing this by users key. ie, having the receiver(toId) and sender (fromId) key to send the notification.
Hope it helps.
exports.sendMessageNotification = functions.database.ref('/messages/{pushId}')
.onWrite(event => {
let message = event.data.current.val();
console.log('Fetched message', event.data.current.val());
let senderUid = message.fromId;
let receiverUid = message.toId;
let promises = [];
console.log('message fromId', receiverUid);
console.log('catch me', admin.database().ref(`/users/${receiverUid}`).once('value'));
if (senderUid == receiverUid) {
//if sender is receiver, don't send notification
//promises.push(event.data.current.ref.remove());
return Promise.all(promises);
}
let messageStats = message.messageStatus;
console.log('message Status', messageStats);
if (messageStats == "read") {
return Promise.all(promises);
}
let getInstanceIdPromise = admin.database().ref(`/users/${receiverUid}/pushToken`).once('value');
let getSenderUidPromise = admin.auth().getUser(senderUid);
return Promise.all([getInstanceIdPromise, getSenderUidPromise]).then(results => {
let instanceId = results[0].val();
let sender = results[1];
console.log('notifying ' + receiverUid + ' about ' + message.text + ' from ' + senderUid);
console.log('Sender ', sender);
var badgeCount = 1;
let payload = {
notification: {
uid: sender.uid,
title: 'New message from' + ' ' + sender.displayName,
body: message.text,
sound: 'default',
badge: badgeCount.toString()
},
'data': {
'notificationType': "messaging",
'uid': sender.uid
}
};
badgeCount++;
admin.messaging().sendToDevice(instanceId, payload)
.then(function (response) {
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
});
});
const getDeviceTokensPromise = event.data.child('token_To');
should be there instated of getting data from database reference.
or
with fixed path without wildcard like below
const getDeviceTokensPromise = admin.database().ref('/${chatroom}/${mid}/token_to').once('value');
where chatroom and mid is variable which contain value
Second thing:
if (!tokensSnapshot.exists()) {
should in replace of
if (!tokensSnapshot.hasChildren()) {
third thing:
I am not sure about push notification tokenId but
is it required to do?
const tokens = Object.keys(tokensSnapshot.val());
may be we can use directly like below to send push notification
const tokens = tokensSnapshot.val();
You could store all device tokens in a node called tokens like in my example. Tokens could be an array if you would like one user to be able to get notifications on multiple devices. Anyway, store them by their UID.
This works for both Andriod and iOS.
Here is my code:
function loadUsers() {
let dbRef = admin.database().ref('/tokens/' + recieveId);
console.log(recieveId)
let defer = new Promise((resolve, reject) => {
dbRef.once('value', (snap) => {
let data = snap.val();
console.log("token: " + data.token)
//userToken = data.token
resolve(data.token);
}, (err) => {
reject(err);
});
});
return defer;
}
Next we create the notification. I created a lastMessage node to capture just the last message sent in the chat. It is just updated every time a new message is sent in a chat between two users. Makes it easy to get the value. Also makes it easy to show the message on the Conversations screen where there is a list of users who are in a conversation with the current user.
exports.newMessagePush =
functions.database.ref('/lastMessages/{rcId}/{sendId}').onWrite(event => {
if (!event.data.exists()) {
console.log("deleted message")
return;
}
recieveId = event.params.rcId
//let path = event.data.adminRef.toString();
// let recieveId = path.slice(53, 81);
return loadUsers().then(user => {
console.log("Event " + event.data.child("text").val());
let payload = {
notification: {
title: event.data.child("name").val(),
body: event.data.child("text").val(),
sound: 'default',
priority: "10",
}
};
return admin.messaging().sendToDevice(user , payload);
});
});
To implement this logic on your current data structure, just change this line:
let dbRef = admin.database().ref('/tokens/' + recieveId);
and this line:
exports.newMessagePush =
functions.database.ref('/lastMessages/{rcId}/{sendId}').onWrite(event
=> {
to your token location:
let dbRef =
admin.database().ref('/${chatroom}/${mid}/token_to');
and your conversation location:
exports.notifyMsg = functions.database.ref('/{chatroom}/{mid}/')
.onWrite(event => {
Then just change the notification payload be the message you want to display and throw in your error handling on the end of the sendToDevice function, as you did in your code.
Hopefully you figured all this out already but if not maybe this will help you or others trying to use Cloud Functions for notifications.
let payload = {
notification: {
uid: sender.uid,
title: 'New message from' + ' ' + sender.displayName,
body: message.text,
sound: 'default',
badge: badgeCount.toString()
},
'data': {
'notificationType': "messaging",
'uid': sender.uid
}
};
There are two types of FCMs.
1) Data
2) Notification
For detailed overview : FCM Reference
You have to fix your payload for both FCMS. And for Data FCM you have to extract Data in your FCM Service (Client) and generate a push notification according to your need.
Related
I am making an android app to send and receive notification using cloud messaging I've tried this to get the child of the ref but none of them worked for me.
I've tried this:
const beforeData = change.before.val(); // data before the write
const afterData = change.after.val(); // data after the write
//get the user id of the person who sent the message
const senderIdbefore = beforeData.val()
const senderIdafter = afterData.val()
const userid = senderIdbefore.user_id
And this:
const senderId = change.ref.parent.child('user_id').val();
but none of them worked for me.
Here is the code that I am working on:
const functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp();
exports.sendNotification =
functions.database.ref('/messages/{userId}/{messageId}').onWrite((change,
context) => {
//get the userId of the person receiving the notification because we need
to get their token
const receiverId = context.params.userId;
console.log("receiverId: ", receiverId);
const beforeData = change.before.val(); // data before the write
const afterData = change.after.val(); // data after the write
//get the user id of the person who sent the message
const senderIdbefore = beforeData.val()
const senderIdafter = afterData.val()
const userid = senderIdbefore.user_id
//
console.log("senderId: ", senderIdbefore);
console.log("senderId: ", senderIdafter);
console.log("senderId: ", user_id);
//get the message
const message = change.data.child('message').val();
console.log("message: ", message);
//get the message id. We'll be sending this in the payload
const messageId = context.params.messageId;
console.log("messageId: ", messageId);
//query the users node and get the name of the user who sent the message
return admin.database().ref("/users/" + senderId).once('value').then((snap,context) => {
const senderName = context.child("name").val();
console.log("senderName: ", senderName);
//get the token of the user receiving the message
return admin.database().ref("/users/" + receiverId).once('value').then((snap,context) => {
const token = context.child("messaging_token").val();
console.log("token: ", token);
//we have everything we need
//Build the message payload and send the message
console.log("Construction the notification message.");
const payload = {
data: {
data_type: "direct_message",
title: "New Message from " + senderName,
message: message,
message_id: messageId,
}
};
return admin.messaging().sendToDevice(token, payload)
.then(function(response) {
console.log("Successfully sent message:", response);
return null
})
.catch(function(error) {
console.log("Error sending message:", error);
});
});
});
});
some of the errors I got:
TypeError: Cannot read property 'child' of undefined
at exports.sendNotification.functions.database.ref.onWrite
(/srv/index.js:14:32)
TypeError: Cannot read property 'val' of null
at exports.sendNotification.functions.database.ref.onWrite
(/srv/index.js:17:36)
Though I am not much of a web developer but I wrote some cloud functions in few of my apps. So what I see wrong here is (snap,context) is not the signature of once() (I am not completely sure). Maybe that's the reason it is getting null value. You should change it to snap(or whatever, say snapshot) and context.child("name").val() to snap.child("name").val() etc and see if it works.
Also, I wrote a similar script for my chatting app, you can check it here
const functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp();
exports.sendNotification =
functions.database.ref('/messages/{userId}/{messageId}').onWrite((change, context)
=>
{
//get the userId of the person receiving the notification because we need to get
their token
const receiverId = context.params.userId;
console.log("receiverId: ", receiverId);
Here I used "after" to get the user_id from the message,
instead of change.after.ref.parent.child here is the correct code
const senderId = change.after.child('user_id').val();
console.log("senderId: ", senderId);
//get the message
const message = change.after.child('message').val();
console.log("message: ", message);
//get the message id. We'll be sending this in the payload
const messageId = context.params.messageId;
console.log("messageId: ", messageId);
//query the users node and get the name of the user who sent the message
return admin.database().ref("/users/" + senderId).once('value').then((snap,context)
=> {
const senderName = snap.child("name").val();
console.log("senderName: ", senderName);
//const senderName = context.child("name").val();
//console.log("senderName: ", senderName);
//get the token of the user receiving the message
return admin.database().ref("/users/" +
receiverId).once('value').then((snap,context) => {
const token = snap.child("messaging_token").val();
console.log("token: ", token);
//we have everything we need
//Build the message payload and send the message
console.log("Construction the notification message.");
const payload = {
data: {
data_type: "direct_message",
title: "New Message from " + senderName,
message: message,
message_id: messageId,
}
};
return admin.messaging().sendToDevice(token, payload)
.then(function(response) {
console.log("Successfully sent message:", response);
return null
})
.catch(function(error) {
console.log("Error sending message:", error);
});
});
});
});
I have a Cloud Function setup on Firebase that involved checking different parts of the Firestore Database and then sending a message via Cloud Messaging
Below is the JavaScript for the function in question:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().Firebase);
var db = admin.firestore();
exports.newMemberNotification = functions.firestore
.document('Teams/{teamId}/Waitlist/{userId}').onDelete((snap, context) => {
// get the user we want to send the message to
const newValue = snap.data();
const teamidno = context.params.teamId;
const useridno = newValue.userID;
//start retrieving Waitlist user's messaging token to send them a message
var tokenRef = db.collection('Users').doc(useridno);
tokenRef.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
} else {
const data = doc.data();
//get the messaging token
var token = data.messaging_token;
console.log("token: ", token);
//reference for the members collection
var memberRef = db.collection('Teams/'+teamidno+' /Members').doc(useridno);
memberRef.get()
.then(doc => {
if (!doc.exists){
console.log('user was not added to team. Informing them');
const negPayload = {
data: {
data_type:"team_rejection",
title:"Request denied",
message: "Your request to join the team has been denied",
}
};
return admin.messaging().sendToDevice(token, negPayload)
.then(function(response){
console.log("Successfully sent rejection message:", response);
return 0;
})
.catch(function(error){
console.log("Error sending rejection message: ", error);
});
} else {
console.log('user was added to the team. Informing them')
const payload = {
data: {
data_type: "team_accept",
title: "Request approved",
message: "You have been added to the team",
}
};
return admin.messaging().sendToDevice(token, payload)
.then(function(response){
console.log("Successfully sent accept message:", response);
return 0;
})
.catch(function(error){
console.log("Error sending accept message: ", error);
});
}
})
.catch(err => {
console.log('Error getting member', err);
});
}
return 0;
})
.catch(err => {
console.log('Error getting token', err);
});
return 0;
});
The issues I have with this are:
The code runs and only sometimes actually checks for the token or sends a message.
the logs show this error when the function runs: "Function returned undefined, expected Promise or value " but as per another Stack Oveflow posts, I have added return 0; everywhere a .then ends.
I am VERY new to node.js, javascript and Cloud Functions so I am unsure what is going wrong or if this is an issue on Firebase's end. Any help you can give will be greatly appreciated
As Doug said, you have to return a promise at each "step" and chain the steps:
the following code should work:
exports.newMemberNotification = functions.firestore
.document('Teams/{teamId}/Waitlist/{userId}').onDelete((snap, context) => {
// get the user we want to send the message to
const newValue = snap.data();
const teamidno = context.params.teamId;
const useridno = newValue.userID;
//start retrieving Waitlist user's messaging token to send them a message
var tokenRef = db.collection('Users').doc(useridno);
tokenRef.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
throw 'No such document!';
} else {
const data = doc.data();
//get the messaging token
var token = data.messaging_token;
console.log("token: ", token);
//reference for the members collection
var memberRef = db.collection('Teams/' + teamidno + '/Members').doc(useridno);
return memberRef.get()
}
})
.then(doc => {
let payload;
if (!doc.exists) {
console.log('user was not added to team. Informing them');
payload = {
data: {
data_type: "team_rejection",
title: "Request denied",
message: "Your request to join the team has been denied",
}
};
} else {
console.log('user was added to the team. Informing them')
payload = {
data: {
data_type: "team_accept",
title: "Request approved",
message: "You have been added to the team",
}
};
}
return admin.messaging().sendToDevice(token, payload);
})
.catch(err => {
console.log(err);
});
});
So I have a functionality in my app wherein a user can post or delete any alert. If a new alert is posted other users must get a notification regarding it. Firebase push notification works good when a new data is added to the database but if a post has been deleted (dataRef.child(root_child).removeValue();) it still sends notification to the user which is not required. How to handle this situation?
index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotificationAlert = functions.database.ref(`AlertPost/{pushId}`).onWrite(event => {
const getDeviceTokensPromise = admin.database().ref(`/Token/token_no`).once('value');
const getBody=admin.database().ref(`/AlertPost`).once('value');
var title_input='You have a new Alert';
var contentAlert = event.data.val();
var body_input=contentAlert.description;
//const tokensSnapshot = results[0];
return Promise.all([getDeviceTokensPromise,getBody]).then(results => {
const tokensSnapshot = results[0];
const notify=results[1];
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.');
var contentAlert = event.data.val();
// Notification details.
const payload = {
data: {
title: title_input,
body: body_input
//icon: follower.photoURL
},
notification: {
title: title_input,
body: body_input
}
};
const tokens = Object.keys(tokensSnapshot.val());
//token_send(admin,tokensSnapshot,tokens,payload,title_input);
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
console.log("Successfully sent message:", response);
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
});
});
You could check if the previous value of the event DataSnapshot is already deleted. Check this documentation for more information.
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original').onWrite((event) => {
// Only edit data when it is first created.
if (event.data.previous.exists()) {
return;
}
// Exit when the data is deleted.
if (!event.data.exists()) {
return;
}
});
You could also check this SO post for reference.
I have a firebase database which has two firebase nodes in the same level. One named NotificaationKey and one named requests. I have an onUpdate cloud function on requests/status field. When the node gets updated I get a field from requests node and use it to retrieve data from the NotificationKey node, but when I access the Notification node I get the following response instead of the data I want. It is printing following log.
Following is the code how I access the database. on onUpdate function.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.notifyUser = functions.database.ref('/requests/{requestId}/status')
.onUpdate(event => {
const adminRefVar = event.data.adminRef.root;
return event.data.adminRef.parent.child('requestorId').once('value').then((snapshot)=>{
var uuid = snapshot.val();
console.log("Data : "+uuid );
return adminRefVar.child('/NotificationKey').orderByChild('uuid').equalTo(uuid)
.once('value').then((snapshot2)=>{
console.log("data: " + snapshot2);
for(var i in snapshot2)
{
console.log("data in snap: "+snapshot2[i]);
}
return snapshot2;
});
});
});
I can't figure out what I'm doing wrong and I'm new to node.js and firebase functions. Thank you in advance.
I was able to access a different node by below code. Hope it would help someone.
exports.notifyUser = functions.database.ref('/requests/{requestId}/status').onUpdate(event => {
const adminRefVar = event.data.ref.firestore;
return event.data.adminRef.parent.child('requestorId').once('value').then((snapshot)=>{
var uuid = snapshot.val();
return admin.database().ref('NotificationKey').orderByChild('uuid').equalTo(uuid).once('value').then(snapshot => {
snapshot.forEach(function (childSnapshot) {
var value = childSnapshot.val();
console.log("notification key : " + value.notificationKey);
const payload = {
notification:{
title:'CLUTS request status has been updated',
body: 'Your ride share request has been '+event.data.val(),
badge: '1',
sound: 'default'
}
};
var userToken;
userToken = value.notificationKey;
return admin.messaging().sendToDeviceGroup(userToken, payload)
.then(function(response) {
console.log("Successfully sent message:", response);
return 0;
})
.catch(function(error) {
console.log("Error sending message:", error);
return 0;
});
});
return snapshot;
});
});
});
I have done authentication trigger, it's working fine. If someone delete their account I need to send to notification "this user deleted his account (email)" like that. Here is my code
const functions = require('firebase-functions')
//initialize the app
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
const ref = admin.database().ref()
//create user account function
exports.createUserAccount = functions.auth.user().onCreate(event => {
const uid = event.data.uid
const email = event.data.email
const newUserRef = ref.child(`/UserNotify/${uid}`)
return newUserRef.set({
email: email
})
})
//delete user account function
exports.cleanupUserData = functions.auth.user().onDelete(event => {
const uid = event.data.uid
const userRef = ref.child(`/UserNotify/${uid}`)
return userRef.update({isDeleted: true})
})
function sendNotification() {
console.log("Successfully sent");
var payload = {
notification: {
title: "User get deleted",
body: "sample#gmail.com"
}
};
admin.messaging().sendToDeveice(payload)
.then(function (response) {
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
})
}
You may have a typing error
admin.messaging().sendToDevice() and not sendToDeveice
check: https://firebase.google.com/docs/cloud-messaging/admin/send-messages