SHA256
Звонки: расширенная диагностика + экран настроек разработчика + обновление TURN-конфига
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user