@webex/contact-center 3.12.0-next.15 → 3.12.0-next.151

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 (322) hide show
  1. package/.sdd/manifest.json +887 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +62 -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 +52 -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 +382 -0
  17. package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
  18. package/ai-docs/features/contact-owner-change-refactor.md +78 -0
  19. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  20. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  21. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  22. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  23. package/ai-docs/patterns/testing-patterns.md +480 -0
  24. package/ai-docs/patterns/typescript-patterns.md +365 -0
  25. package/ai-docs/templates/README.md +102 -0
  26. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  27. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  28. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  29. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  30. package/ai-docs/templates/new-method/00-master.md +80 -0
  31. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  32. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  33. package/ai-docs/templates/new-method/03-tests.md +201 -0
  34. package/ai-docs/templates/new-method/04-validation.md +141 -0
  35. package/ai-docs/templates/new-service/00-master.md +109 -0
  36. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  37. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  38. package/ai-docs/templates/new-service/03-integration.md +178 -0
  39. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  40. package/ai-docs/templates/new-service/05-validation.md +145 -0
  41. package/dist/cc.js +1010 -47
  42. package/dist/cc.js.map +1 -1
  43. package/dist/config.js +13 -0
  44. package/dist/config.js.map +1 -1
  45. package/dist/constants.js +40 -3
  46. package/dist/constants.js.map +1 -1
  47. package/dist/index.js +54 -17
  48. package/dist/index.js.map +1 -1
  49. package/dist/metrics/MetricsManager.js +16 -0
  50. package/dist/metrics/MetricsManager.js.map +1 -1
  51. package/dist/metrics/behavioral-events.js +211 -0
  52. package/dist/metrics/behavioral-events.js.map +1 -1
  53. package/dist/metrics/constants.js +58 -4
  54. package/dist/metrics/constants.js.map +1 -1
  55. package/dist/services/AddressBook.js +18 -15
  56. package/dist/services/AddressBook.js.map +1 -1
  57. package/dist/services/AnswerCallOnWebexService.js +202 -0
  58. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  59. package/dist/services/ApiAiAssistant.js +351 -14
  60. package/dist/services/ApiAiAssistant.js.map +1 -1
  61. package/dist/services/EntryPoint.js +46 -68
  62. package/dist/services/EntryPoint.js.map +1 -1
  63. package/dist/services/Queue.js +27 -22
  64. package/dist/services/Queue.js.map +1 -1
  65. package/dist/services/UserPreference.js +427 -0
  66. package/dist/services/UserPreference.js.map +1 -0
  67. package/dist/services/WebexCrossClientService.js +225 -0
  68. package/dist/services/WebexCrossClientService.js.map +1 -0
  69. package/dist/services/WxAppTelephonyMercurySync.js +111 -0
  70. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  71. package/dist/services/config/Util.js +48 -48
  72. package/dist/services/config/Util.js.map +1 -1
  73. package/dist/services/config/constants.js +56 -78
  74. package/dist/services/config/constants.js.map +1 -1
  75. package/dist/services/config/index.js +100 -226
  76. package/dist/services/config/index.js.map +1 -1
  77. package/dist/services/config/types.js +97 -24
  78. package/dist/services/config/types.js.map +1 -1
  79. package/dist/services/core/Err.js.map +1 -1
  80. package/dist/services/core/Utils.js +111 -32
  81. package/dist/services/core/Utils.js.map +1 -1
  82. package/dist/services/core/WebexRequest.js +6 -2
  83. package/dist/services/core/WebexRequest.js.map +1 -1
  84. package/dist/services/core/aqm-reqs.js +28 -14
  85. package/dist/services/core/aqm-reqs.js.map +1 -1
  86. package/dist/services/core/types.js.map +1 -1
  87. package/dist/services/core/websocket/WebSocketManager.js +2 -1
  88. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  89. package/dist/services/core/websocket/types.js.map +1 -1
  90. package/dist/services/index.js +1 -1
  91. package/dist/services/index.js.map +1 -1
  92. package/dist/services/task/Task.js +955 -0
  93. package/dist/services/task/Task.js.map +1 -0
  94. package/dist/services/task/TaskFactory.js +50 -0
  95. package/dist/services/task/TaskFactory.js.map +1 -0
  96. package/dist/services/task/TaskManager.js +1272 -523
  97. package/dist/services/task/TaskManager.js.map +1 -1
  98. package/dist/services/task/TaskUtils.js +221 -27
  99. package/dist/services/task/TaskUtils.js.map +1 -1
  100. package/dist/services/task/WebexCallingUtils.js +70 -0
  101. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  102. package/dist/services/task/constants.js +21 -2
  103. package/dist/services/task/constants.js.map +1 -1
  104. package/dist/services/task/contact.js +29 -0
  105. package/dist/services/task/contact.js.map +1 -1
  106. package/dist/services/task/dialer.js +78 -0
  107. package/dist/services/task/dialer.js.map +1 -1
  108. package/dist/services/task/digital/Digital.js +78 -0
  109. package/dist/services/task/digital/Digital.js.map +1 -0
  110. package/dist/services/task/state-machine/TaskStateMachine.js +982 -0
  111. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  112. package/dist/services/task/state-machine/actions.js +572 -0
  113. package/dist/services/task/state-machine/actions.js.map +1 -0
  114. package/dist/services/task/state-machine/constants.js +161 -0
  115. package/dist/services/task/state-machine/constants.js.map +1 -0
  116. package/dist/services/task/state-machine/guards.js +379 -0
  117. package/dist/services/task/state-machine/guards.js.map +1 -0
  118. package/dist/services/task/state-machine/index.js +53 -0
  119. package/dist/services/task/state-machine/index.js.map +1 -0
  120. package/dist/services/task/state-machine/types.js +54 -0
  121. package/dist/services/task/state-machine/types.js.map +1 -0
  122. package/dist/services/task/state-machine/uiControlsComputer.js +703 -0
  123. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  124. package/dist/services/task/taskDataNormalizer.js +99 -0
  125. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  126. package/dist/services/task/types.js +259 -5
  127. package/dist/services/task/types.js.map +1 -1
  128. package/dist/services/task/voice/Voice.js +1504 -0
  129. package/dist/services/task/voice/Voice.js.map +1 -0
  130. package/dist/services/task/voice/WebRTC.js +152 -0
  131. package/dist/services/task/voice/WebRTC.js.map +1 -0
  132. package/dist/services/task/voice/wxAppOfferObservability.js +145 -0
  133. package/dist/services/task/voice/wxAppOfferObservability.js.map +1 -0
  134. package/dist/services/task/voice/wxAppVoiceMethods.js +395 -0
  135. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  136. package/dist/services/wxAppDiagnosticLogging.js +159 -0
  137. package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
  138. package/dist/services/wxAppTelephonyUtils.js +19 -0
  139. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  140. package/dist/types/cc.d.ts +196 -9
  141. package/dist/types/config.d.ts +13 -0
  142. package/dist/types/constants.d.ts +38 -1
  143. package/dist/types/index.d.ts +54 -20
  144. package/dist/types/metrics/MetricsManager.d.ts +6 -0
  145. package/dist/types/metrics/constants.d.ts +52 -1
  146. package/dist/types/services/AddressBook.d.ts +2 -1
  147. package/dist/types/services/AnswerCallOnWebexService.d.ts +38 -0
  148. package/dist/types/services/ApiAiAssistant.d.ts +58 -5
  149. package/dist/types/services/EntryPoint.d.ts +7 -5
  150. package/dist/types/services/Queue.d.ts +5 -3
  151. package/dist/types/services/UserPreference.d.ts +118 -0
  152. package/dist/types/services/WebexCrossClientService.d.ts +32 -0
  153. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  154. package/dist/types/services/config/Util.d.ts +4 -8
  155. package/dist/types/services/config/constants.d.ts +49 -76
  156. package/dist/types/services/config/index.d.ts +16 -64
  157. package/dist/types/services/config/types.d.ts +248 -114
  158. package/dist/types/services/core/Err.d.ts +6 -0
  159. package/dist/types/services/core/Utils.d.ts +35 -13
  160. package/dist/types/services/core/WebexRequest.d.ts +1 -0
  161. package/dist/types/services/core/types.d.ts +2 -0
  162. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  163. package/dist/types/services/core/websocket/types.d.ts +1 -1
  164. package/dist/types/services/index.d.ts +1 -1
  165. package/dist/types/services/task/Task.d.ts +186 -0
  166. package/dist/types/services/task/TaskFactory.d.ts +14 -0
  167. package/dist/types/services/task/TaskUtils.d.ts +54 -2
  168. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  169. package/dist/types/services/task/constants.d.ts +19 -0
  170. package/dist/types/services/task/contact.d.ts +4 -0
  171. package/dist/types/services/task/dialer.d.ts +30 -0
  172. package/dist/types/services/task/digital/Digital.d.ts +23 -0
  173. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1442 -0
  174. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  175. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  176. package/dist/types/services/task/state-machine/guards.d.ts +103 -0
  177. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  178. package/dist/types/services/task/state-machine/types.d.ts +277 -0
  179. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  180. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  181. package/dist/types/services/task/types.d.ts +790 -66
  182. package/dist/types/services/task/voice/Voice.d.ts +241 -0
  183. package/dist/types/services/task/voice/WebRTC.d.ts +54 -0
  184. package/dist/types/services/task/voice/wxAppOfferObservability.d.ts +39 -0
  185. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +68 -0
  186. package/dist/types/services/wxAppDiagnosticLogging.d.ts +63 -0
  187. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  188. package/dist/types/types.d.ts +213 -11
  189. package/dist/types/utils/PageCache.d.ts +20 -3
  190. package/dist/types/webex.d.ts +1 -0
  191. package/dist/types.js +168 -6
  192. package/dist/types.js.map +1 -1
  193. package/dist/utils/PageCache.js +19 -5
  194. package/dist/utils/PageCache.js.map +1 -1
  195. package/dist/webex.js +22 -2
  196. package/dist/webex.js.map +1 -1
  197. package/package.json +16 -12
  198. package/src/cc.ts +1338 -121
  199. package/src/config.ts +13 -0
  200. package/src/constants.ts +41 -1
  201. package/src/index.ts +62 -15
  202. package/src/metrics/MetricsManager.ts +16 -0
  203. package/src/metrics/ai-docs/AGENTS.md +350 -0
  204. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  205. package/src/metrics/ai-docs/metrics-spec.md +971 -0
  206. package/src/metrics/behavioral-events.ts +218 -0
  207. package/src/metrics/constants.ts +63 -4
  208. package/src/services/AddressBook.ts +17 -6
  209. package/src/services/AnswerCallOnWebexService.ts +239 -0
  210. package/src/services/ApiAiAssistant.ts +470 -15
  211. package/src/services/EntryPoint.ts +59 -60
  212. package/src/services/Queue.ts +29 -12
  213. package/src/services/UserPreference.ts +509 -0
  214. package/src/services/WebexCrossClientService.ts +298 -0
  215. package/src/services/WxAppTelephonyMercurySync.ts +142 -0
  216. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  217. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  218. package/src/services/agent/ai-docs/agent-spec.md +505 -0
  219. package/src/services/ai-docs/AGENTS.md +386 -0
  220. package/src/services/ai-docs/services-spec.md +505 -0
  221. package/src/services/config/Util.ts +54 -84
  222. package/src/services/config/ai-docs/AGENTS.md +250 -0
  223. package/src/services/config/ai-docs/ARCHITECTURE.md +413 -0
  224. package/src/services/config/ai-docs/config-spec.md +669 -0
  225. package/src/services/config/constants.ts +63 -85
  226. package/src/services/config/index.ts +116 -277
  227. package/src/services/config/types.ts +233 -125
  228. package/src/services/core/Err.ts +3 -0
  229. package/src/services/core/Utils.ts +127 -37
  230. package/src/services/core/WebexRequest.ts +3 -1
  231. package/src/services/core/ai-docs/AGENTS.md +381 -0
  232. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  233. package/src/services/core/ai-docs/core-spec.md +787 -0
  234. package/src/services/core/aqm-reqs.ts +30 -15
  235. package/src/services/core/types.ts +33 -1
  236. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  237. package/src/services/core/websocket/types.ts +1 -1
  238. package/src/services/index.ts +1 -1
  239. package/src/services/task/Task.ts +1211 -0
  240. package/src/services/task/TaskFactory.ts +82 -0
  241. package/src/services/task/TaskManager.ts +1584 -643
  242. package/src/services/task/TaskUtils.ts +316 -25
  243. package/src/services/task/WebexCallingUtils.ts +136 -0
  244. package/src/services/task/ai-docs/AGENTS.md +457 -0
  245. package/src/services/task/ai-docs/ARCHITECTURE.md +595 -0
  246. package/src/services/task/ai-docs/task-spec.md +1501 -0
  247. package/src/services/task/constants.ts +20 -0
  248. package/src/services/task/contact.ts +30 -0
  249. package/src/services/task/dialer.ts +80 -0
  250. package/src/services/task/digital/Digital.ts +102 -0
  251. package/src/services/task/state-machine/TaskStateMachine.ts +1320 -0
  252. package/src/services/task/state-machine/actions.ts +741 -0
  253. package/src/services/task/state-machine/ai-docs/AGENTS.md +460 -0
  254. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1156 -0
  255. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2247 -0
  256. package/src/services/task/state-machine/constants.ts +172 -0
  257. package/src/services/task/state-machine/guards.ts +442 -0
  258. package/src/services/task/state-machine/index.ts +28 -0
  259. package/src/services/task/state-machine/types.ts +258 -0
  260. package/src/services/task/state-machine/uiControlsComputer.ts +1135 -0
  261. package/src/services/task/taskDataNormalizer.ts +137 -0
  262. package/src/services/task/types.ts +974 -70
  263. package/src/services/task/voice/Voice.ts +1878 -0
  264. package/src/services/task/voice/WebRTC.ts +194 -0
  265. package/src/services/task/voice/wxAppOfferObservability.ts +204 -0
  266. package/src/services/task/voice/wxAppVoiceMethods.ts +575 -0
  267. package/src/services/wxAppDiagnosticLogging.ts +286 -0
  268. package/src/services/wxAppTelephonyUtils.ts +14 -0
  269. package/src/types.ts +272 -13
  270. package/src/utils/AGENTS.md +289 -0
  271. package/src/utils/PageCache.ts +38 -5
  272. package/src/utils/ai-docs/utils-spec.md +391 -0
  273. package/src/webex.js +10 -0
  274. package/test/unit/fixtures/aiSummaryTestUtils.ts +40 -0
  275. package/test/unit/spec/cc.ts +3705 -150
  276. package/test/unit/spec/index.ts +885 -0
  277. package/test/unit/spec/logger-proxy.ts +70 -0
  278. package/test/unit/spec/metrics/MetricsManager.ts +25 -0
  279. package/test/unit/spec/metrics/behavioral-events.ts +60 -0
  280. package/test/unit/spec/services/AddressBook.ts +37 -6
  281. package/test/unit/spec/services/AnswerCallOnWebexService.ts +264 -0
  282. package/test/unit/spec/services/ApiAiAssistant.ts +429 -23
  283. package/test/unit/spec/services/EntryPoint.ts +87 -40
  284. package/test/unit/spec/services/Queue.ts +123 -12
  285. package/test/unit/spec/services/UserPreference.ts +401 -0
  286. package/test/unit/spec/services/WebCallingService.ts +7 -1
  287. package/test/unit/spec/services/WebexCrossClientService.ts +509 -0
  288. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +136 -0
  289. package/test/unit/spec/services/config/Util.ts +501 -0
  290. package/test/unit/spec/services/config/index.ts +317 -547
  291. package/test/unit/spec/services/core/Utils.ts +425 -8
  292. package/test/unit/spec/services/core/WebexRequest.ts +8 -7
  293. package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
  294. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  295. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  296. package/test/unit/spec/services/task/Task.ts +2723 -0
  297. package/test/unit/spec/services/task/TaskFactory.ts +116 -0
  298. package/test/unit/spec/services/task/TaskManager.ts +4781 -1585
  299. package/test/unit/spec/services/task/TaskUtils.ts +307 -0
  300. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  301. package/test/unit/spec/services/task/contact.ts +33 -0
  302. package/test/unit/spec/services/task/dialer.ts +190 -0
  303. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  304. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +4060 -0
  305. package/test/unit/spec/services/task/state-machine/guards.ts +839 -0
  306. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  307. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +3101 -0
  308. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  309. package/test/unit/spec/services/task/voice/Voice.ts +2355 -0
  310. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  311. package/test/unit/spec/services/task/voice/wxAppOfferObservability.ts +361 -0
  312. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +719 -0
  313. package/test/unit/spec/services/wxAppDiagnosticLogging.ts +179 -0
  314. package/test/unit/spec/wellnessSampleUtils.ts +288 -0
  315. package/typedoc.md +1 -1
  316. package/umd/contact-center.min.js +2 -2
  317. package/umd/contact-center.min.js.map +1 -1
  318. package/dist/services/task/index.js +0 -1530
  319. package/dist/services/task/index.js.map +0 -1
  320. package/dist/types/services/task/index.d.ts +0 -650
  321. package/src/services/task/index.ts +0 -1806
  322. package/test/unit/spec/services/task/index.ts +0 -2205
