@webex/contact-center 3.12.0-next.12 → 3.12.0-next.121

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 (307) hide show
  1. package/.sdd/manifest.json +884 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +51 -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 +359 -0
  17. package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
  18. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  19. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  20. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  21. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  22. package/ai-docs/patterns/testing-patterns.md +480 -0
  23. package/ai-docs/patterns/typescript-patterns.md +365 -0
  24. package/ai-docs/templates/README.md +102 -0
  25. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  26. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  27. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  28. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  29. package/ai-docs/templates/new-method/00-master.md +80 -0
  30. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  31. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  32. package/ai-docs/templates/new-method/03-tests.md +201 -0
  33. package/ai-docs/templates/new-method/04-validation.md +141 -0
  34. package/ai-docs/templates/new-service/00-master.md +109 -0
  35. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  36. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  37. package/ai-docs/templates/new-service/03-integration.md +178 -0
  38. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  39. package/ai-docs/templates/new-service/05-validation.md +145 -0
  40. package/dist/cc.js +808 -40
  41. package/dist/cc.js.map +1 -1
  42. package/dist/config.js +13 -0
  43. package/dist/config.js.map +1 -1
  44. package/dist/constants.js +27 -3
  45. package/dist/constants.js.map +1 -1
  46. package/dist/index.js +20 -5
  47. package/dist/index.js.map +1 -1
  48. package/dist/metrics/MetricsManager.js +16 -0
  49. package/dist/metrics/MetricsManager.js.map +1 -1
  50. package/dist/metrics/behavioral-events.js +205 -0
  51. package/dist/metrics/behavioral-events.js.map +1 -1
  52. package/dist/metrics/constants.js +43 -4
  53. package/dist/metrics/constants.js.map +1 -1
  54. package/dist/services/AddressBook.js +18 -15
  55. package/dist/services/AddressBook.js.map +1 -1
  56. package/dist/services/AnswerCallOnWebexService.js +202 -0
  57. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  58. package/dist/services/ApiAiAssistant.js +153 -8
  59. package/dist/services/ApiAiAssistant.js.map +1 -1
  60. package/dist/services/EntryPoint.js +46 -68
  61. package/dist/services/EntryPoint.js.map +1 -1
  62. package/dist/services/Queue.js +27 -22
  63. package/dist/services/Queue.js.map +1 -1
  64. package/dist/services/UserPreference.js +427 -0
  65. package/dist/services/UserPreference.js.map +1 -0
  66. package/dist/services/WebexCrossClientService.js +222 -0
  67. package/dist/services/WebexCrossClientService.js.map +1 -0
  68. package/dist/services/WxAppTelephonyMercurySync.js +108 -0
  69. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  70. package/dist/services/config/Util.js +6 -3
  71. package/dist/services/config/Util.js.map +1 -1
  72. package/dist/services/config/constants.js +33 -8
  73. package/dist/services/config/constants.js.map +1 -1
  74. package/dist/services/config/types.js +53 -9
  75. package/dist/services/config/types.js.map +1 -1
  76. package/dist/services/core/Err.js.map +1 -1
  77. package/dist/services/core/Utils.js +107 -32
  78. package/dist/services/core/Utils.js.map +1 -1
  79. package/dist/services/core/WebexRequest.js +6 -2
  80. package/dist/services/core/WebexRequest.js.map +1 -1
  81. package/dist/services/core/aqm-reqs.js +28 -14
  82. package/dist/services/core/aqm-reqs.js.map +1 -1
  83. package/dist/services/core/types.js.map +1 -1
  84. package/dist/services/core/websocket/WebSocketManager.js +2 -1
  85. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  86. package/dist/services/core/websocket/types.js.map +1 -1
  87. package/dist/services/index.js +1 -1
  88. package/dist/services/index.js.map +1 -1
  89. package/dist/services/task/Task.js +754 -0
  90. package/dist/services/task/Task.js.map +1 -0
  91. package/dist/services/task/TaskFactory.js +49 -0
  92. package/dist/services/task/TaskFactory.js.map +1 -0
  93. package/dist/services/task/TaskManager.js +1053 -520
  94. package/dist/services/task/TaskManager.js.map +1 -1
  95. package/dist/services/task/TaskUtils.js +211 -27
  96. package/dist/services/task/TaskUtils.js.map +1 -1
  97. package/dist/services/task/WebexCallingUtils.js +70 -0
  98. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  99. package/dist/services/task/constants.js +12 -2
  100. package/dist/services/task/constants.js.map +1 -1
  101. package/dist/services/task/contact.js +29 -0
  102. package/dist/services/task/contact.js.map +1 -1
  103. package/dist/services/task/dialer.js +78 -0
  104. package/dist/services/task/dialer.js.map +1 -1
  105. package/dist/services/task/digital/Digital.js +78 -0
  106. package/dist/services/task/digital/Digital.js.map +1 -0
  107. package/dist/services/task/state-machine/TaskStateMachine.js +954 -0
  108. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  109. package/dist/services/task/state-machine/actions.js +572 -0
  110. package/dist/services/task/state-machine/actions.js.map +1 -0
  111. package/dist/services/task/state-machine/constants.js +161 -0
  112. package/dist/services/task/state-machine/constants.js.map +1 -0
  113. package/dist/services/task/state-machine/guards.js +379 -0
  114. package/dist/services/task/state-machine/guards.js.map +1 -0
  115. package/dist/services/task/state-machine/index.js +53 -0
  116. package/dist/services/task/state-machine/index.js.map +1 -0
  117. package/dist/services/task/state-machine/types.js +54 -0
  118. package/dist/services/task/state-machine/types.js.map +1 -0
  119. package/dist/services/task/state-machine/uiControlsComputer.js +703 -0
  120. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  121. package/dist/services/task/taskDataNormalizer.js +99 -0
  122. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  123. package/dist/services/task/types.js +228 -5
  124. package/dist/services/task/types.js.map +1 -1
  125. package/dist/services/task/voice/Voice.js +1447 -0
  126. package/dist/services/task/voice/Voice.js.map +1 -0
  127. package/dist/services/task/voice/WebRTC.js +152 -0
  128. package/dist/services/task/voice/WebRTC.js.map +1 -0
  129. package/dist/services/task/voice/wxAppVoiceMethods.js +354 -0
  130. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  131. package/dist/services/wxAppDiagnosticLogging.js +122 -0
  132. package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
  133. package/dist/services/wxAppTelephonyUtils.js +19 -0
  134. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  135. package/dist/types/cc.d.ts +172 -5
  136. package/dist/types/config.d.ts +13 -0
  137. package/dist/types/constants.d.ts +25 -1
  138. package/dist/types/index.d.ts +24 -7
  139. package/dist/types/metrics/MetricsManager.d.ts +6 -0
  140. package/dist/types/metrics/constants.d.ts +38 -1
  141. package/dist/types/services/AddressBook.d.ts +2 -1
  142. package/dist/types/services/AnswerCallOnWebexService.d.ts +38 -0
  143. package/dist/types/services/ApiAiAssistant.d.ts +22 -4
  144. package/dist/types/services/EntryPoint.d.ts +7 -5
  145. package/dist/types/services/Queue.d.ts +5 -3
  146. package/dist/types/services/UserPreference.d.ts +118 -0
  147. package/dist/types/services/WebexCrossClientService.d.ts +32 -0
  148. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  149. package/dist/types/services/config/constants.d.ts +29 -5
  150. package/dist/types/services/config/types.d.ts +184 -10
  151. package/dist/types/services/core/Err.d.ts +6 -0
  152. package/dist/types/services/core/Utils.d.ts +33 -13
  153. package/dist/types/services/core/WebexRequest.d.ts +1 -0
  154. package/dist/types/services/core/types.d.ts +2 -0
  155. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  156. package/dist/types/services/core/websocket/types.d.ts +1 -1
  157. package/dist/types/services/index.d.ts +1 -1
  158. package/dist/types/services/task/Task.d.ts +175 -0
  159. package/dist/types/services/task/TaskFactory.d.ts +13 -0
  160. package/dist/types/services/task/TaskUtils.d.ts +48 -2
  161. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  162. package/dist/types/services/task/constants.d.ts +10 -0
  163. package/dist/types/services/task/contact.d.ts +4 -0
  164. package/dist/types/services/task/dialer.d.ts +30 -0
  165. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  166. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1356 -0
  167. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  168. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  169. package/dist/types/services/task/state-machine/guards.d.ts +103 -0
  170. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  171. package/dist/types/services/task/state-machine/types.d.ts +277 -0
  172. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  173. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  174. package/dist/types/services/task/types.d.ts +680 -66
  175. package/dist/types/services/task/voice/Voice.d.ts +227 -0
  176. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  177. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +55 -0
  178. package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -0
  179. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  180. package/dist/types/types.d.ts +149 -8
  181. package/dist/types/utils/PageCache.d.ts +20 -3
  182. package/dist/types/webex.d.ts +1 -0
  183. package/dist/types.js +125 -4
  184. package/dist/types.js.map +1 -1
  185. package/dist/utils/PageCache.js +19 -5
  186. package/dist/utils/PageCache.js.map +1 -1
  187. package/dist/webex.js +14 -2
  188. package/dist/webex.js.map +1 -1
  189. package/package.json +16 -12
  190. package/src/cc.ts +986 -42
  191. package/src/config.ts +13 -0
  192. package/src/constants.ts +25 -1
  193. package/src/index.ts +25 -5
  194. package/src/metrics/MetricsManager.ts +16 -0
  195. package/src/metrics/ai-docs/AGENTS.md +350 -0
  196. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  197. package/src/metrics/ai-docs/metrics-spec.md +922 -0
  198. package/src/metrics/behavioral-events.ts +212 -0
  199. package/src/metrics/constants.ts +47 -4
  200. package/src/services/AddressBook.ts +17 -6
  201. package/src/services/AnswerCallOnWebexService.ts +239 -0
  202. package/src/services/ApiAiAssistant.ts +203 -8
  203. package/src/services/EntryPoint.ts +59 -60
  204. package/src/services/Queue.ts +29 -12
  205. package/src/services/UserPreference.ts +509 -0
  206. package/src/services/WebexCrossClientService.ts +293 -0
  207. package/src/services/WxAppTelephonyMercurySync.ts +142 -0
  208. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  209. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  210. package/src/services/agent/ai-docs/agent-spec.md +505 -0
  211. package/src/services/ai-docs/AGENTS.md +386 -0
  212. package/src/services/ai-docs/services-spec.md +497 -0
  213. package/src/services/config/Util.ts +6 -3
  214. package/src/services/config/ai-docs/AGENTS.md +255 -0
  215. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  216. package/src/services/config/ai-docs/config-spec.md +675 -0
  217. package/src/services/config/constants.ts +35 -7
  218. package/src/services/config/types.ts +186 -11
  219. package/src/services/core/Err.ts +3 -0
  220. package/src/services/core/Utils.ts +123 -37
  221. package/src/services/core/WebexRequest.ts +3 -1
  222. package/src/services/core/ai-docs/AGENTS.md +381 -0
  223. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  224. package/src/services/core/ai-docs/core-spec.md +787 -0
  225. package/src/services/core/aqm-reqs.ts +30 -15
  226. package/src/services/core/types.ts +2 -0
  227. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  228. package/src/services/core/websocket/types.ts +1 -1
  229. package/src/services/index.ts +1 -1
  230. package/src/services/task/Task.ts +908 -0
  231. package/src/services/task/TaskFactory.ts +60 -0
  232. package/src/services/task/TaskManager.ts +1239 -608
  233. package/src/services/task/TaskUtils.ts +303 -25
  234. package/src/services/task/WebexCallingUtils.ts +136 -0
  235. package/src/services/task/ai-docs/AGENTS.md +457 -0
  236. package/src/services/task/ai-docs/ARCHITECTURE.md +595 -0
  237. package/src/services/task/ai-docs/task-spec.md +1470 -0
  238. package/src/services/task/constants.ts +10 -0
  239. package/src/services/task/contact.ts +30 -0
  240. package/src/services/task/dialer.ts +80 -0
  241. package/src/services/task/digital/Digital.ts +97 -0
  242. package/src/services/task/state-machine/TaskStateMachine.ts +1293 -0
  243. package/src/services/task/state-machine/actions.ts +741 -0
  244. package/src/services/task/state-machine/ai-docs/AGENTS.md +460 -0
  245. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1145 -0
  246. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2207 -0
  247. package/src/services/task/state-machine/constants.ts +172 -0
  248. package/src/services/task/state-machine/guards.ts +442 -0
  249. package/src/services/task/state-machine/index.ts +28 -0
  250. package/src/services/task/state-machine/types.ts +258 -0
  251. package/src/services/task/state-machine/uiControlsComputer.ts +1135 -0
  252. package/src/services/task/taskDataNormalizer.ts +137 -0
  253. package/src/services/task/types.ts +819 -70
  254. package/src/services/task/voice/Voice.ts +1809 -0
  255. package/src/services/task/voice/WebRTC.ts +191 -0
  256. package/src/services/task/voice/wxAppVoiceMethods.ts +516 -0
  257. package/src/services/wxAppDiagnosticLogging.ts +174 -0
  258. package/src/services/wxAppTelephonyUtils.ts +14 -0
  259. package/src/types.ts +199 -11
  260. package/src/utils/AGENTS.md +289 -0
  261. package/src/utils/PageCache.ts +38 -5
  262. package/src/utils/ai-docs/utils-spec.md +391 -0
  263. package/src/webex.js +2 -0
  264. package/test/unit/spec/cc.ts +1947 -124
  265. package/test/unit/spec/logger-proxy.ts +70 -0
  266. package/test/unit/spec/metrics/MetricsManager.ts +25 -0
  267. package/test/unit/spec/metrics/behavioral-events.ts +60 -0
  268. package/test/unit/spec/services/AddressBook.ts +37 -6
  269. package/test/unit/spec/services/AnswerCallOnWebexService.ts +264 -0
  270. package/test/unit/spec/services/ApiAiAssistant.ts +178 -20
  271. package/test/unit/spec/services/EntryPoint.ts +87 -40
  272. package/test/unit/spec/services/Queue.ts +123 -12
  273. package/test/unit/spec/services/UserPreference.ts +401 -0
  274. package/test/unit/spec/services/WebCallingService.ts +7 -1
  275. package/test/unit/spec/services/WebexCrossClientService.ts +467 -0
  276. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +136 -0
  277. package/test/unit/spec/services/config/Util.ts +85 -0
  278. package/test/unit/spec/services/config/index.ts +30 -30
  279. package/test/unit/spec/services/core/Utils.ts +425 -8
  280. package/test/unit/spec/services/core/WebexRequest.ts +3 -1
  281. package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
  282. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  283. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  284. package/test/unit/spec/services/task/Task.ts +677 -0
  285. package/test/unit/spec/services/task/TaskFactory.ts +99 -0
  286. package/test/unit/spec/services/task/TaskManager.ts +1972 -1549
  287. package/test/unit/spec/services/task/TaskUtils.ts +235 -0
  288. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  289. package/test/unit/spec/services/task/contact.ts +33 -0
  290. package/test/unit/spec/services/task/dialer.ts +190 -0
  291. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  292. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +3459 -0
  293. package/test/unit/spec/services/task/state-machine/guards.ts +839 -0
  294. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  295. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +3101 -0
  296. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  297. package/test/unit/spec/services/task/voice/Voice.ts +1903 -0
  298. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  299. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +603 -0
  300. package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
  301. package/umd/contact-center.min.js +2 -2
  302. package/umd/contact-center.min.js.map +1 -1
  303. package/dist/services/task/index.js +0 -1525
  304. package/dist/services/task/index.js.map +0 -1
  305. package/dist/types/services/task/index.d.ts +0 -650
  306. package/src/services/task/index.ts +0 -1801
  307. package/test/unit/spec/services/task/index.ts +0 -2184
