@xtr-dev/rondevu-client 0.21.8 → 0.21.9

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.
@@ -6,7 +6,7 @@
6
6
  * Advanced options use sensible defaults.
7
7
  */
8
8
  export interface ConnectionOptions {
9
- /** Maximum time to wait for connection (ms). Default: 30000 */
9
+ /** Maximum time to wait for connection (ms). Default: 10000 */
10
10
  timeout?: number;
11
11
  /** Enable automatic reconnection on failures. Default: true */
12
12
  reconnect?: boolean;
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const DEFAULT_CONNECTION_CONFIG = {
5
5
  // Timeouts
6
- connectionTimeout: 30000, // 30 seconds
6
+ connectionTimeout: 10000, // 10 seconds
7
7
  iceGatheringTimeout: 10000, // 10 seconds
8
8
  // Reconnection
9
9
  reconnectEnabled: true,
@@ -52,6 +52,7 @@ export declare class OfferPool extends EventEmitter<OfferPoolEvents> {
52
52
  private readonly debugEnabled;
53
53
  private readonly offerCreationThrottleMs;
54
54
  private readonly activeConnections;
55
+ private readonly rotatedOfferIds;
55
56
  private readonly matchedTagsByOffer;
56
57
  private readonly fillLock;
57
58
  private running;
@@ -123,6 +124,11 @@ export declare class OfferPool extends EventEmitter<OfferPoolEvents> {
123
124
  * Called by Rondevu when a poll:ice event is received
124
125
  */
125
126
  handlePollIce(data: PollIceEvent): void;
127
+ /**
128
+ * Resolve an offerId through the rotation chain to find the current offerId
129
+ * Returns the final offerId or undefined if not found
130
+ */
131
+ private resolveRotatedOfferId;
126
132
  /**
127
133
  * Debug logging (only if debug enabled)
128
134
  */
@@ -11,6 +11,7 @@ export class OfferPool extends EventEmitter {
11
11
  super();
12
12
  // State
13
13
  this.activeConnections = new Map();
14
+ this.rotatedOfferIds = new Map(); // Maps old offerId -> new offerId for late-arriving answers
14
15
  this.matchedTagsByOffer = new Map(); // Track matchedTags from answers
15
16
  this.fillLock = new AsyncLock();
16
17
  this.running = false;
@@ -57,6 +58,8 @@ export class OfferPool extends EventEmitter {
57
58
  connection.close();
58
59
  }
59
60
  this.activeConnections.clear();
61
+ this.rotatedOfferIds.clear();
62
+ this.matchedTagsByOffer.clear();
60
63
  }
61
64
  /**
62
65
  * Get count of active offers
@@ -238,6 +241,8 @@ export class OfferPool extends EventEmitter {
238
241
  // Update map: remove old offerId, add new offerId with same connection
239
242
  this.activeConnections.delete(currentOfferId);
240
243
  this.activeConnections.set(newOfferId, connection);
244
+ // Track rotation so late-arriving answers for old offerId can be forwarded
245
+ this.rotatedOfferIds.set(currentOfferId, newOfferId);
241
246
  this.emit('connection:rotated', currentOfferId, newOfferId, connection);
242
247
  this.debug(`Connection rotated: ${currentOfferId} → ${newOfferId}`);
243
248
  }
@@ -268,12 +273,25 @@ export class OfferPool extends EventEmitter {
268
273
  async handlePollAnswer(data) {
269
274
  if (!this.running)
270
275
  return;
271
- const connection = this.activeConnections.get(data.offerId);
276
+ // Find connection - check direct mapping first, then rotated offers
277
+ let connection = this.activeConnections.get(data.offerId);
278
+ let effectiveOfferId = data.offerId;
279
+ if (!connection) {
280
+ // Check if this is a late-arriving answer for a rotated offer
281
+ const newOfferId = this.resolveRotatedOfferId(data.offerId);
282
+ if (newOfferId && newOfferId !== data.offerId) {
283
+ connection = this.activeConnections.get(newOfferId);
284
+ effectiveOfferId = newOfferId;
285
+ if (connection) {
286
+ this.debug(`Late answer for rotated offer ${data.offerId} → forwarding to ${newOfferId}`);
287
+ }
288
+ }
289
+ }
272
290
  if (connection) {
273
- this.debug(`Processing answer for offer ${data.offerId}`);
291
+ this.debug(`Processing answer for offer ${effectiveOfferId}`);
274
292
  // Store matchedTags for when connection opens
275
293
  if (data.matchedTags) {
276
- this.matchedTagsByOffer.set(data.offerId, data.matchedTags);
294
+ this.matchedTagsByOffer.set(effectiveOfferId, data.matchedTags);
277
295
  }
278
296
  try {
279
297
  await connection.processAnswer(data.sdp, data.answererPublicKey);
@@ -281,7 +299,7 @@ export class OfferPool extends EventEmitter {
281
299
  this.fillOffers();
282
300
  }
283
301
  catch (err) {
284
- this.debug(`Failed to process answer for offer ${data.offerId}:`, err);
302
+ this.debug(`Failed to process answer for offer ${effectiveOfferId}:`, err);
285
303
  }
286
304
  }
287
305
  // Silently ignore answers for offers we don't have - they may be for other connections
@@ -293,13 +311,42 @@ export class OfferPool extends EventEmitter {
293
311
  handlePollIce(data) {
294
312
  if (!this.running)
295
313
  return;
296
- const connection = this.activeConnections.get(data.offerId);
314
+ // Find connection - check direct mapping first, then rotated offers
315
+ let connection = this.activeConnections.get(data.offerId);
316
+ if (!connection) {
317
+ // Check if this is late-arriving ICE for a rotated offer
318
+ const newOfferId = this.resolveRotatedOfferId(data.offerId);
319
+ if (newOfferId && newOfferId !== data.offerId) {
320
+ connection = this.activeConnections.get(newOfferId);
321
+ if (connection) {
322
+ this.debug(`Late ICE for rotated offer ${data.offerId} → forwarding to ${newOfferId}`);
323
+ }
324
+ }
325
+ }
297
326
  if (connection) {
298
327
  this.debug(`Processing ${data.candidates.length} ICE candidates for offer ${data.offerId}`);
299
328
  connection.handleRemoteIceCandidates(data.candidates);
300
329
  }
301
330
  // Silently ignore ICE candidates for offers we don't have
302
331
  }
332
+ /**
333
+ * Resolve an offerId through the rotation chain to find the current offerId
334
+ * Returns the final offerId or undefined if not found
335
+ */
336
+ resolveRotatedOfferId(offerId) {
337
+ let currentId = offerId;
338
+ const visited = new Set();
339
+ while (this.rotatedOfferIds.has(currentId)) {
340
+ if (visited.has(currentId)) {
341
+ // Circular reference - shouldn't happen but protect against it
342
+ this.debug(`Circular rotation detected for ${offerId}`);
343
+ return undefined;
344
+ }
345
+ visited.add(currentId);
346
+ currentId = this.rotatedOfferIds.get(currentId);
347
+ }
348
+ return currentId !== offerId ? currentId : undefined;
349
+ }
303
350
  /**
304
351
  * Debug logging (only if debug enabled)
305
352
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtr-dev/rondevu-client",
3
- "version": "0.21.8",
3
+ "version": "0.21.9",
4
4
  "description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing",
5
5
  "type": "module",
6
6
  "main": "dist/core/index.js",