mirror of
https://github.com/xtr-dev/rondevu-client.git
synced 2025-12-10 10:53:24 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb2c61bdb8 | |||
| 3139897b25 |
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@xtr-dev/rondevu-client",
|
"name": "@xtr-dev/rondevu-client",
|
||||||
"version": "0.9.1",
|
"version": "0.9.2",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@xtr-dev/rondevu-client",
|
"name": "@xtr-dev/rondevu-client",
|
||||||
"version": "0.9.1",
|
"version": "0.9.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/ed25519": "^3.0.0",
|
"@noble/ed25519": "^3.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@xtr-dev/rondevu-client",
|
"name": "@xtr-dev/rondevu-client",
|
||||||
"version": "0.9.1",
|
"version": "0.9.2",
|
||||||
"description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing",
|
"description": "TypeScript client for Rondevu with durable WebRTC connections, automatic reconnection, and message queuing",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -385,6 +385,8 @@ export class ServicePool {
|
|||||||
try {
|
try {
|
||||||
// Create peer connections and generate offers
|
// Create peer connections and generate offers
|
||||||
const offerRequests = [];
|
const offerRequests = [];
|
||||||
|
const pendingCandidates: RTCIceCandidateInit[][] = []; // Store candidates before we have offer IDs
|
||||||
|
|
||||||
for (let i = 0; i < batchSize; i++) {
|
for (let i = 0; i < batchSize; i++) {
|
||||||
const pc = new RTCPeerConnection(this.options.rtcConfig || {
|
const pc = new RTCPeerConnection(this.options.rtcConfig || {
|
||||||
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
||||||
@@ -394,9 +396,28 @@ export class ServicePool {
|
|||||||
const channel = pc.createDataChannel('rondevu-service');
|
const channel = pc.createDataChannel('rondevu-service');
|
||||||
dataChannels.push(channel);
|
dataChannels.push(channel);
|
||||||
|
|
||||||
|
// Set up temporary candidate collector BEFORE setLocalDescription
|
||||||
|
const candidatesForThisOffer: RTCIceCandidateInit[] = [];
|
||||||
|
pendingCandidates.push(candidatesForThisOffer);
|
||||||
|
|
||||||
|
pc.onicecandidate = (event) => {
|
||||||
|
if (event.candidate) {
|
||||||
|
const candidateData = event.candidate.toJSON();
|
||||||
|
if (candidateData.candidate && candidateData.candidate !== '') {
|
||||||
|
const type = candidateData.candidate.includes('typ host') ? 'host' :
|
||||||
|
candidateData.candidate.includes('typ srflx') ? 'srflx' :
|
||||||
|
candidateData.candidate.includes('typ relay') ? 'relay' : 'unknown';
|
||||||
|
console.log(`🧊 Service pool generated ${type} ICE candidate:`, candidateData.candidate);
|
||||||
|
candidatesForThisOffer.push(candidateData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('🧊 Service pool ICE gathering complete');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Create offer
|
// Create offer
|
||||||
const offer = await pc.createOffer();
|
const offer = await pc.createOffer();
|
||||||
await pc.setLocalDescription(offer);
|
await pc.setLocalDescription(offer); // ICE gathering starts here, candidates go to collector
|
||||||
|
|
||||||
if (!offer.sdp) {
|
if (!offer.sdp) {
|
||||||
pc.close();
|
pc.close();
|
||||||
@@ -417,19 +438,37 @@ export class ServicePool {
|
|||||||
const createdOffers = await this.offersApi.create(offerRequests);
|
const createdOffers = await this.offersApi.create(offerRequests);
|
||||||
offers.push(...createdOffers);
|
offers.push(...createdOffers);
|
||||||
|
|
||||||
// Set up ICE candidate handlers AFTER we have offer IDs
|
// Now send all pending candidates and set up handlers for future ones
|
||||||
for (let i = 0; i < peerConnections.length; i++) {
|
for (let i = 0; i < peerConnections.length; i++) {
|
||||||
const pc = peerConnections[i];
|
const pc = peerConnections[i];
|
||||||
const offerId = createdOffers[i].id;
|
const offerId = createdOffers[i].id;
|
||||||
|
const candidates = pendingCandidates[i];
|
||||||
|
|
||||||
|
// Send any candidates that were collected while waiting for offer ID
|
||||||
|
if (candidates.length > 0) {
|
||||||
|
console.log(`📤 Sending ${candidates.length} pending ICE candidate(s) for offer ${offerId}`);
|
||||||
|
try {
|
||||||
|
await this.offersApi.addIceCandidates(offerId, candidates);
|
||||||
|
console.log(`✅ Sent ${candidates.length} pending ICE candidate(s)`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ Error sending pending ICE candidates:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace temporary handler with permanent one for any future candidates
|
||||||
pc.onicecandidate = async (event) => {
|
pc.onicecandidate = async (event) => {
|
||||||
if (event.candidate) {
|
if (event.candidate) {
|
||||||
const candidateData = event.candidate.toJSON();
|
const candidateData = event.candidate.toJSON();
|
||||||
if (candidateData.candidate && candidateData.candidate !== '') {
|
if (candidateData.candidate && candidateData.candidate !== '') {
|
||||||
|
const type = candidateData.candidate.includes('typ host') ? 'host' :
|
||||||
|
candidateData.candidate.includes('typ srflx') ? 'srflx' :
|
||||||
|
candidateData.candidate.includes('typ relay') ? 'relay' : 'unknown';
|
||||||
|
console.log(`🧊 Service pool generated late ${type} ICE candidate:`, candidateData.candidate);
|
||||||
try {
|
try {
|
||||||
await this.offersApi.addIceCandidates(offerId, [candidateData]);
|
await this.offersApi.addIceCandidates(offerId, [candidateData]);
|
||||||
|
console.log(`✅ Sent late ${type} ICE candidate`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error sending ICE candidate:', err);
|
console.error(`❌ Error sending ${type} ICE candidate:`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,9 +507,28 @@ export class ServicePool {
|
|||||||
|
|
||||||
const dataChannel = pc.createDataChannel('rondevu-service');
|
const dataChannel = pc.createDataChannel('rondevu-service');
|
||||||
|
|
||||||
|
// Collect candidates before we have offer ID
|
||||||
|
const pendingCandidates: RTCIceCandidateInit[] = [];
|
||||||
|
|
||||||
|
// Set up temporary candidate collector BEFORE setLocalDescription
|
||||||
|
pc.onicecandidate = (event) => {
|
||||||
|
if (event.candidate) {
|
||||||
|
const candidateData = event.candidate.toJSON();
|
||||||
|
if (candidateData.candidate && candidateData.candidate !== '') {
|
||||||
|
const type = candidateData.candidate.includes('typ host') ? 'host' :
|
||||||
|
candidateData.candidate.includes('typ srflx') ? 'srflx' :
|
||||||
|
candidateData.candidate.includes('typ relay') ? 'relay' : 'unknown';
|
||||||
|
console.log(`🧊 Initial service generated ${type} ICE candidate:`, candidateData.candidate);
|
||||||
|
pendingCandidates.push(candidateData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('🧊 Initial service ICE gathering complete');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Create offer
|
// Create offer
|
||||||
const offer = await pc.createOffer();
|
const offer = await pc.createOffer();
|
||||||
await pc.setLocalDescription(offer);
|
await pc.setLocalDescription(offer); // ICE gathering starts here
|
||||||
|
|
||||||
if (!offer.sdp) {
|
if (!offer.sdp) {
|
||||||
pc.close();
|
pc.close();
|
||||||
@@ -512,15 +570,31 @@ export class ServicePool {
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Set up ICE candidate handler now that we have the offer ID
|
// Send any pending candidates
|
||||||
|
if (pendingCandidates.length > 0) {
|
||||||
|
console.log(`📤 Sending ${pendingCandidates.length} pending ICE candidate(s) for initial service`);
|
||||||
|
try {
|
||||||
|
await this.offersApi.addIceCandidates(data.offerId, pendingCandidates);
|
||||||
|
console.log(`✅ Sent ${pendingCandidates.length} pending ICE candidate(s)`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ Error sending pending ICE candidates:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up handler for any future candidates
|
||||||
pc.onicecandidate = async (event) => {
|
pc.onicecandidate = async (event) => {
|
||||||
if (event.candidate) {
|
if (event.candidate) {
|
||||||
const candidateData = event.candidate.toJSON();
|
const candidateData = event.candidate.toJSON();
|
||||||
if (candidateData.candidate && candidateData.candidate !== '') {
|
if (candidateData.candidate && candidateData.candidate !== '') {
|
||||||
|
const type = candidateData.candidate.includes('typ host') ? 'host' :
|
||||||
|
candidateData.candidate.includes('typ srflx') ? 'srflx' :
|
||||||
|
candidateData.candidate.includes('typ relay') ? 'relay' : 'unknown';
|
||||||
|
console.log(`🧊 Initial service generated late ${type} ICE candidate:`, candidateData.candidate);
|
||||||
try {
|
try {
|
||||||
await this.offersApi.addIceCandidates(data.offerId, [candidateData]);
|
await this.offersApi.addIceCandidates(data.offerId, [candidateData]);
|
||||||
|
console.log(`✅ Sent late ${type} ICE candidate`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error sending ICE candidate:', err);
|
console.error(`❌ Error sending ${type} ICE candidate:`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user