@xtr-dev/rondevu-client 0.8.3 → 0.9.2

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.
@@ -31,7 +31,7 @@ export default class RondevuPeer extends EventEmitter {
31
31
  { urls: 'stun:stun.l.google.com:19302' },
32
32
  { urls: 'stun:stun1.l.google.com:19302' }
33
33
  ]
34
- }, rtcPeerConnection, rtcSessionDescription, rtcIceCandidate) {
34
+ }, existingPeerConnection, rtcPeerConnection, rtcSessionDescription, rtcIceCandidate) {
35
35
  super();
36
36
  this.offersApi = offersApi;
37
37
  // Use provided polyfills or fall back to globals
@@ -50,7 +50,8 @@ export default class RondevuPeer extends EventEmitter {
50
50
  : (() => {
51
51
  throw new Error('RTCIceCandidate is not available. Please provide it in the Rondevu constructor options for Node.js environments.');
52
52
  }));
53
- this.pc = new this.RTCPeerConnection(rtcConfig);
53
+ // Use existing peer connection if provided, otherwise create new one
54
+ this.pc = existingPeerConnection || new this.RTCPeerConnection(rtcConfig);
54
55
  this._state = new IdleState(this);
55
56
  this.setupPeerConnection();
56
57
  }
@@ -59,24 +60,39 @@ export default class RondevuPeer extends EventEmitter {
59
60
  */
60
61
  setupPeerConnection() {
61
62
  this.connectionStateChangeHandler = () => {
63
+ console.log(`🔌 Connection state changed: ${this.pc.connectionState}`);
62
64
  switch (this.pc.connectionState) {
63
65
  case 'connected':
66
+ console.log('✅ WebRTC connection established');
64
67
  this.setState(new ConnectedState(this));
65
68
  this.emitEvent('connected');
66
69
  break;
67
70
  case 'disconnected':
71
+ console.log('⚠️ WebRTC connection disconnected');
68
72
  this.emitEvent('disconnected');
69
73
  break;
70
74
  case 'failed':
75
+ console.log('❌ WebRTC connection failed');
71
76
  this.setState(new FailedState(this, new Error('Connection failed')));
72
77
  break;
73
78
  case 'closed':
79
+ console.log('🔒 WebRTC connection closed');
74
80
  this.setState(new ClosedState(this));
75
81
  this.emitEvent('disconnected');
76
82
  break;
77
83
  }
78
84
  };
79
85
  this.pc.addEventListener('connectionstatechange', this.connectionStateChangeHandler);
86
+ // Add ICE connection state logging
87
+ const iceConnectionStateHandler = () => {
88
+ console.log(`🧊 ICE connection state: ${this.pc.iceConnectionState}`);
89
+ };
90
+ this.pc.addEventListener('iceconnectionstatechange', iceConnectionStateHandler);
91
+ // Add ICE gathering state logging
92
+ const iceGatheringStateHandler = () => {
93
+ console.log(`🔍 ICE gathering state: ${this.pc.iceGatheringState}`);
94
+ };
95
+ this.pc.addEventListener('icegatheringstatechange', iceGatheringStateHandler);
80
96
  this.dataChannelHandler = (event) => {
81
97
  this.emitEvent('datachannel', event.channel);
82
98
  };
@@ -86,7 +102,13 @@ export default class RondevuPeer extends EventEmitter {
86
102
  };
87
103
  this.pc.addEventListener('track', this.trackHandler);
88
104
  this.iceCandidateErrorHandler = (event) => {
89
- console.error('ICE candidate error:', event);
105
+ const iceError = event;
106
+ console.error(`❌ ICE candidate error: ${iceError.errorText || 'Unknown error'}`, {
107
+ errorCode: iceError.errorCode,
108
+ url: iceError.url,
109
+ address: iceError.address,
110
+ port: iceError.port
111
+ });
90
112
  };
91
113
  this.pc.addEventListener('icecandidateerror', this.iceCandidateErrorHandler);
92
114
  }
@@ -30,14 +30,22 @@ export class PeerState {
30
30
  if (event.candidate && this.peer.offerId) {
31
31
  const candidateData = event.candidate.toJSON();
32
32
  if (candidateData.candidate && candidateData.candidate !== '') {
33
+ const type = candidateData.candidate.includes('typ host') ? 'host' :
34
+ candidateData.candidate.includes('typ srflx') ? 'srflx' :
35
+ candidateData.candidate.includes('typ relay') ? 'relay' : 'unknown';
36
+ console.log(`🧊 Generated ${type} ICE candidate:`, candidateData.candidate);
33
37
  try {
34
38
  await this.peer.offersApi.addIceCandidates(this.peer.offerId, [candidateData]);
39
+ console.log(`✅ Sent ${type} ICE candidate to server`);
35
40
  }
36
41
  catch (err) {
37
- console.error('Error sending ICE candidate:', err);
42
+ console.error(`❌ Error sending ${type} ICE candidate:`, err);
38
43
  }
39
44
  }
40
45
  }
46
+ else if (!event.candidate) {
47
+ console.log('🧊 ICE gathering complete (null candidate)');
48
+ }
41
49
  };
42
50
  this.peer.pc.addEventListener('icecandidate', this.iceCandidateHandler);
43
51
  }
