Node js twillio webhook - outgoing call not working - javascript

My goal here is for twillio to call a webhook, and then to have a few node js functions record what the user says, and then make an outgoing call to another phone number and play that recording.
However, I can't seem to get an outgoing call set up. The code works in a regular node js file, but as soon as it is called as a part of the webhook, it won't work:
const accountSid =
const authToken =
const client = require('twilio')(accountSid, authToken);
exports.handler = function(context, event, callback) {
console.log(event.CallSid, event.RecordingUrl);
console.log()
console.log("RECORDING URL: " + event.RecordingUrl + ".mp3");
let audioURL = event.RecordingUrl
audioURL+=".mp3";
const response = new Twilio.twiml.VoiceResponse();
response.say({ voice: 'woman', language: 'en-US' }, 'Thank you.');
client.calls
.create({
from: '+14********',
to: '+14***********',
url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-8.mp3'
})
callback(null, response);
};

Twilio developer evangelist here.
The issue here is that you are making the asynchronous request to make the call and then calling the callback immediately. That terminates the execution of the Twilio Function, and because it is immediate cancels that outbound API request. See the documentation on Twilio Function execution for more details.
To fix this you should only call the callback once the create function has completed.
As an extra tip, you can also get an authenticated client from the context object without having to load your accountSid or authToken yourself.
Also, in your example the URL you are providing for the outbound call appears to be an mp3. Your URL should return TwiML, or you can send TwiML as part of the API request.
I would update your function like so:
exports.handler = function(context, event, callback) {
console.log(event.CallSid, event.RecordingUrl);
console.log()
console.log("RECORDING URL: " + event.RecordingUrl + ".mp3");
let audioURL = event.RecordingUrl
audioURL+=".mp3";
const client = context.getTwilioClient();
const response = new Twilio.twiml.VoiceResponse();
response.say({ voice: 'woman', language: 'en-US' }, 'Thank you.');
client.calls
.create({
from: '+14********',
to: '+14***********',
twiml: `<Response><Play>${audioUrl}</Play></Response>`
})
.then(() => {
callback(null, response);
})
.catch(error => {
callback(error);
});
};

Related

Twilio: Delay SMS message while reacting to call

I'm building a twilio function that reacts to incoming phone calls. After a call is received the function should send a SMS to the caller. However, I would like the SMS to be delayed by a couple minutes. Currently, my function looks like this:
exports.handler = function (context, event, callback) {
// Create a new voice response object
const client = context.getTwilioClient();
const client_number = event.From;
const twilio_number = +xxxx;
client.messages.create({ // Send SMS
to: client_number,
from: twilio_number,
body: "Hello on SMS"
}).then(() => { // When request to send SMS is complete, deal with the caller
let twiml = new Twilio.twiml.VoiceResponse();
twiml.say(""); // respond to voice caller
callback(null, twiml);
})
};
Right now the SMS is sent and then the response to the call is sent. I'm not sure how to delay the message.create. I've been reading this but it doesn't work because I can't deal with the call first and then send the message.
https://www.twilio.com/docs/runtime/quickstart/add-delay
Twilio developer evangelist here.
This is not something you can do with just a Twilio Function. While aLittleSalty's answer will work in a situation where your application and event loop continues to run, like in an Express app that you run, but in a Twilio Function once you call the callback method the event loop is terminated so the delay will be cancelled.
Similarly, Twilio Function execution time is limited to 10 seconds, so you can't delay a message by a couple of minutes even if the above would work.
One idea could be that you send the message once the call has completed. Your example code only includes a <Say> so I will work with that, but you could do this in other ways if the call continues differently.
First, update your initial Function to return just the TwiML, but add a <Redirect> after the <Say> so that once the message completes Twilio sends a new webhook.
exports.handler = function (context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
twiml.say(""); // respond to voice caller
twiml.redirect("/after_say");
callback(null, twiml);
};
Then create another Function at the /after_say path that sends the message and hangs up the call.
exports.handler = function (context, event, callback) {
// Create a new voice response object
const client = context.getTwilioClient();
const client_number = event.From;
const twilio_number = +xxxx;
let twiml = new Twilio.twiml.VoiceResponse();
twiml.hangup();
client.messages.create({ // Send SMS
to: client_number,
from: twilio_number,
body: "Hello on SMS"
}).then(() => {
callback(null, twiml);
}).catch(error => {
console.error(error);
callback(null, twiml);
});
}
That's still not going to give you a couple of minutes between the call coming in and the message being sent, but it will hopefully send once the call is complete and be a better experience.
This doesn't work with Twilio Functions check #philnash answer
You have to return the callback first to answer the call, and then in a delayed promise send the SMS
exports.handler = function (context, event, callback) {
const delay = (delayMS) => new Promise((res) => {
setTimeout(res, delayMS)
})
// Create a new voice response object
const client = context.getTwilioClient();
const client_number = event.From;
const twilio_number = +xxxx;
let twiml = new Twilio.twiml.VoiceResponse();
twiml.say(""); // respond to voice caller
callback(null, twiml);
return (
delay(10000)
.then(() =>
client.messages.create({ // Send SMS
to: client_number,
from: twilio_number,
body: "Hello on SMS"
})
)
.then(() => console.log("SMS sent"))
)
}
Hopefully this helps.
Note: delay is an utility function where you pass the amounts of milliseconds you want to wait.

