From 5f4743e0869f2106557d65fd11fdb2129b86aa5b Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Fri, 12 Dec 2025 23:14:54 +0100 Subject: [PATCH] Make README more concise with ADVANCED.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure documentation for better discoverability: Changes: - README.md: 551 → 181 lines (67% reduction) - ADVANCED.md: New comprehensive guide (500+ lines) README.md now contains: ✅ Features overview ✅ Installation ✅ Quick start examples (offerer & answerer) ✅ Core API summary ✅ Links to advanced docs ADVANCED.md contains: 📚 Complete API reference (all methods) 📚 Type definitions 📚 Platform support details (Browser & Node.js) 📚 Advanced usage patterns 📚 Username rules and FQN format 📚 Migration guides 📚 Examples Benefits: - Faster onboarding for new users - Essential info in README - Detailed reference still accessible - Better documentation structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- ADVANCED.md | 490 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 458 +++++------------------------------------------- 2 files changed, 534 insertions(+), 414 deletions(-) create mode 100644 ADVANCED.md diff --git a/ADVANCED.md b/ADVANCED.md new file mode 100644 index 0000000..302f4ac --- /dev/null +++ b/ADVANCED.md @@ -0,0 +1,490 @@ +# Rondevu Client - Advanced Usage + +Comprehensive guide for advanced features, platform support, and detailed API reference. + +## Table of Contents + +- [API Reference](#api-reference) +- [Types](#types) +- [Advanced Usage](#advanced-usage) +- [Platform Support](#platform-support) +- [Username Rules](#username-rules) +- [Service FQN Format](#service-fqn-format) +- [Examples](#examples) +- [Migration Guide](#migration-guide) + +--- + +## API Reference + +### Rondevu Class + +Main class for all Rondevu operations. + +```typescript +import { Rondevu } from '@xtr-dev/rondevu-client' + +// Create and connect to Rondevu +const rondevu = await Rondevu.connect({ + apiUrl: string, // Signaling server URL + username?: string, // Optional: your username (auto-generates anonymous if omitted) + keypair?: Keypair, // Optional: reuse existing keypair + cryptoAdapter?: CryptoAdapter // Optional: platform-specific crypto (defaults to WebCryptoAdapter) + batching?: BatcherOptions | false // Optional: RPC batching configuration + iceServers?: IceServerPreset | RTCIceServer[] // Optional: preset name or custom STUN/TURN servers + debug?: boolean // Optional: enable debug logging (default: false) +}) +``` + +#### Platform Support (Browser & Node.js) + +The client supports both browser and Node.js environments using crypto adapters: + +**Browser (default):** +```typescript +import { Rondevu } from '@xtr-dev/rondevu-client' + +// WebCryptoAdapter is used by default - no configuration needed +const rondevu = await Rondevu.connect({ + apiUrl: 'https://api.ronde.vu', + username: 'alice' +}) +``` + +**Node.js (19+ or 18 with --experimental-global-webcrypto):** +```typescript +import { Rondevu, NodeCryptoAdapter } from '@xtr-dev/rondevu-client' + +const rondevu = await Rondevu.connect({ + apiUrl: 'https://api.ronde.vu', + username: 'alice', + cryptoAdapter: new NodeCryptoAdapter() +}) +``` + +**Note:** Node.js support requires: +- Node.js 19+ (crypto.subtle available globally), OR +- Node.js 18 with `--experimental-global-webcrypto` flag +- WebRTC implementation like `wrtc` or `node-webrtc` for RTCPeerConnection + +**Custom Crypto Adapter:** +```typescript +import { CryptoAdapter, Keypair } from '@xtr-dev/rondevu-client' + +class CustomCryptoAdapter implements CryptoAdapter { + async generateKeypair(): Promise { /* ... */ } + async signMessage(message: string, privateKey: string): Promise { /* ... */ } + async verifySignature(message: string, signature: string, publicKey: string): Promise { /* ... */ } + bytesToBase64(bytes: Uint8Array): string { /* ... */ } + base64ToBytes(base64: string): Uint8Array { /* ... */ } + randomBytes(length: number): Uint8Array { /* ... */ } +} + +const rondevu = await Rondevu.connect({ + apiUrl: 'https://api.ronde.vu', + cryptoAdapter: new CustomCryptoAdapter() +}) +``` + +#### Username Management + +Usernames are **automatically claimed** on the first authenticated request (like `publishService()`). + +```typescript +// Check if username is claimed (checks server) +await rondevu.isUsernameClaimed(): Promise + +// Get username +rondevu.getUsername(): string + +// Get public key +rondevu.getPublicKey(): string + +// Get keypair (for backup/storage) +rondevu.getKeypair(): Keypair +``` + +#### Service Publishing + +```typescript +// Publish service with offers +await rondevu.publishService({ + service: string, // e.g., 'chat:1.0.0' (username auto-appended) + maxOffers: number, // Maximum number of concurrent offers to maintain + offerFactory?: OfferFactory, // Optional: custom offer creation (defaults to simple data channel) + ttl?: number // Optional: milliseconds (default: 300000) +}): Promise +``` + +#### Service Discovery + +```typescript +// Direct lookup by FQN (with username) +await rondevu.getService('chat:1.0.0@alice'): Promise + +// Random discovery (without username) +await rondevu.discoverService('chat:1.0.0'): Promise + +// Paginated discovery (returns multiple offers) +await rondevu.discoverServices( + 'chat:1.0.0', // serviceVersion + 10, // limit + 0 // offset +): Promise<{ services: ServiceOffer[], count: number, limit: number, offset: number }> +``` + +#### WebRTC Signaling + +```typescript +// Post answer SDP +await rondevu.postOfferAnswer( + serviceFqn: string, + offerId: string, + sdp: string +): Promise<{ success: boolean, offerId: string }> + +// Get answer SDP (offerer polls this - deprecated, use pollOffers instead) +await rondevu.getOfferAnswer( + serviceFqn: string, + offerId: string +): Promise<{ sdp: string, offerId: string, answererId: string, answeredAt: number } | null> + +// Combined polling for answers and ICE candidates (RECOMMENDED for offerers) +await rondevu.pollOffers(since?: number): Promise<{ + answers: Array<{ + offerId: string + serviceId?: string + answererId: string + sdp: string + answeredAt: number + }> + iceCandidates: Record> +}> + +// Add ICE candidates +await rondevu.addOfferIceCandidates( + serviceFqn: string, + offerId: string, + candidates: RTCIceCandidateInit[] +): Promise<{ count: number, offerId: string }> + +// Get ICE candidates (with polling support) +await rondevu.getOfferIceCandidates( + serviceFqn: string, + offerId: string, + since: number = 0 +): Promise<{ candidates: IceCandidate[], offerId: string }> +``` + +### RondevuAPI Class + +Low-level HTTP API client (used internally by Rondevu class). + +```typescript +import { RondevuAPI } from '@xtr-dev/rondevu-client' + +const api = new RondevuAPI( + baseUrl: string, + username: string, + keypair: Keypair +) + +// Check username +await api.checkUsername(username: string): Promise<{ + available: boolean + publicKey?: string + claimedAt?: number + expiresAt?: number +}> + +// Note: Username claiming is now implicit - usernames are auto-claimed +// on first authenticated request to the server + +// ... (all other HTTP endpoints) +``` + +#### Cryptographic Helpers + +```typescript +// Generate Ed25519 keypair +const keypair = await RondevuAPI.generateKeypair(): Promise + +// Sign message +const signature = await RondevuAPI.signMessage( + message: string, + privateKey: string +): Promise + +// Verify signature +const valid = await RondevuAPI.verifySignature( + message: string, + signature: string, + publicKey: string +): Promise +``` + +--- + +## Types + +```typescript +interface Keypair { + publicKey: string // Base64-encoded Ed25519 public key + privateKey: string // Base64-encoded Ed25519 private key +} + +interface Service { + serviceId: string + offers: ServiceOffer[] + username: string + serviceFqn: string + createdAt: number + expiresAt: number +} + +interface ServiceOffer { + offerId: string + sdp: string + createdAt: number + expiresAt: number +} + +interface IceCandidate { + candidate: RTCIceCandidateInit | null + createdAt: number +} +``` + +--- + +## Advanced Usage + +### Anonymous Username + +```typescript +// Auto-generate anonymous username (format: anon-{timestamp}-{random}) +const rondevu = await Rondevu.connect({ + apiUrl: 'https://api.ronde.vu' + // No username provided - will generate anonymous username +}) + +console.log(rondevu.getUsername()) // e.g., "anon-lx2w34-a3f501" + +// Anonymous users behave exactly like regular users +await rondevu.publishService({ + service: 'chat:1.0.0', + maxOffers: 5 +}) + +await rondevu.startFilling() +``` + +### Persistent Keypair + +```typescript +// Save keypair and username to localStorage +const rondevu = await Rondevu.connect({ + apiUrl: 'https://api.ronde.vu', + username: 'alice' +}) + +// Save for later (username will be auto-claimed on first authenticated request) +localStorage.setItem('rondevu-username', rondevu.getUsername()) +localStorage.setItem('rondevu-keypair', JSON.stringify(rondevu.getKeypair())) + +// Load on next session +const savedUsername = localStorage.getItem('rondevu-username') +const savedKeypair = JSON.parse(localStorage.getItem('rondevu-keypair')) + +const rondevu2 = await Rondevu.connect({ + apiUrl: 'https://api.ronde.vu', + username: savedUsername, + keypair: savedKeypair +}) +``` + +### Service Discovery + +```typescript +// Get a random available service +const service = await rondevu.discoverService('chat:1.0.0') +console.log('Discovered:', service.username) + +// Get multiple services (paginated) +const result = await rondevu.discoverServices('chat:1.0.0', 10, 0) +console.log(`Found ${result.count} services:`) +result.services.forEach(s => console.log(` - ${s.username}`)) +``` + +### Multiple Concurrent Offers + +```typescript +// Publish service with multiple offers for connection pooling +const offers = [] +const connections = [] + +for (let i = 0; i < 5; i++) { + const pc = new RTCPeerConnection(rtcConfig) + const dc = pc.createDataChannel('chat') + const offer = await pc.createOffer() + await pc.setLocalDescription(offer) + + offers.push({ sdp: offer.sdp }) + connections.push({ pc, dc }) +} + +const service = await rondevu.publishService({ + service: 'chat:1.0.0', + offers, + ttl: 300000 +}) + +// Each offer can be answered independently +console.log(`Published ${service.offers.length} offers`) +``` + +### Debug Logging + +```typescript +// Enable debug logging to see internal operations +const rondevu = await Rondevu.connect({ + apiUrl: 'https://api.ronde.vu', + username: 'alice', + debug: true // All internal logs will be displayed with [Rondevu] prefix +}) + +// Debug logs include: +// - Connection establishment +// - Keypair generation +// - Service publishing +// - Offer creation +// - ICE candidate exchange +// - Connection state changes +``` + +--- + +## Platform Support + +### Modern Browsers +Works out of the box - no additional setup needed. + +### Node.js 18+ +Native fetch is available, but WebRTC requires polyfills: + +```bash +npm install wrtc +``` + +```typescript +import { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate } from 'wrtc' + +// Use wrtc implementations +const pc = new RTCPeerConnection() +``` + +--- + +## Username Rules + +- **Format**: Lowercase alphanumeric + dash (`a-z`, `0-9`, `-`) +- **Length**: 3-32 characters +- **Pattern**: `^[a-z0-9][a-z0-9-]*[a-z0-9]$` +- **Validity**: 365 days from claim/last use +- **Ownership**: Secured by Ed25519 public key signature + +--- + +## Service FQN Format + +- **Format**: `service:version@username` +- **Service**: Lowercase alphanumeric + dash (e.g., `chat`, `video-call`) +- **Version**: Semantic versioning (e.g., `1.0.0`, `2.1.3`) +- **Username**: Claimed username +- **Example**: `chat:1.0.0@alice` + +--- + +## Examples + +### Node.js Service Host Example + +You can host WebRTC services in Node.js that browser clients can connect to. See the [Node.js Host Guide](../demo/NODE_HOST_GUIDE.md) for a complete guide. + +**Quick example:** + +```typescript +import { Rondevu, NodeCryptoAdapter } from '@xtr-dev/rondevu-client' +import wrtc from 'wrtc' + +const { RTCPeerConnection } = wrtc + +// Initialize with Node crypto adapter +const rondevu = await Rondevu.connect({ + apiUrl: 'https://api.ronde.vu', + username: 'mybot', + cryptoAdapter: new NodeCryptoAdapter() +}) + +// Create peer connection (offerer creates data channel) +const pc = new RTCPeerConnection(rtcConfig) +const dc = pc.createDataChannel('chat') + +// Publish service (username auto-claimed on first publish) +await rondevu.publishService({ + service: 'chat:1.0.0', + maxOffers: 5 +}) + +await rondevu.startFilling() + +// Browser clients can now discover and connect to chat:1.0.0@mybot +``` + +See complete examples: +- [Node.js Host Guide](../demo/NODE_HOST_GUIDE.md) - Full guide with complete examples +- [test-connect.js](../demo/test-connect.js) - Working Node.js client example +- [React Demo](https://github.com/xtr-dev/rondevu-demo) - Complete browser UI ([live](https://ronde.vu)) + +--- + +## Migration Guide + +### Migration from v0.3.x + +v0.4.0 removes high-level abstractions and uses manual WebRTC setup: + +**Removed:** +- `ServiceHost` class (use manual WebRTC + `publishService()`) +- `ServiceClient` class (use manual WebRTC + `getService()`) +- `RTCDurableConnection` class (use native WebRTC APIs) +- `RondevuService` class (merged into `Rondevu`) + +**Added:** +- `pollOffers()` - Combined polling for answers and ICE candidates +- `publishService()` - Automatic offer pool management +- `connectToService()` - Automatic answering side setup + +**Migration Example:** + +```typescript +// Before (v0.3.x) - ServiceHost +const host = new ServiceHost({ + service: 'chat@1.0.0', + rondevuService: service +}) +await host.start() + +// After (v0.4.0+) - Automatic setup +await rondevu.publishService({ + service: 'chat:1.0.0', + maxOffers: 5 +}) + +await rondevu.startFilling() +``` diff --git a/README.md b/README.md index 8320b51..17f389e 100644 --- a/README.md +++ b/README.md @@ -38,14 +38,14 @@ npm install @xtr-dev/rondevu-client ```typescript import { Rondevu } from '@xtr-dev/rondevu-client' -// 1. Connect to Rondevu (generates keypair, username auto-claimed on first request) +// 1. Connect to Rondevu const rondevu = await Rondevu.connect({ apiUrl: 'https://api.ronde.vu', username: 'alice', // Or omit for anonymous username - iceServers: 'ipv4-turn' // Use preset: 'ipv4-turn', 'hostname-turns', 'google-stun', or 'relay-only' + iceServers: 'ipv4-turn' // Preset: 'ipv4-turn', 'hostname-turns', 'google-stun', 'relay-only' }) -// 2. Publish service +// 2. Publish service with automatic offer management await rondevu.publishService({ service: 'chat:1.0.0', maxOffers: 5, // Maintain up to 5 concurrent offers @@ -53,28 +53,23 @@ await rondevu.publishService({ const pc = new RTCPeerConnection(rtcConfig) const dc = pc.createDataChannel('chat') - // Set up event listeners during creation dc.addEventListener('open', () => { console.log('Connection opened!') dc.send('Hello from Alice!') }) dc.addEventListener('message', (e) => { - console.log('Received message:', e.data) + console.log('Received:', e.data) }) const offer = await pc.createOffer() await pc.setLocalDescription(offer) return { pc, dc, offer } - }, - ttl: 300000 + } }) // 3. Start accepting connections await rondevu.startFilling() - -// 4. Stop when done -// rondevu.stopFilling() ``` ### Connecting to a Service (Answerer) @@ -82,14 +77,14 @@ await rondevu.startFilling() ```typescript import { Rondevu } from '@xtr-dev/rondevu-client' -// 1. Connect to Rondevu with ICE server preset +// 1. Connect to Rondevu const rondevu = await Rondevu.connect({ apiUrl: 'https://api.ronde.vu', username: 'bob', iceServers: 'ipv4-turn' }) -// 2. Connect to service +// 2. Connect to service (automatic WebRTC setup) const connection = await rondevu.connectToService({ serviceFqn: 'chat:1.0.0@alice', onConnection: ({ dc, peerUsername }) => { @@ -110,441 +105,76 @@ connection.dc.send('Another message') connection.pc.close() // Close when done ``` -## API Reference +## Core API -### Rondevu Class - -Main class for all Rondevu operations. +### Rondevu.connect() ```typescript -import { Rondevu } from '@xtr-dev/rondevu-client' - -// Create and connect to Rondevu const rondevu = await Rondevu.connect({ - apiUrl: string, // Signaling server URL + apiUrl: string, // Required: Signaling server URL username?: string, // Optional: your username (auto-generates anonymous if omitted) keypair?: Keypair, // Optional: reuse existing keypair - cryptoAdapter?: CryptoAdapter // Optional: platform-specific crypto (defaults to WebCryptoAdapter) - batching?: BatcherOptions | false // Optional: RPC batching configuration + iceServers?: IceServerPreset | RTCIceServer[], // Optional: preset or custom config + debug?: boolean // Optional: enable debug logging (default: false) }) ``` -#### Platform Support (Browser & Node.js) - -The client supports both browser and Node.js environments using crypto adapters: - -**Browser (default):** -```typescript -import { Rondevu } from '@xtr-dev/rondevu-client' - -// WebCryptoAdapter is used by default - no configuration needed -const rondevu = await Rondevu.connect({ - apiUrl: 'https://api.ronde.vu', - username: 'alice' -}) -``` - -**Node.js (19+ or 18 with --experimental-global-webcrypto):** -```typescript -import { Rondevu, NodeCryptoAdapter } from '@xtr-dev/rondevu-client' - -const rondevu = await Rondevu.connect({ - apiUrl: 'https://api.ronde.vu', - username: 'alice', - cryptoAdapter: new NodeCryptoAdapter() -}) -``` - -**Note:** Node.js support requires: -- Node.js 19+ (crypto.subtle available globally), OR -- Node.js 18 with `--experimental-global-webcrypto` flag -- WebRTC implementation like `wrtc` or `node-webrtc` for RTCPeerConnection - -**Custom Crypto Adapter:** -```typescript -import { CryptoAdapter, Keypair } from '@xtr-dev/rondevu-client' - -class CustomCryptoAdapter implements CryptoAdapter { - async generateKeypair(): Promise { /* ... */ } - async signMessage(message: string, privateKey: string): Promise { /* ... */ } - async verifySignature(message: string, signature: string, publicKey: string): Promise { /* ... */ } - bytesToBase64(bytes: Uint8Array): string { /* ... */ } - base64ToBytes(base64: string): Uint8Array { /* ... */ } - randomBytes(length: number): Uint8Array { /* ... */ } -} - -const rondevu = await Rondevu.connect({ - apiUrl: 'https://api.ronde.vu', - cryptoAdapter: new CustomCryptoAdapter() -}) -``` - -#### Username Management - -Usernames are **automatically claimed** on the first authenticated request (like `publishService()`). +### Service Publishing ```typescript -// Check if username is claimed (checks server) -await rondevu.isUsernameClaimed(): Promise - -// Get username -rondevu.getUsername(): string - -// Get public key -rondevu.getPublicKey(): string - -// Get keypair (for backup/storage) -rondevu.getKeypair(): Keypair -``` - -#### Service Publishing - -```typescript -// Publish service with offers await rondevu.publishService({ - service: string, // e.g., 'chat:1.0.0' (username auto-appended) - offers: Array<{ sdp: string }>, - ttl?: number // Optional: milliseconds (default: 300000) -}): Promise -``` - -#### Service Discovery - -```typescript -// Direct lookup by FQN (with username) -await rondevu.getService('chat:1.0.0@alice'): Promise - -// Random discovery (without username) -await rondevu.discoverService('chat:1.0.0'): Promise - -// Paginated discovery (returns multiple offers) -await rondevu.discoverServices( - 'chat:1.0.0', // serviceVersion - 10, // limit - 0 // offset -): Promise<{ services: ServiceOffer[], count: number, limit: number, offset: number }> -``` - -#### WebRTC Signaling - -```typescript -// Post answer SDP -await rondevu.postOfferAnswer( - serviceFqn: string, - offerId: string, - sdp: string -): Promise<{ success: boolean, offerId: string }> - -// Get answer SDP (offerer polls this - deprecated, use pollOffers instead) -await rondevu.getOfferAnswer( - serviceFqn: string, - offerId: string -): Promise<{ sdp: string, offerId: string, answererId: string, answeredAt: number } | null> - -// Combined polling for answers and ICE candidates (RECOMMENDED for offerers) -await rondevu.pollOffers(since?: number): Promise<{ - answers: Array<{ - offerId: string - serviceId?: string - answererId: string - sdp: string - answeredAt: number - }> - iceCandidates: Record> -}> - -// Add ICE candidates -await rondevu.addOfferIceCandidates( - serviceFqn: string, - offerId: string, - candidates: RTCIceCandidateInit[] -): Promise<{ count: number, offerId: string }> - -// Get ICE candidates (with polling support) -await rondevu.getOfferIceCandidates( - serviceFqn: string, - offerId: string, - since: number = 0 -): Promise<{ candidates: IceCandidate[], offerId: string }> -``` - -### RondevuAPI Class - -Low-level HTTP API client (used internally by Rondevu class). - -```typescript -import { RondevuAPI } from '@xtr-dev/rondevu-client' - -const api = new RondevuAPI( - baseUrl: string, - username: string, - keypair: Keypair -) - -// Check username -await api.checkUsername(username: string): Promise<{ - available: boolean - publicKey?: string - claimedAt?: number - expiresAt?: number -}> - -// Note: Username claiming is now implicit - usernames are auto-claimed -// on first authenticated request to the server - -// ... (all other HTTP endpoints) -``` - -#### Cryptographic Helpers - -```typescript -// Generate Ed25519 keypair -const keypair = await RondevuAPI.generateKeypair(): Promise - -// Sign message -const signature = await RondevuAPI.signMessage( - message: string, - privateKey: string -): Promise - -// Verify signature -const valid = await RondevuAPI.verifySignature( - message: string, - signature: string, - publicKey: string -): Promise -``` - -## Types - -```typescript -interface Keypair { - publicKey: string // Base64-encoded Ed25519 public key - privateKey: string // Base64-encoded Ed25519 private key -} - -interface Service { - serviceId: string - offers: ServiceOffer[] - username: string - serviceFqn: string - createdAt: number - expiresAt: number -} - -interface ServiceOffer { - offerId: string - sdp: string - createdAt: number - expiresAt: number -} - -interface IceCandidate { - candidate: RTCIceCandidateInit - createdAt: number -} -``` - -## Advanced Usage - -### Anonymous Username - -```typescript -// Auto-generate anonymous username (format: anon-{timestamp}-{random}) -const rondevu = await Rondevu.connect({ - apiUrl: 'https://api.ronde.vu' - // No username provided - will generate anonymous username + 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) }) -console.log(rondevu.getUsername()) // e.g., "anon-lx2w34-a3f501" - -// Anonymous users behave exactly like regular users -await rondevu.publishService({ - service: 'chat:1.0.0', - maxOffers: 5 -}) - -await rondevu.startFilling() -``` - -### Persistent Keypair - -```typescript -// Save keypair and username to localStorage -const rondevu = await Rondevu.connect({ - apiUrl: 'https://api.ronde.vu', - username: 'alice' -}) - -// Save for later (username will be auto-claimed on first authenticated request) -localStorage.setItem('rondevu-username', rondevu.getUsername()) -localStorage.setItem('rondevu-keypair', JSON.stringify(rondevu.getKeypair())) - -// Load on next session -const savedUsername = localStorage.getItem('rondevu-username') -const savedKeypair = JSON.parse(localStorage.getItem('rondevu-keypair')) - -const rondevu2 = await Rondevu.connect({ - apiUrl: 'https://api.ronde.vu', - username: savedUsername, - keypair: savedKeypair -}) +await rondevu.startFilling() // Start accepting connections +rondevu.stopFilling() // Stop and close all connections ``` ### Service Discovery ```typescript -// Get a random available service -const service = await rondevu.discoverService('chat:1.0.0') -console.log('Discovered:', service.username) +// Direct lookup (with username) +await rondevu.getService('chat:1.0.0@alice') -// Get multiple services (paginated) -const result = await rondevu.discoverServices('chat:1.0.0', 10, 0) -console.log(`Found ${result.count} services:`) -result.services.forEach(s => console.log(` - ${s.username}`)) +// Random discovery (without username) +await rondevu.discoverService('chat:1.0.0') + +// Paginated discovery +await rondevu.discoverServices('chat:1.0.0', limit, offset) ``` -### Multiple Concurrent Offers +### Connecting to Services ```typescript -// Publish service with multiple offers for connection pooling -const offers = [] -const connections = [] - -for (let i = 0; i < 5; i++) { - const pc = new RTCPeerConnection(rtcConfig) - const dc = pc.createDataChannel('chat') - const offer = await pc.createOffer() - await pc.setLocalDescription(offer) - - offers.push({ sdp: offer.sdp }) - connections.push({ pc, dc }) -} - -const service = await rondevu.publishService({ - service: 'chat:1.0.0', - offers, - ttl: 300000 +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 }) - -// Each offer can be answered independently -console.log(`Published ${service.offers.length} offers`) ``` -## Platform Support +## Documentation -### Modern Browsers -Works out of the box - no additional setup needed. +📚 **[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 -### Node.js 18+ -Native fetch is available, but WebRTC requires polyfills: - -```bash -npm install wrtc -``` - -```typescript -import { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate } from 'wrtc' - -// Use wrtc implementations -const pc = new RTCPeerConnection() -``` - -## Username Rules - -- **Format**: Lowercase alphanumeric + dash (`a-z`, `0-9`, `-`) -- **Length**: 3-32 characters -- **Pattern**: `^[a-z0-9][a-z0-9-]*[a-z0-9]$` -- **Validity**: 365 days from claim/last use -- **Ownership**: Secured by Ed25519 public key signature - -## Service FQN Format - -- **Format**: `service:version@username` -- **Service**: Lowercase alphanumeric + dash (e.g., `chat`, `video-call`) -- **Version**: Semantic versioning (e.g., `1.0.0`, `2.1.3`) -- **Username**: Claimed username -- **Example**: `chat:1.0.0@alice` +📖 **[Node.js Host Guide](../demo/NODE_HOST_GUIDE.md)** - Host services in Node.js ## Examples -### Node.js Service Host Example - -You can host WebRTC services in Node.js that browser clients can connect to. See the [Node.js Host Guide](../demo/NODE_HOST_GUIDE.md) for a complete guide. - -**Quick example:** - -```typescript -import { Rondevu, NodeCryptoAdapter } from '@xtr-dev/rondevu-client' -import wrtc from 'wrtc' - -const { RTCPeerConnection } = wrtc - -// Initialize with Node crypto adapter -const rondevu = await Rondevu.connect({ - apiUrl: 'https://api.ronde.vu', - username: 'mybot', - cryptoAdapter: new NodeCryptoAdapter() -}) - -// Create peer connection (offerer creates data channel) -const pc = new RTCPeerConnection(rtcConfig) -const dc = pc.createDataChannel('chat') - -// Publish service (username auto-claimed on first publish) -await rondevu.publishService({ - service: 'chat:1.0.0', - maxOffers: 5 -}) - -await rondevu.startFilling() - -// Browser clients can now discover and connect to chat:1.0.0@mybot -``` - -See complete examples: -- [Node.js Host Guide](../demo/NODE_HOST_GUIDE.md) - Full guide with complete examples +- [Node.js Service Host](../demo/NODE_HOST_GUIDE.md) - Complete guide with examples - [test-connect.js](../demo/test-connect.js) - Working Node.js client example -- [React Demo](https://github.com/xtr-dev/rondevu-demo) - Complete browser UI ([live](https://ronde.vu)) - -## Migration from v0.3.x - -v0.4.0 removes high-level abstractions and uses manual WebRTC setup: - -**Removed:** -- `ServiceHost` class (use manual WebRTC + `publishService()`) -- `ServiceClient` class (use manual WebRTC + `getService()`) -- `RTCDurableConnection` class (use native WebRTC APIs) -- `RondevuService` class (merged into `Rondevu`) - -**Added:** -- `pollOffers()` - Combined polling for answers and ICE candidates -- `publishService()` - Automatic offer pool management -- `connectToService()` - Automatic answering side setup - -**Migration Example:** - -```typescript -// Before (v0.3.x) - ServiceHost -const host = new ServiceHost({ - service: 'chat@1.0.0', - rondevuService: service -}) -await host.start() - -// After (v0.4.0+) - Automatic setup -await rondevu.publishService({ - service: 'chat:1.0.0', - maxOffers: 5 -}) - -await rondevu.startFilling() -``` +- [React Demo](https://github.com/xtr-dev/rondevu-demo) - Full browser UI ([live](https://ronde.vu)) ## License