From 035e24977d6d5ad0a3887d5ae71098a556b95b9c Mon Sep 17 00:00:00 2001 From: rku-21 Date: Wed, 6 May 2026 13:31:07 +0530 Subject: [PATCH] Queue ICE candidates until remoteDescription is set --- public/js/client.js | 56 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/public/js/client.js b/public/js/client.js index 65cc3e9e..3303fb2f 100644 --- a/public/js/client.js +++ b/public/js/client.js @@ -625,6 +625,33 @@ let chatDataChannels = {}; // keep track of our peer chat data channels let fileDataChannels = {}; // keep track of our peer file sharing data channels let allPeers = {}; // keep track of all peers in the room, indexed by peer_id == socket.io id + +let pendingIceCandidates = {}; + +function queueIceCandidate(peer_id, ice_candidate) { + if (!peer_id || !ice_candidate) return; + if (!pendingIceCandidates[peer_id]) pendingIceCandidates[peer_id] = []; + pendingIceCandidates[peer_id].push(ice_candidate); +} + +async function flushIceCandidates(peer_id) { + const pc = peerConnections[peer_id]; + const queued = pendingIceCandidates[peer_id]; + + if (!pc || !queued || queued.length === 0) return; + if (!pc.remoteDescription || !pc.remoteDescription.type) return; + + delete pendingIceCandidates[peer_id]; + + for (const ice of queued) { + try { + await pc.addIceCandidate(new RTCIceCandidate(ice)); + } catch (err) { + console.error('[Error] addIceCandidate (queued)', err); + } + } +} + let lastStats = null; // stream @@ -2715,6 +2742,9 @@ async function handleAddPeer(config) { const peerConnection = new RTCPeerConnection({ iceServers: iceServers }); peerConnections[peer_id] = peerConnection; + // If ICE arrived before we processed addPeer, keep it and flush when ready. + flushIceCandidates(peer_id).catch((err) => console.error('[Error] flushIceCandidates', err)); + allPeers = peers; // Ensure extras object exists for every peer to avoid undefined checks later try { @@ -3115,10 +3145,19 @@ function handleSessionDescription(config) { const pc = peerConnections[peer_id]; + if (!pc) { + console.warn('[RTCSessionDescription] peer connection missing, ignoring', { peer_id }); + return; + } + // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/setRemoteDescription pc.setRemoteDescription(remote_description) .then(() => { console.log('setRemoteDescription done!'); + + // Drain any queued ICE now that remoteDescription is set. + flushIceCandidates(peer_id).catch((err) => console.error('[Error] flushIceCandidates', err)); + if (session_description.type == 'offer') { console.log('Creating answer'); // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createAnswer @@ -3166,7 +3205,20 @@ function handleSessionDescription(config) { function handleIceCandidate(config) { const { peer_id, ice_candidate } = config; // https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate - peerConnections[peer_id].addIceCandidate(new RTCIceCandidate(ice_candidate)).catch((err) => { + const pc = peerConnections[peer_id]; + + if (!pc) { + queueIceCandidate(peer_id, ice_candidate); + return; + } + + // Queue until remoteDescription is set; otherwise addIceCandidate can fail and the candidate is lost. + if (!pc.remoteDescription || !pc.remoteDescription.type) { + queueIceCandidate(peer_id, ice_candidate); + return; + } + + pc.addIceCandidate(new RTCIceCandidate(ice_candidate)).catch((err) => { console.error('[Error] addIceCandidate', err); }); } @@ -3229,6 +3281,7 @@ function handleDisconnect(reason) { chatDataChannels = {}; fileDataChannels = {}; peerConnections = {}; + pendingIceCandidates = {}; peerScreenMediaElements = {}; peerVideoMediaElements = {}; peerAudioMediaElements = {}; @@ -3302,6 +3355,7 @@ function handleRemovePeer(config) { delete chatDataChannels[peer_id]; delete fileDataChannels[peer_id]; delete peerConnections[peer_id]; + delete pendingIceCandidates[peer_id]; delete peerScreenMediaElements[peerScreenId]; delete peerVideoMediaElements[peerVideoId]; delete peerAudioMediaElements[peerAudioId];