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