Unhandled Rejection: Headers cant be set after they are sent - node.js

I am creating a chatbot in Dialogflow. I am trying to add the data to the database when its throwing an error of Unhandled Rejection.
This is my index.js file.
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
//const {Card, Suggestion} = require('dialogflow-fulfillment');
var admin = require('firebase-admin');
//require("firebase/firestore");
admin.initializeApp(functions.config().firebase);
var firestore = admin.firestore();
//var db = firebase.firestore();
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
var addRef = firestore.collection('Admission');
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
console.log("request.body.queryResult.parameters: ", request.body.queryResult.parameters);
let params = request.body.queryResult.parameters;
/* function welcome (agent) {
agent.add(`Welcome to my agent!`);
} */
/* function fallback (agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
} */
let responseJson ={};
function yourFunctionHandler(agent) {
var docRef = firestore.collection('users');
name = request.body.queryResult.parameters['myname'];
coll = request.body.queryResult.parameters['college_name'];
name = name.charAt(0).toUpperCase() + name.slice(1);
let balanceresponse = {};
console.log(name);
return docRef.add({
myname: name,
college: coll
})
.then((querySnapshot)=>{
balanceresponse = {
"fulfillmentText": 'Sure '+name+', Do you want to know about Admissions, Fees, Graduates and PG, Contact information, Media or Testimonials?'
}
console.log('before response.send()');
response.send(balanceresponse);
/* console.log('before response.send()');
response.send({
fulfillmentText:
'Sure '+name+', Do you want to know about Admissions, Fees, Graduates and PG, Contact information, Media or Testimonials?'
//response.json(responseJson);
}); */
console.log('after response.send()');
return 1;
})
.catch(error => {
response.send({
fulfillmentText:
'Something went wrong with the database'
});
});
}
function AdmissionHandler(agent) {
console.log("inside Admission Handler");
let balanceresponse = {};
let Question = request.body.queryResult.parameters['question'];
addRef.where("Question", "==", Question)
.get().then((querySnapshot)=>{
if (querySnapshot) {
console.log("Document data:");
const tips = querySnapshot.docs;
const tipIndex = Math.floor(Math.random() * tips.length);
const tip = tips[0];
balanceresponse = {
"fulfillmentText": ' Anything else you wanna know?'
}
console.log('before response.send()');
response.send(balanceresponse);
/* response.send({
fulfillmentText:
//firestore.collection(addRef.Answer)+
' Anything else you wanna know?'
}); */
return 1;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
return 1;
})
.catch(function(error) {
console.log("Error getting document:", error);
});
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
// intentMap.set('Default Welcome Intent', welcome);
// intentMap.set('Default Fallback Intent', fallback);
intentMap.set('GetName', yourFunctionHandler);
intentMap.set('AdmissionCustom', AdmissionHandler);
agent.handleRequest(intentMap);
});
This is the error I receive:
I have seen few similar questions here but none of them are answered. Can anyone please help? I have been stuck in this for over a week already.

The problem is that the yourFunctionHandler(agent) function is doing things asynchronously, but isn't returning a Promise. Instead, it returns nothing, so the processing returns immediately without having a message sent.
Since it looks like myDoc.add() returns a Promise, this is easy to handle by making that return myDoc.add(...).then(...) and so forth. It might look something like this:
function yourFunctionHandler(agent) {
return docRef.add({
myname: name,
college: coll
})
.then(()=>{
response.send({
fulfillmentText:
'Sure '+name+', Do you want to know about Admissions, Fees, Graduates and PG, Contact information, Media or Testimonials?'
});
return 1;
})
.catch(error => {
//console.log('érror',e);
response.send({
fulfillmentText:
'Something went wrong with the database'
});
});
}
Additionally, you're mixing handling the response yourself (by calling response.send()) and using the Dialogflow agent.handleRequest(), which will create a response for you.
You should either use the Dialogflow methods to generate the reply with something like
agent.add("No such document found.");
or use the values in the JSON yourself to determine which handler to call with something like
const intentName = request.body.queryResult.intent.name;
const handler = intentMap[intentName];
handler();
(You may need to vary this. It looks like from your code you're using Dialogflow v1, which I've reflected, and the path for the intent name changes for v2. You also should check for the handler not existing, may want to send parameters, etc.)

