2 Commits

Author SHA1 Message Date
a550641993 Release v0.9.1: Add detailed ICE candidate exchange logging 2025-12-07 11:13:32 +01:00
04603cfe2d Add detailed ICE candidate exchange logging
Added comprehensive logging to track WebRTC ICE candidate exchange:
- Log local candidate generation with type (host/srflx/relay)
- Log when candidates are sent to signaling server
- Log remote candidate reception and addition
- Log ICE gathering state changes
- Log ICE connection state changes
- Enhanced ICE error logging with details

This will help diagnose connection issues and TURN server problems.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-07 11:13:24 +01:00
5 changed files with 46 additions and 7 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.7.12", "version": "0.9.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.7.12", "version": "0.9.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@noble/ed25519": "^3.0.0", "@noble/ed25519": "^3.0.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@xtr-dev/rondevu-client", "name": "@xtr-dev/rondevu-client",
"version": "0.9.0", "version": "0.9.1",
"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",

View File

@@ -40,13 +40,22 @@ export class ExchangingIceState extends PeerState {
this.lastIceTimestamp this.lastIceTimestamp
); );
if (candidates.length > 0) {
console.log(`📥 Received ${candidates.length} remote ICE candidate(s)`);
}
for (const cand of candidates) { for (const cand of candidates) {
if (cand.candidate && cand.candidate.candidate && cand.candidate.candidate !== '') { if (cand.candidate && cand.candidate.candidate && cand.candidate.candidate !== '') {
const type = cand.candidate.candidate.includes('typ host') ? 'host' :
cand.candidate.candidate.includes('typ srflx') ? 'srflx' :
cand.candidate.candidate.includes('typ relay') ? 'relay' : 'unknown';
console.log(`🧊 Adding remote ${type} ICE candidate:`, cand.candidate.candidate);
try { try {
await this.peer.pc.addIceCandidate(new this.peer.RTCIceCandidate(cand.candidate)); await this.peer.pc.addIceCandidate(new this.peer.RTCIceCandidate(cand.candidate));
console.log(`✅ Added remote ${type} ICE candidate`);
this.lastIceTimestamp = cand.createdAt; this.lastIceTimestamp = cand.createdAt;
} catch (err) { } catch (err) {
console.warn('Failed to add ICE candidate:', err); console.warn(`⚠️ Failed to add remote ${type} ICE candidate:`, err);
this.lastIceTimestamp = cand.createdAt; this.lastIceTimestamp = cand.createdAt;
} }
} else { } else {
@@ -54,7 +63,7 @@ export class ExchangingIceState extends PeerState {
} }
} }
} catch (err) { } catch (err) {
console.error('Error polling for ICE candidates:', err); console.error('Error polling for ICE candidates:', err);
if (err instanceof Error && err.message.includes('not found')) { if (err instanceof Error && err.message.includes('not found')) {
this.cleanup(); this.cleanup();
const { FailedState } = await import('./failed-state.js'); const { FailedState } = await import('./failed-state.js');

View File

@@ -105,18 +105,23 @@ export default class RondevuPeer extends EventEmitter<PeerEvents> {
*/ */
private setupPeerConnection(): void { private setupPeerConnection(): void {
this.connectionStateChangeHandler = () => { this.connectionStateChangeHandler = () => {
console.log(`🔌 Connection state changed: ${this.pc.connectionState}`);
switch (this.pc.connectionState) { switch (this.pc.connectionState) {
case 'connected': case 'connected':
console.log('✅ WebRTC connection established');
this.setState(new ConnectedState(this)); this.setState(new ConnectedState(this));
this.emitEvent('connected'); this.emitEvent('connected');
break; break;
case 'disconnected': case 'disconnected':
console.log('⚠️ WebRTC connection disconnected');
this.emitEvent('disconnected'); this.emitEvent('disconnected');
break; break;
case 'failed': case 'failed':
console.log('❌ WebRTC connection failed');
this.setState(new FailedState(this, new Error('Connection failed'))); this.setState(new FailedState(this, new Error('Connection failed')));
break; break;
case 'closed': case 'closed':
console.log('🔒 WebRTC connection closed');
this.setState(new ClosedState(this)); this.setState(new ClosedState(this));
this.emitEvent('disconnected'); this.emitEvent('disconnected');
break; break;
@@ -124,6 +129,18 @@ export default class RondevuPeer extends EventEmitter<PeerEvents> {
}; };
this.pc.addEventListener('connectionstatechange', this.connectionStateChangeHandler); this.pc.addEventListener('connectionstatechange', this.connectionStateChangeHandler);
// Add ICE connection state logging
const iceConnectionStateHandler = () => {
console.log(`🧊 ICE connection state: ${this.pc.iceConnectionState}`);
};
this.pc.addEventListener('iceconnectionstatechange', iceConnectionStateHandler);
// Add ICE gathering state logging
const iceGatheringStateHandler = () => {
console.log(`🔍 ICE gathering state: ${this.pc.iceGatheringState}`);
};
this.pc.addEventListener('icegatheringstatechange', iceGatheringStateHandler);
this.dataChannelHandler = (event: RTCDataChannelEvent) => { this.dataChannelHandler = (event: RTCDataChannelEvent) => {
this.emitEvent('datachannel', event.channel); this.emitEvent('datachannel', event.channel);
}; };
@@ -135,7 +152,13 @@ export default class RondevuPeer extends EventEmitter<PeerEvents> {
this.pc.addEventListener('track', this.trackHandler); this.pc.addEventListener('track', this.trackHandler);
this.iceCandidateErrorHandler = (event: Event) => { this.iceCandidateErrorHandler = (event: Event) => {
console.error('ICE candidate error:', event); const iceError = event as RTCPeerConnectionIceErrorEvent;
console.error(`❌ ICE candidate error: ${iceError.errorText || 'Unknown error'}`, {
errorCode: iceError.errorCode,
url: iceError.url,
address: iceError.address,
port: iceError.port
});
}; };
this.pc.addEventListener('icecandidateerror', this.iceCandidateErrorHandler); this.pc.addEventListener('icecandidateerror', this.iceCandidateErrorHandler);
} }

View File

@@ -40,12 +40,19 @@ export abstract class PeerState {
if (event.candidate && this.peer.offerId) { if (event.candidate && this.peer.offerId) {
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(`🧊 Generated ${type} ICE candidate:`, candidateData.candidate);
try { try {
await this.peer.offersApi.addIceCandidates(this.peer.offerId, [candidateData]); await this.peer.offersApi.addIceCandidates(this.peer.offerId, [candidateData]);
console.log(`✅ Sent ${type} ICE candidate to server`);
} catch (err) { } catch (err) {
console.error('Error sending ICE candidate:', err); console.error(`Error sending ${type} ICE candidate:`, err);
} }
} }
} else if (!event.candidate) {
console.log('🧊 ICE gathering complete (null candidate)');
} }
}; };
this.peer.pc.addEventListener('icecandidate', this.iceCandidateHandler); this.peer.pc.addEventListener('icecandidate', this.iceCandidateHandler);