@xtr-dev/rondevu-client 0.18.9 → 0.20.1

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 (40) hide show
  1. package/README.md +324 -47
  2. package/dist/{api.d.ts → api/client.d.ts} +17 -8
  3. package/dist/{api.js → api/client.js} +114 -81
  4. package/dist/{answerer-connection.d.ts → connections/answerer.d.ts} +13 -5
  5. package/dist/{answerer-connection.js → connections/answerer.js} +17 -32
  6. package/dist/{connection.d.ts → connections/base.d.ts} +26 -5
  7. package/dist/{connection.js → connections/base.js} +45 -4
  8. package/dist/{offerer-connection.d.ts → connections/offerer.d.ts} +30 -5
  9. package/dist/{offerer-connection.js → connections/offerer.js} +93 -32
  10. package/dist/core/index.d.ts +22 -0
  11. package/dist/core/index.js +17 -0
  12. package/dist/core/offer-pool.d.ts +94 -0
  13. package/dist/core/offer-pool.js +267 -0
  14. package/dist/{rondevu.d.ts → core/rondevu.d.ts} +7 -28
  15. package/dist/{rondevu.js → core/rondevu.js} +32 -175
  16. package/dist/{node-crypto-adapter.d.ts → crypto/node.d.ts} +1 -1
  17. package/dist/{web-crypto-adapter.d.ts → crypto/web.d.ts} +1 -1
  18. package/dist/utils/async-lock.d.ts +42 -0
  19. package/dist/utils/async-lock.js +75 -0
  20. package/dist/{message-buffer.d.ts → utils/message-buffer.d.ts} +1 -1
  21. package/package.json +3 -3
  22. package/dist/index.d.ts +0 -22
  23. package/dist/index.js +0 -17
  24. package/dist/rondevu-signaler.d.ts +0 -112
  25. package/dist/rondevu-signaler.js +0 -401
  26. /package/dist/{rpc-batcher.d.ts → api/batcher.d.ts} +0 -0
  27. /package/dist/{rpc-batcher.js → api/batcher.js} +0 -0
  28. /package/dist/{connection-config.d.ts → connections/config.d.ts} +0 -0
  29. /package/dist/{connection-config.js → connections/config.js} +0 -0
  30. /package/dist/{connection-events.d.ts → connections/events.d.ts} +0 -0
  31. /package/dist/{connection-events.js → connections/events.js} +0 -0
  32. /package/dist/{types.d.ts → core/types.d.ts} +0 -0
  33. /package/dist/{types.js → core/types.js} +0 -0
  34. /package/dist/{crypto-adapter.d.ts → crypto/adapter.d.ts} +0 -0
  35. /package/dist/{crypto-adapter.js → crypto/adapter.js} +0 -0
  36. /package/dist/{node-crypto-adapter.js → crypto/node.js} +0 -0
  37. /package/dist/{web-crypto-adapter.js → crypto/web.js} +0 -0
  38. /package/dist/{exponential-backoff.d.ts → utils/exponential-backoff.d.ts} +0 -0
  39. /package/dist/{exponential-backoff.js → utils/exponential-backoff.js} +0 -0
  40. /package/dist/{message-buffer.js → utils/message-buffer.js} +0 -0
@@ -1,14 +1,23 @@
1
1
  /**
2
2
  * Offerer-side WebRTC connection with offer creation and answer processing
3
3
  */
4
- import { RondevuConnection } from './connection.js';
5
- import { ConnectionState } from './connection-events.js';
4
+ import { RondevuConnection } from './base.js';
5
+ import { ConnectionState } from './events.js';
6
+ import { AsyncLock } from '../utils/async-lock.js';
6
7
  /**
7
8
  * Offerer connection - manages already-created offers and waits for answers
8
9
  */
