@xtr-dev/rondevu-client 0.20.1 → 0.21.1

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.
Files changed (43) hide show
  1. package/README.md +83 -385
  2. package/dist/api/batcher.d.ts +60 -38
  3. package/dist/api/batcher.js +121 -77
  4. package/dist/api/client.d.ts +104 -61
  5. package/dist/api/client.js +273 -185
  6. package/dist/connections/answerer.d.ts +15 -6
  7. package/dist/connections/answerer.js +56 -19
  8. package/dist/connections/base.d.ts +6 -4
  9. package/dist/connections/base.js +26 -16
  10. package/dist/connections/config.d.ts +30 -0
  11. package/dist/connections/config.js +20 -0
  12. package/dist/connections/events.d.ts +6 -6
  13. package/dist/connections/offerer.d.ts +37 -8
  14. package/dist/connections/offerer.js +92 -24
  15. package/dist/core/ice-config.d.ts +35 -0
  16. package/dist/core/ice-config.js +111 -0
  17. package/dist/core/index.d.ts +18 -18
  18. package/dist/core/index.js +18 -13
  19. package/dist/core/offer-pool.d.ts +30 -11
  20. package/dist/core/offer-pool.js +90 -76
  21. package/dist/core/peer.d.ts +155 -0
  22. package/dist/core/peer.js +252 -0
  23. package/dist/core/polling-manager.d.ts +71 -0
  24. package/dist/core/polling-manager.js +122 -0
  25. package/dist/core/rondevu-errors.d.ts +59 -0
  26. package/dist/core/rondevu-errors.js +75 -0
  27. package/dist/core/rondevu-types.d.ts +125 -0
  28. package/dist/core/rondevu-types.js +6 -0
  29. package/dist/core/rondevu.d.ts +106 -209
  30. package/dist/core/rondevu.js +221 -349
  31. package/dist/crypto/adapter.d.ts +25 -9
  32. package/dist/crypto/node.d.ts +27 -5
  33. package/dist/crypto/node.js +96 -25
  34. package/dist/crypto/web.d.ts +26 -4
  35. package/dist/crypto/web.js +102 -25
  36. package/dist/utils/message-buffer.js +4 -4
  37. package/dist/webrtc/adapter.d.ts +22 -0
  38. package/dist/webrtc/adapter.js +5 -0
  39. package/dist/webrtc/browser.d.ts +12 -0
  40. package/dist/webrtc/browser.js +15 -0
  41. package/dist/webrtc/node.d.ts +32 -0
  42. package/dist/webrtc/node.js +32 -0
  43. package/package.json +17 -6
@@ -1,146 +1,45 @@
1
- import { RondevuAPI, Keypair, IceCandidate, BatcherOptions } from '../api/client.js';
2
- import { CryptoAdapter } from '../crypto/adapter.js';
1
+ import { Credential, IceCandidate } from '../api/client.js';
2
+ import { WebRTCAdapter } from '../webrtc/adapter.js';
3
3
  import { EventEmitter } from 'eventemitter3';
4
4
  import { OffererConnection } from '../connections/offerer.js';
