@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.
- package/README.md +402 -436
- package/dist/durable/channel.d.ts +115 -0
- package/dist/durable/channel.js +301 -0
- package/dist/durable/connection.d.ts +125 -0
- package/dist/durable/connection.js +370 -0
- package/dist/durable/reconnection.d.ts +90 -0
- package/dist/durable/reconnection.js +127 -0
- package/dist/durable/service.d.ts +103 -0
- package/dist/durable/service.js +264 -0
- package/dist/durable/types.d.ts +149 -0
- package/dist/durable/types.js +28 -0
- package/dist/index.d.ts +5 -10
- package/dist/index.js +5 -9
- package/dist/offer-pool.d.ts +15 -3
- package/dist/offer-pool.js +34 -8
- package/dist/peer/exchanging-ice-state.js +10 -2
- package/dist/peer/index.d.ts +1 -1
- package/dist/peer/index.js +25 -3
- package/dist/peer/state.js +9 -1
- package/dist/rondevu.d.ts +88 -13
- package/dist/rondevu.js +110 -27
- package/dist/service-pool.d.ts +11 -3
- package/dist/service-pool.js +193 -44
- package/package.json +2 -2
- package/dist/bloom.d.ts +0 -30
- package/dist/bloom.js +0 -73
- package/dist/client.d.ts +0 -126
- package/dist/client.js +0 -171
- package/dist/connection.d.ts +0 -127
- package/dist/connection.js +0 -295
- package/dist/discovery.d.ts +0 -93
- package/dist/discovery.js +0 -164
- package/dist/peer.d.ts +0 -111
- package/dist/peer.js +0 -392
- package/dist/services.d.ts +0 -79
- package/dist/services.js +0 -206
- package/dist/types.d.ts +0 -157
- package/dist/types.js +0 -4
package/dist/service-pool.js
CHANGED
|
@@ -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
|
|
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:
|
|
61
|
+
{ id: service.offerId, peerId: this.credentials.peerId, sdp: service.offerSdp, topics: [], expiresAt: service.expiresAt, lastSeen: Date.now() },
|
|
57
62
|
...additionalOffers
|
|
58
63
|
];
|
|
59
|
-
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
//
|
|
229
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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.
|
|
4
|
-
"description": "TypeScript client for Rondevu
|
|
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
|
-
}
|