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