Files
rondevu-client/MIGRATION.md
Bas van den Aakster a480fa3ba4 Add durable WebRTC connections with auto-reconnect and message buffering (v0.18.8)
- Add connection state machine with proper lifecycle management
- Implement automatic reconnection with exponential backoff
- Add message buffering during disconnections
- Create RondevuConnection base class with state tracking
- Create OffererConnection and AnswererConnection classes
- Fix ICE polling lifecycle (now stops when connected)
- Add fillOffers() semaphore to prevent exceeding maxOffers
- Implement answer fingerprinting to prevent duplicate processing
- Add dual ICE state monitoring (iceConnectionState + connectionState)
- Fix data channel handler timing issues
- Add comprehensive event system (20+ events)
- Add connection timeouts and proper cleanup

Breaking changes:
- connectToService() now returns AnswererConnection instead of ConnectionContext
- connection:opened event signature changed: (offerId, dc) → (offerId, connection)
- Direct DataChannel access replaced with connection wrapper API

See MIGRATION.md for upgrade guide.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 16:52:57 +01:00

10 KiB

Migration Guide: v0.18.x → v0.18.8

Version 0.18.8 introduces significant improvements to connection durability and reliability. While we've maintained backward compatibility where possible, there are some breaking changes to be aware of.

Overview of Changes

New Features

  • Automatic reconnection with exponential backoff
  • Message buffering during disconnections
  • Connection state machine with proper lifecycle management
  • Rich event system for connection monitoring
  • ICE polling lifecycle (stops when connected, no more resource leaks)

Breaking Changes

  • connectToService() now returns AnswererConnection instead of ConnectionContext
  • connection:opened event signature changed for offerer side
  • Direct DataChannel access replaced with connection wrapper API

Migration Steps

1. Answerer Side (connectToService)

Old API (v0.18.7 and earlier)

const context = await rondevu.connectToService({
  serviceFqn: 'chat:1.0.0@alice',
  onConnection: ({ dc, pc, peerUsername }) => {
    console.log('Connected to', peerUsername)

    dc.addEventListener('message', (event) => {
      console.log('Received:', event.data)
    })

    dc.addEventListener('open', () => {
      dc.send('Hello!')
    })
  }
})

// Access peer connection
context.pc.getStats()

New API (v0.18.8)

const connection = await rondevu.connectToService({
  serviceFqn: 'chat:1.0.0@alice',
  connectionConfig: {
    reconnectEnabled: true,      // Optional: enable auto-reconnect
    bufferEnabled: true,          // Optional: enable message buffering
    connectionTimeout: 30000      // Optional: connection timeout (ms)
  }
})

// Listen for connection events
connection.on('connected', () => {
  console.log('Connected!')
  connection.send('Hello!')
})

connection.on('message', (data) => {
  console.log('Received:', data)
})

// Optional: monitor reconnection
connection.on('reconnecting', (attempt) => {
  console.log(`Reconnecting, attempt ${attempt}`)
})

connection.on('reconnect:success', () => {
  console.log('Reconnection successful!')
})

// Access peer connection if needed
const pc = connection.getPeerConnection()
const dc = connection.getDataChannel()

Key Changes:

  • Removed onConnection callback
  • Use event listeners instead: connection.on('connected', ...)
  • Removed direct dc.send() access
  • Use connection.send() for automatic buffering support
  • Added automatic reconnection and message buffering

2. Offerer Side (publishService)

Old API (v0.18.7 and earlier)

await rondevu.publishService({
  service: 'chat:1.0.0',
  maxOffers: 5
})

await rondevu.startFilling()

// Handle connections
rondevu.on('connection:opened', (offerId, dc) => {
  console.log('New connection:', offerId)

  dc.addEventListener('message', (event) => {
    console.log('Received:', event.data)
  })

  dc.send('Welcome!')
})

New API (v0.18.8)

await rondevu.publishService({
  service: 'chat:1.0.0',
  maxOffers: 5,
  connectionConfig: {
    reconnectEnabled: true,
    bufferEnabled: true
  }
})

await rondevu.startFilling()

// Handle connections - signature changed!
rondevu.on('connection:opened', (offerId, connection) => {
  console.log('New connection:', offerId)

  connection.on('message', (data) => {
    console.log('Received:', data)
  })

  connection.on('disconnected', () => {
    console.log('Connection lost, will auto-reconnect')
  })

  connection.send('Welcome!')
})

Key Changes:

  • ⚠️ Event signature changed: (offerId, dc)(offerId, connection)
  • Removed direct DataChannel access
  • Use connection.send() and connection.on('message', ...)
  • Connection object provides lifecycle events

New Connection Configuration

All connection-related options are now configured via connectionConfig:

interface ConnectionConfig {
  // Timeouts
  connectionTimeout: number      // Default: 30000ms (30s)
  iceGatheringTimeout: number    // Default: 10000ms (10s)

  // Reconnection
  reconnectEnabled: boolean      // Default: true
  maxReconnectAttempts: number   // Default: 5
  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
}

Example Usage

