Using the onUpdate function in firebase, how do I pass in information from the front end? - node.js

So basically, I have a function that sends a user an email when a room Status becomes "Available"
exports.sendAvailableRoomEmail = functions.firestore.document('rooms/{roomId}/Status').onUpdate(async (snap, context) => {
const newVal = snap.after.data();
const oldVal = snap.before.data();
if(newVal == "Available"){
// send email
}
})
what I need passed into the function is the specific room that the user wants a notification from: {roomId}
How can I pass that specific room only when the user clicks the notify me button right next to it?

The triggers for a Cloud Function needs to be known when you deploy that function. That means you can either trigger for all rooms, or you can trigger for a specific room, but you can't trigger for rooms that meet a dynamic condition such as yours.
The common way to implement the use-case is to query Firestore inside the Cloud Functions code to find if any user need to be notified for the specific room that was updated.

Related

Cloud Functions and Cloud Firestore USER ASSOCIATION FUNCTION

Shortly, imagine I have a Cloud Firestore DB where I store some users data such as email, geo-location data (as geopoint) and some other things.
In Cloud Functions I have "myFunc" that runs trying to "link" two users between them based on a geo-query (I use GeoFirestore for it).
Now everything works well, but I cannot figure out how to avoid this kind of situation:
User A calls myFunc trying to find a person to be associated with, and finds User B as a possible one.
At the same time, User B calls myFunc too, trying to find a person to be associated with, BUT finds User C as possible one.
In this case User A would be associated with User B, but User B would be associated with User C.
I already have a field called "associated" set to FALSE on each user initialization, that becomes TRUE whenever a new possible association has been found.
But this code cannot guarantee the right association if User A and User B trigger the function at the same time, because at the moment in which the function triggered by User A will find User B, the "associated" field of B will be still set to false because B is still searching and has not found anybody yet.
I need to find a solution otherwise I'll end up having
wrong associations ( User A pointing at User B, but User B pointing at User C ).
I also thought about adding a snapshotListener to the user who is searching, so in that way if another User would update the searching user's document, I could terminate the function, but I'm not really sure it will work as expected.
I'd be incredibly grateful if you could help me with this problem.
Thanks a lot!
Cheers,
David
HERE IS MY CODE:
exports.myFunction = functions.region('europe-west1').https.onCall( async (data , context) => {
const userDoc = await firestore.collection('myCollection').doc(context.auth.token.email).get();
if (!userDoc.exists) {
return null;
}
const userData = userDoc.data();
if (userData.associated) { // IF THE USER HAS ALREADY BEEN ASSOCIATED
return null;
}
const latitude = userData.g.geopoint["latitude"];
const longitude = userData.g.geopoint["longitude"];
// Create a GeoQuery based on a location
const query = geocollection.near({ center: new firebase.firestore.GeoPoint(latitude, longitude), radius: userData.maxDistance });
// Get query (as Promise)
let otherUser = []; // ARRAY TO SAVE THE FIRST USER FOUND
query.get().then((value) => {
// CHECK EVERY USER DOC
value.docs.map((doc) => {
doc['data'] = doc['data']();
// IF THE USER HAS NOT BEEN ASSOCIATED YET
if (!doc['data'].associated) {
// SAVE ONLY THE FIRST USER FOUND
if (otherUser.length < 1) {
otherUser = doc['data'];
}
}
return null;
});
return value.docs;
}).catch(error => console.log("ERROR FOUND: ", error));
// HERE I HAVE TO RETURN AN .update() OF DATA ON 2 DOCUMENTS, IN ORDER TO UPDATE THE "associated" and the "userAssociated" FIELDS OF THE USER WHO WAS SEARCHING AND THE USER FOUND
return ........update({
associated: true,
userAssociated: otherUser.name
});
}); // END FUNCTION
You should use a Transaction in your Cloud Function. Since Cloud Functions are using the Admin SDK in the back-end, Transactions in a Cloud Function use pessimistic concurrency controls.
Pessimistic transactions use database locks to prevent other operations from modifying data.
See the doc form more details. In particular, you will read that:
In the server client libraries, transactions place locks on the
documents they read. A transaction's lock on a document blocks other
transactions, batched writes, and non-transactional writes from
changing that document. A transaction releases its document locks at
commit time. It also releases its locks if it times out or fails for
any reason.
When a transaction locks a document, other write operations must wait
for the transaction to release its lock. Transactions acquire their
locks in chronological order.

Unable to pass control to another method within a dialog in botbuilder for Node.js

