@xtr-dev/rondevu-client 0.18.10 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +92 -117
  2. package/dist/api/batcher.d.ts +83 -0
  3. package/dist/api/batcher.js +155 -0
  4. package/dist/api/client.d.ts +198 -0
  5. package/dist/api/client.js +400 -0
  6. package/dist/{answerer-connection.d.ts → connections/answerer.d.ts} +25 -8
  7. package/dist/{answerer-connection.js → connections/answerer.js} +70 -48
  8. package/dist/{connection.d.ts → connections/base.d.ts} +30 -7
  9. package/dist/{connection.js → connections/base.js} +65 -14
  10. package/dist/connections/config.d.ts +51 -0
  11. package/dist/{connection-config.js → connections/config.js} +20 -0
  12. package/dist/{connection-events.d.ts → connections/events.d.ts} +6 -6
  13. package/dist/connections/offerer.d.ts +108 -0
  14. package/dist/connections/offerer.js +306 -0
  15. package/dist/core/ice-config.d.ts +35 -0
  16. package/dist/core/ice-config.js +111 -0
  17. package/dist/core/index.d.ts +22 -0
  18. package/dist/core/index.js +22 -0
  19. package/dist/core/offer-pool.d.ts +113 -0
  20. package/dist/core/offer-pool.js +281 -0
  21. package/dist/core/peer.d.ts +155 -0
  22. package/dist/core/peer.js +252 -0
  23. package/dist/core/polling-manager.d.ts +71 -0
  24. package/dist/core/polling-manager.js +122 -0
  25. package/dist/core/rondevu-errors.d.ts +59 -0
  26. package/dist/core/rondevu-errors.js +75 -0
  27. package/dist/core/rondevu-types.d.ts +125 -0
  28. package/dist/core/rondevu-types.js +6 -0
  29. package/dist/core/rondevu.d.ts +296 -0
  30. package/dist/core/rondevu.js +472 -0
  31. package/dist/crypto/adapter.d.ts +53 -0
  32. package/dist/crypto/node.d.ts +57 -0
  33. package/dist/crypto/node.js +149 -0
  34. package/dist/crypto/web.d.ts +38 -0
  35. package/dist/crypto/web.js +129 -0
  36. package/dist/utils/async-lock.d.ts +42 -0
  37. package/dist/utils/async-lock.js +75 -0
  38. package/dist/{message-buffer.d.ts → utils/message-buffer.d.ts} +1 -1
  39. package/dist/{message-buffer.js → utils/message-buffer.js} +4 -4
  40. package/dist/webrtc/adapter.d.ts +22 -0
  41. package/dist/webrtc/adapter.js +5 -0
  42. package/dist/webrtc/browser.d.ts +12 -0
  43. package/dist/webrtc/browser.js +15 -0
  44. package/dist/webrtc/node.d.ts +32 -0
  45. package/dist/webrtc/node.js +32 -0
  46. package/package.json +20 -9
  47. package/dist/api.d.ts +0 -146
  48. package/dist/api.js +0 -279
  49. package/dist/connection-config.d.ts +0 -21
  50. package/dist/crypto-adapter.d.ts +0 -37
  51. package/dist/index.d.ts +0 -13
  52. package/dist/index.js +0 -10
  53. package/dist/node-crypto-adapter.d.ts +0 -35
  54. package/dist/node-crypto-adapter.js +0 -78
  55. package/dist/offerer-connection.d.ts +0 -54
  56. package/dist/offerer-connection.js +0 -177
  57. package/dist/rondevu-signaler.d.ts +0 -112
  58. package/dist/rondevu-signaler.js +0 -401
  59. package/dist/rondevu.d.ts +0 -407
  60. package/dist/rondevu.js +0 -847
  61. package/dist/rpc-batcher.d.ts +0 -61
  62. package/dist/rpc-batcher.js +0 -111
  63. package/dist/web-crypto-adapter.d.ts +0 -16
  64. package/dist/web-crypto-adapter.js +0 -52
  65. /package/dist/{connection-events.js → connections/events.js} +0 -0
  66. /package/dist/{types.d.ts → core/types.d.ts} +0 -0
  67. /package/dist/{types.js → core/types.js} +0 -0
  68. /package/dist/{crypto-adapter.js → crypto/adapter.js} +0 -0
  69. /package/dist/{exponential-backoff.d.ts → utils/exponential-backoff.d.ts} +0 -0
  70. /package/dist/{exponential-backoff.js → utils/exponential-backoff.js} +0 -0
