@webex/contact-center 3.12.0-llmrefactor.1 → 3.12.0-llmrefactor.10
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 +7 -5
- package/ai-docs/ARCHITECTURE.md +1 -1
- package/ai-docs/CONTRACTS.md +8 -3
- 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 +544 -12
- 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/MetricsManager.js +16 -0
- package/dist/metrics/MetricsManager.js.map +1 -1
- package/dist/metrics/behavioral-events.js +104 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +18 -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 +202 -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 +222 -0
- package/dist/services/WebexCrossClientService.js.map +1 -0
- package/dist/services/WxAppTelephonyMercurySync.js +108 -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 +490 -21
- 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 +162 -53
- 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 +7 -10
- 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 +456 -15
- 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 +354 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
- package/dist/services/wxAppDiagnosticLogging.js +122 -0
- package/dist/services/wxAppDiagnosticLogging.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 +78 -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/MetricsManager.d.ts +6 -0
- package/dist/types/metrics/constants.d.ts +17 -0
- package/dist/types/services/AddressBook.d.ts +2 -1
- package/dist/types/services/AnswerCallOnWebexService.d.ts +38 -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 +32 -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 +304 -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 +52 -9
- package/dist/types/services/task/voice/WebRTC.d.ts +2 -2
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +55 -0
- package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -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 +8 -8
- package/src/cc.ts +657 -12
- package/src/config.ts +7 -0
- package/src/constants.ts +8 -0
- package/src/index.ts +2 -0
- package/src/metrics/MetricsManager.ts +16 -0
- package/src/metrics/ai-docs/metrics-spec.md +78 -10
- package/src/metrics/behavioral-events.ts +106 -0
- package/src/metrics/constants.ts +20 -0
- package/src/services/AddressBook.ts +17 -6
- package/src/services/AnswerCallOnWebexService.ts +239 -0
- package/src/services/EntryPoint.ts +59 -60
- package/src/services/Queue.ts +29 -12
- package/src/services/WebexCrossClientService.ts +293 -0
- package/src/services/WxAppTelephonyMercurySync.ts +142 -0
- package/src/services/agent/ai-docs/agent-spec.md +1 -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 +716 -17
- 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 +156 -3
- 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 +235 -81
- package/src/services/task/state-machine/actions.ts +5 -2
- package/src/services/task/state-machine/ai-docs/AGENTS.md +6 -4
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +35 -16
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +103 -33
- package/src/services/task/state-machine/guards.ts +7 -10
- 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 +595 -14
- package/src/services/task/voice/WebRTC.ts +5 -1
- package/src/services/task/voice/wxAppVoiceMethods.ts +516 -0
- package/src/services/wxAppDiagnosticLogging.ts +174 -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 +1522 -19
- package/test/unit/spec/metrics/MetricsManager.ts +25 -0
- package/test/unit/spec/metrics/behavioral-events.ts +60 -0
- package/test/unit/spec/services/AddressBook.ts +37 -6
- package/test/unit/spec/services/AnswerCallOnWebexService.ts +264 -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 +467 -0
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +136 -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 +274 -6
- package/test/unit/spec/services/task/TaskFactory.ts +39 -2
- package/test/unit/spec/services/task/TaskManager.ts +1717 -9
- 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 +1485 -109
- 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 +1412 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +603 -0
- package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import {EventPayload} from '@webex/internal-plugin-metrics/src/metrics.types';
|
|
2
|
+
|
|
3
|
+
import AnswerCallOnWebexService, {WxAppTelephonyError} from '../../AnswerCallOnWebexService';
|
|
4
|
+
import LoggerProxy from '../../../logger-proxy';
|
|
5
|
+
import {METHODS} from '../../../constants';
|
|
6
|
+
import {getErrorDetails} from '../../core/Utils';
|
|
7
|
+
import {Failure} from '../../core/GlobalTypes';
|
|
8
|
+
import {TaskData, TaskToggleMuteOptions, TaskTransmitDtmfOptions} from '../types';
|
|
9
|
+
import {TaskState} from '../state-machine';
|
|
10
|
+
import MetricsManager from '../../../metrics/MetricsManager';
|
|
11
|
+
import {METRIC_EVENT_NAMES} from '../../../metrics/constants';
|
|
12
|
+
import {
|
|
13
|
+
decodedLineOwnerId,
|
|
14
|
+
getWebexCallingDeviceDetailsForAgent,
|
|
15
|
+
WebexCallingDeviceDetails,
|
|
16
|
+
} from '../WebexCallingUtils';
|
|
17
|
+
import {
|
|
18
|
+
logWxAppTelephonyAction,
|
|
19
|
+
logWxAppValidationFailure,
|
|
20
|
+
type WxAppTelephonyAction,
|
|
21
|
+
} from '../../wxAppDiagnosticLogging';
|
|
22
|
+
|
|
23
|
+
type WxAppParticipant = {
|
|
24
|
+
deviceType?: string;
|
|
25
|
+
deviceCallId?: string;
|
|
26
|
+
deviceId?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type WxAppVoiceDependencies = {
|
|
30
|
+
enableWxBetterTogether: boolean;
|
|
31
|
+
answerCallOnWebexService?: AnswerCallOnWebexService;
|
|
32
|
+
agentId?: string;
|
|
33
|
+
metricsManager: MetricsManager;
|
|
34
|
+
getTaskData: () => TaskData;
|
|
35
|
+
getTaskState: () => TaskState | undefined;
|
|
36
|
+
getWxAppMuted: () => boolean;
|
|
37
|
+
setWxAppMuted: (muted: boolean) => void;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** @deprecated Use {@link WxAppVoiceDependencies} */
|
|
41
|
+
export type WxAppVoiceDeps = WxAppVoiceDependencies;
|
|
42
|
+
|
|
43
|
+
export type WxAppVoiceLifecycle = {
|
|
44
|
+
setWxAppAcceptInFlight: (inFlight: boolean) => void;
|
|
45
|
+
setWxAppAnswerPending: (pending: boolean) => void;
|
|
46
|
+
resetWxAppMuted: () => void;
|
|
47
|
+
syncWxAppMuteFromCallDetails: () => Promise<boolean | undefined>;
|
|
48
|
+
mapWxAppVoiceError: (error: unknown, method: string) => never;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const isWxAppParticipant = (participant: unknown): participant is WxAppParticipant =>
|
|
52
|
+
(participant as WxAppParticipant)?.deviceType === 'wxApp' &&
|
|
53
|
+
typeof (participant as WxAppParticipant)?.deviceCallId === 'string' &&
|
|
54
|
+
(participant as WxAppParticipant).deviceCallId.trim() !== '' &&
|
|
55
|
+
typeof (participant as WxAppParticipant)?.deviceId === 'string' &&
|
|
56
|
+
(participant as WxAppParticipant).deviceId.trim() !== '';
|
|
57
|
+
|
|
58
|
+
const isOutdialTask = (deps: WxAppVoiceDependencies): boolean =>
|
|
59
|
+
deps.getTaskData()?.interaction?.outboundType === 'OUTDIAL';
|
|
60
|
+
|
|
61
|
+
function getWxAppAgentParticipant(deps: WxAppVoiceDependencies): unknown {
|
|
62
|
+
const taskData = deps.getTaskData();
|
|
63
|
+
const participants = taskData.interaction?.participants;
|
|
64
|
+
const agentId = deps.agentId ?? taskData.agentId;
|
|
65
|
+
|
|
66
|
+
if (!participants || !agentId) {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (participants[agentId]) {
|
|
71
|
+
return participants[agentId];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Object.values(participants).find(
|
|
75
|
+
(participant) => (participant as {id?: string})?.id === agentId
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getCallingDeviceDetails(
|
|
80
|
+
deps: WxAppVoiceDependencies
|
|
81
|
+
): WebexCallingDeviceDetails | undefined {
|
|
82
|
+
const taskData = deps.getTaskData();
|
|
83
|
+
const participant = getWxAppAgentParticipant(deps);
|
|
84
|
+
|
|
85
|
+
if (!isWxAppParticipant(participant)) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return getWebexCallingDeviceDetailsForAgent(
|
|
90
|
+
deps.agentId ?? taskData.agentId,
|
|
91
|
+
taskData.interaction?.participants
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function isWebexAppCallingOffer(deps: WxAppVoiceDependencies): boolean {
|
|
96
|
+
if (!deps.enableWxBetterTogether) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const state = deps.getTaskState();
|
|
101
|
+
if (state !== TaskState.OFFERED) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const details = getCallingDeviceDetails(deps);
|
|
106
|
+
|
|
107
|
+
return Boolean(details);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Inbound wxApp offer only — outdial decline uses CC routing, not telephony reject. */
|
|
111
|
+
export function isWebexAppInboundCallingOffer(deps: WxAppVoiceDependencies): boolean {
|
|
112
|
+
return isWebexAppCallingOffer(deps) && !isOutdialTask(deps);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getWebexCallingCallId(deps: WxAppVoiceDependencies): string | null {
|
|
116
|
+
const state = deps.getTaskState();
|
|
117
|
+
if (
|
|
118
|
+
!state ||
|
|
119
|
+
state === TaskState.OFFERED ||
|
|
120
|
+
state === TaskState.IDLE ||
|
|
121
|
+
state === TaskState.WRAPPING_UP ||
|
|
122
|
+
state === TaskState.TERMINATED ||
|
|
123
|
+
state === TaskState.COMPLETED
|
|
124
|
+
) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return getCallingDeviceDetails(deps)?.deviceCallId ?? null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function getWxAppLineOwnerId(deps: WxAppVoiceDependencies): string | undefined {
|
|
132
|
+
const participant = getWxAppAgentParticipant(deps) as {lineOwnerId?: string} | undefined;
|
|
133
|
+
const lineOwnerId = participant?.lineOwnerId;
|
|
134
|
+
|
|
135
|
+
return typeof lineOwnerId === 'string' && lineOwnerId.trim() !== '' ? lineOwnerId : undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function resolveWxAppLineOwnerId(
|
|
139
|
+
deps: WxAppVoiceDependencies,
|
|
140
|
+
override?: string
|
|
141
|
+
): string | undefined {
|
|
142
|
+
if (typeof override === 'string' && override.trim() !== '') {
|
|
143
|
+
return override;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return decodedLineOwnerId(getWxAppLineOwnerId(deps));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getInteractionId(deps: WxAppVoiceDependencies): string | undefined {
|
|
150
|
+
return deps.getTaskData()?.interactionId;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function logTelephonyFailure(
|
|
154
|
+
action: WxAppTelephonyAction,
|
|
155
|
+
deps: WxAppVoiceDependencies,
|
|
156
|
+
error: unknown
|
|
157
|
+
): void {
|
|
158
|
+
const wxError = error as WxAppTelephonyError;
|
|
159
|
+
logWxAppTelephonyAction({
|
|
160
|
+
action,
|
|
161
|
+
phase: 'failed',
|
|
162
|
+
interactionId: getInteractionId(deps),
|
|
163
|
+
trackingId: wxError.trackingId,
|
|
164
|
+
httpStatus: wxError.status,
|
|
165
|
+
failureReason: wxError.message,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getWxAppTelephonyMetricFailurePayload(
|
|
170
|
+
deps: WxAppVoiceDependencies,
|
|
171
|
+
error: unknown
|
|
172
|
+
): Record<string, string> {
|
|
173
|
+
const wxError = error as WxAppTelephonyError;
|
|
174
|
+
const payload: Record<string, string> = {
|
|
175
|
+
taskId: getInteractionId(deps) ?? '',
|
|
176
|
+
error: error instanceof Error ? error.toString() : String(error),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
if (wxError.trackingId) {
|
|
180
|
+
payload.trackingId = wxError.trackingId;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return payload;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
type AqmWrappedError = {
|
|
187
|
+
details?: Failure;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
function getWxAppOutdialDeclineFailurePayload(
|
|
191
|
+
deps: WxAppVoiceDependencies,
|
|
192
|
+
error: unknown
|
|
193
|
+
): EventPayload {
|
|
194
|
+
const base: EventPayload = {
|
|
195
|
+
taskId: getInteractionId(deps) ?? '',
|
|
196
|
+
error: error instanceof Error ? error.toString() : String(error),
|
|
197
|
+
};
|
|
198
|
+
const aqmDetails = (error as AqmWrappedError).details;
|
|
199
|
+
|
|
200
|
+
if (aqmDetails) {
|
|
201
|
+
return {...base, ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(aqmDetails)};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return getWxAppTelephonyMetricFailurePayload(deps, error);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function logWxAppOutdialDeclineFailure(deps: WxAppVoiceDependencies, error: unknown): void {
|
|
208
|
+
const aqmDetails = (error as AqmWrappedError).details;
|
|
209
|
+
const wxError = error as WxAppTelephonyError;
|
|
210
|
+
|
|
211
|
+
logWxAppTelephonyAction({
|
|
212
|
+
action: 'decline',
|
|
213
|
+
phase: 'failed',
|
|
214
|
+
interactionId: getInteractionId(deps),
|
|
215
|
+
trackingId: aqmDetails?.trackingId ?? wxError.trackingId,
|
|
216
|
+
httpStatus: wxError.status,
|
|
217
|
+
failureReason: aqmDetails?.data?.reason ?? wxError.message,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function assertWxAppEnabled(deps: WxAppVoiceDependencies): void {
|
|
222
|
+
if (!deps.enableWxBetterTogether) {
|
|
223
|
+
logWxAppValidationFailure('enableWxBetterTogether_disabled', getInteractionId(deps));
|
|
224
|
+
throw new Error('enableWxBetterTogether is disabled');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!deps.answerCallOnWebexService) {
|
|
228
|
+
logWxAppValidationFailure('answer_call_on_webex_service_unavailable', getInteractionId(deps));
|
|
229
|
+
throw new Error('AnswerCallOnWebexService is unavailable');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function acceptOnWebex(
|
|
234
|
+
deps: WxAppVoiceDependencies,
|
|
235
|
+
options?: {lineOwnerId?: string}
|
|
236
|
+
): Promise<void> {
|
|
237
|
+
const interactionId = getInteractionId(deps);
|
|
238
|
+
logWxAppTelephonyAction({action: 'accept', phase: 'started', interactionId});
|
|
239
|
+
assertWxAppEnabled(deps);
|
|
240
|
+
|
|
241
|
+
if (!isWebexAppCallingOffer(deps)) {
|
|
242
|
+
logWxAppValidationFailure('not_wxApp_calling_offer', interactionId);
|
|
243
|
+
throw new Error('Task is not a wxApp calling offer');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const details = getCallingDeviceDetails(deps);
|
|
247
|
+
if (!details) {
|
|
248
|
+
logWxAppValidationFailure('wxApp_device_details_unavailable', interactionId);
|
|
249
|
+
throw new Error('WxApp calling device details are unavailable');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await deps.answerCallOnWebexService.answerCall({
|
|
253
|
+
callId: details.deviceCallId,
|
|
254
|
+
endpointId: details.deviceId,
|
|
255
|
+
lineOwnerId: resolveWxAppLineOwnerId(deps, options?.lineOwnerId),
|
|
256
|
+
});
|
|
257
|
+
logWxAppTelephonyAction({action: 'accept', phase: 'success', interactionId});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export async function rejectOnWebex(
|
|
261
|
+
deps: WxAppVoiceDependencies,
|
|
262
|
+
options?: {lineOwnerId?: string}
|
|
263
|
+
): Promise<void> {
|
|
264
|
+
const interactionId = getInteractionId(deps);
|
|
265
|
+
logWxAppTelephonyAction({action: 'decline', phase: 'started', interactionId});
|
|
266
|
+
assertWxAppEnabled(deps);
|
|
267
|
+
|
|
268
|
+
if (!isWebexAppInboundCallingOffer(deps)) {
|
|
269
|
+
logWxAppValidationFailure('not_wxApp_inbound_calling_offer', interactionId);
|
|
270
|
+
throw new Error('Task is not a wxApp inbound calling offer');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const details = getCallingDeviceDetails(deps);
|
|
274
|
+
if (!details) {
|
|
275
|
+
logWxAppValidationFailure('wxApp_device_details_unavailable', interactionId);
|
|
276
|
+
throw new Error('WxApp calling device details are unavailable');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await deps.answerCallOnWebexService.rejectCall({
|
|
280
|
+
callId: details.deviceCallId,
|
|
281
|
+
lineOwnerId: resolveWxAppLineOwnerId(deps, options?.lineOwnerId),
|
|
282
|
+
});
|
|
283
|
+
logWxAppTelephonyAction({action: 'decline', phase: 'success', interactionId});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function toggleMuteOnWebex(
|
|
287
|
+
deps: WxAppVoiceDependencies,
|
|
288
|
+
options?: {lineOwnerId?: string; muted?: boolean}
|
|
289
|
+
): Promise<void> {
|
|
290
|
+
const interactionId = getInteractionId(deps);
|
|
291
|
+
logWxAppTelephonyAction({action: 'mute', phase: 'started', interactionId});
|
|
292
|
+
assertWxAppEnabled(deps);
|
|
293
|
+
|
|
294
|
+
const callId = getWebexCallingCallId(deps);
|
|
295
|
+
if (!callId) {
|
|
296
|
+
logWxAppValidationFailure('wxApp_call_id_unavailable', interactionId);
|
|
297
|
+
throw new Error('WxApp call ID is unavailable');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const targetMuted = options?.muted ?? !deps.getWxAppMuted();
|
|
301
|
+
const request = targetMuted
|
|
302
|
+
? deps.answerCallOnWebexService.muteCall.bind(deps.answerCallOnWebexService)
|
|
303
|
+
: deps.answerCallOnWebexService.unmuteCall.bind(deps.answerCallOnWebexService);
|
|
304
|
+
|
|
305
|
+
await request({
|
|
306
|
+
callId,
|
|
307
|
+
lineOwnerId: resolveWxAppLineOwnerId(deps, options?.lineOwnerId),
|
|
308
|
+
});
|
|
309
|
+
deps.setWxAppMuted(targetMuted);
|
|
310
|
+
logWxAppTelephonyAction({action: 'mute', phase: 'success', interactionId});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export async function transmitDtmfOnWebex(
|
|
314
|
+
deps: WxAppVoiceDependencies,
|
|
315
|
+
options: {dtmf: string; lineOwnerId?: string}
|
|
316
|
+
): Promise<void> {
|
|
317
|
+
const interactionId = getInteractionId(deps);
|
|
318
|
+
logWxAppTelephonyAction({action: 'dtmf', phase: 'started', interactionId});
|
|
319
|
+
assertWxAppEnabled(deps);
|
|
320
|
+
|
|
321
|
+
const callId = getWebexCallingCallId(deps);
|
|
322
|
+
if (!callId) {
|
|
323
|
+
logWxAppValidationFailure('wxApp_call_id_unavailable', interactionId);
|
|
324
|
+
throw new Error('WxApp call ID is unavailable');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
LoggerProxy.info('transmitDtmf', {
|
|
328
|
+
module: 'wxAppVoiceMethods',
|
|
329
|
+
method: METHODS.TRANSMIT_DTMF,
|
|
330
|
+
data: {
|
|
331
|
+
interactionId,
|
|
332
|
+
callIdSuffix: callId.length <= 8 ? callId : callId.slice(-8),
|
|
333
|
+
dtmfLength: options.dtmf.length,
|
|
334
|
+
hasLineOwnerId: Boolean(options.lineOwnerId),
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
await deps.answerCallOnWebexService.transmitDtmf({
|
|
339
|
+
callId,
|
|
340
|
+
dtmf: options.dtmf,
|
|
341
|
+
lineOwnerId: resolveWxAppLineOwnerId(deps, options.lineOwnerId),
|
|
342
|
+
});
|
|
343
|
+
logWxAppTelephonyAction({action: 'dtmf', phase: 'success', interactionId});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export async function runWxAppAccept(
|
|
347
|
+
deps: WxAppVoiceDependencies,
|
|
348
|
+
lifecycle: WxAppVoiceLifecycle,
|
|
349
|
+
options?: {lineOwnerId?: string}
|
|
350
|
+
): Promise<void> {
|
|
351
|
+
const taskId = getInteractionId(deps);
|
|
352
|
+
|
|
353
|
+
deps.metricsManager.timeEvent([
|
|
354
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_SUCCESS,
|
|
355
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_FAILED,
|
|
356
|
+
]);
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
lifecycle.setWxAppAcceptInFlight(true);
|
|
360
|
+
lifecycle.setWxAppAnswerPending(true);
|
|
361
|
+
await acceptOnWebex(deps, options);
|
|
362
|
+
lifecycle.resetWxAppMuted();
|
|
363
|
+
await lifecycle.syncWxAppMuteFromCallDetails();
|
|
364
|
+
|
|
365
|
+
deps.metricsManager.trackEvent(METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_SUCCESS, {taskId}, [
|
|
366
|
+
'operational',
|
|
367
|
+
'behavioral',
|
|
368
|
+
]);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
lifecycle.setWxAppAnswerPending(false);
|
|
371
|
+
logTelephonyFailure('accept', deps, error);
|
|
372
|
+
deps.metricsManager.trackEvent(
|
|
373
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_FAILED,
|
|
374
|
+
getWxAppTelephonyMetricFailurePayload(deps, error),
|
|
375
|
+
['operational', 'behavioral']
|
|
376
|
+
);
|
|
377
|
+
lifecycle.mapWxAppVoiceError(error, METHODS.ACCEPT);
|
|
378
|
+
} finally {
|
|
379
|
+
lifecycle.setWxAppAcceptInFlight(false);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export async function runWxAppReject(
|
|
384
|
+
deps: WxAppVoiceDependencies,
|
|
385
|
+
lifecycle: WxAppVoiceLifecycle,
|
|
386
|
+
options?: {lineOwnerId?: string}
|
|
387
|
+
): Promise<void> {
|
|
388
|
+
const taskId = getInteractionId(deps);
|
|
389
|
+
|
|
390
|
+
deps.metricsManager.timeEvent([
|
|
391
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS,
|
|
392
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
|
|
393
|
+
]);
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
await rejectOnWebex(deps, options);
|
|
397
|
+
|
|
398
|
+
deps.metricsManager.trackEvent(METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS, {taskId}, [
|
|
399
|
+
'operational',
|
|
400
|
+
'behavioral',
|
|
401
|
+
]);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
logTelephonyFailure('decline', deps, error);
|
|
404
|
+
deps.metricsManager.trackEvent(
|
|
405
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
|
|
406
|
+
getWxAppTelephonyMetricFailurePayload(deps, error),
|
|
407
|
+
['operational', 'behavioral']
|
|
408
|
+
);
|
|
409
|
+
lifecycle.mapWxAppVoiceError(error, METHODS.REJECT);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export async function runWxAppOutdialDecline<T>(
|
|
414
|
+
deps: WxAppVoiceDependencies,
|
|
415
|
+
executeCancel: () => Promise<T>
|
|
416
|
+
): Promise<T> {
|
|
417
|
+
const taskId = getInteractionId(deps);
|
|
418
|
+
|
|
419
|
+
deps.metricsManager.timeEvent([
|
|
420
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS,
|
|
421
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
|
|
422
|
+
]);
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
const result = await executeCancel();
|
|
426
|
+
|
|
427
|
+
deps.metricsManager.trackEvent(METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS, {taskId}, [
|
|
428
|
+
'operational',
|
|
429
|
+
'behavioral',
|
|
430
|
+
]);
|
|
431
|
+
|
|
432
|
+
return result;
|
|
433
|
+
} catch (error) {
|
|
434
|
+
logWxAppOutdialDeclineFailure(deps, error);
|
|
435
|
+
deps.metricsManager.trackEvent(
|
|
436
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
|
|
437
|
+
getWxAppOutdialDeclineFailurePayload(deps, error),
|
|
438
|
+
['operational', 'behavioral']
|
|
439
|
+
);
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export async function runWxAppToggleMute(
|
|
445
|
+
deps: WxAppVoiceDependencies,
|
|
446
|
+
lifecycle: WxAppVoiceLifecycle,
|
|
447
|
+
options?: TaskToggleMuteOptions
|
|
448
|
+
): Promise<void> {
|
|
449
|
+
const taskId = getInteractionId(deps);
|
|
450
|
+
|
|
451
|
+
deps.metricsManager.timeEvent([
|
|
452
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_MUTE_SUCCESS,
|
|
453
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_MUTE_FAILED,
|
|
454
|
+
]);
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
await toggleMuteOnWebex(deps, options);
|
|
458
|
+
|
|
459
|
+
deps.metricsManager.trackEvent(
|
|
460
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_MUTE_SUCCESS,
|
|
461
|
+
{taskId, targetMuted: deps.getWxAppMuted()},
|
|
462
|
+
['operational', 'behavioral']
|
|
463
|
+
);
|
|
464
|
+
} catch (error) {
|
|
465
|
+
logTelephonyFailure('mute', deps, error);
|
|
466
|
+
deps.metricsManager.trackEvent(
|
|
467
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_MUTE_FAILED,
|
|
468
|
+
getWxAppTelephonyMetricFailurePayload(deps, error),
|
|
469
|
+
['operational', 'behavioral']
|
|
470
|
+
);
|
|
471
|
+
lifecycle.mapWxAppVoiceError(error, METHODS.TOGGLE_MUTE);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export async function runWxAppTransmitDtmf(
|
|
476
|
+
deps: WxAppVoiceDependencies,
|
|
477
|
+
lifecycle: WxAppVoiceLifecycle,
|
|
478
|
+
options: TaskTransmitDtmfOptions
|
|
479
|
+
): Promise<void> {
|
|
480
|
+
const taskId = getInteractionId(deps);
|
|
481
|
+
|
|
482
|
+
deps.metricsManager.timeEvent([
|
|
483
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DTMF_SUCCESS,
|
|
484
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DTMF_FAILED,
|
|
485
|
+
]);
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
await transmitDtmfOnWebex(deps, options);
|
|
489
|
+
|
|
490
|
+
deps.metricsManager.trackEvent(
|
|
491
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DTMF_SUCCESS,
|
|
492
|
+
{taskId, dtmfLength: options.dtmf.length},
|
|
493
|
+
['operational', 'behavioral']
|
|
494
|
+
);
|
|
495
|
+
} catch (error) {
|
|
496
|
+
logTelephonyFailure('dtmf', deps, error);
|
|
497
|
+
deps.metricsManager.trackEvent(
|
|
498
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DTMF_FAILED,
|
|
499
|
+
getWxAppTelephonyMetricFailurePayload(deps, error),
|
|
500
|
+
['operational', 'behavioral']
|
|
501
|
+
);
|
|
502
|
+
lifecycle.mapWxAppVoiceError(error, METHODS.TRANSMIT_DTMF);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function mapWxAppVoiceError(error: unknown, method: string, module: string): never {
|
|
507
|
+
if (
|
|
508
|
+
error instanceof Error &&
|
|
509
|
+
(error as Error & {isWxAppTelephonyError?: boolean}).isWxAppTelephonyError
|
|
510
|
+
) {
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const {error: detailedError} = getErrorDetails(error, method, module);
|
|
515
|
+
throw detailedError;
|
|
516
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import LoggerProxy from '../logger-proxy';
|
|
2
|
+
|
|
3
|
+
export const WXAPP_LOG_FEATURE = 'wxApp';
|
|
4
|
+
export const WXAPP_LOG_PREFIX = '[CC wxApp]';
|
|
5
|
+
|
|
6
|
+
export type WxAppAcceptReason =
|
|
7
|
+
| 'wxApp_offer_ready'
|
|
8
|
+
| 'wxApp_answer_pending'
|
|
9
|
+
| 'wxApp_accept_in_flight'
|
|
10
|
+
| 'extension_non_wxApp_offer'
|
|
11
|
+
| 'browser_webrtc_offer'
|
|
12
|
+
| 'outdial_auto_answer';
|
|
13
|
+
|
|
14
|
+
export type WxAppSessionSkipReason =
|
|
15
|
+
| 'unsupported_browser_login'
|
|
16
|
+
| 'flag_disabled'
|
|
17
|
+
| 'mercury_subscribe_failed'
|
|
18
|
+
| 'publish_failed';
|
|
19
|
+
|
|
20
|
+
export type WxAppTelephonyAction = 'accept' | 'decline' | 'mute' | 'dtmf';
|
|
21
|
+
export type WxAppTelephonyPhase = 'started' | 'success' | 'failed';
|
|
22
|
+
|
|
23
|
+
type WxAppLogData = Record<string, string | boolean | number | undefined>;
|
|
24
|
+
|
|
25
|
+
/** Last 8 chars of callId for correlation without logging full id. */
|
|
26
|
+
export function callIdSuffix(callId: string | null | undefined): string | undefined {
|
|
27
|
+
if (!callId) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return callId.length <= 8 ? callId : callId.slice(-8);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function logWxApp(message: string, method: string, data: WxAppLogData): void {
|
|
35
|
+
LoggerProxy.info(`${WXAPP_LOG_PREFIX} ${message}`, {
|
|
36
|
+
module: 'wxAppDiagnosticLogging',
|
|
37
|
+
method,
|
|
38
|
+
data: {feature: WXAPP_LOG_FEATURE, ...data},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function logWxAppError(message: string, method: string, data: WxAppLogData): void {
|
|
43
|
+
LoggerProxy.error(`${WXAPP_LOG_PREFIX} ${message}`, {
|
|
44
|
+
module: 'wxAppDiagnosticLogging',
|
|
45
|
+
method,
|
|
46
|
+
data: {feature: WXAPP_LOG_FEATURE, ...data},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function logWxAppSessionReadiness(params: {
|
|
51
|
+
enableWxBetterTogether: boolean;
|
|
52
|
+
loginOption: string | undefined;
|
|
53
|
+
wxAppHooksApplied: boolean;
|
|
54
|
+
usersubPublished: boolean;
|
|
55
|
+
mercurySubscribed: boolean;
|
|
56
|
+
telephonyTaskType: 'Voice' | 'WebRTC' | 'unknown';
|
|
57
|
+
skipReason?: WxAppSessionSkipReason;
|
|
58
|
+
}): void {
|
|
59
|
+
logWxApp('session readiness', 'logWxAppSessionReadiness', {
|
|
60
|
+
event: 'session_readiness',
|
|
61
|
+
enableWxBetterTogether: params.enableWxBetterTogether,
|
|
62
|
+
loginOption: params.loginOption,
|
|
63
|
+
wxAppHooksApplied: params.wxAppHooksApplied,
|
|
64
|
+
usersubPublished: params.usersubPublished,
|
|
65
|
+
mercurySubscribed: params.mercurySubscribed,
|
|
66
|
+
telephonyTaskType: params.telephonyTaskType,
|
|
67
|
+
skipReason: params.skipReason,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function deriveWxAppAcceptReason(params: {
|
|
72
|
+
isWxAppInboundOffer: boolean;
|
|
73
|
+
isWxAppOutdialOffer: boolean;
|
|
74
|
+
isWebrtc: boolean;
|
|
75
|
+
isOutdial: boolean;
|
|
76
|
+
wxAppAcceptInFlight: boolean;
|
|
77
|
+
wxAppAnswerPending: boolean;
|
|
78
|
+
enableWxBetterTogether: boolean;
|
|
79
|
+
hasDeviceCallId: boolean;
|
|
80
|
+
}): WxAppAcceptReason {
|
|
81
|
+
if (params.isWxAppInboundOffer || params.isWxAppOutdialOffer) {
|
|
82
|
+
if (params.wxAppAcceptInFlight) {
|
|
83
|
+
return 'wxApp_accept_in_flight';
|
|
84
|
+
}
|
|
85
|
+
if (params.wxAppAnswerPending) {
|
|
86
|
+
return 'wxApp_answer_pending';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return 'wxApp_offer_ready';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (params.isWebrtc && !params.isOutdial) {
|
|
93
|
+
return 'browser_webrtc_offer';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (params.isOutdial) {
|
|
97
|
+
return 'outdial_auto_answer';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (params.enableWxBetterTogether && !params.hasDeviceCallId) {
|
|
101
|
+
return 'extension_non_wxApp_offer';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return 'extension_non_wxApp_offer';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function logWxAppOfferDecision(params: {
|
|
108
|
+
interactionId: string;
|
|
109
|
+
acceptVisible: boolean;
|
|
110
|
+
acceptEnabled: boolean;
|
|
111
|
+
acceptReason: WxAppAcceptReason;
|
|
112
|
+
wxAppParticipantDeviceType?: string;
|
|
113
|
+
hasDeviceCallId: boolean;
|
|
114
|
+
}): void {
|
|
115
|
+
logWxApp('offer decision', 'logWxAppOfferDecision', {
|
|
116
|
+
event: 'offer_decision',
|
|
117
|
+
interactionId: params.interactionId,
|
|
118
|
+
acceptVisible: params.acceptVisible,
|
|
119
|
+
acceptEnabled: params.acceptEnabled,
|
|
120
|
+
acceptReason: params.acceptReason,
|
|
121
|
+
wxAppParticipantDeviceType: params.wxAppParticipantDeviceType,
|
|
122
|
+
hasDeviceCallId: params.hasDeviceCallId,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function logWxAppTelephonyAction(params: {
|
|
127
|
+
action: WxAppTelephonyAction;
|
|
128
|
+
phase: WxAppTelephonyPhase;
|
|
129
|
+
interactionId?: string;
|
|
130
|
+
trackingId?: string;
|
|
131
|
+
httpStatus?: number | string;
|
|
132
|
+
failureReason?: string;
|
|
133
|
+
}): void {
|
|
134
|
+
const payload: WxAppLogData = {
|
|
135
|
+
event: 'telephony_action',
|
|
136
|
+
action: params.action,
|
|
137
|
+
phase: params.phase,
|
|
138
|
+
interactionId: params.interactionId,
|
|
139
|
+
trackingId: params.trackingId,
|
|
140
|
+
httpStatus: params.httpStatus,
|
|
141
|
+
failureReason: params.failureReason,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (params.phase === 'failed') {
|
|
145
|
+
logWxAppError(`telephony ${params.action} ${params.phase}`, 'logWxAppTelephonyAction', payload);
|
|
146
|
+
} else {
|
|
147
|
+
logWxApp(`telephony ${params.action} ${params.phase}`, 'logWxAppTelephonyAction', payload);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function logWxAppValidationFailure(reason: string, interactionId?: string): void {
|
|
152
|
+
logWxAppError('validation failed', 'logWxAppValidationFailure', {
|
|
153
|
+
event: 'validation_failed',
|
|
154
|
+
reason,
|
|
155
|
+
interactionId,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function logWxAppMercuryMuteSync(params: {
|
|
160
|
+
phase: 'received' | 'applied' | 'dropped';
|
|
161
|
+
muted?: boolean;
|
|
162
|
+
callIdSuffix?: string;
|
|
163
|
+
interactionId?: string;
|
|
164
|
+
dropReason?: string;
|
|
165
|
+
}): void {
|
|
166
|
+
logWxApp(`mercury mute sync ${params.phase}`, 'logWxAppMercuryMuteSync', {
|
|
167
|
+
event: 'mercury_mute_sync',
|
|
168
|
+
phase: params.phase,
|
|
169
|
+
muted: params.muted,
|
|
170
|
+
callIdSuffix: params.callIdSuffix,
|
|
171
|
+
interactionId: params.interactionId,
|
|
172
|
+
dropReason: params.dropReason,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expected Hydra responses when mute backfill races call creation (WXCC-6026).
|
|
3
|
+
* These must not be logged as errors — see task-spec wxApp mute backfill guard.
|
|
4
|
+
*/
|
|
5
|
+
export function isWxAppCallNotFoundError(error: unknown): boolean {
|
|
6
|
+
const err = error as {status?: number | string; message?: string};
|
|
7
|
+
if (err?.status === 400 || err?.status === '400') {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const message = String(err?.message ?? error ?? '');
|
|
12
|
+
|
|
13
|
+
return message.includes('Call not found') || message.includes('101002');
|
|
14
|
+
}
|