@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,1375 @@
1
+ import EventEmitter from 'events';
2
+ import {CALL_EVENT_KEYS, LocalMicrophoneStream} from '@webex/calling';
3
+ import {CallId} from '@webex/calling/dist/types/common/types';
4
+ import {getErrorDetails} from '../core/Utils';
5
+ import {LoginOption} from '../../types';
6
+ import {TASK_FILE} from '../../constants';
7
+ import {METHODS} from './constants';
8
+ import routingContact from './contact';
9
+ import LoggerProxy from '../../logger-proxy';
10
+ import {
11
+ ITask,
12
+ TaskResponse,
13
+ TaskData,
14
+ TaskId,
15
+ TASK_EVENTS,
16
+ WrapupPayLoad,
17
+ ResumeRecordingPayload,
18
+ ConsultPayload,
19
+ ConsultEndPayload,
20
+ TransferPayLoad,
21
+ DESTINATION_TYPE,
22
+ CONSULT_TRANSFER_DESTINATION_TYPE,
23
+ ConsultTransferPayLoad,
24
+ MEDIA_CHANNEL,
25
+ } from './types';
26
+ import WebCallingService from '../WebCallingService';
27
+ import MetricsManager from '../../metrics/MetricsManager';
28
+ import {METRIC_EVENT_NAMES} from '../../metrics/constants';
29
+ import {Failure} from '../core/GlobalTypes';
30
+ import AutoWrapup from './AutoWrapup';
31
+ import {WrapupData} from '../config/types';
32
+
33
+ /**
34
+ * Task class represents a contact center task/interaction that can be managed by an agent.
35
+ * This class provides all the necessary methods to manage tasks in a contact center environment,
36
+ * handling various call control operations and task lifecycle management.
37
+ *
38
+ * - Task Lifecycle Management:
39
+ * - {@link accept} - Accept incoming task
40
+ * - {@link decline} - Decline incoming task
41
+ * - {@link end} - End active task
42
+ * - Media Controls:
43
+ * - {@link toggleMute} - Mute/unmute microphone for voice tasks
44
+ * - {@link hold} - Place task on hold
45
+ * - {@link resume} - Resume held task
46
+ * - Recording Controls:
47
+ * - {@link pauseRecording} - Pause task recording
48
+ * - {@link resumeRecording} - Resume paused recording
49
+ * - Task Transfer & Consultation:
50
+ * - {@link consult} - Initiate consultation with another agent/queue
51
+ * - {@link endConsult} - End ongoing consultation
52
+ * - {@link transfer} - Transfer task to another agent/queue
53
+ * - {@link consultTransfer} - Transfer after consultation
54
+ * - Task Completion:
55
+ * - {@link wrapup} - Complete task wrap-up
56
+ *
57
+ * Key events emitted by Task instances (see {@link TASK_EVENTS} for details):
58
+ *
59
+ * - Task Lifecycle:
60
+ * - task:incoming — New task is being offered
61
+ * - task:assigned — Task assigned to agent
62
+ * - task:unassigned — Task unassigned from agent
63
+ * - task:end — Task has ended
64
+ * - task:wrapup — Task entered wrap-up state
65
+ * - task:wrappedup — Task wrap-up completed
66
+ * - task:rejected — Task was rejected/unanswered
67
+ * - task:hydrate — Task data populated
68
+ *
69
+ * - Media & Controls:
70
+ * - task:media — Voice call media track received
71
+ * - task:hold — Task placed on hold
72
+ * - task:unhold — Task resumed from hold
73
+ *
74
+ * - Consultation & Transfer:
75
+ * - task:consultCreated — Consultation initiated
76
+ * - task:consulting — Consultation in progress
77
+ * - task:consultAccepted — Consultation accepted
78
+ * - task:consultEnd — Consultation ended
79
+ * - task:consultQueueCancelled — Queue consultation cancelled
80
+ * - task:consultQueueFailed — Queue consultation failed
81
+ * - task:offerConsult — Consultation offered
82
+ * - task:offerContact — New contact offered
83
+ *
84
+ * - Recording:
85
+ * - task:recordingPaused — Recording paused
86
+ * - task:recordingPauseFailed — Recording pause failed
87
+ * - task:recordingResumed — Recording resumed
88
+ * - task:recordingResumeFailed — Recording resume failed
89
+ *
90
+ * @implements {ITask}
91
+ * @example
92
+ * ```typescript
93
+ * // 1. Initialize task
94
+ * const task = new Task(contact, webCallingService, taskData);
95
+ *
96
+ * // 2. Set up event listeners
97
+ * task.on('task:media', (track) => {
98
+ * // Handle voice call media
99
+ * const audioElement = document.getElementById('remote-audio');
100
+ * audioElement.srcObject = new MediaStream([track]);
101
+ * });
102
+ *
103
+ * task.on('task:hold', () => {
104
+ * console.log('Task is on hold');
105
+ * // Update UI to show hold state
106
+ * });
107
+ *
108
+ * task.on('task:end', () => {
109
+ * console.log('Task ended');
110
+ * if (task.data.wrapUpRequired) {
111
+ * // Show wrap-up form
112
+ * }
113
+ * });
114
+ *
115
+ * // 3. Example task operations
116
+ * await task.accept(); // Accept incoming task
117
+ * await task.hold(); // Place on hold
118
+ * await task.resume(); // Resume from hold
119
+ * await task.end(); // End task
120
+ *
121
+ * // 4. Handle wrap-up if required
122
+ * await task.wrapup({
123
+ * auxCodeId: 'RESOLVED',
124
+ * wrapUpReason: 'Customer issue resolved'
125
+ * });
126
+ * ```
127
+ */
128
+
129
+ export default class Task extends EventEmitter implements ITask {
130
+ private contact: ReturnType<typeof routingContact>;
131
+ private localAudioStream: LocalMicrophoneStream;
132
+ private webCallingService: WebCallingService;
133
+ public data: TaskData;
134
+ private metricsManager: MetricsManager;
135
+ public webCallMap: Record<TaskId, CallId>;
136
+ private wrapupData: WrapupData;
137
+ public autoWrapup?: AutoWrapup;
138
+
139
+ /**
140
+ * Creates a new Task instance which provides the following features:
141
+ * @param contact - The routing contact service instance
142
+ * @param webCallingService - The web calling service instance
143
+ * @param data - Initial task data
144
+ * @param wrapupData - Wrap-up configuration data
145
+ */
146
+ public constructor(
147
+ contact: ReturnType<typeof routingContact>,
148
+ webCallingService: WebCallingService,
149
+ data: TaskData,
150
+ wrapupData: WrapupData
151
+ ) {
152
+ super();
153
+ this.contact = contact;
154
+ this.data = data;
155
+ this.webCallingService = webCallingService;
156
+ this.webCallMap = {};
157
+ this.wrapupData = wrapupData;
158
+ this.metricsManager = MetricsManager.getInstance();
159
+ this.registerWebCallListeners();
160
+ this.setupAutoWrapupTimer();
161
+ }
162
+
163
+ /**
164
+ * Sets up the automatic wrap-up timer if wrap-up is required
165
+ * @private
166
+ */
167
+ private setupAutoWrapupTimer() {
168
+ if (
169
+ this.data.wrapUpRequired && // only when wrapup required
170
+ !this.autoWrapup && // if autoWrapup is not already set
171
+ this.wrapupData && // wrapupData is not defined
172
+ this.wrapupData.wrapUpProps // wrapUpProps is defined
173
+ ) {
174
+ const wrapUpProps = this.wrapupData.wrapUpProps;
175
+ if (!wrapUpProps || wrapUpProps.autoWrapup === false) {
176
+ LoggerProxy.info(`Auto wrap-up is not required for this task`, {
177
+ module: TASK_FILE,
178
+ method: METHODS.SETUP_AUTO_WRAPUP_TIMER,
179
+ interactionId: this.data.interactionId,
180
+ });
181
+
182
+ return;
183
+ }
184
+ const defaultWrapupReason =
185
+ wrapUpProps.wrapUpReasonList?.find((r) => r.isDefault) ?? wrapUpProps.wrapUpReasonList?.[0];
186
+ if (!defaultWrapupReason) {
187
+ LoggerProxy.error('No wrap-up reason configured', {
188
+ module: TASK_FILE,
189
+ method: METHODS.SETUP_AUTO_WRAPUP_TIMER,
190
+ });
191
+
192
+ return;
193
+ }
194
+ const intervalMs = wrapUpProps.autoWrapupInterval;
195
+ if (!intervalMs || intervalMs <= 0) {
196
+ LoggerProxy.error(`Invalid auto wrap-up interval: ${intervalMs}`, {
197
+ module: TASK_FILE,
198
+ method: METHODS.SETUP_AUTO_WRAPUP_TIMER,
199
+ });
200
+ }
201
+ this.autoWrapup = new AutoWrapup(intervalMs, wrapUpProps.allowCancelAutoWrapup);
202
+ this.autoWrapup.start(async () => {
203
+ LoggerProxy.info(`Auto wrap-up timer triggered`, {
204
+ module: TASK_FILE,
205
+ method: METHODS.SETUP_AUTO_WRAPUP_TIMER,
206
+ interactionId: this.data.interactionId,
207
+ });
208
+ await this.wrapup({
209
+ wrapUpReason: defaultWrapupReason.name,
210
+ auxCodeId: defaultWrapupReason.id,
211
+ });
212
+ });
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Cancels the automatic wrap-up timer if it's running
218
+ * @public - Public so it can be called externally when needed
219
+ * Note: This is supported only in single session mode. Not supported in multi-session mode.
220
+ */
221
+ public cancelAutoWrapupTimer() {
222
+ this.autoWrapup?.clear();
223
+ this.autoWrapup = undefined;
224
+ LoggerProxy.info(`Auto wrap-up timer cancelled`, {
225
+ module: TASK_FILE,
226
+ method: METHODS.CANCEL_AUTO_WRAPUP_TIMER,
227
+ interactionId: this.data?.interactionId,
228
+ });
229
+ }
230
+
231
+ /**
232
+ * @ignore
233
+ * @private
234
+ */
235
+ private handleRemoteMedia = (track: MediaStreamTrack) => {
236
+ this.emit(TASK_EVENTS.TASK_MEDIA, track);
237
+ };
238
+
239
+ /**
240
+ * @ignore
241
+ * @private
242
+ */
243
+ private registerWebCallListeners() {
244
+ this.webCallingService.on(CALL_EVENT_KEYS.REMOTE_MEDIA, this.handleRemoteMedia);
245
+ }
246
+
247
+ /**
248
+ * @ignore
249
+ */
250
+ public unregisterWebCallListeners() {
251
+ this.webCallingService.off(CALL_EVENT_KEYS.REMOTE_MEDIA, this.handleRemoteMedia);
252
+ }
253
+
254
+ /**
255
+ * Updates the task data with new information
256
+ * @param updatedData - New task data to merge with existing data
257
+ * @param shouldOverwrite - If true, completely replace data instead of merging
258
+ * @returns The updated task instance
259
+ * @example
260
+ * ```typescript
261
+ * task.updateTaskData(newData);
262
+ * task.updateTaskData(newData, true); // completely replace data
263
+ * ```
264
+ */
265
+ public updateTaskData = (updatedData: TaskData, shouldOverwrite = false) => {
266
+ this.data = shouldOverwrite ? updatedData : this.reconcileData(this.data, updatedData);
267
+ this.setupAutoWrapupTimer();
268
+
269
+ return this;
270
+ };
271
+
272
+ /**
273
+ * Recursively merges old data with new data
274
+ * @private
275
+ */
276
+ private reconcileData(oldData: TaskData, newData: TaskData): TaskData {
277
+ Object.keys(newData).forEach((key) => {
278
+ if (newData[key] && typeof newData[key] === 'object' && !Array.isArray(newData[key])) {
279
+ oldData[key] = this.reconcileData({...oldData[key]}, newData[key]);
280
+ } else {
281
+ oldData[key] = newData[key];
282
+ }
283
+ });
284
+
285
+ return oldData;
286
+ }
287
+
288
+ /**
289
+ * Agent accepts the incoming task.
290
+ * After accepting, the task will emit task:assigned event and for voice calls,
291
+ * a task:media event with the audio stream.
292
+ *
293
+ * @returns Promise<TaskResponse>
294
+ * @throws Error if accepting task fails or media requirements not met
295
+ * @example
296
+ * ```typescript
297
+ * // Set up event handlers before accepting
298
+ * task.on(TASK_EVENTS.TASK_ASSIGNED, () => {
299
+ * console.log('Task assigned, ID:', task.data.interactionId);
300
+ * // Update UI to show active task
301
+ * });
302
+ *
303
+ * // For voice calls, handle media
304
+ * task.on(TASK_EVENTS.TASK_MEDIA, (track) => {
305
+ * const audioElement = document.getElementById('remote-audio');
306
+ * audioElement.srcObject = new MediaStream([track]);
307
+ * });
308
+ *
309
+ * // Accept the task
310
+ * try {
311
+ * await task.accept();
312
+ * console.log('Successfully accepted task');
313
+ * } catch (error) {
314
+ * console.error('Failed to accept task:', error);
315
+ * // Handle error (e.g., show error message to agent)
316
+ * }
317
+ * ```
318
+ */
319
+ public async accept(): Promise<TaskResponse> {
320
+ try {
321
+ LoggerProxy.info(`Accepting task`, {
322
+ module: TASK_FILE,
323
+ method: METHODS.ACCEPT,
324
+ interactionId: this.data.interactionId,
325
+ });
326
+ this.metricsManager.timeEvent([
327
+ METRIC_EVENT_NAMES.TASK_ACCEPT_SUCCESS,
328
+ METRIC_EVENT_NAMES.TASK_ACCEPT_FAILED,
329
+ ]);
330
+
331
+ if (this.data.interaction.mediaType !== MEDIA_CHANNEL.TELEPHONY) {
332
+ const response = await this.contact.accept({interactionId: this.data.interactionId});
333
+ LoggerProxy.log(`Task accepted successfully`, {
334
+ module: TASK_FILE,
335
+ method: METHODS.ACCEPT,
336
+ trackingId: response.trackingId,
337
+ interactionId: this.data.interactionId,
338
+ });
339
+ this.metricsManager.trackEvent(
340
+ METRIC_EVENT_NAMES.TASK_ACCEPT_SUCCESS,
341
+ {
342
+ taskId: this.data.interactionId,
343
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(this.data),
344
+ },
345
+ ['operational', 'behavioral', 'business']
346
+ );
347
+
348
+ return response;
349
+ }
350
+
351
+ if (this.webCallingService.loginOption === LoginOption.BROWSER) {
352
+ const constraints = {audio: true};
353
+
354
+ const localStream = await navigator.mediaDevices.getUserMedia(constraints);
355
+ const audioTrack = localStream.getAudioTracks()[0];
356
+ this.localAudioStream = new LocalMicrophoneStream(new MediaStream([audioTrack]));
357
+ this.webCallingService.answerCall(this.localAudioStream, this.data.interactionId);
358
+ this.metricsManager.trackEvent(
359
+ METRIC_EVENT_NAMES.TASK_ACCEPT_SUCCESS,
360
+ {
361
+ taskId: this.data.interactionId,
362
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(this.data),
363
+ },
364
+ ['operational', 'behavioral', 'business']
365
+ );
366
+
367
+ LoggerProxy.log(`Task accepted successfully with webrtc calling`, {
368
+ module: TASK_FILE,
369
+ method: METHODS.ACCEPT,
370
+ interactionId: this.data.interactionId,
371
+ });
372
+ }
373
+
374
+ return Promise.resolve(); // TODO: reject for extension as part of refactor
375
+ } catch (error) {
376
+ const {error: detailedError} = getErrorDetails(error, METHODS.ACCEPT, TASK_FILE);
377
+ this.metricsManager.trackEvent(
378
+ METRIC_EVENT_NAMES.TASK_ACCEPT_FAILED,
379
+ {
380
+ taskId: this.data.interactionId,
381
+ error: error.toString(),
382
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details as Failure),
383
+ },
384
+ ['operational', 'behavioral', 'business']
385
+ );
386
+ throw detailedError;
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Agent can mute/unmute their microphone during a WebRTC task.
392
+ * This method toggles between muted and unmuted states for the local audio stream.
393
+ *
394
+ * @returns Promise<void> - Resolves when mute/unmute operation completes
395
+ * @throws Error if toggling mute state fails or audio stream is not available
396
+ * @example
397
+ * ```typescript
398
+ * // Toggle mute state
399
+ * task.toggleMute()
400
+ * .then(() => console.log('Mute state toggled successfully'))
401
+ * .catch(error => console.error('Failed to toggle mute:', error));
402
+ * ```
403
+ */
404
+ public async toggleMute() {
405
+ try {
406
+ LoggerProxy.info(`Toggling mute state`, {
407
+ module: TASK_FILE,
408
+ method: METHODS.TOGGLE_MUTE,
409
+ interactionId: this.data.interactionId,
410
+ });
411
+
412
+ this.webCallingService.muteUnmuteCall(this.localAudioStream);
413
+
414
+ LoggerProxy.log(
415
+ `Mute state toggled successfully isCallMuted: ${this.webCallingService.isCallMuted()}`,
416
+ {
417
+ module: TASK_FILE,
418
+ method: METHODS.TOGGLE_MUTE,
419
+ interactionId: this.data.interactionId,
420
+ }
421
+ );
422
+
423
+ return Promise.resolve();
424
+ } catch (error) {
425
+ const {error: detailedError} = getErrorDetails(error, METHODS.TOGGLE_MUTE, TASK_FILE);
426
+ throw detailedError;
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Declines the incoming task. This will reject the task and notify the routing system.
432
+ * For voice calls, this is equivalent to declining the incoming call.
433
+ *
434
+ * @returns Promise<TaskResponse>
435
+ * @throws Error if the decline operation fails
436
+ * @example
437
+ * ```typescript
438
+ * // Decline an incoming task
439
+ * task.decline()
440
+ * .then(() => console.log('Task declined successfully'))
441
+ * .catch(error => console.error('Failed to decline task:', error));
442
+ * ```
443
+ */
444
+ public async decline(): Promise<TaskResponse> {
445
+ try {
446
+ LoggerProxy.info(`Declining task`, {
447
+ module: TASK_FILE,
448
+ method: METHODS.DECLINE,
449
+ interactionId: this.data.interactionId,
450
+ });
451
+ this.metricsManager.timeEvent([
452
+ METRIC_EVENT_NAMES.TASK_DECLINE_SUCCESS,
453
+ METRIC_EVENT_NAMES.TASK_DECLINE_FAILED,
454
+ ]);
455
+
456
+ this.webCallingService.declineCall(this.data.interactionId);
457
+ this.unregisterWebCallListeners();
458
+
459
+ this.metricsManager.trackEvent(
460
+ METRIC_EVENT_NAMES.TASK_DECLINE_SUCCESS,
461
+ {taskId: this.data.interactionId},
462
+ ['operational', 'behavioral']
463
+ );
464
+
465
+ LoggerProxy.log(`Task declined successfully`, {
466
+ module: TASK_FILE,
467
+ method: METHODS.DECLINE,
468
+ interactionId: this.data.interactionId,
469
+ });
470
+
471
+ return Promise.resolve();
472
+ } catch (error) {
473
+ const {error: detailedError} = getErrorDetails(error, METHODS.DECLINE, TASK_FILE);
474
+ this.metricsManager.trackEvent(
475
+ METRIC_EVENT_NAMES.TASK_DECLINE_FAILED,
476
+ {
477
+ taskId: this.data.interactionId,
478
+ error: error.toString(),
479
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
480
+ },
481
+ ['operational', 'behavioral']
482
+ );
483
+ throw detailedError;
484
+ }
485
+ }
486
+
487
+ /**
488
+ * Puts the current task/interaction on hold.
489
+ * Emits task:hold event when successful. For voice tasks, this mutes the audio.
490
+ *
491
+ * @returns Promise<TaskResponse>
492
+ * @throws Error if hold operation fails
493
+ * @example
494
+ * ```typescript
495
+ * // Set up hold event handler
496
+ * task.on(TASK_EVENTS.TASK_HOLD, () => {
497
+ * console.log('Task is now on hold');
498
+ * // Update UI to show hold state (e.g., enable resume button, show hold indicator)
499
+ * document.getElementById('resume-btn').disabled = false;
500
+ * document.getElementById('hold-indicator').style.display = 'block';
501
+ * });
502
+ *
503
+ * // Place task on hold
504
+ * try {
505
+ * await task.hold();
506
+ * console.log('Successfully placed task on hold');
507
+ * } catch (error) {
508
+ * console.error('Failed to place task on hold:', error);
509
+ * // Handle error (e.g., show error message, reset UI state)
510
+ * }
511
+ * ```
512
+ */
513
+ public async hold(): Promise<TaskResponse> {
514
+ try {
515
+ LoggerProxy.info(`Holding task`, {
516
+ module: TASK_FILE,
517
+ method: METHODS.HOLD,
518
+ interactionId: this.data.interactionId,
519
+ });
520
+
521
+ this.metricsManager.timeEvent([
522
+ METRIC_EVENT_NAMES.TASK_HOLD_SUCCESS,
523
+ METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
524
+ ]);
525
+
526
+ const response = await this.contact.hold({
527
+ interactionId: this.data.interactionId,
528
+ data: {mediaResourceId: this.data.mediaResourceId},
529
+ });
530
+
531
+ this.metricsManager.trackEvent(
532
+ METRIC_EVENT_NAMES.TASK_HOLD_SUCCESS,
533
+ {
534
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
535
+ taskId: this.data.interactionId,
536
+ mediaResourceId: this.data.mediaResourceId,
537
+ },
538
+ ['operational', 'behavioral']
539
+ );
540
+
541
+ LoggerProxy.log(`Task placed on hold successfully`, {
542
+ module: TASK_FILE,
543
+ method: METHODS.HOLD,
544
+ trackingId: response.trackingId,
545
+ interactionId: this.data.interactionId,
546
+ });
547
+
548
+ return response;
549
+ } catch (error) {
550
+ const {error: detailedError} = getErrorDetails(error, METHODS.HOLD, TASK_FILE);
551
+ this.metricsManager.trackEvent(
552
+ METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
553
+ {
554
+ taskId: this.data.interactionId,
555
+ mediaResourceId: this.data.mediaResourceId,
556
+ error: error.toString(),
557
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
558
+ },
559
+ ['operational', 'behavioral']
560
+ );
561
+ throw detailedError;
562
+ }
563
+ }
564
+
565
+ /**
566
+ * Resumes the task/interaction that was previously put on hold.
567
+ * Emits task:resume event when successful. For voice tasks, this restores the audio.
568
+ *
569
+ * @returns Promise<TaskResponse>
570
+ * @throws Error if resume operation fails
571
+ * @example
572
+ * ```typescript
573
+ * // Set up resume event handler
574
+ * task.on(TASK_EVENTS.TASK_RESUME, () => {
575
+ * console.log('Task resumed from hold');
576
+ * // Update UI to show active state
577
+ * document.getElementById('hold-btn').disabled = false;
578
+ * document.getElementById('hold-indicator').style.display = 'none';
579
+ * });
580
+ *
581
+ * // Resume task from hold
582
+ * try {
583
+ * await task.resume();
584
+ * console.log('Successfully resumed task from hold');
585
+ * } catch (error) {
586
+ * console.error('Failed to resume task:', error);
587
+ * // Handle error (e.g., show error message)
588
+ * }
589
+ * ```
590
+ */
591
+ public async resume(): Promise<TaskResponse> {
592
+ try {
593
+ LoggerProxy.info(`Resuming task`, {
594
+ module: TASK_FILE,
595
+ method: METHODS.RESUME,
596
+ interactionId: this.data.interactionId,
597
+ });
598
+ const {mainInteractionId} = this.data.interaction;
599
+ const {mediaResourceId} = this.data.interaction.media[mainInteractionId];
600
+
601
+ this.metricsManager.timeEvent([
602
+ METRIC_EVENT_NAMES.TASK_RESUME_SUCCESS,
603
+ METRIC_EVENT_NAMES.TASK_RESUME_FAILED,
604
+ ]);
605
+
606
+ const response = await this.contact.unHold({
607
+ interactionId: this.data.interactionId,
608
+ data: {mediaResourceId},
609
+ });
610
+
611
+ this.metricsManager.trackEvent(
612
+ METRIC_EVENT_NAMES.TASK_RESUME_SUCCESS,
613
+ {
614
+ taskId: this.data.interactionId,
615
+ mainInteractionId,
616
+ mediaResourceId,
617
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
618
+ },
619
+ ['operational', 'behavioral']
620
+ );
621
+
622
+ LoggerProxy.log(`Task resumed successfully`, {
623
+ module: TASK_FILE,
624
+ method: METHODS.RESUME,
625
+ trackingId: response.trackingId,
626
+ interactionId: this.data.interactionId,
627
+ });
628
+
629
+ return response;
630
+ } catch (error) {
631
+ const {error: detailedError} = getErrorDetails(error, METHODS.RESUME, TASK_FILE);
632
+ const mainInteractionId = this.data.interaction?.mainInteractionId;
633
+ this.metricsManager.trackEvent(
634
+ METRIC_EVENT_NAMES.TASK_RESUME_FAILED,
635
+ {
636
+ taskId: this.data.interactionId,
637
+ mainInteractionId,
638
+ mediaResourceId: mainInteractionId
639
+ ? this.data.interaction.media[mainInteractionId].mediaResourceId
640
+ : '',
641
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
642
+ },
643
+ ['operational', 'behavioral']
644
+ );
645
+ throw detailedError;
646
+ }
647
+ }
648
+
649
+ /**
650
+ * Ends the task/interaction with the customer.
651
+ * Emits task:end event when successful. If task requires wrap-up,
652
+ * this will be indicated in the task:end event data.
653
+ *
654
+ * @returns Promise<TaskResponse>
655
+ * @throws Error if ending task fails
656
+ * @example
657
+ * ```typescript
658
+ * // Set up task end event handler
659
+ * task.on(TASK_EVENTS.TASK_END, (data) => {
660
+ * console.log('Task ended:', task.data.interactionId);
661
+ *
662
+ * if (data.wrapUpRequired) {
663
+ * // Show wrap-up form
664
+ * showWrapupForm();
665
+ * } else {
666
+ * // Clean up and prepare for next task
667
+ * cleanupTask();
668
+ * }
669
+ * });
670
+ *
671
+ * // End the task
672
+ * try {
673
+ * await task.end();
674
+ * console.log('Task end request successful');
675
+ * } catch (error) {
676
+ * console.error('Failed to end task:', error);
677
+ * // Handle error (e.g., show error message, retry option)
678
+ * }
679
+ *
680
+ * function showWrapupForm() {
681
+ * // Show wrap-up UI with required codes
682
+ * document.getElementById('wrapup-form').style.display = 'block';
683
+ * }
684
+ *
685
+ * function cleanupTask() {
686
+ * // Reset UI state
687
+ * document.getElementById('active-task').style.display = 'none';
688
+ * document.getElementById('controls').style.display = 'none';
689
+ * }
690
+ * ```
691
+ */
692
+ public async end(): Promise<TaskResponse> {
693
+ try {
694
+ LoggerProxy.info(`Ending task`, {
695
+ module: TASK_FILE,
696
+ method: METHODS.END,
697
+ interactionId: this.data.interactionId,
698
+ });
699
+
700
+ this.metricsManager.timeEvent([
701
+ METRIC_EVENT_NAMES.TASK_END_SUCCESS,
702
+ METRIC_EVENT_NAMES.TASK_END_FAILED,
703
+ ]);
704
+
705
+ const response = await this.contact.end({interactionId: this.data.interactionId});
706
+
707
+ this.metricsManager.trackEvent(
708
+ METRIC_EVENT_NAMES.TASK_END_SUCCESS,
709
+ {
710
+ taskId: this.data.interactionId,
711
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
712
+ },
713
+ ['operational', 'behavioral', 'business']
714
+ );
715
+
716
+ LoggerProxy.log(`Task ended successfully`, {
717
+ module: TASK_FILE,
718
+ method: METHODS.END,
719
+ trackingId: response.trackingId,
720
+ interactionId: this.data.interactionId,
721
+ });
722
+
723
+ return response;
724
+ } catch (error) {
725
+ const {error: detailedError} = getErrorDetails(error, METHODS.END, TASK_FILE);
726
+ this.metricsManager.trackEvent(
727
+ METRIC_EVENT_NAMES.TASK_END_FAILED,
728
+ {
729
+ taskId: this.data.interactionId,
730
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
731
+ },
732
+ ['operational', 'behavioral', 'business']
733
+ );
734
+ throw detailedError;
735
+ }
736
+ }
737
+
738
+ /**
739
+ * Wraps up the task/interaction with the customer.
740
+ * This is called after task:end event if wrapUpRequired is true.
741
+ * Emits task:wrappedup event when successful.
742
+ *
743
+ * @param wrapupPayload - WrapupPayLoad containing:
744
+ * - auxCodeId: Required ID for the wrap-up code
745
+ * - wrapUpReason: Required description of wrap-up reason
746
+ * @returns Promise<TaskResponse>
747
+ * @throws Error if task data is unavailable, auxCodeId is missing, or wrapUpReason is missing
748
+ * @example
749
+ * ```typescript
750
+ * // Set up wrap-up events
751
+ * task.on(TASK_EVENTS.TASK_WRAPUP, () => {
752
+ * console.log('Task ready for wrap-up');
753
+ * // Show wrap-up form
754
+ * document.getElementById('wrapup-form').style.display = 'block';
755
+ * });
756
+ *
757
+ * task.on(TASK_EVENTS.TASK_WRAPPEDUP, () => {
758
+ * console.log('Task wrap-up completed');
759
+ * // Clean up UI
760
+ * document.getElementById('wrapup-form').style.display = 'none';
761
+ * });
762
+ *
763
+ * // Submit wrap-up
764
+ * try {
765
+ * const wrapupPayload = {
766
+ * auxCodeId: selectedCode, // e.g., 'ISSUE_RESOLVED'
767
+ * wrapUpReason: 'Customer issue resolved successfully'
768
+ * };
769
+ * await task.wrapup(wrapupPayload);
770
+ * console.log('Successfully submitted wrap-up');
771
+ * } catch (error) {
772
+ * console.error('Failed to submit wrap-up:', error);
773
+ * // Handle validation errors
774
+ * if (error.message.includes('required')) {
775
+ * // Show validation error to agent
776
+ * }
777
+ * }
778
+ * ```
779
+ */
780
+ public async wrapup(wrapupPayload: WrapupPayLoad): Promise<TaskResponse> {
781
+ try {
782
+ this.cancelAutoWrapupTimer();
783
+ LoggerProxy.info(`Wrapping up task`, {
784
+ module: TASK_FILE,
785
+ method: METHODS.WRAPUP,
786
+ interactionId: this.data.interactionId,
787
+ });
788
+
789
+ this.metricsManager.timeEvent([
790
+ METRIC_EVENT_NAMES.TASK_WRAPUP_SUCCESS,
791
+ METRIC_EVENT_NAMES.TASK_WRAPUP_FAILED,
792
+ ]);
793
+
794
+ if (!this.data) {
795
+ throw new Error('No task data available');
796
+ }
797
+ if (!wrapupPayload.auxCodeId || wrapupPayload.auxCodeId.length === 0) {
798
+ throw new Error('AuxCodeId is required');
799
+ }
800
+ if (!wrapupPayload.wrapUpReason || wrapupPayload.wrapUpReason.length === 0) {
801
+ throw new Error('WrapUpReason is required');
802
+ }
803
+
804
+ const response = await this.contact.wrapup({
805
+ interactionId: this.data.interactionId,
806
+ data: wrapupPayload,
807
+ });
808
+
809
+ this.metricsManager.trackEvent(
810
+ METRIC_EVENT_NAMES.TASK_WRAPUP_SUCCESS,
811
+ {
812
+ taskId: this.data.interactionId,
813
+ wrapUpCode: wrapupPayload.auxCodeId,
814
+ wrapUpReason: wrapupPayload.wrapUpReason,
815
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
816
+ },
817
+ ['operational', 'behavioral', 'business']
818
+ );
819
+
820
+ LoggerProxy.log(`Task wrapped up successfully`, {
821
+ module: TASK_FILE,
822
+ method: METHODS.WRAPUP,
823
+ trackingId: response.trackingId,
824
+ interactionId: this.data.interactionId,
825
+ });
826
+
827
+ return response;
828
+ } catch (error) {
829
+ const {error: detailedError} = getErrorDetails(error, METHODS.WRAPUP, TASK_FILE);
830
+ this.metricsManager.trackEvent(
831
+ METRIC_EVENT_NAMES.TASK_WRAPUP_FAILED,
832
+ {
833
+ taskId: this.data.interactionId,
834
+ wrapUpCode: wrapupPayload.auxCodeId,
835
+ wrapUpReason: wrapupPayload.wrapUpReason,
836
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
837
+ },
838
+ ['operational', 'behavioral', 'business']
839
+ );
840
+ throw detailedError;
841
+ }
842
+ }
843
+
844
+ /**
845
+ * Pauses the recording for the current voice task.
846
+ * Emits task:recordingPaused event when successful.
847
+ *
848
+ * @returns Promise<TaskResponse>
849
+ * @throws Error if pause recording fails
850
+ * @example
851
+ * ```typescript
852
+ * // Set up recording events
853
+ * task.on(TASK_EVENTS.TASK_RECORDING_PAUSED, () => {
854
+ * console.log('Recording paused');
855
+ * // Update UI to show recording paused state
856
+ * document.getElementById('recording-status').textContent = 'Recording Paused';
857
+ * document.getElementById('pause-recording-btn').style.display = 'none';
858
+ * document.getElementById('resume-recording-btn').style.display = 'block';
859
+ * });
860
+ *
861
+ * task.on(TASK_EVENTS.TASK_RECORDING_PAUSE_FAILED, (error) => {
862
+ * console.error('Failed to pause recording:', error);
863
+ * // Show error to agent
864
+ * });
865
+ *
866
+ * // Pause recording
867
+ * try {
868
+ * await task.pauseRecording();
869
+ * console.log('Pause recording request sent');
870
+ * } catch (error) {
871
+ * console.error('Error sending pause recording request:', error);
872
+ * // Handle error
873
+ * }
874
+ * ```
875
+ */
876
+ public async pauseRecording(): Promise<TaskResponse> {
877
+ try {
878
+ LoggerProxy.info(`Pausing recording`, {
879
+ module: TASK_FILE,
880
+ method: METHODS.PAUSE_RECORDING,
881
+ interactionId: this.data.interactionId,
882
+ });
883
+
884
+ this.metricsManager.timeEvent([
885
+ METRIC_EVENT_NAMES.TASK_PAUSE_RECORDING_SUCCESS,
886
+ METRIC_EVENT_NAMES.TASK_PAUSE_RECORDING_FAILED,
887
+ ]);
888
+
889
+ const result = await this.contact.pauseRecording({interactionId: this.data.interactionId});
890
+
891
+ this.metricsManager.trackEvent(
892
+ METRIC_EVENT_NAMES.TASK_PAUSE_RECORDING_SUCCESS,
893
+ {
894
+ taskId: this.data.interactionId,
895
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
896
+ },
897
+ ['operational', 'behavioral', 'business']
898
+ );
899
+
900
+ LoggerProxy.log(`Recording paused successfully`, {
901
+ module: TASK_FILE,
902
+ method: METHODS.PAUSE_RECORDING,
903
+ trackingId: result.trackingId,
904
+ interactionId: this.data.interactionId,
905
+ });
906
+
907
+ return result;
908
+ } catch (error) {
909
+ const {error: detailedError} = getErrorDetails(error, METHODS.PAUSE_RECORDING, TASK_FILE);
910
+ this.metricsManager.trackEvent(
911
+ METRIC_EVENT_NAMES.TASK_PAUSE_RECORDING_FAILED,
912
+ {
913
+ taskId: this.data.interactionId,
914
+ error: error.toString(),
915
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
916
+ },
917
+ ['operational', 'behavioral', 'business']
918
+ );
919
+ throw detailedError;
920
+ }
921
+ }
922
+
923
+ /**
924
+ * Resumes the recording for the voice task that was previously paused.
925
+ * Emits task:recordingResumed event when successful.
926
+ *
927
+ * @param resumeRecordingPayload - Configuration for resuming recording:
928
+ * - autoResumed: Indicates if resume was automatic (defaults to false)
929
+ * @returns Promise<TaskResponse>
930
+ * @throws Error if resume recording fails
931
+ * @example
932
+ * ```typescript
933
+ * // Set up recording resume events
934
+ * task.on(TASK_EVENTS.TASK_RECORDING_RESUMED, () => {
935
+ * console.log('Recording resumed');
936
+ * // Update UI to show active recording state
937
+ * document.getElementById('recording-status').textContent = 'Recording Active';
938
+ * document.getElementById('pause-recording-btn').style.display = 'block';
939
+ * document.getElementById('resume-recording-btn').style.display = 'none';
940
+ * });
941
+ *
942
+ * task.on(TASK_EVENTS.TASK_RECORDING_RESUME_FAILED, (error) => {
943
+ * console.error('Failed to resume recording:', error);
944
+ * // Show error to agent
945
+ * });
946
+ *
947
+ * // Resume recording
948
+ * try {
949
+ * const resumePayload = {
950
+ * autoResumed: false // Set to true if triggered by system
951
+ * };
952
+ * await task.resumeRecording(resumePayload);
953
+ * console.log('Resume recording request sent');
954
+ * } catch (error) {
955
+ * console.error('Error sending resume recording request:', error);
956
+ * // Handle error
957
+ * }
958
+ * ```
959
+ */
960
+ public async resumeRecording(
961
+ resumeRecordingPayload: ResumeRecordingPayload
962
+ ): Promise<TaskResponse> {
963
+ try {
964
+ LoggerProxy.info(`Resuming recording`, {
965
+ module: TASK_FILE,
966
+ method: METHODS.RESUME_RECORDING,
967
+ interactionId: this.data.interactionId,
968
+ });
969
+
970
+ this.metricsManager.timeEvent([
971
+ METRIC_EVENT_NAMES.TASK_RESUME_RECORDING_SUCCESS,
972
+ METRIC_EVENT_NAMES.TASK_RESUME_RECORDING_FAILED,
973
+ ]);
974
+
975
+ resumeRecordingPayload ??= {autoResumed: false};
976
+
977
+ const result = await this.contact.resumeRecording({
978
+ interactionId: this.data.interactionId,
979
+ data: resumeRecordingPayload,
980
+ });
981
+
982
+ this.metricsManager.trackEvent(
983
+ METRIC_EVENT_NAMES.TASK_RESUME_RECORDING_SUCCESS,
984
+ {
985
+ taskId: this.data.interactionId,
986
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
987
+ },
988
+ ['operational', 'behavioral', 'business']
989
+ );
990
+
991
+ LoggerProxy.log(`Recording resumed successfully`, {
992
+ module: TASK_FILE,
993
+ method: METHODS.RESUME_RECORDING,
994
+ trackingId: result.trackingId,
995
+ interactionId: this.data.interactionId,
996
+ });
997
+
998
+ return result;
999
+ } catch (error) {
1000
+ const {error: detailedError} = getErrorDetails(error, METHODS.RESUME_RECORDING, TASK_FILE);
1001
+ this.metricsManager.trackEvent(
1002
+ METRIC_EVENT_NAMES.TASK_RESUME_RECORDING_FAILED,
1003
+ {
1004
+ taskId: this.data.interactionId,
1005
+ error: error.toString(),
1006
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1007
+ },
1008
+ ['operational', 'behavioral', 'business']
1009
+ );
1010
+ throw detailedError;
1011
+ }
1012
+ }
1013
+
1014
+ /**
1015
+ * Consults another agent or queue on an ongoing task for further assistance.
1016
+ * During consultation, the original customer is typically placed on hold while
1017
+ * the agent seeks guidance from another agent or queue.
1018
+ *
1019
+ * @param consultPayload - Configuration for the consultation containing:
1020
+ * - to: ID of the agent or queue to consult with
1021
+ * - destinationType: Type of destination (AGENT, QUEUE, etc.)
1022
+ * - holdParticipants: Whether to hold other participants (defaults to true)
1023
+ * @returns Promise<TaskResponse> - Resolves with consultation result
1024
+ * @throws Error if consultation fails or invalid parameters provided
1025
+ * @example
1026
+ * ```typescript
1027
+ * // Consult with another agent
1028
+ * const consultPayload = {
1029
+ * to: 'agentId123',
1030
+ * destinationType: DESTINATION_TYPE.AGENT,
1031
+ * holdParticipants: true
1032
+ * };
1033
+ * task.consult(consultPayload)
1034
+ * .then(response => console.log('Consultation started successfully'))
1035
+ * .catch(error => console.error('Failed to start consultation:', error));
1036
+ *
1037
+ * // Consult with a queue
1038
+ * const queueConsultPayload = {
1039
+ * to: 'salesQueue123',
1040
+ * destinationType: DESTINATION_TYPE.QUEUE
1041
+ * };
1042
+ * task.consult(queueConsultPayload)
1043
+ * .then(response => console.log('Queue consultation started'))
1044
+ * .catch(error => console.error('Failed to start queue consultation:', error));
1045
+ * ```
1046
+ */
1047
+ public async consult(consultPayload: ConsultPayload): Promise<TaskResponse> {
1048
+ try {
1049
+ LoggerProxy.info(`Starting consult`, {
1050
+ module: TASK_FILE,
1051
+ method: METHODS.CONSULT,
1052
+ interactionId: this.data.interactionId,
1053
+ });
1054
+
1055
+ this.metricsManager.timeEvent([
1056
+ METRIC_EVENT_NAMES.TASK_CONSULT_START_SUCCESS,
1057
+ METRIC_EVENT_NAMES.TASK_CONSULT_START_FAILED,
1058
+ ]);
1059
+
1060
+ const result = await this.contact.consult({
1061
+ interactionId: this.data.interactionId,
1062
+ data: consultPayload,
1063
+ });
1064
+
1065
+ this.metricsManager.trackEvent(
1066
+ METRIC_EVENT_NAMES.TASK_CONSULT_START_SUCCESS,
1067
+ {
1068
+ taskId: this.data.interactionId,
1069
+ destination: consultPayload.to,
1070
+ destinationType: consultPayload.destinationType,
1071
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1072
+ },
1073
+ ['operational', 'behavioral', 'business']
1074
+ );
1075
+
1076
+ LoggerProxy.log(`Consult started successfully to ${consultPayload.to}`, {
1077
+ module: TASK_FILE,
1078
+ method: METHODS.CONSULT,
1079
+ trackingId: result.trackingId,
1080
+ interactionId: this.data.interactionId,
1081
+ });
1082
+
1083
+ return result;
1084
+ } catch (error) {
1085
+ const {error: detailedError} = getErrorDetails(error, METHODS.CONSULT, TASK_FILE);
1086
+ this.metricsManager.trackEvent(
1087
+ METRIC_EVENT_NAMES.TASK_CONSULT_START_FAILED,
1088
+ {
1089
+ taskId: this.data.interactionId,
1090
+ destination: consultPayload.to,
1091
+ destinationType: consultPayload.destinationType,
1092
+ error: error.toString(),
1093
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1094
+ },
1095
+ ['operational', 'behavioral', 'business']
1096
+ );
1097
+ throw detailedError;
1098
+ }
1099
+ }
1100
+
1101
+ /**
1102
+ * Ends an ongoing consultation session for the task.
1103
+ * This terminates the consultation while maintaining the original customer connection.
1104
+ *
1105
+ * @param consultEndPayload - Configuration for ending the consultation containing:
1106
+ * - isConsult: Must be true to indicate this is a consultation end
1107
+ * - taskId: ID of the task being consulted on
1108
+ * - queueId: (Optional) Queue ID if this was a queue consultation
1109
+ * - isSecondaryEpDnAgent: (Optional) Indicates if this involves a secondary entry point
1110
+ * @returns Promise<TaskResponse> - Resolves when consultation is ended
1111
+ * @throws Error if ending consultation fails or invalid parameters provided
1112
+ * @example
1113
+ * ```typescript
1114
+ * // End a direct agent consultation
1115
+ * const consultEndPayload = {
1116
+ * isConsult: true,
1117
+ * taskId: 'task123'
1118
+ * };
1119
+ * task.endConsult(consultEndPayload)
1120
+ * .then(response => console.log('Consultation ended successfully'))
1121
+ * .catch(error => console.error('Failed to end consultation:', error));
1122
+ *
1123
+ * // End a queue consultation
1124
+ * const queueConsultEndPayload = {
1125
+ * isConsult: true,
1126
+ * taskId: 'task123',
1127
+ * queueId: 'queue123'
1128
+ * };
1129
+ * task.endConsult(queueConsultEndPayload)
1130
+ * .then(response => console.log('Queue consultation ended'))
1131
+ * .catch(error => console.error('Failed to end queue consultation:', error));
1132
+ * ```
1133
+ */
1134
+ public async endConsult(consultEndPayload: ConsultEndPayload): Promise<TaskResponse> {
1135
+ try {
1136
+ LoggerProxy.info(`Ending consult`, {
1137
+ module: TASK_FILE,
1138
+ method: METHODS.END_CONSULT,
1139
+ interactionId: this.data.interactionId,
1140
+ });
1141
+
1142
+ this.metricsManager.timeEvent([
1143
+ METRIC_EVENT_NAMES.TASK_CONSULT_END_SUCCESS,
1144
+ METRIC_EVENT_NAMES.TASK_CONSULT_END_FAILED,
1145
+ ]);
1146
+
1147
+ const result = await this.contact.consultEnd({
1148
+ interactionId: this.data.interactionId,
1149
+ data: consultEndPayload,
1150
+ });
1151
+
1152
+ this.metricsManager.trackEvent(
1153
+ METRIC_EVENT_NAMES.TASK_CONSULT_END_SUCCESS,
1154
+ {
1155
+ taskId: this.data.interactionId,
1156
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1157
+ },
1158
+ ['operational', 'behavioral', 'business']
1159
+ );
1160
+
1161
+ LoggerProxy.log(`Consult ended successfully`, {
1162
+ module: TASK_FILE,
1163
+ method: METHODS.END_CONSULT,
1164
+ trackingId: result.trackingId,
1165
+ interactionId: this.data.interactionId,
1166
+ });
1167
+
1168
+ return result;
1169
+ } catch (error) {
1170
+ const {error: detailedError} = getErrorDetails(error, METHODS.END_CONSULT, TASK_FILE);
1171
+ this.metricsManager.trackEvent(
1172
+ METRIC_EVENT_NAMES.TASK_CONSULT_END_FAILED,
1173
+ {
1174
+ taskId: this.data.interactionId,
1175
+ error: error.toString(),
1176
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1177
+ },
1178
+ ['operational', 'behavioral', 'business']
1179
+ );
1180
+ throw detailedError;
1181
+ }
1182
+ }
1183
+
1184
+ /**
1185
+ * Transfer the task to an agent directly or to a queue.
1186
+ * This is a blind transfer that immediately redirects the task to the specified destination.
1187
+ *
1188
+ * @param transferPayload - Transfer configuration containing:
1189
+ * - to: ID of the agent or queue to transfer to
1190
+ * - destinationType: Type of destination (AGENT, QUEUE, etc.)
1191
+ * @returns Promise<TaskResponse> - Resolves when transfer is completed
1192
+ * @throws Error if transfer fails or invalid parameters provided
1193
+ * @example
1194
+ * ```typescript
1195
+ * // Transfer to a queue
1196
+ * const queueTransferPayload = {
1197
+ * to: 'salesQueue123',
1198
+ * destinationType: DESTINATION_TYPE.QUEUE
1199
+ * };
1200
+ * task.transfer(queueTransferPayload)
1201
+ * .then(response => console.log('Task transferred to queue successfully'))
1202
+ * .catch(error => console.error('Failed to transfer to queue:', error));
1203
+ *
1204
+ * // Transfer to an agent
1205
+ * const agentTransferPayload = {
1206
+ * to: 'agentId123',
1207
+ * destinationType: DESTINATION_TYPE.AGENT
1208
+ * };
1209
+ * task.transfer(agentTransferPayload)
1210
+ * .then(response => console.log('Task transferred to agent successfully'))
1211
+ * .catch(error => console.error('Failed to transfer to agent:', error));
1212
+ * ```
1213
+ */
1214
+ public async transfer(transferPayload: TransferPayLoad): Promise<TaskResponse> {
1215
+ try {
1216
+ LoggerProxy.info(`Transferring task to ${transferPayload.to}`, {
1217
+ module: TASK_FILE,
1218
+ method: METHODS.TRANSFER,
1219
+ interactionId: this.data.interactionId,
1220
+ });
1221
+
1222
+ this.metricsManager.timeEvent([
1223
+ METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
1224
+ METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
1225
+ ]);
1226
+
1227
+ let result: TaskResponse;
1228
+ if (transferPayload.destinationType === DESTINATION_TYPE.QUEUE) {
1229
+ result = await this.contact.vteamTransfer({
1230
+ interactionId: this.data.interactionId,
1231
+ data: transferPayload,
1232
+ });
1233
+ } else {
1234
+ result = await this.contact.blindTransfer({
1235
+ interactionId: this.data.interactionId,
1236
+ data: transferPayload,
1237
+ });
1238
+ }
1239
+
1240
+ this.metricsManager.trackEvent(
1241
+ METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
1242
+ {
1243
+ taskId: this.data.interactionId,
1244
+ destination: transferPayload.to,
1245
+ destinationType: transferPayload.destinationType,
1246
+ isConsultTransfer: false,
1247
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1248
+ },
1249
+ ['operational', 'behavioral', 'business']
1250
+ );
1251
+
1252
+ LoggerProxy.log(`Task transferred successfully to ${transferPayload.to}`, {
1253
+ module: TASK_FILE,
1254
+ method: METHODS.TRANSFER,
1255
+ trackingId: result.trackingId,
1256
+ interactionId: this.data.interactionId,
1257
+ });
1258
+
1259
+ return result;
1260
+ } catch (error) {
1261
+ const {error: detailedError} = getErrorDetails(error, METHODS.TRANSFER, TASK_FILE);
1262
+ this.metricsManager.trackEvent(
1263
+ METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
1264
+ {
1265
+ taskId: this.data.interactionId,
1266
+ destination: transferPayload.to,
1267
+ destinationType: transferPayload.destinationType,
1268
+ isConsultTransfer: false,
1269
+ error: error.toString(),
1270
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1271
+ },
1272
+ ['operational', 'behavioral', 'business']
1273
+ );
1274
+ throw detailedError;
1275
+ }
1276
+ }
1277
+
1278
+ /**
1279
+ * Transfer the task to the party that was consulted.
1280
+ * This completes a consultative transfer where the agent first consulted with the target
1281
+ * before transferring the task. For queue consultations, the transfer is automatically
1282
+ * directed to the agent who accepted the consultation.
1283
+ *
1284
+ * @param consultTransferPayload - Configuration for the consultation transfer containing:
1285
+ * - to: ID of the agent or queue to transfer to
1286
+ * - destinationType: Type of destination (AGENT, QUEUE, etc. from CONSULT_TRANSFER_DESTINATION_TYPE)
1287
+ * @returns Promise<TaskResponse> - Resolves when consultation transfer is completed
1288
+ * @throws Error if transfer fails, no agent has accepted a queue consultation, or other validation errors
1289
+ * @example
1290
+ * ```typescript
1291
+ * // Complete consultation transfer to an agent
1292
+ * const agentConsultTransfer = {
1293
+ * to: 'agentId123',
1294
+ * destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT
1295
+ * };
1296
+ * task.consultTransfer(agentConsultTransfer)
1297
+ * .then(response => console.log('Consultation transfer to agent completed'))
1298
+ * .catch(error => console.error('Failed to complete agent consultation transfer:', error));
1299
+ *
1300
+ * // Complete consultation transfer to a queue agent
1301
+ * const queueConsultTransfer = {
1302
+ * to: 'queue123',
1303
+ * destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.QUEUE
1304
+ * };
1305
+ * task.consultTransfer(queueConsultTransfer)
1306
+ * .then(response => console.log('Consultation transfer to queue agent completed'))
1307
+ * .catch(error => console.error('Failed to complete queue consultation transfer:', error));
1308
+ * ```
1309
+ */
1310
+ public async consultTransfer(
1311
+ consultTransferPayload: ConsultTransferPayLoad
1312
+ ): Promise<TaskResponse> {
1313
+ try {
1314
+ LoggerProxy.info(`Initiating consult transfer to ${consultTransferPayload.to}`, {
1315
+ module: TASK_FILE,
1316
+ method: METHODS.CONSULT_TRANSFER,
1317
+ interactionId: this.data.interactionId,
1318
+ });
1319
+
1320
+ // For queue destinations, use the destAgentId from task data
1321
+ if (consultTransferPayload.destinationType === CONSULT_TRANSFER_DESTINATION_TYPE.QUEUE) {
1322
+ if (!this.data.destAgentId) {
1323
+ throw new Error('No agent has accepted this queue consult yet');
1324
+ }
1325
+
1326
+ // Override the destination with the agent who accepted the queue consult
1327
+ consultTransferPayload = {
1328
+ to: this.data.destAgentId,
1329
+ destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
1330
+ };
1331
+ }
1332
+
1333
+ const result = await this.contact.consultTransfer({
1334
+ interactionId: this.data.interactionId,
1335
+ data: consultTransferPayload,
1336
+ });
1337
+
1338
+ this.metricsManager.trackEvent(
1339
+ METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
1340
+ {
1341
+ taskId: this.data.interactionId,
1342
+ destination: consultTransferPayload.to,
1343
+ destinationType: consultTransferPayload.destinationType,
1344
+ isConsultTransfer: true,
1345
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1346
+ },
1347
+ ['operational', 'behavioral', 'business']
1348
+ );
1349
+
1350
+ LoggerProxy.log(`Consult transfer completed successfully to ${consultTransferPayload.to}`, {
1351
+ module: TASK_FILE,
1352
+ method: METHODS.CONSULT_TRANSFER,
1353
+ trackingId: result.trackingId,
1354
+ interactionId: this.data.interactionId,
1355
+ });
1356
+
1357
+ return result;
1358
+ } catch (error) {
1359
+ const {error: detailedError} = getErrorDetails(error, METHODS.CONSULT_TRANSFER, TASK_FILE);
1360
+ this.metricsManager.trackEvent(
1361
+ METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
1362
+ {
1363
+ taskId: this.data.interactionId,
1364
+ destination: consultTransferPayload.to,
1365
+ destinationType: consultTransferPayload.destinationType,
1366
+ isConsultTransfer: true,
1367
+ error: error.toString(),
1368
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1369
+ },
1370
+ ['operational', 'behavioral', 'business']
1371
+ );
1372
+ throw detailedError;
1373
+ }
1374
+ }
1375
+ }