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.
This commit is contained in:
2025-12-10 19:20:34 +01:00
parent 6a24514e7b
commit 538315c51f
2 changed files with 74 additions and 16 deletions

18
package-lock.json generated
View File

@@ -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"
},

View File

@@ -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);