@xtr-dev/rondevu-client 0.9.2 → 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 -488
- package/dist/usernames.d.ts +0 -79
- package/dist/usernames.js +0 -153
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rondevu API Client - Single class for all API endpoints
|
|
3
|
+
*/
|
|
4
|
+
export interface Credentials {
|
|
5
|
+
peerId: string;
|
|
6
|
+
secret: string;
|
|
7
|
+
}
|
|
8
|
+
export interface Keypair {
|
|
9
|
+
publicKey: string;
|
|
10
|
+
privateKey: string;
|
|
11
|
+
}
|
|
12
|
+
export interface OfferRequest {
|
|
13
|
+
sdp: string;
|
|
14
|
+
topics?: string[];
|
|
15
|
+
ttl?: number;
|
|
16
|
+
secret?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface Offer {
|
|
19
|
+
id: string;
|
|
20
|
+
peerId: string;
|
|
21
|
+
sdp: string;
|
|
22
|
+
topics: string[];
|
|
23
|
+
ttl: number;
|
|
24
|
+
createdAt: number;
|
|
25
|
+
expiresAt: number;
|
|
26
|
+
answererPeerId?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface ServiceRequest {
|
|
29
|
+
username: string;
|
|
30
|
+
serviceFqn: string;
|
|
31
|
+
sdp: string;
|
|
32
|
+
ttl?: number;
|
|
33
|
+
isPublic?: boolean;
|
|
34
|
+
metadata?: Record<string, any>;
|
|
35
|
+
signature: string;
|
|
36
|
+
message: string;
|
|
37
|
+
}
|
|
38
|
+
export interface Service {
|
|
39
|
+
serviceId: string;
|
|
40
|
+
uuid: string;
|
|
41
|
+
offerId: string;
|
|
42
|
+
username: string;
|
|
43
|
+
serviceFqn: string;
|
|
44
|
+
isPublic: boolean;
|
|
45
|
+
metadata?: Record<string, any>;
|
|
46
|
+
createdAt: number;
|
|
47
|
+
expiresAt: number;
|
|
48
|
+
}
|
|
49
|
+
export interface IceCandidate {
|
|
50
|
+
candidate: RTCIceCandidateInit;
|
|
51
|
+
createdAt: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* RondevuAPI - Complete API client for Rondevu signaling server
|
|
55
|
+
*/
|
|
56
|
+
export declare class RondevuAPI {
|
|
57
|
+
private baseUrl;
|
|
58
|
+
private credentials?;
|
|
59
|
+
constructor(baseUrl: string, credentials?: Credentials | undefined);
|
|
60
|
+
/**
|
|
61
|
+
* Authentication header
|
|
62
|
+
*/
|
|
63
|
+
private getAuthHeader;
|
|
64
|
+
/**
|
|
65
|
+
* Generate an Ed25519 keypair for username claiming and service publishing
|
|
66
|
+
*/
|
|
67
|
+
static generateKeypair(): Promise<Keypair>;
|
|
68
|
+
/**
|
|
69
|
+
* Sign a message with an Ed25519 private key
|
|
70
|
+
*/
|
|
71
|
+
static signMessage(message: string, privateKeyBase64: string): Promise<string>;
|
|
72
|
+
/**
|
|
73
|
+
* Verify a signature
|
|
74
|
+
*/
|
|
75
|
+
static verifySignature(message: string, signatureBase64: string, publicKeyBase64: string): Promise<boolean>;
|
|
76
|
+
/**
|
|
77
|
+
* Register a new peer and get credentials
|
|
78
|
+
*/
|
|
79
|
+
register(): Promise<Credentials>;
|
|
80
|
+
/**
|
|
81
|
+
* Create one or more offers
|
|
82
|
+
*/
|
|
83
|
+
createOffers(offers: OfferRequest[]): Promise<Offer[]>;
|
|
84
|
+
/**
|
|
85
|
+
* Get offer by ID
|
|
86
|
+
*/
|
|
87
|
+
getOffer(offerId: string): Promise<Offer>;
|
|
88
|
+
/**
|
|
89
|
+
* Answer an offer
|
|
90
|
+
*/
|
|
91
|
+
answerOffer(offerId: string, sdp: string, secret?: string): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Get answer for an offer (offerer polls this)
|
|
94
|
+
*/
|
|
95
|
+
getAnswer(offerId: string): Promise<{
|
|
96
|
+
sdp: string;
|
|
97
|
+
} | null>;
|
|
98
|
+
/**
|
|
99
|
+
* Search offers by topic
|
|
100
|
+
*/
|
|
101
|
+
searchOffers(topic: string): Promise<Offer[]>;
|
|
102
|
+
/**
|
|
103
|
+
* Add ICE candidates to an offer
|
|
104
|
+
*/
|
|
105
|
+
addIceCandidates(offerId: string, candidates: RTCIceCandidateInit[]): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Get ICE candidates for an offer (with polling support)
|
|
108
|
+
*/
|
|
109
|
+
getIceCandidates(offerId: string, since?: number): Promise<IceCandidate[]>;
|
|
110
|
+
/**
|
|
111
|
+
* Publish a service
|
|
112
|
+
*/
|
|
113
|
+
publishService(service: ServiceRequest): Promise<Service>;
|
|
114
|
+
/**
|
|
115
|
+
* Get service by UUID
|
|
116
|
+
*/
|
|
117
|
+
getService(uuid: string): Promise<Service & {
|
|
118
|
+
offerId: string;
|
|
119
|
+
sdp: string;
|
|
120
|
+
}>;
|
|
121
|
+
/**
|
|
122
|
+
* Search services by username
|
|
123
|
+
*/
|
|
124
|
+
searchServicesByUsername(username: string): Promise<Service[]>;
|
|
125
|
+
/**
|
|
126
|
+
* Search services by FQN
|
|
127
|
+
*/
|
|
128
|
+
searchServicesByFqn(serviceFqn: string): Promise<Service[]>;
|
|
129
|
+
/**
|
|
130
|
+
* Search services by username AND FQN
|
|
131
|
+
*/
|
|
132
|
+
searchServices(username: string, serviceFqn: string): Promise<Service[]>;
|
|
133
|
+
/**
|
|
134
|
+
* Check if username is available
|
|
135
|
+
*/
|
|
136
|
+
checkUsername(username: string): Promise<{
|
|
137
|
+
available: boolean;
|
|
138
|
+
owner?: string;
|
|
139
|
+
}>;
|
|
140
|
+
/**
|
|
141
|
+
* Claim a username (requires Ed25519 signature)
|
|
142
|
+
*/
|
|
143
|
+
claimUsername(username: string, publicKey: string, signature: string, message: string): Promise<{
|
|
144
|
+
success: boolean;
|
|
145
|
+
username: string;
|
|
146
|
+
}>;
|
|
147
|
+
}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rondevu API Client - Single class for all API endpoints
|
|
3
|
+
*/
|
|
4
|
+
import * as ed25519 from '@noble/ed25519';
|
|
5
|
+
// Set SHA-512 hash function for ed25519 (required in @noble/ed25519 v3+)
|
|
6
|
+
ed25519.hashes.sha512Async = async (message) => {
|
|
7
|
+
return new Uint8Array(await crypto.subtle.digest('SHA-512', message));
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Helper: Convert Uint8Array to base64 string
|
|
11
|
+
*/
|
|
12
|
+
function bytesToBase64(bytes) {
|
|
13
|
+
const binString = Array.from(bytes, byte => String.fromCodePoint(byte)).join('');
|
|
14
|
+
return btoa(binString);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Helper: Convert base64 string to Uint8Array
|
|
18
|
+
*/
|
|
19
|
+
function base64ToBytes(base64) {
|
|
20
|
+
const binString = atob(base64);
|
|
21
|
+
return Uint8Array.from(binString, char => char.codePointAt(0));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* RondevuAPI - Complete API client for Rondevu signaling server
|
|
25
|
+
*/
|
|
26
|
+
export class RondevuAPI {
|
|
27
|
+
constructor(baseUrl, credentials) {
|
|
28
|
+
this.baseUrl = baseUrl;
|
|
29
|
+
this.credentials = credentials;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Authentication header
|
|
33
|
+
*/
|
|
34
|
+
getAuthHeader() {
|
|
35
|
+
if (!this.credentials) {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
Authorization: `Bearer ${this.credentials.peerId}:${this.credentials.secret}`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// ============================================
|
|
43
|
+
// Ed25519 Cryptography Helpers
|
|
44
|
+
// ============================================
|
|
45
|
+
/**
|
|
46
|
+
* Generate an Ed25519 keypair for username claiming and service publishing
|
|
47
|
+
*/
|
|
48
|
+
static async generateKeypair() {
|
|
49
|
+
const privateKey = ed25519.utils.randomSecretKey();
|
|
50
|
+
const publicKey = await ed25519.getPublicKeyAsync(privateKey);
|
|
51
|
+
return {
|
|
52
|
+
publicKey: bytesToBase64(publicKey),
|
|
53
|
+
privateKey: bytesToBase64(privateKey),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Sign a message with an Ed25519 private key
|
|
58
|
+
*/
|
|
59
|
+
static async signMessage(message, privateKeyBase64) {
|
|
60
|
+
const privateKey = base64ToBytes(privateKeyBase64);
|
|
61
|
+
const encoder = new TextEncoder();
|
|
62
|
+
const messageBytes = encoder.encode(message);
|
|
63
|
+
const signature = await ed25519.signAsync(messageBytes, privateKey);
|
|
64
|
+
return bytesToBase64(signature);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Verify a signature
|
|
68
|
+
*/
|
|
69
|
+
static async verifySignature(message, signatureBase64, publicKeyBase64) {
|
|
70
|
+
const publicKey = base64ToBytes(publicKeyBase64);
|
|
71
|
+
const signature = base64ToBytes(signatureBase64);
|
|
72
|
+
const encoder = new TextEncoder();
|
|
73
|
+
const messageBytes = encoder.encode(message);
|
|
74
|
+
return await ed25519.verifyAsync(signature, messageBytes, publicKey);
|
|
75
|
+
}
|
|
76
|
+
// ============================================
|
|
77
|
+
// Authentication
|
|
78
|
+
// ============================================
|
|
79
|
+
/**
|
|
80
|
+
* Register a new peer and get credentials
|
|
81
|
+
*/
|
|
82
|
+
async register() {
|
|
83
|
+
const response = await fetch(`${this.baseUrl}/register`, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: { 'Content-Type': 'application/json' },
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
89
|
+
throw new Error(`Registration failed: ${error.error || response.statusText}`);
|
|
90
|
+
}
|
|
91
|
+
return await response.json();
|
|
92
|
+
}
|
|
93
|
+
// ============================================
|
|
94
|
+
// Offers
|
|
95
|
+
// ============================================
|
|
96
|
+
/**
|
|
97
|
+
* Create one or more offers
|
|
98
|
+
*/
|
|
99
|
+
async createOffers(offers) {
|
|
100
|
+
const response = await fetch(`${this.baseUrl}/offers`, {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: {
|
|
103
|
+
'Content-Type': 'application/json',
|
|
104
|
+
...this.getAuthHeader(),
|
|
105
|
+
},
|
|
106
|
+
body: JSON.stringify({ offers }),
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
110
|
+
throw new Error(`Failed to create offers: ${error.error || response.statusText}`);
|
|
111
|
+
}
|
|
112
|
+
return await response.json();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get offer by ID
|
|
116
|
+
*/
|
|
117
|
+
async getOffer(offerId) {
|
|
118
|
+
const response = await fetch(`${this.baseUrl}/offers/${offerId}`, {
|
|
119
|
+
headers: this.getAuthHeader(),
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
123
|
+
throw new Error(`Failed to get offer: ${error.error || response.statusText}`);
|
|
124
|
+
}
|
|
125
|
+
return await response.json();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Answer an offer
|
|
129
|
+
*/
|
|
130
|
+
async answerOffer(offerId, sdp, secret) {
|
|
131
|
+
const response = await fetch(`${this.baseUrl}/offers/${offerId}/answer`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
...this.getAuthHeader(),
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify({ sdp, secret }),
|
|
138
|
+
});
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
141
|
+
throw new Error(`Failed to answer offer: ${error.error || response.statusText}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get answer for an offer (offerer polls this)
|
|
146
|
+
*/
|
|
147
|
+
async getAnswer(offerId) {
|
|
148
|
+
const response = await fetch(`${this.baseUrl}/offers/${offerId}/answer`, {
|
|
149
|
+
headers: this.getAuthHeader(),
|
|
150
|
+
});
|
|
151
|
+
if (response.status === 404) {
|
|
152
|
+
return null; // No answer yet
|
|
153
|
+
}
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
156
|
+
throw new Error(`Failed to get answer: ${error.error || response.statusText}`);
|
|
157
|
+
}
|
|
158
|
+
return await response.json();
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Search offers by topic
|
|
162
|
+
*/
|
|
163
|
+
async searchOffers(topic) {
|
|
164
|
+
const response = await fetch(`${this.baseUrl}/offers?topic=${encodeURIComponent(topic)}`, {
|
|
165
|
+
headers: this.getAuthHeader(),
|
|
166
|
+
});
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
169
|
+
throw new Error(`Failed to search offers: ${error.error || response.statusText}`);
|
|
170
|
+
}
|
|
171
|
+
return await response.json();
|
|
172
|
+
}
|
|
173
|
+
// ============================================
|
|
174
|
+
// ICE Candidates
|
|
175
|
+
// ============================================
|
|
176
|
+
/**
|
|
177
|
+
* Add ICE candidates to an offer
|
|
178
|
+
*/
|
|
179
|
+
async addIceCandidates(offerId, candidates) {
|
|
180
|
+
const response = await fetch(`${this.baseUrl}/offers/${offerId}/ice-candidates`, {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
headers: {
|
|
183
|
+
'Content-Type': 'application/json',
|
|
184
|
+
...this.getAuthHeader(),
|
|
185
|
+
},
|
|
186
|
+
body: JSON.stringify({ candidates }),
|
|
187
|
+
});
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
190
|
+
throw new Error(`Failed to add ICE candidates: ${error.error || response.statusText}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get ICE candidates for an offer (with polling support)
|
|
195
|
+
*/
|
|
196
|
+
async getIceCandidates(offerId, since = 0) {
|
|
197
|
+
const response = await fetch(`${this.baseUrl}/offers/${offerId}/ice-candidates?since=${since}`, { headers: this.getAuthHeader() });
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
200
|
+
throw new Error(`Failed to get ICE candidates: ${error.error || response.statusText}`);
|
|
201
|
+
}
|
|
202
|
+
return await response.json();
|
|
203
|
+
}
|
|
204
|
+
// ============================================
|
|
205
|
+
// Services
|
|
206
|
+
// ============================================
|
|
207
|
+
/**
|
|
208
|
+
* Publish a service
|
|
209
|
+
*/
|
|
210
|
+
async publishService(service) {
|
|
211
|
+
const response = await fetch(`${this.baseUrl}/services`, {
|
|
212
|
+
method: 'POST',
|
|
213
|
+
headers: {
|
|
214
|
+
'Content-Type': 'application/json',
|
|
215
|
+
...this.getAuthHeader(),
|
|
216
|
+
},
|
|
217
|
+
body: JSON.stringify(service),
|
|
218
|
+
});
|
|
219
|
+
if (!response.ok) {
|
|
220
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
221
|
+
throw new Error(`Failed to publish service: ${error.error || response.statusText}`);
|
|
222
|
+
}
|
|
223
|
+
return await response.json();
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get service by UUID
|
|
227
|
+
*/
|
|
228
|
+
async getService(uuid) {
|
|
229
|
+
const response = await fetch(`${this.baseUrl}/services/${uuid}`, {
|
|
230
|
+
headers: this.getAuthHeader(),
|
|
231
|
+
});
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
234
|
+
throw new Error(`Failed to get service: ${error.error || response.statusText}`);
|
|
235
|
+
}
|
|
236
|
+
return await response.json();
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Search services by username
|
|
240
|
+
*/
|
|
241
|
+
async searchServicesByUsername(username) {
|
|
242
|
+
const response = await fetch(`${this.baseUrl}/services?username=${encodeURIComponent(username)}`, { headers: this.getAuthHeader() });
|
|
243
|
+
if (!response.ok) {
|
|
244
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
245
|
+
throw new Error(`Failed to search services: ${error.error || response.statusText}`);
|
|
246
|
+
}
|
|
247
|
+
return await response.json();
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Search services by FQN
|
|
251
|
+
*/
|
|
252
|
+
async searchServicesByFqn(serviceFqn) {
|
|
253
|
+
const response = await fetch(`${this.baseUrl}/services?serviceFqn=${encodeURIComponent(serviceFqn)}`, { headers: this.getAuthHeader() });
|
|
254
|
+
if (!response.ok) {
|
|
255
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
256
|
+
throw new Error(`Failed to search services: ${error.error || response.statusText}`);
|
|
257
|
+
}
|
|
258
|
+
return await response.json();
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Search services by username AND FQN
|
|
262
|
+
*/
|
|
263
|
+
async searchServices(username, serviceFqn) {
|
|
264
|
+
const response = await fetch(`${this.baseUrl}/services?username=${encodeURIComponent(username)}&serviceFqn=${encodeURIComponent(serviceFqn)}`, { headers: this.getAuthHeader() });
|
|
265
|
+
if (!response.ok) {
|
|
266
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
267
|
+
throw new Error(`Failed to search services: ${error.error || response.statusText}`);
|
|
268
|
+
}
|
|
269
|
+
return await response.json();
|
|
270
|
+
}
|
|
271
|
+
// ============================================
|
|
272
|
+
// Usernames
|
|
273
|
+
// ============================================
|
|
274
|
+
/**
|
|
275
|
+
* Check if username is available
|
|
276
|
+
*/
|
|
277
|
+
async checkUsername(username) {
|
|
278
|
+
const response = await fetch(`${this.baseUrl}/usernames/${encodeURIComponent(username)}/check`);
|
|
279
|
+
if (!response.ok) {
|
|
280
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
281
|
+
throw new Error(`Failed to check username: ${error.error || response.statusText}`);
|
|
282
|
+
}
|
|
283
|
+
return await response.json();
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Claim a username (requires Ed25519 signature)
|
|
287
|
+
*/
|
|
288
|
+
async claimUsername(username, publicKey, signature, message) {
|
|
289
|
+
const response = await fetch(`${this.baseUrl}/usernames/${encodeURIComponent(username)}`, {
|
|
290
|
+
method: 'POST',
|
|
291
|
+
headers: {
|
|
292
|
+
'Content-Type': 'application/json',
|
|
293
|
+
...this.getAuthHeader(),
|
|
294
|
+
},
|
|
295
|
+
body: JSON.stringify({
|
|
296
|
+
publicKey,
|
|
297
|
+
signature,
|
|
298
|
+
message,
|
|
299
|
+
}),
|
|
300
|
+
});
|
|
301
|
+
if (!response.ok) {
|
|
302
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
303
|
+
throw new Error(`Failed to claim username: ${error.error || response.statusText}`);
|
|
304
|
+
}
|
|
305
|
+
return await response.json();
|
|
306
|
+
}
|
|
307
|
+
}
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binnable - A cleanup function that can be synchronous or asynchronous
|
|
3
|
+
*
|
|
4
|
+
* Used to unsubscribe from events, close connections, or perform other cleanup operations.
|
|
5
|
+
*/
|
|
6
|
+
export type Binnable = () => void | Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Create a cleanup function collector (garbage bin)
|
|
9
|
+
*
|
|
10
|
+
* Collects cleanup functions and provides a single `clean()` method to execute all of them.
|
|
11
|
+
* Useful for managing multiple cleanup operations in a single place.
|
|
12
|
+
*
|
|
13
|
+
* @returns A function that accepts cleanup functions and has a `clean()` method
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const bin = createBin();
|
|
18
|
+
*
|
|
19
|
+
* // Add cleanup functions
|
|
20
|
+
* bin(
|
|
21
|
+
* () => console.log('Cleanup 1'),
|
|
22
|
+
* () => connection.close(),
|
|
23
|
+
* () => clearInterval(timer)
|
|
24
|
+
* );
|
|
25
|
+
*
|
|
26
|
+
* // Later, clean everything
|
|
27
|
+
* bin.clean(); // Executes all cleanup functions
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare const createBin: () => ((...rubbish: Binnable[]) => number) & {
|
|
31
|
+
/**
|
|
32
|
+
* Execute all cleanup functions and clear the bin
|
|
33
|
+
*/
|
|
34
|
+
clean: () => void;
|
|
35
|
+
};
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a cleanup function collector (garbage bin)
|
|
3
|
+
*
|
|
4
|
+
* Collects cleanup functions and provides a single `clean()` method to execute all of them.
|
|
5
|
+
* Useful for managing multiple cleanup operations in a single place.
|
|
6
|
+
*
|
|
7
|
+
* @returns A function that accepts cleanup functions and has a `clean()` method
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const bin = createBin();
|
|
12
|
+
*
|
|
13
|
+
* // Add cleanup functions
|
|
14
|
+
* bin(
|
|
15
|
+
* () => console.log('Cleanup 1'),
|
|
16
|
+
* () => connection.close(),
|
|
17
|
+
* () => clearInterval(timer)
|
|
18
|
+
* );
|
|
19
|
+
*
|
|
20
|
+
* // Later, clean everything
|
|
21
|
+
* bin.clean(); // Executes all cleanup functions
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export const createBin = () => {
|
|
25
|
+
const bin = [];
|
|
26
|
+
return Object.assign((...rubbish) => bin.push(...rubbish), {
|
|
27
|
+
/**
|
|
28
|
+
* Execute all cleanup functions and clear the bin
|
|
29
|
+
*/
|
|
30
|
+
clean: () => {
|
|
31
|
+
bin.forEach(binnable => binnable());
|
|
32
|
+
bin.length = 0;
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { WebRTCRondevuConnection } from './connection.js';
|
|
2
|
+
import { Credentials } from './api.js';
|
|
3
|
+
import { ConnectionInterface } from './types.js';
|
|
4
|
+
export interface ConnectionManagerOptions {
|
|
5
|
+
apiUrl: string;
|
|
6
|
+
username: string;
|
|
7
|
+
credentials?: Credentials;
|
|
8
|
+
autoReconnect?: boolean;
|
|
9
|
+
reconnectDelay?: number;
|
|
10
|
+
maxReconnectAttempts?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface HostServiceOptions {
|
|
13
|
+
service: string;
|
|
14
|
+
ttl?: number;
|
|
15
|
+
onConnection?: (connection: ConnectionInterface) => void;
|
|
16
|
+
}
|
|
17
|
+
export interface ConnectToServiceOptions {
|
|
18
|
+
username: string;
|
|
19
|
+
service: string;
|
|
20
|
+
onConnection?: (connection: ConnectionInterface) => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* ConnectionManager - High-level connection management
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Host a service
|
|
27
|
+
* const manager = new ConnectionManager({
|
|
28
|
+
* apiUrl: 'https://api.ronde.vu',
|
|
29
|
+
* credentials: await api.register()
|
|
30
|
+
* })
|
|
31
|
+
*
|
|
32
|
+
* await manager.hostService({
|
|
33
|
+
* service: 'chat.app@1.0.0',
|
|
34
|
+
* onConnection: (conn) => {
|
|
35
|
+
* conn.events.on('message', msg => console.log('Received:', msg))
|
|
36
|
+
* }
|
|
37
|
+
* })
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // Connect to a service
|
|
41
|
+
* const connection = await manager.connectToService({
|
|
42
|
+
* username: 'alice',
|
|
43
|
+
* service: 'chat.app@1.0.0'
|
|
44
|
+
* })
|
|
45
|
+
*
|
|
46
|
+
* await connection.sendMessage('Hello!')
|
|
47
|
+
*/
|
|
48
|
+
export declare class ConnectionManager {
|
|
49
|
+
private readonly api;
|
|
50
|
+
private readonly username;
|
|
51
|
+
private readonly connections;
|
|
52
|
+
private readonly autoReconnect;
|
|
53
|
+
private readonly reconnectDelay;
|
|
54
|
+
private readonly maxReconnectAttempts;
|
|
55
|
+
private readonly bin;
|
|
56
|
+
constructor(options: ConnectionManagerOptions);
|
|
57
|
+
/**
|
|
58
|
+
* Host a service - Creates an offer and publishes it to the signaling server
|
|
59
|
+
*
|
|
60
|
+
* The service will automatically accept incoming connections and manage them.
|
|
61
|
+
* Each new connection triggers the onConnection callback.
|
|
62
|
+
*
|
|
63
|
+
* @param options - Service hosting options
|
|
64
|
+
* @returns Promise that resolves when the service is published
|
|
65
|
+
*/
|
|
66
|
+
hostService(options: HostServiceOptions): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Connect to a hosted service
|
|
69
|
+
*
|
|
70
|
+
* Searches for the service, retrieves the offer, and creates an answering connection.
|
|
71
|
+
*
|
|
72
|
+
* @param options - Connection options
|
|
73
|
+
* @returns The established connection
|
|
74
|
+
*/
|
|
75
|
+
connectToService(options: ConnectToServiceOptions): Promise<WebRTCRondevuConnection>;
|
|
76
|
+
/**
|
|
77
|
+
* Get a connection by ID
|
|
78
|
+
*/
|
|
79
|
+
getConnection(id: string): WebRTCRondevuConnection | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Get all managed connections
|
|
82
|
+
*/
|
|
83
|
+
getAllConnections(): WebRTCRondevuConnection[];
|
|
84
|
+
/**
|
|
85
|
+
* Remove a connection
|
|
86
|
+
*/
|
|
87
|
+
private removeConnection;
|
|
88
|
+
/**
|
|
89
|
+
* Schedule reconnection for a failed connection
|
|
90
|
+
*/
|
|
91
|
+
private scheduleReconnect;
|
|
92
|
+
/**
|
|
93
|
+
* Attempt to reconnect a failed connection
|
|
94
|
+
*/
|
|
95
|
+
private attemptReconnect;
|
|
96
|
+
/**
|
|
97
|
+
* Create a temporary signaler (used during initial offer creation)
|
|
98
|
+
*/
|
|
99
|
+
private createTempSignaler;
|
|
100
|
+
/**
|
|
101
|
+
* Clean up all connections and resources
|
|
102
|
+
*/
|
|
103
|
+
destroy(): void;
|
|
104
|
+
}
|