@xtr-dev/rondevu-client 0.9.1 → 0.10.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 +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 +92 -0
- package/dist/service-client.js +185 -0
- package/dist/service-host.d.ts +101 -0
- package/dist/service-host.js +185 -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 +6 -0
- package/dist/webrtc-context.js +34 -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 -417
- package/dist/usernames.d.ts +0 -79
- package/dist/usernames.js +0 -153
package/dist/service-pool.js
DELETED
|
@@ -1,417 +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
|
-
for (let i = 0; i < batchSize; i++) {
|
|
249
|
-
const pc = new RTCPeerConnection(this.options.rtcConfig || {
|
|
250
|
-
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
|
251
|
-
});
|
|
252
|
-
// Create data channel (required for offers) and save reference
|
|
253
|
-
const channel = pc.createDataChannel('rondevu-service');
|
|
254
|
-
dataChannels.push(channel);
|
|
255
|
-
// Create offer
|
|
256
|
-
const offer = await pc.createOffer();
|
|
257
|
-
await pc.setLocalDescription(offer);
|
|
258
|
-
if (!offer.sdp) {
|
|
259
|
-
pc.close();
|
|
260
|
-
throw new Error('Failed to generate SDP');
|
|
261
|
-
}
|
|
262
|
-
offerRequests.push({
|
|
263
|
-
sdp: offer.sdp,
|
|
264
|
-
topics: [], // V2 doesn't use topics
|
|
265
|
-
ttl: this.options.ttl
|
|
266
|
-
});
|
|
267
|
-
// Keep peer connection alive - DO NOT CLOSE
|
|
268
|
-
peerConnections.push(pc);
|
|
269
|
-
}
|
|
270
|
-
// Batch create offers
|
|
271
|
-
const createdOffers = await this.offersApi.create(offerRequests);
|
|
272
|
-
offers.push(...createdOffers);
|
|
273
|
-
// Set up ICE candidate handlers AFTER we have offer IDs
|
|
274
|
-
for (let i = 0; i < peerConnections.length; i++) {
|
|
275
|
-
const pc = peerConnections[i];
|
|
276
|
-
const offerId = createdOffers[i].id;
|
|
277
|
-
pc.onicecandidate = async (event) => {
|
|
278
|
-
if (event.candidate) {
|
|
279
|
-
const candidateData = event.candidate.toJSON();
|
|
280
|
-
if (candidateData.candidate && candidateData.candidate !== '') {
|
|
281
|
-
try {
|
|
282
|
-
await this.offersApi.addIceCandidates(offerId, [candidateData]);
|
|
283
|
-
}
|
|
284
|
-
catch (err) {
|
|
285
|
-
console.error('Error sending ICE candidate:', err);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
catch (error) {
|
|
293
|
-
// Close any created peer connections on error
|
|
294
|
-
peerConnections.forEach(pc => pc.close());
|
|
295
|
-
this.status.failedOfferCreations++;
|
|
296
|
-
this.handleError(error, 'offer-creation');
|
|
297
|
-
throw error;
|
|
298
|
-
}
|
|
299
|
-
return { offers, peerConnections, dataChannels };
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Publish the initial service (creates first offer)
|
|
303
|
-
*/
|
|
304
|
-
async publishInitialService() {
|
|
305
|
-
const { username, privateKey, serviceFqn, rtcConfig, isPublic, metadata, ttl } = this.options;
|
|
306
|
-
// Create peer connection for initial offer
|
|
307
|
-
const pc = new RTCPeerConnection(rtcConfig || {
|
|
308
|
-
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
|
309
|
-
});
|
|
310
|
-
const dataChannel = pc.createDataChannel('rondevu-service');
|
|
311
|
-
// Create offer
|
|
312
|
-
const offer = await pc.createOffer();
|
|
313
|
-
await pc.setLocalDescription(offer);
|
|
314
|
-
if (!offer.sdp) {
|
|
315
|
-
pc.close();
|
|
316
|
-
throw new Error('Failed to generate SDP');
|
|
317
|
-
}
|
|
318
|
-
// Store the SDP
|
|
319
|
-
const offerSdp = offer.sdp;
|
|
320
|
-
// Create signature
|
|
321
|
-
const timestamp = Date.now();
|
|
322
|
-
const message = `publish:${username}:${serviceFqn}:${timestamp}`;
|
|
323
|
-
const signature = await this.usernameApi.signMessage(message, privateKey);
|
|
324
|
-
// Publish service
|
|
325
|
-
const response = await fetch(`${this.baseUrl}/services`, {
|
|
326
|
-
method: 'POST',
|
|
327
|
-
headers: {
|
|
328
|
-
'Content-Type': 'application/json',
|
|
329
|
-
'Authorization': `Bearer ${this.credentials.peerId}:${this.credentials.secret}`
|
|
330
|
-
},
|
|
331
|
-
body: JSON.stringify({
|
|
332
|
-
username,
|
|
333
|
-
serviceFqn,
|
|
334
|
-
sdp: offerSdp,
|
|
335
|
-
ttl,
|
|
336
|
-
isPublic,
|
|
337
|
-
metadata,
|
|
338
|
-
signature,
|
|
339
|
-
message
|
|
340
|
-
})
|
|
341
|
-
});
|
|
342
|
-
if (!response.ok) {
|
|
343
|
-
pc.close();
|
|
344
|
-
const error = await response.json();
|
|
345
|
-
throw new Error(error.error || 'Failed to publish service');
|
|
346
|
-
}
|
|
347
|
-
const data = await response.json();
|
|
348
|
-
// Set up ICE candidate handler now that we have the offer ID
|
|
349
|
-
pc.onicecandidate = async (event) => {
|
|
350
|
-
if (event.candidate) {
|
|
351
|
-
const candidateData = event.candidate.toJSON();
|
|
352
|
-
if (candidateData.candidate && candidateData.candidate !== '') {
|
|
353
|
-
try {
|
|
354
|
-
await this.offersApi.addIceCandidates(data.offerId, [candidateData]);
|
|
355
|
-
}
|
|
356
|
-
catch (err) {
|
|
357
|
-
console.error('Error sending ICE candidate:', err);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
return {
|
|
363
|
-
serviceId: data.serviceId,
|
|
364
|
-
uuid: data.uuid,
|
|
365
|
-
offerId: data.offerId,
|
|
366
|
-
offerSdp,
|
|
367
|
-
expiresAt: data.expiresAt,
|
|
368
|
-
peerConnection: pc, // Keep peer connection alive
|
|
369
|
-
dataChannel // Keep data channel alive
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Manually add offers to the pool
|
|
374
|
-
*/
|
|
375
|
-
async manualRefill(count) {
|
|
376
|
-
if (!this.offerPool) {
|
|
377
|
-
throw new Error('Pool not started');
|
|
378
|
-
}
|
|
379
|
-
const result = await this.createOffers(count);
|
|
380
|
-
await this.offerPool.addOffers(result.offers, result.peerConnections, result.dataChannels);
|
|
381
|
-
this.updateStatus();
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Get current pool status
|
|
385
|
-
*/
|
|
386
|
-
getStatus() {
|
|
387
|
-
return { ...this.status };
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* Update status and notify listeners
|
|
391
|
-
*/
|
|
392
|
-
updateStatus() {
|
|
393
|
-
if (this.offerPool) {
|
|
394
|
-
this.status.activeOffers = this.offerPool.getActiveOfferCount();
|
|
395
|
-
}
|
|
396
|
-
if (this.options.onPoolStatus) {
|
|
397
|
-
this.options.onPoolStatus(this.getStatus());
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Handle errors
|
|
402
|
-
*/
|
|
403
|
-
handleError(error, context) {
|
|
404
|
-
if (this.options.onError) {
|
|
405
|
-
this.options.onError(error, context);
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
console.error(`ServicePool error (${context}):`, error);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Generate a unique connection ID
|
|
413
|
-
*/
|
|
414
|
-
generateConnectionId() {
|
|
415
|
-
return `conn-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
416
|
-
}
|
|
417
|
-
}
|
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
|
-
}
|
package/dist/usernames.js
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import * as ed25519 from '@noble/ed25519';
|
|
2
|
-
// Set SHA-512 hash function for ed25519 (required in @noble/ed25519 v3+)
|
|
3
|
-
// Uses built-in WebCrypto API which only provides async digest
|
|
4
|
-
// We use the async ed25519 functions (signAsync, verifyAsync, getPublicKeyAsync)
|
|
5
|
-
ed25519.hashes.sha512Async = async (message) => {
|
|
6
|
-
return new Uint8Array(await crypto.subtle.digest('SHA-512', message));
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* Convert Uint8Array to base64 string
|
|
10
|
-
*/
|
|
11
|
-
function bytesToBase64(bytes) {
|
|
12
|
-
const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join('');
|
|
13
|
-
return btoa(binString);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Convert base64 string to Uint8Array
|
|
17
|
-
*/
|
|
18
|
-
function base64ToBytes(base64) {
|
|
19
|
-
const binString = atob(base64);
|
|
20
|
-
return Uint8Array.from(binString, (char) => char.codePointAt(0));
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Rondevu Username API
|
|
24
|
-
* Handles username claiming with Ed25519 cryptographic proof
|
|
25
|
-
*/
|
|
26
|
-
export class RondevuUsername {
|
|
27
|
-
constructor(baseUrl) {
|
|
28
|
-
this.baseUrl = baseUrl;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Generates an Ed25519 keypair for username claiming
|
|
32
|
-
*/
|
|
33
|
-
async generateKeypair() {
|
|
34
|
-
const privateKey = ed25519.utils.randomSecretKey();
|
|
35
|
-
const publicKey = await ed25519.getPublicKeyAsync(privateKey);
|
|
36
|
-
return {
|
|
37
|
-
publicKey: bytesToBase64(publicKey),
|
|
38
|
-
privateKey: bytesToBase64(privateKey)
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Signs a message with an Ed25519 private key
|
|
43
|
-
*/
|
|
44
|
-
async signMessage(message, privateKeyBase64) {
|
|
45
|
-
const privateKey = base64ToBytes(privateKeyBase64);
|
|
46
|
-
const encoder = new TextEncoder();
|
|
47
|
-
const messageBytes = encoder.encode(message);
|
|
48
|
-
const signature = await ed25519.signAsync(messageBytes, privateKey);
|
|
49
|
-
return bytesToBase64(signature);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Claims a username
|
|
53
|
-
* Generates a new keypair if one is not provided
|
|
54
|
-
*/
|
|
55
|
-
async claimUsername(username, existingKeypair) {
|
|
56
|
-
// Generate or use existing keypair
|
|
57
|
-
const keypair = existingKeypair || await this.generateKeypair();
|
|
58
|
-
// Create signed message
|
|
59
|
-
const timestamp = Date.now();
|
|
60
|
-
const message = `claim:${username}:${timestamp}`;
|
|
61
|
-
const signature = await this.signMessage(message, keypair.privateKey);
|
|
62
|
-
// Send claim request
|
|
63
|
-
const response = await fetch(`${this.baseUrl}/usernames/claim`, {
|
|
64
|
-
method: 'POST',
|
|
65
|
-
headers: { 'Content-Type': 'application/json' },
|
|
66
|
-
body: JSON.stringify({
|
|
67
|
-
username,
|
|
68
|
-
publicKey: keypair.publicKey,
|
|
69
|
-
signature,
|
|
70
|
-
message
|
|
71
|
-
})
|
|
72
|
-
});
|
|
73
|
-
if (!response.ok) {
|
|
74
|
-
const error = await response.json();
|
|
75
|
-
throw new Error(error.error || 'Failed to claim username');
|
|
76
|
-
}
|
|
77
|
-
const data = await response.json();
|
|
78
|
-
return {
|
|
79
|
-
username: data.username,
|
|
80
|
-
publicKey: keypair.publicKey,
|
|
81
|
-
privateKey: keypair.privateKey,
|
|
82
|
-
claimedAt: data.claimedAt,
|
|
83
|
-
expiresAt: data.expiresAt
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Checks if a username is available
|
|
88
|
-
*/
|
|
89
|
-
async checkUsername(username) {
|
|
90
|
-
const response = await fetch(`${this.baseUrl}/usernames/${username}`);
|
|
91
|
-
if (!response.ok) {
|
|
92
|
-
throw new Error('Failed to check username');
|
|
93
|
-
}
|
|
94
|
-
const data = await response.json();
|
|
95
|
-
return {
|
|
96
|
-
username: data.username,
|
|
97
|
-
available: data.available,
|
|
98
|
-
claimedAt: data.claimedAt,
|
|
99
|
-
expiresAt: data.expiresAt,
|
|
100
|
-
publicKey: data.publicKey
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Helper: Save keypair to localStorage
|
|
105
|
-
* WARNING: This stores the private key in localStorage which is not the most secure
|
|
106
|
-
* For production use, consider using IndexedDB with encryption or hardware security modules
|
|
107
|
-
*/
|
|
108
|
-
saveKeypairToStorage(username, publicKey, privateKey) {
|
|
109
|
-
const data = { username, publicKey, privateKey, savedAt: Date.now() };
|
|
110
|
-
localStorage.setItem(`rondevu:keypair:${username}`, JSON.stringify(data));
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Helper: Load keypair from localStorage
|
|
114
|
-
*/
|
|
115
|
-
loadKeypairFromStorage(username) {
|
|
116
|
-
const stored = localStorage.getItem(`rondevu:keypair:${username}`);
|
|
117
|
-
if (!stored)
|
|
118
|
-
return null;
|
|
119
|
-
try {
|
|
120
|
-
const data = JSON.parse(stored);
|
|
121
|
-
return { publicKey: data.publicKey, privateKey: data.privateKey };
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Helper: Delete keypair from localStorage
|
|
129
|
-
*/
|
|
130
|
-
deleteKeypairFromStorage(username) {
|
|
131
|
-
localStorage.removeItem(`rondevu:keypair:${username}`);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Export keypair as JSON string (for backup)
|
|
135
|
-
*/
|
|
136
|
-
exportKeypair(publicKey, privateKey) {
|
|
137
|
-
return JSON.stringify({
|
|
138
|
-
publicKey,
|
|
139
|
-
privateKey,
|
|
140
|
-
exportedAt: Date.now()
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Import keypair from JSON string
|
|
145
|
-
*/
|
|
146
|
-
importKeypair(json) {
|
|
147
|
-
const data = JSON.parse(json);
|
|
148
|
-
if (!data.publicKey || !data.privateKey) {
|
|
149
|
-
throw new Error('Invalid keypair format');
|
|
150
|
-
}
|
|
151
|
-
return { publicKey: data.publicKey, privateKey: data.privateKey };
|
|
152
|
-
}
|
|
153
|
-
}
|