I'm creating my first bot with Node.js and MS Bot Framework and I'm trying to figure out how to pass control from one method to another within a dialog.
In Bot Framework for C#, it's very easy:
context.Wait(NextMethodName);
where NextMethodName is the name of the method that runs after the bot receives the next user message.
I am trying to do a similar thing in Node.js. I have two functions. The first one prompts the user to enter something or click a button, and the second should process the user's input. I am struggling with passing control to the second function.
bot.dialog('subscribe', [
function (session) {
var card = new builder.HeroCard(session)
.title("Subscribe for reminders?")
.text("It seems you're not enrolled yet. Subscribe for reminders to submit your work hours?")
.buttons([
builder.CardAction.imBack(session, "subscribe", "Subscribe")
]);
var msg = new builder.Message(session).attachments([card]);
session.send(msg);
//next(); //compile error
},
function (session, results) {
if (results.response === 'subscribe') {
session.send('You are now subscribed to reminders through ' + session.message.user.channelId + '.');
}
else {
session.send('You must subscribe to reminders before using this bot.');
}
}
]);
How do I make the second function run after the user clicks the button or answers anything?
In node's botbuilder sdk, you can define waterfall dialogs that contains what are called as 'steps'. Each step leads to another (like a waterfall). According to docs:
'Waterfalls let you collect input from a user using a sequence of steps. A bot is
always in a state of providing a user with information or asking a
question and then waiting for input. In the Node version of Bot
Builder it's waterfalls that drive this back-n-forth flow'.
Some steps can start with a prompt to ask the user for information, and then the following step handles the response by saving it using dialogData. Then you can invoke the next function argument to pass control to the next step. In your case, calling next() gives you an error because that function isn't in scope, you must provide it as a parameter to your step function.
Check this sample here:
https://github.com/Microsoft/BotBuilder-Samples/tree/master/Node/core-MultiDialogs
In your first step code I would do:
function (session,args,next) {
var card = new builder.HeroCard(session)
.title("Subscribe for reminders?")
.text("It seems you're not enrolled yet. Subscribe for reminders to submit your work hours?")
.buttons([
builder.CardAction.imBack(session, "subscribe", "Subscribe")
]);
var msg = new builder.Message(session).attachments([card]);
session.send(msg);
next();
}
But that would just lead you to the next step, so if you want to wait for user input (with text prompt), or for example using HeroCard actions, like you defined in your sample:
Your card triggers an action called "subscribe" with the parameter "Subscribe" via a button. Think of this as a function that is called within your dialog by pressing the button on the card. Now to define that function, we do:
// An action is essentially a card calling a global dialog method
// with respective parameters. This dialog action will route the action
// command to a dialog.
bot.beginDialogAction('subscribe', '/subscribe');
// Create the dialog for the action...
bot.dialog('/subscribe', [
function (session, args) {
//do something!
}
]);

how to trace user reply to a specific chatbot message in node.js

I wonder how to catch a user reply to a specific chatbot question? I mean for example if the user asks the chatbot for the weather and the chatbot responds back by asking the user for in which city. I would then like to trace what the user responds to that question. So that the city could be used for calling a weather api for the city. I don't know how to track the user reply to that question. Does anyone know if and how this is possible?
So that multiple users can access the chatbot simultaneously, it's best to keep track of each user, and each user's conversation state. In the case of the Messenger API, this would be:
const users = {}
const nextStates = {
'What country are you in?': 'What city are you in?',
'What city are you in?': 'Let me look up the weather for that city...'
}
const receivedMessage = (event) => {
// keep track of each user by their senderId
const senderId = event.sender.id
if (!users[senderId].currentState){
// set the initial state
users[senderId].currentState = 'What country are you in?'
} else {
// store the answer and update the state
users[senderId][users[senderId].currentState] = event.message.text
users[senderId].currentState = nextStates[users[senderId.currentState]]
}
// send a message to the user via the Messenger API
sendTextMessage(senderId, users[senderId].currentState)
}
Then you will have the answer for each user stored in the users object. You can also use a database to store this.
..I solved it by setting a global variable when the chatbot asks the question
global.variable = 1;
When the user replies the incoming text message event is fired and I can check if the global flag is set. This indicates that this is the user reply after the question was asked. I can then get the message text city from that message. This works fine in my case but if anyone knows a better alternative please let me know

What is the right way to save/track state inside a Facebook Messenger bot?

