@xtr-dev/rondevu-client 0.12.4 → 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 +75 -96
- package/dist/api.js +202 -243
- package/dist/crypto-adapter.d.ts +37 -0
- package/dist/crypto-adapter.js +4 -0
- package/dist/index.d.ts +8 -15
- package/dist/index.js +5 -8
- package/dist/node-crypto-adapter.d.ts +35 -0
- package/dist/node-crypto-adapter.js +80 -0
- package/dist/rondevu-signaler.d.ts +14 -12
- package/dist/rondevu-signaler.js +111 -95
- package/dist/rondevu.d.ts +329 -0
- package/dist/rondevu.js +648 -0
- package/dist/rpc-batcher.d.ts +61 -0
- package/dist/rpc-batcher.js +111 -0
- package/dist/types.d.ts +8 -21
- package/dist/types.js +4 -6
- package/dist/web-crypto-adapter.d.ts +16 -0
- package/dist/web-crypto-adapter.js +52 -0
- package/package.json +1 -1
- package/dist/bin.d.ts +0 -35
- package/dist/bin.js +0 -35
- package/dist/connection-manager.d.ts +0 -104
- package/dist/connection-manager.js +0 -324
- package/dist/connection.d.ts +0 -112
- package/dist/connection.js +0 -194
- package/dist/durable-connection.d.ts +0 -120
- package/dist/durable-connection.js +0 -244
- package/dist/event-bus.d.ts +0 -52
- package/dist/event-bus.js +0 -84
- package/dist/noop-signaler.d.ts +0 -14
- package/dist/noop-signaler.js +0 -27
- package/dist/quick-start.d.ts +0 -29
- package/dist/quick-start.js +0 -44
- package/dist/rondevu-context.d.ts +0 -10
- package/dist/rondevu-context.js +0 -20
- package/dist/rondevu-service.d.ts +0 -87
- package/dist/rondevu-service.js +0 -170
- package/dist/service-client.d.ts +0 -77
- package/dist/service-client.js +0 -158
- package/dist/service-host.d.ts +0 -67
- package/dist/service-host.js +0 -120
- package/dist/signaler.d.ts +0 -25
- package/dist/signaler.js +0 -89
- package/dist/webrtc-context.d.ts +0 -5
- package/dist/webrtc-context.js +0 -35
package/dist/api.js
CHANGED
|
@@ -1,320 +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
21
|
}
|
|
48
|
-
// ============================================
|
|
49
|
-
// Ed25519 Cryptography Helpers
|
|
50
|
-
// ============================================
|
|
51
22
|
/**
|
|
52
|
-
* Generate
|
|
23
|
+
* Generate authentication parameters for RPC calls
|
|
53
24
|
*/
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
};
|
|
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 };
|
|
61
32
|
}
|
|
62
33
|
/**
|
|
63
|
-
*
|
|
34
|
+
* Execute RPC call with optional batching
|
|
64
35
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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);
|
|
71
43
|
}
|
|
72
44
|
/**
|
|
73
|
-
*
|
|
45
|
+
* Execute single RPC call directly (bypasses batcher)
|
|
74
46
|
*/
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
const signature = base64ToBytes(signatureBase64);
|
|
78
|
-
const encoder = new TextEncoder();
|
|
79
|
-
const messageBytes = encoder.encode(message);
|
|
80
|
-
return await ed25519.verifyAsync(signature, messageBytes, publicKey);
|
|
81
|
-
}
|
|
82
|
-
// ============================================
|
|
83
|
-
// Authentication
|
|
84
|
-
// ============================================
|
|
85
|
-
/**
|
|
86
|
-
* Register a new peer and get credentials
|
|
87
|
-
*/
|
|
88
|
-
async register() {
|
|
89
|
-
const response = await fetch(`${this.baseUrl}/register`, {
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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)');
|
|
79
|
+
}
|
|
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}`);
|
|
130
83
|
}
|
|
131
|
-
return
|
|
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 service: ${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
|
-
const data = await response.json();
|
|
167
|
-
return { sdp: data.sdp, offerId: data.offerId };
|
|
109
|
+
static async signMessage(message, privateKeyBase64, cryptoAdapter) {
|
|
110
|
+
const adapter = cryptoAdapter || new WebCryptoAdapter();
|
|
111
|
+
return await adapter.signMessage(message, privateKeyBase64);
|
|
168
112
|
}
|
|
169
113
|
/**
|
|
170
|
-
*
|
|
114
|
+
* Verify an Ed25519 signature
|
|
115
|
+
* @param cryptoAdapter - Optional crypto adapter (defaults to WebCryptoAdapter)
|
|
171
116
|
*/
|
|
172
|
-
async
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
});
|
|
176
|
-
if (!response.ok) {
|
|
177
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
178
|
-
throw new Error(`Failed to search offers: ${error.error || response.statusText}`);
|
|
179
|
-
}
|
|
180
|
-
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);
|
|
181
120
|
}
|
|
182
121
|
// ============================================
|
|
183
|
-
//
|
|
122
|
+
// Username Management
|
|
184
123
|
// ============================================
|
|
185
124
|
/**
|
|
186
|
-
*
|
|
125
|
+
* Check if a username is available
|
|
187
126
|
*/
|
|
188
|
-
async
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
},
|
|
195
|
-
body: JSON.stringify({ candidates, offerId }),
|
|
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 },
|
|
196
134
|
});
|
|
197
|
-
|
|
198
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
199
|
-
throw new Error(`Failed to add ICE candidates: ${error.error || response.statusText}`);
|
|
200
|
-
}
|
|
201
|
-
return await response.json();
|
|
135
|
+
return result.available;
|
|
202
136
|
}
|
|
203
137
|
/**
|
|
204
|
-
*
|
|
138
|
+
* Check if current username is claimed
|
|
205
139
|
*/
|
|
206
|
-
async
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
throw new Error(`Failed to get ICE candidates: ${error.error || response.statusText}`);
|
|
216
|
-
}
|
|
217
|
-
const data = await response.json();
|
|
218
|
-
return {
|
|
219
|
-
candidates: data.candidates || [],
|
|
220
|
-
offerId: data.offerId
|
|
221
|
-
};
|
|
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;
|
|
222
149
|
}
|
|
223
150
|
// ============================================
|
|
224
|
-
//
|
|
151
|
+
// Service Management
|
|
225
152
|
// ============================================
|
|
226
153
|
/**
|
|
227
154
|
* Publish a service
|
|
228
155
|
*/
|
|
229
156
|
async publishService(service) {
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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,
|
|
235
167
|
},
|
|
236
|
-
body: JSON.stringify(service),
|
|
237
168
|
});
|
|
238
|
-
if (!response.ok) {
|
|
239
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
240
|
-
throw new Error(`Failed to publish service: ${error.error || response.statusText}`);
|
|
241
|
-
}
|
|
242
|
-
return await response.json();
|
|
243
169
|
}
|
|
244
170
|
/**
|
|
245
|
-
* Get service by
|
|
171
|
+
* Get service by FQN (direct lookup, random, or paginated)
|
|
246
172
|
*/
|
|
247
|
-
async getService(
|
|
248
|
-
const
|
|
249
|
-
|
|
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
|
+
},
|
|
250
184
|
});
|
|
251
|
-
if (!response.ok) {
|
|
252
|
-
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
253
|
-
throw new Error(`Failed to get service: ${error.error || response.statusText}`);
|
|
254
|
-
}
|
|
255
|
-
return await response.json();
|
|
256
185
|
}
|
|
257
186
|
/**
|
|
258
|
-
*
|
|
187
|
+
* Delete a service
|
|
259
188
|
*/
|
|
260
|
-
async
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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 },
|
|
197
|
+
});
|
|
268
198
|
}
|
|
199
|
+
// ============================================
|
|
200
|
+
// WebRTC Signaling
|
|
201
|
+
// ============================================
|
|
269
202
|
/**
|
|
270
|
-
*
|
|
203
|
+
* Answer an offer
|
|
271
204
|
*/
|
|
272
|
-
async
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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 },
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get answer for a specific offer (offerer polls this)
|
|
217
|
+
*/
|
|
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;
|
|
277
232
|
}
|
|
278
|
-
|
|
279
|
-
throw new Error(`Failed to search services: ${error.error || response.statusText}`);
|
|
233
|
+
throw err;
|
|
280
234
|
}
|
|
281
|
-
const service = await response.json();
|
|
282
|
-
return [service];
|
|
283
235
|
}
|
|
284
|
-
// ============================================
|
|
285
|
-
// Usernames
|
|
286
|
-
// ============================================
|
|
287
236
|
/**
|
|
288
|
-
*
|
|
237
|
+
* Combined polling for answers and ICE candidates
|
|
289
238
|
*/
|
|
290
|
-
async
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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 },
|
|
247
|
+
});
|
|
297
248
|
}
|
|
298
249
|
/**
|
|
299
|
-
*
|
|
250
|
+
* Add ICE candidates to a specific offer
|
|
300
251
|
*/
|
|
301
|
-
async
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
publicKey,
|
|
310
|
-
signature,
|
|
311
|
-
message,
|
|
312
|
-
}),
|
|
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 },
|
|
313
260
|
});
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
+
};
|
|
319
278
|
}
|
|
320
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
|
@@ -2,19 +2,12 @@
|
|
|
2
2
|
* @xtr-dev/rondevu-client
|
|
3
3
|
* WebRTC peer signaling client
|
|
4
4
|
*/
|
|
5
|
-
export {
|
|
5
|
+
export { Rondevu } from './rondevu.js';
|
|
6
6
|
export { RondevuAPI } from './api.js';
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export type { ConnectionInterface, QueueMessageOptions, Message, ConnectionEvents, Signaler, } from './types.js';
|
|
15
|
-
export type { Credentials, Keypair, OfferRequest, Offer, ServiceRequest, Service, IceCandidate, } from './api.js';
|
|
16
|
-
export type { Binnable } from './bin.js';
|
|
17
|
-
export type { RondevuServiceOptions, PublishServiceOptions } from './rondevu-service.js';
|
|
18
|
-
export type { ServiceHostOptions, ServiceHostEvents } from './service-host.js';
|
|
19
|
-
export type { ServiceClientOptions, ServiceClientEvents } from './service-client.js';
|
|
20
|
-
export type { PollingConfig } from './rondevu-signaler.js';
|
|
7
|
+
export { RpcBatcher } from './rpc-batcher.js';
|
|
8
|
+
export { WebCryptoAdapter } from './web-crypto-adapter.js';
|
|
9
|
+
export { NodeCryptoAdapter } from './node-crypto-adapter.js';
|
|
10
|
+
export type { Signaler, Binnable, } from './types.js';
|
|
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
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
* @xtr-dev/rondevu-client
|
|
3
3
|
* WebRTC peer signaling client
|
|
4
4
|
*/
|
|
5
|
-
export {
|
|
5
|
+
export { Rondevu } from './rondevu.js';
|
|
6
6
|
export { RondevuAPI } from './api.js';
|
|
7
|
-
export {
|
|
8
|
-
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
export { ServiceHost } from './service-host.js';
|
|
12
|
-
export { ServiceClient } from './service-client.js';
|
|
13
|
-
export { createBin } from './bin.js';
|
|
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
|
+
}
|