Bas van den Aakster f8fb842935 Revert to v0.18.3 answer processing logic
Reverted pollInternal to exactly match v0.18.3 which was the last
working version. The changes in v0.18.5 and v0.18.6 that moved the
answered flag and timestamp updates were causing issues.

v0.18.3 working logic restored:
- Check !activeOffer.answered
- Call setRemoteDescription (no try/catch)
- Set answered = true AFTER
- Update lastPollTimestamp AFTER
- No pre-emptive timestamp updates

The only difference from v0.18.3 is the eventemitter3 import which
should not affect answer processing behavior.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 14:39:15 +01:00
2025-12-14 10:21:43 +00: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 (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 }
  }
})

// 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 1.2 MiB
Languages
TypeScript 98.3%
JavaScript 1.7%