Problem with cloud function push notification - node.js

In my flutter firebase app, I am trying to configure push notification but I am finding it very challenging since I am very new to javascript,. The below codes, I am trying to send a notification based on a user activity item field(comment). If the comment field of the activity item in empty, it means the post of that activity item is being liked and if it is not empty, it means the post of that activity item is being commented on. Most of the code works from getting the user device token, saving it, and even console login snapshot of the data and sending the message to the device. But the problem is when the cloud function triggers and the message is being sent to the user device, the message body appears to be null, I am getting the hint that I am not writing the switch statement on the right value of the activity item(comment). I have been reviewing this some months now, Thank you
//This part of the cloud function code to listens to new document on
// the activity path
exports.onCreateActivityNotification = functions.firestore
.document('/activities/{userId}/userActivities/{userActivitiesId}')
.onCreate(async (snapshot, context) => {
console.log('activity notification created', snapshot.data());
//getting user connected to the activity
const userId = context.params.userId;
const createdActivityItem = snapshot.data();
const usersRef = admin.firestore().doc(`users/${userId}`);
const doc = await usersRef.get();
const androidNotificationToken =
doc.data().androidNotificationToken;
//checks if user has an androidnotification token
if(androidNotificationToken){
//sennds notification if users has a token
sendNotification(androidNotificationToken, createdActivityItem )
} else {
console.log('no notification token');
}
//function for sending the notification, I am very sure my problem is coming from this
//part of the code. I am not writing the switch statement on the right value. I think I
//need to get the exact fields of the activity item
function sendNotification(androidNotificationToken, userActivities)
{
let body;
switch (userActivities){
case userActivities.comment !== null:
body = `${userActivities.fromUserId} commented :
${userActivities.comment}`
break;
case userActivities.comment === null:
body = `${userActivities.fromUserId} liked your punch`
break;
default:
break;
}
//creates message for push notification
const message = {
notification: {body: body},
token: androidNotificationToken,
data: {recipient: userId},
};
//sends message with admin.messaging()
admin
.messaging()
.send(message)
.then(response => {
return console.log('message sent', response);
}).catch(error =>{
console.log('error sending message', error);
})
}
});
// This is the code to create the activity item for which triggers the cloud function
// just for reference
static void addActivityItem(
{String currentUserId, Post post, String comment}) {
if (currentUserId != post.authorId) {
activitiesRef.document(post.authorId).collection('userActivities').add({
'fromUserId': currentUserId,
'postId': post.id,
'seen': '',
'postImageUrl': post.imageUrl,
'comment': comment,
'timestamp': Timestamp.fromDate(DateTime.now()),
});
}
}

Try changing your switch like this:
switch (userActivities.comment){
case null:
body = `${userActivities.fromUserId} commented : ${userActivities.comment}`
break;
default: body = `${userActivities.fromUserId} liked your punch`
}
or consider using if else

Related

Sequelize not retrieving all data after insert

