@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
@@ -15,7 +15,7 @@ import type {ContactServiceQueuesResponse} from '../../../src/types';
15
15
  import MockWebex from '@webex/test-helper-mock-webex';
16
16
  import {StationLoginSuccess, AGENT_EVENTS} from '../../../src/services/agent/types';
17
17
  import {SetStateResponse} from '../../../src/types';
18
- import {AGENT, WEB_RTC_PREFIX} from '../../../src/services/constants';
18
+ import {AGENT, SUBSCRIBE_API, WEB_RTC_PREFIX} from '../../../src/services/constants';
19
19
  import Services from '../../../src/services';
20
20
  import config from '../../../src/config';
21
21
  import {CC_EVENTS} from '../../../src/services/config/types';
@@ -89,6 +89,8 @@ describe('webex.cc', () => {
89
89
  initWebSocket: jest.fn(),
90
90
  on: jest.fn(),
91
91
  off: jest.fn(),
92
+ close: jest.fn(),
93
+ isSocketClosed: false,
92
94
  };
93
95
 
94
96
  mockContact = {
@@ -130,6 +132,13 @@ describe('webex.cc', () => {
130
132
  getOutdialAniEntries: jest.fn(),
131
133
  },
132
134
  webSocketManager: mockWebSocketManager,
135
+ rtdWebSocketManager: {
136
+ initWebSocket: jest.fn().mockResolvedValue({}),
137
+ on: jest.fn(),
138
+ off: jest.fn(),
139
+ close: jest.fn(),
140
+ isSocketClosed: false,
141
+ },
133
142
  connectionService: {
134
143
  on: jest.fn(),
135
144
  off: jest.fn(),
@@ -138,6 +147,13 @@ describe('webex.cc', () => {
138
147
 
139
148
  dialer: {
140
149
  startOutdial: jest.fn(),
150
+ acceptPreviewContact: jest.fn(),
151
+ skipPreviewContact: jest.fn(),
152
+ removePreviewContact: jest.fn(),
153
+ },
154
+ apiAIAssistant: {
155
+ sendEvent: jest.fn(),
156
+ fetchHistoricTranscripts: jest.fn(),
141
157
  },
142
158
  };
143
159
 
@@ -195,6 +211,35 @@ describe('webex.cc', () => {
195
211
  webex.emit('ready');
196
212
  });
197
213
 
214
+ it('should throw when WebRTC registration is disabled without multi-login', () => {
215
+ const invalidWebex = MockWebex({
216
+ children: {
217
+ mercury: Mercury,
218
+ },
219
+ logger: {
220
+ log: jest.fn(),
221
+ error: jest.fn(),
222
+ info: jest.fn(),
223
+ },
224
+ credentials: {
225
+ getOrgId: jest.fn(() => 'mockOrgId'),
226
+ },
227
+ config: {
228
+ ...config,
229
+ cc: {
230
+ ...config.cc,
231
+ allowMultiLogin: false,
232
+ disableWebRTCRegistration: true,
233
+ },
234
+ },
235
+ once: jest.fn((event, callback) => callback()),
236
+ }) as unknown as WebexSDK;
237
+
238
+ expect(() => new ContactCenter({parent: invalidWebex})).toThrow(
239
+ '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.'
240
+ );
241
+ });
242
+
198
243
  describe('cc.getDeviceId', () => {
199
244
  it('should return dialNumber when loginOption is EXTENSION', () => {
200
245
  const loginOption = LoginOption.EXTENSION;
@@ -273,6 +318,7 @@ describe('webex.cc', () => {
273
318
  };
274
319
 
275
320
  it('should register successfully and return agent profile', async () => {
321
+ mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: true}} as any;
276
322
  const mercuryConnect = jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
277
323
  const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
278
324
  const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
@@ -294,7 +340,7 @@ describe('webex.cc', () => {
294
340
  const result = await webex.cc.register();
295
341
 
296
342
  // Verify logging calls
297
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
343
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
298
344
  module: CC_FILE,
299
345
  method: 'register',
300
346
  });
@@ -316,7 +362,7 @@ describe('webex.cc', () => {
316
362
  clientType: 'WebexCCSDK',
317
363
  allowMultiLogin: false,
318
364
  },
319
- resource: 'v1/notification/subscribe',
365
+ resource: SUBSCRIBE_API,
320
366
  });
321
367
 
322
368
  // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
@@ -328,7 +374,24 @@ describe('webex.cc', () => {
328
374
  TASK_EVENTS.TASK_HYDRATE,
329
375
  expect.any(Function)
330
376
  );
377
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
378
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
379
+ expect.any(Function)
380
+ );
331
381
  expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
382
+ expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
383
+ body: {
384
+ force: true,
385
+ isKeepAliveEnabled: false,
386
+ clientType: 'WebexCCSDK',
387
+ allowMultiLogin: false,
388
+ },
389
+ resource: 'v1/realtime/subscribe',
390
+ });
391
+ expect(webex.cc.services.rtdWebSocketManager.on).toHaveBeenCalledWith(
392
+ 'message',
393
+ expect.any(Function)
394
+ );
332
395
 
333
396
  expect(configSpy).toHaveBeenCalled();
334
397
  expect(LoggerProxy.log).toHaveBeenCalledWith('Agent config is fetched successfully', {
@@ -384,7 +447,7 @@ describe('webex.cc', () => {
384
447
  clientType: 'WebexCCSDK',
385
448
  allowMultiLogin: true,
386
449
  },
387
- resource: 'v1/notification/subscribe',
450
+ resource: SUBSCRIBE_API,
388
451
  });
389
452
  expect(configSpy).toHaveBeenCalled();
390
453
  expect(LoggerProxy.log).toHaveBeenCalledWith('Agent config is fetched successfully', {
@@ -406,7 +469,7 @@ describe('webex.cc', () => {
406
469
 
407
470
  await expect(webex.cc.register()).rejects.toThrow('Error while performing register');
408
471
 
409
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
472
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
410
473
  module: CC_FILE,
411
474
  method: 'register',
412
475
  });
@@ -464,7 +527,7 @@ describe('webex.cc', () => {
464
527
  clientType: 'WebexCCSDK',
465
528
  allowMultiLogin: false,
466
529
  },
467
- resource: 'v1/notification/subscribe',
530
+ resource: SUBSCRIBE_API,
468
531
  });
469
532
 
470
533
  expect(mockTaskManager.on).toHaveBeenCalledWith(
@@ -475,6 +538,10 @@ describe('webex.cc', () => {
475
538
  TASK_EVENTS.TASK_HYDRATE,
476
539
  expect.any(Function)
477
540
  );
541
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
542
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
543
+ expect.any(Function)
544
+ );
478
545
  expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
479
546
 
480
547
  expect(configSpy).toHaveBeenCalled();
@@ -486,8 +553,39 @@ describe('webex.cc', () => {
486
553
  expect(result).toEqual(mockAgentProfile);
487
554
  });
488
555
 
556
+ it('should skip mercury connection when disableWebRTCRegistration is enabled', async () => {
557
+ webex.cc.$config = {
558
+ ...webex.cc.$config,
559
+ allowAutomatedRelogin: false,
560
+ disableWebRTCRegistration: true,
561
+ };
562
+ mockAgentProfile.webRtcEnabled = true;
563
+ const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
564
+ const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
565
+ const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
566
+ jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
567
+ mockWebSocketManager.initWebSocket.mockResolvedValue({
568
+ agentId: 'agent123',
569
+ });
570
+
571
+ const result = await webex.cc.register();
572
+
573
+ expect(connectWebsocketSpy).toHaveBeenCalled();
574
+ expect(setupEventListenersSpy).toHaveBeenCalled();
575
+ expect(mercurySpy).not.toHaveBeenCalled();
576
+ expect(LoggerProxy.info).toHaveBeenCalledWith(
577
+ 'Skipping Mobius registration because disableWebRTCRegistration is enabled',
578
+ {
579
+ module: CC_FILE,
580
+ method: 'connectWebsocket',
581
+ }
582
+ );
583
+ expect(result).toEqual(mockAgentProfile);
584
+ });
585
+
489
586
  it('should not attempt for mercury connection when webrtc is disabled', async () => {
490
587
  mockAgentProfile.webRtcEnabled = false;
588
+ mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
491
589
  const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
492
590
  const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
493
591
  const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
@@ -517,13 +615,39 @@ describe('webex.cc', () => {
517
615
  clientType: 'WebexCCSDK',
518
616
  allowMultiLogin: false,
519
617
  },
520
- resource: 'v1/notification/subscribe',
618
+ resource: SUBSCRIBE_API,
521
619
  });
522
620
 
523
621
  expect(configSpy).toHaveBeenCalled();
524
622
  expect(mercurySpy).not.toHaveBeenCalled();
623
+ expect(webex.cc.services.rtdWebSocketManager.initWebSocket).not.toHaveBeenCalled();
525
624
  expect(result).toEqual(mockAgentProfile);
526
625
  });
626
+
627
+ it('should not connect RTD websocket when realtime transcripts feature is disabled', async () => {
628
+ mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
629
+ jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
630
+ jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
631
+ data: {
632
+ auxCodeId: 'auxCodeId',
633
+ agentId: 'agentId',
634
+ deviceType: LoginOption.EXTENSION,
635
+ dn: '12345',
636
+ },
637
+ });
638
+ jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
639
+ mockWebSocketManager.initWebSocket.mockResolvedValue({
640
+ agentId: 'agent123',
641
+ });
642
+
643
+ await webex.cc.register();
644
+
645
+ expect(webex.cc.services.rtdWebSocketManager.initWebSocket).not.toHaveBeenCalled();
646
+ expect(webex.cc.services.rtdWebSocketManager.on).not.toHaveBeenCalledWith(
647
+ 'message',
648
+ expect.any(Function)
649
+ );
650
+ });
527
651
  });
528
652
 
529
653
  describe('stationLogin', () => {
@@ -627,10 +751,8 @@ describe('webex.cc', () => {
627
751
 
628
752
  expect(emitSpy).toHaveBeenCalledTimes(1);
629
753
  expect(emitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, mockTask);
630
- // Verify message event listener
631
- const messageCallback = mockWebSocketManager.on.mock.calls.find(
632
- (call) => call[0] === 'message'
633
- )[1];
754
+ // Verify websocket message handling
755
+ const messageCallback = webex.cc['handleWebsocketMessage'];
634
756
  const agentStateChangeEventData = {
635
757
  type: CC_EVENTS.AGENT_STATE_CHANGE,
636
758
  data: {some: 'data'},
@@ -658,6 +780,59 @@ describe('webex.cc', () => {
658
780
  );
659
781
  });
660
782
 
783
+ it('should skip web calling line registration when disableWebRTCRegistration is enabled', async () => {
784
+ const options = {
785
+ teamId: 'teamId',
786
+ loginOption: LoginOption.BROWSER,
787
+ };
788
+
789
+ webex.cc.$config = {
790
+ ...webex.cc.$config,
791
+ disableWebRTCRegistration: true,
792
+ };
793
+ webex.cc.agentConfig = {
794
+ agentId: 'agentId',
795
+ webRtcEnabled: true,
796
+ loginVoiceOptions: ['BROWSER', 'EXTENSION', 'AGENT_DN'],
797
+ };
798
+
799
+ const registerWebCallingLineSpy = jest.spyOn(
800
+ webex.cc.webCallingService,
801
+ 'registerWebCallingLine'
802
+ );
803
+
804
+ jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
805
+ data: {
806
+ loginOption: LoginOption.BROWSER,
807
+ agentId: 'agentId',
808
+ teamId: 'teamId',
809
+ siteId: 'siteId',
810
+ roles: [AGENT],
811
+ channelsMap: {
812
+ chat: [],
813
+ email: [],
814
+ social: [],
815
+ telephony: [],
816
+ },
817
+ },
818
+ trackingId: 'notifs_52628',
819
+ orgId: 'orgId',
820
+ type: 'StationLoginSuccess',
821
+ eventType: 'STATION_LOGIN',
822
+ } as unknown as StationLoginSuccess);
823
+
824
+ await webex.cc.stationLogin(options);
825
+
826
+ expect(registerWebCallingLineSpy).not.toHaveBeenCalled();
827
+ expect(LoggerProxy.info).toHaveBeenCalledWith(
828
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
829
+ {
830
+ module: CC_FILE,
831
+ method: 'stationLogin',
832
+ }
833
+ );
834
+ });
835
+
661
836
  it('should not attempt mobius registration for LoginOption.BROWSER if webrtc is disabled', async () => {
662
837
  const options = {
663
838
  teamId: 'teamId',
@@ -780,10 +955,13 @@ describe('webex.cc', () => {
780
955
  const result = await webex.cc.stationLogin(options);
781
956
 
782
957
  // Verify logging calls
783
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
784
- module: CC_FILE,
785
- method: 'stationLogin',
786
- });
958
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
959
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
960
+ {
961
+ module: CC_FILE,
962
+ method: 'stationLogin',
963
+ }
964
+ );
787
965
  expect(LoggerProxy.log).toHaveBeenCalledWith(
788
966
  `Agent station login completed successfully agentId: ${mockData.data.agentId} loginOption: ${mockData.data.loginOption} teamId: ${mockData.data.teamId}`,
789
967
  {
@@ -834,10 +1012,13 @@ describe('webex.cc', () => {
834
1012
 
835
1013
  await expect(webex.cc.stationLogin(options)).rejects.toThrow(error.details.data.reason);
836
1014
 
837
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
838
- module: CC_FILE,
839
- method: 'stationLogin',
840
- });
1015
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1016
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
1017
+ {
1018
+ module: CC_FILE,
1019
+ method: 'stationLogin',
1020
+ }
1021
+ );
841
1022
  expect(LoggerProxy.error).toHaveBeenCalledWith(
842
1023
  `stationLogin failed with reason: ${error.details.data.reason}`,
843
1024
  {module: CC_FILE, method: 'stationLogin', trackingId: error.details.trackingId}
@@ -1176,11 +1357,11 @@ describe('webex.cc', () => {
1176
1357
  const webSocketManagerOnSpy = jest.spyOn(webex.cc.services.webSocketManager, 'on');
1177
1358
  await webex.cc['silentRelogin']();
1178
1359
 
1179
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1360
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1180
1361
  module: CC_FILE,
1181
1362
  method: 'silentRelogin',
1182
1363
  });
1183
- expect(LoggerProxy.info).toHaveBeenCalledWith(
1364
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1184
1365
  'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1185
1366
  {module: CC_FILE, method: 'silentRelogin'}
1186
1367
  );
@@ -1225,7 +1406,7 @@ describe('webex.cc', () => {
1225
1406
 
1226
1407
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1227
1408
  await webex.cc['silentRelogin']();
1228
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1409
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1229
1410
  module: CC_FILE,
1230
1411
  method: 'silentRelogin',
1231
1412
  });
@@ -1240,7 +1421,7 @@ describe('webex.cc', () => {
1240
1421
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1241
1422
 
1242
1423
  await expect(webex.cc['silentRelogin']()).rejects.toThrow(error);
1243
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1424
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1244
1425
  module: CC_FILE,
1245
1426
  method: 'silentRelogin',
1246
1427
  });
@@ -1282,7 +1463,7 @@ describe('webex.cc', () => {
1282
1463
 
1283
1464
  await webex.cc['silentRelogin']();
1284
1465
 
1285
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1466
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1286
1467
  module: CC_FILE,
1287
1468
  method: 'silentRelogin',
1288
1469
  });
@@ -1558,6 +1739,13 @@ describe('webex.cc', () => {
1558
1739
  });
1559
1740
 
1560
1741
  it('should unregister successfully and clean up all resources when webrtc is enabled', async () => {
1742
+ webex.cc.services.rtdWebSocketManager = {
1743
+ isSocketClosed: false,
1744
+ close: jest.fn(),
1745
+ off: jest.fn(),
1746
+ on: jest.fn(),
1747
+ } as any;
1748
+
1561
1749
  await webex.cc.deregister();
1562
1750
 
1563
1751
  expect(mockTaskManager.off).toHaveBeenCalledWith(
@@ -1568,14 +1756,25 @@ describe('webex.cc', () => {
1568
1756
  TASK_EVENTS.TASK_HYDRATE,
1569
1757
  expect.any(Function)
1570
1758
  );
1759
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1760
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1761
+ expect.any(Function)
1762
+ );
1571
1763
  expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1764
+ expect(webex.cc.services.rtdWebSocketManager.off).toHaveBeenCalledWith(
1765
+ 'message',
1766
+ expect.any(Function)
1767
+ );
1572
1768
  expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
1573
1769
  'connectionLost',
1574
1770
  expect.any(Function)
1575
1771
  );
1576
1772
 
1577
1773
  expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1578
- expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1774
+ expect(webex.cc.services.rtdWebSocketManager.close).toHaveBeenCalledWith(
1775
+ false,
1776
+ 'Unregistering the RTD websocket'
1777
+ );
1579
1778
  expect(webex.cc.agentConfig).toBeNull();
1580
1779
 
1581
1780
  expect(webex.internal.mercury.off).toHaveBeenCalledWith('online');
@@ -1617,6 +1816,13 @@ describe('webex.cc', () => {
1617
1816
  const [, hydrateCallback] = hydrateCalls[0];
1618
1817
  expect(hydrateCallback).toBe(webex.cc['handleTaskHydrate']);
1619
1818
 
1819
+ const multiLoginHydrateCalls = mockTaskManager.off.mock.calls.filter(
1820
+ ([evt]) => evt === TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE
1821
+ );
1822
+ expect(multiLoginHydrateCalls).toHaveLength(1);
1823
+ const [, multiLoginHydrateCallback] = multiLoginHydrateCalls[0];
1824
+ expect(multiLoginHydrateCallback).toBe(webex.cc['handleTaskMultiLoginHydrate']);
1825
+
1620
1826
  const messageCalls = mockWebSocketManager.off.mock.calls.filter(([evt]) => evt === 'message');
1621
1827
  expect(messageCalls).toHaveLength(1);
1622
1828
  const [, messageCallback] = messageCalls[0];
@@ -1642,6 +1848,10 @@ describe('webex.cc', () => {
1642
1848
  TASK_EVENTS.TASK_HYDRATE,
1643
1849
  expect.any(Function)
1644
1850
  );
1851
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1852
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1853
+ expect.any(Function)
1854
+ );
1645
1855
  expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1646
1856
  expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
1647
1857
  'connectionLost',
@@ -1687,6 +1897,10 @@ describe('webex.cc', () => {
1687
1897
  TASK_EVENTS.TASK_HYDRATE,
1688
1898
  expect.any(Function)
1689
1899
  );
1900
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1901
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1902
+ expect.any(Function)
1903
+ );
1690
1904
 
1691
1905
  expect(LoggerProxy.error).toHaveBeenCalledWith(`Error during deregister: ${mockError}`, {
1692
1906
  module: CC_FILE,
@@ -1709,7 +1923,7 @@ describe('webex.cc', () => {
1709
1923
 
1710
1924
  beforeEach(() => {
1711
1925
  emitSpy = jest.spyOn(webex.cc, 'emit');
1712
- messageCallback = mockWebSocketManager.on.mock.calls.find((c) => c[0] === 'message')[1];
1926
+ messageCallback = webex.cc['handleWebsocketMessage'];
1713
1927
  });
1714
1928
 
1715
1929
  it('should emit AGENT_STATION_LOGIN_SUCCESS on CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS with mapped payload', () => {
@@ -2239,4 +2453,196 @@ describe('webex.cc', () => {
2239
2453
  );
2240
2454
  });
2241
2455
  });
2456
+
2457
+ describe('acceptPreviewContact', () => {
2458
+ const previewPayload = {
2459
+ interactionId: 'interaction-123',
2460
+ campaignId: 'campaign-456',
2461
+ };
2462
+
2463
+ it('should accept preview contact successfully', async () => {
2464
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2465
+
2466
+ const acceptPreviewContactMock = jest
2467
+ .spyOn(webex.cc.services.dialer, 'acceptPreviewContact')
2468
+ .mockResolvedValue(mockResponse);
2469
+
2470
+ const result = await webex.cc.acceptPreviewContact(previewPayload);
2471
+
2472
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Accepting campaign preview contact', {
2473
+ module: CC_FILE,
2474
+ method: 'acceptPreviewContact',
2475
+ });
2476
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2477
+ 'Campaign preview contact accepted successfully',
2478
+ {
2479
+ module: CC_FILE,
2480
+ method: 'acceptPreviewContact',
2481
+ trackingId: 'track-123',
2482
+ interactionId: previewPayload.interactionId,
2483
+ }
2484
+ );
2485
+
2486
+ expect(acceptPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2487
+ expect(result).toEqual(mockResponse);
2488
+ });
2489
+
2490
+ it('should handle error during acceptPreviewContact', async () => {
2491
+ getErrorDetailsSpy.mockRestore();
2492
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2493
+
2494
+ const error = {
2495
+ details: {
2496
+ trackingId: '1234',
2497
+ data: {
2498
+ reason: 'Error while performing acceptPreviewContact',
2499
+ },
2500
+ },
2501
+ };
2502
+
2503
+ jest.spyOn(webex.cc.services.dialer, 'acceptPreviewContact').mockRejectedValue(error);
2504
+
2505
+ await expect(webex.cc.acceptPreviewContact(previewPayload)).rejects.toThrow(
2506
+ error.details.data.reason
2507
+ );
2508
+
2509
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Accepting campaign preview contact', {
2510
+ module: CC_FILE,
2511
+ method: 'acceptPreviewContact',
2512
+ });
2513
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2514
+ `acceptPreviewContact failed with reason: ${error.details.data.reason}`,
2515
+ {module: CC_FILE, method: 'acceptPreviewContact', trackingId: error.details.trackingId}
2516
+ );
2517
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'acceptPreviewContact', CC_FILE);
2518
+ });
2519
+ });
2520
+
2521
+ describe('skipPreviewContact', () => {
2522
+ const previewPayload = {
2523
+ interactionId: 'interaction-123',
2524
+ campaignId: 'campaign-456',
2525
+ };
2526
+
2527
+ it('should skip preview contact successfully', async () => {
2528
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2529
+
2530
+ const skipPreviewContactMock = jest
2531
+ .spyOn(webex.cc.services.dialer, 'skipPreviewContact')
2532
+ .mockResolvedValue(mockResponse);
2533
+
2534
+ const result = await webex.cc.skipPreviewContact(previewPayload);
2535
+
2536
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
2537
+ module: CC_FILE,
2538
+ method: 'skipPreviewContact',
2539
+ });
2540
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2541
+ 'Campaign preview contact skipped successfully',
2542
+ {
2543
+ module: CC_FILE,
2544
+ method: 'skipPreviewContact',
2545
+ trackingId: 'track-123',
2546
+ interactionId: previewPayload.interactionId,
2547
+ }
2548
+ );
2549
+
2550
+ expect(skipPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2551
+ expect(result).toEqual(mockResponse);
2552
+ });
2553
+
2554
+ it('should handle error during skipPreviewContact', async () => {
2555
+ getErrorDetailsSpy.mockRestore();
2556
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2557
+
2558
+ const error = {
2559
+ details: {
2560
+ trackingId: '1234',
2561
+ data: {
2562
+ reason: 'Error while performing skipPreviewContact',
2563
+ },
2564
+ },
2565
+ };
2566
+
2567
+ jest.spyOn(webex.cc.services.dialer, 'skipPreviewContact').mockRejectedValue(error);
2568
+
2569
+ await expect(webex.cc.skipPreviewContact(previewPayload)).rejects.toThrow(
2570
+ error.details.data.reason
2571
+ );
2572
+
2573
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
2574
+ module: CC_FILE,
2575
+ method: 'skipPreviewContact',
2576
+ });
2577
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2578
+ `skipPreviewContact failed with reason: ${error.details.data.reason}`,
2579
+ {module: CC_FILE, method: 'skipPreviewContact', trackingId: error.details.trackingId}
2580
+ );
2581
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'skipPreviewContact', CC_FILE);
2582
+ });
2583
+ });
2584
+
2585
+ describe('removePreviewContact', () => {
2586
+ const previewPayload = {
2587
+ interactionId: 'interaction-123',
2588
+ campaignId: 'campaign-456',
2589
+ };
2590
+
2591
+ it('should remove preview contact successfully', async () => {
2592
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2593
+
2594
+ const removePreviewContactMock = jest
2595
+ .spyOn(webex.cc.services.dialer, 'removePreviewContact')
2596
+ .mockResolvedValue(mockResponse);
2597
+
2598
+ const result = await webex.cc.removePreviewContact(previewPayload);
2599
+
2600
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
2601
+ module: CC_FILE,
2602
+ method: 'removePreviewContact',
2603
+ });
2604
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2605
+ 'Campaign preview contact removed successfully',
2606
+ {
2607
+ module: CC_FILE,
2608
+ method: 'removePreviewContact',
2609
+ trackingId: 'track-123',
2610
+ interactionId: previewPayload.interactionId,
2611
+ }
2612
+ );
2613
+
2614
+ expect(removePreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2615
+ expect(result).toEqual(mockResponse);
2616
+ });
2617
+
2618
+ it('should handle error during removePreviewContact', async () => {
2619
+ getErrorDetailsSpy.mockRestore();
2620
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2621
+
2622
+ const error = {
2623
+ details: {
2624
+ trackingId: '1234',
2625
+ data: {
2626
+ reason: 'Error while performing removePreviewContact',
2627
+ },
2628
+ },
2629
+ };
2630
+
2631
+ jest.spyOn(webex.cc.services.dialer, 'removePreviewContact').mockRejectedValue(error);
2632
+
2633
+ await expect(webex.cc.removePreviewContact(previewPayload)).rejects.toThrow(
2634
+ error.details.data.reason
2635
+ );
2636
+
2637
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
2638
+ module: CC_FILE,
2639
+ method: 'removePreviewContact',
2640
+ });
2641
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2642
+ `removePreviewContact failed with reason: ${error.details.data.reason}`,
2643
+ {module: CC_FILE, method: 'removePreviewContact', trackingId: error.details.trackingId}
2644
+ );
2645
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'removePreviewContact', CC_FILE);
2646
+ });
2647
+ });
2242
2648
  });