@webex/contact-center 3.12.0-next.9 → 3.12.0-next.91

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 (229) hide show
  1. package/.sdd/manifest.json +876 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +46 -0
  5. package/ai-docs/GETTING_STARTED.md +168 -0
  6. package/ai-docs/GLOSSARY.md +43 -0
  7. package/ai-docs/README.md +138 -0
  8. package/ai-docs/REVIEW_CHECKLIST.md +41 -0
  9. package/ai-docs/RULES.md +444 -0
  10. package/ai-docs/SECURITY.md +52 -0
  11. package/ai-docs/SERVICE_STATE.md +48 -0
  12. package/ai-docs/SPEC_INDEX.md +65 -0
  13. package/ai-docs/adr/0001-spec-source-policy.md +55 -0
  14. package/ai-docs/adr/README.md +8 -0
  15. package/ai-docs/adr/_adr-template.md +31 -0
  16. package/ai-docs/contact-center-spec.md +341 -0
  17. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  18. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  19. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  20. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  21. package/ai-docs/patterns/testing-patterns.md +480 -0
  22. package/ai-docs/patterns/typescript-patterns.md +365 -0
  23. package/ai-docs/templates/README.md +102 -0
  24. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  25. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  26. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  27. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  28. package/ai-docs/templates/new-method/00-master.md +80 -0
  29. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  30. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  31. package/ai-docs/templates/new-method/03-tests.md +201 -0
  32. package/ai-docs/templates/new-method/04-validation.md +141 -0
  33. package/ai-docs/templates/new-service/00-master.md +109 -0
  34. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  35. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  36. package/ai-docs/templates/new-service/03-integration.md +178 -0
  37. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  38. package/ai-docs/templates/new-service/05-validation.md +145 -0
  39. package/dist/cc.js +265 -29
  40. package/dist/cc.js.map +1 -1
  41. package/dist/config.js +6 -0
  42. package/dist/config.js.map +1 -1
  43. package/dist/constants.js +17 -1
  44. package/dist/constants.js.map +1 -1
  45. package/dist/index.js +20 -5
  46. package/dist/index.js.map +1 -1
  47. package/dist/metrics/behavioral-events.js +101 -0
  48. package/dist/metrics/behavioral-events.js.map +1 -1
  49. package/dist/metrics/constants.js +25 -4
  50. package/dist/metrics/constants.js.map +1 -1
  51. package/dist/services/ApiAiAssistant.js +153 -8
  52. package/dist/services/ApiAiAssistant.js.map +1 -1
  53. package/dist/services/UserPreference.js +427 -0
  54. package/dist/services/UserPreference.js.map +1 -0
  55. package/dist/services/config/Util.js +3 -3
  56. package/dist/services/config/Util.js.map +1 -1
  57. package/dist/services/config/constants.js +23 -2
  58. package/dist/services/config/constants.js.map +1 -1
  59. package/dist/services/config/types.js +49 -9
  60. package/dist/services/config/types.js.map +1 -1
  61. package/dist/services/core/Err.js.map +1 -1
  62. package/dist/services/core/Utils.js +107 -32
  63. package/dist/services/core/Utils.js.map +1 -1
  64. package/dist/services/core/websocket/WebSocketManager.js +2 -1
  65. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  66. package/dist/services/core/websocket/types.js.map +1 -1
  67. package/dist/services/index.js +1 -1
  68. package/dist/services/index.js.map +1 -1
  69. package/dist/services/task/Task.js +688 -0
  70. package/dist/services/task/Task.js.map +1 -0
  71. package/dist/services/task/TaskFactory.js +45 -0
  72. package/dist/services/task/TaskFactory.js.map +1 -0
  73. package/dist/services/task/TaskManager.js +728 -527
  74. package/dist/services/task/TaskManager.js.map +1 -1
  75. package/dist/services/task/TaskUtils.js +162 -26
  76. package/dist/services/task/TaskUtils.js.map +1 -1
  77. package/dist/services/task/constants.js +9 -2
  78. package/dist/services/task/constants.js.map +1 -1
  79. package/dist/services/task/dialer.js +78 -0
  80. package/dist/services/task/dialer.js.map +1 -1
  81. package/dist/services/task/digital/Digital.js +77 -0
  82. package/dist/services/task/digital/Digital.js.map +1 -0
  83. package/dist/services/task/state-machine/TaskStateMachine.js +837 -0
  84. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  85. package/dist/services/task/state-machine/actions.js +543 -0
  86. package/dist/services/task/state-machine/actions.js.map +1 -0
  87. package/dist/services/task/state-machine/constants.js +161 -0
  88. package/dist/services/task/state-machine/constants.js.map +1 -0
  89. package/dist/services/task/state-machine/guards.js +340 -0
  90. package/dist/services/task/state-machine/guards.js.map +1 -0
  91. package/dist/services/task/state-machine/index.js +53 -0
  92. package/dist/services/task/state-machine/index.js.map +1 -0
  93. package/dist/services/task/state-machine/types.js +54 -0
  94. package/dist/services/task/state-machine/types.js.map +1 -0
  95. package/dist/services/task/state-machine/uiControlsComputer.js +553 -0
  96. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  97. package/dist/services/task/taskDataNormalizer.js +99 -0
  98. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  99. package/dist/services/task/types.js +212 -4
  100. package/dist/services/task/types.js.map +1 -1
  101. package/dist/services/task/voice/Voice.js +1042 -0
  102. package/dist/services/task/voice/Voice.js.map +1 -0
  103. package/dist/services/task/voice/WebRTC.js +149 -0
  104. package/dist/services/task/voice/WebRTC.js.map +1 -0
  105. package/dist/types/cc.d.ts +94 -1
  106. package/dist/types/config.d.ts +6 -0
  107. package/dist/types/constants.d.ts +17 -1
  108. package/dist/types/index.d.ts +21 -6
  109. package/dist/types/metrics/constants.d.ts +21 -1
  110. package/dist/types/services/ApiAiAssistant.d.ts +22 -4
  111. package/dist/types/services/UserPreference.d.ts +118 -0
  112. package/dist/types/services/config/constants.d.ts +21 -0
  113. package/dist/types/services/config/types.d.ts +171 -10
  114. package/dist/types/services/core/Err.d.ts +4 -0
  115. package/dist/types/services/core/Utils.d.ts +33 -13
  116. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  117. package/dist/types/services/core/websocket/types.d.ts +1 -1
  118. package/dist/types/services/index.d.ts +1 -1
  119. package/dist/types/services/task/Task.d.ts +157 -0
  120. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  121. package/dist/types/services/task/TaskUtils.d.ts +46 -2
  122. package/dist/types/services/task/constants.d.ts +7 -0
  123. package/dist/types/services/task/dialer.d.ts +30 -0
  124. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  125. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1144 -0
  126. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  127. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  128. package/dist/types/services/task/state-machine/guards.d.ts +90 -0
  129. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  130. package/dist/types/services/task/state-machine/types.d.ts +267 -0
  131. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  132. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  133. package/dist/types/services/task/types.d.ts +603 -66
  134. package/dist/types/services/task/voice/Voice.d.ts +184 -0
  135. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  136. package/dist/types/types.d.ts +135 -0
  137. package/dist/types/webex.d.ts +1 -0
  138. package/dist/types.js +118 -2
  139. package/dist/types.js.map +1 -1
  140. package/dist/webex.js +14 -2
  141. package/dist/webex.js.map +1 -1
  142. package/package.json +15 -12
  143. package/src/cc.ts +329 -30
  144. package/src/config.ts +6 -0
  145. package/src/constants.ts +17 -1
  146. package/src/index.ts +23 -5
  147. package/src/metrics/ai-docs/AGENTS.md +350 -0
  148. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  149. package/src/metrics/ai-docs/metrics-spec.md +854 -0
  150. package/src/metrics/behavioral-events.ts +106 -0
  151. package/src/metrics/constants.ts +27 -4
  152. package/src/services/ApiAiAssistant.ts +203 -8
  153. package/src/services/UserPreference.ts +509 -0
  154. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  155. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  156. package/src/services/agent/ai-docs/agent-spec.md +504 -0
  157. package/src/services/ai-docs/AGENTS.md +386 -0
  158. package/src/services/ai-docs/services-spec.md +492 -0
  159. package/src/services/config/Util.ts +3 -3
  160. package/src/services/config/ai-docs/AGENTS.md +255 -0
  161. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  162. package/src/services/config/ai-docs/config-spec.md +669 -0
  163. package/src/services/config/constants.ts +25 -1
  164. package/src/services/config/types.ts +174 -11
  165. package/src/services/core/Err.ts +2 -0
  166. package/src/services/core/Utils.ts +123 -37
  167. package/src/services/core/ai-docs/AGENTS.md +381 -0
  168. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  169. package/src/services/core/ai-docs/core-spec.md +783 -0
  170. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  171. package/src/services/core/websocket/types.ts +1 -1
  172. package/src/services/index.ts +1 -1
  173. package/src/services/task/Task.ts +837 -0
  174. package/src/services/task/TaskFactory.ts +55 -0
  175. package/src/services/task/TaskManager.ts +739 -614
  176. package/src/services/task/TaskUtils.ts +205 -25
  177. package/src/services/task/ai-docs/AGENTS.md +457 -0
  178. package/src/services/task/ai-docs/ARCHITECTURE.md +594 -0
  179. package/src/services/task/ai-docs/task-spec.md +1319 -0
  180. package/src/services/task/constants.ts +7 -0
  181. package/src/services/task/dialer.ts +80 -0
  182. package/src/services/task/digital/Digital.ts +95 -0
  183. package/src/services/task/state-machine/TaskStateMachine.ts +1077 -0
  184. package/src/services/task/state-machine/actions.ts +685 -0
  185. package/src/services/task/state-machine/ai-docs/AGENTS.md +458 -0
  186. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1137 -0
  187. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2115 -0
  188. package/src/services/task/state-machine/constants.ts +172 -0
  189. package/src/services/task/state-machine/guards.ts +406 -0
  190. package/src/services/task/state-machine/index.ts +28 -0
  191. package/src/services/task/state-machine/types.ts +241 -0
  192. package/src/services/task/state-machine/uiControlsComputer.ts +867 -0
  193. package/src/services/task/taskDataNormalizer.ts +137 -0
  194. package/src/services/task/types.ts +710 -71
  195. package/src/services/task/voice/Voice.ts +1267 -0
  196. package/src/services/task/voice/WebRTC.ts +187 -0
  197. package/src/types.ts +166 -2
  198. package/src/utils/AGENTS.md +278 -0
  199. package/src/utils/ai-docs/utils-spec.md +381 -0
  200. package/src/webex.js +2 -0
  201. package/test/unit/spec/cc.ts +343 -23
  202. package/test/unit/spec/logger-proxy.ts +70 -0
  203. package/test/unit/spec/services/ApiAiAssistant.ts +178 -20
  204. package/test/unit/spec/services/UserPreference.ts +401 -0
  205. package/test/unit/spec/services/WebCallingService.ts +7 -1
  206. package/test/unit/spec/services/config/index.ts +30 -30
  207. package/test/unit/spec/services/core/Utils.ts +425 -8
  208. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  209. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  210. package/test/unit/spec/services/task/Task.ts +477 -0
  211. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  212. package/test/unit/spec/services/task/TaskManager.ts +834 -1704
  213. package/test/unit/spec/services/task/TaskUtils.ts +206 -0
  214. package/test/unit/spec/services/task/dialer.ts +190 -0
  215. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  216. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +1825 -0
  217. package/test/unit/spec/services/task/state-machine/guards.ts +479 -0
  218. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  219. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2020 -0
  220. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  221. package/test/unit/spec/services/task/voice/Voice.ts +631 -0
  222. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  223. package/umd/contact-center.min.js +2 -2
  224. package/umd/contact-center.min.js.map +1 -1
  225. package/dist/services/task/index.js +0 -1525
  226. package/dist/services/task/index.js.map +0 -1
  227. package/dist/types/services/task/index.d.ts +0 -650
  228. package/src/services/task/index.ts +0 -1801
  229. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -59,6 +59,7 @@ global.URL.createObjectURL = jest.fn(() => 'blob:http://localhost:3000/12345');
