Skip to contentSkip to navigationSkip to topbar
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
1
import java.io.IOException;
2
import java.util.HashMap;
3
import java.util.List;
4
import java.util.Map;
5
6
import javax.servlet.RequestDispatcher;
7
import javax.servlet.ServletException;
8
import javax.servlet.http.HttpServlet;
9
import javax.servlet.http.HttpServletRequest;
10
import javax.servlet.http.HttpServletResponse;
11
12
import com.twilio.Twilio;
13
import com.twilio.http.HttpMethod;
14
import com.twilio.jwt.taskrouter.*;
15
import com.twilio.rest.taskrouter.v1.workspace.Task;
16
import com.twilio.rest.taskrouter.v1.workspace.task.Reservation;
17
import com.twilio.twiml.*;
18
import org.json.simple.JSONObject;
19
20
public class TwilioTaskRouterServlet extends HttpServlet {
21
22
private String accountSid;
23
private String authToken;
24
private String workspaceSid;
25
private String workflowSid;
26
27
@Override
28
public void init() {
29
accountSid = this.getServletConfig().getInitParameter("AccountSid");
30
authToken = this.getServletConfig().getInitParameter("AuthToken");
31
workspaceSid = this.getServletConfig().getInitParameter("WorkspaceSid");
32
workflowSid = this.getServletConfig().getInitParameter("WorkflowSid");
33
34
Twilio.init(accountSid, authToken);
35
}
36
37
// service() responds to both GET and POST requests.
38
// You can also use doGet() or doPost()
39
@Override
40
public void service(final HttpServletRequest request, final HttpServletResponse response)
41
throws IOException, ServletException {
42
if (request.getPathInfo() == null || request.getPathInfo().isEmpty()) {
43
return;
44
}
45
46
if (request.getPathInfo().equals("/assignment_callback")) {
47
response.setContentType("application/json");
48
49
final Map<String, String> dequeueInstruction = new HashMap<String, String>();
50
dequeueInstruction.put("instruction", "dequeue");
51
dequeueInstruction.put("from", "+15556667777");
52
dequeueInstruction.put("post_work_activity_sid", "WA0123401234...");
53
54
response.getWriter().print(JSONObject.toJSONString(dequeueInstruction));
55
} else if (request.getPathInfo().equals("/create_task")) {
56
response.setContentType("application/json");
57
final String taskAttributes = createTask();
58
response.getWriter().print(createTask());
59
} else if (request.getPathInfo().equals("/accept_reservation")) {
60
response.setContentType("application/json");
61
final String taskSid = request.getParameter("TaskSid");
62
final String reservationSid = request.getParameter("ReservationSid");
63
response.getWriter().print(acceptReservation(taskSid, reservationSid));
64
} else if (request.getPathInfo().equals("/incoming_call")) {
65
response.setContentType("application/xml");
66
response.getWriter().print(handleIncomingCall());
67
} else if (request.getPathInfo().equals("/enqueue_call")) {
68
response.setContentType("application/xml");
69
response.getWriter().print(enqueueTask());
70
} else if (request.getPathInfo().equals("/agents")) {
71
generateAgentView(request, response);
72
}
73
}
74
75
public String createTask() {
76
String attributes = "{\"selected_language\":\"es\"}";
77
78
Task task = Task.creator(workspaceSid, attributes, workflowSid).create();
79
80
return "{\"task_sid\":\"" + task.getSid() + "\"}";
81
}
82
83
public String acceptReservation(final String taskSid, final String reservationSid) {
84
Reservation reservation = Reservation.updater(workspaceSid, taskSid, reservationSid)
85
.setReservationStatus(Reservation.Status.ACCEPTED).update();
86
87
return "{\"worker_name\":\"" + reservation.getWorkerName() + "\"}";
88
}
89
90
public String handleIncomingCall() {
91
VoiceResponse twiml =
92
new VoiceResponse.Builder()
93
.gather(new Gather.Builder()
94
.say(new Say.Builder("Para Español oprime el uno.").language(Say.Language.ES)
95
.build())
96
.say(new Say.Builder("For English, please hold or press two.")
97
.language(Say.Language.EN).build())
98
.numDigits(1).timeout(5).build())
99
.build();
100
101
try {
102
return twiml.toXml();
103
} catch (TwiMLException e) {
104
return "Error creating TwiML: " + e.getMessage();
105
}
106
}
107
108
public String enqueueTask() {
109
com.twilio.twiml.Task task =
110
new com.twilio.twiml.Task.Builder().data("{\"selected_language\":\"es\"}").build();
111
112
EnqueueTask enqueue = new EnqueueTask.Builder(task).workflowSid(workflowSid).build();
113
114
VoiceResponse twiml = new VoiceResponse.Builder().enqueue(enqueue).build();
115
116
try {
117
return twiml.toXml();
118
} catch (TwiMLException e) {
119
return "Error creating TwiML: " + e.getMessage();
120
}
121
}
122
123
public void generateAgentView(final HttpServletRequest request, final HttpServletResponse response)
124
throws ServletException, IOException {
125
final String workerSid = request.getParameter("WorkerSid");
126
127
List<Policy> policies = PolicyUtils.defaultWorkerPolicies(workspaceSid, workerSid);
128
129
Map<String, FilterRequirement> activityUpdateFilter = new HashMap<>();
130
activityUpdateFilter.put("ActivitySid", FilterRequirement.REQUIRED);
131
132
Policy allowActivityUpdates = new Policy.Builder()
133
.url(UrlUtils.worker(workspaceSid, workerSid))
134
.method(HttpMethod.POST)
135
.postFilter(activityUpdateFilter).build();
136
137
Policy allowTasksUpdate = new Policy.Builder()
138
.url(UrlUtils.allTasks(workerSid))
139
.method(HttpMethod.POST)
140
.build();
141
142
Policy allowReservationUpdate = new Policy.Builder()
143
.url(UrlUtils.allReservations(workspaceSid, workerSid))
144
.method(HttpMethod.POST)
145
.build();
146
147
policies.add(allowActivityUpdates);
148
policies.add(allowTasksUpdate);
149
policies.add(allowReservationUpdate);
150
151
152
TaskRouterCapability.Builder capabilityBuilder =
153
new TaskRouterCapability.Builder(accountSid, authToken, workspaceSid, workerSid)
154
.policies(policies);
155
156
String token = capabilityBuilder.build().toJwt();
157
158
System.out.println(token);
159
160
// By default, tokens are good for one hour.
161
// Override this default timeout by specifiying a new value (in seconds).
162
// For example, to generate a token good for 8 hours:
163
token = capabilityBuilder.build().toJwt();
164
165
// Forward the token information to a JSP view
166
response.setContentType("text/html");
167
request.setAttribute("worker_token", token);
168
final RequestDispatcher view = request.getServletContext().getRequestDispatcher("/agent.jsp");
169
view.forward(request, response);
170
}
171
}

taskrouter/WEB-INF/web.xml

taskrouterweb-infwebxml page anchor
1
<?xml version="1.0" encoding="ISO-8859-1"?>
2
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
3
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
5
version="2.4">
6
7
<display-name>Twilio TaskRouter App</display-name>
8
9
<servlet>
10
<servlet-name>TwilioTaskRouterServlet</servlet-name>
11
<servlet-class>com.twilio.TwilioTaskRouterServlet</servlet-class>
12
</servlet>
13
14
<servlet-mapping>
15
<servlet-name>TwilioTaskRouterServlet</servlet-name>
16
<url-pattern>/taskrouter/*</url-pattern>
17
</servlet-mapping>
18
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
1
<!DOCTYPE html>
2
<html>
3
<head>
4
<title>Customer Care - Voice Agent Screen</title>
5
<link rel="stylesheet" href="//media.twiliocdn.com/taskrouter/quickstart/agent.css"/>
6
<script src="https://sdk.twilio.com/js/taskrouter/v1.21/taskrouter.min.js" integrity="sha384-5fq+0qjayReAreRyHy38VpD3Gr9R2OYIzonwIkoGI4M9dhfKW6RWeRnZjfwSrpN8" crossorigin="anonymous"></script>
7
<script type="text/javascript">
8
/* Subscribe to a subset of the available TaskRouter.js events for a worker */
9
function registerTaskRouterCallbacks() {
10
worker.on('ready', function(worker) {
11
agentActivityChanged(worker.activityName);
12
logger("Successfully registered as: " + worker.friendlyName)
13
logger("Current activity is: " + worker.activityName);
14
});
15
16
worker.on('activity.update', function(worker) {
17
agentActivityChanged(worker.activityName);
18
logger("Worker activity changed to: " + worker.activityName);
19
});
20
21
worker.on("reservation.created", function(reservation) {
22
logger("-----");
23
logger("You have been reserved to handle a call!");
24
logger("Call from: " + reservation.task.attributes.from);
25
logger("Selected language: " + reservation.task.attributes.selected_language);
26
logger("-----");
27
});
28
29
worker.on("reservation.accepted", function(reservation) {
30
logger("Reservation " + reservation.sid + " accepted!");
31
});
32
33
worker.on("reservation.rejected", function(reservation) {
34
logger("Reservation " + reservation.sid + " rejected!");
35
});
36
37
worker.on("reservation.timeout", function(reservation) {
38
logger("Reservation " + reservation.sid + " timed out!");
39
});
40
41
worker.on("reservation.canceled", function(reservation) {
42
logger("Reservation " + reservation.sid + " canceled!");
43
});
44
}
45
46
/* Hook up the agent Activity buttons to TaskRouter.js */
47
48
function bindAgentActivityButtons() {
49
// Fetch the full list of available Activities from TaskRouter. Store each
50
// ActivitySid against the matching Friendly Name
51
var activitySids = {};
52
worker.activities.fetch(function(error, activityList) {
53
var activities = activityList.data;
54
var i = activities.length;
55
while (i--) {
56
activitySids[activities[i].friendlyName] = activities[i].sid;
57
}
58
});
59
60
/* For each button of class 'change-activity' in our Agent UI, look up the
61
ActivitySid corresponding to the Friendly Name in the button's next-activity
62
data attribute. Use Worker.js to transition the agent to that ActivitySid
63
when the button is clicked.*/
64
var elements = document.getElementsByClassName('change-activity');
65
var i = elements.length;
66
while (i--) {
67
elements[i].onclick = function() {
68
var nextActivity = this.dataset.nextActivity;
69
var nextActivitySid = activitySids[nextActivity];
70
worker.update({"ActivitySid":nextActivitySid});
71
}
72
}
73
}
74
75
/* Update the UI to reflect a change in Activity */
76
77
function agentActivityChanged(activity) {
78
hideAgentActivities();
79
showAgentActivity(activity);
80
}
81
82
function hideAgentActivities() {
83
var elements = document.getElementsByClassName('agent-activity');
84
var i = elements.length;
85
while (i--) {
86
elements[i].style.display = 'none';
87
}
88
}
89
90
function showAgentActivity(activity) {
91
activity = activity.toLowerCase();
92
var elements = document.getElementsByClassName(('agent-activity ' + activity));
93
elements.item(0).style.display = 'block';
94
}
95
96
/* Other stuff */
97
98
function logger(message) {
99
var log = document.getElementById('log');
100
log.value += "\n> " + message;
101
log.scrollTop = log.scrollHeight;
102
}
103
104
window.onload = function() {
105
// Initialize TaskRouter.js on page load using window.workerToken -
106
// a Twilio Capability token that was set from rendering the template with agents endpoint
107
logger("Initializing...");
108
window.worker = new Twilio.TaskRouter.Worker("${worker_token}");
109
110
registerTaskRouterCallbacks();
111
bindAgentActivityButtons();
112
};
113
</script>
114
</head>
115
<body>
116
<div class="content">
117
<section class="agent-activity offline">
118
<p class="activity">Offline</p>
119
<button class="change-activity" data-next-activity="Idle">Go Available</button>
120
</section>
121
<section class="agent-activity idle">
122
<p class="activity"><span>Available</span></p>
123
<button class="change-activity" data-next-activity="Offline">Go Offline</button>
124
</section>
125
<section class="agent-activity reserved">
126
<p class="activity">Reserved</p>
127
</section>
128
<section class="agent-activity busy">
129
<p class="activity">Busy</p>
130
</section>
131
<section class="agent-activity wrapup">
132
<p class="activity">Wrap-Up</p>
133
<button class="change-activity" data-next-activity="Idle">Go Available</button>
134
<button class="change-activity" data-next-activity="Offline">Go Offline</button>
135
</section>
136
<section class="log">
137
<textarea id="log" readonly="true"></textarea>
138
</section>
139
</div>
140
</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.

Need some help?

Terms of service

Copyright © 2024 Twilio Inc.