@webex/contact-center 3.12.0-next.9 → 3.12.0-task-refactor.2

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 (200) hide show
  1. package/AGENTS.md +438 -0
  2. package/ai-docs/README.md +131 -0
  3. package/ai-docs/RULES.md +455 -0
  4. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  5. package/ai-docs/patterns/testing-patterns.md +480 -0
  6. package/ai-docs/patterns/typescript-patterns.md +365 -0
  7. package/ai-docs/templates/README.md +102 -0
  8. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  9. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  10. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  11. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  12. package/ai-docs/templates/new-method/00-master.md +80 -0
  13. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  14. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  15. package/ai-docs/templates/new-method/03-tests.md +201 -0
  16. package/ai-docs/templates/new-method/04-validation.md +141 -0
  17. package/ai-docs/templates/new-service/00-master.md +109 -0
  18. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  19. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  20. package/ai-docs/templates/new-service/03-integration.md +178 -0
  21. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  22. package/ai-docs/templates/new-service/05-validation.md +145 -0
  23. package/dist/cc.js +65 -123
  24. package/dist/cc.js.map +1 -1
  25. package/dist/constants.js +13 -2
  26. package/dist/constants.js.map +1 -1
  27. package/dist/index.js +13 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/metrics/behavioral-events.js +26 -13
  30. package/dist/metrics/behavioral-events.js.map +1 -1
  31. package/dist/metrics/constants.js +7 -6
  32. package/dist/metrics/constants.js.map +1 -1
  33. package/dist/services/ApiAiAssistant.js +0 -3
  34. package/dist/services/ApiAiAssistant.js.map +1 -1
  35. package/dist/services/config/Util.js +2 -3
  36. package/dist/services/config/Util.js.map +1 -1
  37. package/dist/services/config/types.js +16 -14
  38. package/dist/services/config/types.js.map +1 -1
  39. package/dist/services/constants.js +0 -1
  40. package/dist/services/constants.js.map +1 -1
  41. package/dist/services/core/Err.js.map +1 -1
  42. package/dist/services/core/Utils.js +79 -55
  43. package/dist/services/core/Utils.js.map +1 -1
  44. package/dist/services/core/aqm-reqs.js +17 -92
  45. package/dist/services/core/aqm-reqs.js.map +1 -1
  46. package/dist/services/core/websocket/WebSocketManager.js +5 -25
  47. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  48. package/dist/services/core/websocket/types.js.map +1 -1
  49. package/dist/services/index.js +1 -2
  50. package/dist/services/index.js.map +1 -1
  51. package/dist/services/task/Task.js +644 -0
  52. package/dist/services/task/Task.js.map +1 -0
  53. package/dist/services/task/TaskFactory.js +45 -0
  54. package/dist/services/task/TaskFactory.js.map +1 -0
  55. package/dist/services/task/TaskManager.js +570 -535
  56. package/dist/services/task/TaskManager.js.map +1 -1
  57. package/dist/services/task/TaskUtils.js +132 -28
  58. package/dist/services/task/TaskUtils.js.map +1 -1
  59. package/dist/services/task/constants.js +7 -6
  60. package/dist/services/task/constants.js.map +1 -1
  61. package/dist/services/task/dialer.js +0 -51
  62. package/dist/services/task/dialer.js.map +1 -1
  63. package/dist/services/task/digital/Digital.js +77 -0
  64. package/dist/services/task/digital/Digital.js.map +1 -0
  65. package/dist/services/task/state-machine/TaskStateMachine.js +634 -0
  66. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  67. package/dist/services/task/state-machine/actions.js +372 -0
  68. package/dist/services/task/state-machine/actions.js.map +1 -0
  69. package/dist/services/task/state-machine/constants.js +139 -0
  70. package/dist/services/task/state-machine/constants.js.map +1 -0
  71. package/dist/services/task/state-machine/guards.js +263 -0
  72. package/dist/services/task/state-machine/guards.js.map +1 -0
  73. package/dist/services/task/state-machine/index.js +53 -0
  74. package/dist/services/task/state-machine/index.js.map +1 -0
  75. package/dist/services/task/state-machine/types.js +54 -0
  76. package/dist/services/task/state-machine/types.js.map +1 -0
  77. package/dist/services/task/state-machine/uiControlsComputer.js +377 -0
  78. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  79. package/dist/services/task/taskDataNormalizer.js +99 -0
  80. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  81. package/dist/services/task/types.js +157 -18
  82. package/dist/services/task/types.js.map +1 -1
  83. package/dist/services/task/voice/Voice.js +1031 -0
  84. package/dist/services/task/voice/Voice.js.map +1 -0
  85. package/dist/services/task/voice/WebRTC.js +149 -0
  86. package/dist/services/task/voice/WebRTC.js.map +1 -0
  87. package/dist/types/cc.d.ts +4 -33
  88. package/dist/types/constants.d.ts +13 -2
  89. package/dist/types/index.d.ts +11 -5
  90. package/dist/types/metrics/constants.d.ts +5 -3
  91. package/dist/types/services/ApiAiAssistant.d.ts +1 -1
  92. package/dist/types/services/config/types.d.ts +97 -25
  93. package/dist/types/services/core/Err.d.ts +0 -2
  94. package/dist/types/services/core/Utils.d.ts +25 -23
  95. package/dist/types/services/core/aqm-reqs.d.ts +0 -49
  96. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -1
  97. package/dist/types/services/core/websocket/connection-service.d.ts +0 -1
  98. package/dist/types/services/core/websocket/types.d.ts +1 -1
  99. package/dist/types/services/index.d.ts +1 -1
  100. package/dist/types/services/task/Task.d.ts +146 -0
  101. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  102. package/dist/types/services/task/TaskUtils.d.ts +39 -8
  103. package/dist/types/services/task/constants.d.ts +5 -4
  104. package/dist/types/services/task/dialer.d.ts +0 -15
  105. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  106. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +906 -0
  107. package/dist/types/services/task/state-machine/actions.d.ts +8 -0
  108. package/dist/types/services/task/state-machine/constants.d.ts +91 -0
  109. package/dist/types/services/task/state-machine/guards.d.ts +78 -0
  110. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  111. package/dist/types/services/task/state-machine/types.d.ts +256 -0
  112. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  113. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  114. package/dist/types/services/task/types.d.ts +539 -88
  115. package/dist/types/services/task/voice/Voice.d.ts +183 -0
  116. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  117. package/dist/types/types.d.ts +68 -0
  118. package/dist/types/webex.d.ts +1 -0
  119. package/dist/types.js +70 -0
  120. package/dist/types.js.map +1 -1
  121. package/dist/webex.js +14 -2
  122. package/dist/webex.js.map +1 -1
  123. package/package.json +14 -11
  124. package/src/cc.ts +91 -177
  125. package/src/constants.ts +13 -2
  126. package/src/index.ts +14 -5
  127. package/src/metrics/ai-docs/AGENTS.md +348 -0
  128. package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
  129. package/src/metrics/behavioral-events.ts +28 -14
  130. package/src/metrics/constants.ts +7 -8
  131. package/src/services/ApiAiAssistant.ts +2 -4
  132. package/src/services/agent/ai-docs/AGENTS.md +238 -0
  133. package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
  134. package/src/services/ai-docs/AGENTS.md +384 -0
  135. package/src/services/config/Util.ts +2 -3
  136. package/src/services/config/ai-docs/AGENTS.md +253 -0
  137. package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
  138. package/src/services/config/types.ts +108 -20
  139. package/src/services/constants.ts +0 -1
  140. package/src/services/core/Err.ts +0 -1
  141. package/src/services/core/Utils.ts +90 -67
  142. package/src/services/core/ai-docs/AGENTS.md +379 -0
  143. package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
  144. package/src/services/core/aqm-reqs.ts +22 -100
  145. package/src/services/core/websocket/WebSocketManager.ts +4 -23
  146. package/src/services/core/websocket/types.ts +1 -1
  147. package/src/services/index.ts +1 -2
  148. package/src/services/task/Task.ts +785 -0
  149. package/src/services/task/TaskFactory.ts +55 -0
  150. package/src/services/task/TaskManager.ts +579 -633
  151. package/src/services/task/TaskUtils.ts +175 -31
  152. package/src/services/task/ai-docs/AGENTS.md +448 -0
  153. package/src/services/task/ai-docs/ARCHITECTURE.md +573 -0
  154. package/src/services/task/constants.ts +5 -4
  155. package/src/services/task/dialer.ts +1 -56
  156. package/src/services/task/digital/Digital.ts +95 -0
  157. package/src/services/task/state-machine/TaskStateMachine.ts +793 -0
  158. package/src/services/task/state-machine/actions.ts +422 -0
  159. package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
  160. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
  161. package/src/services/task/state-machine/constants.ts +150 -0
  162. package/src/services/task/state-machine/guards.ts +303 -0
  163. package/src/services/task/state-machine/index.ts +28 -0
  164. package/src/services/task/state-machine/types.ts +228 -0
  165. package/src/services/task/state-machine/uiControlsComputer.ts +542 -0
  166. package/src/services/task/taskDataNormalizer.ts +137 -0
  167. package/src/services/task/types.ts +641 -95
  168. package/src/services/task/voice/Voice.ts +1255 -0
  169. package/src/services/task/voice/WebRTC.ts +187 -0
  170. package/src/types.ts +88 -5
  171. package/src/utils/AGENTS.md +276 -0
  172. package/src/webex.js +2 -0
  173. package/test/unit/spec/cc.ts +59 -142
  174. package/test/unit/spec/logger-proxy.ts +70 -0
  175. package/test/unit/spec/services/ApiAiAssistant.ts +17 -0
  176. package/test/unit/spec/services/config/index.ts +26 -55
  177. package/test/unit/spec/services/core/Utils.ts +103 -52
  178. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +48 -112
  179. package/test/unit/spec/services/core/websocket/connection-service.ts +5 -4
  180. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  181. package/test/unit/spec/services/task/Task.ts +416 -0
  182. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  183. package/test/unit/spec/services/task/TaskManager.ts +781 -1735
  184. package/test/unit/spec/services/task/TaskUtils.ts +125 -0
  185. package/test/unit/spec/services/task/dialer.ts +112 -198
  186. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  187. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +473 -0
  188. package/test/unit/spec/services/task/state-machine/guards.ts +288 -0
  189. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  190. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +147 -0
  191. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  192. package/test/unit/spec/services/task/voice/Voice.ts +587 -0
  193. package/test/unit/spec/services/task/voice/WebRTC.ts +242 -0
  194. package/umd/contact-center.min.js +2 -2
  195. package/umd/contact-center.min.js.map +1 -1
  196. package/dist/services/task/index.js +0 -1525
  197. package/dist/services/task/index.js.map +0 -1
  198. package/dist/types/services/task/index.d.ts +0 -650
  199. package/src/services/task/index.ts +0 -1801
  200. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -1,5 +1,4 @@
