@webex/contact-center 3.9.0 → 3.10.0-next.2
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 +196 -47
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/logger-proxy.js +24 -1
- package/dist/logger-proxy.js.map +1 -1
- package/dist/metrics/MetricsManager.js +1 -1
- package/dist/metrics/MetricsManager.js.map +1 -1
- package/dist/metrics/behavioral-events.js +89 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +32 -2
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AddressBook.js +271 -0
- package/dist/services/AddressBook.js.map +1 -0
- package/dist/services/EntryPoint.js +227 -0
- package/dist/services/EntryPoint.js.map +1 -0
- package/dist/services/Queue.js +261 -0
- package/dist/services/Queue.js.map +1 -0
- package/dist/services/config/constants.js +36 -2
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/index.js +29 -21
- package/dist/services/config/index.js.map +1 -1
- package/dist/services/config/types.js +33 -1
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/GlobalTypes.js.map +1 -1
- package/dist/services/core/Utils.js +162 -2
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +0 -4
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +0 -4
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/task/TaskManager.js +114 -3
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +76 -0
- package/dist/services/task/TaskUtils.js.map +1 -0
- package/dist/services/task/constants.js +26 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/contact.js +86 -0
- package/dist/services/task/contact.js.map +1 -1
- package/dist/services/task/index.js +418 -87
- package/dist/services/task/index.js.map +1 -1
- package/dist/services/task/types.js +14 -0
- package/dist/services/task/types.js.map +1 -1
- package/dist/types/cc.d.ts +115 -35
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/metrics/constants.d.ts +25 -1
- package/dist/types/services/AddressBook.d.ts +74 -0
- package/dist/types/services/EntryPoint.d.ts +67 -0
- package/dist/types/services/Queue.d.ts +76 -0
- package/dist/types/services/config/constants.d.ts +35 -1
- package/dist/types/services/config/index.d.ts +6 -9
- package/dist/types/services/config/types.d.ts +79 -58
- package/dist/types/services/core/GlobalTypes.d.ts +25 -0
- package/dist/types/services/core/Utils.d.ts +40 -1
- package/dist/types/services/task/TaskUtils.d.ts +28 -0
- package/dist/types/services/task/constants.d.ts +23 -0
- package/dist/types/services/task/contact.d.ts +10 -0
- package/dist/types/services/task/index.d.ts +85 -4
- package/dist/types/services/task/types.d.ts +233 -21
- package/dist/types/types.d.ts +162 -0
- package/dist/types/utils/PageCache.d.ts +173 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/PageCache.js +192 -0
- package/dist/utils/PageCache.js.map +1 -0
- package/dist/webex.js +1 -1
- package/package.json +10 -9
- package/src/cc.ts +221 -52
- package/src/constants.ts +1 -0
- package/src/index.ts +16 -2
- package/src/logger-proxy.ts +24 -1
- package/src/metrics/MetricsManager.ts +1 -1
- package/src/metrics/behavioral-events.ts +94 -0
- package/src/metrics/constants.ts +37 -1
- package/src/services/AddressBook.ts +291 -0
- package/src/services/EntryPoint.ts +241 -0
- package/src/services/Queue.ts +277 -0
- package/src/services/config/constants.ts +42 -2
- package/src/services/config/index.ts +30 -30
- package/src/services/config/types.ts +59 -58
- package/src/services/core/GlobalTypes.ts +27 -0
- package/src/services/core/Utils.ts +199 -1
- package/src/services/core/aqm-reqs.ts +0 -5
- package/src/services/core/websocket/WebSocketManager.ts +0 -4
- package/src/services/task/TaskManager.ts +123 -5
- package/src/services/task/TaskUtils.ts +81 -0
- package/src/services/task/constants.ts +25 -0
- package/src/services/task/contact.ts +80 -0
- package/src/services/task/index.ts +510 -71
- package/src/services/task/types.ts +251 -20
- package/src/types.ts +180 -0
- package/src/utils/PageCache.ts +252 -0
- package/test/unit/spec/cc.ts +282 -85
- package/test/unit/spec/metrics/MetricsManager.ts +0 -1
- package/test/unit/spec/metrics/behavioral-events.ts +42 -0
- package/test/unit/spec/services/AddressBook.ts +332 -0
- package/test/unit/spec/services/EntryPoint.ts +259 -0
- package/test/unit/spec/services/Queue.ts +323 -0
- package/test/unit/spec/services/config/index.ts +279 -65
- package/test/unit/spec/services/core/Utils.ts +50 -0
- package/test/unit/spec/services/core/aqm-reqs.ts +1 -3
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +0 -4
- package/test/unit/spec/services/task/TaskManager.ts +390 -1
- package/test/unit/spec/services/task/TaskUtils.ts +131 -0
- package/test/unit/spec/services/task/contact.ts +31 -1
- package/test/unit/spec/services/task/index.ts +585 -130
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import * as Err from './Err';
|
|
2
2
|
import {LoginOption, WebexRequestPayload} from '../../types';
|
|
3
|
-
import {Failure} from './GlobalTypes';
|
|
3
|
+
import {Failure, AugmentedError} from './GlobalTypes';
|
|
4
4
|
import LoggerProxy from '../../logger-proxy';
|
|
5
5
|
import WebexRequest from './WebexRequest';
|
|
6
|
+
import {
|
|
7
|
+
TaskData,
|
|
8
|
+
ConsultTransferPayLoad,
|
|
9
|
+
ConsultConferenceData,
|
|
10
|
+
consultConferencePayloadData,
|
|
11
|
+
CONSULT_TRANSFER_DESTINATION_TYPE,
|
|
12
|
+
Interaction,
|
|
13
|
+
} from '../task/types';
|
|
6
14
|
|
|
7
15
|
/**
|
|
8
16
|
* Extracts common error details from a Webex request payload.
|
|
@@ -19,6 +27,28 @@ const getCommonErrorDetails = (errObj: WebexRequestPayload) => {
|
|
|
19
27
|
};
|
|
20
28
|
};
|
|
21
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Checks if the destination type represents an entry point variant (EPDN or ENTRYPOINT).
|
|
32
|
+
*/
|
|
33
|
+
const isEntryPointOrEpdn = (destAgentType?: string): boolean => {
|
|
34
|
+
return destAgentType === 'EPDN' || destAgentType === 'ENTRYPOINT';
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Determines if the task involves dialing a number based on the destination type.
|
|
39
|
+
* Returns 'DIAL_NUMBER' for dial-related destinations, empty string otherwise.
|
|
40
|
+
*/
|
|
41
|
+
const getAgentActionTypeFromTask = (taskData?: TaskData): 'DIAL_NUMBER' | '' => {
|
|
42
|
+
const destAgentType = taskData?.destinationType;
|
|
43
|
+
|
|
44
|
+
// Check if destination requires dialing: direct dial number or entry point variants
|
|
45
|
+
const isDialNumber = destAgentType === 'DN';
|
|
46
|
+
const isEntryPointVariant = isEntryPointOrEpdn(destAgentType);
|
|
47
|
+
|
|
48
|
+
// If the destination type is a dial number or an entry point variant, return 'DIAL_NUMBER'
|
|
49
|
+
return isDialNumber || isEntryPointVariant ? 'DIAL_NUMBER' : '';
|
|
50
|
+
};
|
|
51
|
+
|
|
22
52
|
export const isValidDialNumber = (input: string): boolean => {
|
|
23
53
|
// This regex checks for a valid dial number format for only few countries such as US, Canada.
|
|
24
54
|
const regexForDn = /1[0-9]{3}[2-9][0-9]{6}([,]{1,10}[0-9]+){0,1}/;
|
|
@@ -115,6 +145,62 @@ export const getErrorDetails = (error: any, methodName: string, moduleName: stri
|
|
|
115
145
|
};
|
|
116
146
|
};
|
|
117
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Extracts error details from task API errors and logs them. Also uploads logs for the error.
|
|
150
|
+
* This handles the specific error format returned by task API calls.
|
|
151
|
+
*
|
|
152
|
+
* @param error - The error object from task API calls with structure: {id: string, details: {trackingId: string, msg: {...}}}
|
|
153
|
+
* @param methodName - The name of the method where the error occurred.
|
|
154
|
+
* @param moduleName - The name of the module where the error occurred.
|
|
155
|
+
* @returns AugmentedError containing structured error details on err.data for metrics and logging
|
|
156
|
+
* @public
|
|
157
|
+
* @example
|
|
158
|
+
* const taskError = generateTaskErrorObject(error, 'transfer', 'TaskModule');
|
|
159
|
+
* throw taskError.error;
|
|
160
|
+
* @ignore
|
|
161
|
+
*/
|
|
162
|
+
export const generateTaskErrorObject = (
|
|
163
|
+
error: any,
|
|
164
|
+
methodName: string,
|
|
165
|
+
moduleName: string
|
|
166
|
+
): AugmentedError => {
|
|
167
|
+
const trackingId = error?.details?.trackingId || error?.trackingId || '';
|
|
168
|
+
const errorMsg = error?.details?.msg;
|
|
169
|
+
|
|
170
|
+
const fallbackMessage =
|
|
171
|
+
(error && typeof error.message === 'string' && error.message) ||
|
|
172
|
+
`Error while performing ${methodName}`;
|
|
173
|
+
const errorMessage = errorMsg?.errorMessage || fallbackMessage;
|
|
174
|
+
const errorType =
|
|
175
|
+
errorMsg?.errorType ||
|
|
176
|
+
(error && typeof error.name === 'string' && error.name) ||
|
|
177
|
+
'Unknown Error';
|
|
178
|
+
const errorData = errorMsg?.errorData || '';
|
|
179
|
+
const reasonCode = errorMsg?.reasonCode || 0;
|
|
180
|
+
|
|
181
|
+
// Log and upload for Task API formatted errors
|
|
182
|
+
LoggerProxy.error(`${methodName} failed: ${errorMessage} (${errorType})`, {
|
|
183
|
+
module: moduleName,
|
|
184
|
+
method: methodName,
|
|
185
|
+
trackingId,
|
|
186
|
+
});
|
|
187
|
+
WebexRequest.getInstance().uploadLogs({
|
|
188
|
+
correlationId: trackingId,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const reason = `${errorType}: ${errorMessage}${errorData ? ` (${errorData})` : ''}`;
|
|
192
|
+
const err: AugmentedError = new Error(reason);
|
|
193
|
+
err.data = {
|
|
194
|
+
message: errorMessage,
|
|
195
|
+
errorType,
|
|
196
|
+
errorData,
|
|
197
|
+
reasonCode,
|
|
198
|
+
trackingId,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
return err;
|
|
202
|
+
};
|
|
203
|
+
|
|
118
204
|
/**
|
|
119
205
|
* Creates an error details object suitable for use with the Err.Details class.
|
|
120
206
|
*
|
|
@@ -130,3 +216,115 @@ export const createErrDetailsObject = (errObj: WebexRequestPayload) => {
|
|
|
130
216
|
|
|
131
217
|
return new Err.Details('Service.reqs.generic.failure', details);
|
|
132
218
|
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Derives the consult transfer destination type based on the provided task data.
|
|
222
|
+
*
|
|
223
|
+
* Logic parity with desktop behavior:
|
|
224
|
+
* - If agent action is dialing a number (DN/EPDN/ENTRYPOINT):
|
|
225
|
+
* - ENTRYPOINT/EPDN map to ENTRYPOINT
|
|
226
|
+
* - DN maps to DIALNUMBER
|
|
227
|
+
* - Otherwise defaults to AGENT
|
|
228
|
+
*
|
|
229
|
+
* @param taskData - The task data used to infer the agent action and destination type
|
|
230
|
+
* @returns The normalized destination type to be used for consult transfer
|
|
231
|
+
*/
|
|
232
|
+
/**
|
|
233
|
+
* Checks if a participant type represents a non-customer participant.
|
|
234
|
+
* Non-customer participants include agents, dial numbers, entry point dial numbers,
|
|
235
|
+
* and entry points.
|
|
236
|
+
*/
|
|
237
|
+
const isNonCustomerParticipant = (participantType: string): boolean => {
|
|
238
|
+
return (
|
|
239
|
+
participantType === 'Agent' ||
|
|
240
|
+
participantType === 'DN' ||
|
|
241
|
+
participantType === 'EpDn' ||
|
|
242
|
+
participantType === 'entryPoint'
|
|
243
|
+
);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Gets the destination agent ID from participants data by finding the first
|
|
248
|
+
* non-customer participant that is not the current agent and is not in wrap-up state.
|
|
249
|
+
*
|
|
250
|
+
* @param participants - The participants data from the interaction
|
|
251
|
+
* @param agentId - The current agent's ID to exclude from the search
|
|
252
|
+
* @returns The destination agent ID, or empty string if none found
|
|
253
|
+
*/
|
|
254
|
+
export const getDestinationAgentId = (
|
|
255
|
+
participants: Interaction['participants'],
|
|
256
|
+
agentId: string
|
|
257
|
+
): string => {
|
|
258
|
+
let id = '';
|
|
259
|
+
|
|
260
|
+
if (participants) {
|
|
261
|
+
Object.keys(participants).forEach((participant) => {
|
|
262
|
+
const participantData = participants[participant];
|
|
263
|
+
if (
|
|
264
|
+
isNonCustomerParticipant(participantData.type) &&
|
|
265
|
+
participantData.id !== agentId &&
|
|
266
|
+
!participantData.isWrapUp
|
|
267
|
+
) {
|
|
268
|
+
id = participantData.id;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return id;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
export const deriveConsultTransferDestinationType = (
|
|
277
|
+
taskData?: TaskData
|
|
278
|
+
): ConsultTransferPayLoad['destinationType'] => {
|
|
279
|
+
const agentActionType = getAgentActionTypeFromTask(taskData);
|
|
280
|
+
|
|
281
|
+
if (agentActionType === 'DIAL_NUMBER') {
|
|
282
|
+
return isEntryPointOrEpdn(taskData?.destinationType)
|
|
283
|
+
? CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT
|
|
284
|
+
: CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return CONSULT_TRANSFER_DESTINATION_TYPE.AGENT;
|
|
288
|
+
};
|
|
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
|
+
};
|
|
@@ -178,10 +178,6 @@ export class WebSocketManager extends EventEmitter {
|
|
|
178
178
|
issueReason = 'WebSocket auto close timed out. Forcefully closed websocket.';
|
|
179
179
|
} else {
|
|
180
180
|
const onlineStatus = navigator.onLine;
|
|
181
|
-
LoggerProxy.info(`[WebSocketStatus] | desktop online status is ${onlineStatus}`, {
|
|
182
|
-
module: WEB_SOCKET_MANAGER_FILE,
|
|
183
|
-
method: METHODS.WEB_SOCKET_ON_CLOSE_HANDLER,
|
|
184
|
-
});
|
|
185
181
|
issueReason = !onlineStatus
|
|
186
182
|
? 'network issue'
|
|
187
183
|
: 'missing keepalive from either desktop or notif service';
|
|
@@ -12,6 +12,12 @@ import LoggerProxy from '../../logger-proxy';
|
|
|
12
12
|
import Task from '.';
|
|
13
13
|
import MetricsManager from '../../metrics/MetricsManager';
|
|
14
14
|
import {METRIC_EVENT_NAMES} from '../../metrics/constants';
|
|
15
|
+
import {
|
|
16
|
+
checkParticipantNotInInteraction,
|
|
17
|
+
getIsConferenceInProgress,
|
|
18
|
+
isParticipantInMainInteraction,
|
|
19
|
+
isPrimary,
|
|
20
|
+
} from './TaskUtils';
|
|
15
21
|
|
|
16
22
|
/** @internal */
|
|
17
23
|
export default class TaskManager extends EventEmitter {
|
|
@@ -28,6 +34,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
28
34
|
private metricsManager: MetricsManager;
|
|
29
35
|
private static taskManager;
|
|
30
36
|
private wrapupData: WrapupData;
|
|
37
|
+
private agentId: string;
|
|
31
38
|
/**
|
|
32
39
|
* @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises
|
|
33
40
|
* @param webCallingService - Webrtc Service Layer
|
|
@@ -52,6 +59,19 @@ export default class TaskManager extends EventEmitter {
|
|
|
52
59
|
this.wrapupData = wrapupData;
|
|
53
60
|
}
|
|
54
61
|
|
|
62
|
+
public setAgentId(agentId: string) {
|
|
63
|
+
this.agentId = agentId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Gets the current agent ID
|
|
68
|
+
* @returns {string} The agent ID set for this task manager instance
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
71
|
+
public getAgentId(): string {
|
|
72
|
+
return this.agentId;
|
|
73
|
+
}
|
|
74
|
+
|
|
55
75
|
private handleIncomingWebCall = (call: ICall) => {
|
|
56
76
|
const currentTask = Object.values(this.taskCollection).find(
|
|
57
77
|
(task) => task.data.interaction.mediaType === 'telephony'
|
|
@@ -114,10 +134,10 @@ export default class TaskManager extends EventEmitter {
|
|
|
114
134
|
{
|
|
115
135
|
...payload.data,
|
|
116
136
|
wrapUpRequired:
|
|
117
|
-
payload.data.interaction?.participants?.[
|
|
118
|
-
false,
|
|
137
|
+
payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false,
|
|
119
138
|
},
|
|
120
|
-
this.wrapupData
|
|
139
|
+
this.wrapupData,
|
|
140
|
+
this.agentId
|
|
121
141
|
);
|
|
122
142
|
this.taskCollection[payload.data.interactionId] = task;
|
|
123
143
|
// Condition 1: The state is=new i.e it is a incoming task
|
|
@@ -153,8 +173,9 @@ export default class TaskManager extends EventEmitter {
|
|
|
153
173
|
...payload.data,
|
|
154
174
|
isConsulted: false,
|
|
155
175
|
},
|
|
156
|
-
this.wrapupData
|
|
157
|
-
|
|
176
|
+
this.wrapupData,
|
|
177
|
+
this.agentId
|
|
178
|
+
);
|
|
158
179
|
this.taskCollection[payload.data.interactionId] = task;
|
|
159
180
|
if (
|
|
160
181
|
this.webCallingService.loginOption !== LoginOption.BROWSER ||
|
|
@@ -270,6 +291,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
270
291
|
break;
|
|
271
292
|
case CC_EVENTS.AGENT_CONSULTING:
|
|
272
293
|
// Received when agent is in an active consult state
|
|
294
|
+
// TODO: Check if we can use backend consult state instead of isConsulted
|
|
273
295
|
task = this.updateTaskData(task, payload.data);
|
|
274
296
|
if (task.data.isConsulted) {
|
|
275
297
|
// Fire only if you are the agent who received the consult request
|
|
@@ -323,6 +345,102 @@ export default class TaskManager extends EventEmitter {
|
|
|
323
345
|
task = this.updateTaskData(task, payload.data);
|
|
324
346
|
task.emit(TASK_EVENTS.TASK_RECORDING_RESUME_FAILED, task);
|
|
325
347
|
break;
|
|
348
|
+
case CC_EVENTS.AGENT_CONSULT_CONFERENCING:
|
|
349
|
+
// Conference is being established - update task state and emit establishing event
|
|
350
|
+
task = this.updateTaskData(task, payload.data);
|
|
351
|
+
task.emit(TASK_EVENTS.TASK_CONFERENCE_ESTABLISHING, task);
|
|
352
|
+
break;
|
|
353
|
+
case CC_EVENTS.AGENT_CONSULT_CONFERENCED:
|
|
354
|
+
// Conference started successfully - update task state and emit event
|
|
355
|
+
task = this.updateTaskData(task, payload.data);
|
|
356
|
+
task.emit(TASK_EVENTS.TASK_CONFERENCE_STARTED, task);
|
|
357
|
+
break;
|
|
358
|
+
case CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED:
|
|
359
|
+
// Conference failed - update task state and emit failure event
|
|
360
|
+
task = this.updateTaskData(task, payload.data);
|
|
361
|
+
task.emit(TASK_EVENTS.TASK_CONFERENCE_FAILED, task);
|
|
362
|
+
break;
|
|
363
|
+
case CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED:
|
|
364
|
+
// Conference ended - update task state and emit event
|
|
365
|
+
task = this.updateTaskData(task, payload.data);
|
|
366
|
+
if (
|
|
367
|
+
!task ||
|
|
368
|
+
isPrimary(task, this.agentId) ||
|
|
369
|
+
isParticipantInMainInteraction(task, this.agentId)
|
|
370
|
+
) {
|
|
371
|
+
LoggerProxy.log('Primary or main interaction participant leaving conference');
|
|
372
|
+
} else {
|
|
373
|
+
this.removeTaskFromCollection(task);
|
|
374
|
+
}
|
|
375
|
+
task?.emit(TASK_EVENTS.TASK_CONFERENCE_ENDED, task);
|
|
376
|
+
break;
|
|
377
|
+
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
|
+
task = this.updateTaskData(task, {
|
|
385
|
+
...payload.data,
|
|
386
|
+
isConferenceInProgress: getIsConferenceInProgress(simulatedTaskForJoin),
|
|
387
|
+
});
|
|
388
|
+
task.emit(TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
case CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE: {
|
|
392
|
+
// Conference ended - update task state and emit event
|
|
393
|
+
// Pre-calculate isConferenceInProgress with updated data to avoid double update
|
|
394
|
+
const simulatedTaskForLeft = {
|
|
395
|
+
...task,
|
|
396
|
+
data: {...task.data, ...payload.data},
|
|
397
|
+
};
|
|
398
|
+
task = this.updateTaskData(task, {
|
|
399
|
+
...payload.data,
|
|
400
|
+
isConferenceInProgress: getIsConferenceInProgress(simulatedTaskForLeft),
|
|
401
|
+
});
|
|
402
|
+
if (checkParticipantNotInInteraction(task, this.agentId)) {
|
|
403
|
+
if (
|
|
404
|
+
isParticipantInMainInteraction(task, this.agentId) ||
|
|
405
|
+
isPrimary(task, this.agentId)
|
|
406
|
+
) {
|
|
407
|
+
LoggerProxy.log('Primary or main interaction participant leaving conference');
|
|
408
|
+
} else {
|
|
409
|
+
this.removeTaskFromCollection(task);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
task.emit(TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
case CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED:
|
|
416
|
+
// Conference exit failed - update task state and emit failure event
|
|
417
|
+
task = this.updateTaskData(task, payload.data);
|
|
418
|
+
task.emit(TASK_EVENTS.TASK_PARTICIPANT_LEFT_FAILED, task);
|
|
419
|
+
break;
|
|
420
|
+
case CC_EVENTS.AGENT_CONSULT_CONFERENCE_END_FAILED:
|
|
421
|
+
// Conference end failed - update task state with error details and emit failure event
|
|
422
|
+
task = this.updateTaskData(task, payload.data);
|
|
423
|
+
task.emit(TASK_EVENTS.TASK_CONFERENCE_END_FAILED, task);
|
|
424
|
+
break;
|
|
425
|
+
case CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED:
|
|
426
|
+
// Conference was transferred - update task state and emit transfer success event
|
|
427
|
+
// Note: Backend should provide hasLeft and wrapUpRequired status
|
|
428
|
+
task = this.updateTaskData(task, payload.data);
|
|
429
|
+
task.emit(TASK_EVENTS.TASK_CONFERENCE_TRANSFERRED, task);
|
|
430
|
+
break;
|
|
431
|
+
case CC_EVENTS.AGENT_CONFERENCE_TRANSFER_FAILED:
|
|
432
|
+
// Conference transfer failed - update task state with error details and emit failure event
|
|
433
|
+
task = this.updateTaskData(task, payload.data);
|
|
434
|
+
task.emit(TASK_EVENTS.TASK_CONFERENCE_TRANSFER_FAILED, task);
|
|
435
|
+
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
|
+
case CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY:
|
|
441
|
+
// Post-call activity for participant - update task state with activity details
|
|
442
|
+
task = this.updateTaskData(task, payload.data);
|
|
443
|
+
break;
|
|
326
444
|
default:
|
|
327
445
|
break;
|
|
328
446
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* eslint-disable import/prefer-default-export */
|
|
2
|
+
import {ITask} from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Determines if the given agent is the primary agent (owner) of the task
|
|
6
|
+
* @param task - The task to check
|
|
7
|
+
* @param agentId - The agent ID to check for primary status
|
|
8
|
+
* @returns true if the agent is the primary agent, false otherwise
|
|
9
|
+
*/
|
|
10
|
+
export const isPrimary = (task: ITask, agentId: string): boolean => {
|
|
11
|
+
if (!task.data?.interaction?.owner) {
|
|
12
|
+
// Fall back to checking data.agentId when owner is not set
|
|
13
|
+
return task.data.agentId === agentId;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return task.data.interaction.owner === agentId;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Checks if the given agent is a participant in the main interaction (mainCall)
|
|
21
|
+
* @param task - The task to check
|
|
22
|
+
* @param agentId - The agent ID to check for participation
|
|
23
|
+
* @returns true if the agent is a participant in the main interaction, false otherwise
|
|
24
|
+
*/
|
|
25
|
+
export const isParticipantInMainInteraction = (task: ITask, agentId: string): boolean => {
|
|
26
|
+
if (!task?.data?.interaction?.media) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return Object.values(task.data.interaction.media).some(
|
|
31
|
+
(mediaObj) =>
|
|
32
|
+
mediaObj && mediaObj.mType === 'mainCall' && mediaObj.participants?.includes(agentId)
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Checks if the given agent is not in the interaction or has left the interaction
|
|
38
|
+
* @param task - The task to check
|
|
39
|
+
* @param agentId - The agent ID to check
|
|
40
|
+
* @returns true if the agent is not in the interaction or has left, false otherwise
|
|
41
|
+
*/
|
|
42
|
+
export const checkParticipantNotInInteraction = (task: ITask, agentId: string): boolean => {
|
|
43
|
+
if (!task?.data?.interaction?.participants) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
const {data} = task;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
!(agentId in data.interaction.participants) ||
|
|
50
|
+
(agentId in data.interaction.participants && data.interaction.participants[agentId].hasLeft)
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Determines if a conference is currently in progress based on the number of active agent participants
|
|
56
|
+
* @param task - The task to check for conference status
|
|
57
|
+
* @returns true if there are 2 or more active agent participants in the main call, false otherwise
|
|
58
|
+
*/
|
|
59
|
+
export const getIsConferenceInProgress = (task: ITask): boolean => {
|
|
60
|
+
const mediaMainCall = task?.data?.interaction?.media?.[task?.data?.interactionId];
|
|
61
|
+
const participantsInMainCall = new Set(mediaMainCall?.participants);
|
|
62
|
+
const participants = task?.data?.interaction?.participants;
|
|
63
|
+
|
|
64
|
+
const agentParticipants = new Set();
|
|
65
|
+
if (participantsInMainCall.size > 0) {
|
|
66
|
+
participantsInMainCall.forEach((participantId: string) => {
|
|
67
|
+
const participant = participants?.[participantId];
|
|
68
|
+
if (
|
|
69
|
+
participant &&
|
|
70
|
+
participant.pType !== 'Customer' &&
|
|
71
|
+
participant.pType !== 'Supervisor' &&
|
|
72
|
+
!participant.hasLeft &&
|
|
73
|
+
participant.pType !== 'VVA'
|
|
74
|
+
) {
|
|
75
|
+
agentParticipants.add(participantId);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return agentParticipants.size >= 2;
|
|
81
|
+
};
|
|
@@ -17,9 +17,31 @@ export const PAUSE = '/record/pause';
|
|
|
17
17
|
export const RESUME = '/record/resume';
|
|
18
18
|
export const WRAPUP = '/wrapup';
|
|
19
19
|
export const END = '/end';
|
|
20
|
+
export const CONSULT_CONFERENCE = '/consult/conference';
|
|
21
|
+
export const CONFERENCE_EXIT = '/conference/exit';
|
|
22
|
+
export const CONFERENCE_TRANSFER = '/conference/transfer';
|
|
20
23
|
export const TASK_MANAGER_FILE = 'taskManager';
|
|
21
24
|
export const TASK_FILE = 'task';
|
|
22
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Task data field names that should be preserved during reconciliation
|
|
28
|
+
* These fields are retained even if not present in new data during updates
|
|
29
|
+
*/
|
|
30
|
+
export const PRESERVED_TASK_DATA_FIELDS = {
|
|
31
|
+
/** Indicates if the task is in consultation state */
|
|
32
|
+
IS_CONSULTED: 'isConsulted',
|
|
33
|
+
/** Indicates if wrap-up is required for this task */
|
|
34
|
+
WRAP_UP_REQUIRED: 'wrapUpRequired',
|
|
35
|
+
/** Indicates if a conference is currently in progress (2+ active agents) */
|
|
36
|
+
IS_CONFERENCE_IN_PROGRESS: 'isConferenceInProgress',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Array of task data field names that should not be deleted during reconciliation
|
|
41
|
+
* Used by reconcileData method to preserve important task state fields
|
|
42
|
+
*/
|
|
43
|
+
export const KEYS_TO_NOT_DELETE: string[] = Object.values(PRESERVED_TASK_DATA_FIELDS);
|
|
44
|
+
|
|
23
45
|
// METHOD NAMES
|
|
24
46
|
export const METHODS = {
|
|
25
47
|
// Task class methods
|
|
@@ -36,6 +58,9 @@ export const METHODS = {
|
|
|
36
58
|
END_CONSULT: 'endConsult',
|
|
37
59
|
TRANSFER: 'transfer',
|
|
38
60
|
CONSULT_TRANSFER: 'consultTransfer',
|
|
61
|
+
CONSULT_CONFERENCE: 'consultConference',
|
|
62
|
+
EXIT_CONFERENCE: 'exitConference',
|
|
63
|
+
TRANSFER_CONFERENCE: 'transferConference',
|
|
39
64
|
UPDATE_TASK_DATA: 'updateTaskData',
|
|
40
65
|
RECONCILE_DATA: 'reconcileData',
|
|
41
66
|
|
|
@@ -17,6 +17,9 @@ import {
|
|
|
17
17
|
TRANSFER,
|
|
18
18
|
UNHOLD,
|
|
19
19
|
WRAPUP,
|
|
20
|
+
CONSULT_CONFERENCE,
|
|
21
|
+
CONFERENCE_EXIT,
|
|
22
|
+
CONFERENCE_TRANSFER,
|
|
20
23
|
} from './constants';
|
|
21
24
|
import * as Contact from './types';
|
|
22
25
|
import {DESTINATION_TYPE} from './types';
|
|
@@ -425,5 +428,82 @@ export default function routingContact(aqm: AqmReqs) {
|
|
|
425
428
|
errId: 'Service.aqm.task.cancelCtq',
|
|
426
429
|
},
|
|
427
430
|
})),
|
|
431
|
+
|
|
432
|
+
/*
|
|
433
|
+
* Start consult conference
|
|
434
|
+
*/
|
|
435
|
+
consultConference: aqm.req(
|
|
436
|
+
(p: {interactionId: string; data: Contact.ConsultConferenceData}) => ({
|
|
437
|
+
url: `${TASK_API}${p.interactionId}${CONSULT_CONFERENCE}`,
|
|
438
|
+
data: p.data,
|
|
439
|
+
host: WCC_API_GATEWAY,
|
|
440
|
+
err,
|
|
441
|
+
notifSuccess: {
|
|
442
|
+
bind: {
|
|
443
|
+
type: TASK_MESSAGE_TYPE,
|
|
444
|
+
data: {
|
|
445
|
+
type: [CC_EVENTS.AGENT_CONSULT_CONFERENCED, CC_EVENTS.AGENT_CONSULT_CONFERENCING],
|
|
446
|
+
interactionId: p.interactionId,
|
|
447
|
+
}, // any of the two events can be received for API success event
|
|
448
|
+
},
|
|
449
|
+
msg: {} as Contact.AgentContact,
|
|
450
|
+
},
|
|
451
|
+
notifFail: {
|
|
452
|
+
bind: {
|
|
453
|
+
type: TASK_MESSAGE_TYPE,
|
|
454
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED},
|
|
455
|
+
},
|
|
456
|
+
errId: 'Service.aqm.task.consultConference',
|
|
457
|
+
},
|
|
458
|
+
})
|
|
459
|
+
),
|
|
460
|
+
|
|
461
|
+
/*
|
|
462
|
+
* Exit conference
|
|
463
|
+
*/
|
|
464
|
+
exitConference: aqm.req((p: {interactionId: string}) => ({
|
|
465
|
+
url: `${TASK_API}${p.interactionId}${CONFERENCE_EXIT}`,
|
|
466
|
+
data: {},
|
|
467
|
+
host: WCC_API_GATEWAY,
|
|
468
|
+
err,
|
|
469
|
+
notifSuccess: {
|
|
470
|
+
bind: {
|
|
471
|
+
type: TASK_MESSAGE_TYPE,
|
|
472
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, interactionId: p.interactionId},
|
|
473
|
+
},
|
|
474
|
+
msg: {} as Contact.AgentContact,
|
|
475
|
+
},
|
|
476
|
+
notifFail: {
|
|
477
|
+
bind: {
|
|
478
|
+
type: TASK_MESSAGE_TYPE,
|
|
479
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED}, // to be finalized
|
|
480
|
+
},
|
|
481
|
+
errId: 'Service.aqm.task.consultConference',
|
|
482
|
+
},
|
|
483
|
+
})),
|
|
484
|
+
|
|
485
|
+
/*
|
|
486
|
+
* Transfer conference
|
|
487
|
+
*/
|
|
488
|
+
conferenceTransfer: aqm.req((p: {interactionId: string}) => ({
|
|
489
|
+
url: `${TASK_API}${p.interactionId}${CONFERENCE_TRANSFER}`,
|
|
490
|
+
data: {},
|
|
491
|
+
host: WCC_API_GATEWAY,
|
|
492
|
+
err,
|
|
493
|
+
notifSuccess: {
|
|
494
|
+
bind: {
|
|
495
|
+
type: TASK_MESSAGE_TYPE,
|
|
496
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, interactionId: p.interactionId},
|
|
497
|
+
},
|
|
498
|
+
msg: {} as Contact.AgentContact,
|
|
499
|
+
},
|
|
500
|
+
notifFail: {
|
|
501
|
+
bind: {
|
|
502
|
+
type: TASK_MESSAGE_TYPE,
|
|
503
|
+
data: {type: CC_EVENTS.AGENT_CONFERENCE_TRANSFER_FAILED},
|
|
504
|
+
},
|
|
505
|
+
errId: 'Service.aqm.task.consultConference',
|
|
506
|
+
},
|
|
507
|
+
})),
|
|
428
508
|
};
|
|
429
509
|
}
|