chatly-sdk 1.0.0 โ†’ 2.0.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.
Files changed (63) hide show
  1. package/CONTRIBUTING.md +658 -0
  2. package/IMPROVEMENTS.md +402 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1576 -162
  5. package/dist/index.d.ts +502 -11
  6. package/dist/index.js +1619 -66
  7. package/examples/01-basic-chat/README.md +61 -0
  8. package/examples/01-basic-chat/index.js +58 -0
  9. package/examples/01-basic-chat/package.json +13 -0
  10. package/examples/02-group-chat/README.md +78 -0
  11. package/examples/02-group-chat/index.js +76 -0
  12. package/examples/02-group-chat/package.json +13 -0
  13. package/examples/03-offline-messaging/README.md +73 -0
  14. package/examples/03-offline-messaging/index.js +80 -0
  15. package/examples/03-offline-messaging/package.json +13 -0
  16. package/examples/04-live-chat/README.md +80 -0
  17. package/examples/04-live-chat/index.js +114 -0
  18. package/examples/04-live-chat/package.json +13 -0
  19. package/examples/05-hybrid-messaging/README.md +71 -0
  20. package/examples/05-hybrid-messaging/index.js +106 -0
  21. package/examples/05-hybrid-messaging/package.json +13 -0
  22. package/examples/06-postgresql-integration/README.md +101 -0
  23. package/examples/06-postgresql-integration/adapters/groupStore.js +73 -0
  24. package/examples/06-postgresql-integration/adapters/messageStore.js +47 -0
  25. package/examples/06-postgresql-integration/adapters/userStore.js +40 -0
  26. package/examples/06-postgresql-integration/index.js +92 -0
  27. package/examples/06-postgresql-integration/package.json +14 -0
  28. package/examples/06-postgresql-integration/schema.sql +58 -0
  29. package/examples/08-customer-support/README.md +70 -0
  30. package/examples/08-customer-support/index.js +104 -0
  31. package/examples/08-customer-support/package.json +13 -0
  32. package/examples/README.md +105 -0
  33. package/jest.config.cjs +28 -0
  34. package/package.json +15 -6
  35. package/src/chat/ChatSession.ts +160 -3
  36. package/src/chat/GroupSession.ts +108 -1
  37. package/src/constants.ts +61 -0
  38. package/src/crypto/e2e.ts +9 -20
  39. package/src/crypto/utils.ts +3 -1
  40. package/src/index.ts +530 -63
  41. package/src/models/mediaTypes.ts +62 -0
  42. package/src/models/message.ts +4 -1
  43. package/src/storage/adapters.ts +36 -0
  44. package/src/storage/localStorage.ts +49 -0
  45. package/src/storage/s3Storage.ts +84 -0
  46. package/src/stores/adapters.ts +2 -0
  47. package/src/stores/memory/messageStore.ts +8 -0
  48. package/src/transport/adapters.ts +51 -1
  49. package/src/transport/memoryTransport.ts +75 -13
  50. package/src/transport/websocketClient.ts +269 -21
  51. package/src/transport/websocketServer.ts +26 -26
  52. package/src/utils/errors.ts +97 -0
  53. package/src/utils/logger.ts +96 -0
  54. package/src/utils/mediaUtils.ts +235 -0
  55. package/src/utils/messageQueue.ts +162 -0
  56. package/src/utils/validation.ts +99 -0
  57. package/test/crypto.test.ts +122 -35
  58. package/test/sdk.test.ts +276 -0
  59. package/test/validation.test.ts +64 -0
  60. package/tsconfig.json +11 -10
  61. package/tsconfig.test.json +11 -0
  62. package/src/ChatManager.ts +0 -103
  63. package/src/crypto/keyManager.ts +0 -28
package/README.md CHANGED
@@ -1,74 +1,203 @@
1
- # chatly-sdk
1
+ # ๐Ÿ” Chatly SDK
2
2
 
3
- Production-ready end-to-end encrypted chat SDK with WhatsApp-style features.
3
+ Production-ready end-to-end encrypted chat SDK with WhatsApp-style features, event-driven architecture, and automatic reconnection.
4
4
 