If my bot asks different questions and if the user answers each of them, how do I find out which answer relates to which question. There is a field called metadata that you can attach to the sendTextMessage API but when the user responds, this metadata comes in as undefined. Do you guys use any node-cache for tracking state or an FSM such as machina.js? How can I best figure out at what of the conversation we are currently stuck in?
When your app receives a message, there's no payload or metadata associated with it. This is as opposed to a quick-reply or post-back which can have a payload. The only way to associate a response with a question this is to manually track the conversation state in your app as suggested by #anshuman-dhamoon
To do this, it's best to maintain a state for each user, as well as the next state for each state.
// optionally store this in a database
const users = {}
// an object of state constants
const states = {
question1: 'question1',
question2: 'question2',
closing: 'closing',
}
// mapping of each to state to the message associated with each state
const messages = {
[states.question1]: 'How are you today?',
[states.question2]: 'Where are you from?',
[states.closing]: 'That\'s cool. It\'s nice to meet you!',
}
// mapping of each state to the next state
const nextStates = {
[states.question1]: states.question2,
[states.question2]: states.closing,
}
const receivedMessage = (event) => {
// keep track of each user by their senderId
const senderId = event.sender.id
if (!users[senderId].currentState){
// set the initial state
users[senderId].currentState = states.question1
} else {
// store the answer and update the state
users[senderId][users[senderId].currentState] = event.message.text
users[senderId].currentState = nextStates[users[senderId.currentState]]
}
// send a message to the user via the Messenger API
sendTextMessage(senderId, messages[users[senderId].currentState])
}
Note If you wanted, you can even make the values of nextStates into callable functions that take the answer of the current state and branch off into different conversation flows by passing the user to a different state depending on his/her response.
As per my knowledge,in Facebook chatbot you can send data from user to chatbot just by setting payload from postback buttons as they have given in API reference.
And chatbot won't store your session or any states/flags.you can set status or flags or arrays but all will be lost when you update your application or restart your server.
so,if you really want to set status you should use database for that.and senderID will remain same everytime so you can handle data from database by that particular id for particular user.
For more details checkout technical referance here.
I hope this will help you.if so,kindly mark it as an answer.
You can have a status code in your code, to keep track of where the user conversation with the bot is.
For eg. if you have 10 questions, keep statuscode = 0 initially, and ask the first question. When you receive a message to your webhook, check if statuscode==0, and store that user message as a response to your first question. Then increment statusCode=1 and ask the next question.
You can have multiple flags and statusCodes to deal with different conversation flows.
I'm running into this issue myself. Though it hasn't been mentioned at all in their documentation, I don't think attaching an in-memory database is out of the question. It seems that the user_id is the same regardless of when the conversation is initiated.
Making an API call every time the user rejoins the session would probably slow down the performance of the bot. Also, I noticed that you can't really construct a "pseudo-distributed database" by using the metadata key in the API if that is what you were suggesting. The metadata tag can be sent from Server -> Client (Messenger) but not from Client -> Server from what the documentation says.
I've spent some time working with this. The best solution is to use a database to track the user's conversation flow. The POST object contains the senders ID. You can use this ID to make a row in the database in which you'd definitely need to store this ID, any answers to the questions, and a field to keep track of of which step in the conversation.
Then you can use if statements in your code to return the proper responses. Some example code below:
if( $currentStep == '1' ){
// Ask Next Question
$message_to_reply = "Thank you! What's your name?";
$message_to_reply = '"text":"'.$message_to_reply.'"';
} elseif( $currentStep == '2' ){
// Ask Next Question
$message_to_reply = "Thank you! What's your email address?";
$message_to_reply = '"text":"'.$message_to_reply.'"';
} elseif( $currentStep == '3' ){
// Ask Next Question
$message_to_reply = "Thank you! What's your address?";
$message_to_reply = '"text":"'.$message_to_reply.'"';
}

Facebook Messenger Bot, can someone tell me how i catch the answer of a something i asked

So i working on my Facebook Messenger Bot.
I want to know ho can i catch a answer for a question like
Bot: Enter your E-mail
User: enters e-mail
Bot: adress was added
My code looks like the sample app from Facebook
app.post('/webhook', function (req, res) {
var data = req.body;
// Make sure this is a page subscription
if (data.object == 'page') {
// Iterate over each entry
// There may be multiple if batched
data.entry.forEach(function(pageEntry) {
var pageID = pageEntry.id;
var timeOfEvent = pageEntry.time;
// Iterate over each messaging event
pageEntry.messaging.forEach(function(messagingEvent) {
if (messagingEvent.optin) {
receivedAuthentication(messagingEvent);
} else if (messagingEvent.message) {
receivedMessage(messagingEvent);
} else if (messagingEvent.delivery) {
receivedDeliveryConfirmation(messagingEvent);
} else if (messagingEvent.postback) {
receivedPostback(messagingEvent);
} else {
console.log("Webhook received unknown messagingEvent: ", messagingEvent);
}
});
});
// Assume all went well.
//
// You must send back a 200, within 20 seconds, to let us know you've
// successfully received the callback. Otherwise, the request will time out.
res.sendStatus(200);
}
});
You can set a flag for their ID that the E-Mail prompt was sent, and then after they respond check to see if it's an E-mail, and if so, then save it and echo it back to them.
If the bot is based on question/answer, what I normally do to handle response tracking is treat the bot like a finite state automata. Assign every "state" your bot can be in to some unique state identifier, and use said state identifier to determine what the user is replying to. You could also store callbacks instead of state ids, but high level this will behave the same way.
For Example:
First define a finite automata. In this case, lets assume it's:
0 --> 1 --> 2
Where 0 means new user, 1 means waiting for email response, 2 means user successfully completed registration.
User messages bot
We check our database and see it's a new user. We assume
state==0.
Because state is 0, we ignore what was sent and prompt for email
Change state to 1 to denote the email was prompted.
User replies with email.
We check database and see state==1. We use the "1" routine to do fancy stuff to verify the email and store it.
Change state to 2 to denote the email was received and the program has ended.
Note:
If the conversation id for the platform you're targeting is reset
after a certain amount of inactivity (or if you just want the bot to
mimic real conversations), store the time of each user's last
interaction and purge all inactive conversations well after the
conversation has been terminated.

Resources