@xtr-dev/rondevu-client 0.12.4 → 0.17.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 +100 -381
- package/dist/api.d.ts +75 -96
- package/dist/api.js +202 -243
- package/dist/crypto-adapter.d.ts +37 -0
- package/dist/crypto-adapter.js +4 -0
- package/dist/index.d.ts +8 -15
- package/dist/index.js +5 -8
- package/dist/node-crypto-adapter.d.ts +35 -0
- package/dist/node-crypto-adapter.js +80 -0
- package/dist/rondevu-signaler.d.ts +14 -12
- package/dist/rondevu-signaler.js +111 -95
- package/dist/rondevu.d.ts +329 -0
- package/dist/rondevu.js +648 -0
- package/dist/rpc-batcher.d.ts +61 -0
- package/dist/rpc-batcher.js +111 -0
- package/dist/types.d.ts +8 -21
- package/dist/types.js +4 -6
- package/dist/web-crypto-adapter.d.ts +16 -0
- package/dist/web-crypto-adapter.js +52 -0
- package/package.json +1 -1
- package/dist/bin.d.ts +0 -35
- package/dist/bin.js +0 -35
- package/dist/connection-manager.d.ts +0 -104
- package/dist/connection-manager.js +0 -324
- package/dist/connection.d.ts +0 -112
- package/dist/connection.js +0 -194
- package/dist/durable-connection.d.ts +0 -120
- package/dist/durable-connection.js +0 -244
- package/dist/event-bus.d.ts +0 -52
- package/dist/event-bus.js +0 -84
- package/dist/noop-signaler.d.ts +0 -14
- package/dist/noop-signaler.js +0 -27
- package/dist/quick-start.d.ts +0 -29
- package/dist/quick-start.js +0 -44
- package/dist/rondevu-context.d.ts +0 -10
- package/dist/rondevu-context.js +0 -20
- package/dist/rondevu-service.d.ts +0 -87
- package/dist/rondevu-service.js +0 -170
- package/dist/service-client.d.ts +0 -77
- package/dist/service-client.js +0 -158
- package/dist/service-host.d.ts +0 -67
- package/dist/service-host.js +0 -120
- package/dist/signaler.d.ts +0 -25
- package/dist/signaler.js +0 -89
- package/dist/webrtc-context.d.ts +0 -5
- package/dist/webrtc-context.js +0 -35
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { RondevuAPI, Credentials, Keypair, Service } from './api.js';
|
|
2
|
-
export interface RondevuServiceOptions {
|
|
3
|
-
apiUrl: string;
|
|
4
|
-
username: string;
|
|
5
|
-
keypair?: Keypair;
|
|
6
|
-
credentials?: Credentials;
|
|
7
|
-
}
|
|
8
|
-
export interface PublishServiceOptions {
|
|
9
|
-
serviceFqn: string;
|
|
10
|
-
offers: Array<{
|
|
11
|
-
sdp: string;
|
|
12
|
-
}>;
|
|
13
|
-
ttl?: number;
|
|
14
|
-
isPublic?: boolean;
|
|
15
|
-
metadata?: Record<string, any>;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* RondevuService - High-level service management with automatic signature handling
|
|
19
|
-
*
|
|
20
|
-
* Provides a simplified API for:
|
|
21
|
-
* - Username claiming with Ed25519 signatures
|
|
22
|
-
* - Service publishing with automatic signature generation
|
|
23
|
-
* - Keypair management
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```typescript
|
|
27
|
-
* // Initialize service (generates keypair automatically)
|
|
28
|
-
* const service = new RondevuService({
|
|
29
|
-
* apiUrl: 'https://signal.example.com',
|
|
30
|
-
* username: 'myusername',
|
|
31
|
-
* })
|
|
32
|
-
*
|
|
33
|
-
* await service.initialize()
|
|
34
|
-
*
|
|
35
|
-
* // Claim username (one time)
|
|
36
|
-
* await service.claimUsername()
|
|
37
|
-
*
|
|
38
|
-
* // Publish a service
|
|
39
|
-
* const publishedService = await service.publishService({
|
|
40
|
-
* serviceFqn: 'chat.app@1.0.0',
|
|
41
|
-
* offers: [{ sdp: offerSdp }],
|
|
42
|
-
* ttl: 300000,
|
|
43
|
-
* isPublic: true,
|
|
44
|
-
* })
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
export declare class RondevuService {
|
|
48
|
-
private readonly api;
|
|
49
|
-
private readonly username;
|
|
50
|
-
private keypair;
|
|
51
|
-
private usernameClaimed;
|
|
52
|
-
constructor(options: RondevuServiceOptions);
|
|
53
|
-
/**
|
|
54
|
-
* Initialize the service - generates keypair if not provided
|
|
55
|
-
* Call this before using other methods
|
|
56
|
-
*/
|
|
57
|
-
initialize(): Promise<void>;
|
|
58
|
-
/**
|
|
59
|
-
* Claim the username with Ed25519 signature
|
|
60
|
-
* Should be called once before publishing services
|
|
61
|
-
*/
|
|
62
|
-
claimUsername(): Promise<void>;
|
|
63
|
-
/**
|
|
64
|
-
* Publish a service with automatic signature generation
|
|
65
|
-
*/
|
|
66
|
-
publishService(options: PublishServiceOptions): Promise<Service>;
|
|
67
|
-
/**
|
|
68
|
-
* Get the current keypair (for backup/storage)
|
|
69
|
-
*/
|
|
70
|
-
getKeypair(): Keypair | null;
|
|
71
|
-
/**
|
|
72
|
-
* Get the username
|
|
73
|
-
*/
|
|
74
|
-
getUsername(): string;
|
|
75
|
-
/**
|
|
76
|
-
* Get the public key
|
|
77
|
-
*/
|
|
78
|
-
getPublicKey(): string | null;
|
|
79
|
-
/**
|
|
80
|
-
* Check if username has been claimed (checks with server)
|
|
81
|
-
*/
|
|
82
|
-
isUsernameClaimed(): Promise<boolean>;
|
|
83
|
-
/**
|
|
84
|
-
* Access to underlying API for advanced operations
|
|
85
|
-
*/
|
|
86
|
-
getAPI(): RondevuAPI;
|
|
87
|
-
}
|
package/dist/rondevu-service.js
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { RondevuAPI } from './api.js';
|
|
2
|
-
/**
|
|
3
|
-
* RondevuService - High-level service management with automatic signature handling
|
|
4
|
-
*
|
|
5
|
-
* Provides a simplified API for:
|
|
6
|
-
* - Username claiming with Ed25519 signatures
|
|
7
|
-
* - Service publishing with automatic signature generation
|
|
8
|
-
* - Keypair management
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```typescript
|
|
12
|
-
* // Initialize service (generates keypair automatically)
|
|
13
|
-
* const service = new RondevuService({
|
|
14
|
-
* apiUrl: 'https://signal.example.com',
|
|
15
|
-
* username: 'myusername',
|
|
16
|
-
* })
|
|
17
|
-
*
|
|
18
|
-
* await service.initialize()
|
|
19
|
-
*
|
|
20
|
-
* // Claim username (one time)
|
|
21
|
-
* await service.claimUsername()
|
|
22
|
-
*
|
|
23
|
-
* // Publish a service
|
|
24
|
-
* const publishedService = await service.publishService({
|
|
25
|
-
* serviceFqn: 'chat.app@1.0.0',
|
|
26
|
-
* offers: [{ sdp: offerSdp }],
|
|
27
|
-
* ttl: 300000,
|
|
28
|
-
* isPublic: true,
|
|
29
|
-
* })
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export class RondevuService {
|
|
33
|
-
constructor(options) {
|
|
34
|
-
this.keypair = null;
|
|
35
|
-
this.usernameClaimed = false;
|
|
36
|
-
this.username = options.username;
|
|
37
|
-
this.keypair = options.keypair || null;
|
|
38
|
-
this.api = new RondevuAPI(options.apiUrl, options.credentials);
|
|
39
|
-
console.log('[RondevuService] Constructor called:', {
|
|
40
|
-
username: this.username,
|
|
41
|
-
hasKeypair: !!this.keypair,
|
|
42
|
-
publicKey: this.keypair?.publicKey
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Initialize the service - generates keypair if not provided
|
|
47
|
-
* Call this before using other methods
|
|
48
|
-
*/
|
|
49
|
-
async initialize() {
|
|
50
|
-
console.log('[RondevuService] Initialize called, hasKeypair:', !!this.keypair);
|
|
51
|
-
if (!this.keypair) {
|
|
52
|
-
console.log('[RondevuService] Generating new keypair...');
|
|
53
|
-
this.keypair = await RondevuAPI.generateKeypair();
|
|
54
|
-
console.log('[RondevuService] Generated keypair, publicKey:', this.keypair.publicKey);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
console.log('[RondevuService] Using existing keypair, publicKey:', this.keypair.publicKey);
|
|
58
|
-
}
|
|
59
|
-
// Register with API if no credentials provided
|
|
60
|
-
if (!this.api['credentials']) {
|
|
61
|
-
const credentials = await this.api.register();
|
|
62
|
-
this.api.setCredentials(credentials);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Claim the username with Ed25519 signature
|
|
67
|
-
* Should be called once before publishing services
|
|
68
|
-
*/
|
|
69
|
-
async claimUsername() {
|
|
70
|
-
if (!this.keypair) {
|
|
71
|
-
throw new Error('Service not initialized. Call initialize() first.');
|
|
72
|
-
}
|
|
73
|
-
// Check if username is already claimed
|
|
74
|
-
const check = await this.api.checkUsername(this.username);
|
|
75
|
-
if (!check.available) {
|
|
76
|
-
// Verify it's claimed by us
|
|
77
|
-
if (check.publicKey === this.keypair.publicKey) {
|
|
78
|
-
this.usernameClaimed = true;
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
throw new Error(`Username "${this.username}" is already claimed by another user`);
|
|
82
|
-
}
|
|
83
|
-
// Generate signature for username claim
|
|
84
|
-
const message = `claim:${this.username}:${Date.now()}`;
|
|
85
|
-
const signature = await RondevuAPI.signMessage(message, this.keypair.privateKey);
|
|
86
|
-
// Claim the username
|
|
87
|
-
await this.api.claimUsername(this.username, this.keypair.publicKey, signature, message);
|
|
88
|
-
this.usernameClaimed = true;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Publish a service with automatic signature generation
|
|
92
|
-
*/
|
|
93
|
-
async publishService(options) {
|
|
94
|
-
if (!this.keypair) {
|
|
95
|
-
throw new Error('Service not initialized. Call initialize() first.');
|
|
96
|
-
}
|
|
97
|
-
if (!this.usernameClaimed) {
|
|
98
|
-
throw new Error('Username not claimed. Call claimUsername() first or the server will reject the service.');
|
|
99
|
-
}
|
|
100
|
-
const { serviceFqn, offers, ttl, isPublic, metadata } = options;
|
|
101
|
-
// Generate signature for service publication
|
|
102
|
-
const message = `publish:${this.username}:${serviceFqn}:${Date.now()}`;
|
|
103
|
-
const signature = await RondevuAPI.signMessage(message, this.keypair.privateKey);
|
|
104
|
-
// Create service request
|
|
105
|
-
const serviceRequest = {
|
|
106
|
-
username: this.username,
|
|
107
|
-
serviceFqn,
|
|
108
|
-
offers,
|
|
109
|
-
signature,
|
|
110
|
-
message,
|
|
111
|
-
ttl,
|
|
112
|
-
isPublic,
|
|
113
|
-
metadata,
|
|
114
|
-
};
|
|
115
|
-
// Publish to server
|
|
116
|
-
return await this.api.publishService(serviceRequest);
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Get the current keypair (for backup/storage)
|
|
120
|
-
*/
|
|
121
|
-
getKeypair() {
|
|
122
|
-
return this.keypair;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Get the username
|
|
126
|
-
*/
|
|
127
|
-
getUsername() {
|
|
128
|
-
return this.username;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Get the public key
|
|
132
|
-
*/
|
|
133
|
-
getPublicKey() {
|
|
134
|
-
return this.keypair?.publicKey || null;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Check if username has been claimed (checks with server)
|
|
138
|
-
*/
|
|
139
|
-
async isUsernameClaimed() {
|
|
140
|
-
if (!this.keypair) {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
try {
|
|
144
|
-
const check = await this.api.checkUsername(this.username);
|
|
145
|
-
// Debug logging
|
|
146
|
-
console.log('[RondevuService] Username check:', {
|
|
147
|
-
username: this.username,
|
|
148
|
-
available: check.available,
|
|
149
|
-
serverPublicKey: check.publicKey,
|
|
150
|
-
localPublicKey: this.keypair.publicKey,
|
|
151
|
-
match: check.publicKey === this.keypair.publicKey
|
|
152
|
-
});
|
|
153
|
-
// Username is claimed if it's not available and owned by our public key
|
|
154
|
-
const claimed = !check.available && check.publicKey === this.keypair.publicKey;
|
|
155
|
-
// Update internal flag to match server state
|
|
156
|
-
this.usernameClaimed = claimed;
|
|
157
|
-
return claimed;
|
|
158
|
-
}
|
|
159
|
-
catch (err) {
|
|
160
|
-
console.error('Failed to check username claim status:', err);
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Access to underlying API for advanced operations
|
|
166
|
-
*/
|
|
167
|
-
getAPI() {
|
|
168
|
-
return this.api;
|
|
169
|
-
}
|
|
170
|
-
}
|
package/dist/service-client.d.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { RondevuService } from './rondevu-service.js';
|
|
2
|
-
import { RTCDurableConnection } from './durable-connection.js';
|
|
3
|
-
import { EventBus } from './event-bus.js';
|
|
4
|
-
export interface ServiceClientOptions {
|
|
5
|
-
username: string;
|
|
6
|
-
serviceFqn: string;
|
|
7
|
-
rondevuService: RondevuService;
|
|
8
|
-
autoReconnect?: boolean;
|
|
9
|
-
maxReconnectAttempts?: number;
|
|
10
|
-
rtcConfiguration?: RTCConfiguration;
|
|
11
|
-
}
|
|
12
|
-
export interface ServiceClientEvents {
|
|
13
|
-
connected: RTCDurableConnection;
|
|
14
|
-
disconnected: void;
|
|
15
|
-
reconnecting: {
|
|
16
|
-
attempt: number;
|
|
17
|
-
maxAttempts: number;
|
|
18
|
-
};
|
|
19
|
-
error: Error;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* ServiceClient - High-level wrapper for connecting to a WebRTC service
|
|
23
|
-
*
|
|
24
|
-
* Simplifies client connection by handling:
|
|
25
|
-
* - Service discovery
|
|
26
|
-
* - Offer/answer exchange
|
|
27
|
-
* - ICE candidate polling
|
|
28
|
-
* - Automatic reconnection
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* ```typescript
|
|
32
|
-
* const client = new ServiceClient({
|
|
33
|
-
* username: 'host-user',
|
|
34
|
-
* serviceFqn: 'chat.app@1.0.0',
|
|
35
|
-
* rondevuService: myService
|
|
36
|
-
* })
|
|
37
|
-
*
|
|
38
|
-
* client.events.on('connected', conn => {
|
|
39
|
-
* conn.events.on('message', msg => console.log('Received:', msg))
|
|
40
|
-
* conn.sendMessage('Hello from client!')
|
|
41
|
-
* })
|
|
42
|
-
*
|
|
43
|
-
* await client.connect()
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
export declare class ServiceClient {
|
|
47
|
-
private options;
|
|
48
|
-
events: EventBus<ServiceClientEvents>;
|
|
49
|
-
private signaler;
|
|
50
|
-
private webrtcContext;
|
|
51
|
-
private connection;
|
|
52
|
-
private autoReconnect;
|
|
53
|
-
private maxReconnectAttempts;
|
|
54
|
-
private reconnectAttempts;
|
|
55
|
-
private isConnecting;
|
|
56
|
-
constructor(options: ServiceClientOptions);
|
|
57
|
-
/**
|
|
58
|
-
* Connect to the service
|
|
59
|
-
*/
|
|
60
|
-
connect(): Promise<RTCDurableConnection>;
|
|
61
|
-
/**
|
|
62
|
-
* Disconnect from the service
|
|
63
|
-
*/
|
|
64
|
-
dispose(): void;
|
|
65
|
-
/**
|
|
66
|
-
* @deprecated Use dispose() instead
|
|
67
|
-
*/
|
|
68
|
-
disconnect(): void;
|
|
69
|
-
/**
|
|
70
|
-
* Attempt to reconnect
|
|
71
|
-
*/
|
|
72
|
-
private attemptReconnect;
|
|
73
|
-
/**
|
|
74
|
-
* Get the current connection
|
|
75
|
-
*/
|
|
76
|
-
getConnection(): RTCDurableConnection | null;
|
|
77
|
-
}
|
package/dist/service-client.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { RondevuSignaler } from './rondevu-signaler.js';
|
|
2
|
-
import { WebRTCContext } from './webrtc-context.js';
|
|
3
|
-
import { RTCDurableConnection } from './durable-connection.js';
|
|
4
|
-
import { EventBus } from './event-bus.js';
|
|
5
|
-
/**
|
|
6
|
-
* ServiceClient - High-level wrapper for connecting to a WebRTC service
|
|
7
|
-
*
|
|
8
|
-
* Simplifies client connection by handling:
|
|
9
|
-
* - Service discovery
|
|
10
|
-
* - Offer/answer exchange
|
|
11
|
-
* - ICE candidate polling
|
|
12
|
-
* - Automatic reconnection
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```typescript
|
|
16
|
-
* const client = new ServiceClient({
|
|
17
|
-
* username: 'host-user',
|
|
18
|
-
* serviceFqn: 'chat.app@1.0.0',
|
|
19
|
-
* rondevuService: myService
|
|
20
|
-
* })
|
|
21
|
-
*
|
|
22
|
-
* client.events.on('connected', conn => {
|
|
23
|
-
* conn.events.on('message', msg => console.log('Received:', msg))
|
|
24
|
-
* conn.sendMessage('Hello from client!')
|
|
25
|
-
* })
|
|
26
|
-
*
|
|
27
|
-
* await client.connect()
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export class ServiceClient {
|
|
31
|
-
constructor(options) {
|
|
32
|
-
this.options = options;
|
|
33
|
-
this.signaler = null;
|
|
34
|
-
this.connection = null;
|
|
35
|
-
this.reconnectAttempts = 0;
|
|
36
|
-
this.isConnecting = false;
|
|
37
|
-
this.events = new EventBus();
|
|
38
|
-
this.webrtcContext = new WebRTCContext(options.rtcConfiguration);
|
|
39
|
-
this.autoReconnect = options.autoReconnect !== undefined ? options.autoReconnect : true;
|
|
40
|
-
this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Connect to the service
|
|
44
|
-
*/
|
|
45
|
-
async connect() {
|
|
46
|
-
if (this.isConnecting) {
|
|
47
|
-
throw new Error('Connection already in progress');
|
|
48
|
-
}
|
|
49
|
-
if (this.connection) {
|
|
50
|
-
throw new Error('Already connected. Disconnect first.');
|
|
51
|
-
}
|
|
52
|
-
this.isConnecting = true;
|
|
53
|
-
try {
|
|
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
|
-
});
|
|
65
|
-
});
|
|
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
|
-
}
|
|
86
|
-
});
|
|
87
|
-
this.connection = connection;
|
|
88
|
-
this.isConnecting = false;
|
|
89
|
-
return connection;
|
|
90
|
-
}
|
|
91
|
-
catch (err) {
|
|
92
|
-
this.isConnecting = false;
|
|
93
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
94
|
-
this.events.emit('error', error);
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Disconnect from the service
|
|
100
|
-
*/
|
|
101
|
-
dispose() {
|
|
102
|
-
if (this.signaler) {
|
|
103
|
-
this.signaler.dispose();
|
|
104
|
-
this.signaler = null;
|
|
105
|
-
}
|
|
106
|
-
if (this.connection) {
|
|
107
|
-
this.connection.disconnect();
|
|
108
|
-
this.connection = null;
|
|
109
|
-
}
|
|
110
|
-
this.isConnecting = false;
|
|
111
|
-
this.reconnectAttempts = 0;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* @deprecated Use dispose() instead
|
|
115
|
-
*/
|
|
116
|
-
disconnect() {
|
|
117
|
-
this.dispose();
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Attempt to reconnect
|
|
121
|
-
*/
|
|
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;
|
|
135
|
-
}
|
|
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);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Get the current connection
|
|
154
|
-
*/
|
|
155
|
-
getConnection() {
|
|
156
|
-
return this.connection;
|
|
157
|
-
}
|
|
158
|
-
}
|
package/dist/service-host.d.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { RondevuService } from './rondevu-service.js';
|
|
2
|
-
import { RTCDurableConnection } from './durable-connection.js';
|
|
3
|
-
import { EventBus } from './event-bus.js';
|
|
4
|
-
export interface ServiceHostOptions {
|
|
5
|
-
service: string;
|
|
6
|
-
rondevuService: RondevuService;
|
|
7
|
-
maxPeers?: number;
|
|
8
|
-
ttl?: number;
|
|
9
|
-
isPublic?: boolean;
|
|
10
|
-
rtcConfiguration?: RTCConfiguration;
|
|
11
|
-
metadata?: Record<string, any>;
|
|
12
|
-
}
|
|
13
|
-
export interface ServiceHostEvents {
|
|
14
|
-
connection: RTCDurableConnection;
|
|
15
|
-
error: Error;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* ServiceHost - High-level wrapper for hosting a WebRTC service
|
|
19
|
-
*
|
|
20
|
-
* Simplifies hosting by handling:
|
|
21
|
-
* - Offer/answer exchange
|
|
22
|
-
* - ICE candidate polling
|
|
23
|
-
* - Connection pool management
|
|
24
|
-
* - Automatic reconnection
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```typescript
|
|
28
|
-
* const host = new ServiceHost({
|
|
29
|
-
* service: 'chat.app@1.0.0',
|
|
30
|
-
* rondevuService: myService,
|
|
31
|
-
* maxPeers: 5
|
|
32
|
-
* })
|
|
33
|
-
*
|
|
34
|
-
* host.events.on('connection', conn => {
|
|
35
|
-
* conn.events.on('message', msg => console.log('Received:', msg))
|
|
36
|
-
* conn.sendMessage('Hello!')
|
|
37
|
-
* })
|
|
38
|
-
*
|
|
39
|
-
* await host.start()
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
export declare class ServiceHost {
|
|
43
|
-
private options;
|
|
44
|
-
events: EventBus<ServiceHostEvents>;
|
|
45
|
-
private signaler;
|
|
46
|
-
private webrtcContext;
|
|
47
|
-
private connections;
|
|
48
|
-
private maxPeers;
|
|
49
|
-
private running;
|
|
50
|
-
constructor(options: ServiceHostOptions);
|
|
51
|
-
/**
|
|
52
|
-
* Start hosting the service
|
|
53
|
-
*/
|
|
54
|
-
start(): Promise<void>;
|
|
55
|
-
/**
|
|
56
|
-
* Create the next connection for incoming peers
|
|
57
|
-
*/
|
|
58
|
-
private createNextConnection;
|
|
59
|
-
/**
|
|
60
|
-
* Stop hosting the service
|
|
61
|
-
*/
|
|
62
|
-
dispose(): void;
|
|
63
|
-
/**
|
|
64
|
-
* Get all active connections
|
|
65
|
-
*/
|
|
66
|
-
getConnections(): RTCDurableConnection[];
|
|
67
|
-
}
|
package/dist/service-host.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { RondevuSignaler } from './rondevu-signaler.js';
|
|
2
|
-
import { WebRTCContext } from './webrtc-context.js';
|
|
3
|
-
import { RTCDurableConnection } from './durable-connection.js';
|
|
4
|
-
import { EventBus } from './event-bus.js';
|
|
5
|
-
/**
|
|
6
|
-
* ServiceHost - High-level wrapper for hosting a WebRTC service
|
|
7
|
-
*
|
|
8
|
-
* Simplifies hosting by handling:
|
|
9
|
-
* - Offer/answer exchange
|
|
10
|
-
* - ICE candidate polling
|
|
11
|
-
* - Connection pool management
|
|
12
|
-
* - Automatic reconnection
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```typescript
|
|
16
|
-
* const host = new ServiceHost({
|
|
17
|
-
* service: 'chat.app@1.0.0',
|
|
18
|
-
* rondevuService: myService,
|
|
19
|
-
* maxPeers: 5
|
|
20
|
-
* })
|
|
21
|
-
*
|
|
22
|
-
* host.events.on('connection', conn => {
|
|
23
|
-
* conn.events.on('message', msg => console.log('Received:', msg))
|
|
24
|
-
* conn.sendMessage('Hello!')
|
|
25
|
-
* })
|
|
26
|
-
*
|
|
27
|
-
* await host.start()
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export class ServiceHost {
|
|
31
|
-
constructor(options) {
|
|
32
|
-
this.options = options;
|
|
33
|
-
this.signaler = null;
|
|
34
|
-
this.connections = [];
|
|
35
|
-
this.running = false;
|
|
36
|
-
this.events = new EventBus();
|
|
37
|
-
this.webrtcContext = new WebRTCContext(options.rtcConfiguration);
|
|
38
|
-
this.maxPeers = options.maxPeers || 5;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Start hosting the service
|
|
42
|
-
*/
|
|
43
|
-
async start() {
|
|
44
|
-
if (this.running) {
|
|
45
|
-
throw new Error('ServiceHost already running');
|
|
46
|
-
}
|
|
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');
|
|
83
|
-
}
|
|
84
|
-
await this.signaler.setOffer(offer);
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Create the next connection for incoming peers
|
|
88
|
-
*/
|
|
89
|
-
async createNextConnection() {
|
|
90
|
-
if (!this.signaler || !this.running) {
|
|
91
|
-
return;
|
|
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
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Stop hosting the service
|
|
100
|
-
*/
|
|
101
|
-
dispose() {
|
|
102
|
-
this.running = false;
|
|
103
|
-
// Cleanup signaler
|
|
104
|
-
if (this.signaler) {
|
|
105
|
-
this.signaler.dispose();
|
|
106
|
-
this.signaler = null;
|
|
107
|
-
}
|
|
108
|
-
// Disconnect all connections
|
|
109
|
-
for (const conn of this.connections) {
|
|
110
|
-
conn.disconnect();
|
|
111
|
-
}
|
|
112
|
-
this.connections = [];
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Get all active connections
|
|
116
|
-
*/
|
|
117
|
-
getConnections() {
|
|
118
|
-
return [...this.connections];
|
|
119
|
-
}
|
|
120
|
-
}
|
package/dist/signaler.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Signaler } from './types.js';
|
|
2
|
-
import { Binnable } from './bin.js';
|
|
3
|
-
import { RondevuAPI } from './api.js';
|
|
4
|
-
/**
|
|
5
|
-
* RondevuSignaler - Handles ICE candidate exchange via Rondevu API
|
|
6
|
-
* Uses polling to retrieve remote candidates
|
|
7
|
-
*/
|
|
8
|
-
export declare class RondevuSignaler implements Signaler {
|
|
9
|
-
private api;
|
|
10
|
-
private offerId;
|
|
11
|
-
constructor(api: RondevuAPI, offerId: string);
|
|
12
|
-
addOfferListener(callback: (offer: RTCSessionDescriptionInit) => void): Binnable;
|
|
13
|
-
addAnswerListener(callback: (answer: RTCSessionDescriptionInit) => void): Binnable;
|
|
14
|
-
setOffer(offer: RTCSessionDescriptionInit): Promise<void>;
|
|
15
|
-
setAnswer(answer: RTCSessionDescriptionInit): Promise<void>;
|
|
16
|
-
/**
|
|
17
|
-
* Send a local ICE candidate to signaling server
|
|
18
|
-
*/
|
|
19
|
-
addIceCandidate(candidate: RTCIceCandidate): Promise<void>;
|
|
20
|
-
/**
|
|
21
|
-
* Poll for remote ICE candidates and call callback for each one
|
|
22
|
-
* Returns cleanup function to stop polling
|
|
23
|
-
*/
|
|
24
|
-
addListener(callback: (candidate: RTCIceCandidate) => void): Binnable;
|
|
25
|
-
}
|