@webex/contact-center 3.12.0-task-refactor.11 → 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 (101) hide show
  1. package/dist/cc.js +297 -83
  2. package/dist/cc.js.map +1 -1
  3. package/dist/constants.js +3 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/metrics/behavioral-events.js +39 -0
  6. package/dist/metrics/behavioral-events.js.map +1 -1
  7. package/dist/metrics/constants.js +8 -1
  8. package/dist/metrics/constants.js.map +1 -1
  9. package/dist/services/ApiAiAssistant.js +3 -0
  10. package/dist/services/ApiAiAssistant.js.map +1 -1
  11. package/dist/services/config/Util.js +1 -1
  12. package/dist/services/config/Util.js.map +1 -1
  13. package/dist/services/config/types.js +10 -0
  14. package/dist/services/config/types.js.map +1 -1
  15. package/dist/services/constants.js +1 -0
  16. package/dist/services/constants.js.map +1 -1
  17. package/dist/services/core/Err.js.map +1 -1
  18. package/dist/services/core/Utils.js +57 -6
  19. package/dist/services/core/Utils.js.map +1 -1
  20. package/dist/services/core/aqm-reqs.js +92 -17
  21. package/dist/services/core/aqm-reqs.js.map +1 -1
  22. package/dist/services/core/websocket/WebSocketManager.js +24 -3
  23. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  24. package/dist/services/index.js +1 -0
  25. package/dist/services/index.js.map +1 -1
  26. package/dist/services/task/Task.js +6 -0
  27. package/dist/services/task/Task.js.map +1 -1
  28. package/dist/services/task/TaskManager.js +155 -6
  29. package/dist/services/task/TaskManager.js.map +1 -1
  30. package/dist/services/task/TaskUtils.js +39 -11
  31. package/dist/services/task/TaskUtils.js.map +1 -1
  32. package/dist/services/task/constants.js +7 -1
  33. package/dist/services/task/constants.js.map +1 -1
  34. package/dist/services/task/dialer.js +129 -0
  35. package/dist/services/task/dialer.js.map +1 -1
  36. package/dist/services/task/state-machine/TaskStateMachine.js +30 -3
  37. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  38. package/dist/services/task/state-machine/actions.js +4 -0
  39. package/dist/services/task/state-machine/actions.js.map +1 -1
  40. package/dist/services/task/state-machine/constants.js +3 -0
  41. package/dist/services/task/state-machine/constants.js.map +1 -1
  42. package/dist/services/task/state-machine/guards.js +23 -0
  43. package/dist/services/task/state-machine/guards.js.map +1 -1
  44. package/dist/services/task/state-machine/types.js.map +1 -1
  45. package/dist/services/task/types.js +59 -0
  46. package/dist/services/task/types.js.map +1 -1
  47. package/dist/types/cc.d.ts +76 -2
  48. package/dist/types/constants.d.ts +3 -0
  49. package/dist/types/metrics/constants.d.ts +6 -0
  50. package/dist/types/services/config/types.d.ts +22 -1
  51. package/dist/types/services/core/Err.d.ts +6 -0
  52. package/dist/types/services/core/Utils.d.ts +20 -2
  53. package/dist/types/services/core/aqm-reqs.d.ts +49 -0
  54. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  55. package/dist/types/services/core/websocket/connection-service.d.ts +1 -0
  56. package/dist/types/services/task/Task.d.ts +1 -0
  57. package/dist/types/services/task/TaskUtils.d.ts +13 -0
  58. package/dist/types/services/task/constants.d.ts +6 -0
  59. package/dist/types/services/task/dialer.d.ts +45 -0
  60. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +36 -8
  61. package/dist/types/services/task/state-machine/constants.d.ts +3 -0
  62. package/dist/types/services/task/state-machine/guards.d.ts +2 -0
  63. package/dist/types/services/task/state-machine/types.d.ts +11 -0
  64. package/dist/types/services/task/types.d.ts +75 -1
  65. package/dist/types.js.map +1 -1
  66. package/dist/webex.js +1 -1
  67. package/package.json +2 -2
  68. package/src/cc.ts +389 -111
  69. package/src/constants.ts +3 -0
  70. package/src/metrics/behavioral-events.ts +42 -0
  71. package/src/metrics/constants.ts +9 -1
  72. package/src/services/ApiAiAssistant.ts +3 -0
  73. package/src/services/config/Util.ts +1 -1
  74. package/src/services/config/types.ts +12 -1
  75. package/src/services/constants.ts +1 -0
  76. package/src/services/core/Err.ts +3 -0
  77. package/src/services/core/Utils.ts +68 -5
  78. package/src/services/core/aqm-reqs.ts +100 -22
  79. package/src/services/core/websocket/WebSocketManager.ts +23 -2
  80. package/src/services/index.ts +1 -0
  81. package/src/services/task/Task.ts +13 -0
  82. package/src/services/task/TaskManager.ts +171 -7
  83. package/src/services/task/TaskUtils.ts +33 -1
  84. package/src/services/task/constants.ts +6 -0
  85. package/src/services/task/dialer.ts +136 -1
  86. package/src/services/task/state-machine/TaskStateMachine.ts +37 -5
  87. package/src/services/task/state-machine/actions.ts +4 -0
  88. package/src/services/task/state-machine/constants.ts +3 -0
  89. package/src/services/task/state-machine/guards.ts +30 -0
  90. package/src/services/task/state-machine/types.ts +14 -1
  91. package/src/services/task/types.ts +80 -0
  92. package/src/types.ts +4 -1
  93. package/test/unit/spec/cc.ts +293 -26
  94. package/test/unit/spec/services/config/index.ts +30 -2
  95. package/test/unit/spec/services/core/Utils.ts +367 -1
  96. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +90 -1
  97. package/test/unit/spec/services/core/websocket/connection-service.ts +4 -5
  98. package/test/unit/spec/services/task/TaskManager.ts +2325 -150
  99. package/test/unit/spec/services/task/dialer.ts +372 -96
  100. package/umd/contact-center.min.js +2 -2
  101. 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
  *
