Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Control Worker Activities using Worker.js: Add an Agent UI to our Project


Let's get started on our agent UI. Assuming you've followed the conventions so far in this tutorial, the UI we create will be accessible using your web browser at:

http://localhost:8080/agents?WorkerSid=WK01234012340123401234 (substitute your Alice's WorkerSid)

We pass the WorkerSid in the URL to avoid implementing complex user management in our demo. In reality, you are likely to store a user's WorkerSid in your database alongside other User attributes.

Let's add on our TwilioTaskRouterServlet file to add an endpoint to generate a page based on a template.


TwilioTaskRouterServlet.java

twiliotaskrouterservletjava page anchor

_171
import java.io.IOException;
_171
import java.util.HashMap;
_171
import java.util.List;
_171
import java.util.Map;
_171
_171
import javax.servlet.RequestDispatcher;
_171
import javax.servlet.ServletException;
_171
import javax.servlet.http.HttpServlet;
_171
import javax.servlet.http.HttpServletRequest;
_171
import javax.servlet.http.HttpServletResponse;
_171
_171
import com.twilio.Twilio;
_171
import com.twilio.http.HttpMethod;
_171
import com.twilio.jwt.taskrouter.*;
_171
import com.twilio.rest.taskrouter.v1.workspace.Task;
_171
import com.twilio.rest.taskrouter.v1.workspace.task.Reservation;
_171
import com.twilio.twiml.*;
_171
import org.json.simple.JSONObject;
_171
_171
public class TwilioTaskRouterServlet extends HttpServlet {
_171
_171
private String accountSid;
_171
private String authToken;
_171
private String workspaceSid;
_171
private String workflowSid;
_171
_171
@Override
_171
public void init() {
_171
accountSid = this.getServletConfig().getInitParameter("AccountSid");
_171
authToken = this.getServletConfig().getInitParameter("AuthToken");
_171
workspaceSid = this.getServletConfig().getInitParameter("WorkspaceSid");
_171
workflowSid = this.getServletConfig().getInitParameter("WorkflowSid");
_171
_171
Twilio.init(accountSid, authToken);
_171
}
_171
_171
// service() responds to both GET and POST requests.
_171
// You can also use doGet() or doPost()
_171
@Override
_171
public void service(final HttpServletRequest request, final HttpServletResponse response)
_171
throws IOException, ServletException {
_171
if (request.getPathInfo() == null || request.getPathInfo().isEmpty()) {
_171
return;
_171
}
_171
_171
if (request.getPathInfo().equals("/assignment_callback")) {
_171
response.setContentType("application/json");
_171
_171
final Map<String, String> dequeueInstruction = new HashMap<String, String>();
_171
dequeueInstruction.put("instruction", "dequeue");
_171
dequeueInstruction.put("from", "+15556667777");
_171
dequeueInstruction.put("post_work_activity_sid", "WA0123401234...");
_171
_171
response.getWriter().print(JSONObject.toJSONString(dequeueInstruction));
_171
} else if (request.getPathInfo().equals("/create_task")) {
_171
response.setContentType("application/json");
_171
final String taskAttributes = createTask();
_171
response.getWriter().print(createTask());
_171
} else if (request.getPathInfo().equals("/accept_reservation")) {
_171
response.setContentType("application/json");
_171
final String taskSid = request.getParameter("TaskSid");
_171
final String reservationSid = request.getParameter("ReservationSid");
_171
response.getWriter().print(acceptReservation(taskSid, reservationSid));
_171
} else if (request.getPathInfo().equals("/incoming_call")) {
_171
response.setContentType("application/xml");
_171
response.getWriter().print(handleIncomingCall());
_171
} else if (request.getPathInfo().equals("/enqueue_call")) {
_171
response.setContentType("application/xml");
_171
response.getWriter().print(enqueueTask());
_171
} else if (request.getPathInfo().equals("/agents")) {
_171
generateAgentView(request, response);
_171
}
_171
}
_171
_171
public String createTask() {
_171
String attributes = "{\"selected_language\":\"es\"}";
_171
_171
Task task = Task.creator(workspaceSid, attributes, workflowSid).create();
_171
_171
return "{\"task_sid\":\"" + task.getSid() + "\"}";
_171
}
_171
_171
public String acceptReservation(final String taskSid, final String reservationSid) {
_171
Reservation reservation = Reservation.updater(workspaceSid, taskSid, reservationSid)
_171
.setReservationStatus(Reservation.Status.ACCEPTED).update();
_171
_171
return "{\"worker_name\":\"" + reservation.getWorkerName() + "\"}";
_171
}
_171
_171
public String handleIncomingCall() {
_171
VoiceResponse twiml =
_171
new VoiceResponse.Builder()
_171
.gather(new Gather.Builder()
_171
.say(new Say.Builder("Para Español oprime el uno.").language(Say.Language.ES)
_171
.build())
_171
.say(new Say.Builder("For English, please hold or press two.")
_171
.language(Say.Language.EN).build())
_171
.numDigits(1).timeout(5).build())
_171
.build();
_171
_171
try {
_171
return twiml.toXml();
_171
} catch (TwiMLException e) {
_171
return "Error creating TwiML: " + e.getMessage();
_171
}
_171
}
_171
_171
public String enqueueTask() {
_171
com.twilio.twiml.Task task =
_171
new com.twilio.twiml.Task.Builder().data("{\"selected_language\":\"es\"}").build();
_171
_171
EnqueueTask enqueue = new EnqueueTask.Builder(task).workflowSid(workflowSid).build();
_171
_171
VoiceResponse twiml = new VoiceResponse.Builder().enqueue(enqueue).build();
_171
_171
try {
_171
return twiml.toXml();
_171
} catch (TwiMLException e) {
_171
return "Error creating TwiML: " + e.getMessage();
_171
}
_171
}
_171
_171
public void generateAgentView(final HttpServletRequest request, final HttpServletResponse response)
_171
throws ServletException, IOException {
_171
final String workerSid = request.getParameter("WorkerSid");
_171
_171
List<Policy> policies = PolicyUtils.defaultWorkerPolicies(workspaceSid, workerSid);
_171
_171
Map<String, FilterRequirement> activityUpdateFilter = new HashMap<>();
_171
activityUpdateFilter.put("ActivitySid", FilterRequirement.REQUIRED);
_171
_171
Policy allowActivityUpdates = new Policy.Builder()
_171
.url(UrlUtils.worker(workspaceSid, workerSid))
_171
.method(HttpMethod.POST)
_171
.postFilter(activityUpdateFilter).build();
_171
_171
Policy allowTasksUpdate = new Policy.Builder()
_171
.url(UrlUtils.allTasks(workerSid))
_171
.method(HttpMethod.POST)
_171
.build();
_171
_171
Policy allowReservationUpdate = new Policy.Builder()
_171
.url(UrlUtils.allReservations(workspaceSid, workerSid))
_171
.method(HttpMethod.POST)
_171
.build();
_171
_171
policies.add(allowActivityUpdates);
_171
policies.add(allowTasksUpdate);
_171
policies.add(allowReservationUpdate);
_171
_171
_171
TaskRouterCapability.Builder capabilityBuilder =
_171
new TaskRouterCapability.Builder(accountSid, authToken, workspaceSid, workerSid)
_171
.policies(policies);
_171
_171
String token = capabilityBuilder.build().toJwt();
_171
_171
System.out.println(token);
_171
_171
// By default, tokens are good for one hour.
_171
// Override this default timeout by specifiying a new value (in seconds).
_171
// For example, to generate a token good for 8 hours:
_171
token = capabilityBuilder.build().toJwt();
_171
_171
// Forward the token information to a JSP view
_171
response.setContentType("text/html");
_171
request.setAttribute("worker_token", token);
_171
final RequestDispatcher view = request.getServletContext().getRequestDispatcher("/agent.jsp");
_171
view.forward(request, response);
_171
}
_171
}


