@xtr-dev/rondevu-client 0.3.5 → 0.4.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 +401 -60
- package/dist/auth.d.ts +18 -0
- package/dist/auth.js +39 -0
- package/dist/bloom.d.ts +30 -0
- package/dist/bloom.js +73 -0
- package/dist/connection.d.ts +94 -49
- package/dist/connection.js +214 -211
- package/dist/index.d.ts +8 -3
- package/dist/index.js +9 -5
- package/dist/offers.d.ts +108 -0
- package/dist/offers.js +214 -0
- package/dist/rondevu.d.ts +39 -42
- package/dist/rondevu.js +33 -175
- package/package.json +2 -2
package/dist/offers.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { RondevuAuth } from './auth.js';
|
|
2
|
+
export class RondevuOffers {
|
|
3
|
+
constructor(baseUrl, credentials, fetchFn) {
|
|
4
|
+
this.baseUrl = baseUrl;
|
|
5
|
+
this.credentials = credentials;
|
|
6
|
+
// Use provided fetch or fall back to global fetch
|
|
7
|
+
this.fetchFn = fetchFn || ((...args) => {
|
|
8
|
+
if (typeof globalThis.fetch === 'function') {
|
|
9
|
+
return globalThis.fetch(...args);
|
|
10
|
+
}
|
|
11
|
+
throw new Error('fetch is not available. Please provide a fetch implementation in the constructor options.');
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Create one or more offers
|
|
16
|
+
*/
|
|
17
|
+
async create(offers) {
|
|
18
|
+
const response = await this.fetchFn(`${this.baseUrl}/offers`, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
23
|
+
},
|
|
24
|
+
body: JSON.stringify({ offers }),
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
28
|
+
throw new Error(`Failed to create offers: ${error.error || response.statusText}`);
|
|
29
|
+
}
|
|
30
|
+
const data = await response.json();
|
|
31
|
+
return data.offers;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Find offers by topic with optional bloom filter
|
|
35
|
+
*/
|
|
36
|
+
async findByTopic(topic, options) {
|
|
37
|
+
const params = new URLSearchParams();
|
|
38
|
+
if (options?.bloomFilter) {
|
|
39
|
+
// Convert to base64
|
|
40
|
+
const binaryString = String.fromCharCode(...Array.from(options.bloomFilter));
|
|
41
|
+
const base64 = typeof btoa !== 'undefined'
|
|
42
|
+
? btoa(binaryString)
|
|
43
|
+
: (typeof Buffer !== 'undefined' ? Buffer.from(options.bloomFilter).toString('base64') : '');
|
|
44
|
+
params.set('bloom', base64);
|
|
45
|
+
}
|
|
46
|
+
if (options?.limit) {
|
|
47
|
+
params.set('limit', options.limit.toString());
|
|
48
|
+
}
|
|
49
|
+
const url = `${this.baseUrl}/offers/by-topic/${encodeURIComponent(topic)}${params.toString() ? '?' + params.toString() : ''}`;
|
|
50
|
+
const response = await this.fetchFn(url, {
|
|
51
|
+
method: 'GET',
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
55
|
+
throw new Error(`Failed to find offers: ${error.error || response.statusText}`);
|
|
56
|
+
}
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
return data.offers;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get all offers from a specific peer
|
|
62
|
+
*/
|
|
63
|
+
async getByPeerId(peerId) {
|
|
64
|
+
const response = await this.fetchFn(`${this.baseUrl}/peers/${encodeURIComponent(peerId)}/offers`, {
|
|
65
|
+
method: 'GET',
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
69
|
+
throw new Error(`Failed to get peer offers: ${error.error || response.statusText}`);
|
|
70
|
+
}
|
|
71
|
+
return await response.json();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get topics with active peer counts (paginated)
|
|
75
|
+
*/
|
|
76
|
+
async getTopics(options) {
|
|
77
|
+
const params = new URLSearchParams();
|
|
78
|
+
if (options?.limit) {
|
|
79
|
+
params.set('limit', options.limit.toString());
|
|
80
|
+
}
|
|
81
|
+
if (options?.offset) {
|
|
82
|
+
params.set('offset', options.offset.toString());
|
|
83
|
+
}
|
|
84
|
+
const url = `${this.baseUrl}/topics${params.toString() ? '?' + params.toString() : ''}`;
|
|
85
|
+
const response = await this.fetchFn(url, {
|
|
86
|
+
method: 'GET',
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
90
|
+
throw new Error(`Failed to get topics: ${error.error || response.statusText}`);
|
|
91
|
+
}
|
|
92
|
+
return await response.json();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get own offers
|
|
96
|
+
*/
|
|
97
|
+
async getMine() {
|
|
98
|
+
const response = await this.fetchFn(`${this.baseUrl}/offers/mine`, {
|
|
99
|
+
method: 'GET',
|
|
100
|
+
headers: {
|
|
101
|
+
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
106
|
+
throw new Error(`Failed to get own offers: ${error.error || response.statusText}`);
|
|
107
|
+
}
|
|
108
|
+
const data = await response.json();
|
|
109
|
+
return data.offers;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Update offer heartbeat
|
|
113
|
+
*/
|
|
114
|
+
async heartbeat(offerId) {
|
|
115
|
+
const response = await this.fetchFn(`${this.baseUrl}/offers/${encodeURIComponent(offerId)}/heartbeat`, {
|
|
116
|
+
method: 'PUT',
|
|
117
|
+
headers: {
|
|
118
|
+
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
123
|
+
throw new Error(`Failed to update heartbeat: ${error.error || response.statusText}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Delete an offer
|
|
128
|
+
*/
|
|
129
|
+
async delete(offerId) {
|
|
130
|
+
const response = await this.fetchFn(`${this.baseUrl}/offers/${encodeURIComponent(offerId)}`, {
|
|
131
|
+
method: 'DELETE',
|
|
132
|
+
headers: {
|
|
133
|
+
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
138
|
+
throw new Error(`Failed to delete offer: ${error.error || response.statusText}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Answer an offer
|
|
143
|
+
*/
|
|
144
|
+
async answer(offerId, sdp) {
|
|
145
|
+
const response = await this.fetchFn(`${this.baseUrl}/offers/${encodeURIComponent(offerId)}/answer`, {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: {
|
|
148
|
+
'Content-Type': 'application/json',
|
|
149
|
+
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
150
|
+
},
|
|
151
|
+
body: JSON.stringify({ sdp }),
|
|
152
|
+
});
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
155
|
+
throw new Error(`Failed to answer offer: ${error.error || response.statusText}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get answers to your offers
|
|
160
|
+
*/
|
|
161
|
+
async getAnswers() {
|
|
162
|
+
const response = await this.fetchFn(`${this.baseUrl}/offers/answers`, {
|
|
163
|
+
method: 'GET',
|
|
164
|
+
headers: {
|
|
165
|
+
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
170
|
+
throw new Error(`Failed to get answers: ${error.error || response.statusText}`);
|
|
171
|
+
}
|
|
172
|
+
const data = await response.json();
|
|
173
|
+
return data.answers;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Post ICE candidates for an offer
|
|
177
|
+
*/
|
|
178
|
+
async addIceCandidates(offerId, candidates) {
|
|
179
|
+
const response = await this.fetchFn(`${this.baseUrl}/offers/${encodeURIComponent(offerId)}/ice-candidates`, {
|
|
180
|
+
method: 'POST',
|
|
181
|
+
headers: {
|
|
182
|
+
'Content-Type': 'application/json',
|
|
183
|
+
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
184
|
+
},
|
|
185
|
+
body: JSON.stringify({ candidates }),
|
|
186
|
+
});
|
|
187
|
+
if (!response.ok) {
|
|
188
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
189
|
+
throw new Error(`Failed to add ICE candidates: ${error.error || response.statusText}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get ICE candidates for an offer
|
|
194
|
+
*/
|
|
195
|
+
async getIceCandidates(offerId, since) {
|
|
196
|
+
const params = new URLSearchParams();
|
|
197
|
+
if (since !== undefined) {
|
|
198
|
+
params.set('since', since.toString());
|
|
199
|
+
}
|
|
200
|
+
const url = `${this.baseUrl}/offers/${encodeURIComponent(offerId)}/ice-candidates${params.toString() ? '?' + params.toString() : ''}`;
|
|
201
|
+
const response = await this.fetchFn(url, {
|
|
202
|
+
method: 'GET',
|
|
203
|
+
headers: {
|
|
204
|
+
Authorization: RondevuAuth.createAuthHeader(this.credentials),
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
209
|
+
throw new Error(`Failed to get ICE candidates: ${error.error || response.statusText}`);
|
|
210
|
+
}
|
|
211
|
+
const data = await response.json();
|
|
212
|
+
return data.candidates;
|
|
213
|
+
}
|
|
214
|
+
}
|
package/dist/rondevu.d.ts
CHANGED
|
@@ -1,60 +1,57 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RondevuAuth, Credentials, FetchFunction } from './auth.js';
|
|
2
|
+
import { RondevuOffers } from './offers.js';
|
|
2
3
|
import { RondevuConnection } from './connection.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Main Rondevu WebRTC client with automatic connection management
|
|
6
|
-
*/
|
|
7
|
-
export declare class Rondevu {
|
|
8
|
-
readonly peerId: string;
|
|
9
|
-
readonly api: RondevuAPI;
|
|
10
|
-
private baseUrl;
|
|
11
|
-
private fetchImpl?;
|
|
12
|
-
private rtcConfig?;
|
|
13
|
-
private pollingInterval;
|
|
14
|
-
private connectionTimeout;
|
|
15
|
-
private wrtc?;
|
|
16
|
-
private RTCPeerConnection;
|
|
17
|
-
private RTCIceCandidate;
|
|
18
|
-
/**
|
|
19
|
-
* Creates a new Rondevu client instance
|
|
20
|
-
* @param options - Client configuration options
|
|
21
|
-
*/
|
|
22
|
-
constructor(options?: RondevuOptions);
|
|
4
|
+
export interface RondevuOptions {
|
|
23
5
|
/**
|
|
24
|
-
*
|
|
6
|
+
* Base URL of the Rondevu server
|
|
7
|
+
* @default 'https://api.ronde.vu'
|
|
25
8
|
*/
|
|
26
|
-
|
|
9
|
+
baseUrl?: string;
|
|
27
10
|
/**
|
|
28
|
-
*
|
|
29
|
-
* For now, just check major version compatibility
|
|
11
|
+
* Existing credentials (peerId + secret) to skip registration
|
|
30
12
|
*/
|
|
31
|
-
|
|
13
|
+
credentials?: Credentials;
|
|
32
14
|
/**
|
|
33
|
-
*
|
|
15
|
+
* Custom fetch implementation for environments without native fetch
|
|
16
|
+
* (Node.js < 18, some Workers environments, etc.)
|
|
17
|
+
*
|
|
18
|
+
* @example Node.js
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import fetch from 'node-fetch';
|
|
21
|
+
* const client = new Rondevu({ fetch });
|
|
22
|
+
* ```
|
|
34
23
|
*/
|
|
35
|
-
|
|
24
|
+
fetch?: FetchFunction;
|
|
25
|
+
}
|
|
26
|
+
export declare class Rondevu {
|
|
27
|
+
readonly auth: RondevuAuth;
|
|
28
|
+
private _offers?;
|
|
29
|
+
private credentials?;
|
|
30
|
+
private baseUrl;
|
|
31
|
+
private fetchFn?;
|
|
32
|
+
constructor(options?: RondevuOptions);
|
|
36
33
|
/**
|
|
37
|
-
*
|
|
34
|
+
* Get offers API (requires authentication)
|
|
38
35
|
*/
|
|
39
|
-
|
|
36
|
+
get offers(): RondevuOffers;
|
|
40
37
|
/**
|
|
41
|
-
*
|
|
42
|
-
* @param id - Offer identifier (custom code)
|
|
43
|
-
* @returns Promise that resolves to RondevuConnection
|
|
38
|
+
* Register and initialize authenticated client
|
|
44
39
|
*/
|
|
45
|
-
|
|
40
|
+
register(): Promise<Credentials>;
|
|
46
41
|
/**
|
|
47
|
-
*
|
|
48
|
-
* @param id - Offer code
|
|
49
|
-
* @returns Promise that resolves to RondevuConnection
|
|
42
|
+
* Check if client is authenticated
|
|
50
43
|
*/
|
|
51
|
-
|
|
44
|
+
isAuthenticated(): boolean;
|
|
52
45
|
/**
|
|
53
|
-
*
|
|
46
|
+
* Get current credentials
|
|
54
47
|
*/
|
|
55
|
-
|
|
48
|
+
getCredentials(): Credentials | undefined;
|
|
56
49
|
/**
|
|
57
|
-
*
|
|
50
|
+
* Create a new WebRTC connection (requires authentication)
|
|
51
|
+
* This is a high-level helper that creates and manages WebRTC connections
|
|
52
|
+
*
|
|
53
|
+
* @param rtcConfig Optional RTCConfiguration for the peer connection
|
|
54
|
+
* @returns RondevuConnection instance
|
|
58
55
|
*/
|
|
59
|
-
|
|
56
|
+
createConnection(rtcConfig?: RTCConfiguration): RondevuConnection;
|
|
60
57
|
}
|
package/dist/rondevu.js
CHANGED
|
@@ -1,199 +1,57 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RondevuAuth } from './auth.js';
|
|
2
|
+
import { RondevuOffers } from './offers.js';
|
|
2
3
|
import { RondevuConnection } from './connection.js';
|
|
3
|
-
/**
|
|
4
|
-
* Main Rondevu WebRTC client with automatic connection management
|
|
5
|
-
*/
|
|
6
4
|
export class Rondevu {
|
|
7
|
-
/**
|
|
8
|
-
* Creates a new Rondevu client instance
|
|
9
|
-
* @param options - Client configuration options
|
|
10
|
-
*/
|
|
11
5
|
constructor(options = {}) {
|
|
12
6
|
this.baseUrl = options.baseUrl || 'https://api.ronde.vu';
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
});
|
|
19
|
-
// Auto-generate peer ID if not provided
|
|
20
|
-
this.peerId = options.peerId || this.generatePeerId();
|
|
21
|
-
this.rtcConfig = options.rtcConfig;
|
|
22
|
-
this.pollingInterval = options.pollingInterval || 1000;
|
|
23
|
-
this.connectionTimeout = options.connectionTimeout || 30000;
|
|
24
|
-
// Use injected WebRTC polyfill or fall back to global
|
|
25
|
-
this.RTCPeerConnection = options.wrtc?.RTCPeerConnection || globalThis.RTCPeerConnection;
|
|
26
|
-
this.RTCIceCandidate = options.wrtc?.RTCIceCandidate || globalThis.RTCIceCandidate;
|
|
27
|
-
if (!this.RTCPeerConnection) {
|
|
28
|
-
throw new Error('RTCPeerConnection not available. ' +
|
|
29
|
-
'In Node.js, provide a WebRTC polyfill via the wrtc option. ' +
|
|
30
|
-
'Install: npm install @roamhq/wrtc or npm install wrtc');
|
|
7
|
+
this.fetchFn = options.fetch;
|
|
8
|
+
this.auth = new RondevuAuth(this.baseUrl, this.fetchFn);
|
|
9
|
+
if (options.credentials) {
|
|
10
|
+
this.credentials = options.credentials;
|
|
11
|
+
this._offers = new RondevuOffers(this.baseUrl, this.credentials, this.fetchFn);
|
|
31
12
|
}
|
|
32
|
-
// Check server version compatibility (async, don't block constructor)
|
|
33
|
-
this.checkServerVersion().catch(() => {
|
|
34
|
-
// Silently fail version check - connection will work even if version check fails
|
|
35
|
-
});
|
|
36
13
|
}
|
|
37
14
|
/**
|
|
38
|
-
*
|
|
15
|
+
* Get offers API (requires authentication)
|
|
39
16
|
*/
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const clientVersion = '0.3.5'; // Should match package.json
|
|
44
|
-
if (!this.isVersionCompatible(clientVersion, serverVersion)) {
|
|
45
|
-
console.warn(`[Rondevu] Version mismatch: client v${clientVersion}, server v${serverVersion}. ` +
|
|
46
|
-
'This may cause compatibility issues.');
|
|
47
|
-
}
|
|
17
|
+
get offers() {
|
|
18
|
+
if (!this._offers) {
|
|
19
|
+
throw new Error('Not authenticated. Call register() first or provide credentials.');
|
|
48
20
|
}
|
|
49
|
-
|
|
50
|
-
// Version check failed - server might not support /health endpoint
|
|
51
|
-
console.debug('[Rondevu] Could not check server version');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Check if client and server versions are compatible
|
|
56
|
-
* For now, just check major version compatibility
|
|
57
|
-
*/
|
|
58
|
-
isVersionCompatible(clientVersion, serverVersion) {
|
|
59
|
-
const clientMajor = parseInt(clientVersion.split('.')[0]);
|
|
60
|
-
const serverMajor = parseInt(serverVersion.split('.')[0]);
|
|
61
|
-
// Major versions must match
|
|
62
|
-
return clientMajor === serverMajor;
|
|
21
|
+
return this._offers;
|
|
63
22
|
}
|
|
64
23
|
/**
|
|
65
|
-
*
|
|
24
|
+
* Register and initialize authenticated client
|
|
66
25
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
26
|
+
async register() {
|
|
27
|
+
this.credentials = await this.auth.register();
|
|
28
|
+
// Create offers API instance
|
|
29
|
+
this._offers = new RondevuOffers(this.baseUrl, this.credentials, this.fetchFn);
|
|
30
|
+
return this.credentials;
|
|
69
31
|
}
|
|
70
32
|
/**
|
|
71
|
-
*
|
|
33
|
+
* Check if client is authenticated
|
|
72
34
|
*/
|
|
73
|
-
|
|
74
|
-
this.
|
|
35
|
+
isAuthenticated() {
|
|
36
|
+
return !!this.credentials;
|
|
75
37
|
}
|
|
76
38
|
/**
|
|
77
|
-
*
|
|
78
|
-
* @param id - Offer identifier (custom code)
|
|
79
|
-
* @returns Promise that resolves to RondevuConnection
|
|
39
|
+
* Get current credentials
|
|
80
40
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const pc = new this.RTCPeerConnection(this.rtcConfig);
|
|
84
|
-
// Create initial data channel for negotiation (required for offer creation)
|
|
85
|
-
pc.createDataChannel('_negotiation');
|
|
86
|
-
// Generate offer
|
|
87
|
-
const offer = await pc.createOffer();
|
|
88
|
-
await pc.setLocalDescription(offer);
|
|
89
|
-
// Wait for ICE gathering to complete
|
|
90
|
-
await this.waitForIceGathering(pc);
|
|
91
|
-
// Create offer on server with custom code
|
|
92
|
-
await this.api.createOffer({
|
|
93
|
-
peerId: this.peerId,
|
|
94
|
-
offer: pc.localDescription.sdp,
|
|
95
|
-
code: id,
|
|
96
|
-
});
|
|
97
|
-
// Create connection object
|
|
98
|
-
const connectionParams = {
|
|
99
|
-
id,
|
|
100
|
-
role: 'offerer',
|
|
101
|
-
pc,
|
|
102
|
-
localPeerId: this.peerId,
|
|
103
|
-
remotePeerId: '', // Will be populated when answer is received
|
|
104
|
-
pollingInterval: this.pollingInterval,
|
|
105
|
-
connectionTimeout: this.connectionTimeout,
|
|
106
|
-
wrtc: this.wrtc,
|
|
107
|
-
};
|
|
108
|
-
const connection = new RondevuConnection(connectionParams, this.api);
|
|
109
|
-
// Start polling for answer
|
|
110
|
-
connection.startPolling();
|
|
111
|
-
return connection;
|
|
41
|
+
getCredentials() {
|
|
42
|
+
return this.credentials;
|
|
112
43
|
}
|
|
113
44
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
45
|
+
* Create a new WebRTC connection (requires authentication)
|
|
46
|
+
* This is a high-level helper that creates and manages WebRTC connections
|
|
47
|
+
*
|
|
48
|
+
* @param rtcConfig Optional RTCConfiguration for the peer connection
|
|
49
|
+
* @returns RondevuConnection instance
|
|
117
50
|
*/
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (!offerData) {
|
|
122
|
-
throw new Error(`Offer ${id} not found or expired`);
|
|
123
|
-
}
|
|
124
|
-
// Create peer connection
|
|
125
|
-
const pc = new this.RTCPeerConnection(this.rtcConfig);
|
|
126
|
-
// Set remote offer
|
|
127
|
-
await pc.setRemoteDescription({
|
|
128
|
-
type: 'offer',
|
|
129
|
-
sdp: offerData.offer,
|
|
130
|
-
});
|
|
131
|
-
// Generate answer
|
|
132
|
-
const answer = await pc.createAnswer();
|
|
133
|
-
await pc.setLocalDescription(answer);
|
|
134
|
-
// Wait for ICE gathering
|
|
135
|
-
await this.waitForIceGathering(pc);
|
|
136
|
-
// Send answer to server
|
|
137
|
-
await this.api.sendAnswer({
|
|
138
|
-
code: id,
|
|
139
|
-
answer: pc.localDescription.sdp,
|
|
140
|
-
side: 'answerer',
|
|
141
|
-
});
|
|
142
|
-
// Create connection object
|
|
143
|
-
const connectionParams = {
|
|
144
|
-
id,
|
|
145
|
-
role: 'answerer',
|
|
146
|
-
pc,
|
|
147
|
-
localPeerId: this.peerId,
|
|
148
|
-
remotePeerId: '', // Will be determined from peerId in offer
|
|
149
|
-
pollingInterval: this.pollingInterval,
|
|
150
|
-
connectionTimeout: this.connectionTimeout,
|
|
151
|
-
wrtc: this.wrtc,
|
|
152
|
-
};
|
|
153
|
-
const connection = new RondevuConnection(connectionParams, this.api);
|
|
154
|
-
// Start polling for ICE candidates
|
|
155
|
-
connection.startPolling();
|
|
156
|
-
return connection;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Wait for ICE gathering to complete
|
|
160
|
-
*/
|
|
161
|
-
async waitForIceGathering(pc) {
|
|
162
|
-
if (pc.iceGatheringState === 'complete') {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
return new Promise((resolve) => {
|
|
166
|
-
const checkState = () => {
|
|
167
|
-
if (pc.iceGatheringState === 'complete') {
|
|
168
|
-
pc.removeEventListener('icegatheringstatechange', checkState);
|
|
169
|
-
resolve();
|
|
170
|
-
}
|
|
171
|
-
};
|
|
172
|
-
pc.addEventListener('icegatheringstatechange', checkState);
|
|
173
|
-
// Also set a timeout in case gathering takes too long
|
|
174
|
-
setTimeout(() => {
|
|
175
|
-
pc.removeEventListener('icegatheringstatechange', checkState);
|
|
176
|
-
resolve();
|
|
177
|
-
}, 5000);
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Find an offer by code
|
|
182
|
-
*/
|
|
183
|
-
async findOfferById(id) {
|
|
184
|
-
try {
|
|
185
|
-
// Poll for the offer directly
|
|
186
|
-
const response = await this.api.poll(id, 'answerer');
|
|
187
|
-
const answererResponse = response;
|
|
188
|
-
if (answererResponse.offer) {
|
|
189
|
-
return {
|
|
190
|
-
offer: answererResponse.offer,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
catch (err) {
|
|
196
|
-
throw new Error(`Failed to find offer ${id}: ${err.message}`);
|
|
51
|
+
createConnection(rtcConfig) {
|
|
52
|
+
if (!this._offers) {
|
|
53
|
+
throw new Error('Not authenticated. Call register() first or provide credentials.');
|
|
197
54
|
}
|
|
55
|
+
return new RondevuConnection(this._offers, rtcConfig);
|
|
198
56
|
}
|
|
199
57
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xtr-dev/rondevu-client",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "TypeScript client for Rondevu peer
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "TypeScript client for Rondevu topic-based peer discovery and signaling server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|