@webex/contact-center 3.12.0-next.7 → 3.12.0-next.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/cc.js +212 -23
  2. package/dist/cc.js.map +1 -1
  3. package/dist/constants.js +2 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/metrics/behavioral-events.js +26 -0
  6. package/dist/metrics/behavioral-events.js.map +1 -1
  7. package/dist/metrics/constants.js +4 -0
  8. package/dist/metrics/constants.js.map +1 -1
  9. package/dist/services/config/Util.js +1 -1
  10. package/dist/services/config/Util.js.map +1 -1
  11. package/dist/services/config/constants.js +1 -1
  12. package/dist/services/config/constants.js.map +1 -1
  13. package/dist/services/config/types.js +4 -0
  14. package/dist/services/config/types.js.map +1 -1
  15. package/dist/services/core/Err.js.map +1 -1
  16. package/dist/services/core/Utils.js +37 -9
  17. package/dist/services/core/Utils.js.map +1 -1
  18. package/dist/services/task/TaskManager.js +94 -8
  19. package/dist/services/task/TaskManager.js.map +1 -1
  20. package/dist/services/task/constants.js +3 -1
  21. package/dist/services/task/constants.js.map +1 -1
  22. package/dist/services/task/dialer.js +78 -0
  23. package/dist/services/task/dialer.js.map +1 -1
  24. package/dist/services/task/index.js +7 -2
  25. package/dist/services/task/index.js.map +1 -1
  26. package/dist/services/task/types.js +56 -0
  27. package/dist/services/task/types.js.map +1 -1
  28. package/dist/types/cc.d.ts +61 -0
  29. package/dist/types/constants.d.ts +2 -0
  30. package/dist/types/metrics/constants.d.ts +4 -0
  31. package/dist/types/services/config/types.d.ts +10 -1
  32. package/dist/types/services/core/Err.d.ts +4 -0
  33. package/dist/types/services/core/Utils.d.ts +10 -3
  34. package/dist/types/services/task/constants.d.ts +2 -0
  35. package/dist/types/services/task/dialer.d.ts +30 -0
  36. package/dist/types/services/task/types.d.ts +65 -1
  37. package/dist/types/types.d.ts +2 -0
  38. package/dist/types.js.map +1 -1
  39. package/dist/webex.js +1 -1
  40. package/package.json +9 -9
  41. package/src/cc.ts +270 -24
  42. package/src/constants.ts +2 -0
  43. package/src/metrics/behavioral-events.ts +28 -0
  44. package/src/metrics/constants.ts +4 -0
  45. package/src/services/config/Util.ts +1 -1
  46. package/src/services/config/constants.ts +1 -1
  47. package/src/services/config/types.ts +6 -1
  48. package/src/services/core/Err.ts +2 -0
  49. package/src/services/core/Utils.ts +43 -8
  50. package/src/services/task/TaskManager.ts +106 -22
  51. package/src/services/task/constants.ts +2 -0
  52. package/src/services/task/dialer.ts +80 -0
  53. package/src/services/task/index.ts +7 -2
  54. package/src/services/task/types.ts +70 -0
  55. package/src/types.ts +2 -0
  56. package/test/unit/spec/cc.ts +210 -20
  57. package/test/unit/spec/services/config/index.ts +3 -3
  58. package/test/unit/spec/services/core/Utils.ts +90 -7
  59. package/test/unit/spec/services/task/TaskManager.ts +252 -7
  60. package/test/unit/spec/services/task/dialer.ts +190 -0
  61. package/test/unit/spec/services/task/index.ts +21 -0
  62. package/umd/contact-center.min.js +2 -2
  63. package/umd/contact-center.min.js.map +1 -1
@@ -449,6 +449,34 @@ const eventTaxonomyMap: Record<string, BehavioralEventTaxonomy> = {
449
449
  target: 'campaign_preview_accept',
450
450
  verb: 'fail',
451
451
  },
