Звонки: расширенная диагностика + экран настроек разработчика + обновление TURN-конфига

This commit is contained in:
AidarKC
2026-05-01 16:39:03 +03:00
parent a2ed41514d
commit e3377a48b3
10 changed files with 479 additions and 296 deletions
+107 -6
View File
@@ -284,20 +284,106 @@ function buildCallFactsJson(call, extra = {}) {
}
}
function buildCallFactsLine(call, extra = {}) {
const pc = call?.pc || null;
const facts = {
callId: call?.callId || '',
peerLogin: call?.peerLogin || '',
remoteSessionId: call?.remoteSessionId || '',
direction: call?.direction || '',
phase: call?.phase || '',
statusText: call?.statusText || '',
startedAtMs: Number(call?.startedAtMs || 0),
startedAtIso: toIsoTs(call?.startedAtMs),
connectedAtMs: Number(call?.connectedAtMs || 0),
connectedAtIso: toIsoTs(call?.connectedAtMs),
routeLabel: call?.connectionRouteLabel || '',
routeDetails: call?.connectionRouteDetails || '',
pcConnectionState: pc?.connectionState || '',
pcIceConnectionState: pc?.iceConnectionState || '',
pcSignalingState: pc?.signalingState || '',
hasLocalStream: Boolean(call?.localStream),
localAudioTracksCount: call?.localStream?.getAudioTracks?.()?.length || 0,
...extra,
};
return Object.entries(facts)
.map(([k, v]) => `${k}=${String(v ?? '').replace(/,/g, ';')}`)
.join(', ');
}
function getCallDiagnosticsContext(call) {
const pc = call?.pc || null;
const nav = typeof navigator !== 'undefined' ? navigator : null;
const conn = nav?.connection || nav?.mozConnection || nav?.webkitConnection || null;
const permissionsApiAvailable = typeof nav?.permissions?.query === 'function';
const mediaDevicesAvailable = Boolean(nav?.mediaDevices?.getUserMedia);
const online = typeof nav?.onLine === 'boolean' ? nav.onLine : null;
const visibilityState = typeof document !== 'undefined' ? String(document.visibilityState || '') : '';
const pageFocused = typeof document !== 'undefined' && typeof document.hasFocus === 'function'
? Boolean(document.hasFocus())
: false;
const localTracks = call?.localStream?.getTracks?.() || [];
const localAudioTracks = call?.localStream?.getAudioTracks?.() || [];
const enabledLocalAudioTracks = localAudioTracks.filter((track) => track?.enabled).length;
const transceiversCount = pc?.getTransceivers?.()?.length || 0;
const sendersCount = pc?.getSenders?.()?.length || 0;
const receiversCount = pc?.getReceivers?.()?.length || 0;
const iceGatheringState = pc?.iceGatheringState || '';
const currentLocalDescType = pc?.localDescription?.type || '';
const currentRemoteDescType = pc?.remoteDescription?.type || '';
return {
remoteSessionIdPresent: Boolean(call?.remoteSessionId),
callExistsInStore: calls.has(String(call?.callId || '')),
browserOnline: online === null ? '' : String(online),
documentVisibilityState: visibilityState,
pageFocused,
userAgent: typeof nav?.userAgent === 'string' ? nav.userAgent : '',
platform: typeof nav?.platform === 'string' ? nav.platform : '',
language: typeof nav?.language === 'string' ? nav.language : '',
permissionsApiAvailable,
mediaDevicesApiAvailable: mediaDevicesAvailable,
connectionType: String(conn?.type || ''),
effectiveConnectionType: String(conn?.effectiveType || ''),
networkRttMs: Number(conn?.rtt || 0),
networkDownlinkMbps: Number(conn?.downlink || 0),
saveData: conn?.saveData === true,
localTrackCount: localTracks.length,
localAudioTracksCount: localAudioTracks.length,
localAudioTracksEnabledCount: enabledLocalAudioTracks,
localAudioTrackLabels: localAudioTracks.map((t) => String(t?.label || '')).join('|'),
hasPeerConnection: Boolean(pc),
pcConnectionState: pc?.connectionState || '',
pcIceConnectionState: pc?.iceConnectionState || '',
pcIceGatheringState: iceGatheringState,
pcSignalingState: pc?.signalingState || '',
pcCanTrickleIceCandidates: pc?.canTrickleIceCandidates === null || pc?.canTrickleIceCandidates === undefined
? ''
: String(pc?.canTrickleIceCandidates),
localDescriptionType: currentLocalDescType,
remoteDescriptionType: currentRemoteDescType,
pcTransceiversCount: transceiversCount,
pcSendersCount: sendersCount,
pcReceiversCount: receiversCount,
};
}
async function sendCallDeliveryReport(call, eventType, eventCode, reason = '', extraFacts = {}) {
if (!call || !authService || typeof authService.sendCallDeliveryReport !== 'function') return;
try {
const valueJson = buildCallFactsJson(call, {
const diagnostics = getCallDiagnosticsContext(call);
const valueLine = buildCallFactsLine(call, {
eventType: String(eventType || '').trim(),
eventCode: String(eventCode || '').trim(),
reason: String(reason || '').trim(),
reportedAtMs: nowMs(),
reportedAtIso: toIsoTs(nowMs()),
...diagnostics,
...extraFacts,
});
await authService.sendCallDeliveryReport({
type: String(eventType || '').trim(),
value: valueJson,
value: valueLine,
});
} catch {}
}
@@ -569,13 +655,23 @@ async function finalizeCall(call, {
const reasonText = debugReason || localReasonCode;
if (String(localReasonCode || '') !== 'completed') {
const failureStage = call.phase || '';
const failureContext = {
failureStage,
connectedBeforeFailure: Boolean(call.connectedAtMs),
};
if (call.direction === 'out') {
await sendCallDeliveryReport(call, 'outgoing_failed', `outgoing_${localReasonCode}`, reasonText);
await sendCallDeliveryReport(call, 'outgoing_failed', `outgoing_${localReasonCode}`, reasonText, failureContext);
} else if (call.direction === 'in') {
await sendCallDeliveryReport(call, 'incoming_failed', `incoming_${localReasonCode}`, reasonText);
await sendCallDeliveryReport(call, 'incoming_failed', `incoming_${localReasonCode}`, reasonText, failureContext);
}
if (String(localReasonCode || '') === 'busy') {
await sendCallDeliveryReport(call, 'call_busy', 'call_busy', reasonText, failureContext);
} else if (String(localReasonCode || '') === 'declined') {
await sendCallDeliveryReport(call, 'call_declined', 'call_declined', reasonText, failureContext);
}
if (String(localReasonCode || '') === 'error') {
await sendCallDeliveryReport(call, 'unknown_error', 'call_unknown_error', reasonText);
await sendCallDeliveryReport(call, 'unknown_error', 'call_unknown_error', reasonText, failureContext);
}
}
@@ -1081,7 +1177,12 @@ export async function handleIncomingCallSignal(evt) {
}
if (type === TYPES.DECLINE_BUSY) {
await finalizeCall(call, { localReasonCode: 'busy', debugReason: 'decline_or_busy' });
const normalized = data.trim().toLowerCase();
const isDeclined = normalized === 'decline' || normalized === 'declined';
await finalizeCall(call, {
localReasonCode: isDeclined ? 'declined' : 'busy',
debugReason: isDeclined ? 'declined_by_remote' : 'busy_by_remote',
});
return;
}