@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 +4 -0
- package/dist/rondevu.js +59 -6
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
298
|
-
|
|
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