@webex/contact-center 3.12.0-next.103 → 3.12.0-next.105

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 (93) hide show
  1. package/.sdd/manifest.json +2 -1
  2. package/ai-docs/CONTRACTS.md +2 -0
  3. package/ai-docs/contact-center-spec.md +13 -0
  4. package/dist/cc.js +416 -1
  5. package/dist/cc.js.map +1 -1
  6. package/dist/config.js +7 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.js +7 -1
  9. package/dist/constants.js.map +1 -1
  10. package/dist/services/AnswerCallOnWebexService.js +174 -0
  11. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  12. package/dist/services/WebexCrossClientService.js +171 -0
  13. package/dist/services/WebexCrossClientService.js.map +1 -0
  14. package/dist/services/WxAppTelephonyMercurySync.js +93 -0
  15. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  16. package/dist/services/task/Task.js +60 -5
  17. package/dist/services/task/Task.js.map +1 -1
  18. package/dist/services/task/TaskFactory.js +4 -2
  19. package/dist/services/task/TaskFactory.js.map +1 -1
  20. package/dist/services/task/TaskManager.js +130 -17
  21. package/dist/services/task/TaskManager.js.map +1 -1
  22. package/dist/services/task/WebexCallingUtils.js +70 -0
  23. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  24. package/dist/services/task/state-machine/TaskStateMachine.js +74 -6
  25. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  26. package/dist/services/task/state-machine/types.js.map +1 -1
  27. package/dist/services/task/state-machine/uiControlsComputer.js +68 -13
  28. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
  29. package/dist/services/task/types.js +6 -0
  30. package/dist/services/task/types.js.map +1 -1
  31. package/dist/services/task/voice/Voice.js +280 -13
  32. package/dist/services/task/voice/Voice.js.map +1 -1
  33. package/dist/services/task/voice/WebRTC.js +4 -1
  34. package/dist/services/task/voice/WebRTC.js.map +1 -1
  35. package/dist/services/task/voice/wxAppVoiceMethods.js +201 -0
  36. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  37. package/dist/services/wxAppTelephonyUtils.js +19 -0
  38. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  39. package/dist/types/cc.d.ts +73 -0
  40. package/dist/types/config.d.ts +7 -0
  41. package/dist/types/constants.d.ts +6 -0
  42. package/dist/types/services/AnswerCallOnWebexService.d.ts +37 -0
  43. package/dist/types/services/WebexCrossClientService.d.ts +28 -0
  44. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  45. package/dist/types/services/task/Task.d.ts +19 -2
  46. package/dist/types/services/task/TaskFactory.d.ts +2 -1
  47. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  48. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +158 -4
  49. package/dist/types/services/task/state-machine/types.d.ts +6 -0
  50. package/dist/types/services/task/types.d.ts +52 -3
  51. package/dist/types/services/task/voice/Voice.d.ts +41 -9
  52. package/dist/types/services/task/voice/WebRTC.d.ts +2 -2
  53. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +52 -0
  54. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  55. package/dist/types/types.d.ts +2 -0
  56. package/dist/types.js +6 -0
  57. package/dist/types.js.map +1 -1
  58. package/dist/webex.js +1 -1
  59. package/package.json +3 -3
  60. package/src/cc.ts +484 -0
  61. package/src/config.ts +7 -0
  62. package/src/constants.ts +6 -0
  63. package/src/services/AnswerCallOnWebexService.ts +206 -0
  64. package/src/services/WebexCrossClientService.ts +212 -0
  65. package/src/services/WxAppTelephonyMercurySync.ts +115 -0
  66. package/src/services/ai-docs/services-spec.md +1 -0
  67. package/src/services/task/Task.ts +59 -5
  68. package/src/services/task/TaskFactory.ts +5 -1
  69. package/src/services/task/TaskManager.ts +172 -12
  70. package/src/services/task/WebexCallingUtils.ts +136 -0
  71. package/src/services/task/ai-docs/task-spec.md +8 -0
  72. package/src/services/task/state-machine/TaskStateMachine.ts +99 -4
  73. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +15 -6
  74. package/src/services/task/state-machine/types.ts +6 -0
  75. package/src/services/task/state-machine/uiControlsComputer.ts +117 -29
  76. package/src/services/task/types.ts +71 -3
  77. package/src/services/task/voice/Voice.ts +367 -12
  78. package/src/services/task/voice/WebRTC.ts +5 -1
  79. package/src/services/task/voice/wxAppVoiceMethods.ts +307 -0
  80. package/src/services/wxAppTelephonyUtils.ts +14 -0
  81. package/src/types.ts +18 -1
  82. package/test/unit/spec/cc.ts +1230 -0
  83. package/test/unit/spec/services/AnswerCallOnWebexService.ts +223 -0
  84. package/test/unit/spec/services/WebexCrossClientService.ts +261 -0
  85. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +113 -0
  86. package/test/unit/spec/services/task/TaskManager.ts +405 -1
  87. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  88. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +261 -0
  89. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +292 -0
  90. package/test/unit/spec/services/task/voice/Voice.ts +716 -0
  91. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +459 -0
  92. package/umd/contact-center.min.js +2 -2
  93. package/umd/contact-center.min.js.map +1 -1
