@xtr-dev/rondevu-client 0.18.6 → 0.18.8

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.
@@ -0,0 +1,481 @@
1
+ /**
2
+ * Base connection class with state machine, reconnection, and message buffering
3
+ */
4
+ import { EventEmitter } from 'eventemitter3';
5
+ import { mergeConnectionConfig } from './connection-config.js';
6
+ import { ConnectionState, } from './connection-events.js';
7
+ import { ExponentialBackoff } from './exponential-backoff.js';
8
+ import { MessageBuffer } from './message-buffer.js';
9
+ /**
10
+ * Abstract base class for WebRTC connections with durability features
11
+ */
12
+ export class RondevuConnection extends EventEmitter {
13
+ constructor(rtcConfig, userConfig) {
14
+ super();
15
+ this.rtcConfig = rtcConfig;
16
+ this.pc = null;
17
+ this.dc = null;
18
+ this.state = ConnectionState.INITIALIZING;
19
+ // Message buffering
20
+ this.messageBuffer = null;
21
+ // Reconnection
22
+ this.backoff = null;
23
+ this.reconnectTimeout = null;
24
+ this.reconnectAttempts = 0;
25
+ // Timeouts
26
+ this.connectionTimeout = null;
27
+ this.iceGatheringTimeout = null;
28
+ // ICE polling
29
+ this.icePollingInterval = null;
30
+ this.lastIcePollTime = 0;
31
+ // Answer fingerprinting (for offerer)
32
+ this.answerProcessed = false;
33
+ this.answerSdpFingerprint = null;
34
+ this.config = mergeConnectionConfig(userConfig);
35
+ // Initialize message buffer if enabled
36
+ if (this.config.bufferEnabled) {
37
+ this.messageBuffer = new MessageBuffer({
38
+ maxSize: this.config.maxBufferSize,
39
+ maxAge: this.config.maxBufferAge,
40
+ });
41
+ }
42
+ // Initialize backoff if reconnection enabled
43
+ if (this.config.reconnectEnabled) {
44
+ this.backoff = new ExponentialBackoff({
45
+ base: this.config.reconnectBackoffBase,
46
+ max: this.config.reconnectBackoffMax,
47
+ jitter: this.config.reconnectJitter,
48
+ });
49
+ }
50
+ }
51
+ /**
52
+ * Transition to a new state and emit events
53
+ */
54
+ transitionTo(newState, reason) {
55
+ if (this.state === newState)
56
+ return;
57
+ const oldState = this.state;
58
+ this.state = newState;
59
+ this.debug(`State transition: ${oldState} → ${newState}${reason ? ` (${reason})` : ''}`);
60
+ this.emit('state:changed', { oldState, newState, reason });
61
+ // Emit specific lifecycle events
62
+ switch (newState) {
63
+ case ConnectionState.CONNECTING:
64
+ this.emit('connecting');
65
+ break;
66
+ case ConnectionState.CONNECTED:
67
+ this.emit('connected');
68
+ break;
69
+ case ConnectionState.DISCONNECTED:
70
+ this.emit('disconnected', reason);
71
+ break;
72
+ case ConnectionState.FAILED:
73
+ this.emit('failed', new Error(reason || 'Connection failed'));
74
+ break;
75
+ case ConnectionState.CLOSED:
76
+ this.emit('closed', reason);
77
+ break;
78
+ }
79
+ }
80
+ /**
81
+ * Create and configure RTCPeerConnection
82
+ */
83
+ createPeerConnection() {
84
+ this.pc = new RTCPeerConnection(this.rtcConfig);
85
+ // Setup event handlers BEFORE any signaling
86
+ this.pc.onicecandidate = (event) => this.handleIceCandidate(event);
87
+ this.pc.oniceconnectionstatechange = () => this.handleIceConnectionStateChange();
88
+ this.pc.onconnectionstatechange = () => this.handleConnectionStateChange();
89
+ this.pc.onicegatheringstatechange = () => this.handleIceGatheringStateChange();
90
+ return this.pc;
91
+ }
92
+ /**
93
+ * Setup data channel event handlers
94
+ */
95
+ setupDataChannelHandlers(dc) {
96
+ dc.onopen = () => this.handleDataChannelOpen();
97
+ dc.onclose = () => this.handleDataChannelClose();
98
+ dc.onerror = (error) => this.handleDataChannelError(error);
99
+ dc.onmessage = (event) => this.handleMessage(event);
100
+ }
101
+ /**
102
+ * Handle local ICE candidate generation
103
+ */
104
+ handleIceCandidate(event) {
105
+ this.emit('ice:candidate:local', event.candidate);
106
+ if (event.candidate) {
107
+ this.onLocalIceCandidate(event.candidate);
108
+ }
109
+ }
110
+ /**
111
+ * Handle ICE connection state changes (primary state driver)
112
+ */
113
+ handleIceConnectionStateChange() {
114
+ if (!this.pc)
115
+ return;
116
+ const iceState = this.pc.iceConnectionState;
117
+ this.emit('ice:connection:state', iceState);
118
+ this.debug(`ICE connection state: ${iceState}`);
119
+ switch (iceState) {
120
+ case 'checking':
121
+ if (this.state === ConnectionState.SIGNALING) {
122
+ this.transitionTo(ConnectionState.CHECKING, 'ICE checking started');
123
+ }
124
+ this.startIcePolling();
125
+ break;
126
+ case 'connected':
127
+ case 'completed':
128
+ this.stopIcePolling();
129
+ // Wait for data channel to open before transitioning to CONNECTED
130
+ if (this.dc?.readyState === 'open') {
131
+ this.transitionTo(ConnectionState.CONNECTED, 'ICE connected and data channel open');
132
+ this.onConnected();
133
+ }
134
+ break;
135
+ case 'disconnected':
136
+ if (this.state === ConnectionState.CONNECTED) {
137
+ this.transitionTo(ConnectionState.DISCONNECTED, 'ICE disconnected');
138
+ this.scheduleReconnect();
139
+ }
140
+ break;
141
+ case 'failed':
142
+ this.stopIcePolling();
143
+ this.transitionTo(ConnectionState.FAILED, 'ICE connection failed');
144
+ this.scheduleReconnect();
145
+ break;
146
+ case 'closed':
147
+ this.stopIcePolling();
148
+ this.transitionTo(ConnectionState.CLOSED, 'ICE connection closed');
149
+ break;
150
+ }
151
+ }
152
+ /**
153
+ * Handle connection state changes (backup validation)
154
+ */
155
+ handleConnectionStateChange() {
156
+ if (!this.pc)
157
+ return;
158
+ const connState = this.pc.connectionState;
159
+ this.emit('connection:state', connState);
160
+ this.debug(`Connection state: ${connState}`);
161
+ // Connection state provides backup validation
162
+ if (connState === 'failed' && this.state !== ConnectionState.FAILED) {
163
+ this.transitionTo(ConnectionState.FAILED, 'PeerConnection failed');
164
+ this.scheduleReconnect();
165
+ }
166
+ else if (connState === 'closed' && this.state !== ConnectionState.CLOSED) {
167
+ this.transitionTo(ConnectionState.CLOSED, 'PeerConnection closed');
168
+ }
169
+ }
170
+ /**
171
+ * Handle ICE gathering state changes
172
+ */
173
+ handleIceGatheringStateChange() {
174
+ if (!this.pc)
175
+ return;
176
+ const gatheringState = this.pc.iceGatheringState;
177
+ this.emit('ice:gathering:state', gatheringState);
178
+ this.debug(`ICE gathering state: ${gatheringState}`);
179
+ if (gatheringState === 'gathering' && this.state === ConnectionState.INITIALIZING) {
180
+ this.transitionTo(ConnectionState.GATHERING, 'ICE gathering started');
181
+ this.startIceGatheringTimeout();
182
+ }
183
+ else if (gatheringState === 'complete') {
184
+ this.clearIceGatheringTimeout();
185
+ }
186
+ }
187
+ /**
188
+ * Handle data channel open event
189
+ */
190
+ handleDataChannelOpen() {
191
+ this.debug('Data channel opened');
192
+ this.emit('datachannel:open');
193
+ // Only transition to CONNECTED if ICE is also connected
194
+ if (this.pc && (this.pc.iceConnectionState === 'connected' || this.pc.iceConnectionState === 'completed')) {
195
+ this.transitionTo(ConnectionState.CONNECTED, 'Data channel opened and ICE connected');
196
+ this.onConnected();
197
+ }
198
+ }
199
+ /**
200
+ * Handle data channel close event
201
+ */
202
+ handleDataChannelClose() {
203
+ this.debug('Data channel closed');
204
+ this.emit('datachannel:close');
205
+ if (this.state === ConnectionState.CONNECTED) {
206
+ this.transitionTo(ConnectionState.DISCONNECTED, 'Data channel closed');
207
+ this.scheduleReconnect();
208
+ }
209
+ }
210
+ /**
211
+ * Handle data channel error event
212
+ */
213
+ handleDataChannelError(error) {
214
+ this.debug('Data channel error:', error);
215
+ this.emit('datachannel:error', error);
216
+ }
217
+ /**
218
+ * Handle incoming message
219
+ */
220
+ handleMessage(event) {
221
+ this.emit('message', event.data);
222
+ }
223
+ /**
224
+ * Called when connection is successfully established
225
+ */
226
+ onConnected() {
227
+ this.clearConnectionTimeout();
228
+ this.reconnectAttempts = 0;
229
+ this.backoff?.reset();
230
+ // Replay buffered messages
231
+ if (this.messageBuffer && !this.messageBuffer.isEmpty()) {
232
+ const messages = this.messageBuffer.getValid();
233
+ this.debug(`Replaying ${messages.length} buffered messages`);
234
+ for (const message of messages) {
235
+ try {
236
+ this.sendDirect(message.data);
237
+ this.emit('message:replayed', message);
238
+ this.messageBuffer.remove(message.id);
239
+ }
240
+ catch (error) {
241
+ this.debug('Failed to replay message:', error);
242
+ }
243
+ }
244
+ // Remove expired messages
245
+ const expired = this.messageBuffer.getExpired();
246
+ for (const msg of expired) {
247
+ this.emit('message:buffer:expired', msg);
248
+ }
249
+ }
250
+ }
251
+ /**
252
+ * Start ICE candidate polling
253
+ */
254
+ startIcePolling() {
255
+ if (this.icePollingInterval)
256
+ return;
257
+ this.debug('Starting ICE polling');
258
+ this.emit('ice:polling:started');
259
+ this.lastIcePollTime = Date.now();
260
+ this.icePollingInterval = setInterval(() => {
261
+ const elapsed = Date.now() - this.lastIcePollTime;
262
+ if (elapsed > this.config.icePollingTimeout) {
263
+ this.debug('ICE polling timeout');
264
+ this.stopIcePolling();
265
+ return;
266
+ }
267
+ this.pollIceCandidates();
268
+ }, this.config.icePollingInterval);
269
+ }
270
+ /**
271
+ * Stop ICE candidate polling
272
+ */
273
+ stopIcePolling() {
274
+ if (!this.icePollingInterval)
275
+ return;
276
+ this.debug('Stopping ICE polling');
277
+ clearInterval(this.icePollingInterval);
278
+ this.icePollingInterval = null;
279
+ this.emit('ice:polling:stopped');
280
+ }
281
+ /**
282
+ * Start connection timeout
283
+ */
284
+ startConnectionTimeout() {
285
+ this.clearConnectionTimeout();
286
+ this.connectionTimeout = setTimeout(() => {
287
+ if (this.state !== ConnectionState.CONNECTED) {
288
+ this.debug('Connection timeout');
289
+ this.emit('connection:timeout');
290
+ this.transitionTo(ConnectionState.FAILED, 'Connection timeout');
291
+ this.scheduleReconnect();
292
+ }
293
+ }, this.config.connectionTimeout);
294
+ }
295
+ /**
296
+ * Clear connection timeout
297
+ */
298
+ clearConnectionTimeout() {
299
+ if (this.connectionTimeout) {
300
+ clearTimeout(this.connectionTimeout);
301
+ this.connectionTimeout = null;
302
+ }
303
+ }
304
+ /**
305
+ * Start ICE gathering timeout
306
+ */
307
+ startIceGatheringTimeout() {
308
+ this.clearIceGatheringTimeout();
309
+ this.iceGatheringTimeout = setTimeout(() => {
310
+ if (this.pc && this.pc.iceGatheringState !== 'complete') {
311
+ this.debug('ICE gathering timeout');
312
+ this.emit('ice:gathering:timeout');
313
+ }
314
+ }, this.config.iceGatheringTimeout);
315
+ }
316
+ /**
317
+ * Clear ICE gathering timeout
318
+ */
319
+ clearIceGatheringTimeout() {
320
+ if (this.iceGatheringTimeout) {
321
+ clearTimeout(this.iceGatheringTimeout);
322
+ this.iceGatheringTimeout = null;
323
+ }
324
+ }
325
+ /**
326
+ * Schedule reconnection attempt
327
+ */
328
+ scheduleReconnect() {
329
+ if (!this.config.reconnectEnabled || !this.backoff)
330
+ return;
331
+ // Check if we've exceeded max attempts
332
+ if (this.config.maxReconnectAttempts > 0 && this.reconnectAttempts >= this.config.maxReconnectAttempts) {
333
+ this.debug('Max reconnection attempts reached');
334
+ this.emit('reconnect:exhausted', this.reconnectAttempts);
335
+ return;
336
+ }
337
+ const delay = this.backoff.next();
338
+ this.reconnectAttempts++;
339
+ this.debug(`Scheduling reconnection attempt ${this.reconnectAttempts} in ${delay}ms`);
340
+ this.emit('reconnect:scheduled', {
341
+ attempt: this.reconnectAttempts,
342
+ delay,
343
+ maxAttempts: this.config.maxReconnectAttempts,
344
+ });
345
+ this.transitionTo(ConnectionState.RECONNECTING, `Attempt ${this.reconnectAttempts}`);
346
+ this.reconnectTimeout = setTimeout(() => {
347
+ this.emit('reconnect:attempting', this.reconnectAttempts);
348
+ this.attemptReconnect();
349
+ }, delay);
350
+ }
351
+ /**
352
+ * Cancel scheduled reconnection
353
+ */
354
+ cancelReconnect() {
355
+ if (this.reconnectTimeout) {
356
+ clearTimeout(this.reconnectTimeout);
357
+ this.reconnectTimeout = null;
358
+ }
359
+ }
360
+ /**
361
+ * Send a message directly (bypasses buffer)
362
+ */
363
+ sendDirect(data) {
364
+ if (!this.dc || this.dc.readyState !== 'open') {
365
+ throw new Error('Data channel is not open');
366
+ }
367
+ // Handle different data types explicitly
368
+ this.dc.send(data);
369
+ }
370
+ /**
371
+ * Send a message with automatic buffering
372
+ */
373
+ send(data) {
374
+ if (this.state === ConnectionState.CONNECTED && this.dc?.readyState === 'open') {
375
+ // Send directly
376
+ try {
377
+ this.sendDirect(data);
378
+ this.emit('message:sent', data, false);
379
+ }
380
+ catch (error) {
381
+ this.debug('Failed to send message:', error);
382
+ this.bufferMessage(data);
383
+ }
384
+ }
385
+ else {
386
+ // Buffer for later
387
+ this.bufferMessage(data);
388
+ }
389
+ }
390
+ /**
391
+ * Buffer a message for later delivery
392
+ */
393
+ bufferMessage(data) {
394
+ if (!this.messageBuffer) {
395
+ this.debug('Message buffering disabled, message dropped');
396
+ return;
397
+ }
398
+ if (this.messageBuffer.isFull()) {
399
+ const oldest = this.messageBuffer.getAll()[0];
400
+ this.emit('message:buffer:overflow', oldest);
401
+ }
402
+ const message = this.messageBuffer.add(data);
403
+ this.emit('message:buffered', data);
404
+ this.emit('message:sent', data, true);
405
+ this.debug(`Message buffered (${this.messageBuffer.size()}/${this.config.maxBufferSize})`);
406
+ }
407
+ /**
408
+ * Get current connection state
409
+ */
410
+ getState() {
411
+ return this.state;
412
+ }
413
+ /**
414
+ * Get the data channel
415
+ */
416
+ getDataChannel() {
417
+ return this.dc;
418
+ }
419
+ /**
420
+ * Get the peer connection
421
+ */
422
+ getPeerConnection() {
423
+ return this.pc;
424
+ }
425
+ /**
426
+ * Close the connection
427
+ */
428
+ close() {
429
+ this.debug('Closing connection');
430
+ this.transitionTo(ConnectionState.CLOSED, 'User requested close');
431
+ this.cleanup();
432
+ }
433
+ /**
434
+ * Complete cleanup of all resources
435
+ */
436
+ cleanup() {
437
+ this.debug('Cleaning up connection');
438
+ this.emit('cleanup:started');
439
+ // Clear all timeouts
440
+ this.clearConnectionTimeout();
441
+ this.clearIceGatheringTimeout();
442
+ this.cancelReconnect();
443
+ // Stop ICE polling
444
+ this.stopIcePolling();
445
+ // Close data channel
446
+ if (this.dc) {
447
+ this.dc.onopen = null;
448
+ this.dc.onclose = null;
449
+ this.dc.onerror = null;
450
+ this.dc.onmessage = null;
451
+ if (this.dc.readyState !== 'closed') {
452
+ this.dc.close();
453
+ }
454
+ this.dc = null;
455
+ }
456
+ // Close peer connection
457
+ if (this.pc) {
458
+ this.pc.onicecandidate = null;
459
+ this.pc.oniceconnectionstatechange = null;
460
+ this.pc.onconnectionstatechange = null;
461
+ this.pc.onicegatheringstatechange = null;
462
+ if (this.pc.connectionState !== 'closed') {
463
+ this.pc.close();
464
+ }
465
+ this.pc = null;
466
+ }
467
+ // Clear message buffer if not preserving
468
+ if (this.messageBuffer && !this.config.preserveBufferOnClose) {
469
+ this.messageBuffer.clear();
470
+ }
471
+ this.emit('cleanup:complete');
472
+ }
473
+ /**
474
+ * Debug logging helper
475
+ */
476
+ debug(...args) {
477
+ if (this.config.debug) {
478
+ console.log('[RondevuConnection]', ...args);
479
+ }
480
+ }
481
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Exponential backoff utility for connection reconnection
3
+ */
4
+ export interface BackoffConfig {
5
+ base: number;
6
+ max: number;
7
+ jitter: number;
8
+ }
9
+ export declare class ExponentialBackoff {
10
+ private config;
11
+ private attempt;
12
+ constructor(config: BackoffConfig);
13
+ /**
14
+ * Calculate the next delay based on the current attempt number
15
+ * Formula: min(base * 2^attempt, max) with jitter
16
+ */
17
+ next(): number;
18
+ /**
19
+ * Get the current attempt number
20
+ */
21
+ getAttempt(): number;
22
+ /**
23
+ * Reset the backoff state
24
+ */
25
+ reset(): void;
26
+ /**
27
+ * Peek at what the next delay would be without incrementing
28
+ */
29
+ peek(): number;
30
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Exponential backoff utility for connection reconnection
3
+ */
4
+ export class ExponentialBackoff {
5
+ constructor(config) {
6
+ this.config = config;
7
+ this.attempt = 0;
8
+ if (config.jitter < 0 || config.jitter > 1) {
9
+ throw new Error('Jitter must be between 0 and 1');
10
+ }
11
+ }
12
+ /**
13
+ * Calculate the next delay based on the current attempt number
14
+ * Formula: min(base * 2^attempt, max) with jitter
15
+ */
16
+ next() {
17
+ const exponentialDelay = this.config.base * Math.pow(2, this.attempt);
18
+ const cappedDelay = Math.min(exponentialDelay, this.config.max);
19
+ // Add jitter: delay ± (jitter * delay)
20
+ const jitterAmount = cappedDelay * this.config.jitter;
21
+ const jitter = (Math.random() * 2 - 1) * jitterAmount; // Random value between -jitterAmount and +jitterAmount
22
+ const finalDelay = Math.max(0, cappedDelay + jitter);
23
+ this.attempt++;
24
+ return Math.round(finalDelay);
25
+ }
26
+ /**
27
+ * Get the current attempt number
28
+ */
29
+ getAttempt() {
30
+ return this.attempt;
31
+ }
32
+ /**
33
+ * Reset the backoff state
34
+ */
35
+ reset() {
36
+ this.attempt = 0;
37
+ }
38
+ /**
39
+ * Peek at what the next delay would be without incrementing
40
+ */
41
+ peek() {
42
+ const exponentialDelay = this.config.base * Math.pow(2, this.attempt);
43
+ const cappedDelay = Math.min(exponentialDelay, this.config.max);
44
+ return cappedDelay;
45
+ }
46
+ }
package/dist/index.d.ts CHANGED
@@ -5,9 +5,18 @@
5
5
  export { Rondevu, RondevuError, NetworkError, ValidationError, ConnectionError } from './rondevu.js';
6
6
  export { RondevuAPI } from './api.js';
7
7
  export { RpcBatcher } from './rpc-batcher.js';
8
+ export { RondevuConnection } from './connection.js';
9
+ export { OffererConnection } from './offerer-connection.js';
10
+ export { AnswererConnection } from './answerer-connection.js';
11
+ export { ExponentialBackoff } from './exponential-backoff.js';
12
+ export { MessageBuffer } from './message-buffer.js';
8
13
  export { WebCryptoAdapter } from './web-crypto-adapter.js';
9
14
  export { NodeCryptoAdapter } from './node-crypto-adapter.js';
10
15
  export type { Signaler, Binnable, } from './types.js';
11
16
  export type { Keypair, OfferRequest, ServiceRequest, Service, ServiceOffer, IceCandidate, } from './api.js';
12
17
  export type { RondevuOptions, PublishServiceOptions, ConnectToServiceOptions, ConnectionContext, OfferContext, OfferFactory, ActiveOffer, FindServiceOptions, ServiceResult, PaginatedServiceResult } from './rondevu.js';
13
18
  export type { CryptoAdapter } from './crypto-adapter.js';
19
+ export type { ConnectionConfig, } from './connection-config.js';
20
+ export type { ConnectionState, BufferedMessage, ReconnectInfo, StateChangeInfo, ConnectionEventMap, ConnectionEventName, ConnectionEventArgs, } from './connection-events.js';
21
+ export type { OffererOptions, } from './offerer-connection.js';
22
+ export type { AnswererOptions, } from './answerer-connection.js';
package/dist/index.js CHANGED
@@ -5,6 +5,13 @@
5
5
  export { Rondevu, RondevuError, NetworkError, ValidationError, ConnectionError } from './rondevu.js';
6
6
  export { RondevuAPI } from './api.js';
7
7
  export { RpcBatcher } from './rpc-batcher.js';
8
+ // Export connection classes
9
+ export { RondevuConnection } from './connection.js';
10
+ export { OffererConnection } from './offerer-connection.js';
11
+ export { AnswererConnection } from './answerer-connection.js';
12
+ // Export utilities
13
+ export { ExponentialBackoff } from './exponential-backoff.js';
14
+ export { MessageBuffer } from './message-buffer.js';
8
15
  // Export crypto adapters
9
16
  export { WebCryptoAdapter } from './web-crypto-adapter.js';
10
17
  export { NodeCryptoAdapter } from './node-crypto-adapter.js';
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Message buffering system for storing messages during disconnections
3
+ */
4
+ import { BufferedMessage } from './connection-events.js';
5
+ export interface MessageBufferConfig {
6
+ maxSize: number;
7
+ maxAge: number;
8
+ }
9
+ export declare class MessageBuffer {
10
+ private config;
11
+ private buffer;
12
+ private messageIdCounter;
13
+ constructor(config: MessageBufferConfig);
14
+ /**
15
+ * Add a message to the buffer
16
+ * Returns the buffered message with metadata
17
+ */
18
+ add(data: string | ArrayBuffer | Blob): BufferedMessage;
19
+ /**
20
+ * Get all messages in the buffer
21
+ */
22
+ getAll(): BufferedMessage[];
23
+ /**
24
+ * Get messages that haven't exceeded max age
25
+ */
26
+ getValid(): BufferedMessage[];
27
+ /**
28
+ * Get and remove expired messages
29
+ */
30
+ getExpired(): BufferedMessage[];
31
+ /**
32
+ * Remove a specific message by ID
33
+ */
34
+ remove(messageId: string): BufferedMessage | null;
35
+ /**
36
+ * Clear all messages from the buffer
37
+ */
38
+ clear(): BufferedMessage[];
39
+ /**
40
+ * Increment attempt count for a message
41
+ */
42
+ incrementAttempt(messageId: string): boolean;
43
+ /**
44
+ * Get the current size of the buffer
45
+ */
46
+ size(): number;
47
+ /**
48
+ * Check if buffer is empty
49
+ */
50
+ isEmpty(): boolean;
51
+ /**
52
+ * Check if buffer is full
53
+ */
54
+ isFull(): boolean;
55
+ }