Extract duplicate ICE candidate handler code to base PeerState class

Refactored common ICE candidate handling logic to reduce code duplication:
- Added setupIceCandidateHandler() method to base PeerState class
- Moved iceCandidateHandler property to base class
- Updated cleanup() in base class to remove ICE candidate handler
- Removed duplicate handler code from CreatingOfferState and AnsweringState
- Both states now call this.setupIceCandidateHandler(offerId)

This eliminates ~15 lines of duplicated code per state and ensures consistent ICE candidate handling across all states that need it.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-16 17:35:40 +01:00
parent 00f4da7250
commit 6e661f69bc
3 changed files with 28 additions and 43 deletions

View File

@@ -6,8 +6,6 @@ import type RondevuPeer from './index.js';
* Answering an offer and sending to server * Answering an offer and sending to server
*/ */
export class AnsweringState extends PeerState { export class AnsweringState extends PeerState {
private iceCandidateHandler?: (event: RTCPeerConnectionIceEvent) => void;
constructor(peer: RondevuPeer) { constructor(peer: RondevuPeer) {
super(peer); super(peer);
} }
@@ -33,19 +31,7 @@ export class AnsweringState extends PeerState {
await this.peer.offersApi.answer(offerId, answer.sdp!); await this.peer.offersApi.answer(offerId, answer.sdp!);
// Enable trickle ICE - send candidates as they arrive // Enable trickle ICE - send candidates as they arrive
this.iceCandidateHandler = async (event: RTCPeerConnectionIceEvent) => { this.setupIceCandidateHandler(offerId);
if (event.candidate && offerId) {
const candidateData = event.candidate.toJSON();
if (candidateData.candidate && candidateData.candidate !== '') {
try {
await this.peer.offersApi.addIceCandidates(offerId, [candidateData]);
} catch (err) {
console.error('Error sending ICE candidate:', err);
}
}
}
};
this.peer.pc.addEventListener('icecandidate', this.iceCandidateHandler);
// Transition to exchanging ICE // Transition to exchanging ICE
const { ExchangingIceState } = await import('./exchanging-ice-state.js'); const { ExchangingIceState } = await import('./exchanging-ice-state.js');
@@ -56,10 +42,4 @@ export class AnsweringState extends PeerState {
throw error; throw error;
} }
} }
cleanup(): void {
if (this.iceCandidateHandler) {
this.peer.pc.removeEventListener('icecandidate', this.iceCandidateHandler);
}
}
} }

View File

@@ -6,8 +6,6 @@ import type RondevuPeer from './index.js';
* Creating offer and sending to server * Creating offer and sending to server
*/ */
export class CreatingOfferState extends PeerState { export class CreatingOfferState extends PeerState {
private iceCandidateHandler?: (event: RTCPeerConnectionIceEvent) => void;
constructor(peer: RondevuPeer, private options: PeerOptions) { constructor(peer: RondevuPeer, private options: PeerOptions) {
super(peer); super(peer);
} }
@@ -41,19 +39,7 @@ export class CreatingOfferState extends PeerState {
this.peer.offerId = offerId; this.peer.offerId = offerId;
// Enable trickle ICE - send candidates as they arrive // Enable trickle ICE - send candidates as they arrive
this.iceCandidateHandler = async (event: RTCPeerConnectionIceEvent) => { this.setupIceCandidateHandler(offerId);
if (event.candidate && offerId) {
const candidateData = event.candidate.toJSON();
if (candidateData.candidate && candidateData.candidate !== '') {
try {
await this.peer.offersApi.addIceCandidates(offerId, [candidateData]);
} catch (err) {
console.error('Error sending ICE candidate:', err);
}
}
}
};
this.peer.pc.addEventListener('icecandidate', this.iceCandidateHandler);
// Transition to waiting for answer // Transition to waiting for answer
const { WaitingForAnswerState } = await import('./waiting-for-answer-state.js'); const { WaitingForAnswerState } = await import('./waiting-for-answer-state.js');
@@ -66,10 +52,4 @@ export class CreatingOfferState extends PeerState {
throw error; throw error;
} }
} }
cleanup(): void {
if (this.iceCandidateHandler) {
this.peer.pc.removeEventListener('icecandidate', this.iceCandidateHandler);
}
}
} }

View File

@@ -6,6 +6,8 @@ import type RondevuPeer from './index.js';
* Implements the State pattern for managing WebRTC connection lifecycle * Implements the State pattern for managing WebRTC connection lifecycle
*/ */
export abstract class PeerState { export abstract class PeerState {
protected iceCandidateHandler?: (event: RTCPeerConnectionIceEvent) => void;
constructor(protected peer: RondevuPeer) {} constructor(protected peer: RondevuPeer) {}
abstract get name(): string; abstract get name(): string;
@@ -29,8 +31,31 @@ export abstract class PeerState {
} }
} }
/**
* Setup trickle ICE candidate handler
* Sends local ICE candidates to server as they are discovered
*/
protected setupIceCandidateHandler(offerId: string): void {
this.iceCandidateHandler = async (event: RTCPeerConnectionIceEvent) => {
if (event.candidate && offerId) {
const candidateData = event.candidate.toJSON();
if (candidateData.candidate && candidateData.candidate !== '') {
try {
await this.peer.offersApi.addIceCandidates(offerId, [candidateData]);
} catch (err) {
console.error('Error sending ICE candidate:', err);
}
}
}
};
this.peer.pc.addEventListener('icecandidate', this.iceCandidateHandler);
}
cleanup(): void { cleanup(): void {
// Override in states that need cleanup // Clean up ICE candidate handler if it exists
if (this.iceCandidateHandler) {
this.peer.pc.removeEventListener('icecandidate', this.iceCandidateHandler);
}
} }
async close(): Promise<void> { async close(): Promise<void> {