@xtr-dev/rondevu-client 0.21.3 → 0.21.6

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 CHANGED
@@ -8,7 +8,7 @@ TypeScript client for [Rondevu](https://github.com/xtr-dev/rondevu-server), prov
8
8
 
9
9
  ## Features
10
10
 
11
- - **Simple Peer API**: Connect with `rondevu.peer({ tags, username })`
11
+ - **Ed25519 Identity**: Your public key IS your identity (like Ethereum addresses)
12
12
  - **Tags-Based Discovery**: Find peers using tags (e.g., `["chat", "video"]`)
13
13
  - **Automatic Reconnection**: Built-in exponential backoff
14
14
  - **Message Buffering**: Queues messages during disconnections
@@ -27,10 +27,10 @@ import { Rondevu } from '@xtr-dev/rondevu-client'
27
27
  // ============================================
28
28
  // ALICE: Host and wait for connections
29
29
  // ============================================
30
- const alice = await Rondevu.connect({ username: 'alice' })
30
+ const alice = await Rondevu.connect()
31
31
 
32
32
  alice.on('connection:opened', (offerId, connection) => {
33
- console.log('Connected to', connection.peerUsername)
33
+ console.log('Connected to', connection.peerPublicKey)
34
34
  connection.on('message', (data) => console.log('Received:', data))
35
35
  connection.send('Hello!')
36
36
  })
@@ -43,10 +43,7 @@ const offer = await alice.offer({ tags: ['chat'], maxOffers: 5 })
43
43
  // ============================================
44
44
  const bob = await Rondevu.connect()
45
45
 
46
- const peer = await bob.peer({
47
- username: 'alice',
48
- tags: ['chat']
49
- })
46
+ const peer = await bob.peer({ tags: ['chat'] })
50
47
 
51
48
  peer.on('open', () => peer.send('Hello Alice!'))
52
49
  peer.on('message', (data) => console.log('Received:', data))
@@ -59,14 +56,13 @@ peer.on('message', (data) => console.log('Received:', data))
59
56
  ```typescript
60
57
  const rondevu = await Rondevu.connect({
61
58
  apiUrl?: string, // Default: 'https://api.ronde.vu'
62
- credential?: Credential, // Reuse existing credential
63
- username?: string, // Claim username (4-32 chars)
59
+ keyPair?: KeyPair, // Reuse existing keypair
64
60
  iceServers?: IceServerPreset | RTCIceServer[], // Default: 'rondevu'
65
61
  debug?: boolean
66
62
  })
67
63
 
68
- rondevu.getName() // Get username
69
- rondevu.getCredential() // Get credential for reuse
64
+ rondevu.getPublicKey() // Get public key (your identity)
65
+ rondevu.getKeyPair() // Get keypair for persistence
70
66
  ```
71
67
 
72
68
  **ICE Presets**: `'rondevu'` (default), `'rondevu-relay'`, `'google-stun'`, `'public-stun'`
@@ -76,7 +72,7 @@ rondevu.getCredential() // Get credential for reuse
76
72
  ```typescript
77
73
  const peer = await rondevu.peer({
78
74
  tags: string[],
79
- username?: string,
75
+ publicKey?: string, // Connect to specific peer
80
76
  rtcConfig?: RTCConfiguration
81
77
  })
82
78
 
@@ -89,7 +85,7 @@ peer.on('reconnecting', (attempt, max) => {})
89
85
 
90
86
  // Properties & Methods
91
87
  peer.state // 'connecting' | 'connected' | 'reconnecting' | ...
92
- peer.peerUsername
88
+ peer.peerPublicKey
93
89
  peer.send(data)
94
90
  peer.close()
95
91
  ```
@@ -116,25 +112,25 @@ rondevu.on('connection:opened', (offerId, connection) => {
116
112
 
117
113
  ```typescript
118
114
  const result = await rondevu.discover(['chat'], { limit: 20 })
119
- result.offers.forEach(o => console.log(o.username, o.tags))
115
+ result.offers.forEach(o => console.log(o.publicKey, o.tags))
120
116
  ```
121
117
 
122
- ## Credentials
118
+ ## Identity (Ed25519 Keypairs)
119
+
120
+ Your identity is an Ed25519 public key - no usernames, no registration, no claiming conflicts. Generate a keypair locally and start making requests immediately.
123
121
 
124
122
  ```typescript
125
- // Auto-generated username
123
+ // Auto-generated keypair
126
124
  const rondevu = await Rondevu.connect()
127
- // rondevu.getName() === 'friendly-panda-a1b2c3'
128
-
129
- // Claimed username
130
- const rondevu = await Rondevu.connect({ username: 'alice' })
125
+ console.log(rondevu.getPublicKey()) // '5a7f3e2d...' (64 hex chars)
131
126
 
132
- // Save and restore credentials
133
- const credential = rondevu.getCredential()
134
- localStorage.setItem('cred', JSON.stringify(credential))
127
+ // Save and restore keypair for persistent identity
128
+ const keyPair = rondevu.getKeyPair()
129
+ localStorage.setItem('keypair', JSON.stringify(keyPair))
135
130
 
136
- const saved = JSON.parse(localStorage.getItem('cred'))
137
- const rondevu = await Rondevu.connect({ credential: saved })
131
+ // Later: restore
132
+ const saved = JSON.parse(localStorage.getItem('keypair'))
133
+ const rondevu = await Rondevu.connect({ keyPair: saved })
138
134
  ```
139
135
 
140
136
  ## Tag Validation
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Rondevu API Client - RPC interface
3
3
  */
4
- import { CryptoAdapter, Credential } from '../crypto/adapter.js';
4
+ import { CryptoAdapter, KeyPair } from '../crypto/adapter.js';
5
5
  import { BatcherOptions } from './batcher.js';
6
- export type { Credential } from '../crypto/adapter.js';
6
+ export type { KeyPair } from '../crypto/adapter.js';
7
7
  export type { BatcherOptions } from './batcher.js';
8
8
  export interface OfferRequest {
9
9
  sdp: string;
@@ -20,7 +20,7 @@ export interface DiscoverRequest {
20
20
  }
21
21
  export interface TaggedOffer {
22
22
  offerId: string;
23
- username: string;
23
+ publicKey: string;
24
24
  tags: string[];
25
25
  sdp: string;
26
26
  createdAt: number;
@@ -33,7 +33,7 @@ export interface DiscoverResponse {
33
33
  offset: number;
34
34
  }
35
35
  export interface PublishResponse {
36
- username: string;
36
+ publicKey: string;
37
37
  tags: string[];
38
38
  offers: Array<{
39
39
  offerId: string;
@@ -51,19 +51,19 @@ export interface IceCandidate {
51
51
  }
52
52
  /**
53
53
  * RondevuAPI - RPC-based API client for Rondevu signaling server
54
+ *
55
+ * Uses Ed25519 public key cryptography for authentication.
56
+ * The public key IS the identity (like Ethereum addresses).
54
57
  */
55
58
  export declare class RondevuAPI {
56
59
  private baseUrl;
57
- private credential;
58
- private static readonly DEFAULT_MAX_RETRIES;
59
- private static readonly DEFAULT_TIMEOUT_MS;
60
- private static readonly DEFAULT_CREDENTIAL_NAME_MAX_LENGTH;
61
- private static readonly DEFAULT_SECRET_MIN_LENGTH;
62
- private static readonly MAX_BACKOFF_MS;
60
+ private keyPair;
61
+ private static readonly PUBLIC_KEY_LENGTH;
62
+ private static readonly PRIVATE_KEY_LENGTH;
63
63
  private static readonly MAX_CANONICALIZE_DEPTH;
64
64
  private crypto;
65
65
  private batcher;
66
- constructor(baseUrl: string, credential: Credential, cryptoAdapter?: CryptoAdapter, batcherOptions?: BatcherOptions);
66
+ constructor(baseUrl: string, keyPair: KeyPair, cryptoAdapter?: CryptoAdapter, batcherOptions?: BatcherOptions);
67
67
  /**
68
68
  * Canonical JSON serialization with sorted keys
69
69
  * Ensures deterministic output regardless of property insertion order
@@ -90,7 +90,7 @@ export declare class RondevuAPI {
90
90
  private generateNonce;
91
91
  /**
92
92
  * Generate authentication headers for RPC request
93
- * Uses HMAC-SHA256 signature with nonce for replay protection
93
+ * Uses Ed25519 signature with nonce for replay protection
94
94
  *
95
95
  * Security notes:
96
96
  * - Nonce: Cryptographically secure random value (UUID or 128-bit hex)
@@ -98,7 +98,7 @@ export declare class RondevuAPI {
98
98
  * - Server validates timestamp is within acceptable range (typically ±5 minutes)
99
99
  * - Tolerates reasonable clock skew between client and server
100
100
  * - Requests with stale timestamps are rejected
101
- * - Signature: HMAC-SHA256 ensures message integrity and authenticity
101
+ * - Signature: Ed25519 ensures message integrity and authenticity
102
102
  * - Server validates nonce uniqueness to prevent replay within time window
103
103
  * - Each nonce can only be used once within the timestamp validity window
104
104
  * - Server maintains nonce cache with expiration matching timestamp window
@@ -110,31 +110,13 @@ export declare class RondevuAPI {
110
110
  */
111
111
  private rpc;
112
112
  /**
113
- * Generate new credentials (name + secret pair)
114
- * This is the entry point for new users - no authentication required
115
- * Credentials are generated server-side to ensure security and uniqueness
113
+ * Generate a new Ed25519 keypair locally
114
+ * This is completely client-side - no server communication
116
115
  *
117
- * ⚠️ SECURITY NOTE:
118
- * - Store the returned credential securely
119
- * - The secret provides full access to this identity
120
- * - Credentials should be persisted encrypted and never logged
121
- *
122
- * @param baseUrl - Rondevu server URL
123
- * @param expiresAt - Optional custom expiry timestamp (defaults to 1 year)
124
- * @param options - Optional: { maxRetries: number, timeout: number }
125
- * @returns Generated credential with name and secret
126
- */
127
- static generateCredentials(baseUrl: string, options?: {
128
- name?: string;
129
- expiresAt?: number;
130
- maxRetries?: number;
131
- timeout?: number;
132
- }): Promise<Credential>;
133
- /**
134
- * Generate a random secret locally (for advanced use cases)
135
- * @param cryptoAdapter - Optional crypto adapter
116
+ * @param cryptoAdapter - Optional crypto adapter (defaults to WebCryptoAdapter)
117
+ * @returns Generated keypair with publicKey and privateKey as hex strings
136
118
  */
137
- static generateSecret(cryptoAdapter?: CryptoAdapter): string;
119
+ static generateKeyPair(cryptoAdapter?: CryptoAdapter): Promise<KeyPair>;
138
120
  /**
139
121
  * Publish offers with tags
140
122
  */
@@ -153,16 +135,20 @@ export declare class RondevuAPI {
153
135
  }>;
154
136
  /**
155
137
  * Answer an offer
138
+ * @param offerId The offer ID to answer
139
+ * @param sdp The SDP answer
140
+ * @param matchedTags Optional tags that were used to discover this offer
156
141
  */
157
- answerOffer(offerId: string, sdp: string): Promise<void>;
142
+ answerOffer(offerId: string, sdp: string, matchedTags?: string[]): Promise<void>;
158
143
  /**
159
144
  * Get answer for a specific offer (offerer polls this)
160
145
  */
161
146
  getOfferAnswer(offerId: string): Promise<{
162
147
  sdp: string;
163
148
  offerId: string;
164
- answererId: string;
149
+ answererPublicKey: string;
165
150
  answeredAt: number;
151
+ matchedTags?: string[];
166
152
  } | null>;
167
153
  /**
168
154
  * Combined polling for answers and ICE candidates
@@ -170,14 +156,15 @@ export declare class RondevuAPI {
170
156
  poll(since?: number): Promise<{
171
157
  answers: Array<{
172
158
  offerId: string;
173
- answererId: string;
159
+ answererPublicKey: string;
174
160
  sdp: string;
175
161
  answeredAt: number;
162
+ matchedTags?: string[];
176
163
  }>;
177
164
  iceCandidates: Record<string, Array<{
178
165
  candidate: RTCIceCandidateInit | null;
179
166
  role: 'offerer' | 'answerer';
180
- peerId: string;
167
+ peerPublicKey: string;
181
168
  createdAt: number;
182
169
  }>>;
183
170
  }>;
@@ -5,41 +5,37 @@ import { WebCryptoAdapter } from '../crypto/web.js';
5
5
  import { RpcBatcher } from './batcher.js';
6
6
  /**
7
7
  * RondevuAPI - RPC-based API client for Rondevu signaling server
8
+ *
9
+ * Uses Ed25519 public key cryptography for authentication.
10
+ * The public key IS the identity (like Ethereum addresses).
8
11
  */
9
12
  export class RondevuAPI {
10
- constructor(baseUrl, credential, cryptoAdapter, batcherOptions) {
13
+ constructor(baseUrl, keyPair, cryptoAdapter, batcherOptions) {
11
14
  this.baseUrl = baseUrl;
12
- this.credential = credential;
15
+ this.keyPair = keyPair;
13
16
  // Use WebCryptoAdapter by default (browser environment)
14
17
  this.crypto = cryptoAdapter || new WebCryptoAdapter();
15
18
  // Create batcher for request batching with throttling
16
19
  this.batcher = new RpcBatcher(baseUrl, batcherOptions);
17
- // Validate credential format early to provide clear error messages
18
- if (!credential.name || typeof credential.name !== 'string') {
19
- throw new Error('Invalid credential: name must be a non-empty string');
20
+ // Validate public key format
21
+ if (!keyPair.publicKey || typeof keyPair.publicKey !== 'string') {
22
+ throw new Error('Invalid keypair: publicKey must be a non-empty string');
20
23
  }
21
- // Validate name format (alphanumeric, dots, underscores, hyphens only)
22
- // Limit to prevent HTTP header size issues
23
- if (credential.name.length > RondevuAPI.DEFAULT_CREDENTIAL_NAME_MAX_LENGTH) {
24
- throw new Error(`Invalid credential: name must not exceed ${RondevuAPI.DEFAULT_CREDENTIAL_NAME_MAX_LENGTH} characters`);
24
+ if (keyPair.publicKey.length !== RondevuAPI.PUBLIC_KEY_LENGTH) {
25
+ throw new Error(`Invalid keypair: publicKey must be ${RondevuAPI.PUBLIC_KEY_LENGTH} hex characters (32 bytes)`);
25
26
  }
26
- if (!/^[a-zA-Z0-9._-]+$/.test(credential.name)) {
27
- throw new Error('Invalid credential: name must contain only alphanumeric characters, dots, underscores, and hyphens');
27
+ if (!/^[0-9a-fA-F]+$/.test(keyPair.publicKey)) {
28
+ throw new Error('Invalid keypair: publicKey must contain only hexadecimal characters');
28
29
  }
29
- // Validate secret
30
- if (!credential.secret || typeof credential.secret !== 'string') {
31
- throw new Error('Invalid credential: secret must be a non-empty string');
30
+ // Validate private key format
31
+ if (!keyPair.privateKey || typeof keyPair.privateKey !== 'string') {
32
+ throw new Error('Invalid keypair: privateKey must be a non-empty string');
32
33
  }
33
- // Minimum 256 bits (64 hex characters) for security
34
- if (credential.secret.length < RondevuAPI.DEFAULT_SECRET_MIN_LENGTH) {
35
- throw new Error(`Invalid credential: secret must be at least 256 bits (${RondevuAPI.DEFAULT_SECRET_MIN_LENGTH} hex characters)`);
34
+ if (keyPair.privateKey.length !== RondevuAPI.PRIVATE_KEY_LENGTH) {
35
+ throw new Error(`Invalid keypair: privateKey must be ${RondevuAPI.PRIVATE_KEY_LENGTH} hex characters (32 bytes)`);
36
36
  }
37
- // Validate secret is valid hex (even length, only hex characters)
38
- if (credential.secret.length % 2 !== 0) {
39
- throw new Error('Invalid credential: secret must be a valid hex string (even length)');
40
- }
41
- if (!/^[0-9a-fA-F]+$/.test(credential.secret)) {
42
- throw new Error('Invalid credential: secret must contain only hexadecimal characters');
37
+ if (!/^[0-9a-fA-F]+$/.test(keyPair.privateKey)) {
38
+ throw new Error('Invalid keypair: privateKey must contain only hexadecimal characters');
43
39
  }
44
40
  }
45
41
  /**
@@ -133,7 +129,7 @@ export class RondevuAPI {
133
129
  }
134
130
  /**
135
131
  * Generate authentication headers for RPC request
136
- * Uses HMAC-SHA256 signature with nonce for replay protection
132
+ * Uses Ed25519 signature with nonce for replay protection
137
133
  *
138
134
  * Security notes:
139
135
  * - Nonce: Cryptographically secure random value (UUID or 128-bit hex)
@@ -141,7 +137,7 @@ export class RondevuAPI {
141
137
  * - Server validates timestamp is within acceptable range (typically ±5 minutes)
142
138
  * - Tolerates reasonable clock skew between client and server
143
139
  * - Requests with stale timestamps are rejected
144
- * - Signature: HMAC-SHA256 ensures message integrity and authenticity
140
+ * - Signature: Ed25519 ensures message integrity and authenticity
145
141
  * - Server validates nonce uniqueness to prevent replay within time window
146
142
  * - Each nonce can only be used once within the timestamp validity window
147
143
  * - Server maintains nonce cache with expiration matching timestamp window
@@ -149,11 +145,11 @@ export class RondevuAPI {
149
145
  async generateAuthHeaders(request) {
150
146
  const timestamp = Date.now();
151
147
  const nonce = this.generateNonce();
152
- // Build message and generate signature
148
+ // Build message and generate Ed25519 signature
153
149
  const message = this.buildSignatureMessage(timestamp, nonce, request.method, request.params);
154
- const signature = await this.crypto.generateSignature(this.credential.secret, message);
150
+ const signature = await this.crypto.signMessage(this.keyPair.privateKey, message);
155
151
  return {
156
- 'X-Name': this.credential.name,
152
+ 'X-PublicKey': this.keyPair.publicKey,
157
153
  'X-Timestamp': timestamp.toString(),
158
154
  'X-Nonce': nonce,
159
155
  'X-Signature': signature,
@@ -167,112 +163,18 @@ export class RondevuAPI {
167
163
  return this.batcher.add(request, authHeaders);
168
164
  }
169
165
  // ============================================
170
- // Credential Management
166
+ // Identity Management (Ed25519 Public Key)
171
167
  // ============================================
172
168
  /**
173
- * Generate new credentials (name + secret pair)
174
- * This is the entry point for new users - no authentication required
175
- * Credentials are generated server-side to ensure security and uniqueness
176
- *
177
- * ⚠️ SECURITY NOTE:
178
- * - Store the returned credential securely
179
- * - The secret provides full access to this identity
180
- * - Credentials should be persisted encrypted and never logged
169
+ * Generate a new Ed25519 keypair locally
170
+ * This is completely client-side - no server communication
181
171
  *
182
- * @param baseUrl - Rondevu server URL
183
- * @param expiresAt - Optional custom expiry timestamp (defaults to 1 year)
184
- * @param options - Optional: { maxRetries: number, timeout: number }
185
- * @returns Generated credential with name and secret
186
- */
187
- static async generateCredentials(baseUrl, options) {
188
- const maxRetries = options?.maxRetries ?? RondevuAPI.DEFAULT_MAX_RETRIES;
189
- const timeout = options?.timeout ?? RondevuAPI.DEFAULT_TIMEOUT_MS;
190
- let lastError = null;
191
- // Build params object with optional name and expiresAt
192
- const params = {};
193
- if (options?.name)
194
- params.name = options.name;
195
- if (options?.expiresAt)
196
- params.expiresAt = options.expiresAt;
197
- const request = {
198
- method: 'generateCredentials',
199
- params: Object.keys(params).length > 0 ? params : undefined,
200
- };
201
- for (let attempt = 0; attempt < maxRetries; attempt++) {
202
- // httpStatus is scoped to each iteration intentionally - resets on each retry
203
- let httpStatus = null;
204
- try {
205
- // Create abort controller for timeout
206
- if (typeof AbortController === 'undefined') {
207
- throw new Error('AbortController not supported in this environment');
208
- }
209
- const controller = new AbortController();
210
- const timeoutId = setTimeout(() => controller.abort(), timeout);
211
- try {
212
- const response = await fetch(`${baseUrl}/rpc`, {
213
- method: 'POST',
214
- headers: {
215
- 'Content-Type': 'application/json',
216
- },
217
- body: JSON.stringify([request]), // Server expects array (batch format)
218
- signal: controller.signal,
219
- });
220
- httpStatus = response.status;
221
- if (!response.ok) {
222
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
223
- }
224
- // Server returns array of responses
225
- const results = await response.json();
226
- const result = results[0];
227
- if (!result || !result.success) {
228
- throw new Error(result?.error || 'Failed to generate credentials');
229
- }
230
- // Validate credential structure
231
- const credential = result.result;
232
- if (!credential || typeof credential !== 'object') {
233
- throw new Error('Invalid credential response: result is not an object');
234
- }
235
- if (typeof credential.name !== 'string' || !credential.name) {
236
- throw new Error('Invalid credential response: missing or invalid name');
237
- }
238
- if (typeof credential.secret !== 'string' || !credential.secret) {
239
- throw new Error('Invalid credential response: missing or invalid secret');
240
- }
241
- return credential;
242
- }
243
- finally {
244
- // Always clear timeout to prevent memory leaks
245
- clearTimeout(timeoutId);
246
- }
247
- }
248
- catch (error) {
249
- lastError = error;
250
- // Don't retry on abort (timeout)
251
- if (error instanceof Error && error.name === 'AbortError') {
252
- throw new Error(`Credential generation timed out after ${timeout}ms`);
253
- }
254
- // Don't retry on 4xx errors (client errors) - check actual status
255
- if (httpStatus !== null && httpStatus >= 400 && httpStatus < 500) {
256
- throw error;
257
- }
258
- // Retry with exponential backoff + jitter for network/server errors (5xx or network failures)
259
- // Jitter prevents thundering herd when many clients retry simultaneously
260
- // Cap backoff to prevent excessive waits
261
- if (attempt < maxRetries - 1) {
262
- const backoffMs = Math.min(1000 * Math.pow(2, attempt) + Math.random() * 1000, RondevuAPI.MAX_BACKOFF_MS);
263
- await new Promise(resolve => setTimeout(resolve, backoffMs));
264
- }
265
- }
266
- }
267
- throw new Error(`Failed to generate credentials after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}`);
268
- }
269
- /**
270
- * Generate a random secret locally (for advanced use cases)
271
- * @param cryptoAdapter - Optional crypto adapter
172
+ * @param cryptoAdapter - Optional crypto adapter (defaults to WebCryptoAdapter)
173
+ * @returns Generated keypair with publicKey and privateKey as hex strings
272
174
  */
273
- static generateSecret(cryptoAdapter) {
175
+ static async generateKeyPair(cryptoAdapter) {
274
176
  const adapter = cryptoAdapter || new WebCryptoAdapter();
275
- return adapter.generateSecret();
177
+ return adapter.generateKeyPair();
276
178
  }
277
179
  // ============================================
278
180
  // Tags-based Offer Management (v2)
@@ -325,11 +227,14 @@ export class RondevuAPI {
325
227
  // ============================================
326
228
  /**
327
229
  * Answer an offer
230
+ * @param offerId The offer ID to answer
231
+ * @param sdp The SDP answer
232
+ * @param matchedTags Optional tags that were used to discover this offer
328
233
  */
329
- async answerOffer(offerId, sdp) {
234
+ async answerOffer(offerId, sdp, matchedTags) {
330
235
  const request = {
331
236
  method: 'answerOffer',
332
- params: { offerId, sdp },
237
+ params: { offerId, sdp, matchedTags },
333
238
  };
334
239
  const authHeaders = await this.generateAuthHeaders(request);
335
240
  await this.rpc(request, authHeaders);
@@ -391,10 +296,7 @@ export class RondevuAPI {
391
296
  };
392
297
  }
393
298
  }
394
- // Default values for credential generation
395
- RondevuAPI.DEFAULT_MAX_RETRIES = 3;
396
- RondevuAPI.DEFAULT_TIMEOUT_MS = 30000; // 30 seconds
397
- RondevuAPI.DEFAULT_CREDENTIAL_NAME_MAX_LENGTH = 128;
398
- RondevuAPI.DEFAULT_SECRET_MIN_LENGTH = 64; // 256 bits
399
- RondevuAPI.MAX_BACKOFF_MS = 60000; // 60 seconds max backoff
299
+ // Key length constants
300
+ RondevuAPI.PUBLIC_KEY_LENGTH = 64; // 32 bytes = 64 hex chars
301
+ RondevuAPI.PRIVATE_KEY_LENGTH = 64; // 32 bytes = 64 hex chars
400
302
  RondevuAPI.MAX_CANONICALIZE_DEPTH = 100; // Prevent stack overflow
@@ -7,23 +7,25 @@ import { ConnectionConfig } from './config.js';
7
7
  import { WebRTCAdapter } from '../webrtc/adapter.js';
8
8
  export interface AnswererOptions {
9
9
  api: RondevuAPI;
10
- ownerUsername: string;
10
+ ownerPublicKey: string;
11
11
  tags: string[];
12
12
  offerId: string;
13
13
  offerSdp: string;
14
14
  rtcConfig?: RTCConfiguration;
15
15
  webrtcAdapter?: WebRTCAdapter;
16
16
  config?: Partial<ConnectionConfig>;
17
+ matchedTags?: string[];
17
18
  }
18
19
  /**
19
20
  * Answerer connection - processes offers and creates answers
20
21
  */
21
22
  export declare class AnswererConnection extends RondevuConnection {
22
23
  private api;
23
- private ownerUsername;
24
+ private ownerPublicKey;
24
25
  private tags;
25
26
  private offerId;
26
27
  private offerSdp;
28
+ private matchedTags?;
27
29
  constructor(options: AnswererOptions);
28
30
  /**
29
31
  * Initialize the connection by processing offer and creating answer
@@ -38,15 +40,15 @@ export declare class AnswererConnection extends RondevuConnection {
38
40
  */
39
41
  protected getApi(): any;
40
42
  /**
41
- * Get the owner username
43
+ * Get the owner public key (implements abstract method)
42
44
  */
43
- protected getOwnerUsername(): string;
45
+ protected getOwnerPublicKey(): string;
44
46
  /**
45
47
  * Answerers accept ICE candidates from offerers only
46
48
  */
47
49
  protected getIceCandidateRole(): 'offerer' | null;
48
50
  /**
49
- * Attempt to reconnect to the same user
51
+ * Attempt to reconnect to the same peer
50
52
  */
51
53
  protected attemptReconnect(): void;
52
54
  /**
@@ -10,10 +10,11 @@ export class AnswererConnection extends RondevuConnection {
10
10
  constructor(options) {
11
11
  super(options.rtcConfig, options.config, options.webrtcAdapter);
12
12
  this.api = options.api;
13
- this.ownerUsername = options.ownerUsername;
13
+ this.ownerPublicKey = options.ownerPublicKey;
14
14
  this.tags = options.tags;
15
15
  this.offerId = options.offerId;
16
16
  this.offerSdp = options.offerSdp;
17
+ this.matchedTags = options.matchedTags;
17
18
  }
18
19
  /**
19
20
  * Initialize the connection by processing offer and creating answer
@@ -43,8 +44,8 @@ export class AnswererConnection extends RondevuConnection {
43
44
  const answer = await this.pc.createAnswer();
44
45
  await this.pc.setLocalDescription(answer);
45
46
  this.debug('Answer created, sending to server');
46
- // Send answer to server
47
- await this.api.answerOffer(this.offerId, answer.sdp);
47
+ // Send answer to server (including matched tags so offerer knows which tags we searched for)
48
+ await this.api.answerOffer(this.offerId, answer.sdp, this.matchedTags);
48
49
  // Note: ICE candidate polling is handled by PollingManager
49
50
  // Candidates are received via handleRemoteIceCandidates()
50
51
  this.debug('Answer sent successfully');
@@ -75,10 +76,10 @@ export class AnswererConnection extends RondevuConnection {
75
76
  return this.api;
76
77
  }
77
78
  /**
78
- * Get the owner username
79
+ * Get the owner public key (implements abstract method)
79
80
  */
80
- getOwnerUsername() {
81
- return this.ownerUsername;
81
+ getOwnerPublicKey() {
82
+ return this.ownerPublicKey;
82
83
  }
83
84
  /**
84
85
  * Answerers accept ICE candidates from offerers only
@@ -87,11 +88,11 @@ export class AnswererConnection extends RondevuConnection {
87
88
  return 'offerer';
88
89
  }
89
90
  /**
90
- * Attempt to reconnect to the same user
91
+ * Attempt to reconnect to the same peer
91
92
  */
92
93
  attemptReconnect() {
93
- this.debug(`Attempting to reconnect to ${this.ownerUsername}`);
94
- // For answerer, we need to fetch a new offer from the same user
94
+ this.debug(`Attempting to reconnect to ${this.ownerPublicKey}`);
95
+ // For answerer, we need to fetch a new offer from the same peer
95
96
  // Clean up old connection
96
97
  if (this.pc) {
97
98
  this.pc.close();
@@ -109,16 +110,16 @@ export class AnswererConnection extends RondevuConnection {
109
110
  if (!response || !response.offers || response.offers.length === 0) {
110
111
  throw new Error('No offers available for reconnection');
111
112
  }
112
- // Filter for offers from the same user
113
- const userOffers = response.offers.filter(o => o.username === this.ownerUsername);
114
- if (userOffers.length === 0) {
115
- throw new Error(`No offers available from ${this.ownerUsername}`);
113
+ // Filter for offers from the same peer
114
+ const peerOffers = response.offers.filter(o => o.publicKey === this.ownerPublicKey);
115
+ if (peerOffers.length === 0) {
116
+ throw new Error(`No offers available from ${this.ownerPublicKey}`);
116
117
  }
117
- // Pick a random offer from the same user
118
- const offer = userOffers[Math.floor(Math.random() * userOffers.length)];
118
+ // Pick a random offer from the same peer
119
+ const offer = peerOffers[Math.floor(Math.random() * peerOffers.length)];
119
120
  this.offerId = offer.offerId;
120
121
  this.offerSdp = offer.sdp;
121
- this.debug(`Found new offer ${offer.offerId} from ${this.ownerUsername}`);
122
+ this.debug(`Found new offer ${offer.offerId} from ${this.ownerPublicKey}`);
122
123
  // Reinitialize with new offer
123
124
  return this.initialize();
124
125
  })
@@ -89,9 +89,9 @@ export declare abstract class RondevuConnection extends EventEmitter<ConnectionE
89
89
  */
90
90
  protected abstract getApi(): any;
91
91
  /**
92
- * Get the owner username - subclasses must provide
92
+ * Get the owner public key - subclasses must provide
93
93
  */
94
- protected abstract getOwnerUsername(): string;
94
+ protected abstract getOwnerPublicKey(): string;
95
95
  /**
96
96
  * Get the offer ID - subclasses must provide
97
97
  */