@xtr-dev/rondevu-client 0.8.3 → 0.9.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.
- package/README.md +402 -436
- package/dist/durable/channel.d.ts +115 -0
- package/dist/durable/channel.js +301 -0
- package/dist/durable/connection.d.ts +125 -0
- package/dist/durable/connection.js +370 -0
- package/dist/durable/reconnection.d.ts +90 -0
- package/dist/durable/reconnection.js +127 -0
- package/dist/durable/service.d.ts +103 -0
- package/dist/durable/service.js +264 -0
- package/dist/durable/types.d.ts +149 -0
- package/dist/durable/types.js +28 -0
- package/dist/index.d.ts +5 -10
- package/dist/index.js +5 -9
- package/dist/offer-pool.d.ts +15 -3
- package/dist/offer-pool.js +34 -8
- package/dist/peer/exchanging-ice-state.js +10 -2
- package/dist/peer/index.d.ts +1 -1
- package/dist/peer/index.js +25 -3
- package/dist/peer/state.js +9 -1
- package/dist/rondevu.d.ts +88 -13
- package/dist/rondevu.js +110 -27
- package/dist/service-pool.d.ts +11 -3
- package/dist/service-pool.js +120 -42
- package/package.json +2 -2
- package/dist/bloom.d.ts +0 -30
- package/dist/bloom.js +0 -73
- package/dist/client.d.ts +0 -126
- package/dist/client.js +0 -171
- package/dist/connection.d.ts +0 -127
- package/dist/connection.js +0 -295
- package/dist/discovery.d.ts +0 -93
- package/dist/discovery.js +0 -164
- package/dist/peer.d.ts +0 -111
- package/dist/peer.js +0 -392
- package/dist/services.d.ts +0 -79
- package/dist/services.js +0 -206
- package/dist/types.d.ts +0 -157
- package/dist/types.js +0 -4
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DurableService - Service with automatic TTL refresh
|
|
3
|
+
*
|
|
4
|
+
* Manages service publishing with automatic reconnection for incoming
|
|
5
|
+
* connections and TTL auto-refresh to prevent expiration.
|
|
6
|
+
*/
|
|
7
|
+
import { EventEmitter } from '../event-emitter.js';
|
|
8
|
+
import { ServicePool } from '../service-pool.js';
|
|
9
|
+
import { DurableChannel } from './channel.js';
|
|
10
|
+
/**
|
|
11
|
+
* Default configuration for durable services
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_CONFIG = {
|
|
14
|
+
isPublic: false,
|
|
15
|
+
ttlRefreshMargin: 0.2,
|
|
16
|
+
poolSize: 1,
|
|
17
|
+
pollingInterval: 2000,
|
|
18
|
+
maxReconnectAttempts: 10,
|
|
19
|
+
reconnectBackoffBase: 1000,
|
|
20
|
+
reconnectBackoffMax: 30000,
|
|
21
|
+
reconnectJitter: 0.2,
|
|
22
|
+
connectionTimeout: 30000,
|
|
23
|
+
maxQueueSize: 1000,
|
|
24
|
+
maxMessageAge: 60000,
|
|
25
|
+
rtcConfig: {
|
|
26
|
+
iceServers: [
|
|
27
|
+
{ urls: 'stun:stun.l.google.com:19302' },
|
|
28
|
+
{ urls: 'stun:stun1.l.google.com:19302' }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Durable service that automatically refreshes TTL and handles reconnections
|
|
34
|
+
*
|
|
35
|
+
* The DurableService manages service publishing and provides:
|
|
36
|
+
* - Automatic TTL refresh before expiration
|
|
37
|
+
* - Durable connections for incoming peers
|
|
38
|
+
* - Connection pooling for multiple simultaneous connections
|
|
39
|
+
* - High-level connection lifecycle events
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const service = new DurableService(
|
|
44
|
+
* offersApi,
|
|
45
|
+
* (channel, connectionId) => {
|
|
46
|
+
* channel.on('message', (data) => {
|
|
47
|
+
* console.log(`Message from ${connectionId}:`, data);
|
|
48
|
+
* channel.send(`Echo: ${data}`);
|
|
49
|
+
* });
|
|
50
|
+
* },
|
|
51
|
+
* {
|
|
52
|
+
* username: 'alice',
|
|
53
|
+
* privateKey: keypair.privateKey,
|
|
54
|
+
* serviceFqn: 'chat@1.0.0',
|
|
55
|
+
* poolSize: 10
|
|
56
|
+
* }
|
|
57
|
+
* );
|
|
58
|
+
*
|
|
59
|
+
* service.on('published', (serviceId, uuid) => {
|
|
60
|
+
* console.log(`Service published: ${uuid}`);
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* service.on('connection', (connectionId) => {
|
|
64
|
+
* console.log(`New connection: ${connectionId}`);
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* await service.start();
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export class DurableService extends EventEmitter {
|
|
71
|
+
constructor(offersApi, baseUrl, credentials, handler, config) {
|
|
72
|
+
super();
|
|
73
|
+
this.offersApi = offersApi;
|
|
74
|
+
this.baseUrl = baseUrl;
|
|
75
|
+
this.credentials = credentials;
|
|
76
|
+
this.handler = handler;
|
|
77
|
+
this.activeChannels = new Map();
|
|
78
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Start the service
|
|
82
|
+
*
|
|
83
|
+
* Publishes the service and begins accepting connections.
|
|
84
|
+
*
|
|
85
|
+
* @returns Service information
|
|
86
|
+
*/
|
|
87
|
+
async start() {
|
|
88
|
+
if (this.servicePool) {
|
|
89
|
+
throw new Error('Service already started');
|
|
90
|
+
}
|
|
91
|
+
// Create and start service pool
|
|
92
|
+
this.servicePool = new ServicePool(this.baseUrl, this.credentials, {
|
|
93
|
+
username: this.config.username,
|
|
94
|
+
privateKey: this.config.privateKey,
|
|
95
|
+
serviceFqn: this.config.serviceFqn,
|
|
96
|
+
rtcConfig: this.config.rtcConfig,
|
|
97
|
+
isPublic: this.config.isPublic,
|
|
98
|
+
metadata: this.config.metadata,
|
|
99
|
+
ttl: this.config.ttl,
|
|
100
|
+
poolSize: this.config.poolSize,
|
|
101
|
+
pollingInterval: this.config.pollingInterval,
|
|
102
|
+
handler: (channel, peer, connectionId) => {
|
|
103
|
+
this.handleNewConnection(channel, connectionId);
|
|
104
|
+
},
|
|
105
|
+
onPoolStatus: (status) => {
|
|
106
|
+
// Could emit pool status event if needed
|
|
107
|
+
},
|
|
108
|
+
onError: (error, context) => {
|
|
109
|
+
this.emit('error', error, context);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
const handle = await this.servicePool.start();
|
|
113
|
+
// Store service info
|
|
114
|
+
this.serviceId = handle.serviceId;
|
|
115
|
+
this.uuid = handle.uuid;
|
|
116
|
+
this.expiresAt = Date.now() + (this.config.ttl || 300000); // Default 5 minutes
|
|
117
|
+
this.emit('published', this.serviceId, this.uuid);
|
|
118
|
+
// Schedule TTL refresh
|
|
119
|
+
this.scheduleRefresh();
|
|
120
|
+
return {
|
|
121
|
+
serviceId: this.serviceId,
|
|
122
|
+
uuid: this.uuid,
|
|
123
|
+
expiresAt: this.expiresAt
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Stop the service
|
|
128
|
+
*
|
|
129
|
+
* Unpublishes the service and closes all active connections.
|
|
130
|
+
*/
|
|
131
|
+
async stop() {
|
|
132
|
+
// Cancel TTL refresh
|
|
133
|
+
if (this.ttlRefreshTimer) {
|
|
134
|
+
clearTimeout(this.ttlRefreshTimer);
|
|
135
|
+
this.ttlRefreshTimer = undefined;
|
|
136
|
+
}
|
|
137
|
+
// Close all active channels
|
|
138
|
+
for (const channel of this.activeChannels.values()) {
|
|
139
|
+
channel.close();
|
|
140
|
+
}
|
|
141
|
+
this.activeChannels.clear();
|
|
142
|
+
// Stop service pool
|
|
143
|
+
if (this.servicePool) {
|
|
144
|
+
await this.servicePool.stop();
|
|
145
|
+
this.servicePool = undefined;
|
|
146
|
+
}
|
|
147
|
+
this.emit('closed');
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get list of active connection IDs
|
|
151
|
+
*/
|
|
152
|
+
getActiveConnections() {
|
|
153
|
+
return Array.from(this.activeChannels.keys());
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get service information
|
|
157
|
+
*/
|
|
158
|
+
getServiceInfo() {
|
|
159
|
+
if (!this.serviceId || !this.uuid || !this.expiresAt) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
serviceId: this.serviceId,
|
|
164
|
+
uuid: this.uuid,
|
|
165
|
+
expiresAt: this.expiresAt
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Schedule TTL refresh
|
|
170
|
+
*/
|
|
171
|
+
scheduleRefresh() {
|
|
172
|
+
if (!this.expiresAt || !this.config.ttl) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Cancel existing timer
|
|
176
|
+
if (this.ttlRefreshTimer) {
|
|
177
|
+
clearTimeout(this.ttlRefreshTimer);
|
|
178
|
+
}
|
|
179
|
+
// Calculate refresh time (default: refresh at 80% of TTL)
|
|
180
|
+
const timeUntilExpiry = this.expiresAt - Date.now();
|
|
181
|
+
const refreshMargin = timeUntilExpiry * this.config.ttlRefreshMargin;
|
|
182
|
+
const refreshTime = Math.max(0, timeUntilExpiry - refreshMargin);
|
|
183
|
+
// Schedule refresh
|
|
184
|
+
this.ttlRefreshTimer = setTimeout(() => {
|
|
185
|
+
this.refreshServiceTTL().catch(error => {
|
|
186
|
+
this.emit('error', error, 'ttl-refresh');
|
|
187
|
+
// Retry after short delay
|
|
188
|
+
setTimeout(() => this.scheduleRefresh(), 5000);
|
|
189
|
+
});
|
|
190
|
+
}, refreshTime);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Refresh service TTL
|
|
194
|
+
*/
|
|
195
|
+
async refreshServiceTTL() {
|
|
196
|
+
if (!this.serviceId || !this.uuid) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// Delete old service
|
|
200
|
+
await this.servicePool?.stop();
|
|
201
|
+
// Recreate service pool (this republishes the service)
|
|
202
|
+
this.servicePool = new ServicePool(this.baseUrl, this.credentials, {
|
|
203
|
+
username: this.config.username,
|
|
204
|
+
privateKey: this.config.privateKey,
|
|
205
|
+
serviceFqn: this.config.serviceFqn,
|
|
206
|
+
rtcConfig: this.config.rtcConfig,
|
|
207
|
+
isPublic: this.config.isPublic,
|
|
208
|
+
metadata: this.config.metadata,
|
|
209
|
+
ttl: this.config.ttl,
|
|
210
|
+
poolSize: this.config.poolSize,
|
|
211
|
+
pollingInterval: this.config.pollingInterval,
|
|
212
|
+
handler: (channel, peer, connectionId) => {
|
|
213
|
+
this.handleNewConnection(channel, connectionId);
|
|
214
|
+
},
|
|
215
|
+
onPoolStatus: (status) => {
|
|
216
|
+
// Could emit pool status event if needed
|
|
217
|
+
},
|
|
218
|
+
onError: (error, context) => {
|
|
219
|
+
this.emit('error', error, context);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
const handle = await this.servicePool.start();
|
|
223
|
+
// Update service info
|
|
224
|
+
this.serviceId = handle.serviceId;
|
|
225
|
+
this.uuid = handle.uuid;
|
|
226
|
+
this.expiresAt = Date.now() + (this.config.ttl || 300000);
|
|
227
|
+
this.emit('ttl-refreshed', this.expiresAt);
|
|
228
|
+
// Schedule next refresh
|
|
229
|
+
this.scheduleRefresh();
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Handle new incoming connection
|
|
233
|
+
*/
|
|
234
|
+
handleNewConnection(channel, connectionId) {
|
|
235
|
+
// Create durable channel
|
|
236
|
+
const durableChannel = new DurableChannel(channel.label, {
|
|
237
|
+
maxQueueSize: this.config.maxQueueSize,
|
|
238
|
+
maxMessageAge: this.config.maxMessageAge
|
|
239
|
+
});
|
|
240
|
+
// Attach to underlying channel
|
|
241
|
+
durableChannel.attachToChannel(channel);
|
|
242
|
+
// Track channel
|
|
243
|
+
this.activeChannels.set(connectionId, durableChannel);
|
|
244
|
+
// Setup cleanup on close
|
|
245
|
+
durableChannel.on('close', () => {
|
|
246
|
+
this.activeChannels.delete(connectionId);
|
|
247
|
+
this.emit('disconnection', connectionId);
|
|
248
|
+
});
|
|
249
|
+
// Emit connection event
|
|
250
|
+
this.emit('connection', connectionId);
|
|
251
|
+
// Invoke user handler
|
|
252
|
+
try {
|
|
253
|
+
const result = this.handler(durableChannel, connectionId);
|
|
254
|
+
if (result && typeof result.then === 'function') {
|
|
255
|
+
result.catch(error => {
|
|
256
|
+
this.emit('error', error, 'handler');
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
this.emit('error', error, 'handler');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for durable WebRTC connections
|
|
3
|
+
*
|
|
4
|
+
* This module defines all interfaces, enums, and types used by the durable
|
|
5
|
+
* connection system for automatic reconnection and message queuing.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Connection state enum
|
|
9
|
+
*/
|
|
10
|
+
export declare enum DurableConnectionState {
|
|
11
|
+
CONNECTING = "connecting",
|
|
12
|
+
CONNECTED = "connected",
|
|
13
|
+
RECONNECTING = "reconnecting",
|
|
14
|
+
DISCONNECTED = "disconnected",
|
|
15
|
+
FAILED = "failed",
|
|
16
|
+
CLOSED = "closed"
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Channel state enum
|
|
20
|
+
*/
|
|
21
|
+
export declare enum DurableChannelState {
|
|
22
|
+
CONNECTING = "connecting",
|
|
23
|
+
OPEN = "open",
|
|
24
|
+
CLOSING = "closing",
|
|
25
|
+
CLOSED = "closed"
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Configuration for durable connections
|
|
29
|
+
*/
|
|
30
|
+
export interface DurableConnectionConfig {
|
|
31
|
+
/** Maximum number of reconnection attempts (default: 10) */
|
|
32
|
+
maxReconnectAttempts?: number;
|
|
33
|
+
/** Base delay for exponential backoff in milliseconds (default: 1000) */
|
|
34
|
+
reconnectBackoffBase?: number;
|
|
35
|
+
/** Maximum delay between reconnection attempts in milliseconds (default: 30000) */
|
|
36
|
+
reconnectBackoffMax?: number;
|
|
37
|
+
/** Jitter factor for randomizing reconnection delays (default: 0.2 = ±20%) */
|
|
38
|
+
reconnectJitter?: number;
|
|
39
|
+
/** Timeout for initial connection attempt in milliseconds (default: 30000) */
|
|
40
|
+
connectionTimeout?: number;
|
|
41
|
+
/** Maximum number of messages to queue during disconnection (default: 1000) */
|
|
42
|
+
maxQueueSize?: number;
|
|
43
|
+
/** Maximum age of queued messages in milliseconds (default: 60000) */
|
|
44
|
+
maxMessageAge?: number;
|
|
45
|
+
/** WebRTC configuration */
|
|
46
|
+
rtcConfig?: RTCConfiguration;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Configuration for durable channels
|
|
50
|
+
*/
|
|
51
|
+
export interface DurableChannelConfig {
|
|
52
|
+
/** Maximum number of messages to queue (default: 1000) */
|
|
53
|
+
maxQueueSize?: number;
|
|
54
|
+
/** Maximum age of queued messages in milliseconds (default: 60000) */
|
|
55
|
+
maxMessageAge?: number;
|
|
56
|
+
/** Whether messages should be delivered in order (default: true) */
|
|
57
|
+
ordered?: boolean;
|
|
58
|
+
/** Maximum retransmits for unordered channels (default: undefined) */
|
|
59
|
+
maxRetransmits?: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Configuration for durable services
|
|
63
|
+
*/
|
|
64
|
+
export interface DurableServiceConfig extends DurableConnectionConfig {
|
|
65
|
+
/** Username that owns the service */
|
|
66
|
+
username: string;
|
|
67
|
+
/** Private key for signing service operations */
|
|
68
|
+
privateKey: string;
|
|
69
|
+
/** Fully qualified service name (e.g., com.example.chat@1.0.0) */
|
|
70
|
+
serviceFqn: string;
|
|
71
|
+
/** Whether the service is publicly discoverable (default: false) */
|
|
72
|
+
isPublic?: boolean;
|
|
73
|
+
/** Optional metadata for the service */
|
|
74
|
+
metadata?: Record<string, any>;
|
|
75
|
+
/** Time-to-live for service in milliseconds (default: server default) */
|
|
76
|
+
ttl?: number;
|
|
77
|
+
/** Margin before TTL expiry to trigger refresh (default: 0.2 = refresh at 80%) */
|
|
78
|
+
ttlRefreshMargin?: number;
|
|
79
|
+
/** Number of simultaneous open offers to maintain (default: 1) */
|
|
80
|
+
poolSize?: number;
|
|
81
|
+
/** Polling interval for checking answers in milliseconds (default: 2000) */
|
|
82
|
+
pollingInterval?: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Queued message structure
|
|
86
|
+
*/
|
|
87
|
+
export interface QueuedMessage {
|
|
88
|
+
/** Message data */
|
|
89
|
+
data: string | Blob | ArrayBuffer | ArrayBufferView;
|
|
90
|
+
/** Timestamp when message was enqueued */
|
|
91
|
+
enqueuedAt: number;
|
|
92
|
+
/** Unique message ID */
|
|
93
|
+
id: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Event type map for DurableConnection
|
|
97
|
+
*/
|
|
98
|
+
export interface DurableConnectionEvents extends Record<string, (...args: any[]) => void> {
|
|
99
|
+
'state': (state: DurableConnectionState, previousState: DurableConnectionState) => void;
|
|
100
|
+
'connected': () => void;
|
|
101
|
+
'reconnecting': (attempt: number, maxAttempts: number, nextRetryIn: number) => void;
|
|
102
|
+
'disconnected': () => void;
|
|
103
|
+
'failed': (error: Error, permanent: boolean) => void;
|
|
104
|
+
'closed': () => void;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Event type map for DurableChannel
|
|
108
|
+
*/
|
|
109
|
+
export interface DurableChannelEvents extends Record<string, (...args: any[]) => void> {
|
|
110
|
+
'open': () => void;
|
|
111
|
+
'message': (data: any) => void;
|
|
112
|
+
'error': (error: Error) => void;
|
|
113
|
+
'close': () => void;
|
|
114
|
+
'bufferedAmountLow': () => void;
|
|
115
|
+
'queueOverflow': (droppedCount: number) => void;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Event type map for DurableService
|
|
119
|
+
*/
|
|
120
|
+
export interface DurableServiceEvents extends Record<string, (...args: any[]) => void> {
|
|
121
|
+
'published': (serviceId: string, uuid: string) => void;
|
|
122
|
+
'connection': (connectionId: string) => void;
|
|
123
|
+
'disconnection': (connectionId: string) => void;
|
|
124
|
+
'ttl-refreshed': (expiresAt: number) => void;
|
|
125
|
+
'error': (error: Error, context: string) => void;
|
|
126
|
+
'closed': () => void;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Information about a durable connection
|
|
130
|
+
*/
|
|
131
|
+
export interface ConnectionInfo {
|
|
132
|
+
/** Username (for username-based connections) */
|
|
133
|
+
username?: string;
|
|
134
|
+
/** Service FQN (for service-based connections) */
|
|
135
|
+
serviceFqn?: string;
|
|
136
|
+
/** UUID (for UUID-based connections) */
|
|
137
|
+
uuid?: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Service information returned when service is published
|
|
141
|
+
*/
|
|
142
|
+
export interface ServiceInfo {
|
|
143
|
+
/** Service ID */
|
|
144
|
+
serviceId: string;
|
|
145
|
+
/** Service UUID for discovery */
|
|
146
|
+
uuid: string;
|
|
147
|
+
/** Expiration timestamp */
|
|
148
|
+
expiresAt: number;
|
|
149
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for durable WebRTC connections
|
|
3
|
+
*
|
|
4
|
+
* This module defines all interfaces, enums, and types used by the durable
|
|
5
|
+
* connection system for automatic reconnection and message queuing.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Connection state enum
|
|
9
|
+
*/
|
|
10
|
+
export var DurableConnectionState;
|
|
11
|
+
(function (DurableConnectionState) {
|
|
12
|
+
DurableConnectionState["CONNECTING"] = "connecting";
|
|
13
|
+
DurableConnectionState["CONNECTED"] = "connected";
|
|
14
|
+
DurableConnectionState["RECONNECTING"] = "reconnecting";
|
|
15
|
+
DurableConnectionState["DISCONNECTED"] = "disconnected";
|
|
16
|
+
DurableConnectionState["FAILED"] = "failed";
|
|
17
|
+
DurableConnectionState["CLOSED"] = "closed";
|
|
18
|
+
})(DurableConnectionState || (DurableConnectionState = {}));
|
|
19
|
+
/**
|
|
20
|
+
* Channel state enum
|
|
21
|
+
*/
|
|
22
|
+
export var DurableChannelState;
|
|
23
|
+
(function (DurableChannelState) {
|
|
24
|
+
DurableChannelState["CONNECTING"] = "connecting";
|
|
25
|
+
DurableChannelState["OPEN"] = "open";
|
|
26
|
+
DurableChannelState["CLOSING"] = "closing";
|
|
27
|
+
DurableChannelState["CLOSED"] = "closed";
|
|
28
|
+
})(DurableChannelState || (DurableChannelState = {}));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @xtr-dev/rondevu-client
|
|
3
|
-
* WebRTC peer signaling and discovery client with
|
|
3
|
+
* WebRTC peer signaling and discovery client with durable connections
|
|
4
4
|
*/
|
|
5
5
|
export { Rondevu } from './rondevu.js';
|
|
6
6
|
export type { RondevuOptions } from './rondevu.js';
|
|
7
7
|
export { RondevuAuth } from './auth.js';
|
|
8
8
|
export type { Credentials, FetchFunction } from './auth.js';
|
|
9
|
-
export { RondevuOffers } from './offers.js';
|
|
10
|
-
export type { CreateOfferRequest, Offer, IceCandidate, TopicInfo } from './offers.js';
|
|
11
|
-
export { default as RondevuPeer } from './peer/index.js';
|
|
12
|
-
export type { PeerOptions, PeerEvents, PeerTimeouts } from './peer/index.js';
|
|
13
9
|
export { RondevuUsername } from './usernames.js';
|
|
14
10
|
export type { UsernameClaimResult, UsernameCheckResult } from './usernames.js';
|
|
15
|
-
export {
|
|
16
|
-
export
|
|
17
|
-
export {
|
|
18
|
-
export type {
|
|
19
|
-
export type { PoolStatus, PooledServiceHandle } from './service-pool.js';
|
|
11
|
+
export { DurableConnection } from './durable/connection.js';
|
|
12
|
+
export { DurableChannel } from './durable/channel.js';
|
|
13
|
+
export { DurableService } from './durable/service.js';
|
|
14
|
+
export type { DurableConnectionState, DurableChannelState, DurableConnectionConfig, DurableChannelConfig, DurableServiceConfig, QueuedMessage, DurableConnectionEvents, DurableChannelEvents, DurableServiceEvents, ConnectionInfo, ServiceInfo } from './durable/types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @xtr-dev/rondevu-client
|
|
3
|
-
* WebRTC peer signaling and discovery client with
|
|
3
|
+
* WebRTC peer signaling and discovery client with durable connections
|
|
4
4
|
*/
|
|
5
5
|
// Export main client class
|
|
6
6
|
export { Rondevu } from './rondevu.js';
|
|
7
7
|
// Export authentication
|
|
8
8
|
export { RondevuAuth } from './auth.js';
|
|
9
|
-
// Export offers API
|
|
10
|
-
export { RondevuOffers } from './offers.js';
|
|
11
|
-
// Export peer manager
|
|
12
|
-
export { default as RondevuPeer } from './peer/index.js';
|
|
13
9
|
// Export username API
|
|
14
10
|
export { RondevuUsername } from './usernames.js';
|
|
15
|
-
// Export
|
|
16
|
-
export {
|
|
17
|
-
|
|
18
|
-
export {
|
|
11
|
+
// Export durable connection APIs
|
|
12
|
+
export { DurableConnection } from './durable/connection.js';
|
|
13
|
+
export { DurableChannel } from './durable/channel.js';
|
|
14
|
+
export { DurableService } from './durable/service.js';
|
package/dist/offer-pool.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface AnsweredOffer {
|
|
|
6
6
|
offerId: string;
|
|
7
7
|
answererId: string;
|
|
8
8
|
sdp: string;
|
|
9
|
+
peerConnection: RTCPeerConnection;
|
|
10
|
+
dataChannel?: RTCDataChannel;
|
|
9
11
|
answeredAt: number;
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
@@ -19,7 +21,11 @@ export interface OfferPoolOptions {
|
|
|
19
21
|
/** Callback invoked when an offer is answered */
|
|
20
22
|
onAnswered: (answer: AnsweredOffer) => Promise<void>;
|
|
21
23
|
/** Callback to create new offers when refilling the pool */
|
|
22
|
-
onRefill: (count: number) => Promise<
|
|
24
|
+
onRefill: (count: number) => Promise<{
|
|
25
|
+
offers: Offer[];
|
|
26
|
+
peerConnections: RTCPeerConnection[];
|
|
27
|
+
dataChannels: RTCDataChannel[];
|
|
28
|
+
}>;
|
|
23
29
|
/** Error handler for pool operations */
|
|
24
30
|
onError: (error: Error, context: string) => void;
|
|
25
31
|
}
|
|
@@ -34,15 +40,17 @@ export declare class OfferPool {
|
|
|
34
40
|
private offersApi;
|
|
35
41
|
private options;
|
|
36
42
|
private offers;
|
|
43
|
+
private peerConnections;
|
|
44
|
+
private dataChannels;
|
|
37
45
|
private polling;
|
|
38
46
|
private pollingTimer?;
|
|
39
47
|
private lastPollTime;
|
|
40
48
|
private readonly pollingInterval;
|
|
41
49
|
constructor(offersApi: RondevuOffers, options: OfferPoolOptions);
|
|
42
50
|
/**
|
|
43
|
-
* Add offers to the pool
|
|
51
|
+
* Add offers to the pool with their peer connections and data channels
|
|
44
52
|
*/
|
|
45
|
-
addOffers(offers: Offer[]): Promise<void>;
|
|
53
|
+
addOffers(offers: Offer[], peerConnections?: RTCPeerConnection[], dataChannels?: RTCDataChannel[]): Promise<void>;
|
|
46
54
|
/**
|
|
47
55
|
* Start polling for answers
|
|
48
56
|
*/
|
|
@@ -63,6 +71,10 @@ export declare class OfferPool {
|
|
|
63
71
|
* Get all active offer IDs
|
|
64
72
|
*/
|
|
65
73
|
getActiveOfferIds(): string[];
|
|
74
|
+
/**
|
|
75
|
+
* Get all active peer connections
|
|
76
|
+
*/
|
|
77
|
+
getActivePeerConnections(): RTCPeerConnection[];
|
|
66
78
|
/**
|
|
67
79
|
* Get the last poll timestamp
|
|
68
80
|
*/
|
package/dist/offer-pool.js
CHANGED
|
@@ -10,16 +10,25 @@ export class OfferPool {
|
|
|
10
10
|
this.offersApi = offersApi;
|
|
11
11
|
this.options = options;
|
|
12
12
|
this.offers = new Map();
|
|
13
|
+
this.peerConnections = new Map();
|
|
14
|
+
this.dataChannels = new Map();
|
|
13
15
|
this.polling = false;
|
|
14
16
|
this.lastPollTime = 0;
|
|
15
17
|
this.pollingInterval = options.pollingInterval || 2000;
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
|
-
* Add offers to the pool
|
|
20
|
+
* Add offers to the pool with their peer connections and data channels
|
|
19
21
|
*/
|
|
20
|
-
async addOffers(offers) {
|
|
21
|
-
for (
|
|
22
|
+
async addOffers(offers, peerConnections, dataChannels) {
|
|
23
|
+
for (let i = 0; i < offers.length; i++) {
|
|
24
|
+
const offer = offers[i];
|
|
22
25
|
this.offers.set(offer.id, offer);
|
|
26
|
+
if (peerConnections && peerConnections[i]) {
|
|
27
|
+
this.peerConnections.set(offer.id, peerConnections[i]);
|
|
28
|
+
}
|
|
29
|
+
if (dataChannels && dataChannels[i]) {
|
|
30
|
+
this.dataChannels.set(offer.id, dataChannels[i]);
|
|
31
|
+
}
|
|
23
32
|
}
|
|
24
33
|
}
|
|
25
34
|
/**
|
|
@@ -64,22 +73,33 @@ export class OfferPool {
|
|
|
64
73
|
const myAnswers = answers.filter(a => this.offers.has(a.offerId));
|
|
65
74
|
// Process each answer
|
|
66
75
|
for (const answer of myAnswers) {
|
|
67
|
-
//
|
|
76
|
+
// Get the original offer, peer connection, and data channel
|
|
77
|
+
const offer = this.offers.get(answer.offerId);
|
|
78
|
+
const pc = this.peerConnections.get(answer.offerId);
|
|
79
|
+
const channel = this.dataChannels.get(answer.offerId);
|
|
80
|
+
if (!offer || !pc) {
|
|
81
|
+
continue; // Offer or peer connection already consumed, skip
|
|
82
|
+
}
|
|
83
|
+
// Remove from pool BEFORE processing to prevent duplicate processing
|
|
84
|
+
this.offers.delete(answer.offerId);
|
|
85
|
+
this.peerConnections.delete(answer.offerId);
|
|
86
|
+
this.dataChannels.delete(answer.offerId);
|
|
87
|
+
// Notify ServicePool with answer, original peer connection, and data channel
|
|
68
88
|
await this.options.onAnswered({
|
|
69
89
|
offerId: answer.offerId,
|
|
70
90
|
answererId: answer.answererId,
|
|
71
91
|
sdp: answer.sdp,
|
|
92
|
+
peerConnection: pc,
|
|
93
|
+
dataChannel: channel,
|
|
72
94
|
answeredAt: answer.answeredAt
|
|
73
95
|
});
|
|
74
|
-
// Remove consumed offer from pool
|
|
75
|
-
this.offers.delete(answer.offerId);
|
|
76
96
|
}
|
|
77
97
|
// Immediate refill if below pool size
|
|
78
98
|
if (this.offers.size < this.options.poolSize) {
|
|
79
99
|
const needed = this.options.poolSize - this.offers.size;
|
|
80
100
|
try {
|
|
81
|
-
const
|
|
82
|
-
await this.addOffers(
|
|
101
|
+
const result = await this.options.onRefill(needed);
|
|
102
|
+
await this.addOffers(result.offers, result.peerConnections, result.dataChannels);
|
|
83
103
|
}
|
|
84
104
|
catch (refillError) {
|
|
85
105
|
this.options.onError(refillError, 'refill');
|
|
@@ -104,6 +124,12 @@ export class OfferPool {
|
|
|
104
124
|
getActiveOfferIds() {
|
|
105
125
|
return Array.from(this.offers.keys());
|
|
106
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Get all active peer connections
|
|
129
|
+
*/
|
|
130
|
+
getActivePeerConnections() {
|
|
131
|
+
return Array.from(this.peerConnections.values());
|
|
132
|
+
}
|
|
107
133
|
/**
|
|
108
134
|
* Get the last poll timestamp
|
|
109
135
|
*/
|
|
@@ -21,14 +21,22 @@ export class ExchangingIceState extends PeerState {
|
|
|
21
21
|
this.pollingInterval = setInterval(async () => {
|
|
22
22
|
try {
|
|
23
23
|
const candidates = await this.peer.offersApi.getIceCandidates(this.offerId, this.lastIceTimestamp);
|
|
24
|
+
if (candidates.length > 0) {
|
|
25
|
+
console.log(`📥 Received ${candidates.length} remote ICE candidate(s)`);
|
|
26
|
+
}
|
|
24
27
|
for (const cand of candidates) {
|
|
25
28
|
if (cand.candidate && cand.candidate.candidate && cand.candidate.candidate !== '') {
|
|
29
|
+
const type = cand.candidate.candidate.includes('typ host') ? 'host' :
|
|
30
|
+
cand.candidate.candidate.includes('typ srflx') ? 'srflx' :
|
|
31
|
+
cand.candidate.candidate.includes('typ relay') ? 'relay' : 'unknown';
|
|
32
|
+
console.log(`🧊 Adding remote ${type} ICE candidate:`, cand.candidate.candidate);
|
|
26
33
|
try {
|
|
27
34
|
await this.peer.pc.addIceCandidate(new this.peer.RTCIceCandidate(cand.candidate));
|
|
35
|
+
console.log(`✅ Added remote ${type} ICE candidate`);
|
|
28
36
|
this.lastIceTimestamp = cand.createdAt;
|
|
29
37
|
}
|
|
30
38
|
catch (err) {
|
|
31
|
-
console.warn(
|
|
39
|
+
console.warn(`⚠️ Failed to add remote ${type} ICE candidate:`, err);
|
|
32
40
|
this.lastIceTimestamp = cand.createdAt;
|
|
33
41
|
}
|
|
34
42
|
}
|
|
@@ -38,7 +46,7 @@ export class ExchangingIceState extends PeerState {
|
|
|
38
46
|
}
|
|
39
47
|
}
|
|
40
48
|
catch (err) {
|
|
41
|
-
console.error('Error polling for ICE candidates:', err);
|
|
49
|
+
console.error('❌ Error polling for ICE candidates:', err);
|
|
42
50
|
if (err instanceof Error && err.message.includes('not found')) {
|
|
43
51
|
this.cleanup();
|
|
44
52
|
const { FailedState } = await import('./failed-state.js');
|
package/dist/peer/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export default class RondevuPeer extends EventEmitter<PeerEvents> {
|
|
|
32
32
|
* RTCPeerConnection state
|
|
33
33
|
*/
|
|
34
34
|
get connectionState(): RTCPeerConnectionState;
|
|
35
|
-
constructor(offersApi: RondevuOffers, rtcConfig?: RTCConfiguration, rtcPeerConnection?: typeof RTCPeerConnection, rtcSessionDescription?: typeof RTCSessionDescription, rtcIceCandidate?: typeof RTCIceCandidate);
|
|
35
|
+
constructor(offersApi: RondevuOffers, rtcConfig?: RTCConfiguration, existingPeerConnection?: RTCPeerConnection, rtcPeerConnection?: typeof RTCPeerConnection, rtcSessionDescription?: typeof RTCSessionDescription, rtcIceCandidate?: typeof RTCIceCandidate);
|
|
36
36
|
/**
|
|
37
37
|
* Set up peer connection event handlers
|
|
38
38
|
*/
|