@xtr-dev/rondevu-client 0.10.1 → 0.11.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.
- package/README.md +332 -533
- package/dist/api.d.ts +17 -8
- 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 +8 -2
- package/dist/rondevu-service.js +12 -6
- package/dist/rondevu-signaler.d.ts +110 -0
- package/dist/rondevu-signaler.js +372 -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-host.js
CHANGED
|
@@ -1,186 +1,120 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RondevuSignaler } from './rondevu-signaler.js';
|
|
2
2
|
import { WebRTCContext } from './webrtc-context.js';
|
|
3
|
-
import {
|
|
4
|
-
import { NoOpSignaler } from './noop-signaler.js';
|
|
3
|
+
import { RTCDurableConnection } from './durable-connection.js';
|
|
5
4
|
import { EventBus } from './event-bus.js';
|
|
6
|
-
import { createBin } from './bin.js';
|
|
7
5
|
/**
|
|
8
|
-
* ServiceHost -
|
|
6
|
+
* ServiceHost - High-level wrapper for hosting a WebRTC service
|
|
9
7
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Simplifies hosting by handling:
|
|
9
|
+
* - Offer/answer exchange
|
|
10
|
+
* - ICE candidate polling
|
|
11
|
+
* - Connection pool management
|
|
12
|
+
* - Automatic reconnection
|
|
12
13
|
*
|
|
13
14
|
* @example
|
|
14
15
|
* ```typescript
|
|
15
|
-
* const
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* const host = new ServiceHost({
|
|
17
|
+
* service: 'chat.app@1.0.0',
|
|
18
|
+
* rondevuService: myService,
|
|
19
|
+
* maxPeers: 5
|
|
18
20
|
* })
|
|
19
21
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* const host = new ServiceHost({
|
|
24
|
-
* service: 'chat.app@1.0.0',
|
|
25
|
-
* rondevuService,
|
|
26
|
-
* maxPeers: 5,
|
|
22
|
+
* host.events.on('connection', conn => {
|
|
23
|
+
* conn.events.on('message', msg => console.log('Received:', msg))
|
|
24
|
+
* conn.sendMessage('Hello!')
|
|
27
25
|
* })
|
|
28
26
|
*
|
|
29
27
|
* await host.start()
|
|
30
|
-
*
|
|
31
|
-
* host.events.on('connection', (conn) => {
|
|
32
|
-
* console.log('New connection:', conn.id)
|
|
33
|
-
* conn.events.on('message', (msg) => {
|
|
34
|
-
* console.log('Message:', msg)
|
|
35
|
-
* })
|
|
36
|
-
* })
|
|
37
28
|
* ```
|
|
38
29
|
*/
|
|
39
30
|
export class ServiceHost {
|
|
40
31
|
constructor(options) {
|
|
41
|
-
this.
|
|
42
|
-
this.
|
|
43
|
-
this.
|
|
32
|
+
this.options = options;
|
|
33
|
+
this.signaler = null;
|
|
34
|
+
this.connections = [];
|
|
35
|
+
this.running = false;
|
|
44
36
|
this.events = new EventBus();
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
47
|
-
this.maxPeers = options.maxPeers || 20;
|
|
48
|
-
this.ttl = options.ttl || 300000;
|
|
49
|
-
this.isPublic = options.isPublic !== false;
|
|
50
|
-
this.metadata = options.metadata;
|
|
51
|
-
this.rtcConfiguration = options.rtcConfiguration;
|
|
37
|
+
this.webrtcContext = new WebRTCContext(options.rtcConfiguration);
|
|
38
|
+
this.maxPeers = options.maxPeers || 5;
|
|
52
39
|
}
|
|
53
40
|
/**
|
|
54
|
-
* Start hosting the service
|
|
41
|
+
* Start hosting the service
|
|
55
42
|
*/
|
|
56
43
|
async start() {
|
|
57
|
-
if (this.
|
|
58
|
-
throw new Error('ServiceHost already
|
|
44
|
+
if (this.running) {
|
|
45
|
+
throw new Error('ServiceHost already running');
|
|
59
46
|
}
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const offerPromises = [];
|
|
97
|
-
for (let i = 0; i < needed; i++) {
|
|
98
|
-
offerPromises.push(this.createOffer());
|
|
47
|
+
this.running = true;
|
|
48
|
+
// Create signaler
|
|
49
|
+
this.signaler = new RondevuSignaler(this.options.rondevuService, this.options.service);
|
|
50
|
+
// Create first connection (offerer)
|
|
51
|
+
const connection = new RTCDurableConnection({
|
|
52
|
+
context: this.webrtcContext,
|
|
53
|
+
signaler: this.signaler,
|
|
54
|
+
offer: null // null means we're the offerer
|
|
55
|
+
});
|
|
56
|
+
// Wait for connection to be ready
|
|
57
|
+
await connection.ready;
|
|
58
|
+
// Set up connection event listeners
|
|
59
|
+
connection.events.on('state-change', (state) => {
|
|
60
|
+
if (state === 'connected') {
|
|
61
|
+
this.connections.push(connection);
|
|
62
|
+
this.events.emit('connection', connection);
|
|
63
|
+
// Create next connection if under maxPeers
|
|
64
|
+
if (this.connections.length < this.maxPeers) {
|
|
65
|
+
this.createNextConnection().catch(err => {
|
|
66
|
+
console.error('Failed to create next connection:', err);
|
|
67
|
+
this.events.emit('error', err);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else if (state === 'disconnected') {
|
|
72
|
+
// Remove from connections list
|
|
73
|
+
const index = this.connections.indexOf(connection);
|
|
74
|
+
if (index > -1) {
|
|
75
|
+
this.connections.splice(index, 1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// Publish service with the offer
|
|
80
|
+
const offer = connection.connection?.localDescription;
|
|
81
|
+
if (!offer?.sdp) {
|
|
82
|
+
throw new Error('Offer SDP is empty');
|
|
99
83
|
}
|
|
100
|
-
await
|
|
84
|
+
await this.signaler.setOffer(offer);
|
|
101
85
|
}
|
|
102
86
|
/**
|
|
103
|
-
* Create
|
|
87
|
+
* Create the next connection for incoming peers
|
|
104
88
|
*/
|
|
105
|
-
async
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const tempContext = new WebRTCContext(new NoOpSignaler(), this.rtcConfiguration);
|
|
109
|
-
// Create connection (offerer role)
|
|
110
|
-
const conn = new WebRTCRondevuConnection({
|
|
111
|
-
id: `${this.service}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
112
|
-
service: this.service,
|
|
113
|
-
offer: null,
|
|
114
|
-
context: tempContext,
|
|
115
|
-
});
|
|
116
|
-
// Wait for offer to be created
|
|
117
|
-
await conn.ready;
|
|
118
|
-
// Get offer SDP
|
|
119
|
-
if (!conn.connection?.localDescription?.sdp) {
|
|
120
|
-
throw new Error('Failed to create offer SDP');
|
|
121
|
-
}
|
|
122
|
-
const sdp = conn.connection.localDescription.sdp;
|
|
123
|
-
// Publish service offer
|
|
124
|
-
const service = await this.rondevuService.publishService({
|
|
125
|
-
serviceFqn: this.service,
|
|
126
|
-
sdp,
|
|
127
|
-
ttl: this.ttl,
|
|
128
|
-
isPublic: this.isPublic,
|
|
129
|
-
metadata: this.metadata,
|
|
130
|
-
});
|
|
131
|
-
// Replace with real signaler now that we have offerId
|
|
132
|
-
const realSignaler = new RondevuSignaler(this.rondevuService.getAPI(), service.offerId);
|
|
133
|
-
tempContext.signaler = realSignaler;
|
|
134
|
-
// Track connection
|
|
135
|
-
this.connections.set(conn.id, conn);
|
|
136
|
-
// Listen for state changes
|
|
137
|
-
const cleanup = conn.events.on('state-change', state => {
|
|
138
|
-
this.handleConnectionStateChange(conn, state);
|
|
139
|
-
});
|
|
140
|
-
this.bin(cleanup);
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
this.events.emit('error', error);
|
|
89
|
+
async createNextConnection() {
|
|
90
|
+
if (!this.signaler || !this.running) {
|
|
91
|
+
return;
|
|
144
92
|
}
|
|
93
|
+
// For now, we'll use the same offer for all connections
|
|
94
|
+
// In a production scenario, you'd create multiple offers
|
|
95
|
+
// This is a limitation of the current service model
|
|
96
|
+
// which publishes one offer per service
|
|
145
97
|
}
|
|
146
98
|
/**
|
|
147
|
-
*
|
|
99
|
+
* Stop hosting the service
|
|
148
100
|
*/
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
this.fillOfferPool().catch(error => {
|
|
156
|
-
this.events.emit('error', error);
|
|
157
|
-
});
|
|
158
|
-
}
|
|
101
|
+
dispose() {
|
|
102
|
+
this.running = false;
|
|
103
|
+
// Cleanup signaler
|
|
104
|
+
if (this.signaler) {
|
|
105
|
+
this.signaler.dispose();
|
|
106
|
+
this.signaler = null;
|
|
159
107
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
this.events.emit('connection-closed', {
|
|
164
|
-
connectionId: conn.id,
|
|
165
|
-
reason: state,
|
|
166
|
-
});
|
|
167
|
-
if (this.isStarted) {
|
|
168
|
-
this.fillOfferPool().catch(error => {
|
|
169
|
-
this.events.emit('error', error);
|
|
170
|
-
});
|
|
171
|
-
}
|
|
108
|
+
// Disconnect all connections
|
|
109
|
+
for (const conn of this.connections) {
|
|
110
|
+
conn.disconnect();
|
|
172
111
|
}
|
|
112
|
+
this.connections = [];
|
|
173
113
|
}
|
|
174
114
|
/**
|
|
175
115
|
* Get all active connections
|
|
176
116
|
*/
|
|
177
117
|
getConnections() {
|
|
178
|
-
return
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Get a specific connection by ID
|
|
182
|
-
*/
|
|
183
|
-
getConnection(connectionId) {
|
|
184
|
-
return this.connections.get(connectionId);
|
|
118
|
+
return [...this.connections];
|
|
185
119
|
}
|
|
186
120
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -14,8 +14,6 @@ export interface ConnectionEvents {
|
|
|
14
14
|
export declare const ConnectionStates: readonly ["connected", "disconnected", "connecting"];
|
|
15
15
|
export declare const isConnectionState: (state: string) => state is (typeof ConnectionStates)[number];
|
|
16
16
|
export interface ConnectionInterface {
|
|
17
|
-
id: string;
|
|
18
|
-
service: string;
|
|
19
17
|
state: (typeof ConnectionStates)[number];
|
|
20
18
|
lastActive: number;
|
|
21
19
|
expiresAt?: number;
|
|
@@ -24,7 +22,7 @@ export interface ConnectionInterface {
|
|
|
24
22
|
sendMessage(message: Message): Promise<boolean>;
|
|
25
23
|
}
|
|
26
24
|
export interface Signaler {
|
|
27
|
-
addIceCandidate(candidate: RTCIceCandidate): Promise<void
|
|
25
|
+
addIceCandidate(candidate: RTCIceCandidate): Promise<void>;
|
|
28
26
|
addListener(callback: (candidate: RTCIceCandidate) => void): Binnable;
|
|
29
27
|
addOfferListener(callback: (offer: RTCSessionDescriptionInit) => void): Binnable;
|
|
30
28
|
addAnswerListener(callback: (answer: RTCSessionDescriptionInit) => void): Binnable;
|
package/dist/types.js
CHANGED
package/dist/webrtc-context.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { Signaler } from './types';
|
|
2
1
|
export declare class WebRTCContext {
|
|
3
|
-
readonly signaler: Signaler;
|
|
4
2
|
private readonly config?;
|
|
5
|
-
constructor(
|
|
3
|
+
constructor(config?: RTCConfiguration | undefined);
|
|
6
4
|
createPeerConnection(): RTCPeerConnection;
|
|
7
5
|
}
|
package/dist/webrtc-context.js
CHANGED
package/package.json
CHANGED