9
10
  export class OffererConnection extends RondevuConnection {
10
11
  constructor(options) {
11
- super(undefined, options.config); // rtcConfig not needed, PC already created
12
+ // Force reconnectEnabled: false for offerer connections (offers are ephemeral)
13
+ super(undefined, {
14
+ ...options.config,
15
+ reconnectEnabled: false
16
+ });
17
+ // Rotation tracking
18
+ this.rotationLock = new AsyncLock();
19
+ this.rotating = false;
20
+ this.rotationAttempts = 0;
12
21
  this.api = options.api;
13
22
  this.serviceFqn = options.serviceFqn;
14
23
  this.offerId = options.offerId;
@@ -82,6 +91,71 @@ export class OffererConnection extends RondevuConnection {
82
91
  throw error;
83
92
  }
84
93
  }
94
+ /**
95
+ * Rebind this connection to a new offer (when previous offer failed)
96
+ * Keeps the same connection object alive but with new underlying WebRTC
97
+ */
98
+ async rebindToOffer(newOfferId, newPc, newDc) {
99
+ return this.rotationLock.run(async () => {
100
+ if (this.rotating) {
101
+ throw new Error('Rotation already in progress');
102
+ }
103
+ this.rotating = true;
104
+ try {
105
+ this.rotationAttempts++;
106
+ if (this.rotationAttempts > OffererConnection.MAX_ROTATION_ATTEMPTS) {
107
+ throw new Error('Max rotation attempts exceeded');
108
+ }
109
+ this.debug(`Rebinding connection from ${this.offerId} to ${newOfferId}`);
110
+ // 1. Clean up old peer connection
111
+ if (this.pc) {
112
+ this.pc.close();
113
+ }
114
+ if (this.dc && this.dc !== newDc) {
115
+ this.dc.close();
116
+ }
117
+ // 2. Update to new offer
118
+ this.offerId = newOfferId;
119
+ this.pc = newPc;
120
+ this.dc = newDc || null;
121
+ // 3. Reset answer processing flags
122
+ this.answerProcessed = false;
123
+ this.answerSdpFingerprint = null;
124
+ // 4. Setup event handlers for new peer connection
125
+ this.pc.onicecandidate = (event) => this.handleIceCandidate(event);
126
+ this.pc.oniceconnectionstatechange = () => this.handleIceConnectionStateChange();
127
+ this.pc.onconnectionstatechange = () => this.handleConnectionStateChange();
128
+ this.pc.onicegatheringstatechange = () => this.handleIceGatheringStateChange();
129
+ // 5. Setup data channel handlers if we have one
130
+ if (this.dc) {
131
+ this.setupDataChannelHandlers(this.dc);
132
+ }
133
+ // 6. Restart connection timeout
134
+ this.startConnectionTimeout();
135
+ // 7. Transition to SIGNALING state (waiting for answer)
136
+ this.transitionTo(ConnectionState.SIGNALING, 'Offer rotated, waiting for answer');
137
+ // Note: Message buffer is NOT cleared - it persists!
138
+ this.debug(`Rebind complete. Buffer has ${this.messageBuffer?.size() ?? 0} messages`);
139
+ }
140
+ finally {
141
+ this.rotating = false;
142
+ }
143
+ });
144
+ }
145
+ /**
146
+ * Check if connection is currently rotating
147
+ */
148
+ isRotating() {
149
+ return this.rotating;
150
+ }
151
+ /**
152
+ * Override onConnected to reset rotation attempts
153
+ */
154
+ onConnected() {
155
+ super.onConnected();
156
+ this.rotationAttempts = 0;
157
+ this.debug('Connection established, rotation attempts reset');
158
+ }
85
159
  /**
86
160
  * Generate a hash fingerprint of SDP for deduplication
87
161
  */
@@ -124,36 +198,22 @@ export class OffererConnection extends RondevuConnection {
124
198
  });
125
199
  }
126
200
  /**
127
- * Poll for remote ICE candidates
201
+ * Get the API instance
128
202
  */