1
1
  import * as Utils from '../../../../../src/services/core/Utils';
2
- import {FALLBACK_DIAL_NUMBER_REGEX} from '../../../../../src/services/core/Utils';
3
2
  import LoggerProxy from '../../../../../src/logger-proxy';
4
3
  import WebexRequest from '../../../../../src/services/core/WebexRequest';
5
4
  import {LoginOption, WebexRequestPayload} from '../../../../../src/types';
@@ -245,7 +244,7 @@ describe('Utils', () => {
245
244
  const result = Utils.getStationLoginErrorData(failure, LoginOption.AGENT_DN);
246
245
  expect(result).toEqual({
247
246
  message:
248
- 'Enter a valid dial number. For help, reach out to your administrator or support team.',
247
+ 'Enter a valid US dial number. For help, reach out to your administrator or support team.',
249
248
  fieldName: LoginOption.AGENT_DN,
250
249
  });
251
250
  });
@@ -531,7 +530,7 @@ describe('Utils', () => {
531
530
  };
532
531
 
533
532
  const result = Utils.calculateDestAgentId(interaction, currentAgentId);
534
- expect(result).toBeUndefined();
533
+ expect(result).toBe('');
535
534
  });
536
535
 
537
536
  it('should handle CBT scenario when phone number is not a direct participant key', () => {
@@ -558,65 +557,117 @@ describe('Utils', () => {
558
557
  });
559
558
  });
