@xtr-dev/rondevu-client 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,25 +16,13 @@ export class AnsweringState extends PeerState {
16
16
  type: 'offer',
17
17
  sdp: offerSdp
18
18
  });
19
+ // Enable trickle ICE - set up handler before ICE gathering starts
20
+ this.setupIceCandidateHandler();
19
21
  // Create answer
20
22
  const answer = await this.peer.pc.createAnswer();
21
- await this.peer.pc.setLocalDescription(answer);
23
+ await this.peer.pc.setLocalDescription(answer); // ICE gathering starts here
22
24
  // Send answer to server immediately (don't wait for ICE)
23
25
  await this.peer.offersApi.answer(offerId, answer.sdp);
24
- // Enable trickle ICE - send candidates as they arrive
25
- this.peer.pc.onicecandidate = async (event) => {
26
- if (event.candidate && offerId) {
27
- const candidateData = event.candidate.toJSON();
28
- if (candidateData.candidate && candidateData.candidate !== '') {
29
- try {
30
- await this.peer.offersApi.addIceCandidates(offerId, [candidateData]);
31
- }
32
- catch (err) {
33
- console.error('Error sending ICE candidate:', err);
34
- }
35
- }
36
- }
37
- };
38
26
  // Transition to exchanging ICE
39
27
  const { ExchangingIceState } = await import('./exchanging-ice-state.js');
40
28
  this.peer.setState(new ExchangingIceState(this.peer, offerId, options));
@@ -16,9 +16,12 @@ export class CreatingOfferState extends PeerState {
16
16
  const channel = this.peer.pc.createDataChannel(options.dataChannelLabel || 'data');
17
17
  this.peer.emitEvent('datachannel', channel);
18
18
  }
19
+ // Enable trickle ICE - set up handler before ICE gathering starts
20
+ // Handler will check this.peer.offerId before sending
21
+ this.setupIceCandidateHandler();
19
22
  // Create WebRTC offer
20
23
  const offer = await this.peer.pc.createOffer();
21
- await this.peer.pc.setLocalDescription(offer);
24
+ await this.peer.pc.setLocalDescription(offer); // ICE gathering starts here
22
25
  // Send offer to server immediately (don't wait for ICE)