package/src/cc.ts CHANGED
@@ -25,6 +25,7 @@ import {
25
25
  UploadLogsResponse,
26
26
  UpdateDeviceTypeResponse,
27
27
  GenericError,
28
+ ConfigFlags,
28
29
  } from './types';
29
30
  import {
30
31
  READY,
@@ -55,6 +56,10 @@ import {
55
56
  import {ConnectionLostDetails} from './services/core/websocket/types';
56
57
  import TaskManager from './services/task/TaskManager';
57
58
  import WebCallingService from './services/WebCallingService';
59
+ import AnswerCallOnWebexService from './services/AnswerCallOnWebexService';
60
+ import WebexCrossClientService from './services/WebexCrossClientService';
61
+ import WxAppTelephonyMercurySync from './services/WxAppTelephonyMercurySync';
62
+ import {logWxAppSessionReadiness} from './services/wxAppDiagnosticLogging';
58
63
  import {
59
64
  ITask,
60
65
  TASK_EVENTS,
@@ -69,6 +74,7 @@ import {EntryPoint} from './services/EntryPoint';
69
74
  import {AddressBook} from './services/AddressBook';
70
75
  import {Queue} from './services/Queue';
71
76
  import {ApiAIAssistant} from './services/ApiAiAssistant';
77
+ import {UserPreference} from './services/UserPreference';
72
78
  import type {
73
79
  EntryPointListResponse,
74
80
  EntryPointSearchParams,
@@ -230,6 +236,34 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
230
236
  */
231
237
  private webCallingService: WebCallingService;
232
238
 
239
+ /**
240
+ * REST telephony for wxApp thick-client answer (WXCC-6026)
241
+ * @private
242
+ */
243
+ private answerCallOnWebexService: AnswerCallOnWebexService;
244
+
245
+ /**
246
+ * usersub cross-client state for Webex App toast suppression
247
+ * @private
248
+ */
249
+ private webexCrossClientService: WebexCrossClientService;
250
+
251
+ /**
252
+ * Mercury telephony mute sync for wxApp thick-client answer
253
+ * @private
254
+ */
255
+ private wxAppTelephonyMercurySync: WxAppTelephonyMercurySync;
256
+
257
+ /** True when wxApp mute sync opened Mercury (Extension/DN-only profiles). */
258
+ private wxAppMercuryConnectedByCc = false;
259
+
260
+ /** True when wxApp mute sync registered the Webex device. */
261
+ private wxAppDeviceRegisteredByCc = false;
262
+
263
+ private static readonly WXAPP_FALSE_PUBLISH_RETRY_DELAY_MS = 30_000;
264
+ private static readonly WXAPP_FALSE_PUBLISH_MAX_RETRIES = 3;
265
+ private wxAppFalsePublishRetryTimer?: ReturnType<typeof setTimeout>;
266
+
233
267
  /**
234
268
  * Core service managers for Contact Center operations
235
269
  * Includes agent, connection, and configuration services
@@ -325,7 +359,38 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
325
359
  * });
326
360
  * ```
327
361
  */
328
- private queue: Queue;
362
+ public queue: Queue;
363
+
364
+ /**
365
+ * API instance for managing Webex Contact Center user preferences
366
+ * Provides functionality to get, create, update, and delete user preferences
367
+ * @type {UserPreference}
368
+ * @public
369
+ * @example
370
+ * ```typescript
371
+ * const cc = webex.cc;
372
+ * await cc.register();
373
+ * await cc.stationLogin({ teamId: 'team123', loginOption: 'BROWSER' });
374
+ *
375
+ * // Get user preferences for current user
376
+ * const preferences = await cc.userPreference.getUserPreference();
377
+ *
378
+ * // Create new user preferences
379
+ * const newPrefs = await cc.userPreference.createUserPreference({
380
+ * userId: 'user123',
381
+ * preferences: { e911Reminder: true }
382
+ * });
383
+ *
384
+ * // Update user preferences
385
+ * const updatedPrefs = await cc.userPreference.updateUserPreference('user123', {
386
+ * preferences: { e911Reminder: false }
387
+ * });
388
+ *
389
+ * // Delete user preferences
390
+ * await cc.userPreference.deleteUserPreference('user123');
391
+ * ```
392
+ */
393
+ public userPreference: UserPreference;
329
394
 
330
395
  /**
331
396
  * API instance for AI Assistant operations such as transcript controls.
@@ -357,6 +422,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
357
422
  this.$webex.once(READY, () => {
358
423
  // @ts-ignore
359
424
  this.$config = this.config;
425
+ this.validatePluginConfig();
360
426
 
361
427
  /**
362
428
  * This is used for handling the async requests by sending webex.request and wait for corresponding websocket event.
@@ -369,17 +435,21 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
369
435
  webex: this.$webex,
370
436
  connectionConfig: this.getConnectionConfig(),
371
437
  });
372
- this.services.webSocketManager.on('message', this.handleWebsocketMessage);
373
438
 
374
439
  this.webCallingService = new WebCallingService(this.$webex);
440
+ this.answerCallOnWebexService = new AnswerCallOnWebexService(this.$webex);
441
+ this.webexCrossClientService = new WebexCrossClientService(this.$webex);
442
+ this.wxAppTelephonyMercurySync = new WxAppTelephonyMercurySync(this.$webex);
375
443
  this.apiAIAssistant = new ApiAIAssistant(this.$webex);
376
444
  this.metricsManager = MetricsManager.getInstance({webex: this.$webex});
377
445
  this.taskManager = TaskManager.getTaskManager(
378
446
  this.apiAIAssistant,
379
447
  this.services.contact,
380
448
  this.webCallingService,
381
- this.services.webSocketManager
449
+ this.services.webSocketManager,
450
+ this.services.rtdWebSocketManager
382
451
  );
452
+ this.taskManager.setAnswerCallOnWebexService(this.answerCallOnWebexService);
383
453
  this.incomingTaskListener();
384
454
 
385
455
  // Initialize API instances
@@ -387,6 +457,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
387
457
  this.entryPoint = new EntryPoint(this.$webex);
388
458
  this.addressBook = new AddressBook(this.$webex, () => this.agentConfig?.addressBookId);
389
459
  this.queue = new Queue(this.$webex);
460
+ this.userPreference = new UserPreference(this.$webex, () => this.agentConfig?.agentId);
390
461
  LoggerProxy.initialize(this.$webex.logger);
391
462
  });
392
463
  }
@@ -411,6 +482,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
411
482
  this.trigger(TASK_EVENTS.TASK_HYDRATE, task);
412
483
  };
413
484
 
485
+ /**
486
+ * Handles multi-login hydrate events for SDK instances without Mobius registration
487
+ * @private
488
+ * @param {ITask} task The task object associated with the multi-login hydrate
489
+ */
490
+ private handleTaskMultiLoginHydrate = (task: ITask) => {
491
+ // @ts-ignore
492
+ this.trigger(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
493
+ };
494
+
414
495
  /**
415
496
  * Handles task merged events when tasks are combined eg: EPDN merge/transfer
416
497
  * @private
@@ -431,8 +512,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
431
512
  this.trigger(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
432
513
  };
433
514
 
434
- private handleRTDWebsocketMessage = (payload: string) => {
435
- this.taskManager.handleRealtimeWebsocketEvent(payload);
515
+ private handleRTDWebsocketMessage = (event: string) => {
516
+ this.taskManager.handleRealtimeWebsocketEvent(event);
436
517
  };
437
518
 
438
519
  /**
@@ -443,6 +524,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
443
524
  private incomingTaskListener() {
444
525
  this.taskManager.on(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
445
526
  this.taskManager.on(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
527
+ this.taskManager.on(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
446
528
  this.taskManager.on(TASK_EVENTS.TASK_MERGED, this.handleTaskMerged);
447
529
  this.taskManager.on(
448
530
  TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
@@ -487,7 +569,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
487
569
  * ```
488
570
  */
489
571
  public async register(): Promise<Profile> {
490
- LoggerProxy.info('Starting CC SDK registration', {
572
+ LoggerProxy.log('Starting CC SDK registration', {
491
573
  module: CC_FILE,
492
574
  method: METHODS.REGISTER,
493
575
  });
@@ -497,6 +579,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
497
579
  METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
498
580
  ]);
499
581
  this.setupEventListeners();
582
+ this.services.webSocketManager.on('message', this.handleWebsocketMessage);
500
583
 
501
584
  const resp = await this.connectWebsocket();
502
585
  // Ensure 'dn' is always populated from 'defaultDn'
@@ -576,6 +659,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
576
659
 
577
660
  this.taskManager.off(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
578
661
  this.taskManager.off(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
662
+ this.taskManager.off(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
579
663
  this.taskManager.off(
580
664
  TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
581
665
  this.handleCampaignPreviewReservation
@@ -586,6 +670,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
586
670
  this.services.rtdWebSocketManager.off('message', this.handleRTDWebsocketMessage);
587
671
  this.services.connectionService.off('connectionLost', this.handleConnectionLost);
588
672
 
673
+ const {publishError: wxAppPublishError} = await this.teardownWxAppLocalState({
674
+ clearInitFlag: true,
675
+ });
676
+
589
677
  if (
590
678
  this.agentConfig.webRtcEnabled &&
591
679
  this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER)
@@ -606,6 +694,9 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
606
694
  if (!this.services.webSocketManager.isSocketClosed) {
607
695
  this.services.webSocketManager.close(false, 'Unregistering the SDK');
608
696
  }
697
+ if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
698
+ this.services.rtdWebSocketManager.close(false, 'Unregistering the SDK');
699
+ }
609
700
 
610
701
  if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
611
702
  this.services.rtdWebSocketManager.close(false, 'Unregistering the RTD websocket');
@@ -614,6 +705,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
614
705
  // Clear any cached agent configuration
615
706
  this.agentConfig = null;
616
707
 
708
+ if (wxAppPublishError) {
709
+ throw wxAppPublishError;
710
+ }
711
+
617
712
  LoggerProxy.log('Deregistered successfully', {
618
713
  module: CC_FILE,
619
714
  method: METHODS.DEREGISTER,
@@ -676,6 +771,9 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
676
771
  * ```
677
772
  */
678
773
  public async getBuddyAgents(data: BuddyAgents): Promise<BuddyAgentsResponse> {
774
+ const {action, state, mediaType = 'telephony'} = data;
775
+ const buddyAgentState = state ?? (action === 'Transfer' ? AGENT_STATE_AVAILABLE : undefined);
776
+
679
777
  LoggerProxy.info('Fetching buddy agents', {
680
778
  module: CC_FILE,
681
779
  method: METHODS.GET_BUDDY_AGENTS,
@@ -686,15 +784,19 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
686
784
  METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED,
687
785
  ]);
688
786
  const resp = await this.services.agent.buddyAgents({
689
- data: {agentProfileId: this.agentConfig.agentProfileID, ...data},
787
+ data: {
788
+ agentProfileId: this.agentConfig.agentProfileID,
789
+ mediaType,
790
+ ...(buddyAgentState ? {state: buddyAgentState} : {}),
791
+ },
690
792
  });
691
793
 
692
794
  this.metricsManager.trackEvent(
693
795
  METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_SUCCESS,
694
796
  {
695
797
  ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
696
- mediaType: data.mediaType,
697
- buddyAgentState: data.state,
798
+ mediaType,
799
+ buddyAgentState,
698
800
  buddyAgentCount: resp.data.agentList.length,
699
801
  },
700
802
  ['operational']
@@ -714,8 +816,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
714
816
  METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED,
715
817
  {
716
818
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failureResp),
717
- mediaType: data.mediaType,
718
- buddyAgentState: data.state,
819
+ mediaType,
820
+ buddyAgentState,
719
821
  },
720
822
  ['operational']
721
823
  );
@@ -731,7 +833,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
731
833
  * @private
732
834
  */
733
835
  private async connectWebsocket() {
734
- LoggerProxy.info('Connecting to websocket', {
836
+ LoggerProxy.log('Connecting to websocket', {
735
837
  module: CC_FILE,
736
838
  method: METHODS.CONNECT_WEBSOCKET,
737
839
  });
@@ -751,19 +853,41 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
751
853
  method: METHODS.CONNECT_WEBSOCKET,
752
854
  });
753
855
 
856
+ const configFlags: ConfigFlags = {
857
+ isEndTaskEnabled: this.agentConfig.isEndTaskEnabled,
858
+ isEndConsultEnabled: this.agentConfig.isEndConsultEnabled,
859
+ webRtcEnabled: this.agentConfig.webRtcEnabled,
860
+ autoWrapup: this.agentConfig.wrapUpData?.wrapUpProps?.autoWrapup ?? false,
861
+ aiFeature: this.agentConfig.aiFeature,
862
+ consultTransfer: {
863
+ allowConsultToQueue: this.agentConfig.allowConsultToQueue,
864
+ accessQueue: this.agentConfig.accessQueue,
865
+ accessEntryPoint: this.agentConfig.accessEntryPoint,
866
+ accessBuddyTeam: this.agentConfig.accessBuddyTeam,
867
+ },
868
+ enableWxBetterTogether: this.isWxBetterTogetherEnabled(),
869
+ };
870
+ this.taskManager.setConfigFlags(configFlags);
754
871
  // TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
755
872
  this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
756
873
  this.taskManager.setAgentId(this.agentConfig.agentId);
757
874
  this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
758
875
  this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
759
876
 
760
- if (this.agentConfig.aiFeature?.realtimeTranscripts?.enable) {
877
+ /**
878
+ * RTD websocket currently supports realtime transcripts and suggested responses.
879
+ * Extend this condition when additional AI RTD features are introduced.
880
+ */
881
+ if (
882
+ this.agentConfig.aiFeature?.realtimeTranscripts?.enable ||
883
+ this.agentConfig.aiFeature?.suggestedResponses?.enable
884
+ ) {
761
885
  LoggerProxy.info('Connecting to RTD websocket', {
762
886
  module: CC_FILE,
763
887
  method: METHODS.CONNECT_WEBSOCKET,
764
888
  });
765
889
 
766
- await this.services.rtdWebSocketManager
890
+ this.services.rtdWebSocketManager
767
891
  .initWebSocket({
768
892
  body: this.getConnectionConfig(),
769
893
  resource: RTD_SUBSCRIBE_API,
@@ -776,7 +900,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
776
900
  this.services.rtdWebSocketManager.on('message', this.handleRTDWebsocketMessage);
777
901
  })
778
902
  .catch((error) => {
779
- LoggerProxy.error(`Error during RTD websocket setup: ${error}`, {
903
+ LoggerProxy.error(`Error connecting to RTD websocket ${error}`, {
780
904
  module: CC_FILE,
781
905
  method: METHODS.CONNECT_WEBSOCKET,
782
906
  });
@@ -785,7 +909,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
785
909
 
786
910
  if (
787
911
  this.agentConfig.webRtcEnabled &&
788
- this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER)
912
+ this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER) &&
913
+ !this.isWebRTCRegistrationDisabled()
789
914
  ) {
790
915
  try {
791
916
  await this.$webex.internal.mercury.connect();
@@ -799,10 +924,23 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
799
924
  method: METHODS.CONNECT_WEBSOCKET,
800
925
  });
801
926
  }
927
+ } else if (this.isWebRTCRegistrationDisabled()) {
928
+ LoggerProxy.info(
929
+ 'Skipping Mobius registration because disableWebRTCRegistration is enabled',
930
+ {
931
+ module: CC_FILE,
932
+ method: METHODS.CONNECT_WEBSOCKET,
933
+ }
934
+ );
802
935
  }
803
936
 
804
937
  if (this.$config && this.$config.allowAutomatedRelogin) {
805
938
  await this.silentRelogin();
939
+ } else {
940
+ LoggerProxy.log('Skipping silent relogin: allowAutomatedRelogin is disabled', {
941
+ module: CC_FILE,
942
+ method: METHODS.CONNECT_WEBSOCKET,
943
+ });
806
944
  }
807
945
 
808
946
  return this.agentConfig;
@@ -845,36 +983,48 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
845
983
  * ```
846
984
  */
847
985
  public async stationLogin(data: AgentLogin): Promise<StationLoginResponse> {
848
- LoggerProxy.info('Starting agent station login', {
849
- module: CC_FILE,
850
- method: METHODS.STATION_LOGIN,
851
- });
986
+ const loggerContext = {module: CC_FILE, method: METHODS.STATION_LOGIN};
987
+
852
988
  try {
989
+ LoggerProxy.log(
990
+ `Starting agent station login | loginOption: ${data?.loginOption} teamId: ${data?.teamId}`,
991
+ loggerContext
992
+ );
853
993
  this.metricsManager.timeEvent([
854
994
  METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
855
995
  METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
856
996
  ]);
857
997
 
858
998
  const dialPlanEntries = this.agentConfig?.dialPlan?.dialPlanEntity ?? [];
859
- if (
860
- data.loginOption === LoginOption.AGENT_DN &&
861
- !isValidDialNumber(data.dialNumber, dialPlanEntries)
862
- ) {
863
- const error = new Error('INVALID_DIAL_NUMBER');
864
- // @ts-ignore - adding custom key to the error object
865
- error.details = {data: {reason: 'INVALID_DIAL_NUMBER'}} as Failure;
999
+ if (data.loginOption === LoginOption.AGENT_DN) {
1000
+ LoggerProxy.log(
1001
+ `Validating dial number | dialPlanEnabled: ${!!this.agentConfig
1002
+ ?.dialPlan} | dialPlanEntryCount: ${dialPlanEntries.length}`,
1003
+ loggerContext
1004
+ );
866
1005
 
867
- throw error;
1006
+ if (!isValidDialNumber(data.dialNumber, dialPlanEntries)) {
1007
+ LoggerProxy.log(
1008
+ `Dial number validation failed | dialNumber: ${data.dialNumber} | dialPlanEntryCount: ${dialPlanEntries.length}`,
1009
+ loggerContext
1010
+ );
1011
+ const error = new Error('INVALID_DIAL_NUMBER');
1012
+ // @ts-ignore - adding custom key to the error object
1013
+ error.details = {data: {reason: 'INVALID_DIAL_NUMBER'}} as Failure;
1014
+
1015
+ throw error;
1016
+ }
868
1017
  }
869
1018
 
1019
+ const deviceId = this.getDeviceId(data.loginOption, data.dialNumber);
870
1020
  const loginResponse = await this.services.agent.stationLogin({
871
1021
  data: {
872
- dialNumber:
873
- data.loginOption === LoginOption.BROWSER ? this.agentConfig.agentId : data.dialNumber,
1022
+ // BROWSER DN must be webrtc-{agentId} so VPOP/RTMS do not treat a bare UUID as PSTN.
1023
+ dialNumber: data.loginOption === LoginOption.BROWSER ? deviceId : data.dialNumber,
874
1024
  teamId: data.teamId,
875
1025
  deviceType: data.loginOption,
876
1026
  isExtension: data.loginOption === LoginOption.EXTENSION,
877
- deviceId: this.getDeviceId(data.loginOption, data.dialNumber),
1027
+ deviceId,
878
1028
  roles: [AGENT],
879
1029
  teamName: EMPTY_STRING,
880
1030
  siteId: EMPTY_STRING,
@@ -883,8 +1033,24 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
883
1033
  },
884
1034
  });
885
1035
 
886
- if (this.agentConfig.webRtcEnabled && data.loginOption === LoginOption.BROWSER) {
1036
+ if (
1037
+ this.agentConfig.webRtcEnabled &&
1038
+ data.loginOption === LoginOption.BROWSER &&
1039
+ !this.isWebRTCRegistrationDisabled()
1040
+ ) {
887
1041
  await this.webCallingService.registerWebCallingLine();
1042
+ } else if (
1043
+ this.agentConfig.webRtcEnabled &&
1044
+ data.loginOption === LoginOption.BROWSER &&
1045
+ this.isWebRTCRegistrationDisabled()
1046
+ ) {
1047
+ LoggerProxy.info(
1048
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
1049
+ {
1050
+ module: CC_FILE,
1051
+ method: METHODS.STATION_LOGIN,
1052
+ }
1053
+ );
888
1054
  }
889
1055
 
890
1056
  const resp = await loginResponse;
@@ -902,6 +1068,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
902
1068
  };
903
1069
 
904
1070
  this.webCallingService.setLoginOption(data.loginOption);
1071
+ if (this.agentConfig) {
1072
+ this.agentConfig.deviceType = data.loginOption;
1073
+ if (
1074
+ data.loginOption === LoginOption.AGENT_DN ||
1075
+ data.loginOption === LoginOption.EXTENSION
1076
+ ) {
1077
+ this.agentConfig.defaultDn = data.dialNumber;
1078
+ this.agentConfig.dn = data.dialNumber;
1079
+ }
1080
+ }
905
1081
  this.metricsManager.trackEvent(
906
1082
  METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
907
1083
  {
@@ -923,6 +1099,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
923
1099
  }
924
1100
  );
925
1101
 
1102
+ await this.ensureWxAppPostStationLogin();
1103
+
926
1104
  return response;
927
1105
  } catch (error) {
928
1106
  const failure = error.details as Failure;
@@ -991,6 +1169,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
991
1169
  this.webCallingService.deregisterWebCallingLine();
992
1170
  }
993
1171
 
1172
+ await this.teardownWxAppLocalState();
1173
+
994
1174
  LoggerProxy.log(`Agent station logout completed successfully`, {
995
1175
  module: CC_FILE,
996
1176
  method: METHODS.STATION_LOGOUT,
@@ -1246,6 +1426,600 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1246
1426
  }
1247
1427
  };
1248
1428
 
1429
+ /**
1430
+ * Checks whether Mobius/WebRTC registration should be skipped by config
1431
+ * @returns {boolean} True when browser WebRTC registration is disabled
1432
+ * @private
1433
+ */
1434
+ private isWebRTCRegistrationDisabled(): boolean {
1435
+ return this.$config?.disableWebRTCRegistration === true;
1436
+ }
1437
+
1438
+ /**
1439
+ * Validates contact-center plugin configuration before service initialization
1440
+ * @private
1441
+ */
1442
+ private validatePluginConfig(): void {
1443
+ if (
1444
+ this.$config?.disableWebRTCRegistration === true &&
1445
+ this.$config?.allowMultiLogin === false
1446
+ ) {
1447
+ throw new Error(
1448
+ '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.'
1449
+ );
1450
+ }
1451
+ }
1452
+
1453
+ /**
1454
+ * Whether wxApp thick-client answer is enabled for this SDK instance.
1455
+ * @public
1456
+ */
1457
+ public isWxBetterTogetherEnabled(): boolean {
1458
+ return this.$config?.enableWxBetterTogether === true;
1459
+ }
1460
+
1461
+ /**
1462
+ * Current station login option (Extension, Dial Number, or Browser/Desktop).
1463
+ * Prefer webCallingService — updated on every stationLogin before wxApp hooks run.
1464
+ * @private
1465
+ */
1466
+ private getCurrentStationLoginOption(): LoginOption | undefined {
1467
+ return this.webCallingService?.loginOption ?? this.agentConfig?.deviceType;
1468
+ }
1469
+
1470
+ /**
1471
+ * wxApp thick-client answer is supported for Extension and Dial Number station login only —
1472
+ * not Browser/Desktop (WebRTC) login.
1473
+ * @private
1474
+ */
1475
+ private assertWxAppStationLoginSupportedForEnable(): void {
1476
+ const loginOption = this.getCurrentStationLoginOption();
1477
+ if (loginOption !== LoginOption.EXTENSION && loginOption !== LoginOption.AGENT_DN) {
1478
+ throw new Error(
1479
+ 'setManageWebexCallingInWxcc requires EXTENSION or AGENT_DN station login. Complete station login before enabling.'
1480
+ );
1481
+ }
1482
+ }
1483
+
1484
+ /**
1485
+ * Clears runtime wxApp config after logout/deregister so stale flags do not apply on relogin.
1486
+ * @private
1487
+ */
1488
+ private resetEnableWxBetterTogetherConfig(): void {
1489
+ if (this.$config) {
1490
+ this.$config.enableWxBetterTogether = false;
1491
+ }
1492
+ this.taskManager.applyEnableWxBetterTogether(false);
1493
+ }
1494
+
1495
+ /**
1496
+ * Runtime enable/disable for wxApp Better Together (toast suppression + Mercury mute sync).
1497
+ *
1498
+ * **Phase 1 (WXCC-6026 / MMT): NOT part of the public host contract.**
1499
+ * Hosts must set `webexConfig.cc.enableWxBetterTogether` before `webex.init()` / `cc.register()`.
1500
+ * To change the flag after the SDK is initialized, re-init the SDK with the updated config.
1501
+ *
1502
+ * **Supported public read API:** {@link isWxBetterTogetherEnabled}.
1503
+ * **Supported telephony API:** unified `task.accept()`, `task.decline()`, `task.toggleMute()`,
1504
+ * `task.transmitDtmf()` — SDK routes wxApp calls internally when the init flag is active.
1505
+ *
1506
+ * **Production lifecycle does not call this method.** Station login, silent relogin, logout,
1507
+ * and deregister use {@link ensureWxAppPostStationLogin} and {@link teardownWxAppLocalState}
1508
+ * (usersub publish + Mercury subscribe/unsubscribe based on the init flag).
1509
+ *
1510
+ * This setter remains as a **private implementation** for Phase 2 runtime toggle and unit tests.
1511
+ * It updates `$config.enableWxBetterTogether`, TaskManager flags, cross-client usersub
1512
+ * (`answer-calls-on-wxcc`), and Mercury mute sync; rolls back config on publish/connect failure.
1513
+ *
1514
+ * @param enabled - `true` to enable wxApp answer + usersub suppression; `false` to disable and tear down.
1515
+ * @throws When enabling without login, on unsupported BROWSER station login, or when usersub/Mercury init fails.
1516
+ * @internal
1517
+ * @see enableWxBetterTogether (CCPluginConfig init flag)
1518
+ * @see ensureWxAppPostStationLogin
1519
+ * @see teardownWxAppLocalState
1520
+ */
1521
+ private async setManageWebexCallingInWxcc(enabled: boolean): Promise<void> {
1522
+ if (enabled && !this.$webex.internal.device?.userId) {
1523
+ throw new Error('Cannot publish answer-calls-on-wxcc: user is not logged in');
1524
+ }
1525
+
1526
+ if (enabled) {
1527
+ this.assertWxAppStationLoginSupportedForEnable();
1528
+ }
1529
+
1530
+ const previousEnabled = this.isWxBetterTogetherEnabled();
1531
+
1532
+ if (this.$config) {
1533
+ this.$config.enableWxBetterTogether = enabled;
1534
+ }
1535
+
1536
+ this.taskManager.applyEnableWxBetterTogether(enabled);
1537
+
1538
+ let publishedEnable = false;
1539
+
1540
+ try {
1541
+ if (enabled) {
1542
+ await this.ensureWxAppMercuryAndSubscribe();
1543
+ await this.publishAnswerOnWebexCrossClientState(true);
1544
+ publishedEnable = true;
1545
+ this.taskManager.syncWxAppMuteFromCallDetailsForAllTasks();
1546
+ } else {
1547
+ await this.publishAnswerOnWebexCrossClientState(false);
1548
+ this.wxAppTelephonyMercurySync.unsubscribe();
1549
+ await this.releaseWxAppMercuryResources();
1550
+ }
1551
+ } catch (error) {
1552
+ if (publishedEnable) {
1553
+ await this.revertWxAppCrossClientPublish();
1554
+ } else if (enabled) {
1555
+ this.wxAppTelephonyMercurySync.unsubscribe();
1556
+ await this.releaseWxAppMercuryResources();
1557
+ }
1558
+
1559
+ if (this.$config) {
1560
+ this.$config.enableWxBetterTogether = previousEnabled;
1561
+ }
1562
+ this.taskManager.applyEnableWxBetterTogether(previousEnabled);
1563
+ throw error;
1564
+ }
1565
+ }
1566
+
1567
+ private getWxAppTelephonyTaskType(): 'Voice' | 'WebRTC' | 'unknown' {
1568
+ const loginOption = this.getCurrentStationLoginOption();
1569
+
1570
+ if (loginOption === LoginOption.BROWSER) {
1571
+ return 'WebRTC';
1572
+ }
1573
+
1574
+ if (loginOption === LoginOption.EXTENSION || loginOption === LoginOption.AGENT_DN) {
1575
+ return 'Voice';
1576
+ }
1577
+
1578
+ return 'unknown';
1579
+ }
1580
+
1581
+ private async ensureWxAppPostStationLogin(): Promise<void> {
1582
+ const loginOption = this.getCurrentStationLoginOption();
1583
+ const flagEnabled = this.isWxBetterTogetherEnabled();
1584
+ const telephonyTaskType = this.getWxAppTelephonyTaskType();
1585
+
1586
+ if (loginOption === LoginOption.BROWSER) {
1587
+ if (flagEnabled) {
1588
+ logWxAppSessionReadiness({
1589
+ enableWxBetterTogether: true,
1590
+ loginOption,
1591
+ wxAppHooksApplied: false,
1592
+ usersubPublished: false,
1593
+ mercurySubscribed: false,
1594
+ telephonyTaskType,
1595
+ skipReason: 'unsupported_browser_login',
1596
+ });
1597
+
1598
+ this.metricsManager.trackEvent(
1599
+ METRIC_EVENT_NAMES.WXAPP_SESSION_SKIPPED,
1600
+ {
1601
+ loginOption,
1602
+ enableWxBetterTogether: true,
1603
+ skipReason: 'unsupported_browser_login',
1604
+ },
1605
+ ['operational', 'behavioral']
1606
+ );
1607
+ }
1608
+
1609
+ return;
1610
+ }
1611
+
1612
+ let publishedEnable = false;
1613
+ let wxAppInitStage: 'mercury' | 'publish' = 'mercury';
1614
+
1615
+ try {
1616
+ if (flagEnabled) {
1617
+ this.metricsManager.timeEvent([
1618
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_SUCCESS,
1619
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
1620
+ ]);
1621
+
1622
+ await this.ensureWxAppMercuryAndSubscribe();
1623
+ wxAppInitStage = 'publish';
1624
+ await this.publishAnswerOnWebexCrossClientState(true);
1625
+ publishedEnable = true;
1626
+ this.taskManager.syncWxAppMuteFromCallDetailsForAllTasks();
1627
+
1628
+ const mercurySubscribed = this.wxAppTelephonyMercurySync.isSubscribed();
1629
+ const usersubPublished = this.webexCrossClientService.isAnswerCallsStateActive();
1630
+
1631
+ logWxAppSessionReadiness({
1632
+ enableWxBetterTogether: true,
1633
+ loginOption,
1634
+ wxAppHooksApplied: true,
1635
+ usersubPublished,
1636
+ mercurySubscribed,
1637
+ telephonyTaskType,
1638
+ });
1639
+
1640
+ const sessionInitReady = mercurySubscribed && usersubPublished;
1641
+
1642
+ if (sessionInitReady) {
1643
+ this.metricsManager.trackEvent(
1644
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_SUCCESS,
1645
+ {loginOption, enableWxBetterTogether: true},
1646
+ ['operational', 'behavioral']
1647
+ );
1648
+ } else {
1649
+ this.metricsManager.trackEvent(
1650
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
1651
+ {
1652
+ loginOption,
1653
+ enableWxBetterTogether: true,
1654
+ skipReason: !usersubPublished ? 'usersub_not_published' : 'mercury_not_subscribed',
1655
+ },
1656
+ ['operational', 'behavioral']
1657
+ );
1658
+ }
1659
+ } else {
1660
+ await this.ensureWxAppDeviceRegistered();
1661
+ await this.publishAnswerOnWebexCrossClientState(false, {force: true});
1662
+ this.webexCrossClientService.teardown();
1663
+ this.wxAppTelephonyMercurySync.unsubscribe();
1664
+ await this.releaseWxAppMercuryResources();
1665
+
1666
+ logWxAppSessionReadiness({
1667
+ enableWxBetterTogether: false,
1668
+ loginOption,
1669
+ wxAppHooksApplied: true,
1670
+ usersubPublished: false,
1671
+ mercurySubscribed: false,
1672
+ telephonyTaskType,
1673
+ skipReason: 'flag_disabled',
1674
+ });
1675
+
1676
+ this.metricsManager.trackEvent(
1677
+ METRIC_EVENT_NAMES.WXAPP_SESSION_SKIPPED,
1678
+ {loginOption, enableWxBetterTogether: false, skipReason: 'flag_disabled'},
1679
+ ['operational', 'behavioral']
1680
+ );
1681
+ }
1682
+ } catch (error) {
1683
+ LoggerProxy.error(`Failed to initialize wxApp post-station-login: ${error}`, {
1684
+ module: CC_FILE,
1685
+ method: METHODS.STATION_LOGIN,
1686
+ });
1687
+
1688
+ if (flagEnabled) {
1689
+ const sessionInitFailureReason =
1690
+ wxAppInitStage === 'mercury' ? 'mercury_subscribe_failed' : 'publish_failed';
1691
+
1692
+ this.metricsManager.trackEvent(
1693
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
1694
+ {
1695
+ loginOption,
1696
+ enableWxBetterTogether: true,
1697
+ skipReason: sessionInitFailureReason,
1698
+ error: error instanceof Error ? error.toString() : String(error),
1699
+ },
1700
+ ['operational', 'behavioral']
1701
+ );
1702
+
1703
+ logWxAppSessionReadiness({
1704
+ enableWxBetterTogether: true,
1705
+ loginOption,
1706
+ wxAppHooksApplied: false,
1707
+ usersubPublished:
1708
+ publishedEnable && this.webexCrossClientService.isAnswerCallsStateActive(),
1709
+ mercurySubscribed: this.wxAppTelephonyMercurySync.isSubscribed(),
1710
+ telephonyTaskType,
1711
+ skipReason: sessionInitFailureReason,
1712
+ });
1713
+
1714
+ if (publishedEnable) {
1715
+ await this.revertWxAppCrossClientPublish();
1716
+ } else {
1717
+ this.wxAppTelephonyMercurySync.unsubscribe();
1718
+ await this.releaseWxAppMercuryResources();
1719
+ }
1720
+ this.resetEnableWxBetterTogetherConfig();
1721
+ } else {
1722
+ this.scheduleForcedFalsePublishRetry();
1723
+ }
1724
+ }
1725
+ }
1726
+
1727
+ private clearWxAppFalsePublishRetryTimer(): void {
1728
+ if (this.wxAppFalsePublishRetryTimer) {
1729
+ clearTimeout(this.wxAppFalsePublishRetryTimer);
1730
+ this.wxAppFalsePublishRetryTimer = undefined;
1731
+ }
1732
+ }
1733
+
1734
+ private scheduleForcedFalsePublishRetry(retryAttempt = 0): void {
1735
+ this.scheduleFalsePublishRetry(retryAttempt, {requireDisabled: true});
1736
+ }
1737
+
1738
+ /**
1739
+ * Retries compensating false publish after rollback failure, regardless of enabled flag.
1740
+ * @private
1741
+ */
1742
+ private scheduleCompensatingFalsePublishRetry(retryAttempt = 0): void {
1743
+ this.scheduleFalsePublishRetry(retryAttempt);
1744
+ }
1745
+
1746
+ private scheduleFalsePublishRetry(
1747
+ retryAttempt = 0,
1748
+ options: {requireDisabled?: boolean} = {}
1749
+ ): void {
1750
+ if (retryAttempt >= ContactCenter.WXAPP_FALSE_PUBLISH_MAX_RETRIES) {
1751
+ return;
1752
+ }
1753
+
1754
+ if (options?.requireDisabled && this.isWxBetterTogetherEnabled()) {
1755
+ return;
1756
+ }
1757
+
1758
+ this.clearWxAppFalsePublishRetryTimer();
1759
+
1760
+ this.wxAppFalsePublishRetryTimer = setTimeout(async () => {
1761
+ this.wxAppFalsePublishRetryTimer = undefined;
1762
+
1763
+ if (options?.requireDisabled && this.isWxBetterTogetherEnabled()) {
1764
+ return;
1765
+ }
1766
+
1767
+ if (
1768
+ !options?.requireDisabled &&
1769
+ this.isWxBetterTogetherEnabled() &&
1770
+ this.webexCrossClientService.isAnswerCallsStateActive()
1771
+ ) {
1772
+ return;
1773
+ }
1774
+
1775
+ try {
1776
+ await this.ensureWxAppDeviceRegistered();
1777
+ await this.publishAnswerOnWebexCrossClientState(false, {force: true});
1778
+ this.webexCrossClientService.teardown();
1779
+ this.wxAppTelephonyMercurySync.unsubscribe();
1780
+ await this.releaseWxAppMercuryResources();
1781
+ } catch (retryError) {
1782
+ LoggerProxy.error(
1783
+ `Failed to retry forced answer-calls-on-wxcc false publish: ${retryError}`,
1784
+ {
1785
+ module: CC_FILE,
1786
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
1787
+ }
1788
+ );
1789
+ this.scheduleFalsePublishRetry(retryAttempt + 1, options);
1790
+ }
1791
+ }, ContactCenter.WXAPP_FALSE_PUBLISH_RETRY_DELAY_MS);
1792
+ }
1793
+
1794
+ /**
1795
+ * Best-effort rollback when usersub true was published but wxApp setup did not complete.
1796
+ * @private
1797
+ */
1798
+ private async revertWxAppCrossClientPublish(): Promise<void> {
1799
+ try {
1800
+ await this.publishAnswerOnWebexCrossClientState(false, {force: true});
1801
+ } catch (error) {
1802
+ LoggerProxy.error(
1803
+ `Failed to publish compensating answer-calls-on-wxcc false during wxApp rollback: ${error}`,
1804
+ {
1805
+ module: CC_FILE,
1806
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
1807
+ }
1808
+ );
1809
+ this.scheduleCompensatingFalsePublishRetry();
1810
+ }
1811
+
1812
+ this.webexCrossClientService.teardown();
1813
+ this.wxAppTelephonyMercurySync.unsubscribe();
1814
+ await this.releaseWxAppMercuryResources();
1815
+ }
1816
+
1817
+ private async ensureWxAppDeviceRegistered(): Promise<void> {
1818
+ const device = this.$webex.internal.device;
1819
+
1820
+ if (device && !device.registered && device.register) {
1821
+ await device.register();
1822
+ this.wxAppDeviceRegisteredByCc = true;
1823
+ LoggerProxy.log('WxApp: device registered for cross-client publish', {
1824
+ module: CC_FILE,
1825
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
1826
+ });
1827
+ }
1828
+ }
1829
+
1830
+ private async ensureWxAppMercuryConnected(): Promise<void> {
1831
+ const mercury = this.$webex.internal.mercury;
1832
+
1833
+ await this.ensureWxAppDeviceRegistered();
1834
+
1835
+ if (mercury && !mercury.connected) {
1836
+ await mercury.connect();
1837
+ this.wxAppMercuryConnectedByCc = true;
1838
+ LoggerProxy.log('WxApp mute sync: mercury connected', {
1839
+ module: CC_FILE,
1840
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
1841
+ });
1842
+ }
1843
+ }
1844
+
1845
+ /**
1846
+ * Disconnects Mercury and unregisters the device when wxApp mute sync opened them.
1847
+ * @private
1848
+ */
1849
+ private async releaseWxAppMercuryResources(): Promise<void> {
1850
+ const mercury = this.$webex.internal.mercury;
1851
+ const device = this.$webex.internal.device;
1852
+
1853
+ if (this.wxAppMercuryConnectedByCc && mercury?.connected) {
1854
+ try {
1855
+ await mercury.disconnect();
1856
+ this.wxAppMercuryConnectedByCc = false;
1857
+ LoggerProxy.log('WxApp mute sync: mercury disconnected', {
1858
+ module: CC_FILE,
1859
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
1860
+ });
1861
+ } catch (error) {
1862
+ LoggerProxy.error(`Failed to disconnect wxApp Mercury: ${error}`, {
1863
+ module: CC_FILE,
1864
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
1865
+ });
1866
+ }
1867
+ }
1868
+
1869
+ if (this.wxAppDeviceRegisteredByCc && device?.registered && device.unregister) {
1870
+ try {
1871
+ // @ts-ignore
1872
+ await device.unregister();
1873
+ this.wxAppDeviceRegisteredByCc = false;
1874
+ LoggerProxy.log('WxApp mute sync: device unregistered', {
1875
+ module: CC_FILE,
1876
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
1877
+ });
1878
+ } catch (error) {
1879
+ LoggerProxy.error(`Failed to unregister wxApp device: ${error}`, {
1880
+ module: CC_FILE,
1881
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
1882
+ });
1883
+ }
1884
+ } else if (this.wxAppDeviceRegisteredByCc) {
1885
+ this.wxAppDeviceRegisteredByCc = false;
1886
+ }
1887
+ }
1888
+
1889
+ /**
1890
+ * Clears wxApp runtime state and feature-owned resources. Publish failures are logged;
1891
+ * optional rethrow for deregister when callers need to surface usersub errors.
1892
+ * @private
1893
+ */
1894
+ private async teardownWxAppLocalState(options?: {
1895
+ rethrowPublishError?: boolean;
1896
+ clearInitFlag?: boolean;
1897
+ }): Promise<{publishError?: unknown}> {
1898
+ this.clearWxAppFalsePublishRetryTimer();
1899
+ let publishError: unknown;
1900
+
1901
+ try {
1902
+ const shouldForceFalsePublish =
1903
+ this.isWxBetterTogetherEnabled() || this.webexCrossClientService.isAnswerCallsStateActive();
1904
+
1905
+ await this.publishAnswerOnWebexCrossClientState(false, {
1906
+ force: shouldForceFalsePublish,
1907
+ });
1908
+ } catch (error) {
1909
+ publishError = error;
1910
+ LoggerProxy.error(
1911
+ `Failed to publish answer-calls-on-wxcc false during wxApp teardown: ${error}`,
1912
+ {
1913
+ module: CC_FILE,
1914
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
1915
+ }
1916
+ );
1917
+ }
1918
+
1919
+ this.webexCrossClientService.teardown();
1920
+ this.wxAppTelephonyMercurySync.unsubscribe();
1921
+ await this.releaseWxAppMercuryResources();
1922
+
1923
+ if (options?.clearInitFlag) {
1924
+ this.resetEnableWxBetterTogetherConfig();
1925
+ }
1926
+
1927
+ if (publishError) {
1928
+ this.scheduleCompensatingFalsePublishRetry();
1929
+ }
1930
+
1931
+ if (options?.rethrowPublishError && publishError) {
1932
+ throw publishError;
1933
+ }
1934
+
1935
+ return {publishError: publishError ?? undefined};
1936
+ }
1937
+
1938
+ private async ensureWxAppMercuryAndSubscribe(): Promise<void> {
1939
+ if (!this.isWxBetterTogetherEnabled()) {
1940
+ return;
1941
+ }
1942
+
1943
+ if (this.getCurrentStationLoginOption() === LoginOption.BROWSER) {
1944
+ return;
1945
+ }
1946
+
1947
+ const agentId = this.agentConfig?.agentId;
1948
+ if (!agentId) {
1949
+ LoggerProxy.error('Cannot subscribe wxApp mute sync: agentId unavailable', {
1950
+ module: CC_FILE,
1951
+ method: METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY,
1952
+ });
1953
+
1954
+ this.metricsManager.trackEvent(
1955
+ METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
1956
+ {error: 'agentId unavailable'},
1957
+ ['operational', 'behavioral']
1958
+ );
1959
+
1960
+ return;
1961
+ }
1962
+
1963
+ try {
1964
+ await this.ensureWxAppMercuryConnected();
1965
+ this.wxAppTelephonyMercurySync.subscribe(agentId, (callId, muted) => {
1966
+ this.taskManager.applyWxAppMuteStateFromSync(callId, muted);
1967
+ });
1968
+ } catch (error) {
1969
+ LoggerProxy.error(`Failed to ensure wxApp Mercury mute sync: ${error}`, {
1970
+ module: CC_FILE,
1971
+ method: METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY,
1972
+ });
1973
+ this.metricsManager.trackEvent(
1974
+ METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
1975
+ {error: error instanceof Error ? error.toString() : String(error)},
1976
+ ['operational', 'behavioral']
1977
+ );
1978
+ this.wxAppTelephonyMercurySync.unsubscribe();
1979
+ await this.releaseWxAppMercuryResources();
1980
+ throw error;
1981
+ }
1982
+ }
1983
+
1984
+ private async publishAnswerOnWebexCrossClientState(
1985
+ enable: boolean,
1986
+ options?: {force?: boolean}
1987
+ ): Promise<void> {
1988
+ if (enable && !this.isWxBetterTogetherEnabled()) {
1989
+ return;
1990
+ }
1991
+
1992
+ if (!enable && !options?.force && !this.webexCrossClientService.isAnswerCallsStateActive()) {
1993
+ return;
1994
+ }
1995
+
1996
+ const userId = this.$webex.internal.device?.userId;
1997
+ if (!userId) {
1998
+ if (enable && this.isWxBetterTogetherEnabled()) {
1999
+ this.metricsManager.trackEvent(
2000
+ METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
2001
+ {
2002
+ enableWxBetterTogether: true,
2003
+ skipReason: 'user_id_unavailable',
2004
+ error: 'User ID is unavailable for cross-client publish',
2005
+ },
2006
+ ['operational', 'behavioral']
2007
+ );
2008
+ }
2009
+
2010
+ return;
2011
+ }
2012
+
2013
+ await this.webexCrossClientService.setManageWebexCallingInWxcc(enable, {
2014
+ userId,
2015
+ trackPublishMetrics: true,
2016
+ });
2017
+
2018
+ if (enable) {
2019
+ this.clearWxAppFalsePublishRetryTimer();
2020
+ }
2021
+ }
2022
+
1249
2023
  /**
1250
2024
  * Initializes event listeners for the Contact Center service
1251
2025
  * Sets up handlers for connection state changes and other core events
@@ -1277,13 +2051,13 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1277
2051
  private async handleConnectionLost(msg: ConnectionLostDetails): Promise<void> {
1278
2052
  if (msg.isConnectionLost) {
1279
2053
  // TODO: Emit an event saying connection is lost
1280
- LoggerProxy.info('event=handleConnectionLost | Connection lost', {
2054
+ LoggerProxy.log('event=handleConnectionLost | Connection lost', {
1281
2055
  module: CC_FILE,
1282
2056
  method: METHODS.HANDLE_CONNECTION_LOST,
1283
2057
  });
1284
2058
  } else if (msg.isSocketReconnected) {
1285
2059
  // TODO: Emit an event saying connection is re-estabilished
1286
- LoggerProxy.info(
2060
+ LoggerProxy.log(
1287
2061
  'event=handleConnectionReconnect | Connection reconnected attempting to request silent relogin',
1288
2062
  {module: CC_FILE, method: METHODS.HANDLE_CONNECTION_LOST}
1289
2063
  );
@@ -1298,7 +2072,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1298
2072
  * @private
1299
2073
  */
1300
2074
  private async silentRelogin(): Promise<void> {
1301
- LoggerProxy.info('Starting silent relogin process', {
2075
+ LoggerProxy.log('Starting silent relogin process', {
1302
2076
  module: CC_FILE,
1303
2077
  method: METHODS.SILENT_RELOGIN,
1304
2078
  });
@@ -1320,7 +2094,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1320
2094
  await this.handleDeviceType(deviceType as LoginOption, dn);
1321
2095
 
1322
2096
  if (lastStateChangeReason === 'agent-wss-disconnect') {
1323
- LoggerProxy.info(
2097
+ LoggerProxy.log(
1324
2098
  'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1325
2099
  {module: CC_FILE, method: METHODS.SILENT_RELOGIN}
1326
2100
  );
@@ -1349,8 +2123,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1349
2123
  }
1350
2124
  this.agentConfig.lastStateAuxCodeId = auxCodeId;
1351
2125
  this.agentConfig.isAgentLoggedIn = true;
1352
- // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
1353
- this.services.webSocketManager.on('message', this.handleWebsocketMessage);
2126
+
2127
+ await this.ensureWxAppPostStationLogin();
1354
2128
 
1355
2129
  LoggerProxy.log(
1356
2130
  `Silent relogin process completed successfully with login Option: ${reLoginResponse.data.deviceType} teamId: ${reLoginResponse.data.teamId}`,
@@ -1391,6 +2165,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1391
2165
  this.agentConfig.deviceType = deviceType;
1392
2166
  switch (deviceType) {
1393
2167
  case LoginOption.BROWSER:
2168
+ if (this.isWebRTCRegistrationDisabled()) {
2169
+ LoggerProxy.info(
2170
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
2171
+ {
2172
+ module: CC_FILE,
2173
+ method: METHODS.HANDLE_DEVICE_TYPE,
2174
+ }
2175
+ );
2176
+ break;
2177
+ }
1394
2178
  try {
1395
2179
  await this.webCallingService.registerWebCallingLine();
1396
2180
  } catch (error) {
@@ -1656,6 +2440,166 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1656
2440
  }
1657
2441
  }
1658
2442
 
2443
+ /**
2444
+ * Skips a campaign preview contact, requesting the next contact from the campaign.
2445
+ *
2446
+ * When a campaign manager reserves a contact for an agent, the agent receives an
2447
+ * `AgentOfferCampaignReservation` event. Instead of accepting, the agent can skip the
2448
+ * preview contact to move to the next contact in the campaign.
2449
+ *
2450
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
2451
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
2452
+ * @throws {Error} If the operation fails (network error, etc.)
2453
+ *
2454
+ * @example
2455
+ * ```typescript
2456
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
2457
+ * const { interactionId } = task.data;
2458
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
2459
+ *
2460
+ * const result = await webex.cc.skipPreviewContact({ interactionId, campaignId });
2461
+ * });
2462
+ * ```
2463
+ */
2464
+ public async skipPreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
2465
+ const task = this.taskManager.getTask(payload.interactionId);
2466
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled === 'true') {
2467
+ LoggerProxy.warn('Skip action is disabled for this campaign preview contact', {
2468
+ module: CC_FILE,
2469
+ method: METHODS.SKIP_PREVIEW_CONTACT,
2470
+ interactionId: payload.interactionId,
2471
+ });
2472
+ throw new Error('Skip action is disabled for this campaign preview contact');
2473
+ }
2474
+
2475
+ LoggerProxy.info('Skipping campaign preview contact', {
2476
+ module: CC_FILE,
2477
+ method: METHODS.SKIP_PREVIEW_CONTACT,
2478
+ });
2479
+ try {
2480
+ this.metricsManager.timeEvent([
2481
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
2482
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
2483
+ ]);
2484
+
2485
+ const result = await this.services.dialer.skipPreviewContact({data: payload});
2486
+
2487
+ this.metricsManager.trackEvent(
2488
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
2489
+ {
2490
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
2491
+ interactionId: payload.interactionId,
2492
+ campaignId: payload.campaignId,
2493
+ },
2494
+ ['behavioral', 'business', 'operational']
2495
+ );
2496
+
2497
+ LoggerProxy.log('Campaign preview contact skipped successfully', {
2498
+ module: CC_FILE,
2499
+ method: METHODS.SKIP_PREVIEW_CONTACT,
2500
+ trackingId: result.trackingId,
2501
+ interactionId: payload.interactionId,
2502
+ });
2503
+
2504
+ return result;
2505
+ } catch (error) {
2506
+ const failure = error.details as Failure;
2507
+ this.metricsManager.trackEvent(
2508
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
2509
+ {
2510
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
2511
+ interactionId: payload.interactionId,
2512
+ campaignId: payload.campaignId,
2513
+ },
2514
+ ['behavioral', 'business', 'operational']
2515
+ );
2516
+ const {error: detailedError} = getErrorDetails(error, METHODS.SKIP_PREVIEW_CONTACT, CC_FILE);
2517
+ throw detailedError;
2518
+ }
2519
+ }
2520
+
2521
+ /**
2522
+ * Removes a campaign preview contact from the campaign list entirely.
2523
+ *
2524
+ * When a campaign manager reserves a contact for an agent, the agent receives an
2525
+ * `AgentOfferCampaignReservation` event. Instead of accepting or skipping, the agent can
2526
+ * remove the preview contact to permanently take it out of the campaign contact list.
2527
+ *
2528
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
2529
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
2530
+ * @throws {Error} If the operation fails (network error, etc.)
2531
+ *
2532
+ * @example
2533
+ * ```typescript
2534
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
2535
+ * const { interactionId } = task.data;
2536
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
2537
+ *
2538
+ * const result = await webex.cc.removePreviewContact({ interactionId, campaignId });
2539
+ * });
2540
+ * ```
2541
+ */
2542
+ public async removePreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
2543
+ const task = this.taskManager.getTask(payload.interactionId);
2544
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled === 'true') {
2545
+ LoggerProxy.warn('Remove action is disabled for this campaign preview contact', {
2546
+ module: CC_FILE,
2547
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
2548
+ interactionId: payload.interactionId,
2549
+ });
2550
+ throw new Error('Remove action is disabled for this campaign preview contact');
2551
+ }
2552
+
2553
+ LoggerProxy.info('Removing campaign preview contact', {
2554
+ module: CC_FILE,
2555
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
2556
+ });
2557
+ try {
2558
+ this.metricsManager.timeEvent([
2559
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
2560
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
2561
+ ]);
2562
+
2563
+ const result = await this.services.dialer.removePreviewContact({data: payload});
2564
+
2565
+ this.metricsManager.trackEvent(
2566
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
2567
+ {
2568
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
2569
+ interactionId: payload.interactionId,
2570
+ campaignId: payload.campaignId,
2571
+ },
2572
+ ['behavioral', 'business', 'operational']
2573
+ );
2574
+
2575
+ LoggerProxy.log('Campaign preview contact removed successfully', {
2576
+ module: CC_FILE,
2577
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
2578
+ trackingId: result.trackingId,
2579
+ interactionId: payload.interactionId,
2580
+ });
2581
+
2582
+ return result;
2583
+ } catch (error) {
2584
+ const failure = error.details as Failure;
2585
+ this.metricsManager.trackEvent(
2586
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
2587
+ {
2588
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
2589
+ interactionId: payload.interactionId,
2590
+ campaignId: payload.campaignId,
2591
+ },
2592
+ ['behavioral', 'business', 'operational']
2593
+ );
2594
+ const {error: detailedError} = getErrorDetails(
2595
+ error,
2596
+ METHODS.REMOVE_PREVIEW_CONTACT,
2597
+ CC_FILE
2598
+ );
2599
+ throw detailedError;
2600
+ }
2601
+ }
2602
+
1659
2603
  /**
1660
2604
  * Fetches outdial ANI (Automatic Number Identification) entries for an outdial ANI ID.
1661
2605
  *
@@ -1900,8 +2844,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1900
2844
  }
1901
2845
 
1902
2846
  /**
1903
- * Returns paginated entry points for the organization.
1904
- * Thin wrapper around internal EntryPoint instance.
2847
+ * Returns paginated entry points using the EntryPoint service defaults.
2848
+ * Existing request parameters can override those defaults.
1905
2849
  * @public
1906
2850
  */
1907
2851
  public async getEntryPoints(
@@ -1911,8 +2855,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1911
2855
  }
1912
2856
 
1913
2857
  /**
1914
- * Returns paginated contact service queues for the organization.
1915
- * Thin wrapper around internal Queue instance.
2858
+ * Returns paginated contact service queues using the Queue service defaults.
2859
+ * Existing request parameters can override those defaults.
1916
2860
  * @public
1917
2861
  */
1918
2862
  public async getQueues(