@xtr-dev/rondevu-client 0.12.3 → 0.13.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.
@@ -1,49 +1,70 @@
1
1
  import { RondevuAPI } from './api.js';
2
2
  /**
3
- * RondevuService - High-level service management with automatic signature handling
3
+ * Rondevu - Complete WebRTC signaling client
4
4
  *
5
- * Provides a simplified API for:
5
+ * Provides a unified API for:
6
6
  * - Username claiming with Ed25519 signatures
7
7
  * - Service publishing with automatic signature generation
8
+ * - Service discovery (direct, random, paginated)
9
+ * - WebRTC signaling (offer/answer exchange, ICE relay)
8
10
  * - Keypair management
9
11
  *
10
12
  * @example
11
13
  * ```typescript
12
- * // Initialize service (generates keypair automatically)
13
- * const service = new RondevuService({
14
+ * // Initialize (generates keypair automatically)
15
+ * const rondevu = new Rondevu({
14
16
  * apiUrl: 'https://signal.example.com',
15
- * username: 'myusername',
17
+ * username: 'alice',
16
18
  * })
17
19
  *
18
- * await service.initialize()
20
+ * await rondevu.initialize()
19
21
  *
20
22
  * // Claim username (one time)
21
- * await service.claimUsername()
23
+ * await rondevu.claimUsername()
22
24
  *
23
25
  * // Publish a service
24
- * const publishedService = await service.publishService({
25
- * serviceFqn: 'chat.app@1.0.0',
26
+ * const publishedService = await rondevu.publishService({
27
+ * serviceFqn: 'chat:1.0.0@alice',
26
28
  * offers: [{ sdp: offerSdp }],
27
29
  * ttl: 300000,
28
- * isPublic: true,
29
30
  * })
31
+ *
32
+ * // Discover a service
33
+ * const service = await rondevu.getService('chat:1.0.0@bob')
34
+ *
35
+ * // Post answer
36
+ * await rondevu.postOfferAnswer(service.serviceFqn, service.offerId, answerSdp)
30
37
  * ```
31
38
  */