Related

Is there any way to read and update data in Firestore using Twilio Functions?

I am trying to read data from Firestore using the following code. I have added the firebase dependency in the dependencies file. But nothing seems to run except the template code at the end. I have also set the read rules for firestore to true for checking. I'm not even sure if Twilio can be used for this.
var firebase = require('firebase');
exports.handler = function(context, event, callback) {
var firebaseConfig = {
apiKey: "[API_KEY]",
authDomain: "[AUTH_DOMAIN]",
projectId: "[PROJECT_ID]",
storageBucket: "[STORAGE_BUCKET]",
messagingSenderId: "[MESSAGING_SENDER_ID]",
appId: "[APP_ID]"
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
console.log('Initialized Firebase app');
}else {
firebase.app();
console.log('Firebase initialized');
}
try{
const userRef = firebase.db.collection('users').doc('123');
const doc = userRef.get();
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
} catch(e) {
console.log(e);
}
// Here's an example of setting up some TWiML to respond to with this function
let twiml = new Twilio.twiml.VoiceResponse();
twiml.say('Hello World');
let variable = 'welcome!';
// You can log with console.log
console.log('error', variable);
// This callback is what is returned in response to this function being invoked.
// It's really important! E.g. you might respond with TWiML here for a voice or SMS response.
// Or you might return JSON data to a studio flow. Don't forget it!
return callback(null, twiml);
};
These are the dependencies added to the environment
Twilio developer evangelist here.
I think you have two issues with your code and they are both related to the asynchronous nature of JavaScript.
You are not treating any of the Firebase functions as asynchronous. It turns out that most are synchronous, but when you call .get on the userRef that is an asynchronous call. Since you are working with try/catch we can fix this quickly by defining the whole Twilio Function as an async function.
exports.handler = async function(context, event, callback) {
You can then use await before userRef.get() and things should start to work.
const doc = await userRef.get();
The second issue is that you were making that asynchronous call and not waiting for the response before you eventually called callback. Once you call the callback function the rest of the function execution is terminated. So while that asynchronous call to userRef.get() was actually made, it was cancelled once the function reached the callback because you weren't waiting for the result.
In both these cases, adding the async and await should sort out the issue and your function should run as expected.
Here's the whole function with the addition of async and await:
var firebase = require('firebase');
exports.handler = async function(context, event, callback) {
var firebaseConfig = {
apiKey: "[API_KEY]",
authDomain: "[AUTH_DOMAIN]",
projectId: "[PROJECT_ID]",
storageBucket: "[STORAGE_BUCKET]",
messagingSenderId: "[MESSAGING_SENDER_ID]",
appId: "[APP_ID]"
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
console.log('Initialized Firebase app');
}else {
firebase.app();
console.log('Firebase initialized');
}
try{
const userRef = firebase.db.collection('users').doc('123');
const doc = await userRef.get();
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
} catch(e) {
console.log(e);
}
// Here's an example of setting up some TWiML to respond to with this function
let twiml = new Twilio.twiml.VoiceResponse();
twiml.say('Hello World');
let variable = 'welcome!';
// You can log with console.log
console.log('error', variable);
// This callback is what is returned in response to this function being invoked.
// It's really important! E.g. you might respond with TWiML here for a voice or SMS response.
// Or you might return JSON data to a studio flow. Don't forget it!
return callback(null, twiml);
};

Writing better code for Dialogflow Webhooks

I'm currently calling a fulfillment webhook with dialogflow in my node backend, performing crud operations on a firestore db. Is there a better, cleaner way to write these?
My code seems very poorly written but it works. I am striving to write cleaner more readable code so I'm looking for someone to give me some pointers on how to write better API calls with webhooks.
//DATABASE API CALLS HERE!//
case "FAV_COLOR":
agent.handleRequest(agent => {
return new Promise(() => {
async function writeToDb() {
// Get parameter from Dialogflow with the string to add to the database doc
const databaseEntry = agent.parameters.color;
// Get the database collection 'user' and document 'color' and store
// the document {entry: "<value of database entry>"} in the 'color' document
const dialogflowAgentRef = db.collection("user").doc("color");
try {
await db.runTransaction(transaction => {
transaction.set(dialogflowAgentRef, {
entry: databaseEntry
});
return Promise.resolve("Write complete");
});
agent.add(
`Wrote "${databaseEntry}" to the Firestore database.`
);
} catch (e) {
agent.add(
`Failed to write "${databaseEntry}" to the Firestore database.`
);
}
}
writeToDb();
});
});
break;
default:
console.log("ITS BROKEN");
It's currently inside a switch statement because I want to trigger different fulfillments based on actions. Both agent.add statements don't trigger at all.
Also, if someone could throw in some tips about debugging these I would really appreciate it. I've just been deploying the functions, adding a console.log(JSON.stringify()); and then checking in the firebase console functions section for errors. Seems incredibly inefficient.
Thanks for taking the time to answer :)
Jacks
Your index.js
const functions = require('firebase-functions');
const { WebhookClient } = require('dialogflow-fulfillment');
const welcome = require('./welcome')
const fallback = require('./fallback')
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
agent.handleRequest(intentMap);
});
You can split files like welcome, fallback
=> welcome.js
const welcome = (agent) => {
agent.add(`Welcome to my agent!`);
}
module.exports = welcome
=> fallback.js
const fallback = (agent) => {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
module.exports = fallback
You can use same method with thie Example

Firebase Cloud Functions get updated document Details

I'm trying to write a cloud function where if my app changed some string in user's firestore DB. A cloud function need to send a push notification. Database architecture is Messages => {UID} => UpdatedMessages . The problem is I cannot figure How to retrive which updateMessage under which UID has been updated.
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp()
const toUpperCase = (string)=> string.toUpperCase()
var registrationToken = 'dfJY6hYzJyE:APdfsdfsdddfdfGt9HMfTXmei4QFtO0u1ePVpNYaOqZ1rnDpB8xfSjx7-G6tFY-vWQY3vDPEwn_iZVK2PrsGUVB0q9L_QoRYpLJ3_6l1SVHd_0gQxJb_Kq-IBlavyJCkgkIZ';
exports.sendNotification = functions.firestore
.document('messages/{userId}/{updatedMessage}')
.onUpdate((change, context) => {
var message = {
data: {
title: 'Update',
body: 'New Update'
},
token: registrationToken
};
// Send a message to the device corresponding to the provided
// registration token.
admin.messaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent messagesssss:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
});
Simply I need to rerive "var registrationToken" from UID .
You have to use the params property of the context object as follows
exports.sendNotification = functions.firestore
.document('messages/{userId}/{updatedMessage}')
.onUpdate((change, context) => {
const userId = context.params.userId;
const updatedMessage = context.params.updatedMessage;
var message = {
data: {
title: 'Update',
body: updatedMessage //For example, use the value of updatedMessage here
},
//...
};
//IMPORTANT: don't forget to return the promise returned by the asynchronous send() method
return admin.messaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent messagesssss:', response);
return null;
})
.catch((error) => {
console.log('Error sending message:', error);
return null;
});
});
See https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters and https://firebase.google.com/docs/reference/functions/functions.EventContext#params for more info.
About the remark in the above code noted as "IMPORTANT", you may watch the official Firebase video series here: https://firebase.google.com/docs/functions/video-series/. In particular watch the three videos titled "Learn JavaScript Promises" (Parts 2 & 3 especially focus on background triggered Cloud Functions, but it really worth watching Part 1 before).