taskrouter/WEB-INF/web.xml

taskrouterweb-infwebxml page anchor

_19
<?xml version="1.0" encoding="ISO-8859-1"?>
_19
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
_19
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
_19
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
_19
version="2.4">
_19
_19
<display-name>Twilio TaskRouter App</display-name>
_19
_19
<servlet>
_19
<servlet-name>TwilioTaskRouterServlet</servlet-name>
_19
<servlet-class>com.twilio.TwilioTaskRouterServlet</servlet-class>
_19
</servlet>
_19
_19
<servlet-mapping>
_19
<servlet-name>TwilioTaskRouterServlet</servlet-name>
_19
<url-pattern>/taskrouter/*</url-pattern>
_19
</servlet-mapping>
_19
_19
</web-app>

Now create a JSP file that will be rendered when the URL is requested:


taskrouter/WEB-INF/agent.jsp

taskrouterweb-infagentjsp page anchor

_141
<!DOCTYPE html>
_141
<html>
_141
<head>
_141
<title>Customer Care - Voice Agent Screen</title>
_141
<link rel="stylesheet" href="//media.twiliocdn.com/taskrouter/quickstart/agent.css"/>
_141
<script src="https://sdk.twilio.com/js/taskrouter/v1.21/taskrouter.min.js" integrity="sha384-5fq+0qjayReAreRyHy38VpD3Gr9R2OYIzonwIkoGI4M9dhfKW6RWeRnZjfwSrpN8" crossorigin="anonymous"></script>
_141
<script type="text/javascript">
_141
/* Subscribe to a subset of the available TaskRouter.js events for a worker */
_141
function registerTaskRouterCallbacks() {
_141
worker.on('ready', function(worker) {
_141
agentActivityChanged(worker.activityName);
_141
logger("Successfully registered as: " + worker.friendlyName)
_141
logger("Current activity is: " + worker.activityName);
_141
});
_141
_141
worker.on('activity.update', function(worker) {
_141
agentActivityChanged(worker.activityName);
_141
logger("Worker activity changed to: " + worker.activityName);
_141
});
_141
_141
worker.on("reservation.created", function(reservation) {
_141
logger("-----");
_141
logger("You have been reserved to handle a call!");
_141
logger("Call from: " + reservation.task.attributes.from);
_141
logger("Selected language: " + reservation.task.attributes.selected_language);
_141
logger("-----");
_141
});
_141
_141
worker.on("reservation.accepted", function(reservation) {
_141
logger("Reservation " + reservation.sid + " accepted!");
_141
});
_141
_141
worker.on("reservation.rejected", function(reservation) {
_141
logger("Reservation " + reservation.sid + " rejected!");
_141
});
_141
_141
worker.on("reservation.timeout", function(reservation) {
_141
logger("Reservation " + reservation.sid + " timed out!");
_141
});
_141
_141
worker.on("reservation.canceled", function(reservation) {
_141
logger("Reservation " + reservation.sid + " canceled!");
_141
});
_141
}
_141
_141
/* Hook up the agent Activity buttons to TaskRouter.js */
_141
_141
function bindAgentActivityButtons() {
_141
// Fetch the full list of available Activities from TaskRouter. Store each
_141
// ActivitySid against the matching Friendly Name
_141
var activitySids = {};
_141
worker.activities.fetch(function(error, activityList) {
_141
var activities = activityList.data;
_141
var i = activities.length;
_141
while (i--) {
_141
activitySids[activities[i].friendlyName] = activities[i].sid;
_141
}
_141
});
_141
_141
/* For each button of class 'change-activity' in our Agent UI, look up the
_141
ActivitySid corresponding to the Friendly Name in the button's next-activity
_141
data attribute. Use Worker.js to transition the agent to that ActivitySid
_141
when the button is clicked.*/
_141
var elements = document.getElementsByClassName('change-activity');
_141
var i = elements.length;
_141
while (i--) {
_141
elements[i].onclick = function() {
_141
var nextActivity = this.dataset.nextActivity;
_141
var nextActivitySid = activitySids[nextActivity];
_141
worker.update({"ActivitySid":nextActivitySid});
_141
}
_141
}
_141
}
_141
_141
/* Update the UI to reflect a change in Activity */
_141
_141
function agentActivityChanged(activity) {
_141
hideAgentActivities();
_141
showAgentActivity(activity);
_141
}
_141
_141
function hideAgentActivities() {
_141
var elements = document.getElementsByClassName('agent-activity');
_141
var i = elements.length;
_141
while (i--) {
_141
elements[i].style.display = 'none';
_141
}
_141
}
_141
_141
function showAgentActivity(activity) {
_141
activity = activity.toLowerCase();
_141
var elements = document.getElementsByClassName(('agent-activity ' + activity));
_141
elements.item(0).style.display = 'block';
_141
}
_141
_141
/* Other stuff */
_141
_141
function logger(message) {
_141
var log = document.getElementById('log');
_141
log.value += "\n> " + message;
_141
log.scrollTop = log.scrollHeight;
_141
}
_141
_141
window.onload = function() {
_141
// Initialize TaskRouter.js on page load using window.workerToken -
_141
// a Twilio Capability token that was set from rendering the template with agents endpoint
_141
logger("Initializing...");
_141
window.worker = new Twilio.TaskRouter.Worker("${worker_token}");
_141
_141
registerTaskRouterCallbacks();
_141
bindAgentActivityButtons();
_141
};
_141
</script>
_141
</head>
_141
<body>
_141
<div class="content">
_141
<section class="agent-activity offline">
_141
<p class="activity">Offline</p>
_141
<button class="change-activity" data-next-activity="Idle">Go Available</button>
_141
</section>
_141
<section class="agent-activity idle">
_141
<p class="activity"><span>Available</span></p>
_141
<button class="change-activity" data-next-activity="Offline">Go Offline</button>
_141
</section>
_141
<section class="agent-activity reserved">
_141
<p class="activity">Reserved</p>
_141
</section>
_141
<section class="agent-activity busy">
_141
<p class="activity">Busy</p>
_141
</section>
_141
<section class="agent-activity wrapup">
_141
<p class="activity">Wrap-Up</p>
_141
<button class="change-activity" data-next-activity="Idle">Go Available</button>
_141
<button class="change-activity" data-next-activity="Offline">Go Offline</button>
_141
</section>
_141
<section class="log">
_141
<textarea id="log" readonly="true"></textarea>
_141
</section>
_141
</div>
_141
</body>
_141
</html>

You'll notice that we included two external files:

  • taskrouter.min.js is the primary TaskRouter.js JavaScript file that communicates with TaskRouter's infrastructure on our behalf. You can use this URL to include Worker.js in your production application, but first check the reference documentation to ensure that you include the latest version number.
  • agent.css is a simple CSS file created for the purpose of this Quickstart. It saves us having to type out some simple pre-defined styles.

And that's it! Compile your Java class and start your server.

Open http://yourserver.com/<path-to-servlet>/agents?WorkerSid=WK012340123401234 in your browser and you should see the screen below. If you make the same phone call as we made in Part 3, you should see Alice's Activity transition on screen as she is reserved and assigned to handle the Task.

If you see "Initializing..." and no progress, make sure that you have included the correct WorkerSid in the "WorkerSid" request parameter of the URL.

For more details, refer to the TaskRouter JavaScript SDK documentation.


  • This simple PoC has been tested in the latest version of popular browsers, including IE 11. *
Completed Agent UI.

Rate this page: