@webex/contact-center 0.0.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +81 -0
  2. package/__mocks__/workerMock.js +15 -0
  3. package/babel.config.js +15 -0
  4. package/dist/cc.js +1416 -0
  5. package/dist/cc.js.map +1 -0
  6. package/dist/config.js +72 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/constants.js +58 -0
  9. package/dist/constants.js.map +1 -0
  10. package/dist/index.js +142 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/logger-proxy.js +115 -0
  13. package/dist/logger-proxy.js.map +1 -0
  14. package/dist/metrics/MetricsManager.js +474 -0
  15. package/dist/metrics/MetricsManager.js.map +1 -0
  16. package/dist/metrics/behavioral-events.js +322 -0
  17. package/dist/metrics/behavioral-events.js.map +1 -0
  18. package/dist/metrics/constants.js +134 -0
  19. package/dist/metrics/constants.js.map +1 -0
  20. package/dist/services/WebCallingService.js +323 -0
  21. package/dist/services/WebCallingService.js.map +1 -0
  22. package/dist/services/agent/index.js +177 -0
  23. package/dist/services/agent/index.js.map +1 -0
  24. package/dist/services/agent/types.js +137 -0
  25. package/dist/services/agent/types.js.map +1 -0
  26. package/dist/services/config/Util.js +203 -0
  27. package/dist/services/config/Util.js.map +1 -0
  28. package/dist/services/config/constants.js +221 -0
  29. package/dist/services/config/constants.js.map +1 -0
  30. package/dist/services/config/index.js +607 -0
  31. package/dist/services/config/index.js.map +1 -0
  32. package/dist/services/config/types.js +334 -0
  33. package/dist/services/config/types.js.map +1 -0
  34. package/dist/services/constants.js +117 -0
  35. package/dist/services/constants.js.map +1 -0
  36. package/dist/services/core/Err.js +43 -0
  37. package/dist/services/core/Err.js.map +1 -0
  38. package/dist/services/core/GlobalTypes.js +6 -0
  39. package/dist/services/core/GlobalTypes.js.map +1 -0
  40. package/dist/services/core/Utils.js +126 -0
  41. package/dist/services/core/Utils.js.map +1 -0
  42. package/dist/services/core/WebexRequest.js +96 -0
  43. package/dist/services/core/WebexRequest.js.map +1 -0
  44. package/dist/services/core/aqm-reqs.js +246 -0
  45. package/dist/services/core/aqm-reqs.js.map +1 -0
  46. package/dist/services/core/constants.js +109 -0
  47. package/dist/services/core/constants.js.map +1 -0
  48. package/dist/services/core/types.js +6 -0
  49. package/dist/services/core/types.js.map +1 -0
  50. package/dist/services/core/websocket/WebSocketManager.js +187 -0
  51. package/dist/services/core/websocket/WebSocketManager.js.map +1 -0
  52. package/dist/services/core/websocket/connection-service.js +111 -0
  53. package/dist/services/core/websocket/connection-service.js.map +1 -0
  54. package/dist/services/core/websocket/keepalive.worker.js +94 -0
  55. package/dist/services/core/websocket/keepalive.worker.js.map +1 -0
  56. package/dist/services/core/websocket/types.js +6 -0
  57. package/dist/services/core/websocket/types.js.map +1 -0
  58. package/dist/services/index.js +78 -0
  59. package/dist/services/index.js.map +1 -0
  60. package/dist/services/task/AutoWrapup.js +88 -0
  61. package/dist/services/task/AutoWrapup.js.map +1 -0
  62. package/dist/services/task/TaskManager.js +369 -0
  63. package/dist/services/task/TaskManager.js.map +1 -0
  64. package/dist/services/task/constants.js +58 -0
  65. package/dist/services/task/constants.js.map +1 -0
  66. package/dist/services/task/contact.js +464 -0
  67. package/dist/services/task/contact.js.map +1 -0
  68. package/dist/services/task/dialer.js +60 -0
  69. package/dist/services/task/dialer.js.map +1 -0
  70. package/dist/services/task/index.js +1188 -0
  71. package/dist/services/task/index.js.map +1 -0
  72. package/dist/services/task/types.js +214 -0
  73. package/dist/services/task/types.js.map +1 -0
  74. package/dist/types/cc.d.ts +676 -0
  75. package/dist/types/config.d.ts +66 -0
  76. package/dist/types/constants.d.ts +45 -0
  77. package/dist/types/index.d.ts +178 -0
  78. package/dist/types/logger-proxy.d.ts +71 -0
  79. package/dist/types/metrics/MetricsManager.d.ts +223 -0
  80. package/dist/types/metrics/behavioral-events.d.ts +29 -0
  81. package/dist/types/metrics/constants.d.ts +127 -0
  82. package/dist/types/services/WebCallingService.d.ts +1 -0
  83. package/dist/types/services/agent/index.d.ts +46 -0
  84. package/dist/types/services/agent/types.d.ts +413 -0
  85. package/dist/types/services/config/Util.d.ts +19 -0
  86. package/dist/types/services/config/constants.d.ts +203 -0
  87. package/dist/types/services/config/index.d.ts +171 -0
  88. package/dist/types/services/config/types.d.ts +1113 -0
  89. package/dist/types/services/constants.d.ts +97 -0
  90. package/dist/types/services/core/Err.d.ts +119 -0
  91. package/dist/types/services/core/GlobalTypes.d.ts +33 -0
  92. package/dist/types/services/core/Utils.d.ts +36 -0
  93. package/dist/types/services/core/WebexRequest.d.ts +22 -0
  94. package/dist/types/services/core/aqm-reqs.d.ts +16 -0
  95. package/dist/types/services/core/constants.d.ts +85 -0
  96. package/dist/types/services/core/types.d.ts +47 -0
  97. package/dist/types/services/core/websocket/WebSocketManager.d.ts +34 -0
  98. package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
  99. package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
  100. package/dist/types/services/core/websocket/types.d.ts +37 -0
  101. package/dist/types/services/index.d.ts +52 -0
  102. package/dist/types/services/task/AutoWrapup.d.ts +40 -0
  103. package/dist/types/services/task/TaskManager.d.ts +1 -0
  104. package/dist/types/services/task/constants.d.ts +46 -0
  105. package/dist/types/services/task/contact.d.ts +59 -0
  106. package/dist/types/services/task/dialer.d.ts +28 -0
  107. package/dist/types/services/task/index.d.ts +569 -0
  108. package/dist/types/services/task/types.d.ts +1041 -0
  109. package/dist/types/types.d.ts +452 -0
  110. package/dist/types/webex-config.d.ts +53 -0
  111. package/dist/types/webex.d.ts +7 -0
  112. package/dist/types.js +292 -0
  113. package/dist/types.js.map +1 -0
  114. package/dist/webex-config.js +60 -0
  115. package/dist/webex-config.js.map +1 -0
  116. package/dist/webex.js +99 -0
  117. package/dist/webex.js.map +1 -0
  118. package/jest.config.js +45 -0
  119. package/package.json +83 -0
  120. package/src/cc.ts +1618 -0
  121. package/src/config.ts +65 -0
  122. package/src/constants.ts +51 -0
  123. package/src/index.ts +220 -0
  124. package/src/logger-proxy.ts +110 -0
  125. package/src/metrics/MetricsManager.ts +512 -0
  126. package/src/metrics/behavioral-events.ts +332 -0
  127. package/src/metrics/constants.ts +135 -0
  128. package/src/services/WebCallingService.ts +351 -0
  129. package/src/services/agent/index.ts +149 -0
  130. package/src/services/agent/types.ts +440 -0
  131. package/src/services/config/Util.ts +261 -0
  132. package/src/services/config/constants.ts +249 -0
  133. package/src/services/config/index.ts +743 -0
  134. package/src/services/config/types.ts +1117 -0
  135. package/src/services/constants.ts +111 -0
  136. package/src/services/core/Err.ts +126 -0
  137. package/src/services/core/GlobalTypes.ts +34 -0
  138. package/src/services/core/Utils.ts +132 -0
  139. package/src/services/core/WebexRequest.ts +103 -0
  140. package/src/services/core/aqm-reqs.ts +272 -0
  141. package/src/services/core/constants.ts +106 -0
  142. package/src/services/core/types.ts +48 -0
  143. package/src/services/core/websocket/WebSocketManager.ts +196 -0
  144. package/src/services/core/websocket/connection-service.ts +142 -0
  145. package/src/services/core/websocket/keepalive.worker.js +88 -0
  146. package/src/services/core/websocket/types.ts +40 -0
  147. package/src/services/index.ts +71 -0
  148. package/src/services/task/AutoWrapup.ts +86 -0
  149. package/src/services/task/TaskManager.ts +420 -0
  150. package/src/services/task/constants.ts +52 -0
  151. package/src/services/task/contact.ts +429 -0
  152. package/src/services/task/dialer.ts +52 -0
  153. package/src/services/task/index.ts +1375 -0
  154. package/src/services/task/types.ts +1113 -0
  155. package/src/types.ts +639 -0
  156. package/src/webex-config.ts +54 -0
  157. package/src/webex.js +96 -0
  158. package/test/unit/spec/cc.ts +1985 -0
  159. package/test/unit/spec/metrics/MetricsManager.ts +491 -0
  160. package/test/unit/spec/metrics/behavioral-events.ts +102 -0
  161. package/test/unit/spec/services/WebCallingService.ts +416 -0
  162. package/test/unit/spec/services/agent/index.ts +65 -0
  163. package/test/unit/spec/services/config/index.ts +1035 -0
  164. package/test/unit/spec/services/core/Utils.ts +279 -0
  165. package/test/unit/spec/services/core/WebexRequest.ts +144 -0
  166. package/test/unit/spec/services/core/aqm-reqs.ts +570 -0
  167. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +378 -0
  168. package/test/unit/spec/services/core/websocket/connection-service.ts +178 -0
  169. package/test/unit/spec/services/task/TaskManager.ts +1351 -0
  170. package/test/unit/spec/services/task/contact.ts +204 -0
  171. package/test/unit/spec/services/task/dialer.ts +157 -0
  172. package/test/unit/spec/services/task/index.ts +1474 -0
  173. package/tsconfig.json +6 -0
  174. package/typedoc.json +37 -0
  175. package/typedoc.md +240 -0
  176. package/umd/contact-center.min.js +3 -0
  177. package/umd/contact-center.min.js.map +1 -0