package/src/cc.ts CHANGED
@@ -25,6 +25,10 @@ import {
25
25
  UploadLogsResponse,
26
26
  UpdateDeviceTypeResponse,
27
27
  GenericError,
28
+ ConfigFlags,
29
+ WellnessBreakEvent,
30
+ WellnessBreakNotificationAction,
31
+ WELLNESS_BREAK_NOTIFICATION_ACTIONS,
28
32
  } from './types';
29
33
  import {
30
34
  READY,
@@ -37,6 +41,7 @@ import {
37
41
  UNKNOWN_ERROR,
38
42
  MERCURY_DISCONNECTED_SUCCESS,
39
43
  METHODS,
44
+ WELLNESS_BREAK_HANDLER,
40
45
  } from './constants';
41
46
  import {AGENT_STATE_AVAILABLE, AGENT_STATE_AVAILABLE_ID} from './services/config/constants';
42
47
  import {AGENT, RTD_SUBSCRIBE_API, SUBSCRIBE_API, WEB_RTC_PREFIX} from './services/constants';
@@ -44,17 +49,22 @@ import Services from './services';
44
49
  import WebexRequest from './services/core/WebexRequest';
45
50
  import LoggerProxy from './logger-proxy';
46
51
  import {StateChange, Logout, StateChangeSuccess, AGENT_EVENTS} from './services/agent/types';
47
- import {getErrorDetails, isValidDialNumber} from './services/core/Utils';
52
+ import {getErrorDetails, isRecord, isValidDialNumber} from './services/core/Utils';
48
53
  import {
49
54
  Profile,
50
55
  WelcomeEvent,
51
56
  CC_EVENTS,
52
57
  OutdialAniEntriesResponse,
53
58
  OutdialAniParams,
59
+ Entity,
54
60
  } from './services/config/types';
55
61
  import {ConnectionLostDetails} from './services/core/websocket/types';
56
62
  import TaskManager from './services/task/TaskManager';
57
63
  import WebCallingService from './services/WebCallingService';
64
+ import AnswerCallOnWebexService from './services/AnswerCallOnWebexService';
65
+ import WebexCrossClientService from './services/WebexCrossClientService';
66
+ import WxAppTelephonyMercurySync from './services/WxAppTelephonyMercurySync';
67
+ import {logWxAppSessionReadiness} from './services/wxAppDiagnosticLogging';
58
68
  import {
59
69
  ITask,
60
70
  TASK_EVENTS,
@@ -69,6 +79,7 @@ import {EntryPoint} from './services/EntryPoint';
69
79
  import {AddressBook} from './services/AddressBook';
70
80
  import {Queue} from './services/Queue';
71
81
  import {ApiAIAssistant} from './services/ApiAiAssistant';
82
+ import {UserPreference} from './services/UserPreference';
72
83
  import type {
73
84
  EntryPointListResponse,
74
85
  EntryPointSearchParams,
@@ -159,7 +170,7 @@ import type {
159
170
  *
160
171
  * // Login with browser-based calling
161
172
  * await cc.stationLogin({
162
- * teamId: profile.teams[0].teamId,
173
+ * teamId: profile.teams[0].id,
163
174
  * loginOption: 'BROWSER'
164
175
  * });
165
176
  *
@@ -230,6 +241,34 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
230
241
  */
231
242
  private webCallingService: WebCallingService;
232
243
 
244
+ /**
245
+ * REST telephony for wxApp thick-client answer (WXCC-6026)
246
+ * @private
247
+ */
248
+ private answerCallOnWebexService: AnswerCallOnWebexService;
249
+
250
+ /**
251
+ * usersub cross-client state for Webex App toast suppression
252
+ * @private
253
+ */
254
+ private webexCrossClientService: WebexCrossClientService;
255
+
256
+ /**
257
+ * Mercury telephony mute sync for wxApp thick-client answer
258
+ * @private
259
+ */
260
+ private wxAppTelephonyMercurySync: WxAppTelephonyMercurySync;
261
+
262
+ /** True when wxApp mute sync opened Mercury (Extension/DN-only profiles). */
263
+ private wxAppMercuryConnectedByCc = false;
264
+
265
+ /** True when wxApp mute sync registered the Webex device. */
266
+ private wxAppDeviceRegisteredByCc = false;
267
+
268
+ private static readonly WXAPP_FALSE_PUBLISH_RETRY_DELAY_MS = 30_000;
269
+ private static readonly WXAPP_FALSE_PUBLISH_MAX_RETRIES = 3;
270
+ private wxAppFalsePublishRetryTimer?: ReturnType<typeof setTimeout>;
271
+
233
272
  /**
234
273
  * Core service managers for Contact Center operations
235
274
  * Includes agent, connection, and configuration services
@@ -262,6 +301,19 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
262
301
  */
263
302
  private metricsManager: MetricsManager;
264
303
 
304
+ /** Current login/relogin session used for agent-scoped wellness validation. */
305
+ private currentAgentSessionId?: string;
306
+
307
+ /** Registered-session cache for the system WellbeingBreak idle code. */
308
+ private wellbeingBreakIdleCode?: Entity;
309
+
310
+ /** Registration generation that owns the wellness idle-code cache and in-flight lookups. */
311
+ private wellnessRegistrationGeneration = 0;
312
+
313
+ private updateWellnessSession(agentSessionId?: string): void {
314
+ this.currentAgentSessionId = agentSessionId;
315
+ }
316
+
265
317
  /**
266
318
  * API instance for managing Webex Contact Center entry points
267
319
  * Provides functionality to fetch entry points with caching support
@@ -325,7 +377,38 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
325
377
  * });
326
378
  * ```
327
379
  */
328
- private queue: Queue;
380
+ public queue: Queue;
381
+
382
+ /**
383
+ * API instance for managing Webex Contact Center user preferences
384
+ * Provides functionality to get, create, update, and delete user preferences
385
+ * @type {UserPreference}
386
+ * @public
387
+ * @example
388
+ * ```typescript
389
+ * const cc = webex.cc;
390
+ * await cc.register();
391
+ * await cc.stationLogin({ teamId: 'team123', loginOption: 'BROWSER' });
392
+ *
393
+ * // Get user preferences for current user
394
+ * const preferences = await cc.userPreference.getUserPreference();
395
+ *
396
+ * // Create new user preferences
397
+ * const newPrefs = await cc.userPreference.createUserPreference({
398
+ * userId: 'user123',
399
+ * preferences: { e911Reminder: true }
400
+ * });
401
+ *
402
+ * // Update user preferences
403
+ * const updatedPrefs = await cc.userPreference.updateUserPreference('user123', {
404
+ * preferences: { e911Reminder: false }
405
+ * });
406
+ *
407
+ * // Delete user preferences
408
+ * await cc.userPreference.deleteUserPreference('user123');
409
+ * ```
410
+ */
411
+ public userPreference: UserPreference;
329
412
 
330
413
  /**
331
414
  * API instance for AI Assistant operations such as transcript controls.
@@ -357,6 +440,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
357
440
  this.$webex.once(READY, () => {
358
441
  // @ts-ignore
359
442
  this.$config = this.config;
443
+ this.validatePluginConfig();
360
444
 
361
445
  /**
362
446
  * This is used for handling the async requests by sending webex.request and wait for corresponding websocket event.
@@ -369,17 +453,25 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
369
453
  webex: this.$webex,
370
454
  connectionConfig: this.getConnectionConfig(),
371
455
  });
372
- this.services.webSocketManager.on('message', this.handleWebsocketMessage);
373
456
 
374
457
  this.webCallingService = new WebCallingService(this.$webex);
375
- this.apiAIAssistant = new ApiAIAssistant(this.$webex);
458
+ this.answerCallOnWebexService = new AnswerCallOnWebexService(this.$webex);
459
+ this.webexCrossClientService = new WebexCrossClientService(this.$webex);
460
+ this.wxAppTelephonyMercurySync = new WxAppTelephonyMercurySync(this.$webex);
461
+ this.apiAIAssistant = new ApiAIAssistant(this.$webex, () => ({
462
+ isWellnessBreakEnabled: this.agentConfig?.isWellnessBreakEnabled === true,
463
+ agentId: this.agentConfig?.agentId,
464
+ agentSessionId: this.currentAgentSessionId,
465
+ }));
376
466
  this.metricsManager = MetricsManager.getInstance({webex: this.$webex});
377
467
  this.taskManager = TaskManager.getTaskManager(
378
468
  this.apiAIAssistant,
379
469
  this.services.contact,
380
470
  this.webCallingService,
381
- this.services.webSocketManager
471
+ this.services.webSocketManager,
472
+ this.services.rtdWebSocketManager
382
473
  );
474
+ this.taskManager.setAnswerCallOnWebexService(this.answerCallOnWebexService);
383
475
  this.incomingTaskListener();
384
476
 
385
477
  // Initialize API instances
@@ -387,6 +479,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
387
479
  this.entryPoint = new EntryPoint(this.$webex);
388
480
  this.addressBook = new AddressBook(this.$webex, () => this.agentConfig?.addressBookId);
389
481
  this.queue = new Queue(this.$webex);
482
+ this.userPreference = new UserPreference(this.$webex, () => this.agentConfig?.agentId);
390
483
  LoggerProxy.initialize(this.$webex.logger);
391
484
  });
392
485
  }
@@ -411,6 +504,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
411
504
  this.trigger(TASK_EVENTS.TASK_HYDRATE, task);
412
505
  };
413
506
 
507
+ /**
508
+ * Handles multi-login hydrate events for SDK instances without Mobius registration
509
+ * @private
510
+ * @param {ITask} task The task object associated with the multi-login hydrate
511
+ */
512
+ private handleTaskMultiLoginHydrate = (task: ITask) => {
513
+ // @ts-ignore
514
+ this.trigger(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
515
+ };
516
+
414
517
  /**
415
518
  * Handles task merged events when tasks are combined eg: EPDN merge/transfer
416
519
  * @private
@@ -431,8 +534,127 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
431
534
  this.trigger(TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
432
535
  };
433
536
 
434
- private handleRTDWebsocketMessage = (payload: string) => {
435
- this.taskManager.handleRealtimeWebsocketEvent(payload);
537
+ private trackInvalidWellnessEvent(reason: string): void {
538
+ this.metricsManager.trackEvent(
539
+ METRIC_EVENT_NAMES.AI_ASSISTANT_WELLNESS_EVENT_INVALID,
540
+ {reason},
541
+ ['operational']
542
+ );
543
+ }
544
+
545
+ private normalizeWellnessBreakEvent(payload: Record<string, unknown>): WellnessBreakEvent | null {
546
+ if (this.agentConfig?.isWellnessBreakEnabled !== true) {
547
+ this.trackInvalidWellnessEvent('wellness_disabled');
548
+
549
+ return null;
550
+ }
551
+
552
+ const notificationData = isRecord(payload.data) ? payload.data : null;
553
+ const notificationDetails =
554
+ notificationData && isRecord(notificationData.notifDetails)
555
+ ? notificationData.notifDetails
556
+ : null;
557
+ const sessionData =
558
+ notificationData && isRecord(notificationData.data) ? notificationData.data : null;
559
+ const actionEvent = notificationDetails?.actionEvent;
560
+
561
+ if (
562
+ payload.type !== WELLNESS_BREAK_HANDLER ||
563
+ notificationData?.notifType !== WELLNESS_BREAK_HANDLER ||
564
+ typeof actionEvent !== 'string'
565
+ ) {
566
+ this.trackInvalidWellnessEvent('malformed_wellness_event');
567
+
568
+ return null;
569
+ }
570
+
571
+ if (
572
+ !Object.values(WELLNESS_BREAK_NOTIFICATION_ACTIONS).includes(
573
+ actionEvent as WellnessBreakNotificationAction
574
+ )
575
+ ) {
576
+ this.trackInvalidWellnessEvent('unknown_wellness_action');
577
+
578
+ return null;
579
+ }
580
+
581
+ const agentId = notificationData.agentId;
582
+ const eventOrgId = notificationData.orgId;
583
+ const sessionOrgId = sessionData?.orgId;
584
+ const envelopeOrgId = payload.orgId;
585
+ const agentSessionId = sessionData?.agentSessionId;
586
+ const currentOrgId = this.$webex.credentials.getOrgId();
587
+
588
+ if (
589
+ typeof agentId !== 'string' ||
590
+ typeof eventOrgId !== 'string' ||
591
+ typeof sessionOrgId !== 'string' ||
592
+ typeof envelopeOrgId !== 'string' ||
593
+ typeof agentSessionId !== 'string' ||
594
+ !agentId ||
595
+ !eventOrgId ||
596
+ !sessionOrgId ||
597
+ !envelopeOrgId ||
598
+ !agentSessionId
599
+ ) {
600
+ this.trackInvalidWellnessEvent('missing_wellness_identity');
601
+
602
+ return null;
603
+ }
604
+
605
+ if (
606
+ agentId !== this.agentConfig?.agentId ||
607
+ eventOrgId !== currentOrgId ||
608
+ sessionOrgId !== currentOrgId ||
609
+ envelopeOrgId !== currentOrgId
610
+ ) {
611
+ this.trackInvalidWellnessEvent('mismatched_wellness_identity');
612
+
613
+ return null;
614
+ }
615
+
616
+ const interactionValue = sessionData.interactionId ?? sessionData.InteractionId;
617
+ const actionText = notificationDetails.actionText;
618
+ const trackingId = payload.trackingId;
619
+
620
+ return {
621
+ agentId,
622
+ orgId: currentOrgId,
623
+ agentSessionId,
624
+ actionEvent: actionEvent as WellnessBreakNotificationAction,
625
+ ...(typeof actionText === 'string' ? {actionText} : {}),
626
+ ...(typeof interactionValue === 'string' ? {interactionId: interactionValue} : {}),
627
+ ...(typeof trackingId === 'string' ? {trackingId} : {}),
628
+ };
629
+ }
630
+
631
+ private handleWellnessBreakWebsocketPayload(payload: Record<string, unknown>): boolean {
632
+ if (payload.type !== WELLNESS_BREAK_HANDLER) {
633
+ return false;
634
+ }
635
+
636
+ const wellnessEvent = this.normalizeWellnessBreakEvent(payload);
637
+ if (wellnessEvent) {
638
+ // @ts-ignore - WebexPlugin emit is available at runtime but absent from its declaration.
639
+ this.emit(CC_EVENTS.WELLNESS_BREAK, wellnessEvent);
640
+ }
641
+
642
+ return true;
643
+ }
644
+
645
+ private handleRTDWebsocketMessage = (event: string) => {
646
+ let payload: unknown;
647
+ try {
648
+ payload = JSON.parse(event);
649
+ } catch {
650
+ // TaskManager retains its existing malformed RTD message handling.
651
+ }
652
+
653
+ if (isRecord(payload) && this.handleWellnessBreakWebsocketPayload(payload)) {
654
+ return;
655
+ }
656
+
657
+ this.taskManager.handleRealtimeWebsocketEvent(event);
436
658
  };
437
659
 
438
660
  /**
@@ -443,6 +665,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
443
665
  private incomingTaskListener() {
444
666
  this.taskManager.on(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
445
667
  this.taskManager.on(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
668
+ this.taskManager.on(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
446
669
  this.taskManager.on(TASK_EVENTS.TASK_MERGED, this.handleTaskMerged);
447
670
  this.taskManager.on(
448
671
  TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
@@ -478,16 +701,18 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
478
701
  *
479
702
  * console.log('Agent ID:', profile.agentId);
480
703
  * console.log('Default DN:', profile.defaultDn);
481
- * console.log('Teams:', profile.teams.map(t => t.teamId));
704
+ * console.log('Teams:', profile.teams.map(t => t.id));
482
705
  * console.log('WebRTC Enabled:', profile.webRtcEnabled);
483
706
  * console.log('Supported Login Options:', profile.loginVoiceOptions);
484
707
  *
485
708
  * // Now you can proceed with station login, state changes, etc.
486
- * await cc.stationLogin({ teamId: profile.teams[0].teamId, loginOption: 'BROWSER' });
709
+ * await cc.stationLogin({ teamId: profile.teams[0].id, loginOption: 'BROWSER' });
487
710
  * ```
488
711
  */
489
712
  public async register(): Promise<Profile> {
490
- LoggerProxy.info('Starting CC SDK registration', {
713
+ this.wellnessRegistrationGeneration += 1;
714
+ this.wellbeingBreakIdleCode = undefined;
715
+ LoggerProxy.log('Starting CC SDK registration', {
491
716
  module: CC_FILE,
492
717
  method: METHODS.REGISTER,
493
718
  });
@@ -497,6 +722,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
497
722
  METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
498
723
  ]);
499
724
  this.setupEventListeners();
725
+ this.services.webSocketManager.on('message', this.handleWebsocketMessage);
500
726
 
501
727
  const resp = await this.connectWebsocket();
502
728
  // Ensure 'dn' is always populated from 'defaultDn'
@@ -515,7 +741,12 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
515
741
  method: METHODS.REGISTER,
516
742
  });
517
743
 
518
- return resp;
744
+ // Registration can silently restore a station session without emitting a
745
+ // relogin event. Project the active session for consumers that must
746
+ // recover session-scoped state after a page refresh.
747
+ return resp.isAgentLoggedIn && this.currentAgentSessionId
748
+ ? {...resp, agentSessionId: this.currentAgentSessionId}
749
+ : resp;
519
750
  } catch (error) {
520
751
  this.metricsManager.trackEvent(
521
752
  METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED,
@@ -568,14 +799,19 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
568
799
 
569
800
  */
570
801
  public async deregister(): Promise<void> {
802
+ this.wellnessRegistrationGeneration += 1;
803
+ this.wellbeingBreakIdleCode = undefined;
804
+ this.updateWellnessSession();
571
805
  try {
572
806
  this.metricsManager.timeEvent([
573
807
  METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_SUCCESS,
574
808
  METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_FAIL,
575
809
  ]);
576
810
 
811
+ this.taskManager.clearAISummaryState();
577
812
  this.taskManager.off(TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
578
813
  this.taskManager.off(TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
814
+ this.taskManager.off(TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, this.handleTaskMultiLoginHydrate);
579
815
  this.taskManager.off(
580
816
  TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION,
581
817
  this.handleCampaignPreviewReservation
@@ -586,6 +822,10 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
586
822
  this.services.rtdWebSocketManager.off('message', this.handleRTDWebsocketMessage);
587
823
  this.services.connectionService.off('connectionLost', this.handleConnectionLost);
588
824
 
825
+ const {publishError: wxAppPublishError} = await this.teardownWxAppLocalState({
826
+ clearInitFlag: true,
827
+ });
828
+
589
829
  if (
590
830
  this.agentConfig.webRtcEnabled &&
591
831
  this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER)
@@ -606,13 +846,22 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
606
846
  if (!this.services.webSocketManager.isSocketClosed) {
607
847
  this.services.webSocketManager.close(false, 'Unregistering the SDK');
608
848
  }
849
+ if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
850
+ this.services.rtdWebSocketManager.close(false, 'Unregistering the SDK');
851
+ }
609
852
 
610
853
  if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
611
854
  this.services.rtdWebSocketManager.close(false, 'Unregistering the RTD websocket');
612
855
  }
613
856
 
614
857
  // Clear any cached agent configuration
858
+ this.wellbeingBreakIdleCode = undefined;
615
859
  this.agentConfig = null;
860
+ this.updateWellnessSession();
861
+
862
+ if (wxAppPublishError) {
863
+ throw wxAppPublishError;
864
+ }
616
865
 
617
866
  LoggerProxy.log('Deregistered successfully', {
618
867
  module: CC_FILE,
@@ -676,6 +925,9 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
676
925
  * ```
677
926
  */
678
927
  public async getBuddyAgents(data: BuddyAgents): Promise<BuddyAgentsResponse> {
928
+ const {action, state, mediaType = 'telephony'} = data;
929
+ const buddyAgentState = state ?? (action === 'Transfer' ? AGENT_STATE_AVAILABLE : undefined);
930
+
679
931
  LoggerProxy.info('Fetching buddy agents', {
680
932
  module: CC_FILE,
681
933
  method: METHODS.GET_BUDDY_AGENTS,
@@ -686,15 +938,19 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
686
938
  METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED,
687
939
  ]);
688
940
  const resp = await this.services.agent.buddyAgents({
689
- data: {agentProfileId: this.agentConfig.agentProfileID, ...data},
941
+ data: {
942
+ agentProfileId: this.agentConfig.agentProfileID,
943
+ mediaType,
944
+ ...(buddyAgentState ? {state: buddyAgentState} : {}),
945
+ },
690
946
  });
691
947
 
692
948
  this.metricsManager.trackEvent(
693
949
  METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_SUCCESS,
694
950
  {
695
951
  ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp),
696
- mediaType: data.mediaType,
697
- buddyAgentState: data.state,
952
+ mediaType,
953
+ buddyAgentState,
698
954
  buddyAgentCount: resp.data.agentList.length,
699
955
  },
700
956
  ['operational']
@@ -714,8 +970,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
714
970
  METRIC_EVENT_NAMES.FETCH_BUDDY_AGENTS_FAILED,
715
971
  {
716
972
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failureResp),
717
- mediaType: data.mediaType,
718
- buddyAgentState: data.state,
973
+ mediaType,
974
+ buddyAgentState,
719
975
  },
720
976
  ['operational']
721
977
  );
@@ -731,7 +987,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
731
987
  * @private
732
988
  */
733
989
  private async connectWebsocket() {
734
- LoggerProxy.info('Connecting to websocket', {
990
+ LoggerProxy.log('Connecting to websocket', {
735
991
  module: CC_FILE,
736
992
  method: METHODS.CONNECT_WEBSOCKET,
737
993
  });
@@ -751,19 +1007,49 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
751
1007
  method: METHODS.CONNECT_WEBSOCKET,
752
1008
  });
753
1009
 
1010
+ const configFlags: ConfigFlags = {
1011
+ isEndTaskEnabled: this.agentConfig.isEndTaskEnabled,
1012
+ isEndConsultEnabled: this.agentConfig.isEndConsultEnabled,
1013
+ webRtcEnabled: this.agentConfig.webRtcEnabled,
1014
+ autoWrapup: this.agentConfig.wrapUpData?.wrapUpProps?.autoWrapup ?? false,
1015
+ aiFeature: this.agentConfig.aiFeature,
1016
+ consultTransfer: {
1017
+ allowConsultToQueue: this.agentConfig.allowConsultToQueue,
1018
+ accessQueue: this.agentConfig.accessQueue,
1019
+ accessEntryPoint: this.agentConfig.accessEntryPoint,
1020
+ accessBuddyTeam: this.agentConfig.accessBuddyTeam,
1021
+ },
1022
+ enableWxBetterTogether: this.isWxBetterTogetherEnabled(),
1023
+ getWxAppUsersubPublished: () => this.webexCrossClientService.isAnswerCallsStateActive(),
1024
+ };
1025
+ this.taskManager.setConfigFlags(configFlags);
754
1026
  // TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
755
1027
  this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
756
1028
  this.taskManager.setAgentId(this.agentConfig.agentId);
1029
+ this.taskManager.setAgentName(this.agentConfig.agentName);
757
1030
  this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
758
1031
  this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
1032
+ this.wellbeingBreakIdleCode = undefined;
1033
+ this.updateWellnessSession();
759
1034
 
760
- if (this.agentConfig.aiFeature?.realtimeTranscripts?.enable) {
1035
+ /**
1036
+ * RTD websocket currently supports realtime transcripts and suggested responses.
1037
+ * Extend this condition when additional AI RTD features are introduced.
1038
+ * Wellness break notifications also arrive on this websocket.
1039
+ */
1040
+ if (
1041
+ this.agentConfig.aiFeature?.realtimeTranscripts?.enable ||
1042
+ this.agentConfig.aiFeature?.suggestedResponses?.enable ||
1043
+ this.agentConfig.aiFeature?.generatedSummaries?.wrapUpSummariesEnabled === true ||
1044
+ this.agentConfig.aiFeature?.generatedSummaries?.consultTransferSummariesEnabled === true ||
1045
+ this.agentConfig.isWellnessBreakEnabled
1046
+ ) {
761
1047
  LoggerProxy.info('Connecting to RTD websocket', {
762
1048
  module: CC_FILE,
763
1049
  method: METHODS.CONNECT_WEBSOCKET,
764
1050
  });
765
1051
 
766
- await this.services.rtdWebSocketManager
1052
+ this.services.rtdWebSocketManager
767
1053
  .initWebSocket({
768
1054
  body: this.getConnectionConfig(),
769
1055
  resource: RTD_SUBSCRIBE_API,
@@ -776,7 +1062,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
776
1062
  this.services.rtdWebSocketManager.on('message', this.handleRTDWebsocketMessage);
777
1063
  })
778
1064
  .catch((error) => {
779
- LoggerProxy.error(`Error during RTD websocket setup: ${error}`, {
1065
+ LoggerProxy.error(`Error connecting to RTD websocket ${error}`, {
780
1066
  module: CC_FILE,
781
1067
  method: METHODS.CONNECT_WEBSOCKET,
782
1068
  });
@@ -785,7 +1071,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
785
1071
 
786
1072
  if (
787
1073
  this.agentConfig.webRtcEnabled &&
788
- this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER)
1074
+ this.agentConfig.loginVoiceOptions.includes(LoginOption.BROWSER) &&
1075
+ !this.isWebRTCRegistrationDisabled()
789
1076
  ) {
790
1077
  try {
791
1078
  await this.$webex.internal.mercury.connect();
@@ -799,14 +1086,28 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
799
1086
  method: METHODS.CONNECT_WEBSOCKET,
800
1087
  });
801
1088
  }
1089
+ } else if (this.isWebRTCRegistrationDisabled()) {
1090
+ LoggerProxy.info(
1091
+ 'Skipping Mobius registration because disableWebRTCRegistration is enabled',
1092
+ {
1093
+ module: CC_FILE,
1094
+ method: METHODS.CONNECT_WEBSOCKET,
1095
+ }
1096
+ );
802
1097
  }
803
1098
 
804
1099
  if (this.$config && this.$config.allowAutomatedRelogin) {
805
1100
  await this.silentRelogin();
1101
+ } else {
1102
+ LoggerProxy.log('Skipping silent relogin: allowAutomatedRelogin is disabled', {
1103
+ module: CC_FILE,
1104
+ method: METHODS.CONNECT_WEBSOCKET,
1105
+ });
806
1106
  }
807
1107
 
808
1108
  return this.agentConfig;
809
1109
  } catch (error) {
1110
+ this.updateWellnessSession();
810
1111
  LoggerProxy.error(`Error during register: ${error}`, {
811
1112
  module: CC_FILE,
812
1113
  method: METHODS.CONNECT_WEBSOCKET,
@@ -845,36 +1146,48 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
845
1146
  * ```
846
1147
  */
847
1148
  public async stationLogin(data: AgentLogin): Promise<StationLoginResponse> {
848
- LoggerProxy.info('Starting agent station login', {
849
- module: CC_FILE,
850
- method: METHODS.STATION_LOGIN,
851
- });
1149
+ const loggerContext = {module: CC_FILE, method: METHODS.STATION_LOGIN};
1150
+
852
1151
  try {
1152
+ LoggerProxy.log(
1153
+ `Starting agent station login | loginOption: ${data?.loginOption} teamId: ${data?.teamId}`,
1154
+ loggerContext
1155
+ );
853
1156
  this.metricsManager.timeEvent([
854
1157
  METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
855
1158
  METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
856
1159
  ]);
857
1160
 
858
1161
  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;
1162
+ if (data.loginOption === LoginOption.AGENT_DN) {
1163
+ LoggerProxy.log(
1164
+ `Validating dial number | dialPlanEnabled: ${!!this.agentConfig
1165
+ ?.dialPlan} | dialPlanEntryCount: ${dialPlanEntries.length}`,
1166
+ loggerContext
1167
+ );
1168
+
1169
+ if (!isValidDialNumber(data.dialNumber, dialPlanEntries)) {
1170
+ LoggerProxy.log(
1171
+ `Dial number validation failed | dialNumber: ${data.dialNumber} | dialPlanEntryCount: ${dialPlanEntries.length}`,
1172
+ loggerContext
1173
+ );
1174
+ const error = new Error('INVALID_DIAL_NUMBER');
1175
+ // @ts-ignore - adding custom key to the error object
1176
+ error.details = {data: {reason: 'INVALID_DIAL_NUMBER'}} as Failure;
866
1177
 
867
- throw error;
1178
+ throw error;
1179
+ }
868
1180
  }
869
1181
 
1182
+ const deviceId = this.getDeviceId(data.loginOption, data.dialNumber);
870
1183
  const loginResponse = await this.services.agent.stationLogin({
871
1184
  data: {
872
- dialNumber:
873
- data.loginOption === LoginOption.BROWSER ? this.agentConfig.agentId : data.dialNumber,
1185
+ // BROWSER DN must be webrtc-{agentId} so VPOP/RTMS do not treat a bare UUID as PSTN.
1186
+ dialNumber: data.loginOption === LoginOption.BROWSER ? deviceId : data.dialNumber,
874
1187
  teamId: data.teamId,
875
1188
  deviceType: data.loginOption,
876
1189
  isExtension: data.loginOption === LoginOption.EXTENSION,
877
- deviceId: this.getDeviceId(data.loginOption, data.dialNumber),
1190
+ deviceId,
878
1191
  roles: [AGENT],
879
1192
  teamName: EMPTY_STRING,
880
1193
  siteId: EMPTY_STRING,
@@ -883,12 +1196,29 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
883
1196
  },
884
1197
  });
885
1198
 
886
- if (this.agentConfig.webRtcEnabled && data.loginOption === LoginOption.BROWSER) {
1199
+ if (
1200
+ this.agentConfig.webRtcEnabled &&
1201
+ data.loginOption === LoginOption.BROWSER &&
1202
+ !this.isWebRTCRegistrationDisabled()
1203
+ ) {
887
1204
  await this.webCallingService.registerWebCallingLine();
1205
+ } else if (
1206
+ this.agentConfig.webRtcEnabled &&
1207
+ data.loginOption === LoginOption.BROWSER &&
1208
+ this.isWebRTCRegistrationDisabled()
1209
+ ) {
1210
+ LoggerProxy.info(
1211
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
1212
+ {
1213
+ module: CC_FILE,
1214
+ method: METHODS.STATION_LOGIN,
1215
+ }
1216
+ );
888
1217
  }
889
1218
 
890
1219
  const resp = await loginResponse;
891
1220
  const {channelsMap, ...loginData} = resp.data;
1221
+ this.updateWellnessSession(resp.data.agentSessionId);
892
1222
  this.agentConfig.currentTeamId = resp.data.teamId;
893
1223
  const response = {
894
1224
  ...loginData,
@@ -902,6 +1232,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
902
1232
  };
903
1233
 
904
1234
  this.webCallingService.setLoginOption(data.loginOption);
1235
+ if (this.agentConfig) {
1236
+ this.agentConfig.deviceType = data.loginOption;
1237
+ if (
1238
+ data.loginOption === LoginOption.AGENT_DN ||
1239
+ data.loginOption === LoginOption.EXTENSION
1240
+ ) {
1241
+ this.agentConfig.defaultDn = data.dialNumber;
1242
+ this.agentConfig.dn = data.dialNumber;
1243
+ }
1244
+ }
905
1245
  this.metricsManager.trackEvent(
906
1246
  METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
907
1247
  {
@@ -923,6 +1263,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
923
1263
  }
924
1264
  );
925
1265
 
1266
+ await this.ensureWxAppPostStationLogin();
1267
+
926
1268
  return response;
927
1269
  } catch (error) {
928
1270
  const failure = error.details as Failure;
@@ -991,6 +1333,9 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
991
1333
  this.webCallingService.deregisterWebCallingLine();
992
1334
  }
993
1335
 
1336
+ await this.teardownWxAppLocalState();
1337
+ this.updateWellnessSession();
1338
+
994
1339
  LoggerProxy.log(`Agent station logout completed successfully`, {
995
1340
  module: CC_FILE,
996
1341
  method: METHODS.STATION_LOGOUT,
@@ -1091,7 +1436,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1091
1436
  {
1092
1437
  ...MetricsManager.getCommonTrackingFieldForAQMResponse(agentStatusResponse),
1093
1438
  requestedState: data.state,
1094
- teamId: this.agentConfig?.teams[0]?.teamId ?? EMPTY_STRING,
1439
+ teamId: this.agentConfig?.teams[0]?.id ?? EMPTY_STRING,
1095
1440
  status: agentStatusResponse.data?.status,
1096
1441
  subStatus: agentStatusResponse.data?.subStatus,
1097
1442
  auxCodeId: data.auxCodeId,
@@ -1127,6 +1472,77 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1127
1472
  }
1128
1473
  }
1129
1474
 
1475
+ /**
1476
+ * Returns the system-managed `WellbeingBreak` idle code for the current registration.
1477
+ * The value is fetched outside Desktop Profile filtering and cached until deregistration.
1478
+ * @returns The normalized system idle-code entity
1479
+ * @throws Structured Contact Center error when wellness is disabled or the code is unavailable
1480
+ * @example
1481
+ * const idleCode = await webex.cc.getWellbeingBreakIdleCode();
1482
+ * @public
1483
+ */
1484
+ public async getWellbeingBreakIdleCode(): Promise<Entity> {
1485
+ const method = METHODS.GET_WELLBEING_BREAK_IDLE_CODE;
1486
+ try {
1487
+ if (!this.agentConfig?.isWellnessBreakEnabled) {
1488
+ const validationError = new Error('WELLNESS_BREAK_NOT_ENABLED') as GenericError;
1489
+ validationError.details = {
1490
+ type: 'SDK_VALIDATION_ERROR',
1491
+ orgId: this.$webex.credentials.getOrgId(),
1492
+ trackingId: EMPTY_STRING,
1493
+ data: {reason: 'WELLNESS_BREAK_NOT_ENABLED'},
1494
+ };
1495
+ throw validationError;
1496
+ }
1497
+
1498
+ if (this.wellbeingBreakIdleCode) {
1499
+ return this.wellbeingBreakIdleCode;
1500
+ }
1501
+
1502
+ this.metricsManager.timeEvent([
1503
+ METRIC_EVENT_NAMES.WELLBEING_BREAK_IDLE_CODE_FETCH_SUCCESS,
1504
+ METRIC_EVENT_NAMES.WELLBEING_BREAK_IDLE_CODE_FETCH_FAILED,
1505
+ ]);
1506
+ const registrationGeneration = this.wellnessRegistrationGeneration;
1507
+ const orgId = this.$webex.credentials.getOrgId();
1508
+ const idleCode = await this.services.config.getWellbeingBreakIdleCode(orgId);
1509
+
1510
+ if (
1511
+ registrationGeneration !== this.wellnessRegistrationGeneration ||
1512
+ orgId !== this.$webex.credentials.getOrgId() ||
1513
+ !this.agentConfig?.isWellnessBreakEnabled
1514
+ ) {
1515
+ const staleRegistrationError = new Error(
1516
+ 'WELLNESS_BREAK_REGISTRATION_CHANGED'
1517
+ ) as GenericError;
1518
+ staleRegistrationError.details = {
1519
+ type: 'SDK_VALIDATION_ERROR',
1520
+ orgId,
1521
+ trackingId: EMPTY_STRING,
1522
+ data: {reason: 'WELLNESS_BREAK_REGISTRATION_CHANGED'},
1523
+ };
1524
+ throw staleRegistrationError;
1525
+ }
1526
+
1527
+ this.wellbeingBreakIdleCode = idleCode;
1528
+ this.metricsManager.trackEvent(
1529
+ METRIC_EVENT_NAMES.WELLBEING_BREAK_IDLE_CODE_FETCH_SUCCESS,
1530
+ {},
1531
+ ['operational']
1532
+ );
1533
+
1534
+ return idleCode;
1535
+ } catch (error) {
1536
+ this.metricsManager.trackEvent(
1537
+ METRIC_EVENT_NAMES.WELLBEING_BREAK_IDLE_CODE_FETCH_FAILED,
1538
+ {reason: error instanceof Error ? error.message : UNKNOWN_ERROR},
1539
+ ['operational']
1540
+ );
1541
+ const {error: detailedError} = getErrorDetails(error, method, CC_FILE);
1542
+ throw detailedError;
1543
+ }
1544
+ }
1545
+
1130
1546
  /**
1131
1547
  * Processes incoming websocket messages and emits corresponding events
1132
1548
  * Handles various event types including agent state changes, login events,
@@ -1165,8 +1581,19 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1165
1581
  ]);
1166
1582
  }
1167
1583
 
1584
+ if (this.handleWellnessBreakWebsocketPayload(eventData)) {
1585
+ return;
1586
+ }
1587
+
1168
1588
  switch (eventData.type) {
1169
1589
  case CC_EVENTS.AGENT_MULTI_LOGIN:
1590
+ if (
1591
+ eventData.data?.type === 'AgentMultiLoginCloseSession' &&
1592
+ (!eventData.data.agentSessionId ||
1593
+ eventData.data.agentSessionId === this.currentAgentSessionId)
1594
+ ) {
1595
+ this.updateWellnessSession();
1596
+ }
1170
1597
  // @ts-ignore
1171
1598
  this.emit(AGENT_EVENTS.AGENT_MULTI_LOGIN, eventData.data);
1172
1599
  break;
@@ -1185,6 +1612,7 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1185
1612
  switch (eventData.data.type) {
1186
1613
  case CC_EVENTS.AGENT_STATION_LOGIN_SUCCESS: {
1187
1614
  const {channelsMap, ...loginData} = eventData.data;
1615
+ this.updateWellnessSession(loginData.agentSessionId);
1188
1616
  const stationLoginData = {
1189
1617
  ...loginData,
1190
1618
  mmProfile: {
@@ -1200,23 +1628,23 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1200
1628
  this.emit(AGENT_EVENTS.AGENT_STATION_LOGIN_SUCCESS, stationLoginData);
1201
1629
  break;
1202
1630
  }
1203
- case CC_EVENTS.AGENT_RELOGIN_SUCCESS:
1204
- {
1205
- const {channelsMap, ...loginData} = eventData.data;
1206
- const stationReLoginData = {
1207
- ...loginData,
1208
- mmProfile: {
1209
- chat: channelsMap.chat?.length,
1210
- email: channelsMap.email?.length,
1211
- social: channelsMap.social?.length,
1212
- telephony: channelsMap.telephony?.length,
1213
- },
1214
- notifsTrackingId: eventData.trackingId,
1215
- };
1216
- // @ts-ignore
1217
- this.emit(AGENT_EVENTS.AGENT_RELOGIN_SUCCESS, stationReLoginData);
1218
- }
1631
+ case CC_EVENTS.AGENT_RELOGIN_SUCCESS: {
1632
+ const {channelsMap, ...loginData} = eventData.data;
1633
+ this.updateWellnessSession(loginData.agentSessionId);
1634
+ const stationReLoginData = {
1635
+ ...loginData,
1636
+ mmProfile: {
1637
+ chat: channelsMap.chat?.length,
1638
+ email: channelsMap.email?.length,
1639
+ social: channelsMap.social?.length,
1640
+ telephony: channelsMap.telephony?.length,
1641
+ },
1642
+ notifsTrackingId: eventData.trackingId,
1643
+ };
1644
+ // @ts-ignore
1645
+ this.emit(AGENT_EVENTS.AGENT_RELOGIN_SUCCESS, stationReLoginData);
1219
1646
  break;
1647
+ }
1220
1648
  case CC_EVENTS.AGENT_STATE_CHANGE_SUCCESS:
1221
1649
  // @ts-ignore
1222
1650
  this.emit(AGENT_EVENTS.AGENT_STATE_CHANGE_SUCCESS, eventData.data);
@@ -1230,6 +1658,12 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1230
1658
  this.emit(AGENT_EVENTS.AGENT_STATION_LOGIN_FAILED, eventData.data);
1231
1659
  break;
1232
1660
  case CC_EVENTS.AGENT_LOGOUT_SUCCESS:
1661
+ if (
1662
+ !eventData.data.agentSessionId ||
1663
+ eventData.data.agentSessionId === this.currentAgentSessionId
1664
+ ) {
1665
+ this.updateWellnessSession();
1666
+ }
1233
1667
  // @ts-ignore
1234
1668
  this.emit(AGENT_EVENTS.AGENT_LOGOUT_SUCCESS, eventData.data);
1235
1669
  break;
@@ -1247,87 +1681,700 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1247
1681
  };
1248
1682
 
1249
1683
  /**
1250
- * Initializes event listeners for the Contact Center service
1251
- * Sets up handlers for connection state changes and other core events
1684
+ * Checks whether Mobius/WebRTC registration should be skipped by config
1685
+ * @returns {boolean} True when browser WebRTC registration is disabled
1252
1686
  * @private
1253
1687
  */
1254
- private setupEventListeners() {
1255
- this.services.connectionService.on('connectionLost', this.handleConnectionLost.bind(this));
1688
+ private isWebRTCRegistrationDisabled(): boolean {
1689
+ return this.$config?.disableWebRTCRegistration === true;
1256
1690
  }
1257
1691
 
1258
1692
  /**
1259
- * Returns the connection configuration
1260
- * @returns {SubscribeRequest} Connection configuration
1693
+ * Validates contact-center plugin configuration before service initialization
1261
1694
  * @private
1262
1695
  */
1263
- private getConnectionConfig(): SubscribeRequest {
1264
- return {
1265
- force: this.$config?.force ?? true,
1266
- isKeepAliveEnabled: this.$config?.isKeepAliveEnabled ?? false,
1267
- clientType: this.$config?.clientType ?? 'WebexCCSDK',
1268
- allowMultiLogin: this.$config?.allowMultiLogin ?? true,
1269
- };
1696
+ private validatePluginConfig(): void {
1697
+ if (
1698
+ this.$config?.disableWebRTCRegistration === true &&
1699
+ this.$config?.allowMultiLogin === false
1700
+ ) {
1701
+ throw new Error(
1702
+ '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.'
1703
+ );
1704
+ }
1270
1705
  }
1271
1706
 
1272
1707
  /**
1273
- * Handles connection lost events and reconnection attempts
1274
- * @param {ConnectionLostDetails} msg Connection lost details
1708
+ * Whether wxApp thick-client answer is enabled for this SDK instance.
1709
+ * @public
1710
+ */
1711
+ public isWxBetterTogetherEnabled(): boolean {
1712
+ return this.$config?.enableWxBetterTogether === true;
1713
+ }
1714
+
1715
+ /**
1716
+ * Current station login option (Extension, Dial Number, or Browser/Desktop).
1717
+ * Prefer webCallingService — updated on every stationLogin before wxApp hooks run.
1275
1718
  * @private
1276
1719
  */
1277
- private async handleConnectionLost(msg: ConnectionLostDetails): Promise<void> {
1278
- if (msg.isConnectionLost) {
1279
- // TODO: Emit an event saying connection is lost
1280
- LoggerProxy.info('event=handleConnectionLost | Connection lost', {
1281
- module: CC_FILE,
1282
- method: METHODS.HANDLE_CONNECTION_LOST,
1283
- });
1284
- } else if (msg.isSocketReconnected) {
1285
- // TODO: Emit an event saying connection is re-estabilished
1286
- LoggerProxy.info(
1287
- 'event=handleConnectionReconnect | Connection reconnected attempting to request silent relogin',
1288
- {module: CC_FILE, method: METHODS.HANDLE_CONNECTION_LOST}
1720
+ private getCurrentStationLoginOption(): LoginOption | undefined {
1721
+ return this.webCallingService?.loginOption ?? this.agentConfig?.deviceType;
1722
+ }
1723
+
1724
+ /**
1725
+ * wxApp thick-client answer is supported for Extension and Dial Number station login only —
1726
+ * not Browser/Desktop (WebRTC) login.
1727
+ * @private
1728
+ */
1729
+ private assertWxAppStationLoginSupportedForEnable(): void {
1730
+ const loginOption = this.getCurrentStationLoginOption();
1731
+ if (loginOption !== LoginOption.EXTENSION && loginOption !== LoginOption.AGENT_DN) {
1732
+ throw new Error(
1733
+ 'setManageWebexCallingInWxcc requires EXTENSION or AGENT_DN station login. Complete station login before enabling.'
1289
1734
  );
1290
- if (this.$config && this.$config.allowAutomatedRelogin) {
1291
- await this.silentRelogin();
1292
- }
1293
1735
  }
1294
1736
  }
1295
1737
 
1296
1738
  /**
1297
- * Handles silent relogin after registration completion
1739
+ * Clears runtime wxApp config after logout/deregister so stale flags do not apply on relogin.
1298
1740
  * @private
1299
1741
  */
1300
- private async silentRelogin(): Promise<void> {
1301
- LoggerProxy.info('Starting silent relogin process', {
1302
- module: CC_FILE,
1303
- method: METHODS.SILENT_RELOGIN,
1304
- });
1742
+ private resetEnableWxBetterTogetherConfig(): void {
1743
+ if (this.$config) {
1744
+ this.$config.enableWxBetterTogether = false;
1745
+ }
1746
+ this.taskManager.applyEnableWxBetterTogether(false);
1747
+ }
1305
1748
 
1306
- try {
1307
- const reLoginResponse = await this.services.agent.reload();
1308
- const {
1309
- agentId,
1310
- lastStateChangeReason,
1311
- deviceType,
1312
- dn,
1313
- lastStateChangeTimestamp,
1314
- lastIdleCodeChangeTimestamp,
1315
- } = reLoginResponse.data;
1316
- let {auxCodeId} = reLoginResponse.data;
1317
- this.agentConfig.lastStateChangeTimestamp = lastStateChangeTimestamp;
1318
- this.agentConfig.lastIdleCodeChangeTimestamp = lastIdleCodeChangeTimestamp;
1319
- this.agentConfig.currentTeamId = reLoginResponse.data.teamId;
1320
- await this.handleDeviceType(deviceType as LoginOption, dn);
1749
+ /**
1750
+ * Runtime enable/disable for wxApp Better Together (toast suppression + Mercury mute sync).
1751
+ *
1752
+ * **Phase 1 (WXCC-6026 / MMT): NOT part of the public host contract.**
1753
+ * Hosts must set `webexConfig.cc.enableWxBetterTogether` before `webex.init()` / `cc.register()`.
1754
+ * To change the flag after the SDK is initialized, re-init the SDK with the updated config.
1755
+ *
1756
+ * **Supported public read API:** {@link isWxBetterTogetherEnabled}.
1757
+ * **Supported telephony API:** unified `task.accept()`, `task.decline()`, `task.toggleMute()`,
1758
+ * `task.transmitDtmf()` — SDK routes wxApp calls internally when the init flag is active.
1759
+ *
1760
+ * **Production lifecycle does not call this method.** Station login, silent relogin, logout,
1761
+ * and deregister use {@link ensureWxAppPostStationLogin} and {@link teardownWxAppLocalState}
1762
+ * (usersub publish + Mercury subscribe/unsubscribe based on the init flag).
1763
+ *
1764
+ * This setter remains as a **private implementation** for Phase 2 runtime toggle and unit tests.
1765
+ * It updates `$config.enableWxBetterTogether`, TaskManager flags, cross-client usersub
1766
+ * (`answer-calls-on-wxcc`), and Mercury mute sync; rolls back config on publish/connect failure.
1767
+ *
1768
+ * @param enabled - `true` to enable wxApp answer + usersub suppression; `false` to disable and tear down.
1769
+ * @throws When enabling without login, on unsupported BROWSER station login, or when usersub/Mercury init fails.
1770
+ * @internal
1771
+ * @see enableWxBetterTogether (CCPluginConfig init flag)
1772
+ * @see ensureWxAppPostStationLogin
1773
+ * @see teardownWxAppLocalState
1774
+ */
1775
+ private async setManageWebexCallingInWxcc(enabled: boolean): Promise<void> {
1776
+ if (enabled && !this.$webex.internal.device?.userId) {
1777
+ throw new Error('Cannot publish answer-calls-on-wxcc: user is not logged in');
1778
+ }
1321
1779
 
1322
- if (lastStateChangeReason === 'agent-wss-disconnect') {
1323
- LoggerProxy.info(
1324
- 'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
1325
- {module: CC_FILE, method: METHODS.SILENT_RELOGIN}
1326
- );
1327
- auxCodeId = AGENT_STATE_AVAILABLE_ID;
1328
- const stateChangeData: StateChange = {
1329
- state: AGENT_STATE_AVAILABLE,
1330
- auxCodeId,
1780
+ if (enabled) {
1781
+ this.assertWxAppStationLoginSupportedForEnable();
1782
+ }
1783
+
1784
+ const previousEnabled = this.isWxBetterTogetherEnabled();
1785
+
1786
+ if (this.$config) {
1787
+ this.$config.enableWxBetterTogether = enabled;
1788
+ }
1789
+
1790
+ this.taskManager.applyEnableWxBetterTogether(enabled);
1791
+
1792
+ let publishedEnable = false;
1793
+
1794
+ try {
1795
+ if (enabled) {
1796
+ await this.ensureWxAppMercuryAndSubscribe();
1797
+ await this.publishAnswerOnWebexCrossClientState(true);
1798
+ publishedEnable = true;
1799
+ this.taskManager.syncWxAppMuteFromCallDetailsForAllTasks();
1800
+ this.taskManager.refreshWxAppOfferObservabilityForAllTasks();
1801
+ } else {
1802
+ await this.publishAnswerOnWebexCrossClientState(false);
1803
+ this.wxAppTelephonyMercurySync.unsubscribe();
1804
+ await this.releaseWxAppMercuryResources();
1805
+ }
1806
+ } catch (error) {
1807
+ if (publishedEnable) {
1808
+ await this.revertWxAppCrossClientPublish();
1809
+ } else if (enabled) {
1810
+ this.wxAppTelephonyMercurySync.unsubscribe();
1811
+ await this.releaseWxAppMercuryResources();
1812
+ }
1813
+
1814
+ if (this.$config) {
1815
+ this.$config.enableWxBetterTogether = previousEnabled;
1816
+ }
1817
+ this.taskManager.applyEnableWxBetterTogether(previousEnabled);
1818
+ throw error;
1819
+ }
1820
+ }
1821
+
1822
+ private getWxAppTelephonyTaskType(): 'Voice' | 'WebRTC' | 'unknown' {
1823
+ const loginOption = this.getCurrentStationLoginOption();
1824
+
1825
+ if (loginOption === LoginOption.BROWSER) {
1826
+ return 'WebRTC';
1827
+ }
1828
+
1829
+ if (loginOption === LoginOption.EXTENSION || loginOption === LoginOption.AGENT_DN) {
1830
+ return 'Voice';
1831
+ }
1832
+
1833
+ return 'unknown';
1834
+ }
1835
+
1836
+ private async ensureWxAppPostStationLogin(): Promise<void> {
1837
+ const loginOption = this.getCurrentStationLoginOption();
1838
+ const flagEnabled = this.isWxBetterTogetherEnabled();
1839
+ const telephonyTaskType = this.getWxAppTelephonyTaskType();
1840
+
1841
+ if (loginOption === LoginOption.BROWSER) {
1842
+ if (flagEnabled) {
1843
+ logWxAppSessionReadiness({
1844
+ enableWxBetterTogether: true,
1845
+ loginOption,
1846
+ wxAppHooksApplied: false,
1847
+ usersubPublished: false,
1848
+ mercurySubscribed: false,
1849
+ telephonyTaskType,
1850
+ skipReason: 'unsupported_browser_login',
1851
+ });
1852
+
1853
+ this.metricsManager.trackEvent(
1854
+ METRIC_EVENT_NAMES.WXAPP_SESSION_SKIPPED,
1855
+ {
1856
+ loginOption,
1857
+ enableWxBetterTogether: true,
1858
+ skipReason: 'unsupported_browser_login',
1859
+ },
1860
+ ['operational', 'behavioral']
1861
+ );
1862
+ }
1863
+
1864
+ return;
1865
+ }
1866
+
1867
+ let publishedEnable = false;
1868
+ let wxAppInitStage: 'mercury' | 'publish' = 'mercury';
1869
+
1870
+ try {
1871
+ if (flagEnabled) {
1872
+ this.metricsManager.timeEvent([
1873
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_SUCCESS,
1874
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
1875
+ ]);
1876
+
1877
+ await this.ensureWxAppMercuryAndSubscribe();
1878
+ wxAppInitStage = 'publish';
1879
+ await this.publishAnswerOnWebexCrossClientState(true);
1880
+ publishedEnable = true;
1881
+ this.taskManager.syncWxAppMuteFromCallDetailsForAllTasks();
1882
+ this.taskManager.refreshWxAppOfferObservabilityForAllTasks();
1883
+
1884
+ const mercurySubscribed = this.wxAppTelephonyMercurySync.isSubscribed();
1885
+ const usersubPublished = this.webexCrossClientService.isAnswerCallsStateActive();
1886
+
1887
+ logWxAppSessionReadiness({
1888
+ enableWxBetterTogether: true,
1889
+ loginOption,
1890
+ wxAppHooksApplied: true,
1891
+ usersubPublished,
1892
+ mercurySubscribed,
1893
+ telephonyTaskType,
1894
+ });
1895
+
1896
+ const sessionInitReady = mercurySubscribed && usersubPublished;
1897
+
1898
+ if (sessionInitReady) {
1899
+ this.metricsManager.trackEvent(
1900
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_SUCCESS,
1901
+ {
1902
+ loginOption,
1903
+ enableWxBetterTogether: true,
1904
+ usersubPublished,
1905
+ mercurySubscribed,
1906
+ telephonyTaskType,
1907
+ },
1908
+ ['operational', 'behavioral']
1909
+ );
1910
+ } else {
1911
+ this.metricsManager.trackEvent(
1912
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
1913
+ {
1914
+ loginOption,
1915
+ enableWxBetterTogether: true,
1916
+ usersubPublished,
1917
+ mercurySubscribed,
1918
+ telephonyTaskType,
1919
+ skipReason: !usersubPublished ? 'usersub_not_published' : 'mercury_not_subscribed',
1920
+ },
1921
+ ['operational', 'behavioral']
1922
+ );
1923
+ }
1924
+ } else {
1925
+ await this.ensureWxAppDeviceRegistered();
1926
+ await this.publishAnswerOnWebexCrossClientState(false, {force: true});
1927
+ this.webexCrossClientService.teardown();
1928
+ this.wxAppTelephonyMercurySync.unsubscribe();
1929
+ await this.releaseWxAppMercuryResources();
1930
+
1931
+ logWxAppSessionReadiness({
1932
+ enableWxBetterTogether: false,
1933
+ loginOption,
1934
+ wxAppHooksApplied: true,
1935
+ usersubPublished: false,
1936
+ mercurySubscribed: false,
1937
+ telephonyTaskType,
1938
+ skipReason: 'flag_disabled',
1939
+ });
1940
+
1941
+ this.metricsManager.trackEvent(
1942
+ METRIC_EVENT_NAMES.WXAPP_SESSION_SKIPPED,
1943
+ {loginOption, enableWxBetterTogether: false, skipReason: 'flag_disabled'},
1944
+ ['operational', 'behavioral']
1945
+ );
1946
+ }
1947
+ } catch (error) {
1948
+ LoggerProxy.error(`Failed to initialize wxApp post-station-login: ${error}`, {
1949
+ module: CC_FILE,
1950
+ method: METHODS.STATION_LOGIN,
1951
+ });
1952
+
1953
+ if (flagEnabled) {
1954
+ const sessionInitFailureReason =
1955
+ wxAppInitStage === 'mercury' ? 'mercury_subscribe_failed' : 'publish_failed';
1956
+
1957
+ this.metricsManager.trackEvent(
1958
+ METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED,
1959
+ {
1960
+ loginOption,
1961
+ enableWxBetterTogether: true,
1962
+ usersubPublished: this.webexCrossClientService.isAnswerCallsStateActive(),
1963
+ mercurySubscribed: this.wxAppTelephonyMercurySync.isSubscribed(),
1964
+ telephonyTaskType,
1965
+ skipReason: sessionInitFailureReason,
1966
+ error: error instanceof Error ? error.toString() : String(error),
1967
+ },
1968
+ ['operational', 'behavioral']
1969
+ );
1970
+
1971
+ logWxAppSessionReadiness({
1972
+ enableWxBetterTogether: true,
1973
+ loginOption,
1974
+ wxAppHooksApplied: false,
1975
+ usersubPublished: this.webexCrossClientService.isAnswerCallsStateActive(),
1976
+ mercurySubscribed: this.wxAppTelephonyMercurySync.isSubscribed(),
1977
+ telephonyTaskType,
1978
+ skipReason: sessionInitFailureReason,
1979
+ });
1980
+
1981
+ if (publishedEnable) {
1982
+ await this.revertWxAppCrossClientPublish();
1983
+ } else {
1984
+ this.wxAppTelephonyMercurySync.unsubscribe();
1985
+ await this.releaseWxAppMercuryResources();
1986
+ }
1987
+ this.resetEnableWxBetterTogetherConfig();
1988
+ } else {
1989
+ this.scheduleForcedFalsePublishRetry();
1990
+ }
1991
+ }
1992
+ }
1993
+
1994
+ private clearWxAppFalsePublishRetryTimer(): void {
1995
+ if (this.wxAppFalsePublishRetryTimer) {
1996
+ clearTimeout(this.wxAppFalsePublishRetryTimer);
1997
+ this.wxAppFalsePublishRetryTimer = undefined;
1998
+ }
1999
+ }
2000
+
2001
+ private scheduleForcedFalsePublishRetry(retryAttempt = 0): void {
2002
+ this.scheduleFalsePublishRetry(retryAttempt, {requireDisabled: true});
2003
+ }
2004
+
2005
+ /**
2006
+ * Retries compensating false publish after rollback failure, regardless of enabled flag.
2007
+ * @private
2008
+ */
2009
+ private scheduleCompensatingFalsePublishRetry(retryAttempt = 0): void {
2010
+ this.scheduleFalsePublishRetry(retryAttempt);
2011
+ }
2012
+
2013
+ private scheduleFalsePublishRetry(
2014
+ retryAttempt = 0,
2015
+ options: {requireDisabled?: boolean} = {}
2016
+ ): void {
2017
+ if (retryAttempt >= ContactCenter.WXAPP_FALSE_PUBLISH_MAX_RETRIES) {
2018
+ return;
2019
+ }
2020
+
2021
+ if (options?.requireDisabled && this.isWxBetterTogetherEnabled()) {
2022
+ return;
2023
+ }
2024
+
2025
+ this.clearWxAppFalsePublishRetryTimer();
2026
+
2027
+ this.wxAppFalsePublishRetryTimer = setTimeout(async () => {
2028
+ this.wxAppFalsePublishRetryTimer = undefined;
2029
+
2030
+ if (options?.requireDisabled && this.isWxBetterTogetherEnabled()) {
2031
+ return;
2032
+ }
2033
+
2034
+ if (
2035
+ !options?.requireDisabled &&
2036
+ this.isWxBetterTogetherEnabled() &&
2037
+ this.webexCrossClientService.isAnswerCallsStateActive()
2038
+ ) {
2039
+ return;
2040
+ }
2041
+
2042
+ try {
2043
+ await this.ensureWxAppDeviceRegistered();
2044
+ await this.publishAnswerOnWebexCrossClientState(false, {force: true});
2045
+ this.webexCrossClientService.teardown();
2046
+ this.wxAppTelephonyMercurySync.unsubscribe();
2047
+ await this.releaseWxAppMercuryResources();
2048
+ } catch (retryError) {
2049
+ LoggerProxy.error(
2050
+ `Failed to retry forced answer-calls-on-wxcc false publish: ${retryError}`,
2051
+ {
2052
+ module: CC_FILE,
2053
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
2054
+ }
2055
+ );
2056
+ this.scheduleFalsePublishRetry(retryAttempt + 1, options);
2057
+ }
2058
+ }, ContactCenter.WXAPP_FALSE_PUBLISH_RETRY_DELAY_MS);
2059
+ }
2060
+
2061
+ /**
2062
+ * Best-effort rollback when usersub true was published but wxApp setup did not complete.
2063
+ * @private
2064
+ */
2065
+ private async revertWxAppCrossClientPublish(): Promise<void> {
2066
+ try {
2067
+ await this.publishAnswerOnWebexCrossClientState(false, {force: true});
2068
+ } catch (error) {
2069
+ LoggerProxy.error(
2070
+ `Failed to publish compensating answer-calls-on-wxcc false during wxApp rollback: ${error}`,
2071
+ {
2072
+ module: CC_FILE,
2073
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
2074
+ }
2075
+ );
2076
+ this.scheduleCompensatingFalsePublishRetry();
2077
+ }
2078
+
2079
+ this.webexCrossClientService.teardown();
2080
+ this.wxAppTelephonyMercurySync.unsubscribe();
2081
+ await this.releaseWxAppMercuryResources();
2082
+ }
2083
+
2084
+ private async ensureWxAppDeviceRegistered(): Promise<void> {
2085
+ const device = this.$webex.internal.device;
2086
+
2087
+ if (device && !device.registered && device.register) {
2088
+ await device.register();
2089
+ this.wxAppDeviceRegisteredByCc = true;
2090
+ LoggerProxy.log('WxApp: device registered for cross-client publish', {
2091
+ module: CC_FILE,
2092
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2093
+ });
2094
+ }
2095
+ }
2096
+
2097
+ private async ensureWxAppMercuryConnected(): Promise<void> {
2098
+ const mercury = this.$webex.internal.mercury;
2099
+
2100
+ await this.ensureWxAppDeviceRegistered();
2101
+
2102
+ if (mercury && !mercury.connected) {
2103
+ await mercury.connect();
2104
+ this.wxAppMercuryConnectedByCc = true;
2105
+ LoggerProxy.log('WxApp mute sync: mercury connected', {
2106
+ module: CC_FILE,
2107
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2108
+ });
2109
+ }
2110
+ }
2111
+
2112
+ /**
2113
+ * Disconnects Mercury and unregisters the device when wxApp mute sync opened them.
2114
+ * @private
2115
+ */
2116
+ private async releaseWxAppMercuryResources(): Promise<void> {
2117
+ const mercury = this.$webex.internal.mercury;
2118
+ const device = this.$webex.internal.device;
2119
+
2120
+ if (this.wxAppMercuryConnectedByCc && mercury?.connected) {
2121
+ try {
2122
+ await mercury.disconnect();
2123
+ this.wxAppMercuryConnectedByCc = false;
2124
+ LoggerProxy.log('WxApp mute sync: mercury disconnected', {
2125
+ module: CC_FILE,
2126
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2127
+ });
2128
+ } catch (error) {
2129
+ LoggerProxy.error(`Failed to disconnect wxApp Mercury: ${error}`, {
2130
+ module: CC_FILE,
2131
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2132
+ });
2133
+ }
2134
+ }
2135
+
2136
+ if (this.wxAppDeviceRegisteredByCc && device?.registered && device.unregister) {
2137
+ try {
2138
+ // @ts-ignore
2139
+ await device.unregister();
2140
+ this.wxAppDeviceRegisteredByCc = false;
2141
+ LoggerProxy.log('WxApp mute sync: device unregistered', {
2142
+ module: CC_FILE,
2143
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2144
+ });
2145
+ } catch (error) {
2146
+ LoggerProxy.error(`Failed to unregister wxApp device: ${error}`, {
2147
+ module: CC_FILE,
2148
+ method: METHODS.ENSURE_WXAPP_MERCURY_CONNECTED,
2149
+ });
2150
+ }
2151
+ } else if (this.wxAppDeviceRegisteredByCc) {
2152
+ this.wxAppDeviceRegisteredByCc = false;
2153
+ }
2154
+ }
2155
+
2156
+ /**
2157
+ * Clears wxApp runtime state and feature-owned resources. Publish failures are logged;
2158
+ * optional rethrow for deregister when callers need to surface usersub errors.
2159
+ * @private
2160
+ */
2161
+ private async teardownWxAppLocalState(options?: {
2162
+ rethrowPublishError?: boolean;
2163
+ clearInitFlag?: boolean;
2164
+ }): Promise<{publishError?: unknown}> {
2165
+ this.clearWxAppFalsePublishRetryTimer();
2166
+ let publishError: unknown;
2167
+
2168
+ try {
2169
+ const shouldForceFalsePublish =
2170
+ this.isWxBetterTogetherEnabled() || this.webexCrossClientService.isAnswerCallsStateActive();
2171
+
2172
+ await this.publishAnswerOnWebexCrossClientState(false, {
2173
+ force: shouldForceFalsePublish,
2174
+ });
2175
+ } catch (error) {
2176
+ publishError = error;
2177
+ LoggerProxy.error(
2178
+ `Failed to publish answer-calls-on-wxcc false during wxApp teardown: ${error}`,
2179
+ {
2180
+ module: CC_FILE,
2181
+ method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
2182
+ }
2183
+ );
2184
+ }
2185
+
2186
+ this.webexCrossClientService.teardown();
2187
+ this.wxAppTelephonyMercurySync.unsubscribe();
2188
+ await this.releaseWxAppMercuryResources();
2189
+
2190
+ if (options?.clearInitFlag) {
2191
+ this.resetEnableWxBetterTogetherConfig();
2192
+ }
2193
+
2194
+ if (publishError) {
2195
+ this.scheduleCompensatingFalsePublishRetry();
2196
+ }
2197
+
2198
+ if (options?.rethrowPublishError && publishError) {
2199
+ throw publishError;
2200
+ }
2201
+
2202
+ return {publishError: publishError ?? undefined};
2203
+ }
2204
+
2205
+ private async ensureWxAppMercuryAndSubscribe(): Promise<void> {
2206
+ if (!this.isWxBetterTogetherEnabled()) {
2207
+ return;
2208
+ }
2209
+
2210
+ if (this.getCurrentStationLoginOption() === LoginOption.BROWSER) {
2211
+ return;
2212
+ }
2213
+
2214
+ const agentId = this.agentConfig?.agentId;
2215
+ if (!agentId) {
2216
+ LoggerProxy.error('Cannot subscribe wxApp mute sync: agentId unavailable', {
2217
+ module: CC_FILE,
2218
+ method: METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY,
2219
+ });
2220
+
2221
+ this.metricsManager.trackEvent(
2222
+ METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
2223
+ {error: 'agentId unavailable', mercurySubscribed: false},
2224
+ ['operational', 'behavioral']
2225
+ );
2226
+
2227
+ return;
2228
+ }
2229
+
2230
+ try {
2231
+ await this.ensureWxAppMercuryConnected();
2232
+ this.wxAppTelephonyMercurySync.subscribe(agentId, (callId, muted) => {
2233
+ this.taskManager.applyWxAppMuteStateFromSync(callId, muted);
2234
+ });
2235
+ } catch (error) {
2236
+ LoggerProxy.error(`Failed to ensure wxApp Mercury mute sync: ${error}`, {
2237
+ module: CC_FILE,
2238
+ method: METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY,
2239
+ });
2240
+ this.metricsManager.trackEvent(
2241
+ METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
2242
+ {
2243
+ error: error instanceof Error ? error.toString() : String(error),
2244
+ mercurySubscribed: false,
2245
+ },
2246
+ ['operational', 'behavioral']
2247
+ );
2248
+ this.wxAppTelephonyMercurySync.unsubscribe();
2249
+ await this.releaseWxAppMercuryResources();
2250
+ throw error;
2251
+ }
2252
+ }
2253
+
2254
+ private async publishAnswerOnWebexCrossClientState(
2255
+ enable: boolean,
2256
+ options?: {force?: boolean}
2257
+ ): Promise<void> {
2258
+ if (enable && !this.isWxBetterTogetherEnabled()) {
2259
+ return;
2260
+ }
2261
+
2262
+ if (!enable && !options?.force && !this.webexCrossClientService.isAnswerCallsStateActive()) {
2263
+ return;
2264
+ }
2265
+
2266
+ const userId = this.$webex.internal.device?.userId;
2267
+ if (!userId) {
2268
+ if (enable && this.isWxBetterTogetherEnabled()) {
2269
+ this.metricsManager.trackEvent(
2270
+ METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
2271
+ {
2272
+ enableWxBetterTogether: true,
2273
+ usersubPublished: this.webexCrossClientService.isAnswerCallsStateActive(),
2274
+ skipReason: 'user_id_unavailable',
2275
+ error: 'User ID is unavailable for cross-client publish',
2276
+ },
2277
+ ['operational', 'behavioral']
2278
+ );
2279
+ }
2280
+
2281
+ return;
2282
+ }
2283
+
2284
+ await this.webexCrossClientService.setManageWebexCallingInWxcc(enable, {
2285
+ userId,
2286
+ trackPublishMetrics: true,
2287
+ });
2288
+
2289
+ if (enable) {
2290
+ this.clearWxAppFalsePublishRetryTimer();
2291
+ }
2292
+ }
2293
+
2294
+ /**
2295
+ * Initializes event listeners for the Contact Center service
2296
+ * Sets up handlers for connection state changes and other core events
2297
+ * @private
2298
+ */
2299
+ private setupEventListeners() {
2300
+ this.services.connectionService.on('connectionLost', this.handleConnectionLost.bind(this));
2301
+ }
2302
+
2303
+ /**
2304
+ * Returns the connection configuration
2305
+ * @returns {SubscribeRequest} Connection configuration
2306
+ * @private
2307
+ */
2308
+ private getConnectionConfig(): SubscribeRequest {
2309
+ return {
2310
+ force: this.$config?.force ?? true,
2311
+ isKeepAliveEnabled: this.$config?.isKeepAliveEnabled ?? false,
2312
+ clientType: this.$config?.clientType ?? 'WebexCCSDK',
2313
+ allowMultiLogin: this.$config?.allowMultiLogin ?? true,
2314
+ };
2315
+ }
2316
+
2317
+ /**
2318
+ * Handles connection lost events and reconnection attempts
2319
+ * @param {ConnectionLostDetails} msg Connection lost details
2320
+ * @private
2321
+ */
2322
+ private async handleConnectionLost(msg: ConnectionLostDetails): Promise<void> {
2323
+ if (msg.isConnectionLost) {
2324
+ // TODO: Emit an event saying connection is lost
2325
+ LoggerProxy.log('event=handleConnectionLost | Connection lost', {
2326
+ module: CC_FILE,
2327
+ method: METHODS.HANDLE_CONNECTION_LOST,
2328
+ });
2329
+ } else if (msg.isSocketReconnected) {
2330
+ // TODO: Emit an event saying connection is re-estabilished
2331
+ LoggerProxy.log(
2332
+ 'event=handleConnectionReconnect | Connection reconnected attempting to request silent relogin',
2333
+ {module: CC_FILE, method: METHODS.HANDLE_CONNECTION_LOST}
2334
+ );
2335
+ if (this.$config && this.$config.allowAutomatedRelogin) {
2336
+ await this.silentRelogin();
2337
+ }
2338
+ }
2339
+ }
2340
+
2341
+ /**
2342
+ * Handles silent relogin after registration completion
2343
+ * @private
2344
+ */
2345
+ private async silentRelogin(): Promise<void> {
2346
+ LoggerProxy.log('Starting silent relogin process', {
2347
+ module: CC_FILE,
2348
+ method: METHODS.SILENT_RELOGIN,
2349
+ });
2350
+
2351
+ try {
2352
+ const reLoginResponse = await this.services.agent.reload();
2353
+ this.updateWellnessSession(reLoginResponse.data.agentSessionId);
2354
+
2355
+ const {
2356
+ agentId,
2357
+ lastStateChangeReason,
2358
+ deviceType,
2359
+ dn,
2360
+ lastStateChangeTimestamp,
2361
+ lastIdleCodeChangeTimestamp,
2362
+ } = reLoginResponse.data;
2363
+ let {auxCodeId} = reLoginResponse.data;
2364
+ this.agentConfig.lastStateChangeTimestamp = lastStateChangeTimestamp;
2365
+ this.agentConfig.lastIdleCodeChangeTimestamp = lastIdleCodeChangeTimestamp;
2366
+ this.agentConfig.currentTeamId = reLoginResponse.data.teamId;
2367
+ await this.handleDeviceType(deviceType as LoginOption, dn);
2368
+
2369
+ if (lastStateChangeReason === 'agent-wss-disconnect') {
2370
+ LoggerProxy.log(
2371
+ 'event=requestAutoStateChange | Requesting state change to available on socket reconnect',
2372
+ {module: CC_FILE, method: METHODS.SILENT_RELOGIN}
2373
+ );
2374
+ auxCodeId = AGENT_STATE_AVAILABLE_ID;
2375
+ const stateChangeData: StateChange = {
2376
+ state: AGENT_STATE_AVAILABLE,
2377
+ auxCodeId,
1331
2378
  lastStateChangeReason,
1332
2379
  agentId,
1333
2380
  };
@@ -1349,8 +2396,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1349
2396
  }
1350
2397
  this.agentConfig.lastStateAuxCodeId = auxCodeId;
1351
2398
  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);
2399
+
2400
+ await this.ensureWxAppPostStationLogin();
1354
2401
 
1355
2402
  LoggerProxy.log(
1356
2403
  `Silent relogin process completed successfully with login Option: ${reLoginResponse.data.deviceType} teamId: ${reLoginResponse.data.teamId}`,
@@ -1391,6 +2438,16 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1391
2438
  this.agentConfig.deviceType = deviceType;
1392
2439
  switch (deviceType) {
1393
2440
  case LoginOption.BROWSER:
2441
+ if (this.isWebRTCRegistrationDisabled()) {
2442
+ LoggerProxy.info(
2443
+ 'Skipping web calling line registration because disableWebRTCRegistration is enabled',
2444
+ {
2445
+ module: CC_FILE,
2446
+ method: METHODS.HANDLE_DEVICE_TYPE,
2447
+ }
2448
+ );
2449
+ break;
2450
+ }
1394
2451
  try {
1395
2452
  await this.webCallingService.registerWebCallingLine();
1396
2453
  } catch (error) {
@@ -1656,6 +2713,166 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1656
2713
  }
1657
2714
  }
1658
2715
 
2716
+ /**
2717
+ * Skips a campaign preview contact, requesting the next contact from the campaign.
2718
+ *
2719
+ * When a campaign manager reserves a contact for an agent, the agent receives an
2720
+ * `AgentOfferCampaignReservation` event. Instead of accepting, the agent can skip the
2721
+ * preview contact to move to the next contact in the campaign.
2722
+ *
2723
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
2724
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
2725
+ * @throws {Error} If the operation fails (network error, etc.)
2726
+ *
2727
+ * @example
2728
+ * ```typescript
2729
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
2730
+ * const { interactionId } = task.data;
2731
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
2732
+ *
2733
+ * const result = await webex.cc.skipPreviewContact({ interactionId, campaignId });
2734
+ * });
2735
+ * ```
2736
+ */
2737
+ public async skipPreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
2738
+ const task = this.taskManager.getTask(payload.interactionId);
2739
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled === 'true') {
2740
+ LoggerProxy.warn('Skip action is disabled for this campaign preview contact', {
2741
+ module: CC_FILE,
2742
+ method: METHODS.SKIP_PREVIEW_CONTACT,
2743
+ interactionId: payload.interactionId,
2744
+ });
2745
+ throw new Error('Skip action is disabled for this campaign preview contact');
2746
+ }
2747
+
2748
+ LoggerProxy.info('Skipping campaign preview contact', {
2749
+ module: CC_FILE,
2750
+ method: METHODS.SKIP_PREVIEW_CONTACT,
2751
+ });
2752
+ try {
2753
+ this.metricsManager.timeEvent([
2754
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
2755
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
2756
+ ]);
2757
+
2758
+ const result = await this.services.dialer.skipPreviewContact({data: payload});
2759
+
2760
+ this.metricsManager.trackEvent(
2761
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_SUCCESS,
2762
+ {
2763
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
2764
+ interactionId: payload.interactionId,
2765
+ campaignId: payload.campaignId,
2766
+ },
2767
+ ['behavioral', 'business', 'operational']
2768
+ );
2769
+
2770
+ LoggerProxy.log('Campaign preview contact skipped successfully', {
2771
+ module: CC_FILE,
2772
+ method: METHODS.SKIP_PREVIEW_CONTACT,
2773
+ trackingId: result.trackingId,
2774
+ interactionId: payload.interactionId,
2775
+ });
2776
+
2777
+ return result;
2778
+ } catch (error) {
2779
+ const failure = error.details as Failure;
2780
+ this.metricsManager.trackEvent(
2781
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_SKIP_FAILED,
2782
+ {
2783
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
2784
+ interactionId: payload.interactionId,
2785
+ campaignId: payload.campaignId,
2786
+ },
2787
+ ['behavioral', 'business', 'operational']
2788
+ );
2789
+ const {error: detailedError} = getErrorDetails(error, METHODS.SKIP_PREVIEW_CONTACT, CC_FILE);
2790
+ throw detailedError;
2791
+ }
2792
+ }
2793
+
2794
+ /**
2795
+ * Removes a campaign preview contact from the campaign list entirely.
2796
+ *
2797
+ * When a campaign manager reserves a contact for an agent, the agent receives an
2798
+ * `AgentOfferCampaignReservation` event. Instead of accepting or skipping, the agent can
2799
+ * remove the preview contact to permanently take it out of the campaign contact list.
2800
+ *
2801
+ * @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
2802
+ * @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
2803
+ * @throws {Error} If the operation fails (network error, etc.)
2804
+ *
2805
+ * @example
2806
+ * ```typescript
2807
+ * webex.cc.on('task:campaignPreviewReservation', async (task) => {
2808
+ * const { interactionId } = task.data;
2809
+ * const campaignId = task.data.interaction.callProcessingDetails.campaignId;
2810
+ *
2811
+ * const result = await webex.cc.removePreviewContact({ interactionId, campaignId });
2812
+ * });
2813
+ * ```
2814
+ */
2815
+ public async removePreviewContact(payload: PreviewContactPayload): Promise<TaskResponse> {
2816
+ const task = this.taskManager.getTask(payload.interactionId);
2817
+ if (task?.data?.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled === 'true') {
2818
+ LoggerProxy.warn('Remove action is disabled for this campaign preview contact', {
2819
+ module: CC_FILE,
2820
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
2821
+ interactionId: payload.interactionId,
2822
+ });
2823
+ throw new Error('Remove action is disabled for this campaign preview contact');
2824
+ }
2825
+
2826
+ LoggerProxy.info('Removing campaign preview contact', {
2827
+ module: CC_FILE,
2828
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
2829
+ });
2830
+ try {
2831
+ this.metricsManager.timeEvent([
2832
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
2833
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
2834
+ ]);
2835
+
2836
+ const result = await this.services.dialer.removePreviewContact({data: payload});
2837
+
2838
+ this.metricsManager.trackEvent(
2839
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_SUCCESS,
2840
+ {
2841
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
2842
+ interactionId: payload.interactionId,
2843
+ campaignId: payload.campaignId,
2844
+ },
2845
+ ['behavioral', 'business', 'operational']
2846
+ );
2847
+
2848
+ LoggerProxy.log('Campaign preview contact removed successfully', {
2849
+ module: CC_FILE,
2850
+ method: METHODS.REMOVE_PREVIEW_CONTACT,
2851
+ trackingId: result.trackingId,
2852
+ interactionId: payload.interactionId,
2853
+ });
2854
+
2855
+ return result;
2856
+ } catch (error) {
2857
+ const failure = error.details as Failure;
2858
+ this.metricsManager.trackEvent(
2859
+ METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_REMOVE_FAILED,
2860
+ {
2861
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure),
2862
+ interactionId: payload.interactionId,
2863
+ campaignId: payload.campaignId,
2864
+ },
2865
+ ['behavioral', 'business', 'operational']
2866
+ );
2867
+ const {error: detailedError} = getErrorDetails(
2868
+ error,
2869
+ METHODS.REMOVE_PREVIEW_CONTACT,
2870
+ CC_FILE
2871
+ );
2872
+ throw detailedError;
2873
+ }
2874
+ }
2875
+
1659
2876
  /**
1660
2877
  * Fetches outdial ANI (Automatic Number Identification) entries for an outdial ANI ID.
1661
2878
  *
@@ -1900,8 +3117,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1900
3117
  }
1901
3118
 
1902
3119
  /**
1903
- * Returns paginated entry points for the organization.
1904
- * Thin wrapper around internal EntryPoint instance.
3120
+ * Returns paginated entry points using the EntryPoint service defaults.
3121
+ * Existing request parameters can override those defaults.
1905
3122
  * @public
1906
3123
  */
1907
3124
  public async getEntryPoints(
@@ -1911,8 +3128,8 @@ export default class ContactCenter extends WebexPlugin implements IContactCenter
1911
3128
  }
1912
3129
 
1913
3130
  /**
1914
- * Returns paginated contact service queues for the organization.
1915
- * Thin wrapper around internal Queue instance.
3131
+ * Returns paginated contact service queues using the Queue service defaults.
3132
+ * Existing request parameters can override those defaults.
1916
3133
  * @public
1917
3134
  */
1918
3135
  public async getQueues(