I have noticed that my backend is not retrieving the expected data after an insert.
In my React application, I have one function which inserts data into the database and after getting a response, a new request is sent to update the current component state with the newly fetched data.
All my functions are using await/async and in the backend, all transactions are correctly used and committed in order.
My client is calling the following endpoints:
-POST: api/ticket ( INSERT AN ITEM)
-GET: api/ticket (GET ALL ITEMS)
Here is what the backend is showing which looks correct to me, the problem is that in the 'SELECT' statement, the inserted item is not retrieved.
The transactions are started from two different routes but I don't see why it should be an issue.
In addition, I tried to change the AddItem function to output the same findAll statement which is called when using the GET method and the data returned are correct.
So why if I separate these two flows I do not get all the items? I always need to refresh the page to get the added item.
START TRANSACTION;
Executing (a9d14d5c-c0ac-4821-9b88-293b086debaa): INSERT INTO `messages` (`id`,`message`,`createdAt`,`updatedAt`,`ticketId`,`userId`) VALUES (DEFAULT,?,?,?,?,?);
Executing (a9d14d5c-c0ac-4821-9b88-293b086debaa): COMMIT;
Executing (9ee9ddaa-294e-41d1-9e03-9f02a2737030): START TRANSACTION;
Executing (9ee9ddaa-294e-41d1-9e03-9f02a2737030): SELECT `ticket`.`id`, `ticket`.`subject`, `ticket`.`status`, `ticket`.`createdAt`, `ticket`.`updatedAt`, `ticket`.`deletedAt`, `ticket`.`userId`, `messages`.`id` AS `messages.id`, `messages`.`message` AS `messages.message`, `messages`.`sender` AS `messages.sender`, `messages`.`createdAt` AS `messages.createdAt`, `messages`.`updatedAt` AS `messages.updatedAt`, `messages`.`deletedAt` AS `messages.deletedAt`, `messages`.`ticketId` AS `messages.ticketId`, `messages`.`userId` AS `messages.userId`, `messages->user`.`id` AS `messages.user.id`, `messages->user`.`firstname` AS `messages.user.firstname`, `messages->user`.`surname` AS `messages.user.surname`, `messages->user`.`email` AS `messages.user.email`, `messages->user`.`password` AS `messages.user.password`, `messages->user`.`stripeId` AS `messages.user.stripeId`, `messages->user`.`token` AS `messages.user.token`, `messages->user`.`birthDate` AS `messages.user.birthDate`, `messages->user`.`status` AS `messages.user.status`, `messages->user`.`confirmationCode` AS `messages.user.confirmationCode`, `messages->user`.`createdAt` AS `messages.user.createdAt`, `messages->user`.`updatedAt` AS `messages.user.updatedAt`, `messages->user`.`deletedAt` AS `messages.user.deletedAt` FROM `tickets` AS `ticket` LEFT OUTER JOIN `messages` AS `messages` ON `ticket`.`id` = `messages`.`ticketId` AND (`messages`.`deletedAt` IS NULL) LEFT OUTER JOIN `users` AS `messages->user` ON `messages`.`userId` = `messages->user`.`id` AND (`messages->user`.`deletedAt` IS NULL) WHERE (`ticket`.`deletedAt` IS NULL);
Executing (9ee9ddaa-294e-41d1-9e03-9f02a2737030): COMMIT;
-- POST '/api/ticket
exports.addMessage = async (req, res) => {
try {
const result = await sequelize.transaction(async (t) => {
var ticketId = req.body.ticketId;
const userId = req.body.userId;
const message = req.body.message;
const subject = req.body.subject;
// Validate input - If new ticket, a subject must be provided
if (!ticketId && !subject) {
return res
.status(400)
.send({ message: "New ticket must have a subject" });
}
// Validate input - If ticket exists, userId and message must be provided
if (!userId && !message && ticketId) {
return res
.status(400)
.send({ message: "UserID and message are required" });
}
// Create ticket is no ticketID was provided
if (!ticketId) {
const [ticket, created] = await Ticket.findOrCreate({
where: {
subject: subject,
userId: userId,
},
transaction: t,
});
ticketId = ticket.id;
}
// Create a new message object
const messageObject = await db.message.create(
{
message: message,
userId: userId,
ticketId: ticketId,
},
{ transaction: t }
);
// Output message object
return res.send(messageObject);
});
} catch (err) {
console.log(err);
return res.status(500).send({
message:
err.message || "Some error occurred while creating the ticket message.",
});
}
};
-- GET: api/ticket
exports.findAll = async (req, res) => {
try {
const result = await sequelize.transaction(async (t) => {
const tickets = await db.ticket.findAll(
{
include: [{ model: db.message, include: [db.user] }],
},
{ transaction: t }
);
tickets.forEach((ticket) => {
console.log(JSON.stringify(ticket.messages.length));
});
return res.send(tickets);
});
} catch (err) {
console.log(err);
res.status(500).send({
message: err.message || "Some error occurred while retrieving Tickets.",
});
}
};
You sent a response to a client before the transaction actually was committed. You just need to move res.send(messageObject); outside the transaction call.
You can try to look what's going on in the current version of your code if you add several console.log with messages to see what the actual order of actions is (I mean a couple of messages in POST (the last statement inside transaction and after transaction before res.send) and at least one at the beginning of GET).
Actually if the transaction was rolled back you'd send an uncommited and already removed object/record that I suppose is not your goal.

