Replace .on* event handlers with addEventListener/removeEventListener

Updated all event handler assignments to use addEventListener instead of .on* properties:
- peer/index.ts: Replaced onconnectionstatechange, ondatachannel, ontrack, onicecandidateerror
- creating-offer-state.ts: Replaced onicecandidate
- answering-state.ts: Replaced onicecandidate

Benefits:
- Proper cleanup with removeEventListener
- Prevents memory leaks by removing listeners when states/peer close
- Allows multiple listeners for the same event
- More modern and explicit event handling approach

All event handlers are now stored as class properties and properly cleaned up in cleanup()/close() methods.

🤖 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:33:32 +01:00
parent 6c344ec8e1
commit 00f4da7250
3 changed files with 48 additions and 6 deletions

View File

@@ -6,6 +6,8 @@ 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);
}
@@ -31,7 +33,7 @@ export class AnsweringState extends PeerState {
await this.peer.offersApi.answer(offerId, answer.sdp!);
// Enable trickle ICE - send candidates as they arrive
this.peer.pc.onicecandidate = async (event: RTCPeerConnectionIceEvent) => {
this.iceCandidateHandler = async (event: RTCPeerConnectionIceEvent) => {
if (event.candidate && offerId) {
const candidateData = event.candidate.toJSON();
if (candidateData.candidate && candidateData.candidate !== '') {
@@ -43,6 +45,7 @@ export class AnsweringState extends PeerState {
}
}
};
this.peer.pc.addEventListener('icecandidate', this.iceCandidateHandler);
// Transition to exchanging ICE
const { ExchangingIceState } = await import('./exchanging-ice-state.js');
@@ -53,4 +56,10 @@ export class AnsweringState extends PeerState {
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';
* Creating offer and sending to server
*/
export class CreatingOfferState extends PeerState {
private iceCandidateHandler?: (event: RTCPeerConnectionIceEvent) => void;
constructor(peer: RondevuPeer, private options: PeerOptions) {
super(peer);
}
@@ -39,7 +41,7 @@ export class CreatingOfferState extends PeerState {
this.peer.offerId = offerId;
// Enable trickle ICE - send candidates as they arrive
this.peer.pc.onicecandidate = async (event: RTCPeerConnectionIceEvent) => {
this.iceCandidateHandler = async (event: RTCPeerConnectionIceEvent) => {
if (event.candidate && offerId) {
const candidateData = event.candidate.toJSON();
if (candidateData.candidate && candidateData.candidate !== '') {
@@ -51,6 +53,7 @@ export class CreatingOfferState extends PeerState {
}
}
};
this.peer.pc.addEventListener('icecandidate', this.iceCandidateHandler);
// Transition to waiting for answer
const { WaitingForAnswerState } = await import('./waiting-for-answer-state.js');
@@ -63,4 +66,10 @@ export class CreatingOfferState extends PeerState {
throw error;
}
}
cleanup(): void {
if (this.iceCandidateHandler) {
this.peer.pc.removeEventListener('icecandidate', this.iceCandidateHandler);
}
}
}

View File

@@ -26,6 +26,12 @@ export default class RondevuPeer extends EventEmitter<PeerEvents> {
private _state: PeerState;
// Event handler references for cleanup
private connectionStateChangeHandler?: () => void;
private dataChannelHandler?: (event: RTCDataChannelEvent) => void;
private trackHandler?: (event: RTCTrackEvent) => void;
private iceCandidateErrorHandler?: (event: Event) => void;
/**
* Current connection state name
*/
@@ -68,7 +74,7 @@ export default class RondevuPeer extends EventEmitter<PeerEvents> {
* Set up peer connection event handlers
*/
private setupPeerConnection(): void {
this.pc.onconnectionstatechange = () => {
this.connectionStateChangeHandler = () => {
switch (this.pc.connectionState) {
case 'connected':
this.setState(new ConnectedState(this));
@@ -86,18 +92,22 @@ export default class RondevuPeer extends EventEmitter<PeerEvents> {
break;
}
};
this.pc.addEventListener('connectionstatechange', this.connectionStateChangeHandler);
this.pc.ondatachannel = (event) => {
this.dataChannelHandler = (event: RTCDataChannelEvent) => {
this.emitEvent('datachannel', event.channel);
};
this.pc.addEventListener('datachannel', this.dataChannelHandler);
this.pc.ontrack = (event) => {
this.trackHandler = (event: RTCTrackEvent) => {
this.emitEvent('track', event);
};
this.pc.addEventListener('track', this.trackHandler);
this.pc.onicecandidateerror = (event) => {
this.iceCandidateErrorHandler = (event: Event) => {
console.error('ICE candidate error:', event);
};
this.pc.addEventListener('icecandidateerror', this.iceCandidateErrorHandler);
}
/**
@@ -145,6 +155,20 @@ export default class RondevuPeer extends EventEmitter<PeerEvents> {
* Close the connection and clean up
*/
async close(): Promise<void> {
// Remove RTCPeerConnection event listeners
if (this.connectionStateChangeHandler) {
this.pc.removeEventListener('connectionstatechange', this.connectionStateChangeHandler);
}
if (this.dataChannelHandler) {
this.pc.removeEventListener('datachannel', this.dataChannelHandler);
}
if (this.trackHandler) {
this.pc.removeEventListener('track', this.trackHandler);
}
if (this.iceCandidateErrorHandler) {
this.pc.removeEventListener('icecandidateerror', this.iceCandidateErrorHandler);
}
await this._state.close();
this.removeAllListeners();
}