Twilio issue with task sitting in "Wrapping Up" - twilio

I'm testing out a new phone system design using Twilio TaskRouter, Studio, and Functions. I've gotten to the point that I can finish the call, but the task sits in "Wrapping Up" and won't allow a new call from the queue to go to the worker associated with that task until I physically delete that task. I've looked everywhere on how to close the task (get out of Wrapping Up), but can't find any good documentation anywhere.
I have a URL for the "Event Callbacks" of the TaskRouter and can capture exactly when the call moves to this EventType "task.wrapup", but don't know what to do at this point to move it past this step so it releases the task and worker.

So, with a little more digging I found the solution. For anyone coming here and having the problem I was having, here is the answer.
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
let client = context.getTwilioClient();
switch(event.EventType) {
case 'task.wrapup':
let workspaceId = 'WSxxxxxxxxxxxxxxxxxxxxxxxxx';
console.log(event.TaskSid);
client.taskrouter.workspaces(workspaceId)
.tasks(event.TaskSid)
.update({
assignmentStatus: 'completed',
reason: 'Call completed'
})
.then(task => {
callback(null, twiml);
})
.catch(err => {
console.log(err);
callback(null, twiml);
});
break;
default:
callback(null, twiml);
break;
}
};
Hope this helps someone else :D

I ran into the same issues and came up with this solution. It's client-side Javascript based and using Twilio's taskrouter.js:
function registerTaskRouterCallbacks() {
worker.on("reservation.wrapup", function(reservation) {
worker.completeTask(reservation.task.sid,
function(error,completedTask) {
// Do stuff here if needed
});
});
}
window.onload = function() {
// Initialize TaskRouter.js on page load using window.workerToken -
// a Twilio Capability token that was set in a <script> in associated web page (PHP, etc.) script
window.worker = new Twilio.TaskRouter.Worker(workerToken);
registerTaskRouterCallbacks();
};

Related

Forwarding an incoming call after task reservation timeout