Currently I am facing challenge in sending proactive 1 to 1 messages to a large number of users in parallel without delay

Solution illustration:
Currently, i am facing challenge in sending proactive 1 to 1 messages to a large number of users in parallel without delay.
Right now I am trying these approaches:
For an array of conversationID’s we use .map() to call our async/await function that sends a proactive messages, then Promise.all() to gather them back up again.
I am also trying to create workers using cluster module and distributing split of conversationid’s to each worker.
What is the recommended approach to overcome this issue?
Can I improve the solution by integrating with Microsoft graph API?
// Listen for incoming notifications and send proactive messages to users.
server.get('/api/notify', async (req, res) => {
// map through the agents list
const promises = userslist.map(async agent => {
var sendmsg = sendUserMessage(user.conversationid);
return sendmsg;
})
const results = await Promise.all(promises)
console.log(results);
res.json(results);
res.end();
});
// function to send message
const sendUserMessage = async function (userConversationReference) {
try{
MicrosoftAppCredentials.trustServiceUrl(serviceUrl);
var credentials = new MicrosoftAppCredentials(process.env.BotId, process.env.BotPassword);
var connectorClient = new ConnectorClient(credentials, { baseUri: serviceUrl });
var message = MessageFactory.text("Hello this is a test notification!");
// User Scope
const conversationParameters = {
isGroup: false,
channelData: {
tenant: {
id: process.env.TENANT_ID
}
},
bot: {
id: process.env.BotId,
name: process.env.BotName
},
members: [
{
id: userConversationReference
}
]
};
var conversationResponse = await connectorClient.conversations.createConversation(conversationParameters);
var response = await connectorClient.conversations.sendToConversation(conversationResponse.id, message);
return response;
} catch (error) {
return error.statusCode;
console.error(error);
}
}
Here is documentation on how to Optimize your bot with rate limiting in Teams.
Sharing code snippet from the doc for your reference:
try
{
// Perform Bot Framework operation
// for example, await connector.Conversations.UpdateActivityAsync(reply);
}
catch (HttpOperationException ex)
{
if (ex.Response != null && (uint)ex.Response.StatusCode == 429)
{
//Perform retry of the above operation/Action method
}
}
Sample code references:
Teams Proactive Messaging Samples
Company Communicator App Template

How to read the latest document created in Firestore using node?

I wrote a cloud function, to listen for document creation in a collection, in my database
here is the function,
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().functions);
var newData;
exports.myTrigger = functions.firestore.document('FCM/{id}').onCreate(async (snapshot, context) => {
//
if (snapshot.empty) {
console.log('No Devices');
return;
}
newData = 'hello';
const deviceIdTokens = await admin
.firestore()
.collection('FCM')
.get();
var tokens = [];
var i=0;
for (var token of deviceIdTokens.docs) {
tokens.push(token.data().ar1[i]);
i++;
}
var payload = {
notification: {
title: 'push title',
body: 'push body',
sound: 'default',
},
data: {
push_key: 'Push Key Value',
key1: newData,
},
};
try {
const response = await admin.messaging().sendToDevice(tokens, payload);
console.log('Notification sent successfully');
} catch (err) {
console.log(err);
}
});
This function works weirdly,
For example, sometimes it sends notification, and sometimes it does not.
I don't know how to resolve this issue,
In my arr1 field, i have an array of device tokens, to whom i want to send notifications to,
i want the function to send notifications only to the devices(using tokens) which are just created(in the newly created document ),then delete the document.
I think it's sending notifications to all the documents at once.
I'm pretty new at node..
Is there anyway to only send notifications to the device tokens present in only one document (..the latest created document)?.. I think it's sending notifications to all.
please help me out.
UPDATE:- Here is my document structure
Firstly, in an onCreate handler, you can access the id of just the newly created document by through the snap object, you can access the id via snapshot.id and the body via snapshot.data().
You already have the newly created document fetched, so no need to fetch entire collection. You should replace this entire section:
const deviceIdTokens = await admin
.firestore()
.collection('FCM')
.get();
var tokens = [];
var i=0;
for (var token of deviceIdTokens.docs) {
tokens.push(token.data().ar1[i]);
i++;
}
with this:
const tokens = snapshot.data().ar1;
UPDATE: To delete the new document, you could do
await firestore().collection("FCM").doc(snapshot.id).delete();
since the new document belongs in the FCM collection, not "helpReqs"