452
+
453
+ // Campaign Preview Skip API Events
454
+ [METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS]: {
455
+ product,
456
+ agent: 'user',
457
+ target: 'campaign_preview_skip',
458
+ verb: 'complete',
459
+ },
460
+ [METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED]: {
461
+ product,
462
+ agent: 'user',
463
+ target: 'campaign_preview_skip',
464
+ verb: 'fail',
465
+ },
466
+
467
+ // Campaign Preview Remove API Events
468
+ [METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS]: {
469
+ product,
470
+ agent: 'user',
471
+ target: 'campaign_preview_remove',
472
+ verb: 'complete',
473
+ },
474
+ [METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED]: {
475
+ product,
476
+ agent: 'user',
477
+ target: 'campaign_preview_remove',
478
+ verb: 'fail',
479
+ },
452
480
  };
453
481
 
454
482
  /**
@@ -163,6 +163,10 @@ export const METRIC_EVENT_NAMES = {
163
163
  // Campaign Preview API Events
164
164
  CAMPAIGN_PREVIEW_ACCEPT_SUCCESS: 'Campaign Preview Accept Success',
165
165
  CAMPAIGN_PREVIEW_ACCEPT_FAILED: 'Campaign Preview Accept Failed',
166
+ CAMPAIGN_PREVIEW_SKIP_SUCCESS: 'Campaign Preview Skip Success',
167
+ CAMPAIGN_PREVIEW_SKIP_FAILED: 'Campaign Preview Skip Failed',
168
+ CAMPAIGN_PREVIEW_REMOVE_SUCCESS: 'Campaign Preview Remove Success',
169
+ CAMPAIGN_PREVIEW_REMOVE_FAILED: 'Campaign Preview Remove Failed',
166
170
 
167
171
  // AI Assistant transcript events
168
172
  AI_ASSISTANT_SEND_EVENT_SUCCESS: 'AI Assistant Send Event Success',
@@ -189,7 +189,7 @@ function parseAgentConfigs(profileData: {
189
189
 
190
190
  const finalData = {
191
191
  teams: teamData,
192
- defaultDn: userData.defaultDialledNumber,
192
+ defaultDn: userData.deafultDialledNumber,
193
193
  forceDefaultDn: tenantData.forceDefaultDn,
194
194
  forceDefaultDnForAgent: getDefaultAgentDN(agentProfileData.agentDNValidation),
195
195
  regexUS: tenantData.dnDefaultRegex,
@@ -166,7 +166,7 @@ export const endPointMap = {
166
166
  ) =>
167
167
  `organization/${orgId}/v2/auxiliary-code?page=${page}&pageSize=${pageSize}${
168
168
  filter && filter.length > 0 ? `&filter=id=in=(${filter})` : ''
169
- }&attributes=${attributes}`,
169
+ }&attributes=${attributes}&desktopProfileFilter=true`,
170
170
 
171
171
  /**
172
172
  * Gets the endpoint for organization info.
@@ -117,6 +117,10 @@ export const CC_TASK_EVENTS = {
117
117
  CAMPAIGN_CONTACT_UPDATED: 'CampaignContactUpdated',
118
118
  /** Event emitted when accepting a campaign preview contact fails */
119
119
  CAMPAIGN_PREVIEW_ACCEPT_FAILED: 'CampaignPreviewAcceptFailed',
120
+ /** Event emitted when skipping a campaign preview contact fails */
121
+ CAMPAIGN_PREVIEW_SKIP_FAILED: 'CampaignPreviewSkipFailed',
122
+ /** Event emitted when removing a campaign preview contact fails */
123
+ CAMPAIGN_PREVIEW_REMOVE_FAILED: 'CampaignPreviewRemoveFailed',
120
124
  /** Event emitted when a real-time transcript chunk is received */
121
125
  REAL_TIME_TRANSCRIPTION: 'REAL_TIME_TRANSCRIPTION',