I am using Twilio Flex to support a call center. I have a TaskRouter workflow set up where Task Reservation Timeout is set to 120 seconds. In its filter, I've created two routing steps. The first one finds matching workers in the main queue and has a timeout of 120 seconds. After 120 seconds, it should move to Call Forward Queue. In the call forward queue, no workers exist (target worker expression: 1==2). I'm catching all these events with a "trEventListener" function. Once a task is moved into the Call Forward queue, I call the "callForward" function which uses twiml.dial() to connect the call to an external number. I also change this task's status to "canceled" with a custom reason so I can track it in flex insights. I am using the guide in this link to form my logic: https://support.twilio.com/hc/en-us/articles/360021082934-Implementing-Voicemail-with-Twilio-Flex-TaskRouter-and-WFO.
Call forwarding is working fine but according to Flex insights, there are some calls that get handled after 120 seconds (between 120 - 300 seconds). Ideally, these should be forwarded as well. There is also no error logged for me to track down why this is happening to only a handful of calls.
Furthermore, in some cases, when I try to change the task status to cancel with my custom reason, it spits out the following error: Cannot cancel task because it is not pending or reserved. In other cases, it works fine. It's again hard to figure out why it's selectively working and not consistent in its behavior.
Here is the function code.
trEventListener.js:
exports.handler = function(context, event, callback) {
const client = context.getTwilioClient();
let task = '';
let workspace = '';
console.log(`__[trEventStream]__: Event recieved of type: ${event.EventType}`);
// setup an empty success response
let response = new Twilio.Response();
response.setStatusCode(204);
// switch on the event type
switch(event.EventType) {
case 'task-queue.entered':
// ignore events that are not entering the 'Call Forward' TaskQueue
if (event.TaskQueueName !== 'Call Forward') {
console.log(`__[trEventStream]__: Entered ${event.TaskQueueName} queue - no forwarding required!`);
return callback(null, response);
}
console.log(`__[trEventStream]__: entered ${event.TaskQueueName} queue - forwarding call!`);
task = event.TaskSid;
workspace = event.WorkspaceSid;
const ta = JSON.parse(event.TaskAttributes);
const callSid = ta.call_sid;
let url = `https://${context.DOMAIN_NAME}/forwardCall`;
// redirect call to forwardCall function
client.calls(callSid).update({
method: 'POST',
url: encodeURI(url),
}).then(() => {
console.log(`__[trEventStream]__: [SUCCESS] ~> Task with id ${task} forwarded to external DID`);
// change task status to canceled so it doesn't appear in flex or show up as a pending task
client.taskrouter.workspaces(workspace)
.tasks(task)
.update({
assignmentStatus: 'canceled',
reason: 'Call forwarded'
})
.then(task => {
console.log(`__[trEventStream]__: [SUCCESS] ~> Task canceled`);
return callback(null, response);
}).catch(err => {
console.log(`__[trEventStream]__: [ERROR] ~> Task not marked complete: `, err);
// doesn't warrant reponse 500 since call still forwarded :)
return callback(null, response);
});
}).catch(err => {
console.log(`__[trEventStream]__: [ERROR] ~> Task failed to forward to external DID: `, err);
response.setStatusCode(500);
return callback(err, response);
});
break;
default:
return callback(null, response);
}
};
callForward.js:
exports.handler = function(context, event, callback) {
console.log(`forwarding call`);
// set-up the variables that this Function will use to forward a phone call using TwiML
// REQUIRED - you must set this
let phoneNumber = event.PhoneNumber || context.NUMBER;
// OPTIONAL
let callerId = event.CallerId || null;
// OPTIONAL
let timeout = event.Timeout || null;
// OPTIONAL
let allowedCallers = event.allowedCallers || [];
let allowedThrough = true;
if (allowedCallers.length > 0) {
if (allowedCallers.indexOf(event.From) === -1) {
allowedThrough = false;
}
}
// generate the TwiML to tell Twilio how to forward this call
let twiml = new Twilio.twiml.VoiceResponse();
let dialParams = {};
if (callerId) {
dialParams.callerId = callerId;
}
if (timeout) {
dialParams.timeout = timeout;
}
if (allowedThrough) {
twiml.dial(dialParams, phoneNumber); // making call :)
}
else {
twiml.say('Sorry, you are calling from a restricted number. Good bye.');
}
// return the TwiML
callback(null, twiml);
};
Any kind of help and/or guidance will be appreciated.
Twilio developer evangelist here.
When you redirect a call from a task, its task is cancelled with the reason "redirected" so you don't need to cancel it yourself.
Your code was failing to update the task occasionally because of a race condition between your code and the task getting cancelled by Twilio.

How to properly Loop through SMS sending via Twilio Functions

I have Twilio Studio calling a Twilio Function and need it to send email to a variable list (small list) of emails. This question is mostly around looping through them as I can pass variables just fine. I have an array of emails to send a text to and am in a Twilio Function. But all examples I find online are about sending to just ONE. Part of me thinks this needs to be a Twilio Function calling another Twilio function (one loops, the other sends Emails)... but I can't figure out a way to do that. If I could contain it to one Twilio function, that would be great.
I have Twilio Studio calling a Twilio function. I need to keep this all on Twilio... so looping through via PHP and running functions one at a time through there doesn't work. I need this to run on Twilio's serverless setup.
Here's an example of what I have that works:
exports.handler = function(context, event, callback) {
// using SendGrid's v3 Node.js Library
// https://github.com/sendgrid/sendgrid-nodejs
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(context.SENDGRID_API_KEY);
const msg = {
to: 'me#example.com',
from: 'noreply#example.com',
templateId: 'my-id-goes-here',
dynamic_template_data: {
recipient_name: 'John Smith'
}
};
sgMail.send(msg).then(response => {
let twiml = new Twilio.twiml.MessagingResponse();
callback(null, twiml);
})
.catch(err => {
callback(err);
});
};
Here's me trying to loop through in similar fashion and failing
exports.handler = function(context, event, callback) {
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(context.SENDGRID_API_KEY);
var responder_emails = 'me#example.com,me+test1#example.com';
var emails_a = responder_emails.split(',');
emails_a.forEach(function(responder_email) {
const msg = {
to: responder_email,
from: 'noreply#example.com',
templateId: 'my-id-goes-here',
dynamic_template_data: {
recipient_name: 'John Smith'
}
};
sgMail.send(msg);
});
callback();
};
I can pass in multiple emails into a Twilio function... I'm just not sure how to loop through correctly.
Heyo. Twilio Evangelist here. 👋
In your first example, you rightfully waited for the send call to be done by using then. In your second example, you missed that. You run several send calls but immediately call callback without waiting.
A fixed (roughly prototyped version) could look as follows.
exports.handler = function(context, event, callback) {
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(context.SENDGRID_API_KEY);
var responder_emails = 'me#example.com,me+test1#example.com';
var emails_a = responder_emails.split(',');
Promise.all(emails_a.map(function(responder_email) {
const msg = {
to: responder_email,
from: 'noreply#example.com',
templateId: 'my-id-goes-here',
dynamic_template_data: {
recipient_name: 'John Smith'
}
};
return sgMail.send(msg);
})).then(function() {
callback();
}).catch(function(e) {
callback(e);
})
});
You have already an array of emails because you called split. You can use this array in combination with Array.map and Promise.all.
Map basically iterates over your array and lets you create a new array with whatever you return from the function inside of map. What the code above does is that it transforms [email, email] to [Promise, Promise]. The promises are the return value of sgMail.send.
Now, that you have an array holding promises that will resolve when sendgrid accepted your call, you can use Promise.all. This method waits for all the promises to be resolved (or rejected) and returns itself a new promise which you can use then with. When all sendgrid calls are done it's time to finish the function by calling the function callback.
Side note: this "map/Promise.all" trick performs all send grid calls in parallel. There might be situations where you want to call them one after another (saying you are doing a lot of calls and run into rate limiting).
Hope that helps and let me know how it goes. :)

