@chaterafrikang/sdk 0.1.0-beta.0

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,1072 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+
3
+ /**
4
+ * Configuration options for the ChatAfrika SDK client
5
+ */
6
+ interface ClientConfig {
7
+ /**
8
+ * WebSocket server URL
9
+ */
10
+ wsUrl: string;
11
+ /**
12
+ * REST API base URL (optional, for fallback operations)
13
+ */
14
+ apiUrl?: string;
15
+ /**
16
+ * SDK token for authentication
17
+ */
18
+ token: string;
19
+ /**
20
+ * Enable automatic reconnection
21
+ * @default true
22
+ */
23
+ autoReconnect?: boolean;
24
+ /**
25
+ * Maximum reconnection attempts
26
+ * @default 5
27
+ */
28
+ maxReconnectAttempts?: number;
29
+ /**
30
+ * Reconnection delay in milliseconds
31
+ * @default 1000
32
+ */
33
+ reconnectDelay?: number;
34
+ /**
35
+ * Enable debug logging
36
+ * @default false
37
+ */
38
+ debug?: boolean;
39
+ }
40
+
41
+ /**
42
+ * Connection state enumeration for the SDK
43
+ */
44
+ declare enum ConnectionState {
45
+ DISCONNECTED = "disconnected",
46
+ CONNECTING = "connecting",
47
+ CONNECTED = "connected",
48
+ RECONNECTING = "reconnecting",
49
+ ERROR = "error"
50
+ }
51
+
52
+ /**
53
+ * Manages SDK token storage and validation
54
+ *
55
+ * Note: This manager only CONSUMES tokens.
56
+ * Token issuance is handled by the backend API.
57
+ */
58
+ declare class TokenManager {
59
+ private token;
60
+ /**
61
+ * Set the SDK token
62
+ */
63
+ setToken(token: string): void;
64
+ /**
65
+ * Get the current SDK token
66
+ */
67
+ getToken(): string | null;
68
+ /**
69
+ * Check if a token is set
70
+ */
71
+ hasToken(): boolean;
72
+ /**
73
+ * Clear the token
74
+ */
75
+ clearToken(): void;
76
+ /**
77
+ * Replace the current token with a new one (token rotation)
78
+ */
79
+ rotateToken(newToken: string): void;
80
+ /**
81
+ * Validate token format (basic validation)
82
+ */
83
+ isValidFormat(token: string): boolean;
84
+ /**
85
+ * Validate that a token is present before operations
86
+ */
87
+ validateTokenPresence(): void;
88
+ }
89
+
90
+ /**
91
+ * WebSocket message protocol matching the Go backend
92
+ */
93
+ type ClientMessageType = 'join' | 'leave' | 'message' | 'typing_start' | 'typing_stop';
94
+ /**
95
+ * Base structure for all client messages
96
+ */
97
+ interface BaseClientMessage {
98
+ type: ClientMessageType;
99
+ conversation_id: string;
100
+ }
101
+ /**
102
+ * Join conversation message
103
+ */
104
+ interface JoinMessage extends BaseClientMessage {
105
+ type: 'join';
106
+ }
107
+ /**
108
+ * Leave conversation message
109
+ */
110
+ interface LeaveMessage extends BaseClientMessage {
111
+ type: 'leave';
112
+ }
113
+ /**
114
+ * Send message payload
115
+ */
116
+ interface MessagePayload {
117
+ message_id: string;
118
+ content: string;
119
+ message_type: string;
120
+ }
121
+ /**
122
+ * Send message to conversation
123
+ */
124
+ interface SendMessageMessage extends BaseClientMessage {
125
+ type: 'message';
126
+ payload: MessagePayload;
127
+ }
128
+ /**
129
+ * Typing start message
130
+ */
131
+ interface TypingStartMessage extends BaseClientMessage {
132
+ type: 'typing_start';
133
+ }
134
+ /**
135
+ * Typing stop message
136
+ */
137
+ interface TypingStopMessage extends BaseClientMessage {
138
+ type: 'typing_stop';
139
+ }
140
+ /**
141
+ * Union type for all client messages
142
+ */
143
+ type ClientMessage = JoinMessage | LeaveMessage | SendMessageMessage | TypingStartMessage | TypingStopMessage;
144
+ type ServerMessageType = 'joined' | 'message' | 'typing' | 'presence' | 'receipt' | 'support' | 'error';
145
+ /**
146
+ * Base structure for all server messages
147
+ */
148
+ interface BaseServerMessage {
149
+ type: ServerMessageType;
150
+ conversation_id?: string;
151
+ }
152
+ /**
153
+ * Joined conversation response
154
+ */
155
+ interface JoinedMessage extends BaseServerMessage {
156
+ type: 'joined';
157
+ payload?: {
158
+ conversation_id: string;
159
+ [key: string]: unknown;
160
+ };
161
+ }
162
+ /**
163
+ * Broadcast message payload
164
+ */
165
+ interface BroadcastMessagePayload {
166
+ message_id: string;
167
+ sender_id: string;
168
+ sender_type: string;
169
+ content: string;
170
+ message_type: string;
171
+ sent_at: string;
172
+ }
173
+ /**
174
+ * Received message from server
175
+ */
176
+ interface ReceivedMessageMessage extends BaseServerMessage {
177
+ type: 'message';
178
+ conversation_id: string;
179
+ payload: BroadcastMessagePayload;
180
+ }
181
+ /**
182
+ * Typing indicator payload
183
+ */
184
+ interface TypingPayload {
185
+ user_id: string;
186
+ state: 'start' | 'stop';
187
+ }
188
+ /**
189
+ * Typing indicator message
190
+ */
191
+ interface TypingMessage extends BaseServerMessage {
192
+ type: 'typing';
193
+ conversation_id: string;
194
+ payload: TypingPayload;
195
+ }
196
+ /**
197
+ * Presence payload
198
+ */
199
+ interface PresencePayload {
200
+ user_id: string;
201
+ state: 'online' | 'offline';
202
+ }
203
+ /**
204
+ * Presence update message
205
+ */
206
+ interface PresenceMessage extends BaseServerMessage {
207
+ type: 'presence';
208
+ payload: PresencePayload;
209
+ }
210
+ /**
211
+ * Receipt payload
212
+ */
213
+ interface ReceiptPayload {
214
+ message_id: string;
215
+ user_id?: string;
216
+ state: 'delivered' | 'read';
217
+ }
218
+ /**
219
+ * Receipt message from server
220
+ */
221
+ interface ReceiptMessage extends BaseServerMessage {
222
+ type: 'receipt';
223
+ conversation_id: string;
224
+ payload: ReceiptPayload;
225
+ }
226
+ /**
227
+ * Support event type
228
+ */
229
+ type SupportEventType = 'assigned' | 'unassigned' | 'bot_takeover' | 'bot_released' | 'closed' | 'reopened';
230
+ /**
231
+ * Support event payload
232
+ */
233
+ interface SupportEventPayload {
234
+ event: SupportEventType;
235
+ agent_id?: string;
236
+ [key: string]: unknown;
237
+ }
238
+ /**
239
+ * Support message from server
240
+ */
241
+ interface SupportMessage extends BaseServerMessage {
242
+ type: 'support';
243
+ conversation_id: string;
244
+ payload: SupportEventPayload;
245
+ }
246
+ /**
247
+ * Error message from server
248
+ */
249
+ interface ErrorMessage extends BaseServerMessage {
250
+ type: 'error';
251
+ error: string;
252
+ }
253
+ /**
254
+ * Union type for all server messages
255
+ */
256
+ type ServerMessage = JoinedMessage | ReceivedMessageMessage | TypingMessage | PresenceMessage | ReceiptMessage | SupportMessage | ErrorMessage;
257
+
258
+ /**
259
+ * Conversation class representing a single conversation
260
+ *
261
+ * This class provides a scoped API for conversation-level operations.
262
+ * It does NOT persist messages or store history.
263
+ */
264
+ declare class Conversation extends EventEmitter {
265
+ readonly id: string;
266
+ private _isJoined;
267
+ private messageManager;
268
+ private typingManager;
269
+ /**
270
+ * Callback function to send messages via WebSocket
271
+ * Set by ConversationManager
272
+ */
273
+ private sendMessageHandler?;
274
+ /**
275
+ * Callback function to send typing events via WebSocket
276
+ * Set by ConversationManager
277
+ */
278
+ private sendTypingHandler?;
279
+ constructor(conversationId: string, debug?: boolean);
280
+ /**
281
+ * Check if this conversation is currently joined
282
+ */
283
+ get isJoined(): boolean;
284
+ /**
285
+ * Set the send message handler (called by ConversationManager)
286
+ */
287
+ setSendMessageHandler(handler: (message: SendMessageMessage) => void): void;
288
+ /**
289
+ * Set the send typing handler (called by ConversationManager)
290
+ */
291
+ setSendTypingHandler(handler: (message: TypingStartMessage | TypingStopMessage) => void): void;
292
+ /**
293
+ * Mark conversation as joined
294
+ */
295
+ markJoined(): void;
296
+ /**
297
+ * Mark conversation as left
298
+ */
299
+ markLeft(): void;
300
+ /**
301
+ * Send a message to this conversation
302
+ *
303
+ * This method:
304
+ * 1. Generates a message_id (UUID v4)
305
+ * 2. Creates an optimistic Message
306
+ * 3. Emits the optimistic message immediately
307
+ * 4. Sends the message via transport
308
+ */
309
+ sendMessage(content: string, messageType?: 'text' | 'system', metadata?: Record<string, unknown>): void;
310
+ /**
311
+ * Handle an incoming message from the server
312
+ * Called by ConversationManager when routing messages
313
+ */
314
+ handleIncomingMessage(payload: BroadcastMessagePayload): void;
315
+ /**
316
+ * Handle a receipt update
317
+ * Called by ConversationManager when routing receipt messages
318
+ */
319
+ handleReceipt(messageId: string, receiptState: 'delivered' | 'read', userId?: string): void;
320
+ /**
321
+ * Handle a typing event from the server
322
+ * Called by ConversationManager when routing typing messages
323
+ */
324
+ handleTyping(payload: TypingPayload): void;
325
+ /**
326
+ * Emit a presence event (called by ConversationManager when routing)
327
+ * Note: Presence is SDK-global, but we emit it here for convenience
328
+ */
329
+ emitPresence(payload: PresencePayload): void;
330
+ /**
331
+ * Clear message tracking for this conversation
332
+ */
333
+ clearMessages(): void;
334
+ /**
335
+ * Clear typing state for this conversation
336
+ */
337
+ clearTyping(): void;
338
+ /**
339
+ * Start typing indicator
340
+ *
341
+ * This method sends a typing_start message to the server.
342
+ * The TypingManager will handle debouncing and auto-expiry.
343
+ */
344
+ startTyping(): void;
345
+ /**
346
+ * Stop typing indicator
347
+ *
348
+ * This method sends a typing_stop message to the server.
349
+ */
350
+ stopTyping(): void;
351
+ }
352
+
353
+ /**
354
+ * Support session role
355
+ */
356
+ type SupportRole = 'customer' | 'agent';
357
+ /**
358
+ * Support session status
359
+ */
360
+ type SupportStatus = 'queued' | 'assigned' | 'active' | 'closed';
361
+ /**
362
+ * SupportSession wraps a Conversation and adds support-specific semantics
363
+ *
364
+ * This is a thin semantic layer that provides:
365
+ * - Role awareness (customer vs agent)
366
+ * - Assignment state tracking
367
+ * - Bot handover awareness
368
+ *
369
+ * It does NOT replace Conversation - it wraps it.
370
+ */
371
+ declare class SupportSession extends EventEmitter {
372
+ readonly conversationId: string;
373
+ private conversation;
374
+ private role;
375
+ private assignedAgentId?;
376
+ private botActive;
377
+ private status;
378
+ private logger;
379
+ constructor(conversationId: string, conversation: Conversation, role: SupportRole, debug?: boolean);
380
+ /**
381
+ * Get the underlying Conversation instance
382
+ */
383
+ getConversation(): Conversation;
384
+ /**
385
+ * Check if this session is for a customer
386
+ */
387
+ isCustomer(): boolean;
388
+ /**
389
+ * Check if this session is for an agent
390
+ */
391
+ isAgent(): boolean;
392
+ /**
393
+ * Get the current role
394
+ */
395
+ getRole(): SupportRole;
396
+ /**
397
+ * Check if bot is currently active
398
+ */
399
+ isBotActive(): boolean;
400
+ /**
401
+ * Get the assigned agent ID (if any)
402
+ */
403
+ getAssignedAgent(): string | undefined;
404
+ /**
405
+ * Get the current status
406
+ */
407
+ getStatus(): SupportStatus;
408
+ /**
409
+ * Handle assignment of an agent
410
+ */
411
+ handleAssigned(agentId: string): void;
412
+ /**
413
+ * Handle unassignment of an agent
414
+ */
415
+ handleUnassigned(): void;
416
+ /**
417
+ * Handle bot takeover
418
+ */
419
+ handleBotTakeover(): void;
420
+ /**
421
+ * Handle bot release
422
+ */
423
+ handleBotReleased(): void;
424
+ /**
425
+ * Handle session closed
426
+ */
427
+ handleClosed(): void;
428
+ /**
429
+ * Handle session reopened
430
+ */
431
+ handleReopened(): void;
432
+ /**
433
+ * Send a message (delegates to Conversation)
434
+ */
435
+ sendMessage(content: string, messageType?: 'text' | 'system', metadata?: Record<string, unknown>): void;
436
+ /**
437
+ * Join the conversation (delegates to Conversation)
438
+ */
439
+ join(): Promise<void>;
440
+ /**
441
+ * Leave the conversation (delegates to Conversation)
442
+ */
443
+ leave(): Promise<void>;
444
+ }
445
+
446
+ /**
447
+ * Main ChatAfrika SDK class
448
+ *
449
+ * This is the primary entry point for the SDK.
450
+ * It manages the connection state and provides access to all SDK features.
451
+ */
452
+ declare class ChatAfrika extends EventEmitter {
453
+ private config;
454
+ private connectionState;
455
+ private wsClient;
456
+ private tokenManager;
457
+ private conversationManager;
458
+ private presenceManager;
459
+ private supportManager;
460
+ private logger;
461
+ constructor(config: ClientConfig);
462
+ /**
463
+ * Set up WebSocket event handlers
464
+ */
465
+ private setupWebSocketHandlers;
466
+ /**
467
+ * Get the current connection state
468
+ */
469
+ getState(): ConnectionState;
470
+ /**
471
+ * Get the current configuration
472
+ */
473
+ getConfig(): Readonly<Required<ClientConfig>>;
474
+ /**
475
+ * Connect to the ChatAfrika service
476
+ */
477
+ connect(): Promise<void>;
478
+ /**
479
+ * Disconnect from the ChatAfrika service
480
+ */
481
+ disconnect(): Promise<void>;
482
+ /**
483
+ * Check if the client is connected
484
+ */
485
+ isConnected(): boolean;
486
+ /**
487
+ * Update the authentication token
488
+ * Use this when your token expires and you need to refresh it
489
+ * After updating, call connect() to reconnect with the new token
490
+ */
491
+ updateToken(newToken: string): void;
492
+ /**
493
+ * Get the conversations manager
494
+ * Exposes: sdk.conversations.get(id), sdk.conversations.join(id), etc.
495
+ */
496
+ get conversations(): {
497
+ get: (id: string) => Conversation;
498
+ has: (id: string) => boolean;
499
+ join: (id: string) => Promise<void>;
500
+ leave: (id: string) => Promise<void>;
501
+ };
502
+ /**
503
+ * Get the support manager
504
+ * Exposes: sdk.support.get(conversationId, role?)
505
+ */
506
+ get support(): {
507
+ get: (id: string, role?: 'customer' | 'agent') => SupportSession;
508
+ has: (id: string) => boolean;
509
+ };
510
+ /**
511
+ * Join a conversation room (internal method used by ConversationManager)
512
+ */
513
+ private joinConversation;
514
+ /**
515
+ * Leave a conversation room (internal method used by ConversationManager)
516
+ */
517
+ private leaveConversation;
518
+ /**
519
+ * Rotate the authentication token
520
+ */
521
+ rotateToken(newToken: string): void;
522
+ /**
523
+ * Get the token manager (for advanced use cases)
524
+ */
525
+ getTokenManager(): TokenManager;
526
+ }
527
+
528
+ /**
529
+ * Manages Conversation instances and routes incoming protocol messages
530
+ */
531
+ declare class ConversationManager extends EventEmitter {
532
+ private conversations;
533
+ private sendMessageHandler?;
534
+ private sendTypingHandler?;
535
+ private supportMessageHandler?;
536
+ private logger;
537
+ private debug;
538
+ constructor(sendMessageHandler: (message: SendMessageMessage) => void, debug?: boolean, supportMessageHandler?: (conversationId: string, eventType: string, payload?: Record<string, unknown>) => void, sendTypingHandler?: (message: TypingStartMessage | TypingStopMessage) => void);
539
+ /**
540
+ * Set the support message handler (called by ChatAfrika)
541
+ */
542
+ setSupportMessageHandler(handler: (conversationId: string, eventType: string, payload?: Record<string, unknown>) => void): void;
543
+ /**
544
+ * Set the send typing handler (called by ChatAfrika)
545
+ */
546
+ setSendTypingHandler(handler: (message: TypingStartMessage | TypingStopMessage) => void): void;
547
+ /**
548
+ * Get or create a Conversation instance
549
+ * Conversations are lazy-created
550
+ */
551
+ get(conversationId: string): Conversation;
552
+ /**
553
+ * Check if a conversation instance exists
554
+ */
555
+ has(conversationId: string): boolean;
556
+ /**
557
+ * Join a conversation
558
+ * This delegates to the transport layer (ChatAfrika)
559
+ */
560
+ join(conversationId: string, joinHandler: (conversationId: string) => Promise<void>): Promise<void>;
561
+ /**
562
+ * Leave a conversation
563
+ * This delegates to the transport layer (ChatAfrika)
564
+ */
565
+ leave(conversationId: string, leaveHandler: (conversationId: string) => Promise<void>): Promise<void>;
566
+ /**
567
+ * Route an incoming protocol message to the appropriate Conversation
568
+ *
569
+ * Messages are routed through MessageManager for lifecycle handling.
570
+ * Typing events are routed through TypingManager.
571
+ * Only messages for joined conversations are processed.
572
+ */
573
+ routeIncomingMessage(message: ServerMessage): void;
574
+ /**
575
+ * Route a support protocol message to SupportManager
576
+ */
577
+ routeSupportMessage(message: SupportMessage): void;
578
+ /**
579
+ * Remove a conversation instance
580
+ */
581
+ remove(conversationId: string): void;
582
+ /**
583
+ * Get all conversation IDs
584
+ */
585
+ getAllIds(): string[];
586
+ /**
587
+ * Clear all conversations
588
+ */
589
+ clear(): void;
590
+ }
591
+
592
+ /**
593
+ * Receipt state enumeration (monotonic progression)
594
+ */
595
+ type ReceiptState$1 = 'sent' | 'delivered' | 'read';
596
+ /**
597
+ * Message model with lifecycle awareness
598
+ */
599
+ interface Message {
600
+ id: string;
601
+ conversationId: string;
602
+ content: string;
603
+ senderId?: string;
604
+ senderType?: 'user' | 'sdk' | 'bot';
605
+ messageType: 'text' | 'system';
606
+ createdAt?: Date;
607
+ optimistic: boolean;
608
+ receiptState?: ReceiptState$1;
609
+ metadata?: Record<string, unknown>;
610
+ }
611
+ /**
612
+ * Static helpers for Message creation
613
+ */
614
+ declare class MessageFactory {
615
+ /**
616
+ * Create an optimistic message (client-generated, not yet confirmed by server)
617
+ */
618
+ static createOptimistic(conversationId: string, messageId: string, content: string, messageType?: 'text' | 'system', metadata?: Record<string, unknown>): Message;
619
+ /**
620
+ * Create a message from server payload (confirmed message)
621
+ */
622
+ static fromServerPayload(conversationId: string, payload: BroadcastMessagePayload): Message;
623
+ /**
624
+ * Convert an optimistic message to confirmed (reconciliation)
625
+ */
626
+ static confirmOptimistic(optimisticMessage: Message, serverPayload: BroadcastMessagePayload): Message;
627
+ /**
628
+ * Update receipt state on a message (immutable update)
629
+ */
630
+ static updateReceiptState(message: Message, receiptState: ReceiptState$1): Message;
631
+ }
632
+
633
+ /**
634
+ * Manages message lifecycle: optimistic messages, deduplication, and reconciliation
635
+ *
636
+ * This manager does NOT store message history.
637
+ * It only tracks message IDs for deduplication and optimistic messages for reconciliation.
638
+ */
639
+ declare class MessageManager extends EventEmitter {
640
+ /**
641
+ * Track seen message IDs per conversation to prevent duplicates
642
+ * Map<conversationId, Set<message_id>>
643
+ */
644
+ private seenMessageIds;
645
+ /**
646
+ * Track optimistic messages that are waiting for server confirmation
647
+ * Map<conversationId, Map<message_id, Message>>
648
+ */
649
+ private optimisticMessages;
650
+ /**
651
+ * Track confirmed messages for receipt updates
652
+ * Map<conversationId, Map<message_id, Message>>
653
+ */
654
+ private confirmedMessages;
655
+ /**
656
+ * Receipt manager for handling receipt lifecycle
657
+ */
658
+ private receiptManager;
659
+ private logger;
660
+ constructor(debug?: boolean);
661
+ /**
662
+ * Create an optimistic message and emit it immediately
663
+ *
664
+ * @returns The created optimistic message
665
+ */
666
+ createOptimisticMessage(conversationId: string, messageId: string, content: string, messageType?: 'text' | 'system', metadata?: Record<string, unknown>): Message;
667
+ /**
668
+ * Handle an incoming message from the server
669
+ *
670
+ * This method:
671
+ * - Checks for duplicates
672
+ * - Reconciles optimistic messages if message_id matches
673
+ * - Emits confirmed messages
674
+ *
675
+ * @returns The message (reconciled if optimistic, new if not), or null if duplicate
676
+ */
677
+ handleIncomingMessage(conversationId: string, serverPayload: BroadcastMessagePayload): Message | null;
678
+ /**
679
+ * Handle a receipt update
680
+ *
681
+ * This method:
682
+ * - Routes receipt to ReceiptManager
683
+ * - Updates message receipt state if message exists
684
+ * - Emits updated message
685
+ */
686
+ handleReceipt(_conversationId: string, messageId: string, receiptState: 'delivered' | 'read', userId?: string): void;
687
+ /**
688
+ * Update message receipt state and emit updated message
689
+ */
690
+ private updateMessageReceiptState;
691
+ /**
692
+ * Reconcile an optimistic message with a server confirmation
693
+ *
694
+ * This is called when we receive a server message with the same message_id
695
+ * as an optimistic message we previously created.
696
+ *
697
+ * @returns The reconciled (confirmed) message
698
+ */
699
+ private reconcileOptimisticMessage;
700
+ /**
701
+ * Check if we've already seen a message ID for a conversation
702
+ */
703
+ hasSeenMessage(conversationId: string, messageId: string): boolean;
704
+ /**
705
+ * Track a message ID for a conversation (for deduplication)
706
+ */
707
+ private trackMessageId;
708
+ /**
709
+ * Track an optimistic message for later reconciliation
710
+ */
711
+ private trackOptimisticMessage;
712
+ /**
713
+ * Get an optimistic message by ID
714
+ */
715
+ private getOptimisticMessage;
716
+ /**
717
+ * Remove an optimistic message from tracking
718
+ */
719
+ private removeOptimisticMessage;
720
+ /**
721
+ * Track a confirmed message for receipt updates
722
+ */
723
+ private trackConfirmedMessage;
724
+ /**
725
+ * Get all message IDs for a conversation (for receipt clearing)
726
+ */
727
+ getMessageIds(conversationId: string): string[];
728
+ /**
729
+ * Clear tracked message IDs and optimistic messages for a conversation
730
+ */
731
+ clearConversation(conversationId: string): void;
732
+ /**
733
+ * Clear all tracked message IDs and optimistic messages
734
+ */
735
+ clear(): void;
736
+ }
737
+
738
+ /**
739
+ * Presence event payload
740
+ */
741
+ interface PresenceEvent {
742
+ userId: string;
743
+ state: 'online' | 'offline';
744
+ }
745
+ /**
746
+ * Manages user presence with state tracking and deduplication
747
+ *
748
+ * This manager:
749
+ * - Tracks presence state per user (SDK-global, not conversation-scoped)
750
+ * - Emits only on state changes
751
+ * - Deduplicates duplicate events
752
+ */
753
+ declare class PresenceManager extends EventEmitter {
754
+ /**
755
+ * Track current presence state per user
756
+ * Map<userId, "online" | "offline">
757
+ */
758
+ private presenceState;
759
+ private logger;
760
+ constructor(debug?: boolean);
761
+ /**
762
+ * Handle a presence event
763
+ *
764
+ * Rules:
765
+ * - Emit only on state change
766
+ * - Ignore duplicate online/online or offline/offline events
767
+ */
768
+ handlePresence(userId: string, state: 'online' | 'offline'): void;
769
+ /**
770
+ * Get current presence state for a user
771
+ */
772
+ getPresence(userId: string): 'online' | 'offline' | undefined;
773
+ /**
774
+ * Get presence state for multiple users
775
+ */
776
+ getPresences(userIds: string[]): Map<string, 'online' | 'offline'>;
777
+ /**
778
+ * Clear presence for a user
779
+ */
780
+ clearPresence(userId: string): void;
781
+ /**
782
+ * Clear all presence state
783
+ */
784
+ clear(): void;
785
+ }
786
+
787
+ /**
788
+ * Typing event payload
789
+ */
790
+ interface TypingEvent {
791
+ conversationId: string;
792
+ userId: string;
793
+ state: 'start' | 'stop';
794
+ }
795
+ /**
796
+ * Manages typing indicators with automatic lifecycle management
797
+ *
798
+ * This manager:
799
+ * - Tracks typing state per conversation per user
800
+ * - Automatically expires typing after 5 seconds
801
+ * - Prevents duplicate events
802
+ * - Emits clean, minimal events
803
+ */
804
+ declare class TypingManager extends EventEmitter {
805
+ /**
806
+ * Track active typing timers per conversation per user
807
+ * Map<conversationId, Map<userId, timeout>>
808
+ */
809
+ private typingTimers;
810
+ /**
811
+ * Track who is currently typing per conversation
812
+ * Map<conversationId, Set<userId>>
813
+ */
814
+ private activeTyping;
815
+ private logger;
816
+ private readonly TYPING_TIMEOUT_MS;
817
+ constructor(debug?: boolean);
818
+ /**
819
+ * Handle a typing_start event
820
+ *
821
+ * Rules:
822
+ * - Emit only once per user until stopped
823
+ * - Restart timeout if typing_start repeats
824
+ */
825
+ handleTypingStart(conversationId: string, userId: string): void;
826
+ /**
827
+ * Handle a typing_stop event
828
+ *
829
+ * Rules:
830
+ * - Emit only if user was typing
831
+ */
832
+ handleTypingStop(conversationId: string, userId: string): void;
833
+ /**
834
+ * Clear all typing state for a conversation
835
+ */
836
+ clearConversation(conversationId: string): void;
837
+ /**
838
+ * Clear all typing state
839
+ */
840
+ clear(): void;
841
+ /**
842
+ * Get currently typing users for a conversation
843
+ */
844
+ getTypingUsers(conversationId: string): string[];
845
+ }
846
+
847
+ /**
848
+ * Receipt state enumeration (monotonic progression)
849
+ *
850
+ * Note: This is also exported from messages/Message.ts for consistency
851
+ */
852
+ type ReceiptState = 'sent' | 'delivered' | 'read';
853
+ /**
854
+ * Receipt event payload
855
+ */
856
+ interface ReceiptEvent {
857
+ messageId: string;
858
+ state: 'delivered' | 'read';
859
+ userId?: string;
860
+ }
861
+ /**
862
+ * Manages receipt lifecycle with monotonic state transitions
863
+ *
864
+ * This manager:
865
+ * - Tracks receipt state per message
866
+ * - Enforces valid state transitions (sent → delivered → read)
867
+ * - Deduplicates receipt events
868
+ * - Emits only meaningful transitions
869
+ */
870
+ declare class ReceiptManager extends EventEmitter {
871
+ /**
872
+ * Track receipt state per message
873
+ * Map<messageId, ReceiptState>
874
+ */
875
+ private receiptState;
876
+ private logger;
877
+ constructor(debug?: boolean);
878
+ /**
879
+ * Get receipt state for a message
880
+ */
881
+ getReceiptState(messageId: string): ReceiptState | undefined;
882
+ /**
883
+ * Handle a delivered receipt
884
+ *
885
+ * Rules:
886
+ * - Only update if current state < delivered
887
+ * - Ignore duplicate or regressive updates
888
+ */
889
+ handleDelivered(messageId: string, userId?: string): boolean;
890
+ /**
891
+ * Handle a read receipt
892
+ *
893
+ * Rules:
894
+ * - Only update if current state < read
895
+ * - Ignore duplicate or regressive updates
896
+ */
897
+ handleRead(messageId: string, userId?: string): boolean;
898
+ /**
899
+ * Compare two receipt states
900
+ * Returns: -1 if a < b, 0 if a === b, 1 if a > b
901
+ */
902
+ private compareStates;
903
+ /**
904
+ * Clear receipt state for a conversation
905
+ * Note: We track by messageId, so we need to clear all messages for a conversation
906
+ * This is called when leaving a conversation
907
+ */
908
+ clearConversation(conversationId: string, messageIds: string[]): void;
909
+ /**
910
+ * Clear receipt state for a specific message
911
+ */
912
+ clearMessage(messageId: string): void;
913
+ /**
914
+ * Clear all receipt state
915
+ */
916
+ clear(): void;
917
+ }
918
+
919
+ /**
920
+ * Manages SupportSession instances
921
+ *
922
+ * This manager:
923
+ * - Creates SupportSession instances lazily
924
+ * - Tracks support sessions by conversation ID
925
+ * - Provides role-aware access to support conversations
926
+ */
927
+ declare class SupportManager extends EventEmitter {
928
+ private sessions;
929
+ private getConversationHandler;
930
+ private logger;
931
+ private debug;
932
+ constructor(getConversationHandler: (conversationId: string) => Conversation, debug?: boolean);
933
+ /**
934
+ * Get or create a SupportSession for a conversation
935
+ *
936
+ * Note: This assumes the conversation is a support conversation.
937
+ * The role is determined by the SDK user's context (not inferred).
938
+ *
939
+ * @param conversationId - The conversation ID
940
+ * @param role - The role of the current user ('customer' or 'agent')
941
+ * @returns SupportSession instance
942
+ */
943
+ get(conversationId: string, role?: SupportRole): SupportSession;
944
+ /**
945
+ * Check if a support session exists
946
+ */
947
+ has(conversationId: string): boolean;
948
+ /**
949
+ * Get a support session if it exists
950
+ */
951
+ getSession(conversationId: string): SupportSession | undefined;
952
+ /**
953
+ * Handle a support protocol event
954
+ */
955
+ handleSupportEvent(conversationId: string, eventType: 'assigned' | 'unassigned' | 'bot_takeover' | 'bot_released' | 'closed' | 'reopened', payload?: Record<string, unknown>): void;
956
+ /**
957
+ * Remove a support session
958
+ */
959
+ remove(conversationId: string): void;
960
+ /**
961
+ * Clear all support sessions
962
+ */
963
+ clear(): void;
964
+ }
965
+
966
+ /**
967
+ * Low-level WebSocket client for handling connections
968
+ */
969
+ declare class WebSocketClient extends EventEmitter {
970
+ private ws;
971
+ private url;
972
+ private token;
973
+ private reconnectAttempts;
974
+ private maxReconnectAttempts;
975
+ private autoReconnect;
976
+ private reconnectTimer;
977
+ private isManuallyDisconnected;
978
+ private isTokenExpired;
979
+ private logger;
980
+ constructor(url: string, options?: {
981
+ autoReconnect?: boolean;
982
+ maxReconnectAttempts?: number;
983
+ reconnectDelay?: number;
984
+ debug?: boolean;
985
+ });
986
+ /**
987
+ * Connect to the WebSocket server with authentication token
988
+ */
989
+ connect(token: string): Promise<void>;
990
+ /**
991
+ * Attempt a WebSocket connection
992
+ */
993
+ private attemptConnection;
994
+ /**
995
+ * Set up WebSocket event handlers
996
+ */
997
+ private setupEventHandlers;
998
+ /**
999
+ * Handle token expiration - stop reconnection attempts
1000
+ */
1001
+ private handleTokenExpiration;
1002
+ /**
1003
+ * Disconnect from the WebSocket server
1004
+ */
1005
+ disconnect(): void;
1006
+ /**
1007
+ * Send a message to the server
1008
+ */
1009
+ send(message: ClientMessage): void;
1010
+ /**
1011
+ * Check if the WebSocket is connected
1012
+ */
1013
+ isConnected(): boolean;
1014
+ /**
1015
+ * Get current reconnection attempt count
1016
+ */
1017
+ getReconnectAttempts(): number;
1018
+ /**
1019
+ * Schedule a reconnection attempt with exponential backoff
1020
+ * Backoff: 1s → 2s → 5s → 10s (max)
1021
+ */
1022
+ private scheduleReconnect;
1023
+ }
1024
+
1025
+ /**
1026
+ * Simple logger utility
1027
+ */
1028
+ declare class Logger {
1029
+ private enabled;
1030
+ constructor(enabled?: boolean);
1031
+ setEnabled(enabled: boolean): void;
1032
+ debug(...args: unknown[]): void;
1033
+ info(...args: unknown[]): void;
1034
+ warn(...args: unknown[]): void;
1035
+ error(...args: unknown[]): void;
1036
+ }
1037
+
1038
+ /**
1039
+ * Retry utility for async operations
1040
+ */
1041
+ interface RetryOptions {
1042
+ maxAttempts?: number;
1043
+ delay?: number;
1044
+ backoff?: boolean;
1045
+ onRetry?: (attempt: number, error: Error) => void;
1046
+ }
1047
+ /**
1048
+ * Retry an async function with exponential backoff
1049
+ */
1050
+ declare function retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
1051
+
1052
+ /**
1053
+ * Type guards and validation utilities
1054
+ */
1055
+ /**
1056
+ * Check if a value is a non-empty string
1057
+ */
1058
+ declare function isNonEmptyString(value: unknown): value is string;
1059
+ /**
1060
+ * Check if a value is a valid URL string
1061
+ */
1062
+ declare function isValidUrl(value: unknown): value is string;
1063
+ /**
1064
+ * Check if a value is a number
1065
+ */
1066
+ declare function isNumber(value: unknown): value is number;
1067
+ /**
1068
+ * Check if a value is a positive integer
1069
+ */
1070
+ declare function isPositiveInteger(value: unknown): value is number;
1071
+
1072
+ export { type BroadcastMessagePayload, ChatAfrika, type ClientConfig, type ClientMessage, type ClientMessageType, ConnectionState, Conversation, ConversationManager, type ErrorMessage, type JoinMessage, type JoinedMessage, type LeaveMessage, Logger, type Message, MessageFactory, MessageManager, type MessagePayload, type PresenceEvent, PresenceManager, type PresenceMessage, type PresencePayload, type ReceiptEvent, ReceiptManager, type ReceiptMessage, type ReceiptPayload, type ReceiptState$1 as ReceiptState, type ReceivedMessageMessage, type RetryOptions, type SendMessageMessage, type ServerMessage, type ServerMessageType, type SupportEventPayload, type SupportEventType, SupportManager, type SupportMessage, type SupportRole, SupportSession, type SupportStatus, TokenManager, type TypingEvent, TypingManager, type TypingMessage, type TypingPayload, type TypingStartMessage, type TypingStopMessage, WebSocketClient, isNonEmptyString, isNumber, isPositiveInteger, isValidUrl, retry };