mirror of
https://github.com/xtr-dev/rondevu-demo.git
synced 2025-12-12 03:23:23 +00:00
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:
18
package-lock.json
generated
18
package-lock.json
generated
@@ -1233,9 +1233,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.9.3",
|
"version": "2.9.6",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz",
|
||||||
"integrity": "sha512-8QdH6czo+G7uBsNo0GiUfouPN1lRzKdJTGnKXwe12gkFbnnOUaUKGN55dMkfy+mnxmvjwl9zcI4VncczcVXDhA==",
|
"integrity": "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -1286,9 +1286,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001759",
|
"version": "1.0.30001760",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz",
|
||||||
"integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==",
|
"integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -1382,9 +1382,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.266",
|
"version": "1.5.267",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.266.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
||||||
"integrity": "sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==",
|
"integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
|||||||
72
src/App.jsx
72
src/App.jsx
@@ -94,6 +94,8 @@ export default function App() {
|
|||||||
// Service - we publish one service that can accept multiple connections
|
// Service - we publish one service that can accept multiple connections
|
||||||
const [myServicePublished, setMyServicePublished] = useState(false);
|
const [myServicePublished, setMyServicePublished] = useState(false);
|
||||||
const [hostConnections, setHostConnections] = useState({}); // Track incoming connections as host
|
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);
|
const chatEndRef = useRef(null);
|
||||||
|
|
||||||
@@ -208,6 +210,53 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
}, [setupStep, myUsername, rondevu, myServicePublished]);
|
}, [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
|
// Check online status periodically
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (setupStep !== 'ready' || !rondevu) return;
|
if (setupStep !== 'ready' || !rondevu) return;
|
||||||
@@ -303,6 +352,7 @@ export default function App() {
|
|||||||
// We'll create a pool of offers manually
|
// We'll create a pool of offers manually
|
||||||
const offers = [];
|
const offers = [];
|
||||||
const poolSize = 10; // Support up to 10 simultaneous connections
|
const poolSize = 10; // Support up to 10 simultaneous connections
|
||||||
|
const connections = []; // Track connections before publishing
|
||||||
|
|
||||||
console.log('[Publish] Creating', poolSize, 'peer connections...');
|
console.log('[Publish] Creating', poolSize, 'peer connections...');
|
||||||
for (let i = 0; i < poolSize; i++) {
|
for (let i = 0; i < poolSize; i++) {
|
||||||
@@ -317,12 +367,7 @@ export default function App() {
|
|||||||
await pc.setLocalDescription(offer);
|
await pc.setLocalDescription(offer);
|
||||||
|
|
||||||
offers.push({ sdp: offer.sdp });
|
offers.push({ sdp: offer.sdp });
|
||||||
|
connections.push({ pc, dc, index: i });
|
||||||
// Store connection for later
|
|
||||||
setHostConnections(prev => ({
|
|
||||||
...prev,
|
|
||||||
[`host-${i}`]: { pc, dc, status: 'waiting' }
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish service
|
// Publish service
|
||||||
@@ -330,14 +375,27 @@ export default function App() {
|
|||||||
console.log('[Publish] Publishing service with FQN:', fqn);
|
console.log('[Publish] Publishing service with FQN:', fqn);
|
||||||
console.log('[Publish] Public key:', rondevu.getPublicKey());
|
console.log('[Publish] Public key:', rondevu.getPublicKey());
|
||||||
|
|
||||||
await rondevu.publishService({
|
const publishResult = await rondevu.publishService({
|
||||||
serviceFqn: fqn,
|
serviceFqn: fqn,
|
||||||
offers,
|
offers,
|
||||||
ttl: 300000, // 5 minutes
|
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);
|
setMyServicePublished(true);
|
||||||
|
|
||||||
console.log('✅ Chat service published successfully with', poolSize, 'offers');
|
console.log('✅ Chat service published successfully with', poolSize, 'offers');
|
||||||
|
console.log('[Publish] Offer IDs:', Object.keys(offerMapping));
|
||||||
toast.success('Chat service started!');
|
toast.success('Chat service started!');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Publish] Failed to publish service:', err);
|
console.error('[Publish] Failed to publish service:', err);
|
||||||
|
|||||||
Reference in New Issue
Block a user