@xtr-dev/rondevu-client 0.8.3 → 0.9.2

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.
@@ -14,6 +14,7 @@ export class ServicePool {
14
14
  this.credentials = credentials;
15
15
  this.options = options;
16
16
  this.connections = new Map();
17
+ this.peerConnections = new Map();
17
18
  this.status = {
18
19
  activeOffers: 0,
19
20
  activeConnections: 0,
@@ -34,10 +35,14 @@ export class ServicePool {
34
35
  this.uuid = service.uuid;
35
36
  // 2. Create additional offers for pool (poolSize - 1)
36
37
  const additionalOffers = [];
38
+ const additionalPeerConnections = [];
39
+ const additionalDataChannels = [];
37
40
  if (poolSize > 1) {
38
41
  try {
39
- const offers = await this.createOffers(poolSize - 1);
40
- additionalOffers.push(...offers);
42
+ const result = await this.createOffers(poolSize - 1);
43
+ additionalOffers.push(...result.offers);
44
+ additionalPeerConnections.push(...result.peerConnections);
45
+ additionalDataChannels.push(...result.dataChannels);
41
46
  }
42
47
  catch (error) {
43
48
  this.handleError(error, 'initial-offer-creation');
@@ -51,12 +56,20 @@ export class ServicePool {
51
56
  onRefill: (count) => this.createOffers(count),
52
57
  onError: (err, ctx) => this.handleError(err, ctx)
53
58
  });
54
- // Add all offers to pool
59
+ // Add all offers to pool with their peer connections and data channels
55
60
  const allOffers = [
56
- { id: service.offerId, peerId: this.credentials.peerId, sdp: '', topics: [], expiresAt: service.expiresAt, lastSeen: Date.now() },
61
+ { id: service.offerId, peerId: this.credentials.peerId, sdp: service.offerSdp, topics: [], expiresAt: service.expiresAt, lastSeen: Date.now() },
57
62
  ...additionalOffers
58
63
  ];
59
- await this.offerPool.addOffers(allOffers);
64
+ const allPeerConnections = [
65
+ service.peerConnection,
66
+ ...additionalPeerConnections
67
+ ];
68
+ const allDataChannels = [
69
+ service.dataChannel,
70
+ ...additionalDataChannels
71
+ ];
72
+ await this.offerPool.addOffers(allOffers, allPeerConnections, allDataChannels);
60
73
  // 4. Start polling
61
74
  await this.offerPool.start();
62
75
  // Update status
@@ -79,12 +92,24 @@ export class ServicePool {
79
92
  if (this.offerPool) {
80
93
  await this.offerPool.stop();
81
94
  }
82
- // 2. Delete remaining offers
95
+ // 2. Close peer connections from the pool
96
+ if (this.offerPool) {
97
+ const poolPeerConnections = this.offerPool.getActivePeerConnections();
98
+ poolPeerConnections.forEach(pc => {
99
+ try {
100
+ pc.close();
101
+ }
102
+ catch {
103
+ // Ignore errors during cleanup
104
+ }
105
+ });
106
+ }
107
+ // 3. Delete remaining offers
83
108
  if (this.offerPool) {
84
109
  const offerIds = this.offerPool.getActiveOfferIds();
85
110
  await Promise.allSettled(offerIds.map(id => this.offersApi.delete(id).catch(() => { })));
86
111
  }
87
- // 3. Close active connections
112
+ // 4. Close active connections
88
113
  const closePromises = Array.from(this.connections.values()).map(async (conn) => {
89
114
  try {
90
115
  // Give a brief moment for graceful closure
@@ -96,7 +121,7 @@ export class ServicePool {
96
121
  }
97
122
  });
98
123
  await Promise.allSettled(closePromises);
99
- // 4. Delete service if we have a serviceId
124
+ // 5. Delete service if we have a serviceId
100
125
  if (this.serviceId) {
101
126
  try {
102
127
  const response = await fetch(`${this.baseUrl}/services/${this.serviceId}`, {
@@ -125,41 +150,52 @@ export class ServicePool {
125
150
  async handleConnection(answer) {
126
151
  const connectionId = this.generateConnectionId();
127
152
  try {
128
- // Create peer connection
153
+ // Use the existing peer connection from the pool
129
154
  const peer = new RondevuPeer(this.offersApi, this.options.rtcConfig || {
130
155
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
131
- });
156
+ }, answer.peerConnection // Use the existing peer connection
157
+ );
132
158
  peer.role = 'offerer';
133
159
  peer.offerId = answer.offerId;
160
+ // Verify peer connection is in correct state
161
+ if (peer.pc.signalingState !== 'have-local-offer') {
162
+ console.error('Peer connection state info:', {
163
+ signalingState: peer.pc.signalingState,
164
+ connectionState: peer.pc.connectionState,
165
+ iceConnectionState: peer.pc.iceConnectionState,
166
+ iceGatheringState: peer.pc.iceGatheringState,
167
+ hasLocalDescription: !!peer.pc.localDescription,
168
+ hasRemoteDescription: !!peer.pc.remoteDescription,
169
+ localDescriptionType: peer.pc.localDescription?.type,
170
+ remoteDescriptionType: peer.pc.remoteDescription?.type,
171
+ offerId: answer.offerId
172
+ });
173
+ throw new Error(`Invalid signaling state: ${peer.pc.signalingState}. Expected 'have-local-offer' to set remote answer.`);
174
+ }
134
175
  // Set remote description (the answer)
135
176
  await peer.pc.setRemoteDescription({
136
177
  type: 'answer',
137
178
  sdp: answer.sdp
138
179
  });
139
- // Wait for data channel (answerer creates it, we receive it)
140
- const channel = await new Promise((resolve, reject) => {
141
- const timeout = setTimeout(() => reject(new Error('Timeout waiting for data channel')), 30000);
142
- peer.on('datachannel', (ch) => {
143
- clearTimeout(timeout);
144
- resolve(ch);
145
- });
146
- // Also check if channel already exists
147
- if (peer.pc.ondatachannel) {
148
- const existingHandler = peer.pc.ondatachannel;
149
- peer.pc.ondatachannel = (event) => {
180
+ // Use the data channel we created when making the offer
181
+ if (!answer.dataChannel) {
182
+ throw new Error('No data channel found for answered offer');
183
+ }
184
+ const channel = answer.dataChannel;
185
+ // Wait for the channel to open (it was created when we made the offer)
186
+ if (channel.readyState !== 'open') {
187
+ await new Promise((resolve, reject) => {
188
+ const timeout = setTimeout(() => reject(new Error('Timeout waiting for data channel to open')), 30000);
189
+ channel.onopen = () => {
150
190
  clearTimeout(timeout);
151
- resolve(event.channel);
152
- if (existingHandler)
153
- existingHandler.call(peer.pc, event);
191
+ resolve();
154
192
  };
155
- }
156
- else {
157
- peer.pc.ondatachannel = (event) => {
193
+ channel.onerror = (error) => {
158
194
  clearTimeout(timeout);
159
- resolve(event.channel);
195
+ reject(new Error('Data channel error'));
160
196
  };
161
- }
162
- });
197
+ });
198
+ }
163
199
  // Register connection
164
200
  this.connections.set(connectionId, {
165
201
  peer,
@@ -199,23 +235,45 @@ export class ServicePool {
199
235
  */
200
236
  async createOffers(count) {
201
237
  if (count <= 0) {
202
- return [];
238
+ return { offers: [], peerConnections: [], dataChannels: [] };
203
239
  }
204
240
  // Server supports max 10 offers per request
205
241
  const batchSize = Math.min(count, 10);
206
242
  const offers = [];
243
+ const peerConnections = [];
244
+ const dataChannels = [];
207
245
  try {
208
246
  // Create peer connections and generate offers
209
247
  const offerRequests = [];
248
+ const pendingCandidates = []; // Store candidates before we have offer IDs
210
249
  for (let i = 0; i < batchSize; i++) {
211
250
  const pc = new RTCPeerConnection(this.options.rtcConfig || {
212
251
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
213
252
  });
214
- // Create data channel (required for offers)
215
- pc.createDataChannel('rondevu-service');
253
+ // Create data channel (required for offers) and save reference
254
+ const channel = pc.createDataChannel('rondevu-service');
255
+ dataChannels.push(channel);
256
+ // Set up temporary candidate collector BEFORE setLocalDescription
257
+ const candidatesForThisOffer = [];
258
+ pendingCandidates.push(candidatesForThisOffer);
259
+ pc.onicecandidate = (event) => {
260
+ if (event.candidate) {
261
+ const candidateData = event.candidate.toJSON();
262
+ if (candidateData.candidate && candidateData.candidate !== '') {
263
+ const type = candidateData.candidate.includes('typ host') ? 'host' :
264
+ candidateData.candidate.includes('typ srflx') ? 'srflx' :
265
+ candidateData.candidate.includes('typ relay') ? 'relay' : 'unknown';
266
+ console.log(`🧊 Service pool generated ${type} ICE candidate:`, candidateData.candidate);
267
+ candidatesForThisOffer.push(candidateData);
268
+ }
269
+ }
270
+ else {
271
+ console.log('🧊 Service pool ICE gathering complete');
272
+ }
273
+ };
216
274
  // Create offer
217
275
  const offer = await pc.createOffer();
218
- await pc.setLocalDescription(offer);
276
+ await pc.setLocalDescription(offer); // ICE gathering starts here, candidates go to collector
219
277
  if (!offer.sdp) {
220
278
  pc.close();
221
279
  throw new Error('Failed to generate SDP');
@@ -225,19 +283,57 @@ export class ServicePool {
225
283
  topics: [], // V2 doesn't use topics
226
284
  ttl: this.options.ttl
227
285
  });
228
- // Close the PC immediately - we only needed the SDP
229
- pc.close();
286
+ // Keep peer connection alive - DO NOT CLOSE
287
+ peerConnections.push(pc);
230
288
  }
231
289
  // Batch create offers
232
290
  const createdOffers = await this.offersApi.create(offerRequests);
233
291
  offers.push(...createdOffers);
292
+ // Now send all pending candidates and set up handlers for future ones
293
+ for (let i = 0; i < peerConnections.length; i++) {
294
+ const pc = peerConnections[i];
295
+ const offerId = createdOffers[i].id;
296
+ const candidates = pendingCandidates[i];
297
+ // Send any candidates that were collected while waiting for offer ID
298
+ if (candidates.length > 0) {
299
+ console.log(`📤 Sending ${candidates.length} pending ICE candidate(s) for offer ${offerId}`);
300
+ try {
301
+ await this.offersApi.addIceCandidates(offerId, candidates);
302
+ console.log(`✅ Sent ${candidates.length} pending ICE candidate(s)`);
303
+ }
304
+ catch (err) {
305
+ console.error('❌ Error sending pending ICE candidates:', err);
306
+ }
307
+ }
308
+ // Replace temporary handler with permanent one for any future candidates
309
+ pc.onicecandidate = async (event) => {
310
+ if (event.candidate) {
311
+ const candidateData = event.candidate.toJSON();
312
+ if (candidateData.candidate && candidateData.candidate !== '') {
313
+ const type = candidateData.candidate.includes('typ host') ? 'host' :
314
+ candidateData.candidate.includes('typ srflx') ? 'srflx' :
315
+ candidateData.candidate.includes('typ relay') ? 'relay' : 'unknown';
316
+ console.log(`🧊 Service pool generated late ${type} ICE candidate:`, candidateData.candidate);
317
+ try {
318
+ await this.offersApi.addIceCandidates(offerId, [candidateData]);
319
+ console.log(`✅ Sent late ${type} ICE candidate`);
320
+ }
321
+ catch (err) {
322
+ console.error(`❌ Error sending ${type} ICE candidate:`, err);
323
+ }
324
+ }
325
+ }
326
+ };
327
+ }
234
328
  }
235
329
  catch (error) {
330
+ // Close any created peer connections on error
331
+ peerConnections.forEach(pc => pc.close());
236
332
  this.status.failedOfferCreations++;
237
333
  this.handleError(error, 'offer-creation');
238
334
  throw error;
239
335
  }
240
- return offers;
336
+ return { offers, peerConnections, dataChannels };
241
337
  }
242
338
  /**
243
339
  * Publish the initial service (creates first offer)
@@ -248,14 +344,34 @@ export class ServicePool {
248
344
  const pc = new RTCPeerConnection(rtcConfig || {
249
345
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
250
346
  });
251
- pc.createDataChannel('rondevu-service');
347
+ const dataChannel = pc.createDataChannel('rondevu-service');
348
+ // Collect candidates before we have offer ID
349
+ const pendingCandidates = [];
350
+ // Set up temporary candidate collector BEFORE setLocalDescription
351
+ pc.onicecandidate = (event) => {
352
+ if (event.candidate) {
353
+ const candidateData = event.candidate.toJSON();
354
+ if (candidateData.candidate && candidateData.candidate !== '') {
355
+ const type = candidateData.candidate.includes('typ host') ? 'host' :
356
+ candidateData.candidate.includes('typ srflx') ? 'srflx' :
357
+ candidateData.candidate.includes('typ relay') ? 'relay' : 'unknown';
358
+ console.log(`🧊 Initial service generated ${type} ICE candidate:`, candidateData.candidate);
359
+ pendingCandidates.push(candidateData);
360
+ }
361
+ }
362
+ else {
363
+ console.log('🧊 Initial service ICE gathering complete');
364
+ }
365
+ };
252
366
  // Create offer
253
367
  const offer = await pc.createOffer();
254
- await pc.setLocalDescription(offer);
368
+ await pc.setLocalDescription(offer); // ICE gathering starts here
255
369
  if (!offer.sdp) {
256
370
  pc.close();
257
371
  throw new Error('Failed to generate SDP');
258
372
  }
373
+ // Store the SDP
374
+ const offerSdp = offer.sdp;
259
375
  // Create signature
260
376
  const timestamp = Date.now();
261
377
  const message = `publish:${username}:${serviceFqn}:${timestamp}`;
@@ -270,7 +386,7 @@ export class ServicePool {
270
386
  body: JSON.stringify({
271
387
  username,
272
388
  serviceFqn,
273
- sdp: offer.sdp,
389
+ sdp: offerSdp,
274
390
  ttl,
275
391
  isPublic,
276
392
  metadata,
@@ -278,17 +394,50 @@ export class ServicePool {
278
394
  message
279
395
  })
280
396
  });
281
- pc.close();
282
397
  if (!response.ok) {
398
+ pc.close();
283
399
  const error = await response.json();
284
400
  throw new Error(error.error || 'Failed to publish service');
285
401
  }
286
402
  const data = await response.json();
403
+ // Send any pending candidates
404
+ if (pendingCandidates.length > 0) {
405
+ console.log(`📤 Sending ${pendingCandidates.length} pending ICE candidate(s) for initial service`);
406
+ try {
407
+ await this.offersApi.addIceCandidates(data.offerId, pendingCandidates);
408
+ console.log(`✅ Sent ${pendingCandidates.length} pending ICE candidate(s)`);
409
+ }
410
+ catch (err) {
411
+ console.error('❌ Error sending pending ICE candidates:', err);
412
+ }
413
+ }
414
+ // Set up handler for any future candidates
415
+ pc.onicecandidate = async (event) => {
416
+ if (event.candidate) {
417
+ const candidateData = event.candidate.toJSON();
418
+ if (candidateData.candidate && candidateData.candidate !== '') {
419
+ const type = candidateData.candidate.includes('typ host') ? 'host' :
420
+ candidateData.candidate.includes('typ srflx') ? 'srflx' :
421
+ candidateData.candidate.includes('typ relay') ? 'relay' : 'unknown';
422
+ console.log(`🧊 Initial service generated late ${type} ICE candidate:`, candidateData.candidate);
423
+ try {
424
+ await this.offersApi.addIceCandidates(data.offerId, [candidateData]);
425
+ console.log(`✅ Sent late ${type} ICE candidate`);
426
+ }
427
+ catch (err) {
428
+ console.error(`❌ Error sending ${type} ICE candidate:`, err);
429
+ }
430
+ }
431
+ }
432
+ };
287
433
  return {
288
434
  serviceId: data.serviceId,
289
435
  uuid: data.uuid,
290
436
  offerId: data.offerId,
291
- expiresAt: data.expiresAt
437
+ offerSdp,
438
+ expiresAt: data.expiresAt,
439
+ peerConnection: pc, // Keep peer connection alive
440
+ dataChannel // Keep data channel alive
292
441
  };
293
442
  }
294
443
  /**
@@ -298,8 +447,8 @@ export class ServicePool {
298
447
  if (!this.offerPool) {
299
448
  throw new Error('Pool not started');
300
449
  }
301
- const offers = await this.createOffers(count);
302
- await this.offerPool.addOffers(offers);
450
+ const result = await this.createOffers(count);
451
+ await this.offerPool.addOffers(result.offers, result.peerConnections, result.dataChannels);
303
452
  this.updateStatus();
304
453
  }
305
454
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xtr-dev/rondevu-client",
3
- "version": "0.8.3",
4
- "description": "TypeScript client for Rondevu DNS-like WebRTC with username claiming and service discovery",
3
+ "version": "0.9.2",
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",
7
7
  "types": "dist/index.d.ts",
package/dist/bloom.d.ts DELETED
@@ -1,30 +0,0 @@
1
- /**
2
- * Simple bloom filter implementation for peer ID exclusion
3
- * Uses multiple hash functions for better distribution
4
- */
5
- export declare class BloomFilter {
6
- private bits;
7
- private size;
8
- private numHashes;
9
- constructor(size?: number, numHashes?: number);
10
- /**
11
- * Add a peer ID to the filter
12
- */
13
- add(peerId: string): void;
14
- /**
15
- * Test if peer ID might be in the filter
16
- */
17
- test(peerId: string): boolean;
18
- /**
19
- * Get raw bits for transmission
20
- */
21
- toBytes(): Uint8Array;
22
- /**
23
- * Convert to base64 for URL parameters
24
- */
25
- toBase64(): string;
26
- /**
27
- * Simple hash function (FNV-1a variant)
28
- */
29
- private hash;
30
- }
package/dist/bloom.js DELETED
@@ -1,73 +0,0 @@
1
- /**
2
- * Simple bloom filter implementation for peer ID exclusion
3
- * Uses multiple hash functions for better distribution
4
- */
5
- export class BloomFilter {
6
- constructor(size = 1024, numHashes = 3) {
7
- this.size = size;
8
- this.numHashes = numHashes;
9
- this.bits = new Uint8Array(Math.ceil(size / 8));
10
- }
11
- /**
12
- * Add a peer ID to the filter
13
- */
14
- add(peerId) {
15
- for (let i = 0; i < this.numHashes; i++) {
16
- const hash = this.hash(peerId, i);
17
- const index = hash % this.size;
18
- const byteIndex = Math.floor(index / 8);
19
- const bitIndex = index % 8;
20
- this.bits[byteIndex] |= 1 << bitIndex;
21
- }
22
- }
23
- /**
24
- * Test if peer ID might be in the filter
25
- */
26
- test(peerId) {
27
- for (let i = 0; i < this.numHashes; i++) {
28
- const hash = this.hash(peerId, i);
29
- const index = hash % this.size;
30
- const byteIndex = Math.floor(index / 8);
31
- const bitIndex = index % 8;
32
- if (!(this.bits[byteIndex] & (1 << bitIndex))) {
33
- return false;
34
- }
35
- }
36
- return true;
37
- }
38
- /**
39
- * Get raw bits for transmission
40
- */
41
- toBytes() {
42
- return this.bits;
43
- }
44
- /**
45
- * Convert to base64 for URL parameters
46
- */
47
- toBase64() {
48
- // Convert Uint8Array to regular array then to string
49
- const binaryString = String.fromCharCode(...Array.from(this.bits));
50
- // Use btoa for browser, or Buffer for Node.js
51
- if (typeof btoa !== 'undefined') {
52
- return btoa(binaryString);
53
- }
54
- else if (typeof Buffer !== 'undefined') {
55
- return Buffer.from(this.bits).toString('base64');
56
- }
57
- else {
58
- // Fallback: manual base64 encoding
59
- throw new Error('No base64 encoding available');
60
- }
61
- }
62
- /**
63
- * Simple hash function (FNV-1a variant)
64
- */
65
- hash(str, seed) {
66
- let hash = 2166136261 ^ seed;
67
- for (let i = 0; i < str.length; i++) {
68
- hash ^= str.charCodeAt(i);
69
- hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
70
- }
71
- return hash >>> 0;
72
- }
73
- }
package/dist/client.d.ts DELETED
@@ -1,126 +0,0 @@
1
- import { RondevuClientOptions, CreateOfferRequest, CreateOfferResponse, AnswerRequest, AnswerResponse, PollOffererResponse, PollAnswererResponse, VersionResponse, HealthResponse, Side } from './types.js';
2
- /**
3
- * HTTP API client for Rondevu peer signaling server
4
- */
5
- export declare class RondevuAPI {
6
- private readonly baseUrl;
7
- private readonly fetchImpl;
8
- /**
9
- * Creates a new Rondevu API client instance
10
- * @param options - Client configuration options
11
- */
12
- constructor(options: RondevuClientOptions);
13
- /**
14
- * Makes an HTTP request to the Rondevu server
15
- */
16
- private request;
17
- /**
18
- * Gets server version information
19
- *
20
- * @returns Server version
21
- *
22
- * @example
23
- * ```typescript
24
- * const api = new RondevuAPI({ baseUrl: 'https://example.com' });
25
- * const { version } = await api.getVersion();
26
- * console.log('Server version:', version);
27
- * ```
28
- */
29
- getVersion(): Promise<VersionResponse>;
30
- /**
31
- * Creates a new offer
32
- *
33
- * @param request - Offer details including peer ID, signaling data, and optional custom code
34
- * @returns Unique offer code (UUID or custom code)
35
- *
36
- * @example
37
- * ```typescript
38
- * const api = new RondevuAPI({ baseUrl: 'https://example.com' });
39
- * const { code } = await api.createOffer({
40
- * peerId: 'peer-123',
41
- * offer: signalingData,
42
- * code: 'my-custom-code' // optional
43
- * });
44
- * console.log('Offer code:', code);
45
- * ```
46
- */
47
- createOffer(request: CreateOfferRequest): Promise<CreateOfferResponse>;
48
- /**
49
- * Sends an answer or candidate to an existing offer
50
- *
51
- * @param request - Answer details including offer code and signaling data
52
- * @returns Success confirmation
53
- *
54
- * @example
55
- * ```typescript
56
- * const api = new RondevuAPI({ baseUrl: 'https://example.com' });
57
- *
58
- * // Send answer
59
- * await api.sendAnswer({
60
- * code: offerCode,
61
- * answer: answerData,
62
- * side: 'answerer'
63
- * });
64
- *
65
- * // Send candidate
66
- * await api.sendAnswer({
67
- * code: offerCode,
68
- * candidate: candidateData,
69
- * side: 'offerer'
70
- * });
71
- * ```
72
- */
73
- sendAnswer(request: AnswerRequest): Promise<AnswerResponse>;
74
- /**
75
- * Polls for offer data from the other peer
76
- *
77
- * @param code - Offer code
78
- * @param side - Which side is polling ('offerer' or 'answerer')
79
- * @returns Offer data including offers, answers, and candidates
80
- *
81
- * @example
82
- * ```typescript
83
- * const api = new RondevuAPI({ baseUrl: 'https://example.com' });
84
- *
85
- * // Offerer polls for answer
86
- * const offererData = await api.poll(offerCode, 'offerer');
87
- * if (offererData.answer) {
88
- * console.log('Received answer:', offererData.answer);
89
- * }
90
- *
91
- * // Answerer polls for offer
92
- * const answererData = await api.poll(offerCode, 'answerer');
93
- * console.log('Received offer:', answererData.offer);
94
- * ```
95
- */
96
- poll(code: string, side: Side): Promise<PollOffererResponse | PollAnswererResponse>;
97
- /**
98
- * Checks server health and version
99
- *
100
- * @returns Health status, timestamp, and version
101
- *
102
- * @example
103
- * ```typescript
104
- * const api = new RondevuAPI({ baseUrl: 'https://example.com' });
105
- * const health = await api.health();
106
- * console.log('Server status:', health.status);
107
- * console.log('Server version:', health.version);
108
- * ```
109
- */
110
- health(): Promise<HealthResponse>;
111
- /**
112
- * Ends a session by deleting the offer from the server
113
- *
114
- * @param code - The offer code
115
- * @returns Success confirmation
116
- *
117
- * @example
118
- * ```typescript
119
- * const api = new RondevuAPI({ baseUrl: 'https://example.com' });
120
- * await api.leave('my-offer-code');
121
- * ```
122
- */
123
- leave(code: string): Promise<{
124
- success: boolean;
125
- }>;
126
- }