package/dist/rondevu.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { RondevuAuth, Credentials, FetchFunction } from './auth.js';
2
2
  import { RondevuOffers } from './offers.js';
3
3
  import { RondevuUsername } from './usernames.js';
4
- import { RondevuServices } from './services.js';
5
- import { RondevuDiscovery } from './discovery.js';
6
4
  import RondevuPeer from './peer/index.js';
5
+ import { DurableService } from './durable/service.js';
6
+ import { DurableConnection } from './durable/connection.js';
7
+ import { DurableChannel } from './durable/channel.js';
8
+ import type { DurableServiceConfig, DurableConnectionConfig } from './durable/types.js';
7
9
  export interface RondevuOptions {
8
10
  /**
9
11
  * Base URL of the Rondevu server
@@ -63,8 +65,6 @@ export declare class Rondevu {
63
65
  readonly auth: RondevuAuth;
64
66
  readonly usernames: RondevuUsername;
65
67
  private _offers?;
66
- private _services?;
67
- private _discovery?;
68
68
  private credentials?;
69
69
  private baseUrl;
70
70
  private fetchFn?;
@@ -74,17 +74,9 @@ export declare class Rondevu {
74
74
  constructor(options?: RondevuOptions);
75
75
  /**
76
76
  * Get offers API (low-level access, requires authentication)
77
- * For most use cases, use services and discovery APIs instead
77
+ * For most use cases, use the durable connection APIs instead
78
78
  */
79
79
  get offers(): RondevuOffers;
80
- /**
81
- * Get services API (requires authentication)
82
- */
83
- get services(): RondevuServices;
84
- /**
85
- * Get discovery API (requires authentication)
86
- */
87
- get discovery(): RondevuDiscovery;
88
80
  /**
89
81
  * Register and initialize authenticated client
90
82
  * Generates a cryptographically random peer ID (128-bit)
@@ -106,4 +98,87 @@ export declare class Rondevu {
106
98
  * @returns RondevuPeer instance
107
99
  */
108
100
  createPeer(rtcConfig?: RTCConfiguration): RondevuPeer;
101
+ /**
102
+ * Expose a durable service with automatic reconnection and TTL refresh
103
+ *
104
+ * Creates a service that handles incoming connections with automatic
105
+ * reconnection and message queuing during network interruptions.
106
+ *
107
+ * @param config Service configuration
108
+ * @returns DurableService instance
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * const service = await client.exposeService({
113
+ * username: 'alice',
114
+ * privateKey: keypair.privateKey,
115
+ * serviceFqn: 'chat@1.0.0',
116
+ * poolSize: 10,
117
+ * handler: (channel, connectionId) => {
118
+ * channel.on('message', (data) => {
119
+ * console.log('Received:', data);
120
+ * channel.send(`Echo: ${data}`);
121
+ * });
122
+ * }
123
+ * });
124
+ *
125
+ * await service.start();
126
+ * ```
127
+ */
128
+ exposeService(config: DurableServiceConfig & {
129
+ handler: (channel: DurableChannel, connectionId: string) => void | Promise<void>;
130
+ }): Promise<DurableService>;
131
+ /**
132
+ * Create a durable connection to a service by username and service FQN
133
+ *
134
+ * Establishes a WebRTC connection with automatic reconnection and
135
+ * message queuing during network interruptions.
136
+ *
137
+ * @param username Username of the service provider
138
+ * @param serviceFqn Fully qualified service name
139
+ * @param config Optional connection configuration
140
+ * @returns DurableConnection instance
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * const connection = await client.connect('alice', 'chat@1.0.0', {
145
+ * maxReconnectAttempts: 5
146
+ * });
147
+ *
148
+ * const channel = connection.createChannel('main');
149
+ * channel.on('message', (data) => {
150
+ * console.log('Received:', data);
151
+ * });
152
+ *
153
+ * await connection.connect();
154
+ * channel.send('Hello!');
155
+ * ```
156
+ */
157
+ connect(username: string, serviceFqn: string, config?: DurableConnectionConfig): Promise<DurableConnection>;
158
+ /**
159
+ * Create a durable connection to a service by UUID
160
+ *
161
+ * Establishes a WebRTC connection with automatic reconnection and
162
+ * message queuing during network interruptions.
163
+ *
164
+ * @param uuid Service UUID
165
+ * @param config Optional connection configuration
166
+ * @returns DurableConnection instance
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const connection = await client.connectByUuid('service-uuid-here', {
171
+ * maxReconnectAttempts: 5
172
+ * });
173
+ *
174
+ * const channel = connection.createChannel('main');
175
+ * channel.on('message', (data) => {
176
+ * console.log('Received:', data);
177
+ * });
178
+ *
179
+ * await connection.connect();
180
+ * channel.send('Hello!');
181
+ * ```
182
+ */
183
+ connectByUuid(uuid: string, config?: DurableConnectionConfig): Promise<DurableConnection>;
109
184
  }
