@xtr-dev/rondevu-client 0.17.0 → 0.18.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.
package/dist/rondevu.d.ts CHANGED
@@ -10,6 +10,8 @@ export interface RondevuOptions {
10
10
  batching?: BatcherOptions | false;
11
11
  iceServers?: IceServerPreset | RTCIceServer[];
12
12
  debug?: boolean;
13
+ rtcPeerConnection?: typeof RTCPeerConnection;
14
+ rtcIceCandidate?: typeof RTCIceCandidate;
13
15
  }
14
16
  export interface OfferContext {
15
17
  pc: RTCPeerConnection;
@@ -103,6 +105,8 @@ export declare class Rondevu {
103
105
  private batchingOptions?;
104
106
  private iceServers;
105
107
  private debugEnabled;
108
+ private rtcPeerConnection?;
109
+ private rtcIceCandidate?;
106
110
  private currentService;
107
111
  private maxOffers;
108
112
  private offerFactory;
package/dist/rondevu.js CHANGED
@@ -98,7 +98,7 @@ export const ICE_SERVER_PRESETS = {
98
98
  * ```
99
99
  */
100
100
  export class Rondevu {
101
- constructor(apiUrl, username, keypair, api, iceServers, cryptoAdapter, batchingOptions, debugEnabled = false) {
101
+ constructor(apiUrl, username, keypair, api, iceServers, cryptoAdapter, batchingOptions, debugEnabled = false, rtcPeerConnection, rtcIceCandidate) {
102
102
  this.usernameClaimed = false;
103
103
  // Service management
104
104
  this.currentService = null;
@@ -118,6 +118,8 @@ export class Rondevu {
118
118
  this.cryptoAdapter = cryptoAdapter;
119
119
  this.batchingOptions = batchingOptions;
120
120
  this.debugEnabled = debugEnabled;
121
+ this.rtcPeerConnection = rtcPeerConnection;
122
+ this.rtcIceCandidate = rtcIceCandidate;
121
123
  this.debug('Instance created:', {
122
124
  username: this.username,
123
125
  publicKey: this.keypair.publicKey,
@@ -146,6 +148,13 @@ export class Rondevu {
146
148
  */
147
149
  static async connect(options) {
148
150
  const username = options.username || Rondevu.generateAnonymousUsername();
151
+ // Apply WebRTC polyfills to global scope if provided (Node.js environments)
152
+ if (options.rtcPeerConnection) {
153
+ globalThis.RTCPeerConnection = options.rtcPeerConnection;
154
+ }
155
+ if (options.rtcIceCandidate) {
156
+ globalThis.RTCIceCandidate = options.rtcIceCandidate;
157
+ }
149
158
  // Handle preset string or custom array
150
159
  let iceServers;
151
160
  if (typeof options.iceServers === 'string') {
@@ -181,7 +190,7 @@ export class Rondevu {
181
190
  const api = new RondevuAPI(options.apiUrl, username, keypair, options.cryptoAdapter, options.batching);
182
191
  if (options.debug)
183
192
  console.log('[Rondevu] Created API instance');
184
- return new Rondevu(options.apiUrl, username, keypair, api, iceServers, options.cryptoAdapter, options.batching, options.debug || false);
193
+ return new Rondevu(options.apiUrl, username, keypair, api, iceServers, options.cryptoAdapter, options.batching, options.debug || false, options.rtcPeerConnection, options.rtcIceCandidate);
185
194
  }
186
195
  /**
187
196
  * Generate an anonymous username with timestamp and random component
@@ -252,7 +261,13 @@ export class Rondevu {
252
261
  pc.onicecandidate = async (event) => {
253
262
  if (event.candidate) {
254
263
  try {
255
- await this.api.addOfferIceCandidates(serviceFqn, offerId, [event.candidate.toJSON()]);
264
+ // Handle both browser and Node.js (wrtc) environments
265
+ // Browser: candidate.toJSON() exists
266
+ // Node.js wrtc: candidate is already a plain object
267
+ const candidateData = typeof event.candidate.toJSON === 'function'
268
+ ? event.candidate.toJSON()
269
+ : event.candidate;
270
+ await this.api.addOfferIceCandidates(serviceFqn, offerId, [candidateData]);
256
271
  }
257
272
  catch (err) {
258
273
  console.error('[Rondevu] Failed to send ICE candidate:', err);
@@ -272,9 +287,39 @@ export class Rondevu {
272
287
  };
273
288
  this.debug('Creating new offer...');
274
289
  // Create the offer using the factory
290
+ // Note: The factory may call setLocalDescription() which triggers ICE gathering
275
291
  const { pc, dc, offer } = await this.offerFactory(rtcConfig);
276
292
  // Auto-append username to service
277
293
  const serviceFqn = `${this.currentService}@${this.username}`;
294
+ // Queue to buffer ICE candidates generated before we have the offerId
295
+ // This fixes the race condition where ICE candidates are lost because
296
+ // they're generated before we can set up the handler with the offerId
297
+ const earlyIceCandidates = [];
298
+ let offerId = null;
299
+ // Set up a queuing ICE candidate handler immediately after getting the pc
300
+ // This captures any candidates that fire before we have the offerId
301
+ pc.onicecandidate = async (event) => {
302
+ if (event.candidate) {
303
+ // Handle both browser and Node.js (wrtc) environments
304
+ const candidateData = typeof event.candidate.toJSON === 'function'
305
+ ? event.candidate.toJSON()
306
+ : event.candidate;
307
+ if (offerId) {
308
+ // We have the offerId, send directly
309
+ try {
310
+ await this.api.addOfferIceCandidates(serviceFqn, offerId, [candidateData]);
311
+ }
312
+ catch (err) {
313
+ console.error('[Rondevu] Failed to send ICE candidate:', err);
314
+ }
315
+ }
316
+ else {
317
+ // Queue for later - we don't have the offerId yet
318
+ this.debug('Queuing early ICE candidate');
319
+ earlyIceCandidates.push(candidateData);
320
+ }
321
+ }
322
+ };
278
323
  // Publish to server
279
324
  const result = await this.api.publishService({
280
325
  serviceFqn,
@@ -283,7 +328,7 @@ export class Rondevu {
283
328
  signature: '',
284
329
  message: '',
285
330
  });
286
- const offerId = result.offers[0].offerId;
331
+ offerId = result.offers[0].offerId;
287
332
  // Store active offer
288
333
  this.activeOffers.set(offerId, {
289
334
  offerId,
@@ -294,8 +339,16 @@ export class Rondevu {
294
339
  createdAt: Date.now()
295
340
  });
296
341
  this.debug(`Offer created: ${offerId}`);
297
- // Set up ICE candidate handler
298
- this.setupIceCandidateHandler(pc, serviceFqn, offerId);
342
+ // Send any queued early ICE candidates
343
+ if (earlyIceCandidates.length > 0) {
344
+ this.debug(`Sending ${earlyIceCandidates.length} early ICE candidates`);
345
+ try {
346
+ await this.api.addOfferIceCandidates(serviceFqn, offerId, earlyIceCandidates);
347
+ }
348
+ catch (err) {
349
+ console.error('[Rondevu] Failed to send early ICE candidates:', err);
350
+ }
351
+ }
299
352
  // Monitor connection state
300
353
  pc.onconnectionstatechange = () => {
301
354
  this.debug(`Offer ${offerId} connection state: ${pc.connectionState}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtr-dev/rondevu-client",
3
- "version": "0.17.0",
3
+ "version": "0.18.1",
4
4
  "description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",