mirror of
https://github.com/xtr-dev/rondevu-demo.git
synced 2025-12-10 18:53:24 +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": {
|
||||
"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"
|
||||
},
|
||||
|
||||
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
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user