23
26
  const offers = await this.peer.offersApi.create([{
24
27
  sdp: offer.sdp,
@@ -26,21 +29,7 @@ export class CreatingOfferState extends PeerState {
26
29
  ttl: options.ttl || 300000
27
30
  }]);
28
31
  const offerId = offers[0].id;
29
- this.peer.offerId = offerId;
30
- // Enable trickle ICE - send candidates as they arrive
31
- this.peer.pc.onicecandidate = async (event) => {
32
- if (event.candidate && offerId) {
33
- const candidateData = event.candidate.toJSON();
34
- if (candidateData.candidate && candidateData.candidate !== '') {
35
- try {
36
- await this.peer.offersApi.addIceCandidates(offerId, [candidateData]);
37
- }
38
- catch (err) {
39
- console.error('Error sending ICE candidate:', err);
40
- }
41
- }
42
- }
43
- };
32
+ this.peer.offerId = offerId; // Now handler can send candidates
44
33
  // Transition to waiting for answer
45
34
  const { WaitingForAnswerState } = await import('./waiting-for-answer-state.js');
46
35
  this.peer.setState(new WaitingForAnswerState(this.peer, offerId, options));
@@ -13,6 +13,10 @@ export default class RondevuPeer extends EventEmitter<PeerEvents> {
13
13
  offerId?: string;
14
14
  role?: 'offerer' | 'answerer';
15
15
  private _state;
16
+ private connectionStateChangeHandler?;
17
+ private dataChannelHandler?;
18
+ private trackHandler?;
19
+ private iceCandidateErrorHandler?;
16
20
  /**
17
21
  * Current connection state name
18
22
  */
@@ -42,7 +42,7 @@ export default class RondevuPeer extends EventEmitter {
42
42
  * Set up peer connection event handlers
43
43
  */
44
44
  setupPeerConnection() {
45
- this.pc.onconnectionstatechange = () => {
45
+ this.connectionStateChangeHandler = () => {
46
46
  switch (this.pc.connectionState) {
47
47
  case 'connected':
48
48
  this.setState(new ConnectedState(this));
@@ -60,15 +60,19 @@ export default class RondevuPeer extends EventEmitter {
60
60
  break;
61
61
  }
62
62
  };
63
- this.pc.ondatachannel = (event) => {
63
+ this.pc.addEventListener('connectionstatechange', this.connectionStateChangeHandler);
64
+ this.dataChannelHandler = (event) => {
64
65
  this.emitEvent('datachannel', event.channel);
65
66
  };
66
- this.pc.ontrack = (event) => {
67
+ this.pc.addEventListener('datachannel', this.dataChannelHandler);
68
+ this.trackHandler = (event) => {
67
69
  this.emitEvent('track', event);
68
70
  };
69
- this.pc.onicecandidateerror = (event) => {
71
+ this.pc.addEventListener('track', this.trackHandler);
72
+ this.iceCandidateErrorHandler = (event) => {
70
73
  console.error('ICE candidate error:', event);
71
74
  };
75
+ this.pc.addEventListener('icecandidateerror', this.iceCandidateErrorHandler);
72
76
  }
73
77
  /**
74
78
  * Set new state and emit state change event
@@ -107,6 +111,19 @@ export default class RondevuPeer extends EventEmitter {
107
111
  * Close the connection and clean up
108
112
  */
109
113
  async close() {
114
+ // Remove RTCPeerConnection event listeners
115
+ if (this.connectionStateChangeHandler) {
116
+ this.pc.removeEventListener('connectionstatechange', this.connectionStateChangeHandler);
117
+ }
118
+ if (this.dataChannelHandler) {
119
+ this.pc.removeEventListener('datachannel', this.dataChannelHandler);
120
+ }
121
+ if (this.trackHandler) {
122
+ this.pc.removeEventListener('track', this.trackHandler);
123
+ }
124
+ if (this.iceCandidateErrorHandler) {
125
+ this.pc.removeEventListener('icecandidateerror', this.iceCandidateErrorHandler);
126
+ }
110
127
  await this._state.close();
111
128
  this.removeAllListeners();
112
129
  }
@@ -6,12 +6,18 @@ import type RondevuPeer from './index.js';
6
6
  */
7
7
  export declare abstract class PeerState {
8
8
  protected peer: RondevuPeer;
9
+ protected iceCandidateHandler?: (event: RTCPeerConnectionIceEvent) => void;
9
10
  constructor(peer: RondevuPeer);
10
11
  abstract get name(): string;
11
12
  createOffer(options: PeerOptions): Promise<string>;
12
13
  answer(offerId: string, offerSdp: string, options: PeerOptions): Promise<void>;
13
14
  handleAnswer(sdp: string): Promise<void>;
14
15
  handleIceCandidate(candidate: any): Promise<void>;
16
+ /**
17
+ * Setup trickle ICE candidate handler
18
+ * Sends local ICE candidates to server as they are discovered
19
+ */
20
+ protected setupIceCandidateHandler(): void;
15
21
  cleanup(): void;
16
22
  close(): Promise<void>;
17
23
  }
@@ -21,8 +21,31 @@ export class PeerState {
21
21
  await this.peer.pc.addIceCandidate(new RTCIceCandidate(candidate));
22
22
  }
23
23
  }
24
+ /**
25
+ * Setup trickle ICE candidate handler
26
+ * Sends local ICE candidates to server as they are discovered
27
+ */
28
+ setupIceCandidateHandler() {
29
+ this.iceCandidateHandler = async (event) => {
30
+ if (event.candidate && this.peer.offerId) {
31
+ const candidateData = event.candidate.toJSON();
32
+ if (candidateData.candidate && candidateData.candidate !== '') {
33
+ try {
34
+ await this.peer.offersApi.addIceCandidates(this.peer.offerId, [candidateData]);
35
+ }
36
+ catch (err) {
37
+ console.error('Error sending ICE candidate:', err);
38
+ }
39
+ }
40
+ }
41
+ };
42
+ this.peer.pc.addEventListener('icecandidate', this.iceCandidateHandler);
43
+ }
24
44
  cleanup() {
25
- // Override in states that need cleanup
45
+ // Clean up ICE candidate handler if it exists
46
+ if (this.iceCandidateHandler) {
47
+ this.peer.pc.removeEventListener('icecandidate', this.iceCandidateHandler);
48
+ }
26
49
  }
27
50
  async close() {
28
51
  this.cleanup();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtr-dev/rondevu-client",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "TypeScript client for Rondevu topic-based peer discovery and signaling server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",