5
- import { AnswererConnection } from '../connections/answerer.js';
6
- import { ConnectionConfig } from '../connections/config.js';
7
- export type IceServerPreset = 'ipv4-turn' | 'hostname-turns' | 'google-stun' | 'relay-only';
8
- export declare const ICE_SERVER_PRESETS: Record<IceServerPreset, RTCIceServer[]>;
9
- export interface RondevuOptions {
10
- apiUrl: string;
11
- username?: string;
12
- keypair?: Keypair;
13
- cryptoAdapter?: CryptoAdapter;
14
- batching?: BatcherOptions | false;
15
- iceServers?: IceServerPreset | RTCIceServer[];
16
- debug?: boolean;
17
- rtcPeerConnection?: typeof RTCPeerConnection;
18
- rtcIceCandidate?: typeof RTCIceCandidate;
19
- }
20
- export interface OfferContext {
21
- dc?: RTCDataChannel;
22
- offer: RTCSessionDescriptionInit;
23
- }
24
- /**
25
- * Factory function for creating WebRTC offers.
26
- * Rondevu creates the RTCPeerConnection and passes it to the factory,
27
- * allowing ICE candidate handlers to be set up before setLocalDescription() is called.
28
- *
29
- * @param pc - The RTCPeerConnection created by Rondevu (already configured with ICE servers)
30
- * @returns Promise containing the data channel (optional) and offer SDP
31
- */
32
- export type OfferFactory = (pc: RTCPeerConnection) => Promise<OfferContext>;
33
- export interface PublishServiceOptions {
34
- service: string;
35
- maxOffers: number;
36
- offerFactory?: OfferFactory;
37
- ttl?: number;
38
- connectionConfig?: Partial<ConnectionConfig>;
39
- }
40
- export interface ConnectionContext {
41
- pc: RTCPeerConnection;
42
- dc: RTCDataChannel;
43
- serviceFqn: string;
44
- offerId: string;
45
- peerUsername: string;
46
- }
47
- export interface ConnectToServiceOptions {
48
- serviceFqn?: string;
49
- service?: string;
50
- username?: string;
51
- rtcConfig?: RTCConfiguration;
52
- connectionConfig?: Partial<ConnectionConfig>;
53
- }
54
- export interface ActiveOffer {
55
- offerId: string;
56
- serviceFqn: string;
57
- pc: RTCPeerConnection;
58
- dc?: RTCDataChannel;
59
- answered: boolean;
60
- createdAt: number;
61
- }
62
- export interface FindServiceOptions {
63
- mode?: 'direct' | 'random' | 'paginated';
64
- limit?: number;
65
- offset?: number;
66
- }
67
- export interface ServiceResult {
68
- serviceId: string;
69
- username: string;
70
- serviceFqn: string;
71
- offerId: string;
72
- sdp: string;
73
- createdAt: number;
74
- expiresAt: number;
75
- }
76
- export interface PaginatedServiceResult {
77
- services: ServiceResult[];
78
- count: number;
79
- limit: number;
80
- offset: number;
81
- }
82
- /**
83
- * Base error class for Rondevu errors
84
- */
85
- export declare class RondevuError extends Error {
86
- context?: Record<string, any> | undefined;
87
- constructor(message: string, context?: Record<string, any> | undefined);
88
- }
89
- /**
90
- * Network-related errors (API calls, connectivity)
91
- */
92
- export declare class NetworkError extends RondevuError {
93
- constructor(message: string, context?: Record<string, any>);
94
- }
95
- /**
96
- * Validation errors (invalid input, malformed data)
97
- */
98
- export declare class ValidationError extends RondevuError {
99
- constructor(message: string, context?: Record<string, any>);
100
- }
101
- /**
102
- * WebRTC connection errors (peer connection failures, ICE issues)
103
- */
104
- export declare class ConnectionError extends RondevuError {
105
- constructor(message: string, context?: Record<string, any>);
106
- }
5
+ import { Peer, PeerOptions } from './peer.js';
6
+ import type { RondevuOptions, OfferOptions, OfferHandle, DiscoverOptions, DiscoverResult } from './rondevu-types.js';
7
+ export type { RondevuOptions, OfferContext, OfferFactory, OfferOptions, OfferHandle, ConnectionContext, DiscoverOptions, DiscoveredOffer, DiscoverResult, } from './rondevu-types.js';
8
+ export { ICE_SERVER_PRESETS } from './ice-config.js';
9
+ export type { IceServerPreset, IcePresetConfig } from './ice-config.js';
10
+ export type { PollAnswerEvent, PollIceEvent } from './polling-manager.js';
107
11
  /**
108
12
  * Rondevu - Complete WebRTC signaling client with durable connections
109
13
  *
110
- * v1.0.0 introduces breaking changes:
111
- * - connectToService() now returns AnswererConnection instead of ConnectionContext
112
- * - Automatic reconnection and message buffering built-in
113
- * - Connection objects expose .send() method instead of raw DataChannel
114
- * - Rich event system for connection lifecycle (connected, disconnected, reconnecting, etc.)
14
+ * Uses a tags-based discovery system where offers have 1+ tags for matching.
115
15
  *
116
16
  * @example
117
17
  * ```typescript
118
18
  * // Create and initialize Rondevu instance with preset ICE servers
119
19
  * const rondevu = await Rondevu.connect({
120
20
  * apiUrl: 'https://signal.example.com',
121
- * username: 'alice',
122
21
  * iceServers: 'ipv4-turn' // Use preset: 'ipv4-turn', 'hostname-turns', 'google-stun', or 'relay-only'
123
22
  * })
124
23
  *
125
- * // Publish a service with automatic offer management
126
- * await rondevu.publishService({
127
- * service: 'chat:2.0.0',
24
+ * // Create offers with tags for discovery
25
+ * await rondevu.offer({
26
+ * tags: ['chat', 'video'],
128
27
  * maxOffers: 5 // Maintain up to 5 concurrent offers
129
28
  * })
130
29
  *
131
30
  * // Start accepting connections (auto-fills offers and polls)
132
31
  * await rondevu.startFilling()
133
32
  *
134
- * // Listen for connections (v1.0.0 API)
33
+ * // Listen for connections
135
34
  * rondevu.on('connection:opened', (offerId, connection) => {
136
35
  * connection.on('connected', () => console.log('Connected!'))
137
36
  * connection.on('message', (data) => console.log('Received:', data))
138
37
  * connection.send('Hello!')
139
38
  * })
140
39
  *
141
- * // Connect to a service (v1.0.0 - returns AnswererConnection)
142
- * const connection = await rondevu.connectToService({
143
- * serviceFqn: 'chat:2.0.0@bob'
40
+ * // Connect by discovering offers with matching tags
41
+ * const connection = await rondevu.connect({
42
+ * tags: ['chat']
144
43
  * })
145
44
  *
146
45
  * connection.on('connected', () => {
@@ -158,22 +57,21 @@ export declare class ConnectionError extends RondevuError {
158
57
  * ```
159
58
  */
