SHA256
Звонки: фиксы session fallback/registry, аналитика ICE/TURN, авточек UI-версии и перенос кнопки разработчика
This commit is contained in:
@@ -86,6 +86,23 @@ function uniqueUrls(urls = []) {
|
||||
return out;
|
||||
}
|
||||
|
||||
function parseTurnHostFromUrl(rawUrl) {
|
||||
const value = String(rawUrl || '').trim();
|
||||
if (!value) return '';
|
||||
const noProto = value.replace(/^turns?:/i, '');
|
||||
const noQuery = noProto.split('?')[0];
|
||||
const noPath = noQuery.split('/')[0];
|
||||
const hostPort = noPath.replace(/^\/\//, '').trim();
|
||||
if (!hostPort) return '';
|
||||
if (hostPort.startsWith('[')) {
|
||||
const end = hostPort.indexOf(']');
|
||||
if (end > 1) return hostPort.slice(1, end);
|
||||
return '';
|
||||
}
|
||||
const idx = hostPort.indexOf(':');
|
||||
return (idx >= 0 ? hostPort.slice(0, idx) : hostPort).trim();
|
||||
}
|
||||
|
||||
async function resolveIceServers(call) {
|
||||
try {
|
||||
const payload = await authService.getCallIceConfig();
|
||||
@@ -95,6 +112,7 @@ async function resolveIceServers(call) {
|
||||
const turnPassword = String(payload?.turnPassword || '').trim();
|
||||
const turnServers = Array.isArray(payload?.turnServers) ? payload.turnServers : [];
|
||||
|
||||
const turnHostSet = new Set();
|
||||
const iceServers = [];
|
||||
if (stunUrls.length > 0) {
|
||||
iceServers.push({ urls: stunUrls.length === 1 ? stunUrls[0] : stunUrls });
|
||||
@@ -105,6 +123,10 @@ async function resolveIceServers(call) {
|
||||
const username = String(item?.username || '').trim();
|
||||
const password = String(item?.password || '').trim();
|
||||
if (urls.length === 0 || !username || !password) return;
|
||||
urls.forEach((url) => {
|
||||
const host = parseTurnHostFromUrl(url);
|
||||
if (host) turnHostSet.add(host);
|
||||
});
|
||||
iceServers.push({
|
||||
urls: urls.length === 1 ? urls[0] : urls,
|
||||
username,
|
||||
@@ -112,6 +134,10 @@ async function resolveIceServers(call) {
|
||||
});
|
||||
});
|
||||
} else if (turnUrls.length > 0 && turnUsername && turnPassword) {
|
||||
turnUrls.forEach((url) => {
|
||||
const host = parseTurnHostFromUrl(url);
|
||||
if (host) turnHostSet.add(host);
|
||||
});
|
||||
iceServers.push({
|
||||
urls: turnUrls.length === 1 ? turnUrls[0] : turnUrls,
|
||||
username: turnUsername,
|
||||
@@ -123,6 +149,9 @@ async function resolveIceServers(call) {
|
||||
await emitDebug(call, 'warn', 'call_ice_empty_from_server', 'using_default_stun');
|
||||
return cloneDefaultIceServers();
|
||||
}
|
||||
if (call) {
|
||||
call.turnHostsConfigured = Array.from(turnHostSet);
|
||||
}
|
||||
await emitDebug(call, 'info', 'call_ice_loaded_from_server', `stun=${stunUrls.length}; turnEntries=${Math.max(0, iceServers.length - (stunUrls.length > 0 ? 1 : 0))}`);
|
||||
return iceServers;
|
||||
} catch (error) {
|
||||
@@ -131,6 +160,97 @@ async function resolveIceServers(call) {
|
||||
}
|
||||
}
|
||||
|
||||
async function collectIceCandidateAnalytics(call) {
|
||||
const pc = call?.pc;
|
||||
if (!pc || typeof pc.getStats !== 'function') return {};
|
||||
try {
|
||||
const stats = await pc.getStats();
|
||||
const localCounts = { host: 0, srflx: 0, relay: 0, prflx: 0, other: 0 };
|
||||
const remoteCounts = { host: 0, srflx: 0, relay: 0, prflx: 0, other: 0 };
|
||||
const relayLocalAddresses = new Set();
|
||||
const relayRemoteAddresses = new Set();
|
||||
const configuredHosts = new Set((call?.turnHostsConfigured || []).map((v) => String(v || '').trim()).filter(Boolean));
|
||||
const matchedConfiguredHosts = new Set();
|
||||
let succeededPairsCount = 0;
|
||||
let succeededPairsWithRelayCount = 0;
|
||||
|
||||
const bump = (bucket, rawType) => {
|
||||
const type = String(rawType || '').trim().toLowerCase();
|
||||
if (!type) bucket.other += 1;
|
||||
else if (Object.prototype.hasOwnProperty.call(bucket, type)) bucket[type] += 1;
|
||||
else bucket.other += 1;
|
||||
};
|
||||
const hostOf = (candidate) => {
|
||||
const raw = String(candidate?.ip || candidate?.address || '').trim();
|
||||
if (!raw) return '';
|
||||
if (raw.startsWith('[')) {
|
||||
const end = raw.indexOf(']');
|
||||
if (end > 1) return raw.slice(1, end);
|
||||
}
|
||||
return raw;
|
||||
};
|
||||
|
||||
stats.forEach((report) => {
|
||||
if (!report || report.type !== 'local-candidate') return;
|
||||
bump(localCounts, report.candidateType);
|
||||
if (String(report.candidateType || '').toLowerCase() === 'relay') {
|
||||
const addr = String(report.ip || report.address || '');
|
||||
const port = String(report.port || '');
|
||||
relayLocalAddresses.add(port ? `${addr}:${port}` : addr);
|
||||
const host = hostOf(report);
|
||||
if (host && configuredHosts.has(host)) matchedConfiguredHosts.add(host);
|
||||
}
|
||||
});
|
||||
stats.forEach((report) => {
|
||||
if (!report || report.type !== 'remote-candidate') return;
|
||||
bump(remoteCounts, report.candidateType);
|
||||
if (String(report.candidateType || '').toLowerCase() === 'relay') {
|
||||
const addr = String(report.ip || report.address || '');
|
||||
const port = String(report.port || '');
|
||||
relayRemoteAddresses.add(port ? `${addr}:${port}` : addr);
|
||||
}
|
||||
});
|
||||
stats.forEach((report) => {
|
||||
if (!report || report.type !== 'candidate-pair') return;
|
||||
const state = String(report.state || '').toLowerCase();
|
||||
const ok = report.selected === true || (report.nominated === true && state === 'succeeded') || state === 'succeeded';
|
||||
if (!ok) return;
|
||||
succeededPairsCount += 1;
|
||||
const local = report.localCandidateId && typeof stats.get === 'function' ? stats.get(report.localCandidateId) : null;
|
||||
const remote = report.remoteCandidateId && typeof stats.get === 'function' ? stats.get(report.remoteCandidateId) : null;
|
||||
const lt = String(local?.candidateType || '').toLowerCase();
|
||||
const rt = String(remote?.candidateType || '').toLowerCase();
|
||||
if (lt === 'relay' || rt === 'relay') succeededPairsWithRelayCount += 1;
|
||||
});
|
||||
|
||||
return {
|
||||
localCandidatesHost: localCounts.host,
|
||||
localCandidatesSrflx: localCounts.srflx,
|
||||
localCandidatesRelay: localCounts.relay,
|
||||
localCandidatesPrflx: localCounts.prflx,
|
||||
localCandidatesOther: localCounts.other,
|
||||
remoteCandidatesHost: remoteCounts.host,
|
||||
remoteCandidatesSrflx: remoteCounts.srflx,
|
||||
remoteCandidatesRelay: remoteCounts.relay,
|
||||
remoteCandidatesPrflx: remoteCounts.prflx,
|
||||
remoteCandidatesOther: remoteCounts.other,
|
||||
relayLocalCandidatesFound: relayLocalAddresses.size,
|
||||
relayRemoteCandidatesFound: relayRemoteAddresses.size,
|
||||
relayLocalCandidatesAddresses: Array.from(relayLocalAddresses).join('|'),
|
||||
relayRemoteCandidatesAddresses: Array.from(relayRemoteAddresses).join('|'),
|
||||
configuredTurnHosts: Array.from(configuredHosts).join('|'),
|
||||
configuredTurnHostsCount: configuredHosts.size,
|
||||
reachableTurnHostsCount: matchedConfiguredHosts.size,
|
||||
reachableTurnHosts: Array.from(matchedConfiguredHosts).join('|'),
|
||||
turnConfiguredButNotReachedHosts: Array.from(configuredHosts).filter((host) => !matchedConfiguredHosts.has(host)).join('|'),
|
||||
succeededCandidatePairsCount: succeededPairsCount,
|
||||
succeededCandidatePairsWithRelayCount: succeededPairsWithRelayCount,
|
||||
};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function ensureAudioContext() {
|
||||
if (audioContext) return audioContext;
|
||||
const Ctx = window.AudioContext || window.webkitAudioContext;
|
||||
@@ -633,14 +753,23 @@ async function finalizeCall(call, {
|
||||
stopReconnectFlow(call);
|
||||
stopTone();
|
||||
|
||||
if (notifyRemoteHangup && call.remoteSessionId) {
|
||||
const shouldNotifyRemoteFailure =
|
||||
!notifyRemoteHangup
|
||||
&& Boolean(call.remoteSessionId)
|
||||
&& String(localReasonCode || '') !== 'completed'
|
||||
&& String(debugReason || '') !== 'remote_hangup';
|
||||
|
||||
if ((notifyRemoteHangup || shouldNotifyRemoteFailure) && call.remoteSessionId) {
|
||||
try {
|
||||
const dataValue = notifyRemoteHangup
|
||||
? ''
|
||||
: `setup_failed:${String(localReasonCode || 'error')}:${String(debugReason || '').slice(0, 80)}`;
|
||||
await authService.callSignalToSession({
|
||||
toLogin: call.peerLogin,
|
||||
targetSessionId: call.remoteSessionId,
|
||||
callId: call.callId,
|
||||
type: TYPES.HANGUP,
|
||||
data: '',
|
||||
data: dataValue,
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
@@ -769,6 +898,7 @@ async function ensurePeerConnection(call) {
|
||||
call.connectionSuccessReported = true;
|
||||
void (async () => {
|
||||
const route = await detectConnectionRoute(call);
|
||||
const candidateAnalytics = await collectIceCandidateAnalytics(call);
|
||||
if (route?.label) {
|
||||
call.connectionRouteLabel = route.label;
|
||||
}
|
||||
@@ -785,6 +915,7 @@ async function ensurePeerConnection(call) {
|
||||
localIp: route?.localIp || '',
|
||||
remoteIp: route?.remoteIp || '',
|
||||
turnCandidateAddress: route?.turnCandidateAddress || '',
|
||||
...candidateAnalytics,
|
||||
},
|
||||
);
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user