Create a HTTP POST to Twilio Functions (send SMS)

I have created a Twilio function that I would like to use to send my affiliate referral link to subscribers of an application that come through my channel.
It works fine with a static to / from number, however I would like to make the "to" field a dynamic variable that can be manipulated via a HTTP/Webhook POST when a Zapier detects a new subscriber to my Mailchimp mailing list and pass their phone number as the variable.
I am also unclear what I need to do to authenticate the client (Zapier) that is making the POST as I do not want the function open to the world to use, if any insights can be shared on this it would be sincerely appreciated - I am a very inexperienced programmer trying to learn very quickly!
#philnash - thanks for your suggestion, implementing it slowly!
Many thanks in advance!
exports.handler = function(context, event, callback) {
const appCodes = ['code1', 'code2', 'code3', 'code4']
var smsBody = refCode ();
function refCode () {
return appCodes[Math.floor((Math.random() * appCodes.length))];
};
context.getTwilioClient().messages.create({
to: '+11112223333', // How do I make this dynamic from HTTP/Zapier Webhook POST???
from: '+1444555666',
body: `Get the App: ${smsBody}`
}).then(msg => {
callback(null, msg.sid);
}).catch(err => callback(err));
}
Twilio developer evangelist here.
I presume the Zapier webhook is sending the details, including the phone number, as the body of the POST request.
All the parameters in a request body appear on the event object that is passed into your handler. You probably want to run a test where you print out the contents of the event object to see what you are being passed. You can do this with:
exports.handler = function(context, event, callback) {
for (let key in event) {
console.log(`${key}: ${event[key]}`);
}
// ... rest of the function
}
Then, when you figure out what parameter is storing the number, you can use that in the call to create the message.
Let me know if that helps at all.
Try this:
exports.handler = function(context, event, callback) {
for (let key in event) {
console.log(`${key}: ${event[key]}`);
}
// ... rest of the function
callback(null, 'complete');
};
Thanks everyone for your input, it was sincerely appreciated! I was able to solve this with the following code:
exports.handler = function(context, event, callback) {
const appCodes = ['code1', 'code2', 'code3', 'code4']
var smsBody = refCode ();
var subNum = event.primaryPhone || 'There is no subscriber number'; // primaryPhone sent via HTTP post to twilio function
function refCode () {
return appCodes[Math.floor((Math.random() * appCodes.length))];
};
context.getTwilioClient().messages.create({
to: `${subNum}`, // parameters & values recieved from HTTP POST are available within the twilio functions "event" context
from: '+1444555666',
body: `Get the App: ${smsBody}`
}).then(msg => {
callback(null, msg.sid);
}).catch(err => callback(err));
}