129
- pollIceCandidates() {
130
- this.api
131
- .getOfferIceCandidates(this.serviceFqn, this.offerId, this.lastIcePollTime)
132
- .then((result) => {
133
- if (result.candidates.length > 0) {
134
- this.debug(`Received ${result.candidates.length} remote ICE candidates`);
135
- for (const iceCandidate of result.candidates) {
136
- if (iceCandidate.candidate && this.pc) {
137
- const candidate = iceCandidate.candidate;
138
- this.pc
139
- .addIceCandidate(new RTCIceCandidate(candidate))
140
- .then(() => {
141
- this.emit('ice:candidate:remote', new RTCIceCandidate(candidate));
142
- })
143
- .catch((error) => {
144
- this.debug('Failed to add ICE candidate:', error);
145
- });
146
- }
147
- // Update last poll time
148
- if (iceCandidate.createdAt > this.lastIcePollTime) {
149
- this.lastIcePollTime = iceCandidate.createdAt;
150
- }
151
- }
152
- }
153
- })
154
- .catch((error) => {
155
- this.debug('Failed to poll ICE candidates:', error);
156
- });
203
+ getApi() {
204
+ return this.api;
205
+ }
206
+ /**
207
+ * Get the service FQN
208
+ */
209
+ getServiceFqn() {
210
+ return this.serviceFqn;
211
+ }
212
+ /**
213
+ * Offerers accept all ICE candidates (no filtering)
214
+ */
215
+ getIceCandidateRole() {
216
+ return null;
157
217
  }
158
218
  /**
159
219
  * Attempt to reconnect
@@ -175,3 +235,4 @@ export class OffererConnection extends RondevuConnection {
175
235
  return this.offerId;
176
236
  }
177
237
  }
238
+ OffererConnection.MAX_ROTATION_ATTEMPTS = 5;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @xtr-dev/rondevu-client
3
+ * WebRTC peer signaling client
4
+ */
5
+ export { Rondevu, RondevuError, NetworkError, ValidationError, ConnectionError } from './rondevu.js';
6
+ export { RondevuAPI } from '../api/client.js';
7
+ export { RpcBatcher } from '../api/batcher.js';
8
+ export { RondevuConnection } from '../connections/base.js';
9
+ export { OffererConnection } from '../connections/offerer.js';
10
+ export { AnswererConnection } from '../connections/answerer.js';
11
+ export { ExponentialBackoff } from '../utils/exponential-backoff.js';
12
+ export { MessageBuffer } from '../utils/message-buffer.js';
13
+ export { WebCryptoAdapter } from '../crypto/web.js';
14
+ export { NodeCryptoAdapter } from '../crypto/node.js';
15
+ export type { Signaler, Binnable, } from './types.js';
16
+ export type { Keypair, OfferRequest, ServiceRequest, Service, ServiceOffer, IceCandidate, } from '../api/client.js';
17
+ export type { RondevuOptions, PublishServiceOptions, ConnectToServiceOptions, ConnectionContext, OfferContext, OfferFactory, ActiveOffer, FindServiceOptions, ServiceResult, PaginatedServiceResult } from './rondevu.js';
18
+ export type { CryptoAdapter } from '../crypto/adapter.js';
19
+ export type { ConnectionConfig, } from '../connections/config.js';
20
+ export type { ConnectionState, BufferedMessage, ReconnectInfo, StateChangeInfo, ConnectionEventMap, ConnectionEventName, ConnectionEventArgs, } from '../connections/events.js';
21
+ export type { OffererOptions, } from '../connections/offerer.js';
22
+ export type { AnswererOptions, } from '../connections/answerer.js';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @xtr-dev/rondevu-client
3
+ * WebRTC peer signaling client
4
+ */
5
+ export { Rondevu, RondevuError, NetworkError, ValidationError, ConnectionError } from './rondevu.js';
6
+ export { RondevuAPI } from '../api/client.js';
7
+ export { RpcBatcher } from '../api/batcher.js';
8
+ // Export connection classes
9
+ export { RondevuConnection } from '../connections/base.js';
10
+ export { OffererConnection } from '../connections/offerer.js';
11
+ export { AnswererConnection } from '../connections/answerer.js';
12
+ // Export utilities
13
+ export { ExponentialBackoff } from '../utils/exponential-backoff.js';
14
+ export { MessageBuffer } from '../utils/message-buffer.js';
15
+ // Export crypto adapters
16
+ export { WebCryptoAdapter } from '../crypto/web.js';
17
+ export { NodeCryptoAdapter } from '../crypto/node.js';
@@ -0,0 +1,94 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+ import { RondevuAPI } from '../api/client.js';
3
+ import { OffererConnection } from '../connections/offerer.js';
4
+ import { ConnectionConfig } from '../connections/config.js';
5
+ export type OfferFactory = (pc: RTCPeerConnection) => Promise<{
6
+ dc?: RTCDataChannel;
7
+ offer: RTCSessionDescriptionInit;
8
+ }>;
9
+ export interface OfferPoolOptions {
10
+ api: RondevuAPI;
11
+ serviceFqn: string;
12
+ maxOffers: number;
13
+ offerFactory: OfferFactory;
14
+ ttl: number;
15
+ iceServers: RTCIceServer[];
16
+ connectionConfig?: Partial<ConnectionConfig>;
17
+ debugEnabled?: boolean;
18
+ }
19
+ interface OfferPoolEvents {
20
+ 'connection:opened': (offerId: string, connection: OffererConnection) => void;
21
+ 'offer:created': (offerId: string, serviceFqn: string) => void;
22
+ 'offer:failed': (offerId: string, error: Error) => void;
23
+ 'connection:rotated': (oldOfferId: string, newOfferId: string, connection: OffererConnection) => void;
24
+ }
25
+ /**
26
+ * OfferPool manages a pool of WebRTC offers for a published service.
27
+ * Maintains a target number of active offers and automatically replaces
28
+ * offers that fail or get answered.
29
+ */
30
+ export declare class OfferPool extends EventEmitter<OfferPoolEvents> {
31
+ private readonly api;
32
+ private readonly serviceFqn;
33
+ private readonly maxOffers;
34
+ private readonly offerFactory;
35
+ private readonly ttl;
36
+ private readonly iceServers;
37
+ private readonly connectionConfig?;
38
+ private readonly debugEnabled;
39
+ private readonly activeConnections;
40
+ private readonly fillLock;
41
+ private running;
42
+ private pollingInterval;
43
+ private lastPollTimestamp;
44
+ private static readonly POLLING_INTERVAL_MS;
45
+ constructor(options: OfferPoolOptions);
46
+ /**
47
+ * Start filling offers and polling for answers
48
+ */
49
+ start(): Promise<void>;
50
+ /**
51
+ * Stop filling offers and polling
52
+ * Closes all active connections
53
+ */
54
+ stop(): void;
55
+ /**
56
+ * Get count of active offers
57
+ */
58
+ getOfferCount(): number;
59
+ /**
60
+ * Get all active connections
61
+ */
62
+ getActiveConnections(): Map<string, OffererConnection>;
63
+ /**
64
+ * Check if a specific offer is connected
65
+ */
66
+ isConnected(offerId: string): boolean;
67
+ /**
68
+ * Disconnect all active offers
69
+ */
70
+ disconnectAll(): void;
71
+ /**
72
+ * Fill offers to reach maxOffers count
73
+ * Uses AsyncLock to prevent concurrent fills
74
+ */
75
+ private fillOffers;
76
+ /**
77
+ * Create a new offer for rotation (reuses existing creation logic)
78
+ * Similar to createOffer() but only creates the offer, doesn't create connection
79
+ */
80
+ private createNewOfferForRotation;
81
+ /**
82
+ * Create a single offer and publish it to the server
83
+ */
84
+ private createOffer;
85
+ /**
86
+ * Poll for answers and delegate to OffererConnections
87
+ */
88
+ private pollInternal;
89
+ /**
90
+ * Debug logging (only if debug enabled)
91
+ */
92
+ private debug;
93
+ }
94
+ export {};
@@ -0,0 +1,267 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+ import { OffererConnection } from '../connections/offerer.js';
3
+ import { AsyncLock } from '../utils/async-lock.js';
4
+ /**
5
+ * OfferPool manages a pool of WebRTC offers for a published service.
6
+ * Maintains a target number of active offers and automatically replaces
7
+ * offers that fail or get answered.
8
+ */
9
+ export class OfferPool extends EventEmitter {
10
+ constructor(options) {
11
+ super();
12
+ // State
13
+ this.activeConnections = new Map();
14
+ this.fillLock = new AsyncLock();
15
+ this.running = false;
16
+ this.pollingInterval = null;
17
+ this.lastPollTimestamp = 0;
18
+ this.api = options.api;
19
+ this.serviceFqn = options.serviceFqn;
20
+ this.maxOffers = options.maxOffers;
21
+ this.offerFactory = options.offerFactory;
22
+ this.ttl = options.ttl;
23
+ this.iceServers = options.iceServers;
24
+ this.connectionConfig = options.connectionConfig;
25
+ this.debugEnabled = options.debugEnabled || false;
26
+ }
27
+ /**
28
+ * Start filling offers and polling for answers
29
+ */
30
+ async start() {
31
+ if (this.running) {
32
+ this.debug('Already running');
33
+ return;
34
+ }
35
+ this.debug('Starting offer pool');
36
+ this.running = true;
37
+ // Fill initial offers
38
+ await this.fillOffers();
39
+ // Start polling for answers
40
+ this.pollingInterval = setInterval(() => {
41
+ this.pollInternal();
42
+ }, OfferPool.POLLING_INTERVAL_MS);
43
+ }
44
+ /**
45
+ * Stop filling offers and polling
46
+ * Closes all active connections
47
+ */
48
+ stop() {
49
+ this.debug('Stopping offer pool');
50
+ this.running = false;
51
+ // Stop polling
52
+ if (this.pollingInterval) {
53
+ clearInterval(this.pollingInterval);
54
+ this.pollingInterval = null;
55
+ }
56
+ // Close all active connections
57
+ for (const [offerId, connection] of this.activeConnections.entries()) {
58
+ if (connection.isRotating()) {
59
+ this.debug(`Connection ${offerId} is rotating, will close anyway`);
60
+ }
61
+ this.debug(`Closing connection ${offerId}`);
62
+ connection.close();
63
+ }
64
+ this.activeConnections.clear();
65
+ }
66
+ /**
67
+ * Get count of active offers
68
+ */
69
+ getOfferCount() {
70
+ return this.activeConnections.size;
71
+ }
72
+ /**
73
+ * Get all active connections
74
+ */
75
+ getActiveConnections() {
76
+ return this.activeConnections;
77
+ }
78
+ /**
79
+ * Check if a specific offer is connected
80
+ */
81
+ isConnected(offerId) {
82
+ const connection = this.activeConnections.get(offerId);
83
+ return connection ? connection.getState() === 'connected' : false;
84
+ }
85
+ /**
86
+ * Disconnect all active offers
87
+ */
88
+ disconnectAll() {
89
+ this.debug('Disconnecting all offers');
90
+ for (const [offerId, connection] of this.activeConnections.entries()) {
91
+ this.debug(`Closing connection ${offerId}`);
92
+ connection.close();
93
+ }
94
+ this.activeConnections.clear();
95
+ }
96
+ /**
97
+ * Fill offers to reach maxOffers count
98
+ * Uses AsyncLock to prevent concurrent fills
99
+ */
100
+ async fillOffers() {
101
+ if (!this.running)
102
+ return;
103
+ return this.fillLock.run(async () => {
104
+ const currentCount = this.activeConnections.size;
105
+ const needed = this.maxOffers - currentCount;
106
+ this.debug(`Filling offers: current=${currentCount}, needed=${needed}`);
107
+ for (let i = 0; i < needed; i++) {
108
+ try {
109
+ await this.createOffer();
110
+ }
111
+ catch (err) {
112
+ console.error('[OfferPool] Failed to create offer:', err);
113
+ }
114
+ }
115
+ });
116
+ }
117
+ /**
118
+ * Create a new offer for rotation (reuses existing creation logic)
119
+ * Similar to createOffer() but only creates the offer, doesn't create connection
120
+ */
121
+ async createNewOfferForRotation() {
122
+ const rtcConfig = {
123
+ iceServers: this.iceServers
124
+ };
125
+ this.debug('Creating new offer for rotation...');
126
+ // 1. Create RTCPeerConnection
127
+ const pc = new RTCPeerConnection(rtcConfig);
128
+ // 2. Call the factory to create offer
129
+ let dc;
130
+ let offer;
131
+ try {
132
+ const factoryResult = await this.offerFactory(pc);
133
+ dc = factoryResult.dc;
134
+ offer = factoryResult.offer;
135
+ }
136
+ catch (err) {
137
+ pc.close();
138
+ throw err;
139
+ }
140
+ // 3. Publish to server to get offerId
141
+ const result = await this.api.publishService({
142
+ serviceFqn: this.serviceFqn,
143
+ offers: [{ sdp: offer.sdp }],
144
+ ttl: this.ttl,
145
+ });
146
+ const newOfferId = result.offers[0].offerId;
147
+ this.debug(`New offer created for rotation: ${newOfferId}`);
148
+ return { newOfferId, pc, dc };
149
+ }
150
+ /**
151
+ * Create a single offer and publish it to the server
152
+ */
153
+ async createOffer() {
154
+ const rtcConfig = {
155
+ iceServers: this.iceServers
156
+ };
157
+ this.debug('Creating new offer...');
158
+ // 1. Create RTCPeerConnection
159
+ const pc = new RTCPeerConnection(rtcConfig);
160
+ // 2. Call the factory to create offer
161
+ let dc;
162
+ let offer;
163
+ try {
164
+ const factoryResult = await this.offerFactory(pc);
165
+ dc = factoryResult.dc;
166
+ offer = factoryResult.offer;
167
+ }
168
+ catch (err) {
169
+ pc.close();
170
+ throw err;
171
+ }
172
+ // 3. Publish to server to get offerId
173
+ const result = await this.api.publishService({
174
+ serviceFqn: this.serviceFqn,
175
+ offers: [{ sdp: offer.sdp }],
176
+ ttl: this.ttl,
177
+ });
178
+ const offerId = result.offers[0].offerId;
179
+ // 4. Create OffererConnection instance
180
+ const connection = new OffererConnection({
181
+ api: this.api,
182
+ serviceFqn: this.serviceFqn,
183
+ offerId,
184
+ pc,
185
+ dc,
186
+ config: {
187
+ ...this.connectionConfig,
188
+ debug: this.debugEnabled,
189
+ },
190
+ });
191
+ // Setup connection event handlers
192
+ connection.on('connected', () => {
193
+ this.debug(`Connection established for offer ${offerId}`);
194
+ this.emit('connection:opened', offerId, connection);
195
+ });
196
+ connection.on('failed', async (error) => {
197
+ const currentOfferId = connection.getOfferId();
198
+ this.debug(`Connection failed for offer ${currentOfferId}, rotating...`);
199
+ try {
200
+ // Create new offer and rebind existing connection
201
+ const { newOfferId, pc, dc } = await this.createNewOfferForRotation();
202
+ // Rebind the connection to new offer
203
+ await connection.rebindToOffer(newOfferId, pc, dc);
204
+ // Update map: remove old offerId, add new offerId with same connection
205
+ this.activeConnections.delete(currentOfferId);
206
+ this.activeConnections.set(newOfferId, connection);
207
+ this.emit('connection:rotated', currentOfferId, newOfferId, connection);
208
+ this.debug(`Connection rotated: ${currentOfferId} → ${newOfferId}`);
209
+ }
210
+ catch (rotationError) {
211
+ // If rotation fails, fall back to destroying connection
212
+ this.debug(`Rotation failed for ${currentOfferId}:`, rotationError);
213
+ this.activeConnections.delete(currentOfferId);
214
+ this.emit('offer:failed', currentOfferId, error);
215
+ this.fillOffers(); // Create replacement
216
+ }
217
+ });
218
+ connection.on('closed', () => {
219
+ this.debug(`Connection closed for offer ${offerId}`);
220
+ this.activeConnections.delete(offerId);
221
+ this.fillOffers(); // Replace closed offer
222
+ });
223
+ // Store active connection
224
+ this.activeConnections.set(offerId, connection);
225
+ // Initialize the connection
226
+ await connection.initialize();
227
+ this.debug(`Offer created: ${offerId}`);
228
+ this.emit('offer:created', offerId, this.serviceFqn);
229
+ }
230
+ /**
231
+ * Poll for answers and delegate to OffererConnections
232
+ */
233
+ async pollInternal() {
234
+ if (!this.running)
235
+ return;
236
+ try {
237
+ const result = await this.api.poll(this.lastPollTimestamp);
238
+ // Process answers - delegate to OffererConnections
239
+ for (const answer of result.answers) {
240
+ const connection = this.activeConnections.get(answer.offerId);
241
+ if (connection) {
242
+ try {
243
+ await connection.processAnswer(answer.sdp, answer.answererId);
244
+ this.lastPollTimestamp = Math.max(this.lastPollTimestamp, answer.answeredAt);
245
+ // Create replacement offer
246
+ this.fillOffers();
247
+ }
248
+ catch (err) {
249
+ this.debug(`Failed to process answer for offer ${answer.offerId}:`, err);
250
+ }
251
+ }
252
+ }
253
+ }
254
+ catch (err) {
255
+ console.error('[OfferPool] Polling error:', err);
256
+ }
257
+ }
258
+ /**
259
+ * Debug logging (only if debug enabled)
260
+ */
261
+ debug(...args) {
262
+ if (this.debugEnabled) {
263
+ console.log('[OfferPool]', ...args);
264
+ }
265
+ }
266
+ }
267
+ OfferPool.POLLING_INTERVAL_MS = 1000;
@@ -1,9 +1,9 @@
1
- import { RondevuAPI, Keypair, IceCandidate, BatcherOptions } from './api.js';
2
- import { CryptoAdapter } from './crypto-adapter.js';
1
+ import { RondevuAPI, Keypair, IceCandidate, BatcherOptions } from '../api/client.js';
2
+ import { CryptoAdapter } from '../crypto/adapter.js';
3
3
  import { EventEmitter } from 'eventemitter3';
4
- import { OffererConnection } from './offerer-connection.js';
5
- import { AnswererConnection } from './answerer-connection.js';
6
- import { ConnectionConfig } from './connection-config.js';
4
+ import { OffererConnection } from '../connections/offerer.js';
5
+ import { AnswererConnection } from '../connections/answerer.js';
6
+ import { ConnectionConfig } from '../connections/config.js';
7
7
  export type IceServerPreset = 'ipv4-turn' | 'hostname-turns' | 'google-stun' | 'relay-only';
8
8
  export declare const ICE_SERVER_PRESETS: Record<IceServerPreset, RTCIceServer[]>;
9
9
  export interface RondevuOptions {
@@ -172,15 +172,8 @@ export declare class Rondevu extends EventEmitter {
172
172
  private rtcPeerConnection?;
173
173
  private rtcIceCandidate?;
174
174
  private currentService;
175
- private maxOffers;
176
- private offerFactory;
177
- private ttl;
178
- private activeConnections;
179
175
  private connectionConfig?;
180
- private filling;
181
- private fillingSemaphore;
182
- private pollingInterval;
183
- private lastPollTimestamp;
176
+ private offerPool;
184
177
  private constructor();
185
178
  /**
186
179
  * Internal debug logging - only logs if debug mode is enabled
@@ -229,18 +222,6 @@ export declare class Rondevu extends EventEmitter {
229
222
  * ```
230
223
  */
231
224
  publishService(options: PublishServiceOptions): Promise<void>;
232
- /**
233
- * Create a single offer and publish it to the server using OffererConnection
234
- */
235
- private createOffer;
236
- /**
237
- * Fill offers to reach maxOffers count with semaphore protection
238
- */
239
- private fillOffers;
240
- /**
241
- * Poll for answers and ICE candidates (internal use for automatic offer management)
242
- */
243
- private pollInternal;
244
225
  /**
245
226
  * Start filling offers and polling for answers/ICE
246
227
  * Call this after publishService() to begin accepting connections
@@ -266,7 +247,7 @@ export declare class Rondevu extends EventEmitter {
266
247
  * Disconnect all active offers
267
248
  * Similar to stopFilling() but doesn't stop the polling/filling process
268
249
  */
269
- disconnectAll(): Promise<void>;
250
+ disconnectAll(): void;
270
251
  /**
271
252
  * Get the current service status
272
253
  * @returns Object with service state information
@@ -274,8 +255,6 @@ export declare class Rondevu extends EventEmitter {
274
255
  getServiceStatus(): {
275
256
  active: boolean;
276
257
  offerCount: number;
277
- maxOffers: number;
278
- filling: boolean;
279
258
  };
280
259
  /**
281
260
  * Resolve the full service FQN from various input options