@@ -51,7 +51,8 @@
51
51
  "canonical_spec": "ai-docs/contact-center-spec.md",
52
52
  "contracts": {
53
53
  "provides": [
54
- "Published @webex/contact-center package; ContactCenter WebexPlugin; public SDK methods, events, types, Task, AddressBook, ApiAIAssistant, UserPreference, cc.userPreference, exported user-preference request/response types, and preview-campaign methods acceptPreviewContact/skipPreviewContact/removePreviewContact"
54
+ "Published @webex/contact-center package; ContactCenter WebexPlugin; public SDK methods, events, types, Task, AddressBook, ApiAIAssistant, UserPreference, cc.userPreference, exported user-preference request/response types, and preview-campaign methods acceptPreviewContact/skipPreviewContact/removePreviewContact",
55
+ "WXCC-6026 wxApp Better Together: enableWxBetterTogether init config, isWxBetterTogetherEnabled read API, unified task telephony (task.accept/decline/toggleMute/transmitDtmf), TASK_WXAPP_MUTE_STATE_UPDATED event, optional main.keypad uiControl, multi-login supported; runtime toggle deferred to Phase 2 (private)"
55
56
  ],
56
57
  "requires": [
57
58
  "Webex SDK host/plugin runtime; Contact Center REST and WebSocket services; internal Webex calling, metrics, support, authorization, logger, and mercury packages"
@@ -13,6 +13,7 @@
13
13
  | `contact-center.user-preference` | Services/Config | `UserPreference`, `cc.userPreference`, `UserPreferenceData`, request/response types | `getUserPreference(params?)`, `createUserPreference(data)`, `updateUserPreference(userId, data)`, `deleteUserPreference(userId)` | semver public | `src/services/ai-docs/services-spec.md` | `src/index.ts`, `src/cc.ts`, `src/services/UserPreference.ts`, `src/services/config/types.ts` |
14
14
  | `contact-center.preview-campaign` | Contact Center/Task | `acceptPreviewContact`, `skipPreviewContact`, `removePreviewContact` | `(payload: PreviewContactPayload) => Promise<TaskResponse>` | semver public | `ai-docs/contact-center-spec.md`, `src/services/task/ai-docs/task-spec.md` | `src/cc.ts`, `src/services/task/dialer.ts`, `src/services/task/types.ts` |
15
15
  | `contact-center.state-controls` | Task state machine | `getDefaultUIControls`, task state/control types | function is exported directly from `uiControlsComputer.ts` by package root | semver public | `src/services/task/state-machine/ai-docs/task-state-machine-spec.md` | `src/index.ts`, `src/services/task/state-machine/uiControlsComputer.ts` |
16
+ | `contact-center.wxapp-answer` | Contact Center / Task | `enableWxBetterTogether`, `isWxBetterTogetherEnabled()`, `getWxAppMuted`, `syncWxAppMuteFromCallDetails`, unified task telephony (`ITask.accept`, `decline`, `toggleMute({ muted?, lineOwnerId? })`, `transmitDtmf({ dtmf, lineOwnerId? })`) | init flag ON → usersub `true` + Mercury on supported station login **and silent relogin**; init flag OFF → force usersub `false` on supported station login **and silent relogin** (clears stale suppression after refresh); **Phase 1 init-only** — change flag via re-init; multi-login supported; mute backfill via GET call details + `getWxAppMuted()` on hydrate/refresh; shared-line `lineOwnerId` defaults from participant; SDK `Voice` routes wxApp telephony when flag active | semver public | `src/services/task/ai-docs/task-spec.md`, `ai-docs/contact-center-spec.md`, `src/services/ai-docs/services-spec.md` | `src/cc.ts`, `src/services/task/voice/Voice.ts`, `src/services/task/voice/wxAppVoiceMethods.ts` |
16
17
 
17
18
  ### Events
18
19
 
@@ -20,6 +21,7 @@
20
21
  |---|---|---|---|---|---|---|---|
21
22
  | `agent.events` | Agent | `agent:*` | publish to application | agent spec/types | realtime; backend ordering/correlation | additive constants | `src/services/agent/types.ts` |
22
23
  | `task.events` | Task | `task:*` | publish to application/task | task spec/types | event-driven; state guarded | additive constants | `src/services/task/types.ts` |
24
+ | `task.wxapp-mute.events` | Task | `task:wxapp-mute-state-updated` | publish to application/task | task spec/types | event-driven | additive | `src/services/task/types.ts`, `src/services/task/voice/Voice.ts` |
23
25
  | `cc.events` | Config/Core | backend CC_EVENTS | consume/map | config/core specs | remote WebSocket delivery; AQM correlation where configured | backend contract | `src/services/config/types.ts` |
24
26
  | `rtd.events` | Task | realtime transcription/suggestion | consume then publish per task | task spec | websocket best-effort according to remote service | additive payloads | `src/services/task/TaskManager.ts` |
25
27
 
@@ -99,6 +99,19 @@ Direct data/configuration and user-preference operations return authenticated RE
99
99
 
100
100
  Durable agent, task, and configuration records remain remote-system owned. The package owns only in-memory profile/task/listener/cache/connection state.
101
101
 
102
+ ### wxApp Better Together (WXCC-6026)
103
+
104
+ Contract id: `contact-center.wxapp-answer` ([CONTRACTS.md](./CONTRACTS.md)). Task telephony routing, UI controls, and mute events are specified in [task-spec.md](../src/services/task/ai-docs/task-spec.md). Service collaborators: [services-spec.md](../src/services/ai-docs/services-spec.md).
105
+
106
+ - **Init flag:** `webex.init({ cc: { enableWxBetterTogether: boolean } })` — default `false`. When `true`, ContactCenter enables wxApp telephony routing on tasks after supported station login. **Compatible with `allowMultiLogin: true`** (multiple SDK sessions may receive offers; wxApp telephony is routed per active task instance). **Phase 1 is init-only** — to change the flag after SDK init, re-init with updated config.
107
+ - **Read API:** `cc.isWxBetterTogetherEnabled()` returns the current init flag value.
108
+ - **Phase 2 (internal/private):** `setManageWebexCallingInWxcc(enabled)` remains as a private implementation for future runtime toggle; hosts must not call it in Phase 1.
109
+ - **Post-login hooks:** `ensureWxAppPostStationLogin()` runs after successful `stationLogin()` and after `silentRelogin()` on socket reconnect. When the init flag is ON, it publishes usersub `true`, connects Mercury/device for mute sync, and backfills mute state on active tasks. Failures roll back wxApp config and release partial Mercury/device resources without failing station login. When OFF, it force-publishes usersub `false` (clears stale suppression after page refresh) and tears down wxApp Mercury resources.
110
+ - **Teardown:** `deregister()`, station logout, and wxApp teardown paths call `teardownWxAppLocalState()` — publish usersub `false`, unsubscribe Mercury, release CC-owned device/Mercury connections, and reset **session runtime** state (usersub active flag, Mercury subscriptions, task-manager wxApp routing) so stale session flags do not apply on relogin. The host init flag (`enableWxBetterTogether`) is **not** cleared on station logout — it persists until `deregister()` or re-init per Phase 1 contract. Local cleanup runs even when usersub publish fails.
111
+ - **Host telephony surface:** Hosts call unified `task.accept()`, `task.decline()`, `task.toggleMute({ muted? })`, and `task.transmitDtmf({ dtmf })`; SDK `Voice` routes wxApp legs internally when the flag is active. Shared-line `lineOwnerId` defaults from the wxApp agent participant when omitted.
112
+
113
+ Evidence: `src/cc.ts`, `src/config.ts`, `src/services/WebexCrossClientService.ts`, `src/services/WxAppTelephonyMercurySync.ts`, `test/unit/spec/cc.ts`.
114
+
102
115
  ## Data Flow
103
116
  ```mermaid
104
117
  flowchart TD
package/dist/cc.js CHANGED
@@ -19,6 +19,9 @@ var _Utils = require("./services/core/Utils");
19
19
  var _types3 = require("./services/config/types");
20
20
  var _TaskManager = _interopRequireDefault(require("./services/task/TaskManager"));
21
21
  var _WebCallingService = _interopRequireDefault(require("./services/WebCallingService"));
22
+ var _AnswerCallOnWebexService = _interopRequireDefault(require("./services/AnswerCallOnWebexService"));
23
+ var _WebexCrossClientService = _interopRequireDefault(require("./services/WebexCrossClientService"));
24
+ var _WxAppTelephonyMercurySync = _interopRequireDefault(require("./services/WxAppTelephonyMercurySync"));
22
25
  var _types4 = require("./services/task/types");
23
26
  var _MetricsManager = _interopRequireDefault(require("./metrics/MetricsManager"));
24
27
  var _constants4 = require("./metrics/constants");
@@ -185,6 +188,29 @@ class ContactCenter extends _webexCore.WebexPlugin {
185
188
  * @private
186
189
  */
187
190
 
191
+ /**
192
+ * REST telephony for wxApp thick-client answer (WXCC-6026)
193
+ * @private
194
+ */
195
+
196
+ /**
197
+ * usersub cross-client state for Webex App toast suppression
198
+ * @private
199
+ */
200
+
201
+ /**
202
+ * Mercury telephony mute sync for wxApp thick-client answer
203
+ * @private
204
+ */
205
+
206
+ /** True when wxApp mute sync opened Mercury (Extension/DN-only profiles). */
207
+ wxAppMercuryConnectedByCc = false;
208
+
209
+ /** True when wxApp mute sync registered the Webex device. */
210
+ wxAppDeviceRegisteredByCc = false;
211
+ static WXAPP_FALSE_PUBLISH_RETRY_DELAY_MS = 30_000;
212
+ static WXAPP_FALSE_PUBLISH_MAX_RETRIES = 3;
213
+
188
214
  /**
189
215
  * Core service managers for Contact Center operations
190
216
  * Includes agent, connection, and configuration services
@@ -345,11 +371,15 @@ class ContactCenter extends _webexCore.WebexPlugin {
345
371
  connectionConfig: this.getConnectionConfig()
346
372
  });
347
373
  this.webCallingService = new _WebCallingService.default(this.$webex);
374
+ this.answerCallOnWebexService = new _AnswerCallOnWebexService.default(this.$webex);
375
+ this.webexCrossClientService = new _WebexCrossClientService.default(this.$webex);
376
+ this.wxAppTelephonyMercurySync = new _WxAppTelephonyMercurySync.default(this.$webex);
348
377
  this.apiAIAssistant = new _ApiAiAssistant.ApiAIAssistant(this.$webex);
349
378
  this.metricsManager = _MetricsManager.default.getInstance({
350
379
  webex: this.$webex
351
380
  });
352
381
  this.taskManager = _TaskManager.default.getTaskManager(this.apiAIAssistant, this.services.contact, this.webCallingService, this.services.webSocketManager, this.services.rtdWebSocketManager);
382
+ this.taskManager.setAnswerCallOnWebexService(this.answerCallOnWebexService);
353
383
  this.incomingTaskListener();
354
384
 
355
385
  // Initialize API instances
@@ -541,6 +571,11 @@ class ContactCenter extends _webexCore.WebexPlugin {
541
571
  this.services.webSocketManager.off('message', this.handleWebsocketMessage);
542
572
  this.services.rtdWebSocketManager.off('message', this.handleRTDWebsocketMessage);
543
573
  this.services.connectionService.off('connectionLost', this.handleConnectionLost);
574
+ const {
575
+ publishError: wxAppPublishError
576
+ } = await this.teardownWxAppLocalState({
577
+ clearInitFlag: true
578
+ });
544
579
  if (this.agentConfig.webRtcEnabled && this.agentConfig.loginVoiceOptions.includes(_types.LoginOption.BROWSER)) {
545
580
  if (this.$webex.internal.mercury.connected) {
546
581
  this.$webex.internal.mercury.off('online');
@@ -566,6 +601,9 @@ class ContactCenter extends _webexCore.WebexPlugin {
566
601
 
567
602
  // Clear any cached agent configuration
568
603
  this.agentConfig = null;
604
+ if (wxAppPublishError) {
605
+ throw wxAppPublishError;
606
+ }
569
607
  _loggerProxy.default.log('Deregistered successfully', {
570
608
  module: _constants.CC_FILE,
571
609
  method: _constants.METHODS.DEREGISTER
@@ -685,7 +723,8 @@ class ContactCenter extends _webexCore.WebexPlugin {
685
723
  isEndConsultEnabled: this.agentConfig.isEndConsultEnabled,
686
724
  webRtcEnabled: this.agentConfig.webRtcEnabled,
687
725
  autoWrapup: this.agentConfig.wrapUpData?.wrapUpProps?.autoWrapup ?? false,
688
- aiFeature: this.agentConfig.aiFeature
726
+ aiFeature: this.agentConfig.aiFeature,
727
+ enableWxBetterTogether: this.isWxBetterTogetherEnabled()
689
728
  };
690
729
  this.taskManager.setConfigFlags(configFlags);
691
730
  // TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
@@ -846,6 +885,13 @@ class ContactCenter extends _webexCore.WebexPlugin {
846
885
  notifsTrackingId: resp.trackingId
847
886
  };
848
887
  this.webCallingService.setLoginOption(data.loginOption);
888
+ if (this.agentConfig) {
889
+ this.agentConfig.deviceType = data.loginOption;
890
+ if (data.loginOption === _types.LoginOption.AGENT_DN || data.loginOption === _types.LoginOption.EXTENSION) {
891
+ this.agentConfig.defaultDn = data.dialNumber;
892
+ this.agentConfig.dn = data.dialNumber;
893
+ }
894
+ }
849
895
  this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS, {
850
896
  ..._MetricsManager.default.getCommonTrackingFieldForAQMResponse(resp),
851
897
  loginType: data.loginOption,
@@ -858,6 +904,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
858
904
  method: _constants.METHODS.STATION_LOGIN,
859
905
  trackingId: resp.trackingId
860
906
  });
907
+ await this.ensureWxAppPostStationLogin();
861
908
  return response;
862
909
  } catch (error) {
863
910
  const failure = error.details;
@@ -912,6 +959,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
912
959
  if (this.webCallingService) {
913
960
  this.webCallingService.deregisterWebCallingLine();
914
961
  }
962
+ await this.teardownWxAppLocalState();
915
963
  _loggerProxy.default.log(`Agent station logout completed successfully`, {
916
964
  module: _constants.CC_FILE,
917
965
  method: _constants.METHODS.STATION_LOGOUT,
@@ -1170,6 +1218,372 @@ class ContactCenter extends _webexCore.WebexPlugin {
1170
1218
  }
1171
1219
  }
1172
1220
 
1221
+ /**
1222
+ * Whether wxApp thick-client answer is enabled for this SDK instance.
1223
+ * @public
1224
+ */
1225
+ isWxBetterTogetherEnabled() {
1226
+ return this.$config?.enableWxBetterTogether === true;
1227
+ }
1228
+
1229
+ /**
1230
+ * Current station login option (Extension, Dial Number, or Browser/Desktop).
1231
+ * Prefer webCallingService — updated on every stationLogin before wxApp hooks run.
1232
+ * @private
1233
+ */
1234
+ getCurrentStationLoginOption() {
1235
+ return this.webCallingService?.loginOption ?? this.agentConfig?.deviceType;
1236
+ }
1237
+
1238
+ /**
1239
+ * wxApp thick-client answer is supported for Extension and Dial Number station login only —
1240
+ * not Browser/Desktop (WebRTC) login.
1241
+ * @private
1242
+ */
1243
+ assertWxAppStationLoginSupportedForEnable() {
1244
+ const loginOption = this.getCurrentStationLoginOption();
1245
+ if (loginOption !== _types.LoginOption.EXTENSION && loginOption !== _types.LoginOption.AGENT_DN) {
1246
+ throw new Error('setManageWebexCallingInWxcc requires EXTENSION or AGENT_DN station login. Complete station login before enabling.');
1247
+ }
1248
+ }
1249
+
1250
+ /**
1251
+ * Clears runtime wxApp config after logout/deregister so stale flags do not apply on relogin.
1252
+ * @private
1253
+ */
1254
+ resetEnableWxBetterTogetherConfig() {
1255
+ if (this.$config) {
1256
+ this.$config.enableWxBetterTogether = false;
1257
+ }
1258
+ this.taskManager.applyEnableWxBetterTogether(false);
1259
+ }
1260
+
1261
+ /**
1262
+ * Runtime enable/disable for wxApp Better Together (toast suppression + Mercury mute sync).
1263
+ *
1264
+ * **Phase 1 (WXCC-6026 / MMT): NOT part of the public host contract.**
1265
+ * Hosts must set `webexConfig.cc.enableWxBetterTogether` before `webex.init()` / `cc.register()`.
1266
+ * To change the flag after the SDK is initialized, re-init the SDK with the updated config.
1267
+ *
1268
+ * **Supported public read API:** {@link isWxBetterTogetherEnabled}.
1269
+ * **Supported telephony API:** unified `task.accept()`, `task.decline()`, `task.toggleMute()`,
1270
+ * `task.transmitDtmf()` — SDK routes wxApp calls internally when the init flag is active.
1271
+ *
1272
+ * **Production lifecycle does not call this method.** Station login, silent relogin, logout,
1273
+ * and deregister use {@link ensureWxAppPostStationLogin} and {@link teardownWxAppLocalState}
1274
+ * (usersub publish + Mercury subscribe/unsubscribe based on the init flag).
1275
+ *
1276
+ * This setter remains as a **private implementation** for Phase 2 runtime toggle and unit tests.
1277
+ * It updates `$config.enableWxBetterTogether`, TaskManager flags, cross-client usersub
1278
+ * (`answer-calls-on-wxcc`), and Mercury mute sync; rolls back config on publish/connect failure.
1279
+ *
1280
+ * @param enabled - `true` to enable wxApp answer + usersub suppression; `false` to disable and tear down.
1281
+ * @throws When enabling without login, on unsupported BROWSER station login, or when usersub/Mercury init fails.
1282
+ * @internal
1283
+ * @see enableWxBetterTogether (CCPluginConfig init flag)
1284
+ * @see ensureWxAppPostStationLogin
1285
+ * @see teardownWxAppLocalState
1286
+ */
1287
+ async setManageWebexCallingInWxcc(enabled) {
1288
+ if (enabled && !this.$webex.internal.device?.userId) {
1289
+ throw new Error('Cannot publish answer-calls-on-wxcc: user is not logged in');
1290
+ }
1291
+ if (enabled) {
1292
+ this.assertWxAppStationLoginSupportedForEnable();
1293
+ }
1294
+ const previousEnabled = this.isWxBetterTogetherEnabled();
1295
+ if (this.$config) {
1296
+ this.$config.enableWxBetterTogether = enabled;
1297
+ }
1298
+ this.taskManager.applyEnableWxBetterTogether(enabled);
1299
+ let publishedEnable = false;
1300
+ try {
1301
+ if (enabled) {
1302
+ await this.ensureWxAppMercuryAndSubscribe();
1303
+ await this.publishAnswerOnWebexCrossClientState(true);
1304
+ publishedEnable = true;
1305
+ this.taskManager.syncWxAppMuteFromCallDetailsForAllTasks();
1306
+ } else {
1307
+ await this.publishAnswerOnWebexCrossClientState(false);
1308
+ this.wxAppTelephonyMercurySync.unsubscribe();
1309
+ await this.releaseWxAppMercuryResources();
1310
+ }
1311
+ } catch (error) {
1312
+ if (publishedEnable) {
1313
+ await this.revertWxAppCrossClientPublish();
1314
+ } else if (enabled) {
1315
+ this.wxAppTelephonyMercurySync.unsubscribe();
1316
+ await this.releaseWxAppMercuryResources();
1317
+ }
1318
+ if (this.$config) {
1319
+ this.$config.enableWxBetterTogether = previousEnabled;
1320
+ }
1321
+ this.taskManager.applyEnableWxBetterTogether(previousEnabled);
1322
+ throw error;
1323
+ }
1324
+ }
1325
+ async ensureWxAppPostStationLogin() {
1326
+ if (this.getCurrentStationLoginOption() === _types.LoginOption.BROWSER) {
1327
+ return;
1328
+ }
1329
+ let publishedEnable = false;
1330
+ try {
1331
+ if (this.isWxBetterTogetherEnabled()) {
1332
+ await this.ensureWxAppMercuryAndSubscribe();
1333
+ await this.publishAnswerOnWebexCrossClientState(true);
1334
+ publishedEnable = true;
1335
+ this.taskManager.syncWxAppMuteFromCallDetailsForAllTasks();
1336
+ } else {
1337
+ await this.ensureWxAppDeviceRegistered();
1338
+ await this.publishAnswerOnWebexCrossClientState(false, {
1339
+ force: true
1340
+ });
1341
+ this.webexCrossClientService.teardown();
1342
+ this.wxAppTelephonyMercurySync.unsubscribe();
1343
+ await this.releaseWxAppMercuryResources();
1344
+ }
1345
+ } catch (error) {
1346
+ _loggerProxy.default.error(`Failed to initialize wxApp post-station-login: ${error}`, {
1347
+ module: _constants.CC_FILE,
1348
+ method: _constants.METHODS.STATION_LOGIN
1349
+ });
1350
+ if (this.isWxBetterTogetherEnabled()) {
1351
+ if (publishedEnable) {
1352
+ await this.revertWxAppCrossClientPublish();
1353
+ } else {
1354
+ this.wxAppTelephonyMercurySync.unsubscribe();
1355
+ await this.releaseWxAppMercuryResources();
1356
+ }
1357
+ this.resetEnableWxBetterTogetherConfig();
1358
+ } else {
1359
+ this.scheduleForcedFalsePublishRetry();
1360
+ }
1361
+ }
1362
+ }
1363
+ clearWxAppFalsePublishRetryTimer() {
1364
+ if (this.wxAppFalsePublishRetryTimer) {
1365
+ clearTimeout(this.wxAppFalsePublishRetryTimer);
1366
+ this.wxAppFalsePublishRetryTimer = undefined;
1367
+ }
1368
+ }
1369
+ scheduleForcedFalsePublishRetry(retryAttempt = 0) {
1370
+ this.scheduleFalsePublishRetry(retryAttempt, {
1371
+ requireDisabled: true
1372
+ });
1373
+ }
1374
+
1375
+ /**
1376
+ * Retries compensating false publish after rollback failure, regardless of enabled flag.
1377
+ * @private
1378
+ */
1379
+ scheduleCompensatingFalsePublishRetry(retryAttempt = 0) {
1380
+ this.scheduleFalsePublishRetry(retryAttempt);
1381
+ }
1382
+ scheduleFalsePublishRetry(retryAttempt = 0, options = {}) {
1383
+ if (retryAttempt >= ContactCenter.WXAPP_FALSE_PUBLISH_MAX_RETRIES) {
1384
+ return;
1385
+ }
1386
+ if (options?.requireDisabled && this.isWxBetterTogetherEnabled()) {
1387
+ return;
1388
+ }
1389
+ this.clearWxAppFalsePublishRetryTimer();
1390
+ this.wxAppFalsePublishRetryTimer = setTimeout(async () => {
1391
+ this.wxAppFalsePublishRetryTimer = undefined;
1392
+ if (options?.requireDisabled && this.isWxBetterTogetherEnabled()) {
1393
+ return;
1394
+ }
1395
+ if (!options?.requireDisabled && this.isWxBetterTogetherEnabled() && this.webexCrossClientService.isAnswerCallsStateActive()) {
1396
+ return;
1397
+ }
1398
+ try {
1399
+ await this.ensureWxAppDeviceRegistered();
1400
+ await this.publishAnswerOnWebexCrossClientState(false, {
1401
+ force: true
1402
+ });
1403
+ this.webexCrossClientService.teardown();
1404
+ this.wxAppTelephonyMercurySync.unsubscribe();
1405
+ await this.releaseWxAppMercuryResources();
1406
+ } catch (retryError) {
1407
+ _loggerProxy.default.error(`Failed to retry forced answer-calls-on-wxcc false publish: ${retryError}`, {
1408
+ module: _constants.CC_FILE,
1409
+ method: _constants.METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC
1410
+ });
1411
+ this.scheduleFalsePublishRetry(retryAttempt + 1, options);
1412
+ }
1413
+ }, ContactCenter.WXAPP_FALSE_PUBLISH_RETRY_DELAY_MS);
1414
+ }
1415
+
1416
+ /**
1417
+ * Best-effort rollback when usersub true was published but wxApp setup did not complete.
1418
+ * @private
1419
+ */
1420
+ async revertWxAppCrossClientPublish() {
1421
+ try {
1422
+ await this.publishAnswerOnWebexCrossClientState(false, {
1423
+ force: true
1424
+ });
1425
+ } catch (error) {
1426
+ _loggerProxy.default.error(`Failed to publish compensating answer-calls-on-wxcc false during wxApp rollback: ${error}`, {
1427
+ module: _constants.CC_FILE,
1428
+ method: _constants.METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC
1429
+ });
1430
+ this.scheduleCompensatingFalsePublishRetry();
1431
+ }
1432
+ this.webexCrossClientService.teardown();
1433
+ this.wxAppTelephonyMercurySync.unsubscribe();
1434
+ await this.releaseWxAppMercuryResources();
1435
+ }
1436
+ async ensureWxAppDeviceRegistered() {
1437
+ const device = this.$webex.internal.device;
1438
+ if (device && !device.registered && device.register) {
1439
+ await device.register();
1440
+ this.wxAppDeviceRegisteredByCc = true;
1441
+ _loggerProxy.default.log('WxApp: device registered for cross-client publish', {
1442
+ module: _constants.CC_FILE,
1443
+ method: _constants.METHODS.ENSURE_WXAPP_MERCURY_CONNECTED
1444
+ });
1445
+ }
1446
+ }
1447
+ async ensureWxAppMercuryConnected() {
1448
+ const mercury = this.$webex.internal.mercury;
1449
+ await this.ensureWxAppDeviceRegistered();
1450
+ if (mercury && !mercury.connected) {
1451
+ await mercury.connect();
1452
+ this.wxAppMercuryConnectedByCc = true;
1453
+ _loggerProxy.default.log('WxApp mute sync: mercury connected', {
1454
+ module: _constants.CC_FILE,
1455
+ method: _constants.METHODS.ENSURE_WXAPP_MERCURY_CONNECTED
1456
+ });
1457
+ }
1458
+ }
1459
+
1460
+ /**
1461
+ * Disconnects Mercury and unregisters the device when wxApp mute sync opened them.
1462
+ * @private
1463
+ */
1464
+ async releaseWxAppMercuryResources() {
1465
+ const mercury = this.$webex.internal.mercury;
1466
+ const device = this.$webex.internal.device;
1467
+ if (this.wxAppMercuryConnectedByCc && mercury?.connected) {
1468
+ try {
1469
+ await mercury.disconnect();
1470
+ this.wxAppMercuryConnectedByCc = false;
1471
+ _loggerProxy.default.log('WxApp mute sync: mercury disconnected', {
1472
+ module: _constants.CC_FILE,
1473
+ method: _constants.METHODS.ENSURE_WXAPP_MERCURY_CONNECTED
1474
+ });
1475
+ } catch (error) {
1476
+ _loggerProxy.default.error(`Failed to disconnect wxApp Mercury: ${error}`, {
1477
+ module: _constants.CC_FILE,
1478
+ method: _constants.METHODS.ENSURE_WXAPP_MERCURY_CONNECTED
1479
+ });
1480
+ }
1481
+ }
1482
+ if (this.wxAppDeviceRegisteredByCc && device?.registered && device.unregister) {
1483
+ try {
1484
+ // @ts-ignore
1485
+ await device.unregister();
1486
+ this.wxAppDeviceRegisteredByCc = false;
1487
+ _loggerProxy.default.log('WxApp mute sync: device unregistered', {
1488
+ module: _constants.CC_FILE,
1489
+ method: _constants.METHODS.ENSURE_WXAPP_MERCURY_CONNECTED
1490
+ });
1491
+ } catch (error) {
1492
+ _loggerProxy.default.error(`Failed to unregister wxApp device: ${error}`, {
1493
+ module: _constants.CC_FILE,
1494
+ method: _constants.METHODS.ENSURE_WXAPP_MERCURY_CONNECTED
1495
+ });
1496
+ }
1497
+ } else if (this.wxAppDeviceRegisteredByCc) {
1498
+ this.wxAppDeviceRegisteredByCc = false;
1499
+ }
1500
+ }
1501
+
1502
+ /**
1503
+ * Clears wxApp runtime state and feature-owned resources. Publish failures are logged;
1504
+ * optional rethrow for deregister when callers need to surface usersub errors.
1505
+ * @private
1506
+ */
1507
+ async teardownWxAppLocalState(options) {
1508
+ this.clearWxAppFalsePublishRetryTimer();
1509
+ let publishError;
1510
+ try {
1511
+ const shouldForceFalsePublish = this.isWxBetterTogetherEnabled() || this.webexCrossClientService.isAnswerCallsStateActive();
1512
+ await this.publishAnswerOnWebexCrossClientState(false, {
1513
+ force: shouldForceFalsePublish
1514
+ });
1515
+ } catch (error) {
1516
+ publishError = error;
1517
+ _loggerProxy.default.error(`Failed to publish answer-calls-on-wxcc false during wxApp teardown: ${error}`, {
1518
+ module: _constants.CC_FILE,
1519
+ method: _constants.METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC
1520
+ });
1521
+ }
1522
+ this.webexCrossClientService.teardown();
1523
+ this.wxAppTelephonyMercurySync.unsubscribe();
1524
+ await this.releaseWxAppMercuryResources();
1525
+ if (options?.clearInitFlag) {
1526
+ this.resetEnableWxBetterTogetherConfig();
1527
+ }
1528
+ if (publishError) {
1529
+ this.scheduleCompensatingFalsePublishRetry();
1530
+ }
1531
+ if (options?.rethrowPublishError && publishError) {
1532
+ throw publishError;
1533
+ }
1534
+ return {
1535
+ publishError: publishError ?? undefined
1536
+ };
1537
+ }
1538
+ async ensureWxAppMercuryAndSubscribe() {
1539
+ if (!this.isWxBetterTogetherEnabled()) {
1540
+ return;
1541
+ }
1542
+ if (this.getCurrentStationLoginOption() === _types.LoginOption.BROWSER) {
1543
+ return;
1544
+ }
1545
+ const agentId = this.agentConfig?.agentId;
1546
+ if (!agentId) {
1547
+ _loggerProxy.default.error('Cannot subscribe wxApp mute sync: agentId unavailable', {
1548
+ module: _constants.CC_FILE,
1549
+ method: _constants.METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY
1550
+ });
1551
+ return;
1552
+ }
1553
+ try {
1554
+ await this.ensureWxAppMercuryConnected();
1555
+ this.wxAppTelephonyMercurySync.subscribe(agentId, (callId, muted) => {
1556
+ this.taskManager.applyWxAppMuteStateFromSync(callId, muted);
1557
+ });
1558
+ } catch (error) {
1559
+ _loggerProxy.default.error(`Failed to ensure wxApp Mercury mute sync: ${error}`, {
1560
+ module: _constants.CC_FILE,
1561
+ method: _constants.METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY
1562
+ });
1563
+ this.wxAppTelephonyMercurySync.unsubscribe();
1564
+ await this.releaseWxAppMercuryResources();
1565
+ throw error;
1566
+ }
1567
+ }
1568
+ async publishAnswerOnWebexCrossClientState(enable, options) {
1569
+ if (enable && !this.isWxBetterTogetherEnabled()) {
1570
+ return;
1571
+ }
1572
+ if (!enable && !options?.force && !this.webexCrossClientService.isAnswerCallsStateActive()) {
1573
+ return;
1574
+ }
1575
+ const userId = this.$webex.internal.device?.userId;
1576
+ if (!userId) {
1577
+ return;
1578
+ }
1579
+ await this.webexCrossClientService.setManageWebexCallingInWxcc(enable, {
1580
+ userId
1581
+ });
1582
+ if (enable) {
1583
+ this.clearWxAppFalsePublishRetryTimer();
1584
+ }
1585
+ }
1586
+
1173
1587
  /**
1174
1588
  * Initializes event listeners for the Contact Center service
1175
1589
  * Sets up handlers for connection state changes and other core events
@@ -1268,6 +1682,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
1268
1682
  }
1269
1683
  this.agentConfig.lastStateAuxCodeId = auxCodeId;
1270
1684
  this.agentConfig.isAgentLoggedIn = true;
1685
+ await this.ensureWxAppPostStationLogin();
1271
1686
  _loggerProxy.default.log(`Silent relogin process completed successfully with login Option: ${reLoginResponse.data.deviceType} teamId: ${reLoginResponse.data.teamId}`, {
1272
1687
  module: _constants.CC_FILE,
1273
1688
  method: _constants.METHODS.SILENT_RELOGIN,