@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 +49 -21
- package/dist/api.js +40 -38
- package/dist/index.d.ts +3 -12
- package/dist/index.js +1 -7
- package/dist/rondevu-signaler.d.ts +4 -5
- package/dist/rondevu-signaler.js +23 -39
- package/dist/rondevu.d.ts +167 -0
- package/dist/{rondevu-service.js → rondevu.js} +125 -46
- package/dist/types.d.ts +8 -21
- package/dist/types.js +4 -6
- package/package.json +1 -1
- package/dist/bin.d.ts +0 -35
- package/dist/bin.js +0 -35
- package/dist/connection-manager.d.ts +0 -104
- package/dist/connection-manager.js +0 -324
- package/dist/connection.d.ts +0 -112
- package/dist/connection.js +0 -194
- package/dist/durable-connection.d.ts +0 -120
- package/dist/durable-connection.js +0 -244
- package/dist/event-bus.d.ts +0 -52
- package/dist/event-bus.js +0 -84
- package/dist/noop-signaler.d.ts +0 -14
- package/dist/noop-signaler.js +0 -27
- package/dist/quick-start.d.ts +0 -29
- package/dist/quick-start.js +0 -44
- package/dist/rondevu-context.d.ts +0 -10
- package/dist/rondevu-context.js +0 -20
- package/dist/rondevu-service.d.ts +0 -87
- package/dist/service-client.d.ts +0 -77
- package/dist/service-client.js +0 -158
- package/dist/service-host.d.ts +0 -67
- package/dist/service-host.js +0 -120
- package/dist/signaler.d.ts +0 -25
- package/dist/signaler.js +0 -89
- package/dist/webrtc-context.d.ts +0 -5
- package/dist/webrtc-context.js +0 -35
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
|
-
|
|
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
|
|
103
|
+
* Get answer for a specific offer (offerer polls this)
|
|
109
104
|
*/
|
|
110
|
-
|
|
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
|
|
116
|
+
* Add ICE candidates to a specific offer
|
|
120
117
|
*/
|
|
121
|
-
|
|
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
|
|
123
|
+
* Get ICE candidates for a specific offer (with polling support)
|
|
126
124
|
*/
|
|
127
|
-
|
|
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
|
|
135
|
+
* Get service by FQN (with username) - Direct lookup
|
|
136
|
+
* Example: chat:1.0.0@alice
|
|
137
137
|
*/
|
|
138
|
-
getService(
|
|
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
|
-
*
|
|
148
|
+
* Discover a random available service without knowing the username
|
|
149
|
+
* Example: chat:1.0.0 (without @username)
|
|
144
150
|
*/
|
|
145
|
-
|
|
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
|
-
*
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
137
|
-
const response = await fetch(`${this.baseUrl}/services/${
|
|
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
|
|
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
|
|
152
|
+
* Get answer for a specific offer (offerer polls this)
|
|
153
153
|
*/
|
|
154
|
-
async
|
|
155
|
-
const response = await fetch(`${this.baseUrl}/services/${
|
|
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
|
-
|
|
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
|
|
185
|
+
* Add ICE candidates to a specific offer
|
|
187
186
|
*/
|
|
188
|
-
async
|
|
189
|
-
const response = await fetch(`${this.baseUrl}/services/${
|
|
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
|
|
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
|
|
203
|
+
* Get ICE candidates for a specific offer (with polling support)
|
|
205
204
|
*/
|
|
206
|
-
async
|
|
207
|
-
const url = new URL(`${this.baseUrl}/services/${
|
|
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}/
|
|
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
|
|
242
|
+
* Get service by FQN (with username) - Direct lookup
|
|
243
|
+
* Example: chat:1.0.0@alice
|
|
246
244
|
*/
|
|
247
|
-
async getService(
|
|
248
|
-
const response = await fetch(`${this.baseUrl}/services/${
|
|
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
|
-
*
|
|
256
|
+
* Discover a random available service without knowing the username
|
|
257
|
+
* Example: chat:1.0.0 (without @username)
|
|
259
258
|
*/
|
|
260
|
-
async
|
|
261
|
-
const response = await fetch(`${this.baseUrl}/
|
|
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
|
|
265
|
+
throw new Error(`Failed to discover service: ${error.error || response.statusText}`);
|
|
265
266
|
}
|
|
266
|
-
|
|
267
|
-
return data.services || [];
|
|
267
|
+
return await response.json();
|
|
268
268
|
}
|
|
269
269
|
/**
|
|
270
|
-
*
|
|
270
|
+
* Discover multiple available services with pagination
|
|
271
|
+
* Example: chat:1.0.0 (without @username)
|
|
271
272
|
*/
|
|
272
|
-
async
|
|
273
|
-
const
|
|
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
|
|
282
|
+
throw new Error(`Failed to discover services: ${error.error || response.statusText}`);
|
|
280
283
|
}
|
|
281
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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:
|
|
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
|
package/dist/rondevu-signaler.js
CHANGED
|
@@ -35,7 +35,7 @@ export class RondevuSignaler {
|
|
|
35
35
|
this.service = service;
|
|
36
36
|
this.host = host;
|
|
37
37
|
this.offerId = null;
|
|
38
|
-
this.
|
|
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.
|
|
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.
|
|
89
|
-
throw new Error('No service
|
|
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().
|
|
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.
|
|
134
|
-
console.warn('Cannot send ICE candidate: no service
|
|
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
|
-
|
|
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
|
-
//
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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().
|
|
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.
|
|
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.
|
|
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
|
-
.
|
|
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
|
+
}
|