@webex/contact-center 3.12.0-llmrefactor.2 → 3.12.0-llmrefactor.3
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 +5 -4
- package/ai-docs/ARCHITECTURE.md +1 -1
- package/ai-docs/CONTRACTS.md +5 -1
- package/ai-docs/SECURITY.md +1 -1
- package/ai-docs/contact-center-spec.md +22 -4
- package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
- package/dist/cc.js +440 -10
- package/dist/cc.js.map +1 -1
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.js +10 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +13 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +2 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AddressBook.js +18 -15
- package/dist/services/AddressBook.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +174 -0
- package/dist/services/AnswerCallOnWebexService.js.map +1 -0
- package/dist/services/EntryPoint.js +46 -68
- package/dist/services/EntryPoint.js.map +1 -1
- package/dist/services/Queue.js +27 -22
- package/dist/services/Queue.js.map +1 -1
- 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/config/Util.js +3 -0
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +10 -6
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/types.js +4 -0
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/WebexRequest.js +6 -2
- package/dist/services/core/WebexRequest.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +28 -14
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/types.js.map +1 -1
- package/dist/services/task/Task.js +73 -7
- package/dist/services/task/Task.js.map +1 -1
- package/dist/services/task/TaskFactory.js +8 -4
- package/dist/services/task/TaskFactory.js.map +1 -1
- package/dist/services/task/TaskManager.js +176 -20
- 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/constants.js +4 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/contact.js +29 -0
- package/dist/services/task/contact.js.map +1 -1
- package/dist/services/task/digital/Digital.js +3 -2
- package/dist/services/task/digital/Digital.js.map +1 -1
- package/dist/services/task/state-machine/TaskStateMachine.js +135 -37
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
- package/dist/services/task/state-machine/actions.js +6 -1
- package/dist/services/task/state-machine/actions.js.map +1 -1
- package/dist/services/task/state-machine/guards.js +38 -11
- package/dist/services/task/state-machine/guards.js.map +1 -1
- package/dist/services/task/state-machine/types.js.map +1 -1
- package/dist/services/task/state-machine/uiControlsComputer.js +118 -18
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
- package/dist/services/task/types.js +16 -1
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +350 -14
- 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 +77 -4
- package/dist/types/config.d.ts +7 -0
- package/dist/types/constants.d.ts +8 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/metrics/constants.d.ts +2 -0
- package/dist/types/services/AddressBook.d.ts +2 -1
- package/dist/types/services/AnswerCallOnWebexService.d.ts +37 -0
- package/dist/types/services/EntryPoint.d.ts +7 -5
- package/dist/types/services/Queue.d.ts +5 -3
- package/dist/types/services/WebexCrossClientService.d.ts +28 -0
- package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
- package/dist/types/services/config/constants.d.ts +8 -5
- package/dist/types/services/config/types.d.ts +17 -4
- package/dist/types/services/core/Err.d.ts +2 -0
- package/dist/types/services/core/WebexRequest.d.ts +1 -0
- package/dist/types/services/core/types.d.ts +2 -0
- package/dist/types/services/task/Task.d.ts +20 -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/constants.d.ts +3 -0
- package/dist/types/services/task/contact.d.ts +4 -0
- package/dist/types/services/task/digital/Digital.d.ts +2 -2
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +260 -56
- package/dist/types/services/task/state-machine/guards.d.ts +6 -5
- package/dist/types/services/task/state-machine/types.d.ts +9 -1
- package/dist/types/services/task/types.d.ts +81 -4
- package/dist/types/services/task/voice/Voice.d.ts +48 -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 +14 -8
- package/dist/types/utils/PageCache.d.ts +20 -3
- package/dist/types.js +7 -2
- package/dist/types.js.map +1 -1
- package/dist/utils/PageCache.js +19 -5
- package/dist/utils/PageCache.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +1 -1
- package/src/cc.ts +506 -9
- package/src/config.ts +7 -0
- package/src/constants.ts +8 -0
- package/src/index.ts +2 -0
- package/src/metrics/ai-docs/metrics-spec.md +9 -3
- package/src/metrics/behavioral-events.ts +14 -0
- package/src/metrics/constants.ts +2 -0
- package/src/services/AddressBook.ts +17 -6
- package/src/services/AnswerCallOnWebexService.ts +206 -0
- package/src/services/EntryPoint.ts +59 -60
- package/src/services/Queue.ts +29 -12
- package/src/services/WebexCrossClientService.ts +212 -0
- package/src/services/WxAppTelephonyMercurySync.ts +115 -0
- package/src/services/ai-docs/AGENTS.md +10 -10
- package/src/services/ai-docs/services-spec.md +6 -1
- package/src/services/config/Util.ts +3 -0
- package/src/services/config/ai-docs/AGENTS.md +1 -1
- package/src/services/config/ai-docs/ARCHITECTURE.md +2 -2
- package/src/services/config/ai-docs/config-spec.md +6 -0
- package/src/services/config/constants.ts +10 -6
- package/src/services/config/types.ts +16 -4
- package/src/services/core/Err.ts +1 -0
- package/src/services/core/WebexRequest.ts +3 -1
- package/src/services/core/ai-docs/core-spec.md +5 -1
- package/src/services/core/aqm-reqs.ts +30 -15
- package/src/services/core/types.ts +2 -0
- package/src/services/task/Task.ts +79 -8
- package/src/services/task/TaskFactory.ts +8 -3
- package/src/services/task/TaskManager.ts +254 -15
- package/src/services/task/WebexCallingUtils.ts +136 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +1 -0
- package/src/services/task/ai-docs/task-spec.md +117 -2
- package/src/services/task/constants.ts +3 -0
- package/src/services/task/contact.ts +30 -0
- package/src/services/task/digital/Digital.ts +4 -2
- package/src/services/task/state-machine/TaskStateMachine.ts +210 -63
- package/src/services/task/state-machine/actions.ts +5 -2
- package/src/services/task/state-machine/ai-docs/AGENTS.md +8 -4
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +17 -8
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +46 -22
- package/src/services/task/state-machine/guards.ts +64 -11
- package/src/services/task/state-machine/types.ts +16 -1
- package/src/services/task/state-machine/uiControlsComputer.ts +206 -32
- package/src/services/task/types.ts +114 -4
- package/src/services/task/voice/Voice.ts +462 -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 +33 -9
- package/src/utils/AGENTS.md +21 -10
- package/src/utils/PageCache.ts +38 -5
- package/src/utils/ai-docs/utils-spec.md +21 -11
- package/test/unit/spec/cc.ts +1274 -0
- package/test/unit/spec/metrics/behavioral-events.ts +18 -0
- package/test/unit/spec/services/AddressBook.ts +37 -6
- package/test/unit/spec/services/AnswerCallOnWebexService.ts +223 -0
- package/test/unit/spec/services/EntryPoint.ts +87 -40
- package/test/unit/spec/services/Queue.ts +123 -12
- package/test/unit/spec/services/WebexCrossClientService.ts +261 -0
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +113 -0
- package/test/unit/spec/services/config/Util.ts +85 -0
- package/test/unit/spec/services/core/WebexRequest.ts +3 -1
- package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
- package/test/unit/spec/services/task/Task.ts +200 -0
- package/test/unit/spec/services/task/TaskFactory.ts +39 -2
- package/test/unit/spec/services/task/TaskManager.ts +653 -1
- package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
- package/test/unit/spec/services/task/contact.ts +33 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +854 -72
- package/test/unit/spec/services/task/state-machine/guards.ts +210 -8
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +445 -7
- package/test/unit/spec/services/task/voice/Voice.ts +874 -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
|
@@ -12,8 +12,11 @@ import {
|
|
|
12
12
|
ResumeRecordingPayload,
|
|
13
13
|
TaskData,
|
|
14
14
|
TaskResponse,
|
|
15
|
+
DropConferenceParticipantPayload,
|
|
15
16
|
IVoice,
|
|
16
17
|
VoiceUIControlOptions,
|
|
18
|
+
TaskToggleMuteOptions,
|
|
19
|
+
TaskTransmitDtmfOptions,
|
|
17
20
|
TransferPayLoad,
|
|
18
21
|
ConsultTransferPayLoad,
|
|
19
22
|
consultConferencePayloadData,
|
|
@@ -28,8 +31,35 @@ import {METRIC_EVENT_NAMES} from '../../../metrics/constants';
|
|
|
28
31
|
import {TaskState, TaskEvent, TaskActionArgs} from '../state-machine';
|
|
29
32
|
import {WrapupData} from '../../config/types';
|
|
30
33
|
import {getConsultMediaResourceId, getIsConferenceInProgress} from '../TaskUtils';
|
|
34
|
+
import AnswerCallOnWebexService from '../../AnswerCallOnWebexService';
|
|
35
|
+
import {isWxAppCallNotFoundError} from '../../wxAppTelephonyUtils';
|
|
36
|
+
import {
|
|
37
|
+
getCallingDeviceDetails,
|
|
38
|
+
getWebexCallingCallId,
|
|
39
|
+
resolveWxAppLineOwnerId,
|
|
40
|
+
isWebexAppCallingOffer as wxIsWebexAppCallingOffer,
|
|
41
|
+
isWebexAppInboundCallingOffer as wxIsWebexAppInboundCallingOffer,
|
|
42
|
+
mapWxAppVoiceError,
|
|
43
|
+
runWxAppAccept,
|
|
44
|
+
runWxAppReject,
|
|
45
|
+
runWxAppToggleMute,
|
|
46
|
+
runWxAppTransmitDtmf,
|
|
47
|
+
WxAppVoiceDependencies,
|
|
48
|
+
WxAppVoiceLifecycle,
|
|
49
|
+
} from './wxAppVoiceMethods';
|
|
31
50
|
|
|
32
51
|
export default class Voice extends Task implements IVoice {
|
|
52
|
+
private static readonly WXAPP_MUTE_SYNC_RETRY_DELAY_MS = 50;
|
|
53
|
+
private static readonly WXAPP_MUTE_SYNC_MAX_RETRIES = 3;
|
|
54
|
+
|
|
55
|
+
private answerCallOnWebexService?: AnswerCallOnWebexService;
|
|
56
|
+
private enableWxBetterTogether = false;
|
|
57
|
+
private wxAppMuted = false;
|
|
58
|
+
private wxAppAnswerPending = false;
|
|
59
|
+
private wxAppAcceptInFlight = false;
|
|
60
|
+
private wxAppMuteSyncInFlight?: Promise<boolean | undefined>;
|
|
61
|
+
private wxAppMuteToggleInFlight?: Promise<void>;
|
|
62
|
+
|
|
33
63
|
constructor(
|
|
34
64
|
contact: ReturnType<typeof routingContact>,
|
|
35
65
|
data: TaskData,
|
|
@@ -42,6 +72,7 @@ export default class Voice extends Task implements IVoice {
|
|
|
42
72
|
isEndConsultEnabled: callOptions?.isEndConsultEnabled ?? true,
|
|
43
73
|
voiceVariant: callOptions?.voiceVariant ?? VOICE_VARIANT.PSTN,
|
|
44
74
|
isRecordingEnabled: callOptions?.isRecordingEnabled ?? true,
|
|
75
|
+
enableWxBetterTogether: callOptions?.enableWxBetterTogether ?? false,
|
|
45
76
|
};
|
|
46
77
|
|
|
47
78
|
super(
|
|
@@ -49,10 +80,232 @@ export default class Voice extends Task implements IVoice {
|
|
|
49
80
|
data,
|
|
50
81
|
{
|
|
51
82
|
...resolvedOptions,
|
|
83
|
+
consultTransferConfig: callOptions?.consultTransferConfig,
|
|
52
84
|
},
|
|
53
85
|
wrapupData,
|
|
54
86
|
agentId
|
|
55
87
|
);
|
|
88
|
+
|
|
89
|
+
this.enableWxBetterTogether = resolvedOptions.enableWxBetterTogether;
|
|
90
|
+
this.answerCallOnWebexService = callOptions?.answerCallOnWebexService;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private getWxAppVoiceDependencies(): WxAppVoiceDependencies {
|
|
94
|
+
return {
|
|
95
|
+
enableWxBetterTogether: this.enableWxBetterTogether,
|
|
96
|
+
answerCallOnWebexService: this.answerCallOnWebexService,
|
|
97
|
+
agentId: this.agentId,
|
|
98
|
+
getTaskData: () => this.data,
|
|
99
|
+
getTaskState: () => this.stateMachineService?.getSnapshot?.()?.value as TaskState | undefined,
|
|
100
|
+
getWxAppMuted: () => this.wxAppMuted,
|
|
101
|
+
setWxAppMuted: (muted: boolean) => {
|
|
102
|
+
this.wxAppMuted = muted;
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @deprecated Use {@link Voice.getWxAppVoiceDependencies} */
|
|
108
|
+
private getWxAppVoiceDeps(): WxAppVoiceDependencies {
|
|
109
|
+
return this.getWxAppVoiceDependencies();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public isWebexAppCallingOffer(): boolean {
|
|
113
|
+
return wxIsWebexAppCallingOffer(this.getWxAppVoiceDeps());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public isWebexAppInboundCallingOffer(): boolean {
|
|
117
|
+
return wxIsWebexAppInboundCallingOffer(this.getWxAppVoiceDeps());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private setWxAppAnswerPending(pending: boolean): void {
|
|
121
|
+
this.wxAppAnswerPending = pending;
|
|
122
|
+
this.uiControlConfig = {...this.uiControlConfig, wxAppAnswerPending: pending};
|
|
123
|
+
this.updateUiControls(true);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private setWxAppAcceptInFlight(inFlight: boolean): void {
|
|
127
|
+
this.wxAppAcceptInFlight = inFlight;
|
|
128
|
+
this.uiControlConfig = {...this.uiControlConfig, wxAppAcceptInFlight: inFlight};
|
|
129
|
+
this.updateUiControls(true);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public setEnableWxBetterTogether(enabled: boolean): void {
|
|
133
|
+
this.enableWxBetterTogether = enabled;
|
|
134
|
+
this.uiControlConfig = {...this.uiControlConfig, enableWxBetterTogether: enabled};
|
|
135
|
+
this.updateUiControls(true);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public applyWxAppMuteStateFromSync(incomingCallId: string, muted: boolean): void {
|
|
139
|
+
if (!this.enableWxBetterTogether) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const activeCallId = this.getWebexCallingCallId();
|
|
144
|
+
if (!activeCallId || !incomingCallId.endsWith(activeCallId) || this.wxAppMuted === muted) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.wxAppMuted = muted;
|
|
149
|
+
this.emit(TASK_EVENTS.TASK_WXAPP_MUTE_STATE_UPDATED, {muted});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public getWxAppMuted(): boolean {
|
|
153
|
+
return this.wxAppMuted;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private static delay(ms: number): Promise<void> {
|
|
157
|
+
return new Promise((resolve) => {
|
|
158
|
+
setTimeout(resolve, ms);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private canRetryWxAppMuteSync(): boolean {
|
|
163
|
+
if (!this.enableWxBetterTogether) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
Boolean(getCallingDeviceDetails(this.getWxAppVoiceDeps())?.deviceCallId) &&
|
|
169
|
+
!this.getWebexCallingCallId()
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private shouldSkipWxAppMuteSync(): boolean {
|
|
174
|
+
if (this.data?.interaction?.isTerminated) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return this.isWebexAppCallingOffer() && !this.uiControlConfig?.wxAppAnswerPending;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private applyWxAppMuteFromBackfill(callId: string, muted: boolean): void {
|
|
182
|
+
if (!this.enableWxBetterTogether || this.wxAppMuted === muted) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const activeCallId = this.getWebexCallingCallId();
|
|
187
|
+
if (activeCallId && !callId.endsWith(activeCallId) && !activeCallId.endsWith(callId)) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.wxAppMuted = muted;
|
|
192
|
+
this.emit(TASK_EVENTS.TASK_WXAPP_MUTE_STATE_UPDATED, {muted});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public async syncWxAppMuteFromCallDetails(retryAttempt = 0): Promise<boolean | undefined> {
|
|
196
|
+
if (!this.enableWxBetterTogether || !this.answerCallOnWebexService) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (retryAttempt === 0 && this.shouldSkipWxAppMuteSync()) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (retryAttempt === 0 && this.wxAppMuteSyncInFlight) {
|
|
205
|
+
return this.wxAppMuteSyncInFlight;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const syncPromise = this.performWxAppMuteSync(retryAttempt);
|
|
209
|
+
|
|
210
|
+
if (retryAttempt === 0) {
|
|
211
|
+
this.wxAppMuteSyncInFlight = syncPromise;
|
|
212
|
+
syncPromise
|
|
213
|
+
.finally(() => {
|
|
214
|
+
if (this.wxAppMuteSyncInFlight === syncPromise) {
|
|
215
|
+
this.wxAppMuteSyncInFlight = undefined;
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
.catch(() => undefined);
|
|
219
|
+
|
|
220
|
+
return syncPromise;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return syncPromise;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private async performWxAppMuteSync(retryAttempt: number): Promise<boolean | undefined> {
|
|
227
|
+
if (this.shouldSkipWxAppMuteSync()) {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let callId = this.getWebexCallingCallId();
|
|
232
|
+
if (!callId) {
|
|
233
|
+
if (retryAttempt < Voice.WXAPP_MUTE_SYNC_MAX_RETRIES && this.canRetryWxAppMuteSync()) {
|
|
234
|
+
await Voice.delay(Voice.WXAPP_MUTE_SYNC_RETRY_DELAY_MS);
|
|
235
|
+
|
|
236
|
+
return this.performWxAppMuteSync(retryAttempt + 1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
callId = getCallingDeviceDetails(this.getWxAppVoiceDeps())?.deviceCallId ?? null;
|
|
240
|
+
if (!callId) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
const lineOwnerId = resolveWxAppLineOwnerId(this.getWxAppVoiceDeps());
|
|
247
|
+
const details = await this.answerCallOnWebexService.getCallDetails({callId, lineOwnerId});
|
|
248
|
+
if (details.muted !== undefined) {
|
|
249
|
+
this.applyWxAppMuteStateFromSync(callId, details.muted);
|
|
250
|
+
if (this.wxAppMuted !== details.muted) {
|
|
251
|
+
this.applyWxAppMuteFromBackfill(callId, details.muted);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return details.muted;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return this.wxAppMuted;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (!isWxAppCallNotFoundError(error)) {
|
|
260
|
+
LoggerProxy.error(`Failed to sync wxApp mute from call details: ${error}`, {
|
|
261
|
+
module: CC_FILE,
|
|
262
|
+
method: METHODS.GET_CALL_DETAILS_ON_WEBEX,
|
|
263
|
+
interactionId: this.data?.interactionId,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
protected onTaskAssigned(): void {
|
|
272
|
+
this.setWxAppAnswerPending(false);
|
|
273
|
+
queueMicrotask(() => {
|
|
274
|
+
this.syncWxAppMuteFromCallDetails().catch(() => undefined);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
protected onTaskHydrated(): void {
|
|
279
|
+
const state = this.stateMachineService?.getSnapshot?.()?.value as TaskState | undefined;
|
|
280
|
+
const answerStillPending =
|
|
281
|
+
state === TaskState.OFFERED &&
|
|
282
|
+
(this.uiControlConfig?.wxAppAcceptInFlight || this.uiControlConfig?.wxAppAnswerPending);
|
|
283
|
+
|
|
284
|
+
if (!answerStillPending) {
|
|
285
|
+
this.setWxAppAnswerPending(false);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
this.syncWxAppMuteFromCallDetails().catch(() => undefined);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public getCallingDeviceDetails() {
|
|
292
|
+
return getCallingDeviceDetails(this.getWxAppVoiceDeps());
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
public getWebexCallingCallId(): string | null {
|
|
296
|
+
return getWebexCallingCallId(this.getWxAppVoiceDeps());
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private createWxAppLifecycle(): WxAppVoiceLifecycle {
|
|
300
|
+
return {
|
|
301
|
+
setWxAppAcceptInFlight: (inFlight) => this.setWxAppAcceptInFlight(inFlight),
|
|
302
|
+
setWxAppAnswerPending: (pending) => this.setWxAppAnswerPending(pending),
|
|
303
|
+
resetWxAppMuted: () => {
|
|
304
|
+
this.wxAppMuted = false;
|
|
305
|
+
},
|
|
306
|
+
syncWxAppMuteFromCallDetails: () => this.syncWxAppMuteFromCallDetails(),
|
|
307
|
+
mapWxAppVoiceError: (error, method) => mapWxAppVoiceError(error, method, CC_FILE),
|
|
308
|
+
};
|
|
56
309
|
}
|
|
57
310
|
|
|
58
311
|
private getStateMachineSnapshot() {
|
|
@@ -60,23 +313,123 @@ export default class Voice extends Task implements IVoice {
|
|
|
60
313
|
}
|
|
61
314
|
|
|
62
315
|
/**
|
|
63
|
-
*
|
|
64
|
-
* It is expected to be overridden by child classes.
|
|
65
|
-
* @returns Promise<TaskResponse>
|
|
66
|
-
* @throws Error
|
|
316
|
+
* Accepts the task. Routes to wxApp telephony answer when `enableWxBetterTogether` and wxApp offer.
|
|
67
317
|
*/
|
|
68
318
|
public async accept(): Promise<TaskResponse> {
|
|
69
|
-
|
|
319
|
+
if (this.enableWxBetterTogether && this.isWebexAppCallingOffer()) {
|
|
320
|
+
await runWxAppAccept(this.getWxAppVoiceDependencies(), this.createWxAppLifecycle());
|
|
321
|
+
|
|
322
|
+
return Promise.resolve();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return super.unsupportedMethodError(METHODS.ACCEPT);
|
|
70
326
|
}
|
|
71
327
|
|
|
72
328
|
/**
|
|
73
|
-
*
|
|
74
|
-
* It is expected to be overridden by child classes.
|
|
75
|
-
* @returns Promise<TaskResponse>
|
|
76
|
-
* @throws Error
|
|
329
|
+
* Declines the task. Routes wxApp inbound to telephony reject; wxApp outdial offer to cancelTask.
|
|
77
330
|
*/
|
|
78
331
|
public async decline(): Promise<TaskResponse> {
|
|
79
|
-
|
|
332
|
+
if (this.enableWxBetterTogether && this.isWebexAppInboundCallingOffer()) {
|
|
333
|
+
await runWxAppReject(this.getWxAppVoiceDependencies(), this.createWxAppLifecycle());
|
|
334
|
+
|
|
335
|
+
return Promise.resolve();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const isOutdial = this.data?.interaction?.outboundType === 'OUTDIAL';
|
|
339
|
+
if (!this.enableWxBetterTogether || !isOutdial || !this.isWebexAppCallingOffer()) {
|
|
340
|
+
return super.unsupportedMethodError(METHODS.REJECT);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const interactionId = this.data.interactionId;
|
|
344
|
+
LoggerProxy.log(`Declining wxApp outdial task for taskId:${interactionId}`, {
|
|
345
|
+
module: CC_FILE,
|
|
346
|
+
method: METHODS.REJECT,
|
|
347
|
+
interactionId,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
this.metricsManager.timeEvent([
|
|
352
|
+
METRIC_EVENT_NAMES.TASK_DECLINE_SUCCESS,
|
|
353
|
+
METRIC_EVENT_NAMES.TASK_DECLINE_FAILED,
|
|
354
|
+
]);
|
|
355
|
+
|
|
356
|
+
this.setWxAppAnswerPending(false);
|
|
357
|
+
const response = await this.contact.cancelTask({interactionId});
|
|
358
|
+
|
|
359
|
+
this.metricsManager.trackEvent(
|
|
360
|
+
METRIC_EVENT_NAMES.TASK_DECLINE_SUCCESS,
|
|
361
|
+
{
|
|
362
|
+
taskId: interactionId,
|
|
363
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
|
|
364
|
+
},
|
|
365
|
+
['operational', 'behavioral']
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
return response;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
this.metricsManager.trackEvent(
|
|
371
|
+
METRIC_EVENT_NAMES.TASK_DECLINE_FAILED,
|
|
372
|
+
{
|
|
373
|
+
taskId: interactionId,
|
|
374
|
+
error: error.toString(),
|
|
375
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(
|
|
376
|
+
(error as any).details || {}
|
|
377
|
+
),
|
|
378
|
+
},
|
|
379
|
+
['operational', 'behavioral']
|
|
380
|
+
);
|
|
381
|
+
const {error: detailedError} = getErrorDetails(error, METHODS.REJECT, CC_FILE);
|
|
382
|
+
throw detailedError;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Toggles mute for wxApp engaged telephony when `enableWxBetterTogether` is active.
|
|
388
|
+
*/
|
|
389
|
+
public async toggleMute(options?: TaskToggleMuteOptions): Promise<void> {
|
|
390
|
+
if (this.enableWxBetterTogether && this.getWebexCallingCallId()) {
|
|
391
|
+
if (this.wxAppMuteToggleInFlight) {
|
|
392
|
+
await this.wxAppMuteToggleInFlight.catch(() => undefined);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const togglePromise = runWxAppToggleMute(
|
|
396
|
+
this.getWxAppVoiceDependencies(),
|
|
397
|
+
this.createWxAppLifecycle(),
|
|
398
|
+
options
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
this.wxAppMuteToggleInFlight = togglePromise;
|
|
402
|
+
togglePromise
|
|
403
|
+
.finally(() => {
|
|
404
|
+
if (this.wxAppMuteToggleInFlight === togglePromise) {
|
|
405
|
+
this.wxAppMuteToggleInFlight = undefined;
|
|
406
|
+
}
|
|
407
|
+
})
|
|
408
|
+
.catch(() => undefined);
|
|
409
|
+
|
|
410
|
+
await togglePromise;
|
|
411
|
+
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
super.unsupportedMethodError(METHODS.TOGGLE_MUTE);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Sends in-call DTMF for wxApp engaged telephony when `enableWxBetterTogether` is active.
|
|
420
|
+
*/
|
|
421
|
+
public async transmitDtmf(options: TaskTransmitDtmfOptions): Promise<void> {
|
|
422
|
+
if (this.enableWxBetterTogether && this.getWebexCallingCallId()) {
|
|
423
|
+
await runWxAppTransmitDtmf(
|
|
424
|
+
this.getWxAppVoiceDependencies(),
|
|
425
|
+
this.createWxAppLifecycle(),
|
|
426
|
+
options
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
super.unsupportedMethodError(METHODS.TRANSMIT_DTMF);
|
|
80
433
|
}
|
|
81
434
|
|
|
82
435
|
/**
|
|
@@ -827,6 +1180,99 @@ export default class Voice extends Task implements IVoice {
|
|
|
827
1180
|
}
|
|
828
1181
|
}
|
|
829
1182
|
|
|
1183
|
+
/**
|
|
1184
|
+
* Removes another participant from an active conference.
|
|
1185
|
+
* Completion is correlated with ParticipantLeftConference by the AQM request layer.
|
|
1186
|
+
* @param payload - Participant identifier to remove
|
|
1187
|
+
* @returns Promise<TaskResponse>
|
|
1188
|
+
*/
|
|
1189
|
+
public async dropConferenceParticipant(
|
|
1190
|
+
payload: DropConferenceParticipantPayload
|
|
1191
|
+
): Promise<TaskResponse> {
|
|
1192
|
+
// The TypeScript contract requires payload, but the published SDK is also callable from
|
|
1193
|
+
// untyped JavaScript. Keep an explicit runtime boundary check before direct property access.
|
|
1194
|
+
if (!payload) {
|
|
1195
|
+
throw new Error('participantId must be a non-empty string');
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
const {participantId} = payload;
|
|
1199
|
+
|
|
1200
|
+
if (typeof participantId !== 'string' || participantId.trim().length === 0) {
|
|
1201
|
+
throw new Error('participantId must be a non-empty string');
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const requestInteractionId =
|
|
1205
|
+
this.data.interaction?.mainInteractionId || this.data.interactionId;
|
|
1206
|
+
// taskId identifies the SDK task instance, which can remain child-interaction keyed after
|
|
1207
|
+
// consult/EP-DN flows. requestInteractionId identifies the main interaction sent to AQM.
|
|
1208
|
+
|
|
1209
|
+
LoggerProxy.info('Dropping conference participant', {
|
|
1210
|
+
module: CC_FILE,
|
|
1211
|
+
method: METHODS.DROP_CONFERENCE_PARTICIPANT,
|
|
1212
|
+
interactionId: requestInteractionId,
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
try {
|
|
1216
|
+
this.metricsManager.timeEvent([
|
|
1217
|
+
METRIC_EVENT_NAMES.TASK_CONFERENCE_PARTICIPANT_DROP_SUCCESS,
|
|
1218
|
+
METRIC_EVENT_NAMES.TASK_CONFERENCE_PARTICIPANT_DROP_FAILED,
|
|
1219
|
+
]);
|
|
1220
|
+
|
|
1221
|
+
const response = await this.contact.dropConferenceParticipant({
|
|
1222
|
+
interactionId: requestInteractionId,
|
|
1223
|
+
participantId,
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
this.metricsManager.trackEvent(
|
|
1227
|
+
METRIC_EVENT_NAMES.TASK_CONFERENCE_PARTICIPANT_DROP_SUCCESS,
|
|
1228
|
+
{
|
|
1229
|
+
taskId: this.data.interactionId,
|
|
1230
|
+
requestInteractionId,
|
|
1231
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
|
|
1232
|
+
agentId: this.agentId,
|
|
1233
|
+
},
|
|
1234
|
+
['operational', 'behavioral', 'business']
|
|
1235
|
+
);
|
|
1236
|
+
|
|
1237
|
+
LoggerProxy.log('Successfully dropped conference participant', {
|
|
1238
|
+
module: CC_FILE,
|
|
1239
|
+
method: METHODS.DROP_CONFERENCE_PARTICIPANT,
|
|
1240
|
+
trackingId: response?.trackingId,
|
|
1241
|
+
interactionId: requestInteractionId,
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
return response;
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
const failureTrackingFields = MetricsManager.getCommonTrackingFieldForAQMResponseFailed(
|
|
1247
|
+
error?.details || {}
|
|
1248
|
+
);
|
|
1249
|
+
|
|
1250
|
+
this.metricsManager.trackEvent(
|
|
1251
|
+
METRIC_EVENT_NAMES.TASK_CONFERENCE_PARTICIPANT_DROP_FAILED,
|
|
1252
|
+
{
|
|
1253
|
+
taskId: this.data.interactionId,
|
|
1254
|
+
requestInteractionId,
|
|
1255
|
+
agentId: this.agentId,
|
|
1256
|
+
trackingId: failureTrackingFields.trackingId,
|
|
1257
|
+
notifTrackingId: failureTrackingFields.notifTrackingId,
|
|
1258
|
+
orgId: failureTrackingFields.orgId,
|
|
1259
|
+
failureType: failureTrackingFields.failureType,
|
|
1260
|
+
reasonCode: failureTrackingFields.reasonCode,
|
|
1261
|
+
},
|
|
1262
|
+
['operational', 'behavioral', 'business']
|
|
1263
|
+
);
|
|
1264
|
+
|
|
1265
|
+
LoggerProxy.error('Failed to drop conference participant', {
|
|
1266
|
+
module: CC_FILE,
|
|
1267
|
+
method: METHODS.DROP_CONFERENCE_PARTICIPANT,
|
|
1268
|
+
trackingId: failureTrackingFields.trackingId,
|
|
1269
|
+
interactionId: requestInteractionId,
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
throw error;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
830
1276
|
/**
|
|
831
1277
|
* Exit from a conference call.
|
|
832
1278
|
* Per conference-spec.md:
|
|
@@ -1259,8 +1705,12 @@ export default class Voice extends Task implements IVoice {
|
|
|
1259
1705
|
{updateTaskData: true}
|
|
1260
1706
|
),
|
|
1261
1707
|
emitTaskOutdialFailed: ({event}: TaskActionArgs) => {
|
|
1262
|
-
|
|
1263
|
-
|
|
1708
|
+
const taskData = (event as {taskData?: TaskData})?.taskData;
|
|
1709
|
+
if (taskData) {
|
|
1710
|
+
this.updateTaskData(taskData);
|
|
1711
|
+
}
|
|
1712
|
+
if (taskData?.suppressOutdialFailedPopup) {
|
|
1713
|
+
return;
|
|
1264
1714
|
}
|
|
1265
1715
|
const reason = (event as {reason?: string})?.reason || 'Outdial failed';
|
|
1266
1716
|
this.emit(TASK_EVENTS.TASK_OUTDIAL_FAILED, reason);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
IWebRTC,
|
|
10
10
|
VoiceUIControlOptions,
|
|
11
11
|
VOICE_VARIANT,
|
|
12
|
+
TaskToggleMuteOptions,
|
|
12
13
|
} from '../types';
|
|
13
14
|
import Voice from './Voice';
|
|
14
15
|
import WebCallingService from '../../WebCallingService';
|
|
@@ -170,7 +171,10 @@ export default class WebRTC extends Voice implements IWebRTC {
|
|
|
170
171
|
* task.toggleMute().then(()=>{}).catch(()=>{})
|
|
171
172
|
* ```
|
|
172
173
|
*/
|
|
173
|
-
public async toggleMute() {
|
|
174
|
+
public async toggleMute(options?: TaskToggleMuteOptions) {
|
|
175
|
+
if (options) {
|
|
176
|
+
// parameter intentionally unused — WebRTC toggles from local stream state
|
|
177
|
+
}
|
|
174
178
|
LoggerProxy.log(`Toggling mute WebRTC task for taskId:${this.data.interactionId}`, {
|
|
175
179
|
module: 'WebRTC',
|
|
176
180
|
method: 'toggleMute',
|