I am using firebase cloud functions to send a user push notifications. I dont understand JS well but I would like to be able to auto-increment the apps badge number through the notification payload and increase the number by 1 for each notification recieved. This is what I have now. I have read the documentation for firebase but I dont think I have enough JS understanding to figure out what they are describing.
exports.sendPushNotificationLikes = functions.database.ref('/friend-like-push-notifications/{userId}/{postId}/{likerId}').onWrite(event => {
const userUid = event.params.userId;
const postUid = event.params.postId;
const likerUid = event.params.likerId;
if (!event.data.val()) {
return;
}
// const likerProfile = admin.database().ref(`/users/${likerUid}/profile/`).once('value');
const getDeviceTokensPromise = admin.database().ref(`/users/${userUid}/fcmToken`).once('value');
// Get the follower profile.
const getLikerProfilePromise = admin.auth().getUser(likerUid);
return Promise.all([getDeviceTokensPromise, getLikerProfilePromise]).then(results => {
const tokensSnapshot = results[0];
const user = results[1];
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
const payload = {
notification: {
title: 'New Like!',
body: '${user.username} liked your post!',
sound: 'default',
badge: += 1.toString()
}
};
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.
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);
});
});
});
Thanks in advance for any help
I'm guessing this is what the issue is:
const payload = {
notification: {
title: 'New Like!',
body: '${user.username} liked your post!',
sound: 'default',
badge: += 1.toString()
}
};
Assume you have a notification count property available in your schema say notificationCount then you can do this:
const payload = {
notification: {
title: 'New Like!',
body: `${user.username} liked your post!`,
sound: 'default',
badge: Number(notificationCount++) // => notificationCount + 1
}
};
Also on this body: '${user.username} liked your post!', this will be saved as "user.username like your post!". This is not the behaviour you want, what you should be doing is this:
body: `${user.username} liked your post!`
Assuming this is the line in question:
badge: += 1.toString()
Careful of type conversion assumptions. Adding "1" + "1" will give you "11", not "2". Why not try something like:
badge: `${targetUser.notificationCount + 1}`
This is assuming the notificationCount is a key in your schema, and that it is typed as a string. You will need to persist the target user's notification count somewhere so it can be incremented when a new notification comes in. It could also be an integer and then the string interpolation is unnecessary, i.e.:
badge: targetUser.notificationCount + 1
Also, be aware that your string interpolation here needs to be wrapped in backticks instead of single quotes, i.e.:
body: `${user.username} liked your post!`
I can't tell how the interactions are mapped in your database. This approach requires persisting and updating the notification count of the target user.
Related
I'm building a flatten functionality using flatten() from Loadash.
This is required to have an array of objects containing messages which need to be sent it out. Email templates are to be passed to another service which will be sent out to users the email.
The issue is with the following code
const messages = flatten(
input.map(async (recipientObject) => {
// Validate input
const { documentId, studyId, recipientDetails } = recipientObject;
if (isEmpty(studyId) || !documentId || !recipientDetails) {
log.error('Missing required input variables!');
return null;
}
const { id: recipientId, email, phone, locale } = recipientDetails;
if (isEmpty(email) && isEmpty(phone)) {
log.error(
`Email|Phone for recipient ${recipientId} not found. Invitation can not be sent!`,
{
recipientId,
documentId,
}
);
return null;
}
const studyName = await getStudyName(studyId);
// Prepare the messages to be sent for each recipient and append them to messages array
const msgs = [];
const title = `Get started on your eConsent for ${studyName}!`;
// TODO: change to new message recipientType
const recipientType = 'EXTERNAL_USER';
const originType = 'INVITATION';
if (email) {
const messageContent = getMessageLinkContent({
link: tokenLinks[recipientId].link,
locale,
type: 'EMAIL',
});
msgs.push({
type: 'EMAIL',
recipient: email,
title,
content: messageContent,
recipientType,
originType,
originId: tokenLinks[recipientId].tokenId,
extra: {
template: getContentTemplate({
templateName: 'invitation',
locale,
title,
messageContent,
log,
}),
},
});
}
if (!msgs || msgs.length < 1) {
log.error(
`No messages found for recipient: ${recipientId} on document: ${documentId}!`
);
return null;
}
log.info(
`${msgs.length} invitation messages prepared for recipient: ${recipientId} on document: ${documentId} and ready to be added to message-service`
);
log.info('INSIDE MESSAGES ---------------------');
log.info('MESGS %o', msgs); // Messages are present here!!!!
log.info('RETURN ---------------------');
return msgs;
}),
true
);
In the above snippet when I do a console log of msgs I see my messages and expect that will be also the result of the const messages
Instead when I'm doing a console of messages return this [{}] an empty obj.
I have no clue what's wrong with that messages.
Cannot spot an issue in my snippet which will allow me to understand why I have inside the messages are present but they are not returned.
Answering my question as I resolved the issue by doing as follow
const messages = await Promise.all(<everything_else>);
flatten(messages) // where I need it to use
This was empty as I had promises inside and using Promise.all fixed the issue in the end
I want to send a notification to a specific device so I write this function and its work right but I got undefined in the username
Logs output:
Get this
after: { '-LhjfwZeu0Ryr6jYRq5r': { Price: '888', date: '2019-6-19', description: 'Ghh', id: 50, nameOfProblem: 'Vbh', providerName: 'Loy', providerService: 'Carpenter', statusInfo: 'Incomplete', time: '15:22', username:"devas" }}
And the username is undefined
Here is the function
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders')
.onWrite(async (snapshot, context) => {
const registrationTokens = "------";
const providerId = context.params.pid;
const userId = context.params.uid;
const event = context.params;
console.log("event", event);
console.log(`New Order from ${userId} to ${providerId}`);
const afterData = snapshot.after.val(); // data after the write
const username = snapshot.after.val().username;
console.log(afterData);
console.log(username);
const payload = {
notification: {
title: 'Message received',
body: `You received a new order from ${username} check it now! `,
sound: "default",
icon: "default",
}
};
try {
const response = await admin.messaging().sendToDevice(registrationTokens, payload);
console.log('Successfully sent message:', response);
}
catch (error) {
console.log('Error sending message:', error);
}
return null;
});
It looks like the code you wrote is meant to run when a new order is added to the database. But you've declared it to trigger like this:
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders')
.onWrite(async (snapshot, context) => {
This means that the code instead triggers whenever anything is written under the orders node for a user. To trigger only when an order is written under that orders node, define your trigger as:
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders/{orderid}')
.onWrite(async (snapshot, context) => {
The difference above is that the path now includes {orderid} meaning that it triggers one level lower in the tree, and your snapshot.after will no longer contain the -L level.
Since you actually only seem to care about when an order gets created, you can also only trigger on that (meaning your function won't get called when an order gets updated or deleted). That'd be something like this:
exports.sendPushR = functions.database.ref('/request/{pid}/{uid}/orders/{orderid}')
.onCreate(async (snapshot, context) => {
...
const afterData = snapshot.val();
const username = snapshot.val().username;
console.log(afterData);
console.log(username);
...
});
Here we again trigger on the lower-level in the JSON. But since we now trigger onCreate, we no longer have a before and after snapshot, and instead just do snapshot.val() to get the data that was just created.
Since the object you are retrieving has a generated member you could use a for-in loop to retrieve the value.
const object = snapshot.after.val()
for(const key in object) {
if (object.hasOwnProperty(key)) {
const element = object[key];
if(element.username) {
console.log(element.username);
break;
}
}
}
This is for using conditionals with Firebase Functions (node.js). I am a total noob when it comes to Javascript so please bear with me. I am trying to send push notifications with different payload (sound) depending on the category of the food ordered. I tried the following code but no notifications are sent. If I removed the if-else conditionals then notifications are sent. Not sure what I am doing wrong. The code compiled fine. Thanks guys. The log showed "ReferenceError: payload is not defined
at admin.database.ref.once.then.allToken (/user_code/index.js:60:58)
at process._tickDomainCallback (internal/process/next_tick.js:135:7)"
exports.sendRequestNotification = functions.database.ref('/Requests/{id}').onCreate((snap, context) => {
const snapShot = snap.val();
console.log(snapShot);
if (snap.child("RequestItemCategory").val() === 1) {
const payload = {
notification: {
title: 'Food Ordered',
body: 'Seat Number: ' + snap.child("Seat").val() + ' Request Item: ' + snap.child("RequestItem").val(),
sound: 'food.wav'
}
};
} else {
const payload = {
notification: {
title: 'New Request Received',
body: 'Seat Number: ' + snap.child("Seat").val() + ' Request Item: ' + snap.child("RequestItem").val(),
sound: 'drinks.wav'
}
};
}
return admin.database().ref('/Tokens').once('value').then(allToken => {
const token = Object.keys(allToken.val());
console.log(token);
return admin.messaging().sendToDevice(token, payload)
});
});
A const is only visible within the block where it was declared. You're declaring two different const payload vars embedded in a block that's not accessible to where you're trying to use it. You'll need to declare it like this instead:
let payload;
if (...) {
payload = ...
} else {
payload = ...
}
Then you can use payload immediately after the conditional, because it will be in scope for your call to the admin SDK.
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.
Possible duplicate. Not sure.
connections: {
connectionID : {
userID: true,
anotherUserID: true
},
users: {
userID : {
deviceToken : "tokenID",
name : "Display Name"
},
anotherUserID : {
deviceToken : "tokenID",
name : "Display Name"
}
}
and so on and so forth.
This is my index.js:
exports.sendConnectionNotification = functions.database.ref('/connections/{connectionID}/{userID}').onWrite(event => {
const parentRef = event.data.ref.parent;
const userID = event.params.userID;
const connectionID = event.params.connectionID;
// If un-follow we exit the function.
if (!event.data.val()) {
return console.log('Connection', connectionID, 'was removed.');
}
// Get the list of device notification tokens.
const getDeviceTokensPromise = admin.database().ref('/users/${userID}/deviceToken').once('value');
// Get the user profile.
const getUserProfilePromise = admin.auth().getUser(userID);
and it continues. I am getting this error in my logcat:
Error: Firebase.child failed: First argument was an invalid path: "/users/${userID}/deviceToken". Paths must be non-empty strings and can't contain ".", "#", "$", "[", or "]"
at Error (native)
at Ge (/user_code/node_modules/firebase-admin/lib/database/database.js:111:59)
at R.h.n (/user_code/node_modules/firebase-admin/lib/database/database.js:243:178)
at Fd.h.gf (/user_code/node_modules/firebase-admin/lib/database/database.js:91:631)
at exports.sendConnectionNotification.functions.database.ref.onWrite.event (/user_code/index.js:31:51)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:35:20
at process._tickDomainCallback (internal/process/next_tick.js:129:7)
I do not understand why Firebase is not able to reach the node. Clearly, my path is valid. Where am I going wrong? Sorry, I happen to start learning Firebase Functions just today.
**EDIT 1: **
After replacing:
const getDeviceTokensPromise = admin.database().ref('/users/${userID}/deviceToken').once('value');
with
const getDeviceTokensPromise = admin.database().ref(`/users/${userID}/deviceToken`).once('value');
I have gotten a new error. My console log displays:
There are no notification tokens to send to.
Here is my full index.js:
// // Create and Deploy Your First Cloud Functions
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
/**
* Triggers when a user gets a new follower and sends a notification.
*
* Followers add a flag to `/followers/{followedUid}/{followerUid}`.
* Users save their device notification tokens to `/users/{followedUid}/notificationTokens/{notificationToken}`.
*/
exports.sendConnectionNotification = functions.database.ref('/connections/{connectionID}/{userID}').onWrite(event => {
const parentRef = event.data.ref.parent;
const userID = event.params.userID;
const connectionID = event.params.connectionID;
// If un-follow we exit the function.
if (!event.data.val()) {
return console.log('Connection', connectionID, 'was removed.');
}
// Get the list of device notification tokens.
const getDeviceTokensPromise = admin.database().ref(`/users/${userID}/deviceToken`).once('value');
// Get the user profile.
const getUserProfilePromise = admin.auth().getUser(userID);
return Promise.all([getDeviceTokensPromise, getUserProfilePromise]).then(results => {
const tokensSnapshot = results[0];
const user = 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 user profile', user);
// Notification details.
const payload = {
notification: {
title: `${user.userNickName} is here!`,
body: 'You can now talk to each other.'
}
};
// 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.
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 can do use (`) instead of (') as i was also having same problem and solved by using this.
thanks
Change
const getDeviceTokensPromise = admin.database().ref('/users/${userID}/deviceToken').once('value');
to
const getDeviceTokensPromise = admin.database().ref('/users/' + userID + '${userID}/deviceToken').once('value');
'/users/${userID}/deviceToken' is not a valid path.
but '/users/123456/deviceToken' where 123456 represents the user ID, is.
maybe you are using single quote instead of back-ticks.
https://developers.google.com/web/updates/2015/01/ES6-Template-Strings
so the path is not concatenated in a right way.