@xtr-dev/rondevu-client 0.21.9 → 0.21.10

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.
@@ -18,6 +18,12 @@ export interface DiscoverRequest {
18
18
  limit?: number;
19
19
  offset?: number;
20
20
  }
21
+ export interface CountOffersByTagsRequest {
22
+ tags: string[];
23
+ }
24
+ export interface CountOffersByTagsResponse {
25
+ counts: Record<string, number>;
26
+ }
21
27
  export interface TaggedOffer {
22
28
  offerId: string;
23
29
  publicKey: string;
@@ -127,6 +133,13 @@ export declare class RondevuAPI {
127
133
  * @returns Paginated response if limit provided, single offer if not
128
134
  */
129
135
  discover(request: DiscoverRequest): Promise<DiscoverResponse | TaggedOffer>;
136
+ /**
137
+ * Count available offers by tags
138
+ * Returns the count of available (unanswered, non-expired) offers for each tag
139
+ * @param request - Request with tags to count
140
+ * @returns Object mapping each tag to its offer count
141
+ */
142
+ countOffersByTags(request: CountOffersByTagsRequest): Promise<CountOffersByTagsResponse>;
130
143
  /**
131
144
  * Delete an offer by ID
132
145
  */
@@ -211,6 +211,22 @@ export class RondevuAPI {
211
211
  const authHeaders = await this.generateAuthHeaders(rpcRequest);
212
212
  return await this.rpc(rpcRequest, authHeaders);
213
213
  }
214
+ /**
215
+ * Count available offers by tags
216
+ * Returns the count of available (unanswered, non-expired) offers for each tag
217
+ * @param request - Request with tags to count
218
+ * @returns Object mapping each tag to its offer count
219
+ */
220
+ async countOffersByTags(request) {
221
+ const rpcRequest = {
222
+ method: 'countOffersByTags',
223
+ params: {
224
+ tags: request.tags,
225
+ },
226
+ };
227
+ const authHeaders = await this.generateAuthHeaders(rpcRequest);
228
+ return await this.rpc(rpcRequest, authHeaders);
229
+ }
214
230
  /**
215
231
  * Delete an offer by ID
216
232
  */
@@ -233,11 +233,15 @@ export class OfferPool extends EventEmitter {
233
233
  return;
234
234
  }
235
235
  this.debug(`Proceeding with rotation for offer ${currentOfferId}`);
236
+ // Track new RTCPeerConnection for cleanup if rotation fails
237
+ let newPcForCleanup = null;
236
238
  try {
237
239
  // Create new offer and rebind existing connection
238
240
  const { newOfferId, pc, dc } = await this.createNewOfferForRotation();
239
- // Rebind the connection to new offer
241
+ newPcForCleanup = pc; // Track for cleanup if rebind fails
242
+ // Rebind the connection to new offer (this closes the old pc)
240
243
  await connection.rebindToOffer(newOfferId, pc, dc);
244
+ newPcForCleanup = null; // Rebind succeeded, pc is now managed by connection
241
245
  // Update map: remove old offerId, add new offerId with same connection
242
246
  this.activeConnections.delete(currentOfferId);
243
247
  this.activeConnections.set(newOfferId, connection);
@@ -247,8 +251,19 @@ export class OfferPool extends EventEmitter {
247
251
  this.debug(`Connection rotated: ${currentOfferId} → ${newOfferId}`);
248
252
  }
249
253
  catch (rotationError) {
250
- // If rotation fails, fall back to destroying connection
254
+ // If rotation fails, clean up all RTCPeerConnections to prevent resource leak
251
255
  this.debug(`Rotation failed for ${currentOfferId}:`, rotationError);
256
+ // Close the new pc if it was created but rebind failed
257
+ if (newPcForCleanup) {
258
+ try {
259
+ newPcForCleanup.close();
260
+ }
261
+ catch {
262
+ // Ignore close errors
263
+ }
264
+ }
265
+ // Close the old connection (closes its RTCPeerConnection)
266
+ connection.close();
252
267
  this.activeConnections.delete(currentOfferId);
253
268
  this.emit('offer:failed', currentOfferId, error);
254
269
  this.fillOffers(); // Create replacement
@@ -255,6 +255,22 @@ export declare class Rondevu extends EventEmitter {
255
255
  * ```
256
256
  */
257
257
  discover(tags: string[], options?: DiscoverOptions): Promise<DiscoverResult>;
258
+ /**
259
+ * Count available offers by tags
260
+ * Returns the count of available (unanswered, non-expired) offers for each tag
261
+ *
262
+ * @param tags - Tags to count offers for
263
+ * @returns Object mapping each tag to its offer count
264
+ *
265
+ * @example
266
+ * ```typescript
267
+ * const counts = await rondevu.countOffersByTags(['chat', 'video'])
268
+ * console.log(counts.counts) // { chat: 5, video: 3 }
269
+ * ```
270
+ */
271
+ countOffersByTags(tags: string[]): Promise<{
272
+ counts: Record<string, number>;
273
+ }>;
258
274
  /**
259
275
  * Post answer SDP to specific offer
260
276
  */
@@ -446,6 +446,22 @@ export class Rondevu extends EventEmitter {
446
446
  // Always pass limit to ensure we get DiscoverResponse (paginated mode)
447
447
  return (await this.api.discover({ tags, limit, offset }));
448
448
  }
449
+ /**
450
+ * Count available offers by tags
451
+ * Returns the count of available (unanswered, non-expired) offers for each tag
452
+ *
453
+ * @param tags - Tags to count offers for
454
+ * @returns Object mapping each tag to its offer count
455
+ *
456
+ * @example
457
+ * ```typescript
458
+ * const counts = await rondevu.countOffersByTags(['chat', 'video'])
459
+ * console.log(counts.counts) // { chat: 5, video: 3 }
460
+ * ```
461
+ */
462
+ async countOffersByTags(tags) {
463
+ return await this.api.countOffersByTags({ tags });
464
+ }
449
465
  // ============================================
450
466
  // WebRTC Signaling
451
467
  // ============================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtr-dev/rondevu-client",
3
- "version": "0.21.9",
3
+ "version": "0.21.10",
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",