Firebase cloud function doesn't send push notification with async - node.js

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.

Related

use webhook to trigger a get request in twilio

I am building a chat component. This component uses Redux. My Chat component has a useEffect that dispatch(listConversations()) and retrieves the conversation along with its messages.
When someone sends a text to my chatApp twilio automatically updates the conversation with the new text message. The problem is, in order for me to see the message in my app I have to hit refresh and Execute a useEffect that dispatch(listConversations()).
My solution im thinking is a webhook?
I set up Ngrok successfully on my computer to create a url that points to my localhost backend for development.
http://ngrok.io ----> localhost:5000
I have successfully created a webhook for my conversation like so
await subClient.conversations.services(convoServiceSid)
.conversations(newConvoId)
.webhooks
.create({
'configuration.method': 'GET',
'configuration.filters': ['onMessageAdded', 'onConversationRemoved'],
'configuration.url': ' http://3de1-a30-00-8547.ngrok.io/conversation-messages',
target: 'webhook'
})
.then(webhook => console.log(webhook.sid));
My thought is that this will trigger a 'GET' request to my backend /conversation-messages and this will trigger the function I have below. But what I'm not sure of is if the GET comes from twilio, will the response go to my frontend or back to the Webhook server in this case twilio?
When I refresh my page this function below is called, But when I setup my webhook it doesnt get called. Am I doing this wrong?
const listConversationMessages = async (req, res) => {
console.log("list conversation Messages triggered")
console.log(req.body)
const { subActServiceSid, subActAuthToken, convoServiceSid} = req.user
const subClient = require('twilio')(subActServiceSid, subActAuthToken)
const allConversationSids = []
const allMessages = []
const sortedMessages = []
const options = { weekday: 'long'}
const monthOptions = { month: 'long'}
await subClient.conversations.services(convoServiceSid)
.conversations
.list()
.then(conversations => conversations.forEach(c => allConversationSids.push(c.sid))); // conversation possibly has "read" property?
await Promise.all(allConversationSids.map( async (convo) => {
await subClient.conversations.services(convoServiceSid)
.conversations(convo)
.messages
.list({limit: 20})
.then(messages => messages.forEach(m => allMessages.push({"messagesId": m.conversationSid, "participantSid": m.participantSid, "message": m.body, "time": { "day": new Intl.DateTimeFormat('en-US', options).format(m.dateCreated.getDay()), "hour": m.dateCreated.getHours() + ":" + m.dateCreated.getMinutes(), "date": new Intl.DateTimeFormat('en-US', monthOptions).format(m.dateCreated.getMonth()) + " " + m.dateCreated.getDate() + ", " + m.dateCreated.getFullYear()}})));
}))
let response
if(allMessages.length < 1 ) {
response = [{"messagesId": 1 , "content": [{ "participantSid": 2, "message": "test"} ]}]
} else {
response = allMessages
}
result = await response.reduce(function (accumulator, indexOfObject) {
accumulator[indexOfObject.messagesId] = accumulator[indexOfObject.messagesId] || [];
accumulator[indexOfObject.messagesId].push(indexOfObject); //it generates a new property a.messagesId with an array if it does not exist (the value of not existing properties is undefined). if exist, it assign itself
return accumulator;
}, Object.create({}));
sortedMessages.push(result)
const finalResult = Object.entries(sortedMessages[0]).map(([id, content]) => ({ id, content }))
res.json(finalResult)
}```
DETAILS....
My problem was when I declare the webhook 'configuration.url: '// I have a space before the http://'
Once I remove the space It works.

Cloud functions for Firebase FCM notifications to multiple users

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);
}
});

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,
});

FCM : getting ' Error: data must be a non-null object' error?

I am trying to send a push notification via Firebase cloud messaging. I am using Firebase admin sdk to send push notification in fcm . I am using nodejs
When I am trying to send a push msg , ...
I am getting this error
{
code: 'messaging/invalid-payload',
message: 'data must be a non-null object' },
codePrefix: 'messaging'
}
My code :
const admin = require('firebase-admin');
const serviceAccount = require(`${__dirname}/fet_firebase.json`);
function sendPushNot(to, body, sendId, type) {
const registrationToken = to;
const notification = {};
let message = { };
const pbody = { body };
if (type === 'User') {
pbody.userId = sendId;
notification.userId = sendId;
notification.title = 'New user Follwed';
}
if (type === 'Post') {
pbody.postId = sendId;
notification.postId = sendId;
notification.title = 'Post Liked';
}
if (type === 'Room') {
pbody.roomId = sendId;
notification.roomId = sendId;
notification.title = 'New Chat messsage';
}
message = {
data: JSON.stringify(pbody),
token: registrationToken,
notification
};
console.log('messgae',message);
admin.messaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent cloud message:', response);
})
.catch((error) => {
console.log('Error sending cloud message:', error);
});
}
I thought that body is null
But the console output of console.log('messgae',message); is ::
{
data:
'{"body":"pankaj Liked Your Post","postId":"5ed1055ddf0efd2a42f6a28a"}',
token:
'f2umP-jfQyeM1suN77zz7-:APA91bHKzfqfRnmuBom2PIDB8cCPwZtq28JCWLSi1OMPO55JRzyhFZJpTkkNyDu_StTYID-scu-grejaxxn3d4iR6Xidz9-JCk_h-bRsdGHe8nzMrIVsc8vZDFgayiFgJrJ53DaDzb9b',
notification: { postId: 5ed1055ddf0efd2a42f6a28a, title: 'Post Liked'
}
}
So the body is not null
But I am getting data must be a non-null object' error ..
Why?
I fixed this by wrapping the stringified object with curly braces
data : { data: JSON.stringify(object) } // correct
data : JSON.stringify(object) // will result to the described error.
Data must be a non-null object. Above code sample is passing a string. Just remove the JSON.stringify() part.

Error: 5 NOT_FOUND: Requested entity was not found on LongRunningRecognize

I'm trying to transcribe an audio file with the node.js client Google Speech to Text and Google Cloud Function.
Unfortunately I get this error :
Error: 5 NOT_FOUND: Requested entity was not found
I supposed it comes from authentification problem, but i am not sure.
First, I tried without credentials assuming that GCF will use ADC (Application Default Credentials).
After, I added client_email et private_key from service account to SpeechClient options param, but it didn't work.
I added projectId and keyFilename... not better.
Maybe it isn't the good way... I have no idea !
Here is my code. Thanks for your help.
const audioFilename = 'gs://' + outputBucket.name + '/' + event.data.name;
const request = {
"config": {
"enableWordTimeOffsets": false,
"languageCode": "fr-FR",
"encoding":"FLAC"
},
"audio": {
"uri": audioFilename
}
}
const options = {
credentials :{
projectId: 'xxxxxx',
keyFilename: './xxxxx.json',
client_email:'xxxx#xxxxx',
private_key:'xxxxxxxxx'
}
};
const client = new speech.SpeechClient(options);
client
.longRunningRecognize(request)
.then(data => {
const response = data[0];
const operation = response;
operation.on('progress', (metadata, apiResponse) => {
console.log(JSON.stringify(metadata))
});
// Get a Promise representation of the final result of the job
return operation.promise();
})
.then(data => {
const [response] = data[0];
const content = response.results
.map(result => result.alternatives[0].transcript)
.join('\n');
console.log(`Transcription: ${content}`);
resolve(content);
})
.catch(err => {
reject(err);
});

Resources