package/dist/rondevu.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { RondevuAuth } from './auth.js';
2
2
  import { RondevuOffers } from './offers.js';
3
3
  import { RondevuUsername } from './usernames.js';
4
- import { RondevuServices } from './services.js';
5
- import { RondevuDiscovery } from './discovery.js';
6
4
  import RondevuPeer from './peer/index.js';
5
+ import { DurableService } from './durable/service.js';
6
+ import { DurableConnection } from './durable/connection.js';
7
7
  export class Rondevu {
8
8
  constructor(options = {}) {
9
9
  this.baseUrl = options.baseUrl || 'https://api.ronde.vu';
@@ -16,13 +16,11 @@ export class Rondevu {
16
16
  if (options.credentials) {
17
17
  this.credentials = options.credentials;
18
18
  this._offers = new RondevuOffers(this.baseUrl, this.credentials, this.fetchFn);
19
- this._services = new RondevuServices(this.baseUrl, this.credentials);
20
- this._discovery = new RondevuDiscovery(this.baseUrl, this.credentials);
21
19
  }
22
20
  }
23
21
  /**
24
22
  * Get offers API (low-level access, requires authentication)
25
- * For most use cases, use services and discovery APIs instead
23
+ * For most use cases, use the durable connection APIs instead
26
24
  */
27
25
  get offers() {
28
26
  if (!this._offers) {
@@ -30,34 +28,14 @@ export class Rondevu {
30
28
  }
31
29
  return this._offers;
32
30
  }
33
- /**
34
- * Get services API (requires authentication)
35
- */
36
- get services() {
37
- if (!this._services) {
38
- throw new Error('Not authenticated. Call register() first or provide credentials.');
39
- }
40
- return this._services;
41
- }
42
- /**
43
- * Get discovery API (requires authentication)
44
- */
45
- get discovery() {
46
- if (!this._discovery) {
47
- throw new Error('Not authenticated. Call register() first or provide credentials.');
48
- }
49
- return this._discovery;
50
- }
51
31
  /**
52
32
  * Register and initialize authenticated client
53
33
  * Generates a cryptographically random peer ID (128-bit)
54
34
  */
55
35
  async register() {
56
36
  this.credentials = await this.auth.register();
57
- // Create API instances
37
+ // Create offers API instance
58
38
  this._offers = new RondevuOffers(this.baseUrl, this.credentials, this.fetchFn);
59
- this._services = new RondevuServices(this.baseUrl, this.credentials);
60
- this._discovery = new RondevuDiscovery(this.baseUrl, this.credentials);
61
39
  return this.credentials;
62
40
  }
63
41
  /**
@@ -83,6 +61,111 @@ export class Rondevu {
83
61
  if (!this._offers) {
84
62
  throw new Error('Not authenticated. Call register() first or provide credentials.');
85
63
  }
86
- return new RondevuPeer(this._offers, rtcConfig, this.rtcPeerConnection, this.rtcSessionDescription, this.rtcIceCandidate);
64
+ return new RondevuPeer(this._offers, rtcConfig, undefined, // No existing peer connection
65
+ this.rtcPeerConnection, this.rtcSessionDescription, this.rtcIceCandidate);
66
+ }
67
+ /**
68
+ * Expose a durable service with automatic reconnection and TTL refresh
69
+ *
70
+ * Creates a service that handles incoming connections with automatic
71
+ * reconnection and message queuing during network interruptions.
72
+ *
73
+ * @param config Service configuration
74
+ * @returns DurableService instance
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const service = await client.exposeService({
79
+ * username: 'alice',
80
+ * privateKey: keypair.privateKey,
81
+ * serviceFqn: 'chat@1.0.0',
82
+ * poolSize: 10,
83
+ * handler: (channel, connectionId) => {
84
+ * channel.on('message', (data) => {
85
+ * console.log('Received:', data);
86
+ * channel.send(`Echo: ${data}`);
87
+ * });
88
+ * }
89
+ * });
90
+ *
91
+ * await service.start();
92
+ * ```
93
+ */
94
+ async exposeService(config) {
95
+ if (!this._offers || !this.credentials) {
96
+ throw new Error('Not authenticated. Call register() first or provide credentials.');
97
+ }
98
+ const service = new DurableService(this._offers, this.baseUrl, this.credentials, config.handler, config);
99
+ return service;
100
+ }
101
+ /**
102
+ * Create a durable connection to a service by username and service FQN
103
+ *
104
+ * Establishes a WebRTC connection with automatic reconnection and
105
+ * message queuing during network interruptions.
106
+ *
107
+ * @param username Username of the service provider
108
+ * @param serviceFqn Fully qualified service name
109
+ * @param config Optional connection configuration
110
+ * @returns DurableConnection instance
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const connection = await client.connect('alice', 'chat@1.0.0', {
115
+ * maxReconnectAttempts: 5
116
+ * });
117
+ *
118
+ * const channel = connection.createChannel('main');
119
+ * channel.on('message', (data) => {
120
+ * console.log('Received:', data);
121
+ * });
122
+ *
123
+ * await connection.connect();
124
+ * channel.send('Hello!');
125
+ * ```
126
+ */
127
+ async connect(username, serviceFqn, config) {
128
+ if (!this._offers) {
129
+ throw new Error('Not authenticated. Call register() first or provide credentials.');
130
+ }
131
+ const connectionInfo = {
132
+ username,
133
+ serviceFqn
134
+ };
135
+ return new DurableConnection(this._offers, connectionInfo, config);
136
+ }
137
+ /**
138
+ * Create a durable connection to a service by UUID
139
+ *
140
+ * Establishes a WebRTC connection with automatic reconnection and
141
+ * message queuing during network interruptions.
142
+ *
143
+ * @param uuid Service UUID
144
+ * @param config Optional connection configuration
145
+ * @returns DurableConnection instance
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * const connection = await client.connectByUuid('service-uuid-here', {
150
+ * maxReconnectAttempts: 5
151
+ * });
152
+ *
153
+ * const channel = connection.createChannel('main');
154
+ * channel.on('message', (data) => {
155
+ * console.log('Received:', data);
156
+ * });
157
+ *
158
+ * await connection.connect();
159
+ * channel.send('Hello!');
160
+ * ```
161
+ */
162
+ async connectByUuid(uuid, config) {
163
+ if (!this._offers) {
164
+ throw new Error('Not authenticated. Call register() first or provide credentials.');
165
+ }
166
+ const connectionInfo = {
167
+ uuid
168
+ };
169
+ return new DurableConnection(this._offers, connectionInfo, config);
87
170
  }
88
171
  }
@@ -1,5 +1,4 @@
1
1
  import RondevuPeer from './peer/index.js';
2
- import { ServiceHandle } from './services.js';
3
2
  /**
4
3
  * Status information about the pool
5
4
  */
@@ -43,9 +42,17 @@ export interface ServicePoolOptions {
43
42
  onError?: (error: Error, context: string) => void;
44
43
  }
45
44
  /**
46
- * Extended service handle with pool-specific methods
45
+ * Service handle with pool-specific methods
47
46
  */
48
- export interface PooledServiceHandle extends ServiceHandle {
47
+ export interface PooledServiceHandle {
48
+ /** Service ID */
49
+ serviceId: string;
50
+ /** Service UUID */
51
+ uuid: string;
52
+ /** Offer ID */
53
+ offerId: string;
54
+ /** Unpublish the service */
55
+ unpublish: () => Promise<void>;
49
56
  /** Get current pool status */
50
57
  getStatus: () => PoolStatus;
51
58
  /** Manually add offers to the pool */
@@ -63,6 +70,7 @@ export declare class ServicePool {
63
70
  private options;
64
71
  private offerPool?;
65
72
  private connections;
73
+ private peerConnections;
66
74
  private status;
67
75
  private serviceId?;
68
76
  private uuid?;