Add polling guard to prevent concurrent pollInternal() execution.
The setInterval callback doesn't await the async pollInternal(),
which could cause multiple polls to process the same answer before
lastPollTimestamp is updated, resulting in "Called in wrong state:
stable" errors from setRemoteDescription().
- Update README.md example to match new OfferFactory signature
- Add error handling and RTCPeerConnection cleanup on factory failure
- Document setupIceCandidateHandler() method usage
- Use undefined instead of null for offerId variable
Co-authored-by: Bas <bvdaakster@users.noreply.github.com>
Change the OfferFactory signature to receive the RTCPeerConnection as a
parameter instead of rtcConfig. This allows Rondevu to:
1. Create the RTCPeerConnection itself
2. Set up ICE candidate handlers BEFORE the factory runs
3. Ensure no candidates are lost when setLocalDescription() triggers
ICE gathering
This is a cleaner fix for #2 that eliminates the race condition at the
source rather than working around it with queuing.
BREAKING CHANGE: OfferFactory signature changed from
(rtcConfig: RTCConfiguration) => Promise<OfferContext>
to
(pc: RTCPeerConnection) => Promise<OfferContext>
OfferContext no longer includes 'pc' since it's now provided by Rondevu.
Queue ICE candidates that are generated before we have the offerId from
the server. When the factory calls setLocalDescription(), ICE gathering
starts immediately, but we couldn't send candidates until we had the
offerId from publishService(). Now we:
1. Set up a queuing handler immediately after getting the pc from factory
2. Buffer any early candidates while publishing to get the offerId
3. Flush all queued candidates once we have the offerId
4. Continue handling future candidates normally
Fixes#2
Allow users to pass WebRTC polyfills (RTCPeerConnection, RTCIceCandidate) through RondevuOptions instead of manually setting global variables. The client now automatically applies these to globalThis when provided.
This simplifies Node.js integration:
- Before: Users had to manually set globalThis.RTCPeerConnection
- After: Pass rtcPeerConnection and rtcIceCandidate options
Example:
const rondevu = await Rondevu.connect({
apiUrl: 'https://api.example.com',
username: 'alice',
cryptoAdapter: new NodeCryptoAdapter(),
rtcPeerConnection: wrtc.RTCPeerConnection,
rtcIceCandidate: wrtc.RTCIceCandidate
})
Handle both browser and Node.js (wrtc) environments when processing ICE candidates. Browsers provide toJSON() method on candidates, but wrtc library candidates are already plain objects. Check for toJSON() existence before calling it.
Fixes: TypeError: event.candidate.toJSON is not a function in Node.js
Remove all references to NODE_HOST_GUIDE.md and test-connect.js:
- README.md: Removed Node.js Host Guide section and links
- ADVANCED.md: Simplified Node.js example, removed guide references
Keep inline Node.js example code for reference.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Change relative paths to absolute GitHub URLs:
- ../demo/NODE_HOST_GUIDE.md → github.com/xtr-dev/rondevu-demo/blob/main/NODE_HOST_GUIDE.md
- ../demo/test-connect.js → github.com/xtr-dev/rondevu-demo/blob/main/test-connect.js
Affected files:
- README.md (2 links fixed)
- ADVANCED.md (2 links fixed)
Why: Client and demo are separate repositories, so relative paths
don't work when viewing on GitHub. Now links work from anywhere.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace `candidate: any` with `candidate: RTCIceCandidateInit | null`:
Changes:
- api.ts poll() return type (line 366)
- rondevu.ts pollOffers() return type (line 827)
- IceCandidate interface (line 41)
Benefits:
- Better type safety and IntelliSense support
- Matches WebRTC spec (candidates can be null)
- Catches potential type errors at compile time
- Clearer API contracts for consumers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Break down 129-line method into focused helper methods:
New private methods:
- resolveServiceFqn(): Determines full FQN from various input options
Handles direct FQN, service+username, or discovery mode
- startIcePolling(): Manages remote ICE candidate polling
Encapsulates polling logic and interval management
Benefits:
- connectToService() reduced from 129 to ~98 lines
- Each method has single responsibility
- Easier to test and maintain
- Better code readability with clear method names
- Reusable components for future features
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Refactor ICE candidate handler setup:
- Create setupIceCandidateHandler() private method
- Remove duplicate code from createOffer() and connectToService()
- Both methods now call the shared handler setup
Benefits:
- DRY principle: 13 lines of duplicate code eliminated
- Easier to maintain: changes to ICE handling logic only needed in one place
- Consistent error handling across both code paths
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Validation: Add checks for empty strings in connectToService()
- Validates serviceFqn is not empty string
- Validates service is not empty string
- Validates username is not empty string
- Prevents silent failures from whitespace-only inputs
Impact: Better error messages for invalid inputs
Cleanup: Remove Service and ServiceRequest types from imports
- Not used anywhere in rondevu.ts
- Reduces unnecessary dependencies
- Verified: Build passes with no TypeScript errors
- Remove entire RondevuSignaler class documentation section
- Remove PollingConfig interface from Types section
- Delete obsolete USAGE.md file (completely outdated)
- Update all examples to use new automatic API
- Fix migration examples to show current publishService() format
- Remove serviceFqn, offers array from PublishServiceOptions
- Make service and maxOffers required fields
- Simplify publishService() to only support automatic mode
- Remove RondevuSignaler class completely
- Update exports to include new types (ConnectionContext, ConnectToServiceOptions)
- Update test-connect.js to use connectToService()
- Remove all "manual mode" and "legacy" references from documentation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add 4 ICE server presets: ipv4-turn, hostname-turns, google-stun, relay-only
- Update Rondevu.connect() to accept preset string or custom RTCIceServer array
- Support both automatic (maxOffers) and manual (offers array) modes in publishService()
- Rename internal poll() to pollInternal() to avoid conflict with public API
- Update README examples to use presets and proper offer factory pattern
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds automatic request batching to reduce HTTP overhead by combining
multiple RPC calls into a single request.
Features:
- RpcBatcher class for intelligent request batching
- Configurable batch size (default: 10 requests)
- Configurable wait time (default: 50ms)
- Throttling to prevent overwhelming the server (default: 10ms)
- Automatic flushing when batch is full
- Enabled by default, can be disabled via options
Changes:
- Created rpc-batcher.ts with RpcBatcher class
- Updated RondevuAPI to use batcher by default
- Added batching option to RondevuOptions
- Updated README with batching documentation
- Bumped version to 0.16.0
Example usage:
// Default (batching enabled with defaults)
const rondevu = new Rondevu({ apiUrl: 'https://api.ronde.vu' })
// Custom batching settings
const rondevu = new Rondevu({
apiUrl: 'https://api.ronde.vu',
batching: { maxBatchSize: 20, maxWaitTime: 100 }
})
// Disable batching
const rondevu = new Rondevu({
apiUrl: 'https://api.ronde.vu',
batching: false
})
This can reduce HTTP requests by up to 90% during intensive operations
like ICE candidate exchange.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Adds CryptoAdapter interface with WebCryptoAdapter (browser) and
NodeCryptoAdapter (Node.js 19+) implementations.
Changes:
- Created crypto-adapter.ts interface
- Created web-crypto-adapter.ts for browser environments
- Created node-crypto-adapter.ts for Node.js environments
- Updated RondevuAPI to accept optional CryptoAdapter
- Updated Rondevu class to pass crypto adapter through
- Exported adapters and types in index.ts
- Updated README with platform support documentation
- Bumped version to 0.15.0
This allows the client library to work in both browser and Node.js
environments by providing platform-specific crypto implementations.
Example usage in Node.js:
import { Rondevu, NodeCryptoAdapter } from '@xtr-dev/rondevu-client'
const rondevu = new Rondevu({
apiUrl: 'https://api.ronde.vu',
cryptoAdapter: new NodeCryptoAdapter()
})
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The rondevu.ts wrapper was generating its own message and signature
which were being ignored by the API layer (which generates its own).
This caused the old signature to be passed but not used.
Simplified by removing the redundant code and letting the API layer
handle all authentication.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Updated client to send publicKey in all authenticated RPC requests,
enabling server-side implicit username claiming.
Changes:
- Added publicKey field to RpcRequest interface
- Added publicKey to all authenticated RPC method calls
- Removed username claiming requirement from publishService()
- Auto-mark username as claimed after successful publish
Users no longer need to call claimUsername() before publishing
services. The server will automatically claim the username on
the first authenticated request.
🤖 Generated with Claude Code
https://claude.com/claude-code
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
BREAKING CHANGES:
- All API calls now go to POST /rpc endpoint
- Request format: { method, message, signature, params }
- Response format: { success, result } or { success: false, error }
- Simplified API methods to match RPC methods
- Removed checkUsername, added isUsernameAvailable
- Renamed postOfferAnswer to answerOffer
- Removed discoverService/discoverServices (use getService)
Changes:
- Completely refactored api.ts for RPC interface
- Updated rondevu.ts wrapper methods
- Updated rondevu-signaler.ts to use new API
- Fixed exports in index.ts
BREAKING CHANGES:
- Renamed pollOffers() to poll() (matches new /poll endpoint)
- Removed getAnsweredOffers() method (use poll() instead)
- Updated endpoint path from /offers/poll to /poll
- Updated auth message format from 'pollOffers' to 'poll'
The auth middleware expects username, signature, and message in the request
body for POST requests. The service object already contains signature and
message from rondevu.ts, so we just need to ensure they're sent by spreading
the service object and adding username.
- Removed auto-claim logic from initialize() method
- Users must now explicitly call claimUsername()
- Updated JSDoc to reflect manual claiming requirement
- Anonymous usernames are generated but not auto-claimed
BREAKING CHANGE: Anonymous users are no longer automatically claimed.
Applications must explicitly call claimUsername() before publishing services.
BREAKING CHANGE: Remove credential-based authentication
- Remove Credentials interface and all credential-related code
- Remove register() method from RondevuAPI
- Remove setCredentials() and getAuthHeader() methods
RondevuAPI changes:
- Constructor now requires username and keypair (not credentials)
- Add generateAuthParams() helper for automatic signature generation
- All API methods now include {username, signature, message} auth
- POST requests: auth in body
- GET requests: auth in query params
- Remove Authorization header from all fetch calls
Rondevu class changes:
- Make username optional in RondevuOptions (auto-generates anon username)
- Make keypair optional (auto-generates if not provided)
- Add generateAnonymousUsername() method (anon-{timestamp}-{random})
- Update initialize() to create API with username+keypair (no register call)
- Auto-claim username for anonymous users during initialize()
- Add lazy getAPI() to ensure initialization
Message format for auth:
- Format: action:username:params:timestamp
- Examples: publishService:alice:chat:1.0.0@alice:1234567890
- Each request generates unique signature with timestamp
Index exports:
- Remove Credentials export (no longer exists)
Breaking Change: Offerer now uses pollOffers() for efficient batch polling
Changes:
- Offerer: use pollOffers() for combined answer+ICE polling (1 request vs 2)
- Answerer: keep using getOfferIceCandidates() (separate endpoint still needed)
- Add isOfferer flag to track role
- Replace startAnswerPolling() with startPolling() using pollOffers()
- Filter ICE candidates by role (answerer candidates for offerer)
- Use single lastPollTimestamp for both answers and ICE
- Reduce HTTP requests by 50% for offerers
- More efficient signaling with timestamp-based filtering
No backwards compatibility maintained per user request.
- ICE candidates now include role ('offerer' | 'answerer')
- ICE candidates now include peerId for matching
- Enables clients to filter candidates by role
- Supports bidirectional ICE exchange
- Add pollOffers() method to RondevuAPI class
- Expose pollOffers() in Rondevu class
- Returns both answered offers and ICE candidates in single call
- Supports timestamp-based filtering with optional 'since' parameter
- More efficient than separate getAnsweredOffers() and getOfferIceCandidates() calls
Added RondevuAPI.getAnsweredOffers() and Rondevu.getAnsweredOffers()
methods to efficiently retrieve all answered offers with optional
timestamp filtering.
This enables offerers to poll for incoming connections in a single
request instead of polling each offer individually.