Merge pull request #3 from xtr-dev/claude/fix-issue-2-6zYvD

Fix issue #2
This commit is contained in:
Bas
2025-12-14 11:03:47 +01:00
committed by GitHub
2 changed files with 43 additions and 15 deletions

12
package-lock.json generated
View File

@@ -9,8 +9,7 @@
"version": "0.18.0", "version": "0.18.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/ed25519": "^3.0.0", "@noble/ed25519": "^3.0.0"
"@xtr-dev/rondevu-client": "^0.9.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.1", "@eslint/js": "^9.39.1",
@@ -1310,15 +1309,6 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/@xtr-dev/rondevu-client": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@xtr-dev/rondevu-client/-/rondevu-client-0.9.2.tgz",
"integrity": "sha512-DVow5AOPU40dqQtlfQK7J2GNX8dz2/4UzltMqublaPZubbkRYgocvp0b76NQu5F6v150IstMV2N49uxAYqogVw==",
"license": "MIT",
"dependencies": {
"@noble/ed25519": "^3.0.0"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.15.0", "version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",

View File

@@ -418,11 +418,42 @@ export class Rondevu {
this.debug('Creating new offer...') this.debug('Creating new offer...')
// Create the offer using the factory // Create the offer using the factory
// Note: The factory may call setLocalDescription() which triggers ICE gathering
const { pc, dc, offer } = await this.offerFactory(rtcConfig) const { pc, dc, offer } = await this.offerFactory(rtcConfig)
// Auto-append username to service // Auto-append username to service
const serviceFqn = `${this.currentService}@${this.username}` const serviceFqn = `${this.currentService}@${this.username}`
// Queue to buffer ICE candidates generated before we have the offerId
// This fixes the race condition where ICE candidates are lost because
// they're generated before we can set up the handler with the offerId
const earlyIceCandidates: RTCIceCandidateInit[] = []
let offerId: string | null = null
// Set up a queuing ICE candidate handler immediately after getting the pc
// This captures any candidates that fire before we have the offerId
pc.onicecandidate = async (event) => {
if (event.candidate) {
// Handle both browser and Node.js (wrtc) environments
const candidateData = typeof event.candidate.toJSON === 'function'
? event.candidate.toJSON()
: event.candidate
if (offerId) {
// We have the offerId, send directly
try {
await this.api.addOfferIceCandidates(serviceFqn, offerId, [candidateData])
} catch (err) {
console.error('[Rondevu] Failed to send ICE candidate:', err)
}
} else {
// Queue for later - we don't have the offerId yet
this.debug('Queuing early ICE candidate')
earlyIceCandidates.push(candidateData)
}
}
}
// Publish to server // Publish to server
const result = await this.api.publishService({ const result = await this.api.publishService({
serviceFqn, serviceFqn,
@@ -432,7 +463,7 @@ export class Rondevu {
message: '', message: '',
}) })
const offerId = result.offers[0].offerId offerId = result.offers[0].offerId
// Store active offer // Store active offer
this.activeOffers.set(offerId, { this.activeOffers.set(offerId, {
@@ -446,15 +477,22 @@ export class Rondevu {
this.debug(`Offer created: ${offerId}`) this.debug(`Offer created: ${offerId}`)
// Set up ICE candidate handler // Send any queued early ICE candidates
this.setupIceCandidateHandler(pc, serviceFqn, offerId) if (earlyIceCandidates.length > 0) {
this.debug(`Sending ${earlyIceCandidates.length} early ICE candidates`)
try {
await this.api.addOfferIceCandidates(serviceFqn, offerId, earlyIceCandidates)
} catch (err) {
console.error('[Rondevu] Failed to send early ICE candidates:', err)
}
}
// Monitor connection state // Monitor connection state
pc.onconnectionstatechange = () => { pc.onconnectionstatechange = () => {
this.debug(`Offer ${offerId} connection state: ${pc.connectionState}`) this.debug(`Offer ${offerId} connection state: ${pc.connectionState}`)
if (pc.connectionState === 'failed' || pc.connectionState === 'closed') { if (pc.connectionState === 'failed' || pc.connectionState === 'closed') {
this.activeOffers.delete(offerId) this.activeOffers.delete(offerId!)
this.fillOffers() // Try to replace failed offer this.fillOffers() // Try to replace failed offer
} }
} }