Implement combined polling and ICE candidate exchange for host

- Add offerId to RTCPeerConnection mapping for answer processing
- Setup ICE candidate handlers on offerer peer connections
- Replace answer-only polling with combined pollOffers() endpoint
- Process both answers and ICE candidates in single poll operation
- Track last poll timestamp to avoid reprocessing old data
- Send offerer ICE candidates to server via addOfferIceCandidates()
- Reduces HTTP requests and completes bidirectional ICE exchange
This commit is contained in:
2025-12-10 19:33:31 +01:00
parent 538315c51f
commit a6329c8708

View File

@@ -210,26 +210,27 @@ export default function App() {
}
}, [setupStep, myUsername, rondevu, myServicePublished]);
// Poll for answered offers (host side)
// Combined polling for answers and ICE candidates (host side)
useEffect(() => {
if (!myServicePublished || !rondevu || Object.keys(offerIdToPeerConnection).length === 0) {
return;
}
console.log('[Answer Polling] Starting to poll for answers...');
console.log('[Host Polling] Starting combined polling for answers and ICE candidates...');
const pollForAnswers = async () => {
const poll = async () => {
try {
const result = await rondevu.getAnsweredOffers(lastAnswerTimestamp);
const result = await rondevu.pollOffers(lastAnswerTimestamp);
if (result.offers.length > 0) {
console.log(`[Answer Polling] Found ${result.offers.length} new answered offer(s)`);
// Process answers
if (result.answers.length > 0) {
console.log(`[Host Polling] Found ${result.answers.length} new answer(s)`);
for (const answer of result.offers) {
for (const answer of result.answers) {
const pc = offerIdToPeerConnection[answer.offerId];
if (pc && pc.signalingState !== 'stable') {
console.log(`[Answer Polling] Setting remote answer for offer ${answer.offerId}`);
console.log(`[Host Polling] Setting remote answer for offer ${answer.offerId}`);
await pc.setRemoteDescription({
type: 'answer',
@@ -239,20 +240,47 @@ export default function App() {
// Update last answer timestamp
setLastAnswerTimestamp(prev => Math.max(prev, answer.answeredAt));
console.log(`✅ [Answer Polling] Remote answer set for offer ${answer.offerId}`);
console.log(`✅ [Host Polling] Remote answer set for offer ${answer.offerId}`);
} else if (pc) {
console.log(`[Answer Polling] Skipping offer ${answer.offerId} - already in stable state`);
console.log(`[Host Polling] Skipping offer ${answer.offerId} - already in stable state`);
}
}
}
// Process ICE candidates
let totalIceCandidates = 0;
for (const [offerId, candidates] of Object.entries(result.iceCandidates)) {
const pc = offerIdToPeerConnection[offerId];
if (pc && candidates.length > 0) {
console.log(`[Host Polling] Processing ${candidates.length} ICE candidate(s) for offer ${offerId}`);
for (const item of candidates) {
if (item.candidate) {
try {
await pc.addIceCandidate(new RTCIceCandidate(item.candidate));
totalIceCandidates++;
// Update timestamp
setLastAnswerTimestamp(prev => Math.max(prev, item.createdAt));
} catch (err) {
console.warn(`[Host Polling] Failed to add ICE candidate for offer ${offerId}:`, err);
}
}
}
}
}
if (totalIceCandidates > 0) {
console.log(`✅ [Host Polling] Added ${totalIceCandidates} ICE candidate(s)`);
}
} catch (err) {
console.error('[Answer Polling] Error polling for answers:', err);
console.error('[Host Polling] Error polling:', err);
}
};
// Poll every 2 seconds
const interval = setInterval(pollForAnswers, 2000);
pollForAnswers(); // Initial poll
const interval = setInterval(poll, 2000);
poll(); // Initial poll
return () => clearInterval(interval);
}, [myServicePublished, rondevu, offerIdToPeerConnection, lastAnswerTimestamp]);
@@ -359,7 +387,7 @@ export default function App() {
const pc = new RTCPeerConnection(getCurrentRtcConfig());
const dc = pc.createDataChannel('chat');
// Setup handlers
// Setup handlers (will be enhanced with offerId later)
setupHostConnection(pc, dc, myUsername);
// Create offer
@@ -381,13 +409,24 @@ export default function App() {
ttl: 300000, // 5 minutes
});
// Map offerIds to peer connections
// Map offerIds to peer connections and setup ICE handlers
const offerMapping = {};
const hostConnMap = {};
publishResult.offers.forEach((offer, idx) => {
const conn = connections[idx];
offerMapping[offer.offerId] = conn.pc;
hostConnMap[`host-${idx}`] = { pc: conn.pc, dc: conn.dc, offerId: offer.offerId, status: 'waiting' };
// Setup ICE candidate handler for offerer
conn.pc.onicecandidate = (event) => {
if (event.candidate) {
rondevu.addOfferIceCandidates(
fqn,
offer.offerId,
[event.candidate.toJSON()]
).catch(err => console.error(`[Host] Failed to send ICE candidate for offer ${offer.offerId}:`, err));
}
};
});
setOfferIdToPeerConnection(offerMapping);