160
59
  export declare class Rondevu extends EventEmitter {
60
+ private static readonly DEFAULT_API_URL;
161
61
  private static readonly DEFAULT_TTL_MS;
162
62
  private static readonly POLLING_INTERVAL_MS;
163
63
  private api;
164
64
  private readonly apiUrl;
165
- private username;
166
- private keypair;
167
- private usernameClaimed;
65
+ private credential;
168
66
  private cryptoAdapter?;
169
- private batchingOptions?;
67
+ private webrtcAdapter;
170
68
  private iceServers;
69
+ private iceTransportPolicy?;
171
70
  private debugEnabled;
172
- private rtcPeerConnection?;
173
- private rtcIceCandidate?;
174
- private currentService;
71
+ private currentTags;
175
72
  private connectionConfig?;
176
73
  private offerPool;
74
+ private pollingManager;
177
75
  private constructor();
178
76
  /**
179
77
  * Internal debug logging - only logs if debug mode is enabled
@@ -184,47 +82,61 @@ export declare class Rondevu extends EventEmitter {
184
82
  *
185
83
  * @example
186
84
  * ```typescript
85
+ * const rondevu = await Rondevu.connect({}) // Uses default API URL
86
+ * // or
187
87
  * const rondevu = await Rondevu.connect({
188
- * apiUrl: 'https://api.ronde.vu',
189
- * username: 'alice'
88
+ * apiUrl: 'https://custom.api.com'
190
89
  * })
191
90
  * ```
192
91
  */
193
- static connect(options: RondevuOptions): Promise<Rondevu>;
92
+ static connect(options?: RondevuOptions): Promise<Rondevu>;
194
93
  /**
195
- * Generate an anonymous username with timestamp and random component
94
+ * Get the current credential name
196
95
  */
197
- private static generateAnonymousUsername;
96
+ getName(): string;
198
97
  /**
199
- * Check if username has been claimed (checks with server)
98
+ * Get the full credential (name + secret)
99
+ * Use this to persist credentials for future sessions
100
+ *
101
+ * ⚠️ SECURITY WARNING:
102
+ * - The secret grants full access to this identity
103
+ * - Store credentials securely (encrypted storage, never in logs)
104
+ * - Never expose credentials in URLs, console output, or error messages
105
+ * - Treat the secret like a password or API key
200
106
  */
201
- isUsernameClaimed(): Promise<boolean>;
107
+ getCredential(): Credential;
108
+ /**
109
+ * Get the WebRTC adapter for creating peer connections
110
+ * Used internally by offer pool and connections
111
+ */
112
+ getWebRTCAdapter(): WebRTCAdapter;
202
113
  /**
203
114
  * Default offer factory - creates a simple data channel connection
204
115
  * The RTCPeerConnection is created by Rondevu and passed in
205
116
  */
206
117
  private defaultOfferFactory;
207
118
  /**
208
- * Publish a service with automatic offer management
209
- * Call startFilling() to begin accepting connections
119
+ * Create offers with tags for discovery (offerer/host side)
120
+ * Auto-starts filling by default. Use the returned object to cancel.
210
121
  *
211
122
  * @example
212
123
  * ```typescript
213
- * await rondevu.publishService({
214
- * service: 'chat:2.0.0',
215
- * maxOffers: 5,
216
- * connectionConfig: {
217
- * reconnectEnabled: true,
218
- * bufferEnabled: true
219
- * }
124
+ * // Auto-start (default)
125
+ * const offer = await rondevu.offer({
126
+ * tags: ['chat', 'video'],
127
+ * maxOffers: 5
220
128
  * })
129
+ * // Later: offer.cancel() to stop
130
+ *
131
+ * // Manual start
132
+ * await rondevu.offer({ tags: ['chat'], maxOffers: 5, autoStart: false })
221
133
  * await rondevu.startFilling()
222
134
  * ```
223
135
  */
224
- publishService(options: PublishServiceOptions): Promise<void>;
136
+ offer(options: OfferOptions): Promise<OfferHandle>;
225
137
  /**
226
138
  * Start filling offers and polling for answers/ICE
227
- * Call this after publishService() to begin accepting connections
139
+ * Call this after offer() to begin accepting connections
228
140
  */
229
141
  startFilling(): Promise<void>;
230
142
  /**
@@ -232,6 +144,19 @@ export declare class Rondevu extends EventEmitter {
232
144
  * Closes all active peer connections
233
145
  */
234
146
  stopFilling(): void;
147
+ /**
148
+ * Start the centralized polling manager
149
+ * Use this when you need polling without offers (e.g., answerer connections)
150
+ */
151
+ startPolling(): void;
152
+ /**
153
+ * Stop the centralized polling manager
154
+ */
155
+ stopPolling(): void;
156
+ /**
157
+ * Check if polling is active
158
+ */
159
+ isPolling(): boolean;
235
160
  /**
236
161
  * Get the count of active offers
237
162
  * @returns Number of active offers
@@ -249,89 +174,84 @@ export declare class Rondevu extends EventEmitter {
249
174
  */
250
175
  disconnectAll(): void;
251
176
  /**
252
- * Get the current service status
253
- * @returns Object with service state information
177
+ * Get the current publishing status
178
+ * @returns Object with publishing state information
254
179
  */
255
- getServiceStatus(): {
180
+ getPublishStatus(): {
256
181
  active: boolean;
257
182
  offerCount: number;
183
+ tags: string[] | null;
258
184
  };
259
185
  /**
260
- * Resolve the full service FQN from various input options
261
- * Supports direct FQN, service+username, or service discovery
262
- */
263
- private resolveServiceFqn;
264
- /**
265
- * Connect to a service (answerer side) - v1.0.0 API
266
- * Returns an AnswererConnection with automatic reconnection and buffering
267
- *
268
- * BREAKING CHANGE: This now returns AnswererConnection instead of ConnectionContext
186
+ * Create a peer connection with simplified DX
187
+ * Returns a Peer object with clean state management and events
269
188
  *
270
189
  * @example
271
190
  * ```typescript
191
+ * // Connect to any peer matching tags
192
+ * const peer = await rondevu.peer({ tags: ['chat'] })
193
+ *
272
194
  * // Connect to specific user
273
- * const connection = await rondevu.connectToService({
274
- * serviceFqn: 'chat:2.0.0@alice',
275
- * connectionConfig: {
276
- * reconnectEnabled: true,
277
- * bufferEnabled: true
278
- * }
195
+ * const peer = await rondevu.peer({
196
+ * username: 'alice',
197
+ * tags: ['chat']
279
198
  * })
280
199
  *
281
- * connection.on('connected', () => {
282
- * console.log('Connected!')
283
- * connection.send('Hello!')
200
+ * peer.on('open', () => {
201
+ * console.log('Connected to', peer.peerUsername)
202
+ * peer.send('Hello!')
284
203
  * })
285
204
  *
286
- * connection.on('message', (data) => {
205
+ * peer.on('message', (data) => {
287
206
  * console.log('Received:', data)
288
207
  * })
289
208
  *
290
- * connection.on('reconnecting', (attempt) => {
291
- * console.log(`Reconnecting, attempt ${attempt}`)
209
+ * peer.on('state', (state, prevState) => {
210
+ * console.log(`State: ${prevState} ${state}`)
292
211
  * })
293
212
  *
294
- * // Discover random service
295
- * const connection = await rondevu.connectToService({
296
- * service: 'chat:2.0.0'
297
- * })
213
+ * // Access underlying RTCPeerConnection
214
+ * if (peer.peerConnection) {
215
+ * console.log('ICE state:', peer.peerConnection.iceConnectionState)
216
+ * }
298
217
  * ```
299
218
  */
300
- connectToService(options: ConnectToServiceOptions): Promise<AnswererConnection>;
219
+ peer(options: PeerOptions): Promise<Peer>;
301
220
  /**
302
- * Find a service - unified discovery method
221
+ * Discover offers by tags
303
222
  *
304
- * @param serviceFqn - Service identifier (e.g., 'chat:1.0.0' or 'chat:1.0.0@alice')
305
- * @param options - Discovery options
223
+ * @param tags - Tags to search for (OR logic - matches any tag)
224
+ * @param options - Discovery options (pagination)
306
225
  *
307
226
  * @example
308
227
  * ```typescript
309
- * // Direct lookup (has username)
310
- * const service = await rondevu.findService('chat:1.0.0@alice')
311
- *
312
- * // Random discovery (no username)
313
- * const service = await rondevu.findService('chat:1.0.0')
228
+ * // Discover offers matching any of the tags
229
+ * const result = await rondevu.discover(['chat', 'video'])
314
230
  *
315
231
  * // Paginated discovery
316
- * const result = await rondevu.findService('chat:1.0.0', {
317
- * mode: 'paginated',
232
+ * const result = await rondevu.discover(['chat'], {
318
233
  * limit: 20,
319
234
  * offset: 0
320
235
  * })
236
+ *
237
+ * // Access offers
238
+ * for (const offer of result.offers) {
239
+ * console.log(offer.username, offer.tags)
240
+ * }
321
241
  * ```
322
242
  */
323
- findService(serviceFqn: string, options?: FindServiceOptions): Promise<ServiceResult | PaginatedServiceResult>;
243
+ discover(tags: string[], options?: DiscoverOptions): Promise<DiscoverResult>;
324
244
  /**
325
245
  * Post answer SDP to specific offer
326
246
  */
327
- postOfferAnswer(serviceFqn: string, offerId: string, sdp: string): Promise<{
247
+ postOfferAnswer(offerId: string, sdp: string): Promise<{
328
248
  success: boolean;
329
249
  offerId: string;
330
250
  }>;
331
251
  /**
332
252
  * Get answer SDP (offerer polls this)
333
253
  */
334
- getOfferAnswer(serviceFqn: string, offerId: string): Promise<{
254
+ getOfferAnswer(offerId: string): Promise<{
335
255
  sdp: string;
336
256
  offerId: string;
337
257
  answererId: string;
@@ -344,7 +264,6 @@ export declare class Rondevu extends EventEmitter {
344
264
  poll(since?: number): Promise<{
345
265
  answers: Array<{
346
266
  offerId: string;
347
- serviceId?: string;
348
267
  answererId: string;
349
268
  sdp: string;
350
269
  answeredAt: number;
@@ -359,41 +278,19 @@ export declare class Rondevu extends EventEmitter {
359
278
  /**
360
279
  * Add ICE candidates to specific offer
361
280
  */
362
- addOfferIceCandidates(serviceFqn: string, offerId: string, candidates: RTCIceCandidateInit[]): Promise<{
281
+ addOfferIceCandidates(offerId: string, candidates: RTCIceCandidateInit[]): Promise<{
363
282
  count: number;
364
283
  offerId: string;
365
284
  }>;
366
285
  /**
367
286
  * Get ICE candidates for specific offer (with polling support)
368
287
  */
369
- getOfferIceCandidates(serviceFqn: string, offerId: string, since?: number): Promise<{
288
+ getOfferIceCandidates(offerId: string, since?: number): Promise<{
370
289
  candidates: IceCandidate[];
371
290
  offerId: string;
372
291
  }>;
373
- /**
374
- * Get the current keypair (for backup/storage)
375
- */
376
- getKeypair(): Keypair;
377
- /**
378
- * Get the username
379
- */
380
- getUsername(): string;
381
- /**
382
- * Get the public key
383
- */
384
- getPublicKey(): string;
385
292
  /**
386
293
  * Get active connections (for offerer side)
387
294
  */
388
295
  getActiveConnections(): Map<string, OffererConnection>;
389
- /**
390
- * Get all active offers (legacy compatibility)
391
- * @deprecated Use getActiveConnections() instead
392
- */
393
- getActiveOffers(): ActiveOffer[];
394
- /**
395
- * Access to underlying API for advanced operations
396
- * @deprecated Use direct methods on Rondevu instance instead
397
- */
398
- getAPIPublic(): RondevuAPI;
399
296
  }