@webex/contact-center 3.10.0-next.2 → 3.10.0-next.21
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 +177 -56
- 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 +46 -40
- 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 +8 -8
- 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 +204 -36
- package/src/services/task/TaskUtils.ts +145 -5
- package/src/services/task/constants.ts +2 -0
- package/src/services/task/index.ts +50 -63
- 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 +748 -5
- package/test/unit/spec/services/task/TaskUtils.ts +311 -9
- package/test/unit/spec/services/task/index.ts +323 -68
- 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'
|
|
@@ -128,6 +135,15 @@ export default class TaskManager extends EventEmitter {
|
|
|
128
135
|
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
129
136
|
interactionId: payload.data.interactionId,
|
|
130
137
|
});
|
|
138
|
+
|
|
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
|
+
);
|
|
146
|
+
|
|
131
147
|
task = new Task(
|
|
132
148
|
this.contact,
|
|
133
149
|
this.webCallingService,
|
|
@@ -135,6 +151,8 @@ export default class TaskManager extends EventEmitter {
|
|
|
135
151
|
...payload.data,
|
|
136
152
|
wrapUpRequired:
|
|
137
153
|
payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false,
|
|
154
|
+
isConferenceInProgress: getIsConferenceInProgress(payload.data),
|
|
155
|
+
isAutoAnswering: shouldAutoAnswer, // Set flag before emitting
|
|
138
156
|
},
|
|
139
157
|
this.wrapupData,
|
|
140
158
|
this.agentId
|
|
@@ -165,13 +183,23 @@ export default class TaskManager extends EventEmitter {
|
|
|
165
183
|
}
|
|
166
184
|
}
|
|
167
185
|
break;
|
|
168
|
-
|
|
186
|
+
|
|
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
|
+
|
|
169
196
|
task = new Task(
|
|
170
197
|
this.contact,
|
|
171
198
|
this.webCallingService,
|
|
172
199
|
{
|
|
173
200
|
...payload.data,
|
|
174
201
|
isConsulted: false,
|
|
202
|
+
isAutoAnswering: shouldAutoAnswerReserved, // Set flag before emitting
|
|
175
203
|
},
|
|
176
204
|
this.wrapupData,
|
|
177
205
|
this.agentId
|
|
@@ -186,6 +214,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
186
214
|
this.emit(TASK_EVENTS.TASK_INCOMING, task);
|
|
187
215
|
}
|
|
188
216
|
break;
|
|
217
|
+
}
|
|
189
218
|
case CC_EVENTS.AGENT_OFFER_CONTACT:
|
|
190
219
|
// We don't have to emit any event here since this will be result of promise.
|
|
191
220
|
task = this.updateTaskData(task, payload.data);
|
|
@@ -195,17 +224,29 @@ export default class TaskManager extends EventEmitter {
|
|
|
195
224
|
interactionId: payload.data?.interactionId,
|
|
196
225
|
});
|
|
197
226
|
this.emit(TASK_EVENTS.TASK_OFFER_CONTACT, task);
|
|
227
|
+
|
|
228
|
+
// Handle auto-answer for offer contact
|
|
229
|
+
this.handleAutoAnswer(task);
|
|
198
230
|
break;
|
|
199
231
|
case CC_EVENTS.AGENT_OUTBOUND_FAILED:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
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');
|
|
203
249
|
}
|
|
204
|
-
LoggerProxy.log(`Agent outbound failed for task`, {
|
|
205
|
-
module: TASK_MANAGER_FILE,
|
|
206
|
-
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
207
|
-
interactionId: payload.data?.interactionId,
|
|
208
|
-
});
|
|
209
250
|
break;
|
|
210
251
|
case CC_EVENTS.AGENT_CONTACT_ASSIGNED:
|
|
211
252
|
task = this.updateTaskData(task, payload.data);
|
|
@@ -244,13 +285,23 @@ export default class TaskManager extends EventEmitter {
|
|
|
244
285
|
break;
|
|
245
286
|
}
|
|
246
287
|
case CC_EVENTS.CONTACT_ENDED:
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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);
|
|
253
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);
|
|
254
305
|
break;
|
|
255
306
|
case CC_EVENTS.AGENT_CONTACT_HELD:
|
|
256
307
|
// As soon as the main interaction is held, we need to emit TASK_HOLD
|
|
@@ -288,6 +339,9 @@ export default class TaskManager extends EventEmitter {
|
|
|
288
339
|
isConsulted: true, // This ensures that the task is marked as us being requested for a consult
|
|
289
340
|
});
|
|
290
341
|
task.emit(TASK_EVENTS.TASK_OFFER_CONSULT, task);
|
|
342
|
+
|
|
343
|
+
// Handle auto-answer for consult offer
|
|
344
|
+
this.handleAutoAnswer(task);
|
|
291
345
|
break;
|
|
292
346
|
case CC_EVENTS.AGENT_CONSULTING:
|
|
293
347
|
// Received when agent is in an active consult state
|
|
@@ -372,32 +426,22 @@ export default class TaskManager extends EventEmitter {
|
|
|
372
426
|
} else {
|
|
373
427
|
this.removeTaskFromCollection(task);
|
|
374
428
|
}
|
|
375
|
-
task
|
|
429
|
+
task.emit(TASK_EVENTS.TASK_CONFERENCE_ENDED, task);
|
|
376
430
|
break;
|
|
377
431
|
case CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE: {
|
|
378
|
-
// Participant joined conference - update task state with participant information and emit event
|
|
379
|
-
// Pre-calculate isConferenceInProgress with updated data to avoid double update
|
|
380
|
-
const simulatedTaskForJoin = {
|
|
381
|
-
...task,
|
|
382
|
-
data: {...task.data, ...payload.data},
|
|
383
|
-
};
|
|
384
432
|
task = this.updateTaskData(task, {
|
|
385
433
|
...payload.data,
|
|
386
|
-
isConferenceInProgress: getIsConferenceInProgress(
|
|
434
|
+
isConferenceInProgress: getIsConferenceInProgress(payload.data),
|
|
387
435
|
});
|
|
388
436
|
task.emit(TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
|
|
389
437
|
break;
|
|
390
438
|
}
|
|
391
439
|
case CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE: {
|
|
392
440
|
// Conference ended - update task state and emit event
|
|
393
|
-
|
|
394
|
-
const simulatedTaskForLeft = {
|
|
395
|
-
...task,
|
|
396
|
-
data: {...task.data, ...payload.data},
|
|
397
|
-
};
|
|
441
|
+
|
|
398
442
|
task = this.updateTaskData(task, {
|
|
399
443
|
...payload.data,
|
|
400
|
-
isConferenceInProgress: getIsConferenceInProgress(
|
|
444
|
+
isConferenceInProgress: getIsConferenceInProgress(payload.data),
|
|
401
445
|
});
|
|
402
446
|
if (checkParticipantNotInInteraction(task, this.agentId)) {
|
|
403
447
|
if (
|
|
@@ -433,13 +477,10 @@ export default class TaskManager extends EventEmitter {
|
|
|
433
477
|
task = this.updateTaskData(task, payload.data);
|
|
434
478
|
task.emit(TASK_EVENTS.TASK_CONFERENCE_TRANSFER_FAILED, task);
|
|
435
479
|
break;
|
|
436
|
-
case CC_EVENTS.CONSULTED_PARTICIPANT_MOVING:
|
|
437
|
-
// Participant is being moved/transferred - update task state with movement info
|
|
438
|
-
task = this.updateTaskData(task, payload.data);
|
|
439
|
-
break;
|
|
440
480
|
case CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY:
|
|
441
481
|
// Post-call activity for participant - update task state with activity details
|
|
442
482
|
task = this.updateTaskData(task, payload.data);
|
|
483
|
+
task.emit(TASK_EVENTS.TASK_POST_CALL_ACTIVITY, task);
|
|
443
484
|
break;
|
|
444
485
|
default:
|
|
445
486
|
break;
|
|
@@ -479,6 +520,54 @@ export default class TaskManager extends EventEmitter {
|
|
|
479
520
|
}
|
|
480
521
|
}
|
|
481
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
|
+
|
|
482
571
|
private removeTaskFromCollection(task: ITask) {
|
|
483
572
|
if (task?.data?.interactionId) {
|
|
484
573
|
delete this.taskCollection[task.data.interactionId];
|
|
@@ -490,7 +579,79 @@ export default class TaskManager extends EventEmitter {
|
|
|
490
579
|
}
|
|
491
580
|
}
|
|
492
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
|
+
*/
|
|
493
653
|
private handleTaskCleanup(task: ITask) {
|
|
654
|
+
// Clean up Desktop/WebRTC calling resources for browser-based telephony tasks
|
|
494
655
|
if (
|
|
495
656
|
this.webCallingService.loginOption === LoginOption.BROWSER &&
|
|
496
657
|
task.data.interaction.mediaType === 'telephony'
|
|
@@ -498,9 +659,16 @@ export default class TaskManager extends EventEmitter {
|
|
|
498
659
|
task.unregisterWebCallListeners();
|
|
499
660
|
this.webCallingService.cleanUpCall();
|
|
500
661
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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)) {
|
|
504
672
|
this.removeTaskFromCollection(task);
|
|
505
673
|
}
|
|
506
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
|
/**
|