@xtr-dev/rondevu-client 0.9.2 → 0.10.1
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 +147 -0
- package/dist/api.js +307 -0
- package/dist/bin.d.ts +35 -0
- package/dist/bin.js +35 -0
- package/dist/connection-manager.d.ts +104 -0
- package/dist/connection-manager.js +324 -0
- package/dist/connection.d.ts +112 -0
- package/dist/connection.js +194 -0
- package/dist/event-bus.d.ts +52 -0
- package/dist/event-bus.js +84 -0
- package/dist/index.d.ts +15 -11
- package/dist/index.js +9 -11
- package/dist/noop-signaler.d.ts +14 -0
- package/dist/noop-signaler.js +27 -0
- package/dist/rondevu-service.d.ts +81 -0
- package/dist/rondevu-service.js +131 -0
- package/dist/service-client.d.ts +94 -0
- package/dist/service-client.js +186 -0
- package/dist/service-host.d.ts +103 -0
- package/dist/service-host.js +186 -0
- package/dist/signaler.d.ts +25 -0
- package/dist/signaler.js +89 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +2 -0
- package/dist/webrtc-context.d.ts +7 -0
- package/dist/webrtc-context.js +36 -0
- package/package.json +16 -2
- package/dist/auth.d.ts +0 -20
- package/dist/auth.js +0 -41
- package/dist/durable/channel.d.ts +0 -115
- package/dist/durable/channel.js +0 -301
- package/dist/durable/connection.d.ts +0 -125
- package/dist/durable/connection.js +0 -370
- package/dist/durable/reconnection.d.ts +0 -90
- package/dist/durable/reconnection.js +0 -127
- package/dist/durable/service.d.ts +0 -103
- package/dist/durable/service.js +0 -264
- package/dist/durable/types.d.ts +0 -149
- package/dist/durable/types.js +0 -28
- package/dist/event-emitter.d.ts +0 -54
- package/dist/event-emitter.js +0 -102
- package/dist/offer-pool.d.ts +0 -86
- package/dist/offer-pool.js +0 -145
- package/dist/offers.d.ts +0 -101
- package/dist/offers.js +0 -202
- package/dist/peer/answering-state.d.ts +0 -11
- package/dist/peer/answering-state.js +0 -39
- package/dist/peer/closed-state.d.ts +0 -8
- package/dist/peer/closed-state.js +0 -10
- package/dist/peer/connected-state.d.ts +0 -8
- package/dist/peer/connected-state.js +0 -11
- package/dist/peer/creating-offer-state.d.ts +0 -12
- package/dist/peer/creating-offer-state.js +0 -45
- package/dist/peer/exchanging-ice-state.d.ts +0 -17
- package/dist/peer/exchanging-ice-state.js +0 -64
- package/dist/peer/failed-state.d.ts +0 -10
- package/dist/peer/failed-state.js +0 -16
- package/dist/peer/idle-state.d.ts +0 -7
- package/dist/peer/idle-state.js +0 -14
- package/dist/peer/index.d.ts +0 -71
- package/dist/peer/index.js +0 -176
- package/dist/peer/state.d.ts +0 -23
- package/dist/peer/state.js +0 -63
- package/dist/peer/types.d.ts +0 -43
- package/dist/peer/types.js +0 -1
- package/dist/peer/waiting-for-answer-state.d.ts +0 -17
- package/dist/peer/waiting-for-answer-state.js +0 -60
- package/dist/rondevu.d.ts +0 -184
- package/dist/rondevu.js +0 -171
- package/dist/service-pool.d.ts +0 -123
- package/dist/service-pool.js +0 -488
- package/dist/usernames.d.ts +0 -79
- package/dist/usernames.js +0 -153
package/dist/service-pool.js
DELETED
|
@@ -1,488 +0,0 @@
|
|
|
1
|
-
import { RondevuOffers } from './offers.js';
|
|
2
|
-
import { RondevuUsername } from './usernames.js';
|
|
3
|
-
import RondevuPeer from './peer/index.js';
|
|
4
|
-
import { OfferPool } from './offer-pool.js';
|
|
5
|
-
/**
|
|
6
|
-
* Manages a pooled service with multiple concurrent connections
|
|
7
|
-
*
|
|
8
|
-
* ServicePool coordinates offer creation, answer polling, and connection
|
|
9
|
-
* management for services that need to handle multiple simultaneous connections.
|
|
10
|
-
*/
|
|
11
|
-
export class ServicePool {
|
|
12
|
-
constructor(baseUrl, credentials, options) {
|
|
13
|
-
this.baseUrl = baseUrl;
|
|
14
|
-
this.credentials = credentials;
|
|
15
|
-
this.options = options;
|
|
16
|
-
this.connections = new Map();
|
|
17
|
-
this.peerConnections = new Map();
|
|
18
|
-
this.status = {
|
|
19
|
-
activeOffers: 0,
|
|
20
|
-
activeConnections: 0,
|
|
21
|
-
totalConnectionsHandled: 0,
|
|
22
|
-
failedOfferCreations: 0
|
|
23
|
-
};
|
|
24
|
-
this.offersApi = new RondevuOffers(baseUrl, credentials);
|
|
25
|
-
this.usernameApi = new RondevuUsername(baseUrl);
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Start the pooled service
|
|
29
|
-
*/
|
|
30
|
-
async start() {
|
|
31
|
-
const poolSize = this.options.poolSize || 1;
|
|
32
|
-
// 1. Create initial service (publishes first offer)
|
|
33
|
-
const service = await this.publishInitialService();
|
|
34
|
-
this.serviceId = service.serviceId;
|
|
35
|
-
this.uuid = service.uuid;
|
|
36
|
-
// 2. Create additional offers for pool (poolSize - 1)
|
|
37
|
-
const additionalOffers = [];
|
|
38
|
-
const additionalPeerConnections = [];
|
|
39
|
-
const additionalDataChannels = [];
|
|
40
|
-
if (poolSize > 1) {
|
|
41
|
-
try {
|
|
42
|
-
const result = await this.createOffers(poolSize - 1);
|
|
43
|
-
additionalOffers.push(...result.offers);
|
|
44
|
-
additionalPeerConnections.push(...result.peerConnections);
|
|
45
|
-
additionalDataChannels.push(...result.dataChannels);
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
this.handleError(error, 'initial-offer-creation');
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
// 3. Initialize OfferPool with all offers
|
|
52
|
-
this.offerPool = new OfferPool(this.offersApi, {
|
|
53
|
-
poolSize,
|
|
54
|
-
pollingInterval: this.options.pollingInterval || 2000,
|
|
55
|
-
onAnswered: (answer) => this.handleConnection(answer),
|
|
56
|
-
onRefill: (count) => this.createOffers(count),
|
|
57
|
-
onError: (err, ctx) => this.handleError(err, ctx)
|
|
58
|
-
});
|
|
59
|
-
// Add all offers to pool with their peer connections and data channels
|
|
60
|
-
const allOffers = [
|
|
61
|
-
{ id: service.offerId, peerId: this.credentials.peerId, sdp: service.offerSdp, topics: [], expiresAt: service.expiresAt, lastSeen: Date.now() },
|
|
62
|
-
...additionalOffers
|
|
63
|
-
];
|
|
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);
|
|
73
|
-
// 4. Start polling
|
|
74
|
-
await this.offerPool.start();
|
|
75
|
-
// Update status
|
|
76
|
-
this.updateStatus();
|
|
77
|
-
// 5. Return handle
|
|
78
|
-
return {
|
|
79
|
-
serviceId: this.serviceId,
|
|
80
|
-
uuid: this.uuid,
|
|
81
|
-
offerId: service.offerId,
|
|
82
|
-
unpublish: () => this.stop(),
|
|
83
|
-
getStatus: () => this.getStatus(),
|
|
84
|
-
addOffers: (count) => this.manualRefill(count)
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Stop the pooled service and clean up
|
|
89
|
-
*/
|
|
90
|
-
async stop() {
|
|
91
|
-
// 1. Stop accepting new connections
|
|
92
|
-
if (this.offerPool) {
|
|
93
|
-
await this.offerPool.stop();
|
|
94
|
-
}
|
|
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
|
|
108
|
-
if (this.offerPool) {
|
|
109
|
-
const offerIds = this.offerPool.getActiveOfferIds();
|
|
110
|
-
await Promise.allSettled(offerIds.map(id => this.offersApi.delete(id).catch(() => { })));
|
|
111
|
-
}
|
|
112
|
-
// 4. Close active connections
|
|
113
|
-
const closePromises = Array.from(this.connections.values()).map(async (conn) => {
|
|
114
|
-
try {
|
|
115
|
-
// Give a brief moment for graceful closure
|
|
116
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
117
|
-
conn.peer.pc.close();
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
// Ignore errors during cleanup
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
await Promise.allSettled(closePromises);
|
|
124
|
-
// 5. Delete service if we have a serviceId
|
|
125
|
-
if (this.serviceId) {
|
|
126
|
-
try {
|
|
127
|
-
const response = await fetch(`${this.baseUrl}/services/${this.serviceId}`, {
|
|
128
|
-
method: 'DELETE',
|
|
129
|
-
headers: {
|
|
130
|
-
'Content-Type': 'application/json',
|
|
131
|
-
'Authorization': `Bearer ${this.credentials.peerId}:${this.credentials.secret}`
|
|
132
|
-
},
|
|
133
|
-
body: JSON.stringify({ username: this.options.username })
|
|
134
|
-
});
|
|
135
|
-
if (!response.ok) {
|
|
136
|
-
console.error('Failed to delete service:', await response.text());
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
|
-
console.error('Error deleting service:', error);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
// Clear all state
|
|
144
|
-
this.connections.clear();
|
|
145
|
-
this.offerPool = undefined;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Handle an answered offer by setting up the connection
|
|
149
|
-
*/
|
|
150
|
-
async handleConnection(answer) {
|
|
151
|
-
const connectionId = this.generateConnectionId();
|
|
152
|
-
try {
|
|
153
|
-
// Use the existing peer connection from the pool
|
|
154
|
-
const peer = new RondevuPeer(this.offersApi, this.options.rtcConfig || {
|
|
155
|
-
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
|
156
|
-
}, answer.peerConnection // Use the existing peer connection
|
|
157
|
-
);
|
|
158
|
-
peer.role = 'offerer';
|
|
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
|
-
}
|
|
175
|
-
// Set remote description (the answer)
|
|
176
|
-
await peer.pc.setRemoteDescription({
|
|
177
|
-
type: 'answer',
|
|
178
|
-
sdp: answer.sdp
|
|
179
|
-
});
|
|
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 = () => {
|
|
190
|
-
clearTimeout(timeout);
|
|
191
|
-
resolve();
|
|
192
|
-
};
|
|
193
|
-
channel.onerror = (error) => {
|
|
194
|
-
clearTimeout(timeout);
|
|
195
|
-
reject(new Error('Data channel error'));
|
|
196
|
-
};
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
// Register connection
|
|
200
|
-
this.connections.set(connectionId, {
|
|
201
|
-
peer,
|
|
202
|
-
channel,
|
|
203
|
-
connectedAt: Date.now(),
|
|
204
|
-
offerId: answer.offerId
|
|
205
|
-
});
|
|
206
|
-
this.status.activeConnections++;
|
|
207
|
-
this.status.totalConnectionsHandled++;
|
|
208
|
-
// Setup cleanup on disconnect
|
|
209
|
-
peer.on('disconnected', () => {
|
|
210
|
-
this.connections.delete(connectionId);
|
|
211
|
-
this.status.activeConnections--;
|
|
212
|
-
this.updateStatus();
|
|
213
|
-
});
|
|
214
|
-
peer.on('failed', () => {
|
|
215
|
-
this.connections.delete(connectionId);
|
|
216
|
-
this.status.activeConnections--;
|
|
217
|
-
this.updateStatus();
|
|
218
|
-
});
|
|
219
|
-
// Update status
|
|
220
|
-
this.updateStatus();
|
|
221
|
-
// Invoke user handler (wrapped in try-catch)
|
|
222
|
-
try {
|
|
223
|
-
this.options.handler(channel, peer, connectionId);
|
|
224
|
-
}
|
|
225
|
-
catch (handlerError) {
|
|
226
|
-
this.handleError(handlerError, 'handler');
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
this.handleError(error, 'connection-setup');
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Create multiple offers
|
|
235
|
-
*/
|
|
236
|
-
async createOffers(count) {
|
|
237
|
-
if (count <= 0) {
|
|
238
|
-
return { offers: [], peerConnections: [], dataChannels: [] };
|
|
239
|
-
}
|
|
240
|
-
// Server supports max 10 offers per request
|
|
241
|
-
const batchSize = Math.min(count, 10);
|
|
242
|
-
const offers = [];
|
|
243
|
-
const peerConnections = [];
|
|
244
|
-
const dataChannels = [];
|
|
245
|
-
try {
|
|
246
|
-
// Create peer connections and generate offers
|
|
247
|
-
const offerRequests = [];
|
|
248
|
-
const pendingCandidates = []; // Store candidates before we have offer IDs
|
|
249
|
-
for (let i = 0; i < batchSize; i++) {
|
|
250
|
-
const pc = new RTCPeerConnection(this.options.rtcConfig || {
|
|
251
|
-
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
|
252
|
-
});
|
|
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
|
-
};
|
|
274
|
-
// Create offer
|
|
275
|
-
const offer = await pc.createOffer();
|
|
276
|
-
await pc.setLocalDescription(offer); // ICE gathering starts here, candidates go to collector
|
|
277
|
-
if (!offer.sdp) {
|
|
278
|
-
pc.close();
|
|
279
|
-
throw new Error('Failed to generate SDP');
|
|
280
|
-
}
|
|
281
|
-
offerRequests.push({
|
|
282
|
-
sdp: offer.sdp,
|
|
283
|
-
topics: [], // V2 doesn't use topics
|
|
284
|
-
ttl: this.options.ttl
|
|
285
|
-
});
|
|
286
|
-
// Keep peer connection alive - DO NOT CLOSE
|
|
287
|
-
peerConnections.push(pc);
|
|
288
|
-
}
|
|
289
|
-
// Batch create offers
|
|
290
|
-
const createdOffers = await this.offersApi.create(offerRequests);
|
|
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
|
-
}
|
|
328
|
-
}
|
|
329
|
-
catch (error) {
|
|
330
|
-
// Close any created peer connections on error
|
|
331
|
-
peerConnections.forEach(pc => pc.close());
|
|
332
|
-
this.status.failedOfferCreations++;
|
|
333
|
-
this.handleError(error, 'offer-creation');
|
|
334
|
-
throw error;
|
|
335
|
-
}
|
|
336
|
-
return { offers, peerConnections, dataChannels };
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Publish the initial service (creates first offer)
|
|
340
|
-
*/
|
|
341
|
-
async publishInitialService() {
|
|
342
|
-
const { username, privateKey, serviceFqn, rtcConfig, isPublic, metadata, ttl } = this.options;
|
|
343
|
-
// Create peer connection for initial offer
|
|
344
|
-
const pc = new RTCPeerConnection(rtcConfig || {
|
|
345
|
-
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
|
346
|
-
});
|
|
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
|
-
};
|
|
366
|
-
// Create offer
|
|
367
|
-
const offer = await pc.createOffer();
|
|
368
|
-
await pc.setLocalDescription(offer); // ICE gathering starts here
|
|
369
|
-
if (!offer.sdp) {
|
|
370
|
-
pc.close();
|
|
371
|
-
throw new Error('Failed to generate SDP');
|
|
372
|
-
}
|
|
373
|
-
// Store the SDP
|
|
374
|
-
const offerSdp = offer.sdp;
|
|
375
|
-
// Create signature
|
|
376
|
-
const timestamp = Date.now();
|
|
377
|
-
const message = `publish:${username}:${serviceFqn}:${timestamp}`;
|
|
378
|
-
const signature = await this.usernameApi.signMessage(message, privateKey);
|
|
379
|
-
// Publish service
|
|
380
|
-
const response = await fetch(`${this.baseUrl}/services`, {
|
|
381
|
-
method: 'POST',
|
|
382
|
-
headers: {
|
|
383
|
-
'Content-Type': 'application/json',
|
|
384
|
-
'Authorization': `Bearer ${this.credentials.peerId}:${this.credentials.secret}`
|
|
385
|
-
},
|
|
386
|
-
body: JSON.stringify({
|
|
387
|
-
username,
|
|
388
|
-
serviceFqn,
|
|
389
|
-
sdp: offerSdp,
|
|
390
|
-
ttl,
|
|
391
|
-
isPublic,
|
|
392
|
-
metadata,
|
|
393
|
-
signature,
|
|
394
|
-
message
|
|
395
|
-
})
|
|
396
|
-
});
|
|
397
|
-
if (!response.ok) {
|
|
398
|
-
pc.close();
|
|
399
|
-
const error = await response.json();
|
|
400
|
-
throw new Error(error.error || 'Failed to publish service');
|
|
401
|
-
}
|
|
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
|
-
};
|
|
433
|
-
return {
|
|
434
|
-
serviceId: data.serviceId,
|
|
435
|
-
uuid: data.uuid,
|
|
436
|
-
offerId: data.offerId,
|
|
437
|
-
offerSdp,
|
|
438
|
-
expiresAt: data.expiresAt,
|
|
439
|
-
peerConnection: pc, // Keep peer connection alive
|
|
440
|
-
dataChannel // Keep data channel alive
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
/**
|
|
444
|
-
* Manually add offers to the pool
|
|
445
|
-
*/
|
|
446
|
-
async manualRefill(count) {
|
|
447
|
-
if (!this.offerPool) {
|
|
448
|
-
throw new Error('Pool not started');
|
|
449
|
-
}
|
|
450
|
-
const result = await this.createOffers(count);
|
|
451
|
-
await this.offerPool.addOffers(result.offers, result.peerConnections, result.dataChannels);
|
|
452
|
-
this.updateStatus();
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* Get current pool status
|
|
456
|
-
*/
|
|
457
|
-
getStatus() {
|
|
458
|
-
return { ...this.status };
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* Update status and notify listeners
|
|
462
|
-
*/
|
|
463
|
-
updateStatus() {
|
|
464
|
-
if (this.offerPool) {
|
|
465
|
-
this.status.activeOffers = this.offerPool.getActiveOfferCount();
|
|
466
|
-
}
|
|
467
|
-
if (this.options.onPoolStatus) {
|
|
468
|
-
this.options.onPoolStatus(this.getStatus());
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Handle errors
|
|
473
|
-
*/
|
|
474
|
-
handleError(error, context) {
|
|
475
|
-
if (this.options.onError) {
|
|
476
|
-
this.options.onError(error, context);
|
|
477
|
-
}
|
|
478
|
-
else {
|
|
479
|
-
console.error(`ServicePool error (${context}):`, error);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Generate a unique connection ID
|
|
484
|
-
*/
|
|
485
|
-
generateConnectionId() {
|
|
486
|
-
return `conn-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
487
|
-
}
|
|
488
|
-
}
|
package/dist/usernames.d.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Username claim result
|
|
3
|
-
*/
|
|
4
|
-
export interface UsernameClaimResult {
|
|
5
|
-
username: string;
|
|
6
|
-
publicKey: string;
|
|
7
|
-
privateKey: string;
|
|
8
|
-
claimedAt: number;
|
|
9
|
-
expiresAt: number;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Username availability check result
|
|
13
|
-
*/
|
|
14
|
-
export interface UsernameCheckResult {
|
|
15
|
-
username: string;
|
|
16
|
-
available: boolean;
|
|
17
|
-
claimedAt?: number;
|
|
18
|
-
expiresAt?: number;
|
|
19
|
-
publicKey?: string;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Rondevu Username API
|
|
23
|
-
* Handles username claiming with Ed25519 cryptographic proof
|
|
24
|
-
*/
|
|
25
|
-
export declare class RondevuUsername {
|
|
26
|
-
private baseUrl;
|
|
27
|
-
constructor(baseUrl: string);
|
|
28
|
-
/**
|
|
29
|
-
* Generates an Ed25519 keypair for username claiming
|
|
30
|
-
*/
|
|
31
|
-
generateKeypair(): Promise<{
|
|
32
|
-
publicKey: string;
|
|
33
|
-
privateKey: string;
|
|
34
|
-
}>;
|
|
35
|
-
/**
|
|
36
|
-
* Signs a message with an Ed25519 private key
|
|
37
|
-
*/
|
|
38
|
-
signMessage(message: string, privateKeyBase64: string): Promise<string>;
|
|
39
|
-
/**
|
|
40
|
-
* Claims a username
|
|
41
|
-
* Generates a new keypair if one is not provided
|
|
42
|
-
*/
|
|
43
|
-
claimUsername(username: string, existingKeypair?: {
|
|
44
|
-
publicKey: string;
|
|
45
|
-
privateKey: string;
|
|
46
|
-
}): Promise<UsernameClaimResult>;
|
|
47
|
-
/**
|
|
48
|
-
* Checks if a username is available
|
|
49
|
-
*/
|
|
50
|
-
checkUsername(username: string): Promise<UsernameCheckResult>;
|
|
51
|
-
/**
|
|
52
|
-
* Helper: Save keypair to localStorage
|
|
53
|
-
* WARNING: This stores the private key in localStorage which is not the most secure
|
|
54
|
-
* For production use, consider using IndexedDB with encryption or hardware security modules
|
|
55
|
-
*/
|
|
56
|
-
saveKeypairToStorage(username: string, publicKey: string, privateKey: string): void;
|
|
57
|
-
/**
|
|
58
|
-
* Helper: Load keypair from localStorage
|
|
59
|
-
*/
|
|
60
|
-
loadKeypairFromStorage(username: string): {
|
|
61
|
-
publicKey: string;
|
|
62
|
-
privateKey: string;
|
|
63
|
-
} | null;
|
|
64
|
-
/**
|
|
65
|
-
* Helper: Delete keypair from localStorage
|
|
66
|
-
*/
|
|
67
|
-
deleteKeypairFromStorage(username: string): void;
|
|
68
|
-
/**
|
|
69
|
-
* Export keypair as JSON string (for backup)
|
|
70
|
-
*/
|
|
71
|
-
exportKeypair(publicKey: string, privateKey: string): string;
|
|
72
|
-
/**
|
|
73
|
-
* Import keypair from JSON string
|
|
74
|
-
*/
|
|
75
|
-
importKeypair(json: string): {
|
|
76
|
-
publicKey: string;
|
|
77
|
-
privateKey: string;
|
|
78
|
-
};
|
|
79
|
-
}
|