WebRTC: строгая обработка сигналов (ICE до PC в очередь, ANSWER только для outgoing)

This commit is contained in:
AidarKC 2026-05-01 18:16:01 +03:00
parent c5dfa47903
commit db93eace30
2 changed files with 52 additions and 8 deletions

View File

@ -1,2 +1,2 @@
client.version=1.2.30 client.version=1.2.32
server.version=1.2.26 server.version=1.2.26

View File

@ -1029,13 +1029,24 @@ async function ensurePeerConnection(call) {
} }
async function onAccept(call) { async function onAccept(call) {
if (!call) return;
if (call.initialOfferInProgress || call.initialOfferSent) {
await emitDebug(call, 'warn', 'accept_duplicate_ignored', `phase=${call.phase || ''}`);
return;
}
call.initialOfferInProgress = true;
cleanupTimers(call); cleanupTimers(call);
setStatus(call, 'Соединяем…', 'connecting'); setStatus(call, 'Соединяем…', 'connecting');
try {
const pc = await ensurePeerConnection(call); const pc = await ensurePeerConnection(call);
const offer = await pc.createOffer(); const offer = await pc.createOffer();
await pc.setLocalDescription(offer); await pc.setLocalDescription(offer);
call.initialOfferSent = true;
await sendSignal(call, TYPES.OFFER, JSON.stringify(offer)); await sendSignal(call, TYPES.OFFER, JSON.stringify(offer));
await emitDebug(call, 'info', 'offer_sent', 'offer created and sent'); await emitDebug(call, 'info', 'offer_sent', 'offer created and sent');
} finally {
call.initialOfferInProgress = false;
}
} }
function ensureIncomingNotification(peerLogin) { function ensureIncomingNotification(peerLogin) {
@ -1145,10 +1156,14 @@ export async function startDebugConnectionAsResponder({ runId, callId, peerLogin
debugRunId: String(runId || '').trim(), debugRunId: String(runId || '').trim(),
debugRole: 'responder', debugRole: 'responder',
pendingRemoteIceCandidates: [], pendingRemoteIceCandidates: [],
initialOfferInProgress: false,
initialOfferSent: false,
}; };
calls.set(cleanCallId, call); calls.set(cleanCallId, call);
} }
if (!Array.isArray(call.pendingRemoteIceCandidates)) call.pendingRemoteIceCandidates = []; if (!Array.isArray(call.pendingRemoteIceCandidates)) call.pendingRemoteIceCandidates = [];
if (typeof call.initialOfferInProgress !== 'boolean') call.initialOfferInProgress = false;
if (typeof call.initialOfferSent !== 'boolean') call.initialOfferSent = false;
activeCallId = cleanCallId; activeCallId = cleanCallId;
await emitDebug(call, 'info', 'debug_prepare_responder', `peerSessionId=${cleanPeerSessionId}`); await emitDebug(call, 'info', 'debug_prepare_responder', `peerSessionId=${cleanPeerSessionId}`);
@ -1180,6 +1195,8 @@ export async function startDebugConnectionAsInitiator({ runId, callId, peerLogin
debugRunId: String(runId || '').trim(), debugRunId: String(runId || '').trim(),
debugRole: 'initiator', debugRole: 'initiator',
pendingRemoteIceCandidates: [], pendingRemoteIceCandidates: [],
initialOfferInProgress: false,
initialOfferSent: false,
}; };
calls.set(cleanCallId, call); calls.set(cleanCallId, call);
@ -1225,6 +1242,8 @@ export async function startOutgoingCall(peerLogin) {
debugRunId: '', debugRunId: '',
debugRole: '', debugRole: '',
pendingRemoteIceCandidates: [], pendingRemoteIceCandidates: [],
initialOfferInProgress: false,
initialOfferSent: false,
}; };
calls.set(callId, call); calls.set(callId, call);
activeCallId = callId; activeCallId = callId;
@ -1295,6 +1314,8 @@ export async function handleIncomingCallInvite(evt) {
debugRunId: '', debugRunId: '',
debugRole: '', debugRole: '',
pendingRemoteIceCandidates: [], pendingRemoteIceCandidates: [],
initialOfferInProgress: false,
initialOfferSent: false,
}; };
calls.set(callId, call); calls.set(callId, call);
} }
@ -1355,6 +1376,10 @@ export async function handleIncomingCallSignal(evt) {
} }
if (type === TYPES.ACCEPT) { if (type === TYPES.ACCEPT) {
if (call.direction !== 'out') {
await emitDebug(call, 'warn', 'accept_ignored_for_non_outgoing_call', `direction=${call.direction || ''}`);
return;
}
call.phase = 'connecting'; call.phase = 'connecting';
setStatus(call, 'Соединяем…', 'connecting'); setStatus(call, 'Соединяем…', 'connecting');
await onAccept(call); await onAccept(call);
@ -1403,7 +1428,20 @@ export async function handleIncomingCallSignal(evt) {
if (type === TYPES.ANSWER) { if (type === TYPES.ANSWER) {
try { try {
const pc = await ensurePeerConnection(call); if (call.direction !== 'out') {
await emitDebug(call, 'warn', 'answer_ignored_for_non_outgoing_call', `direction=${call.direction || ''}`);
return;
}
if (!call.pc) {
await emitDebug(call, 'warn', 'answer_ignored_without_pc', 'no local peer connection');
return;
}
const pc = call.pc;
const localType = String(pc.localDescription?.type || '').trim().toLowerCase();
if (localType !== 'offer') {
await emitDebug(call, 'warn', 'answer_ignored_without_local_offer', `localType=${localType || 'none'}`);
return;
}
if (pc.signalingState === 'stable' && pc.remoteDescription) { if (pc.signalingState === 'stable' && pc.remoteDescription) {
await emitDebug(call, 'warn', 'answer_duplicate_ignored', 'remote description already set'); await emitDebug(call, 'warn', 'answer_duplicate_ignored', 'remote description already set');
return; return;
@ -1421,8 +1459,14 @@ export async function handleIncomingCallSignal(evt) {
if (type === TYPES.ICE) { if (type === TYPES.ICE) {
try { try {
const pc = await ensurePeerConnection(call);
const candidate = JSON.parse(data); const candidate = JSON.parse(data);
if (!call.pc) {
if (!Array.isArray(call.pendingRemoteIceCandidates)) call.pendingRemoteIceCandidates = [];
call.pendingRemoteIceCandidates.push(candidate);
await emitDebug(call, 'info', 'ice_queued_before_pc', `queue=${call.pendingRemoteIceCandidates.length}`);
return;
}
const pc = call.pc;
if (!pc.remoteDescription) { if (!pc.remoteDescription) {
if (!Array.isArray(call.pendingRemoteIceCandidates)) call.pendingRemoteIceCandidates = []; if (!Array.isArray(call.pendingRemoteIceCandidates)) call.pendingRemoteIceCandidates = [];
call.pendingRemoteIceCandidates.push(candidate); call.pendingRemoteIceCandidates.push(candidate);