@webex/contact-center 3.12.0-task-refactor.10 → 3.12.0-task-refactor.12

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 (110) hide show
  1. package/dist/cc.js +345 -82
  2. package/dist/cc.js.map +1 -1
  3. package/dist/config.js +6 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/constants.js +3 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/metrics/behavioral-events.js +39 -0
  8. package/dist/metrics/behavioral-events.js.map +1 -1
  9. package/dist/metrics/constants.js +8 -1
  10. package/dist/metrics/constants.js.map +1 -1
  11. package/dist/services/ApiAiAssistant.js +3 -0
  12. package/dist/services/ApiAiAssistant.js.map +1 -1
  13. package/dist/services/config/Util.js +1 -1
  14. package/dist/services/config/Util.js.map +1 -1
  15. package/dist/services/config/types.js +10 -0
  16. package/dist/services/config/types.js.map +1 -1
  17. package/dist/services/constants.js +1 -0
  18. package/dist/services/constants.js.map +1 -1
  19. package/dist/services/core/Err.js.map +1 -1
  20. package/dist/services/core/Utils.js +57 -6
  21. package/dist/services/core/Utils.js.map +1 -1
  22. package/dist/services/core/aqm-reqs.js +92 -17
  23. package/dist/services/core/aqm-reqs.js.map +1 -1
  24. package/dist/services/core/websocket/WebSocketManager.js +24 -3
  25. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  26. package/dist/services/index.js +1 -0
  27. package/dist/services/index.js.map +1 -1
  28. package/dist/services/task/Task.js +16 -4
  29. package/dist/services/task/Task.js.map +1 -1
  30. package/dist/services/task/TaskManager.js +158 -6
  31. package/dist/services/task/TaskManager.js.map +1 -1
  32. package/dist/services/task/TaskUtils.js +39 -11
  33. package/dist/services/task/TaskUtils.js.map +1 -1
  34. package/dist/services/task/constants.js +7 -1
  35. package/dist/services/task/constants.js.map +1 -1
  36. package/dist/services/task/dialer.js +129 -0
  37. package/dist/services/task/dialer.js.map +1 -1
  38. package/dist/services/task/state-machine/TaskStateMachine.js +30 -3
  39. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  40. package/dist/services/task/state-machine/actions.js +4 -0
  41. package/dist/services/task/state-machine/actions.js.map +1 -1
  42. package/dist/services/task/state-machine/constants.js +3 -0
  43. package/dist/services/task/state-machine/constants.js.map +1 -1
  44. package/dist/services/task/state-machine/guards.js +23 -0
  45. package/dist/services/task/state-machine/guards.js.map +1 -1
  46. package/dist/services/task/state-machine/types.js.map +1 -1
  47. package/dist/services/task/state-machine/uiControlsComputer.js +1 -1
  48. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
  49. package/dist/services/task/types.js +69 -0
  50. package/dist/services/task/types.js.map +1 -1
  51. package/dist/types/cc.d.ts +93 -2
  52. package/dist/types/config.d.ts +6 -0
  53. package/dist/types/constants.d.ts +3 -0
  54. package/dist/types/metrics/constants.d.ts +6 -0
  55. package/dist/types/services/config/types.d.ts +22 -1
  56. package/dist/types/services/core/Err.d.ts +6 -0
  57. package/dist/types/services/core/Utils.d.ts +20 -2
  58. package/dist/types/services/core/aqm-reqs.d.ts +49 -0
  59. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  60. package/dist/types/services/core/websocket/connection-service.d.ts +1 -0
  61. package/dist/types/services/task/Task.d.ts +1 -0
  62. package/dist/types/services/task/TaskUtils.d.ts +13 -0
  63. package/dist/types/services/task/constants.d.ts +6 -0
  64. package/dist/types/services/task/dialer.d.ts +45 -0
  65. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +36 -8
  66. package/dist/types/services/task/state-machine/constants.d.ts +3 -0
  67. package/dist/types/services/task/state-machine/guards.d.ts +2 -0
  68. package/dist/types/services/task/state-machine/types.d.ts +11 -0
  69. package/dist/types/services/task/types.d.ts +85 -1
  70. package/dist/types/types.d.ts +2 -0
  71. package/dist/types.js.map +1 -1
  72. package/dist/webex.js +1 -1
  73. package/package.json +2 -2
  74. package/src/cc.ts +452 -102
  75. package/src/config.ts +6 -0
  76. package/src/constants.ts +3 -0
  77. package/src/metrics/behavioral-events.ts +42 -0
  78. package/src/metrics/constants.ts +9 -1
  79. package/src/services/ApiAiAssistant.ts +3 -0
  80. package/src/services/config/Util.ts +1 -1
  81. package/src/services/config/types.ts +12 -1
  82. package/src/services/constants.ts +1 -0
  83. package/src/services/core/Err.ts +3 -0
  84. package/src/services/core/Utils.ts +68 -5
  85. package/src/services/core/aqm-reqs.ts +100 -22
  86. package/src/services/core/websocket/WebSocketManager.ts +23 -2
  87. package/src/services/index.ts +1 -0
  88. package/src/services/task/Task.ts +22 -4
  89. package/src/services/task/TaskManager.ts +175 -7
  90. package/src/services/task/TaskUtils.ts +33 -1
  91. package/src/services/task/constants.ts +6 -0
  92. package/src/services/task/dialer.ts +136 -1
  93. package/src/services/task/state-machine/TaskStateMachine.ts +37 -5
  94. package/src/services/task/state-machine/actions.ts +4 -0
  95. package/src/services/task/state-machine/constants.ts +3 -0
  96. package/src/services/task/state-machine/guards.ts +30 -0
  97. package/src/services/task/state-machine/types.ts +14 -1
  98. package/src/services/task/state-machine/uiControlsComputer.ts +1 -1
  99. package/src/services/task/types.ts +91 -0
  100. package/src/types.ts +6 -1
  101. package/test/unit/spec/cc.ts +432 -26
  102. package/test/unit/spec/services/config/index.ts +30 -2
  103. package/test/unit/spec/services/core/Utils.ts +367 -1
  104. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +90 -1
  105. package/test/unit/spec/services/core/websocket/connection-service.ts +4 -5
  106. package/test/unit/spec/services/task/TaskManager.ts +2334 -145
  107. package/test/unit/spec/services/task/dialer.ts +372 -96
  108. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +47 -0
  109. package/umd/contact-center.min.js +2 -2
  110. package/umd/contact-center.min.js.map +1 -1
