@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
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js Crypto adapter for Node.js environments
|
|
3
|
+
* Requires Node.js 19+ or Node.js 18 with --experimental-global-webcrypto flag
|
|
4
|
+
*/
|
|
5
|
+
import * as ed25519 from '@noble/ed25519';
|
|
6
|
+
/**
|
|
7
|
+
* Node.js Crypto implementation using Node.js built-in APIs
|
|
8
|
+
* Uses Buffer for base64 encoding and crypto.randomBytes for random generation
|
|
9
|
+
*
|
|
10
|
+
* Requirements:
|
|
11
|
+
* - Node.js 19+ (crypto.subtle available globally)
|
|
12
|
+
* - OR Node.js 18 with --experimental-global-webcrypto flag
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { RondevuAPI } from '@xtr-dev/rondevu-client'
|
|
17
|
+
* import { NodeCryptoAdapter } from '@xtr-dev/rondevu-client/node'
|
|
18
|
+
*
|
|
19
|
+
* const api = new RondevuAPI(
|
|
20
|
+
* 'https://signal.example.com',
|
|
21
|
+
* 'alice',
|
|
22
|
+
* keypair,
|
|
23
|
+
* new NodeCryptoAdapter()
|
|
24
|
+
* )
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export class NodeCryptoAdapter {
|
|
28
|
+
constructor() {
|
|
29
|
+
// Set SHA-512 hash function for ed25519 using Node's crypto.subtle
|
|
30
|
+
if (typeof crypto === 'undefined' || !crypto.subtle) {
|
|
31
|
+
throw new Error('crypto.subtle is not available. ' +
|
|
32
|
+
'Node.js 19+ is required, or Node.js 18 with --experimental-global-webcrypto flag');
|
|
33
|
+
}
|
|
34
|
+
ed25519.hashes.sha512Async = async (message) => {
|
|
35
|
+
const hash = await crypto.subtle.digest('SHA-512', message);
|
|
36
|
+
return new Uint8Array(hash);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async generateKeypair() {
|
|
40
|
+
const privateKey = ed25519.utils.randomSecretKey();
|
|
41
|
+
const publicKey = await ed25519.getPublicKeyAsync(privateKey);
|
|
42
|
+
return {
|
|
43
|
+
publicKey: this.bytesToBase64(publicKey),
|
|
44
|
+
privateKey: this.bytesToBase64(privateKey),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async signMessage(message, privateKeyBase64) {
|
|
48
|
+
const privateKey = this.base64ToBytes(privateKeyBase64);
|
|
49
|
+
const encoder = new TextEncoder();
|
|
50
|
+
const messageBytes = encoder.encode(message);
|
|
51
|
+
const signature = await ed25519.signAsync(messageBytes, privateKey);
|
|
52
|
+
return this.bytesToBase64(signature);
|
|
53
|
+
}
|
|
54
|
+
async verifySignature(message, signatureBase64, publicKeyBase64) {
|
|
55
|
+
try {
|
|
56
|
+
const signature = this.base64ToBytes(signatureBase64);
|
|
57
|
+
const publicKey = this.base64ToBytes(publicKeyBase64);
|
|
58
|
+
const encoder = new TextEncoder();
|
|
59
|
+
const messageBytes = encoder.encode(message);
|
|
60
|
+
return await ed25519.verifyAsync(signature, messageBytes, publicKey);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
bytesToBase64(bytes) {
|
|
67
|
+
// Node.js Buffer provides native base64 encoding
|
|
68
|
+
// @ts-expect-error - Buffer is available in Node.js but not in browser TypeScript definitions
|
|
69
|
+
return Buffer.from(bytes).toString('base64');
|
|
70
|
+
}
|
|
71
|
+
base64ToBytes(base64) {
|
|
72
|
+
// Node.js Buffer provides native base64 decoding
|
|
73
|
+
// @ts-expect-error - Buffer is available in Node.js but not in browser TypeScript definitions
|
|
74
|
+
return new Uint8Array(Buffer.from(base64, 'base64'));
|
|
75
|
+
}
|
|
76
|
+
randomBytes(length) {
|
|
77
|
+
// Use Web Crypto API's getRandomValues (available in Node 19+)
|
|
78
|
+
return crypto.getRandomValues(new Uint8Array(length));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { Signaler } from './types.js';
|
|
2
|
-
import {
|
|
3
|
-
import { Binnable } from './bin.js';
|
|
1
|
+
import { Signaler, Binnable } from './types.js';
|
|
2
|
+
import { Rondevu } from './rondevu.js';
|
|
4
3
|
export interface PollingConfig {
|
|
5
4
|
initialInterval?: number;
|
|
6
5
|
maxInterval?: number;
|
|
@@ -44,16 +43,17 @@ export declare class RondevuSignaler implements Signaler {
|
|
|
44
43
|
private readonly service;
|
|
45
44
|
private readonly host?;
|
|
46
45
|
private offerId;
|
|
47
|
-
private
|
|
46
|
+
private serviceFqn;
|
|
48
47
|
private offerListeners;
|
|
49
48
|
private answerListeners;
|
|
50
49
|
private iceListeners;
|
|
51
|
-
private
|
|
50
|
+
private pollingTimeout;
|
|
52
51
|
private icePollingTimeout;
|
|
53
|
-
private
|
|
52
|
+
private lastPollTimestamp;
|
|
54
53
|
private isPolling;
|
|
54
|
+
private isOfferer;
|
|
55
55
|
private pollingConfig;
|
|
56
|
-
constructor(rondevu:
|
|
56
|
+
constructor(rondevu: Rondevu, service: string, host?: string | undefined, pollingConfig?: PollingConfig);
|
|
57
57
|
/**
|
|
58
58
|
* Publish an offer as a service
|
|
59
59
|
* Used by the offerer to make their offer available
|
|
@@ -88,15 +88,17 @@ export declare class RondevuSignaler implements Signaler {
|
|
|
88
88
|
*/
|
|
89
89
|
private searchForOffer;
|
|
90
90
|
/**
|
|
91
|
-
* Start polling for
|
|
91
|
+
* Start combined polling for answers and ICE candidates (offerer side)
|
|
92
|
+
* Uses poll() for efficient batch polling
|
|
92
93
|
*/
|
|
93
|
-
private
|
|
94
|
+
private startPolling;
|
|
94
95
|
/**
|
|
95
|
-
* Stop polling
|
|
96
|
+
* Stop combined polling
|
|
96
97
|
*/
|
|
97
|
-
private
|
|
98
|
+
private stopPolling;
|
|
98
99
|
/**
|
|
99
|
-
* Start polling for ICE candidates
|
|
100
|
+
* Start polling for ICE candidates (answerer side only)
|
|
101
|
+
* Answerers use the separate endpoint since they don't have offers to poll
|
|
100
102
|
*/
|
|
101
103
|
private startIcePolling;
|
|
102
104
|
/**
|
package/dist/rondevu-signaler.js
CHANGED
|
@@ -35,14 +35,15 @@ export class RondevuSignaler {
|
|
|
35
35
|
this.service = service;
|
|
36
36
|
this.host = host;
|
|
37
37
|
this.offerId = null;
|
|
38
|
-
this.
|
|
38
|
+
this.serviceFqn = null;
|
|
39
39
|
this.offerListeners = [];
|
|
40
40
|
this.answerListeners = [];
|
|
41
41
|
this.iceListeners = [];
|
|
42
|
-
this.
|
|
42
|
+
this.pollingTimeout = null;
|
|
43
43
|
this.icePollingTimeout = null;
|
|
44
|
-
this.
|
|
44
|
+
this.lastPollTimestamp = 0;
|
|
45
45
|
this.isPolling = false;
|
|
46
|
+
this.isOfferer = false;
|
|
46
47
|
this.pollingConfig = {
|
|
47
48
|
initialInterval: pollingConfig?.initialInterval ?? 500,
|
|
48
49
|
maxInterval: pollingConfig?.maxInterval ?? 5000,
|
|
@@ -64,18 +65,16 @@ export class RondevuSignaler {
|
|
|
64
65
|
serviceFqn: this.service,
|
|
65
66
|
offers: [{ sdp: offer.sdp }],
|
|
66
67
|
ttl: 300000, // 5 minutes
|
|
67
|
-
isPublic: true,
|
|
68
68
|
});
|
|
69
69
|
// Get the first offer from the published service
|
|
70
70
|
if (!publishedService.offers || publishedService.offers.length === 0) {
|
|
71
71
|
throw new Error('No offers returned from service publication');
|
|
72
72
|
}
|
|
73
73
|
this.offerId = publishedService.offers[0].offerId;
|
|
74
|
-
this.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.startIcePolling();
|
|
74
|
+
this.serviceFqn = publishedService.serviceFqn;
|
|
75
|
+
this.isOfferer = true;
|
|
76
|
+
// Start combined polling for answers and ICE candidates
|
|
77
|
+
this.startPolling();
|
|
79
78
|
}
|
|
80
79
|
/**
|
|
81
80
|
* Send an answer to the offerer
|
|
@@ -85,13 +84,13 @@ export class RondevuSignaler {
|
|
|
85
84
|
if (!answer.sdp) {
|
|
86
85
|
throw new Error('Answer SDP is required');
|
|
87
86
|
}
|
|
88
|
-
if (!this.
|
|
89
|
-
throw new Error('No service
|
|
87
|
+
if (!this.serviceFqn || !this.offerId) {
|
|
88
|
+
throw new Error('No service FQN or offer ID available. Must receive offer first.');
|
|
90
89
|
}
|
|
91
90
|
// Send answer to the service
|
|
92
|
-
|
|
93
|
-
this.
|
|
94
|
-
// Start polling for ICE candidates
|
|
91
|
+
await this.rondevu.getAPIPublic().answerOffer(this.serviceFqn, this.offerId, answer.sdp);
|
|
92
|
+
this.isOfferer = false;
|
|
93
|
+
// Start polling for ICE candidates (answerer uses separate endpoint)
|
|
95
94
|
this.startIcePolling();
|
|
96
95
|
}
|
|
97
96
|
/**
|
|
@@ -130,8 +129,8 @@ export class RondevuSignaler {
|
|
|
130
129
|
* Send an ICE candidate to the remote peer
|
|
131
130
|
*/
|
|
132
131
|
async addIceCandidate(candidate) {
|
|
133
|
-
if (!this.
|
|
134
|
-
console.warn('Cannot send ICE candidate: no service
|
|
132
|
+
if (!this.serviceFqn || !this.offerId) {
|
|
133
|
+
console.warn('Cannot send ICE candidate: no service FQN or offer ID');
|
|
135
134
|
return;
|
|
136
135
|
}
|
|
137
136
|
const candidateData = candidate.toJSON();
|
|
@@ -140,11 +139,7 @@ export class RondevuSignaler {
|
|
|
140
139
|
return;
|
|
141
140
|
}
|
|
142
141
|
try {
|
|
143
|
-
|
|
144
|
-
// Store offerId if we didn't have it yet
|
|
145
|
-
if (!this.offerId) {
|
|
146
|
-
this.offerId = result.offerId;
|
|
147
|
-
}
|
|
142
|
+
await this.rondevu.getAPIPublic().addOfferIceCandidates(this.serviceFqn, this.offerId, [candidateData]);
|
|
148
143
|
}
|
|
149
144
|
catch (err) {
|
|
150
145
|
console.error('Failed to send ICE candidate:', err);
|
|
@@ -173,28 +168,21 @@ export class RondevuSignaler {
|
|
|
173
168
|
}
|
|
174
169
|
this.isPolling = true;
|
|
175
170
|
try {
|
|
176
|
-
//
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
// Get the first available service (already has full details from searchServices)
|
|
184
|
-
const service = services[0];
|
|
185
|
-
// Get the first available offer from the service
|
|
186
|
-
if (!service.offers || service.offers.length === 0) {
|
|
187
|
-
console.warn(`No offers available for service ${this.host}/${this.service}`);
|
|
171
|
+
// Get service by FQN (service should include @username)
|
|
172
|
+
const serviceFqn = `${this.service}@${this.host}`;
|
|
173
|
+
const serviceData = await this.rondevu.getAPIPublic().getService(serviceFqn);
|
|
174
|
+
if (!serviceData) {
|
|
175
|
+
console.warn(`No service found for ${serviceFqn}`);
|
|
188
176
|
this.isPolling = false;
|
|
189
177
|
return;
|
|
190
178
|
}
|
|
191
|
-
|
|
192
|
-
this.offerId =
|
|
193
|
-
this.
|
|
179
|
+
// Store service details
|
|
180
|
+
this.offerId = serviceData.offerId;
|
|
181
|
+
this.serviceFqn = serviceData.serviceFqn;
|
|
194
182
|
// Notify offer listeners
|
|
195
183
|
const offer = {
|
|
196
184
|
type: 'offer',
|
|
197
|
-
sdp:
|
|
185
|
+
sdp: serviceData.sdp,
|
|
198
186
|
};
|
|
199
187
|
this.offerListeners.forEach(listener => {
|
|
200
188
|
try {
|
|
@@ -211,101 +199,129 @@ export class RondevuSignaler {
|
|
|
211
199
|
}
|
|
212
200
|
}
|
|
213
201
|
/**
|
|
214
|
-
* Start polling for
|
|
202
|
+
* Start combined polling for answers and ICE candidates (offerer side)
|
|
203
|
+
* Uses poll() for efficient batch polling
|
|
215
204
|
*/
|
|
216
|
-
|
|
217
|
-
if (this.
|
|
205
|
+
startPolling() {
|
|
206
|
+
if (this.pollingTimeout || !this.isOfferer) {
|
|
218
207
|
return;
|
|
219
208
|
}
|
|
220
209
|
let interval = this.pollingConfig.initialInterval;
|
|
221
210
|
let retries = 0;
|
|
211
|
+
let answerReceived = false;
|
|
222
212
|
const poll = async () => {
|
|
223
|
-
if (!this.serviceUuid) {
|
|
224
|
-
this.stopAnswerPolling();
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
213
|
try {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
214
|
+
const result = await this.rondevu.poll(this.lastPollTimestamp);
|
|
215
|
+
let foundActivity = false;
|
|
216
|
+
// Process answers
|
|
217
|
+
if (result.answers.length > 0 && !answerReceived) {
|
|
218
|
+
foundActivity = true;
|
|
219
|
+
// Find answer for our offerId
|
|
220
|
+
const answer = result.answers.find(a => a.offerId === this.offerId);
|
|
221
|
+
if (answer && answer.sdp) {
|
|
222
|
+
answerReceived = true;
|
|
223
|
+
const answerDesc = {
|
|
224
|
+
type: 'answer',
|
|
225
|
+
sdp: answer.sdp,
|
|
226
|
+
};
|
|
227
|
+
this.answerListeners.forEach(listener => {
|
|
228
|
+
try {
|
|
229
|
+
listener(answerDesc);
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
console.error('Answer listener error:', err);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
this.lastPollTimestamp = Math.max(this.lastPollTimestamp, answer.answeredAt);
|
|
233
236
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
237
|
+
}
|
|
238
|
+
// Process ICE candidates for our offer
|
|
239
|
+
if (this.offerId && result.iceCandidates[this.offerId]) {
|
|
240
|
+
const candidates = result.iceCandidates[this.offerId];
|
|
241
|
+
// Filter for answerer candidates (offerer receives answerer's candidates)
|
|
242
|
+
const answererCandidates = candidates.filter(c => c.role === 'answerer');
|
|
243
|
+
if (answererCandidates.length > 0) {
|
|
244
|
+
foundActivity = true;
|
|
245
|
+
for (const item of answererCandidates) {
|
|
246
|
+
if (item.candidate && item.candidate.candidate && item.candidate.candidate !== '') {
|
|
247
|
+
try {
|
|
248
|
+
const rtcCandidate = new RTCIceCandidate(item.candidate);
|
|
249
|
+
this.iceListeners.forEach(listener => {
|
|
250
|
+
try {
|
|
251
|
+
listener(rtcCandidate);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
console.error('ICE listener error:', err);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
this.lastPollTimestamp = Math.max(this.lastPollTimestamp, item.createdAt);
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
console.warn('Failed to process ICE candidate:', err);
|
|
261
|
+
this.lastPollTimestamp = Math.max(this.lastPollTimestamp, item.createdAt);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
245
264
|
}
|
|
246
|
-
}
|
|
247
|
-
// Stop polling once we get the answer
|
|
248
|
-
this.stopAnswerPolling();
|
|
249
|
-
return;
|
|
265
|
+
}
|
|
250
266
|
}
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
267
|
+
// Adjust interval based on activity
|
|
268
|
+
if (foundActivity) {
|
|
269
|
+
interval = this.pollingConfig.initialInterval;
|
|
270
|
+
retries = 0;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
retries++;
|
|
274
|
+
if (retries > this.pollingConfig.maxRetries) {
|
|
275
|
+
console.warn('Max retries reached for polling');
|
|
276
|
+
this.stopPolling();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
interval = Math.min(interval * this.pollingConfig.backoffMultiplier, this.pollingConfig.maxInterval);
|
|
257
280
|
}
|
|
258
|
-
interval = Math.min(interval * this.pollingConfig.backoffMultiplier, this.pollingConfig.maxInterval);
|
|
259
281
|
// Add jitter to prevent thundering herd
|
|
260
282
|
const finalInterval = this.pollingConfig.jitter
|
|
261
283
|
? interval + Math.random() * 100
|
|
262
284
|
: interval;
|
|
263
|
-
this.
|
|
285
|
+
this.pollingTimeout = setTimeout(poll, finalInterval);
|
|
264
286
|
}
|
|
265
287
|
catch (err) {
|
|
266
|
-
|
|
267
|
-
if (err instanceof Error && !err.message?.includes('404')) {
|
|
268
|
-
console.error('Error polling for answer:', err);
|
|
269
|
-
}
|
|
288
|
+
console.error('Error polling offers:', err);
|
|
270
289
|
// Retry with backoff
|
|
271
290
|
const finalInterval = this.pollingConfig.jitter
|
|
272
291
|
? interval + Math.random() * 100
|
|
273
292
|
: interval;
|
|
274
|
-
this.
|
|
293
|
+
this.pollingTimeout = setTimeout(poll, finalInterval);
|
|
275
294
|
}
|
|
276
295
|
};
|
|
277
296
|
poll(); // Start immediately
|
|
278
297
|
}
|
|
279
298
|
/**
|
|
280
|
-
* Stop polling
|
|
299
|
+
* Stop combined polling
|
|
281
300
|
*/
|
|
282
|
-
|
|
283
|
-
if (this.
|
|
284
|
-
clearTimeout(this.
|
|
285
|
-
this.
|
|
301
|
+
stopPolling() {
|
|
302
|
+
if (this.pollingTimeout) {
|
|
303
|
+
clearTimeout(this.pollingTimeout);
|
|
304
|
+
this.pollingTimeout = null;
|
|
286
305
|
}
|
|
287
306
|
}
|
|
288
307
|
/**
|
|
289
|
-
* Start polling for ICE candidates
|
|
308
|
+
* Start polling for ICE candidates (answerer side only)
|
|
309
|
+
* Answerers use the separate endpoint since they don't have offers to poll
|
|
290
310
|
*/
|
|
291
311
|
startIcePolling() {
|
|
292
|
-
if (this.icePollingTimeout || !this.
|
|
312
|
+
if (this.icePollingTimeout || !this.serviceFqn || !this.offerId || this.isOfferer) {
|
|
293
313
|
return;
|
|
294
314
|
}
|
|
295
315
|
let interval = this.pollingConfig.initialInterval;
|
|
296
316
|
const poll = async () => {
|
|
297
|
-
if (!this.
|
|
317
|
+
if (!this.serviceFqn || !this.offerId) {
|
|
298
318
|
this.stopIcePolling();
|
|
299
319
|
return;
|
|
300
320
|
}
|
|
301
321
|
try {
|
|
302
322
|
const result = await this.rondevu
|
|
303
|
-
.
|
|
304
|
-
.
|
|
305
|
-
// Store offerId if we didn't have it yet
|
|
306
|
-
if (!this.offerId) {
|
|
307
|
-
this.offerId = result.offerId;
|
|
308
|
-
}
|
|
323
|
+
.getAPIPublic()
|
|
324
|
+
.getOfferIceCandidates(this.serviceFqn, this.offerId, this.lastPollTimestamp);
|
|
309
325
|
let foundCandidates = false;
|
|
310
326
|
for (const item of result.candidates) {
|
|
311
327
|
if (item.candidate && item.candidate.candidate && item.candidate.candidate !== '') {
|
|
@@ -320,15 +336,15 @@ export class RondevuSignaler {
|
|
|
320
336
|
console.error('ICE listener error:', err);
|
|
321
337
|
}
|
|
322
338
|
});
|
|
323
|
-
this.
|
|
339
|
+
this.lastPollTimestamp = item.createdAt;
|
|
324
340
|
}
|
|
325
341
|
catch (err) {
|
|
326
342
|
console.warn('Failed to process ICE candidate:', err);
|
|
327
|
-
this.
|
|
343
|
+
this.lastPollTimestamp = item.createdAt;
|
|
328
344
|
}
|
|
329
345
|
}
|
|
330
346
|
else {
|
|
331
|
-
this.
|
|
347
|
+
this.lastPollTimestamp = item.createdAt;
|
|
332
348
|
}
|
|
333
349
|
}
|
|
334
350
|
// If candidates found, reset interval to initial value
|
|
@@ -376,7 +392,7 @@ export class RondevuSignaler {
|
|
|
376
392
|
* Stop all polling and cleanup
|
|
377
393
|
*/
|
|
378
394
|
dispose() {
|
|
379
|
-
this.
|
|
395
|
+
this.stopPolling();
|
|
380
396
|
this.stopIcePolling();
|
|
381
397
|
this.offerListeners = [];
|
|
382
398
|
this.answerListeners = [];
|