@xtr-dev/rondevu-client 0.20.1 → 0.21.3

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.
Files changed (43) hide show
  1. package/README.md +83 -385
  2. package/dist/api/batcher.d.ts +60 -38
  3. package/dist/api/batcher.js +121 -77
  4. package/dist/api/client.d.ts +104 -61
  5. package/dist/api/client.js +273 -185
  6. package/dist/connections/answerer.d.ts +15 -6
  7. package/dist/connections/answerer.js +56 -19
  8. package/dist/connections/base.d.ts +6 -4
  9. package/dist/connections/base.js +26 -16
  10. package/dist/connections/config.d.ts +30 -0
  11. package/dist/connections/config.js +20 -0
  12. package/dist/connections/events.d.ts +6 -6
  13. package/dist/connections/offerer.d.ts +37 -8
  14. package/dist/connections/offerer.js +92 -24
  15. package/dist/core/ice-config.d.ts +35 -0
  16. package/dist/core/ice-config.js +111 -0
  17. package/dist/core/index.d.ts +18 -18
  18. package/dist/core/index.js +18 -13
  19. package/dist/core/offer-pool.d.ts +30 -11
  20. package/dist/core/offer-pool.js +90 -76
  21. package/dist/core/peer.d.ts +158 -0
  22. package/dist/core/peer.js +254 -0
  23. package/dist/core/polling-manager.d.ts +71 -0
  24. package/dist/core/polling-manager.js +122 -0
  25. package/dist/core/rondevu-errors.d.ts +59 -0
  26. package/dist/core/rondevu-errors.js +75 -0
  27. package/dist/core/rondevu-types.d.ts +125 -0
  28. package/dist/core/rondevu-types.js +6 -0
  29. package/dist/core/rondevu.d.ts +106 -209
  30. package/dist/core/rondevu.js +222 -349
  31. package/dist/crypto/adapter.d.ts +25 -9
  32. package/dist/crypto/node.d.ts +27 -5
  33. package/dist/crypto/node.js +96 -25
  34. package/dist/crypto/web.d.ts +26 -4
  35. package/dist/crypto/web.js +102 -25
  36. package/dist/utils/message-buffer.js +4 -4
  37. package/dist/webrtc/adapter.d.ts +22 -0
  38. package/dist/webrtc/adapter.js +5 -0
  39. package/dist/webrtc/browser.d.ts +12 -0
  40. package/dist/webrtc/browser.js +15 -0
  41. package/dist/webrtc/node.d.ts +32 -0
  42. package/dist/webrtc/node.js +32 -0
  43. package/package.json +17 -6
@@ -8,9 +8,10 @@ import { ConnectionState } from './events.js';
8
8
  */