5
- ## Features
5
+ You can find the sample project repository here: [Chatly SDK Sample Project](https://github.com/bharath-arch/chatly-sdk-demo.git)
6
6
 
7
- - ๐Ÿ” **End-to-End Encryption**
8
- - ECDH key exchange (P-256 curve)
9
- - AES-GCM message encryption
10
- - Per-user identity keys
11
- - Per-session ephemeral keys
12
- - Group shared keys
13
7
 
14
- - ๐Ÿ’ฌ **1:1 Messaging**
15
- - Secure key exchange
16
- - Encrypt/decrypt functions
17
- - Message payload schemas
8
+ [![npm version](https://img.shields.io/npm/v/chatly-sdk.svg)](https://www.npmjs.com/package/chatly-sdk)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
18
10
 
19
- - ๐Ÿ‘ฅ **Group Messaging**
20
- - Create groups
21
- - Add/remove members
22
- - Per-group shared key
23
- - Group message encryption
24
- - Message ordering & timestamps
11
+ ## โœจ Features
25
12
 
26
- - ๐Ÿ—„๏ธ **Database Integration**
27
- - Adapter pattern for flexible storage
28
- - In-memory implementations included
29
- - UserStoreAdapter, MessageStoreAdapter, GroupStoreAdapter
13
+ ### ๐Ÿ” Security
14
+ - **End-to-End Encryption** - ECDH (P-256) + AES-256-GCM
15
+ - **Per-User Identity Keys** - Unique cryptographic identity
16
+ - **Session-Based Encryption** - Secure 1:1 and group messaging
17
+ - **Input Validation** - Protection against injection attacks
18
+ - **Configurable Media Storage** - Offload encrypted files to S3 or Local storage
30
19
 
31
- - ๐ŸŒ **Networking Layer**
32
- - Transport adapter interface
33
- - In-memory transport for testing
34
- - Easy integration with your own WebSocket server
20
+ ### ๐Ÿ’ฌ Messaging
21
+ - **1:1 Chat** - Secure direct messaging
22
+ - **Group Chat** - Multi-user encrypted groups (2-256 members)
23
+ - **Message Queue** - Offline support with automatic retry
24
+ - **Delivery Tracking** - Message status (pending, sent, failed)
35
25
 
36
- ## Installation
26
+ ### ๐ŸŒ Connectivity
27
+ - **Auto-Reconnection** - Exponential backoff (up to 5 attempts)
28
+ - **Heartbeat Monitoring** - Connection health checks
29
+ - **Connection States** - Disconnected, connecting, connected, reconnecting, failed
30
+ - **Event-Driven** - Real-time events for all state changes
31
+
32
+ ### ๐Ÿ› ๏ธ Developer Experience
33
+ - **TypeScript First** - Full type safety
34
+ - **Event Emitter** - React to SDK events
35
+ - **Adapter Pattern** - Flexible storage and transport
36
+ - **Comprehensive Tests** - 40+ test cases
37
+ - **Structured Logging** - Configurable log levels
38
+
39
+ ---
40
+
41
+ ## ๐ŸŽฏ What Makes This SDK Production-Ready?
42
+
43
+ ### 1. **Message Queue with Automatic Retry**
44
+ - Offline message support with persistent queue
45
+ - Configurable retry attempts (default: 3)
46
+ - Exponential backoff for failed messages
47
+ - Queue size management (default: 1000 messages)
48
+ - Message status tracking (pending, sent, failed)
49
+
50
+ ### 2. **Event-Driven Architecture**
51
+ - Real-time event emissions for all state changes
52
+ - Extends Node.js `EventEmitter` for familiar API
53
+ - Events for messages, connections, users, groups, and errors
54
+ - Easy integration with React, Vue, or any framework
55
+
56
+ ### 3. **Robust Connection Management**
57
+ - WebSocket support with automatic reconnection
58
+ - Exponential backoff strategy (up to 5 attempts)
59
+ - Heartbeat/ping-pong for connection health monitoring
60
+ - Connection state tracking (disconnected, connecting, connected, reconnecting, failed)
61
+ - Graceful degradation and error recovery
62
+
63
+ ### 4. **Flexible Storage Adapters**
64
+ - Adapter pattern for any database (PostgreSQL, MySQL, MongoDB, Redis, etc.)
65
+ - In-memory stores for development and testing
66
+ - Easy migration from in-memory to production database
67
+ - Support for caching layers
68
+
69
+ ### 5. **Enterprise-Grade Security**
70
+ - End-to-end encryption using ECDH (P-256) + AES-256-GCM
71
+ - Per-user cryptographic identity keys
72
+ - Session-based encryption for 1:1 and group chats
73
+ - Input validation to prevent injection attacks
74
+ - Secure key derivation and storage patterns
75
+
76
+ ### 6. **Developer Experience**
77
+ - Full TypeScript support with comprehensive types
78
+ - Detailed error classes for better error handling
79
+ - Structured logging with configurable levels
80
+ - Extensive documentation and examples
81
+ - React hooks and context providers included
82
+
83
+
84
+ ---
85
+
86
+ ## ๐Ÿ“ฆ Installation
37
87
 
38
88
  ```bash
39
89
  npm install chatly-sdk
40
90
  ```
41
91
 
42
- ## Quick Start
92
+ ---
93
+
94
+ ## ๐Ÿ—๏ธ Architecture
95
+
96
+ ### System Overview
97
+
98
+ ```
99
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
100
+ โ”‚ ChatSDK โ”‚
101
+ โ”‚ (EventEmitter) โ”‚
102
+ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
103
+ โ”‚ โ”‚ ChatSession โ”‚ โ”‚ GroupSession โ”‚ โ”‚ Message Queueโ”‚ โ”‚
104
+ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
105
+ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
106
+ โ”‚ โ”‚ Crypto (E2E) โ”‚ โ”‚ Validation โ”‚ โ”‚ Logger โ”‚ โ”‚
107
+ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
108
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
109
+ โ”‚
110
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
111
+ โ”‚ โ”‚ โ”‚
112
+ โ”Œโ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”
113
+ โ”‚ User โ”‚ โ”‚ Message โ”‚ โ”‚ Group โ”‚
114
+ โ”‚ Store โ”‚ โ”‚ Store โ”‚ โ”‚ Store โ”‚
115
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
116
+ โ”‚
117
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”
118
+ โ”‚ Transport โ”‚
119
+ โ”‚ (WebSocketโ”‚
120
+ โ”‚ /Memory) โ”‚
121
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
122
+ ```
123
+
124
+ ### Message Flow (1:1 Chat)
125
+
126
+ ```
127
+ Alice SDK Bob
128
+ โ”‚ โ”‚ โ”‚
129
+ โ”œโ”€ sendMessage() โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ โ”‚
130
+ โ”‚ โ”œโ”€ encrypt(ECDH+AES) โ”€โ”€โ–ถโ”‚
131
+ โ”‚ โ”œโ”€ store message โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚
132
+ โ”‚ โ”œโ”€ queue if offline โ”€โ”€โ”€โ–ถโ”‚
133
+ โ”‚ โ”œโ”€ send via transport โ”€โ–ถโ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ WebSocket
134
+ โ”‚ โ”‚ โ”‚
135
+ โ”‚ โ”‚โ—€โ”€โ”€โ”€โ”€ receive โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”คโ—€โ”€โ”€โ”€โ”€โ”€โ”€ WebSocket
136
+ โ”‚ โ”œโ”€ emit MESSAGE_RECEIVEDโ”‚
137
+ โ”‚ โ”œโ”€ store message โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚
138
+ โ”‚ โ”‚ โ”‚
139
+ โ”‚ โ”‚ โ”œโ”€ decryptMessage()
140
+ โ”‚ โ”‚โ—€โ”€ decrypt(ECDH+AES) โ”€โ”ค
141
+ โ”‚ โ”‚ โ”‚
142
+ โ”‚ โ”œโ”€ "Hello Bob!" โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚
143
+ ```
144
+
145
+ ### Connection Lifecycle
146
+
147
+ ```
148
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
149
+ โ”‚ DISCONNECTED โ”‚
150
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
151
+ โ”‚ connect()
152
+ โ–ผ
153
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” timeout/error โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
154
+ โ”‚ CONNECTING โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ FAILED โ”‚
155
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
156
+ โ”‚ onopen
157
+ โ–ผ
158
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” onclose โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
159
+ โ”‚ CONNECTED โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ RECONNECTING โ”‚
160
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
161
+ โ”‚ โ”‚
162
+ โ”‚ heartbeat (30s) โ”‚ exponential
163
+ โ”‚ ping/pong โ”‚ backoff
164
+ โ”‚ โ”‚
165
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
166
+ ```
167
+
168
+ ---
169
+
170
+ ## ๐Ÿš€ Quick Start
43
171
 
44
172
  ### Basic Setup
45
173
 
46
174
  ```typescript
47
- import { ChatSDK, InMemoryUserStore, InMemoryMessageStore, InMemoryGroupStore } from 'chatly-sdk';
48
-
175
+ import {
176
+ ChatSDK,
177
+ InMemoryUserStore,
178
+ InMemoryMessageStore,
179
+ InMemoryGroupStore,
180
+ LogLevel
181
+ } from 'chatly-sdk';
182
+
183
+ // Initialize SDK
49
184
  const sdk = new ChatSDK({
50
185
  userStore: new InMemoryUserStore(),
51
186
  messageStore: new InMemoryMessageStore(),
52
187
  groupStore: new InMemoryGroupStore(),
188
+ storageProvider: new LocalStorageProvider('./uploads'), // Optional: S3 or Local
189
+ logLevel: LogLevel.INFO, // Optional: DEBUG, INFO, WARN, ERROR, NONE
53
190
  });
54
191
 
55
192
  // Create a user
56
- const user = await sdk.createUser('alice');
57
- sdk.setCurrentUser(user);
193
+ const alice = await sdk.createUser('alice');
194
+ sdk.setCurrentUser(alice);
58
195
  ```
59
196
 
60
197
  ### 1:1 Chat Example
61
198
 
62
199
  ```typescript
63
- import { ChatSDK, InMemoryUserStore, InMemoryMessageStore, InMemoryGroupStore } from 'chatly-sdk';
64
-
65
- const sdk = new ChatSDK({
66
- userStore: new InMemoryUserStore(),
67
- messageStore: new InMemoryMessageStore(),
68
- groupStore: new InMemoryGroupStore(),
69
- });
70
-
71
- // Create two users
200
+ // Create users
72
201
  const alice = await sdk.createUser('alice');
73
202
  const bob = await sdk.createUser('bob');
74
203
 
@@ -78,13 +207,14 @@ const session = await sdk.startSession(alice, bob);
78
207
 
79
208
  // Send a message
80
209
  const message = await sdk.sendMessage(session, 'Hello Bob!');
210
+ console.log('Message sent:', message.id);
81
211
 
82
212
  // Bob receives and decrypts
83
213
  sdk.setCurrentUser(bob);
84
214
  const messages = await sdk.getMessagesForUser(bob.id);
85
215
  for (const msg of messages) {
86
- const decrypted = await sdk.decryptMessage(msg, bob);
87
- console.log(decrypted); // "Hello Bob!"
216
+ const plaintext = await sdk.decryptMessage(msg, bob);
217
+ console.log('Received:', plaintext); // "Hello Bob!"
88
218
  }
89
219
  ```
90
220
 
@@ -97,94 +227,429 @@ const bob = await sdk.createUser('bob');
97
227
  const charlie = await sdk.createUser('charlie');
98
228
 
99
229
  // Create a group
100
- const group = await sdk.createGroup('Project Team', [alice, bob, charlie]);
230
+ const group = await sdk.createGroup('Team Chat', [alice, bob, charlie]);
101
231
 
102
- // Send a group message
232
+ // Alice sends a message
103
233
  sdk.setCurrentUser(alice);
104
- const message = await sdk.sendMessage(group, 'Hello team!');
234
+ await sdk.sendMessage(group, 'Hello team!');
105
235
 
106
- // Members can read the message
236
+ // Bob and Charlie can decrypt
107
237
  sdk.setCurrentUser(bob);
108
- const groupMessages = await sdk.getMessagesForGroup(group.group.id);
109
- for (const msg of groupMessages) {
110
- const decrypted = await sdk.decryptMessage(msg, bob);
111
- console.log(decrypted);
238
+ const messages = await sdk.getMessagesForGroup(group.group.id);
239
+ for (const msg of messages) {
240
+ const plaintext = await sdk.decryptMessage(msg, bob);
241
+ console.log('Bob received:', plaintext);
112
242
  }
113
243
  ```
114
244
 
115
- ### Save and Load User
245
+ ### Media Sharing Example
116
246
 
117
247
  ```typescript
118
- // Create and save user
119
- const user = await sdk.createUser('john_doe');
120
- const storedUser = {
121
- ...user,
122
- createdAt: Date.now(),
123
- };
124
- await sdk.config.userStore.save(storedUser);
248
+ import { createMediaAttachment } from 'chatly-sdk';
249
+
250
+ // Create users and session
251
+ const alice = await sdk.createUser('alice');
252
+ const bob = await sdk.createUser('bob');
253
+ const session = await sdk.startSession(alice, bob);
125
254
 
126
- // Later, load user
127
- const loadedUser = await sdk.importUser(storedUser);
128
- sdk.setCurrentUser(loadedUser);
255
+ // Send an image
256
+ sdk.setCurrentUser(alice);
257
+ const imageFile = new File([imageBlob], 'photo.jpg', { type: 'image/jpeg' });
258
+ const imageMedia = await createMediaAttachment(imageFile);
259
+ await sdk.sendMediaMessage(session, 'Check out this photo!', imageMedia);
260
+
261
+ // Bob receives and decrypts
262
+ sdk.setCurrentUser(bob);
263
+ const messages = await sdk.getMessagesForUser(bob.id);
264
+ for (const msg of messages) {
265
+ if (msg.type === 'media' && msg.media) {
266
+ const { text, media } = await sdk.decryptMediaMessage(msg, bob);
267
+ console.log('Caption:', text);
268
+ console.log('Media type:', media.type);
269
+ console.log('Filename:', media.metadata.filename);
270
+ console.log('Size:', media.metadata.size);
271
+
272
+ // Convert back to file
273
+ const blob = decodeBase64ToBlob(media.data, media.metadata.mimeType);
274
+ // Use blob as needed (display, download, etc.)
275
+ }
276
+ }
129
277
  ```
130
278
 
131
- ## API Reference
279
+ ---
132
280
 
133
- ### ChatSDK
281
+ ## ๐Ÿ“ Media Sharing
134
282
 
135
- Main SDK class for managing chat functionality.
283
+ Send encrypted images, audio, video, and documents with full end-to-end encryption.
136
284
 
137
- #### Constructor
285
+ ### Supported Media Types
286
+
287
+ | Type | Formats | Max Size |
288
+ |------|---------|----------|
289
+ | **Images** | JPEG, PNG, GIF, WebP | 10 MB |
290
+ | **Audio** | MP3, MP4, OGG, WAV, WebM | 16 MB |
291
+ | **Video** | MP4, WebM, OGG | 100 MB |
292
+ | **Documents** | PDF, DOC, DOCX, XLS, XLSX, TXT | 100 MB |
293
+
294
+ ### Sending Media
138
295
 
139
296
  ```typescript
140
- new ChatSDK(config: {
141
- userStore: UserStoreAdapter;
142
- messageStore: MessageStoreAdapter;
143
- groupStore: GroupStoreAdapter;
144
- transport?: TransportAdapter;
145
- })
297
+ import { createMediaAttachment, MediaType } from 'chatly-sdk';
298
+
299
+ // From a File object
300
+ const file = new File([blob], 'document.pdf', { type: 'application/pdf' });
301
+ const media = await createMediaAttachment(file);
302
+
303
+ // Send in 1:1 chat
304
+ await sdk.sendMediaMessage(session, 'Here is the document', media);
305
+
306
+ // Send in group chat
307
+ await sdk.sendMediaMessage(groupSession, 'Team photo!', media);
146
308
  ```
147
309
 
148
- #### Methods
310
+ ### Receiving Media
311
+
312
+ ```typescript
313
+ // Get messages
314
+ const messages = await sdk.getMessagesForUser(userId);
315
+
316
+ // Check for media messages
317
+ for (const msg of messages) {
318
+ if (msg.type === 'media' && msg.media) {
319
+ // Decrypt media message
320
+ const { text, media } = await sdk.decryptMediaMessage(msg, currentUser);
321
+
322
+ // Access media data
323
+ console.log('Caption:', text);
324
+ console.log('Type:', media.type); // 'image', 'audio', 'video', 'document'
325
+ console.log('Filename:', media.metadata.filename);
326
+ console.log('Size:', media.metadata.size);
327
+ console.log('MIME type:', media.metadata.mimeType);
328
+
329
+ // For images/videos
330
+ if (media.metadata.width) {
331
+ console.log('Dimensions:', media.metadata.width, 'x', media.metadata.height);
332
+ }
333
+
334
+ // Thumbnail (for images/videos)
335
+ if (media.metadata.thumbnail) {
336
+ const thumbnailBlob = decodeBase64ToBlob(
337
+ media.metadata.thumbnail,
338
+ 'image/jpeg'
339
+ );
340
+ }
341
+
342
+ // Convert to Blob for use
343
+ const blob = decodeBase64ToBlob(media.data, media.metadata.mimeType);
344
+ const url = URL.createObjectURL(blob);
345
+ // Use URL for display, download, etc.
346
+ }
347
+ }
348
+ ```
349
+
350
+ ### Media Utilities
351
+
352
+ ```typescript
353
+ import {
354
+ createMediaAttachment,
355
+ encodeFileToBase64,
356
+ decodeBase64ToBlob,
357
+ validateMediaFile,
358
+ formatFileSize,
359
+ MediaType,
360
+ SUPPORTED_MIME_TYPES,
361
+ FILE_SIZE_LIMITS
362
+ } from 'chatly-sdk';
363
+
364
+ // Validate before sending
365
+ try {
366
+ validateMediaFile(file);
367
+ console.log('File is valid');
368
+ } catch (error) {
369
+ console.error('Invalid file:', error.message);
370
+ }
371
+
372
+ // Manual encoding/decoding
373
+ const base64 = await encodeFileToBase64(file);
374
+ const blob = decodeBase64ToBlob(base64, 'image/jpeg');
375
+
376
+ // Format file size
377
+ const sizeStr = formatFileSize(1024 * 1024); // "1.0 MB"
378
+
379
+ // Check supported types
380
+ console.log('Supported image types:', SUPPORTED_MIME_TYPES.image);
381
+ console.log('Max video size:', FILE_SIZE_LIMITS.video); // 100 MB
382
+ ```
383
+
384
+ ### ๐Ÿ“ฆ Media Storage & Offloading
385
+
386
+ To prevent database bloat, large media files are automatically offloaded to a storage provider. The SDK encrypts the file locally, then uploads the ciphertext.
387
+
388
+ #### Local Storage
389
+ Store files on the server's local filesystem:
390
+
391
+ ```typescript
392
+ import { LocalStorageProvider } from 'chatly-sdk';
393
+
394
+ const storage = new LocalStorageProvider('./uploads');
395
+ const sdk = new ChatSDK({ storageProvider: storage, ... });
396
+ ```
397
+
398
+ #### AWS S3 Storage
399
+ Store files in an S3 bucket:
400
+
401
+ ```typescript
402
+ import { S3StorageProvider } from 'chatly-sdk';
403
+
404
+ const storage = new S3StorageProvider({
405
+ region: 'us-east-1',
406
+ bucket: 'my-chat-app-media',
407
+ credentials: {
408
+ accessKeyId: '...',
409
+ secretAccessKey: '...'
410
+ }
411
+ });
412
+ const sdk = new ChatSDK({ storageProvider: storage, ... });
413
+ ```
414
+
415
+ ### Media Encryption
416
+
417
+ All media files are **fully encrypted end-to-end**:
418
+
419
+ 1. **Double Encryption**: File data and captions are encrypted separately with unique IVs.
420
+ 2. **Offloading**: Encrypted data is sent to the `StorageProvider`, leaving only a small reference in the database.
421
+ 3. **Automatic Management**: The SDK automatically downloads and decrypts media when needed.
422
+ 4. **Cleanup**: Deleting a message automatically triggers deletion of the remote file.
423
+
424
+ ```typescript
425
+ // Message in database now looks like this:
426
+ {
427
+ "id": "msg_123",
428
+ "type": "media",
429
+ "media": {
430
+ "storage": "s3",
431
+ "storageKey": "session_id/unique-file-name.jpg",
432
+ "iv": "media_specific_iv",
433
+ "data": undefined, // Raw data is removed from DB
434
+ "metadata": { ... }
435
+ }
436
+ }
437
+ ```
438
+
439
+ ### Example: Sending an Image
440
+
441
+ ```typescript
442
+ // Browser environment
443
+ const input = document.querySelector('input[type="file"]');
444
+ const file = input.files[0];
149
445
 
150
- - `createUser(username: string): Promise<User>` - Create a new user with generated keys
151
- - `importUser(userData: StoredUser): Promise<User>` - Import an existing user
152
- - `setCurrentUser(user: User): void` - Set the active user
153
- - `getCurrentUser(): User | null` - Get the current user
154
- - `startSession(userA: User, userB: User): Promise<ChatSession>` - Start a 1:1 chat
155
- - `createGroup(name: string, members: User[]): Promise<GroupSession>` - Create a group
156
- - `loadGroup(id: string): Promise<GroupSession>` - Load an existing group
157
- - `sendMessage(session: ChatSession | GroupSession, plaintext: string): Promise<Message>` - Send a message
158
- - `decryptMessage(message: Message, user: User): Promise<string>` - Decrypt a message
159
- - `getMessagesForUser(userId: string): Promise<Message[]>` - Get user's messages
160
- - `getMessagesForGroup(groupId: string): Promise<Message[]>` - Get group messages
446
+ // Create media attachment (validates, encodes, generates thumbnail)
447
+ const media = await createMediaAttachment(file);
161
448
 
162
- ### Adapters
449
+ // Send with caption
450
+ await sdk.sendMediaMessage(session, 'Check this out!', media);
451
+ ```
163
452
 
164
- #### UserStoreAdapter
453
+ ### Example: Displaying Received Images
165
454
 
166
455
  ```typescript
456
+ // Get and decrypt media message
457
+ const { text, media } = await sdk.decryptMediaMessage(message, currentUser);
458
+
459
+ // Create blob and display
460
+ const blob = decodeBase64ToBlob(media.data, media.metadata.mimeType);
461
+ const url = URL.createObjectURL(blob);
462
+
463
+ // Show image
464
+ const img = document.createElement('img');
465
+ img.src = url;
466
+ document.body.appendChild(img);
467
+
468
+ // Show thumbnail first (faster)
469
+ if (media.metadata.thumbnail) {
470
+ const thumbBlob = decodeBase64ToBlob(media.metadata.thumbnail, 'image/jpeg');
471
+ const thumbUrl = URL.createObjectURL(thumbBlob);
472
+ img.src = thumbUrl; // Show thumbnail
473
+
474
+ // Load full image
475
+ img.onload = () => {
476
+ URL.revokeObjectURL(thumbUrl);
477
+ img.src = url; // Replace with full image
478
+ };
479
+ }
480
+ ```
481
+
482
+ ---
483
+
484
+ ## ๐ŸŽฏ Event-Driven Architecture
485
+
486
+
487
+ The SDK extends `EventEmitter` and emits events for all state changes:
488
+
489
+ ```typescript
490
+ import { EVENTS, ConnectionState } from 'chatly-sdk';
491
+
492
+ // Message events
493
+ sdk.on(EVENTS.MESSAGE_SENT, (message) => {
494
+ console.log('โœ… Message sent:', message.id);
495
+ updateUI('sent', message);
496
+ });
497
+
498
+ sdk.on(EVENTS.MESSAGE_RECEIVED, (message) => {
499
+ console.log('๐Ÿ“จ Message received:', message.id);
500
+ notifyUser(message);
501
+ });
502
+
503
+ sdk.on(EVENTS.MESSAGE_FAILED, (message, error) => {
504
+ console.error('โŒ Message failed:', message.id, error);
505
+ showRetryButton(message);
506
+ });
507
+
508
+ // Connection events
509
+ sdk.on(EVENTS.CONNECTION_STATE_CHANGED, (state) => {
510
+ switch (state) {
511
+ case ConnectionState.CONNECTED:
512
+ console.log('๐ŸŸข Connected');
513
+ break;
514
+ case ConnectionState.RECONNECTING:
515
+ console.log('๐ŸŸก Reconnecting...');
516
+ break;
517
+ case ConnectionState.DISCONNECTED:
518
+ console.log('๐Ÿ”ด Disconnected');
519
+ break;
520
+ case ConnectionState.FAILED:
521
+ console.log('๐Ÿ’ฅ Connection failed');
522
+ break;
523
+ }
524
+ });
525
+
526
+ // User and group events
527
+ sdk.on(EVENTS.USER_CREATED, (user) => {
528
+ console.log('๐Ÿ‘ค User created:', user.username);
529
+ });
530
+
531
+ sdk.on(EVENTS.SESSION_CREATED, (session) => {
532
+ console.log('๐Ÿ’ฌ Session created:', session.id);
533
+ });
534
+
535
+ sdk.on(EVENTS.GROUP_CREATED, (group) => {
536
+ console.log('๐Ÿ‘ฅ Group created:', group.group.name);
537
+ });
538
+
539
+ // Error handling
540
+ sdk.on(EVENTS.ERROR, (error) => {
541
+ console.error('โš ๏ธ SDK error:', error);
542
+ if (error.retryable) {
543
+ // Retry the operation
544
+ }
545
+ });
546
+ ```
547
+
548
+ ---
549
+
550
+ ## ๐Ÿ”Œ WebSocket Integration
551
+
552
+ ### Client-Side Setup
553
+
554
+ ```typescript
555
+ import { ChatSDK, WebSocketClient } from 'chatly-sdk';
556
+
557
+ // Create WebSocket transport
558
+ const transport = new WebSocketClient('wss://your-server.com/ws');
559
+
560
+ const sdk = new ChatSDK({
561
+ userStore: new InMemoryUserStore(),
562
+ messageStore: new InMemoryMessageStore(),
563
+ groupStore: new InMemoryGroupStore(),
564
+ transport, // Add transport
565
+ });
566
+
567
+ // Set current user (automatically connects WebSocket)
568
+ await sdk.setCurrentUser(user);
569
+
570
+ // Listen for connection state
571
+ sdk.on(EVENTS.CONNECTION_STATE_CHANGED, (state) => {
572
+ console.log('Connection:', state);
573
+ });
574
+
575
+ // Receive messages in real-time
576
+ sdk.on(EVENTS.MESSAGE_RECEIVED, async (message) => {
577
+ const plaintext = await sdk.decryptMessage(message, currentUser);
578
+ displayMessage(plaintext);
579
+ });
580
+ ```
581
+
582
+ ### Server-Side Setup (Node.js)
583
+
584
+ ```javascript
585
+ const WebSocket = require('ws');
586
+ const wss = new WebSocket.Server({ port: 8080 });
587
+
588
+ const clients = new Map(); // userId -> WebSocket
589
+
590
+ wss.on('connection', (ws, req) => {
591
+ const userId = new URL(req.url, 'ws://localhost').searchParams.get('userId');
592
+
593
+ if (!userId) {
594
+ ws.close(4001, 'Missing userId');
595
+ return;
596
+ }
597
+
598
+ clients.set(userId, ws);
599
+ console.log(`User ${userId} connected`);
600
+
601
+ // Handle ping/pong
602
+ ws.on('message', (data) => {
603
+ const message = JSON.parse(data.toString());
604
+
605
+ if (message.type === 'ping') {
606
+ ws.send(JSON.stringify({ type: 'pong' }));
607
+ return;
608
+ }
609
+
610
+ // Forward message to recipient
611
+ const recipientId = message.receiverId || message.groupId;
612
+ const recipientWs = clients.get(recipientId);
613
+
614
+ if (recipientWs && recipientWs.readyState === WebSocket.OPEN) {
615
+ recipientWs.send(JSON.stringify(message));
616
+ }
617
+ });
618
+
619
+ ws.on('close', () => {
620
+ clients.delete(userId);
621
+ console.log(`User ${userId} disconnected`);
622
+ });
623
+ });
624
+ ```
625
+
626
+ ---
627
+
628
+ ## ๐Ÿ—„๏ธ Database Integration
629
+
630
+ The SDK uses the **Adapter Pattern** to support any database. You can implement custom storage adapters for your preferred database.
631
+
632
+ ### Storage Adapter Interfaces
633
+
634
+ The SDK defines three adapter interfaces:
635
+
636
+ ```typescript
637
+ // User storage
167
638
  interface UserStoreAdapter {
168
639
  create(user: User): Promise<User>;
169
640
  findById(id: string): Promise<User | undefined>;
170
641
  save(user: StoredUser): Promise<void>;
171
642
  list(): Promise<User[]>;
172
643
  }
173
- ```
174
644
 
175
- #### MessageStoreAdapter
176
-
177
- ```typescript
645
+ // Message storage
178
646
  interface MessageStoreAdapter {
179
647
  create(message: Message): Promise<Message>;
180
648
  listByUser(userId: string): Promise<Message[]>;
181
649
  listByGroup(groupId: string): Promise<Message[]>;
182
650
  }
183
- ```
184
651
 
185
- #### GroupStoreAdapter
186
-
187
- ```typescript
652
+ // Group storage
188
653
  interface GroupStoreAdapter {
189
654
  create(group: Group): Promise<Group>;
190
655
  findById(id: string): Promise<Group | undefined>;
@@ -192,121 +657,1070 @@ interface GroupStoreAdapter {
192
657
  }
193
658
  ```
194
659
 
195
- #### TransportAdapter
660
+ ---
661
+
662
+ ### PostgreSQL Implementation
663
+
664
+ #### Database Schema
665
+
666
+ ```sql
667
+ -- Users table
668
+ CREATE TABLE users (
669
+ id VARCHAR(255) PRIMARY KEY,
670
+ username VARCHAR(50) NOT NULL UNIQUE,
671
+ public_key TEXT NOT NULL,
672
+ private_key TEXT NOT NULL,
673
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
674
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
675
+ );
676
+
677
+ -- Messages table
678
+ CREATE TABLE messages (
679
+ id VARCHAR(255) PRIMARY KEY,
680
+ sender_id VARCHAR(255) NOT NULL REFERENCES users(id),
681
+ receiver_id VARCHAR(255) REFERENCES users(id),
682
+ group_id VARCHAR(255),
683
+ ciphertext TEXT NOT NULL,
684
+ iv VARCHAR(255) NOT NULL,
685
+ timestamp BIGINT NOT NULL,
686
+ status VARCHAR(20) DEFAULT 'pending',
687
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
688
+ INDEX idx_receiver (receiver_id),
689
+ INDEX idx_group (group_id),
690
+ INDEX idx_timestamp (timestamp)
691
+ );
692
+
693
+ -- Groups table
694
+ CREATE TABLE groups (
695
+ id VARCHAR(255) PRIMARY KEY,
696
+ name VARCHAR(100) NOT NULL,
697
+ shared_secret TEXT NOT NULL,
698
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
699
+ );
700
+
701
+ -- Group members table
702
+ CREATE TABLE group_members (
703
+ group_id VARCHAR(255) NOT NULL REFERENCES groups(id) ON DELETE CASCADE,
704
+ user_id VARCHAR(255) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
705
+ joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
706
+ PRIMARY KEY (group_id, user_id)
707
+ );
708
+ ```
709
+
710
+ #### Adapter Implementation
196
711
 
197
712
  ```typescript
198
- interface TransportAdapter {
199
- connect(userId: string): Promise<void>;
200
- send(message: Message): Promise<void>;
201
- onMessage(handler: (message: Message) => void): void;
713
+ import { Pool } from 'pg';
714
+ import { UserStoreAdapter, MessageStoreAdapter, GroupStoreAdapter } from 'chatly-sdk';
715
+ import type { User, StoredUser, Message, Group } from 'chatly-sdk';
716
+
717
+ // PostgreSQL User Store
718
+ export class PostgreSQLUserStore implements UserStoreAdapter {
719
+ constructor(private pool: Pool) {}
720
+
721
+ async create(user: User): Promise<User> {
722
+ await this.pool.query(
723
+ `INSERT INTO users (id, username, public_key, private_key)
724
+ VALUES ($1, $2, $3, $4)`,
725
+ [user.id, user.username, user.publicKey, user.privateKey]
726
+ );
727
+ return user;
728
+ }
729
+
730
+ async findById(id: string): Promise<User | undefined> {
731
+ const result = await this.pool.query(
732
+ 'SELECT id, username, public_key as "publicKey", private_key as "privateKey" FROM users WHERE id = $1',
733
+ [id]
734
+ );
735
+ return result.rows[0];
736
+ }
737
+
738
+ async save(user: StoredUser): Promise<void> {
739
+ await this.pool.query(
740
+ `UPDATE users
741
+ SET username = $1, public_key = $2, updated_at = CURRENT_TIMESTAMP
742
+ WHERE id = $3`,
743
+ [user.username, user.publicKey, user.id]
744
+ );
745
+ }
746
+
747
+ async list(): Promise<User[]> {
748
+ const result = await this.pool.query(
749
+ 'SELECT id, username, public_key as "publicKey", private_key as "privateKey" FROM users'
750
+ );
751
+ return result.rows;
752
+ }
753
+ }
754
+
755
+ // PostgreSQL Message Store
756
+ export class PostgreSQLMessageStore implements MessageStoreAdapter {
757
+ constructor(private pool: Pool) {}
758
+
759
+ async create(message: Message): Promise<Message> {
760
+ await this.pool.query(
761
+ `INSERT INTO messages (id, sender_id, receiver_id, group_id, ciphertext, iv, timestamp, status)
762
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
763
+ [
764
+ message.id,
765
+ message.senderId,
766
+ message.receiverId || null,
767
+ message.groupId || null,
768
+ message.ciphertext,
769
+ message.iv,
770
+ message.timestamp,
771
+ message.status || 'pending'
772
+ ]
773
+ );
774
+ return message;
775
+ }
776
+
777
+ async listByUser(userId: string): Promise<Message[]> {
778
+ const result = await this.pool.query(
779
+ `SELECT id, sender_id as "senderId", receiver_id as "receiverId",
780
+ group_id as "groupId", ciphertext, iv, timestamp, status
781
+ FROM messages
782
+ WHERE receiver_id = $1 OR sender_id = $1
783
+ ORDER BY timestamp ASC`,
784
+ [userId]
785
+ );
786
+ return result.rows;
787
+ }
788
+
789
+ async listByGroup(groupId: string): Promise<Message[]> {
790
+ const result = await this.pool.query(
791
+ `SELECT id, sender_id as "senderId", receiver_id as "receiverId",
792
+ group_id as "groupId", ciphertext, iv, timestamp, status
793
+ FROM messages
794
+ WHERE group_id = $1
795
+ ORDER BY timestamp ASC`,
796
+ [groupId]
797
+ );
798
+ return result.rows;
799
+ }
202
800
  }
801
+
802
+ // PostgreSQL Group Store
803
+ export class PostgreSQLGroupStore implements GroupStoreAdapter {
804
+ constructor(private pool: Pool) {}
805
+
806
+ async create(group: Group): Promise<Group> {
807
+ const client = await this.pool.connect();
808
+ try {
809
+ await client.query('BEGIN');
810
+
811
+ // Insert group
812
+ await client.query(
813
+ 'INSERT INTO groups (id, name, shared_secret) VALUES ($1, $2, $3)',
814
+ [group.id, group.name, group.sharedSecret]
815
+ );
816
+
817
+ // Insert members
818
+ for (const userId of group.members) {
819
+ await client.query(
820
+ 'INSERT INTO group_members (group_id, user_id) VALUES ($1, $2)',
821
+ [group.id, userId]
822
+ );
823
+ }
824
+
825
+ await client.query('COMMIT');
826
+ return group;
827
+ } catch (error) {
828
+ await client.query('ROLLBACK');
829
+ throw error;
830
+ } finally {
831
+ client.release();
832
+ }
833
+ }
834
+
835
+ async findById(id: string): Promise<Group | undefined> {
836
+ const groupResult = await this.pool.query(
837
+ 'SELECT id, name, shared_secret as "sharedSecret" FROM groups WHERE id = $1',
838
+ [id]
839
+ );
840
+
841
+ if (groupResult.rows.length === 0) return undefined;
842
+
843
+ const membersResult = await this.pool.query(
844
+ 'SELECT user_id FROM group_members WHERE group_id = $1',
845
+ [id]
846
+ );
847
+
848
+ return {
849
+ ...groupResult.rows[0],
850
+ members: membersResult.rows.map(row => row.user_id)
851
+ };
852
+ }
853
+
854
+ async list(): Promise<Group[]> {
855
+ const groupsResult = await this.pool.query(
856
+ 'SELECT id, name, shared_secret as "sharedSecret" FROM groups'
857
+ );
858
+
859
+ const groups: Group[] = [];
860
+ for (const group of groupsResult.rows) {
861
+ const membersResult = await this.pool.query(
862
+ 'SELECT user_id FROM group_members WHERE group_id = $1',
863
+ [group.id]
864
+ );
865
+ groups.push({
866
+ ...group,
867
+ members: membersResult.rows.map(row => row.user_id)
868
+ });
869
+ }
870
+
871
+ return groups;
872
+ }
873
+ }
874
+
875
+ // Usage
876
+ import { Pool } from 'pg';
877
+ import { ChatSDK } from 'chatly-sdk';
878
+
879
+ const pool = new Pool({
880
+ host: 'localhost',
881
+ port: 5432,
882
+ database: 'chatly',
883
+ user: 'your_user',
884
+ password: 'your_password',
885
+ });
886
+
887
+ const sdk = new ChatSDK({
888
+ userStore: new PostgreSQLUserStore(pool),
889
+ messageStore: new PostgreSQLMessageStore(pool),
890
+ groupStore: new PostgreSQLGroupStore(pool),
891
+ });
203
892
  ```
204
893
 
205
- ## Extending the SDK
894
+ ---
206
895
 
207
- ### Custom Store Adapters
896
+ ### MongoDB Implementation
208
897
 
209
- Implement the adapter interfaces to use your own database:
898
+ #### Adapter Implementation
210
899
 
211
900
  ```typescript
212
- import { UserStoreAdapter, User } from 'chatly-sdk';
901
+ import { Collection, MongoClient } from 'mongodb';
902
+ import { UserStoreAdapter, MessageStoreAdapter, GroupStoreAdapter } from 'chatly-sdk';
903
+ import type { User, StoredUser, Message, Group } from 'chatly-sdk';
904
+
905
+ // MongoDB User Store
906
+ export class MongoDBUserStore implements UserStoreAdapter {
907
+ constructor(private collection: Collection) {}
213
908
 
214
- class PostgreSQLUserStore implements UserStoreAdapter {
215
909
  async create(user: User): Promise<User> {
216
- // Save to PostgreSQL
217
- const result = await db.query('INSERT INTO users ...');
218
- return result.rows[0];
910
+ await this.collection.insertOne({
911
+ _id: user.id,
912
+ username: user.username,
913
+ publicKey: user.publicKey,
914
+ privateKey: user.privateKey,
915
+ createdAt: new Date(),
916
+ });
917
+ return user;
219
918
  }
220
-
919
+
221
920
  async findById(id: string): Promise<User | undefined> {
222
- const result = await db.query('SELECT * FROM users WHERE id = $1', [id]);
223
- return result.rows[0];
921
+ const doc = await this.collection.findOne({ _id: id });
922
+ if (!doc) return undefined;
923
+
924
+ return {
925
+ id: doc._id,
926
+ username: doc.username,
927
+ publicKey: doc.publicKey,
928
+ privateKey: doc.privateKey,
929
+ };
930
+ }
931
+
932
+ async save(user: StoredUser): Promise<void> {
933
+ await this.collection.updateOne(
934
+ { _id: user.id },
935
+ {
936
+ $set: {
937
+ username: user.username,
938
+ publicKey: user.publicKey,
939
+ updatedAt: new Date()
940
+ }
941
+ }
942
+ );
943
+ }
944
+
945
+ async list(): Promise<User[]> {
946
+ const docs = await this.collection.find({}).toArray();
947
+ return docs.map(doc => ({
948
+ id: doc._id,
949
+ username: doc.username,
950
+ publicKey: doc.publicKey,
951
+ privateKey: doc.privateKey,
952
+ }));
224
953
  }
225
-
226
- // ... implement other methods
227
954
  }
955
+
956
+ // MongoDB Message Store
957
+ export class MongoDBMessageStore implements MessageStoreAdapter {
958
+ constructor(private collection: Collection) {}
959
+
960
+ async create(message: Message): Promise<Message> {
961
+ await this.collection.insertOne({
962
+ _id: message.id,
963
+ senderId: message.senderId,
964
+ receiverId: message.receiverId,
965
+ groupId: message.groupId,
966
+ ciphertext: message.ciphertext,
967
+ iv: message.iv,
968
+ timestamp: message.timestamp,
969
+ status: message.status || 'pending',
970
+ createdAt: new Date(),
971
+ });
972
+ return message;
973
+ }
974
+
975
+ async listByUser(userId: string): Promise<Message[]> {
976
+ const docs = await this.collection
977
+ .find({
978
+ $or: [{ receiverId: userId }, { senderId: userId }]
979
+ })
980
+ .sort({ timestamp: 1 })
981
+ .toArray();
982
+
983
+ return docs.map(doc => ({
984
+ id: doc._id,
985
+ senderId: doc.senderId,
986
+ receiverId: doc.receiverId,
987
+ groupId: doc.groupId,
988
+ ciphertext: doc.ciphertext,
989
+ iv: doc.iv,
990
+ timestamp: doc.timestamp,
991
+ status: doc.status,
992
+ }));
993
+ }
994
+
995
+ async listByGroup(groupId: string): Promise<Message[]> {
996
+ const docs = await this.collection
997
+ .find({ groupId })
998
+ .sort({ timestamp: 1 })
999
+ .toArray();
1000
+
1001
+ return docs.map(doc => ({
1002
+ id: doc._id,
1003
+ senderId: doc.senderId,
1004
+ receiverId: doc.receiverId,
1005
+ groupId: doc.groupId,
1006
+ ciphertext: doc.ciphertext,
1007
+ iv: doc.iv,
1008
+ timestamp: doc.timestamp,
1009
+ status: doc.status,
1010
+ }));
1011
+ }
1012
+ }
1013
+
1014
+ // MongoDB Group Store
1015
+ export class MongoDBGroupStore implements GroupStoreAdapter {
1016
+ constructor(private collection: Collection) {}
1017
+
1018
+ async create(group: Group): Promise<Group> {
1019
+ await this.collection.insertOne({
1020
+ _id: group.id,
1021
+ name: group.name,
1022
+ sharedSecret: group.sharedSecret,
1023
+ members: group.members,
1024
+ createdAt: new Date(),
1025
+ });
1026
+ return group;
1027
+ }
1028
+
1029
+ async findById(id: string): Promise<Group | undefined> {
1030
+ const doc = await this.collection.findOne({ _id: id });
1031
+ if (!doc) return undefined;
1032
+
1033
+ return {
1034
+ id: doc._id,
1035
+ name: doc.name,
1036
+ sharedSecret: doc.sharedSecret,
1037
+ members: doc.members,
1038
+ };
1039
+ }
1040
+
1041
+ async list(): Promise<Group[]> {
1042
+ const docs = await this.collection.find({}).toArray();
1043
+ return docs.map(doc => ({
1044
+ id: doc._id,
1045
+ name: doc.name,
1046
+ sharedSecret: doc.sharedSecret,
1047
+ members: doc.members,
1048
+ }));
1049
+ }
1050
+ }
1051
+
1052
+ // Usage
1053
+ import { MongoClient } from 'mongodb';
1054
+ import { ChatSDK } from 'chatly-sdk';
1055
+
1056
+ const client = new MongoClient('mongodb://localhost:27017');
1057
+ await client.connect();
1058
+ const db = client.db('chatly');
1059
+
1060
+ // Create indexes for better performance
1061
+ await db.collection('messages').createIndex({ receiverId: 1, timestamp: 1 });
1062
+ await db.collection('messages').createIndex({ groupId: 1, timestamp: 1 });
1063
+ await db.collection('users').createIndex({ username: 1 }, { unique: true });
1064
+
1065
+ const sdk = new ChatSDK({
1066
+ userStore: new MongoDBUserStore(db.collection('users')),
1067
+ messageStore: new MongoDBMessageStore(db.collection('messages')),
1068
+ groupStore: new MongoDBGroupStore(db.collection('groups')),
1069
+ });
228
1070
  ```
229
1071
 
230
- ### Custom Transport
1072
+ ---
1073
+
1074
+ ### MySQL Implementation
1075
+
1076
+ #### Database Schema
1077
+
1078
+ ```sql
1079
+ CREATE DATABASE chatly CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
1080
+ USE chatly;
1081
+
1082
+ CREATE TABLE users (
1083
+ id VARCHAR(255) PRIMARY KEY,
1084
+ username VARCHAR(50) NOT NULL UNIQUE,
1085
+ public_key TEXT NOT NULL,
1086
+ private_key TEXT NOT NULL,
1087
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1088
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
1089
+ INDEX idx_username (username)
1090
+ ) ENGINE=InnoDB;
1091
+
1092
+ CREATE TABLE messages (
1093
+ id VARCHAR(255) PRIMARY KEY,
1094
+ sender_id VARCHAR(255) NOT NULL,
1095
+ receiver_id VARCHAR(255),
1096
+ group_id VARCHAR(255),
1097
+ ciphertext MEDIUMTEXT NOT NULL,
1098
+ iv VARCHAR(255) NOT NULL,
1099
+ timestamp BIGINT NOT NULL,
1100
+ status VARCHAR(20) DEFAULT 'pending',
1101
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1102
+ INDEX idx_receiver_time (receiver_id, timestamp),
1103
+ INDEX idx_group_time (group_id, timestamp),
1104
+ INDEX idx_sender (sender_id),
1105
+ FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE
1106
+ ) ENGINE=InnoDB;
1107
+
1108
+ CREATE TABLE groups (
1109
+ id VARCHAR(255) PRIMARY KEY,
1110
+ name VARCHAR(100) NOT NULL,
1111
+ shared_secret TEXT NOT NULL,
1112
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1113
+ ) ENGINE=InnoDB;
1114
+
1115
+ CREATE TABLE group_members (
1116
+ group_id VARCHAR(255) NOT NULL,
1117
+ user_id VARCHAR(255) NOT NULL,
1118
+ joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
1119
+ PRIMARY KEY (group_id, user_id),
1120
+ FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE,
1121
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
1122
+ ) ENGINE=InnoDB;
1123
+ ```
231
1124
 
232
- Implement `TransportAdapter` to use your own WebSocket server:
1125
+ #### Adapter Implementation
233
1126
 
234
1127
  ```typescript
235
- import { TransportAdapter, Message } from 'chatly-sdk';
1128
+ import mysql from 'mysql2/promise';
1129
+ import { UserStoreAdapter, MessageStoreAdapter, GroupStoreAdapter } from 'chatly-sdk';
1130
+ import type { User, StoredUser, Message, Group } from 'chatly-sdk';
236
1131
 
237
- class WebSocketTransport implements TransportAdapter {
238
- private ws: WebSocket;
239
-
240
- async connect(userId: string): Promise<void> {
241
- this.ws = new WebSocket(`wss://your-server.com/ws?userId=${userId}`);
1132
+ // MySQL User Store (similar to PostgreSQL, with minor syntax differences)
1133
+ export class MySQLUserStore implements UserStoreAdapter {
1134
+ constructor(private pool: mysql.Pool) {}
1135
+
1136
+ async create(user: User): Promise<User> {
1137
+ await this.pool.execute(
1138
+ 'INSERT INTO users (id, username, public_key, private_key) VALUES (?, ?, ?, ?)',
1139
+ [user.id, user.username, user.publicKey, user.privateKey]
1140
+ );
1141
+ return user;
242
1142
  }
243
-
244
- async send(message: Message): Promise<void> {
245
- this.ws.send(JSON.stringify(message));
1143
+
1144
+ async findById(id: string): Promise<User | undefined> {
1145
+ const [rows] = await this.pool.execute(
1146
+ 'SELECT id, username, public_key as publicKey, private_key as privateKey FROM users WHERE id = ?',
1147
+ [id]
1148
+ );
1149
+ return (rows as any[])[0];
246
1150
  }
1151
+
1152
+ async save(user: StoredUser): Promise<void> {
1153
+ await this.pool.execute(
1154
+ 'UPDATE users SET username = ?, public_key = ? WHERE id = ?',
1155
+ [user.username, user.publicKey, user.id]
1156
+ );
1157
+ }
1158
+
1159
+ async list(): Promise<User[]> {
1160
+ const [rows] = await this.pool.execute(
1161
+ 'SELECT id, username, public_key as publicKey, private_key as privateKey FROM users'
1162
+ );
1163
+ return rows as User[];
1164
+ }
1165
+ }
1166
+
1167
+ // Usage
1168
+ import mysql from 'mysql2/promise';
1169
+ import { ChatSDK } from 'chatly-sdk';
1170
+
1171
+ const pool = mysql.createPool({
1172
+ host: 'localhost',
1173
+ user: 'your_user',
1174
+ password: 'your_password',
1175
+ database: 'chatly',
1176
+ waitForConnections: true,
1177
+ connectionLimit: 10,
1178
+ });
1179
+
1180
+ const sdk = new ChatSDK({
1181
+ userStore: new MySQLUserStore(pool),
1182
+ messageStore: new MySQLMessageStore(pool),
1183
+ groupStore: new MySQLGroupStore(pool),
1184
+ });
1185
+ ```
1186
+
1187
+ ---
1188
+
1189
+ ### Redis (Caching Layer)
1190
+
1191
+ Use Redis as a caching layer on top of your primary database:
1192
+
1193
+ ```typescript
1194
+ import { createClient } from 'redis';
1195
+ import { UserStoreAdapter } from 'chatly-sdk';
1196
+ import type { User, StoredUser } from 'chatly-sdk';
1197
+
1198
+ export class CachedUserStore implements UserStoreAdapter {
1199
+ private redis: ReturnType<typeof createClient>;
1200
+ private primaryStore: UserStoreAdapter;
1201
+ private ttl: number = 3600; // 1 hour
1202
+
1203
+ constructor(primaryStore: UserStoreAdapter, redisClient: ReturnType<typeof createClient>) {
1204
+ this.primaryStore = primaryStore;
1205
+ this.redis = redisClient;
1206
+ }
1207
+
1208
+ async create(user: User): Promise<User> {
1209
+ const result = await this.primaryStore.create(user);
1210
+ // Cache the user
1211
+ await this.redis.setEx(
1212
+ `user:${user.id}`,
1213
+ this.ttl,
1214
+ JSON.stringify(result)
1215
+ );
1216
+ return result;
1217
+ }
1218
+
1219
+ async findById(id: string): Promise<User | undefined> {
1220
+ // Try cache first
1221
+ const cached = await this.redis.get(`user:${id}`);
1222
+ if (cached) {
1223
+ return JSON.parse(cached);
1224
+ }
1225
+
1226
+ // Fallback to primary store
1227
+ const user = await this.primaryStore.findById(id);
1228
+ if (user) {
1229
+ await this.redis.setEx(
1230
+ `user:${id}`,
1231
+ this.ttl,
1232
+ JSON.stringify(user)
1233
+ );
1234
+ }
1235
+ return user;
1236
+ }
1237
+
1238
+ async save(user: StoredUser): Promise<void> {
1239
+ await this.primaryStore.save(user);
1240
+ // Invalidate cache
1241
+ await this.redis.del(`user:${user.id}`);
1242
+ }
1243
+
1244
+ async list(): Promise<User[]> {
1245
+ return this.primaryStore.list();
1246
+ }
1247
+ }
1248
+
1249
+ // Usage
1250
+ import { createClient } from 'redis';
1251
+
1252
+ const redis = createClient({ url: 'redis://localhost:6379' });
1253
+ await redis.connect();
1254
+
1255
+ const sdk = new ChatSDK({
1256
+ userStore: new CachedUserStore(new PostgreSQLUserStore(pool), redis),
1257
+ messageStore: new PostgreSQLMessageStore(pool),
1258
+ groupStore: new PostgreSQLGroupStore(pool),
1259
+ });
1260
+ ```
1261
+
1262
+ ---
1263
+
1264
+ ### Best Practices
1265
+
1266
+ #### 1. Connection Pooling
1267
+
1268
+ ```typescript
1269
+ // โœ… DO: Use connection pooling
1270
+ const pool = new Pool({ max: 20, min: 5 });
1271
+
1272
+ // โŒ DON'T: Create new connections for each query
1273
+ const client = new Client();
1274
+ await client.connect();
1275
+ ```
1276
+
1277
+ #### 2. Error Handling
1278
+
1279
+ ```typescript
1280
+ export class PostgreSQLUserStore implements UserStoreAdapter {
1281
+ async create(user: User): Promise<User> {
1282
+ try {
1283
+ await this.pool.query(/* ... */);
1284
+ return user;
1285
+ } catch (error) {
1286
+ if (error.code === '23505') { // Unique violation
1287
+ throw new Error(`User ${user.username} already exists`);
1288
+ }
1289
+ throw error;
1290
+ }
1291
+ }
1292
+ }
1293
+ ```
1294
+
1295
+ #### 3. Transactions
1296
+
1297
+ ```typescript
1298
+ // Use transactions for multi-step operations
1299
+ async create(group: Group): Promise<Group> {
1300
+ const client = await this.pool.connect();
1301
+ try {
1302
+ await client.query('BEGIN');
1303
+ // Multiple operations...
1304
+ await client.query('COMMIT');
1305
+ return group;
1306
+ } catch (error) {
1307
+ await client.query('ROLLBACK');
1308
+ throw error;
1309
+ } finally {
1310
+ client.release();
1311
+ }
1312
+ }
1313
+ ```
1314
+
1315
+ #### 4. Indexing
1316
+
1317
+ ```sql
1318
+ -- Index frequently queried fields
1319
+ CREATE INDEX idx_messages_receiver_time ON messages(receiver_id, timestamp);
1320
+ CREATE INDEX idx_messages_group_time ON messages(group_id, timestamp);
1321
+ CREATE INDEX idx_users_username ON users(username);
1322
+ ```
1323
+
1324
+ #### 5. Data Migration
1325
+
1326
+ When migrating from in-memory to database storage:
1327
+
1328
+ ```typescript
1329
+ // Export data from in-memory store
1330
+ const users = await inMemoryStore.list();
1331
+
1332
+ // Import to database
1333
+ for (const user of users) {
1334
+ await dbStore.create(user);
1335
+ }
1336
+ ```
1337
+
1338
+ ---
1339
+
1340
+ ## โš›๏ธ React Integration
1341
+
1342
+ ### Context Provider
1343
+
1344
+ ```typescript
1345
+ // contexts/SDKContext.tsx
1346
+ import { createContext, useContext, useState, useEffect } from 'react';
1347
+ import { ChatSDK, User, EVENTS, ConnectionState } from 'chatly-sdk';
1348
+
1349
+ interface SDKContextType {
1350
+ sdk: ChatSDK;
1351
+ currentUser: User | null;
1352
+ connectionState: ConnectionState;
1353
+ setCurrentUser: (user: User) => Promise<void>;
1354
+ }
1355
+
1356
+ const SDKContext = createContext<SDKContextType | undefined>(undefined);
1357
+
1358
+ export function SDKProvider({ children }: { children: React.ReactNode }) {
1359
+ const [sdk] = useState(() => new ChatSDK({
1360
+ userStore: new InMemoryUserStore(),
1361
+ messageStore: new InMemoryMessageStore(),
1362
+ groupStore: new InMemoryGroupStore(),
1363
+ transport: new WebSocketClient('wss://your-server.com/ws'),
1364
+ }));
247
1365
 
248
- onMessage(handler: (message: Message) => void): void {
249
- this.ws.on('message', (data) => {
250
- handler(JSON.parse(data.toString()));
251
- });
1366
+ const [currentUser, setCurrentUserState] = useState<User | null>(null);
1367
+ const [connectionState, setConnectionState] = useState<ConnectionState>(
1368
+ ConnectionState.DISCONNECTED
1369
+ );
1370
+
1371
+ useEffect(() => {
1372
+ // Listen for connection state changes
1373
+ sdk.on(EVENTS.CONNECTION_STATE_CHANGED, setConnectionState);
1374
+
1375
+ return () => {
1376
+ sdk.off(EVENTS.CONNECTION_STATE_CHANGED, setConnectionState);
1377
+ };
1378
+ }, [sdk]);
1379
+
1380
+ const setCurrentUser = async (user: User) => {
1381
+ setCurrentUserState(user);
1382
+ await sdk.setCurrentUser(user);
1383
+ };
1384
+
1385
+ return (
1386
+ <SDKContext.Provider value={{ sdk, currentUser, connectionState, setCurrentUser }}>
1387
+ {children}
1388
+ </SDKContext.Provider>
1389
+ );
1390
+ }
1391
+
1392
+ export function useSDK() {
1393
+ const context = useContext(SDKContext);
1394
+ if (!context) throw new Error('useSDK must be used within SDKProvider');
1395
+ return context;
1396
+ }
1397
+ ```
1398
+
1399
+ ### Custom Hooks
1400
+
1401
+ ```typescript
1402
+ // hooks/useMessages.ts
1403
+ import { useState, useEffect } from 'react';
1404
+ import { Message, ChatSession, EVENTS } from 'chatly-sdk';
1405
+ import { useSDK } from '../contexts/SDKContext';
1406
+
1407
+ export function useMessages(session: ChatSession | null) {
1408
+ const { sdk, currentUser } = useSDK();
1409
+ const [messages, setMessages] = useState<Message[]>([]);
1410
+ const [decrypted, setDecrypted] = useState<Map<string, string>>(new Map());
1411
+
1412
+ useEffect(() => {
1413
+ if (!session || !currentUser) return;
1414
+
1415
+ // Load existing messages
1416
+ const loadMessages = async () => {
1417
+ const msgs = await sdk.getMessagesForUser(currentUser.id);
1418
+ setMessages(msgs);
1419
+
1420
+ // Decrypt messages
1421
+ const decryptedMap = new Map();
1422
+ for (const msg of msgs) {
1423
+ const plaintext = await sdk.decryptMessage(msg, currentUser);
1424
+ decryptedMap.set(msg.id, plaintext);
1425
+ }
1426
+ setDecrypted(decryptedMap);
1427
+ };
1428
+
1429
+ loadMessages();
1430
+
1431
+ // Listen for new messages
1432
+ const handleNewMessage = async (message: Message) => {
1433
+ setMessages(prev => [...prev, message]);
1434
+ const plaintext = await sdk.decryptMessage(message, currentUser);
1435
+ setDecrypted(prev => new Map(prev).set(message.id, plaintext));
1436
+ };
1437
+
1438
+ sdk.on(EVENTS.MESSAGE_RECEIVED, handleNewMessage);
1439
+
1440
+ return () => {
1441
+ sdk.off(EVENTS.MESSAGE_RECEIVED, handleNewMessage);
1442
+ };
1443
+ }, [session, currentUser, sdk]);
1444
+
1445
+ const sendMessage = async (text: string) => {
1446
+ if (!session) return;
1447
+ const message = await sdk.sendMessage(session, text);
1448
+ setMessages(prev => [...prev, message]);
1449
+ };
1450
+
1451
+ return { messages, decrypted, sendMessage };
1452
+ }
1453
+ ```
1454
+
1455
+ ### Component Usage
1456
+
1457
+ ```typescript
1458
+ // components/ChatView.tsx
1459
+ import { useSDK } from '../contexts/SDKContext';
1460
+ import { useMessages } from '../hooks/useMessages';
1461
+
1462
+ function ChatView() {
1463
+ const { sdk, currentUser, connectionState } = useSDK();
1464
+ const [session, setSession] = useState<ChatSession | null>(null);
1465
+ const { messages, decrypted, sendMessage } = useMessages(session);
1466
+ const [input, setInput] = useState('');
1467
+
1468
+ const handleSend = async () => {
1469
+ await sendMessage(input);
1470
+ setInput('');
1471
+ };
1472
+
1473
+ return (
1474
+ <div>
1475
+ <div className="connection-status">
1476
+ {connectionState === ConnectionState.CONNECTED ? '๐ŸŸข' : '๐Ÿ”ด'} {connectionState}
1477
+ </div>
1478
+
1479
+ <div className="messages">
1480
+ {messages.map(msg => (
1481
+ <div key={msg.id}>
1482
+ {decrypted.get(msg.id) || 'Decrypting...'}
1483
+ </div>
1484
+ ))}
1485
+ </div>
1486
+
1487
+ <input
1488
+ value={input}
1489
+ onChange={(e) => setInput(e.target.value)}
1490
+ onKeyPress={(e) => e.key === 'Enter' && handleSend()}
1491
+ />
1492
+ </div>
1493
+ );
1494
+ }
1495
+ ```
1496
+
1497
+ ---
1498
+
1499
+ ## ๐Ÿ›ก๏ธ Error Handling
1500
+
1501
+ The SDK uses typed errors for better error handling:
1502
+
1503
+ ```typescript
1504
+ import {
1505
+ ValidationError,
1506
+ NetworkError,
1507
+ SessionError,
1508
+ EncryptionError,
1509
+ StorageError
1510
+ } from 'chatly-sdk';
1511
+
1512
+ try {
1513
+ await sdk.sendMessage(session, message);
1514
+ } catch (error) {
1515
+ if (error instanceof ValidationError) {
1516
+ // Show validation error to user
1517
+ alert(`Invalid input: ${error.message}`);
1518
+ } else if (error instanceof NetworkError) {
1519
+ // Network error - check if retryable
1520
+ if (error.retryable) {
1521
+ console.log('Will retry automatically');
1522
+ } else {
1523
+ alert('Network error - please check your connection');
1524
+ }
1525
+ } else if (error instanceof SessionError) {
1526
+ // Session error - user not logged in
1527
+ redirectToLogin();
1528
+ } else if (error instanceof EncryptionError) {
1529
+ // Encryption failed - keys may be corrupted
1530
+ console.error('Encryption error:', error.details);
1531
+ } else if (error instanceof StorageError) {
1532
+ // Database error
1533
+ console.error('Storage error:', error.details);
252
1534
  }
253
1535
  }
254
1536
  ```
255
1537
 
256
- ## Cryptography
1538
+ ---
257
1539
 
258
- The SDK uses Node.js built-in `crypto` module:
1540
+ ## ๐Ÿ“Š API Reference
259
1541
 
260
- - **Key Exchange**: ECDH with P-256 (prime256v1) curve
261
- - **Encryption**: AES-256-GCM
262
- - **Key Derivation**: PBKDF2 with SHA-256
263
- - **Key Storage**: Base64-encoded strings
1542
+ ### ChatSDK
264
1543
 
265
- ### Security Notes
1544
+ #### Constructor
266
1545
 
267
- - Identity keys are long-term keys for user authentication
268
- - Ephemeral keys are generated per-session (future: Double Ratchet support)
269
- - Group keys are derived deterministically from group ID
270
- - All messages use unique IVs for encryption
1546
+ ```typescript
1547
+ new ChatSDK(config: ChatSDKConfig)
1548
+ ```
271
1549
 
272
- ## Examples
1550
+ **Config Options:**
1551
+ - `userStore: UserStoreAdapter` - User storage adapter
1552
+ - `messageStore: MessageStoreAdapter` - Message storage adapter
1553
+ - `groupStore: GroupStoreAdapter` - Group storage adapter
1554
+ - `transport?: TransportAdapter` - Optional transport layer
1555
+ - `logLevel?: LogLevel` - Optional log level (DEBUG, INFO, WARN, ERROR, NONE)
273
1556
 
274
- See the `examples/` directory for complete examples:
1557
+ #### Methods
275
1558
 
276
- - `oneToOne.ts` - 1:1 chat between two users
277
- - `groupChat.ts` - Group chat with multiple members
278
- - `saveLoadUser.ts` - Save and load user data
1559
+ | Method | Description | Returns |
1560
+ |--------|-------------|---------|
1561
+ | `createUser(username)` | Create a new user | `Promise<User>` |
1562
+ | `importUser(userData)` | Import existing user | `Promise<User>` |
1563
+ | `setCurrentUser(user)` | Set active user | `Promise<void>` |
1564
+ | `getCurrentUser()` | Get active user | `User \| null` |
1565
+ | `startSession(userA, userB)` | Start 1:1 chat | `Promise<ChatSession>` |
1566
+ | `createGroup(name, members)` | Create group | `Promise<GroupSession>` |
1567
+ | `loadGroup(id)` | Load existing group | `Promise<GroupSession>` |
1568
+ | `sendMessage(session, text)` | Send message | `Promise<Message>` |
1569
+ | `decryptMessage(message, user)` | Decrypt message | `Promise<string>` |
1570
+ | `getMessagesForUser(userId)` | Get user messages | `Promise<Message[]>` |
1571
+ | `getMessagesForGroup(groupId)` | Get group messages | `Promise<Message[]>` |
1572
+ | `listUsers()` | Get all users | `Promise<User[]>` |
1573
+ | `getUserById(id)` | Get user by ID | `Promise<User \| undefined>` |
1574
+ | `listGroups()` | Get all groups | `Promise<Group[]>` |
1575
+ | `getConnectionState()` | Get connection state | `ConnectionState` |
1576
+ | `isConnected()` | Check if connected | `boolean` |
1577
+ | `disconnect()` | Disconnect transport | `Promise<void>` |
1578
+ | `reconnect()` | Reconnect transport | `Promise<void>` |
1579
+ | `getQueueStatus()` | Get message queue status | `QueueStatus` |
1580
+
1581
+ #### Events
1582
+
1583
+ | Event | Payload | Description |
1584
+ |-------|---------|-------------|
1585
+ | `message:sent` | `Message` | Message sent successfully |
1586
+ | `message:received` | `Message` | Message received |
1587
+ | `message:failed` | `Message, Error` | Message send failed |
1588
+ | `connection:state` | `ConnectionState` | Connection state changed |
1589
+ | `session:created` | `ChatSession` | Chat session created |
1590
+ | `group:created` | `GroupSession` | Group created |
1591
+ | `user:created` | `User` | User created |
1592
+ | `error` | `Error` | SDK error occurred |
1593
+
1594
+ ---
1595
+
1596
+ ## ๐Ÿ”’ Security Best Practices
1597
+
1598
+ ### 1. Secure Key Storage
279
1599
 
280
- Run examples:
1600
+ ```typescript
1601
+ // โŒ DON'T: Store private keys in plaintext
1602
+ localStorage.setItem('privateKey', user.privateKey);
281
1603
 
282
- ```bash
283
- npm run build
284
- node dist/examples/oneToOne.js
1604
+ // โœ… DO: Encrypt private keys with user password
1605
+ import { encryptWithPassword } from './crypto';
1606
+ const encrypted = await encryptWithPassword(user.privateKey, userPassword);
1607
+ localStorage.setItem('encryptedKey', encrypted);
285
1608
  ```
286
1609
 
287
- ## Building
1610
+ ### 2. Use HTTPS/WSS
288
1611
 
289
- ```bash
290
- npm run build
1612
+ ```typescript
1613
+ // โŒ DON'T: Use unencrypted connections
1614
+ const transport = new WebSocketClient('ws://server.com');
1615
+
1616
+ // โœ… DO: Use secure WebSocket
1617
+ const transport = new WebSocketClient('wss://server.com');
291
1618
  ```
292
1619
 
293
- This generates:
294
- - `dist/index.js` - ES module bundle
295
- - `dist/index.d.ts` - TypeScript definitions
1620
+ ### 3. Validate All Input
1621
+
1622
+ ```typescript
1623
+ // โœ… SDK automatically validates
1624
+ await sdk.createUser('alice'); // โœ… Valid
1625
+ await sdk.createUser('ab'); // โŒ Throws ValidationError
1626
+ await sdk.sendMessage(session, ''); // โŒ Throws ValidationError
1627
+ ```
296
1628
 
297
- ## Development
1629
+ ### 4. Handle Errors Properly
298
1630
 
299
- ```bash
300
- # Install dependencies
301
- npm install
1631
+ ```typescript
1632
+ // โœ… Use typed errors
1633
+ sdk.on(EVENTS.ERROR, (error) => {
1634
+ if (error instanceof NetworkError && error.retryable) {
1635
+ // Will retry automatically
1636
+ } else {
1637
+ // Log to error tracking service
1638
+ Sentry.captureException(error);
1639
+ }
1640
+ });
1641
+ ```
1642
+
1643
+ ---
1644
+
1645
+ ## ๐Ÿงช Testing
302
1646
 
1647
+ ```bash
303
1648
  # Run tests
304
1649
  npm test
305
1650
 
306
- # Build
307
- npm run build
1651
+ # Watch mode
1652
+ npm run test:watch
1653
+
1654
+ # Coverage
1655
+ npm run test:coverage
1656
+ ```
1657
+
1658
+ ### Example Test
1659
+
1660
+ ```typescript
1661
+ import { ChatSDK, InMemoryUserStore } from 'chatly-sdk';
1662
+
1663
+ describe('ChatSDK', () => {
1664
+ it('should create and decrypt messages', async () => {
1665
+ const sdk = new ChatSDK({
1666
+ userStore: new InMemoryUserStore(),
1667
+ messageStore: new InMemoryMessageStore(),
1668
+ groupStore: new InMemoryGroupStore(),
1669
+ });
1670
+
1671
+ const alice = await sdk.createUser('alice');
1672
+ const bob = await sdk.createUser('bob');
1673
+ const session = await sdk.startSession(alice, bob);
1674
+
1675
+ sdk.setCurrentUser(alice);
1676
+ const message = await sdk.sendMessage(session, 'Hello!');
1677
+
1678
+ const decrypted = await sdk.decryptMessage(message, bob);
1679
+ expect(decrypted).toBe('Hello!');
1680
+ });
1681
+ });
308
1682
  ```
309
1683
 
310
- ## License
1684
+ ---
1685
+
1686
+ ## ๐Ÿ“š Examples
1687
+
1688
+ Check out the [examples](./examples) directory for complete implementations:
1689
+
1690
+ - **Basic Chat** - Simple 1:1 messaging
1691
+ - **Group Chat** - Multi-user groups
1692
+ - **React App** - Full React integration
1693
+ - **WebSocket Server** - Node.js WebSocket server
1694
+ - **MongoDB Integration** - Database persistence
1695
+
1696
+ ---
1697
+
1698
+ ## ๐Ÿค Contributing
1699
+
1700
+ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.
1701
+
1702
+ ---
1703
+
1704
+ ## ๐Ÿ“„ License
1705
+
1706
+ MIT ยฉ [Bharath](https://github.com/bharath-arch)
1707
+
1708
+ ---
1709
+
1710
+ ## ๐Ÿ”— Links
1711
+
1712
+ - [NPM Package](https://www.npmjs.com/package/chatly-sdk)
1713
+ - [GitHub Repository](https://github.com/bharath-arch/chatly-sdk)
1714
+ - [Documentation](https://github.com/bharath-arch/chatly-sdk#readme)
1715
+ - [Issue Tracker](https://github.com/bharath-arch/chatly-sdk/issues)
1716
+
1717
+ ---
1718
+
1719
+ ## ๐Ÿ“ž Support
1720
+
1721
+ - **Issues**: [GitHub Issues](https://github.com/bharath-arch/chatly-sdk/issues)
1722
+ - **Discussions**: [GitHub Discussions](https://github.com/bharath-arch/chatly-sdk/discussions)
1723
+
1724
+ ---
311
1725
 
312
- ISC
1726
+ **Built with โค๏ธ for secure, private messaging**