How to subscribe to and see events from Hyperledger Composer transactions

I am running an NodeJS server with the following code to connect to the Hyperledger Runtime:
const BusinessNetworkConnection = require("composer-client")
.BusinessNetworkConnection;
this.businessNetworkConnection = new BusinessNetworkConnection();
this.CONNECTION_PROFILE_NAME = "hlfv1";
this.businessNetworkIdentifier = "testNetwork";
this.businessNetworkConnection
.connect(
this.CONNECTION_PROFILE_NAME,
this.businessNetworkIdentifier,
"admin",
"adminpwd"
)
.then(result => {
this.businessNetworkDefinition = result;
console.log("BusinessNetworkConnection: ", result);
})
.then(() => {
// Subscribe to events.
this.businessNetworkConnection.on("events", events => {
console.log("**********business event received**********", events);
});
})
// and catch any exceptions that are triggered
.catch(function(error) {
throw error;
});
I see data returned after the connection has been made in the result object and it is the correct network data that has been deployed.
However, when I submit transactions and made request VIA my generated REST APIs no events are seen by my server. In the Historian, I can see that events are emitted. Is there something else that I should be doing to see those events emitted by my transactions?
I tried same kind of test and I could receive events. I compared my test code and yours, and I found following difference:
this.bizNetworkConnection.on('events'
this.bizNetworkConnection.on('event'
I hope it helps.

Service Worker caching not recognizing timeout as a function

I was watching Steve Sanderson's NDC presentation on up-and-coming web features, and saw his caching example as a prime candidate for an application I am developing. I couldn't find the code, so I have typed it up off the Youtube video as well as I could.
Unfortunately it doesn't work in Chrome (which is also what he is using in the demo) It fails with Uncaught TypeError: fetch(...).then(...).timeout is not a function
at self.addEventListener.event.
I trawled through Steve's Github, and found no trace of this, nor could I find anything on the NDC Conference page
//inspiration:
// https://www.youtube.com/watch?v=MiLAE6HMr10
//self.importScripts('scripts/util.js');
console.log('Service Worker script running');
self.addEventListener('install', event => {
console.log('WORKER: installing');
const urlsToCache = ['/ServiceWorkerExperiment/', '/ServiceWorkerExperiment/scripts/page.js'];
caches.delete('mycache');
event.waitUntil(
caches.open('mycache')
.then(cache => cache.addAll(urlsToCache))
.then(_ => self.skipWaiting())
);
});
self.addEventListener('fetch', event => {
console.log(`WORKER: Intercepted request for ${event.request.url}`);
if (event.request.method !== 'GET') {
return;
}
event.respondWith(
fetch(event.request)
.then(networkResponse => {
console.log(`WORKER: Updating cached data for ${event.request.url}`);
var responseClone = networkResponse.clone();
caches.open('mycache').then(cache => cache.put(event.request, responseClone));
return networkResponse;
})
//if network fails or is too slow, return cached data
//reference for this code: https://youtu.be/MiLAE6HMr10?t=1003
.timeout(200)
.catch(_ => {
console.log(`WORKER: Serving ${event.request.url} from CACHE`);
return caches.match(event.request);
})
);
});
As far as I read the fetch() documentation, there is no timeout function, so my assumption is that the timeout function is added in the util.js which is never shown in the presentation... can anyone confirm this? and does anyone have an Idea about how this is implemented?
Future:
It's coming.
According to Jake Archibald's comment on whatwg/fetch the future syntax will be:
Using the abort syntax, you'll be able to do:
const controller = new AbortController();
const signal = controller.signal;
const fetchPromise = fetch(url, {signal});
// 5 second timeout:
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetchPromise;
// …
If you only wanted to timeout the request, not the response, add:
clearTimeout(timeoutId);
// …
And from another comment:
Edge & Firefox are already implementing. Chrome will start shortly.
Now:
If you want to try the solution that works now, the most sensible way is to use this module.
It allows you to use syntax like:
return fetch('/path', {timeout: 500}).then(function() {
// successful fetch
}).catch(function(error) {
// network request failed / timeout
})

Resources