@xtr-dev/rondevu-client 0.12.3 → 0.13.0

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/api.d.ts CHANGED
@@ -29,12 +29,9 @@ export interface OfferRequest {
29
29
  sdp: string;
30
30
  }
31
31
  export interface ServiceRequest {
32
- username: string;
33
32
  serviceFqn: string;
34
33
  offers: OfferRequest[];
35
34
  ttl?: number;
36
- isPublic?: boolean;
37
- metadata?: Record<string, any>;
38
35
  signature: string;
39
36
  message: string;
40
37
  }
@@ -46,12 +43,9 @@ export interface ServiceOffer {
46
43
  }
47
44
  export interface Service {
48
45
  serviceId: string;
49
- uuid: string;
50
46
  offers: ServiceOffer[];
51
47
  username: string;
52
48
  serviceFqn: string;
53
- isPublic: boolean;
54
- metadata?: Record<string, any>;
55
49
  createdAt: number;
56
50
  expiresAt: number;
57
51
  }
@@ -99,54 +93,88 @@ export declare class RondevuAPI {
99
93
  */
100
94
  getOffer(offerId: string): Promise<Offer>;
101
95
  /**
102
- * Answer a service
96
+ * Answer a specific offer from a service
103
97
  */
104
- answerService(serviceUuid: string, sdp: string): Promise<{
98
+ postOfferAnswer(serviceFqn: string, offerId: string, sdp: string): Promise<{
99
+ success: boolean;
105
100
  offerId: string;
106
101
  }>;
107
102
  /**
108
- * Get answer for a service (offerer polls this)
103
+ * Get answer for a specific offer (offerer polls this)
109
104
  */
110
- getServiceAnswer(serviceUuid: string): Promise<{
105
+ getOfferAnswer(serviceFqn: string, offerId: string): Promise<{
111
106
  sdp: string;
112
107
  offerId: string;
108
+ answererId: string;
109
+ answeredAt: number;
113
110
  } | null>;
114
111
  /**
115
112
  * Search offers by topic
116
113
  */
117
114
  searchOffers(topic: string): Promise<Offer[]>;
118
115
  /**
119
- * Add ICE candidates to a service
116
+ * Add ICE candidates to a specific offer
120
117
  */
121
- addServiceIceCandidates(serviceUuid: string, candidates: RTCIceCandidateInit[], offerId?: string): Promise<{
118
+ addOfferIceCandidates(serviceFqn: string, offerId: string, candidates: RTCIceCandidateInit[]): Promise<{
119
+ count: number;
122
120
  offerId: string;
123
121
  }>;
124
122
  /**
125
- * Get ICE candidates for a service (with polling support)
123
+ * Get ICE candidates for a specific offer (with polling support)
126
124
  */
127
- getServiceIceCandidates(serviceUuid: string, since?: number, offerId?: string): Promise<{
125
+ getOfferIceCandidates(serviceFqn: string, offerId: string, since?: number): Promise<{
128
126
  candidates: IceCandidate[];
129
127
  offerId: string;
130
128
  }>;
131
129
  /**
132
130
  * Publish a service
131
+ * Service FQN must include username: service:version@username
133
132
  */
134
133
  publishService(service: ServiceRequest): Promise<Service>;
135
134
  /**
136
- * Get service by UUID
135
+ * Get service by FQN (with username) - Direct lookup
136
+ * Example: chat:1.0.0@alice
137
137
  */
138
- getService(uuid: string): Promise<Service & {
138
+ getService(serviceFqn: string): Promise<{
139
+ serviceId: string;
140
+ username: string;
141
+ serviceFqn: string;
139
142
  offerId: string;
140
143
  sdp: string;
144
+ createdAt: number;
145
+ expiresAt: number;
141
146
  }>;
142
147
  /**
143
- * Search services by username - lists all services for a username
148
+ * Discover a random available service without knowing the username
149
+ * Example: chat:1.0.0 (without @username)
144
150
  */
145
- searchServicesByUsername(username: string): Promise<Service[]>;
151
+ discoverService(serviceVersion: string): Promise<{
152
+ serviceId: string;
153
+ username: string;
154
+ serviceFqn: string;
155
+ offerId: string;
156
+ sdp: string;
157
+ createdAt: number;
158
+ expiresAt: number;
159
+ }>;
146
160
  /**
147
- * Search services by username AND FQN - returns full service details
148
- */
149
- searchServices(username: string, serviceFqn: string): Promise<Service[]>;
161
+ * Discover multiple available services with pagination
162
+ * Example: chat:1.0.0 (without @username)
163
+ */
164
+ discoverServices(serviceVersion: string, limit?: number, offset?: number): Promise<{
165
+ services: Array<{
166
+ serviceId: string;
167
+ username: string;
168
+ serviceFqn: string;
169
+ offerId: string;
170
+ sdp: string;
171
+ createdAt: number;
172
+ expiresAt: number;
173
+ }>;
174
+ count: number;
175
+ limit: number;
176
+ offset: number;
177
+ }>;
150
178
  /**
151
179
  * Check if username is available
152
180
  */
package/dist/api.js CHANGED
@@ -131,10 +131,10 @@ export class RondevuAPI {
131
131
  return await response.json();
132
132
  }
133
133
  /**
134
- * Answer a service
134
+ * Answer a specific offer from a service
135
135
  */
136
- async answerService(serviceUuid, sdp) {
137
- const response = await fetch(`${this.baseUrl}/services/${serviceUuid}/answer`, {
136
+ async postOfferAnswer(serviceFqn, offerId, sdp) {
137
+ const response = await fetch(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}/offers/${offerId}/answer`, {
138
138
  method: 'POST',
139
139
  headers: {
140
140
  'Content-Type': 'application/json',
@@ -144,15 +144,15 @@ export class RondevuAPI {
144
144
  });
145
145
  if (!response.ok) {
146
146
  const error = await response.json().catch(() => ({ error: 'Unknown error' }));
147
- throw new Error(`Failed to answer service: ${error.error || response.statusText}`);
147
+ throw new Error(`Failed to answer offer: ${error.error || response.statusText}`);
148
148
  }
149
149
  return await response.json();
150
150
  }
151
151
  /**
152
- * Get answer for a service (offerer polls this)
152
+ * Get answer for a specific offer (offerer polls this)
153
153
  */
154
- async getServiceAnswer(serviceUuid) {
155
- const response = await fetch(`${this.baseUrl}/services/${serviceUuid}/answer`, {
154
+ async getOfferAnswer(serviceFqn, offerId) {
155
+ const response = await fetch(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}/offers/${offerId}/answer`, {
156
156
  headers: this.getAuthHeader(),
157
157
  });
158
158
  if (!response.ok) {
@@ -163,8 +163,7 @@ export class RondevuAPI {
163
163
  const error = await response.json().catch(() => ({ error: 'Unknown error' }));
164
164
  throw new Error(`Failed to get answer: ${error.error || response.statusText}`);
165
165
  }
166
- const data = await response.json();
167
- return { sdp: data.sdp, offerId: data.offerId };
166
+ return await response.json();
168
167
  }
169
168
  /**
170
169
  * Search offers by topic
@@ -183,16 +182,16 @@ export class RondevuAPI {
183
182
  // ICE Candidates
184
183
  // ============================================
185
184
  /**
186
- * Add ICE candidates to a service
185
+ * Add ICE candidates to a specific offer
187
186
  */
188
- async addServiceIceCandidates(serviceUuid, candidates, offerId) {
189
- const response = await fetch(`${this.baseUrl}/services/${serviceUuid}/ice-candidates`, {
187
+ async addOfferIceCandidates(serviceFqn, offerId, candidates) {
188
+ const response = await fetch(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}/offers/${offerId}/ice-candidates`, {
190
189
  method: 'POST',
191
190
  headers: {
192
191
  'Content-Type': 'application/json',
193
192
  ...this.getAuthHeader(),
194
193
  },
195
- body: JSON.stringify({ candidates, offerId }),
194
+ body: JSON.stringify({ candidates }),
196
195
  });
197
196
  if (!response.ok) {
198
197
  const error = await response.json().catch(() => ({ error: 'Unknown error' }));
@@ -201,14 +200,11 @@ export class RondevuAPI {
201
200
  return await response.json();
202
201
  }
203
202
  /**
204
- * Get ICE candidates for a service (with polling support)
203
+ * Get ICE candidates for a specific offer (with polling support)
205
204
  */
206
- async getServiceIceCandidates(serviceUuid, since = 0, offerId) {
207
- const url = new URL(`${this.baseUrl}/services/${serviceUuid}/ice-candidates`);
205
+ async getOfferIceCandidates(serviceFqn, offerId, since = 0) {
206
+ const url = new URL(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}/offers/${offerId}/ice-candidates`);
208
207
  url.searchParams.set('since', since.toString());
209
- if (offerId) {
210
- url.searchParams.set('offerId', offerId);
211
- }
212
208
  const response = await fetch(url.toString(), { headers: this.getAuthHeader() });
213
209
  if (!response.ok) {
214
210
  const error = await response.json().catch(() => ({ error: 'Unknown error' }));
@@ -225,9 +221,10 @@ export class RondevuAPI {
225
221
  // ============================================
226
222
  /**
227
223
  * Publish a service
224
+ * Service FQN must include username: service:version@username
228
225
  */
229
226
  async publishService(service) {
230
- const response = await fetch(`${this.baseUrl}/users/${encodeURIComponent(service.username)}/services`, {
227
+ const response = await fetch(`${this.baseUrl}/services`, {
231
228
  method: 'POST',
232
229
  headers: {
233
230
  'Content-Type': 'application/json',
@@ -242,10 +239,11 @@ export class RondevuAPI {
242
239
  return await response.json();
243
240
  }
244
241
  /**
245
- * Get service by UUID
242
+ * Get service by FQN (with username) - Direct lookup
243
+ * Example: chat:1.0.0@alice
246
244
  */
247
- async getService(uuid) {
248
- const response = await fetch(`${this.baseUrl}/services/${uuid}`, {
245
+ async getService(serviceFqn) {
246
+ const response = await fetch(`${this.baseUrl}/services/${encodeURIComponent(serviceFqn)}`, {
249
247
  headers: this.getAuthHeader(),
250
248
  });
251
249
  if (!response.ok) {
@@ -255,31 +253,35 @@ export class RondevuAPI {
255
253
  return await response.json();
256
254
  }
257
255
  /**
258
- * Search services by username - lists all services for a username
256
+ * Discover a random available service without knowing the username
257
+ * Example: chat:1.0.0 (without @username)
259
258
  */
260
- async searchServicesByUsername(username) {
261
- const response = await fetch(`${this.baseUrl}/users/${encodeURIComponent(username)}/services`, { headers: this.getAuthHeader() });
259
+ async discoverService(serviceVersion) {
260
+ const response = await fetch(`${this.baseUrl}/services/${encodeURIComponent(serviceVersion)}`, {
261
+ headers: this.getAuthHeader(),
262
+ });
262
263
  if (!response.ok) {
263
264
  const error = await response.json().catch(() => ({ error: 'Unknown error' }));
264
- throw new Error(`Failed to search services: ${error.error || response.statusText}`);
265
+ throw new Error(`Failed to discover service: ${error.error || response.statusText}`);
265
266
  }
266
- const data = await response.json();
267
- return data.services || [];
267
+ return await response.json();
268
268
  }
269
269
  /**
270
- * Search services by username AND FQN - returns full service details
270
+ * Discover multiple available services with pagination
271
+ * Example: chat:1.0.0 (without @username)
271
272
  */
272
- async searchServices(username, serviceFqn) {
273
- const response = await fetch(`${this.baseUrl}/users/${encodeURIComponent(username)}/services/${encodeURIComponent(serviceFqn)}`, { headers: this.getAuthHeader() });
273
+ async discoverServices(serviceVersion, limit = 10, offset = 0) {
274
+ const url = new URL(`${this.baseUrl}/services/${encodeURIComponent(serviceVersion)}`);
275
+ url.searchParams.set('limit', limit.toString());
276
+ url.searchParams.set('offset', offset.toString());
277
+ const response = await fetch(url.toString(), {
278
+ headers: this.getAuthHeader(),
279
+ });
274
280
  if (!response.ok) {
275
- if (response.status === 404) {
276
- return [];
277
- }
278
281
  const error = await response.json().catch(() => ({ error: 'Unknown error' }));
279
- throw new Error(`Failed to search services: ${error.error || response.statusText}`);
282
+ throw new Error(`Failed to discover services: ${error.error || response.statusText}`);
280
283
  }
281
- const service = await response.json();
282
- return [service];
284
+ return await response.json();
283
285
  }
284
286
  // ============================================
285
287
  // Usernames
package/dist/index.d.ts CHANGED
@@ -2,19 +2,10 @@
2
2
  * @xtr-dev/rondevu-client
3
3
  * WebRTC peer signaling client
4
4
  */
5
- export { EventBus } from './event-bus.js';
5
+ export { Rondevu } from './rondevu.js';
6
6
  export { RondevuAPI } from './api.js';
7
- export { RondevuService } from './rondevu-service.js';
8
7
  export { RondevuSignaler } from './rondevu-signaler.js';
9
- export { WebRTCContext } from './webrtc-context.js';
10
- export { RTCDurableConnection } from './durable-connection';
11
- export { ServiceHost } from './service-host.js';
12
- export { ServiceClient } from './service-client.js';
13
- export { createBin } from './bin.js';
14
- export type { ConnectionInterface, QueueMessageOptions, Message, ConnectionEvents, Signaler, } from './types.js';
8
+ export type { Signaler, Binnable, } from './types.js';
15
9
  export type { Credentials, Keypair, OfferRequest, Offer, ServiceRequest, Service, IceCandidate, } from './api.js';
16
- export type { Binnable } from './bin.js';
17
- export type { RondevuServiceOptions, PublishServiceOptions } from './rondevu-service.js';
18
- export type { ServiceHostOptions, ServiceHostEvents } from './service-host.js';
19
- export type { ServiceClientOptions, ServiceClientEvents } from './service-client.js';
10
+ export type { RondevuOptions, PublishServiceOptions } from './rondevu.js';
20
11
  export type { PollingConfig } from './rondevu-signaler.js';
package/dist/index.js CHANGED
@@ -2,12 +2,6 @@
2
2
  * @xtr-dev/rondevu-client
3
3
  * WebRTC peer signaling client
4
4
  */
5
- export { EventBus } from './event-bus.js';
5
+ export { Rondevu } from './rondevu.js';
6
6
  export { RondevuAPI } from './api.js';
7
- export { RondevuService } from './rondevu-service.js';
8
7
  export { RondevuSignaler } from './rondevu-signaler.js';
9
- export { WebRTCContext } from './webrtc-context.js';
10
- export { RTCDurableConnection } from './durable-connection';
11
- export { ServiceHost } from './service-host.js';
12
- export { ServiceClient } from './service-client.js';
13
- export { createBin } from './bin.js';
@@ -1,6 +1,5 @@
1
- import { Signaler } from './types.js';
2
- import { RondevuService } from './rondevu-service.js';
3
- import { Binnable } from './bin.js';
1
+ import { Signaler, Binnable } from './types.js';
2
+ import { Rondevu } from './rondevu.js';
4
3
  export interface PollingConfig {
5
4
  initialInterval?: number;
6
5
  maxInterval?: number;
@@ -44,7 +43,7 @@ export declare class RondevuSignaler implements Signaler {
44
43
  private readonly service;
45
44
  private readonly host?;
46
45
  private offerId;
47
- private serviceUuid;
46
+ private serviceFqn;
48
47
  private offerListeners;
49
48
  private answerListeners;
50
49
  private iceListeners;
@@ -53,7 +52,7 @@ export declare class RondevuSignaler implements Signaler {
53
52
  private lastIceTimestamp;
54
53
  private isPolling;
55
54
  private pollingConfig;
56
- constructor(rondevu: RondevuService, service: string, host?: string | undefined, pollingConfig?: PollingConfig);
55
+ constructor(rondevu: Rondevu, service: string, host?: string | undefined, pollingConfig?: PollingConfig);
57
56
  /**
58
57
  * Publish an offer as a service
59
58
  * Used by the offerer to make their offer available
@@ -35,7 +35,7 @@ export class RondevuSignaler {
35
35
  this.service = service;
36
36
  this.host = host;
37
37
  this.offerId = null;
38
- this.serviceUuid = null;
38
+ this.serviceFqn = null;
39
39
  this.offerListeners = [];
40
40
  this.answerListeners = [];
41
41
  this.iceListeners = [];
@@ -64,14 +64,13 @@ export class RondevuSignaler {
64
64
  serviceFqn: this.service,
65
65
  offers: [{ sdp: offer.sdp }],
66
66
  ttl: 300000, // 5 minutes
67
- isPublic: true,
68
67
  });
69
68
  // Get the first offer from the published service
70
69
  if (!publishedService.offers || publishedService.offers.length === 0) {
71
70
  throw new Error('No offers returned from service publication');
72
71
  }
73
72
  this.offerId = publishedService.offers[0].offerId;
74
- this.serviceUuid = publishedService.uuid;
73
+ this.serviceFqn = publishedService.serviceFqn;
75
74
  // Start polling for answer
76
75
  this.startAnswerPolling();
77
76
  // Start polling for ICE candidates
@@ -85,11 +84,11 @@ export class RondevuSignaler {
85
84
  if (!answer.sdp) {
86
85
  throw new Error('Answer SDP is required');
87
86
  }
88
- if (!this.serviceUuid) {
89
- throw new Error('No service UUID available. Must receive offer first.');
87
+ if (!this.serviceFqn || !this.offerId) {
88
+ throw new Error('No service FQN or offer ID available. Must receive offer first.');
90
89
  }
91
90
  // Send answer to the service
92
- const result = await this.rondevu.getAPI().answerService(this.serviceUuid, answer.sdp);
91
+ const result = await this.rondevu.getAPI().postOfferAnswer(this.serviceFqn, this.offerId, answer.sdp);
93
92
  this.offerId = result.offerId;
94
93
  // Start polling for ICE candidates
95
94
  this.startIcePolling();
@@ -130,8 +129,8 @@ export class RondevuSignaler {
130
129
  * Send an ICE candidate to the remote peer
131
130
  */
132
131
  async addIceCandidate(candidate) {
133
- if (!this.serviceUuid) {
134
- console.warn('Cannot send ICE candidate: no service UUID');
132
+ if (!this.serviceFqn || !this.offerId) {
133
+ console.warn('Cannot send ICE candidate: no service FQN or offer ID');
135
134
  return;
136
135
  }
137
136
  const candidateData = candidate.toJSON();
@@ -140,11 +139,7 @@ export class RondevuSignaler {
140
139
  return;
141
140
  }
142
141
  try {
143
- const result = await this.rondevu.getAPI().addServiceIceCandidates(this.serviceUuid, [candidateData], this.offerId || undefined);
144
- // Store offerId if we didn't have it yet
145
- if (!this.offerId) {
146
- this.offerId = result.offerId;
147
- }
142
+ await this.rondevu.getAPI().addOfferIceCandidates(this.serviceFqn, this.offerId, [candidateData]);
148
143
  }
149
144
  catch (err) {
150
145
  console.error('Failed to send ICE candidate:', err);
@@ -173,28 +168,21 @@ export class RondevuSignaler {
173
168
  }
174
169
  this.isPolling = true;
175
170
  try {
176
- // Search for services by username and service FQN
177
- const services = await this.rondevu.getAPI().searchServices(this.host, this.service);
178
- if (services.length === 0) {
179
- console.warn(`No services found for ${this.host}/${this.service}`);
171
+ // Get service by FQN (service should include @username)
172
+ const serviceFqn = `${this.service}@${this.host}`;
173
+ const serviceData = await this.rondevu.getAPI().getService(serviceFqn);
174
+ if (!serviceData) {
175
+ console.warn(`No service found for ${serviceFqn}`);
180
176
  this.isPolling = false;
181
177
  return;
182
178
  }
183
- // Get the first available service (already has full details from searchServices)
184
- const service = services[0];
185
- // Get the first available offer from the service
186
- if (!service.offers || service.offers.length === 0) {
187
- console.warn(`No offers available for service ${this.host}/${this.service}`);
188
- this.isPolling = false;
189
- return;
190
- }
191
- const firstOffer = service.offers[0];
192
- this.offerId = firstOffer.offerId;
193
- this.serviceUuid = service.uuid;
179
+ // Store service details
180
+ this.offerId = serviceData.offerId;
181
+ this.serviceFqn = serviceData.serviceFqn;
194
182
  // Notify offer listeners
195
183
  const offer = {
196
184
  type: 'offer',
197
- sdp: firstOffer.sdp,
185
+ sdp: serviceData.sdp,
198
186
  };
199
187
  this.offerListeners.forEach(listener => {
200
188
  try {
@@ -214,18 +202,18 @@ export class RondevuSignaler {
214
202
  * Start polling for answer (offerer side) with exponential backoff
215
203
  */
216
204
  startAnswerPolling() {
217
- if (this.answerPollingTimeout || !this.serviceUuid) {
205
+ if (this.answerPollingTimeout || !this.serviceFqn || !this.offerId) {
218
206
  return;
219
207
  }
220
208
  let interval = this.pollingConfig.initialInterval;
221
209
  let retries = 0;
222
210
  const poll = async () => {
223
- if (!this.serviceUuid) {
211
+ if (!this.serviceFqn || !this.offerId) {
224
212
  this.stopAnswerPolling();
225
213
  return;
226
214
  }
227
215
  try {
228
- const answer = await this.rondevu.getAPI().getServiceAnswer(this.serviceUuid);
216
+ const answer = await this.rondevu.getAPI().getOfferAnswer(this.serviceFqn, this.offerId);
229
217
  if (answer && answer.sdp) {
230
218
  // Store offerId if we didn't have it yet
231
219
  if (!this.offerId) {
@@ -289,23 +277,19 @@ export class RondevuSignaler {
289
277
  * Start polling for ICE candidates with adaptive backoff
290
278
  */
291
279
  startIcePolling() {
292
- if (this.icePollingTimeout || !this.serviceUuid) {
280
+ if (this.icePollingTimeout || !this.serviceFqn || !this.offerId) {
293
281
  return;
294
282
  }
295
283
  let interval = this.pollingConfig.initialInterval;
296
284
  const poll = async () => {
297
- if (!this.serviceUuid) {
285
+ if (!this.serviceFqn || !this.offerId) {
298
286
  this.stopIcePolling();
299
287
  return;
300
288
  }
301
289
  try {
302
290
  const result = await this.rondevu
303
291
  .getAPI()
304
- .getServiceIceCandidates(this.serviceUuid, this.lastIceTimestamp, this.offerId || undefined);
305
- // Store offerId if we didn't have it yet
306
- if (!this.offerId) {
307
- this.offerId = result.offerId;
308
- }
292
+ .getOfferIceCandidates(this.serviceFqn, this.offerId, this.lastIceTimestamp);
309
293
  let foundCandidates = false;
310
294
  for (const item of result.candidates) {
311
295
  if (item.candidate && item.candidate.candidate && item.candidate.candidate !== '') {
@@ -0,0 +1,167 @@
1
+ import { RondevuAPI, Credentials, Keypair, Service, IceCandidate } from './api.js';
2
+ export interface RondevuOptions {
3
+ apiUrl: string;
4
+ username: string;
5
+ keypair?: Keypair;
6
+ credentials?: Credentials;
7
+ }
8
+ export interface PublishServiceOptions {
9
+ serviceFqn: string;
10
+ offers: Array<{
11
+ sdp: string;
12
+ }>;
13
+ ttl?: number;
14
+ }
15
+ /**
16
+ * Rondevu - Complete WebRTC signaling client
17
+ *
18
+ * Provides a unified API for:
19
+ * - Username claiming with Ed25519 signatures
20
+ * - Service publishing with automatic signature generation
21
+ * - Service discovery (direct, random, paginated)
22
+ * - WebRTC signaling (offer/answer exchange, ICE relay)
23
+ * - Keypair management
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * // Initialize (generates keypair automatically)
28
+ * const rondevu = new Rondevu({
29
+ * apiUrl: 'https://signal.example.com',
30
+ * username: 'alice',
31
+ * })
32
+ *
33
+ * await rondevu.initialize()
34
+ *
35
+ * // Claim username (one time)
36
+ * await rondevu.claimUsername()
37
+ *
38
+ * // Publish a service
39
+ * const publishedService = await rondevu.publishService({
40
+ * serviceFqn: 'chat:1.0.0@alice',
41
+ * offers: [{ sdp: offerSdp }],
42
+ * ttl: 300000,
43
+ * })
44
+ *
45
+ * // Discover a service
46
+ * const service = await rondevu.getService('chat:1.0.0@bob')
47
+ *
48
+ * // Post answer
49
+ * await rondevu.postOfferAnswer(service.serviceFqn, service.offerId, answerSdp)
50
+ * ```
51
+ */
52
+ export declare class Rondevu {
53
+ private readonly api;
54
+ private readonly username;
55
+ private keypair;
56
+ private usernameClaimed;
57
+ constructor(options: RondevuOptions);
58
+ /**
59
+ * Initialize the service - generates keypair if not provided
60
+ * Call this before using other methods
61
+ */
62
+ initialize(): Promise<void>;
63
+ /**
64
+ * Claim the username with Ed25519 signature
65
+ * Should be called once before publishing services
66
+ */
67
+ claimUsername(): Promise<void>;
68
+ /**
69
+ * Check if username has been claimed (checks with server)
70
+ */
71
+ isUsernameClaimed(): Promise<boolean>;
72
+ /**
73
+ * Publish a service with automatic signature generation
74
+ */
75
+ publishService(options: PublishServiceOptions): Promise<Service>;
76
+ /**
77
+ * Get service by FQN (with username) - Direct lookup
78
+ * Example: chat:1.0.0@alice
79
+ */
80
+ getService(serviceFqn: string): Promise<{
81
+ serviceId: string;
82
+ username: string;
83
+ serviceFqn: string;
84
+ offerId: string;
85
+ sdp: string;
86
+ createdAt: number;
87
+ expiresAt: number;
88
+ }>;
89
+ /**
90
+ * Discover a random available service without knowing the username
91
+ * Example: chat:1.0.0 (without @username)
92
+ */
93
+ discoverService(serviceVersion: string): Promise<{
94
+ serviceId: string;
95
+ username: string;
96
+ serviceFqn: string;
97
+ offerId: string;
98
+ sdp: string;
99
+ createdAt: number;
100
+ expiresAt: number;
101
+ }>;
102
+ /**
103
+ * Discover multiple available services with pagination
104
+ * Example: chat:1.0.0 (without @username)
105
+ */
106
+ discoverServices(serviceVersion: string, limit?: number, offset?: number): Promise<{
107
+ services: Array<{
108
+ serviceId: string;
109
+ username: string;
110
+ serviceFqn: string;
111
+ offerId: string;
112
+ sdp: string;
113
+ createdAt: number;
114
+ expiresAt: number;
115
+ }>;
116
+ count: number;
117
+ limit: number;
118
+ offset: number;
119
+ }>;
120
+ /**
121
+ * Post answer SDP to specific offer
122
+ */
123
+ postOfferAnswer(serviceFqn: string, offerId: string, sdp: string): Promise<{
124
+ success: boolean;
125
+ offerId: string;
126
+ }>;
127
+ /**
128
+ * Get answer SDP (offerer polls this)
129
+ */
130
+ getOfferAnswer(serviceFqn: string, offerId: string): Promise<{
131
+ sdp: string;
132
+ offerId: string;
133
+ answererId: string;
134
+ answeredAt: number;
135
+ } | null>;
136
+ /**
137
+ * Add ICE candidates to specific offer
138
+ */
139
+ addOfferIceCandidates(serviceFqn: string, offerId: string, candidates: RTCIceCandidateInit[]): Promise<{
140
+ count: number;
141
+ offerId: string;
142
+ }>;
143
+ /**
144
+ * Get ICE candidates for specific offer (with polling support)
145
+ */
146
+ getOfferIceCandidates(serviceFqn: string, offerId: string, since?: number): Promise<{
147
+ candidates: IceCandidate[];
148
+ offerId: string;
149
+ }>;
150
+ /**
151
+ * Get the current keypair (for backup/storage)
152
+ */
153
+ getKeypair(): Keypair | null;
154
+ /**
155
+ * Get the username
156
+ */
157
+ getUsername(): string;
158
+ /**
159
+ * Get the public key
160
+ */
161
+ getPublicKey(): string | null;
162
+ /**
163
+ * Access to underlying API for advanced operations
164
+ * @deprecated Use direct methods on Rondevu instance instead
165
+ */
166
+ getAPI(): RondevuAPI;
167
+ }