122
126
  } as const;
@@ -275,8 +279,9 @@ export type AgentResponse = {
275
279
 
276
280
  /**
277
281
  * The default dialed number of the agent.
282
+ * Note: The API returns this field as "deafultDialledNumber" (with typo).
278
283
  */
279
- defaultDialledNumber?: string;
284
+ deafultDialledNumber?: string;
280
285
  };
281
286
 
282
287
  /**
@@ -39,6 +39,8 @@ export type TaskErrorIds =
39
39
  | {'Service.aqm.task.resumeRecording': Failure}
40
40
  | {'Service.aqm.dialer.startOutdial': Failure}
41
41
  | {'Service.aqm.dialer.acceptPreviewContact': Failure}
42
+ | {'Service.aqm.dialer.skipPreviewContact': Failure}
43
+ | {'Service.aqm.dialer.removePreviewContact': Failure}
42
44
  | {'Service.reqs.generic.failure': {trackingId: string}};
43
45
 
44
46
  export type ReqError =
@@ -49,34 +49,69 @@ const getAgentActionTypeFromTask = (taskData?: TaskData): 'DIAL_NUMBER' | '' =>
49
49
  return isDialNumber || isEntryPointVariant ? 'DIAL_NUMBER' : '';
50
50
  };
51
51
 
52
- // Fallback regex for US/Canada dial numbers when no dial plan entries are configured
53
- export const FALLBACK_DIAL_NUMBER_REGEX = /1[0-9]{3}[2-9][0-9]{6}([,]{1,10}[0-9]+){0,1}/;
52
+ /**
53
+ * Strips characters defined in the dial plan entry from the input string.
54
+ *
55
+ * @param input - The dial number to sanitize
56
+ * @param strippedChars - String of characters to remove from the input
57
+ * @returns The sanitized input with specified characters removed
58
+ */
59
+ export const stripDialPlanChars = (input: string, strippedChars: string): string => {
60
+ if (!strippedChars) {
61
+ return input;
62
+ }
63
+
64
+ const charsToStrip = new Set(strippedChars.split(''));
65
+
66
+ return input
67
+ .split('')
68
+ .filter((c) => !charsToStrip.has(c))
69
+ .join('');
70
+ };
54
71
 
55
72
  /**
56
73
  * Validates a dial number against the provided dial plan regex patterns.
57
74
  * A number is valid if it matches at least one regex pattern in the dial plans.
58
- * Falls back to US/Canada regex validation if no dial plan entries are configured.
75
+ * Skips validation when no dial plan entries are configured, deferring to the server.
59
76
  *
60
77
  * @param input - The dial number to validate
61
78
  * @param dialPlanEntries - Array of dial plan entries containing regex patterns
62
- * @returns true if the input matches at least one dial plan regex pattern, false otherwise
79
+ * @returns true if the input matches at least one dial plan regex pattern or no entries are configured, false otherwise
63
80
  */
