From 6e661f69bc3c20d51c6170e13f7638a4393e8aa6 Mon Sep 17 00:00:00 2001 From: Bas van den Aakster Date: Sun, 16 Nov 2025 17:35:40 +0100 Subject: [PATCH] Extract duplicate ICE candidate handler code to base PeerState class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/peer/answering-state.ts | 22 +--------------------- src/peer/creating-offer-state.ts | 22 +--------------------- src/peer/state.ts | 27 ++++++++++++++++++++++++++- 3 files changed, 28 insertions(+), 43 deletions(-) diff --git a/src/peer/answering-state.ts b/src/peer/answering-state.ts index ac1868b..600bfc0 100644 --- a/src/peer/answering-state.ts +++ b/src/peer/answering-state.ts @@ -6,8 +6,6 @@ import type RondevuPeer from './index.js'; * Answering an offer and sending to server */ export class AnsweringState extends PeerState { - private iceCandidateHandler?: (event: RTCPeerConnectionIceEvent) => void; - constructor(peer: RondevuPeer) { super(peer); } @@ -33,19 +31,7 @@ export class AnsweringState extends PeerState { await this.peer.offersApi.answer(offerId, answer.sdp!); // Enable trickle ICE - send candidates as they arrive - 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); + this.setupIceCandidateHandler(offerId); // Transition to exchanging ICE const { ExchangingIceState } = await import('./exchanging-ice-state.js'); @@ -56,10 +42,4 @@ export class AnsweringState extends PeerState { throw error; } } - - cleanup(): void { - if (this.iceCandidateHandler) { - this.peer.pc.removeEventListener('icecandidate', this.iceCandidateHandler); - } - } } diff --git a/src/peer/creating-offer-state.ts b/src/peer/creating-offer-state.ts index 4346ba8..b6b4e81 100644 --- a/src/peer/creating-offer-state.ts +++ b/src/peer/creating-offer-state.ts @@ -6,8 +6,6 @@ import type RondevuPeer from './index.js'; * Creating offer and sending to server */ export class CreatingOfferState extends PeerState { - private iceCandidateHandler?: (event: RTCPeerConnectionIceEvent) => void; - constructor(peer: RondevuPeer, private options: PeerOptions) { super(peer); } @@ -41,19 +39,7 @@ export class CreatingOfferState extends PeerState { this.peer.offerId = offerId; // Enable trickle ICE - send candidates as they arrive - 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); + this.setupIceCandidateHandler(offerId); // Transition to waiting for answer const { WaitingForAnswerState } = await import('./waiting-for-answer-state.js'); @@ -66,10 +52,4 @@ export class CreatingOfferState extends PeerState { throw error; } } - - cleanup(): void { - if (this.iceCandidateHandler) { - this.peer.pc.removeEventListener('icecandidate', this.iceCandidateHandler); - } - } } diff --git a/src/peer/state.ts b/src/peer/state.ts index f900d5d..6a27644 100644 --- a/src/peer/state.ts +++ b/src/peer/state.ts @@ -6,6 +6,8 @@ import type RondevuPeer from './index.js'; * Implements the State pattern for managing WebRTC connection lifecycle */ export abstract class PeerState { + protected iceCandidateHandler?: (event: RTCPeerConnectionIceEvent) => void; + constructor(protected peer: RondevuPeer) {} 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 { - // 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 {