twiml.dial(dialNode => {
dialNode.conference('Test conference', {
startConferenceOnEnter: true,
endConferenceOnExit: false,
from: context.CALLER_ID,
to: event.TO
})
I have tried this on Twilio Functions but this returns an error on the client side.
There are quite a few Twilio Function examples, one of which is making an outbound call. You can view the examples here (under Function Examples) on the left side of the screen.
Make a Call
// Description
// Make a call
exports.handler = function (context, event, callback) {
// Make sure under Functions Settings tab:
// "Add my Twilio Credentials (ACCOUNT_SID) and (AUTH_TOKEN) to ENV" is CHECKED
const twilioClient = context.getTwilioClient();
// Pass in From, To, and Url as query parameters
// Example: https://x.x.x.x/<path>?From=%2b15108675310&To=%2b15108675310&Url=http%3A%2F%2Fdemo.twilio.com%2Fdocs%2Fvoice.xml
// Note URL encoding above
let from = event.From || '+15095550100';
// If passing in To, make sure to validate, to avoid placing calls to unexpected locations
let to = event.To || '+15105550100';
let url = event.Url || 'http://demo.twilio.com/docs/voice.xml';
twilioClient.calls
.create({
url: url,
from: from,
to: to,
})
.then((result) => {
console.log('Call successfully placed');
console.log(result.sid);
return callback(null, 'success');
})
.catch((error) => {
console.log(error);
return callback(error);
});
};
Related
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.
I fetch data at server side and push the result to global variable and then send that global variable to client with app.post method using Express.js. My problem is that client fetches the global variable too soon without the data received from the API first. How can I evaluate the response so that client would wait the global variable to reveive data first before displaying anything.
Server side, code looks something like this:
let sharpe = ''
app.post('/api', async(req, res, next) => {
console.log('I got a request!')
thisOne = req.body.stock1
thisOne2 = req.body.stock2
var result = await setup();
res.json({
status: 'success',
stocks: sharpe
});
})
Sharpe is the global variable storing the response from multiple API calls and is the one that should be sent back to client. Client side code is this:
const sendData = async (event) => {
event.preventDefault();
var stock1 = document.getElementById('weight1').value
var stock2 = document.getElementById('weight2').value
const data = {stock1, stock2};
const options = {
method: 'POST',
body: JSON.stringify(data),
headers: {'Content-Type': 'application/json' }
}
fetch('/api', options).then(res => res.json()).then(res => {
console.log(res.stocks);
})
}
As a result SendData() function fetches the sharpe variable that is empty at the moment. How can I adjust client side code or server side code that the client waits for a correct response? Thanks.
One solution would be to store the API results to database and client would fetch ready made datastream but is there more straightforward solution?
To wait for your API Server to set the sharpe Parameter, it needs to be awaited, which you already did. It depends on the setup function (for example setup()) which fills the sharpe parameter. It has to return a promise, which is resolved once sharpe is filled with the data.
let sharpe = ''
async setup() {
return new Promise((resolve, reject) => {
sharpe = 'test';
// set value for sharpe
resolve()
})
}
app.post('/api', async(req, res, next) => {
console.log('I got a request!')
thisOne = req.body.stock1
thisOne2 = req.body.stock2
var result = await setup();
res.json({
status: 'success',
stocks: sharpe
});
})
Eventually it starded working when I pushed all the API calls in the app.post middleware at the server side using promise chaining. The initial problem remains a mystery.
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
I am trying to call a rest API from Firebase function which servers as a fulfillment for Actions on Google.
I tried the following approach:
const { dialogflow } = require('actions-on-google');
const functions = require('firebase-functions');
const http = require('https');
const host = 'wwws.example.com';
const app = dialogflow({debug: true});
app.intent('my_intent_1', (conv, {param1}) => {
// Call the rate API
callApi(param1).then((output) => {
console.log(output);
conv.close(`I found ${output.length} items!`);
}).catch(() => {
conv.close('Error occurred while trying to get vehicles. Please try again later.');
});
});
function callApi (param1) {
return new Promise((resolve, reject) => {
// Create the path for the HTTP request to get the vehicle
let path = '/api/' + encodeURIComponent(param1);
console.log('API Request: ' + host + path);
// Make the HTTP request to get the vehicle
http.get({host: host, path: path}, (res) => {
let body = ''; // var to store the response chunks
res.on('data', (d) => { body += d; }); // store each response chunk
res.on('end', () => {
// After all the data has been received parse the JSON for desired data
let response = JSON.parse(body);
let output = {};
//copy required response attributes to output here
console.log(response.length.toString());
resolve(output);
});
res.on('error', (error) => {
console.log(`Error calling the API: ${error}`)
reject();
});
}); //http.get
}); //promise
}
exports.myFunction = functions.https.onRequest(app);
This is almost working. API is called and I get the data back. The problem is that without async/await, the function does not wait for the "callApi" to complete, and I get an error from Actions on Google that there was no response. After the error, I can see the console.log outputs in the Firebase log, so everything is working, it is just out of sync.
I tried using async/await but got an error which I think is because Firebase uses old version of node.js which does not support async.
How can I get around this?
Your function callApi returns a promise, but you don't return a promise in your intent handler. You should make sure you add the return so that the handler knows to wait for the response.
app.intent('my_intent_1', (conv, {param1}) => {
// Call the rate API
return callApi(param1).then((output) => {
console.log(output);
conv.close(`I found ${output.length} items!`);
}).catch(() => {
conv.close('Error occurred while trying to get vehicles. Please try again later.');
});
});
Here are the steps that I'd like to accomplish:
Call, and eventually text, my Twilio number
Gather a number to call.
Create and move my current call into a conference call.
Call the gathered number.
Add the called number into the conference with me.
Currently I can call my Twilio number, gather a number to be called, create a conference call - but where I'm getting lost is calling the gathered number as well as adding them into the conference I created.
app.post('/call', function(req, res) {
var twiml = new twilio.TwimlResponse();
res.type('text/xml');
if (req.body.From === '+15555555555) {
twiml.gather({
action: '/call/initiate',
finishOnKey: '#',
numDigits: '10',
timeout: '5'
}, function() {
this.say('Enter your number', {
voice: 'man'
});
});
}
else {
twiml.redirect(VOICEMAIL_TWIMLET_OF_CHOICE);
}
res.send(twiml.toString());
});
// Initiate a call from text
app.post('/call/initiate', function(req, res) {
// Create new Twiml response
var twiml = new twilio.TwimlResponse();
// Phone number to call and add to conference
var to = req.body.Digits;
// Create random conference name
var conferenceName = Math.floor(Math.random() * 10000).toString();
// Add myself to the conference call
twiml.dial((node) => {
node.conference(conferenceName, {
startConferenceOnEnter: true,
record: true,
beep: 'true'
});
});
// Redirect twiml to a new url
// Send conferenceName & Number
twiml.redirect('/join_conference?id=' + conferenceName + '&number=' + to);
res.set('Content-Type', 'text/xml');
res.send(twiml.toString());
});
// Call and add caller to conference
app.post('/join_conference', (req, res) => {
var conferenceName = req.query.id;
var to = '+1' + req.query.number;
// Create new Twiml response
var twiml = new twilio.TwimlResponse();
// Call and add user to conference call
twiml.dial(to, (node) => {
node.conference(conferenceName, {
startConferenceOnEnter: true,
});
});
console.log('call called');
res.set('Content-Type', 'text/xml');
res.send(twiml.toString());
});
After I enter the digits and hit the finishOnKey I automatically hear the waiting music. However, at that point - the app just hangs and a call isn't placed.
Twilio developer evangelist here.
The issue is that the TwiML you return to your /call/initiate endpoint is just for the first leg of your call, you can't create another call leg with TwiML alone.
However, you can use the Twilio REST API to generate the second leg of the call within the same request. Here's an updated endpoint that you can use instead:
app.post('/call/initiate', function(req, res) {
// Create new Twiml response
var twiml = new twilio.TwimlResponse();
// Phone number to call and add to conference
var to = req.body.Digits;
// Create random conference name
var conferenceName = Math.floor(Math.random() * 10000).toString();
// Add myself to the conference call
twiml.dial((node) => {
node.conference(conferenceName, {
startConferenceOnEnter: true,
record: true,
beep: 'true'
});
});
// Make call to the other party
// Send conferenceName as part of the URL
var client = new twilio(YOUR_ACCOUNT_SID, YOUR_AUTH_TOKEN);
client.calls.create({
from: YOUR_TWILIO_NUMBER,
to: '+1' + to,
url: 'https://example.com/join_conference?id=' + conferenceName
});
res.set('Content-Type', 'text/xml');
res.send(twiml.toString());
});
Then your '/join_conference' endpoint need only dial the caller into the conference room, like so:
app.post('/join_conference', (req, res) => {
var conferenceName = req.query.id;
// Create new Twiml response
var twiml = new twilio.TwimlResponse();
// Call and add user to conference call
twiml.dial((node) => {
node.conference(conferenceName, {
startConferenceOnEnter: true,
});
});
console.log('call called');
res.set('Content-Type', 'text/xml');
res.send(twiml.toString());
});
Let me know if that helps at all.
Edit: The comment said it still wasn't calling the other party. Let's take a look more closely at the command to call the other side. The call to create will return a Promise, so we can see what happens and whether it is succeeding or failing (and why).
var client = new twilio(YOUR_ACCOUNT_SID, YOUR_AUTH_TOKEN);
client.calls.create({
from: YOUR_TWILIO_NUMBER,
to: '+1' + to,
url: 'http://example.com/join_conference?id=' + conferenceName
}).then(function(call) {
console.log(call.sid);
}).catch(function(err) {
console.error("Something went wrong creating the call.");
console.error(err);
});
Try updating the code to that and see what happens. If there is an error, I'm sure it will help you solve this.