Update README for v0.18.9: Document durable connections and breaking changes

- Add v0.18.9 features section highlighting durable connections
- Document breaking change: connectToService() returns AnswererConnection
- Update Quick Start examples with correct event-driven API
- Add explicit warnings about waiting for 'connected' event
- Include Connection Configuration and Events documentation
- Add Migration Guide section with upgrade instructions
- Update changelog with v0.18.9 changes

This addresses user issues where messages were buffered instead of sent
due to sending before connection established.
This commit is contained in:
2025-12-14 18:27:31 +01:00
parent 919aeb7b90
commit cb0bbe342c

310
README.md
View File

@@ -2,9 +2,9 @@
[![npm version](https://img.shields.io/npm/v/@xtr-dev/rondevu-client)](https://www.npmjs.com/package/@xtr-dev/rondevu-client)
🌐 **Simple WebRTC signaling client with username-based discovery**
🌐 **WebRTC signaling client with durable connections**
TypeScript/JavaScript client for Rondevu, providing WebRTC signaling with username claiming, service publishing/discovery, and efficient batch polling.
TypeScript/JavaScript client for Rondevu, providing WebRTC signaling with **automatic reconnection**, **message buffering**, username claiming, service publishing/discovery, and efficient batch polling.
**Related repositories:**
- [@xtr-dev/rondevu-client](https://github.com/xtr-dev/rondevu-client) - TypeScript client library ([npm](https://www.npmjs.com/package/@xtr-dev/rondevu-client))
@@ -15,15 +15,22 @@ TypeScript/JavaScript client for Rondevu, providing WebRTC signaling with userna
## Features
### ✨ New in v0.18.9
- **🔄 Automatic Reconnection**: Built-in exponential backoff for failed connections
- **📦 Message Buffering**: Queues messages during disconnections, replays on reconnect
- **📊 Connection State Machine**: Explicit lifecycle tracking with native RTC events
- **🎯 Rich Event System**: 20+ events for monitoring connection health
- **⚡ Improved Reliability**: ICE polling lifecycle management, proper cleanup
### Core Features
- **Username Claiming**: Secure ownership with Ed25519 signatures
- **Anonymous Users**: Auto-generated anonymous usernames for quick testing
- **Service Publishing**: Publish services with multiple offers for connection pooling
- **Service Discovery**: Direct lookup, random discovery, or paginated search
- **Efficient Batch Polling**: Single endpoint for answers and ICE candidates (50% fewer requests)
- **Efficient Batch Polling**: Single endpoint for answers and ICE candidates
- **Semantic Version Matching**: Compatible version resolution (chat:1.0.0 matches any 1.x.x)
- **TypeScript**: Full type safety and autocomplete
- **Keypair Management**: Generate or reuse Ed25519 keypairs
- **Automatic Signatures**: All authenticated requests signed automatically
## Installation
@@ -49,27 +56,35 @@ const rondevu = await Rondevu.connect({
await rondevu.publishService({
service: 'chat:1.0.0',
maxOffers: 5, // Maintain up to 5 concurrent offers
offerFactory: async (pc) => {
// pc is created by Rondevu with ICE handlers already attached
const dc = pc.createDataChannel('chat')
dc.addEventListener('open', () => {
console.log('Connection opened!')
dc.send('Hello from Alice!')
})
dc.addEventListener('message', (e) => {
console.log('Received:', e.data)
})
const offer = await pc.createOffer()
await pc.setLocalDescription(offer)
return { dc, offer }
connectionConfig: {
reconnectEnabled: true, // Auto-reconnect on failures
bufferEnabled: true, // Buffer messages during disconnections
connectionTimeout: 30000 // 30 second timeout
}
})
// 3. Start accepting connections
await rondevu.startFilling()
// 4. Handle incoming connections
rondevu.on('connection:opened', (offerId, connection) => {
console.log('New connection:', offerId)
// Listen for messages
connection.on('message', (data) => {
console.log('Received:', data)
})
// Monitor connection state
connection.on('connected', () => {
console.log('Fully connected!')
connection.send('Hello from Alice!')
})
connection.on('disconnected', () => {
console.log('Connection lost, will auto-reconnect')
})
})
```
### Connecting to a Service (Answerer)
@@ -84,25 +99,38 @@ const rondevu = await Rondevu.connect({
iceServers: 'ipv4-turn'
})
// 2. Connect to service (automatic WebRTC setup)
// 2. Connect to service - returns AnswererConnection
const connection = await rondevu.connectToService({
serviceFqn: 'chat:1.0.0@alice',
onConnection: ({ dc, peerUsername }) => {
console.log('Connected to', peerUsername)
dc.addEventListener('message', (e) => {
console.log('Received:', e.data)
})
dc.addEventListener('open', () => {
dc.send('Hello from Bob!')
})
connectionConfig: {
reconnectEnabled: true,
bufferEnabled: true,
maxReconnectAttempts: 5
}
})
// Access connection
connection.dc.send('Another message')
connection.pc.close() // Close when done
// 3. Setup event handlers
connection.on('connected', () => {
console.log('Connected to alice!')
connection.send('Hello from Bob!')
})
connection.on('message', (data) => {
console.log('Received:', data)
})
// 4. Monitor connection health
connection.on('reconnecting', (attempt) => {
console.log(`Reconnecting... attempt ${attempt}`)
})
connection.on('reconnect:success', () => {
console.log('Back online!')
})
connection.on('failed', (error) => {
console.error('Connection failed:', error)
})
```
## Core API
@@ -126,52 +154,234 @@ await rondevu.publishService({
service: string, // e.g., 'chat:1.0.0' (username auto-appended)
maxOffers: number, // Maximum concurrent offers to maintain
offerFactory?: OfferFactory, // Optional: custom offer creation
ttl?: number // Optional: offer lifetime in ms (default: 300000)
ttl?: number, // Optional: offer lifetime in ms (default: 300000)
connectionConfig?: Partial<ConnectionConfig> // Optional: durability settings
})
await rondevu.startFilling() // Start accepting connections
rondevu.stopFilling() // Stop and close all connections
```
### Service Discovery
```typescript
// Direct lookup (with username)
await rondevu.getService('chat:1.0.0@alice')
// Random discovery (without username)
await rondevu.discoverService('chat:1.0.0')
// Paginated discovery
await rondevu.discoverServices('chat:1.0.0', limit, offset)
```
### Connecting to Services
**⚠️ Breaking Change in v0.18.9:** `connectToService()` now returns `AnswererConnection` instead of `ConnectionContext`.
```typescript
// New API (v0.18.9+)
const connection = await rondevu.connectToService({
serviceFqn?: string, // Full FQN like 'chat:1.0.0@alice'
service?: string, // Service without username (for discovery)
username?: string, // Target username (combined with service)
onConnection?: (context) => void, // Called when data channel opens
connectionConfig?: Partial<ConnectionConfig>, // Durability settings
rtcConfig?: RTCConfiguration // Optional: override ICE servers
})
// Setup event handlers
connection.on('connected', () => {
connection.send('Hello!')
})
connection.on('message', (data) => {
console.log(data)
})
```
### Connection Configuration
```typescript
interface ConnectionConfig {
// Timeouts
connectionTimeout: number // Default: 30000ms (30s)
iceGatheringTimeout: number // Default: 10000ms (10s)
// Reconnection
reconnectEnabled: boolean // Default: true
maxReconnectAttempts: number // Default: 5 (0 = infinite)
reconnectBackoffBase: number // Default: 1000ms
reconnectBackoffMax: number // Default: 30000ms (30s)
// Message buffering
bufferEnabled: boolean // Default: true
maxBufferSize: number // Default: 100 messages
maxBufferAge: number // Default: 60000ms (1 min)
// Debug
debug: boolean // Default: false
}
```
### Connection Events
```typescript
// Lifecycle events
connection.on('connecting', () => {})
connection.on('connected', () => {})
connection.on('disconnected', (reason) => {})
connection.on('failed', (error) => {})
connection.on('closed', (reason) => {})
// Reconnection events
connection.on('reconnecting', (attempt) => {})
connection.on('reconnect:success', () => {})
connection.on('reconnect:failed', (error) => {})
connection.on('reconnect:exhausted', (attempts) => {})
// Message events
connection.on('message', (data) => {})
connection.on('message:buffered', (data) => {})
connection.on('message:replayed', (message) => {})
// ICE events
connection.on('ice:connection:state', (state) => {})
connection.on('ice:polling:started', () => {})
connection.on('ice:polling:stopped', () => {})
```
### Service Discovery
```typescript
// Unified discovery API
const service = await rondevu.findService(
'chat:1.0.0@alice', // Direct lookup (with username)
{ mode: 'direct' }
)
const service = await rondevu.findService(
'chat:1.0.0', // Random discovery (without username)
{ mode: 'random' }
)
const result = await rondevu.findService(
'chat:1.0.0',
{
mode: 'paginated',
limit: 20,
offset: 0
}
)
```
## Migration Guide
**Upgrading from v0.18.7 or earlier?** See [MIGRATION.md](./MIGRATION.md) for detailed upgrade instructions.
### Quick Migration Summary
**Before (v0.18.7):**
```typescript
const context = await rondevu.connectToService({
serviceFqn: 'chat:1.0.0@alice',
onConnection: ({ dc }) => {
dc.addEventListener('message', (e) => console.log(e.data))
dc.send('Hello')
}
})
```
**After (v0.18.9):**
```typescript
const connection = await rondevu.connectToService({
serviceFqn: 'chat:1.0.0@alice'
})
connection.on('connected', () => {
connection.send('Hello') // Use connection.send()
})
connection.on('message', (data) => {
console.log(data) // data is already extracted
})
```
## Advanced Usage
### Custom Offer Factory
```typescript
await rondevu.publishService({
service: 'file-transfer:1.0.0',
maxOffers: 3,
offerFactory: async (pc) => {
// Customize data channel settings
const dc = pc.createDataChannel('files', {
ordered: true,
maxRetransmits: 10
})
// Add custom listeners
dc.addEventListener('open', () => {
console.log('Transfer channel ready')
})
const offer = await pc.createOffer()
await pc.setLocalDescription(offer)
return { dc, offer }
}
})
```
### Accessing Raw RTCPeerConnection
```typescript
const connection = await rondevu.connectToService({ ... })
// Get raw objects if needed
const pc = connection.getPeerConnection()
const dc = connection.getDataChannel()
// Note: Using raw DataChannel bypasses buffering/reconnection features
if (dc) {
dc.addEventListener('message', (e) => {
console.log('Raw message:', e.data)
})
}
```
### Disabling Durability Features
```typescript
const connection = await rondevu.connectToService({
serviceFqn: 'chat:1.0.0@alice',
connectionConfig: {
reconnectEnabled: false, // Disable auto-reconnect
bufferEnabled: false, // Disable message buffering
}
})
```
## Documentation
📚 **[MIGRATION.md](./MIGRATION.md)** - Upgrade guide from v0.18.7 to v0.18.9
📚 **[ADVANCED.md](./ADVANCED.md)** - Comprehensive guide including:
- Detailed API reference for all methods
- Type definitions and interfaces
- Platform support (Browser & Node.js)
- Advanced usage patterns
- Username rules and service FQN format
- Examples and migration guides
## Examples
- [React Demo](https://github.com/xtr-dev/rondevu-demo) - Full browser UI ([live](https://ronde.vu))
## Changelog
### v0.18.9 (Latest)
- Add durable WebRTC connections with state machine
- Implement automatic reconnection with exponential backoff
- Add message buffering during disconnections
- Fix ICE polling lifecycle (stops when connected)
- Add fillOffers() semaphore to prevent exceeding maxOffers
- **Breaking:** `connectToService()` returns `AnswererConnection` instead of `ConnectionContext`
- **Breaking:** `connection:opened` event signature changed
- See [MIGRATION.md](./MIGRATION.md) for upgrade guide
### v0.18.8
- Initial durable connections implementation
### v0.18.3
- Fix EventEmitter cross-platform compatibility
## License
MIT