@@ -0,0 +1,420 @@
1
+ import EventEmitter from 'events';
2
+ import {ICall, LINE_EVENTS} from '@webex/calling';
3
+ import {WebSocketManager} from '../core/websocket/WebSocketManager';
4
+ import routingContact from './contact';
5
+ import WebCallingService from '../WebCallingService';
6
+ import {ITask, MEDIA_CHANNEL, TASK_EVENTS, TaskData, TaskId} from './types';
7
+ import {TASK_MANAGER_FILE} from '../../constants';
8
+ import {METHODS} from './constants';
9
+ import {CC_EVENTS, CC_TASK_EVENTS, WrapupData} from '../config/types';
10
+ import {LoginOption} from '../../types';
11
+ import LoggerProxy from '../../logger-proxy';
12
+ import Task from '.';
13
+ import MetricsManager from '../../metrics/MetricsManager';
14
+ import {METRIC_EVENT_NAMES} from '../../metrics/constants';
15
+
16
+ /** @internal */
17
+ export default class TaskManager extends EventEmitter {
18
+ private call: ICall;
19
+ private contact: ReturnType<typeof routingContact>;
20
+ /**
21
+ * Collection of tasks indexed by TaskId
22
+ * @type {Record<TaskId, ITask>}
23
+ * @private
24
+ */
25
+ private taskCollection: Record<TaskId, ITask>;
26
+ private webCallingService: WebCallingService;
27
+ private webSocketManager: WebSocketManager;
28
+ private metricsManager: MetricsManager;
29
+ private static taskManager;
30
+ private wrapupData: WrapupData;
31
+ /**
32
+ * @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises
33
+ * @param webCallingService - Webrtc Service Layer
34
+ * @param webSocketManager - Websocket Manager to maintain websocket connection and keepalives
35
+ */
36
+ constructor(
37
+ contact: ReturnType<typeof routingContact>,
38
+ webCallingService: WebCallingService,
39
+ webSocketManager: WebSocketManager
40
+ ) {
41
+ super();
42
+ this.contact = contact;
43
+ this.taskCollection = {};
44
+ this.webCallingService = webCallingService;
45
+ this.webSocketManager = webSocketManager;
46
+ this.metricsManager = MetricsManager.getInstance();
47
+ this.registerTaskListeners();
48
+ this.registerIncomingCallEvent();
49
+ }
50
+
51
+ public setWrapupData(wrapupData: WrapupData) {
52
+ this.wrapupData = wrapupData;
53
+ }
54
+
55
+ private handleIncomingWebCall = (call: ICall) => {
56
+ const currentTask = Object.values(this.taskCollection).find(
57
+ (task) => task.data.interaction.mediaType === 'telephony'
58
+ );
59
+
60
+ if (currentTask) {
61
+ this.webCallingService.mapCallToTask(call.getCallId(), currentTask.data.interactionId);
62
+ LoggerProxy.log(`Call mapped to task`, {
63
+ module: TASK_MANAGER_FILE,
64
+ method: METHODS.HANDLE_INCOMING_WEB_CALL,
65
+ interactionId: currentTask.data.interactionId,
66
+ });
67
+ this.emit(TASK_EVENTS.TASK_INCOMING, currentTask);
68
+ }
69
+ this.call = call;
70
+ };
71
+
72
+ public registerIncomingCallEvent() {
73
+ this.webCallingService.on(LINE_EVENTS.INCOMING_CALL, this.handleIncomingWebCall);
74
+ }
75
+
76
+ public unregisterIncomingCallEvent() {
77
+ this.webCallingService.off(LINE_EVENTS.INCOMING_CALL, this.handleIncomingWebCall);
78
+ }
79
+
80
+ private registerTaskListeners() {
81
+ this.webSocketManager.on('message', (event) => {
82
+ const payload = JSON.parse(event);
83
+ // Re-emit the task events to the task object
84
+ let task: ITask;
85
+ if (payload.data?.type) {
86
+ if (Object.values(CC_TASK_EVENTS).includes(payload.data.type)) {
87
+ task = this.taskCollection[payload.data.interactionId];
88
+ }
89
+ LoggerProxy.info(`Handling task event ${payload.data?.type}`, {
90
+ module: TASK_MANAGER_FILE,
91
+ method: METHODS.REGISTER_TASK_LISTENERS,
92
+ interactionId: payload.data?.interactionId,
93
+ });
94
+ switch (payload.data.type) {
95
+ case CC_EVENTS.AGENT_CONTACT:
96
+ // Case1 : Task is already present in taskCollection
97
+ if (this.taskCollection[payload.data.interactionId]) {
98
+ LoggerProxy.log(`Got AGENT_CONTACT: Task already exists in collection`, {
99
+ module: TASK_MANAGER_FILE,
100
+ method: METHODS.REGISTER_TASK_LISTENERS,
101
+ interactionId: payload.data.interactionId,
102
+ });
103
+ break;
104
+ } else if (!this.taskCollection[payload.data.interactionId]) {
105
+ // Case2 : Task is not present in taskCollection
106
+ LoggerProxy.log(`Got AGENT_CONTACT : Creating new task in taskManager`, {
107
+ module: TASK_MANAGER_FILE,
108
+ method: METHODS.REGISTER_TASK_LISTENERS,
109
+ interactionId: payload.data.interactionId,
110
+ });
111
+ task = new Task(
112
+ this.contact,
113
+ this.webCallingService,
114
+ {
115
+ ...payload.data,
116
+ wrapUpRequired:
117
+ payload.data.interaction?.participants?.[payload.data.agentId]?.isWrapUp ||
118
+ false,
119
+ },
120
+ this.wrapupData
121
+ );
122
+ this.taskCollection[payload.data.interactionId] = task;
123
+ // Condition 1: The state is=new i.e it is a incoming task
124
+ if (payload.data.interaction.state === 'new') {
125
+ LoggerProxy.log(
126
+ `Got AGENT_CONTACT for a task with state=new, sending TASK_INCOMING event`,
127
+ {
128
+ module: TASK_MANAGER_FILE,
129
+ method: METHODS.REGISTER_TASK_LISTENERS,
130
+ interactionId: payload.data.interactionId,
131
+ }
132
+ );
133
+ this.emit(TASK_EVENTS.TASK_INCOMING, task);
134
+ } else {
135
+ // Condition 2: The state is anything else i.e the task was connected
136
+ LoggerProxy.log(
137
+ `Got AGENT_CONTACT for a task with state=${payload.data.interaction.state}, sending TASK_HYDRATE event`,
138
+ {
139
+ module: TASK_MANAGER_FILE,
140
+ method: METHODS.REGISTER_TASK_LISTENERS,
141
+ interactionId: payload.data.interactionId,
142
+ }
143
+ );
144
+ this.emit(TASK_EVENTS.TASK_HYDRATE, task);
145
+ }
146
+ }
147
+ break;
148
+ case CC_EVENTS.AGENT_CONTACT_RESERVED:
149
+ task = new Task(
150
+ this.contact,
151
+ this.webCallingService,
152
+ {
153
+ ...payload.data,
154
+ isConsulted: false,
155
+ },
156
+ this.wrapupData
157
+ ); // Ensure isConsulted prop exists
158
+ this.taskCollection[payload.data.interactionId] = task;
159
+ if (
160
+ this.webCallingService.loginOption !== LoginOption.BROWSER ||
161
+ task.data.interaction.mediaType !== MEDIA_CHANNEL.TELEPHONY // for digital channels
162
+ ) {
163
+ this.emit(TASK_EVENTS.TASK_INCOMING, task);
164
+ } else if (this.call) {
165
+ this.emit(TASK_EVENTS.TASK_INCOMING, task);
166
+ }
167
+ break;
168
+ case CC_EVENTS.AGENT_OFFER_CONTACT:
169
+ // We don't have to emit any event here since this will be result of promise.
170
+ task = this.updateTaskData(task, payload.data);
171
+ LoggerProxy.log(`Agent offer contact received for task`, {
172
+ module: TASK_MANAGER_FILE,
173
+ method: METHODS.REGISTER_TASK_LISTENERS,
174
+ interactionId: payload.data?.interactionId,
175
+ });
176
+ this.emit(TASK_EVENTS.TASK_OFFER_CONTACT, task);
177
+ break;
178
+ case CC_EVENTS.AGENT_OUTBOUND_FAILED:
179
+ // We don't have to emit any event here since this will be result of promise.
180
+ if (task.data) {
181
+ this.removeTaskFromCollection(task);
182
+ }
183
+ LoggerProxy.log(`Agent outbound failed for task`, {
184
+ module: TASK_MANAGER_FILE,
185
+ method: METHODS.REGISTER_TASK_LISTENERS,
186
+ interactionId: payload.data?.interactionId,
187
+ });
188
+ break;
189
+ case CC_EVENTS.AGENT_CONTACT_ASSIGNED:
190
+ task = this.updateTaskData(task, payload.data);
191
+ task.emit(TASK_EVENTS.TASK_ASSIGNED, task);
192
+ break;
193
+ case CC_EVENTS.AGENT_CONTACT_UNASSIGNED:
194
+ task = this.updateTaskData(task, {
195
+ ...payload.data,
196
+ wrapUpRequired: true,
197
+ });
198
+ task.emit(TASK_EVENTS.TASK_END, task);
199
+ break;
200
+ case CC_EVENTS.AGENT_CONTACT_OFFER_RONA:
201
+ case CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED:
202
+ case CC_EVENTS.AGENT_INVITE_FAILED: {
203
+ task = this.updateTaskData(task, payload.data);
204
+
205
+ const eventTypeToMetricMap: Record<string, keyof typeof METRIC_EVENT_NAMES> = {
206
+ [CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED]: 'AGENT_CONTACT_ASSIGN_FAILED',
207
+ [CC_EVENTS.AGENT_INVITE_FAILED]: 'AGENT_INVITE_FAILED',
208
+ };
209
+ const metricEventName: keyof typeof METRIC_EVENT_NAMES =
210
+ eventTypeToMetricMap[payload.data.type] || 'AGENT_RONA';
211
+
212
+ this.metricsManager.trackEvent(
213
+ METRIC_EVENT_NAMES[metricEventName],
214
+ {
215
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(payload.data),
216
+ taskId: payload.data.interactionId,
217
+ reason: payload.data.reason,
218
+ },
219
+ ['behavioral', 'operational']
220
+ );
221
+ this.handleTaskCleanup(task);
222
+ task.emit(TASK_EVENTS.TASK_REJECT, payload.data.reason);
223
+ break;
224
+ }
225
+ case CC_EVENTS.CONTACT_ENDED:
226
+ task = this.updateTaskData(task, {
227
+ ...payload.data,
228
+ wrapUpRequired: payload.data.interaction.state !== 'new',
229
+ });
230
+ this.handleTaskCleanup(task);
231
+ task.emit(TASK_EVENTS.TASK_END, task);
232
+
233
+ break;
234
+ case CC_EVENTS.AGENT_CONTACT_HELD:
235
+ // As soon as the main interaction is held, we need to emit TASK_HOLD
236
+ task = this.updateTaskData(task, payload.data);
237
+ task.emit(TASK_EVENTS.TASK_HOLD, task);
238
+ break;
239
+ case CC_EVENTS.AGENT_CONTACT_UNHELD:
240
+ // As soon as the main interaction is unheld, we need to emit TASK_RESUME
241
+ task = this.updateTaskData(task, payload.data);
242
+ task.emit(TASK_EVENTS.TASK_RESUME, task);
243
+ break;
244
+ case CC_EVENTS.AGENT_VTEAM_TRANSFERRED:
245
+ task = this.updateTaskData(task, {
246
+ ...payload.data,
247
+ wrapUpRequired: true,
248
+ });
249
+ task.emit(TASK_EVENTS.TASK_END, task);
250
+ break;
251
+ case CC_EVENTS.AGENT_CTQ_CANCEL_FAILED:
252
+ task = this.updateTaskData(task, payload.data);
253
+ task.emit(TASK_EVENTS.TASK_CONSULT_QUEUE_FAILED, task);
254
+ break;
255
+ case CC_EVENTS.AGENT_CONSULT_CREATED:
256
+ // Received when self agent initiates a consult
257
+ task = this.updateTaskData(task, {
258
+ ...payload.data,
259
+ isConsulted: false, // This ensures that the task consult status is always reset
260
+ });
261
+ task.emit(TASK_EVENTS.TASK_CONSULT_CREATED, task);
262
+ break;
263
+ case CC_EVENTS.AGENT_OFFER_CONSULT:
264
+ // Received when other agent sends us a consult offer
265
+ task = this.updateTaskData(task, {
266
+ ...payload.data,
267
+ isConsulted: true, // This ensures that the task is marked as us being requested for a consult
268
+ });
269
+ task.emit(TASK_EVENTS.TASK_OFFER_CONSULT, task);
270
+ break;
271
+ case CC_EVENTS.AGENT_CONSULTING:
272
+ // Received when agent is in an active consult state
273
+ task = this.updateTaskData(task, payload.data);
274
+ if (task.data.isConsulted) {
275
+ // Fire only if you are the agent who received the consult request
276
+ task.emit(TASK_EVENTS.TASK_CONSULT_ACCEPTED, task);
277
+ } else {
278
+ // Fire only if you are the agent who initiated the consult
279
+ task.emit(TASK_EVENTS.TASK_CONSULTING, task);
280
+ }
281
+ break;
282
+ case CC_EVENTS.AGENT_CONSULT_FAILED:
283
+ // This can only be received by the agent who initiated the consult.
284
+ // We need not emit any event here since this will be result of promise
285
+ task = this.updateTaskData(task, payload.data);
286
+ break;
287
+ case CC_EVENTS.AGENT_CONSULT_ENDED:
288
+ task = this.updateTaskData(task, payload.data);
289
+ if (task.data.isConsulted) {
290
+ // This will be the end state of the task as soon as we end the consult in case of
291
+ // us being offered a consult
292
+ this.removeTaskFromCollection(task);
293
+ }
294
+ task.emit(TASK_EVENTS.TASK_CONSULT_END, task);
295
+ break;
296
+ case CC_EVENTS.AGENT_CTQ_CANCELLED:
297
+ // This event is received when the consult using queue is cancelled using API
298
+ task = this.updateTaskData(task, payload.data);
299
+ task.emit(TASK_EVENTS.TASK_CONSULT_QUEUE_CANCELLED, task);
300
+ break;
301
+ case CC_EVENTS.AGENT_WRAPUP:
302
+ task = this.updateTaskData(task, {...payload.data, wrapUpRequired: true});
303
+ task.emit(TASK_EVENTS.TASK_END, task);
304
+ break;
305
+ case CC_EVENTS.AGENT_WRAPPEDUP:
306
+ task.cancelAutoWrapupTimer();
307
+ this.removeTaskFromCollection(task);
308
+ task.emit(TASK_EVENTS.TASK_WRAPPEDUP, task);
309
+ break;
310
+ case CC_EVENTS.CONTACT_RECORDING_PAUSED:
311
+ task = this.updateTaskData(task, payload.data);
312
+ task.emit(TASK_EVENTS.TASK_RECORDING_PAUSED, task);
313
+ break;
314
+ case CC_EVENTS.CONTACT_RECORDING_PAUSE_FAILED:
315
+ task = this.updateTaskData(task, payload.data);
316
+ task.emit(TASK_EVENTS.TASK_RECORDING_PAUSE_FAILED, task);
317
+ break;
318
+ case CC_EVENTS.CONTACT_RECORDING_RESUMED:
319
+ task = this.updateTaskData(task, payload.data);
320
+ task.emit(TASK_EVENTS.TASK_RECORDING_RESUMED, task);
321
+ break;
322
+ case CC_EVENTS.CONTACT_RECORDING_RESUME_FAILED:
323
+ task = this.updateTaskData(task, payload.data);
324
+ task.emit(TASK_EVENTS.TASK_RECORDING_RESUME_FAILED, task);
325
+ break;
326
+ default:
327
+ break;
328
+ }
329
+ if (task) {
330
+ task.emit(payload.data.type, payload.data);
331
+ }
332
+ }
333
+ });
334
+ }
335
+
336
+ private updateTaskData(task: ITask, taskData: TaskData): ITask {
337
+ if (!task) {
338
+ return undefined;
339
+ }
340
+
341
+ if (!taskData?.interactionId) {
342
+ LoggerProxy.warn('Received task update with missing interactionId', {
343
+ module: TASK_MANAGER_FILE,
344
+ method: METHODS.UPDATE_TASK_DATA,
345
+ });
346
+ }
347
+
348
+ try {
349
+ const currentTask = task.updateTaskData(taskData);
350
+ this.taskCollection[taskData.interactionId] = currentTask;
351
+
352
+ return currentTask;
353
+ } catch (error) {
354
+ LoggerProxy.error(`Failed to update task`, {
355
+ module: TASK_MANAGER_FILE,
356
+ method: METHODS.UPDATE_TASK_DATA,
357
+ interactionId: taskData.interactionId,
358
+ });
359
+
360
+ return task;
361
+ }
362
+ }
363
+
364
+ private removeTaskFromCollection(task: ITask) {
365
+ if (task?.data?.interactionId) {
366
+ delete this.taskCollection[task.data.interactionId];
367
+ LoggerProxy.info(`Task removed from collection`, {
368
+ module: TASK_MANAGER_FILE,
369
+ method: METHODS.REMOVE_TASK_FROM_COLLECTION,
370
+ interactionId: task.data.interactionId,
371
+ });
372
+ }
373
+ }
374
+
375
+ private handleTaskCleanup(task: ITask) {
376
+ if (
377
+ this.webCallingService.loginOption === LoginOption.BROWSER &&
378
+ task.data.interaction.mediaType === 'telephony'
379
+ ) {
380
+ task.unregisterWebCallListeners();
381
+ this.webCallingService.cleanUpCall();
382
+ }
383
+ if (task.data.interaction.state === 'new') {
384
+ // Only remove tasks in 'new' state immediately. For other states,
385
+ // retain tasks until they complete wrap-up, unless the task disconnected before being answered.
386
+ this.removeTaskFromCollection(task);
387
+ }
388
+ }
389
+
390
+ /**
391
+ * @param taskId - Unique identifier for each task
392
+ */
393
+ public getTask = (taskId: string) => {
394
+ return this.taskCollection[taskId];
395
+ };
396
+
397
+ /**
398
+ * @param taskId - Unique identifier for each task
399
+ */
400
+ public getAllTasks = (): Record<TaskId, ITask> => {
401
+ return this.taskCollection;
402
+ };
403
+
404
+ /**
405
+ * @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises
406
+ * @param webCallingService - Webrtc Service Layer
407
+ * @param webSocketManager - Websocket Manager to maintain websocket connection and keepalives
408
+ */
409
+ public static getTaskManager = (
410
+ contact: ReturnType<typeof routingContact>,
411
+ webCallingService: WebCallingService,
412
+ webSocketManager: WebSocketManager
413
+ ): TaskManager => {
414
+ if (!this.taskManager) {
415
+ this.taskManager = new TaskManager(contact, webCallingService, webSocketManager);
416
+ }
417
+
418
+ return this.taskManager;
419
+ };
420
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Constants for Task Service
3
+ * @module @webex/contact-center/services/task/constants
4
+ * @ignore
5
+ */
6
+
7
+ export const TASK_MESSAGE_TYPE = 'RoutingMessage';
8
+ export const TASK_API = '/v1/tasks/';
9
+ export const HOLD = '/hold';
10
+ export const UNHOLD = '/unhold';
11
+ export const CONSULT = '/consult';
12
+ export const CONSULT_ACCEPT = '/consult/accept';
13
+ export const CONSULT_END = '/consult/end';
14
+ export const TRANSFER = '/transfer';
15
+ export const CONSULT_TRANSFER = '/consult/transfer';
16
+ export const PAUSE = '/record/pause';
17
+ export const RESUME = '/record/resume';
18
+ export const WRAPUP = '/wrapup';
19
+ export const END = '/end';
20
+ export const TASK_MANAGER_FILE = 'taskManager';
21
+ export const TASK_FILE = 'task';
22
+
23
+ // METHOD NAMES
24
+ export const METHODS = {
25
+ // Task class methods
26
+ ACCEPT: 'accept',
27
+ TOGGLE_MUTE: 'toggleMute',
28
+ DECLINE: 'decline',
29
+ HOLD: 'hold',
30
+ RESUME: 'resume',
31
+ END: 'end',
32
+ WRAPUP: 'wrapup',
33
+ PAUSE_RECORDING: 'pauseRecording',
34
+ RESUME_RECORDING: 'resumeRecording',
35
+ CONSULT: 'consult',
36
+ END_CONSULT: 'endConsult',
37
+ TRANSFER: 'transfer',
38
+ CONSULT_TRANSFER: 'consultTransfer',
39
+ UPDATE_TASK_DATA: 'updateTaskData',
40
+ RECONCILE_DATA: 'reconcileData',
41
+
42
+ // TaskManager class methods
43
+ HANDLE_INCOMING_WEB_CALL: 'handleIncomingWebCall',
44
+ REGISTER_TASK_LISTENERS: 'registerTaskListeners',
45
+ REMOVE_TASK_FROM_COLLECTION: 'removeTaskFromCollection',
46
+ HANDLE_TASK_CLEANUP: 'handleTaskCleanup',
47
+ GET_TASK: 'getTask',
48
+ GET_ALL_TASKS: 'getAllTasks',
49
+ GET_TASK_MANAGER: 'getTaskManager',
50
+ SETUP_AUTO_WRAPUP_TIMER: 'setupAutoWrapupTimer',
51
+ CANCEL_AUTO_WRAPUP_TIMER: 'cancelAutoWrapupTimer',
52
+ };