@xtr-dev/rondevu-client 0.13.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -381
- package/dist/api.d.ts +67 -116
- package/dist/api.js +201 -244
- package/dist/crypto-adapter.d.ts +37 -0
- package/dist/crypto-adapter.js +4 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +4 -1
- package/dist/node-crypto-adapter.d.ts +35 -0
- package/dist/node-crypto-adapter.js +80 -0
- package/dist/rondevu-signaler.d.ts +10 -7
- package/dist/rondevu-signaler.js +96 -64
- package/dist/rondevu.d.ts +199 -37
- package/dist/rondevu.js +513 -103
- package/dist/rpc-batcher.d.ts +61 -0
- package/dist/rpc-batcher.js +111 -0
- package/dist/web-crypto-adapter.d.ts +16 -0
- package/dist/web-crypto-adapter.js +52 -0
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -1,322 +1,279 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Rondevu API Client -
|
|
2
|
+
* Rondevu API Client - RPC interface
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
ed25519.hashes.sha512Async = async (message) => {
|
|
7
|
-
return new Uint8Array(await crypto.subtle.digest('SHA-512', message));
|
|
8
|
-
};
|
|
4
|
+
import { WebCryptoAdapter } from './web-crypto-adapter.js';
|
|
5
|
+
import { RpcBatcher } from './rpc-batcher.js';
|
|
9
6
|
/**
|
|
10
|
-
*
|
|
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
|
|
7
|
+
* RondevuAPI - RPC-based API client for Rondevu signaling server
|
|
25
8
|
*/
|
|
26
9
|
export class RondevuAPI {
|
|
27
|
-
constructor(baseUrl,
|
|
10
|
+
constructor(baseUrl, username, keypair, cryptoAdapter, batcherOptions) {
|
|
28
11
|
this.baseUrl = baseUrl;
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Authentication header
|
|
39
|
-
*/
|
|
40
|
-
getAuthHeader() {
|
|
41
|
-
if (!this.credentials) {
|
|
42
|
-
return {};
|
|
12
|
+
this.username = username;
|
|
13
|
+
this.keypair = keypair;
|
|
14
|
+
this.batcher = null;
|
|
15
|
+
// Use WebCryptoAdapter by default (browser environment)
|
|
16
|
+
this.crypto = cryptoAdapter || new WebCryptoAdapter();
|
|
17
|
+
// Create batcher if not explicitly disabled
|
|
18
|
+
if (batcherOptions !== false) {
|
|
19
|
+
this.batcher = new RpcBatcher((requests) => this.rpcBatchDirect(requests), batcherOptions);
|
|
43
20
|
}
|
|
44
|
-
return {
|
|
45
|
-
Authorization: `Bearer ${this.credentials.peerId}:${this.credentials.secret}`,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
// ============================================
|
|
49
|
-
// Ed25519 Cryptography Helpers
|
|
50
|
-
// ============================================
|
|
51
|
-
/**
|
|
52
|
-
* Generate an Ed25519 keypair for username claiming and service publishing
|
|
53
|
-
*/
|
|
54
|
-
static async generateKeypair() {
|
|
55
|
-
const privateKey = ed25519.utils.randomSecretKey();
|
|
56
|
-
const publicKey = await ed25519.getPublicKeyAsync(privateKey);
|
|
57
|
-
return {
|
|
58
|
-
publicKey: bytesToBase64(publicKey),
|
|
59
|
-
privateKey: bytesToBase64(privateKey),
|
|
60
|
-
};
|
|
61
21
|
}
|
|
62
22
|
/**
|
|
63
|
-
*
|
|
23
|
+
* Generate authentication parameters for RPC calls
|
|
64
24
|
*/
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
25
|
+
async generateAuth(method, params = '') {
|
|
26
|
+
const timestamp = Date.now();
|
|
27
|
+
const message = params
|
|
28
|
+
? `${method}:${this.username}:${params}:${timestamp}`
|
|
29
|
+
: `${method}:${this.username}:${timestamp}`;
|
|
30
|
+
const signature = await this.crypto.signMessage(message, this.keypair.privateKey);
|
|
31
|
+
return { message, signature };
|
|
71
32
|
}
|
|
72
33
|
/**
|
|
73
|
-
*
|
|
34
|
+
* Execute RPC call with optional batching
|
|
74
35
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
36
|
+
async rpc(request) {
|
|
37
|
+
// Use batcher if enabled
|
|
38
|
+
if (this.batcher) {
|
|
39
|
+
return await this.batcher.add(request);
|
|
40
|
+
}
|
|
41
|
+
// Direct call without batching
|
|
42
|
+
return await this.rpcDirect(request);
|
|
81
43
|
}
|
|
82
|
-
// ============================================
|
|
83
|
-
// Authentication
|
|
84
|
-
// ============================================
|
|
85
44
|
/**
|
|
86
|
-
*
|
|
45
|
+
* Execute single RPC call directly (bypasses batcher)
|
|
87
46
|
*/
|
|
88
|
-
async
|
|
89
|
-
const response = await fetch(`${this.baseUrl}/
|
|
47
|
+
async rpcDirect(request) {
|
|
48
|
+
const response = await fetch(`${this.baseUrl}/rpc`, {
|
|
90
49
|
method: 'POST',
|
|
91
50
|
headers: { 'Content-Type': 'application/json' },
|
|
51
|
+
body: JSON.stringify(request),
|
|
92
52
|
});
|
|
93
53
|
if (!response.ok) {
|
|
94
|
-
|
|
95
|
-
|
|
54
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
55
|
+
}
|
|
56
|
+
const result = await response.json();
|
|
57
|
+
if (!result.success) {
|
|
58
|
+
throw new Error(result.error || 'RPC call failed');
|
|
96
59
|
}
|
|
97
|
-
return
|
|
60
|
+
return result.result;
|
|
98
61
|
}
|
|
99
|
-
// ============================================
|
|
100
|
-
// Offers
|
|
101
|
-
// ============================================
|
|
102
62
|
/**
|
|
103
|
-
*
|
|
63
|
+
* Execute batch RPC calls directly (bypasses batcher)
|
|
104
64
|
*/
|
|
105
|
-
async
|
|
106
|
-
const response = await fetch(`${this.baseUrl}/
|
|
65
|
+
async rpcBatchDirect(requests) {
|
|
66
|
+
const response = await fetch(`${this.baseUrl}/rpc`, {
|
|
107
67
|
method: 'POST',
|
|
108
|
-
headers: {
|
|
109
|
-
|
|
110
|
-
...this.getAuthHeader(),
|
|
111
|
-
},
|
|
112
|
-
body: JSON.stringify({ offers }),
|
|
68
|
+
headers: { 'Content-Type': 'application/json' },
|
|
69
|
+
body: JSON.stringify(requests),
|
|
113
70
|
});
|
|
114
71
|
if (!response.ok) {
|
|
115
|
-
|
|
116
|
-
throw new Error(`Failed to create offers: ${error.error || response.statusText}`);
|
|
72
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
117
73
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
async getOffer(offerId) {
|
|
124
|
-
const response = await fetch(`${this.baseUrl}/offers/${offerId}`, {
|
|
125
|
-
headers: this.getAuthHeader(),
|
|
126
|
-
});
|
|
127
|
-
if (!response.ok) {
|
|
128
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
129
|
-
throw new Error(`Failed to get offer: ${error.error || response.statusText}`);
|
|
74
|
+
const results = await response.json();
|
|
75
|
+
// Validate response is an array
|
|
76
|
+
if (!Array.isArray(results)) {
|
|
77
|
+
console.error('Invalid RPC batch response:', results);
|
|
78
|
+
throw new Error('Server returned invalid batch response (not an array)');
|
|
130
79
|
}
|
|
131
|
-
|
|
80
|
+
// Check response length matches request length
|
|
81
|
+
if (results.length !== requests.length) {
|
|
82
|
+
console.error(`Response length mismatch: expected ${requests.length}, got ${results.length}`);
|
|
83
|
+
}
|
|
84
|
+
return results.map((result, i) => {
|
|
85
|
+
if (!result || typeof result !== 'object') {
|
|
86
|
+
throw new Error(`Invalid response at index ${i}`);
|
|
87
|
+
}
|
|
88
|
+
if (!result.success) {
|
|
89
|
+
throw new Error(result.error || `RPC call ${i} failed`);
|
|
90
|
+
}
|
|
91
|
+
return result.result;
|
|
92
|
+
});
|
|
132
93
|
}
|
|
94
|
+
// ============================================
|
|
95
|
+
// Ed25519 Cryptography Helpers
|
|
96
|
+
// ============================================
|
|
133
97
|
/**
|
|
134
|
-
*
|
|
98
|
+
* Generate an Ed25519 keypair for username claiming and service publishing
|
|
99
|
+
* @param cryptoAdapter - Optional crypto adapter (defaults to WebCryptoAdapter)
|
|
135
100
|
*/
|
|
136
|
-
async
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
headers: {
|
|
140
|
-
'Content-Type': 'application/json',
|
|
141
|
-
...this.getAuthHeader(),
|
|
142
|
-
},
|
|
143
|
-
body: JSON.stringify({ sdp }),
|
|
144
|
-
});
|
|
145
|
-
if (!response.ok) {
|
|
146
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
147
|
-
throw new Error(`Failed to answer offer: ${error.error || response.statusText}`);
|
|
148
|
-
}
|
|
149
|
-
return await response.json();
|
|
101
|
+
static async generateKeypair(cryptoAdapter) {
|
|
102
|
+
const adapter = cryptoAdapter || new WebCryptoAdapter();
|
|
103
|
+
return await adapter.generateKeypair();
|
|
150
104
|
}
|
|
151
105
|
/**
|
|
152
|
-
*
|
|
106
|
+
* Sign a message with an Ed25519 private key
|
|
107
|
+
* @param cryptoAdapter - Optional crypto adapter (defaults to WebCryptoAdapter)
|
|
153
108
|
*/
|
|
154
|
-
async
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
});
|
|
158
|
-
if (!response.ok) {
|
|
159
|
-
// 404 means not yet answered
|
|
160
|
-
if (response.status === 404) {
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
164
|
-
throw new Error(`Failed to get answer: ${error.error || response.statusText}`);
|
|
165
|
-
}
|
|
166
|
-
return await response.json();
|
|
109
|
+
static async signMessage(message, privateKeyBase64, cryptoAdapter) {
|
|
110
|
+
const adapter = cryptoAdapter || new WebCryptoAdapter();
|
|
111
|
+
return await adapter.signMessage(message, privateKeyBase64);
|
|
167
112
|
}
|
|
168
113
|
/**
|
|
169
|
-
*
|
|
114
|
+
* Verify an Ed25519 signature
|
|
115
|
+
* @param cryptoAdapter - Optional crypto adapter (defaults to WebCryptoAdapter)
|
|
170
116
|
*/
|
|
171
|
-
async
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
});
|
|
175
|
-
if (!response.ok) {
|
|
176
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
177
|
-
throw new Error(`Failed to search offers: ${error.error || response.statusText}`);
|
|
178
|
-
}
|
|
179
|
-
return await response.json();
|
|
117
|
+
static async verifySignature(message, signatureBase64, publicKeyBase64, cryptoAdapter) {
|
|
118
|
+
const adapter = cryptoAdapter || new WebCryptoAdapter();
|
|
119
|
+
return await adapter.verifySignature(message, signatureBase64, publicKeyBase64);
|
|
180
120
|
}
|
|
181
121
|
// ============================================
|
|
182
|
-
//
|
|
122
|
+
// Username Management
|
|
183
123
|
// ============================================
|
|
184
124
|
/**
|
|
185
|
-
*
|
|
125
|
+
* Check if a username is available
|
|
186
126
|
*/
|
|
187
|
-
async
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
},
|
|
194
|
-
body: JSON.stringify({ candidates }),
|
|
127
|
+
async isUsernameAvailable(username) {
|
|
128
|
+
const auth = await this.generateAuth('getUser', username);
|
|
129
|
+
const result = await this.rpc({
|
|
130
|
+
method: 'getUser',
|
|
131
|
+
message: auth.message,
|
|
132
|
+
signature: auth.signature,
|
|
133
|
+
params: { username },
|
|
195
134
|
});
|
|
196
|
-
|
|
197
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
198
|
-
throw new Error(`Failed to add ICE candidates: ${error.error || response.statusText}`);
|
|
199
|
-
}
|
|
200
|
-
return await response.json();
|
|
135
|
+
return result.available;
|
|
201
136
|
}
|
|
202
137
|
/**
|
|
203
|
-
*
|
|
138
|
+
* Check if current username is claimed
|
|
204
139
|
*/
|
|
205
|
-
async
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
candidates: data.candidates || [],
|
|
216
|
-
offerId: data.offerId
|
|
217
|
-
};
|
|
140
|
+
async isUsernameClaimed() {
|
|
141
|
+
const auth = await this.generateAuth('getUser', this.username);
|
|
142
|
+
const result = await this.rpc({
|
|
143
|
+
method: 'getUser',
|
|
144
|
+
message: auth.message,
|
|
145
|
+
signature: auth.signature,
|
|
146
|
+
params: { username: this.username },
|
|
147
|
+
});
|
|
148
|
+
return !result.available;
|
|
218
149
|
}
|
|
219
150
|
// ============================================
|
|
220
|
-
//
|
|
151
|
+
// Service Management
|
|
221
152
|
// ============================================
|
|
222
153
|
/**
|
|
223
154
|
* Publish a service
|
|
224
|
-
* Service FQN must include username: service:version@username
|
|
225
155
|
*/
|
|
226
156
|
async publishService(service) {
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
157
|
+
const auth = await this.generateAuth('publishService', service.serviceFqn);
|
|
158
|
+
return await this.rpc({
|
|
159
|
+
method: 'publishService',
|
|
160
|
+
message: auth.message,
|
|
161
|
+
signature: auth.signature,
|
|
162
|
+
publicKey: this.keypair.publicKey,
|
|
163
|
+
params: {
|
|
164
|
+
serviceFqn: service.serviceFqn,
|
|
165
|
+
offers: service.offers,
|
|
166
|
+
ttl: service.ttl,
|
|
232
167
|
},
|
|
233
|
-
body: JSON.stringify(service),
|
|
234
168
|
});
|
|
235
|
-
if (!response.ok) {
|
|
236
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
237
|
-
throw new Error(`Failed to publish service: ${error.error || response.statusText}`);
|
|
238
|
-
}
|
|
239
|
-
return await response.json();
|
|
240
169
|
}
|
|
241
170
|
/**
|
|
242
|
-
* Get service by FQN (
|
|
243
|
-
* Example: chat:1.0.0@alice
|
|
171
|
+
* Get service by FQN (direct lookup, random, or paginated)
|
|
244
172
|
*/
|
|
245
|
-
async getService(serviceFqn) {
|
|
246
|
-
const
|
|
247
|
-
|
|
173
|
+
async getService(serviceFqn, options) {
|
|
174
|
+
const auth = await this.generateAuth('getService', serviceFqn);
|
|
175
|
+
return await this.rpc({
|
|
176
|
+
method: 'getService',
|
|
177
|
+
message: auth.message,
|
|
178
|
+
signature: auth.signature,
|
|
179
|
+
publicKey: this.keypair.publicKey,
|
|
180
|
+
params: {
|
|
181
|
+
serviceFqn,
|
|
182
|
+
...options,
|
|
183
|
+
},
|
|
248
184
|
});
|
|
249
|
-
if (!response.ok) {
|
|
250
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
251
|
-
throw new Error(`Failed to get service: ${error.error || response.statusText}`);
|
|
252
|
-
}
|
|
253
|
-
return await response.json();
|
|
254
185
|
}
|
|
255
186
|
/**
|
|
256
|
-
*
|
|
257
|
-
* Example: chat:1.0.0 (without @username)
|
|
187
|
+
* Delete a service
|
|
258
188
|
*/
|
|
259
|
-
async
|
|
260
|
-
const
|
|
261
|
-
|
|
189
|
+
async deleteService(serviceFqn) {
|
|
190
|
+
const auth = await this.generateAuth('deleteService', serviceFqn);
|
|
191
|
+
await this.rpc({
|
|
192
|
+
method: 'deleteService',
|
|
193
|
+
message: auth.message,
|
|
194
|
+
signature: auth.signature,
|
|
195
|
+
publicKey: this.keypair.publicKey,
|
|
196
|
+
params: { serviceFqn },
|
|
262
197
|
});
|
|
263
|
-
if (!response.ok) {
|
|
264
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
265
|
-
throw new Error(`Failed to discover service: ${error.error || response.statusText}`);
|
|
266
|
-
}
|
|
267
|
-
return await response.json();
|
|
268
198
|
}
|
|
199
|
+
// ============================================
|
|
200
|
+
// WebRTC Signaling
|
|
201
|
+
// ============================================
|
|
269
202
|
/**
|
|
270
|
-
*
|
|
271
|
-
* Example: chat:1.0.0 (without @username)
|
|
203
|
+
* Answer an offer
|
|
272
204
|
*/
|
|
273
|
-
async
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
205
|
+
async answerOffer(serviceFqn, offerId, sdp) {
|
|
206
|
+
const auth = await this.generateAuth('answerOffer', offerId);
|
|
207
|
+
await this.rpc({
|
|
208
|
+
method: 'answerOffer',
|
|
209
|
+
message: auth.message,
|
|
210
|
+
signature: auth.signature,
|
|
211
|
+
publicKey: this.keypair.publicKey,
|
|
212
|
+
params: { serviceFqn, offerId, sdp },
|
|
279
213
|
});
|
|
280
|
-
if (!response.ok) {
|
|
281
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
282
|
-
throw new Error(`Failed to discover services: ${error.error || response.statusText}`);
|
|
283
|
-
}
|
|
284
|
-
return await response.json();
|
|
285
214
|
}
|
|
286
|
-
// ============================================
|
|
287
|
-
// Usernames
|
|
288
|
-
// ============================================
|
|
289
215
|
/**
|
|
290
|
-
*
|
|
216
|
+
* Get answer for a specific offer (offerer polls this)
|
|
291
217
|
*/
|
|
292
|
-
async
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
218
|
+
async getOfferAnswer(serviceFqn, offerId) {
|
|
219
|
+
try {
|
|
220
|
+
const auth = await this.generateAuth('getOfferAnswer', offerId);
|
|
221
|
+
return await this.rpc({
|
|
222
|
+
method: 'getOfferAnswer',
|
|
223
|
+
message: auth.message,
|
|
224
|
+
signature: auth.signature,
|
|
225
|
+
publicKey: this.keypair.publicKey,
|
|
226
|
+
params: { serviceFqn, offerId },
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
if (err.message.includes('not yet answered')) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
throw err;
|
|
297
234
|
}
|
|
298
|
-
return await response.json();
|
|
299
235
|
}
|
|
300
236
|
/**
|
|
301
|
-
*
|
|
237
|
+
* Combined polling for answers and ICE candidates
|
|
302
238
|
*/
|
|
303
|
-
async
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
publicKey,
|
|
312
|
-
signature,
|
|
313
|
-
message,
|
|
314
|
-
}),
|
|
239
|
+
async poll(since) {
|
|
240
|
+
const auth = await this.generateAuth('poll');
|
|
241
|
+
return await this.rpc({
|
|
242
|
+
method: 'poll',
|
|
243
|
+
message: auth.message,
|
|
244
|
+
signature: auth.signature,
|
|
245
|
+
publicKey: this.keypair.publicKey,
|
|
246
|
+
params: { since },
|
|
315
247
|
});
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Add ICE candidates to a specific offer
|
|
251
|
+
*/
|
|
252
|
+
async addOfferIceCandidates(serviceFqn, offerId, candidates) {
|
|
253
|
+
const auth = await this.generateAuth('addIceCandidates', offerId);
|
|
254
|
+
return await this.rpc({
|
|
255
|
+
method: 'addIceCandidates',
|
|
256
|
+
message: auth.message,
|
|
257
|
+
signature: auth.signature,
|
|
258
|
+
publicKey: this.keypair.publicKey,
|
|
259
|
+
params: { serviceFqn, offerId, candidates },
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Get ICE candidates for a specific offer
|
|
264
|
+
*/
|
|
265
|
+
async getOfferIceCandidates(serviceFqn, offerId, since = 0) {
|
|
266
|
+
const auth = await this.generateAuth('getIceCandidates', `${offerId}:${since}`);
|
|
267
|
+
const result = await this.rpc({
|
|
268
|
+
method: 'getIceCandidates',
|
|
269
|
+
message: auth.message,
|
|
270
|
+
signature: auth.signature,
|
|
271
|
+
publicKey: this.keypair.publicKey,
|
|
272
|
+
params: { serviceFqn, offerId, since },
|
|
273
|
+
});
|
|
274
|
+
return {
|
|
275
|
+
candidates: result.candidates || [],
|
|
276
|
+
offerId: result.offerId,
|
|
277
|
+
};
|
|
321
278
|
}
|
|
322
279
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto adapter interface for platform-independent cryptographic operations
|
|
3
|
+
*/
|
|
4
|
+
export interface Keypair {
|
|
5
|
+
publicKey: string;
|
|
6
|
+
privateKey: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Platform-independent crypto adapter interface
|
|
10
|
+
* Implementations provide platform-specific crypto operations
|
|
11
|
+
*/
|
|
12
|
+
export interface CryptoAdapter {
|
|
13
|
+
/**
|
|
14
|
+
* Generate an Ed25519 keypair
|
|
15
|
+
*/
|
|
16
|
+
generateKeypair(): Promise<Keypair>;
|
|
17
|
+
/**
|
|
18
|
+
* Sign a message with an Ed25519 private key
|
|
19
|
+
*/
|
|
20
|
+
signMessage(message: string, privateKeyBase64: string): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Verify an Ed25519 signature
|
|
23
|
+
*/
|
|
24
|
+
verifySignature(message: string, signatureBase64: string, publicKeyBase64: string): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Convert Uint8Array to base64 string
|
|
27
|
+
*/
|
|
28
|
+
bytesToBase64(bytes: Uint8Array): string;
|
|
29
|
+
/**
|
|
30
|
+
* Convert base64 string to Uint8Array
|
|
31
|
+
*/
|
|
32
|
+
base64ToBytes(base64: string): Uint8Array;
|
|
33
|
+
/**
|
|
34
|
+
* Generate random bytes
|
|
35
|
+
*/
|
|
36
|
+
randomBytes(length: number): Uint8Array;
|
|
37
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { Rondevu } from './rondevu.js';
|
|
6
6
|
export { RondevuAPI } from './api.js';
|
|
7
|
-
export {
|
|
7
|
+
export { RpcBatcher } from './rpc-batcher.js';
|
|
8
|
+
export { WebCryptoAdapter } from './web-crypto-adapter.js';
|
|
9
|
+
export { NodeCryptoAdapter } from './node-crypto-adapter.js';
|
|
8
10
|
export type { Signaler, Binnable, } from './types.js';
|
|
9
|
-
export type {
|
|
10
|
-
export type { RondevuOptions, PublishServiceOptions } from './rondevu.js';
|
|
11
|
-
export type {
|
|
11
|
+
export type { Keypair, OfferRequest, ServiceRequest, Service, ServiceOffer, IceCandidate, } from './api.js';
|
|
12
|
+
export type { RondevuOptions, PublishServiceOptions, ConnectToServiceOptions, ConnectionContext, OfferContext, OfferFactory } from './rondevu.js';
|
|
13
|
+
export type { CryptoAdapter } from './crypto-adapter.js';
|
package/dist/index.js
CHANGED
|
@@ -4,4 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { Rondevu } from './rondevu.js';
|
|
6
6
|
export { RondevuAPI } from './api.js';
|
|
7
|
-
export {
|
|
7
|
+
export { RpcBatcher } from './rpc-batcher.js';
|
|
8
|
+
// Export crypto adapters
|
|
9
|
+
export { WebCryptoAdapter } from './web-crypto-adapter.js';
|
|
10
|
+
export { NodeCryptoAdapter } from './node-crypto-adapter.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js Crypto adapter for Node.js environments
|
|
3
|
+
* Requires Node.js 19+ or Node.js 18 with --experimental-global-webcrypto flag
|
|
4
|
+
*/
|
|
5
|
+
import { CryptoAdapter, Keypair } from './crypto-adapter.js';
|
|
6
|
+
/**
|
|
7
|
+
* Node.js Crypto implementation using Node.js built-in APIs
|
|
8
|
+
* Uses Buffer for base64 encoding and crypto.randomBytes for random generation
|
|
9
|
+
*
|
|
10
|
+
* Requirements:
|
|
11
|
+
* - Node.js 19+ (crypto.subtle available globally)
|
|
12
|
+
* - OR Node.js 18 with --experimental-global-webcrypto flag
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { RondevuAPI } from '@xtr-dev/rondevu-client'
|
|
17
|
+
* import { NodeCryptoAdapter } from '@xtr-dev/rondevu-client/node'
|
|
18
|
+
*
|
|
19
|
+
* const api = new RondevuAPI(
|
|
20
|
+
* 'https://signal.example.com',
|
|
21
|
+
* 'alice',
|
|
22
|
+
* keypair,
|
|
23
|
+
* new NodeCryptoAdapter()
|
|
24
|
+
* )
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare class NodeCryptoAdapter implements CryptoAdapter {
|
|
28
|
+
constructor();
|
|
29
|
+
generateKeypair(): Promise<Keypair>;
|
|
30
|
+
signMessage(message: string, privateKeyBase64: string): Promise<string>;
|
|
31
|
+
verifySignature(message: string, signatureBase64: string, publicKeyBase64: string): Promise<boolean>;
|
|
32
|
+
bytesToBase64(bytes: Uint8Array): string;
|
|
33
|
+
base64ToBytes(base64: string): Uint8Array;
|
|
34
|
+
randomBytes(length: number): Uint8Array;
|
|
35
|
+
}
|