@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.
- package/.sdd/manifest.json +2 -1
- package/ai-docs/CONTRACTS.md +2 -0
- package/ai-docs/contact-center-spec.md +13 -0
- package/dist/cc.js +416 -1
- package/dist/cc.js.map +1 -1
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +7 -1
- package/dist/constants.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +174 -0
- package/dist/services/AnswerCallOnWebexService.js.map +1 -0
- package/dist/services/WebexCrossClientService.js +171 -0
- package/dist/services/WebexCrossClientService.js.map +1 -0
- package/dist/services/WxAppTelephonyMercurySync.js +93 -0
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
- package/dist/services/task/Task.js +60 -5
- package/dist/services/task/Task.js.map +1 -1
- package/dist/services/task/TaskFactory.js +4 -2
- package/dist/services/task/TaskFactory.js.map +1 -1
- package/dist/services/task/TaskManager.js +130 -17
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/WebexCallingUtils.js +70 -0
- package/dist/services/task/WebexCallingUtils.js.map +1 -0
- package/dist/services/task/state-machine/TaskStateMachine.js +74 -6
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
- package/dist/services/task/state-machine/types.js.map +1 -1
- package/dist/services/task/state-machine/uiControlsComputer.js +68 -13
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
- package/dist/services/task/types.js +6 -0
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +280 -13
- package/dist/services/task/voice/Voice.js.map +1 -1
- package/dist/services/task/voice/WebRTC.js +4 -1
- package/dist/services/task/voice/WebRTC.js.map +1 -1
- package/dist/services/task/voice/wxAppVoiceMethods.js +201 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
- package/dist/services/wxAppTelephonyUtils.js +19 -0
- package/dist/services/wxAppTelephonyUtils.js.map +1 -0
- package/dist/types/cc.d.ts +73 -0
- package/dist/types/config.d.ts +7 -0
- package/dist/types/constants.d.ts +6 -0
- package/dist/types/services/AnswerCallOnWebexService.d.ts +37 -0
- package/dist/types/services/WebexCrossClientService.d.ts +28 -0
- package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
- package/dist/types/services/task/Task.d.ts +19 -2
- package/dist/types/services/task/TaskFactory.d.ts +2 -1
- package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +158 -4
- package/dist/types/services/task/state-machine/types.d.ts +6 -0
- package/dist/types/services/task/types.d.ts +52 -3
- package/dist/types/services/task/voice/Voice.d.ts +41 -9
- package/dist/types/services/task/voice/WebRTC.d.ts +2 -2
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +52 -0
- package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
- package/dist/types/types.d.ts +2 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +3 -3
- package/src/cc.ts +484 -0
- package/src/config.ts +7 -0
- package/src/constants.ts +6 -0
- package/src/services/AnswerCallOnWebexService.ts +206 -0
- package/src/services/WebexCrossClientService.ts +212 -0
- package/src/services/WxAppTelephonyMercurySync.ts +115 -0
- package/src/services/ai-docs/services-spec.md +1 -0
- package/src/services/task/Task.ts +59 -5
- package/src/services/task/TaskFactory.ts +5 -1
- package/src/services/task/TaskManager.ts +172 -12
- package/src/services/task/WebexCallingUtils.ts +136 -0
- package/src/services/task/ai-docs/task-spec.md +8 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +99 -4
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +15 -6
- package/src/services/task/state-machine/types.ts +6 -0
- package/src/services/task/state-machine/uiControlsComputer.ts +117 -29
- package/src/services/task/types.ts +71 -3
- package/src/services/task/voice/Voice.ts +367 -12
- package/src/services/task/voice/WebRTC.ts +5 -1
- package/src/services/task/voice/wxAppVoiceMethods.ts +307 -0
- package/src/services/wxAppTelephonyUtils.ts +14 -0
- package/src/types.ts +18 -1
- package/test/unit/spec/cc.ts +1230 -0
- package/test/unit/spec/services/AnswerCallOnWebexService.ts +223 -0
- package/test/unit/spec/services/WebexCrossClientService.ts +261 -0
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +113 -0
- package/test/unit/spec/services/task/TaskManager.ts +405 -1
- package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +261 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +292 -0
- package/test/unit/spec/services/task/voice/Voice.ts +716 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +459 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
package/.sdd/manifest.json
CHANGED
|
@@ -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"
|
package/ai-docs/CONTRACTS.md
CHANGED
|
@@ -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,
|