feat(call): серверная выдача ICE/TURN и подключение в WebRTC

This commit is contained in:
AidarKC
2026-04-22 18:11:47 +03:00
parent d7c7bb3c23
commit a905822515
8 changed files with 388 additions and 1 deletions
+7
View File
@@ -1412,6 +1412,13 @@ export class AuthService {
if (response.status !== 200) throw opError('CallSignalToSession', response);
return response.payload || {};
}
async getCallIceConfig() {
const response = await this.ws.request('GetCallIceConfig', {});
if (response.status !== 200) throw opError('GetCallIceConfig', response);
return response.payload || {};
}
async listContacts() {
const response = await this.ws.request('ListContacts', {});
if (response.status !== 200) throw opError('ListContacts', response);
+65 -1
View File
@@ -23,6 +23,10 @@ let toneTimerId = null;
let toneName = '';
let toneFlip = false;
const DEFAULT_ICE_SERVERS = Object.freeze([
{ urls: 'stun:stun.l.google.com:19302' },
]);
function nowMs() {
return Date.now();
}
@@ -55,6 +59,65 @@ function formatDuration(ms) {
return `${sec}с`;
}
function cloneDefaultIceServers() {
return DEFAULT_ICE_SERVERS.map((row) => ({ ...row }));
}
function parseIceUrls(raw) {
if (Array.isArray(raw)) {
return raw
.map((item) => String(item || '').trim())
.filter((item) => item.length > 0);
}
const single = String(raw || '').trim();
if (!single) return [];
return [single];
}
function uniqueUrls(urls = []) {
const out = [];
const seen = new Set();
urls.forEach((url) => {
const clean = String(url || '').trim();
if (!clean || seen.has(clean)) return;
seen.add(clean);
out.push(clean);
});
return out;
}
async function resolveIceServers(call) {
try {
const payload = await authService.getCallIceConfig();
const stunUrls = uniqueUrls(parseIceUrls(payload?.stunUrls));
const turnUrls = uniqueUrls(parseIceUrls(payload?.turnUrls));
const turnUsername = String(payload?.turnUsername || '').trim();
const turnPassword = String(payload?.turnPassword || '').trim();
const iceServers = [];
if (stunUrls.length > 0) {
iceServers.push({ urls: stunUrls.length === 1 ? stunUrls[0] : stunUrls });
}
if (turnUrls.length > 0 && turnUsername && turnPassword) {
iceServers.push({
urls: turnUrls.length === 1 ? turnUrls[0] : turnUrls,
username: turnUsername,
credential: turnPassword,
});
}
if (iceServers.length === 0) {
await emitDebug(call, 'warn', 'call_ice_empty_from_server', 'using_default_stun');
return cloneDefaultIceServers();
}
await emitDebug(call, 'info', 'call_ice_loaded_from_server', `stun=${stunUrls.length}; turn=${turnUrls.length}`);
return iceServers;
} catch (error) {
await emitDebug(call, 'warn', 'call_ice_load_failed', toErrorText(error));
return cloneDefaultIceServers();
}
}
function ensureAudioContext() {
if (audioContext) return audioContext;
const Ctx = window.AudioContext || window.webkitAudioContext;
@@ -365,8 +428,9 @@ async function sendSignal(call, type, data = '') {
async function ensurePeerConnection(call) {
if (call.pc) return call.pc;
const iceServers = await resolveIceServers(call);
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
iceServers,
});
if (call.debugMode && call.debugRole === 'initiator') {