@@ -364,7 +371,6 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
364
371
  webex: this.$webex,
365
372
  connectionConfig: this.getConnectionConfig(),
366
373
  });
367
- this.services.webSocketManager.on('message', this.handleWebsocketMessage);
368
374
 
369
375
  this.webCallingService = new WebCallingService(this.$webex);
370
376
  this.apiAIAssistant = new ApiAIAssistant(this.$webex);
@@ -427,6 +433,20 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
427
433
  this.trigger(TASK_EVENTS.TASK_MERGED, task);
428
434
  };
429
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
+
430
450
  /**
431
451
  * Sets up event listeners for incoming tasks and task hydration
432
452
  * Subscribes to task events from the task manager
@@ -437,6 +457,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
437
457
  this.taskManager.on(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
438
458
  this.taskManager.on(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
439
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
+ );
440
464
  }
441
465
 
442
466
  /**
@@ -476,7 +500,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
476
500
  * ```
477
501
  */
478
502
  public async register(): Promise<Profile> {
479
- LoggerProxy.info('Starting CC SDK registration', {
503
+ LoggerProxy.log('Starting CC SDK registration', {
480
504
  module: CC_FILE,
481
505
  method: METHODS.REGISTER,
482
506
  });
@@ -486,6 +510,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
486
510
  METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
487
511
  ]);
488
512
  this.setupEventListeners();
513
+ this.services.webSocketManager.on('message', this.handleWebsocketMessage);
489
514
 
490
515
  const resp = await this.connectWebsocket();
491
516
  // Ensure 'dn' is always populated from 'defaultDn'
@@ -566,6 +591,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
566
591
  this.taskManager.off(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
567
592
  this.taskManager.off(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
568
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
+ );
569
598
  this.taskManager.unregisterIncomingCallEvent();
570
599
 
571
600
  this.services.webSocketManager.off('message', this.handleWebsocketMessage);
@@ -596,6 +625,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
596
625
  this.services.rtdWebSocketManager.close(false, 'Unregistering the SDK');
597
626
  }
598
627
 
628
+ if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
629
+ this.services.rtdWebSocketManager.close(false, 'Unregistering the RTD websocket');
630
+ }
631
+
599
632
  // Clear any cached agent configuration
600
633
  this.agentConfig = null;
601
634
 
@@ -716,107 +749,110 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
716
749
  * @private
717
750
  */
