@xtr-dev/rondevu-client 0.10.1 → 0.10.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.
- package/README.md +326 -533
- package/dist/api.d.ts +6 -6
- package/dist/api.js +28 -25
- package/dist/durable-connection.d.ts +120 -0
- package/dist/durable-connection.js +244 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -2
- package/dist/quick-start.d.ts +29 -0
- package/dist/quick-start.js +44 -0
- package/dist/rondevu-context.d.ts +10 -0
- package/dist/rondevu-context.js +20 -0
- package/dist/rondevu-service.d.ts +4 -0
- package/dist/rondevu-service.js +9 -3
- package/dist/rondevu-signaler.d.ts +110 -0
- package/dist/rondevu-signaler.js +361 -0
- package/dist/service-client.d.ts +30 -47
- package/dist/service-client.js +95 -123
- package/dist/service-host.d.ts +28 -64
- package/dist/service-host.js +81 -147
- package/dist/types.d.ts +1 -3
- package/dist/types.js +5 -1
- package/dist/webrtc-context.d.ts +1 -3
- package/dist/webrtc-context.js +1 -2
- package/package.json +1 -1
package/dist/service-client.js
CHANGED
|
@@ -1,116 +1,96 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RondevuSignaler } from './rondevu-signaler.js';
|
|
2
2
|
import { WebRTCContext } from './webrtc-context.js';
|
|
3
|
-
import {
|
|
3
|
+
import { RTCDurableConnection } from './durable-connection.js';
|
|
4
4
|
import { EventBus } from './event-bus.js';
|
|
5
|
-
import { createBin } from './bin.js';
|
|
6
5
|
/**
|
|
7
|
-
* ServiceClient -
|
|
6
|
+
* ServiceClient - High-level wrapper for connecting to a WebRTC service
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Simplifies client connection by handling:
|
|
9
|
+
* - Service discovery
|
|
10
|
+
* - Offer/answer exchange
|
|
11
|
+
* - ICE candidate polling
|
|
12
|
+
* - Automatic reconnection
|
|
11
13
|
*
|
|
12
14
|
* @example
|
|
13
15
|
* ```typescript
|
|
14
|
-
* const
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* const client = new ServiceClient({
|
|
17
|
+
* username: 'host-user',
|
|
18
|
+
* serviceFqn: 'chat.app@1.0.0',
|
|
19
|
+
* rondevuService: myService
|
|
17
20
|
* })
|
|
18
21
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* username: 'host-user',
|
|
23
|
-
* serviceFqn: 'chat.app@1.0.0',
|
|
24
|
-
* rondevuService,
|
|
25
|
-
* autoReconnect: true,
|
|
22
|
+
* client.events.on('connected', conn => {
|
|
23
|
+
* conn.events.on('message', msg => console.log('Received:', msg))
|
|
24
|
+
* conn.sendMessage('Hello from client!')
|
|
26
25
|
* })
|
|
27
26
|
*
|
|
28
27
|
* await client.connect()
|
|
29
|
-
*
|
|
30
|
-
* client.events.on('connected', (conn) => {
|
|
31
|
-
* console.log('Connected to service')
|
|
32
|
-
* conn.sendMessage('Hello!')
|
|
33
|
-
* })
|
|
34
28
|
* ```
|
|
35
29
|
*/
|
|
36
30
|
export class ServiceClient {
|
|
37
31
|
constructor(options) {
|
|
32
|
+
this.options = options;
|
|
33
|
+
this.signaler = null;
|
|
38
34
|
this.connection = null;
|
|
39
35
|
this.reconnectAttempts = 0;
|
|
40
|
-
this.reconnectTimeout = null;
|
|
41
|
-
this.bin = createBin();
|
|
42
36
|
this.isConnecting = false;
|
|
43
37
|
this.events = new EventBus();
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
46
|
-
this.rondevuService = options.rondevuService;
|
|
47
|
-
this.autoReconnect = options.autoReconnect !== false;
|
|
48
|
-
this.reconnectDelay = options.reconnectDelay || 2000;
|
|
38
|
+
this.webrtcContext = new WebRTCContext(options.rtcConfiguration);
|
|
39
|
+
this.autoReconnect = options.autoReconnect !== undefined ? options.autoReconnect : true;
|
|
49
40
|
this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
|
|
50
|
-
this.rtcConfiguration = options.rtcConfiguration;
|
|
51
41
|
}
|
|
52
42
|
/**
|
|
53
43
|
* Connect to the service
|
|
54
44
|
*/
|
|
55
45
|
async connect() {
|
|
56
46
|
if (this.isConnecting) {
|
|
57
|
-
throw new Error('
|
|
47
|
+
throw new Error('Connection already in progress');
|
|
58
48
|
}
|
|
59
|
-
if (this.connection
|
|
60
|
-
|
|
49
|
+
if (this.connection) {
|
|
50
|
+
throw new Error('Already connected. Disconnect first.');
|
|
61
51
|
}
|
|
62
52
|
this.isConnecting = true;
|
|
63
53
|
try {
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// Create WebRTC context with signaler for this offer
|
|
76
|
-
const signaler = new RondevuSignaler(this.rondevuService.getAPI(), serviceDetails.offerId);
|
|
77
|
-
const context = new WebRTCContext(signaler, this.rtcConfiguration);
|
|
78
|
-
// Create connection (answerer role)
|
|
79
|
-
const conn = new WebRTCRondevuConnection({
|
|
80
|
-
id: `client-${this.serviceFqn}-${Date.now()}`,
|
|
81
|
-
service: this.serviceFqn,
|
|
82
|
-
offer: {
|
|
83
|
-
type: 'offer',
|
|
84
|
-
sdp: serviceDetails.sdp,
|
|
85
|
-
},
|
|
86
|
-
context,
|
|
54
|
+
// Create signaler
|
|
55
|
+
this.signaler = new RondevuSignaler(this.options.rondevuService, this.options.serviceFqn, this.options.username);
|
|
56
|
+
// Wait for remote offer from signaler
|
|
57
|
+
const remoteOffer = await new Promise((resolve, reject) => {
|
|
58
|
+
const timeout = setTimeout(() => {
|
|
59
|
+
reject(new Error('Service discovery timeout'));
|
|
60
|
+
}, 30000);
|
|
61
|
+
this.signaler.addOfferListener((offer) => {
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
resolve(offer);
|
|
64
|
+
});
|
|
87
65
|
});
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
66
|
+
// Create connection with remote offer (makes us the answerer)
|
|
67
|
+
const connection = new RTCDurableConnection({
|
|
68
|
+
context: this.webrtcContext,
|
|
69
|
+
signaler: this.signaler,
|
|
70
|
+
offer: remoteOffer
|
|
71
|
+
});
|
|
72
|
+
// Wait for connection to be ready
|
|
73
|
+
await connection.ready;
|
|
74
|
+
// Set up connection event listeners
|
|
75
|
+
connection.events.on('state-change', (state) => {
|
|
76
|
+
if (state === 'connected') {
|
|
77
|
+
this.reconnectAttempts = 0;
|
|
78
|
+
this.events.emit('connected', connection);
|
|
79
|
+
}
|
|
80
|
+
else if (state === 'disconnected') {
|
|
81
|
+
this.events.emit('disconnected', undefined);
|
|
82
|
+
if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
83
|
+
this.attemptReconnect();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
103
86
|
});
|
|
104
|
-
this.
|
|
87
|
+
this.connection = connection;
|
|
105
88
|
this.isConnecting = false;
|
|
106
|
-
|
|
107
|
-
if (conn.state === 'connected') {
|
|
108
|
-
this.events.emit('connected', conn);
|
|
109
|
-
}
|
|
110
|
-
return conn;
|
|
89
|
+
return connection;
|
|
111
90
|
}
|
|
112
|
-
catch (
|
|
91
|
+
catch (err) {
|
|
113
92
|
this.isConnecting = false;
|
|
93
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
114
94
|
this.events.emit('error', error);
|
|
115
95
|
throw error;
|
|
116
96
|
}
|
|
@@ -118,69 +98,61 @@ export class ServiceClient {
|
|
|
118
98
|
/**
|
|
119
99
|
* Disconnect from the service
|
|
120
100
|
*/
|
|
121
|
-
|
|
122
|
-
if (this.
|
|
123
|
-
|
|
124
|
-
this.
|
|
101
|
+
dispose() {
|
|
102
|
+
if (this.signaler) {
|
|
103
|
+
this.signaler.dispose();
|
|
104
|
+
this.signaler = null;
|
|
125
105
|
}
|
|
126
106
|
if (this.connection) {
|
|
127
107
|
this.connection.disconnect();
|
|
128
108
|
this.connection = null;
|
|
129
109
|
}
|
|
130
|
-
this.
|
|
110
|
+
this.isConnecting = false;
|
|
131
111
|
this.reconnectAttempts = 0;
|
|
132
112
|
}
|
|
133
113
|
/**
|
|
134
|
-
*
|
|
114
|
+
* @deprecated Use dispose() instead
|
|
135
115
|
*/
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Check if currently connected
|
|
141
|
-
*/
|
|
142
|
-
isConnected() {
|
|
143
|
-
return this.connection?.state === 'connected';
|
|
116
|
+
disconnect() {
|
|
117
|
+
this.dispose();
|
|
144
118
|
}
|
|
145
119
|
/**
|
|
146
|
-
*
|
|
120
|
+
* Attempt to reconnect
|
|
147
121
|
*/
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.reconnectAttempts
|
|
122
|
+
async attemptReconnect() {
|
|
123
|
+
this.reconnectAttempts++;
|
|
124
|
+
this.events.emit('reconnecting', {
|
|
125
|
+
attempt: this.reconnectAttempts,
|
|
126
|
+
maxAttempts: this.maxReconnectAttempts
|
|
127
|
+
});
|
|
128
|
+
// Cleanup old connection
|
|
129
|
+
if (this.signaler) {
|
|
130
|
+
this.signaler.dispose();
|
|
131
|
+
this.signaler = null;
|
|
132
|
+
}
|
|
133
|
+
if (this.connection) {
|
|
134
|
+
this.connection = null;
|
|
152
135
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
136
|
+
// Wait a bit before reconnecting
|
|
137
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * this.reconnectAttempts));
|
|
138
|
+
try {
|
|
139
|
+
await this.connect();
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
console.error('Reconnection attempt failed:', err);
|
|
143
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
144
|
+
this.attemptReconnect();
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const error = new Error('Max reconnection attempts reached');
|
|
148
|
+
this.events.emit('error', error);
|
|
158
149
|
}
|
|
159
150
|
}
|
|
160
151
|
}
|
|
161
152
|
/**
|
|
162
|
-
*
|
|
153
|
+
* Get the current connection
|
|
163
154
|
*/
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
this.reconnectAttempts++;
|
|
169
|
-
this.events.emit('reconnecting', {
|
|
170
|
-
attempt: this.reconnectAttempts,
|
|
171
|
-
maxAttempts: this.maxReconnectAttempts,
|
|
172
|
-
});
|
|
173
|
-
// Exponential backoff
|
|
174
|
-
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
175
|
-
this.reconnectTimeout = setTimeout(() => {
|
|
176
|
-
this.reconnectTimeout = null;
|
|
177
|
-
this.connect().catch(error => {
|
|
178
|
-
this.events.emit('error', error);
|
|
179
|
-
// Schedule next attempt if we haven't exceeded max attempts
|
|
180
|
-
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
181
|
-
this.scheduleReconnect();
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}, delay);
|
|
155
|
+
getConnection() {
|
|
156
|
+
return this.connection;
|
|
185
157
|
}
|
|
186
158
|
}
|
package/dist/service-host.d.ts
CHANGED
|
@@ -1,103 +1,67 @@
|
|
|
1
|
-
import { WebRTCRondevuConnection } from './connection.js';
|
|
2
1
|
import { RondevuService } from './rondevu-service.js';
|
|
2
|
+
import { RTCDurableConnection } from './durable-connection.js';
|
|
3
3
|
import { EventBus } from './event-bus.js';
|
|
4
|
-
import { ConnectionInterface } from './types.js';
|
|
5
4
|
export interface ServiceHostOptions {
|
|
6
5
|
service: string;
|
|
7
6
|
rondevuService: RondevuService;
|
|
8
7
|
maxPeers?: number;
|
|
9
8
|
ttl?: number;
|
|
10
9
|
isPublic?: boolean;
|
|
11
|
-
metadata?: Record<string, any>;
|
|
12
10
|
rtcConfiguration?: RTCConfiguration;
|
|
11
|
+
metadata?: Record<string, any>;
|
|
13
12
|
}
|
|
14
13
|
export interface ServiceHostEvents {
|
|
15
|
-
connection:
|
|
16
|
-
'connection-closed': {
|
|
17
|
-
connectionId: string;
|
|
18
|
-
reason: string;
|
|
19
|
-
};
|
|
14
|
+
connection: RTCDurableConnection;
|
|
20
15
|
error: Error;
|
|
21
16
|
}
|
|
22
17
|
/**
|
|
23
|
-
* ServiceHost -
|
|
18
|
+
* ServiceHost - High-level wrapper for hosting a WebRTC service
|
|
24
19
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
20
|
+
* Simplifies hosting by handling:
|
|
21
|
+
* - Offer/answer exchange
|
|
22
|
+
* - ICE candidate polling
|
|
23
|
+
* - Connection pool management
|
|
24
|
+
* - Automatic reconnection
|
|
27
25
|
*
|
|
28
26
|
* @example
|
|
29
27
|
* ```typescript
|
|
30
|
-
* const
|
|
31
|
-
*
|
|
32
|
-
*
|
|
28
|
+
* const host = new ServiceHost({
|
|
29
|
+
* service: 'chat.app@1.0.0',
|
|
30
|
+
* rondevuService: myService,
|
|
31
|
+
* maxPeers: 5
|
|
33
32
|
* })
|
|
34
33
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* const host = new ServiceHost({
|
|
39
|
-
* service: 'chat.app@1.0.0',
|
|
40
|
-
* rondevuService,
|
|
41
|
-
* maxPeers: 5,
|
|
34
|
+
* host.events.on('connection', conn => {
|
|
35
|
+
* conn.events.on('message', msg => console.log('Received:', msg))
|
|
36
|
+
* conn.sendMessage('Hello!')
|
|
42
37
|
* })
|
|
43
38
|
*
|
|
44
39
|
* await host.start()
|
|
45
|
-
*
|
|
46
|
-
* host.events.on('connection', (conn) => {
|
|
47
|
-
* console.log('New connection:', conn.id)
|
|
48
|
-
* conn.events.on('message', (msg) => {
|
|
49
|
-
* console.log('Message:', msg)
|
|
50
|
-
* })
|
|
51
|
-
* })
|
|
52
40
|
* ```
|
|
53
41
|
*/
|
|
54
42
|
export declare class ServiceHost {
|
|
43
|
+
private options;
|
|
44
|
+
events: EventBus<ServiceHostEvents>;
|
|
45
|
+
private signaler;
|
|
46
|
+
private webrtcContext;
|
|
55
47
|
private connections;
|
|
56
|
-
private
|
|
57
|
-
private
|
|
58
|
-
private readonly maxPeers;
|
|
59
|
-
private readonly ttl;
|
|
60
|
-
private readonly isPublic;
|
|
61
|
-
private readonly metadata?;
|
|
62
|
-
private readonly rtcConfiguration?;
|
|
63
|
-
private readonly bin;
|
|
64
|
-
private isStarted;
|
|
65
|
-
readonly events: EventBus<ServiceHostEvents>;
|
|
48
|
+
private maxPeers;
|
|
49
|
+
private running;
|
|
66
50
|
constructor(options: ServiceHostOptions);
|
|
67
51
|
/**
|
|
68
|
-
* Start hosting the service
|
|
52
|
+
* Start hosting the service
|
|
69
53
|
*/
|
|
70
54
|
start(): Promise<void>;
|
|
71
55
|
/**
|
|
72
|
-
*
|
|
73
|
-
*/
|
|
74
|
-
stop(): void;
|
|
75
|
-
/**
|
|
76
|
-
* Get current number of active connections
|
|
56
|
+
* Create the next connection for incoming peers
|
|
77
57
|
*/
|
|
78
|
-
|
|
58
|
+
private createNextConnection;
|
|
79
59
|
/**
|
|
80
|
-
*
|
|
60
|
+
* Stop hosting the service
|
|
81
61
|
*/
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Fill the offer pool up to maxPeers
|
|
85
|
-
*/
|
|
86
|
-
private fillOfferPool;
|
|
87
|
-
/**
|
|
88
|
-
* Create a single offer and publish it
|
|
89
|
-
*/
|
|
90
|
-
private createOffer;
|
|
91
|
-
/**
|
|
92
|
-
* Handle connection state changes
|
|
93
|
-
*/
|
|
94
|
-
private handleConnectionStateChange;
|
|
62
|
+
dispose(): void;
|
|
95
63
|
/**
|
|
96
64
|
* Get all active connections
|
|
97
65
|
*/
|
|
98
|
-
getConnections():
|
|
99
|
-
/**
|
|
100
|
-
* Get a specific connection by ID
|
|
101
|
-
*/
|
|
102
|
-
getConnection(connectionId: string): WebRTCRondevuConnection | undefined;
|
|
66
|
+
getConnections(): RTCDurableConnection[];
|
|
103
67
|
}
|