Set context in Dialogflow through Promise Request - node.js

I am having a problem with the contexts of dialogflow, when trying to change them through fulfillment.
When I want to add (or clear, by setting lifespanCount to -1) a context I generally use this code:
const {WebhookClient} = require('dialogflow-fulfillment');
const agent = new WebhookClient({ request, response });
/*Irrelevant Code Here*/
var context=agent.session+"/contexts/contextName";
agent.setContext({
"name":context,
"lifespanCount": 1
});
If I use this snippet, the contexts can be successfully manipulated. However, I am now trying to use this snippet when sending a request-promise and, based on the response of that request, I want to have different outputContexts. For some reason, this action doesn't have any results, I cannot clear the contexts or create one, as these don't appear on the Firebase logs when I print JSON.stringify(request.body).
return new Promise( (resolve,reject) => {
const request = require('request-promise-native');
const options = {
uri: 'link',
method: 'POST',
headers: {
'api-token': 'token'
},
body: {
someData:"someData",
},
json: true
};
request(options)
.then(function (body) {
if (body.status===0)
agent.add("Option 1");
else {
var sessionIdArray=agent.session.split("/");
var sessionId=sessionIdArray[sessionIdArray.length-1];
var context=agent.session+"/contexts/contextName";
agent.setContext({
"name":context,
"lifespanCount": 1
});
agent.add("type something");
}
resolve();
})
.catch(function (err) {
// POST failed...
agent.add("An error happened during your last call.");
console.log(err.stack);
resolve();
});
});
It's like the promise-request prevents the agent to set the context. Does anyone know how to make this work? Thanks in advance!

Related

Twilio: forward a call to a flow in Twilio

I want to fordward a call to a Studio Flow after the agent in flex hangs up so a CSAT survey can play for the user.
I created a plugin that calls a function inside Twilio but there is a "Error - 11200" after the forwarding is done.
I replaced the hang up action so it redirects the call to a function in twilio. The function is supossed to send the call to a flow that will play the survey. I suspect the problem has to do with authentication but I can't find much about it.
I'm fairly new to twilio, so any help will be greatly appreciated
This is the part of the plugin that calls the function:
flex.Actions.replaceAction("HangupCall", (payload) => {
console.log('task attributes: ' + JSON.stringify(payload.task.attributes));
if (payload.task.attributes.direction === "inbound") {
// Describe the body of your request
const body = {
callSid: payload.task.attributes.call_sid,
callerId: payload.task.attributes.from,
destination: '+18xxxxxxxx',
Token: manager.store.getState().flex.session.ssoTokenPayload.token
};
// Set up the HTTP options for your request
const options = {
method: 'POST',
body: new URLSearchParams(body),
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
};
// Make the network request using the Fetch API
fetch('https://TWILIO INSTANCE.twil.io/TransferUtil', options)
.then(resp => resp.json())
.then(data => console.log(data));
} else {
original(payload);
}
});
And this is the function in twilio:
const TokenValidator = require('twilio-flex-token-validator').functionValidator;
exports.handler = TokenValidator(async function(context, event, callback) {
const response = new Twilio.Response();
response.appendHeader('Access-Control-Allow-Origin', '*');
response.appendHeader('Access-Control-Allow-Methods', 'OPTIONS, POST, GET');
response.appendHeader('Access-Control-Allow-Headers', 'Content-Type');
response.appendHeader('Content-Type', 'application/json');
const client = require('twilio')();
const callSid = event.callSid;
const callerId = event.callerId;
const destination = event.destination;
console.log('Call Sid:', callSid);
console.log('Transfer call from:', callerId, 'to:', destination);
try {
let url = 'https://studio.twilio.com/v2/Flows/FWXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Executions';
let call = await client.calls(callSid).update({method: 'POST', url: encodeURI(url)});
console.log(JSON.stringify(call));
response.appendHeader('Content-Type', 'application/json');
response.setBody(call);
callback(null, response);
}
catch (err) {
response.appendHeader('Content-Type', 'plain/text');
response.setBody(err.message);
console.log(err.message);
response.setStatusCode(500);
callback(null, response);
}
});
EDIT:
In the error Log I get this information:
Argh, I read the error wrong. There isn't anything wrong with the Function, the error is coming from the call trying to make a webhook request to the URL https://studio.twilio.com/v2/Flows/FWXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Executions. That's the REST API trigger and needs to be requested in the same way as any other API request using your account credentials or API keys.
You should set that URL to the webhook trigger URL, which looks like https://webhooks.twilio.com/v1/Accounts/${ACCOUNT_SID}/Flows/${FLOW_SID}. Then the call will be able to request it as part of a normal webhook flow.

