@webex/contact-center 3.10.0-next.3 → 3.10.0-next.31
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.
- package/dist/cc.js +13 -1
- package/dist/cc.js.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/logger-proxy.js.map +1 -1
- package/dist/metrics/MetricsManager.js +2 -1
- package/dist/metrics/MetricsManager.js.map +1 -1
- package/dist/metrics/behavioral-events.js +12 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +4 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AddressBook.js +2 -3
- package/dist/services/AddressBook.js.map +1 -1
- package/dist/services/EntryPoint.js +2 -3
- package/dist/services/EntryPoint.js.map +1 -1
- package/dist/services/Queue.js +2 -3
- package/dist/services/Queue.js.map +1 -1
- package/dist/services/WebCallingService.js +1 -1
- package/dist/services/WebCallingService.js.map +1 -1
- package/dist/services/agent/index.js +1 -2
- package/dist/services/agent/index.js.map +1 -1
- package/dist/services/agent/types.js +10 -0
- package/dist/services/agent/types.js.map +1 -1
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/index.js +1 -1
- package/dist/services/config/index.js.map +1 -1
- package/dist/services/config/types.js +2 -2
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/constants.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/GlobalTypes.js.map +1 -1
- package/dist/services/core/Utils.js +92 -74
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/WebexRequest.js +1 -2
- package/dist/services/core/WebexRequest.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +2 -3
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/constants.js +17 -1
- package/dist/services/core/constants.js.map +1 -1
- package/dist/services/core/types.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +1 -2
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/connection-service.js +1 -1
- package/dist/services/core/websocket/connection-service.js.map +1 -1
- package/dist/services/core/websocket/keepalive.worker.js.map +1 -1
- package/dist/services/core/websocket/types.js.map +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/AutoWrapup.js +1 -1
- package/dist/services/task/AutoWrapup.js.map +1 -1
- package/dist/services/task/TaskManager.js +175 -62
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +122 -5
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +3 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/contact.js +0 -2
- package/dist/services/task/contact.js.map +1 -1
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/index.js +50 -59
- package/dist/services/task/index.js.map +1 -1
- package/dist/services/task/types.js +377 -4
- package/dist/services/task/types.js.map +1 -1
- package/dist/types/cc.d.ts +6 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/metrics/constants.d.ts +4 -0
- package/dist/types/services/config/types.d.ts +4 -4
- package/dist/types/services/core/Utils.d.ts +32 -17
- package/dist/types/services/core/constants.d.ts +14 -0
- package/dist/types/services/task/TaskUtils.d.ts +59 -3
- package/dist/types/services/task/constants.d.ts +2 -0
- package/dist/types/services/task/types.d.ts +57 -13
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/PageCache.js +1 -1
- package/dist/utils/PageCache.js.map +1 -1
- package/dist/webex-config.js.map +1 -1
- package/dist/webex.js +2 -2
- package/dist/webex.js.map +1 -1
- package/package.json +9 -9
- package/src/cc.ts +12 -0
- package/src/index.ts +1 -0
- package/src/metrics/behavioral-events.ts +12 -0
- package/src/metrics/constants.ts +4 -0
- package/src/services/config/types.ts +2 -2
- package/src/services/core/Utils.ts +101 -85
- package/src/services/core/constants.ts +16 -0
- package/src/services/task/TaskManager.ts +201 -41
- package/src/services/task/TaskUtils.ts +145 -5
- package/src/services/task/constants.ts +2 -0
- package/src/services/task/index.ts +54 -89
- package/src/services/task/types.ts +60 -13
- package/test/unit/spec/cc.ts +1 -0
- package/test/unit/spec/metrics/behavioral-events.ts +14 -0
- package/test/unit/spec/services/core/Utils.ts +262 -31
- package/test/unit/spec/services/task/TaskManager.ts +602 -5
- package/test/unit/spec/services/task/TaskUtils.ts +311 -9
- package/test/unit/spec/services/task/index.ts +283 -86
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
getIsConferenceInProgress,
|
|
18
18
|
isParticipantInMainInteraction,
|
|
19
19
|
isPrimary,
|
|
20
|
+
isSecondaryEpDnAgent,
|
|
21
|
+
shouldAutoAnswerTask,
|
|
20
22
|
} from './TaskUtils';
|
|
21
23
|
|
|
22
24
|
/** @internal */
|
|
@@ -35,6 +37,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
35
37
|
private static taskManager;
|
|
36
38
|
private wrapupData: WrapupData;
|
|
37
39
|
private agentId: string;
|
|
40
|
+
private webRtcEnabled: boolean;
|
|
38
41
|
/**
|
|
39
42
|
* @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises
|
|
40
43
|
* @param webCallingService - Webrtc Service Layer
|
|
@@ -72,6 +75,10 @@ export default class TaskManager extends EventEmitter {
|
|
|
72
75
|
return this.agentId;
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
public setWebRtcEnabled(webRtcEnabled: boolean) {
|
|
79
|
+
this.webRtcEnabled = webRtcEnabled;
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
private handleIncomingWebCall = (call: ICall) => {
|
|
76
83
|
const currentTask = Object.values(this.taskCollection).find(
|
|
77
84
|
(task) => task.data.interaction.mediaType === 'telephony'
|
|
@@ -129,10 +136,13 @@ export default class TaskManager extends EventEmitter {
|
|
|
129
136
|
interactionId: payload.data.interactionId,
|
|
130
137
|
});
|
|
131
138
|
|
|
132
|
-
//
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
// Check if auto-answer should happen for this task
|
|
140
|
+
const shouldAutoAnswer = shouldAutoAnswerTask(
|
|
141
|
+
payload.data,
|
|
142
|
+
this.agentId,
|
|
143
|
+
this.webCallingService.loginOption,
|
|
144
|
+
this.webRtcEnabled
|
|
145
|
+
);
|
|
136
146
|
|
|
137
147
|
task = new Task(
|
|
138
148
|
this.contact,
|
|
@@ -141,7 +151,8 @@ export default class TaskManager extends EventEmitter {
|
|
|
141
151
|
...payload.data,
|
|
142
152
|
wrapUpRequired:
|
|
143
153
|
payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false,
|
|
144
|
-
isConferenceInProgress: getIsConferenceInProgress(
|
|
154
|
+
isConferenceInProgress: getIsConferenceInProgress(payload.data),
|
|
155
|
+
isAutoAnswering: shouldAutoAnswer, // Set flag before emitting
|
|
145
156
|
},
|
|
146
157
|
this.wrapupData,
|
|
147
158
|
this.agentId
|
|
@@ -173,13 +184,22 @@ export default class TaskManager extends EventEmitter {
|
|
|
173
184
|
}
|
|
174
185
|
break;
|
|
175
186
|
|
|
176
|
-
case CC_EVENTS.AGENT_CONTACT_RESERVED:
|
|
187
|
+
case CC_EVENTS.AGENT_CONTACT_RESERVED: {
|
|
188
|
+
// Check if auto-answer should happen for this task
|
|
189
|
+
const shouldAutoAnswerReserved = shouldAutoAnswerTask(
|
|
190
|
+
payload.data,
|
|
191
|
+
this.agentId,
|
|
192
|
+
this.webCallingService.loginOption,
|
|
193
|
+
this.webRtcEnabled
|
|
194
|
+
);
|
|
195
|
+
|
|
177
196
|
task = new Task(
|
|
178
197
|
this.contact,
|
|
179
198
|
this.webCallingService,
|
|
180
199
|
{
|
|
181
200
|
...payload.data,
|
|
182
201
|
isConsulted: false,
|
|
202
|
+
isAutoAnswering: shouldAutoAnswerReserved, // Set flag before emitting
|
|
183
203
|
},
|
|
184
204
|
this.wrapupData,
|
|
185
205
|
this.agentId
|
|
@@ -194,6 +214,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
194
214
|
this.emit(TASK_EVENTS.TASK_INCOMING, task);
|
|
195
215
|
}
|
|
196
216
|
break;
|
|
217
|
+
}
|
|
197
218
|
case CC_EVENTS.AGENT_OFFER_CONTACT:
|
|
198
219
|
// We don't have to emit any event here since this will be result of promise.
|
|
199
220
|
task = this.updateTaskData(task, payload.data);
|
|
@@ -203,17 +224,29 @@ export default class TaskManager extends EventEmitter {
|
|
|
203
224
|
interactionId: payload.data?.interactionId,
|
|
204
225
|
});
|
|
205
226
|
this.emit(TASK_EVENTS.TASK_OFFER_CONTACT, task);
|
|
227
|
+
|
|
228
|
+
// Handle auto-answer for offer contact
|
|
229
|
+
this.handleAutoAnswer(task);
|
|
206
230
|
break;
|
|
207
231
|
case CC_EVENTS.AGENT_OUTBOUND_FAILED:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this.
|
|
232
|
+
if (task) {
|
|
233
|
+
task = this.updateTaskData(task, payload.data);
|
|
234
|
+
this.metricsManager.trackEvent(
|
|
235
|
+
METRIC_EVENT_NAMES.TASK_OUTDIAL_FAILED,
|
|
236
|
+
{
|
|
237
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponse(payload.data),
|
|
238
|
+
taskId: payload.data.interactionId,
|
|
239
|
+
reason: payload.data.reasonCode || payload.data.reason,
|
|
240
|
+
},
|
|
241
|
+
['behavioral', 'operational']
|
|
242
|
+
);
|
|
243
|
+
LoggerProxy.log(`Agent outbound failed for task`, {
|
|
244
|
+
module: TASK_MANAGER_FILE,
|
|
245
|
+
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
246
|
+
interactionId: payload.data.interactionId,
|
|
247
|
+
});
|
|
248
|
+
task.emit(TASK_EVENTS.TASK_OUTDIAL_FAILED, payload.data.reason ?? 'UNKNOWN_REASON');
|
|
211
249
|
}
|
|
212
|
-
LoggerProxy.log(`Agent outbound failed for task`, {
|
|
213
|
-
module: TASK_MANAGER_FILE,
|
|
214
|
-
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
215
|
-
interactionId: payload.data?.interactionId,
|
|
216
|
-
});
|
|
217
250
|
break;
|
|
218
251
|
case CC_EVENTS.AGENT_CONTACT_ASSIGNED:
|
|
219
252
|
task = this.updateTaskData(task, payload.data);
|
|
@@ -252,13 +285,23 @@ export default class TaskManager extends EventEmitter {
|
|
|
252
285
|
break;
|
|
253
286
|
}
|
|
254
287
|
case CC_EVENTS.CONTACT_ENDED:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
288
|
+
// Update task data
|
|
289
|
+
if (task) {
|
|
290
|
+
task = this.updateTaskData(task, {
|
|
291
|
+
...payload.data,
|
|
292
|
+
wrapUpRequired:
|
|
293
|
+
payload.data.interaction.state !== 'new' &&
|
|
294
|
+
!isSecondaryEpDnAgent(payload.data.interaction),
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Handle cleanup based on whether task should be deleted
|
|
298
|
+
this.handleTaskCleanup(task);
|
|
261
299
|
|
|
300
|
+
task?.emit(TASK_EVENTS.TASK_END, task);
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
case CC_EVENTS.CONTACT_MERGED:
|
|
304
|
+
task = this.handleContactMerged(task, payload.data);
|
|
262
305
|
break;
|
|
263
306
|
case CC_EVENTS.AGENT_CONTACT_HELD:
|
|
264
307
|
// As soon as the main interaction is held, we need to emit TASK_HOLD
|
|
@@ -296,6 +339,9 @@ export default class TaskManager extends EventEmitter {
|
|
|
296
339
|
isConsulted: true, // This ensures that the task is marked as us being requested for a consult
|
|
297
340
|
});
|
|
298
341
|
task.emit(TASK_EVENTS.TASK_OFFER_CONSULT, task);
|
|
342
|
+
|
|
343
|
+
// Handle auto-answer for consult offer
|
|
344
|
+
this.handleAutoAnswer(task);
|
|
299
345
|
break;
|
|
300
346
|
case CC_EVENTS.AGENT_CONSULTING:
|
|
301
347
|
// Received when agent is in an active consult state
|
|
@@ -380,32 +426,22 @@ export default class TaskManager extends EventEmitter {
|
|
|
380
426
|
} else {
|
|
381
427
|
this.removeTaskFromCollection(task);
|
|
382
428
|
}
|
|
383
|
-
task
|
|
429
|
+
task.emit(TASK_EVENTS.TASK_CONFERENCE_ENDED, task);
|
|
384
430
|
break;
|
|
385
431
|
case CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE: {
|
|
386
|
-
// Participant joined conference - update task state with participant information and emit event
|
|
387
|
-
// Pre-calculate isConferenceInProgress with updated data to avoid double update
|
|
388
|
-
const simulatedTaskForJoin = {
|
|
389
|
-
...task,
|
|
390
|
-
data: {...task.data, ...payload.data},
|
|
391
|
-
};
|
|
392
432
|
task = this.updateTaskData(task, {
|
|
393
433
|
...payload.data,
|
|
394
|
-
isConferenceInProgress: getIsConferenceInProgress(
|
|
434
|
+
isConferenceInProgress: getIsConferenceInProgress(payload.data),
|
|
395
435
|
});
|
|
396
436
|
task.emit(TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
|
|
397
437
|
break;
|
|
398
438
|
}
|
|
399
439
|
case CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE: {
|
|
400
440
|
// Conference ended - update task state and emit event
|
|
401
|
-
|
|
402
|
-
const simulatedTaskForLeft = {
|
|
403
|
-
...task,
|
|
404
|
-
data: {...task.data, ...payload.data},
|
|
405
|
-
};
|
|
441
|
+
|
|
406
442
|
task = this.updateTaskData(task, {
|
|
407
443
|
...payload.data,
|
|
408
|
-
isConferenceInProgress: getIsConferenceInProgress(
|
|
444
|
+
isConferenceInProgress: getIsConferenceInProgress(payload.data),
|
|
409
445
|
});
|
|
410
446
|
if (checkParticipantNotInInteraction(task, this.agentId)) {
|
|
411
447
|
if (
|
|
@@ -441,13 +477,10 @@ export default class TaskManager extends EventEmitter {
|
|
|
441
477
|
task = this.updateTaskData(task, payload.data);
|
|
442
478
|
task.emit(TASK_EVENTS.TASK_CONFERENCE_TRANSFER_FAILED, task);
|
|
443
479
|
break;
|
|
444
|
-
case CC_EVENTS.CONSULTED_PARTICIPANT_MOVING:
|
|
445
|
-
// Participant is being moved/transferred - update task state with movement info
|
|
446
|
-
task = this.updateTaskData(task, payload.data);
|
|
447
|
-
break;
|
|
448
480
|
case CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY:
|
|
449
481
|
// Post-call activity for participant - update task state with activity details
|
|
450
482
|
task = this.updateTaskData(task, payload.data);
|
|
483
|
+
task.emit(TASK_EVENTS.TASK_POST_CALL_ACTIVITY, task);
|
|
451
484
|
break;
|
|
452
485
|
default:
|
|
453
486
|
break;
|
|
@@ -487,6 +520,54 @@ export default class TaskManager extends EventEmitter {
|
|
|
487
520
|
}
|
|
488
521
|
}
|
|
489
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Handles CONTACT_MERGED event logic
|
|
525
|
+
* @param task - The task to process
|
|
526
|
+
* @param taskData - The task data from the event payload
|
|
527
|
+
* @returns Updated or newly created task
|
|
528
|
+
* @private
|
|
529
|
+
*/
|
|
530
|
+
private handleContactMerged(task: ITask, taskData: TaskData): ITask {
|
|
531
|
+
if (taskData.childInteractionId) {
|
|
532
|
+
// remove the child task from collection
|
|
533
|
+
this.removeTaskFromCollection(this.taskCollection[taskData.childInteractionId]);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (this.taskCollection[taskData.interactionId]) {
|
|
537
|
+
LoggerProxy.log(`Got CONTACT_MERGED: Task already exists in collection`, {
|
|
538
|
+
module: TASK_MANAGER_FILE,
|
|
539
|
+
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
540
|
+
interactionId: taskData.interactionId,
|
|
541
|
+
});
|
|
542
|
+
// update the task data
|
|
543
|
+
task = this.updateTaskData(task, taskData);
|
|
544
|
+
} else {
|
|
545
|
+
// Case2 : Task is not present in taskCollection
|
|
546
|
+
LoggerProxy.log(`Got CONTACT_MERGED : Creating new task in taskManager`, {
|
|
547
|
+
module: TASK_MANAGER_FILE,
|
|
548
|
+
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
549
|
+
interactionId: taskData.interactionId,
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
task = new Task(
|
|
553
|
+
this.contact,
|
|
554
|
+
this.webCallingService,
|
|
555
|
+
{
|
|
556
|
+
...taskData,
|
|
557
|
+
wrapUpRequired: taskData.interaction?.participants?.[this.agentId]?.isWrapUp || false,
|
|
558
|
+
isConferenceInProgress: getIsConferenceInProgress(taskData),
|
|
559
|
+
},
|
|
560
|
+
this.wrapupData,
|
|
561
|
+
this.agentId
|
|
562
|
+
);
|
|
563
|
+
this.taskCollection[taskData.interactionId] = task;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
this.emit(TASK_EVENTS.TASK_MERGED, task);
|
|
567
|
+
|
|
568
|
+
return task;
|
|
569
|
+
}
|
|
570
|
+
|
|
490
571
|
private removeTaskFromCollection(task: ITask) {
|
|
491
572
|
if (task?.data?.interactionId) {
|
|
492
573
|
delete this.taskCollection[task.data.interactionId];
|
|
@@ -498,7 +579,79 @@ export default class TaskManager extends EventEmitter {
|
|
|
498
579
|
}
|
|
499
580
|
}
|
|
500
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Handles auto-answer logic for incoming tasks
|
|
584
|
+
* Automatically accepts tasks when isAutoAnswering flag is set
|
|
585
|
+
* The flag is set during task creation based on:
|
|
586
|
+
* 1. WebRTC calls with auto-answer enabled in agent profile
|
|
587
|
+
* 2. Agent-initiated WebRTC outdial calls
|
|
588
|
+
* 3. Agent-initiated digital outbound (Email/SMS) without previous transfers
|
|
589
|
+
*
|
|
590
|
+
* @param task - The task to auto-answer
|
|
591
|
+
* @private
|
|
592
|
+
*/
|
|
593
|
+
private async handleAutoAnswer(task: ITask): Promise<void> {
|
|
594
|
+
if (!task || !task.data || !task.data.isAutoAnswering) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
LoggerProxy.info(`Auto-answering task`, {
|
|
599
|
+
module: TASK_MANAGER_FILE,
|
|
600
|
+
method: 'handleAutoAnswer',
|
|
601
|
+
interactionId: task.data.interactionId,
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
await task.accept();
|
|
606
|
+
LoggerProxy.info(`Task auto-answered successfully`, {
|
|
607
|
+
module: TASK_MANAGER_FILE,
|
|
608
|
+
method: 'handleAutoAnswer',
|
|
609
|
+
interactionId: task.data.interactionId,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// Track successful auto-answer
|
|
613
|
+
this.metricsManager.trackEvent(
|
|
614
|
+
METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_SUCCESS,
|
|
615
|
+
{
|
|
616
|
+
taskId: task.data.interactionId,
|
|
617
|
+
mediaType: task.data.interaction.mediaType,
|
|
618
|
+
isAutoAnswered: true,
|
|
619
|
+
},
|
|
620
|
+
['behavioral', 'operational']
|
|
621
|
+
);
|
|
622
|
+
// Emit task:autoAnswered event for widgets/UI to react
|
|
623
|
+
task.emit(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
|
|
624
|
+
} catch (error) {
|
|
625
|
+
// Reset isAutoAnswering flag on failure
|
|
626
|
+
task.updateTaskData({...task.data, isAutoAnswering: false});
|
|
627
|
+
LoggerProxy.error(`Failed to auto-answer task`, {
|
|
628
|
+
module: TASK_MANAGER_FILE,
|
|
629
|
+
method: 'handleAutoAnswer',
|
|
630
|
+
interactionId: task.data.interactionId,
|
|
631
|
+
error,
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Track auto-answer failure
|
|
635
|
+
this.metricsManager.trackEvent(
|
|
636
|
+
METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_FAILED,
|
|
637
|
+
{
|
|
638
|
+
taskId: task.data.interactionId,
|
|
639
|
+
mediaType: task.data.interaction.mediaType,
|
|
640
|
+
error: error?.message || 'Unknown error',
|
|
641
|
+
isAutoAnswered: false,
|
|
642
|
+
},
|
|
643
|
+
['behavioral', 'operational']
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Handles cleanup of task resources including Desktop/WebRTC call cleanup and task removal
|
|
650
|
+
* @param task - The task to clean up
|
|
651
|
+
* @private
|
|
652
|
+
*/
|
|
501
653
|
private handleTaskCleanup(task: ITask) {
|
|
654
|
+
// Clean up Desktop/WebRTC calling resources for browser-based telephony tasks
|
|
502
655
|
if (
|
|
503
656
|
this.webCallingService.loginOption === LoginOption.BROWSER &&
|
|
504
657
|
task.data.interaction.mediaType === 'telephony'
|
|
@@ -506,9 +659,16 @@ export default class TaskManager extends EventEmitter {
|
|
|
506
659
|
task.unregisterWebCallListeners();
|
|
507
660
|
this.webCallingService.cleanUpCall();
|
|
508
661
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
662
|
+
|
|
663
|
+
const isOutdial = task.data.interaction.outboundType === 'OUTDIAL';
|
|
664
|
+
const isNew = task.data.interaction.state === 'new';
|
|
665
|
+
const needsWrapUp = task.data.agentsPendingWrapUp?.length > 0;
|
|
666
|
+
|
|
667
|
+
// For OUTDIAL: only remove if NOT terminated (user-declined, no wrap-up follows)
|
|
668
|
+
// If terminated, keep task for wrap-up flow (CONTACT_ENDED → AGENT_WRAPUP)
|
|
669
|
+
// For non-OUTDIAL: remove if state is 'new'
|
|
670
|
+
// Always remove if secondary EpDn agent
|
|
671
|
+
if ((isNew && !(isOutdial && needsWrapUp)) || isSecondaryEpDnAgent(task.data.interaction)) {
|
|
512
672
|
this.removeTaskFromCollection(task);
|
|
513
673
|
}
|
|
514
674
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
import {ITask} from './types';
|
|
2
|
+
import {Interaction, ITask, TaskData, MEDIA_CHANNEL} from './types';
|
|
3
|
+
import {OUTDIAL_DIRECTION, OUTDIAL_MEDIA_TYPE, OUTBOUND_TYPE} from '../../constants';
|
|
4
|
+
import {LoginOption} from '../../types';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Determines if the given agent is the primary agent (owner) of the task
|
|
@@ -53,13 +55,13 @@ export const checkParticipantNotInInteraction = (task: ITask, agentId: string):
|
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
57
|
* Determines if a conference is currently in progress based on the number of active agent participants
|
|
56
|
-
* @param
|
|
58
|
+
* @param TaskData - The payLoad data to check for conference status
|
|
57
59
|
* @returns true if there are 2 or more active agent participants in the main call, false otherwise
|
|
58
60
|
*/
|
|
59
|
-
export const getIsConferenceInProgress = (
|
|
60
|
-
const mediaMainCall =
|
|
61
|
+
export const getIsConferenceInProgress = (data: TaskData): boolean => {
|
|
62
|
+
const mediaMainCall = data.interaction.media?.[data?.interactionId];
|
|
61
63
|
const participantsInMainCall = new Set(mediaMainCall?.participants);
|
|
62
|
-
const participants =
|
|
64
|
+
const participants = data.interaction.participants;
|
|
63
65
|
|
|
64
66
|
const agentParticipants = new Set();
|
|
65
67
|
if (participantsInMainCall.size > 0) {
|
|
@@ -79,3 +81,141 @@ export const getIsConferenceInProgress = (task: ITask): boolean => {
|
|
|
79
81
|
|
|
80
82
|
return agentParticipants.size >= 2;
|
|
81
83
|
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Checks if the current agent is a secondary agent in a consultation scenario.
|
|
87
|
+
* Secondary agents are those who were consulted (not the original call owner).
|
|
88
|
+
* @param task - The task object containing interaction details
|
|
89
|
+
* @returns true if this is a secondary agent (consulted party), false otherwise
|
|
90
|
+
*/
|
|
91
|
+
export const isSecondaryAgent = (interaction: Interaction): boolean => {
|
|
92
|
+
if (!interaction.callProcessingDetails) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
interaction.callProcessingDetails.relationshipType === 'consult' &&
|
|
98
|
+
!!interaction.callProcessingDetails.parentInteractionId &&
|
|
99
|
+
interaction.callProcessingDetails.parentInteractionId !== interaction.interactionId
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Checks if the current agent is a secondary EP-DN (Entry Point Dial Number) agent.
|
|
105
|
+
* This is specifically for telephony consultations to external numbers/entry points.
|
|
106
|
+
* @param task - The task object containing interaction details
|
|
107
|
+
* @returns true if this is a secondary EP-DN agent in telephony consultation, false otherwise
|
|
108
|
+
*/
|
|
109
|
+
export const isSecondaryEpDnAgent = (interaction: Interaction): boolean => {
|
|
110
|
+
if (!interaction) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return interaction.mediaType === 'telephony' && isSecondaryAgent(interaction);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Checks if auto-answer is enabled for the agent participant
|
|
119
|
+
* @param interaction - The interaction object
|
|
120
|
+
* @param agentId - Current agent ID
|
|
121
|
+
* @returns true if auto-answer is enabled, false otherwise
|
|
122
|
+
*/
|
|
123
|
+
export const isAutoAnswerEnabled = (interaction: Interaction, agentId: string): boolean => {
|
|
124
|
+
return interaction.participants?.[agentId]?.autoAnswerEnabled === true;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Checks if the interaction is a WebRTC call eligible for auto-answer
|
|
129
|
+
* @param interaction - The interaction object
|
|
130
|
+
* @param loginOption - The agent's login option (BROWSER, AGENT_DN, etc.)
|
|
131
|
+
* @param webRtcEnabled - Whether WebRTC is enabled for the agent
|
|
132
|
+
* @returns true if this is a WebRTC call, false otherwise
|
|
133
|
+
*/
|
|
134
|
+
export const isWebRTCCall = (
|
|
135
|
+
interaction: Interaction,
|
|
136
|
+
loginOption: string,
|
|
137
|
+
webRtcEnabled: boolean
|
|
138
|
+
): boolean => {
|
|
139
|
+
return (
|
|
140
|
+
webRtcEnabled &&
|
|
141
|
+
loginOption === LoginOption.BROWSER &&
|
|
142
|
+
interaction.mediaType === OUTDIAL_MEDIA_TYPE
|
|
143
|
+
);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Checks if the interaction is a digital outbound (Email/SMS)
|
|
148
|
+
* @param interaction - The interaction object
|
|
149
|
+
* @returns true if this is a digital outbound, false otherwise
|
|
150
|
+
*/
|
|
151
|
+
export const isDigitalOutbound = (interaction: Interaction): boolean => {
|
|
152
|
+
return (
|
|
153
|
+
interaction.contactDirection?.type === OUTDIAL_DIRECTION &&
|
|
154
|
+
interaction.outboundType === OUTBOUND_TYPE &&
|
|
155
|
+
(interaction.mediaChannel === MEDIA_CHANNEL.EMAIL ||
|
|
156
|
+
interaction.mediaChannel === MEDIA_CHANNEL.SMS)
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Checks if the outdial was initiated by the current agent
|
|
162
|
+
* @param interaction - The interaction object
|
|
163
|
+
* @param agentId - Current agent ID
|
|
164
|
+
* @returns true if agent initiated the outdial, false otherwise
|
|
165
|
+
*/
|
|
166
|
+
export const hasAgentInitiatedOutdial = (interaction: Interaction, agentId: string): boolean => {
|
|
167
|
+
return (
|
|
168
|
+
interaction.contactDirection?.type === OUTDIAL_DIRECTION &&
|
|
169
|
+
interaction.outboundType === OUTBOUND_TYPE &&
|
|
170
|
+
interaction.callProcessingDetails?.outdialAgentId === agentId &&
|
|
171
|
+
interaction.owner === agentId &&
|
|
172
|
+
!interaction.callProcessingDetails?.BLIND_TRANSFER_IN_PROGRESS
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Determines if a task should be auto-answered based on interaction data
|
|
178
|
+
* Auto-answer logic handles:
|
|
179
|
+
* 1. WebRTC calls with auto-answer enabled in agent profile
|
|
180
|
+
* 2. Agent-initiated WebRTC outdial calls
|
|
181
|
+
* 3. Agent-initiated digital outbound (Email/SMS) without previous transfers
|
|
182
|
+
*
|
|
183
|
+
* @param taskData - The task data
|
|
184
|
+
* @param agentId - Current agent ID
|
|
185
|
+
* @param loginOption - Agent's login option
|
|
186
|
+
* @param webRtcEnabled - Whether WebRTC is enabled for the agent
|
|
187
|
+
* @returns true if task should be auto-answered, false otherwise
|
|
188
|
+
*/
|
|
189
|
+
export const shouldAutoAnswerTask = (
|
|
190
|
+
taskData: TaskData,
|
|
191
|
+
agentId: string,
|
|
192
|
+
loginOption: string,
|
|
193
|
+
webRtcEnabled: boolean
|
|
194
|
+
): boolean => {
|
|
195
|
+
const {interaction} = taskData;
|
|
196
|
+
|
|
197
|
+
if (!interaction || !agentId) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check if auto-answer is enabled for this agent
|
|
202
|
+
const autoAnswerEnabled = isAutoAnswerEnabled(interaction, agentId);
|
|
203
|
+
|
|
204
|
+
// Check if this is an agent-initiated outdial
|
|
205
|
+
const agentInitiatedOutdial = hasAgentInitiatedOutdial(interaction, agentId);
|
|
206
|
+
|
|
207
|
+
// WebRTC telephony calls
|
|
208
|
+
if (isWebRTCCall(interaction, loginOption, webRtcEnabled)) {
|
|
209
|
+
return autoAnswerEnabled || agentInitiatedOutdial;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Digital outbound (Email/SMS)
|
|
213
|
+
if (isDigitalOutbound(interaction) && agentInitiatedOutdial) {
|
|
214
|
+
// Don't auto-answer if task has been transferred (has previous vteams)
|
|
215
|
+
const hasPreviousVteams = interaction.previousVTeams && interaction.previousVTeams.length > 0;
|
|
216
|
+
|
|
217
|
+
return !hasPreviousVteams;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return false;
|
|
221
|
+
};
|
|
@@ -34,6 +34,8 @@ export const PRESERVED_TASK_DATA_FIELDS = {
|
|
|
34
34
|
WRAP_UP_REQUIRED: 'wrapUpRequired',
|
|
35
35
|
/** Indicates if a conference is currently in progress (2+ active agents) */
|
|
36
36
|
IS_CONFERENCE_IN_PROGRESS: 'isConferenceInProgress',
|
|
37
|
+
/** Indicates if auto-answer is in progress for this task */
|
|
38
|
+
IS_AUTO_ANSWERING: 'isAutoAnswering',
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
/**
|