Facebook Graph API in aws lambda failing

I'm having trouble getting this to run. It runs just fine if I make the same FB.api call directly from the front end or through "serverless invoke local" and it console.logs my response. But when I deploy this function to a lambda and try to call it, I hit the "console.log("TRY"), get a 502 error, and then nothing after that. No response from the FB.api call, no errors, no info at all. I've tried upping the timeout as well and have brought it up to as much as 15 seconds and still getting no response. Anyone else run into this? Thanks!
export async function main(event, context, callback){
var FB = require('fb');
const data = JSON.parse(event.body)
console.log("DATA: ", data)
const requestString = data.Id + '/accounts'
console.log(requestString)
console.log("ACCESS TOKEN: ", data.accessToken)
const pages = []
try{
console.log("TRY")
await FB.api(requestString, 'get', { access_token: data.accessToken }, function(response){
console.log("RESPONSE: ", response)
callback(null, success(response));
})
}
catch (e){
console.log("CATCH")
console.log(e)
callback(null, failure({ status: false }));
}
}

Firebase Cloud Function never failing on 404 API call

I have a function running on the creation of a document.
When I send this information to an external API Firebase returns on 'ok' message before the API call is complete.
const functions = require('firebase-functions');
const request = require('request');
const admin = require('firebase-admin');
const rp = require('request-promise');
const port = '****';
const ip = '***.***.***.***';
admin.initializeApp(functions.config().firebase);
exports.sendUser = functions.firestore
.document('user/{userId}')
.onCreate((snap, context) => {
const data = snap.data();
const options = {
method: 'POST',
uri: 'http://' + ip + ':' + port + '/user',
body: data,
json: true,
};
rp(options)
.then(function (parsedBody) {
console.log('TEN ', parsedBody);
return parsedBody;
})
.catch(function (err) {
console.log('ERR ', err);
return err;
});
});
As you can see from my function it is not doing anything special apart from sending the data to an external source.
The API look like the following:-
app.post('/user', function (req, res) {
fs.exists(path, function(exists) {
if (exists === true) {
console.log('Currently Printing Different User Info');
fs.unlinkSync(path);
res.status(404).json({errorCode: 404, errorMessage: 'Currently Printing Different User.'});
return;
} else {
fs.writeFile(path, '', () => { console.log('File Created'); });
fs.unlinkSync(path);
res.status(200).json({statusCode: 200, statusMessage: 'Here we go'});
return;
}
});
})
How can I get Firebase to recognise the returned 404 as a failed call, and also wait until the call is complete before returning ok or failed.
The API is behaving correctly with Postman but not when data is posted via Firebase.
Has anyone encountered this before, or can anybody see what I am doing wrong?
The data is being parse over to the serve but only once Firebase has returned with 'ok' even if I purposely trigger a fail.
I need this in place to be able to use the Firebase Cloud Function retry function.
Images can be seen # https://imgur.com/a/1qYxrci
The Cloud Function returns the result before the call is complete because you don't return the Promise returned by the request-promise call.
Changing your code as follows should do the trick (at least for this problem):
exports.sendUser = functions.firestore
.document('user/{userId}')
.onCreate((snap, context) => {
const data = snap.data();
const options = {
method: 'POST',
uri: 'http://' + ip + ':' + port + '/user',
body: data,
json: true,
};
return rp(options) // <-- See the change here
.then(function (parsedBody) {
console.log('TEN ', parsedBody);
return parsedBody;
})
.catch(function (err) {
console.log('ERR ', err);
return err;
});
});
I would suggest you watch the official Video Series (https://firebase.google.com/docs/functions/video-series/) which explain very well this point about returning Promises for background functions (in particular the ones titled "Learn JavaScript Promises").

Slack slash command delayed response on AWS Lambda

I'm trying to make an integration for Slack that queries a server and gets back some results to the user. The search sometimes takes longer than the window Slack allows for responses, so I need to immediately return status 200.
How can I do this with a lambda function? I tried using the callback function and then sending the data to another lambda service, but the original function waits for it to return, meaning I'm still getting held up by the server I'm querying.
Here's what I'm working with
var rp = require('request-promise');
exports.handler = (event, context, callback) =>{
//I wanted this to send back my STATUS 200 so the command wouldn't time out
callback(null, "Working...");
//I want this post to happen asynchronously so that slack gets the callback response while the search is happening
//but this still waits until the post comes back before resolving the callback
var options = {
method: 'POST',
uri: "https://url-to-other.service",
body:{
"team": event.team,
"label": event.label,
"url": event.aresponse_url
},
json:true
};
rp(options);
};
When I run this, the callback text shows up after the result from the other function, meaning this service is waiting for the other to stop running before returning the status.
That won't work in a single lambda function because as soon as you invoke callback() the lambda container dies. What you could do is have this lambda function invoke another lambda function before calling callback() which in turn will POST to your slack channel url.
Here's an example of how that would work (it's not a 100% but should give you a good idea of how it'll work.)
Function 1: (receive slack event, invoke second function, immediatly return 200
let AWS = require('aws-sdk')
exports.handler = (event, context, callback) => {
let lambda = new AWS.Lambda()
let params = {
FunctionName: 'YOUR_SECOND_FUNCTION_NAME',
InvocationType: 'Event', // Ensures asynchronous execution
Payload: JSON.stringify({
team: event.team,
label: event.label,
url: event.aresponse_url
})
}
return lambda.invoke(params).promise() // Returns 200 immediately after invoking the second lambda, not waiting for the result
.then(() => callback(null, 'Working...'))
}
Function 2: (receive first lambda event, wait for search to complete, POST to slack channel)
let rp = require('request-promise')
exports.handler = (event, context, callback) => {
let searchOptions = {
method: 'POST',
uri: 'https://url-to-other.service',
body: {
'team': event.team,
'label': event.label,
'url': event.aresponse_url
},
json:true
}
return rp(searchOptions)
.then(result => {
let slackOptions = {
method: 'POST',
uri: 'YOUR_SLACK_CHANNEL_URL',
body: {
text: JSON.stringify(result) // I stringified the search result for you for ease's sake, not sure what you need here
},
json: true
}
return rp(slackOptions)
})
.then(() => { callback(null, 'good measure') })
}

Cloud Functions for Firebase HTTP timeout

I'm so close with this one.
I have written a Cloud Function that takes information sent from an Azure token to custom mint a Firebase token and send this token back to the client.
The token is created correctly, but isn't returned on my HTTP-request.
Unfortunately my Firebase app causes a timeout.
Function execution took 60002 ms, finished with status: 'timeout'
I can't really wrap my head around why that is, hence this post. Is there something wrong with my code, or is it me that's calling the HTTP-request wrong?
Here is the log I get from the Firebase Functions console.
Here's my code
// Create a Firebase token from any UID
exports.createFirebaseToken = functions.https.onRequest((req, res) => {
// The UID and other things we'll assign to the user.
const uid = req.body.uid;
const additionalClaims = {
name: req.body.name,
email: req.body.email
};
// Create or update the user account.
const userCreationTask = admin.auth().updateUser(uid, additionalClaims).catch(error => {
// If user does not exists we create it.
if (error.code === 'auth/user-not-found') {
console.log(`Created user with UID:${uid}, Name: ${additionalClaims.name} and e-mail: ${additionalClaims.email}`);
return admin.auth().createUser({
uid: uid,
displayName: displayName,
email: email,
});
}
throw error;
console.log('Error!');
});
// Wait for all async tasks to complete, then generate and return a custom auth token.
return Promise.all([userCreationTask]).then(() => {
console.log('Function create token triggered');
// Create a Firebase custom auth token.
return admin.auth().createCustomToken(uid, additionalClaims).then((token) => {
console.log('Created Custom token for UID "', uid, '" Token:', token);
return token;
});
});
});
When I'm making this HTTP-request, all i'm sending in is a JSON that looks like this:
parameters = [
"uid" : id,
"email" : mail,
"name" : name
]
Cloud Functions triggered by HTTP requests need to be terminated by ending them with a send(), redirect(), or end(), otherwise they will continue running and reach the timeout.
From the terminate HTTP functions section of the documentation on HTTP triggers:
Always end an HTTP function with send(), redirect(), or end(). Otherwise, your function might to continue to run and be forcibly terminated by the system. See also Sync, Async and Promises.
After retrieving and formatting the server time using the Node.js moment module, the date() function concludes by sending the result in the HTTP response:
const formattedDate = moment().format(format);
console.log('Sending Formatted date:', formattedDate);
res.status(200).send(formattedDate);
So, within your code, you could send the token back in the response with send(), for example:
// ...
// Create a Firebase custom auth token.
return admin.auth().createCustomToken(uid, additionalClaims).then((token) => {
console.log('Created Custom token for UID "', uid, '" Token:', token);
res.status(200).send(token);
return token;
});
// ...

Categories

Resources