BREAKING CHANGE: Client API now uses service UUIDs for WebRTC signaling
- Replace answerOffer() with answerService()
- Replace getAnswer() with getServiceAnswer()
- Replace addIceCandidates() with addServiceIceCandidates()
- Replace getIceCandidates() with getServiceIceCandidates()
- Update RondevuSignaler to use service UUID instead of offer ID for signaling
- Automatically track offerId returned from service endpoints
- Bump version to 0.12.0
Matches server v0.4.0 service-based API refactor.
- WebRTCContext now accepts optional RTCConfiguration
- ServiceHost and ServiceClient accept optional rtcConfiguration option
- Allows custom STUN/TURN server configuration
- Version bump to 0.10.1
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add RondevuService: High-level API for username claiming and service publishing with Ed25519 signatures
- Add ServiceHost: Manages offer pool for hosting services with auto-replacement
- Add ServiceClient: Connects to hosted services with automatic reconnection
- Add NoOpSignaler: Placeholder signaler for connection setup
- Integrate Ed25519 signature functionality from @noble/ed25519
- Add ESLint and Prettier configuration with 4-space indentation
- Add demo with local signaling test
- Version bump to 0.10.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added comprehensive API client and signaling implementation:
**RondevuAPI** - Single class for all Rondevu endpoints:
- Authentication: register()
- Offers: createOffers(), getOffer(), answerOffer(), getAnswer(), searchOffers()
- ICE Candidates: addIceCandidates(), getIceCandidates()
- Services: publishService(), getService(), searchServices()
- Usernames: checkUsername(), claimUsername()
**RondevuSignaler** - ICE candidate exchange:
- addIceCandidate() - Send local candidates to server
- addListener() - Poll for remote candidates (1 second intervals)
- Returns cleanup function (Binnable) to stop polling
- Handles offer expiration gracefully
**WebRTCRondevuConnection** - WebRTC connection wrapper:
- Handles offer/answer creation
- Manages ICE candidate exchange via Signaler
- Type-safe event bus for state changes and messages
- Queue and send message interfaces
**Utilities**:
- createBin() - Cleanup function collector
- Binnable type - Cleanup function signature
All classes use the shared RondevuAPI client for consistent
error handling and authentication.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented EventBus class with full TypeScript type inference:
- Generic type parameter TEvents for event name to payload mapping
- Type-safe on/once/off/emit methods with inferred data types
- Utility methods: clear, listenerCount, eventNames
- Complete JSDoc documentation with usage examples
Added core connection types:
- ConnectionIdentity, ConnectionState, ConnectionInterface
- QueueMessageOptions for message queuing
- Connection composite type
All types and classes exported from main index.
Example usage:
```typescript
interface MyEvents {
'user:connected': { userId: string; timestamp: number };
'message:received': string;
}
const bus = new EventBus<MyEvents>();
// TypeScript knows data is { userId: string; timestamp: number }
bus.on('user:connected', (data) => {
console.log(data.userId, data.timestamp);
});
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Removed all complex implementations and kept only the essentials:
- Removed durable/ directory (DurableConnection, DurableChannel, etc.)
- Removed peer/ directory (entire state machine)
- Removed service-pool.ts, offer-pool.ts, rondevu.ts
- Removed auth.ts, offers.ts, usernames.ts, event-emitter.ts
- Added empty ConnectionManager class as starting point
The client now contains just:
- src/connection-manager.ts - Empty class skeleton
- src/index.ts - Simple export
This provides a clean slate to rebuild the client with a simpler
architecture focused on core WebRTC connection management.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed critical timing issue where ICE candidates were generated before
the handler was attached, causing them to be lost:
- Set up onicecandidate handler BEFORE setLocalDescription()
- Collect candidates in array while waiting for offer ID
- Send all pending candidates once offer ID is available
- Add detailed logging for service pool ICE candidates
- Log candidate type (host/srflx/relay) for debugging
This ensures all ICE candidates are captured and sent to the signaling
server, and provides visibility into what types of candidates are being
generated (especially important for diagnosing TURN relay issues).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added comprehensive logging to track WebRTC ICE candidate exchange:
- Log local candidate generation with type (host/srflx/relay)
- Log when candidates are sent to signaling server
- Log remote candidate reception and addition
- Log ICE gathering state changes
- Log ICE connection state changes
- Enhanced ICE error logging with details
This will help diagnose connection issues and TURN server problems.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The service pool was creating peer connections but never setting up
onicecandidate handlers. This meant ICE candidates generated by the
TURN relay were never sent to the signaling server, causing all
ICE connectivity checks to fail with no remote candidates.
Changes:
- Add onicecandidate handlers in createOffers() after getting offer IDs
- Add onicecandidate handler in publishInitialService() after publishing
- Handlers send candidates to server via addIceCandidates()
This fixes the "all checks completed success=0 fail=1" error where
remote candidates were empty.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This fixes the root cause of all connection failures. The service pool
was creating data channels but discarding the references, then trying
to wait for a 'datachannel' event that would never fire.
Changes:
- Add dataChannel tracking to OfferPool and ServicePool
- Save data channel references when creating offers
- Pass channels through the answer flow
- Use the existing channel instead of waiting for an event
- Wait for channel.onopen instead of ondatachannel
The offerer (service pool) creates the data channel and must keep that
reference. The 'ondatachannel' event only fires on the answerer side.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Modified peerDataChannelHandler to automatically create DurableChannel
instances when receiving data channels from the remote peer. This fixes
the connection flow where the answerer needs to receive the data channel
that the offerer created.
Previously, the handler only attached if a DurableChannel already existed,
which meant incoming channels from the remote peer would be ignored.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add signaling state validation before setting remote answer
- Fix race condition by removing offers from pool before processing
- Add detailed debug logging for state mismatch errors
- Prevent duplicate processing of answered offers
This fixes the "Cannot set remote answer in state stable" error
that occurred when multiple answers arrived in quick succession.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The publishInitialService() method was creating an offer with SDP
but not returning it. This caused the first offer in the pool to
have an empty SDP string, which failed when trying to set it as
the local description when an answer arrived.
Fixed by:
- Storing the offer SDP before closing the peer connection
- Adding offerSdp to the return value of publishInitialService()
- Using the returned SDP when creating the initial offer in the pool
This ensures all offers in the pool have valid SDP that can be
used to recreate the peer connection state when answers arrive.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When handling answered offers in pooled services, we were creating fresh
peer connections in "stable" state and trying to set the remote answer,
which caused "Cannot set remote answer in state stable" error.
Fixed by:
- Adding offerSdp to AnsweredOffer interface
- Passing original offer SDP through the offer pool
- Setting local description (offer) before remote description (answer)
This ensures the peer connection is in "have-local-offer" state before
applying the answer, satisfying WebRTC's state machine requirements.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The sync ed25519 functions (sign, getPublicKey) require hashes.sha512,
but WebCrypto only provides async digest. Switch to using the async
ed25519 API which works with hashes.sha512Async.
This fixes the "hashes.sha512 not set" error.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
@noble/ed25519 v3.0.0 requires explicit SHA-512 hash function setup
before using any cryptographic operations. This fixes the
"hashes.sha512 not set" error.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Export RondevuUsername, RondevuServices, RondevuDiscovery classes
- Export all related type interfaces
- Export pool-related types (PoolStatus, PooledServiceHandle)
This fixes the issue where the V2 APIs were available as properties
on the Rondevu client instance but not accessible as standalone imports.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add OfferPool class for managing multiple offers with auto-refill polling
- Add ServicePool class for orchestrating pooled connections and connection registry
- Modify exposeService() to support poolSize parameter (backward compatible)
- Add discovery API with service resolution and online status checking
- Add username claiming with Ed25519 signatures and TTL-based expiry
- Fix TypeScript import errors (RondevuPeer default export)
- Fix RondevuPeer instantiation to use RondevuOffers instance
- Fix peer.answer() calls to include required PeerOptions parameter
- Fix Ed25519 API call (randomSecretKey vs randomPrivateKey)
- Remove bloom filter (V1 legacy code)
- Update version to 0.8.0
- Document pooling feature and new APIs in README
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Always generate cryptographically random 128-bit peer IDs to prevent peer ID hijacking vulnerability. This ensures peer IDs are secure through collision resistance rather than relying on expiration-based protection.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add info field to CreateOfferRequest and Offer types
- Update README with info field examples and documentation
- Public metadata field visible in all API responses
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds a public method to create RTCDataChannels for sending/receiving arbitrary data between peers. The offerer can call this method before creating an offer, and the answerer will receive the channel via the existing 'datachannel' event.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add secret field to PeerOptions interface
- Pass secret when creating offers in CreatingOfferState
- Pass secret when answering offers in AnsweringState
- Bump version to 0.7.7
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Document secret parameter in offer creation examples
- Add Protected Offers section with detailed usage
- Update API reference for create() and answer() methods
- Show hasSecret flag in discovery responses
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added optional startsWith parameter to topics query:
- Filters topics by prefix on the server side
- Updated TypeScript types
- Supports response with startsWith field
Version bumped to 0.7.5
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added optional polyfill parameters to RondevuOptions to support Node.js:
- RTCPeerConnection: Custom peer connection implementation
- RTCSessionDescription: Custom session description implementation
- RTCIceCandidate: Custom ICE candidate implementation
This allows users to plug in wrtc or node-webrtc packages for full
WebRTC support in Node.js environments. Updated documentation with
usage examples and environment compatibility matrix.
Version bumped to 0.7.4
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Removed unused legacy files: client.ts and types.ts (old API)
- Removed heartbeat() method from offers API (doesn't actually reset TTL)
- Removed heartbeat() documentation from README
- Server only uses expires_at for cleanup, last_seen is never checked
- Offers expire purely based on their original TTL
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replaced `new RondevuPeer(client.offers)` with `client.createPeer()`
- Updated import to only import Rondevu (not RondevuPeer)
- Updated Custom RTCConfiguration example to pass config to createPeer()
- Removed rtcConfig from answer() call (should be passed to createPeer)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replaced all references to removed RondevuConnection class
- Updated to use RondevuPeer with state machine lifecycle
- Documented state transitions (idle → creating-offer → waiting-for-answer → exchanging-ice → connected)
- Added trickle ICE documentation
- Updated all code examples to use addEventListener
- Added timeout configuration examples
- Documented peer properties (stateName, connectionState, offerId, role)
- Updated TypeScript types in API reference
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The answerer was getting 403 Forbidden when sending ICE candidates because
the server didn't know who the answerer was yet. ICE gathering starts when
setLocalDescription is called, but we were calling /answer AFTER that.
Fixed by sending the answer to the server BEFORE setLocalDescription:
1. Create answer SDP
2. Send answer to server (registers answererPeerId)
3. Set up ICE handler
4. Set local description (ICE gathering starts)
This ensures the server has answererPeerId set before ICE candidates arrive,
so they're properly authorized.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
ICE candidate handler was being set up AFTER setLocalDescription, but ICE
gathering starts when setLocalDescription is called. This meant candidates
were generated before the handler was attached, so they were never sent to
the server, causing connection failures.
Fixed by:
- Setting up ICE handler BEFORE setLocalDescription in both offer and answer flows
- Changed setupIceCandidateHandler() to use this.peer.offerId instead of parameter
- Handler now checks this.peer.offerId before sending (waits for it to be set)
Order of operations now:
1. Set up ICE candidate handler
2. Call setLocalDescription (ICE gathering starts)
3. Set this.peer.offerId (handler can now send candidates)
This ensures all ICE candidates are captured and sent to the server.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Refactored common ICE candidate handling logic to reduce code duplication:
- Added setupIceCandidateHandler() method to base PeerState class
- Moved iceCandidateHandler property to base class
- Updated cleanup() in base class to remove ICE candidate handler
- Removed duplicate handler code from CreatingOfferState and AnsweringState
- Both states now call this.setupIceCandidateHandler(offerId)
This eliminates ~15 lines of duplicated code per state and ensures consistent ICE candidate handling across all states that need it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>