Claude febe3b7270 Fix early ICE candidates lost due to late handler setup in createOffer()
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
2025-12-14 09:56:27 +00:00
2025-12-12 23:24:56 +01:00

Rondevu Client

npm version

🌐 Simple WebRTC signaling client with username-based discovery

TypeScript/JavaScript client for Rondevu, providing WebRTC signaling with username claiming, service publishing/discovery, and efficient batch polling.

Related repositories:


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)
  • 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

npm install @xtr-dev/rondevu-client

Quick Start

Publishing a Service (Offerer)

import { Rondevu } from '@xtr-dev/rondevu-client'

// 1. Connect to Rondevu
const rondevu = await Rondevu.connect({
  apiUrl: 'https://api.ronde.vu',
  username: 'alice',  // Or omit for anonymous username
  iceServers: 'ipv4-turn'  // Preset: 'ipv4-turn', 'hostname-turns', 'google-stun', 'relay-only'
})

// 2. Publish service with automatic offer management
await rondevu.publishService({
  service: 'chat:1.0.0',
  maxOffers: 5,  // Maintain up to 5 concurrent offers
  offerFactory: async (rtcConfig) => {
    const pc = new RTCPeerConnection(rtcConfig)
    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 { pc, dc, offer }
  }
})

// 3. Start accepting connections
await rondevu.startFilling()

Connecting to a Service (Answerer)

import { Rondevu } from '@xtr-dev/rondevu-client'

// 1. Connect to Rondevu
const rondevu = await Rondevu.connect({
  apiUrl: 'https://api.ronde.vu',
  username: 'bob',
  iceServers: 'ipv4-turn'
})

// 2. Connect to service (automatic WebRTC setup)
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!')
    })
  }
})

// Access connection
connection.dc.send('Another message')
connection.pc.close()  // Close when done

Core API

Rondevu.connect()

const rondevu = await Rondevu.connect({
  apiUrl: string,          // Required: Signaling server URL
  username?: string,       // Optional: your username (auto-generates anonymous if omitted)
  keypair?: Keypair,       // Optional: reuse existing keypair
  iceServers?: IceServerPreset | RTCIceServer[],  // Optional: preset or custom config
  debug?: boolean          // Optional: enable debug logging (default: false)
})

Service Publishing

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)
})

await rondevu.startFilling()  // Start accepting connections
rondevu.stopFilling()         // Stop and close all connections

Service Discovery

// 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

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
  rtcConfig?: RTCConfiguration  // Optional: override ICE servers
})

Documentation

📚 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

License

MIT

Description
No description provided
Readme 985 KiB
Languages
TypeScript 97.4%
JavaScript 2.6%