chatly-sdk 0.0.4 → 0.0.6

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 (56) hide show
  1. package/CONTRIBUTING.md +658 -0
  2. package/IMPROVEMENTS.md +402 -0
  3. package/README.md +1539 -162
  4. package/dist/index.d.ts +430 -9
  5. package/dist/index.js +1420 -63
  6. package/examples/01-basic-chat/README.md +61 -0
  7. package/examples/01-basic-chat/index.js +58 -0
  8. package/examples/01-basic-chat/package.json +13 -0
  9. package/examples/02-group-chat/README.md +78 -0
  10. package/examples/02-group-chat/index.js +76 -0
  11. package/examples/02-group-chat/package.json +13 -0
  12. package/examples/03-offline-messaging/README.md +73 -0
  13. package/examples/03-offline-messaging/index.js +80 -0
  14. package/examples/03-offline-messaging/package.json +13 -0
  15. package/examples/04-live-chat/README.md +80 -0
  16. package/examples/04-live-chat/index.js +114 -0
  17. package/examples/04-live-chat/package.json +13 -0
  18. package/examples/05-hybrid-messaging/README.md +71 -0
  19. package/examples/05-hybrid-messaging/index.js +106 -0
  20. package/examples/05-hybrid-messaging/package.json +13 -0
  21. package/examples/06-postgresql-integration/README.md +101 -0
  22. package/examples/06-postgresql-integration/adapters/groupStore.js +73 -0
  23. package/examples/06-postgresql-integration/adapters/messageStore.js +47 -0
  24. package/examples/06-postgresql-integration/adapters/userStore.js +40 -0
  25. package/examples/06-postgresql-integration/index.js +92 -0
  26. package/examples/06-postgresql-integration/package.json +14 -0
  27. package/examples/06-postgresql-integration/schema.sql +58 -0
  28. package/examples/08-customer-support/README.md +70 -0
  29. package/examples/08-customer-support/index.js +104 -0
  30. package/examples/08-customer-support/package.json +13 -0
  31. package/examples/README.md +105 -0
  32. package/jest.config.cjs +28 -0
  33. package/package.json +12 -8
  34. package/src/chat/ChatSession.ts +81 -0
  35. package/src/chat/GroupSession.ts +79 -0
  36. package/src/constants.ts +61 -0
  37. package/src/crypto/e2e.ts +0 -20
  38. package/src/index.ts +525 -63
  39. package/src/models/mediaTypes.ts +58 -0
  40. package/src/models/message.ts +4 -1
  41. package/src/transport/adapters.ts +51 -1
  42. package/src/transport/memoryTransport.ts +75 -13
  43. package/src/transport/websocketClient.ts +269 -21
  44. package/src/transport/websocketServer.ts +26 -26
  45. package/src/utils/errors.ts +97 -0
  46. package/src/utils/logger.ts +96 -0
  47. package/src/utils/mediaUtils.ts +235 -0
  48. package/src/utils/messageQueue.ts +162 -0
  49. package/src/utils/validation.ts +99 -0
  50. package/test/crypto.test.ts +122 -35
  51. package/test/sdk.test.ts +276 -0
  52. package/test/validation.test.ts +64 -0
  53. package/tsconfig.json +11 -10
  54. package/tsconfig.test.json +11 -0
  55. package/src/ChatManager.ts +0 -103
  56. package/src/crypto/keyManager.ts +0 -28
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { EventEmitter } from 'events';
2
+
1
3
  interface User {
2
4
  id: string;
3
5
  username: string;
@@ -9,7 +11,55 @@ interface StoredUser extends User {
9
11
  createdAt: number;
10
12
  }
11
13
 
12
- type MessageType = "text" | "system";
14
+ /**
15
+ * Media types supported by the SDK
16
+ */
17
+ declare enum MediaType {
18
+ IMAGE = "image",
19
+ AUDIO = "audio",
20
+ VIDEO = "video",
21
+ DOCUMENT = "document"
22
+ }
23
+ /**
24
+ * Media metadata
25
+ */
26
+ interface MediaMetadata {
27
+ filename: string;
28
+ mimeType: string;
29
+ size: number;
30
+ width?: number;
31
+ height?: number;
32
+ duration?: number;
33
+ thumbnail?: string;
34
+ }
35
+ /**
36
+ * Media attachment
37
+ */
38
+ interface MediaAttachment {
39
+ type: MediaType;
40
+ data: string;
41
+ metadata: MediaMetadata;
42
+ }
43
+ /**
44
+ * Supported MIME types
45
+ */
46
+ declare const SUPPORTED_MIME_TYPES: {
47
+ image: string[];
48
+ audio: string[];
49
+ video: string[];
50
+ document: string[];
51
+ };
52
+ /**
53
+ * File size limits (in bytes)
54
+ */
55
+ declare const FILE_SIZE_LIMITS: {
56
+ image: number;
57
+ audio: number;
58
+ video: number;
59
+ document: number;
60
+ };
61
+
62
+ type MessageType = "text" | "media" | "system";
13
63
  interface Message {
14
64
  id: string;
15
65
  senderId: string;
@@ -19,6 +69,7 @@ interface Message {
19
69
  iv: string;
20
70
  timestamp: number;
21
71
  type: MessageType;
72
+ media?: MediaAttachment;
22
73
  }
23
74
 
24
75
  interface Group {
@@ -45,10 +96,99 @@ interface GroupStoreAdapter {
45
96
  list(): Promise<Group[]>;
46
97
  }
47
98
 
99
+ /**
100
+ * SDK Configuration Constants
101
+ */
102
+ declare const SUPPORTED_CURVE = "prime256v1";
103
+ declare const ALGORITHM = "aes-256-gcm";
104
+ declare const IV_LENGTH = 12;
105
+ declare const SALT_LENGTH = 16;
106
+ declare const KEY_LENGTH = 32;
107
+ declare const TAG_LENGTH = 16;
108
+ declare const PBKDF2_ITERATIONS = 100000;
109
+ declare const USERNAME_MIN_LENGTH = 3;
110
+ declare const USERNAME_MAX_LENGTH = 20;
111
+ declare const MESSAGE_MAX_LENGTH = 10000;
112
+ declare const GROUP_NAME_MAX_LENGTH = 100;
113
+ declare const GROUP_MIN_MEMBERS = 2;
114
+ declare const GROUP_MAX_MEMBERS = 256;
115
+ declare const RECONNECT_MAX_ATTEMPTS = 5;
116
+ declare const RECONNECT_BASE_DELAY = 1000;
117
+ declare const RECONNECT_MAX_DELAY = 30000;
118
+ declare const HEARTBEAT_INTERVAL = 30000;
119
+ declare const CONNECTION_TIMEOUT = 10000;
120
+ declare const MAX_QUEUE_SIZE = 1000;
121
+ declare const MESSAGE_RETRY_ATTEMPTS = 3;
122
+ declare const MESSAGE_RETRY_DELAY = 2000;
123
+ declare const EVENTS: {
124
+ readonly MESSAGE_SENT: "message:sent";
125
+ readonly MESSAGE_RECEIVED: "message:received";
126
+ readonly MESSAGE_FAILED: "message:failed";
127
+ readonly CONNECTION_STATE_CHANGED: "connection:state";
128
+ readonly SESSION_CREATED: "session:created";
129
+ readonly GROUP_CREATED: "group:created";
130
+ readonly ERROR: "error";
131
+ readonly USER_CREATED: "user:created";
132
+ };
133
+ declare enum ConnectionState {
134
+ DISCONNECTED = "disconnected",
135
+ CONNECTING = "connecting",
136
+ CONNECTED = "connected",
137
+ RECONNECTING = "reconnecting",
138
+ FAILED = "failed"
139
+ }
140
+ declare enum MessageStatus {
141
+ PENDING = "pending",
142
+ SENT = "sent",
143
+ DELIVERED = "delivered",
144
+ FAILED = "failed"
145
+ }
146
+
147
+ /**
148
+ * Transport adapter interface for network communication
149
+ */
48
150
  interface TransportAdapter {
151
+ /**
152
+ * Connect to the transport
153
+ * @param userId - User ID to connect as
154
+ */
49
155
  connect(userId: string): Promise<void>;
156
+ /**
157
+ * Disconnect from the transport
158
+ */
159
+ disconnect(): Promise<void>;
160
+ /**
161
+ * Reconnect to the transport
162
+ */
163
+ reconnect(): Promise<void>;
164
+ /**
165
+ * Send a message
166
+ * @param message - Message to send
167
+ */
50
168
  send(message: Message): Promise<void>;
169
+ /**
170
+ * Register a message handler
171
+ * @param handler - Function to call when a message is received
172
+ */
51
173
  onMessage(handler: (message: Message) => void): void;
174
+ /**
175
+ * Register a connection state change handler
176
+ * @param handler - Function to call when connection state changes
177
+ */
178
+ onConnectionStateChange?(handler: (state: ConnectionState) => void): void;
179
+ /**
180
+ * Register an error handler
181
+ * @param handler - Function to call when an error occurs
182
+ */
183
+ onError?(handler: (error: Error) => void): void;
184
+ /**
185
+ * Get the current connection state
186
+ */
187
+ getConnectionState(): ConnectionState;
188
+ /**
189
+ * Check if transport is connected
190
+ */
191
+ isConnected(): boolean;
52
192
  }
53
193
 
54
194
  declare class ChatSession {
@@ -71,10 +211,21 @@ declare class ChatSession {
71
211
  * Encrypt a message for this session
72
212
  */
73
213
  encrypt(plaintext: string, senderId: string): Promise<Message>;
214
+ /**
215
+ * Encrypt a media message for this session
216
+ */
217
+ encryptMedia(plaintext: string, media: MediaAttachment, senderId: string): Promise<Message>;
74
218
  /**
75
219
  * Decrypt a message in this session
76
220
  */
77
221
  decrypt(message: Message, user: User): Promise<string>;
222
+ /**
223
+ * Decrypt a media message in this session
224
+ */
225
+ decryptMedia(message: Message, user: User): Promise<{
226
+ text: string;
227
+ media: MediaAttachment;
228
+ }>;
78
229
  }
79
230
 
80
231
  declare class GroupSession {
@@ -89,11 +240,57 @@ declare class GroupSession {
89
240
  * Encrypt a message for this group
90
241
  */
91
242
  encrypt(plaintext: string, senderId: string): Promise<Message>;
243
+ /**
244
+ * Encrypt a media message for this group
245
+ */
246
+ encryptMedia(plaintext: string, media: MediaAttachment, senderId: string): Promise<Message>;
92
247
  /**
93
248
  * Decrypt a message in this group
94
249
  */
95
250
  decrypt(message: Message): Promise<string>;
251
+ /**
252
+ * Decrypt a media message in this group
253
+ */
254
+ decryptMedia(message: Message): Promise<{
255
+ text: string;
256
+ media: MediaAttachment;
257
+ }>;
258
+ }
259
+
260
+ /**
261
+ * Log levels
262
+ */
263
+ declare enum LogLevel {
264
+ DEBUG = 0,
265
+ INFO = 1,
266
+ WARN = 2,
267
+ ERROR = 3,
268
+ NONE = 4
269
+ }
270
+ /**
271
+ * Logger configuration
272
+ */
273
+ interface LoggerConfig {
274
+ level: LogLevel;
275
+ prefix?: string;
276
+ timestamp?: boolean;
277
+ }
278
+ /**
279
+ * Simple structured logger
280
+ */
281
+ declare class Logger {
282
+ private config;
283
+ constructor(config?: Partial<LoggerConfig>);
284
+ private shouldLog;
285
+ private formatMessage;
286
+ debug(message: string, data?: unknown): void;
287
+ info(message: string, data?: unknown): void;
288
+ warn(message: string, data?: unknown): void;
289
+ error(message: string, error?: Error | unknown): void;
290
+ setLevel(level: LogLevel): void;
291
+ getLevel(): LogLevel;
96
292
  }
293
+ declare const logger: Logger;
97
294
 
98
295
  declare class InMemoryUserStore implements UserStoreAdapter {
99
296
  private users;
@@ -117,28 +314,201 @@ declare class InMemoryGroupStore implements GroupStoreAdapter {
117
314
  list(): Promise<Group[]>;
118
315
  }
119
316
 
120
- type MessageHandler = (message: Message) => void;
317
+ /**
318
+ * In-memory transport for testing (no actual network communication)
319
+ */
121
320
  declare class InMemoryTransport implements TransportAdapter {
122
- private handler?;
123
- private connected;
124
- connect(_userId: string): Promise<void>;
321
+ private messageHandler;
322
+ private connectionState;
323
+ private stateHandler;
324
+ private errorHandler;
325
+ connect(userId: string): Promise<void>;
326
+ disconnect(): Promise<void>;
327
+ reconnect(): Promise<void>;
328
+ send(message: Message): Promise<void>;
329
+ onMessage(handler: (message: Message) => void): void;
330
+ onConnectionStateChange(handler: (state: ConnectionState) => void): void;
331
+ onError(handler: (error: Error) => void): void;
332
+ getConnectionState(): ConnectionState;
333
+ isConnected(): boolean;
334
+ simulateReceive(message: Message): void;
335
+ simulateError(error: Error): void;
336
+ }
337
+
338
+ declare class WebSocketClient implements TransportAdapter {
339
+ private ws;
340
+ private url;
341
+ private messageHandler;
342
+ private stateHandler;
343
+ private errorHandler;
344
+ private connectionState;
345
+ private reconnectAttempts;
346
+ private reconnectTimer;
347
+ private heartbeatTimer;
348
+ private currentUserId;
349
+ private shouldReconnect;
350
+ constructor(url: string);
351
+ connect(userId: string): Promise<void>;
352
+ private doConnect;
353
+ disconnect(): Promise<void>;
354
+ reconnect(): Promise<void>;
355
+ private scheduleReconnect;
356
+ private clearReconnectTimer;
357
+ private startHeartbeat;
358
+ private stopHeartbeat;
125
359
  send(message: Message): Promise<void>;
126
- onMessage(handler: MessageHandler): void;
360
+ onMessage(handler: (message: Message) => void): void;
361
+ onConnectionStateChange(handler: (state: ConnectionState) => void): void;
362
+ onError(handler: (error: Error) => void): void;
363
+ getConnectionState(): ConnectionState;
364
+ isConnected(): boolean;
365
+ private updateState;
366
+ private handleError;
367
+ }
368
+
369
+ /**
370
+ * Base SDK Error class
371
+ */
372
+ declare class SDKError extends Error {
373
+ readonly code: string;
374
+ readonly retryable: boolean;
375
+ readonly details?: Record<string, unknown> | undefined;
376
+ constructor(message: string, code: string, retryable?: boolean, details?: Record<string, unknown> | undefined);
377
+ toJSON(): {
378
+ name: string;
379
+ message: string;
380
+ code: string;
381
+ retryable: boolean;
382
+ details: Record<string, unknown> | undefined;
383
+ };
127
384
  }
385
+ /**
386
+ * Network-related errors (connection, timeout, etc.)
387
+ */
388
+ declare class NetworkError extends SDKError {
389
+ constructor(message: string, details?: Record<string, unknown>);
390
+ }
391
+ /**
392
+ * Encryption/Decryption errors
393
+ */
394
+ declare class EncryptionError extends SDKError {
395
+ constructor(message: string, details?: Record<string, unknown>);
396
+ }
397
+ /**
398
+ * Authentication/Authorization errors
399
+ */
400
+ declare class AuthError extends SDKError {
401
+ constructor(message: string, details?: Record<string, unknown>);
402
+ }
403
+ /**
404
+ * Validation errors (invalid input)
405
+ */
406
+ declare class ValidationError extends SDKError {
407
+ constructor(message: string, details?: Record<string, unknown>);
408
+ }
409
+ /**
410
+ * Storage-related errors
411
+ */
412
+ declare class StorageError extends SDKError {
413
+ constructor(message: string, retryable?: boolean, details?: Record<string, unknown>);
414
+ }
415
+ /**
416
+ * Session-related errors
417
+ */
418
+ declare class SessionError extends SDKError {
419
+ constructor(message: string, details?: Record<string, unknown>);
420
+ }
421
+ /**
422
+ * Transport-related errors
423
+ */
424
+ declare class TransportError extends SDKError {
425
+ constructor(message: string, retryable?: boolean, details?: Record<string, unknown>);
426
+ }
427
+ /**
428
+ * Configuration errors
429
+ */
430
+ declare class ConfigError extends SDKError {
431
+ constructor(message: string, details?: Record<string, unknown>);
432
+ }
433
+
434
+ /**
435
+ * Validate username format
436
+ */
437
+ declare function validateUsername(username: string): void;
438
+ /**
439
+ * Validate message content
440
+ */
441
+ declare function validateMessage(message: string): void;
442
+ /**
443
+ * Validate group name
444
+ */
445
+ declare function validateGroupName(name: string): void;
446
+ /**
447
+ * Validate group members count
448
+ */
449
+ declare function validateGroupMembers(memberCount: number): void;
450
+ /**
451
+ * Validate user ID format
452
+ */
453
+ declare function validateUserId(userId: string): void;
454
+
455
+ /**
456
+ * Convert File or Blob to base64 string
457
+ */
458
+ declare function encodeFileToBase64(file: File | Blob): Promise<string>;
459
+ /**
460
+ * Convert base64 string to Blob
461
+ */
462
+ declare function decodeBase64ToBlob(base64: string, mimeType: string): Blob;
463
+ /**
464
+ * Detect media type from MIME type
465
+ */
466
+ declare function getMediaType(mimeType: string): MediaType;
467
+ /**
468
+ * Validate media file
469
+ */
470
+ declare function validateMediaFile(file: File | Blob, filename?: string): void;
471
+ /**
472
+ * Create media metadata from file
473
+ */
474
+ declare function createMediaMetadata(file: File | Blob, filename?: string): Promise<MediaMetadata>;
475
+ /**
476
+ * Create media attachment from file
477
+ */
478
+ declare function createMediaAttachment(file: File | Blob, filename?: string): Promise<MediaAttachment>;
479
+ /**
480
+ * Format file size for display
481
+ */
482
+ declare function formatFileSize(bytes: number): string;
128
483
 
129
484
  interface ChatSDKConfig {
130
485
  userStore: UserStoreAdapter;
131
486
  messageStore: MessageStoreAdapter;
132
487
  groupStore: GroupStoreAdapter;
133
488
  transport?: TransportAdapter;
489
+ logLevel?: LogLevel;
134
490
  }
135
491
  /**
136
492
  * Main ChatSDK class - production-ready WhatsApp-style chat SDK
493
+ * Extends EventEmitter to provide event-driven architecture
494
+ *
495
+ * Events:
496
+ * - message:sent - Emitted when a message is sent
497
+ * - message:received - Emitted when a message is received
498
+ * - message:failed - Emitted when a message fails to send
499
+ * - connection:state - Emitted when connection state changes
500
+ * - session:created - Emitted when a session is created
501
+ * - group:created - Emitted when a group is created
502
+ * - user:created - Emitted when a user is created
503
+ * - error - Emitted when an error occurs
137
504
  */
138
- declare class ChatSDK {
505
+ declare class ChatSDK extends EventEmitter {
139
506
  private config;
140
507
  private currentUser;
508
+ private messageQueue;
141
509
  constructor(config: ChatSDKConfig);
510
+ private setupTransportHandlers;
511
+ private processMessageQueue;
142
512
  /**
143
513
  * Create a new user with generated identity keys
144
514
  */
@@ -150,7 +520,7 @@ declare class ChatSDK {
150
520
  /**
151
521
  * Set the current active user
152
522
  */
153
- setCurrentUser(user: User): void;
523
+ setCurrentUser(user: User): Promise<void>;
154
524
  /**
155
525
  * Get the current active user
156
526
  */
@@ -171,10 +541,21 @@ declare class ChatSDK {
171
541
  * Send a message in a chat session (1:1 or group)
172
542
  */
173
543
  sendMessage(session: ChatSession | GroupSession, plaintext: string): Promise<Message>;
544
+ /**
545
+ * Send a media message in a chat session (1:1 or group)
546
+ */
547
+ sendMediaMessage(session: ChatSession | GroupSession, caption: string, media: MediaAttachment): Promise<Message>;
174
548
  /**
175
549
  * Decrypt a message
176
550
  */
177
551
  decryptMessage(message: Message, user: User): Promise<string>;
552
+ /**
553
+ * Decrypt a media message
554
+ */
555
+ decryptMediaMessage(message: Message, user: User): Promise<{
556
+ text: string;
557
+ media: MediaAttachment;
558
+ }>;
178
559
  /**
179
560
  * Get messages for a user
180
561
  */
@@ -183,6 +564,46 @@ declare class ChatSDK {
183
564
  * Get messages for a group
184
565
  */
185
566
  getMessagesForGroup(groupId: string): Promise<Message[]>;
567
+ /**
568
+ * Get the transport adapter
569
+ */
570
+ getTransport(): TransportAdapter | undefined;
571
+ /**
572
+ * Get all users
573
+ */
574
+ listUsers(): Promise<User[]>;
575
+ /**
576
+ * Get user by ID
577
+ */
578
+ getUserById(userId: string): Promise<User | undefined>;
579
+ /**
580
+ * Get all groups
581
+ */
582
+ listGroups(): Promise<Group[]>;
583
+ /**
584
+ * Get connection state
585
+ */
586
+ getConnectionState(): ConnectionState;
587
+ /**
588
+ * Check if connected
589
+ */
590
+ isConnected(): boolean;
591
+ /**
592
+ * Disconnect transport
593
+ */
594
+ disconnect(): Promise<void>;
595
+ /**
596
+ * Reconnect transport
597
+ */
598
+ reconnect(): Promise<void>;
599
+ /**
600
+ * Get message queue status
601
+ */
602
+ getQueueStatus(): {
603
+ size: number;
604
+ pending: number;
605
+ retryable: number;
606
+ };
186
607
  }
187
608
 
188
- export { ChatSDK, type ChatSDKConfig, ChatSession, type Group, GroupSession, type GroupStoreAdapter, InMemoryGroupStore, InMemoryMessageStore, InMemoryTransport, InMemoryUserStore, type Message, type MessageStoreAdapter, type MessageType, type StoredUser, type TransportAdapter, type User, type UserStoreAdapter };
609
+ export { ALGORITHM, AuthError, CONNECTION_TIMEOUT, ChatSDK, type ChatSDKConfig, ChatSession, ConfigError, ConnectionState, EVENTS, EncryptionError, FILE_SIZE_LIMITS, GROUP_MAX_MEMBERS, GROUP_MIN_MEMBERS, GROUP_NAME_MAX_LENGTH, type Group, GroupSession, type GroupStoreAdapter, HEARTBEAT_INTERVAL, IV_LENGTH, InMemoryGroupStore, InMemoryMessageStore, InMemoryTransport, InMemoryUserStore, KEY_LENGTH, LogLevel, Logger, type LoggerConfig, MAX_QUEUE_SIZE, MESSAGE_MAX_LENGTH, MESSAGE_RETRY_ATTEMPTS, MESSAGE_RETRY_DELAY, type MediaAttachment, type MediaMetadata, MediaType, type Message, MessageStatus, type MessageStoreAdapter, type MessageType, NetworkError, PBKDF2_ITERATIONS, RECONNECT_BASE_DELAY, RECONNECT_MAX_ATTEMPTS, RECONNECT_MAX_DELAY, SALT_LENGTH, SDKError, SUPPORTED_CURVE, SUPPORTED_MIME_TYPES, SessionError, StorageError, type StoredUser, TAG_LENGTH, type TransportAdapter, TransportError, USERNAME_MAX_LENGTH, USERNAME_MIN_LENGTH, type User, type UserStoreAdapter, ValidationError, WebSocketClient, createMediaAttachment, createMediaMetadata, decodeBase64ToBlob, encodeFileToBase64, formatFileSize, getMediaType, logger, validateGroupMembers, validateGroupName, validateMediaFile, validateMessage, validateUserId, validateUsername };