718
751
  private async connectWebsocket() {
719
- LoggerProxy.info('Connecting to websocket', {
752
+ LoggerProxy.log('Connecting to websocket', {
720
753
  module: CC_FILE,
721
754
  method: METHODS.CONNECT_WEBSOCKET,
722
755
  });
756
+
723
757
  try {
724
- return this.services.webSocketManager
725
- .initWebSocket({
726
- body: this.getConnectionConfig(),
727
- resource: SUBSCRIBE_API,
728
- })
729
- .then(async (data: WelcomeEvent) => {
730
- const agentId = data.agentId;
731
- const orgId = this.$webex.credentials.getOrgId();
732
- this.agentConfig = await this.services.config.getAgentConfig(orgId, agentId);
733
- LoggerProxy.log(`Agent config is fetched successfully`, {
734
- module: CC_FILE,
735
- method: METHODS.CONNECT_WEBSOCKET,
736
- });
758
+ const data = (await this.services.webSocketManager.initWebSocket({
759
+ body: this.getConnectionConfig(),
760
+ resource: SUBSCRIBE_API,
761
+ })) as WelcomeEvent;
737
762
 
738
- const configFlags: ConfigFlags = {
739
- isEndTaskEnabled: this.agentConfig.isEndTaskEnabled,
740
- isEndConsultEnabled: this.agentConfig.isEndConsultEnabled,
741
- webRtcEnabled: this.agentConfig.webRtcEnabled,
742
- autoWrapup: this.agentConfig.wrapUpData?.wrapUpProps?.autoWrapup ?? false,
743
- aiFeature: this.agentConfig.aiFeature,
744
- };
745
- this.taskManager.setConfigFlags(configFlags);
746
- // TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
747
- this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
748
- this.taskManager.setAgentId(this.agentConfig.agentId);
749
- this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
750
- this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
751
- /**
752
- * RTD websocket currently supports realtime transcripts and suggested responses.
753
- * Extend this condition when additional AI RTD features are introduced.
754
- */
755
- if (
756
- this.agentConfig.aiFeature?.realtimeTranscripts?.enable ||
757
- this.agentConfig.aiFeature?.suggestedResponses?.enable
758
- ) {
759
- 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}`, {
760
813
  module: CC_FILE,
761
814
  method: METHODS.CONNECT_WEBSOCKET,
762
815
  });
816
+ });
817
+ }
763
818
 
764
- this.services.rtdWebSocketManager
765
- .initWebSocket({
766
- body: this.getConnectionConfig(),
767
- resource: RTD_SUBSCRIBE_API,
768
- })
769
- .then(() => {
770
- LoggerProxy.log('RTD websocket connected successfully', {
771
- module: CC_FILE,
772
- method: METHODS.CONNECT_WEBSOCKET,
773
- });
774
- this.services.rtdWebSocketManager.on('message', this.handleRTDWebsocketMessage);
775
- })
776
- .catch((error) => {
777
- LoggerProxy.error(`Error connecting to RTD websocket ${error}`, {
778
- module: CC_FILE,
779
- method: METHODS.CONNECT_WEBSOCKET,
780
- });
781
- });
782
- }
783
- if (
784
- this.agentConfig.webRtcEnabled &&
785
- this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER) &&
786
- !this.isWebRTCRegistrationDisabled()
787
- ) {
788
- this.$webex.internal.mercury
789
- .connect()
790
- .then(() => {
791
- LoggerProxy.log('Authentication: webex.internal.mercury.connect successful', {
792
- module: CC_FILE,
793
- method: METHODS.CONNECT_WEBSOCKET,
794
- });
795
- })
796
- .catch((error) => {
797
- LoggerProxy.error(`Error occurred during mercury.connect() ${error}`, {
798
- module: CC_FILE,
799
- method: METHODS.CONNECT_WEBSOCKET,
800
- });
801
- });
802
- } else if (this.isWebRTCRegistrationDisabled()) {
803
- LoggerProxy.info(
804
- 'Skipping Mobius registration because disableWebRTCRegistration is enabled',
805
- {
806
- module: CC_FILE,
807
- method: METHODS.CONNECT_WEBSOCKET,
808
- }
809
- );
810
- }
811
- if (this.$config && this.$config.allowAutomatedRelogin) {
812
- 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,
813
842
  }
843
+ );
844
+ }
814
845
 
815
- return this.agentConfig;
816
- })
817
- .catch((error) => {
818
- 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,
819
852
  });
853
+ }
854
+
855
+ return this.agentConfig;
820
856
  } catch (error) {
821
857
  LoggerProxy.error(`Error during register: ${error}`, {
822
858
  module: CC_FILE,
@@ -856,22 +892,37 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
856
892
  * ```
857
893
  */
858
894
  public async stationLogin(data: AgentLogin): Promise<StationLoginResponse> {
859
- LoggerProxy.info('Starting agent station login', {
860
- module: CC_FILE,
861
- method: METHODS.STATION_LOGIN,
862
- });
895
+ const loggerContext = {module: CC_FILE, method: METHODS.STATION_LOGIN};
896
+
863
897
  try {
898
+ LoggerProxy.log(
899
+ `Starting agent station login | loginOption: ${data?.loginOption} teamId: ${data?.teamId}`,
900
+ loggerContext
901
+ );
864
902
  this.metricsManager.timeEvent([
865
903
  METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
866
904
  METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
867
905
  ]);
868
906
 
869
- if (data.loginOption === LoginOption.AGENT_DN && !isValidDialNumber(data.dialNumber)) {
870
- const error = new Error('INVALID_DIAL_NUMBER');
871
- // @ts-ignore - adding custom key to the error object
872
- 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
+ );
873
914
 
874
- throw error;
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;
923
+
924
+ throw error;
925
+ }
875
926
  }
876
927
 
877
928
  const loginResponse = await this.services.agent.stationLogin({
@@ -1269,10 +1320,6 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1269
1320
  }
1270
1321
  };
1271
1322
 
1272
- private handleRTDWebsocketMessage = (event: string) => {
1273
- this.taskManager.handleRealtimeWebsocketEvent(event);
1274
- };
1275
-
1276
1323
  /**
1277
1324
  * Checks whether Mobius/WebRTC registration should be skipped by config
1278
1325
  * @returns {boolean} True when browser WebRTC registration is disabled
@@ -1328,13 +1375,13 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1328
1375
  private async handleConnectionLost(msg: ConnectionLostDetails): Promise<void> {
1329
1376
  if (msg.isConnectionLost) {
1330
1377
  // TODO: Emit an event saying connection is lost
1331
- LoggerProxy.info('event=handleConnectionLost | Connection lost', {
1378
+ LoggerProxy.log('event=handleConnectionLost | Connection lost', {
1332
1379
  module: CC_FILE,
1333
1380
  method: METHODS.HANDLE_CONNECTION_LOST,
1334
1381
  });
1335
1382
  } else if (msg.isSocketReconnected) {
1336
1383
  // TODO: Emit an event saying connection is re-estabilished
1337
- LoggerProxy.info(
1384
+ LoggerProxy.log(
1338
1385
  'event=handleConnectionReconnect | Connection reconnected attempting to request silent relogin',
1339
1386
  {module: CC_FILE, method: METHODS.HANDLE_CONNECTION_LOST}
1340
1387
  );
@@ -1349,7 +1396,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1349
1396
  * @private
1350
1397
  */
1351
1398
  private async silentRelogin(): Promise<void> {
1352
- LoggerProxy.info('Starting silent relogin process', {
1399
+ LoggerProxy.log('Starting silent relogin process', {
1353
1400
  module: CC_FILE,
1354
1401
  method: METHODS.SILENT_RELOGIN,
1355
1402
  });
@@ -1371,7 +1418,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1371
1418
  await this.handleDeviceType(deviceType as LoginOption, dn);
1372
1419
 
1373
1420
  if (lastStateChangeReason === 'agent-wss-disconnect') {
1374
- LoggerProxy.info(
1421
+ LoggerProxy.log(
1375
1422
  'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1376
1423
  {module: CC_FILE, method: METHODS.SILENT_RELOGIN}
1377
1424
  );
@@ -1400,8 +1447,6 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1400
1447
  }
1401
1448
  this.agentConfig.lastStateAuxCodeId = auxCodeId;
1402
1449
  this.agentConfig.isAgentLoggedIn = true;
1403
- // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
1404
- this.services.webSocketManager.on('message', this.handleWebsocketMessage);
1405
1450
 
1406
1451
  LoggerProxy.log(
1407
1452
  `Silent relogin process completed successfully with login Option: ${reLoginResponse.data.deviceType} teamId: ${reLoginResponse.data.teamId}`,
@@ -1644,6 +1689,239 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1644
1689
  }
1645
1690
  }
1646
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
+
1647
1925
  /**
1648
1926
  * Fetches outdial ANI (Automatic Number Identification) entries for an outdial ANI ID.
1649
1927
  *
package/src/constants.ts CHANGED
@@ -61,6 +61,9 @@ export const METHODS = {
61
61
  TOGGLE_MUTE: 'toggleMute',
62
62
  COMPLETE_TRANSFER: 'completeTransfer',
63
63
  GET_OUTDIAL_ANI_ENTRIES: 'getOutdialAniEntries',
64
+ ACCEPT_PREVIEW_CONTACT: 'acceptPreviewContact',
65
+ SKIP_PREVIEW_CONTACT: 'skipPreviewContact',
66
+ REMOVE_PREVIEW_CONTACT: 'removePreviewContact',
64
67
  GET_BASE_URL: 'getBaseUrl',
65
68
  SEND_EVENT: 'sendEvent',
66
69
  GET_SUGGESTED_RESPONSE: 'getSuggestedResponse',