Return Promise from DynamoDB before Alexa output

Building an Alexa skill that requires persistence.
I am calling the database to get the user details prior to managing the flow and I'm struggling to get Node to wait for the response from DynamoDB (using Dynasty.js to handle the db connectivity).
I've tried a whole host of different promise/callback/node "blocking"/async approaches and the best I can do is have the response (in CloudWatch logs) appear when the user quits the skill. I'd really like the user configuration to happen at the start of the process, rather than at the end!
var credentials = {
accessKeyId: process.env.MY_ACCESS_KEY_ID,
secretAccessKey: process.env.MY_SECRET_ACCESS_KEY
};
var dynasty = require('dynasty')(credentials);
var tableName = dynasty.table('dynamoTable');
const promiseFunc = new Promise((resolve,reject)=>{
var myUser = tableName.find(userId);
setTimeout(_=>{resolve(myUser)}, 2000);
});
var checkUser = async function(){
if (true) {
console.log('check, check, 1, 2, 3');
await promiseFunc.then(function(myUser) {
if (myUser) {
console.log("USER FOUND");
console.log(myUser);
} else {
console.log("no user! sigh deeply and start again");
}
})
}
console.log("do more stuff now");
}
exports.handler = function(event, context) {
userId = event.session.user.userId;
const alexa = Alexa.handler(event, context);
// only check the first time, and only once the userId is properly constructed
if (!(userId.startsWith('amzn1.ask.account.testUser')) && (checkedUser != 1)) {
checkedUser = 1;
checkUser();
}
alexa.resources = languageString;
alexa.registerHandlers(newSessionHandlers, startStateHandlers, triviaStateHandlers, helpStateHandlers, stopStateHandlers);
alexa.execute();
};
How do I get node to wait for the response from dynamoDB before the rest of the script runs...
This is an old-ish link, but according to it, you can get a promise from DynamoDB with the .promise() method. With that in mind...
function findUser(userId) {
// initialize tableName somehow
return tableName.find(userId)
}
async function checkUser() {
// initialize userId somehow
let myUser = await findUser(userId);
console.log(`found user ${myUser}`);
return myUser;
}
Finally, remember to await on your call to checkUser()...
if (/* long condition */) {
checkedUser = 1;
let myUser = await checkUser();
}