64
81
  export const isValidDialNumber = (
65
82
  input: string,
66
83
  dialPlanEntries: DialPlan['dialPlanEntity']
67
84
  ): boolean => {
85
+ if (!input) {
86
+ LoggerProxy.warn('Dial number is empty or undefined.', {
87
+ module: 'Utils',
88
+ method: 'isValidDialNumber',
89
+ });
90
+
91
+ return false;
92
+ }
93
+
68
94
  if (!dialPlanEntries || dialPlanEntries.length === 0) {
69
- LoggerProxy.info('No dial plan entries found. Falling back to US number validation.');
95
+ LoggerProxy.log(
96
+ 'No dial plan entries found. Skipping client-side validation, deferring to server.',
97
+ {module: 'Utils', method: 'isValidDialNumber'}
98
+ );
70
99
 
71
- return FALLBACK_DIAL_NUMBER_REGEX.test(input);
100
+ return true;
72
101
  }
73
102
 
74
103
  return dialPlanEntries.some((entry) => {
75
104
  try {
105
+ const sanitizedInput = stripDialPlanChars(input, entry.strippedChars);
76
106
  const regex = new RegExp(entry.regex);
77
107
 
78
- return regex.test(input);
79
- } catch {
108
+ return regex.test(sanitizedInput);
109
+ } catch (e) {
110
+ LoggerProxy.warn(`Failed to validate dial number against entry "${entry.name}": ${e}`, {
111
+ module: 'Utils',
112
+ method: 'isValidDialNumber',
113
+ });
114
+
80
115
  return false;
81
116
  }
82
117
  });
@@ -301,6 +301,8 @@ export default class TaskManager extends EventEmitter {
301
301
  task = this.updateTaskData(task, payload.data);
302
302
  task.emit(TASK_EVENTS.TASK_ASSIGNED, task);
303
303
  }
304
+ // We emit the event to the task manager to send the task data to the instance without mobius registration in case of multi login with desktop login
305
+ this.emit(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
304
306
  break;
305
307
  case CC_EVENTS.AGENT_CONTACT_UNASSIGNED:
306
308
  task = this.updateTaskData(task, {
@@ -312,14 +314,11 @@ export default class TaskManager extends EventEmitter {
312
314
  case CC_EVENTS.AGENT_CONTACT_OFFER_RONA:
313
315
  case CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED:
314
316
  case CC_EVENTS.AGENT_INVITE_FAILED: {
315
- LoggerProxy.warn(
316
- `[DEBUG-CAMPAIGN-CLEAR] Task removal triggered by ${payload.data.type}, interactionId=${payload.data.interactionId}, taskType=${task?.data?.type}`,
317
- {
318
- module: TASK_MANAGER_FILE,
319
- method: METHODS.REGISTER_TASK_LISTENERS,
320
- interactionId: payload.data.interactionId,
321
- }
322
- );
317
+ LoggerProxy.info(`Task removal triggered by ${payload.data.type}`, {
318
+ module: TASK_MANAGER_FILE,
319
+ method: METHODS.REGISTER_TASK_LISTENERS,
320
+ interactionId: payload.data.interactionId,
321
+ });
323
322
  task = this.updateTaskData(task, payload.data);
324
323
 
325
324
  const eventTypeToMetricMap: Record<string, keyof typeof METRIC_EVENT_NAMES> = {
@@ -343,19 +342,31 @@ export default class TaskManager extends EventEmitter {
343
342
  break;
344
343
  }
345
344
  case CC_EVENTS.CONTACT_ENDED:
346
- // Update task data
345
+ // Update task data.
347
346
  if (task) {
348
- LoggerProxy.warn(
349
- `[DEBUG-CAMPAIGN-CLEAR] CONTACT_ENDED, interactionId=${payload.data.interactionId}, taskType=${task?.data?.type}, state=${task?.data?.interaction?.state}`,
350
- {
351
- module: TASK_MANAGER_FILE,
352
- method: METHODS.REGISTER_TASK_LISTENERS,
353
- interactionId: payload.data.interactionId,
354
- }
347
+ LoggerProxy.info(`Contact ended for interaction`, {
348
+ module: TASK_MANAGER_FILE,
349
+ method: METHODS.REGISTER_TASK_LISTENERS,
350
+ interactionId: payload.data.interactionId,
351
+ });
352
+
353
+ // Campaign preview tasks should never trigger wrapup on ContactEnded —
354
+ // they are terminal cleanup events. For all other tasks, derive
355
+ // wrapUpRequired from agentsPendingWrapUp as before.
356
+ const CAMPAIGN_OUTBOUND_TYPES = [
357
+ 'STANDARD_PREVIEW_CAMPAIGN',
358
+ 'DIRECT_PREVIEW_CAMPAIGN',
359
+ ];
360
+ const isCampaignPreview = CAMPAIGN_OUTBOUND_TYPES.includes(
361
+ task.data?.interaction?.outboundType ?? ''
355
362
  );
363
+ const wrapUpRequired = isCampaignPreview
364
+ ? false
365
+ : payload.data.agentsPendingWrapUp?.includes(this.agentId) || false;
366
+
356
367
  task = this.updateTaskData(task, {
357
368
  ...payload.data,
358
- wrapUpRequired: payload.data.agentsPendingWrapUp?.includes(this.agentId) || false,
369
+ wrapUpRequired,
359
370
  });
360
371
 
361
372
  // Handle cleanup based on whether task should be deleted
@@ -364,12 +375,83 @@ export default class TaskManager extends EventEmitter {
364
375
  task?.emit(TASK_EVENTS.TASK_END, task);
365
376
  }
366
377
  break;
367
- case CC_EVENTS.CAMPAIGN_CONTACT_UPDATED:
368
- // CampaignContactUpdated is a non-terminal event (intermediate update during accept).
369
- // Only update the task data do NOT remove the task or emit TASK_END.
370
- // Task cleanup is handled by CONTACT_ENDED or other terminal events.
378
+ case CC_EVENTS.CAMPAIGN_CONTACT_UPDATED: {
379
+ // CampaignContactUpdated is a non-terminal event (e.g., next contact after skip/remove).
380
+ // Update the task data and emit an event so consumers can react to the updated contact.
381
+ // Do NOT remove the task or emit TASK_END — cleanup is handled by CONTACT_ENDED.
371
382
  if (task) {
372
- task = this.updateTaskData(task, payload.data);
383
+ // Carry forward campaign preview fields from existing task data since the updated
384
+ // contact payload may not include them, and reconcileData would delete them.
385
+ const existingCpd = task.data?.interaction?.callProcessingDetails;
386
+ const updatedData = {...payload.data};
387
+
388
+ if (existingCpd) {
389
+ const campaignFields = {
390
+ ...(existingCpd.campaignPreviewAutoAction && {
391
+ campaignPreviewAutoAction: existingCpd.campaignPreviewAutoAction,
392
+ }),
393
+ ...(existingCpd.campaignPreviewOfferTimeout && {
394
+ campaignPreviewOfferTimeout: existingCpd.campaignPreviewOfferTimeout,
395
+ }),
396
+ ...(existingCpd.campaignPreviewSkipDisabled && {
397
+ campaignPreviewSkipDisabled: existingCpd.campaignPreviewSkipDisabled,
398
+ }),
399
+ ...(existingCpd.campaignPreviewRemoveDisabled && {
400
+ campaignPreviewRemoveDisabled: existingCpd.campaignPreviewRemoveDisabled,
401
+ }),
402
+ };
403
+
404
+ if (!updatedData.interaction) {
405
+ updatedData.interaction = {} as typeof updatedData.interaction;
406
+ }
407
+
408
+ updatedData.interaction = {
409
+ ...updatedData.interaction,
410
+ callProcessingDetails: {
411
+ ...campaignFields,
412
+ ...(updatedData.interaction.callProcessingDetails || {}),
413
+ } as typeof existingCpd,
414
+ };
415
+ }
416
+
417
+ LoggerProxy.log('Campaign contact updated - carrying forward preview fields', {
418
+ module: TASK_MANAGER_FILE,
419
+ method: METHODS.REGISTER_TASK_LISTENERS,
420
+ interactionId: payload.data.interactionId,
421
+ data: {
422
+ hasCpd: !!updatedData.interaction?.callProcessingDetails,
423
+ autoAction:
424
+ updatedData.interaction?.callProcessingDetails?.campaignPreviewAutoAction,
425
+ skipDisabled:
426
+ updatedData.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled,
427
+ removeDisabled:
428
+ updatedData.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled,
429
+ },
430
+ });
431
+
432
+ task = this.updateTaskData(task, updatedData);
433
+ task.emit(TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED, task);
434
+ }
435
+ break;
436
+ }
437
+ case CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED:
438
+ if (task) {
439
+ // Failure payloads are sparse (no interaction field). Spread existing
440
+ // task data first so reconcileData doesn't delete interaction/cpd.
441
+ task = this.updateTaskData(task, {...task.data, ...payload.data});
442
+ task.emit(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED, task);
443
+ }
444
+ break;
445
+ case CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED:
446
+ if (task) {
447
+ task = this.updateTaskData(task, {...task.data, ...payload.data});
448
+ task.emit(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED, task);
449
+ }
450
+ break;
451
+ case CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED:
452
+ if (task) {
453
+ task = this.updateTaskData(task, {...task.data, ...payload.data});
454
+ task.emit(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED, task);
373
455
  }
374
456
  break;
375
457
  case CC_EVENTS.CONTACT_MERGED:
@@ -426,6 +508,8 @@ export default class TaskManager extends EventEmitter {
426
508
  // Fire only if you are the agent who initiated the consult
427
509
  task.emit(TASK_EVENTS.TASK_CONSULTING, task);
428
510
  }
511
+ // Also hydrate multi-login listeners when consulting state is received.
512
+ this.emit(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
429
513
  break;
430
514
  case CC_EVENTS.AGENT_CONSULT_FAILED:
431
515
  // This can only be received by the agent who initiated the consult.
@@ -24,6 +24,8 @@ export const CONFERENCE_EXIT = '/conference/exit';
24
24
  export const CONFERENCE_TRANSFER = '/conference/transfer';
25
25
  export const DIALER_API = '/v1/dialer';
26
26
  export const CAMPAIGN_PREVIEW_ACCEPT = '/accept';
27
+ export const CAMPAIGN_PREVIEW_SKIP = '/skip';
28
+ export const CAMPAIGN_PREVIEW_REMOVE = '/remove';
27
29
  /** 80-second timeout for accepting preview contact (outbound call setup takes longer than default 20s) */
28
30
  export const TIMEOUT_PREVIEW_ACCEPT = 80000;
29
31
  export const TASK_MANAGER_FILE = 'taskManager';
@@ -7,6 +7,8 @@ import {
7
7
  TASK_API,
8
8
  DIALER_API,
9
9
  CAMPAIGN_PREVIEW_ACCEPT,
10
+ CAMPAIGN_PREVIEW_SKIP,
11
+ CAMPAIGN_PREVIEW_REMOVE,
10
12
  TIMEOUT_PREVIEW_ACCEPT,
11
13
  } from './constants';
12
14
  import * as Contact from './types';
@@ -103,5 +105,83 @@ export default function aqmDialer(aqm: AqmReqs) {
103
105
  errId: 'Service.aqm.dialer.acceptPreviewContact',
104
106
  },
105
107
  })),
108
+
109
+ /**
110
+ * Skips a campaign preview contact, requesting the next contact from the campaign.
111
+ *
112
+ * @param {Object} p - Parameters object.
113
+ * @param {Contact.PreviewContactPayload} p.data - Payload containing interactionId and campaignId.
114
+ * @returns {Promise<Contact.AgentContact>} A promise that resolves with agent contact on success.
115
+ *
116
+ * Emits:
117
+ * - `CC_EVENTS.CAMPAIGN_CONTACT_UPDATED` or `CC_EVENTS.CONTACT_ENDED` on success
118
+ * - `CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED` on failure
119
+ * @ignore
120
+ */
121
+ skipPreviewContact: aqm.req((p: {data: Contact.PreviewContactPayload}) => ({
122
+ url: `${DIALER_API}/campaign/${encodeURIComponent(p.data.campaignId)}/preview-task/${
123
+ p.data.interactionId
124
+ }${CAMPAIGN_PREVIEW_SKIP}`,
125
+ host: WCC_API_GATEWAY,
126
+ data: {},
127
+ method: HTTP_METHODS.POST,
128
+ err,
129
+ notifSuccess: {
130
+ bind: {
131
+ type: TASK_MESSAGE_TYPE,
132
+ data: {
133
+ type: [CC_EVENTS.CAMPAIGN_CONTACT_UPDATED, CC_EVENTS.CONTACT_ENDED],
134
+ interactionId: p.data.interactionId,
135
+ },
136
+ },
137
+ msg: {} as Contact.AgentContact,
138
+ },
139
+ notifFail: {
140
+ bind: {
141
+ type: TASK_MESSAGE_TYPE,
142
+ data: {type: CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED, campaignId: p.data.campaignId},
143
+ },
144
+ errId: 'Service.aqm.dialer.skipPreviewContact',
145
+ },
146
+ })),
147
+
148
+ /**
149
+ * Removes a campaign preview contact from the campaign list entirely.
150
+ *
151
+ * @param {Object} p - Parameters object.
152
+ * @param {Contact.PreviewContactPayload} p.data - Payload containing interactionId and campaignId.
153
+ * @returns {Promise<Contact.AgentContact>} A promise that resolves with agent contact on success.
154
+ *
155
+ * Emits:
156
+ * - `CC_EVENTS.CAMPAIGN_CONTACT_UPDATED` or `CC_EVENTS.CONTACT_ENDED` on success
157
+ * - `CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED` on failure
158
+ * @ignore
159
+ */
160
+ removePreviewContact: aqm.req((p: {data: Contact.PreviewContactPayload}) => ({
161
+ url: `${DIALER_API}/campaign/${encodeURIComponent(p.data.campaignId)}/preview-task/${
162
+ p.data.interactionId
163
+ }${CAMPAIGN_PREVIEW_REMOVE}`,
164
+ host: WCC_API_GATEWAY,
165
+ data: {},
166
+ method: HTTP_METHODS.POST,
167
+ err,
168
+ notifSuccess: {
169
+ bind: {
170
+ type: TASK_MESSAGE_TYPE,
171
+ data: {
172
+ type: [CC_EVENTS.CAMPAIGN_CONTACT_UPDATED, CC_EVENTS.CONTACT_ENDED],
173
+ interactionId: p.data.interactionId,
174
+ },
175
+ },
176
+ msg: {} as Contact.AgentContact,
177
+ },
178
+ notifFail: {
179
+ bind: {
180
+ type: TASK_MESSAGE_TYPE,
181
+ data: {type: CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED, campaignId: p.data.campaignId},
182
+ },
183
+ errId: 'Service.aqm.dialer.removePreviewContact',
184
+ },
185
+ })),
106
186
  };
107
187
  }
@@ -565,7 +565,10 @@ export default class Task extends EventEmitter implements ITask {
565
565
  METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
566
566
  ]);
567
567
 
568
- const effectiveMediaResourceId = mediaResourceId ?? this.data.mediaResourceId;
568
+ const {mainInteractionId} = this.data.interaction;
569
+ const defaultMediaResourceId =
570
+ this.data.interaction.media[mainInteractionId]?.mediaResourceId;
571
+ const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
569
572
 
570
573
  const response = await this.contact.hold({
571
574
  interactionId: this.data.interactionId,
@@ -599,7 +602,9 @@ export default class Task extends EventEmitter implements ITask {
599
602
  errorData: err.data?.errorData,
600
603
  reasonCode: err.data?.reasonCode,
601
604
  };
602
- const effectiveMediaResourceId = mediaResourceId ?? this.data.mediaResourceId;
605
+ const defaultMediaResourceId =
606
+ this.data.interaction.media[this.data.interaction.mainInteractionId]?.mediaResourceId;
607
+ const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
603
608
 
604
609
  this.metricsManager.trackEvent(
605
610
  METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
@@ -554,6 +554,68 @@ export enum TASK_EVENTS {
554
554
  * ```
555
555
  */
556
556
  TASK_CAMPAIGN_PREVIEW_RESERVATION = 'task:campaignPreviewReservation',
557
+
558
+ /**
559
+ * Triggered when accepting a campaign preview contact fails
560
+ * @example
561
+ * ```typescript
562
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED, (task: ITask) => {
563
+ * console.log('Campaign preview accept failed:', task.data.interactionId);
564
+ * // Handle accept failure
565
+ * });
566
+ * ```
567
+ */
568
+ TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED = 'task:campaignPreviewAcceptFailed',
569
+
570
+ /**
571
+ * Triggered when skipping a campaign preview contact fails
572
+ * @example
573
+ * ```typescript
574
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED, (task: ITask) => {
575
+ * console.log('Campaign preview skip failed:', task.data.interactionId);
576
+ * // Handle skip failure
577
+ * });
578
+ * ```
579
+ */
580
+ TASK_CAMPAIGN_PREVIEW_SKIP_FAILED = 'task:campaignPreviewSkipFailed',
581
+
582
+ /**
583
+ * Triggered when removing a campaign preview contact fails
584
+ * @example
585
+ * ```typescript
586
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED, (task: ITask) => {
587
+ * console.log('Campaign preview remove failed:', task.data.interactionId);
588
+ * // Handle remove failure
589
+ * });
590
+ * ```
591
+ */
592
+ TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED = 'task:campaignPreviewRemoveFailed',
593
+
594
+ /**
595
+ * Triggered when a campaign contact is updated (e.g., after skip or remove, when the next contact is offered)
596
+ * @example
597
+ * ```typescript
598
+ * task.on(TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED, (task: ITask) => {
599
+ * console.log('Campaign contact updated:', task.data.interactionId);
600
+ * // Handle updated campaign contact (e.g., display next contact)
601
+ * });
602
+ * ```
603
+ */
604
+ TASK_CAMPAIGN_CONTACT_UPDATED = 'task:campaignContactUpdated',
605
+
606
+ /**
607
+ * Triggered when a we get accept an incoming web call
608
+ * This is used to send task data to the instance without mobius registration in case of multi login
609
+ * @example
610
+ * ```typescript
611
+ * task.on(TASK_EVENTS.TASK_MULIT_LOGIN_HYDRATE, (task: ITask) => {
612
+ * console.log('Mulit login detected:', task.data.interactionId);
613
+ * // Handle mulit login
614
+ * });
615
+ * ```
616
+ */
617
+
618
+ TASK_MULTI_LOGIN_HYDRATE = 'task:multiLoginHydrate',
557
619
  }
558
620
 
559
621
  /**
@@ -702,6 +764,14 @@ export type Interaction = {
702
764
  fcDesktopView?: string;
703
765
  /** Agent ID who initiated the outdial call */
704
766
  outdialAgentId?: string;
767
+ /** Indicates if the skip action is disabled for campaign preview contacts */
768
+ campaignPreviewSkipDisabled?: string;
769
+ /** Indicates if the remove action is disabled for campaign preview contacts */
770
+ campaignPreviewRemoveDisabled?: string;
771
+ /** Auto-action to perform when campaign preview offer times out (ACCEPT, SKIP, REMOVE) */
772
+ campaignPreviewAutoAction?: string;
773
+ /** Timestamp (ms) when the campaign preview offer expires */
774
+ campaignPreviewOfferTimeout?: string;
705
775
  };
706
776
  /** Main interaction identifier for related interactions */
707
777
  mainInteractionId?: string;
package/src/types.ts CHANGED
@@ -156,6 +156,8 @@ export interface CCPluginConfig {
156
156
  };
157
157
  /** Configuration for the calling client */
158
158
  callingClientConfig: CallingClientConfig;
159
+ /** Whether to skip Mobius/WebRTC registration for browser login flows */
160
+ disableWebRTCRegistration?: boolean;
159
161
  }
160
162
 
161
163
  /**