32
- export class RondevuService {
39
+ export class Rondevu {
33
40
  constructor(options) {
34
41
  this.keypair = null;
35
42
  this.usernameClaimed = false;
36
43
  this.username = options.username;
37
44
  this.keypair = options.keypair || null;
38
45
  this.api = new RondevuAPI(options.apiUrl, options.credentials);
46
+ console.log('[Rondevu] Constructor called:', {
47
+ username: this.username,
48
+ hasKeypair: !!this.keypair,
49
+ publicKey: this.keypair?.publicKey
50
+ });
39
51
  }
52
+ // ============================================
53
+ // Initialization
54
+ // ============================================
40
55
  /**
41
56
  * Initialize the service - generates keypair if not provided
42
57
  * Call this before using other methods
43
58
  */
44
59
  async initialize() {
60
+ console.log('[Rondevu] Initialize called, hasKeypair:', !!this.keypair);
45
61
  if (!this.keypair) {
62
+ console.log('[Rondevu] Generating new keypair...');
46
63
  this.keypair = await RondevuAPI.generateKeypair();
64
+ console.log('[Rondevu] Generated keypair, publicKey:', this.keypair.publicKey);
65
+ }
66
+ else {
67
+ console.log('[Rondevu] Using existing keypair, publicKey:', this.keypair.publicKey);
47
68
  }
48
69
  // Register with API if no credentials provided
49
70
  if (!this.api['credentials']) {
@@ -51,13 +72,16 @@ export class RondevuService {
51
72
  this.api.setCredentials(credentials);
52
73
  }
53
74
  }
75
+ // ============================================
76
+ // Username Management
77
+ // ============================================
54
78
  /**
55
79
  * Claim the username with Ed25519 signature
56
80
  * Should be called once before publishing services
57
81
  */
58
82
  async claimUsername() {
59
83
  if (!this.keypair) {
60
- throw new Error('Service not initialized. Call initialize() first.');
84
+ throw new Error('Not initialized. Call initialize() first.');
61
85
  }
62
86
  // Check if username is already claimed
63
87
  const check = await this.api.checkUsername(this.username);
@@ -76,34 +100,116 @@ export class RondevuService {
76
100
  await this.api.claimUsername(this.username, this.keypair.publicKey, signature, message);
77
101
  this.usernameClaimed = true;
78
102
  }
103
+ /**
104
+ * Check if username has been claimed (checks with server)
105
+ */
106
+ async isUsernameClaimed() {
107
+ if (!this.keypair) {
108
+ return false;
109
+ }
110
+ try {
111
+ const check = await this.api.checkUsername(this.username);
112
+ // Debug logging
113
+ console.log('[Rondevu] Username check:', {
114
+ username: this.username,
115
+ available: check.available,
116
+ serverPublicKey: check.publicKey,
117
+ localPublicKey: this.keypair.publicKey,
118
+ match: check.publicKey === this.keypair.publicKey
119
+ });
120
+ // Username is claimed if it's not available and owned by our public key
121
+ const claimed = !check.available && check.publicKey === this.keypair.publicKey;
122
+ // Update internal flag to match server state
123
+ this.usernameClaimed = claimed;
124
+ return claimed;
125
+ }
126
+ catch (err) {
127
+ console.error('Failed to check username claim status:', err);
128
+ return false;
129
+ }
130
+ }
131
+ // ============================================
132
+ // Service Publishing
133
+ // ============================================
79
134
  /**
80
135
  * Publish a service with automatic signature generation
81
136
  */
82
137
  async publishService(options) {
83
138
  if (!this.keypair) {
84
- throw new Error('Service not initialized. Call initialize() first.');
139
+ throw new Error('Not initialized. Call initialize() first.');
85
140
  }
86
141
  if (!this.usernameClaimed) {
87
142
  throw new Error('Username not claimed. Call claimUsername() first or the server will reject the service.');
88
143
  }
89
- const { serviceFqn, offers, ttl, isPublic, metadata } = options;
144
+ const { serviceFqn, offers, ttl } = options;
90
145
  // Generate signature for service publication
91
146
  const message = `publish:${this.username}:${serviceFqn}:${Date.now()}`;
92
147
  const signature = await RondevuAPI.signMessage(message, this.keypair.privateKey);
93
148
  // Create service request
94
149
  const serviceRequest = {
95
- username: this.username,
96
- serviceFqn,
150
+ serviceFqn, // Must include @username
97
151
  offers,
98
152
  signature,
99
153
  message,
100
154
  ttl,
101
- isPublic,
102
- metadata,
103
155
  };
104
156
  // Publish to server
105
157
  return await this.api.publishService(serviceRequest);
106
158
  }
159
+ // ============================================
160
+ // Service Discovery
161
+ // ============================================
162
+ /**
163
+ * Get service by FQN (with username) - Direct lookup
164
+ * Example: chat:1.0.0@alice
165
+ */
166
+ async getService(serviceFqn) {
167
+ return await this.api.getService(serviceFqn);
168
+ }
169
+ /**
170
+ * Discover a random available service without knowing the username
171
+ * Example: chat:1.0.0 (without @username)
172
+ */
173
+ async discoverService(serviceVersion) {
174
+ return await this.api.discoverService(serviceVersion);
175
+ }
176
+ /**
177
+ * Discover multiple available services with pagination
178
+ * Example: chat:1.0.0 (without @username)
179
+ */
180
+ async discoverServices(serviceVersion, limit = 10, offset = 0) {
181
+ return await this.api.discoverServices(serviceVersion, limit, offset);
182
+ }
183
+ // ============================================
184
+ // WebRTC Signaling
185
+ // ============================================
186
+ /**
187
+ * Post answer SDP to specific offer
188
+ */
189
+ async postOfferAnswer(serviceFqn, offerId, sdp) {
190
+ return await this.api.postOfferAnswer(serviceFqn, offerId, sdp);
191
+ }
192
+ /**
193
+ * Get answer SDP (offerer polls this)
194
+ */
195
+ async getOfferAnswer(serviceFqn, offerId) {
196
+ return await this.api.getOfferAnswer(serviceFqn, offerId);
197
+ }
198
+ /**
199
+ * Add ICE candidates to specific offer
200
+ */
201
+ async addOfferIceCandidates(serviceFqn, offerId, candidates) {
202
+ return await this.api.addOfferIceCandidates(serviceFqn, offerId, candidates);
203
+ }
204
+ /**
205
+ * Get ICE candidates for specific offer (with polling support)
206
+ */
207
+ async getOfferIceCandidates(serviceFqn, offerId, since = 0) {
208
+ return await this.api.getOfferIceCandidates(serviceFqn, offerId, since);
209
+ }
210
+ // ============================================
211
+ // Utility Methods
212
+ // ============================================
107
213
  /**
108
214
  * Get the current keypair (for backup/storage)
109
215
  */
@@ -122,36 +228,9 @@ export class RondevuService {
122
228
  getPublicKey() {
123
229
  return this.keypair?.publicKey || null;
124
230
  }
125
- /**
126
- * Check if username has been claimed (checks with server)
127
- */
128
- async isUsernameClaimed() {
129
- if (!this.keypair) {
130
- return false;
131
- }
132
- try {
133
- const check = await this.api.checkUsername(this.username);
134
- // Debug logging
135
- console.log('[RondevuService] Username check:', {
136
- username: this.username,
137
- available: check.available,
138
- serverPublicKey: check.publicKey,
139
- localPublicKey: this.keypair.publicKey,
140
- match: check.publicKey === this.keypair.publicKey
141
- });
142
- // Username is claimed if it's not available and owned by our public key
143
- const claimed = !check.available && check.publicKey === this.keypair.publicKey;
144
- // Update internal flag to match server state
145
- this.usernameClaimed = claimed;
146
- return claimed;
147
- }
148
- catch (err) {
149
- console.error('Failed to check username claim status:', err);
150
- return false;
151
- }
152
- }
153
231
  /**
154
232
  * Access to underlying API for advanced operations
233
+ * @deprecated Use direct methods on Rondevu instance instead
155
234
  */
156
235
  getAPI() {
157
236
  return this.api;
package/dist/types.d.ts CHANGED
@@ -1,26 +1,13 @@
1
1
  /**
2
- * Core connection types
2
+ * Core signaling types
3
+ */
4
+ /**
5
+ * Cleanup function returned by listener methods
6
+ */
7
+ export type Binnable = () => void;
8
+ /**
9
+ * Signaler interface for WebRTC offer/answer/ICE exchange
3
10
  */
4
- import { EventBus } from './event-bus.js';
5
- import { Binnable } from './bin.js';
6
- export type Message = string | ArrayBuffer;
7
- export interface QueueMessageOptions {
8
- expiresAt?: number;
9
- }
10
- export interface ConnectionEvents {
11
- 'state-change': ConnectionInterface['state'];
12
- message: Message;
13
- }
14
- export declare const ConnectionStates: readonly ["connected", "disconnected", "connecting"];
15
- export declare const isConnectionState: (state: string) => state is (typeof ConnectionStates)[number];
16
- export interface ConnectionInterface {
17
- state: (typeof ConnectionStates)[number];
18
- lastActive: number;
19
- expiresAt?: number;
20
- events: EventBus<ConnectionEvents>;
21
- queueMessage(message: Message, options?: QueueMessageOptions): Promise<void>;
22
- sendMessage(message: Message): Promise<boolean>;
23
- }
24
11
  export interface Signaler {
25
12
  addIceCandidate(candidate: RTCIceCandidate): Promise<void>;
26
13
  addListener(callback: (candidate: RTCIceCandidate) => void): Binnable;
package/dist/types.js CHANGED
@@ -1,6 +1,4 @@
1
- export const ConnectionStates = [
2
- 'connected',
3
- 'disconnected',
4
- 'connecting'
5
- ];
6
- export const isConnectionState = (state) => ConnectionStates.includes(state);
1
+ /**
2
+ * Core signaling types
3
+ */
4
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtr-dev/rondevu-client",
3
- "version": "0.12.3",
3
+ "version": "0.13.0",
4
4
  "description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/dist/bin.d.ts DELETED
@@ -1,35 +0,0 @@
1
- /**
2
- * Binnable - A cleanup function that can be synchronous or asynchronous
3
- *
4
- * Used to unsubscribe from events, close connections, or perform other cleanup operations.
5
- */
6
- export type Binnable = () => void | Promise<void>;
7
- /**
8
- * Create a cleanup function collector (garbage bin)
9
- *
10
- * Collects cleanup functions and provides a single `clean()` method to execute all of them.
11
- * Useful for managing multiple cleanup operations in a single place.
12
- *
13
- * @returns A function that accepts cleanup functions and has a `clean()` method
14
- *
15
- * @example
16
- * ```typescript
17
- * const bin = createBin();
18
- *
19
- * // Add cleanup functions
20
- * bin(
21
- * () => console.log('Cleanup 1'),
22
- * () => connection.close(),
23
- * () => clearInterval(timer)
24
- * );
25
- *
26
- * // Later, clean everything
27
- * bin.clean(); // Executes all cleanup functions
28
- * ```
29
- */
30
- export declare const createBin: () => ((...rubbish: Binnable[]) => number) & {
31
- /**
32
- * Execute all cleanup functions and clear the bin
33
- */
34
- clean: () => void;
35
- };
package/dist/bin.js DELETED
@@ -1,35 +0,0 @@
1
- /**
2
- * Create a cleanup function collector (garbage bin)
3
- *
4
- * Collects cleanup functions and provides a single `clean()` method to execute all of them.
5
- * Useful for managing multiple cleanup operations in a single place.
6
- *
7
- * @returns A function that accepts cleanup functions and has a `clean()` method
8
- *
9
- * @example
10
- * ```typescript
11
- * const bin = createBin();
12
- *
13
- * // Add cleanup functions
14
- * bin(
15
- * () => console.log('Cleanup 1'),
16
- * () => connection.close(),
17
- * () => clearInterval(timer)
18
- * );
19
- *
20
- * // Later, clean everything
21
- * bin.clean(); // Executes all cleanup functions
22
- * ```
23
- */
24
- export const createBin = () => {
25
- const bin = [];
26
- return Object.assign((...rubbish) => bin.push(...rubbish), {
27
- /**
28
- * Execute all cleanup functions and clear the bin
29
- */
30
- clean: () => {
31
- bin.forEach(binnable => binnable());
32
- bin.length = 0;
33
- },
34
- });
35
- };
@@ -1,104 +0,0 @@
1
- import { WebRTCRondevuConnection } from './connection.js';
2
- import { Credentials } from './api.js';
3
- import { ConnectionInterface } from './types.js';
4
- export interface ConnectionManagerOptions {
5
- apiUrl: string;
6
- username: string;
7
- credentials?: Credentials;
8
- autoReconnect?: boolean;
9
- reconnectDelay?: number;
10
- maxReconnectAttempts?: number;
11
- }
12
- export interface HostServiceOptions {
13
- service: string;
14
- ttl?: number;
15
- onConnection?: (connection: ConnectionInterface) => void;
16
- }
17
- export interface ConnectToServiceOptions {
18
- username: string;
19
- service: string;
20
- onConnection?: (connection: ConnectionInterface) => void;
21
- }
22
- /**
23
- * ConnectionManager - High-level connection management
24
- *
25
- * @example
26
- * // Host a service
27
- * const manager = new ConnectionManager({
28
- * apiUrl: 'https://api.ronde.vu',
29
- * credentials: await api.register()
30
- * })
31
- *
32
- * await manager.hostService({
33
- * service: 'chat.app@1.0.0',
34
- * onConnection: (conn) => {
35
- * conn.events.on('message', msg => console.log('Received:', msg))
36
- * }
37
- * })
38
- *
39
- * @example
40
- * // Connect to a service
41
- * const connection = await manager.connectToService({
42
- * username: 'alice',
43
- * service: 'chat.app@1.0.0'
44
- * })
45
- *
46
- * await connection.sendMessage('Hello!')
47
- */
48
- export declare class ConnectionManager {
49
- private readonly api;
50
- private readonly username;
51
- private readonly connections;
52
- private readonly autoReconnect;
53
- private readonly reconnectDelay;
54
- private readonly maxReconnectAttempts;
55
- private readonly bin;
56
- constructor(options: ConnectionManagerOptions);
57
- /**
58
- * Host a service - Creates an offer and publishes it to the signaling server
59
- *
60
- * The service will automatically accept incoming connections and manage them.
61
- * Each new connection triggers the onConnection callback.
62
- *
63
- * @param options - Service hosting options
64
- * @returns Promise that resolves when the service is published
65
- */
66
- hostService(options: HostServiceOptions): Promise<void>;
67
- /**
68
- * Connect to a hosted service
69
- *
70
- * Searches for the service, retrieves the offer, and creates an answering connection.
71
- *
72
- * @param options - Connection options
73
- * @returns The established connection
74
- */
75
- connectToService(options: ConnectToServiceOptions): Promise<WebRTCRondevuConnection>;
76
- /**
77
- * Get a connection by ID
78
- */
79
- getConnection(id: string): WebRTCRondevuConnection | undefined;
80
- /**
81
- * Get all managed connections
82
- */
83
- getAllConnections(): WebRTCRondevuConnection[];
84
- /**
85
- * Remove a connection
86
- */
87
- private removeConnection;
88
- /**
89
- * Schedule reconnection for a failed connection
90
- */
91
- private scheduleReconnect;
92
- /**
93
- * Attempt to reconnect a failed connection
94
- */
95
- private attemptReconnect;
96
- /**
97
- * Create a temporary signaler (used during initial offer creation)
98
- */
99
- private createTempSignaler;
100
- /**
101
- * Clean up all connections and resources
102
- */
103
- destroy(): void;
104
- }