ReferenceError: conv is not defined in actions-on-google

I want to implement Suggestions chips in my Dialog flow (to be use in Google Assistant)..But I am getting this error
"ReferenceError: conv is not defined"
which i didn't understand.I have go through the official docs but what am i missing? I have also added actions_intent_OPTION in his Event
following is my code
const functions = require('firebase-functions');
const {actionssdk} = require('actions-on-google');
const app = actionssdk({debug: true});
var admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
var firestore = admin.firestore();
exports.webhook = functions.https.onRequest((request, response) => {
switch (request.body.result.action) {
case 'countitem':
firestore.collection('orders').get()
.then((querySnapshot) => {
var orders = [];
querySnapshot.forEach((doc) => { orders.push(doc.data()) });
// now orders have something like this [ {...}, {...}, {...} ]
response.send({
speech: `you have ${orders.length} orders11, would you like to see them? (yes/no)`
});
})
.catch((err) => {
console.log('Error getting documents', err);
response.send({
speech: "something went wrong when reading from database"
})
})
conv.ask(new Suggestions('Suggestion Chips'));
conv.ask(new Suggestions(['suggestion 1', 'suggestion 2']));
break;
default:
response.send({
speech: "no action matched in webhook"
})
}
});
The issue is that conv isn't defined. Typically, if you're using the actions-on-google library, conv is passed to your fulfillment function and contains methods you can use to set replies and so forth.
It looks like you're handling everything yourself and generating the JSON response manually. If so, you should consult the guide for using JSON as part of your webhook and the repository of JSON examples.

Resources