Accessing Firestore via Cloud Function

So i have 2 Cloud Functions within the same file:
exports.Auth = functions.region('europe-west1').https.onRequest((req, res) =>
and
exports.IPN = functions.region('europe-west1').https.onRequest((req, res) =>
When adding the following code right at the start of my Auth function it adds a new document to the Firestore as expected, however, when i add the same code at the start of my IPN function, which is currently being called via Paypal's IPN Simulator, it does nothing, no errors.
let pin = RandomPIN(10, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
var userRef = db.collection('Users').doc(pin);
var setWithOptions = userRef.set({ Activated: false }, { merge: true });
console.log("PIN: "+pin);
What on earth is going on, i must be missing something?
Thanks in advance.
Update:
Here are the logs, first with the 2 middle lines commented and then uncommented It seems to be silently failing, i'm just not sure what is causing it.
Update with Complete function:
exports.IPN = functions.region('europe-west1').https.onRequest((req, res) =>
{
console.log("IPN Notification Event Received");
let pin = RandomPIN(10, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
var userRef = db.collection('Users').doc(pin);
var setWithOptions = userRef.set({ Activated: false }, { merge: true });
console.log("PIN: "+pin);
if (req.method !== "POST")
{
console.error("Request method not allowed.");
res.status(405).send("Method Not Allowed");
}
else
{
console.log("IPN Notification Event received successfully.");
res.status(200).end();
}
let ipnTransactionMessage = req.body;
// Convert JSON ipn data to a query string since Google Cloud Function does not expose raw request data.
let formUrlEncodedBody = querystring.stringify(ipnTransactionMessage);
// Build the body of the verification post message by prefixing 'cmd=_notify-validate'.
let verificationBody = `cmd=_notify-validate&${formUrlEncodedBody}`;
console.log(`Verifying IPN: ${verificationBody}`);
let options = {
method: "POST",
uri: getPaypalURI(),
body: verificationBody,
};
// POST verification IPN data to paypal to validate.
request(options, function callback(error, response, body)
{
if(!error && response.statusCode === 200)
{
if(body === "VERIFIED")
{
console.log(`Verified IPN: IPN message for Transaction ID: ${ipnTransactionMessage.txn_id} is verified.`);
SendPIN(ipnTransactionMessage.payer_email, pin);
}
else if(body === "INVALID")
console.error(`Invalid IPN: IPN message for Transaction ID: ${ipnTransactionMessage.txn_id} is invalid.`);
else
console.error("Unexpected reponse body.");
}
else
{
console.error(error);
console.log(body);
}
});
});
Indeed it is a problem of Promises chaining and also a problem due to the request library: request supports callback interfaces natively but does not return a promise, which is what you must do within a Cloud Function.
I would suggest that you watch these official Firebase videos from Doug : https://www.youtube.com/watch?v=7IkUgCLr5oA&t=28s and https://www.youtube.com/watch?v=652XeeKNHSk which explain this key concept.
You can use request-promise (https://github.com/request/request-promise) and the rp() method which "returns a regular Promises/A+ compliant promise".
It is not clear what SendPIN() is doing. Let's make the assumption it returns a Promise. If this is true, you could adapt your code along the following lines:
//....
const rp = require('request-promise');
//....
exports.IPN = functions.region('europe-west1').https.onRequest((req, res) => {
console.log('IPN Notification Event Received');
let pin = RandomPIN(
10,
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
);
var userRef = db.collection('Users').doc(pin);
if (req.method !== 'POST') {
console.error('Request method not allowed.');
res.status(405).send('Method Not Allowed');
} else {
let ipnTransactionMessage;
userRef
.set({ Activated: false }, { merge: true })
.then(() => {
console.log('PIN: ' + pin);
ipnTransactionMessage = req.body;
// Convert JSON ipn data to a query string since Google Cloud Function does not expose raw request data.
let formUrlEncodedBody = querystring.stringify(ipnTransactionMessage);
// Build the body of the verification post message by prefixing 'cmd=_notify-validate'.
let verificationBody = `cmd=_notify-validate&${formUrlEncodedBody}`;
console.log(`Verifying IPN: ${verificationBody}`);
let options = {
method: 'POST',
uri: getPaypalURI(),
body: verificationBody
};
// POST verification IPN data to paypal to validate.
return rp(options);
})
.then(response => {
//Not sure what you will get within the response object...
console.log(
`Verified IPN: IPN message for Transaction ID: ${
ipnTransactionMessage.txn_id
} is verified.`
);
return SendPIN(ipnTransactionMessage.payer_email, pin); //It is not clear what SendPIN is doing, let's make the assumption it returns a Promise...
})
.then(() => {
res.send('Success');
return null;
})
.catch(err => {
console.error(
`Invalid IPN: IPN message for Transaction ID: ${
ipnTransactionMessage.txn_id
} is invalid.`
);
res
.status(500)
.send(
'Error: ' +
err +
` - Invalid IPN: IPN message for Transaction ID: ${
ipnTransactionMessage.txn_id
} is invalid.`
);
return null;
});
}
});

How to make Slack bot dynamically reply at the same channel using node.js

I want to make my slackbot app to answer at the channel that the user mentioned, without manually writing the channel name inside the code.
-example-
problem : I invited my bot into channel #hello, #hi.I mentioned my bot at Channel #hello writing #mybot hi there, but it only replies to channel #hi which I manually wrote down in my code.
I want my bot to automatically find which channel the message came from, and answer back at the same channel that user mentioned.
Not like the code I wrote bot.postMessageToChannel('everyone', `Chuck Norris: ${joke}`,params);
Here is the link of the module that I used and my code
https://github.com/mishk0/slack-bot-api
const SlackBot = require('slackbots');
const axios = require('axios');
const bot = new SlackBot({
token : "",
name : ""
});
// Start Handler
bot.on('start', () =>{
const params = {
icon_emoji: ':)'
};
bot.postMessageToChannel('everyone', 'Feeling tired??? Have some fun with #Joker!'
, params);
});
// Error Handler
bot.on('error', (err) => console.log(err));
//Message Handler
bot.on('message', (data) => {
if(data.type !== 'message'){
return;
}
console.log(data);
handleMessage(data.text);
});
// Responding to Data
function handleMessage(message){
if(message.includes('chucknorris')){
chuckJoke();
}
else if(message.includes(' yomama')){
yoMamaJoke();
}
else if(message.includes(' random')){
randomJoke();
}
else if(message.includes(' help')){
runHelp();
}
}
// Tell a Chuck Norris Joke
function chuckJoke(){
axios.get('http://api.icndb.com/jokes/random/')
.then(res =>{
const joke = res.data.value.joke;
const params = {
icon_emoji: ':laughing:'
};
bot.postMessageToChannel('everyone', `Chuck Norris: ${joke}`,params);
});
}
From here you will find on message it returns you the data object whith channel id
then
you can use postMessage() from the api you have used
postMessage(id, text, params) (return: promise) - posts a message to channel | group | user by ID,
bot.on('message', (data) => {
bot.postMessage(data.channel, 'Feeling tired??? Have some fun with #Joker!'
, params);
console.log(data);
handleMessage(data.text);
});

Resources