@xtr-dev/rondevu-client 0.8.3 → 0.9.1
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 +402 -436
- package/dist/durable/channel.d.ts +115 -0
- package/dist/durable/channel.js +301 -0
- package/dist/durable/connection.d.ts +125 -0
- package/dist/durable/connection.js +370 -0
- package/dist/durable/reconnection.d.ts +90 -0
- package/dist/durable/reconnection.js +127 -0
- package/dist/durable/service.d.ts +103 -0
- package/dist/durable/service.js +264 -0
- package/dist/durable/types.d.ts +149 -0
- package/dist/durable/types.js +28 -0
- package/dist/index.d.ts +5 -10
- package/dist/index.js +5 -9
- package/dist/offer-pool.d.ts +15 -3
- package/dist/offer-pool.js +34 -8
- package/dist/peer/exchanging-ice-state.js +10 -2
- package/dist/peer/index.d.ts +1 -1
- package/dist/peer/index.js +25 -3
- package/dist/peer/state.js +9 -1
- package/dist/rondevu.d.ts +88 -13
- package/dist/rondevu.js +110 -27
- package/dist/service-pool.d.ts +11 -3
- package/dist/service-pool.js +120 -42
- package/package.json +2 -2
- package/dist/bloom.d.ts +0 -30
- package/dist/bloom.js +0 -73
- package/dist/client.d.ts +0 -126
- package/dist/client.js +0 -171
- package/dist/connection.d.ts +0 -127
- package/dist/connection.js +0 -295
- package/dist/discovery.d.ts +0 -93
- package/dist/discovery.js +0 -164
- package/dist/peer.d.ts +0 -111
- package/dist/peer.js +0 -392
- package/dist/services.d.ts +0 -79
- package/dist/services.js +0 -206
- package/dist/types.d.ts +0 -157
- package/dist/types.js +0 -4
package/dist/client.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP API client for Rondevu peer signaling server
|
|
3
|
-
*/
|
|
4
|
-
export class RondevuAPI {
|
|
5
|
-
/**
|
|
6
|
-
* Creates a new Rondevu API client instance
|
|
7
|
-
* @param options - Client configuration options
|
|
8
|
-
*/
|
|
9
|
-
constructor(options) {
|
|
10
|
-
this.baseUrl = options.baseUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
11
|
-
this.fetchImpl = options.fetch || globalThis.fetch.bind(globalThis);
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Makes an HTTP request to the Rondevu server
|
|
15
|
-
*/
|
|
16
|
-
async request(endpoint, options = {}) {
|
|
17
|
-
const url = `${this.baseUrl}${endpoint}`;
|
|
18
|
-
const headers = {
|
|
19
|
-
...options.headers,
|
|
20
|
-
};
|
|
21
|
-
if (options.body) {
|
|
22
|
-
headers['Content-Type'] = 'application/json';
|
|
23
|
-
}
|
|
24
|
-
const response = await this.fetchImpl(url, {
|
|
25
|
-
...options,
|
|
26
|
-
headers,
|
|
27
|
-
});
|
|
28
|
-
const data = await response.json();
|
|
29
|
-
if (!response.ok) {
|
|
30
|
-
const error = data;
|
|
31
|
-
throw new Error(error.error || `HTTP ${response.status}: ${response.statusText}`);
|
|
32
|
-
}
|
|
33
|
-
return data;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Gets server version information
|
|
37
|
-
*
|
|
38
|
-
* @returns Server version
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```typescript
|
|
42
|
-
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
|
43
|
-
* const { version } = await api.getVersion();
|
|
44
|
-
* console.log('Server version:', version);
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
async getVersion() {
|
|
48
|
-
return this.request('/', {
|
|
49
|
-
method: 'GET',
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Creates a new offer
|
|
54
|
-
*
|
|
55
|
-
* @param request - Offer details including peer ID, signaling data, and optional custom code
|
|
56
|
-
* @returns Unique offer code (UUID or custom code)
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```typescript
|
|
60
|
-
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
|
61
|
-
* const { code } = await api.createOffer({
|
|
62
|
-
* peerId: 'peer-123',
|
|
63
|
-
* offer: signalingData,
|
|
64
|
-
* code: 'my-custom-code' // optional
|
|
65
|
-
* });
|
|
66
|
-
* console.log('Offer code:', code);
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
|
-
async createOffer(request) {
|
|
70
|
-
return this.request('/offer', {
|
|
71
|
-
method: 'POST',
|
|
72
|
-
body: JSON.stringify(request),
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Sends an answer or candidate to an existing offer
|
|
77
|
-
*
|
|
78
|
-
* @param request - Answer details including offer code and signaling data
|
|
79
|
-
* @returns Success confirmation
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```typescript
|
|
83
|
-
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
|
84
|
-
*
|
|
85
|
-
* // Send answer
|
|
86
|
-
* await api.sendAnswer({
|
|
87
|
-
* code: offerCode,
|
|
88
|
-
* answer: answerData,
|
|
89
|
-
* side: 'answerer'
|
|
90
|
-
* });
|
|
91
|
-
*
|
|
92
|
-
* // Send candidate
|
|
93
|
-
* await api.sendAnswer({
|
|
94
|
-
* code: offerCode,
|
|
95
|
-
* candidate: candidateData,
|
|
96
|
-
* side: 'offerer'
|
|
97
|
-
* });
|
|
98
|
-
* ```
|
|
99
|
-
*/
|
|
100
|
-
async sendAnswer(request) {
|
|
101
|
-
return this.request('/answer', {
|
|
102
|
-
method: 'POST',
|
|
103
|
-
body: JSON.stringify(request),
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Polls for offer data from the other peer
|
|
108
|
-
*
|
|
109
|
-
* @param code - Offer code
|
|
110
|
-
* @param side - Which side is polling ('offerer' or 'answerer')
|
|
111
|
-
* @returns Offer data including offers, answers, and candidates
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* ```typescript
|
|
115
|
-
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
|
116
|
-
*
|
|
117
|
-
* // Offerer polls for answer
|
|
118
|
-
* const offererData = await api.poll(offerCode, 'offerer');
|
|
119
|
-
* if (offererData.answer) {
|
|
120
|
-
* console.log('Received answer:', offererData.answer);
|
|
121
|
-
* }
|
|
122
|
-
*
|
|
123
|
-
* // Answerer polls for offer
|
|
124
|
-
* const answererData = await api.poll(offerCode, 'answerer');
|
|
125
|
-
* console.log('Received offer:', answererData.offer);
|
|
126
|
-
* ```
|
|
127
|
-
*/
|
|
128
|
-
async poll(code, side) {
|
|
129
|
-
const request = { code, side };
|
|
130
|
-
return this.request('/poll', {
|
|
131
|
-
method: 'POST',
|
|
132
|
-
body: JSON.stringify(request),
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Checks server health and version
|
|
137
|
-
*
|
|
138
|
-
* @returns Health status, timestamp, and version
|
|
139
|
-
*
|
|
140
|
-
* @example
|
|
141
|
-
* ```typescript
|
|
142
|
-
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
|
143
|
-
* const health = await api.health();
|
|
144
|
-
* console.log('Server status:', health.status);
|
|
145
|
-
* console.log('Server version:', health.version);
|
|
146
|
-
* ```
|
|
147
|
-
*/
|
|
148
|
-
async health() {
|
|
149
|
-
return this.request('/health', {
|
|
150
|
-
method: 'GET',
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Ends a session by deleting the offer from the server
|
|
155
|
-
*
|
|
156
|
-
* @param code - The offer code
|
|
157
|
-
* @returns Success confirmation
|
|
158
|
-
*
|
|
159
|
-
* @example
|
|
160
|
-
* ```typescript
|
|
161
|
-
* const api = new RondevuAPI({ baseUrl: 'https://example.com' });
|
|
162
|
-
* await api.leave('my-offer-code');
|
|
163
|
-
* ```
|
|
164
|
-
*/
|
|
165
|
-
async leave(code) {
|
|
166
|
-
return this.request('/leave', {
|
|
167
|
-
method: 'POST',
|
|
168
|
-
body: JSON.stringify({ code }),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
package/dist/connection.d.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { RondevuOffers } from './offers.js';
|
|
2
|
-
/**
|
|
3
|
-
* Events emitted by RondevuConnection
|
|
4
|
-
*/
|
|
5
|
-
export interface RondevuConnectionEvents {
|
|
6
|
-
'connecting': () => void;
|
|
7
|
-
'connected': () => void;
|
|
8
|
-
'disconnected': () => void;
|
|
9
|
-
'error': (error: Error) => void;
|
|
10
|
-
'datachannel': (channel: RTCDataChannel) => void;
|
|
11
|
-
'track': (event: RTCTrackEvent) => void;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Options for creating a WebRTC connection
|
|
15
|
-
*/
|
|
16
|
-
export interface ConnectionOptions {
|
|
17
|
-
/**
|
|
18
|
-
* RTCConfiguration for the peer connection
|
|
19
|
-
* @default { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] }
|
|
20
|
-
*/
|
|
21
|
-
rtcConfig?: RTCConfiguration;
|
|
22
|
-
/**
|
|
23
|
-
* Topics to advertise this connection under
|
|
24
|
-
*/
|
|
25
|
-
topics: string[];
|
|
26
|
-
/**
|
|
27
|
-
* How long the offer should live (milliseconds)
|
|
28
|
-
* @default 300000 (5 minutes)
|
|
29
|
-
*/
|
|
30
|
-
ttl?: number;
|
|
31
|
-
/**
|
|
32
|
-
* Whether to create a data channel automatically (for offerer)
|
|
33
|
-
* @default true
|
|
34
|
-
*/
|
|
35
|
-
createDataChannel?: boolean;
|
|
36
|
-
/**
|
|
37
|
-
* Label for the automatically created data channel
|
|
38
|
-
* @default 'data'
|
|
39
|
-
*/
|
|
40
|
-
dataChannelLabel?: string;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* High-level WebRTC connection manager for Rondevu
|
|
44
|
-
* Handles offer/answer exchange, ICE candidates, and connection lifecycle
|
|
45
|
-
*/
|
|
46
|
-
export declare class RondevuConnection {
|
|
47
|
-
private rtcConfig;
|
|
48
|
-
private pc;
|
|
49
|
-
private offersApi;
|
|
50
|
-
private offerId?;
|
|
51
|
-
private role?;
|
|
52
|
-
private icePollingInterval?;
|
|
53
|
-
private answerPollingInterval?;
|
|
54
|
-
private lastIceTimestamp;
|
|
55
|
-
private eventListeners;
|
|
56
|
-
private dataChannel?;
|
|
57
|
-
private pendingIceCandidates;
|
|
58
|
-
/**
|
|
59
|
-
* Current connection state
|
|
60
|
-
*/
|
|
61
|
-
get connectionState(): RTCPeerConnectionState;
|
|
62
|
-
/**
|
|
63
|
-
* The offer ID for this connection
|
|
64
|
-
*/
|
|
65
|
-
get id(): string | undefined;
|
|
66
|
-
/**
|
|
67
|
-
* Get the primary data channel (if created)
|
|
68
|
-
*/
|
|
69
|
-
get channel(): RTCDataChannel | undefined;
|
|
70
|
-
constructor(offersApi: RondevuOffers, rtcConfig?: RTCConfiguration);
|
|
71
|
-
/**
|
|
72
|
-
* Set up peer connection event handlers
|
|
73
|
-
*/
|
|
74
|
-
private setupPeerConnection;
|
|
75
|
-
/**
|
|
76
|
-
* Flush buffered ICE candidates (trickle ICE support)
|
|
77
|
-
*/
|
|
78
|
-
private flushPendingIceCandidates;
|
|
79
|
-
/**
|
|
80
|
-
* Create an offer and advertise on topics
|
|
81
|
-
*/
|
|
82
|
-
createOffer(options: ConnectionOptions): Promise<string>;
|
|
83
|
-
/**
|
|
84
|
-
* Answer an existing offer
|
|
85
|
-
*/
|
|
86
|
-
answer(offerId: string, offerSdp: string): Promise<void>;
|
|
87
|
-
/**
|
|
88
|
-
* Start polling for answers (offerer only)
|
|
89
|
-
*/
|
|
90
|
-
private startAnswerPolling;
|
|
91
|
-
/**
|
|
92
|
-
* Start polling for ICE candidates
|
|
93
|
-
*/
|
|
94
|
-
private startIcePolling;
|
|
95
|
-
/**
|
|
96
|
-
* Stop answer polling
|
|
97
|
-
*/
|
|
98
|
-
private stopAnswerPolling;
|
|
99
|
-
/**
|
|
100
|
-
* Stop ICE polling
|
|
101
|
-
*/
|
|
102
|
-
private stopIcePolling;
|
|
103
|
-
/**
|
|
104
|
-
* Stop all polling
|
|
105
|
-
*/
|
|
106
|
-
private stopPolling;
|
|
107
|
-
/**
|
|
108
|
-
* Add event listener
|
|
109
|
-
*/
|
|
110
|
-
on<K extends keyof RondevuConnectionEvents>(event: K, listener: RondevuConnectionEvents[K]): void;
|
|
111
|
-
/**
|
|
112
|
-
* Remove event listener
|
|
113
|
-
*/
|
|
114
|
-
off<K extends keyof RondevuConnectionEvents>(event: K, listener: RondevuConnectionEvents[K]): void;
|
|
115
|
-
/**
|
|
116
|
-
* Emit event
|
|
117
|
-
*/
|
|
118
|
-
private emit;
|
|
119
|
-
/**
|
|
120
|
-
* Add a media track to the connection
|
|
121
|
-
*/
|
|
122
|
-
addTrack(track: MediaStreamTrack, ...streams: MediaStream[]): RTCRtpSender;
|
|
123
|
-
/**
|
|
124
|
-
* Close the connection and clean up
|
|
125
|
-
*/
|
|
126
|
-
close(): void;
|
|
127
|
-
}
|
package/dist/connection.js
DELETED
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* High-level WebRTC connection manager for Rondevu
|
|
3
|
-
* Handles offer/answer exchange, ICE candidates, and connection lifecycle
|
|
4
|
-
*/
|
|
5
|
-
export class RondevuConnection {
|
|
6
|
-
/**
|
|
7
|
-
* Current connection state
|
|
8
|
-
*/
|
|
9
|
-
get connectionState() {
|
|
10
|
-
return this.pc.connectionState;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* The offer ID for this connection
|
|
14
|
-
*/
|
|
15
|
-
get id() {
|
|
16
|
-
return this.offerId;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Get the primary data channel (if created)
|
|
20
|
-
*/
|
|
21
|
-
get channel() {
|
|
22
|
-
return this.dataChannel;
|
|
23
|
-
}
|
|
24
|
-
constructor(offersApi, rtcConfig = {
|
|
25
|
-
iceServers: [
|
|
26
|
-
{ urls: 'stun:stun.l.google.com:19302' },
|
|
27
|
-
{ urls: 'stun:stun1.l.google.com:19302' }
|
|
28
|
-
]
|
|
29
|
-
}) {
|
|
30
|
-
this.rtcConfig = rtcConfig;
|
|
31
|
-
this.lastIceTimestamp = 0; // Start at 0 to get all candidates on first poll
|
|
32
|
-
this.eventListeners = new Map();
|
|
33
|
-
this.pendingIceCandidates = []; // Store candidates as plain objects
|
|
34
|
-
this.offersApi = offersApi;
|
|
35
|
-
this.pc = new RTCPeerConnection(rtcConfig);
|
|
36
|
-
this.setupPeerConnection();
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Set up peer connection event handlers
|
|
40
|
-
*/
|
|
41
|
-
setupPeerConnection() {
|
|
42
|
-
this.pc.onicecandidate = async (event) => {
|
|
43
|
-
if (event.candidate) {
|
|
44
|
-
// Serialize the entire candidate object to plain JSON
|
|
45
|
-
const candidateData = event.candidate.toJSON();
|
|
46
|
-
// Skip end-of-candidates signal (empty candidate string)
|
|
47
|
-
// Some browsers send this as a candidate object with empty/null properties
|
|
48
|
-
if (!candidateData.candidate || candidateData.candidate === '') {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (this.offerId) {
|
|
52
|
-
// offerId is set, send immediately (trickle ICE)
|
|
53
|
-
try {
|
|
54
|
-
await this.offersApi.addIceCandidates(this.offerId, [candidateData]);
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
console.error('Error sending ICE candidate:', err);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
// offerId not set yet, buffer the candidate
|
|
62
|
-
this.pendingIceCandidates.push(candidateData);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
this.pc.onconnectionstatechange = () => {
|
|
67
|
-
switch (this.pc.connectionState) {
|
|
68
|
-
case 'connecting':
|
|
69
|
-
this.emit('connecting');
|
|
70
|
-
break;
|
|
71
|
-
case 'connected':
|
|
72
|
-
this.emit('connected');
|
|
73
|
-
// Stop polling once connected - we have all the ICE candidates we need
|
|
74
|
-
this.stopPolling();
|
|
75
|
-
break;
|
|
76
|
-
case 'disconnected':
|
|
77
|
-
case 'failed':
|
|
78
|
-
case 'closed':
|
|
79
|
-
this.emit('disconnected');
|
|
80
|
-
this.stopPolling();
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
this.pc.ondatachannel = (event) => {
|
|
85
|
-
this.dataChannel = event.channel;
|
|
86
|
-
this.emit('datachannel', event.channel);
|
|
87
|
-
};
|
|
88
|
-
this.pc.ontrack = (event) => {
|
|
89
|
-
this.emit('track', event);
|
|
90
|
-
};
|
|
91
|
-
this.pc.onicecandidateerror = (event) => {
|
|
92
|
-
console.error('ICE candidate error:', event);
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Flush buffered ICE candidates (trickle ICE support)
|
|
97
|
-
*/
|
|
98
|
-
async flushPendingIceCandidates() {
|
|
99
|
-
if (this.pendingIceCandidates.length > 0 && this.offerId) {
|
|
100
|
-
try {
|
|
101
|
-
await this.offersApi.addIceCandidates(this.offerId, this.pendingIceCandidates);
|
|
102
|
-
this.pendingIceCandidates = [];
|
|
103
|
-
}
|
|
104
|
-
catch (err) {
|
|
105
|
-
console.error('Error flushing pending ICE candidates:', err);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Create an offer and advertise on topics
|
|
111
|
-
*/
|
|
112
|
-
async createOffer(options) {
|
|
113
|
-
this.role = 'offerer';
|
|
114
|
-
// Create data channel if requested
|
|
115
|
-
if (options.createDataChannel !== false) {
|
|
116
|
-
this.dataChannel = this.pc.createDataChannel(options.dataChannelLabel || 'data');
|
|
117
|
-
this.emit('datachannel', this.dataChannel);
|
|
118
|
-
}
|
|
119
|
-
// Create WebRTC offer
|
|
120
|
-
const offer = await this.pc.createOffer();
|
|
121
|
-
await this.pc.setLocalDescription(offer);
|
|
122
|
-
// Create offer on Rondevu server
|
|
123
|
-
const offers = await this.offersApi.create([{
|
|
124
|
-
sdp: offer.sdp,
|
|
125
|
-
topics: options.topics,
|
|
126
|
-
ttl: options.ttl || 300000
|
|
127
|
-
}]);
|
|
128
|
-
this.offerId = offers[0].id;
|
|
129
|
-
// Flush any ICE candidates that were generated during offer creation
|
|
130
|
-
await this.flushPendingIceCandidates();
|
|
131
|
-
// Start polling for answers
|
|
132
|
-
this.startAnswerPolling();
|
|
133
|
-
return this.offerId;
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Answer an existing offer
|
|
137
|
-
*/
|
|
138
|
-
async answer(offerId, offerSdp) {
|
|
139
|
-
this.role = 'answerer';
|
|
140
|
-
// Set remote description
|
|
141
|
-
await this.pc.setRemoteDescription({
|
|
142
|
-
type: 'offer',
|
|
143
|
-
sdp: offerSdp
|
|
144
|
-
});
|
|
145
|
-
// Create answer
|
|
146
|
-
const answer = await this.pc.createAnswer();
|
|
147
|
-
await this.pc.setLocalDescription(answer);
|
|
148
|
-
// Send answer to server FIRST
|
|
149
|
-
// This registers us as the answerer before ICE candidates arrive
|
|
150
|
-
await this.offersApi.answer(offerId, answer.sdp);
|
|
151
|
-
// Now set offerId to enable ICE candidate sending
|
|
152
|
-
// This prevents a race condition where ICE candidates arrive before answer is registered
|
|
153
|
-
this.offerId = offerId;
|
|
154
|
-
// Flush any ICE candidates that were generated during answer creation
|
|
155
|
-
await this.flushPendingIceCandidates();
|
|
156
|
-
// Start polling for ICE candidates
|
|
157
|
-
this.startIcePolling();
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Start polling for answers (offerer only)
|
|
161
|
-
*/
|
|
162
|
-
startAnswerPolling() {
|
|
163
|
-
if (this.role !== 'offerer' || !this.offerId)
|
|
164
|
-
return;
|
|
165
|
-
this.answerPollingInterval = setInterval(async () => {
|
|
166
|
-
try {
|
|
167
|
-
const answers = await this.offersApi.getAnswers();
|
|
168
|
-
const myAnswer = answers.find(a => a.offerId === this.offerId);
|
|
169
|
-
if (myAnswer) {
|
|
170
|
-
// Set remote description
|
|
171
|
-
await this.pc.setRemoteDescription({
|
|
172
|
-
type: 'answer',
|
|
173
|
-
sdp: myAnswer.sdp
|
|
174
|
-
});
|
|
175
|
-
// Stop answer polling, start ICE polling
|
|
176
|
-
this.stopAnswerPolling();
|
|
177
|
-
this.startIcePolling();
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
catch (err) {
|
|
181
|
-
console.error('Error polling for answers:', err);
|
|
182
|
-
// Stop polling if offer expired/not found
|
|
183
|
-
if (err instanceof Error && err.message.includes('not found')) {
|
|
184
|
-
this.stopPolling();
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}, 2000);
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Start polling for ICE candidates
|
|
191
|
-
*/
|
|
192
|
-
startIcePolling() {
|
|
193
|
-
if (!this.offerId)
|
|
194
|
-
return;
|
|
195
|
-
this.icePollingInterval = setInterval(async () => {
|
|
196
|
-
if (!this.offerId)
|
|
197
|
-
return;
|
|
198
|
-
try {
|
|
199
|
-
const candidates = await this.offersApi.getIceCandidates(this.offerId, this.lastIceTimestamp);
|
|
200
|
-
for (const cand of candidates) {
|
|
201
|
-
// Server already filters candidates by role, so all candidates here are from remote peer
|
|
202
|
-
try {
|
|
203
|
-
// Skip invalid or empty candidates
|
|
204
|
-
if (!cand.candidate || !cand.candidate.candidate || cand.candidate.candidate === '') {
|
|
205
|
-
this.lastIceTimestamp = cand.createdAt;
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
await this.pc.addIceCandidate(new RTCIceCandidate(cand.candidate));
|
|
209
|
-
this.lastIceTimestamp = cand.createdAt;
|
|
210
|
-
}
|
|
211
|
-
catch (err) {
|
|
212
|
-
// Log but don't fail on individual candidate errors
|
|
213
|
-
console.warn('Failed to add ICE candidate:', err, cand);
|
|
214
|
-
this.lastIceTimestamp = cand.createdAt;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
catch (err) {
|
|
219
|
-
console.error('Error polling for ICE candidates:', err);
|
|
220
|
-
// Stop polling if offer expired/not found
|
|
221
|
-
if (err instanceof Error && err.message.includes('not found')) {
|
|
222
|
-
this.stopPolling();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}, 1000);
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Stop answer polling
|
|
229
|
-
*/
|
|
230
|
-
stopAnswerPolling() {
|
|
231
|
-
if (this.answerPollingInterval) {
|
|
232
|
-
clearInterval(this.answerPollingInterval);
|
|
233
|
-
this.answerPollingInterval = undefined;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Stop ICE polling
|
|
238
|
-
*/
|
|
239
|
-
stopIcePolling() {
|
|
240
|
-
if (this.icePollingInterval) {
|
|
241
|
-
clearInterval(this.icePollingInterval);
|
|
242
|
-
this.icePollingInterval = undefined;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* Stop all polling
|
|
247
|
-
*/
|
|
248
|
-
stopPolling() {
|
|
249
|
-
this.stopAnswerPolling();
|
|
250
|
-
this.stopIcePolling();
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Add event listener
|
|
254
|
-
*/
|
|
255
|
-
on(event, listener) {
|
|
256
|
-
if (!this.eventListeners.has(event)) {
|
|
257
|
-
this.eventListeners.set(event, new Set());
|
|
258
|
-
}
|
|
259
|
-
this.eventListeners.get(event).add(listener);
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* Remove event listener
|
|
263
|
-
*/
|
|
264
|
-
off(event, listener) {
|
|
265
|
-
const listeners = this.eventListeners.get(event);
|
|
266
|
-
if (listeners) {
|
|
267
|
-
listeners.delete(listener);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Emit event
|
|
272
|
-
*/
|
|
273
|
-
emit(event, ...args) {
|
|
274
|
-
const listeners = this.eventListeners.get(event);
|
|
275
|
-
if (listeners) {
|
|
276
|
-
listeners.forEach(listener => {
|
|
277
|
-
listener(...args);
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Add a media track to the connection
|
|
283
|
-
*/
|
|
284
|
-
addTrack(track, ...streams) {
|
|
285
|
-
return this.pc.addTrack(track, ...streams);
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Close the connection and clean up
|
|
289
|
-
*/
|
|
290
|
-
close() {
|
|
291
|
-
this.stopPolling();
|
|
292
|
-
this.pc.close();
|
|
293
|
-
this.eventListeners.clear();
|
|
294
|
-
}
|
|
295
|
-
}
|
package/dist/discovery.d.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import RondevuPeer from './peer/index.js';
|
|
2
|
-
/**
|
|
3
|
-
* Service info from discovery
|
|
4
|
-
*/
|
|
5
|
-
export interface ServiceInfo {
|
|
6
|
-
uuid: string;
|
|
7
|
-
isPublic: boolean;
|
|
8
|
-
serviceFqn?: string;
|
|
9
|
-
metadata?: Record<string, any>;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Service list result
|
|
13
|
-
*/
|
|
14
|
-
export interface ServiceListResult {
|
|
15
|
-
username: string;
|
|
16
|
-
services: ServiceInfo[];
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Service query result
|
|
20
|
-
*/
|
|
21
|
-
export interface ServiceQueryResult {
|
|
22
|
-
uuid: string;
|
|
23
|
-
allowed: boolean;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Service details
|
|
27
|
-
*/
|
|
28
|
-
export interface ServiceDetails {
|
|
29
|
-
serviceId: string;
|
|
30
|
-
username: string;
|
|
31
|
-
serviceFqn: string;
|
|
32
|
-
offerId: string;
|
|
33
|
-
sdp: string;
|
|
34
|
-
isPublic: boolean;
|
|
35
|
-
metadata?: Record<string, any>;
|
|
36
|
-
createdAt: number;
|
|
37
|
-
expiresAt: number;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Connect result
|
|
41
|
-
*/
|
|
42
|
-
export interface ConnectResult {
|
|
43
|
-
peer: RondevuPeer;
|
|
44
|
-
channel: RTCDataChannel;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Rondevu Discovery API
|
|
48
|
-
* Handles service discovery and connections
|
|
49
|
-
*/
|
|
50
|
-
export declare class RondevuDiscovery {
|
|
51
|
-
private baseUrl;
|
|
52
|
-
private credentials;
|
|
53
|
-
private offersApi;
|
|
54
|
-
constructor(baseUrl: string, credentials: {
|
|
55
|
-
peerId: string;
|
|
56
|
-
secret: string;
|
|
57
|
-
});
|
|
58
|
-
/**
|
|
59
|
-
* Lists all services for a username
|
|
60
|
-
* Returns UUIDs only for private services, full details for public
|
|
61
|
-
*/
|
|
62
|
-
listServices(username: string): Promise<ServiceListResult>;
|
|
63
|
-
/**
|
|
64
|
-
* Queries a service by FQN
|
|
65
|
-
* Returns UUID if service exists and is allowed
|
|
66
|
-
*/
|
|
67
|
-
queryService(username: string, serviceFqn: string): Promise<ServiceQueryResult>;
|
|
68
|
-
/**
|
|
69
|
-
* Gets service details by UUID
|
|
70
|
-
*/
|
|
71
|
-
getServiceDetails(uuid: string): Promise<ServiceDetails>;
|
|
72
|
-
/**
|
|
73
|
-
* Connects to a service by UUID
|
|
74
|
-
*/
|
|
75
|
-
connectToService(uuid: string, options?: {
|
|
76
|
-
rtcConfig?: RTCConfiguration;
|
|
77
|
-
onConnected?: () => void;
|
|
78
|
-
onData?: (data: any) => void;
|
|
79
|
-
}): Promise<RondevuPeer>;
|
|
80
|
-
/**
|
|
81
|
-
* Convenience method: Query and connect in one call
|
|
82
|
-
* Returns both peer and data channel
|
|
83
|
-
*/
|
|
84
|
-
connect(username: string, serviceFqn: string, options?: {
|
|
85
|
-
rtcConfig?: RTCConfiguration;
|
|
86
|
-
}): Promise<ConnectResult>;
|
|
87
|
-
/**
|
|
88
|
-
* Convenience method: Connect to service by UUID with channel
|
|
89
|
-
*/
|
|
90
|
-
connectByUuid(uuid: string, options?: {
|
|
91
|
-
rtcConfig?: RTCConfiguration;
|
|
92
|
-
}): Promise<ConnectResult>;
|
|
93
|
-
}
|