@webex/contact-center 3.10.0-next.1 → 3.10.0-next.11
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 +11 -0
- package/dist/cc.js.map +1 -1
- package/dist/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/core/Utils.js +90 -71
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/constants.js +17 -1
- package/dist/services/core/constants.js.map +1 -1
- package/dist/services/task/TaskManager.js +62 -29
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +33 -5
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/index.js +45 -39
- package/dist/services/task/index.js.map +1 -1
- package/dist/services/task/types.js +2 -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/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 +17 -3
- package/dist/types/services/task/types.d.ts +25 -13
- package/dist/webex.js +1 -1
- package/package.json +8 -8
- package/src/cc.ts +11 -0
- package/src/index.ts +1 -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 +77 -22
- package/src/services/task/TaskUtils.ts +37 -5
- package/src/services/task/index.ts +50 -63
- package/src/services/task/types.ts +26 -13
- package/test/unit/spec/services/core/Utils.ts +262 -31
- package/test/unit/spec/services/task/TaskManager.ts +370 -1
- package/test/unit/spec/services/task/TaskUtils.ts +6 -6
- 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
|
@@ -6,11 +6,10 @@ import WebexRequest from './WebexRequest';
|
|
|
6
6
|
import {
|
|
7
7
|
TaskData,
|
|
8
8
|
ConsultTransferPayLoad,
|
|
9
|
-
ConsultConferenceData,
|
|
10
|
-
consultConferencePayloadData,
|
|
11
9
|
CONSULT_TRANSFER_DESTINATION_TYPE,
|
|
12
10
|
Interaction,
|
|
13
11
|
} from '../task/types';
|
|
12
|
+
import {PARTICIPANT_TYPES, STATE_CONSULT} from './constants';
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Extracts common error details from a Webex request payload.
|
|
@@ -218,59 +217,118 @@ export const createErrDetailsObject = (errObj: WebexRequestPayload) => {
|
|
|
218
217
|
};
|
|
219
218
|
|
|
220
219
|
/**
|
|
221
|
-
*
|
|
220
|
+
* Gets the consulted agent ID from the media object by finding the agent
|
|
221
|
+
* in the consult media participants (excluding the current agent).
|
|
222
222
|
*
|
|
223
|
-
*
|
|
224
|
-
* -
|
|
225
|
-
*
|
|
226
|
-
|
|
227
|
-
|
|
223
|
+
* @param media - The media object from the interaction
|
|
224
|
+
* @param agentId - The current agent's ID to exclude from the search
|
|
225
|
+
* @returns The consulted agent ID, or empty string if none found
|
|
226
|
+
*/
|
|
227
|
+
export const getConsultedAgentId = (media: Interaction['media'], agentId: string): string => {
|
|
228
|
+
let consultParticipants: string[] = [];
|
|
229
|
+
let consultedParticipantId = '';
|
|
230
|
+
|
|
231
|
+
Object.keys(media).forEach((key) => {
|
|
232
|
+
if (media[key].mType === STATE_CONSULT) {
|
|
233
|
+
consultParticipants = media[key].participants;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (consultParticipants.includes(agentId)) {
|
|
238
|
+
const id = consultParticipants.find((participant) => participant !== agentId);
|
|
239
|
+
consultedParticipantId = id || consultedParticipantId;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return consultedParticipantId;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Gets the destination agent ID for CBT (Capacity Based Team) scenarios.
|
|
247
|
+
* CBT refers to teams created in Control Hub with capacity-based routing
|
|
248
|
+
* (as opposed to agent-based routing). This handles cases where the consulted
|
|
249
|
+
* participant is not directly in participants but can be found by matching
|
|
250
|
+
* the dial number (dn).
|
|
228
251
|
*
|
|
229
|
-
* @param
|
|
230
|
-
* @
|
|
252
|
+
* @param interaction - The interaction object
|
|
253
|
+
* @param consultingAgent - The consulting agent identifier
|
|
254
|
+
* @returns The destination agent ID for CBT scenarios, or empty string if none found
|
|
231
255
|
*/
|
|
256
|
+
export const getDestAgentIdForCBT = (interaction: Interaction, consultingAgent: string): string => {
|
|
257
|
+
const participants = interaction.participants;
|
|
258
|
+
let destAgentIdForCBT = '';
|
|
259
|
+
|
|
260
|
+
// Check if this is a CBT scenario (consultingAgent exists but not directly in participants)
|
|
261
|
+
if (consultingAgent && !participants[consultingAgent]) {
|
|
262
|
+
const foundEntry = Object.entries(participants).find(
|
|
263
|
+
([, participant]: [string, Interaction['participants'][string]]) => {
|
|
264
|
+
return (
|
|
265
|
+
participant.pType.toLowerCase() === PARTICIPANT_TYPES.DN &&
|
|
266
|
+
participant.type === PARTICIPANT_TYPES.AGENT &&
|
|
267
|
+
participant.dn === consultingAgent
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
if (foundEntry) {
|
|
273
|
+
destAgentIdForCBT = foundEntry[0];
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return destAgentIdForCBT;
|
|
278
|
+
};
|
|
279
|
+
|
|
232
280
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
281
|
+
* Calculates the destination agent ID for consult operations.
|
|
282
|
+
*
|
|
283
|
+
* @param interaction - The interaction object
|
|
284
|
+
* @param agentId - The current agent's ID
|
|
285
|
+
* @returns The destination agent ID
|
|
236
286
|
*/
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
)
|
|
287
|
+
export const calculateDestAgentId = (interaction: Interaction, agentId: string): string => {
|
|
288
|
+
const consultingAgent = getConsultedAgentId(interaction.media, agentId);
|
|
289
|
+
|
|
290
|
+
// Check if this is a CBT (Capacity Based Team) scenario
|
|
291
|
+
// If not CBT, the function will return empty string and we'll use the normal flow
|
|
292
|
+
const destAgentIdCBT = getDestAgentIdForCBT(interaction, consultingAgent);
|
|
293
|
+
if (destAgentIdCBT) {
|
|
294
|
+
return destAgentIdCBT;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return interaction.participants[consultingAgent]?.type === PARTICIPANT_TYPES.EP_DN
|
|
298
|
+
? interaction.participants[consultingAgent]?.epId
|
|
299
|
+
: interaction.participants[consultingAgent]?.id;
|
|
244
300
|
};
|
|
245
301
|
|
|
246
302
|
/**
|
|
247
|
-
*
|
|
248
|
-
* non-customer participant that is not the current agent and is not in wrap-up state.
|
|
303
|
+
* Calculates the destination agent ID for fetching destination type.
|
|
249
304
|
*
|
|
250
|
-
* @param
|
|
251
|
-
* @param agentId - The current agent's ID
|
|
252
|
-
* @returns The destination agent ID
|
|
305
|
+
* @param interaction - The interaction object
|
|
306
|
+
* @param agentId - The current agent's ID
|
|
307
|
+
* @returns The destination agent ID for determining destination type
|
|
253
308
|
*/
|
|
254
|
-
export const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
309
|
+
export const calculateDestType = (interaction: Interaction, agentId: string): string => {
|
|
310
|
+
const consultingAgent = getConsultedAgentId(interaction.media, agentId);
|
|
311
|
+
|
|
312
|
+
// Check if this is a CBT (Capacity Based Team) scenario, otherwise use consultingAgent
|
|
313
|
+
const destAgentIdCBT = getDestAgentIdForCBT(interaction, consultingAgent);
|
|
314
|
+
const destinationaegntId = destAgentIdCBT || consultingAgent;
|
|
315
|
+
const destAgentType = destinationaegntId
|
|
316
|
+
? interaction.participants[destinationaegntId]?.pType
|
|
317
|
+
: undefined;
|
|
318
|
+
if (destAgentType) {
|
|
319
|
+
if (destAgentType === 'DN') {
|
|
320
|
+
return CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER;
|
|
321
|
+
}
|
|
322
|
+
if (destAgentType === 'EP-DN') {
|
|
323
|
+
return CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT;
|
|
324
|
+
}
|
|
325
|
+
// Keep the existing destinationType if it's something else (like "agent" or "Agent")
|
|
326
|
+
// Convert "Agent" to lowercase for consistency
|
|
327
|
+
|
|
328
|
+
return destAgentType.toLowerCase();
|
|
271
329
|
}
|
|
272
330
|
|
|
273
|
-
return
|
|
331
|
+
return CONSULT_TRANSFER_DESTINATION_TYPE.AGENT;
|
|
274
332
|
};
|
|
275
333
|
|
|
276
334
|
export const deriveConsultTransferDestinationType = (
|
|
@@ -286,45 +344,3 @@ export const deriveConsultTransferDestinationType = (
|
|
|
286
344
|
|
|
287
345
|
return CONSULT_TRANSFER_DESTINATION_TYPE.AGENT;
|
|
288
346
|
};
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Builds consult conference parameter data using EXACT Agent Desktop logic.
|
|
292
|
-
* This matches the Agent Desktop's consultConference implementation exactly.
|
|
293
|
-
*
|
|
294
|
-
* @param dataPassed - Original consultation data from Agent Desktop format
|
|
295
|
-
* @param interactionIdPassed - The interaction ID for the task
|
|
296
|
-
* @returns Object with interactionId and ConsultConferenceData matching Agent Desktop format
|
|
297
|
-
* @public
|
|
298
|
-
*/
|
|
299
|
-
export const buildConsultConferenceParamData = (
|
|
300
|
-
dataPassed: consultConferencePayloadData,
|
|
301
|
-
interactionIdPassed: string
|
|
302
|
-
): {interactionId: string; data: ConsultConferenceData} => {
|
|
303
|
-
const data: ConsultConferenceData = {
|
|
304
|
-
// Include agentId if present in input data
|
|
305
|
-
...('agentId' in dataPassed && {agentId: dataPassed.agentId}),
|
|
306
|
-
// Handle destAgentId from consultation data
|
|
307
|
-
to: dataPassed.destAgentId,
|
|
308
|
-
destinationType: '',
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
// Agent Desktop destination type logic
|
|
312
|
-
if ('destinationType' in dataPassed) {
|
|
313
|
-
if (dataPassed.destinationType === 'DN') {
|
|
314
|
-
data.destinationType = CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER;
|
|
315
|
-
} else if (dataPassed.destinationType === 'EP_DN') {
|
|
316
|
-
data.destinationType = CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT;
|
|
317
|
-
} else {
|
|
318
|
-
// Keep the existing destinationType if it's something else (like "agent" or "Agent")
|
|
319
|
-
// Convert "Agent" to lowercase for consistency
|
|
320
|
-
data.destinationType = dataPassed.destinationType.toLowerCase();
|
|
321
|
-
}
|
|
322
|
-
} else {
|
|
323
|
-
data.destinationType = CONSULT_TRANSFER_DESTINATION_TYPE.AGENT;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
return {
|
|
327
|
-
interactionId: interactionIdPassed,
|
|
328
|
-
data,
|
|
329
|
-
};
|
|
330
|
-
};
|
|
@@ -64,6 +64,22 @@ export const CONNECTIVITY_CHECK_INTERVAL = 5000;
|
|
|
64
64
|
*/
|
|
65
65
|
export const CLOSE_SOCKET_TIMEOUT = 16000;
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Constants for participant types, destination types, and interaction states
|
|
69
|
+
* @ignore
|
|
70
|
+
*/
|
|
71
|
+
export const PARTICIPANT_TYPES = {
|
|
72
|
+
/** Participant type for Entry Point Dial Number */
|
|
73
|
+
EP_DN: 'EpDn',
|
|
74
|
+
/** Participant type for dial number */
|
|
75
|
+
DN: 'dn',
|
|
76
|
+
/** Participant type for Agent */
|
|
77
|
+
AGENT: 'Agent',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Interaction state for consultation */
|
|
81
|
+
export const STATE_CONSULT = 'consult';
|
|
82
|
+
|
|
67
83
|
// Method names for core services
|
|
68
84
|
export const METHODS = {
|
|
69
85
|
// WebexRequest methods
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
getIsConferenceInProgress,
|
|
18
18
|
isParticipantInMainInteraction,
|
|
19
19
|
isPrimary,
|
|
20
|
+
isSecondaryEpDnAgent,
|
|
20
21
|
} from './TaskUtils';
|
|
21
22
|
|
|
22
23
|
/** @internal */
|
|
@@ -128,6 +129,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
128
129
|
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
129
130
|
interactionId: payload.data.interactionId,
|
|
130
131
|
});
|
|
132
|
+
|
|
131
133
|
task = new Task(
|
|
132
134
|
this.contact,
|
|
133
135
|
this.webCallingService,
|
|
@@ -135,6 +137,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
135
137
|
...payload.data,
|
|
136
138
|
wrapUpRequired:
|
|
137
139
|
payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false,
|
|
140
|
+
isConferenceInProgress: getIsConferenceInProgress(payload.data),
|
|
138
141
|
},
|
|
139
142
|
this.wrapupData,
|
|
140
143
|
this.agentId
|
|
@@ -165,6 +168,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
break;
|
|
171
|
+
|
|
168
172
|
case CC_EVENTS.AGENT_CONTACT_RESERVED:
|
|
169
173
|
task = new Task(
|
|
170
174
|
this.contact,
|
|
@@ -244,13 +248,22 @@ export default class TaskManager extends EventEmitter {
|
|
|
244
248
|
break;
|
|
245
249
|
}
|
|
246
250
|
case CC_EVENTS.CONTACT_ENDED:
|
|
251
|
+
// Update task data
|
|
247
252
|
task = this.updateTaskData(task, {
|
|
248
253
|
...payload.data,
|
|
249
|
-
wrapUpRequired:
|
|
254
|
+
wrapUpRequired:
|
|
255
|
+
payload.data.interaction.state !== 'new' &&
|
|
256
|
+
!isSecondaryEpDnAgent(payload.data.interaction),
|
|
250
257
|
});
|
|
258
|
+
|
|
259
|
+
// Handle cleanup based on whether task should be deleted
|
|
251
260
|
this.handleTaskCleanup(task);
|
|
252
|
-
task.emit(TASK_EVENTS.TASK_END, task);
|
|
253
261
|
|
|
262
|
+
task?.emit(TASK_EVENTS.TASK_END, task);
|
|
263
|
+
|
|
264
|
+
break;
|
|
265
|
+
case CC_EVENTS.CONTACT_MERGED:
|
|
266
|
+
task = this.handleContactMerged(task, payload.data);
|
|
254
267
|
break;
|
|
255
268
|
case CC_EVENTS.AGENT_CONTACT_HELD:
|
|
256
269
|
// As soon as the main interaction is held, we need to emit TASK_HOLD
|
|
@@ -372,32 +385,22 @@ export default class TaskManager extends EventEmitter {
|
|
|
372
385
|
} else {
|
|
373
386
|
this.removeTaskFromCollection(task);
|
|
374
387
|
}
|
|
375
|
-
task
|
|
388
|
+
task.emit(TASK_EVENTS.TASK_CONFERENCE_ENDED, task);
|
|
376
389
|
break;
|
|
377
390
|
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
391
|
task = this.updateTaskData(task, {
|
|
385
392
|
...payload.data,
|
|
386
|
-
isConferenceInProgress: getIsConferenceInProgress(
|
|
393
|
+
isConferenceInProgress: getIsConferenceInProgress(payload.data),
|
|
387
394
|
});
|
|
388
395
|
task.emit(TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
|
|
389
396
|
break;
|
|
390
397
|
}
|
|
391
398
|
case CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE: {
|
|
392
399
|
// Conference ended - update task state and emit event
|
|
393
|
-
|
|
394
|
-
const simulatedTaskForLeft = {
|
|
395
|
-
...task,
|
|
396
|
-
data: {...task.data, ...payload.data},
|
|
397
|
-
};
|
|
400
|
+
|
|
398
401
|
task = this.updateTaskData(task, {
|
|
399
402
|
...payload.data,
|
|
400
|
-
isConferenceInProgress: getIsConferenceInProgress(
|
|
403
|
+
isConferenceInProgress: getIsConferenceInProgress(payload.data),
|
|
401
404
|
});
|
|
402
405
|
if (checkParticipantNotInInteraction(task, this.agentId)) {
|
|
403
406
|
if (
|
|
@@ -433,13 +436,10 @@ export default class TaskManager extends EventEmitter {
|
|
|
433
436
|
task = this.updateTaskData(task, payload.data);
|
|
434
437
|
task.emit(TASK_EVENTS.TASK_CONFERENCE_TRANSFER_FAILED, task);
|
|
435
438
|
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
439
|
case CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY:
|
|
441
440
|
// Post-call activity for participant - update task state with activity details
|
|
442
441
|
task = this.updateTaskData(task, payload.data);
|
|
442
|
+
task.emit(TASK_EVENTS.TASK_POST_CALL_ACTIVITY, task);
|
|
443
443
|
break;
|
|
444
444
|
default:
|
|
445
445
|
break;
|
|
@@ -479,6 +479,54 @@ export default class TaskManager extends EventEmitter {
|
|
|
479
479
|
}
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Handles CONTACT_MERGED event logic
|
|
484
|
+
* @param task - The task to process
|
|
485
|
+
* @param taskData - The task data from the event payload
|
|
486
|
+
* @returns Updated or newly created task
|
|
487
|
+
* @private
|
|
488
|
+
*/
|
|
489
|
+
private handleContactMerged(task: ITask, taskData: TaskData): ITask {
|
|
490
|
+
if (taskData.childInteractionId) {
|
|
491
|
+
// remove the child task from collection
|
|
492
|
+
this.removeTaskFromCollection(this.taskCollection[taskData.childInteractionId]);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (this.taskCollection[taskData.interactionId]) {
|
|
496
|
+
LoggerProxy.log(`Got CONTACT_MERGED: Task already exists in collection`, {
|
|
497
|
+
module: TASK_MANAGER_FILE,
|
|
498
|
+
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
499
|
+
interactionId: taskData.interactionId,
|
|
500
|
+
});
|
|
501
|
+
// update the task data
|
|
502
|
+
task = this.updateTaskData(task, taskData);
|
|
503
|
+
} else {
|
|
504
|
+
// Case2 : Task is not present in taskCollection
|
|
505
|
+
LoggerProxy.log(`Got CONTACT_MERGED : Creating new task in taskManager`, {
|
|
506
|
+
module: TASK_MANAGER_FILE,
|
|
507
|
+
method: METHODS.REGISTER_TASK_LISTENERS,
|
|
508
|
+
interactionId: taskData.interactionId,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
task = new Task(
|
|
512
|
+
this.contact,
|
|
513
|
+
this.webCallingService,
|
|
514
|
+
{
|
|
515
|
+
...taskData,
|
|
516
|
+
wrapUpRequired: taskData.interaction?.participants?.[this.agentId]?.isWrapUp || false,
|
|
517
|
+
isConferenceInProgress: getIsConferenceInProgress(taskData),
|
|
518
|
+
},
|
|
519
|
+
this.wrapupData,
|
|
520
|
+
this.agentId
|
|
521
|
+
);
|
|
522
|
+
this.taskCollection[taskData.interactionId] = task;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
this.emit(TASK_EVENTS.TASK_MERGED, task);
|
|
526
|
+
|
|
527
|
+
return task;
|
|
528
|
+
}
|
|
529
|
+
|
|
482
530
|
private removeTaskFromCollection(task: ITask) {
|
|
483
531
|
if (task?.data?.interactionId) {
|
|
484
532
|
delete this.taskCollection[task.data.interactionId];
|
|
@@ -490,7 +538,13 @@ export default class TaskManager extends EventEmitter {
|
|
|
490
538
|
}
|
|
491
539
|
}
|
|
492
540
|
|
|
541
|
+
/**
|
|
542
|
+
* Handles cleanup of task resources including Desktop/WebRTC call cleanup and task removal
|
|
543
|
+
* @param task - The task to clean up
|
|
544
|
+
* @private
|
|
545
|
+
*/
|
|
493
546
|
private handleTaskCleanup(task: ITask) {
|
|
547
|
+
// Clean up Desktop/WebRTC calling resources for browser-based telephony tasks
|
|
494
548
|
if (
|
|
495
549
|
this.webCallingService.loginOption === LoginOption.BROWSER &&
|
|
496
550
|
task.data.interaction.mediaType === 'telephony'
|
|
@@ -498,8 +552,9 @@ export default class TaskManager extends EventEmitter {
|
|
|
498
552
|
task.unregisterWebCallListeners();
|
|
499
553
|
this.webCallingService.cleanUpCall();
|
|
500
554
|
}
|
|
501
|
-
|
|
502
|
-
|
|
555
|
+
|
|
556
|
+
if (task.data.interaction.state === 'new' || isSecondaryEpDnAgent(task.data.interaction)) {
|
|
557
|
+
// Only remove tasks in 'new' state or isSecondaryEpDnAgent immediately. For other states,
|
|
503
558
|
// retain tasks until they complete wrap-up, unless the task disconnected before being answered.
|
|
504
559
|
this.removeTaskFromCollection(task);
|
|
505
560
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
import {ITask} from './types';
|
|
2
|
+
import {Interaction, ITask, TaskData} from './types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Determines if the given agent is the primary agent (owner) of the task
|
|
@@ -53,13 +53,13 @@ export const checkParticipantNotInInteraction = (task: ITask, agentId: string):
|
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Determines if a conference is currently in progress based on the number of active agent participants
|
|
56
|
-
* @param
|
|
56
|
+
* @param TaskData - The payLoad data to check for conference status
|
|
57
57
|
* @returns true if there are 2 or more active agent participants in the main call, false otherwise
|
|
58
58
|
*/
|
|
59
|
-
export const getIsConferenceInProgress = (
|
|
60
|
-
const mediaMainCall =
|
|
59
|
+
export const getIsConferenceInProgress = (data: TaskData): boolean => {
|
|
60
|
+
const mediaMainCall = data.interaction.media?.[data?.interactionId];
|
|
61
61
|
const participantsInMainCall = new Set(mediaMainCall?.participants);
|
|
62
|
-
const participants =
|
|
62
|
+
const participants = data.interaction.participants;
|
|
63
63
|
|
|
64
64
|
const agentParticipants = new Set();
|
|
65
65
|
if (participantsInMainCall.size > 0) {
|
|
@@ -79,3 +79,35 @@ export const getIsConferenceInProgress = (task: ITask): boolean => {
|
|
|
79
79
|
|
|
80
80
|
return agentParticipants.size >= 2;
|
|
81
81
|
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Checks if the current agent is a secondary agent in a consultation scenario.
|
|
85
|
+
* Secondary agents are those who were consulted (not the original call owner).
|
|
86
|
+
* @param task - The task object containing interaction details
|
|
87
|
+
* @returns true if this is a secondary agent (consulted party), false otherwise
|
|
88
|
+
*/
|
|
89
|
+
export const isSecondaryAgent = (interaction: Interaction): boolean => {
|
|
90
|
+
if (!interaction.callProcessingDetails) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
interaction.callProcessingDetails.relationshipType === 'consult' &&
|
|
96
|
+
!!interaction.callProcessingDetails.parentInteractionId &&
|
|
97
|
+
interaction.callProcessingDetails.parentInteractionId !== interaction.interactionId
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Checks if the current agent is a secondary EP-DN (Entry Point Dial Number) agent.
|
|
103
|
+
* This is specifically for telephony consultations to external numbers/entry points.
|
|
104
|
+
* @param task - The task object containing interaction details
|
|
105
|
+
* @returns true if this is a secondary EP-DN agent in telephony consultation, false otherwise
|
|
106
|
+
*/
|
|
107
|
+
export const isSecondaryEpDnAgent = (interaction: Interaction): boolean => {
|
|
108
|
+
if (!interaction) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return interaction.mediaType === 'telephony' && isSecondaryAgent(interaction);
|
|
113
|
+
};
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import EventEmitter from 'events';
|
|
2
2
|
import {CALL_EVENT_KEYS, LocalMicrophoneStream} from '@webex/calling';
|
|
3
3
|
import {CallId} from '@webex/calling/dist/types/common/types';
|
|
4
|
-
import {
|
|
5
|
-
generateTaskErrorObject,
|
|
6
|
-
deriveConsultTransferDestinationType,
|
|
7
|
-
getDestinationAgentId,
|
|
8
|
-
buildConsultConferenceParamData,
|
|
9
|
-
} from '../core/Utils';
|
|
4
|
+
import {generateTaskErrorObject, calculateDestAgentId, calculateDestType} from '../core/Utils';
|
|
10
5
|
import {Failure} from '../core/GlobalTypes';
|
|
11
6
|
import {LoginOption} from '../../types';
|
|
12
7
|
import {TASK_FILE} from '../../constants';
|
|
@@ -1447,35 +1442,31 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
1447
1442
|
public async consultTransfer(
|
|
1448
1443
|
consultTransferPayload?: ConsultTransferPayLoad
|
|
1449
1444
|
): Promise<TaskResponse> {
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
const destAgentId = getDestinationAgentId(
|
|
1453
|
-
this.data.interaction?.participants,
|
|
1454
|
-
this.data.agentId
|
|
1455
|
-
);
|
|
1456
|
-
|
|
1457
|
-
// Resolve the target id (queue consult transfers go to the accepted agent)
|
|
1458
|
-
if (!destAgentId) {
|
|
1459
|
-
throw new Error('No agent has accepted this queue consult yet');
|
|
1460
|
-
}
|
|
1445
|
+
// Get the destination agent ID using custom logic from participants data
|
|
1446
|
+
const destAgentId = calculateDestAgentId(this.data.interaction, this.agentId);
|
|
1461
1447
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
method: METHODS.CONSULT_TRANSFER,
|
|
1467
|
-
interactionId: this.data.interactionId,
|
|
1468
|
-
}
|
|
1469
|
-
);
|
|
1470
|
-
// Obtain payload based on desktop logic using TaskData
|
|
1471
|
-
const finalDestinationType = deriveConsultTransferDestinationType(this.data);
|
|
1472
|
-
|
|
1473
|
-
// By default we always use the computed destAgentId as the target id
|
|
1474
|
-
const consultTransferRequest: ConsultTransferPayLoad = {
|
|
1475
|
-
to: destAgentId,
|
|
1476
|
-
destinationType: finalDestinationType,
|
|
1477
|
-
};
|
|
1448
|
+
// Resolve the target id (queue consult transfers go to the accepted agent)
|
|
1449
|
+
if (!destAgentId) {
|
|
1450
|
+
throw new Error('No agent has accepted this queue consult yet');
|
|
1451
|
+
}
|
|
1478
1452
|
|
|
1453
|
+
LoggerProxy.info(
|
|
1454
|
+
`Initiating consult transfer to ${consultTransferPayload?.to || destAgentId}`,
|
|
1455
|
+
{
|
|
1456
|
+
module: TASK_FILE,
|
|
1457
|
+
method: METHODS.CONSULT_TRANSFER,
|
|
1458
|
+
interactionId: this.data.interactionId,
|
|
1459
|
+
}
|
|
1460
|
+
);
|
|
1461
|
+
|
|
1462
|
+
// Derive destination type from the participant's type property
|
|
1463
|
+
const destType = calculateDestType(this.data.interaction, this.agentId);
|
|
1464
|
+
// By default we always use the computed destAgentId as the target id
|
|
1465
|
+
const consultTransferRequest: ConsultTransferPayLoad = {
|
|
1466
|
+
to: destAgentId,
|
|
1467
|
+
destinationType: destType,
|
|
1468
|
+
};
|
|
1469
|
+
try {
|
|
1479
1470
|
const result = await this.contact.consultTransfer({
|
|
1480
1471
|
interactionId: this.data.interactionId,
|
|
1481
1472
|
data: consultTransferRequest,
|
|
@@ -1513,17 +1504,12 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
1513
1504
|
errorData: err.data?.errorData,
|
|
1514
1505
|
reasonCode: err.data?.reasonCode,
|
|
1515
1506
|
};
|
|
1516
|
-
const failedDestinationType = deriveConsultTransferDestinationType(this.data);
|
|
1517
|
-
const failedDestAgentId = getDestinationAgentId(
|
|
1518
|
-
this.data.interaction?.participants,
|
|
1519
|
-
this.data.agentId
|
|
1520
|
-
);
|
|
1521
1507
|
this.metricsManager.trackEvent(
|
|
1522
1508
|
METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
|
|
1523
1509
|
{
|
|
1524
1510
|
taskId: this.data.interactionId,
|
|
1525
|
-
destination:
|
|
1526
|
-
destinationType:
|
|
1511
|
+
destination: destAgentId || '',
|
|
1512
|
+
destinationType: destType,
|
|
1527
1513
|
isConsultTransfer: true,
|
|
1528
1514
|
error: error.toString(),
|
|
1529
1515
|
...taskErrorProps,
|
|
@@ -1556,28 +1542,36 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
1556
1542
|
* ```
|
|
1557
1543
|
*/
|
|
1558
1544
|
public async consultConference(): Promise<TaskResponse> {
|
|
1545
|
+
// Get the destination agent ID dynamically from participants
|
|
1546
|
+
// This handles multi-party conference scenarios, CBT (Capacity Based Team), and EP-DN cases
|
|
1547
|
+
const destAgentId = calculateDestAgentId(this.data.interaction, this.agentId);
|
|
1548
|
+
|
|
1549
|
+
// Validate that we have a destination agent (for queue consult scenarios)
|
|
1550
|
+
if (!destAgentId) {
|
|
1551
|
+
throw new Error('No agent has accepted this queue consult yet');
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// Get the destination agent ID for fetching destination type
|
|
1555
|
+
// This helps determine the correct participant type for CBT (Capacity Based Team) and EP-DN scenarios
|
|
1556
|
+
const destAgentType = calculateDestType(this.data.interaction, this.agentId);
|
|
1557
|
+
|
|
1559
1558
|
// Extract consultation conference data from task data (used in both try and catch)
|
|
1560
1559
|
const consultationData = {
|
|
1561
1560
|
agentId: this.agentId,
|
|
1562
|
-
|
|
1563
|
-
destinationType: this.data.destinationType || 'agent',
|
|
1561
|
+
to: destAgentId,
|
|
1562
|
+
destinationType: destAgentType || this.data.destinationType || 'agent',
|
|
1564
1563
|
};
|
|
1565
1564
|
|
|
1566
1565
|
try {
|
|
1567
|
-
LoggerProxy.info(`Initiating consult conference to ${
|
|
1566
|
+
LoggerProxy.info(`Initiating consult conference to ${destAgentId}`, {
|
|
1568
1567
|
module: TASK_FILE,
|
|
1569
1568
|
method: METHODS.CONSULT_CONFERENCE,
|
|
1570
1569
|
interactionId: this.data.interactionId,
|
|
1571
1570
|
});
|
|
1572
1571
|
|
|
1573
|
-
const paramsDataForConferenceV2 = buildConsultConferenceParamData(
|
|
1574
|
-
consultationData,
|
|
1575
|
-
this.data.interactionId
|
|
1576
|
-
);
|
|
1577
|
-
|
|
1578
1572
|
const response = await this.contact.consultConference({
|
|
1579
|
-
interactionId:
|
|
1580
|
-
data:
|
|
1573
|
+
interactionId: this.data.interactionId,
|
|
1574
|
+
data: consultationData,
|
|
1581
1575
|
});
|
|
1582
1576
|
|
|
1583
1577
|
// Track success metrics (following consultTransfer pattern)
|
|
@@ -1585,9 +1579,9 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
1585
1579
|
METRIC_EVENT_NAMES.TASK_CONFERENCE_START_SUCCESS,
|
|
1586
1580
|
{
|
|
1587
1581
|
taskId: this.data.interactionId,
|
|
1588
|
-
destination:
|
|
1589
|
-
destinationType:
|
|
1590
|
-
agentId:
|
|
1582
|
+
destination: consultationData.to,
|
|
1583
|
+
destinationType: consultationData.destinationType,
|
|
1584
|
+
agentId: consultationData.agentId,
|
|
1591
1585
|
...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
|
|
1592
1586
|
},
|
|
1593
1587
|
['operational', 'behavioral', 'business']
|
|
@@ -1610,20 +1604,13 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
1610
1604
|
reasonCode: err.data?.reasonCode,
|
|
1611
1605
|
};
|
|
1612
1606
|
|
|
1613
|
-
// Track failure metrics (following consultTransfer pattern)
|
|
1614
|
-
// Build conference data for error tracking using extracted data
|
|
1615
|
-
const failedParamsData = buildConsultConferenceParamData(
|
|
1616
|
-
consultationData,
|
|
1617
|
-
this.data.interactionId
|
|
1618
|
-
);
|
|
1619
|
-
|
|
1620
1607
|
this.metricsManager.trackEvent(
|
|
1621
1608
|
METRIC_EVENT_NAMES.TASK_CONFERENCE_START_FAILED,
|
|
1622
1609
|
{
|
|
1623
1610
|
taskId: this.data.interactionId,
|
|
1624
|
-
destination:
|
|
1625
|
-
destinationType:
|
|
1626
|
-
agentId:
|
|
1611
|
+
destination: consultationData.to,
|
|
1612
|
+
destinationType: consultationData.destinationType,
|
|
1613
|
+
agentId: consultationData.agentId,
|
|
1627
1614
|
error: error.toString(),
|
|
1628
1615
|
...taskErrorProps,
|
|
1629
1616
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
|