59
59
 
60
60
  describe('webex.cc', () => {
61
61
  let webex;
62
+ let mockApiAIAssistant;
62
63
  let mockContact;
63
64
  let mockTaskManager;
64
65
  let mockMetricsManager;
@@ -109,6 +110,14 @@ describe('webex.cc', () => {
109
110
  cancelCtq: jest.fn(),
110
111
  };
111
112
 
113
+ mockApiAIAssistant = {
114
+ sendEvent: jest.fn(),
115
+ getRealTimeAssistance: jest.fn(),
116
+ fetchHistoricTranscripts: jest.fn(),
117
+ setAIFeatureFlags: jest.fn(),
118
+ setAgentId: jest.fn(),
119
+ };
120
+
112
121
  // Mock Services instance
113
122
  const mockServicesInstance = {
114
123
  agent: {
@@ -139,6 +148,8 @@ describe('webex.cc', () => {
139
148
  dialer: {
140
149
  startOutdial: jest.fn(),
141
150
  acceptPreviewContact: jest.fn(),
151
+ skipPreviewContact: jest.fn(),
152
+ removePreviewContact: jest.fn(),
142
153
  },
143
154
  apiAIAssistant: {
144
155
  sendEvent: jest.fn(),
@@ -147,17 +158,17 @@ describe('webex.cc', () => {
147
158
  };
148
159
 
149
160
  mockTaskManager = {
161
+ apiAIAssistant: mockApiAIAssistant,
150
162
  contact: mockContact,
151
163
  call: undefined,
152
164
  taskCollection: {},
153
165
  webCallingService: undefined,
154
166
  webSocketManager: mockWebSocketManager,
155
167
  task: undefined,
168
+ setConfigFlags: jest.fn(),
156
169
  setWrapupData: jest.fn(),
157
170
  setAgentId: jest.fn(),
158
171
  setWebRtcEnabled: jest.fn(),
159
- handleRealtimeTranscriptEvent: jest.fn(),
160
- setApiAIAssistant: jest.fn(),
161
172
  registerIncomingCallEvent: jest.fn(),
162
173
  registerTaskListeners: jest.fn(),
163
174
  getTask: jest.fn(),
@@ -200,6 +211,35 @@ describe('webex.cc', () => {
200
211
  webex.emit('ready');
201
212
  });
202
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
+
203
243
  describe('cc.getDeviceId', () => {
204
244
  it('should return dialNumber when loginOption is EXTENSION', () => {
205
245
  const loginOption = LoginOption.EXTENSION;
@@ -267,7 +307,7 @@ describe('webex.cc', () => {
267
307
  isAgentAvailableAfterOutdial: false,
268
308
  isCampaignManagementEnabled: false,
269
309
  outDialEp: '',
270
- isEndCallEnabled: false,
310
+ isEndTaskEnabled: false,
271
311
  isEndConsultEnabled: false,
272
312
  agentDbId: '',
273
313
  allowConsultToQueue: false,
@@ -300,7 +340,7 @@ describe('webex.cc', () => {
300
340
  const result = await webex.cc.register();
301
341
 
302
342
  // Verify logging calls
303
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
343
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
304
344
  module: CC_FILE,
305
345
  method: 'register',
306
346
  });
@@ -334,6 +374,10 @@ describe('webex.cc', () => {
334
374
  TASK_EVENTS.TASK_HYDRATE,
335
375
  expect.any(Function)
336
376
  );
377
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
378
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
379
+ expect.any(Function)
380
+ );
337
381
  expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
338
382
  expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
339
383
  body: {
@@ -354,6 +398,13 @@ describe('webex.cc', () => {
354
398
  module: CC_FILE,
355
399
  method: 'connectWebsocket',
356
400
  });
401
+ expect(mockTaskManager.setConfigFlags).toHaveBeenCalledWith({
402
+ isEndTaskEnabled: mockAgentProfile.isEndTaskEnabled,
403
+ isEndConsultEnabled: mockAgentProfile.isEndConsultEnabled,
404
+ webRtcEnabled: mockAgentProfile.webRtcEnabled,
405
+ autoWrapup: mockAgentProfile.wrapUpData.wrapUpProps.autoWrapup ?? false,
406
+ aiFeature: mockAgentProfile.aiFeature,
407
+ });
357
408
  expect(reloadSpy).toHaveBeenCalled();
358
409
  expect(result).toEqual(mockAgentProfile);
359
410
  expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
@@ -413,7 +464,7 @@ describe('webex.cc', () => {
413
464
 
414
465
  await expect(webex.cc.register()).rejects.toThrow('Error while performing register');
415
466
 
416
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting CC SDK registration', {
467
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting CC SDK registration', {
417
468
  module: CC_FILE,
418
469
  method: 'register',
419
470
  });
@@ -482,6 +533,10 @@ describe('webex.cc', () => {
482
533
  TASK_EVENTS.TASK_HYDRATE,
483
534
  expect.any(Function)
484
535
  );
536
+ expect(mockTaskManager.on).toHaveBeenCalledWith(
537
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
538
+ expect.any(Function)
539
+ );
485
540
  expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
486
541
 
487
542
  expect(configSpy).toHaveBeenCalled();
@@ -493,6 +548,36 @@ describe('webex.cc', () => {
493
548
  expect(result).toEqual(mockAgentProfile);
494
549
  });
495
550
 
551
+ it('should skip mercury connection when disableWebRTCRegistration is enabled', async () => {
552
+ webex.cc.$config = {
553
+ ...webex.cc.$config,
554
+ allowAutomatedRelogin: false,
555
+ disableWebRTCRegistration: true,
556
+ };
557
+ mockAgentProfile.webRtcEnabled = true;
558
+ const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
559
+ const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
560
+ const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
561
+ jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
562
+ mockWebSocketManager.initWebSocket.mockResolvedValue({
563
+ agentId: 'agent123',
564
+ });
565
+
566
+ const result = await webex.cc.register();
567
+
568
+ expect(connectWebsocketSpy).toHaveBeenCalled();
569
+ expect(setupEventListenersSpy).toHaveBeenCalled();
570
+ expect(mercurySpy).not.toHaveBeenCalled();
571
+ expect(LoggerProxy.info).toHaveBeenCalledWith(
572
+ 'Skipping Mobius registration because disableWebRTCRegistration is enabled',
573
+ {
574
+ module: CC_FILE,
575
+ method: 'connectWebsocket',
576
+ }
577
+ );
578
+ expect(result).toEqual(mockAgentProfile);
579
+ });
580
+
496
581
  it('should not attempt for mercury connection when webrtc is disabled', async () => {
497
582
  mockAgentProfile.webRtcEnabled = false;
498
583
  mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
@@ -661,10 +746,8 @@ describe('webex.cc', () => {
661
746
 
662
747
  expect(emitSpy).toHaveBeenCalledTimes(1);
663
748
  expect(emitSpy).toHaveBeenCalledWith(TASK_EVENTS.TASK_INCOMING, mockTask);
664
- // Verify message event listener
665
- const messageCallback = mockWebSocketManager.on.mock.calls.find(
666
- (call) => call[0] === 'message'
667
- )[1];
749
+ // Verify websocket message handling
750
+ const messageCallback = webex.cc['handleWebsocketMessage'];
668
751
  const agentStateChangeEventData = {
669
752
  type: CC_EVENTS.AGENT_STATE_CHANGE,
670
753
  data: {some: 'data'},
@@ -692,6 +775,59 @@ describe('webex.cc', () => {
692
775
  );
693
776
  });
694
777
 
778
+ it('should skip web calling line registration when disableWebRTCRegistration is enabled', async () => {
779
+ const options = {
780
+ teamId: 'teamId',
781
+ loginOption: LoginOption.BROWSER,
782
+ };
783
+
784
+ webex.cc.$config = {
785
+ ...webex.cc.$config,
786
+ disableWebRTCRegistration: true,
787
+ };
788
+ webex.cc.agentConfig = {
789
+ agentId: 'agentId',
790
+ webRtcEnabled: true,
791
+ loginVoiceOptions: ['BROWSER', 'EXTENSION', 'AGENT_DN'],
792
+ };
793
+
794
+ const registerWebCallingLineSpy = jest.spyOn(
795
+ webex.cc.webCallingService,
796
+ 'registerWebCallingLine'
797
+ );
798
+
799
+ jest.spyOn(webex.cc.services.agent, 'stationLogin').mockResolvedValue({
800
+ data: {
801
+ loginOption: LoginOption.BROWSER,
802
+ agentId: 'agentId',
803
+ teamId: 'teamId',
804
+ siteId: 'siteId',
805
+ roles: [AGENT],
806
+ channelsMap: {
807
+ chat: [],
808
+ email: [],
809
+ social: [],
810
+ telephony: [],
811
+ },
812
+ },
813
+ trackingId: 'notifs_52628',
814
+ orgId: 'orgId',
815
+ type: 'StationLoginSuccess',
816
+ eventType: 'STATION_LOGIN',
817
+ } as unknown as StationLoginSuccess);
818
+
819
+ await webex.cc.stationLogin(options);
820
+
821
+ expect(registerWebCallingLineSpy).not.toHaveBeenCalled();
822
+ expect(LoggerProxy.info).toHaveBeenCalledWith(
823
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
824
+ {
825
+ module: CC_FILE,
826
+ method: 'stationLogin',
827
+ }
828
+ );
829
+ });
830
+
695
831
  it('should not attempt mobius registration for LoginOption.BROWSER if webrtc is disabled', async () => {
696
832
  const options = {
697
833
  teamId: 'teamId',
@@ -814,10 +950,13 @@ describe('webex.cc', () => {
814
950
  const result = await webex.cc.stationLogin(options);
815
951
 
816
952
  // Verify logging calls
817
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
818
- module: CC_FILE,
819
- method: 'stationLogin',
820
- });
953
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
954
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
955
+ {
956
+ module: CC_FILE,
957
+ method: 'stationLogin',
958
+ }
959
+ );
821
960
  expect(LoggerProxy.log).toHaveBeenCalledWith(
822
961
  `Agent station login completed successfully agentId: ${mockData.data.agentId} loginOption: ${mockData.data.loginOption} teamId: ${mockData.data.teamId}`,
823
962
  {
@@ -868,10 +1007,13 @@ describe('webex.cc', () => {
868
1007
 
869
1008
  await expect(webex.cc.stationLogin(options)).rejects.toThrow(error.details.data.reason);
870
1009
 
871
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting agent station login', {
872
- module: CC_FILE,
873
- method: 'stationLogin',
874
- });
1010
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1011
+ `Starting agent station login | loginOption: ${options.loginOption} teamId: ${options.teamId}`,
1012
+ {
1013
+ module: CC_FILE,
1014
+ method: 'stationLogin',
1015
+ }
1016
+ );
875
1017
  expect(LoggerProxy.error).toHaveBeenCalledWith(
876
1018
  `stationLogin failed with reason: ${error.details.data.reason}`,
877
1019
  {module: CC_FILE, method: 'stationLogin', trackingId: error.details.trackingId}
@@ -1210,11 +1352,11 @@ describe('webex.cc', () => {
1210
1352
  const webSocketManagerOnSpy = jest.spyOn(webex.cc.services.webSocketManager, 'on');
1211
1353
  await webex.cc['silentRelogin']();
1212
1354
 
1213
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1355
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1214
1356
  module: CC_FILE,
1215
1357
  method: 'silentRelogin',
1216
1358
  });
1217
- expect(LoggerProxy.info).toHaveBeenCalledWith(
1359
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
1218
1360
  'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1219
1361
  {module: CC_FILE, method: 'silentRelogin'}
1220
1362
  );
@@ -1259,7 +1401,7 @@ describe('webex.cc', () => {
1259
1401
 
1260
1402
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1261
1403
  await webex.cc['silentRelogin']();
1262
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1404
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1263
1405
  module: CC_FILE,
1264
1406
  method: 'silentRelogin',
1265
1407
  });
@@ -1274,7 +1416,7 @@ describe('webex.cc', () => {
1274
1416
  jest.spyOn(webex.cc.services.agent, 'reload').mockRejectedValue(error);
1275
1417
 
1276
1418
  await expect(webex.cc['silentRelogin']()).rejects.toThrow(error);
1277
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1419
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1278
1420
  module: CC_FILE,
1279
1421
  method: 'silentRelogin',
1280
1422
  });
@@ -1316,7 +1458,7 @@ describe('webex.cc', () => {
1316
1458
 
1317
1459
  await webex.cc['silentRelogin']();
1318
1460
 
1319
- expect(LoggerProxy.info).toHaveBeenCalledWith('Starting silent relogin process', {
1461
+ expect(LoggerProxy.log).toHaveBeenCalledWith('Starting silent relogin process', {
1320
1462
  module: CC_FILE,
1321
1463
  method: 'silentRelogin',
1322
1464
  });
@@ -1500,6 +1642,27 @@ describe('webex.cc', () => {
1500
1642
  });
1501
1643
  });
1502
1644
 
1645
+ describe('getQueues', () => {
1646
+ it('delegates to the queue service when successful', async () => {
1647
+ const mockQueuesResponse = [{queueId: 'queue1', queueName: 'Queue 1'}];
1648
+ const queueSpy = jest
1649
+ .spyOn(webex.cc.queue, 'getQueues')
1650
+ .mockResolvedValue(mockQueuesResponse as any);
1651
+
1652
+ const result = await webex.cc.getQueues({page: 1});
1653
+
1654
+ expect(queueSpy).toHaveBeenCalledWith({page: 1});
1655
+ expect(result).toBe(mockQueuesResponse);
1656
+ });
1657
+
1658
+ it('propagates queue service errors', async () => {
1659
+ const error = new Error('Test error.');
1660
+ jest.spyOn(webex.cc.queue, 'getQueues').mockRejectedValue(error);
1661
+
1662
+ await expect(webex.cc.getQueues()).rejects.toThrow('Test error.');
1663
+ });
1664
+ });
1665
+
1503
1666
  describe('uploadLogs', () => {
1504
1667
  it('should upload logs successfully', async () => {
1505
1668
  const uploadLogsMock = jest.spyOn(webex.cc.webexRequest, 'uploadLogs').mockResolvedValue({
@@ -1529,6 +1692,7 @@ describe('webex.cc', () => {
1529
1692
 
1530
1693
  describe('unregister', () => {
1531
1694
  let mockWebSocketManager;
1695
+ let mockRTDWebSocketManager;
1532
1696
  let mercuryDisconnectSpy;
1533
1697
  let deviceUnregisterSpy;
1534
1698
 
@@ -1545,8 +1709,15 @@ describe('webex.cc', () => {
1545
1709
  off: jest.fn(),
1546
1710
  on: jest.fn(),
1547
1711
  };
1712
+ mockRTDWebSocketManager = {
1713
+ isSocketClosed: false,
1714
+ close: jest.fn(),
1715
+ off: jest.fn(),
1716
+ on: jest.fn(),
1717
+ };
1548
1718
 
1549
1719
  webex.cc.services.webSocketManager = mockWebSocketManager;
1720
+ webex.cc.services.rtdWebSocketManager = mockRTDWebSocketManager;
1550
1721
 
1551
1722
  webex.internal = webex.internal || {};
1552
1723
  webex.internal.mercury = {
@@ -1580,6 +1751,10 @@ describe('webex.cc', () => {
1580
1751
  TASK_EVENTS.TASK_HYDRATE,
1581
1752
  expect.any(Function)
1582
1753
  );
1754
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1755
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1756
+ expect.any(Function)
1757
+ );
1583
1758
  expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1584
1759
  expect(webex.cc.services.rtdWebSocketManager.off).toHaveBeenCalledWith(
1585
1760
  'message',
@@ -1636,6 +1811,13 @@ describe('webex.cc', () => {
1636
1811
  const [, hydrateCallback] = hydrateCalls[0];
1637
1812
  expect(hydrateCallback).toBe(webex.cc['handleTaskHydrate']);
1638
1813
 
1814
+ const multiLoginHydrateCalls = mockTaskManager.off.mock.calls.filter(
1815
+ ([evt]) => evt === TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE
1816
+ );
1817
+ expect(multiLoginHydrateCalls).toHaveLength(1);
1818
+ const [, multiLoginHydrateCallback] = multiLoginHydrateCalls[0];
1819
+ expect(multiLoginHydrateCallback).toBe(webex.cc['handleTaskMultiLoginHydrate']);
1820
+
1639
1821
  const messageCalls = mockWebSocketManager.off.mock.calls.filter(([evt]) => evt === 'message');
1640
1822
  expect(messageCalls).toHaveLength(1);
1641
1823
  const [, messageCallback] = messageCalls[0];
@@ -1661,6 +1843,10 @@ describe('webex.cc', () => {
1661
1843
  TASK_EVENTS.TASK_HYDRATE,
1662
1844
  expect.any(Function)
1663
1845
  );
1846
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1847
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1848
+ expect.any(Function)
1849
+ );
1664
1850
  expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1665
1851
  expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
1666
1852
  'connectionLost',
@@ -1670,6 +1856,7 @@ describe('webex.cc', () => {
1670
1856
  expect(webex.internal.mercury.off).not.toHaveBeenCalled();
1671
1857
  expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
1672
1858
  expect(deviceUnregisterSpy).not.toHaveBeenCalled();
1859
+ expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1673
1860
  });
1674
1861
 
1675
1862
  it('should skip internal mercury cleanup when loginVoiceOptions does not include BROWSER', async () => {
@@ -1687,6 +1874,7 @@ describe('webex.cc', () => {
1687
1874
  expect(deviceUnregisterSpy).not.toHaveBeenCalled();
1688
1875
 
1689
1876
  expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1877
+ expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1690
1878
  expect(webex.cc.agentConfig).toBeNull();
1691
1879
  });
1692
1880
 
@@ -1704,6 +1892,10 @@ describe('webex.cc', () => {
1704
1892
  TASK_EVENTS.TASK_HYDRATE,
1705
1893
  expect.any(Function)
1706
1894
  );
1895
+ expect(mockTaskManager.off).toHaveBeenCalledWith(
1896
+ TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE,
1897
+ expect.any(Function)
1898
+ );
1707
1899
 
1708
1900
  expect(LoggerProxy.error).toHaveBeenCalledWith(`Error during deregister: ${mockError}`, {
1709
1901
  module: CC_FILE,
@@ -1726,7 +1918,7 @@ describe('webex.cc', () => {
1726
1918
 
1727
1919
  beforeEach(() => {
1728
1920
  emitSpy = jest.spyOn(webex.cc, 'emit');
1729
- messageCallback = mockWebSocketManager.on.mock.calls.find((c) => c[0] === 'message')[1];
1921
+ messageCallback = webex.cc['handleWebsocketMessage'];
1730
1922
  });
1731
1923
 
1732
1924
  it('should emit AGENT_STATION_LOGIN_SUCCESS on CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS with mapped payload', () => {
@@ -2320,4 +2512,132 @@ describe('webex.cc', () => {
2320
2512
  expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'acceptPreviewContact', CC_FILE);
2321
2513
  });
2322
2514
  });
2515
+
2516
+ describe('skipPreviewContact', () => {
2517
+ const previewPayload = {
2518
+ interactionId: 'interaction-123',
2519
+ campaignId: 'campaign-456',
2520
+ };
2521
+
2522
+ it('should skip preview contact successfully', async () => {
2523
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2524
+
2525
+ const skipPreviewContactMock = jest
2526
+ .spyOn(webex.cc.services.dialer, 'skipPreviewContact')
2527
+ .mockResolvedValue(mockResponse);
2528
+
2529
+ const result = await webex.cc.skipPreviewContact(previewPayload);
2530
+
2531
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
2532
+ module: CC_FILE,
2533
+ method: 'skipPreviewContact',
2534
+ });
2535
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2536
+ 'Campaign preview contact skipped successfully',
2537
+ {
2538
+ module: CC_FILE,
2539
+ method: 'skipPreviewContact',
2540
+ trackingId: 'track-123',
2541
+ interactionId: previewPayload.interactionId,
2542
+ }
2543
+ );
2544
+
2545
+ expect(skipPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2546
+ expect(result).toEqual(mockResponse);
2547
+ });
2548
+
2549
+ it('should handle error during skipPreviewContact', async () => {
2550
+ getErrorDetailsSpy.mockRestore();
2551
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2552
+
2553
+ const error = {
2554
+ details: {
2555
+ trackingId: '1234',
2556
+ data: {
2557
+ reason: 'Error while performing skipPreviewContact',
2558
+ },
2559
+ },
2560
+ };
2561
+
2562
+ jest.spyOn(webex.cc.services.dialer, 'skipPreviewContact').mockRejectedValue(error);
2563
+
2564
+ await expect(webex.cc.skipPreviewContact(previewPayload)).rejects.toThrow(
2565
+ error.details.data.reason
2566
+ );
2567
+
2568
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Skipping campaign preview contact', {
2569
+ module: CC_FILE,
2570
+ method: 'skipPreviewContact',
2571
+ });
2572
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2573
+ `skipPreviewContact failed with reason: ${error.details.data.reason}`,
2574
+ {module: CC_FILE, method: 'skipPreviewContact', trackingId: error.details.trackingId}
2575
+ );
2576
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'skipPreviewContact', CC_FILE);
2577
+ });
2578
+ });
2579
+
2580
+ describe('removePreviewContact', () => {
2581
+ const previewPayload = {
2582
+ interactionId: 'interaction-123',
2583
+ campaignId: 'campaign-456',
2584
+ };
2585
+
2586
+ it('should remove preview contact successfully', async () => {
2587
+ const mockResponse = {trackingId: 'track-123'} as AgentContact;
2588
+
2589
+ const removePreviewContactMock = jest
2590
+ .spyOn(webex.cc.services.dialer, 'removePreviewContact')
2591
+ .mockResolvedValue(mockResponse);
2592
+
2593
+ const result = await webex.cc.removePreviewContact(previewPayload);
2594
+
2595
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
2596
+ module: CC_FILE,
2597
+ method: 'removePreviewContact',
2598
+ });
2599
+ expect(LoggerProxy.log).toHaveBeenCalledWith(
2600
+ 'Campaign preview contact removed successfully',
2601
+ {
2602
+ module: CC_FILE,
2603
+ method: 'removePreviewContact',
2604
+ trackingId: 'track-123',
2605
+ interactionId: previewPayload.interactionId,
2606
+ }
2607
+ );
2608
+
2609
+ expect(removePreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2610
+ expect(result).toEqual(mockResponse);
2611
+ });
2612
+
2613
+ it('should handle error during removePreviewContact', async () => {
2614
+ getErrorDetailsSpy.mockRestore();
2615
+ getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2616
+
2617
+ const error = {
2618
+ details: {
2619
+ trackingId: '1234',
2620
+ data: {
2621
+ reason: 'Error while performing removePreviewContact',
2622
+ },
2623
+ },
2624
+ };
2625
+
2626
+ jest.spyOn(webex.cc.services.dialer, 'removePreviewContact').mockRejectedValue(error);
2627
+
2628
+ await expect(webex.cc.removePreviewContact(previewPayload)).rejects.toThrow(
2629
+ error.details.data.reason
2630
+ );
2631
+
2632
+ expect(LoggerProxy.info).toHaveBeenCalledWith('Removing campaign preview contact', {
2633
+ module: CC_FILE,
2634
+ method: 'removePreviewContact',
2635
+ });
2636
+ expect(LoggerProxy.error).toHaveBeenCalledWith(
2637
+ `removePreviewContact failed with reason: ${error.details.data.reason}`,
2638
+ {module: CC_FILE, method: 'removePreviewContact', trackingId: error.details.trackingId}
2639
+ );
2640
+ expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'removePreviewContact', CC_FILE);
2641
+ });
2642
+ });
2323
2643
  });
@@ -0,0 +1,70 @@
1
+ import LoggerProxy from '../../../src/logger-proxy';
2
+
3
+ describe('LoggerProxy', () => {
4
+ beforeEach(() => {
5
+ // Reset between tests
6
+ (LoggerProxy as any).logger = undefined;
7
+ });
8
+
9
+ it('no-ops when logger is not initialized', () => {
10
+ expect(() => {
11
+ LoggerProxy.log('msg');
12
+ LoggerProxy.info('msg');
13
+ LoggerProxy.warn('msg');
14
+ LoggerProxy.trace('msg');
15
+ LoggerProxy.error('msg');
16
+ }).not.toThrow();
17
+ });
18
+
19
+ it('routes calls to the injected logger with formatted context', () => {
20
+ const logger = {
21
+ log: jest.fn(),
22
+ info: jest.fn(),
23
+ warn: jest.fn(),
24
+ trace: jest.fn(),
25
+ error: jest.fn(),
26
+ };
27
+
28
+ LoggerProxy.initialize(logger as any);
29
+
30
+ LoggerProxy.log('hello', {module: 'm', method: 'fn', interactionId: 'i', trackingId: 't'});
31
+ expect(logger.log).toHaveBeenCalled();
32
+ expect(String(logger.log.mock.calls[0][0])).toContain('module:m - method:fn');
33
+ expect(String(logger.log.mock.calls[0][0])).toContain('interactionId:i');
34
+ expect(String(logger.log.mock.calls[0][0])).toContain('trackingId:t');
35
+
36
+ LoggerProxy.info('hello', {module: 'm', method: 'fn'});
37
+ LoggerProxy.warn('hello', {module: 'm', method: 'fn'});
38
+ LoggerProxy.trace('hello', {module: 'm', method: 'fn'});
39
+ LoggerProxy.error('hello', {module: 'm', method: 'fn'});
40
+ expect(logger.info).toHaveBeenCalled();
41
+ expect(logger.warn).toHaveBeenCalled();
42
+ expect(logger.trace).toHaveBeenCalled();
43
+ expect(logger.error).toHaveBeenCalled();
44
+ });
45
+
46
+ it('handles unserializable data and different error shapes', () => {
47
+ const logger = {
48
+ log: jest.fn(),
49
+ info: jest.fn(),
50
+ warn: jest.fn(),
51
+ trace: jest.fn(),
52
+ error: jest.fn(),
53
+ };
54
+ LoggerProxy.initialize(logger as any);
55
+
56
+ const circular: any = {};
57
+ circular.self = circular;
58
+
59
+ LoggerProxy.info('msg', {module: 'm', method: 'fn', data: circular, error: 'oops'});
60
+ expect(String(logger.info.mock.calls[0][0])).toContain('data:[object]');
61
+ expect(String(logger.info.mock.calls[0][0])).toContain('error:oops');
62
+
63
+ const err = new Error('boom');
64
+ LoggerProxy.error('msg', {module: 'm', method: 'fn', error: err});
65
+ const errorLine = String(logger.error.mock.calls[0][0]);
66
+ expect(errorLine).toContain('error:Error:boom');
67
+ expect(errorLine).toContain('stack:');
68
+ });
69
+ });
70
+