@xtr-dev/rondevu-client 0.3.4 → 0.4.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/README.md +401 -60
- package/dist/auth.d.ts +18 -0
- package/dist/auth.js +39 -0
- package/dist/bloom.d.ts +30 -0
- package/dist/bloom.js +73 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.js +18 -0
- package/dist/connection.d.ts +96 -45
- package/dist/connection.js +217 -196
- package/dist/index.d.ts +8 -3
- package/dist/index.js +9 -5
- package/dist/offers.d.ts +108 -0
- package/dist/offers.js +214 -0
- package/dist/rondevu.d.ts +39 -42
- package/dist/rondevu.js +33 -175
- package/package.json +2 -2
package/dist/connection.js
CHANGED
|
@@ -1,262 +1,283 @@
|
|
|
1
|
-
import { EventEmitter } from './event-emitter.js';
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
2
|
+
* High-level WebRTC connection manager for Rondevu
|
|
3
|
+
* Handles offer/answer exchange, ICE candidates, and connection lifecycle
|
|
4
4
|
*/
|
|
5
|
-
export class RondevuConnection
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
this.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.
|
|
23
|
-
|
|
5
|
+
export class RondevuConnection {
|
|
6
|
+
/**
|
|
7
|
+
* Current connection state
|
|
8
|
+
*/
|
|
9
|
+
get connectionState() {
|
|
10
|
+
return this.pc.connectionState;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The offer ID for this connection
|
|
14
|
+
*/
|
|
15
|
+
get id() {
|
|
16
|
+
return this.offerId;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get the primary data channel (if created)
|
|
20
|
+
*/
|
|
21
|
+
get channel() {
|
|
22
|
+
return this.dataChannel;
|
|
23
|
+
}
|
|
24
|
+
constructor(offersApi, rtcConfig = {
|
|
25
|
+
iceServers: [
|
|
26
|
+
{ urls: 'stun:stun.l.google.com:19302' },
|
|
27
|
+
{ urls: 'stun:stun1.l.google.com:19302' }
|
|
28
|
+
]
|
|
29
|
+
}) {
|
|
30
|
+
this.rtcConfig = rtcConfig;
|
|
31
|
+
this.lastIceTimestamp = Date.now();
|
|
32
|
+
this.eventListeners = new Map();
|
|
33
|
+
this.pendingIceCandidates = [];
|
|
34
|
+
this.offersApi = offersApi;
|
|
35
|
+
this.pc = new RTCPeerConnection(rtcConfig);
|
|
36
|
+
this.setupPeerConnection();
|
|
24
37
|
}
|
|
25
38
|
/**
|
|
26
|
-
*
|
|
39
|
+
* Set up peer connection event handlers
|
|
27
40
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
setupPeerConnection() {
|
|
42
|
+
this.pc.onicecandidate = async (event) => {
|
|
43
|
+
if (event.candidate) {
|
|
44
|
+
// Convert RTCIceCandidate to RTCIceCandidateInit (plain object)
|
|
45
|
+
const candidateData = {
|
|
46
|
+
candidate: event.candidate.candidate,
|
|
47
|
+
sdpMid: event.candidate.sdpMid,
|
|
48
|
+
sdpMLineIndex: event.candidate.sdpMLineIndex,
|
|
49
|
+
usernameFragment: event.candidate.usernameFragment,
|
|
50
|
+
};
|
|
51
|
+
if (this.offerId) {
|
|
52
|
+
// offerId is set, send immediately (trickle ICE)
|
|
53
|
+
try {
|
|
54
|
+
await this.offersApi.addIceCandidates(this.offerId, [candidateData]);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error('Error sending ICE candidate:', err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// offerId not set yet, buffer the candidate
|
|
62
|
+
this.pendingIceCandidates.push(candidateData);
|
|
63
|
+
}
|
|
35
64
|
}
|
|
36
65
|
};
|
|
37
|
-
// Connection state changes
|
|
38
66
|
this.pc.onconnectionstatechange = () => {
|
|
39
|
-
this.
|
|
67
|
+
switch (this.pc.connectionState) {
|
|
68
|
+
case 'connecting':
|
|
69
|
+
this.emit('connecting');
|
|
70
|
+
break;
|
|
71
|
+
case 'connected':
|
|
72
|
+
this.emit('connected');
|
|
73
|
+
// Stop polling once connected - we have all the ICE candidates we need
|
|
74
|
+
this.stopPolling();
|
|
75
|
+
break;
|
|
76
|
+
case 'disconnected':
|
|
77
|
+
case 'failed':
|
|
78
|
+
case 'closed':
|
|
79
|
+
this.emit('disconnected');
|
|
80
|
+
this.stopPolling();
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
40
83
|
};
|
|
41
|
-
// Remote data channels
|
|
42
84
|
this.pc.ondatachannel = (event) => {
|
|
43
|
-
this.
|
|
85
|
+
this.dataChannel = event.channel;
|
|
86
|
+
this.emit('datachannel', event.channel);
|
|
44
87
|
};
|
|
45
|
-
// Remote media streams
|
|
46
88
|
this.pc.ontrack = (event) => {
|
|
47
|
-
|
|
48
|
-
this.emit('stream', event.streams[0]);
|
|
49
|
-
}
|
|
89
|
+
this.emit('track', event);
|
|
50
90
|
};
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const state = this.pc.iceConnectionState;
|
|
54
|
-
if (state === 'failed' || state === 'closed') {
|
|
55
|
-
this.emit('error', new Error(`ICE connection ${state}`));
|
|
56
|
-
if (state === 'failed') {
|
|
57
|
-
this.close();
|
|
58
|
-
}
|
|
59
|
-
}
|
|
91
|
+
this.pc.onicecandidateerror = (event) => {
|
|
92
|
+
console.error('ICE candidate error:', event);
|
|
60
93
|
};
|
|
61
94
|
}
|
|
62
95
|
/**
|
|
63
|
-
*
|
|
96
|
+
* Flush buffered ICE candidates (trickle ICE support)
|
|
64
97
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.emit('disconnect');
|
|
75
|
-
break;
|
|
76
|
-
case 'failed':
|
|
77
|
-
this.emit('error', new Error('Connection failed'));
|
|
78
|
-
this.close();
|
|
79
|
-
break;
|
|
80
|
-
case 'closed':
|
|
81
|
-
this.emit('disconnect');
|
|
82
|
-
break;
|
|
98
|
+
async flushPendingIceCandidates() {
|
|
99
|
+
if (this.pendingIceCandidates.length > 0 && this.offerId) {
|
|
100
|
+
try {
|
|
101
|
+
await this.offersApi.addIceCandidates(this.offerId, this.pendingIceCandidates);
|
|
102
|
+
this.pendingIceCandidates = [];
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
console.error('Error flushing pending ICE candidates:', err);
|
|
106
|
+
}
|
|
83
107
|
}
|
|
84
108
|
}
|
|
85
109
|
/**
|
|
86
|
-
*
|
|
110
|
+
* Create an offer and advertise on topics
|
|
87
111
|
*/
|
|
88
|
-
async
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
throw new Error(`Failed to send ICE candidate: ${err.message}`);
|
|
112
|
+
async createOffer(options) {
|
|
113
|
+
this.role = 'offerer';
|
|
114
|
+
// Create data channel if requested
|
|
115
|
+
if (options.createDataChannel !== false) {
|
|
116
|
+
this.dataChannel = this.pc.createDataChannel(options.dataChannelLabel || 'data');
|
|
117
|
+
this.emit('datachannel', this.dataChannel);
|
|
98
118
|
}
|
|
119
|
+
// Create WebRTC offer
|
|
120
|
+
const offer = await this.pc.createOffer();
|
|
121
|
+
await this.pc.setLocalDescription(offer);
|
|
122
|
+
// Create offer on Rondevu server
|
|
123
|
+
const offers = await this.offersApi.create([{
|
|
124
|
+
sdp: offer.sdp,
|
|
125
|
+
topics: options.topics,
|
|
126
|
+
ttl: options.ttl || 300000
|
|
127
|
+
}]);
|
|
128
|
+
this.offerId = offers[0].id;
|
|
129
|
+
// Flush any ICE candidates that were generated during offer creation
|
|
130
|
+
await this.flushPendingIceCandidates();
|
|
131
|
+
// Start polling for answers
|
|
132
|
+
this.startAnswerPolling();
|
|
133
|
+
return this.offerId;
|
|
99
134
|
}
|
|
100
135
|
/**
|
|
101
|
-
*
|
|
136
|
+
* Answer an existing offer
|
|
102
137
|
*/
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.poll().catch((err) => {
|
|
110
|
-
this.emit('error', new Error(`Poll error: ${err.message}`));
|
|
138
|
+
async answer(offerId, offerSdp) {
|
|
139
|
+
this.role = 'answerer';
|
|
140
|
+
// Set remote description
|
|
141
|
+
await this.pc.setRemoteDescription({
|
|
142
|
+
type: 'offer',
|
|
143
|
+
sdp: offerSdp
|
|
111
144
|
});
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
145
|
+
// Create answer
|
|
146
|
+
const answer = await this.pc.createAnswer();
|
|
147
|
+
await this.pc.setLocalDescription(answer);
|
|
148
|
+
// Send answer to server FIRST
|
|
149
|
+
// This registers us as the answerer before ICE candidates arrive
|
|
150
|
+
await this.offersApi.answer(offerId, answer.sdp);
|
|
151
|
+
// Now set offerId to enable ICE candidate sending
|
|
152
|
+
// This prevents a race condition where ICE candidates arrive before answer is registered
|
|
153
|
+
this.offerId = offerId;
|
|
154
|
+
// Flush any ICE candidates that were generated during answer creation
|
|
155
|
+
await this.flushPendingIceCandidates();
|
|
156
|
+
// Start polling for ICE candidates
|
|
157
|
+
this.startIcePolling();
|
|
118
158
|
}
|
|
119
159
|
/**
|
|
120
|
-
*
|
|
160
|
+
* Start polling for answers (offerer only)
|
|
121
161
|
*/
|
|
122
|
-
|
|
123
|
-
this.
|
|
124
|
-
if (this.pollingInterval) {
|
|
125
|
-
clearInterval(this.pollingInterval);
|
|
126
|
-
this.pollingInterval = undefined;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Poll the signaling server for remote data
|
|
131
|
-
*/
|
|
132
|
-
async poll() {
|
|
133
|
-
if (this.isClosed) {
|
|
134
|
-
this.stopPolling();
|
|
162
|
+
startAnswerPolling() {
|
|
163
|
+
if (this.role !== 'offerer' || !this.offerId)
|
|
135
164
|
return;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (offererResponse.answer && !this.pc.currentRemoteDescription) {
|
|
165
|
+
this.answerPollingInterval = setInterval(async () => {
|
|
166
|
+
try {
|
|
167
|
+
const answers = await this.offersApi.getAnswers();
|
|
168
|
+
const myAnswer = answers.find(a => a.offerId === this.offerId);
|
|
169
|
+
if (myAnswer) {
|
|
170
|
+
// Set remote description
|
|
143
171
|
await this.pc.setRemoteDescription({
|
|
144
172
|
type: 'answer',
|
|
145
|
-
sdp:
|
|
173
|
+
sdp: myAnswer.sdp
|
|
146
174
|
});
|
|
175
|
+
// Stop answer polling, start ICE polling
|
|
176
|
+
this.stopAnswerPolling();
|
|
177
|
+
this.startIcePolling();
|
|
147
178
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
catch (err) {
|
|
156
|
-
console.warn('Failed to add ICE candidate:', err);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
console.error('Error polling for answers:', err);
|
|
182
|
+
// Stop polling if offer expired/not found
|
|
183
|
+
if (err instanceof Error && err.message.includes('not found')) {
|
|
184
|
+
this.stopPolling();
|
|
159
185
|
}
|
|
160
186
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
187
|
+
}, 2000);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Start polling for ICE candidates
|
|
191
|
+
*/
|
|
192
|
+
startIcePolling() {
|
|
193
|
+
if (!this.offerId)
|
|
194
|
+
return;
|
|
195
|
+
this.icePollingInterval = setInterval(async () => {
|
|
196
|
+
if (!this.offerId)
|
|
197
|
+
return;
|
|
198
|
+
try {
|
|
199
|
+
const candidates = await this.offersApi.getIceCandidates(this.offerId, this.lastIceTimestamp);
|
|
200
|
+
for (const cand of candidates) {
|
|
201
|
+
// Use the candidate object directly - it's already RTCIceCandidateInit
|
|
202
|
+
await this.pc.addIceCandidate(new RTCIceCandidate(cand.candidate));
|
|
203
|
+
this.lastIceTimestamp = cand.createdAt;
|
|
175
204
|
}
|
|
176
205
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
206
|
+
catch (err) {
|
|
207
|
+
console.error('Error polling for ICE candidates:', err);
|
|
208
|
+
// Stop polling if offer expired/not found
|
|
209
|
+
if (err instanceof Error && err.message.includes('not found')) {
|
|
210
|
+
this.stopPolling();
|
|
211
|
+
}
|
|
183
212
|
}
|
|
184
|
-
|
|
185
|
-
}
|
|
213
|
+
}, 1000);
|
|
186
214
|
}
|
|
187
215
|
/**
|
|
188
|
-
*
|
|
216
|
+
* Stop answer polling
|
|
189
217
|
*/
|
|
190
|
-
|
|
191
|
-
this.
|
|
192
|
-
|
|
218
|
+
stopAnswerPolling() {
|
|
219
|
+
if (this.answerPollingInterval) {
|
|
220
|
+
clearInterval(this.answerPollingInterval);
|
|
221
|
+
this.answerPollingInterval = undefined;
|
|
222
|
+
}
|
|
193
223
|
}
|
|
194
224
|
/**
|
|
195
|
-
*
|
|
225
|
+
* Stop ICE polling
|
|
196
226
|
*/
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
this.dataChannels.set(label, channel);
|
|
227
|
+
stopIcePolling() {
|
|
228
|
+
if (this.icePollingInterval) {
|
|
229
|
+
clearInterval(this.icePollingInterval);
|
|
230
|
+
this.icePollingInterval = undefined;
|
|
202
231
|
}
|
|
203
|
-
return channel;
|
|
204
232
|
}
|
|
205
233
|
/**
|
|
206
|
-
*
|
|
234
|
+
* Stop all polling
|
|
207
235
|
*/
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
});
|
|
236
|
+
stopPolling() {
|
|
237
|
+
this.stopAnswerPolling();
|
|
238
|
+
this.stopIcePolling();
|
|
212
239
|
}
|
|
213
240
|
/**
|
|
214
|
-
*
|
|
241
|
+
* Add event listener
|
|
215
242
|
*/
|
|
216
|
-
|
|
217
|
-
|
|
243
|
+
on(event, listener) {
|
|
244
|
+
if (!this.eventListeners.has(event)) {
|
|
245
|
+
this.eventListeners.set(event, new Set());
|
|
246
|
+
}
|
|
247
|
+
this.eventListeners.get(event).add(listener);
|
|
218
248
|
}
|
|
219
249
|
/**
|
|
220
|
-
*
|
|
250
|
+
* Remove event listener
|
|
221
251
|
*/
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
}, this.connectionTimeoutMs);
|
|
252
|
+
off(event, listener) {
|
|
253
|
+
const listeners = this.eventListeners.get(event);
|
|
254
|
+
if (listeners) {
|
|
255
|
+
listeners.delete(listener);
|
|
256
|
+
}
|
|
229
257
|
}
|
|
230
258
|
/**
|
|
231
|
-
*
|
|
259
|
+
* Emit event
|
|
232
260
|
*/
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
261
|
+
emit(event, ...args) {
|
|
262
|
+
const listeners = this.eventListeners.get(event);
|
|
263
|
+
if (listeners) {
|
|
264
|
+
listeners.forEach(listener => {
|
|
265
|
+
listener(...args);
|
|
266
|
+
});
|
|
237
267
|
}
|
|
238
268
|
}
|
|
239
269
|
/**
|
|
240
|
-
*
|
|
270
|
+
* Add a media track to the connection
|
|
271
|
+
*/
|
|
272
|
+
addTrack(track, ...streams) {
|
|
273
|
+
return this.pc.addTrack(track, ...streams);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Close the connection and clean up
|
|
241
277
|
*/
|
|
242
278
|
close() {
|
|
243
|
-
if (this.isClosed) {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
this.isClosed = true;
|
|
247
279
|
this.stopPolling();
|
|
248
|
-
this.
|
|
249
|
-
|
|
250
|
-
this.dataChannels.forEach(dc => {
|
|
251
|
-
if (dc.readyState === 'open' || dc.readyState === 'connecting') {
|
|
252
|
-
dc.close();
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
this.dataChannels.clear();
|
|
256
|
-
// Close peer connection
|
|
257
|
-
if (this.pc.connectionState !== 'closed') {
|
|
258
|
-
this.pc.close();
|
|
259
|
-
}
|
|
260
|
-
this.emit('disconnect');
|
|
280
|
+
this.pc.close();
|
|
281
|
+
this.eventListeners.clear();
|
|
261
282
|
}
|
|
262
283
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @xtr-dev/rondevu-client
|
|
3
|
-
* WebRTC peer signaling and discovery client
|
|
3
|
+
* WebRTC peer signaling and discovery client with topic-based discovery
|
|
4
4
|
*/
|
|
5
5
|
export { Rondevu } from './rondevu.js';
|
|
6
|
+
export type { RondevuOptions } from './rondevu.js';
|
|
7
|
+
export { RondevuAuth } from './auth.js';
|
|
8
|
+
export type { Credentials, FetchFunction } from './auth.js';
|
|
9
|
+
export { RondevuOffers } from './offers.js';
|
|
10
|
+
export type { CreateOfferRequest, Offer, IceCandidate, TopicInfo } from './offers.js';
|
|
11
|
+
export { BloomFilter } from './bloom.js';
|
|
6
12
|
export { RondevuConnection } from './connection.js';
|
|
7
|
-
export {
|
|
8
|
-
export type { RondevuOptions, ConnectionRole, RondevuConnectionParams, RondevuConnectionEvents, WebRTCPolyfill, Side, CreateOfferRequest, CreateOfferResponse, AnswerRequest, AnswerResponse, PollRequest, PollOffererResponse, PollAnswererResponse, PollResponse, VersionResponse, HealthResponse, ErrorResponse, RondevuClientOptions, } from './types.js';
|
|
13
|
+
export type { ConnectionOptions, RondevuConnectionEvents } from './connection.js';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @xtr-dev/rondevu-client
|
|
3
|
-
* WebRTC peer signaling and discovery client
|
|
3
|
+
* WebRTC peer signaling and discovery client with topic-based discovery
|
|
4
4
|
*/
|
|
5
|
-
// Export main
|
|
5
|
+
// Export main client class
|
|
6
6
|
export { Rondevu } from './rondevu.js';
|
|
7
|
-
// Export
|
|
7
|
+
// Export authentication
|
|
8
|
+
export { RondevuAuth } from './auth.js';
|
|
9
|
+
// Export offers API
|
|
10
|
+
export { RondevuOffers } from './offers.js';
|
|
11
|
+
// Export bloom filter
|
|
12
|
+
export { BloomFilter } from './bloom.js';
|
|
13
|
+
// Export connection manager
|
|
8
14
|
export { RondevuConnection } from './connection.js';
|
|
9
|
-
// Export low-level signaling API (for advanced usage)
|
|
10
|
-
export { RondevuAPI } from './client.js';
|
package/dist/offers.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Credentials, FetchFunction } from './auth.js';
|
|
2
|
+
export interface CreateOfferRequest {
|
|
3
|
+
id?: string;
|
|
4
|
+
sdp: string;
|
|
5
|
+
topics: string[];
|
|
6
|
+
ttl?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface Offer {
|
|
9
|
+
id: string;
|
|
10
|
+
peerId: string;
|
|
11
|
+
sdp: string;
|
|
12
|
+
topics: string[];
|
|
13
|
+
createdAt?: number;
|
|
14
|
+
expiresAt: number;
|
|
15
|
+
lastSeen: number;
|
|
16
|
+
answererPeerId?: string;
|
|
17
|
+
answerSdp?: string;
|
|
18
|
+
answeredAt?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* RTCIceCandidateInit interface for environments without native WebRTC types
|
|
22
|
+
*/
|
|
23
|
+
export interface RTCIceCandidateInit {
|
|
24
|
+
candidate?: string;
|
|
25
|
+
sdpMid?: string | null;
|
|
26
|
+
sdpMLineIndex?: number | null;
|
|
27
|
+
usernameFragment?: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface IceCandidate {
|
|
30
|
+
candidate: RTCIceCandidateInit;
|
|
31
|
+
peerId: string;
|
|
32
|
+
role: 'offerer' | 'answerer';
|
|
33
|
+
createdAt: number;
|
|
34
|
+
}
|
|
35
|
+
export interface TopicInfo {
|
|
36
|
+
topic: string;
|
|
37
|
+
activePeers: number;
|
|
38
|
+
}
|
|
39
|
+
export declare class RondevuOffers {
|
|
40
|
+
private baseUrl;
|
|
41
|
+
private credentials;
|
|
42
|
+
private fetchFn;
|
|
43
|
+
constructor(baseUrl: string, credentials: Credentials, fetchFn?: FetchFunction);
|
|
44
|
+
/**
|
|
45
|
+
* Create one or more offers
|
|
46
|
+
*/
|
|
47
|
+
create(offers: CreateOfferRequest[]): Promise<Offer[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Find offers by topic with optional bloom filter
|
|
50
|
+
*/
|
|
51
|
+
findByTopic(topic: string, options?: {
|
|
52
|
+
bloomFilter?: Uint8Array;
|
|
53
|
+
limit?: number;
|
|
54
|
+
}): Promise<Offer[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Get all offers from a specific peer
|
|
57
|
+
*/
|
|
58
|
+
getByPeerId(peerId: string): Promise<{
|
|
59
|
+
offers: Offer[];
|
|
60
|
+
topics: string[];
|
|
61
|
+
}>;
|
|
62
|
+
/**
|
|
63
|
+
* Get topics with active peer counts (paginated)
|
|
64
|
+
*/
|
|
65
|
+
getTopics(options?: {
|
|
66
|
+
limit?: number;
|
|
67
|
+
offset?: number;
|
|
68
|
+
}): Promise<{
|
|
69
|
+
topics: TopicInfo[];
|
|
70
|
+
total: number;
|
|
71
|
+
limit: number;
|
|
72
|
+
offset: number;
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Get own offers
|
|
76
|
+
*/
|
|
77
|
+
getMine(): Promise<Offer[]>;
|
|
78
|
+
/**
|
|
79
|
+
* Update offer heartbeat
|
|
80
|
+
*/
|
|
81
|
+
heartbeat(offerId: string): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Delete an offer
|
|
84
|
+
*/
|
|
85
|
+
delete(offerId: string): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Answer an offer
|
|
88
|
+
*/
|
|
89
|
+
answer(offerId: string, sdp: string): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Get answers to your offers
|
|
92
|
+
*/
|
|
93
|
+
getAnswers(): Promise<Array<{
|
|
94
|
+
offerId: string;
|
|
95
|
+
answererId: string;
|
|
96
|
+
sdp: string;
|
|
97
|
+
answeredAt: number;
|
|
98
|
+
topics: string[];
|
|
99
|
+
}>>;
|
|
100
|
+
/**
|
|
101
|
+
* Post ICE candidates for an offer
|
|
102
|
+
*/
|
|
103
|
+
addIceCandidates(offerId: string, candidates: RTCIceCandidateInit[]): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Get ICE candidates for an offer
|
|
106
|
+
*/
|
|
107
|
+
getIceCandidates(offerId: string, since?: number): Promise<IceCandidate[]>;
|
|
108
|
+
}
|