9
9
  export class AnswererConnection extends RondevuConnection {
10
10
  constructor(options) {
11
- super(options.rtcConfig, options.config);
11
+ super(options.rtcConfig, options.config, options.webrtcAdapter);
12
12
  this.api = options.api;
13
- this.serviceFqn = options.serviceFqn;
13
+ this.ownerUsername = options.ownerUsername;
14
+ this.tags = options.tags;
14
15
  this.offerId = options.offerId;
15
16
  this.offerSdp = options.offerSdp;
16
17
  }
@@ -25,7 +26,7 @@ export class AnswererConnection extends RondevuConnection {
25
26
  throw new Error('Peer connection not created');
26
27
  // Setup ondatachannel handler BEFORE setting remote description
27
28
  // This is critical to avoid race conditions
28
- this.pc.ondatachannel = (event) => {
29
+ this.pc.ondatachannel = event => {
29
30
  this.debug('Received data channel');
30
31
  this.dc = event.channel;
31
32
  this.setupDataChannelHandlers(this.dc);
@@ -43,7 +44,9 @@ export class AnswererConnection extends RondevuConnection {
43
44
  await this.pc.setLocalDescription(answer);
44
45
  this.debug('Answer created, sending to server');
45
46
  // Send answer to server
46
- await this.api.answerOffer(this.serviceFqn, this.offerId, answer.sdp);
47
+ await this.api.answerOffer(this.offerId, answer.sdp);
48
+ // Note: ICE candidate polling is handled by PollingManager
49
+ // Candidates are received via handleRemoteIceCandidates()
47
50
  this.debug('Answer sent successfully');
48
51
  }
49
52
  /**
@@ -54,14 +57,14 @@ export class AnswererConnection extends RondevuConnection {
54
57
  // For answerer, we add ICE candidates to the offer
55
58
  // The server will make them available for the offerer to poll
56
59
  this.api
57
- .addOfferIceCandidates(this.serviceFqn, this.offerId, [
60
+ .addOfferIceCandidates(this.offerId, [
58
61
  {
59
62
  candidate: candidate.candidate,
60
63
  sdpMLineIndex: candidate.sdpMLineIndex,
61
64
  sdpMid: candidate.sdpMid,
62
65
  },
63
66
  ])
64
- .catch((error) => {
67
+ .catch(error => {
65
68
  this.debug('Failed to send ICE candidate:', error);
66
69
  });
67
70
  }
@@ -72,10 +75,10 @@ export class AnswererConnection extends RondevuConnection {
72
75
  return this.api;
73
76
  }
74
77
  /**
75
- * Get the service FQN
78
+ * Get the owner username
76
79
  */
77
- getServiceFqn() {
78
- return this.serviceFqn;
80
+ getOwnerUsername() {
81
+ return this.ownerUsername;
79
82
  }
80
83
  /**
81
84
  * Answerers accept ICE candidates from offerers only
@@ -84,11 +87,11 @@ export class AnswererConnection extends RondevuConnection {
84
87
  return 'offerer';
85
88
  }
86
89
  /**
87
- * Attempt to reconnect
90
+ * Attempt to reconnect to the same user
88
91
  */
89
92
  attemptReconnect() {
90
- this.debug('Attempting to reconnect');
91
- // For answerer, we need to fetch a new offer and create a new answer
93
+ this.debug(`Attempting to reconnect to ${this.ownerUsername}`);
94
+ // For answerer, we need to fetch a new offer from the same user
92
95
  // Clean up old connection
93
96
  if (this.pc) {
94
97
  this.pc.close();
@@ -98,24 +101,31 @@ export class AnswererConnection extends RondevuConnection {
98
101
  this.dc.close();
99
102
  this.dc = null;
100
103
  }
101
- // Fetch new offer from service
104
+ // Discover new offer using tags (use paginated mode to get array)
102
105
  this.api
103
- .getService(this.serviceFqn)
104
- .then((service) => {
105
- if (!service || !service.offers || service.offers.length === 0) {
106
+ .discover({ tags: this.tags, limit: 100 })
107
+ .then(result => {
108
+ const response = result;
109
+ if (!response || !response.offers || response.offers.length === 0) {
106
110
  throw new Error('No offers available for reconnection');
107
111
  }
108
- // Pick a random offer
109
- const offer = service.offers[Math.floor(Math.random() * service.offers.length)];
112
+ // Filter for offers from the same user
113
+ const userOffers = response.offers.filter(o => o.username === this.ownerUsername);
114
+ if (userOffers.length === 0) {
115
+ throw new Error(`No offers available from ${this.ownerUsername}`);
116
+ }
117
+ // Pick a random offer from the same user
118
+ const offer = userOffers[Math.floor(Math.random() * userOffers.length)];
110
119
  this.offerId = offer.offerId;
111
120
  this.offerSdp = offer.sdp;
121
+ this.debug(`Found new offer ${offer.offerId} from ${this.ownerUsername}`);
112
122
  // Reinitialize with new offer
113
123
  return this.initialize();
114
124
  })
115
125
  .then(() => {
116
126
  this.emit('reconnect:success');
117
127
  })
118
- .catch((error) => {
128
+ .catch(error => {
119
129
  this.debug('Reconnection failed:', error);
120
130
  this.emit('reconnect:failed', error);
121
131
  this.scheduleReconnect();
@@ -127,4 +137,31 @@ export class AnswererConnection extends RondevuConnection {
127
137
  getOfferId() {
128
138
  return this.offerId;
129
139
  }
140
+ /**
141
+ * Handle remote ICE candidates received from polling
142
+ * Called by Rondevu when poll:ice event is received
143
+ */
144
+ handleRemoteIceCandidates(candidates) {
145
+ if (!this.pc) {
146
+ this.debug('Cannot add ICE candidates: peer connection not initialized');
147
+ return;
148
+ }
149
+ for (const iceCandidate of candidates) {
150
+ // Answerer only accepts offerer's candidates
151
+ if (iceCandidate.role !== 'offerer') {
152
+ continue;
153
+ }
154
+ if (iceCandidate.candidate) {
155
+ const rtcCandidate = this.webrtcAdapter.createIceCandidate(iceCandidate.candidate);
156
+ this.pc
157
+ .addIceCandidate(rtcCandidate)
158
+ .then(() => {
159
+ this.emit('ice:candidate:remote', rtcCandidate);
160
+ })
161
+ .catch(error => {
162
+ this.debug('Failed to add ICE candidate:', error);
163
+ });
164
+ }
165
+ }
166
+ }
130
167
  }
@@ -6,6 +6,7 @@ import { ConnectionConfig } from './config.js';
6
6
  import { ConnectionState, ConnectionEventMap } from './events.js';
7
7
  import { ExponentialBackoff } from '../utils/exponential-backoff.js';
8
8
  import { MessageBuffer } from '../utils/message-buffer.js';
9
+ import { WebRTCAdapter } from '../webrtc/adapter.js';
9
10
  /**
10
11
  * Abstract base class for WebRTC connections with durability features
11
12
  */
@@ -15,6 +16,7 @@ export declare abstract class RondevuConnection extends EventEmitter<ConnectionE
15
16
  protected dc: RTCDataChannel | null;
16
17
  protected state: ConnectionState;
17
18
  protected config: ConnectionConfig;
19
+ protected webrtcAdapter: WebRTCAdapter;
18
20
  protected messageBuffer: MessageBuffer | null;
19
21
  protected backoff: ExponentialBackoff | null;
20
22
  protected reconnectTimeout: ReturnType<typeof setTimeout> | null;
@@ -25,7 +27,7 @@ export declare abstract class RondevuConnection extends EventEmitter<ConnectionE
25
27
  protected lastIcePollTime: number;
26
28
  protected answerProcessed: boolean;
27
29
  protected answerSdpFingerprint: string | null;
28
- constructor(rtcConfig?: RTCConfiguration | undefined, userConfig?: Partial<ConnectionConfig>);
30
+ constructor(rtcConfig?: RTCConfiguration | undefined, userConfig?: Partial<ConnectionConfig>, webrtcAdapter?: WebRTCAdapter);
29
31
  /**
30
32
  * Transition to a new state and emit events
31
33
  */
@@ -87,9 +89,9 @@ export declare abstract class RondevuConnection extends EventEmitter<ConnectionE
87
89
  */
88
90
  protected abstract getApi(): any;
89
91
  /**
90
- * Get the service FQN - subclasses must provide
92
+ * Get the owner username - subclasses must provide
91
93
  */
92
- protected abstract getServiceFqn(): string;
94
+ protected abstract getOwnerUsername(): string;
93
95
  /**
94
96
  * Get the offer ID - subclasses must provide
95
97
  */
@@ -163,7 +165,7 @@ export declare abstract class RondevuConnection extends EventEmitter<ConnectionE
163
165
  /**
164
166
  * Debug logging helper
165
167
  */
166
- protected debug(...args: any[]): void;
168
+ protected debug(...args: unknown[]): void;
167
169
  protected abstract onLocalIceCandidate(candidate: RTCIceCandidate): void;
168
170
  protected abstract attemptReconnect(): void;
169
171
  }
@@ -6,11 +6,12 @@ import { mergeConnectionConfig } from './config.js';
6
6
  import { ConnectionState, } from './events.js';
7
7
  import { ExponentialBackoff } from '../utils/exponential-backoff.js';
8
8
  import { MessageBuffer } from '../utils/message-buffer.js';
9
+ import { BrowserWebRTCAdapter } from '../webrtc/browser.js';
9
10
  /**
10
11
  * Abstract base class for WebRTC connections with durability features
11
12
  */
12
13
  export class RondevuConnection extends EventEmitter {
13
- constructor(rtcConfig, userConfig) {
14
+ constructor(rtcConfig, userConfig, webrtcAdapter) {
14
15
  super();
15
16
  this.rtcConfig = rtcConfig;
16
17
  this.pc = null;
@@ -32,6 +33,7 @@ export class RondevuConnection extends EventEmitter {
32
33
  this.answerProcessed = false;
33
34
  this.answerSdpFingerprint = null;
34
35
  this.config = mergeConnectionConfig(userConfig);
36
+ this.webrtcAdapter = webrtcAdapter || new BrowserWebRTCAdapter();
35
37
  // Initialize message buffer if enabled
36
38
  if (this.config.bufferEnabled) {
37
39
  this.messageBuffer = new MessageBuffer({
@@ -81,9 +83,9 @@ export class RondevuConnection extends EventEmitter {
81
83
  * Create and configure RTCPeerConnection
82
84
  */
83
85
  createPeerConnection() {
84
- this.pc = new RTCPeerConnection(this.rtcConfig);
86
+ this.pc = this.webrtcAdapter.createPeerConnection(this.rtcConfig);
85
87
  // Setup event handlers BEFORE any signaling
86
- this.pc.onicecandidate = (event) => this.handleIceCandidate(event);
88
+ this.pc.onicecandidate = event => this.handleIceCandidate(event);
87
89
  this.pc.oniceconnectionstatechange = () => this.handleIceConnectionStateChange();
88
90
  this.pc.onconnectionstatechange = () => this.handleConnectionStateChange();
89
91
  this.pc.onicegatheringstatechange = () => this.handleIceGatheringStateChange();
@@ -95,8 +97,8 @@ export class RondevuConnection extends EventEmitter {
95
97
  setupDataChannelHandlers(dc) {
96
98
  dc.onopen = () => this.handleDataChannelOpen();
97
99
  dc.onclose = () => this.handleDataChannelClose();
98
- dc.onerror = (error) => this.handleDataChannelError(error);
99
- dc.onmessage = (event) => this.handleMessage(event);
100
+ dc.onerror = error => this.handleDataChannelError(error);
101
+ dc.onmessage = event => this.handleMessage(event);
100
102
  }
101
103
  /**
102
104
  * Handle local ICE candidate generation
@@ -121,7 +123,8 @@ export class RondevuConnection extends EventEmitter {
121
123
  if (this.state === ConnectionState.SIGNALING) {
122
124
  this.transitionTo(ConnectionState.CHECKING, 'ICE checking started');
123
125
  }
124
- this.startIcePolling();
126
+ // Note: ICE candidate polling is handled by PollingManager
127
+ // Candidates are received via handleRemoteIceCandidates()
125
128
  break;
126
129
  case 'connected':
127
130
  case 'completed':
@@ -191,7 +194,9 @@ export class RondevuConnection extends EventEmitter {
191
194
  this.debug('Data channel opened');
192
195
  this.emit('datachannel:open');
193
196
  // Only transition to CONNECTED if ICE is also connected
194
- if (this.pc && (this.pc.iceConnectionState === 'connected' || this.pc.iceConnectionState === 'completed')) {
197
+ if (this.pc &&
198
+ (this.pc.iceConnectionState === 'connected' ||
199
+ this.pc.iceConnectionState === 'completed')) {
195
200
  this.transitionTo(ConnectionState.CONNECTED, 'Data channel opened and ICE connected');
196
201
  this.onConnected();
197
202
  }
@@ -256,9 +261,14 @@ export class RondevuConnection extends EventEmitter {
256
261
  return;
257
262
  this.debug('Starting ICE polling');
258
263
  this.emit('ice:polling:started');
259
- this.lastIcePollTime = Date.now();
264
+ // Use 0 instead of Date.now() to get ALL existing candidates on first poll
265
+ // Remote candidates may have been created before this peer's ICE checking started
266
+ this.lastIcePollTime = 0;
267
+ const pollingStartTime = Date.now();
268
+ // Immediately poll for existing candidates
269
+ this.pollIceCandidates();
260
270
  this.icePollingInterval = setInterval(() => {
261
- const elapsed = Date.now() - this.lastIcePollTime;
271
+ const elapsed = Date.now() - pollingStartTime;
262
272
  if (elapsed > this.config.icePollingTimeout) {
263
273
  this.debug('ICE polling timeout');
264
274
  this.stopIcePolling();
@@ -285,10 +295,8 @@ export class RondevuConnection extends EventEmitter {
285
295
  pollIceCandidates() {
286
296
  const acceptRole = this.getIceCandidateRole();
287
297
  const api = this.getApi();
288
- const serviceFqn = this.getServiceFqn();
289
298
  const offerId = this.getOfferId();
290
- api
291
- .getOfferIceCandidates(serviceFqn, offerId, this.lastIcePollTime)
299
+ api.getOfferIceCandidates(offerId, this.lastIcePollTime)
292
300
  .then((result) => {
293
301
  if (result.candidates.length > 0) {
294
302
  this.debug(`Received ${result.candidates.length} remote ICE candidates`);
@@ -299,12 +307,13 @@ export class RondevuConnection extends EventEmitter {
299
307
  }
300
308
  if (iceCandidate.candidate && this.pc) {
301
309
  const candidate = iceCandidate.candidate;
310
+ const rtcCandidate = this.webrtcAdapter.createIceCandidate(candidate);
302
311
  this.pc
303
- .addIceCandidate(new RTCIceCandidate(candidate))
312
+ .addIceCandidate(rtcCandidate)
304
313
  .then(() => {
305
- this.emit('ice:candidate:remote', new RTCIceCandidate(candidate));
314
+ this.emit('ice:candidate:remote', rtcCandidate);
306
315
  })
307
- .catch((error) => {
316
+ .catch(error => {
308
317
  this.debug('Failed to add ICE candidate:', error);
309
318
  });
310
319
  }
@@ -370,7 +379,8 @@ export class RondevuConnection extends EventEmitter {
370
379
  if (!this.config.reconnectEnabled || !this.backoff)
371
380
  return;
372
381
  // Check if we've exceeded max attempts
373
- if (this.config.maxReconnectAttempts > 0 && this.reconnectAttempts >= this.config.maxReconnectAttempts) {
382
+ if (this.config.maxReconnectAttempts > 0 &&
383
+ this.reconnectAttempts >= this.config.maxReconnectAttempts) {
374
384
  this.debug('Max reconnection attempts reached');
375
385
  this.emit('reconnect:exhausted', this.reconnectAttempts);
376
386
  return;
@@ -1,6 +1,27 @@
1
1
  /**
2
2
  * Connection configuration interfaces and defaults
3
3
  */
4
+ /**
5
+ * Simplified connection options for public API.
6
+ * Advanced options use sensible defaults.
7
+ */
8
+ export interface ConnectionOptions {
9
+ /** Maximum time to wait for connection (ms). Default: 30000 */
10
+ timeout?: number;
11
+ /** Enable automatic reconnection on failures. Default: true */
12
+ reconnect?: boolean;
13
+ /** Maximum reconnection attempts (0 = infinite). Default: 5 */
14
+ maxReconnects?: number;
15
+ /** Buffer messages during disconnections. Default: true */
16
+ bufferMessages?: boolean;
17
+ /** Enable debug logging. Default: false */
18
+ debug?: boolean;
19
+ }
20
+ /**
21
+ * Full internal configuration with all options.
22
+ * Use ConnectionOptions for public API.
23
+ * @internal
24
+ */
4
25
  export interface ConnectionConfig {
5
26
  connectionTimeout: number;
6
27
  iceGatheringTimeout: number;
@@ -18,4 +39,13 @@ export interface ConnectionConfig {
18
39
  debug: boolean;
19
40
  }
20
41
  export declare const DEFAULT_CONNECTION_CONFIG: ConnectionConfig;
42
+ /**
43
+ * Merge user config with defaults.
44
+ * @internal
45
+ */
21
46
  export declare function mergeConnectionConfig(userConfig?: Partial<ConnectionConfig>): ConnectionConfig;
47
+ /**
48
+ * Convert simplified ConnectionOptions to full ConnectionConfig.
49
+ * Maps user-friendly names to internal config.
50
+ */
51
+ export declare function toConnectionConfig(options?: ConnectionOptions): ConnectionConfig;
@@ -22,9 +22,29 @@ export const DEFAULT_CONNECTION_CONFIG = {
22
22
  // Debug
23
23
  debug: false,
24
24
  };
25
+ /**
26
+ * Merge user config with defaults.
27
+ * @internal
28
+ */
25
29
  export function mergeConnectionConfig(userConfig) {
26
30
  return {
27
31
  ...DEFAULT_CONNECTION_CONFIG,
28
32
  ...userConfig,
29
33
  };
30
34
  }
35
+ /**
36
+ * Convert simplified ConnectionOptions to full ConnectionConfig.
37
+ * Maps user-friendly names to internal config.
38
+ */
39
+ export function toConnectionConfig(options) {
40
+ if (!options)
41
+ return DEFAULT_CONNECTION_CONFIG;
42
+ return {
43
+ ...DEFAULT_CONNECTION_CONFIG,
44
+ ...(options.timeout !== undefined && { connectionTimeout: options.timeout }),
45
+ ...(options.reconnect !== undefined && { reconnectEnabled: options.reconnect }),
46
+ ...(options.maxReconnects !== undefined && { maxReconnectAttempts: options.maxReconnects }),
47
+ ...(options.bufferMessages !== undefined && { bufferEnabled: options.bufferMessages }),
48
+ ...(options.debug !== undefined && { debug: options.debug }),
49
+ };
50
+ }
@@ -35,17 +35,17 @@ export interface StateChangeInfo {
35
35
  */
36
36
  export interface ConnectionEventMap {
37
37
  'state:changed': [StateChangeInfo];
38
- 'connecting': [];
39
- 'connected': [];
40
- 'disconnected': [reason?: string];
41
- 'failed': [error: Error];
42
- 'closed': [reason?: string];
38
+ connecting: [];
39
+ connected: [];
40
+ disconnected: [reason?: string];
41
+ failed: [error: Error];
42
+ closed: [reason?: string];
43
43
  'reconnect:scheduled': [ReconnectInfo];
44
44
  'reconnect:attempting': [attempt: number];
45
45
  'reconnect:success': [];
46
46
  'reconnect:failed': [error: Error];
47
47
  'reconnect:exhausted': [attempts: number];
48
- 'message': [data: string | ArrayBuffer | Blob];
48
+ message: [data: string | ArrayBuffer | Blob];
49
49
  'message:sent': [data: string | ArrayBuffer | Blob, buffered: boolean];
50
50
  'message:buffered': [data: string | ArrayBuffer | Blob];
51
51
  'message:replayed': [message: BufferedMessage];
@@ -2,14 +2,16 @@
2
2
  * Offerer-side WebRTC connection with offer creation and answer processing
3
3
  */
4
4
  import { RondevuConnection } from './base.js';
5
- import { RondevuAPI } from '../api/client.js';
5
+ import { RondevuAPI, IceCandidate } from '../api/client.js';
6
6
  import { ConnectionConfig } from './config.js';
7
+ import { WebRTCAdapter } from '../webrtc/adapter.js';
7
8
  export interface OffererOptions {
8
9
  api: RondevuAPI;
9
- serviceFqn: string;
10
+ ownerUsername: string;
10
11
  offerId: string;
11
12
  pc: RTCPeerConnection;
12
13
  dc?: RTCDataChannel;
14
+ webrtcAdapter?: WebRTCAdapter;
13
15
  config?: Partial<ConnectionConfig>;
14
16
  }
15
17
  /**
@@ -17,12 +19,14 @@ export interface OffererOptions {
17
19
  */
18
20
  export declare class OffererConnection extends RondevuConnection {
19
21
  private api;
20
- private serviceFqn;
22
+ private ownerUsername;
21
23
  private offerId;
24
+ private _peerUsername;
22
25
  private rotationLock;
23
26
  private rotating;
24
27
  private rotationAttempts;
25
28
  private static readonly MAX_ROTATION_ATTEMPTS;
29
+ private pendingIceCandidates;
26
30
  constructor(options: OffererOptions);
27
31
  /**
28
32
  * Initialize the connection - setup handlers for already-created offer
@@ -58,22 +62,47 @@ export declare class OffererConnection extends RondevuConnection {
58
62
  */
59
63
  protected getApi(): any;
60
64
  /**
61
- * Get the service FQN
65
+ * Get the owner username
62
66
  */
63
- protected getServiceFqn(): string;
67
+ protected getOwnerUsername(): string;
64
68
  /**
65
69
  * Offerers accept all ICE candidates (no filtering)
66
70
  */
67
71
  protected getIceCandidateRole(): 'offerer' | null;
68
72
  /**
69
- * Attempt to reconnect
73
+ * Attempt to reconnect (required by abstract base class)
70
74
  *
71
- * Note: For offerer connections, reconnection is handled by the Rondevu instance
72
- * creating a new offer via fillOffers(). This method is a no-op.
75
+ * For OffererConnection, traditional reconnection is NOT used.
76
+ * Instead, the OfferPool handles failures via offer rotation:
77
+ *
78
+ * 1. When this connection fails, the 'failed' event is emitted
79
+ * 2. OfferPool detects the failure and calls createNewOfferForRotation()
80
+ * 3. The new offer is published to the server
81
+ * 4. This connection is rebound via rebindToOffer()
82
+ *
83
+ * This approach ensures the answerer always gets a fresh offer
84
+ * rather than trying to reconnect to a stale one.
85
+ *
86
+ * @see OfferPool.createNewOfferForRotation() - creates replacement offer
87
+ * @see OffererConnection.rebindToOffer() - rebinds connection to new offer
73
88
  */
74
89
  protected attemptReconnect(): void;
75
90
  /**
76
91
  * Get the offer ID
77
92
  */
78
93
  getOfferId(): string;
94
+ /**
95
+ * Get the peer username (who answered this offer)
96
+ * Returns null if no answer has been processed yet
97
+ */
98
+ get peerUsername(): string | null;
99
+ /**
100
+ * Handle remote ICE candidates received from polling
101
+ * Called by OfferPool when poll:ice event is received
102
+ */
103
+ handleRemoteIceCandidates(candidates: IceCandidate[]): void;
104
+ /**
105
+ * Apply ICE candidates to the peer connection
106
+ */
107
+ private applyIceCandidates;
79
108
  }