Cannot set headers after they are sent to the client error upon deleting document and executing post middleware

I have a problem related to, most likely, double execution of the delete query on a document. My suspicion is the issue lies in the middleware executing after deleting the document, could that be the case? The error I get says:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
[0] at ServerResponse.setHeader (_http_outgoing.js:558:11)
[0] at ServerResponse.header
...
Axios call on frontend:
axios.delete(`http://localhost:5000/columns/delete/${columnId}`, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`,
}
}).then(() => {
dispatch({type: ActionTypes.DeleteColumn, payload: columnId})
}).catch((err) => {
console.log(err);
});
Mongoose method on backend:
export const deleteColumn = asyncHandler(async(req, res) => {
try {
await Column.findByIdAndDelete(req.params.columnId);
res.json();
} catch(err) {
res.status(404).json({message: "Column not found"});
throw new Error('Column not found');
}
});
Finally, the middleware that clears all the child elements in a cascade:
columnSchema.post('findOneAndDelete', async function(res) {
const columnId = res._id;
const childIssues = await Issue.find({columnId: columnId});
// array of promises is passed to Promise.all to resolve concurrently
Promise.all(
childIssues.map(async column => {
await Issue.findByIdAndDelete(column._id)
})
);
});
I know this code is far from perfect, especially the middleware part. I'm still learning the related concepts and I find it particularly hard to wrap my head around asynchrounous calls, so I will be more than happy for any feedback and help.

AWS Lambda - unable to make call to 3rd resource

I have Lambda Node function behind API Gateway, what I am trying to do is:
Make call to API
Do some logic
Send a message to Slack
Problem:
When I run default test request, the function finishes successfully but it doesn't send a message to Slack (logs only print Slack and not HAHA1/2),however, it does when I run this function from local machine which leads me to believe that AWS stops any none client-server traffic. My function is not in VPC. What can I do to allow outgoing traffic to Slack? Thanks.
const AWS = require('aws-sdk');
const Slack = require('slack-node');
const dynamo = new AWS.DynamoDB.DocumentClient();
/**
* Demonstrates a simple HTTP endpoint using API Gateway. You have full
* access to the request and response payload, including headers and
* status code.
*
* To scan a DynamoDB table, make a GET request with the TableName as a
* query string parameter. To put, update, or delete an item, make a POST,
* PUT, or DELETE request respectively, passing in the payload to the
* DynamoDB API as a JSON body.
*/
sendSlack()
function sendSlack() {
webhookUri = "https://hooks.slack.com/services/....";
slack = new Slack();
slack.setWebhook(webhookUri);
console.log('Slack');
slack.webhook({
channel: "...",
username: "...",
text: "..."
}, function(err, response) {
console.log("HAHA1");
console.log(response);
console.log("HAHA2");
});
}
exports.handler = async (event, context) => {
console.log('Received event:', JSON.stringify(event, null, 2));
sendSlack()
let body;
let statusCode = '200';
const headers = {
'Content-Type': 'application/json',
};
try {
switch (event.httpMethod) {
case 'DELETE':
body = await dynamo.delete(JSON.parse(event.body)).promise();
break;
case 'GET':
body = await dynamo.scan({ TableName: event.queryStringParameters.TableName }).promise();
break;
case 'POST':
body = await dynamo.put(JSON.parse(event.body)).promise();
break;
case 'PUT':
body = await dynamo.update(JSON.parse(event.body)).promise();
break;
default:
throw new Error(`Unsupported method "${event.httpMethod}"`);
}
} catch (err) {
statusCode = '400';
body = err.message;
} finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers,
};
};
You will need to wait for the slack call by either providing a callback or promisifying it:
function sendSlack() {
return new Promise((resolve, reject) => {
webhookUri = "https://hooks.slack.com/services/....";
slack = new Slack();
slack.setWebhook(webhookUri);
console.log('Slack');
slack.webhook({
channel: "...",
username: "...",
text: "..."
}, function(err, response) {
if(err) return reject(err)
resolve(response)
});
})
}
Then in your code
await sendSlack()
It could be that your lambda handler is completing before your call to Slack is successfully executed.
Since the call to Slack is async, I would suggest you try to await its response before your handler returns a response.
I'm not 100% certain, by my guess is that the async Slack call gets scheduled but not run until the main body of the function has completed, at which point the lambda is "spun down" and nothing further is run.

Express router wait for conditional function before sending the response

I'm building an Express app using Twilio to allow a group of people to communicate via SMS without having to install an app or deal with the limitations on group texts that some phones/carriers seem to have. It's deployed via Azure, but I'm reasonably sure I'm past the configuration headaches. As an early test that I can make this work and for a bit of flavor, I am trying to set up a feature so you can text "joke" (ideally case-insensitive) and it will send a random joke from https://icanhazdadjoke.com/. If anything else is texted, for now it should basically echo it back.
I get the sense this has to do with js being asynchronous and the code moving on before the GET comes back, so I'm trying to use promises to get the code to wait, but the conditional nature is a new wrinkle for me. I've been looking for answers, but nothing seems to work. I've at least isolated the problem so the non-joke arm works correctly.
Here is the function for retrieving the joke, the console.log is outputting correctly:
const rp = require('request-promise-native');
var options = {
headers: {
'Accept': 'application/json'
}
}
function getJoke() {
rp('https://icanhazdadjoke.com/', options) //add in headers
.then(joke => {
theJoke = JSON.parse(joke).joke
console.log(theJoke)
return theJoke
});
}
}
Here is the part of my router that isn't working quite right. If I text something that isn't "joke", I get it echoed back via SMS. If I text "joke", I don't get a reply SMS, I see "undefined" in the Kudu log (from below), and then I see the log of the POST, and then afterward I see the joke from the function above having run.
smsRouter.route('/')
.post((req, res, next) => {
const twiml = new MessagingResponse();
function getMsgText(request) {
return new Promise(function(resolve, reject) {
if (req.body.Body.toLowerCase() == 'joke') {
resolve(getJoke());
}
else {
resolve('You texted: ' + req.body.Body);
}
})
}
getMsgText(req)
.then(msg => {
console.log(msg);
twiml.message(msg);
res.writeHead(200, {'Content-Type': 'text/xml'});
res.end(twiml.toString());
})
})
How can I make it so that getMsgText() waits for the getJoke() call to fully resolve before moving on to the .then?
I think this is what you're looking for.
Note that I've used async/await rather than promise chaining.
// joke.get.js
const rp = require('request-promise-native');
var options = {
headers: {
'Accept': 'application/json'
}
}
async function getJoke() {
const data = await rp('https://icanhazdadjoke.com/', options) //add in headers
return JSON.parse(data).joke;
}
// route.js
smsRouter.route('/')
.post(async (req, res, next) => {
const twiml = new MessagingResponse();
async function getMsgText(request) {
if(req.body.Body.toLowerCase() === 'joke'){
return await getJoke();
}
return `You texted: ${req.body.Body}`
}
const msg = await getMsgText(req);
twiml.message(msg);
res.status(200).send(twiml.toString());
})
async/await in JS

Node and Twilio integration

I have written a programmable SMS feature using Twilio in nodejs. I have a message that has options to select and when user sends back any response I want to send an automated response using twilio.
I have completed all except after processing the response from user my automated response is not being delivered to user.
I keep getting above thing from my twilio dashboard.
Here is my response handler code..
app.post('/ui/sms',function(req, res) {
//req.headers['Content-type'] = 'text/xml';
//req.headers['Accept'] = 'text/xml';
try {
console.log('Processing Response', req.headers);
const MessagingResponse = require('twilio').twiml.MessagingResponse;
const twiml = new MessagingResponse();
const fromTwilio = isFromTwilio(req);
console.log('isFromTwilio: ', fromTwilio);
if (fromTwilio) {
let msg = req.body.Body||'';
if (msg.indexOf('1')>-1) {
twiml.message('Thanks for confirming your appointment.');
} else if (msg.indexOf('2')>-1) {
twiml.message('Please call 408-xxx-xxxx to reschedule.');
} else if (msg.indexOf('3')>-1) {
twiml.message('We will call you to follow up.');
} else {
twiml.message(
'Unknown option, please call 408-xxx-xxxx to talk with us.'
);
}
res.writeHead(200, { 'Content-Type': 'text/xml' });
res.end(twiml.toString());
}
else {
// we don't expect these
res.status(500).json({ error: 'Cannot process your request.' });
}
/*processSMSResponse(req, function(response) {
res.json(response);
});*/
} catch(e) {
res.json(e);
}
});
function isFromTwilio(req) {
console.log('REQ HEADER:::::::::\n', req);
// Get twilio-node from twilio.com/docs/libraries/node
const client = require('twilio');
// Your Auth Token from twilio.com/console
const authToken = 'xxxxxxxxxxxxxxxxx';
// The Twilio request URL
//const url = 'https://mycompany.com/myapp.php?foo=1&bar=2';
const url = 'https://xxxx.com/ui/sms';
var reqUrl = 'https://xxxx.com/ui/sms'
// The post variables in Twilio's request
//const params = {
//CallSid: 'CA1234567890ABCDE',
//Caller: '+14158675310',
//Digits: '1234',
//From: '+14158675310',
//To: '+18005551212',
//};
const params = req.body;
console.log('post params: ', params);
// The X-Twilio-Signature header attached to the request
try{
Object.keys(params).sort().forEach(function(key) {
reqUrl = reqUrl + key + params[key];
});
var twilioSignature = crypto.createHmac('sha1', authToken).update(Buffer.from(reqUrl, 'utf-8')).digest('base64');
//const twilioSignature = req.header('HTTP_X_TWILIO_SIGNATURE');
console.log('twilioSignature: ', twilioSignature);
} catch(e){
console.log(e);
}
return client.validateRequest(
authToken,
twilioSignature,
url,
params
);
}
I have explicitly tried setting headers but no use. I'm clue less on what twilio expects from me or how to modify headers.
{
"status": "Error",
"error": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response><Message>Please call 408-xxx-xxxx to reschedule.</Message></Response>"
}
I see this as body in Twilio console and it has the response that I need but could not send as a message..
Twilio Developer Evangelist here.
From looking at your code and trying it myself I can't see anything generally wrong with it. There are two potential failing points here though:
1) I can't see your isFromTwilio function. If that one fails it might cause an error and then return JSON instead of XML on your error handler. I don't know why it would reply with the TwiML in that JSON though.
2) The other behavior I could reproduce (except that the TwiML is not being sent in the response body) is when I don't include body-parser in the middleware chain. This will cause req.body to be undefined and therefore req.body.Body will throw an error that is then being caught and JSON is being returned.
Do you have body-parser included and properly included as a middleware? You can either do it this way:
const { urlencoded } = require('body-parser');
app.use(urlencoded({ extended: false });
or if you only want to use it for this one endpoint you can add urlencoded({ extended: false }) as an argument before your request handler:
app.post('/ui/sms', urlencoded({ extended: false }), function(req, res) {
I hope this helps.
Cheers,
Dominik

Resources