const connection = await rondevu.connectToService({
  serviceFqn: 'chat:1.0.0@alice',
  connectionConfig: {
    // Disable auto-reconnect if you want manual control
    reconnectEnabled: false,

    // Disable buffering if messages are time-sensitive
    bufferEnabled: false,

    // Increase timeout for slow networks
    connectionTimeout: 60000,

    // Reduce retry attempts
    maxReconnectAttempts: 3
  }
})

New Event System

Connection Lifecycle Events

connection.on('state:changed', ({ oldState, newState, reason }) => {})
connection.on('connecting', () => {})
connection.on('connected', () => {})
connection.on('disconnected', (reason) => {})
connection.on('failed', (error) => {})
connection.on('closed', (reason) => {})

Reconnection Events

connection.on('reconnect:scheduled', ({ attempt, delay, maxAttempts }) => {})
connection.on('reconnect:attempting', (attempt) => {})
connection.on('reconnect:success', () => {})
connection.on('reconnect:failed', (error) => {})
connection.on('reconnect:exhausted', (attempts) => {})

Message Events

connection.on('message', (data) => {})
connection.on('message:sent', (data, buffered) => {})
connection.on('message:buffered', (data) => {})
connection.on('message:replayed', (message) => {})
connection.on('message:buffer:overflow', (discardedMessage) => {})

ICE Events

connection.on('ice:candidate:local', (candidate) => {})
connection.on('ice:candidate:remote', (candidate) => {})
connection.on('ice:connection:state', (state) => {})
connection.on('ice:polling:started', () => {})
connection.on('ice:polling:stopped', () => {})

Common Migration Patterns

Pattern 1: Simple Message Handler

Before:

dc.addEventListener('message', (event) => {
  console.log(event.data)
})
dc.send('Hello')

After:

connection.on('message', (data) => {
  console.log(data)
})
connection.send('Hello')

Pattern 2: Connection State Monitoring

Before:

pc.oniceconnectionstatechange = () => {
  console.log('ICE state:', pc.iceConnectionState)
}

After:

connection.on('ice:connection:state', (state) => {
  console.log('ICE state:', state)
})

// Or use higher-level events
connection.on('connected', () => console.log('Connected!'))
connection.on('disconnected', () => console.log('Disconnected!'))

Pattern 3: Handling Connection Failures

Before:

pc.oniceconnectionstatechange = () => {
  if (pc.iceConnectionState === 'failed') {
    // Manual reconnection logic
    pc.close()
    await setupNewConnection()
  }
}

After:

// Automatic reconnection built-in!
connection.on('reconnecting', (attempt) => {
  console.log(`Reconnecting... attempt ${attempt}`)
})

connection.on('reconnect:success', () => {
  console.log('Back online!')
})

connection.on('reconnect:exhausted', (attempts) => {
  console.log(`Failed after ${attempts} attempts`)
  // Fallback logic here
})

Pattern 4: Accessing Raw RTCPeerConnection/DataChannel

If you need low-level access:

const connection = await rondevu.connectToService({ ... })

// Get raw objects if needed
const pc = connection.getPeerConnection()
const dc = connection.getDataChannel()

// Use them directly (bypasses buffering/reconnection features)
if (dc) {
  dc.addEventListener('message', (event) => {
    console.log(event.data)
  })
}

Note: Using raw DataChannel bypasses automatic buffering and reconnection features.


Backward Compatibility Notes

What Still Works

publishService() API (just add connectionConfig optionally) findService() API (unchanged) All RondevuAPI methods (unchanged) ICE server presets (unchanged) Username and keypair management (unchanged)

What Changed

⚠️ connectToService() return type: ConnectionContextAnswererConnection ⚠️ connection:opened event signature: (offerId, dc)(offerId, connection) ⚠️ Direct DataChannel access replaced with connection wrapper

What's New

Automatic reconnection with exponential backoff Message buffering during disconnections Rich event system (20+ events) Connection state machine ICE polling lifecycle management (no more resource leaks)


Troubleshooting

Issue: "connection.send is not a function"

You're trying to use the old dc.send() API. Update to:

// Old
dc.send('Hello')

// New
connection.send('Hello')

Issue: "Cannot read property 'addEventListener' of undefined"

You're trying to access dc directly. Update to event listeners:

// Old
dc.addEventListener('message', (event) => {
  console.log(event.data)
})

// New
connection.on('message', (data) => {
  console.log(data)
})

Issue: Messages not being delivered

Check if buffering is enabled and connection is established:

connection.on('connected', () => {
  // Only send after connected
  connection.send('Hello')
})

// Monitor buffer
connection.on('message:buffered', (data) => {
  console.log('Message buffered, will send when reconnected')
})

Need Help?


Summary Checklist

When migrating from v0.18.7 to v0.18.8:

  • Update connectToService() to use returned AnswererConnection
  • Replace dc.addEventListener('message', ...) with connection.on('message', ...)
  • Replace dc.send() with connection.send()
  • Update connection:opened event handler signature
  • Consider adding reconnection event handlers
  • Optionally configure connectionConfig for your use case
  • Test connection resilience (disconnect network, should auto-reconnect)
  • Remove manual reconnection logic (now built-in)