@xtr-dev/rondevu-client 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@xtr-dev/rondevu-client)](https://www.npmjs.com/package/@xtr-dev/rondevu-client)
4
4
 
5
- 🌐 **WebRTC with durable connections and automatic reconnection**
5
+ 🌐 **Simple, high-level WebRTC peer-to-peer connections**
6
6
 
7
- TypeScript/JavaScript client for Rondevu, providing durable WebRTC connections that survive network interruptions with automatic reconnection and message queuing.
7
+ TypeScript/JavaScript client for Rondevu, providing easy-to-use WebRTC connections with automatic signaling, username-based discovery, and built-in reconnection support.
8
8
 
9
9
  **Related repositories:**
10
10
  - [@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,14 +15,14 @@ TypeScript/JavaScript client for Rondevu, providing durable WebRTC connections t
15
15
 
16
16
  ## Features
17
17
 
18
- - **Durable Connections**: Automatic reconnection on network drops
19
- - **Message Queuing**: Messages sent during disconnections are queued and flushed on reconnect
20
- - **Durable Channels**: RTCDataChannel wrappers that survive connection drops
21
- - **TTL Auto-Refresh**: Services automatically republish before expiration
22
- - **Username Claiming**: Cryptographic ownership with Ed25519 signatures
23
- - **Service Publishing**: Package-style naming (com.example.chat@1.0.0)
18
+ - **High-Level Wrappers**: ServiceHost and ServiceClient eliminate WebRTC boilerplate
19
+ - **Username-Based Discovery**: Connect to peers by username, not complex offer/answer exchange
20
+ - **Automatic Reconnection**: Built-in retry logic with exponential backoff
21
+ - **Message Queuing**: Messages sent while disconnected are queued and flushed on reconnect
22
+ - **Cryptographic Username Claiming**: Secure ownership with Ed25519 signatures
23
+ - **Service Publishing**: Package-style naming (chat.app@1.0.0)
24
24
  - **TypeScript**: Full type safety and autocomplete
25
- - **Configurable**: All timeouts, retry limits, and queue sizes are configurable
25
+ - **Configurable Polling**: Exponential backoff with jitter to reduce server load
26
26
 
27
27
  ## Install
28
28
 
@@ -32,588 +32,396 @@ npm install @xtr-dev/rondevu-client
32
32
 
33
33
  ## Quick Start
34
34
 
35
- ### Publishing a Service (Alice)
35
+ ### Hosting a Service (Alice)
36
36
 
37
37
  ```typescript
38
- import { Rondevu } from '@xtr-dev/rondevu-client';
39
-
40
- // Initialize client and register
41
- const client = new Rondevu({ baseUrl: 'https://api.ronde.vu' });
42
- await client.register();
43
-
44
- // Step 1: Claim username (one-time)
45
- const claim = await client.usernames.claimUsername('alice');
46
- client.usernames.saveKeypairToStorage('alice', claim.publicKey, claim.privateKey);
47
-
48
- // Step 2: Expose service with handler
49
- const keypair = client.usernames.loadKeypairFromStorage('alice');
50
-
51
- const service = await client.exposeService({
52
- username: 'alice',
53
- privateKey: keypair.privateKey,
54
- serviceFqn: 'chat@1.0.0',
55
- isPublic: true,
56
- poolSize: 10, // Handle 10 concurrent connections
57
- handler: (channel, connectionId) => {
58
- console.log(`📡 New connection: ${connectionId}`);
59
-
60
- channel.on('message', (data) => {
61
- console.log('📥 Received:', data);
62
- channel.send(`Echo: ${data}`);
63
- });
64
-
65
- channel.on('close', () => {
66
- console.log(`👋 Connection ${connectionId} closed`);
67
- });
68
- }
69
- });
70
-
71
- // Start the service
72
- const info = await service.start();
73
- console.log(`Service published with UUID: ${info.uuid}`);
74
- console.log('Waiting for connections...');
75
-
76
- // Later: stop the service
77
- await service.stop();
38
+ import { RondevuService, ServiceHost } from '@xtr-dev/rondevu-client'
39
+
40
+ // Step 1: Create and initialize service
41
+ const service = new RondevuService({
42
+ apiUrl: 'https://api.ronde.vu',
43
+ username: 'alice'
44
+ })
45
+
46
+ await service.initialize() // Generates keypair
47
+ await service.claimUsername() // Claims username with signature
48
+
49
+ // Step 2: Create ServiceHost
50
+ const host = new ServiceHost({
51
+ service: 'chat.app@1.0.0',
52
+ rondevuService: service,
53
+ maxPeers: 5, // Accept up to 5 connections
54
+ ttl: 300000 // 5 minutes
55
+ })
56
+
57
+ // Step 3: Listen for incoming connections
58
+ host.events.on('connection', (connection) => {
59
+ console.log('✅ New connection!')
60
+
61
+ connection.events.on('message', (msg) => {
62
+ console.log('📨 Received:', msg)
63
+ connection.sendMessage('Hello from Alice!')
64
+ })
65
+
66
+ connection.events.on('state-change', (state) => {
67
+ console.log('Connection state:', state)
68
+ })
69
+ })
70
+
71
+ host.events.on('error', (error) => {
72
+ console.error('Host error:', error)
73
+ })
74
+
75
+ // Step 4: Start hosting
76
+ await host.start()
77
+ console.log('Service is now live! Others can connect to @alice')
78
+
79
+ // Later: stop hosting
80
+ host.dispose()
78
81
  ```
79
82
 
80
83
  ### Connecting to a Service (Bob)
81
84
 
82
85
  ```typescript
83
- import { Rondevu } from '@xtr-dev/rondevu-client';
84
-
85
- // Initialize client and register
86
- const client = new Rondevu({ baseUrl: 'https://api.ronde.vu' });
87
- await client.register();
88
-
89
- // Connect to Alice's service
90
- const connection = await client.connect('alice', 'chat@1.0.0', {
91
- maxReconnectAttempts: 5
92
- });
86
+ import { RondevuService, ServiceClient } from '@xtr-dev/rondevu-client'
87
+
88
+ // Step 1: Create and initialize service
89
+ const service = new RondevuService({
90
+ apiUrl: 'https://api.ronde.vu',
91
+ username: 'bob'
92
+ })
93
+
94
+ await service.initialize()
95
+ await service.claimUsername()
96
+
97
+ // Step 2: Create ServiceClient
98
+ const client = new ServiceClient({
99
+ username: 'alice', // Connect to Alice
100
+ serviceFqn: 'chat.app@1.0.0',
101
+ rondevuService: service,
102
+ autoReconnect: true,
103
+ maxReconnectAttempts: 5
104
+ })
105
+
106
+ // Step 3: Listen for connection events
107
+ client.events.on('connected', (connection) => {
108
+ console.log('✅ Connected to Alice!')
109
+
110
+ connection.events.on('message', (msg) => {
111
+ console.log('📨 Received:', msg)
112
+ })
113
+
114
+ // Send a message
115
+ connection.sendMessage('Hello from Bob!')
116
+ })
117
+
118
+ client.events.on('disconnected', () => {
119
+ console.log('🔌 Disconnected')
120
+ })
121
+
122
+ client.events.on('reconnecting', ({ attempt, maxAttempts }) => {
123
+ console.log(`🔄 Reconnecting (${attempt}/${maxAttempts})...`)
124
+ })
125
+
126
+ client.events.on('error', (error) => {
127
+ console.error('❌ Error:', error)
128
+ })
129
+
130
+ // Step 4: Connect
131
+ await client.connect()
132
+
133
+ // Later: disconnect
134
+ client.dispose()
135
+ ```
93
136
 
94
- // Create a durable channel
95
- const channel = connection.createChannel('main');
137
+ ## Core Concepts
96
138
 
97
- channel.on('message', (data) => {
98
- console.log('📥 Received:', data);
99
- });
139
+ ### RondevuService
100
140
 
101
- channel.on('open', () => {
102
- console.log('✅ Channel open');
103
- channel.send('Hello Alice!');
104
- });
141
+ Handles authentication and username management:
142
+ - Generates Ed25519 keypair for signing
143
+ - Claims usernames with cryptographic proof
144
+ - Provides API client for signaling server
105
145
 
106
- // Listen for connection events
107
- connection.on('connected', () => {
108
- console.log('🎉 Connected to Alice');
109
- });
146
+ ### ServiceHost
110
147
 
111
- connection.on('reconnecting', (attempt, max, delay) => {
112
- console.log(`🔄 Reconnecting... (${attempt}/${max}, retry in ${delay}ms)`);
113
- });
148
+ High-level wrapper for hosting a WebRTC service:
149
+ - Automatically creates and publishes offers
150
+ - Handles incoming connections
151
+ - Manages ICE candidate exchange
152
+ - Supports multiple simultaneous peers
114
153
 
115
- connection.on('disconnected', () => {
116
- console.log('🔌 Disconnected');
117
- });
154
+ ### ServiceClient
118
155
 
119
- connection.on('failed', (error) => {
120
- console.error('❌ Connection failed permanently:', error);
121
- });
156
+ High-level wrapper for connecting to services:
157
+ - Discovers services by username
158
+ - Handles offer/answer exchange automatically
159
+ - Built-in auto-reconnection with exponential backoff
160
+ - Event-driven API
122
161
 
123
- // Establish the connection
124
- await connection.connect();
162
+ ### RTCDurableConnection
125
163
 
126
- // Messages sent during disconnection are automatically queued
127
- channel.send('This will be queued if disconnected');
164
+ Low-level connection wrapper (used internally):
165
+ - Manages WebRTC PeerConnection lifecycle
166
+ - Handles ICE candidate polling
167
+ - Provides message queue for reliability
168
+ - State management and events
128
169
 
129
- // Later: close the connection
130
- await connection.close();
131
- ```
170
+ ## API Reference
132
171
 
133
- ## Core Concepts
172
+ ### RondevuService
134
173
 
135
- ### DurableConnection
174
+ ```typescript
175
+ const service = new RondevuService({
176
+ apiUrl: string, // Signaling server URL
177
+ username: string, // Your username
178
+ keypair?: Keypair // Optional: reuse existing keypair
179
+ })
136
180
 
137
- Manages WebRTC peer lifecycle with automatic reconnection:
138
- - Automatically reconnects when connection drops
139
- - Exponential backoff with jitter (1s → 2s → 4s → 8s → ... max 30s)
140
- - Configurable max retry attempts (default: 10)
141
- - Manages multiple DurableChannel instances
181
+ // Initialize service (generates keypair if not provided)
182
+ await service.initialize(): Promise<void>
142
183
 
143
- ### DurableChannel
184
+ // Claim username with cryptographic signature
185
+ await service.claimUsername(): Promise<void>
144
186
 
145
- Wraps RTCDataChannel with message queuing:
146
- - Queues messages during disconnection
147
- - Flushes queue on reconnection
148
- - Configurable queue size and message age limits
149
- - RTCDataChannel-compatible API with event emitters
187
+ // Check if username is claimed
188
+ service.isUsernameClaimed(): boolean
150
189
 
151
- ### DurableService
190
+ // Get current username
191
+ service.getUsername(): string
152
192
 
153
- Server-side service with TTL auto-refresh:
154
- - Automatically republishes service before TTL expires
155
- - Creates DurableConnection for each incoming peer
156
- - Manages connection pool for multiple simultaneous connections
193
+ // Get keypair
194
+ service.getKeypair(): Keypair
157
195
 
158
- ## API Reference
196
+ // Get API client
197
+ service.getAPI(): RondevuAPI
198
+ ```
159
199
 
160
- ### Main Client
200
+ ### ServiceHost
161
201
 
162
202
  ```typescript
163
- const client = new Rondevu({
164
- baseUrl: 'https://api.ronde.vu', // optional, default shown
165
- credentials?: { peerId, secret }, // optional, skip registration
166
- fetch?: customFetch, // optional, for Node.js < 18
167
- RTCPeerConnection?: RTCPeerConnection, // optional, for Node.js
168
- RTCSessionDescription?: RTCSessionDescription,
169
- RTCIceCandidate?: RTCIceCandidate
170
- });
171
-
172
- // Register and get credentials
173
- const creds = await client.register();
174
- // { peerId: '...', secret: '...' }
175
-
176
- // Check if authenticated
177
- client.isAuthenticated(); // boolean
178
-
179
- // Get current credentials
180
- client.getCredentials(); // { peerId, secret } | undefined
203
+ const host = new ServiceHost({
204
+ service: string, // Service FQN (e.g., 'chat.app@1.0.0')
205
+ rondevuService: RondevuService,
206
+ maxPeers?: number, // Default: 5
207
+ ttl?: number, // Default: 300000 (5 minutes)
208
+ isPublic?: boolean, // Default: true
209
+ rtcConfiguration?: RTCConfiguration
210
+ })
211
+
212
+ // Start hosting
213
+ await host.start(): Promise<void>
214
+
215
+ // Stop hosting and cleanup
216
+ host.dispose(): void
217
+
218
+ // Get all active connections
219
+ host.getConnections(): RTCDurableConnection[]
220
+
221
+ // Events
222
+ host.events.on('connection', (conn: RTCDurableConnection) => {})
223
+ host.events.on('error', (error: Error) => {})
181
224
  ```
182
225
 
183
- ### Username API
226
+ ### ServiceClient
184
227
 
185
228
  ```typescript
186
- // Check username availability
187
- const check = await client.usernames.checkUsername('alice');
188
- // { available: true } or { available: false, expiresAt: number, publicKey: string }
189
-
190
- // Claim username with new keypair
191
- const claim = await client.usernames.claimUsername('alice');
192
- // { username, publicKey, privateKey, claimedAt, expiresAt }
193
-
194
- // Save keypair to localStorage
195
- client.usernames.saveKeypairToStorage('alice', claim.publicKey, claim.privateKey);
196
-
197
- // Load keypair from localStorage
198
- const keypair = client.usernames.loadKeypairFromStorage('alice');
199
- // { publicKey, privateKey } | null
229
+ const client = new ServiceClient({
230
+ username: string, // Host username to connect to
231
+ serviceFqn: string, // Service FQN (e.g., 'chat.app@1.0.0')
232
+ rondevuService: RondevuService,
233
+ autoReconnect?: boolean, // Default: true
234
+ maxReconnectAttempts?: number, // Default: 5
235
+ rtcConfiguration?: RTCConfiguration
236
+ })
237
+
238
+ // Connect to service
239
+ await client.connect(): Promise<RTCDurableConnection>
240
+
241
+ // Disconnect and cleanup
242
+ client.dispose(): void
243
+
244
+ // Get current connection
245
+ client.getConnection(): RTCDurableConnection | null
246
+
247
+ // Events
248
+ client.events.on('connected', (conn: RTCDurableConnection) => {})
249
+ client.events.on('disconnected', () => {})
250
+ client.events.on('reconnecting', (info: { attempt: number, maxAttempts: number }) => {})
251
+ client.events.on('error', (error: Error) => {})
200
252
  ```
201
253
 
202
- **Username Rules:**
203
- - Format: Lowercase alphanumeric + dash (`a-z`, `0-9`, `-`)
204
- - Length: 3-32 characters
205
- - Pattern: `^[a-z0-9][a-z0-9-]*[a-z0-9]$`
206
- - Validity: 365 days from claim/last use
207
- - Ownership: Secured by Ed25519 public key
208
-
209
- ### Durable Service API
254
+ ### RTCDurableConnection
210
255
 
211
256
  ```typescript
212
- // Expose a durable service
213
- const service = await client.exposeService({
214
- username: 'alice',
215
- privateKey: keypair.privateKey,
216
- serviceFqn: 'chat@1.0.0',
217
-
218
- // Service options
219
- isPublic: true, // optional, default: false
220
- metadata: { version: '1.0' }, // optional
221
- ttl: 300000, // optional, default: 5 minutes
222
- ttlRefreshMargin: 0.2, // optional, refresh at 80% of TTL
223
-
224
- // Connection pooling
225
- poolSize: 10, // optional, default: 1
226
- pollingInterval: 2000, // optional, default: 2000ms
227
-
228
- // Connection options (applied to incoming connections)
229
- maxReconnectAttempts: 10, // optional, default: 10
230
- reconnectBackoffBase: 1000, // optional, default: 1000ms
231
- reconnectBackoffMax: 30000, // optional, default: 30000ms
232
- reconnectJitter: 0.2, // optional, default: 0.2 (±20%)
233
- connectionTimeout: 30000, // optional, default: 30000ms
234
-
235
- // Message queuing
236
- maxQueueSize: 1000, // optional, default: 1000
237
- maxMessageAge: 60000, // optional, default: 60000ms (1 minute)
238
-
239
- // WebRTC configuration
240
- rtcConfig: {
241
- iceServers: [
242
- { urls: 'stun:stun.l.google.com:19302' }
243
- ]
244
- },
245
-
246
- // Connection handler
247
- handler: (channel, connectionId) => {
248
- // Handle incoming connection
249
- channel.on('message', (data) => {
250
- console.log('Received:', data);
251
- channel.send(`Echo: ${data}`);
252
- });
253
- }
254
- });
255
-
256
- // Start the service
257
- const info = await service.start();
258
- // { serviceId: '...', uuid: '...', expiresAt: 1234567890 }
259
-
260
- // Get active connections
261
- const connections = service.getActiveConnections();
262
- // ['conn-123', 'conn-456']
263
-
264
- // Get service info
265
- const serviceInfo = service.getServiceInfo();
266
- // { serviceId: '...', uuid: '...', expiresAt: 1234567890 } | null
267
-
268
- // Stop the service
269
- await service.stop();
270
- ```
257
+ // Connection state
258
+ connection.state: 'connected' | 'connecting' | 'disconnected'
271
259
 
272
- **Service Events:**
273
- ```typescript
274
- service.on('published', (serviceId, uuid) => {
275
- console.log(`Service published: ${uuid}`);
276
- });
260
+ // Send message (returns true if sent, false if queued)
261
+ await connection.sendMessage(message: string): Promise<boolean>
277
262
 
278
- service.on('connection', (connectionId) => {
279
- console.log(`New connection: ${connectionId}`);
280
- });
263
+ // Queue message for sending when connected
264
+ await connection.queueMessage(message: string, options?: QueueMessageOptions): Promise<void>
281
265
 
282
- service.on('disconnection', (connectionId) => {
283
- console.log(`Connection closed: ${connectionId}`);
284
- });
266
+ // Disconnect
267
+ connection.disconnect(): void
285
268
 
286
- service.on('ttl-refreshed', (expiresAt) => {
287
- console.log(`TTL refreshed, expires at: ${new Date(expiresAt)}`);
288
- });
269
+ // Events
270
+ connection.events.on('message', (msg: string) => {})
271
+ connection.events.on('state-change', (state: ConnectionStates) => {})
272
+ ```
289
273
 
290
- service.on('error', (error, context) => {
291
- console.error(`Service error (${context}):`, error);
292
- });
274
+ ## Configuration
293
275
 
294
- service.on('closed', () => {
295
- console.log('Service stopped');
296
- });
297
- ```
276
+ ### Polling Configuration
298
277
 
299
- ### Durable Connection API
278
+ The signaling uses configurable polling with exponential backoff:
300
279
 
301
280
  ```typescript
302
- // Connect by username and service FQN
303
- const connection = await client.connect('alice', 'chat@1.0.0', {
304
- // Connection options
305
- maxReconnectAttempts: 10, // optional, default: 10
306
- reconnectBackoffBase: 1000, // optional, default: 1000ms
307
- reconnectBackoffMax: 30000, // optional, default: 30000ms
308
- reconnectJitter: 0.2, // optional, default: 0.2 (±20%)
309
- connectionTimeout: 30000, // optional, default: 30000ms
310
-
311
- // Message queuing
312
- maxQueueSize: 1000, // optional, default: 1000
313
- maxMessageAge: 60000, // optional, default: 60000ms
314
-
315
- // WebRTC configuration
316
- rtcConfig: {
317
- iceServers: [
318
- { urls: 'stun:stun.l.google.com:19302' }
319
- ]
320
- }
321
- });
322
-
323
- // Connect by UUID
324
- const connection2 = await client.connectByUuid('service-uuid-here', {
325
- maxReconnectAttempts: 5
326
- });
327
-
328
- // Create channels before connecting
329
- const channel = connection.createChannel('main');
330
- const fileChannel = connection.createChannel('files', {
331
- ordered: false,
332
- maxRetransmits: 3
333
- });
334
-
335
- // Get existing channel
336
- const existingChannel = connection.getChannel('main');
337
-
338
- // Check connection state
339
- const state = connection.getState();
340
- // 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'failed' | 'closed'
341
-
342
- const isConnected = connection.isConnected();
343
-
344
- // Connect
345
- await connection.connect();
346
-
347
- // Close connection
348
- await connection.close();
281
+ // Default polling config
282
+ {
283
+ initialInterval: 500, // Start at 500ms
284
+ maxInterval: 5000, // Max 5 seconds
285
+ backoffMultiplier: 1.5, // Increase by 1.5x each time
286
+ maxRetries: 50, // Max 50 attempts
287
+ jitter: true // Add random 0-100ms to prevent thundering herd
288
+ }
349
289
  ```
350
290
 
351
- **Connection Events:**
352
- ```typescript
353
- connection.on('state', (newState, previousState) => {
354
- console.log(`State: ${previousState} → ${newState}`);
355
- });
291
+ This is handled automatically - no configuration needed.
356
292
 
357
- connection.on('connected', () => {
358
- console.log('Connected');
359
- });
293
+ ### WebRTC Configuration
360
294
 
361
- connection.on('reconnecting', (attempt, maxAttempts, delay) => {
362
- console.log(`Reconnecting (${attempt}/${maxAttempts}) in ${delay}ms`);
363
- });
364
-
365
- connection.on('disconnected', () => {
366
- console.log('Disconnected');
367
- });
368
-
369
- connection.on('failed', (error, permanent) => {
370
- console.error('Connection failed:', error, 'Permanent:', permanent);
371
- });
372
-
373
- connection.on('closed', () => {
374
- console.log('Connection closed');
375
- });
376
- ```
377
-
378
- ### Durable Channel API
295
+ Provide custom STUN/TURN servers:
379
296
 
380
297
  ```typescript
381
- const channel = connection.createChannel('chat', {
382
- ordered: true, // optional, default: true
383
- maxRetransmits: undefined // optional, for unordered channels
384
- });
298
+ const host = new ServiceHost({
299
+ service: 'chat.app@1.0.0',
300
+ rondevuService: service,
301
+ rtcConfiguration: {
302
+ iceServers: [
303
+ { urls: 'stun:stun.l.google.com:19302' },
304
+ {
305
+ urls: 'turn:turn.example.com:3478',
306
+ username: 'user',
307
+ credential: 'pass'
308
+ }
309
+ ]
310
+ }
311
+ })
312
+ ```
385
313
 
386
- // Send data (queued if disconnected)
387
- channel.send('Hello!');
388
- channel.send(new ArrayBuffer(1024));
389
- channel.send(new Blob(['data']));
314
+ ## Username Rules
390
315
 
391
- // Check state
392
- const state = channel.readyState;
393
- // 'connecting' | 'open' | 'closing' | 'closed'
316
+ - **Format**: Lowercase alphanumeric + dash (`a-z`, `0-9`, `-`)
317
+ - **Length**: 3-32 characters
318
+ - **Pattern**: `^[a-z0-9][a-z0-9-]*[a-z0-9]$`
319
+ - **Validity**: 365 days from claim/last use
320
+ - **Ownership**: Secured by Ed25519 public key signature
394
321
 
395
- // Get buffered amount
396
- const buffered = channel.bufferedAmount;
322
+ ## Examples
397
323
 
398
- // Set buffered amount low threshold
399
- channel.bufferedAmountLowThreshold = 16 * 1024; // 16KB
324
+ ### Chat Application
400
325
 
401
- // Get queue size (for debugging)
402
- const queueSize = channel.getQueueSize();
326
+ See [demo/demo.js](./demo/demo.js) for a complete working example.
403
327
 
404
- // Close channel
405
- channel.close();
406
- ```
328
+ ### Persistent Keypair
407
329
 
408
- **Channel Events:**
409
330
  ```typescript
410
- channel.on('open', () => {
411
- console.log('Channel open');
412
- });
331
+ // Save keypair to localStorage
332
+ const service = new RondevuService({
333
+ apiUrl: 'https://api.ronde.vu',
334
+ username: 'alice'
335
+ })
413
336
 
414
- channel.on('message', (data) => {
415
- console.log('Received:', data);
416
- });
337
+ await service.initialize()
338
+ await service.claimUsername()
417
339
 
418
- channel.on('error', (error) => {
419
- console.error('Channel error:', error);
420
- });
340
+ // Save for later
341
+ localStorage.setItem('rondevu-keypair', JSON.stringify(service.getKeypair()))
342
+ localStorage.setItem('rondevu-username', service.getUsername())
421
343
 
422
- channel.on('close', () => {
423
- console.log('Channel closed');
424
- });
344
+ // Load on next session
345
+ const savedKeypair = JSON.parse(localStorage.getItem('rondevu-keypair'))
346
+ const savedUsername = localStorage.getItem('rondevu-username')
425
347
 
426
- channel.on('bufferedAmountLow', () => {
427
- console.log('Buffer drained, safe to send more');
428
- });
348
+ const service2 = new RondevuService({
349
+ apiUrl: 'https://api.ronde.vu',
350
+ username: savedUsername,
351
+ keypair: savedKeypair
352
+ })
429
353
 
430
- channel.on('queueOverflow', (droppedCount) => {
431
- console.warn(`Queue overflow: ${droppedCount} messages dropped`);
432
- });
354
+ await service2.initialize() // Reuses keypair
433
355
  ```
434
356
 
435
- ## Configuration Options
436
-
437
- ### Connection Configuration
357
+ ### Message Queue Example
438
358
 
439
359
  ```typescript
440
- interface DurableConnectionConfig {
441
- maxReconnectAttempts?: number; // default: 10
442
- reconnectBackoffBase?: number; // default: 1000 (1 second)
443
- reconnectBackoffMax?: number; // default: 30000 (30 seconds)
444
- reconnectJitter?: number; // default: 0.2 (±20%)
445
- connectionTimeout?: number; // default: 30000 (30 seconds)
446
- maxQueueSize?: number; // default: 1000 messages
447
- maxMessageAge?: number; // default: 60000 (1 minute)
448
- rtcConfig?: RTCConfiguration;
449
- }
360
+ // Messages are automatically queued if not connected yet
361
+ client.events.on('connected', (connection) => {
362
+ // Send immediately
363
+ connection.sendMessage('Hello!')
364
+ })
365
+
366
+ // Or queue for later
367
+ await client.connect()
368
+ const conn = client.getConnection()
369
+ await conn.queueMessage('This will be sent when connected', {
370
+ expiresAt: Date.now() + 60000 // Expire after 1 minute
371
+ })
450
372
  ```
451
373
 
452
- ### Service Configuration
374
+ ## Migration from v0.9.x
453
375
 
454
- ```typescript
455
- interface DurableServiceConfig extends DurableConnectionConfig {
456
- username: string;
457
- privateKey: string;
458
- serviceFqn: string;
459
- isPublic?: boolean; // default: false
460
- metadata?: Record<string, any>;
461
- ttl?: number; // default: 300000 (5 minutes)
462
- ttlRefreshMargin?: number; // default: 0.2 (refresh at 80%)
463
- poolSize?: number; // default: 1
464
- pollingInterval?: number; // default: 2000 (2 seconds)
465
- }
466
- ```
376
+ v0.11.0 introduces high-level wrappers and RESTful API changes:
467
377
 
468
- ## Examples
378
+ **API Changes:**
379
+ - Server endpoints restructured (`/usernames/*` → `/users/*`)
380
+ - Added `ServiceHost` and `ServiceClient` wrappers
381
+ - Message queue fully implemented
382
+ - Configurable polling with exponential backoff
383
+ - Removed deprecated `cleanup()` methods (use `dispose()`)
469
384
 
470
- ### Chat Application
385
+ **Migration Guide:**
471
386
 
472
387
  ```typescript
473
- // Server
474
- const client = new Rondevu();
475
- await client.register();
476
-
477
- const claim = await client.usernames.claimUsername('alice');
478
- client.usernames.saveKeypairToStorage('alice', claim.publicKey, claim.privateKey);
479
- const keypair = client.usernames.loadKeypairFromStorage('alice');
480
-
481
- const service = await client.exposeService({
482
- username: 'alice',
483
- privateKey: keypair.privateKey,
484
- serviceFqn: 'chat@1.0.0',
485
- isPublic: true,
486
- poolSize: 50, // Handle 50 concurrent users
487
- handler: (channel, connectionId) => {
488
- console.log(`User ${connectionId} joined`);
489
-
490
- channel.on('message', (data) => {
491
- console.log(`[${connectionId}]: ${data}`);
492
- // Broadcast to all users (implement your broadcast logic)
493
- });
494
-
495
- channel.on('close', () => {
496
- console.log(`User ${connectionId} left`);
497
- });
498
- }
499
- });
500
-
501
- await service.start();
502
-
503
- // Client
504
- const client2 = new Rondevu();
505
- await client2.register();
506
-
507
- const connection = await client2.connect('alice', 'chat@1.0.0');
508
- const channel = connection.createChannel('chat');
509
-
510
- channel.on('message', (data) => {
511
- console.log('Message:', data);
512
- });
513
-
514
- await connection.connect();
515
- channel.send('Hello everyone!');
388
+ // Before (v0.9.x) - Manual WebRTC setup
389
+ const signaler = new RondevuSignaler(service, 'chat@1.0.0')
390
+ const context = new WebRTCContext()
391
+ const pc = context.createPeerConnection()
392
+ // ... 50+ lines of boilerplate
393
+
394
+ // After (v0.11.0) - ServiceHost wrapper
395
+ const host = new ServiceHost({
396
+ service: 'chat@1.0.0',
397
+ rondevuService: service
398
+ })
399
+ await host.start()
400
+ // Done!
516
401
  ```
517
402
 
518
- ### File Transfer with Progress
519
-
520
- ```typescript
521
- // Server
522
- const service = await client.exposeService({
523
- username: 'alice',
524
- privateKey: keypair.privateKey,
525
- serviceFqn: 'files@1.0.0',
526
- handler: (channel, connectionId) => {
527
- channel.on('message', async (data) => {
528
- const request = JSON.parse(data);
529
-
530
- if (request.action === 'download') {
531
- const file = await fs.readFile(request.path);
532
- const chunkSize = 16 * 1024; // 16KB chunks
533
-
534
- for (let i = 0; i < file.byteLength; i += chunkSize) {
535
- const chunk = file.slice(i, i + chunkSize);
536
- channel.send(chunk);
537
-
538
- // Wait for buffer to drain if needed
539
- while (channel.bufferedAmount > 16 * 1024 * 1024) { // 16MB
540
- await new Promise(resolve => setTimeout(resolve, 100));
541
- }
542
- }
543
-
544
- channel.send(JSON.stringify({ done: true }));
545
- }
546
- });
547
- }
548
- });
549
-
550
- await service.start();
551
-
552
- // Client
553
- const connection = await client.connect('alice', 'files@1.0.0');
554
- const channel = connection.createChannel('files');
555
-
556
- const chunks = [];
557
- channel.on('message', (data) => {
558
- if (typeof data === 'string') {
559
- const msg = JSON.parse(data);
560
- if (msg.done) {
561
- const blob = new Blob(chunks);
562
- console.log('Download complete:', blob.size, 'bytes');
563
- }
564
- } else {
565
- chunks.push(data);
566
- console.log('Progress:', chunks.length * 16 * 1024, 'bytes');
567
- }
568
- });
569
-
570
- await connection.connect();
571
- channel.send(JSON.stringify({ action: 'download', path: '/file.zip' }));
572
- ```
573
-
574
- ## Platform-Specific Setup
403
+ ## Platform Support
575
404
 
576
405
  ### Modern Browsers
577
406
  Works out of the box - no additional setup needed.
578
407
 
579
408
  ### Node.js 18+
580
- Native fetch is available, but you need WebRTC polyfills:
409
+ Native fetch is available, but WebRTC requires polyfills:
581
410
 
582
411
  ```bash
583
412
  npm install wrtc
584
413
  ```
585
414
 
586
415
  ```typescript
587
- import { Rondevu } from '@xtr-dev/rondevu-client';
588
- import { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate } from 'wrtc';
589
-
590
- const client = new Rondevu({
591
- baseUrl: 'https://api.ronde.vu',
592
- RTCPeerConnection,
593
- RTCSessionDescription,
594
- RTCIceCandidate
595
- });
596
- ```
597
-
598
- ### Node.js < 18
599
- Install both fetch and WebRTC polyfills:
600
-
601
- ```bash
602
- npm install node-fetch wrtc
603
- ```
604
-
605
- ```typescript
606
- import { Rondevu } from '@xtr-dev/rondevu-client';
607
- import fetch from 'node-fetch';
608
- import { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate } from 'wrtc';
609
-
610
- const client = new Rondevu({
611
- baseUrl: 'https://api.ronde.vu',
612
- fetch: fetch as any,
613
- RTCPeerConnection,
614
- RTCSessionDescription,
615
- RTCIceCandidate
616
- });
416
+ import { WebRTCContext } from '@xtr-dev/rondevu-client'
417
+ import { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate } from 'wrtc'
418
+
419
+ // Configure WebRTC context
420
+ const context = new WebRTCContext({
421
+ RTCPeerConnection,
422
+ RTCSessionDescription,
423
+ RTCIceCandidate
424
+ } as any)
617
425
  ```
618
426
 
619
427
  ## TypeScript
@@ -622,38 +430,23 @@ All types are exported:
622
430
 
623
431
  ```typescript
624
432
  import type {
625
- // Client types
626
- Credentials,
627
- RondevuOptions,
628
-
629
- // Username types
630
- UsernameCheckResult,
631
- UsernameClaimResult,
632
-
633
- // Durable connection types
634
- DurableConnectionState,
635
- DurableChannelState,
636
- DurableConnectionConfig,
637
- DurableChannelConfig,
638
- DurableServiceConfig,
639
- QueuedMessage,
640
- DurableConnectionEvents,
641
- DurableChannelEvents,
642
- DurableServiceEvents,
643
- ConnectionInfo,
644
- ServiceInfo
645
- } from '@xtr-dev/rondevu-client';
433
+ RondevuServiceOptions,
434
+ ServiceHostOptions,
435
+ ServiceHostEvents,
436
+ ServiceClientOptions,
437
+ ServiceClientEvents,
438
+ ConnectionInterface,
439
+ ConnectionEvents,
440
+ ConnectionStates,
441
+ Message,
442
+ QueueMessageOptions,
443
+ Signaler,
444
+ PollingConfig,
445
+ Credentials,
446
+ Keypair
447
+ } from '@xtr-dev/rondevu-client'
646
448
  ```
647
449
 
648
- ## Migration from v0.8.x
649
-
650
- v0.9.0 is a **breaking change** that replaces the low-level APIs with high-level durable connections. See [MIGRATION.md](./MIGRATION.md) for detailed migration guide.
651
-
652
- **Key Changes:**
653
- - ❌ Removed: `client.services.*`, `client.discovery.*`, `client.createPeer()` (low-level APIs)
654
- - ✅ Added: `client.exposeService()`, `client.connect()`, `client.connectByUuid()` (durable APIs)
655
- - ✅ Changed: Focus on durable connections with automatic reconnection and message queuing
656
-
657
450
  ## License
658
451
 
659
452
  MIT