From 538315c51f06d5aab5215fa7e4ceb608a56b89d9 Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Wed, 10 Dec 2025 19:20:34 +0100 Subject: [PATCH] Implement answer polling for offerer side Host now polls for answered offers every 2 seconds using the new efficient batch endpoint. When an answer is received, the host automatically sets the remote description to complete the WebRTC connection. Changes: - Store offerId to RTCPeerConnection mapping when publishing - Poll for answered offers with timestamp filtering - Automatically handle incoming answers - Track last answer timestamp to avoid reprocessing This completes the bidirectional WebRTC signaling flow. --- package-lock.json | 18 ++++++------ src/App.jsx | 72 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b20599..d9790c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1233,9 +1233,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.3.tgz", - "integrity": "sha512-8QdH6czo+G7uBsNo0GiUfouPN1lRzKdJTGnKXwe12gkFbnnOUaUKGN55dMkfy+mnxmvjwl9zcI4VncczcVXDhA==", + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz", + "integrity": "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1286,9 +1286,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001759", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", - "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", "dev": true, "funding": [ { @@ -1382,9 +1382,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.266", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz", - "integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "dev": true, "license": "ISC" }, diff --git a/src/App.jsx b/src/App.jsx index 4b71c54..e528531 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -94,6 +94,8 @@ export default function App() { // Service - we publish one service that can accept multiple connections const [myServicePublished, setMyServicePublished] = useState(false); const [hostConnections, setHostConnections] = useState({}); // Track incoming connections as host + const [offerIdToPeerConnection, setOfferIdToPeerConnection] = useState({}); // Map offerId to RTCPeerConnection + const [lastAnswerTimestamp, setLastAnswerTimestamp] = useState(0); // Track last answer timestamp for polling const chatEndRef = useRef(null); @@ -208,6 +210,53 @@ export default function App() { } }, [setupStep, myUsername, rondevu, myServicePublished]); + // Poll for answered offers (host side) + useEffect(() => { + if (!myServicePublished || !rondevu || Object.keys(offerIdToPeerConnection).length === 0) { + return; + } + + console.log('[Answer Polling] Starting to poll for answers...'); + + const pollForAnswers = async () => { + try { + const result = await rondevu.getAnsweredOffers(lastAnswerTimestamp); + + if (result.offers.length > 0) { + console.log(`[Answer Polling] Found ${result.offers.length} new answered offer(s)`); + + for (const answer of result.offers) { + const pc = offerIdToPeerConnection[answer.offerId]; + + if (pc && pc.signalingState !== 'stable') { + console.log(`[Answer Polling] Setting remote answer for offer ${answer.offerId}`); + + await pc.setRemoteDescription({ + type: 'answer', + sdp: answer.sdp + }); + + // Update last answer timestamp + setLastAnswerTimestamp(prev => Math.max(prev, answer.answeredAt)); + + console.log(`✅ [Answer Polling] Remote answer set for offer ${answer.offerId}`); + } else if (pc) { + console.log(`[Answer Polling] Skipping offer ${answer.offerId} - already in stable state`); + } + } + } + } catch (err) { + console.error('[Answer Polling] Error polling for answers:', err); + } + }; + + // Poll every 2 seconds + const interval = setInterval(pollForAnswers, 2000); + pollForAnswers(); // Initial poll + + return () => clearInterval(interval); + }, [myServicePublished, rondevu, offerIdToPeerConnection, lastAnswerTimestamp]); + // Check online status periodically useEffect(() => { if (setupStep !== 'ready' || !rondevu) return; @@ -303,6 +352,7 @@ export default function App() { // We'll create a pool of offers manually const offers = []; const poolSize = 10; // Support up to 10 simultaneous connections + const connections = []; // Track connections before publishing console.log('[Publish] Creating', poolSize, 'peer connections...'); for (let i = 0; i < poolSize; i++) { @@ -317,12 +367,7 @@ export default function App() { await pc.setLocalDescription(offer); offers.push({ sdp: offer.sdp }); - - // Store connection for later - setHostConnections(prev => ({ - ...prev, - [`host-${i}`]: { pc, dc, status: 'waiting' } - })); + connections.push({ pc, dc, index: i }); } // Publish service @@ -330,14 +375,27 @@ export default function App() { console.log('[Publish] Publishing service with FQN:', fqn); console.log('[Publish] Public key:', rondevu.getPublicKey()); - await rondevu.publishService({ + const publishResult = await rondevu.publishService({ serviceFqn: fqn, offers, ttl: 300000, // 5 minutes }); + // Map offerIds to peer connections + 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' }; + }); + + setOfferIdToPeerConnection(offerMapping); + setHostConnections(hostConnMap); setMyServicePublished(true); + console.log('✅ Chat service published successfully with', poolSize, 'offers'); + console.log('[Publish] Offer IDs:', Object.keys(offerMapping)); toast.success('Chat service started!'); } catch (err) { console.error('[Publish] Failed to publish service:', err);