@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/src/cc.ts CHANGED
@@ -56,7 +56,13 @@ import {
56
56
  import {ConnectionLostDetails} from './services/core/websocket/types';
57
57
  import TaskManager from './services/task/TaskManager';
58
58
  import WebCallingService from './services/WebCallingService';
59
- import {ITask, TASK_EVENTS, TaskResponse, DialerPayload} from './services/task/types';
59
+ import {
60
+ ITask,
61
+ TASK_EVENTS,
62
+ TaskResponse,
63
+ DialerPayload,
64
+ PreviewContactPayload,
65
+ } from './services/task/types';
60
66
  import MetricsManager from './metrics/MetricsManager';
61
67
  import {METRIC_EVENT_NAMES} from './metrics/constants';
62
68
  import {Failure} from './services/core/GlobalTypes';
@@ -121,6 +127,7 @@ import type {
121
127
  * - `task:established` - Task/call has been connected
122
128
  * - `task:ended` - Task/call has ended
123
129
  * - `task:error` - An error occurred during task handling
130
+ * - `task:campaignPreviewReservation` - Campaign preview contact offered to agent
124
131
  *
125
132
  * @public
126
133
  *
@@ -351,6 +358,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
351
358
  this.$webex.once(READY, () => {
352
359
  // @ts-ignore
353
360
  this.$config = this.config;
361
+ this.validatePluginConfig();
354
362
 
355
363
  /**
356
364
  * This is used for handling the async requests by sending webex.request and wait for corresponding websocket event.
@@ -363,7 +371,6 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
363
371
  webex: this.$webex,
364
372
  connectionConfig: this.getConnectionConfig(),
365
373
  });
366
- this.services.webSocketManager.on('message', this.handleWebsocketMessage);
367
374
 
368
375
  this.webCallingService = new WebCallingService(this.$webex);
369
376
  this.apiAIAssistant = new ApiAIAssistant(this.$webex);
@@ -406,6 +413,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
406
413
  this.trigger(TASK_EVENTS.TASK_HYDRATE, task);
407
414
  };
408
415
 
416
+ /**
417
+ * Handles multi-login hydrate events for SDK instances without Mobius registration
418
+ * @private
419
+ * @param {ITask} task The task object associated with the multi-login hydrate
420
+ */
421
+ private handleTaskMultiLoginHydrate = (task: ITask) => {
422
+ // @ts-ignore
423
+ this.trigger(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
424
+ };
425
+
409
426
  /**
410
427
  * Handles task merged events when tasks are combined eg: EPDN merge/transfer
411
428
  * @private
@@ -416,6 +433,20 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
416
433
  this.trigger(TASK_EVENTS.TASK_MERGED, task);
417
434
  };
418
435
 
436
+ /**
437
+ * Handles campaign preview reservation events when a contact is offered to the agent
438
+ * @private
439
+ * @param {ITask} task The campaign reservation task
440
+ */
441
+ private handleCampaignPreviewReservation = (task: ITask) => {
442
+ // @ts-ignore
443
+ this.trigger(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
444
+ };
445
+
446
+ private handleRTDWebsocketMessage = (event: string) => {
447
+ this.taskManager.handleRealtimeWebsocketEvent(event);
448
+ };
449
+
419
450
  /**
420
451
  * Sets up event listeners for incoming tasks and task hydration
421
452
  * Subscribes to task events from the task manager
@@ -424,7 +455,12 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
424
455
  private incomingTaskListener() {
425
456
  this.taskManager.on(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
426
457
  this.taskManager.on(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
458
+ this.taskManager.on(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
427
459
  this.taskManager.on(TASK_EVENTS.TASK_MERGED, this.handleTaskMerged);
460
+ this.taskManager.on(
461
+ TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
462
+ this.handleCampaignPreviewReservation
463
+ );
428
464
  }
429
465
 
430
466
  /**
@@ -464,7 +500,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
464
500
  * ```
465
501
  */
466
502
  public async register(): Promise<Profile> {
467
- LoggerProxy.info('Starting CC SDK registration', {
503
+ LoggerProxy.log('Starting CC SDK registration', {
468
504
  module: CC_FILE,
469
505
  method: METHODS.REGISTER,
470
506
  });
@@ -474,6 +510,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
474
510
  METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
475
511
  ]);
476
512
  this.setupEventListeners();
513
+ this.services.webSocketManager.on('message', this.handleWebsocketMessage);
477
514
 
478
515
  const resp = await this.connectWebsocket();
479
516
  // Ensure 'dn' is always populated from 'defaultDn'
@@ -553,6 +590,11 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
553
590
 
554
591
  this.taskManager.off(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
555
592
  this.taskManager.off(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
593
+ this.taskManager.off(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
594
+ this.taskManager.off(
595
+ TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
596
+ this.handleCampaignPreviewReservation
597
+ );
556
598
  this.taskManager.unregisterIncomingCallEvent();
557
599
 
558
600
  this.services.webSocketManager.off('message', this.handleWebsocketMessage);
@@ -583,6 +625,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
583
625
  this.services.rtdWebSocketManager.close(false, 'Unregistering the SDK');
584
626
  }
585
627
 
628
+ if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
629
+ this.services.rtdWebSocketManager.close(false, 'Unregistering the RTD websocket');
630
+ }
631
+
586
632
  // Clear any cached agent configuration
587
633
  this.agentConfig = null;
588
634
 
@@ -703,98 +749,110 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
703
749
  * @private
704
750
  */
705
751
  private async connectWebsocket() {
706
- LoggerProxy.info('Connecting to websocket', {
752
+ LoggerProxy.log('Connecting to websocket', {
707
753
  module: CC_FILE,
708
754
  method: METHODS.CONNECT_WEBSOCKET,
709
755
  });
756
+
710
757
  try {
711
- return this.services.webSocketManager
712
- .initWebSocket({
713
- body: this.getConnectionConfig(),
714
- resource: SUBSCRIBE_API,
715
- })
716
- .then(async (data: WelcomeEvent) => {
717
- const agentId = data.agentId;
718
- const orgId = this.$webex.credentials.getOrgId();
719
- this.agentConfig = await this.services.config.getAgentConfig(orgId, agentId);
720
- LoggerProxy.log(`Agent config is fetched successfully`, {
721
- module: CC_FILE,
722
- method: METHODS.CONNECT_WEBSOCKET,
723
- });
758
+ const data = (await this.services.webSocketManager.initWebSocket({
759
+ body: this.getConnectionConfig(),
760
+ resource: SUBSCRIBE_API,
761
+ })) as WelcomeEvent;
724
762
 
725
- const configFlags: ConfigFlags = {
726
- isEndTaskEnabled: this.agentConfig.isEndTaskEnabled,
727
- isEndConsultEnabled: this.agentConfig.isEndConsultEnabled,
728
- webRtcEnabled: this.agentConfig.webRtcEnabled,
729
- autoWrapup: this.agentConfig.wrapUpData?.wrapUpProps?.autoWrapup ?? false,
730
- aiFeature: this.agentConfig.aiFeature,
731
- };
732
- this.taskManager.setConfigFlags(configFlags);
733
- // TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
734
- this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
735
- this.taskManager.setAgentId(this.agentConfig.agentId);
736
- this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
737
- this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
738
- /**
739
- * RTD websocket currently supports realtime transcripts and suggested responses.
740
- * Extend this condition when additional AI RTD features are introduced.
741
- */
742
- if (
743
- this.agentConfig.aiFeature?.realtimeTranscripts?.enable ||
744
- this.agentConfig.aiFeature?.suggestedResponses?.enable
745
- ) {
746
- LoggerProxy.info('Connecting to RTD websocket', {
763
+ const agentId = data.agentId;
764
+ const orgId = this.$webex.credentials.getOrgId();
765
+ this.agentConfig = await this.services.config.getAgentConfig(orgId, agentId);
766
+
767
+ LoggerProxy.log(`Agent config is fetched successfully`, {
768
+ module: CC_FILE,
769
+ method: METHODS.CONNECT_WEBSOCKET,
770
+ });
771
+
772
+ const configFlags: ConfigFlags = {
773
+ isEndTaskEnabled: this.agentConfig.isEndTaskEnabled,
774
+ isEndConsultEnabled: this.agentConfig.isEndConsultEnabled,
775
+ webRtcEnabled: this.agentConfig.webRtcEnabled,
776
+ autoWrapup: this.agentConfig.wrapUpData?.wrapUpProps?.autoWrapup ?? false,
777
+ aiFeature: this.agentConfig.aiFeature,
778
+ };
779
+ this.taskManager.setConfigFlags(configFlags);
780
+ // TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
781
+ this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
782
+ this.taskManager.setAgentId(this.agentConfig.agentId);
783
+ this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
784
+ this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
785
+
786
+ /**
787
+ * RTD websocket currently supports realtime transcripts and suggested responses.
788
+ * Extend this condition when additional AI RTD features are introduced.
789
+ */
790
+ if (
791
+ this.agentConfig.aiFeature?.realtimeTranscripts?.enable ||
792
+ this.agentConfig.aiFeature?.suggestedResponses?.enable
793
+ ) {
794
+ LoggerProxy.info('Connecting to RTD websocket', {
795
+ module: CC_FILE,
796
+ method: METHODS.CONNECT_WEBSOCKET,
797
+ });
798
+
799
+ this.services.rtdWebSocketManager
800
+ .initWebSocket({
801
+ body: this.getConnectionConfig(),
802
+ resource: RTD_SUBSCRIBE_API,
803
+ })
804
+ .then(() => {
805
+ LoggerProxy.log('RTD websocket connected successfully', {
806
+ module: CC_FILE,
807
+ method: METHODS.CONNECT_WEBSOCKET,
808
+ });
809
+ this.services.rtdWebSocketManager.on('message', this.handleRTDWebsocketMessage);
810
+ })
811
+ .catch((error) => {
812
+ LoggerProxy.error(`Error connecting to RTD websocket ${error}`, {
747
813
  module: CC_FILE,
748
814
  method: METHODS.CONNECT_WEBSOCKET,
749
815
  });
816
+ });
817
+ }
750
818
 
751
- this.services.rtdWebSocketManager
752
- .initWebSocket({
753
- body: this.getConnectionConfig(),
754
- resource: RTD_SUBSCRIBE_API,
755
- })
756
- .then(() => {
757
- LoggerProxy.log('RTD websocket connected successfully', {
758
- module: CC_FILE,
759
- method: METHODS.CONNECT_WEBSOCKET,
760
- });
761
- this.services.rtdWebSocketManager.on('message', this.handleRTDWebsocketMessage);
762
- })
763
- .catch((error) => {
764
- LoggerProxy.error(`Error connecting to RTD websocket ${error}`, {
765
- module: CC_FILE,
766
- method: METHODS.CONNECT_WEBSOCKET,
767
- });
768
- });
769
- }
770
- if (
771
- this.agentConfig.webRtcEnabled &&
772
- this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER)
773
- ) {
774
- this.$webex.internal.mercury
775
- .connect()
776
- .then(() => {
777
- LoggerProxy.log('Authentication: webex.internal.mercury.connect successful', {
778
- module: CC_FILE,
779
- method: METHODS.CONNECT_WEBSOCKET,
780
- });
781
- })
782
- .catch((error) => {
783
- LoggerProxy.error(`Error occurred during mercury.connect() ${error}`, {
784
- module: CC_FILE,
785
- method: METHODS.CONNECT_WEBSOCKET,
786
- });
787
- });
788
- }
789
- if (this.$config && this.$config.allowAutomatedRelogin) {
790
- await this.silentRelogin();
819
+ if (
820
+ this.agentConfig.webRtcEnabled &&
821
+ this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER) &&
822
+ !this.isWebRTCRegistrationDisabled()
823
+ ) {
824
+ try {
825
+ await this.$webex.internal.mercury.connect();
826
+ LoggerProxy.log('Authentication: webex.internal.mercury.connect successful', {
827
+ module: CC_FILE,
828
+ method: METHODS.CONNECT_WEBSOCKET,
829
+ });
830
+ } catch (error) {
831
+ LoggerProxy.error(`Error occurred during mercury.connect() ${error}`, {
832
+ module: CC_FILE,
833
+ method: METHODS.CONNECT_WEBSOCKET,
834
+ });
835
+ }
836
+ } else if (this.isWebRTCRegistrationDisabled()) {
837
+ LoggerProxy.info(
838
+ 'Skipping Mobius registration because disableWebRTCRegistration is enabled',
839
+ {
840
+ module: CC_FILE,
841
+ method: METHODS.CONNECT_WEBSOCKET,
791
842
  }
843
+ );
844
+ }
792
845
 
793
- return this.agentConfig;
794
- })
795
- .catch((error) => {
796
- throw error;
846
+ if (this.$config && this.$config.allowAutomatedRelogin) {
847
+ await this.silentRelogin();
848
+ } else {
849
+ LoggerProxy.log('Skipping silent relogin: allowAutomatedRelogin is disabled', {
850
+ module: CC_FILE,
851
+ method: METHODS.CONNECT_WEBSOCKET,
797
852
  });
853
+ }
854
+
855
+ return this.agentConfig;
798
856
  } catch (error) {
799
857
  LoggerProxy.error(`Error during register: ${error}`, {
800
858
  module: CC_FILE,
@@ -834,22 +892,37 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
834
892
  * ```
835
893
  */
836
894
  public async stationLogin(data: AgentLogin): Promise<StationLoginResponse> {
837
- LoggerProxy.info('Starting agent station login', {
838
- module: CC_FILE,
839
- method: METHODS.STATION_LOGIN,
840
- });
895
+ const loggerContext = {module: CC_FILE, method: METHODS.STATION_LOGIN};
896
+
841
897
  try {
898
+ LoggerProxy.log(
899
+ `Starting agent station login | loginOption: ${data?.loginOption} teamId: ${data?.teamId}`,
900
+ loggerContext
901
+ );
842
902
  this.metricsManager.timeEvent([
843
903
  METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
844
904
  METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
845
905
  ]);
846
906
 
847
- if (data.loginOption === LoginOption.AGENT_DN && !isValidDialNumber(data.dialNumber)) {
848
- const error = new Error('INVALID_DIAL_NUMBER');
849
- // @ts-ignore - adding custom key to the error object
850
- error.details = {data: {reason: 'INVALID_DIAL_NUMBER'}} as Failure;
907
+ const dialPlanEntries = this.agentConfig?.dialPlan?.dialPlanEntity ?? [];
908
+ if (data.loginOption === LoginOption.AGENT_DN) {
909
+ LoggerProxy.log(
910
+ `Validating dial number | dialPlanEnabled: ${!!this.agentConfig
911
+ ?.dialPlan} | dialPlanEntryCount: ${dialPlanEntries.length}`,
912
+ loggerContext
913
+ );
914
+
915
+ if (!isValidDialNumber(data.dialNumber, dialPlanEntries)) {
916
+ LoggerProxy.log(
917
+ `Dial number validation failed | dialNumber: ${data.dialNumber} | dialPlanEntryCount: ${dialPlanEntries.length}`,
918
+ loggerContext
919
+ );
920
+ const error = new Error('INVALID_DIAL_NUMBER');
921
+ // @ts-ignore - adding custom key to the error object
922
+ error.details = {data: {reason: 'INVALID_DIAL_NUMBER'}} as Failure;
851
923
 
852
- throw error;
924
+ throw error;
925
+ }
853
926
  }
854
927
 
855
928
  const loginResponse = await this.services.agent.stationLogin({
@@ -868,8 +941,24 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
868
941
  },
869
942
  });
870
943
 
871
- if (this.agentConfig.webRtcEnabled && data.loginOption === LoginOption.BROWSER) {
944
+ if (
945
+ this.agentConfig.webRtcEnabled &&
946
+ data.loginOption === LoginOption.BROWSER &&
947
+ !this.isWebRTCRegistrationDisabled()
948
+ ) {
872
949
  await this.webCallingService.registerWebCallingLine();
950
+ } else if (
951
+ this.agentConfig.webRtcEnabled &&
952
+ data.loginOption === LoginOption.BROWSER &&
953
+ this.isWebRTCRegistrationDisabled()
954
+ ) {
955
+ LoggerProxy.info(
956
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
957
+ {
958
+ module: CC_FILE,
959
+ method: METHODS.STATION_LOGIN,
960
+ }
961
+ );
873
962
  }
874
963
 
875
964
  const resp = await loginResponse;
@@ -1231,9 +1320,29 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1231
1320
  }
1232
1321
  };
1233
1322
 
1234
- private handleRTDWebsocketMessage = (event: string) => {
1235
- this.taskManager.handleRealtimeWebsocketEvent(event);
1236
- };
1323
+ /**
1324
+ * Checks whether Mobius/WebRTC registration should be skipped by config
1325
+ * @returns {boolean} True when browser WebRTC registration is disabled
1326
+ * @private
1327
+ */
1328
+ private isWebRTCRegistrationDisabled(): boolean {
1329
+ return this.$config?.disableWebRTCRegistration === true;
1330
+ }
1331
+
1332
+ /**
1333
+ * Validates contact-center plugin configuration before service initialization
1334
+ * @private
1335
+ */
1336
+ private validatePluginConfig(): void {
1337
+ if (
1338
+ this.$config?.disableWebRTCRegistration === true &&
1339
+ this.$config?.allowMultiLogin === false
1340
+ ) {
1341
+ throw new Error(
1342
+ '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.'
1343
+ );
1344
+ }
1345
+ }
1237
1346
 
1238
1347
  /**
1239
1348
  * Initializes event listeners for the Contact Center service
@@ -1266,13 +1375,13 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1266
1375
  private async handleConnectionLost(msg: ConnectionLostDetails): Promise<void> {
1267
1376
  if (msg.isConnectionLost) {
1268
1377
  // TODO: Emit an event saying connection is lost
1269
- LoggerProxy.info('event=handleConnectionLost | Connection lost', {
1378
+ LoggerProxy.log('event=handleConnectionLost | Connection lost', {
1270
1379
  module: CC_FILE,
1271
1380
  method: METHODS.HANDLE_CONNECTION_LOST,
1272
1381
  });
1273
1382
  } else if (msg.isSocketReconnected) {
1274
1383
  // TODO: Emit an event saying connection is re-estabilished
1275
- LoggerProxy.info(
1384
+ LoggerProxy.log(
1276
1385
  'event=handleConnectionReconnect | Connection reconnected attempting to request silent relogin',
1277
1386
  {module: CC_FILE, method: METHODS.HANDLE_CONNECTION_LOST}
1278
1387
  );
@@ -1287,7 +1396,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1287
1396
  * @private
1288
1397
  */
1289
1398
  private async silentRelogin(): Promise<void> {
1290
- LoggerProxy.info('Starting silent relogin process', {
1399
+ LoggerProxy.log('Starting silent relogin process', {
1291
1400
  module: CC_FILE,
1292
1401
  method: METHODS.SILENT_RELOGIN,
1293
1402
  });
@@ -1309,7 +1418,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1309
1418
  await this.handleDeviceType(deviceType as LoginOption, dn);
1310
1419
 
1311
1420
  if (lastStateChangeReason === 'agent-wss-disconnect') {
1312
- LoggerProxy.info(
1421
+ LoggerProxy.log(
1313
1422
  'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1314
1423
  {module: CC_FILE, method: METHODS.SILENT_RELOGIN}
1315
1424
  );
@@ -1338,8 +1447,6 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1338
1447
  }
1339
1448
  this.agentConfig.lastStateAuxCodeId = auxCodeId;
1340
1449
  this.agentConfig.isAgentLoggedIn = true;
1341
- // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
1342
- this.services.webSocketManager.on('message', this.handleWebsocketMessage);
1343
1450
 
1344
1451
  LoggerProxy.log(
1345
1452
  `Silent relogin process completed successfully with login Option: ${reLoginResponse.data.deviceType} teamId: ${reLoginResponse.data.teamId}`,
@@ -1380,6 +1487,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1380
1487
  this.agentConfig.deviceType = deviceType;
1381
1488
  switch (deviceType) {
1382
1489
  case LoginOption.BROWSER:
1490
+ if (this.isWebRTCRegistrationDisabled()) {
1491
+ LoggerProxy.info(
1492
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
1493
+ {
1494
+ module: CC_FILE,
1495
+ method: METHODS.HANDLE_DEVICE_TYPE,
1496
+ }
1497
+ );
1498
+ break;
1499
+ }
1383
1500
  try {
1384
1501
  await this.webCallingService.registerWebCallingLine();
1385
1502
  } catch (error) {
@@ -1572,6 +1689,239 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1572
1689
  }
1573
1690
  }
1574
1691
 
1692
+ /**
1693
+ * Accepts a campaign preview contact, initiating the outbound call.
1694
+ *
1695
+ * When a campaign manager reserves a contact for an agent, the agent receives an
1696
+ * `AgentOfferCampaignReservation` event. The agent can then accept the preview contact
1697
+ * to initiate the outbound call.
1698
+ *
1699
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
1700
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
1701
+ * @throws {Error} If the operation fails (network error, customer unavailable, etc.)
1702
+ *
1703
+ * @example
1704
+ * ```typescript
1705
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
1706
+ * const { interactionId } = task.data;
1707
+ * // campaignId is the campaign name (e.g. "MyCampaign"), not a UUID
1708
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
1709
+ *
1710
+ * const result = await webex.cc.acceptPreviewContact({ interactionId, campaignId });
1711
+ * });
1712
+ * ```
1713
+ */
1714
+ public async acceptPreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
1715
+ LoggerProxy.info('Accepting campaign preview contact', {
1716
+ module: CC_FILE,
1717
+ method: METHODS.ACCEPT_PREVIEW_CONTACT,
1718
+ });
1719
+ try {
1720
+ this.metricsManager.timeEvent([
1721
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_SUCCESS,
1722
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_FAILED,
1723
+ ]);
1724
+
1725
+ const result = await this.services.dialer.acceptPreviewContact({data: payload});
1726
+
1727
+ this.metricsManager.trackEvent(
1728
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_SUCCESS,
1729
+ {
1730
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1731
+ interactionId: payload.interactionId,
1732
+ campaignId: payload.campaignId,
1733
+ },
1734
+ ['behavioral', 'business', 'operational']
1735
+ );
1736
+
1737
+ LoggerProxy.log('Campaign preview contact accepted successfully', {
1738
+ module: CC_FILE,
1739
+ method: METHODS.ACCEPT_PREVIEW_CONTACT,
1740
+ trackingId: result.trackingId,
1741
+ interactionId: payload.interactionId,
1742
+ });
1743
+
1744
+ return result;
1745
+ } catch (error) {
1746
+ const failure = error.details as Failure;
1747
+ this.metricsManager.trackEvent(
1748
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_FAILED,
1749
+ {
1750
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
1751
+ interactionId: payload.interactionId,
1752
+ campaignId: payload.campaignId,
1753
+ },
1754
+ ['behavioral', 'business', 'operational']
1755
+ );
1756
+ const {error: detailedError} = getErrorDetails(
1757
+ error,
1758
+ METHODS.ACCEPT_PREVIEW_CONTACT,
1759
+ CC_FILE
1760
+ );
1761
+ throw detailedError;
1762
+ }
1763
+ }
1764
+
1765
+ /**
1766
+ * Skips a campaign preview contact, requesting the next contact from the campaign.
1767
+ *
1768
+ * When a campaign manager reserves a contact for an agent, the agent receives an
1769
+ * `AgentOfferCampaignReservation` event. Instead of accepting, the agent can skip the
1770
+ * preview contact to move to the next contact in the campaign.
1771
+ *
1772
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
1773
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
1774
+ * @throws {Error} If the operation fails (network error, etc.)
1775
+ *
1776
+ * @example
1777
+ * ```typescript
1778
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
1779
+ * const { interactionId } = task.data;
1780
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
1781
+ *
1782
+ * const result = await webex.cc.skipPreviewContact({ interactionId, campaignId });
1783
+ * });
1784
+ * ```
1785
+ */
1786
+ public async skipPreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
1787
+ const task = this.taskManager.getTask(payload.interactionId);
1788
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled === 'true') {
1789
+ LoggerProxy.warn('Skip action is disabled for this campaign preview contact', {
1790
+ module: CC_FILE,
1791
+ method: METHODS.SKIP_PREVIEW_CONTACT,
1792
+ interactionId: payload.interactionId,
1793
+ });
1794
+ throw new Error('Skip action is disabled for this campaign preview contact');
1795
+ }
1796
+
1797
+ LoggerProxy.info('Skipping campaign preview contact', {
1798
+ module: CC_FILE,
1799
+ method: METHODS.SKIP_PREVIEW_CONTACT,
1800
+ });
1801
+ try {
1802
+ this.metricsManager.timeEvent([
1803
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
1804
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
1805
+ ]);
1806
+
1807
+ const result = await this.services.dialer.skipPreviewContact({data: payload});
1808
+
1809
+ this.metricsManager.trackEvent(
1810
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
1811
+ {
1812
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1813
+ interactionId: payload.interactionId,
1814
+ campaignId: payload.campaignId,
1815
+ },
1816
+ ['behavioral', 'business', 'operational']
1817
+ );
1818
+
1819
+ LoggerProxy.log('Campaign preview contact skipped successfully', {
1820
+ module: CC_FILE,
1821
+ method: METHODS.SKIP_PREVIEW_CONTACT,
1822
+ trackingId: result.trackingId,
1823
+ interactionId: payload.interactionId,
1824
+ });
1825
+
1826
+ return result;
1827
+ } catch (error) {
1828
+ const failure = error.details as Failure;
1829
+ this.metricsManager.trackEvent(
1830
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
1831
+ {
1832
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
1833
+ interactionId: payload.interactionId,
1834
+ campaignId: payload.campaignId,
1835
+ },
1836
+ ['behavioral', 'business', 'operational']
1837
+ );
1838
+ const {error: detailedError} = getErrorDetails(error, METHODS.SKIP_PREVIEW_CONTACT, CC_FILE);
1839
+ throw detailedError;
1840
+ }
1841
+ }
1842
+
1843
+ /**
1844
+ * Removes a campaign preview contact from the campaign list entirely.
1845
+ *
1846
+ * When a campaign manager reserves a contact for an agent, the agent receives an
1847
+ * `AgentOfferCampaignReservation` event. Instead of accepting or skipping, the agent can
1848
+ * remove the preview contact to permanently take it out of the campaign contact list.
1849
+ *
1850
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
1851
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
1852
+ * @throws {Error} If the operation fails (network error, etc.)
1853
+ *
1854
+ * @example
1855
+ * ```typescript
1856
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
1857
+ * const { interactionId } = task.data;
1858
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
1859
+ *
1860
+ * const result = await webex.cc.removePreviewContact({ interactionId, campaignId });
1861
+ * });
1862
+ * ```
1863
+ */
1864
+ public async removePreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
1865
+ const task = this.taskManager.getTask(payload.interactionId);
1866
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled === 'true') {
1867
+ LoggerProxy.warn('Remove action is disabled for this campaign preview contact', {
1868
+ module: CC_FILE,
1869
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
1870
+ interactionId: payload.interactionId,
1871
+ });
1872
+ throw new Error('Remove action is disabled for this campaign preview contact');
1873
+ }
1874
+
1875
+ LoggerProxy.info('Removing campaign preview contact', {
1876
+ module: CC_FILE,
1877
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
1878
+ });
1879
+ try {
1880
+ this.metricsManager.timeEvent([
1881
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
1882
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
1883
+ ]);
1884
+
1885
+ const result = await this.services.dialer.removePreviewContact({data: payload});
1886
+
1887
+ this.metricsManager.trackEvent(
1888
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
1889
+ {
1890
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1891
+ interactionId: payload.interactionId,
1892
+ campaignId: payload.campaignId,
1893
+ },
1894
+ ['behavioral', 'business', 'operational']
1895
+ );
1896
+
1897
+ LoggerProxy.log('Campaign preview contact removed successfully', {
1898
+ module: CC_FILE,
1899
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
1900
+ trackingId: result.trackingId,
1901
+ interactionId: payload.interactionId,
1902
+ });
1903
+
1904
+ return result;
1905
+ } catch (error) {
1906
+ const failure = error.details as Failure;
1907
+ this.metricsManager.trackEvent(
1908
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
1909
+ {
1910
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
1911
+ interactionId: payload.interactionId,
1912
+ campaignId: payload.campaignId,
1913
+ },
1914
+ ['behavioral', 'business', 'operational']
1915
+ );
1916
+ const {error: detailedError} = getErrorDetails(
1917
+ error,
1918
+ METHODS.REMOVE_PREVIEW_CONTACT,
1919
+ CC_FILE
1920
+ );
1921
+ throw detailedError;
1922
+ }
1923
+ }
1924
+
1575
1925
  /**
1576
1926
  * Fetches outdial ANI (Automatic Number Identification) entries for an outdial ANI ID.
1577
1927
  *