package/dist/cc.js CHANGED
@@ -85,6 +85,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
85
85
  * - `task:established` - Task/call has been connected
86
86
  * - `task:ended` - Task/call has ended
87
87
  * - `task:error` - An error occurred during task handling
88
+ * - `task:campaignPreviewReservation` - Campaign preview contact offered to agent
88
89
  *
89
90
  * @public
90
91
  *
@@ -300,6 +301,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
300
301
  this.$webex.once(_constants.READY, () => {
301
302
  // @ts-ignore
302
303
  this.$config = this.config;
304
+ this.validatePluginConfig();
303
305
 
304
306
  /**
305
307
  * This is used for handling the async requests by sending webex.request and wait for corresponding websocket event.
@@ -311,7 +313,6 @@ class ContactCenter extends _webexCore.WebexPlugin {
311
313
  webex: this.$webex,
312
314
  connectionConfig: this.getConnectionConfig()
313
315
  });
314
- this.services.webSocketManager.on('message', this.handleWebsocketMessage);
315
316
  this.webCallingService = new _WebCallingService.default(this.$webex);
316
317
  this.apiAIAssistant = new _ApiAiAssistant.ApiAIAssistant(this.$webex);
317
318
  this.metricsManager = _MetricsManager.default.getInstance({
@@ -349,6 +350,16 @@ class ContactCenter extends _webexCore.WebexPlugin {
349
350
  this.trigger(_types4.TASK_EVENTS.TASK_HYDRATE, task);
350
351
  };
351
352
 
353
+ /**
354
+ * Handles multi-login hydrate events for SDK instances without Mobius registration
355
+ * @private
356
+ * @param {ITask} task The task object associated with the multi-login hydrate
357
+ */
358
+ handleTaskMultiLoginHydrate = task => {
359
+ // @ts-ignore
360
+ this.trigger(_types4.TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
361
+ };
362
+
352
363
  /**
353
364
  * Handles task merged events when tasks are combined eg: EPDN merge/transfer
354
365
  * @private
@@ -359,6 +370,19 @@ class ContactCenter extends _webexCore.WebexPlugin {
359
370
  this.trigger(_types4.TASK_EVENTS.TASK_MERGED, task);
360
371
  };
361
372
 
373
+ /**
374
+ * Handles campaign preview reservation events when a contact is offered to the agent
375
+ * @private
376
+ * @param {ITask} task The campaign reservation task
377
+ */
378
+ handleCampaignPreviewReservation = task => {
379
+ // @ts-ignore
380
+ this.trigger(_types4.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
381
+ };
382
+ handleRTDWebsocketMessage = event => {
383
+ this.taskManager.handleRealtimeWebsocketEvent(event);
384
+ };
385
+
362
386
  /**
363
387
  * Sets up event listeners for incoming tasks and task hydration
364
388
  * Subscribes to task events from the task manager
@@ -367,7 +391,9 @@ class ContactCenter extends _webexCore.WebexPlugin {
367
391
  incomingTaskListener() {
368
392
  this.taskManager.on(_types4.TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
369
393
  this.taskManager.on(_types4.TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
394
+ this.taskManager.on(_types4.TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
370
395
  this.taskManager.on(_types4.TASK_EVENTS.TASK_MERGED, this.handleTaskMerged);
396
+ this.taskManager.on(_types4.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, this.handleCampaignPreviewReservation);
371
397
  }
372
398
 
373
399
  /**
@@ -407,13 +433,14 @@ class ContactCenter extends _webexCore.WebexPlugin {
407
433
  * ```
408
434
  */
409
435
  async register() {
410
- _loggerProxy.default.info('Starting CC SDK registration', {
436
+ _loggerProxy.default.log('Starting CC SDK registration', {
411
437
  module: _constants.CC_FILE,
412
438
  method: _constants.METHODS.REGISTER
413
439
  });
414
440
  try {
415
441
  this.metricsManager.timeEvent([_constants4.METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_SUCCESS, _constants4.METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED]);
416
442
  this.setupEventListeners();
443
+ this.services.webSocketManager.on('message', this.handleWebsocketMessage);
417
444
  const resp = await this.connectWebsocket();
418
445
  // Ensure 'dn' is always populated from 'defaultDn'
419
446
  resp.dn = resp.defaultDn;
@@ -476,6 +503,8 @@ class ContactCenter extends _webexCore.WebexPlugin {
476
503
  this.metricsManager.timeEvent([_constants4.METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_SUCCESS, _constants4.METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_FAIL]);
477
504
  this.taskManager.off(_types4.TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
478
505
  this.taskManager.off(_types4.TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
506
+ this.taskManager.off(_types4.TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
507
+ this.taskManager.off(_types4.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, this.handleCampaignPreviewReservation);
479
508
  this.taskManager.unregisterIncomingCallEvent();
480
509
  this.services.webSocketManager.off('message', this.handleWebsocketMessage);
481
510
  this.services.rtdWebSocketManager.off('message', this.handleRTDWebsocketMessage);
@@ -499,6 +528,9 @@ class ContactCenter extends _webexCore.WebexPlugin {
499
528
  if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
500
529
  this.services.rtdWebSocketManager.close(false, 'Unregistering the SDK');
501
530
  }
531
+ if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
532
+ this.services.rtdWebSocketManager.close(false, 'Unregistering the RTD websocket');
533
+ }
502
534
 
503
535
  // Clear any cached agent configuration
504
536
  this.agentConfig = null;
@@ -600,80 +632,89 @@ class ContactCenter extends _webexCore.WebexPlugin {
600
632
  * @private
601
633
  */
602
634
  async connectWebsocket() {
603
- _loggerProxy.default.info('Connecting to websocket', {
635
+ _loggerProxy.default.log('Connecting to websocket', {
604
636
  module: _constants.CC_FILE,
605
637
  method: _constants.METHODS.CONNECT_WEBSOCKET
606
638
  });
607
639
  try {
608
- return this.services.webSocketManager.initWebSocket({
640
+ const data = await this.services.webSocketManager.initWebSocket({
609
641
  body: this.getConnectionConfig(),
610
642
  resource: _constants3.SUBSCRIBE_API
611
- }).then(async data => {
612
- const agentId = data.agentId;
613
- const orgId = this.$webex.credentials.getOrgId();
614
- this.agentConfig = await this.services.config.getAgentConfig(orgId, agentId);
615
- _loggerProxy.default.log(`Agent config is fetched successfully`, {
643
+ });
644
+ const agentId = data.agentId;
645
+ const orgId = this.$webex.credentials.getOrgId();
646
+ this.agentConfig = await this.services.config.getAgentConfig(orgId, agentId);
647
+ _loggerProxy.default.log(`Agent config is fetched successfully`, {
648
+ module: _constants.CC_FILE,
649
+ method: _constants.METHODS.CONNECT_WEBSOCKET
650
+ });
651
+ const configFlags = {
652
+ isEndTaskEnabled: this.agentConfig.isEndTaskEnabled,
653
+ isEndConsultEnabled: this.agentConfig.isEndConsultEnabled,
654
+ webRtcEnabled: this.agentConfig.webRtcEnabled,
655
+ autoWrapup: this.agentConfig.wrapUpData?.wrapUpProps?.autoWrapup ?? false,
656
+ aiFeature: this.agentConfig.aiFeature
657
+ };
658
+ this.taskManager.setConfigFlags(configFlags);
659
+ // TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
660
+ this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
661
+ this.taskManager.setAgentId(this.agentConfig.agentId);
662
+ this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
663
+ this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
664
+
665
+ /**
666
+ * RTD websocket currently supports realtime transcripts and suggested responses.
667
+ * Extend this condition when additional AI RTD features are introduced.
668
+ */
669
+ if (this.agentConfig.aiFeature?.realtimeTranscripts?.enable || this.agentConfig.aiFeature?.suggestedResponses?.enable) {
670
+ _loggerProxy.default.info('Connecting to RTD websocket', {
616
671
  module: _constants.CC_FILE,
617
672
  method: _constants.METHODS.CONNECT_WEBSOCKET
618
673
  });
619
- const configFlags = {
620
- isEndTaskEnabled: this.agentConfig.isEndTaskEnabled,
621
- isEndConsultEnabled: this.agentConfig.isEndConsultEnabled,
622
- webRtcEnabled: this.agentConfig.webRtcEnabled,
623
- autoWrapup: this.agentConfig.wrapUpData?.wrapUpProps?.autoWrapup ?? false,
624
- aiFeature: this.agentConfig.aiFeature
625
- };
626
- this.taskManager.setConfigFlags(configFlags);
627
- // TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
628
- this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
629
- this.taskManager.setAgentId(this.agentConfig.agentId);
630
- this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
631
- this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
632
- /**
633
- * RTD websocket currently supports realtime transcripts and suggested responses.
634
- * Extend this condition when additional AI RTD features are introduced.
635
- */
636
- if (this.agentConfig.aiFeature?.realtimeTranscripts?.enable || this.agentConfig.aiFeature?.suggestedResponses?.enable) {
637
- _loggerProxy.default.info('Connecting to RTD websocket', {
674
+ this.services.rtdWebSocketManager.initWebSocket({
675
+ body: this.getConnectionConfig(),
676
+ resource: _constants3.RTD_SUBSCRIBE_API
677
+ }).then(() => {
678
+ _loggerProxy.default.log('RTD websocket connected successfully', {
638
679
  module: _constants.CC_FILE,
639
680
  method: _constants.METHODS.CONNECT_WEBSOCKET
640
681
  });
641
- this.services.rtdWebSocketManager.initWebSocket({
642
- body: this.getConnectionConfig(),
643
- resource: _constants3.RTD_SUBSCRIBE_API
644
- }).then(() => {
645
- _loggerProxy.default.log('RTD websocket connected successfully', {
646
- module: _constants.CC_FILE,
647
- method: _constants.METHODS.CONNECT_WEBSOCKET
648
- });
649
- this.services.rtdWebSocketManager.on('message', this.handleRTDWebsocketMessage);
650
- }).catch(error => {
651
- _loggerProxy.default.error(`Error connecting to RTD websocket ${error}`, {
652
- module: _constants.CC_FILE,
653
- method: _constants.METHODS.CONNECT_WEBSOCKET
654
- });
682
+ this.services.rtdWebSocketManager.on('message', this.handleRTDWebsocketMessage);
683
+ }).catch(error => {
684
+ _loggerProxy.default.error(`Error connecting to RTD websocket ${error}`, {
685
+ module: _constants.CC_FILE,
686
+ method: _constants.METHODS.CONNECT_WEBSOCKET
655
687
  });
656
- }
657
- if (this.agentConfig.webRtcEnabled && this.agentConfig.loginVoiceOptions.includes(_types.LoginOption.BROWSER)) {
658
- this.$webex.internal.mercury.connect().then(() => {
659
- _loggerProxy.default.log('Authentication: webex.internal.mercury.connect successful', {
660
- module: _constants.CC_FILE,
661
- method: _constants.METHODS.CONNECT_WEBSOCKET
662
- });
663
- }).catch(error => {
664
- _loggerProxy.default.error(`Error occurred during mercury.connect() ${error}`, {
665
- module: _constants.CC_FILE,
666
- method: _constants.METHODS.CONNECT_WEBSOCKET
667
- });
688
+ });
689
+ }
690
+ if (this.agentConfig.webRtcEnabled && this.agentConfig.loginVoiceOptions.includes(_types.LoginOption.BROWSER) && !this.isWebRTCRegistrationDisabled()) {
691
+ try {
692
+ await this.$webex.internal.mercury.connect();
693
+ _loggerProxy.default.log('Authentication: webex.internal.mercury.connect successful', {
694
+ module: _constants.CC_FILE,
695
+ method: _constants.METHODS.CONNECT_WEBSOCKET
696
+ });
697
+ } catch (error) {
698
+ _loggerProxy.default.error(`Error occurred during mercury.connect() ${error}`, {
699
+ module: _constants.CC_FILE,
700
+ method: _constants.METHODS.CONNECT_WEBSOCKET
668
701
  });
669
702
  }
670
- if (this.$config && this.$config.allowAutomatedRelogin) {
671
- await this.silentRelogin();
672
- }
673
- return this.agentConfig;
674
- }).catch(error => {
675
- throw error;
676
- });
703
+ } else if (this.isWebRTCRegistrationDisabled()) {
704
+ _loggerProxy.default.info('Skipping Mobius registration because disableWebRTCRegistration is enabled', {
705
+ module: _constants.CC_FILE,
706
+ method: _constants.METHODS.CONNECT_WEBSOCKET
707
+ });
708
+ }
709
+ if (this.$config && this.$config.allowAutomatedRelogin) {
710
+ await this.silentRelogin();
711
+ } else {
712
+ _loggerProxy.default.log('Skipping silent relogin: allowAutomatedRelogin is disabled', {
713
+ module: _constants.CC_FILE,
714
+ method: _constants.METHODS.CONNECT_WEBSOCKET
715
+ });
716
+ }
717
+ return this.agentConfig;
677
718
  } catch (error) {
678
719
  _loggerProxy.default.error(`Error during register: ${error}`, {
679
720
  module: _constants.CC_FILE,
@@ -712,21 +753,27 @@ class ContactCenter extends _webexCore.WebexPlugin {
712
753
  * ```
713
754
  */
714
755
  async stationLogin(data) {
715
- _loggerProxy.default.info('Starting agent station login', {
756
+ const loggerContext = {
716
757
  module: _constants.CC_FILE,
717
758
  method: _constants.METHODS.STATION_LOGIN
718
- });
759
+ };
719
760
  try {
761
+ _loggerProxy.default.log(`Starting agent station login | loginOption: ${data?.loginOption} teamId: ${data?.teamId}`, loggerContext);
720
762
  this.metricsManager.timeEvent([_constants4.METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS, _constants4.METRIC_EVENT_NAMES.STATION_LOGIN_FAILED]);
721
- if (data.loginOption === _types.LoginOption.AGENT_DN && !(0, _Utils.isValidDialNumber)(data.dialNumber)) {
722
- const error = new Error('INVALID_DIAL_NUMBER');
723
- // @ts-ignore - adding custom key to the error object
724
- error.details = {
725
- data: {
726
- reason: 'INVALID_DIAL_NUMBER'
727
- }
728
- };
729
- throw error;
763
+ const dialPlanEntries = this.agentConfig?.dialPlan?.dialPlanEntity ?? [];
764
+ if (data.loginOption === _types.LoginOption.AGENT_DN) {
765
+ _loggerProxy.default.log(`Validating dial number | dialPlanEnabled: ${!!this.agentConfig?.dialPlan} | dialPlanEntryCount: ${dialPlanEntries.length}`, loggerContext);
766
+ if (!(0, _Utils.isValidDialNumber)(data.dialNumber, dialPlanEntries)) {
767
+ _loggerProxy.default.log(`Dial number validation failed | dialNumber: ${data.dialNumber} | dialPlanEntryCount: ${dialPlanEntries.length}`, loggerContext);
768
+ const error = new Error('INVALID_DIAL_NUMBER');
769
+ // @ts-ignore - adding custom key to the error object
770
+ error.details = {
771
+ data: {
772
+ reason: 'INVALID_DIAL_NUMBER'
773
+ }
774
+ };
775
+ throw error;
776
+ }
730
777
  }
731
778
  const loginResponse = await this.services.agent.stationLogin({
732
779
  data: {
@@ -742,8 +789,13 @@ class ContactCenter extends _webexCore.WebexPlugin {
742
789
  auxCodeId: _constants.EMPTY_STRING
743
790
  }
744
791
  });
745
- if (this.agentConfig.webRtcEnabled && data.loginOption === _types.LoginOption.BROWSER) {
792
+ if (this.agentConfig.webRtcEnabled && data.loginOption === _types.LoginOption.BROWSER && !this.isWebRTCRegistrationDisabled()) {
746
793
  await this.webCallingService.registerWebCallingLine();
794
+ } else if (this.agentConfig.webRtcEnabled && data.loginOption === _types.LoginOption.BROWSER && this.isWebRTCRegistrationDisabled()) {
795
+ _loggerProxy.default.info('Skipping web calling line registration because disableWebRTCRegistration is enabled', {
796
+ module: _constants.CC_FILE,
797
+ method: _constants.METHODS.STATION_LOGIN
798
+ });
747
799
  }
748
800
  const resp = await loginResponse;
749
801
  const {
@@ -1066,9 +1118,25 @@ class ContactCenter extends _webexCore.WebexPlugin {
1066
1118
  break;
1067
1119
  }
1068
1120
  };
1069
- handleRTDWebsocketMessage = event => {
1070
- this.taskManager.handleRealtimeWebsocketEvent(event);
1071
- };
1121
+
1122
+ /**
1123
+ * Checks whether Mobius/WebRTC registration should be skipped by config
1124
+ * @returns {boolean} True when browser WebRTC registration is disabled
1125
+ * @private
1126
+ */
1127
+ isWebRTCRegistrationDisabled() {
1128
+ return this.$config?.disableWebRTCRegistration === true;
1129
+ }
1130
+
1131
+ /**
1132
+ * Validates contact-center plugin configuration before service initialization
1133
+ * @private
1134
+ */
1135
+ validatePluginConfig() {
1136
+ if (this.$config?.disableWebRTCRegistration === true && this.$config?.allowMultiLogin === false) {
1137
+ throw new Error('Invalid Contact Center configuration: disableWebRTCRegistration cannot be true when allowMultiLogin is false. Enable allowMultiLogin or allow WebRTC registration so an SDK instance can receive Mobius/WebRTC task events.');
1138
+ }
1139
+ }
1072
1140
 
1073
1141
  /**
1074
1142
  * Initializes event listeners for the Contact Center service
@@ -1101,13 +1169,13 @@ class ContactCenter extends _webexCore.WebexPlugin {
1101
1169
  async handleConnectionLost(msg) {
1102
1170
  if (msg.isConnectionLost) {
1103
1171
  // TODO: Emit an event saying connection is lost
1104
- _loggerProxy.default.info('event=handleConnectionLost | Connection lost', {
1172
+ _loggerProxy.default.log('event=handleConnectionLost | Connection lost', {
1105
1173
  module: _constants.CC_FILE,
1106
1174
  method: _constants.METHODS.HANDLE_CONNECTION_LOST
1107
1175
  });
1108
1176
  } else if (msg.isSocketReconnected) {
1109
1177
  // TODO: Emit an event saying connection is re-estabilished
1110
- _loggerProxy.default.info('event=handleConnectionReconnect | Connection reconnected attempting to request silent relogin', {
1178
+ _loggerProxy.default.log('event=handleConnectionReconnect | Connection reconnected attempting to request silent relogin', {
1111
1179
  module: _constants.CC_FILE,
1112
1180
  method: _constants.METHODS.HANDLE_CONNECTION_LOST
1113
1181
  });
@@ -1122,7 +1190,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
1122
1190
  * @private
1123
1191
  */
1124
1192
  async silentRelogin() {
1125
- _loggerProxy.default.info('Starting silent relogin process', {
1193
+ _loggerProxy.default.log('Starting silent relogin process', {
1126
1194
  module: _constants.CC_FILE,
1127
1195
  method: _constants.METHODS.SILENT_RELOGIN
1128
1196
  });
@@ -1144,7 +1212,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
1144
1212
  this.agentConfig.currentTeamId = reLoginResponse.data.teamId;
1145
1213
  await this.handleDeviceType(deviceType, dn);
1146
1214
  if (lastStateChangeReason === 'agent-wss-disconnect') {
1147
- _loggerProxy.default.info('event=requestAutoStateChange | Requesting state change to available on socket reconnect', {
1215
+ _loggerProxy.default.log('event=requestAutoStateChange | Requesting state change to available on socket reconnect', {
1148
1216
  module: _constants.CC_FILE,
1149
1217
  method: _constants.METHODS.SILENT_RELOGIN
1150
1218
  });
@@ -1168,8 +1236,6 @@ class ContactCenter extends _webexCore.WebexPlugin {
1168
1236
  }
1169
1237
  this.agentConfig.lastStateAuxCodeId = auxCodeId;
1170
1238
  this.agentConfig.isAgentLoggedIn = true;
1171
- // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
1172
- this.services.webSocketManager.on('message', this.handleWebsocketMessage);
1173
1239
  _loggerProxy.default.log(`Silent relogin process completed successfully with login Option: ${reLoginResponse.data.deviceType} teamId: ${reLoginResponse.data.teamId}`, {
1174
1240
  module: _constants.CC_FILE,
1175
1241
  method: _constants.METHODS.SILENT_RELOGIN,
@@ -1204,6 +1270,13 @@ class ContactCenter extends _webexCore.WebexPlugin {
1204
1270
  this.agentConfig.deviceType = deviceType;
1205
1271
  switch (deviceType) {
1206
1272
  case _types.LoginOption.BROWSER:
1273
+ if (this.isWebRTCRegistrationDisabled()) {
1274
+ _loggerProxy.default.info('Skipping web calling line registration because disableWebRTCRegistration is enabled', {
1275
+ module: _constants.CC_FILE,
1276
+ method: _constants.METHODS.HANDLE_DEVICE_TYPE
1277
+ });
1278
+ break;
1279
+ }
1207
1280
  try {
1208
1281
  await this.webCallingService.registerWebCallingLine();
1209
1282
  } catch (error) {
@@ -1385,6 +1458,196 @@ class ContactCenter extends _webexCore.WebexPlugin {
1385
1458
  }
1386
1459
  }
1387
1460
 
1461
+ /**
1462
+ * Accepts a campaign preview contact, initiating the outbound call.
1463
+ *
1464
+ * When a campaign manager reserves a contact for an agent, the agent receives an
1465
+ * `AgentOfferCampaignReservation` event. The agent can then accept the preview contact
1466
+ * to initiate the outbound call.
1467
+ *
1468
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
1469
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
1470
+ * @throws {Error} If the operation fails (network error, customer unavailable, etc.)
1471
+ *
1472
+ * @example
1473
+ * ```typescript
1474
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
1475
+ * const { interactionId } = task.data;
1476
+ * // campaignId is the campaign name (e.g. "MyCampaign"), not a UUID
1477
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
1478
+ *
1479
+ * const result = await webex.cc.acceptPreviewContact({ interactionId, campaignId });
1480
+ * });
1481
+ * ```
1482
+ */
1483
+ async acceptPreviewContact(payload) {
1484
+ _loggerProxy.default.info('Accepting campaign preview contact', {
1485
+ module: _constants.CC_FILE,
1486
+ method: _constants.METHODS.ACCEPT_PREVIEW_CONTACT
1487
+ });
1488
+ try {
1489
+ this.metricsManager.timeEvent([_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_SUCCESS, _constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_FAILED]);
1490
+ const result = await this.services.dialer.acceptPreviewContact({
1491
+ data: payload
1492
+ });
1493
+ this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_SUCCESS, {
1494
+ ..._MetricsManager.default.getCommonTrackingFieldForAQMResponse(result),
1495
+ interactionId: payload.interactionId,
1496
+ campaignId: payload.campaignId
1497
+ }, ['behavioral', 'business', 'operational']);
1498
+ _loggerProxy.default.log('Campaign preview contact accepted successfully', {
1499
+ module: _constants.CC_FILE,
1500
+ method: _constants.METHODS.ACCEPT_PREVIEW_CONTACT,
1501
+ trackingId: result.trackingId,
1502
+ interactionId: payload.interactionId
1503
+ });
1504
+ return result;
1505
+ } catch (error) {
1506
+ const failure = error.details;
1507
+ this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_FAILED, {
1508
+ ..._MetricsManager.default.getCommonTrackingFieldForAQMResponseFailed(failure),
1509
+ interactionId: payload.interactionId,
1510
+ campaignId: payload.campaignId
1511
+ }, ['behavioral', 'business', 'operational']);
1512
+ const {
1513
+ error: detailedError
1514
+ } = (0, _Utils.getErrorDetails)(error, _constants.METHODS.ACCEPT_PREVIEW_CONTACT, _constants.CC_FILE);
1515
+ throw detailedError;
1516
+ }
1517
+ }
1518
+
1519
+ /**
1520
+ * Skips a campaign preview contact, requesting the next contact from the campaign.
1521
+ *
1522
+ * When a campaign manager reserves a contact for an agent, the agent receives an
1523
+ * `AgentOfferCampaignReservation` event. Instead of accepting, the agent can skip the
1524
+ * preview contact to move to the next contact in the campaign.
1525
+ *
1526
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
1527
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
1528
+ * @throws {Error} If the operation fails (network error, etc.)
1529
+ *
1530
+ * @example
1531
+ * ```typescript
1532
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
1533
+ * const { interactionId } = task.data;
1534
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
1535
+ *
1536
+ * const result = await webex.cc.skipPreviewContact({ interactionId, campaignId });
1537
+ * });
1538
+ * ```
1539
+ */
1540
+ async skipPreviewContact(payload) {
1541
+ const task = this.taskManager.getTask(payload.interactionId);
1542
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled === 'true') {
1543
+ _loggerProxy.default.warn('Skip action is disabled for this campaign preview contact', {
1544
+ module: _constants.CC_FILE,
1545
+ method: _constants.METHODS.SKIP_PREVIEW_CONTACT,
1546
+ interactionId: payload.interactionId
1547
+ });
1548
+ throw new Error('Skip action is disabled for this campaign preview contact');
1549
+ }
1550
+ _loggerProxy.default.info('Skipping campaign preview contact', {
1551
+ module: _constants.CC_FILE,
1552
+ method: _constants.METHODS.SKIP_PREVIEW_CONTACT
1553
+ });
1554
+ try {
1555
+ this.metricsManager.timeEvent([_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS, _constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED]);
1556
+ const result = await this.services.dialer.skipPreviewContact({
1557
+ data: payload
1558
+ });
1559
+ this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS, {
1560
+ ..._MetricsManager.default.getCommonTrackingFieldForAQMResponse(result),
1561
+ interactionId: payload.interactionId,
1562
+ campaignId: payload.campaignId
1563
+ }, ['behavioral', 'business', 'operational']);
1564
+ _loggerProxy.default.log('Campaign preview contact skipped successfully', {
1565
+ module: _constants.CC_FILE,
1566
+ method: _constants.METHODS.SKIP_PREVIEW_CONTACT,
1567
+ trackingId: result.trackingId,
1568
+ interactionId: payload.interactionId
1569
+ });
1570
+ return result;
1571
+ } catch (error) {
1572
+ const failure = error.details;
1573
+ this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED, {
1574
+ ..._MetricsManager.default.getCommonTrackingFieldForAQMResponseFailed(failure),
1575
+ interactionId: payload.interactionId,
1576
+ campaignId: payload.campaignId
1577
+ }, ['behavioral', 'business', 'operational']);
1578
+ const {
1579
+ error: detailedError
1580
+ } = (0, _Utils.getErrorDetails)(error, _constants.METHODS.SKIP_PREVIEW_CONTACT, _constants.CC_FILE);
1581
+ throw detailedError;
1582
+ }
1583
+ }
1584
+
1585
+ /**
1586
+ * Removes a campaign preview contact from the campaign list entirely.
1587
+ *
1588
+ * When a campaign manager reserves a contact for an agent, the agent receives an
1589
+ * `AgentOfferCampaignReservation` event. Instead of accepting or skipping, the agent can
1590
+ * remove the preview contact to permanently take it out of the campaign contact list.
1591
+ *
1592
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
1593
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
1594
+ * @throws {Error} If the operation fails (network error, etc.)
1595
+ *
1596
+ * @example
1597
+ * ```typescript
1598
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
1599
+ * const { interactionId } = task.data;
1600
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
1601
+ *
1602
+ * const result = await webex.cc.removePreviewContact({ interactionId, campaignId });
1603
+ * });
1604
+ * ```
1605
+ */
1606
+ async removePreviewContact(payload) {
1607
+ const task = this.taskManager.getTask(payload.interactionId);
1608
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled === 'true') {
1609
+ _loggerProxy.default.warn('Remove action is disabled for this campaign preview contact', {
1610
+ module: _constants.CC_FILE,
1611
+ method: _constants.METHODS.REMOVE_PREVIEW_CONTACT,
1612
+ interactionId: payload.interactionId
1613
+ });
1614
+ throw new Error('Remove action is disabled for this campaign preview contact');
1615
+ }
1616
+ _loggerProxy.default.info('Removing campaign preview contact', {
1617
+ module: _constants.CC_FILE,
1618
+ method: _constants.METHODS.REMOVE_PREVIEW_CONTACT
1619
+ });
1620
+ try {
1621
+ this.metricsManager.timeEvent([_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS, _constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED]);
1622
+ const result = await this.services.dialer.removePreviewContact({
1623
+ data: payload
1624
+ });
1625
+ this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS, {
1626
+ ..._MetricsManager.default.getCommonTrackingFieldForAQMResponse(result),
1627
+ interactionId: payload.interactionId,
1628
+ campaignId: payload.campaignId
1629
+ }, ['behavioral', 'business', 'operational']);
1630
+ _loggerProxy.default.log('Campaign preview contact removed successfully', {
1631
+ module: _constants.CC_FILE,
1632
+ method: _constants.METHODS.REMOVE_PREVIEW_CONTACT,
1633
+ trackingId: result.trackingId,
1634
+ interactionId: payload.interactionId
1635
+ });
1636
+ return result;
1637
+ } catch (error) {
1638
+ const failure = error.details;
1639
+ this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED, {
1640
+ ..._MetricsManager.default.getCommonTrackingFieldForAQMResponseFailed(failure),
1641
+ interactionId: payload.interactionId,
1642
+ campaignId: payload.campaignId
1643
+ }, ['behavioral', 'business', 'operational']);
1644
+ const {
1645
+ error: detailedError
1646
+ } = (0, _Utils.getErrorDetails)(error, _constants.METHODS.REMOVE_PREVIEW_CONTACT, _constants.CC_FILE);
1647
+ throw detailedError;
1648
+ }
1649
+ }
1650
+
1388
1651
  /**
1389
1652
  * Fetches outdial ANI (Automatic Number Identification) entries for an outdial ANI ID.
1390
1653
  *