@@ -0,0 +1,472 @@
1
+ import { RondevuAPI } from '../api/client.js';
2
+ import { BrowserWebRTCAdapter } from '../webrtc/browser.js';
3
+ import { EventEmitter } from 'eventemitter3';
4
+ import { OfferPool } from './offer-pool.js';
5
+ import { Peer } from './peer.js';
6
+ import { getIceConfiguration } from './ice-config.js';
7
+ import { PollingManager } from './polling-manager.js';
8
+ // Re-export ICE config for backward compatibility
9
+ export { ICE_SERVER_PRESETS } from './ice-config.js';
10
+ /**
11
+ * Rondevu - Complete WebRTC signaling client with durable connections
12
+ *
13
+ * Uses a tags-based discovery system where offers have 1+ tags for matching.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // Create and initialize Rondevu instance with preset ICE servers
18
+ * const rondevu = await Rondevu.connect({
19
+ * apiUrl: 'https://signal.example.com',
20
+ * iceServers: 'ipv4-turn' // Use preset: 'ipv4-turn', 'hostname-turns', 'google-stun', or 'relay-only'
21
+ * })
22
+ *
23
+ * // Create offers with tags for discovery
24
+ * await rondevu.offer({
25
+ * tags: ['chat', 'video'],
26
+ * maxOffers: 5 // Maintain up to 5 concurrent offers
27
+ * })
28
+ *
29
+ * // Start accepting connections (auto-fills offers and polls)
30
+ * await rondevu.startFilling()
31
+ *
32
+ * // Listen for connections
33
+ * rondevu.on('connection:opened', (offerId, connection) => {
34
+ * connection.on('connected', () => console.log('Connected!'))
35
+ * connection.on('message', (data) => console.log('Received:', data))
36
+ * connection.send('Hello!')
37
+ * })
38
+ *
39
+ * // Connect by discovering offers with matching tags
40
+ * const connection = await rondevu.connect({
41
+ * tags: ['chat']
42
+ * })
43
+ *
44
+ * connection.on('connected', () => {
45
+ * console.log('Connected!')
46
+ * connection.send('Hello!')
47
+ * })
48
+ *
49
+ * connection.on('message', (data) => {
50
+ * console.log('Received:', data)
51
+ * })
52
+ *
53
+ * connection.on('reconnecting', (attempt) => {
54
+ * console.log(`Reconnecting, attempt ${attempt}`)
55
+ * })
56
+ * ```
57
+ */
58
+ export class Rondevu extends EventEmitter {
59
+ constructor(apiUrl, credential, api, iceServers, iceTransportPolicy, webrtcAdapter, cryptoAdapter, debugEnabled = false) {
60
+ super();
61
+ // Publishing state
62
+ this.currentTags = null;
63
+ this.offerPool = null;
64
+ this.apiUrl = apiUrl;
65
+ this.credential = credential;
66
+ this.api = api;
67
+ this.iceServers = iceServers;
68
+ this.iceTransportPolicy = iceTransportPolicy;
69
+ this.webrtcAdapter = webrtcAdapter;
70
+ this.cryptoAdapter = cryptoAdapter;
71
+ this.debugEnabled = debugEnabled;
72
+ // Initialize centralized polling manager
73
+ this.pollingManager = new PollingManager({
74
+ api: this.api,
75
+ debugEnabled: this.debugEnabled,
76
+ });
77
+ // Forward polling events to Rondevu instance
78
+ this.pollingManager.on('poll:answer', data => {
79
+ this.emit('poll:answer', data);
80
+ });
81
+ this.pollingManager.on('poll:ice', data => {
82
+ this.emit('poll:ice', data);
83
+ });
84
+ this.debug('Instance created:', {
85
+ name: this.credential.name,
86
+ hasIceServers: iceServers.length > 0,
87
+ iceTransportPolicy: iceTransportPolicy || 'all',
88
+ });
89
+ }
90
+ /**
91
+ * Internal debug logging - only logs if debug mode is enabled
92
+ */
93
+ debug(message, ...args) {
94
+ if (this.debugEnabled) {
95
+ console.log(`[Rondevu] ${message}`, ...args);
96
+ }
97
+ }
98
+ /**
99
+ * Create and initialize a Rondevu client
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const rondevu = await Rondevu.connect({}) // Uses default API URL
104
+ * // or
105
+ * const rondevu = await Rondevu.connect({
106
+ * apiUrl: 'https://custom.api.com'
107
+ * })
108
+ * ```
109
+ */
110
+ static async connect(options = {}) {
111
+ const apiUrl = options.apiUrl || Rondevu.DEFAULT_API_URL;
112
+ // Use provided WebRTC adapter or default to browser adapter
113
+ const webrtcAdapter = options.webrtcAdapter || new BrowserWebRTCAdapter();
114
+ // Handle preset string or custom array, extracting iceTransportPolicy if present
115
+ const iceConfig = getIceConfiguration(options.iceServers);
116
+ if (options.debug) {
117
+ console.log('[Rondevu] Connecting:', {
118
+ apiUrl,
119
+ hasCredential: !!options.credential,
120
+ iceServers: iceConfig.iceServers?.length ?? 0,
121
+ iceTransportPolicy: iceConfig.iceTransportPolicy || 'all',
122
+ });
123
+ }
124
+ // Generate credential if not provided
125
+ let credential = options.credential;
126
+ if (!credential) {
127
+ if (options.debug)
128
+ console.log('[Rondevu] Generating new credentials...');
129
+ credential = await RondevuAPI.generateCredentials(apiUrl, {
130
+ name: options.username, // Will claim this username if provided
131
+ });
132
+ if (options.debug)
133
+ console.log('[Rondevu] Generated credentials, name:', credential.name);
134
+ }
135
+ else {
136
+ if (options.debug)
137
+ console.log('[Rondevu] Using existing credential, name:', credential.name);
138
+ }
139
+ // Create API instance
140
+ const api = new RondevuAPI(apiUrl, credential, options.cryptoAdapter);
141
+ if (options.debug)
142
+ console.log('[Rondevu] Created API instance');
143
+ return new Rondevu(apiUrl, credential, api, iceConfig.iceServers || [], iceConfig.iceTransportPolicy, webrtcAdapter, options.cryptoAdapter, options.debug || false);
144
+ }
145
+ // ============================================
146
+ // Credential Access
147
+ // ============================================
148
+ /**
149
+ * Get the current credential name
150
+ */
151
+ getName() {
152
+ return this.credential.name;
153
+ }
154
+ /**
155
+ * Get the full credential (name + secret)
156
+ * Use this to persist credentials for future sessions
157
+ *
158
+ * ⚠️ SECURITY WARNING:
159
+ * - The secret grants full access to this identity
160
+ * - Store credentials securely (encrypted storage, never in logs)
161
+ * - Never expose credentials in URLs, console output, or error messages
162
+ * - Treat the secret like a password or API key
163
+ */
164
+ getCredential() {
165
+ return { ...this.credential };
166
+ }
167
+ /**
168
+ * Get the WebRTC adapter for creating peer connections
169
+ * Used internally by offer pool and connections
170
+ */
171
+ getWebRTCAdapter() {
172
+ return this.webrtcAdapter;
173
+ }
174
+ // ============================================
175
+ // Service Publishing
176
+ // ============================================
177
+ /**
178
+ * Default offer factory - creates a simple data channel connection
179
+ * The RTCPeerConnection is created by Rondevu and passed in
180
+ */
181
+ async defaultOfferFactory(pc) {
182
+ const dc = pc.createDataChannel('default');
183
+ const offer = await pc.createOffer();
184
+ await pc.setLocalDescription(offer);
185
+ return { dc, offer };
186
+ }
187
+ /**
188
+ * Create offers with tags for discovery (offerer/host side)
189
+ * Auto-starts filling by default. Use the returned object to cancel.
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * // Auto-start (default)
194
+ * const offer = await rondevu.offer({
195
+ * tags: ['chat', 'video'],
196
+ * maxOffers: 5
197
+ * })
198
+ * // Later: offer.cancel() to stop
199
+ *
200
+ * // Manual start
201
+ * await rondevu.offer({ tags: ['chat'], maxOffers: 5, autoStart: false })
202
+ * await rondevu.startFilling()
203
+ * ```
204
+ */
205
+ async offer(options) {
206
+ const { tags, maxOffers, offerFactory, ttl, connectionConfig, autoStart = true } = options;
207
+ this.currentTags = tags;
208
+ this.connectionConfig = connectionConfig;
209
+ this.debug(`Creating offers with tags: ${tags.join(', ')} with maxOffers: ${maxOffers}`);
210
+ // Create OfferPool
211
+ this.offerPool = new OfferPool({
212
+ api: this.api,
213
+ tags,
214
+ ownerUsername: this.credential.name,
215
+ maxOffers,
216
+ offerFactory: offerFactory || this.defaultOfferFactory.bind(this),
217
+ ttl: ttl || Rondevu.DEFAULT_TTL_MS,
218
+ iceServers: this.iceServers,
219
+ iceTransportPolicy: this.iceTransportPolicy,
220
+ webrtcAdapter: this.webrtcAdapter,
221
+ connectionConfig,
222
+ debugEnabled: this.debugEnabled,
223
+ });
224
+ // Forward events from OfferPool
225
+ this.offerPool.on('connection:opened', (offerId, connection) => {
226
+ this.emit('connection:opened', offerId, connection);
227
+ });
228
+ this.offerPool.on('offer:created', (offerId, tags) => {
229
+ this.emit('offer:created', offerId, tags);
230
+ });
231
+ this.offerPool.on('connection:rotated', (oldOfferId, newOfferId, connection) => {
232
+ this.emit('connection:rotated', oldOfferId, newOfferId, connection);
233
+ });
234
+ // Subscribe to polling events and forward to OfferPool
235
+ this.on('poll:answer', data => {
236
+ this.offerPool?.handlePollAnswer(data);
237
+ });
238
+ this.on('poll:ice', data => {
239
+ this.offerPool?.handlePollIce(data);
240
+ });
241
+ // Auto-start if enabled (default)
242
+ if (autoStart) {
243
+ await this.startFilling();
244
+ }
245
+ // Return handle for cancellation
246
+ return {
247
+ cancel: () => this.stopFilling(),
248
+ };
249
+ }
250
+ /**
251
+ * Start filling offers and polling for answers/ICE
252
+ * Call this after offer() to begin accepting connections
253
+ */
254
+ async startFilling() {
255
+ if (!this.offerPool) {
256
+ throw new Error('No offers created. Call offer() first.');
257
+ }
258
+ this.debug('Starting offer filling and polling');
259
+ // Start the centralized polling manager
260
+ this.pollingManager.start();
261
+ await this.offerPool.start();
262
+ }
263
+ /**
264
+ * Stop filling offers and polling
265
+ * Closes all active peer connections
266
+ */
267
+ stopFilling() {
268
+ this.debug('Stopping offer filling and polling');
269
+ // Stop the centralized polling manager
270
+ this.pollingManager.stop();
271
+ this.offerPool?.stop();
272
+ }
273
+ /**
274
+ * Start the centralized polling manager
275
+ * Use this when you need polling without offers (e.g., answerer connections)
276
+ */
277
+ startPolling() {
278
+ this.debug('Starting polling manager');
279
+ this.pollingManager.start();
280
+ }
281
+ /**
282
+ * Stop the centralized polling manager
283
+ */
284
+ stopPolling() {
285
+ this.debug('Stopping polling manager');
286
+ this.pollingManager.stop();
287
+ }
288
+ /**
289
+ * Check if polling is active
290
+ */
291
+ isPolling() {
292
+ return this.pollingManager.isRunning();
293
+ }
294
+ /**
295
+ * Get the count of active offers
296
+ * @returns Number of active offers
297
+ */
298
+ getOfferCount() {
299
+ return this.offerPool?.getOfferCount() ?? 0;
300
+ }
301
+ /**
302
+ * Check if an offer is currently connected
303
+ * @param offerId - The offer ID to check
304
+ * @returns True if the offer exists and is connected
305
+ */
306
+ isConnected(offerId) {
307
+ return this.offerPool?.isConnected(offerId) ?? false;
308
+ }
309
+ /**
310
+ * Disconnect all active offers
311
+ * Similar to stopFilling() but doesn't stop the polling/filling process
312
+ */
313
+ disconnectAll() {
314
+ this.debug('Disconnecting all offers');
315
+ this.offerPool?.disconnectAll();
316
+ }
317
+ /**
318
+ * Get the current publishing status
319
+ * @returns Object with publishing state information
320
+ */
321
+ getPublishStatus() {
322
+ return {
323
+ active: this.currentTags !== null,
324
+ offerCount: this.offerPool?.getOfferCount() ?? 0,
325
+ tags: this.currentTags,
326
+ };
327
+ }
328
+ /**
329
+ * Create a peer connection with simplified DX
330
+ * Returns a Peer object with clean state management and events
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * // Connect to any peer matching tags
335
+ * const peer = await rondevu.peer({ tags: ['chat'] })
336
+ *
337
+ * // Connect to specific user
338
+ * const peer = await rondevu.peer({
339
+ * username: 'alice',
340
+ * tags: ['chat']
341
+ * })
342
+ *
343
+ * peer.on('open', () => {
344
+ * console.log('Connected to', peer.peerUsername)
345
+ * peer.send('Hello!')
346
+ * })
347
+ *
348
+ * peer.on('message', (data) => {
349
+ * console.log('Received:', data)
350
+ * })
351
+ *
352
+ * peer.on('state', (state, prevState) => {
353
+ * console.log(`State: ${prevState} → ${state}`)
354
+ * })
355
+ *
356
+ * // Access underlying RTCPeerConnection
357
+ * if (peer.peerConnection) {
358
+ * console.log('ICE state:', peer.peerConnection.iceConnectionState)
359
+ * }
360
+ * ```
361
+ */
362
+ async peer(options) {
363
+ const peer = new Peer({
364
+ ...options,
365
+ api: this.api,
366
+ iceServers: this.iceServers,
367
+ iceTransportPolicy: this.iceTransportPolicy,
368
+ debug: this.debugEnabled,
369
+ });
370
+ await peer.initialize();
371
+ // Subscribe to poll:ice events for this peer's connection
372
+ const peerOfferId = peer.offerId;
373
+ const peerConnection = peer.getConnection();
374
+ if (peerConnection) {
375
+ const pollIceHandler = (data) => {
376
+ if (data.offerId === peerOfferId) {
377
+ peerConnection.handleRemoteIceCandidates(data.candidates);
378
+ }
379
+ };
380
+ this.on('poll:ice', pollIceHandler);
381
+ // Clean up handler when connection closes
382
+ peerConnection.on('closed', () => {
383
+ this.off('poll:ice', pollIceHandler);
384
+ });
385
+ }
386
+ // Start polling if not already running
387
+ if (!this.pollingManager.isRunning()) {
388
+ this.debug('Starting polling for peer connection');
389
+ this.pollingManager.start();
390
+ }
391
+ return peer;
392
+ }
393
+ // ============================================
394
+ // Discovery
395
+ // ============================================
396
+ /**
397
+ * Discover offers by tags
398
+ *
399
+ * @param tags - Tags to search for (OR logic - matches any tag)
400
+ * @param options - Discovery options (pagination)
401
+ *
402
+ * @example
403
+ * ```typescript
404
+ * // Discover offers matching any of the tags
405
+ * const result = await rondevu.discover(['chat', 'video'])
406
+ *
407
+ * // Paginated discovery
408
+ * const result = await rondevu.discover(['chat'], {
409
+ * limit: 20,
410
+ * offset: 0
411
+ * })
412
+ *
413
+ * // Access offers
414
+ * for (const offer of result.offers) {
415
+ * console.log(offer.username, offer.tags)
416
+ * }
417
+ * ```
418
+ */
419
+ async discover(tags, options) {
420
+ const { limit = 10, offset = 0 } = options || {};
421
+ // Always pass limit to ensure we get DiscoverResponse (paginated mode)
422
+ return (await this.api.discover({ tags, limit, offset }));
423
+ }
424
+ // ============================================
425
+ // WebRTC Signaling
426
+ // ============================================
427
+ /**
428
+ * Post answer SDP to specific offer
429
+ */
430
+ async postOfferAnswer(offerId, sdp) {
431
+ await this.api.answerOffer(offerId, sdp);
432
+ return { success: true, offerId };
433
+ }
434
+ /**
435
+ * Get answer SDP (offerer polls this)
436
+ */
437
+ async getOfferAnswer(offerId) {
438
+ return await this.api.getOfferAnswer(offerId);
439
+ }
440
+ /**
441
+ * Combined polling for answers and ICE candidates
442
+ * Returns all answered offers and ICE candidates for all peer's offers since timestamp
443
+ */
444
+ async poll(since) {
445
+ return await this.api.poll(since);
446
+ }
447
+ /**
448
+ * Add ICE candidates to specific offer
449
+ */
450
+ async addOfferIceCandidates(offerId, candidates) {
451
+ return await this.api.addOfferIceCandidates(offerId, candidates);
452
+ }
453
+ /**
454
+ * Get ICE candidates for specific offer (with polling support)
455
+ */
456
+ async getOfferIceCandidates(offerId, since = 0) {
457
+ return await this.api.getOfferIceCandidates(offerId, since);
458
+ }
459
+ // ============================================
460
+ // Utility Methods
461
+ // ============================================
462
+ /**
463
+ * Get active connections (for offerer side)
464
+ */
465
+ getActiveConnections() {
466
+ return this.offerPool?.getActiveConnections() ?? new Map();
467
+ }
468
+ }
469
+ // Constants
470
+ Rondevu.DEFAULT_API_URL = 'https://api.ronde.vu';
471
+ Rondevu.DEFAULT_TTL_MS = 300000; // 5 minutes
472
+ Rondevu.POLLING_INTERVAL_MS = 1000; // 1 second
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Crypto adapter interface for platform-independent cryptographic operations
3
+ */
4
+ export interface Credential {
5
+ name: string;
6
+ secret: string;
7
+ }
8
+ /**
9
+ * Platform-independent crypto adapter interface
10
+ * Implementations provide platform-specific crypto operations
11
+ */
12
+ export interface CryptoAdapter {
13
+ /**
14
+ * Generate HMAC-SHA256 signature for message authentication
15
+ * @param secret - The credential secret (hex string)
16
+ * @param message - The message to sign
17
+ * @returns Base64-encoded signature
18
+ */
19
+ generateSignature(secret: string, message: string): Promise<string>;
20
+ /**
21
+ * Verify HMAC-SHA256 signature
22
+ * @param secret - The credential secret (hex string)
23
+ * @param message - The message that was signed
24
+ * @param signature - The signature to verify (base64)
25
+ * @returns True if signature is valid
26
+ */
27
+ verifySignature(secret: string, message: string, signature: string): Promise<boolean>;
28
+ /**
29
+ * Generate a random secret (256-bit hex string)
30
+ * @returns 64-character hex string
31
+ */
32
+ generateSecret(): string;
33
+ /**
34
+ * Convert hex string to bytes
35
+ */
36
+ hexToBytes(hex: string): Uint8Array;
37
+ /**
38
+ * Convert bytes to hex string
39
+ */
40
+ bytesToHex(bytes: Uint8Array): string;
41
+ /**
42
+ * Convert Uint8Array to base64 string
43
+ */
44
+ bytesToBase64(bytes: Uint8Array): string;
45
+ /**
46
+ * Convert base64 string to Uint8Array
47
+ */
48
+ base64ToBytes(base64: string): Uint8Array;
49
+ /**
50
+ * Generate random bytes
51
+ */
52
+ randomBytes(length: number): Uint8Array;
53
+ }
@@ -0,0 +1,57 @@
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 } from './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
+ * { name: 'alice', secret: '...' },
23
+ * new NodeCryptoAdapter()
24
+ * )
25
+ * ```
26
+ */
27
+ export declare class NodeCryptoAdapter implements CryptoAdapter {
28
+ constructor();
29
+ /**
30
+ * Generate HMAC-SHA256 signature
31
+ */
32
+ generateSignature(secret: string, message: string): Promise<string>;
33
+ /**
34
+ * Verify HMAC-SHA256 signature
35
+ * Uses constant-time comparison via Web Crypto API to prevent timing attacks
36
+ *
37
+ * @returns false for invalid signatures, throws for malformed input
38
+ * @throws Error if secret/signature format is invalid (not a verification failure)
39
+ */
40
+ verifySignature(secret: string, message: string, signature: string): Promise<boolean>;
41
+ /**
42
+ * Generate a random secret (256-bit hex string)
43
+ */
44
+ generateSecret(): string;
45
+ /**
46
+ * Convert hex string to bytes
47
+ * @throws Error if hex string is invalid
48
+ */
49
+ hexToBytes(hex: string): Uint8Array;
50
+ /**
51
+ * Convert bytes to hex string
52
+ */
53
+ bytesToHex(bytes: Uint8Array): string;
54
+ bytesToBase64(bytes: Uint8Array): string;
55
+ base64ToBytes(base64: string): Uint8Array;
56
+ randomBytes(length: number): Uint8Array;
57
+ }