560
559
 
561
- describe('isValidDialNumber', () => {
562
- const anyFormatEntry = {
563
- name: 'Any Format',
564
- prefix: '',
565
- regex: '([0-9a-zA-Z]+[-._])*[0-9a-zA-Z]+',
566
- strippedChars: '( )-',
567
- };
568
-
569
- const usOnlyEntry = {
570
- name: 'US',
571
- prefix: '1',
572
- regex: FALLBACK_DIAL_NUMBER_REGEX.source,
573
- strippedChars: '( )-',
574
- };
575
-
576
- describe('with multiple dial plan entries (Any Format + US)', () => {
577
- const dialPlanEntries = [anyFormatEntry, usOnlyEntry];
578
-
579
- it('should return true for a valid US phone number', () => {
580
- const result = Utils.isValidDialNumber('12223334567', dialPlanEntries);
581
- expect(result).toBe(true);
582
- });
560
+ describe('calculateDestType', () => {
561
+ const currentAgentId = 'current-agent-123';
583
562
 
584
- it('should return true for a UK phone number', () => {
585
- const result = Utils.isValidDialNumber('+442030484377', dialPlanEntries);
586
- expect(result).toBe(true);
587
- });
563
+ it('should return DIALNUMBER when pType is DN', () => {
564
+ const interaction: any = {
565
+ media: {
566
+ consult: {
567
+ mType: 'consult',
568
+ participants: [currentAgentId, 'dest-agent-456'],
569
+ },
570
+ },
571
+ participants: {
572
+ [currentAgentId]: {type: 'Agent', pType: 'Agent'},
573
+ 'dest-agent-456': {type: 'Agent', pType: 'DN', id: 'dest-agent-456'},
574
+ },
575
+ };
576
+
577
+ const result = Utils.calculateDestType(interaction, currentAgentId);
578
+ expect(result).toBe('dialNumber');
588
579
  });
589
580
 
590
- describe('with US-only dial plan entry', () => {
591
- const dialPlanEntries = [usOnlyEntry];
581
+ it('should return ENTRYPOINT when pType is EP-DN', () => {
582
+ const interaction: any = {
583
+ media: {
584
+ consult: {
585
+ mType: 'consult',
586
+ participants: [currentAgentId, 'dest-agent-456'],
587
+ },
588
+ },
589
+ participants: {
590
+ [currentAgentId]: {type: 'Agent', pType: 'Agent'},
591
+ 'dest-agent-456': {type: 'Agent', pType: 'EP-DN', id: 'dest-agent-456'},
592
+ },
593
+ };
592
594
 
593
- it('should return true for a valid US phone number', () => {
594
- const result = Utils.isValidDialNumber('12223334567', dialPlanEntries);
595
- expect(result).toBe(true);
596
- });
595
+ const result = Utils.calculateDestType(interaction, currentAgentId);
596
+ expect(result).toBe('entryPoint');
597
+ });
597
598
 
598
- it('should return false for a UK phone number', () => {
599
- const result = Utils.isValidDialNumber('+442030484377', dialPlanEntries);
600
- expect(result).toBe(false);
601
- });
599
+ it('should return lowercase pType for other types', () => {
600
+ const interaction: any = {
601
+ media: {
602
+ consult: {
603
+ mType: 'consult',
604
+ participants: [currentAgentId, 'dest-agent-456'],
605
+ },
606
+ },
607
+ participants: {
608
+ [currentAgentId]: {type: 'Agent', pType: 'Agent'},
609
+ 'dest-agent-456': {type: 'Agent', pType: 'Agent', id: 'dest-agent-456'},
610
+ },
611
+ };
602
612
 
603
- it('should return false for an invalid US number format', () => {
604
- const result = Utils.isValidDialNumber('1234567890', dialPlanEntries);
605
- expect(result).toBe(false);
606
- });
613
+ const result = Utils.calculateDestType(interaction, currentAgentId);
614
+ expect(result).toBe('agent');
607
615
  });
608
616
 
609
- describe('with empty dial plan entries (fallback to US regex)', () => {
610
- it('should return true for a valid US phone number', () => {
611
- const result = Utils.isValidDialNumber('12223334567', []);
612
- expect(result).toBe(true);
613
- });
617
+ it('should return agent when no destination agent found', () => {
618
+ const interaction: any = {
619
+ media: {},
620
+ participants: {
621
+ [currentAgentId]: {type: 'Agent', pType: 'Agent'},
622
+ },
623
+ };
614
624
 
615
- it('should return false for a UK phone number', () => {
616
- const result = Utils.isValidDialNumber('+442030484377', []);
617
- expect(result).toBe(false);
618
- });
625
+ const result = Utils.calculateDestType(interaction, currentAgentId);
626
+ expect(result).toBe('agent');
619
627
  });
620
628
  });
621
629
 
630
+ describe('buildConsultConferenceParamData', () => {
631
+ it('maps entryPoint destinationType correctly', () => {
632
+ const result = Utils.buildConsultConferenceParamData(
633
+ {
634
+ agentId: 'agent1',
635
+ destinationType: 'entryPoint',
636
+ destAgentId: 'ep123',
637
+ },
638
+ 'interaction123'
639
+ );
640
+
641
+ expect(result).toEqual({
642
+ interactionId: 'interaction123',
643
+ data: {
644
+ agentId: 'agent1',
645
+ to: 'ep123',
646
+ destinationType: 'entryPoint',
647
+ },
648
+ });
649
+ });
650
+
651
+ it('maps EP-DN/EP_DN destinationType variants to entryPoint', () => {
652
+ const hyphenResult = Utils.buildConsultConferenceParamData(
653
+ {
654
+ agentId: 'agent1',
655
+ destinationType: 'EP-DN',
656
+ destAgentId: 'ep123',
657
+ },
658
+ 'interaction123'
659
+ );
660
+ const underscoreResult = Utils.buildConsultConferenceParamData(
661
+ {
662
+ agentId: 'agent1',
663
+ destinationType: 'EP_DN',
664
+ destAgentId: 'ep123',
665
+ },
666
+ 'interaction123'
667
+ );
668
+
669
+ expect(hyphenResult.data.destinationType).toBe('entryPoint');
670
+ expect(underscoreResult.data.destinationType).toBe('entryPoint');
671
+ });
672
+ });
622
673
  });
@@ -1,7 +1,11 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import {WebSocketManager} from '../../../../../../src/services/core/websocket/WebSocketManager';
3
3
  import {WebexSDK, SubscribeRequest} from '../../../../../../src/types';
4
- import {SUBSCRIBE_API, WCC_API_GATEWAY} from '../../../../../../src/services/constants';
4
+ import {
5
+ RTD_SUBSCRIBE_API,
6
+ SUBSCRIBE_API,
7
+ WCC_API_GATEWAY,
8
+ } from '../../../../../../src/services/constants';
5
9
  import {WEB_SOCKET_MANAGER_FILE} from '../../../../../../src/constants';
6
10
  import LoggerProxy from '../../../../../../src/logger-proxy';
7
11
 
@@ -18,10 +22,10 @@ jest.mock('../../../../../../src/logger-proxy', () => ({
18
22
 
19
23
  class MockWebSocket {
20
24
  static inst: MockWebSocket;
21
- onopen: () => void = () => { };
22
- onerror: (event: any) => void = () => { };
23
- onclose: (event: any) => void = () => { };
24
- onmessage: (msg: any) => void = () => { };
25
+ onopen: () => void = () => {};
26
+ onerror: (event: any) => void = () => {};
27
+ onclose: (event: any) => void = () => {};
28
+ onmessage: (msg: any) => void = () => {};
25
29
  close = jest.fn();
26
30
  send = jest.fn();
27
31
 
@@ -37,7 +41,7 @@ class MockWebSocket {
37
41
  class MockCustomEvent<T> extends Event {
38
42
  detail: T;
39
43
 
40
- constructor(event: string, params: { detail: T }) {
44
+ constructor(event: string, params: {detail: T}) {
41
45
  super(event);
42
46
  this.detail = params.detail;
43
47
  }
@@ -49,7 +53,7 @@ global.CustomEvent = MockCustomEvent as any;
49
53
  class MockMessageEvent extends Event {
50
54
  data: any;
51
55
 
52
- constructor(type: string, eventInitDict: { data: any }) {
56
+ constructor(type: string, eventInitDict: {data: any}) {
53
57
  super(type);
54
58
  this.data = eventInitDict.data;
55
59
  }
@@ -74,14 +78,6 @@ describe('WebSocketManager', () => {
74
78
 
75
79
  mockWebex = {
76
80
  request: jest.fn(),
77
- credentials: {
78
- getOrgId: jest.fn().mockReturnValue('test-org-id'),
79
- },
80
- internal: {
81
- services: {
82
- isIntegrationEnvironment: jest.fn().mockReturnValue(true), // INT environment by default
83
- },
84
- },
85
81
  } as unknown as WebexSDK;
86
82
 
87
83
  mockWorker = {
@@ -93,18 +89,18 @@ describe('WebSocketManager', () => {
93
89
  global.WebSocket = MockWebSocket as any;
94
90
 
95
91
  global.Blob = function (content: any[], options: any) {
96
- return { content, options };
92
+ return {content, options};
97
93
  } as any;
98
94
 
99
95
  global.URL.createObjectURL = function (blob: Blob) {
100
96
  return 'blob:http://localhost:3000/12345';
101
97
  };
102
98
 
103
- webSocketManager = new WebSocketManager({ webex: mockWebex });
99
+ webSocketManager = new WebSocketManager({webex: mockWebex});
104
100
 
105
101
  setTimeout(() => {
106
102
  MockWebSocket.inst.onopen();
107
- MockWebSocket.inst.onmessage({ data: JSON.stringify({ type: "Welcome" }) });
103
+ MockWebSocket.inst.onmessage({data: JSON.stringify({type: 'Welcome'})});
108
104
  }, 1);
109
105
 
110
106
  console.log = jest.fn();
@@ -115,107 +111,47 @@ describe('WebSocketManager', () => {
115
111
  expect(webSocketManager).toBeDefined();
116
112
  });
117
113
 
118
- it('should register and connect to WebSocket with X-ORGANIZATION-ID header for INT environment', async () => {
114
+ it('should register and connect to WebSocket', async () => {
119
115
  const subscribeResponse = {
120
116
  body: {
121
117
  webSocketUrl: 'wss://fake-url',
122
118
  },
123
119
  };
124
120
 
125
- // Mock INT environment (services.isIntegrationEnvironment returns true)
126
- (mockWebex.internal.services.isIntegrationEnvironment as jest.Mock).mockReturnValue(true);
127
121
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
128
122
 
129
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
123
+ await webSocketManager.initWebSocket({body: fakeSubscribeRequest, resource: SUBSCRIBE_API});
130
124
 
131
125
  expect(mockWebex.request).toHaveBeenCalledWith({
132
126
  service: WCC_API_GATEWAY,
133
127
  resource: SUBSCRIBE_API,
134
128
  method: 'POST',
135
129
  body: fakeSubscribeRequest,
136
- headers: {'X-ORGANIZATION-ID': 'test-org-id'},
137
130
  });
138
131
  });
139
132
 
140
- it('should register and connect to WebSocket without X-ORGANIZATION-ID header for production environment', async () => {
133
+ it('should connect rtd websocket', async () => {
141
134
  const subscribeResponse = {
142
135
  body: {
143
136
  webSocketUrl: 'wss://fake-url',
144
137
  },
145
138
  };
146
139
 
147
- // Mock production environment (services.isIntegrationEnvironment returns false)
148
- (mockWebex.internal.services.isIntegrationEnvironment as jest.Mock).mockReturnValue(false);
149
140
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
150
141
 
151
- // Create new WebSocketManager instance with production mock
152
- webSocketManager = new WebSocketManager({ webex: mockWebex });
153
-
154
- setTimeout(() => {
155
- MockWebSocket.inst.onopen();
156
- MockWebSocket.inst.onmessage({ data: JSON.stringify({ type: "Welcome" }) });
157
- }, 1);
158
-
159
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
160
-
161
- expect(mockWebex.request).toHaveBeenCalledWith({
162
- service: WCC_API_GATEWAY,
163
- resource: SUBSCRIBE_API,
164
- method: 'POST',
142
+ await webSocketManager.initWebSocket({
165
143
  body: fakeSubscribeRequest,
166
- headers: undefined,
144
+ resource: RTD_SUBSCRIBE_API,
167
145
  });
168
- });
169
-
170
- it('should not send X-ORGANIZATION-ID header when services.isIntegrationEnvironment is not available', async () => {
171
- const subscribeResponse = {
172
- body: {
173
- webSocketUrl: 'wss://fake-url',
174
- },
175
- };
176
-
177
- // Mock services not available (defaults to production behavior)
178
- (mockWebex as any).internal = undefined;
179
- (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
180
-
181
- webSocketManager = new WebSocketManager({ webex: mockWebex });
182
-
183
- setTimeout(() => {
184
- MockWebSocket.inst.onopen();
185
- MockWebSocket.inst.onmessage({ data: JSON.stringify({ type: "Welcome" }) });
186
- }, 1);
187
-
188
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
189
146
 
190
147
  expect(mockWebex.request).toHaveBeenCalledWith({
191
148
  service: WCC_API_GATEWAY,
192
- resource: SUBSCRIBE_API,
149
+ resource: RTD_SUBSCRIBE_API,
193
150
  method: 'POST',
194
151
  body: fakeSubscribeRequest,
195
- headers: undefined,
196
152
  });
197
153
  });
198
154
 
199
- it('should log error and throw when register API fails in initWebSocket', async () => {
200
- const error = new Error('Register API failed');
201
-
202
- (mockWebex.request as jest.Mock).mockRejectedValueOnce(error);
203
-
204
- await expect(
205
- webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API })
206
- ).rejects.toThrow(error);
207
-
208
- expect(LoggerProxy.error).toHaveBeenCalledWith(
209
- `Register API Failed, Request to RoutingNotifs websocket registration API failed ${error}`,
210
- { module: WEB_SOCKET_MANAGER_FILE, method: 'register' }
211
- );
212
-
213
- expect(LoggerProxy.error).toHaveBeenCalledWith(
214
- `[WebSocketStatus] | Error in registering Websocket ${error}`,
215
- { module: WEB_SOCKET_MANAGER_FILE, method: 'initWebSocket' }
216
- );
217
- });
218
-
219
155
  it('should close WebSocket connection', async () => {
220
156
  const subscribeResponse = {
221
157
  body: {
@@ -225,12 +161,12 @@ describe('WebSocketManager', () => {
225
161
 
226
162
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
227
163
 
228
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
164
+ await webSocketManager.initWebSocket({body: fakeSubscribeRequest, resource: SUBSCRIBE_API});
229
165
 
230
166
  webSocketManager.close(true, 'Test reason');
231
167
 
232
168
  expect(MockWebSocket.inst.close).toHaveBeenCalled();
233
- expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'terminate' });
169
+ expect(mockWorker.postMessage).toHaveBeenCalledWith({type: 'terminate'});
234
170
  });
235
171
 
236
172
  it('should handle WebSocket keepalive messages', async () => {
@@ -242,19 +178,19 @@ describe('WebSocketManager', () => {
242
178
 
243
179
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
244
180
 
245
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
181
+ await webSocketManager.initWebSocket({body: fakeSubscribeRequest, resource: SUBSCRIBE_API});
246
182
 
247
183
  setTimeout(() => {
248
184
  MockWebSocket.inst.onopen();
249
- MockWebSocket.inst.onmessage({ data: JSON.stringify({ type: 'keepalive' }) });
185
+ MockWebSocket.inst.onmessage({data: JSON.stringify({type: 'keepalive'})});
250
186
  mockWorker.onmessage({
251
187
  data: {
252
- type: 'keepalive'
253
- }
188
+ type: 'keepalive',
189
+ },
254
190
  });
255
191
  }, 1);
256
192
 
257
- expect(MockWebSocket.inst.send).toHaveBeenCalledWith(JSON.stringify({ keepalive: 'true' }));
193
+ expect(MockWebSocket.inst.send).toHaveBeenCalledWith(JSON.stringify({keepalive: 'true'}));
258
194
  });
259
195
 
260
196
  it('should handle WebSocket close due to network issue', async () => {
@@ -266,7 +202,7 @@ describe('WebSocketManager', () => {
266
202
 
267
203
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
268
204
 
269
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
205
+ await webSocketManager.initWebSocket({body: fakeSubscribeRequest, resource: SUBSCRIBE_API});
270
206
 
271
207
  // Mock navigator.onLine to simulate network issue
272
208
  Object.defineProperty(global, 'navigator', {
@@ -289,10 +225,10 @@ describe('WebSocketManager', () => {
289
225
  // Wait for the close event to be handled
290
226
  await new Promise((resolve) => setTimeout(resolve, 10));
291
227
 
292
- expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'terminate' });
228
+ expect(mockWorker.postMessage).toHaveBeenCalledWith({type: 'terminate'});
293
229
  expect(LoggerProxy.error).toHaveBeenCalledWith(
294
230
  '[WebSocketStatus] | event=webSocketClose | WebSocket connection closed REASON: network issue',
295
- { module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler' }
231
+ {module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler'}
296
232
  );
297
233
 
298
234
  // Restore navigator.onLine to true
@@ -313,14 +249,14 @@ describe('WebSocketManager', () => {
313
249
 
314
250
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
315
251
 
316
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
252
+ await webSocketManager.initWebSocket({body: fakeSubscribeRequest, resource: SUBSCRIBE_API});
317
253
 
318
254
  const errorEvent = new Event('error');
319
255
  MockWebSocket.inst.onerror(errorEvent);
320
256
 
321
257
  expect(LoggerProxy.error).toHaveBeenCalledWith(
322
258
  '[WebSocketStatus] | event=socketConnectionFailed | WebSocket connection failed [object Event]',
323
- { module: WEB_SOCKET_MANAGER_FILE, method: 'connect' }
259
+ {module: WEB_SOCKET_MANAGER_FILE, method: 'connect'}
324
260
  );
325
261
  });
326
262
 
@@ -333,17 +269,17 @@ describe('WebSocketManager', () => {
333
269
 
334
270
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
335
271
 
336
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
272
+ await webSocketManager.initWebSocket({body: fakeSubscribeRequest, resource: SUBSCRIBE_API});
337
273
 
338
274
  const messageEvent = new MessageEvent('message', {
339
- data: JSON.stringify({ type: 'AGENT_MULTI_LOGIN' }),
275
+ data: JSON.stringify({type: 'AGENT_MULTI_LOGIN'}),
340
276
  });
341
277
  MockWebSocket.inst.onmessage(messageEvent);
342
278
 
343
279
  expect(MockWebSocket.inst.close).toHaveBeenCalled();
344
280
  expect(LoggerProxy.error).toHaveBeenCalledWith(
345
281
  '[WebSocketStatus] | event=agentMultiLogin | WebSocket connection closed by agent multiLogin',
346
- { module: WEB_SOCKET_MANAGER_FILE, method: 'connect' }
282
+ {module: WEB_SOCKET_MANAGER_FILE, method: 'connect'}
347
283
  );
348
284
  });
349
285
 
@@ -356,10 +292,10 @@ describe('WebSocketManager', () => {
356
292
 
357
293
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
358
294
 
359
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
295
+ await webSocketManager.initWebSocket({body: fakeSubscribeRequest, resource: SUBSCRIBE_API});
360
296
 
361
297
  const messageEvent = new MessageEvent('message', {
362
- data: JSON.stringify({ type: 'Welcome', data: { someData: 'data' } }),
298
+ data: JSON.stringify({type: 'Welcome', data: {someData: 'data'}}),
363
299
  });
364
300
  MockWebSocket.inst.onmessage(messageEvent);
365
301
 
@@ -375,7 +311,7 @@ describe('WebSocketManager', () => {
375
311
 
376
312
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
377
313
 
378
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
314
+ await webSocketManager.initWebSocket({body: fakeSubscribeRequest, resource: SUBSCRIBE_API});
379
315
 
380
316
  webSocketManager['forceCloseWebSocketOnTimeout'] = true;
381
317
 
@@ -394,10 +330,10 @@ describe('WebSocketManager', () => {
394
330
  // Wait for the close event to be handled
395
331
  await new Promise((resolve) => setTimeout(resolve, 10));
396
332
 
397
- expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'terminate' });
333
+ expect(mockWorker.postMessage).toHaveBeenCalledWith({type: 'terminate'});
398
334
  expect(LoggerProxy.error).toHaveBeenCalledWith(
399
335
  '[WebSocketStatus] | event=webSocketClose | WebSocket connection closed REASON: WebSocket auto close timed out. Forcefully closed websocket.',
400
- { module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler' }
336
+ {module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler'}
401
337
  );
402
338
  });
403
339
 
@@ -410,7 +346,7 @@ describe('WebSocketManager', () => {
410
346
 
411
347
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
412
348
 
413
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
349
+ await webSocketManager.initWebSocket({body: fakeSubscribeRequest, resource: SUBSCRIBE_API});
414
350
  webSocketManager.shouldReconnect = false;
415
351
  // Simulate the WebSocket close event
416
352
  setTimeout(() => {
@@ -421,13 +357,13 @@ describe('WebSocketManager', () => {
421
357
  target: MockWebSocket.inst,
422
358
  });
423
359
  }, 1);
424
-
360
+
425
361
  await new Promise((resolve) => setTimeout(resolve, 10));
426
362
 
427
- expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'terminate' });
363
+ expect(mockWorker.postMessage).toHaveBeenCalledWith({type: 'terminate'});
428
364
  expect(LoggerProxy.error).not.toHaveBeenCalledWith(
429
365
  '[WebSocketStatus] | event=webSocketClose | WebSocket connection closed REASON: no reconnect',
430
- { module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler' }
366
+ {module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler'}
431
367
  );
432
368
  });
433
369
 
@@ -440,7 +376,7 @@ describe('WebSocketManager', () => {
440
376
 
441
377
  (mockWebex.request as jest.Mock).mockResolvedValueOnce(subscribeResponse);
442
378
 
443
- await webSocketManager.initWebSocket({ body: fakeSubscribeRequest, resource: SUBSCRIBE_API });
379
+ await webSocketManager.initWebSocket({body: fakeSubscribeRequest, resource: SUBSCRIBE_API});
444
380
 
445
381
  // Simulate the WebSocket close event
446
382
  setTimeout(() => {
@@ -455,10 +391,10 @@ describe('WebSocketManager', () => {
455
391
  // Wait for the close event to be handled
456
392
  await new Promise((resolve) => setTimeout(resolve, 10));
457
393
 
458
- expect(mockWorker.postMessage).toHaveBeenCalledWith({ type: 'terminate' });
394
+ expect(mockWorker.postMessage).toHaveBeenCalledWith({type: 'terminate'});
459
395
  expect(LoggerProxy.error).not.toHaveBeenCalledWith(
460
396
  '[WebSocketStatus] | event=webSocketClose | WebSocket connection closed REASON: clean close',
461
- { module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler' }
397
+ {module: WEB_SOCKET_MANAGER_FILE, method: 'webSocketOnCloseHandler'}
462
398
  );
463
399
  });
464
- });
400
+ });
@@ -3,9 +3,7 @@ import {WebSocketManager} from '../../../../../../src/services/core/websocket/We
3
3
  import {SubscribeRequest} from '../../../../../../src/types';
4
4
  import LoggerProxy from '../../../../../../src/logger-proxy';
5
5
  import {CONNECTIVITY_CHECK_INTERVAL} from '../../../../../../src/services/core/constants';
6
- import { CONNECTION_SERVICE_FILE } from '../../../../../../src/constants';
7
- import {SUBSCRIBE_API} from '../../../../../../src/services/constants';
8
-
6
+ import {CONNECTION_SERVICE_FILE} from '../../../../../../src/constants';
9
7
 
10
8
  jest.mock('../../../../../../src/services/core/websocket/WebSocketManager');
11
9
  jest.mock('../../../../../../src/logger-proxy', () => ({
@@ -111,7 +109,10 @@ describe('ConnectionService', () => {
111
109
  'event=socketConnectionRetry | Trying to reconnect to websocket',
112
110
  {module: CONNECTION_SERVICE_FILE, method: 'handleSocketClose'}
113
111
  );
114
- expect(mockWebSocketManager.initWebSocket).toHaveBeenCalledWith({body: mockSubscribeRequest, resource: SUBSCRIBE_API});
112
+ expect(mockWebSocketManager.initWebSocket).toHaveBeenCalledWith({
113
+ body: mockSubscribeRequest,
114
+ resource: 'v1/notification/subscribe',
115
+ });
115
116
  });
116
117
 
117
118
  describe('ConnectionService onPing', () => {
@@ -0,0 +1,63 @@
1
+ import AutoWrapup from '../../../../../src/services/task/AutoWrapup';
2
+
3
+ describe('AutoWrapup', () => {
4
+ beforeEach(() => {
5
+ jest.useFakeTimers();
6
+ });
7
+
8
+ afterEach(() => {
9
+ jest.useRealTimers();
10
+ });
11
+
12
+ it('starts, reports running, and completes', () => {
13
+ const onComplete = jest.fn();
14
+ const timer = new AutoWrapup(1000, true);
15
+
16
+ expect(timer.allowCancelAutoWrapup).toBe(true);
17
+ expect(timer.isRunning()).toBe(false);
18
+
19
+ timer.start(onComplete);
20
+ expect(timer.isRunning()).toBe(true);
21
+ expect(timer.getTimeLeft()).toBeGreaterThan(0);
22
+ expect(timer.getTimeLeftSeconds()).toBeGreaterThan(0);
23
+
24
+ jest.advanceTimersByTime(1000);
25
+ expect(onComplete).toHaveBeenCalledTimes(1);
26
+ expect(timer.isRunning()).toBe(false);
27
+ });
28
+
29
+ it('clear() is safe when not running and cancels an active timer', () => {
30
+ const onComplete = jest.fn();
31
+ const timer = new AutoWrapup(1000);
32
+
33
+ timer.clear(); // no-op
34
+ expect(timer.isRunning()).toBe(false);
35
+
36
+ timer.start(onComplete);
37
+ expect(timer.isRunning()).toBe(true);
38
+
39
+ timer.clear();
40
+ expect(timer.isRunning()).toBe(false);
41
+
42
+ jest.advanceTimersByTime(1000);
43
+ expect(onComplete).not.toHaveBeenCalled();
44
+ });
45
+
46
+ it('start() replaces an existing timer', () => {
47
+ const onComplete1 = jest.fn();
48
+ const onComplete2 = jest.fn();
49
+ const timer = new AutoWrapup(1000);
50
+
51
+ timer.start(onComplete1);
52
+ jest.advanceTimersByTime(500);
53
+
54
+ timer.start(onComplete2);
55
+ jest.advanceTimersByTime(500);
56
+ expect(onComplete1).not.toHaveBeenCalled();
57
+ expect(onComplete2).not.toHaveBeenCalled();
58
+
59
+ jest.advanceTimersByTime(500);
60
+ expect(onComplete2).toHaveBeenCalledTimes(1);
61
+ });
62
+ });
63
+