chatly-sdk 0.0.8 → 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.
- package/IMPROVEMENTS.md +1 -1
- package/README.md +53 -13
- package/dist/index.d.ts +74 -4
- package/dist/index.js +292 -96
- package/package.json +2 -1
- package/src/chat/ChatSession.ts +88 -12
- package/src/chat/GroupSession.ts +35 -7
- package/src/crypto/e2e.ts +9 -0
- package/src/crypto/utils.ts +3 -1
- package/src/index.ts +11 -6
- package/src/models/mediaTypes.ts +5 -1
- package/src/storage/adapters.ts +36 -0
- package/src/storage/localStorage.ts +49 -0
- package/src/storage/s3Storage.ts +84 -0
- package/src/stores/adapters.ts +2 -0
- package/src/stores/memory/messageStore.ts +8 -0
- package/test/crypto.test.ts +17 -17
package/IMPROVEMENTS.md
CHANGED
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# 🔐 Chatly SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Production-ready end-to-end encrypted chat SDK with WhatsApp-style features, event-driven architecture, and automatic reconnection.
|
|
4
|
+
|
|
5
|
+
You can find the sample project repository here: [Chatly SDK Sample Project](https://github.com/bharath-arch/chatly-sdk-demo.git)
|
|
6
|
+
|
|
4
7
|
|
|
5
8
|
[](https://www.npmjs.com/package/chatly-sdk)
|
|
6
9
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -12,6 +15,7 @@ Beta end-to-end encrypted chat SDK with WhatsApp-style features, event-driven ar
|
|
|
12
15
|
- **Per-User Identity Keys** - Unique cryptographic identity
|
|
13
16
|
- **Session-Based Encryption** - Secure 1:1 and group messaging
|
|
14
17
|
- **Input Validation** - Protection against injection attacks
|
|
18
|
+
- **Configurable Media Storage** - Offload encrypted files to S3 or Local storage
|
|
15
19
|
|
|
16
20
|
### 💬 Messaging
|
|
17
21
|
- **1:1 Chat** - Secure direct messaging
|
|
@@ -181,6 +185,7 @@ const sdk = new ChatSDK({
|
|
|
181
185
|
userStore: new InMemoryUserStore(),
|
|
182
186
|
messageStore: new InMemoryMessageStore(),
|
|
183
187
|
groupStore: new InMemoryGroupStore(),
|
|
188
|
+
storageProvider: new LocalStorageProvider('./uploads'), // Optional: S3 or Local
|
|
184
189
|
logLevel: LogLevel.INFO, // Optional: DEBUG, INFO, WARN, ERROR, NONE
|
|
185
190
|
});
|
|
186
191
|
|
|
@@ -376,24 +381,59 @@ console.log('Supported image types:', SUPPORTED_MIME_TYPES.image);
|
|
|
376
381
|
console.log('Max video size:', FILE_SIZE_LIMITS.video); // 100 MB
|
|
377
382
|
```
|
|
378
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
|
+
|
|
379
415
|
### Media Encryption
|
|
380
416
|
|
|
381
417
|
All media files are **fully encrypted end-to-end**:
|
|
382
418
|
|
|
383
|
-
1. **File data
|
|
384
|
-
2. **
|
|
385
|
-
3. **
|
|
386
|
-
4. **
|
|
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.
|
|
387
423
|
|
|
388
424
|
```typescript
|
|
389
|
-
//
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
+
}
|
|
397
437
|
```
|
|
398
438
|
|
|
399
439
|
### Example: Sending an Image
|
package/dist/index.d.ts
CHANGED
|
@@ -37,8 +37,12 @@ interface MediaMetadata {
|
|
|
37
37
|
*/
|
|
38
38
|
interface MediaAttachment {
|
|
39
39
|
type: MediaType;
|
|
40
|
-
data
|
|
40
|
+
data?: string | undefined;
|
|
41
|
+
iv?: string | undefined;
|
|
41
42
|
metadata: MediaMetadata;
|
|
43
|
+
storage?: 'local' | 's3' | undefined;
|
|
44
|
+
storageKey?: string | undefined;
|
|
45
|
+
url?: string | undefined;
|
|
42
46
|
}
|
|
43
47
|
/**
|
|
44
48
|
* Supported MIME types
|
|
@@ -87,8 +91,10 @@ interface UserStoreAdapter {
|
|
|
87
91
|
}
|
|
88
92
|
interface MessageStoreAdapter {
|
|
89
93
|
create(message: Message): Promise<Message>;
|
|
94
|
+
findById(id: string): Promise<Message | undefined>;
|
|
90
95
|
listByUser(userId: string): Promise<Message[]>;
|
|
91
96
|
listByGroup(groupId: string): Promise<Message[]>;
|
|
97
|
+
delete(id: string): Promise<void>;
|
|
92
98
|
}
|
|
93
99
|
interface GroupStoreAdapter {
|
|
94
100
|
create(group: Group): Promise<Group>;
|
|
@@ -191,13 +197,43 @@ interface TransportAdapter {
|
|
|
191
197
|
isConnected(): boolean;
|
|
192
198
|
}
|
|
193
199
|
|
|
200
|
+
interface StorageUploadResult {
|
|
201
|
+
storageKey: string;
|
|
202
|
+
url?: string;
|
|
203
|
+
}
|
|
204
|
+
interface StorageProvider {
|
|
205
|
+
/**
|
|
206
|
+
* Name of the storage provider (e.g., 'local', 's3')
|
|
207
|
+
*/
|
|
208
|
+
readonly name: string;
|
|
209
|
+
/**
|
|
210
|
+
* Upload data to storage
|
|
211
|
+
* @param data Base64 encoded data or Buffer
|
|
212
|
+
* @param filename Desired filename or path
|
|
213
|
+
* @param mimeType MIME type of the file
|
|
214
|
+
*/
|
|
215
|
+
upload(data: string | Buffer, filename: string, mimeType: string): Promise<StorageUploadResult>;
|
|
216
|
+
/**
|
|
217
|
+
* Download data from storage
|
|
218
|
+
* @param storageKey Key/path of the file in storage
|
|
219
|
+
* @returns Base64 encoded data
|
|
220
|
+
*/
|
|
221
|
+
download(storageKey: string): Promise<string>;
|
|
222
|
+
/**
|
|
223
|
+
* Delete data from storage
|
|
224
|
+
* @param storageKey Key/path of the file in storage
|
|
225
|
+
*/
|
|
226
|
+
delete(storageKey: string): Promise<void>;
|
|
227
|
+
}
|
|
228
|
+
|
|
194
229
|
declare class ChatSession {
|
|
195
230
|
readonly id: string;
|
|
196
231
|
readonly userA: User;
|
|
197
232
|
readonly userB: User;
|
|
233
|
+
private storageProvider?;
|
|
198
234
|
private sharedSecret;
|
|
199
235
|
private ephemeralKeyPair;
|
|
200
|
-
constructor(id: string, userA: User, userB: User);
|
|
236
|
+
constructor(id: string, userA: User, userB: User, storageProvider?: StorageProvider | undefined);
|
|
201
237
|
/**
|
|
202
238
|
* Initialize the session by deriving the shared secret
|
|
203
239
|
* ECDH is commutative, so we can use either user's keys
|
|
@@ -219,6 +255,7 @@ declare class ChatSession {
|
|
|
219
255
|
* Decrypt a message in this session
|
|
220
256
|
*/
|
|
221
257
|
decrypt(message: Message, user: User): Promise<string>;
|
|
258
|
+
private deriveLegacySecret;
|
|
222
259
|
/**
|
|
223
260
|
* Decrypt a media message in this session
|
|
224
261
|
*/
|
|
@@ -230,8 +267,9 @@ declare class ChatSession {
|
|
|
230
267
|
|
|
231
268
|
declare class GroupSession {
|
|
232
269
|
readonly group: Group;
|
|
270
|
+
private storageProvider?;
|
|
233
271
|
private groupKey;
|
|
234
|
-
constructor(group: Group);
|
|
272
|
+
constructor(group: Group, storageProvider?: StorageProvider | undefined);
|
|
235
273
|
/**
|
|
236
274
|
* Initialize the session by deriving the group key
|
|
237
275
|
*/
|
|
@@ -303,8 +341,10 @@ declare class InMemoryUserStore implements UserStoreAdapter {
|
|
|
303
341
|
declare class InMemoryMessageStore implements MessageStoreAdapter {
|
|
304
342
|
private messages;
|
|
305
343
|
create(message: Message): Promise<Message>;
|
|
344
|
+
findById(id: string): Promise<Message | undefined>;
|
|
306
345
|
listByUser(userId: string): Promise<Message[]>;
|
|
307
346
|
listByGroup(groupId: string): Promise<Message[]>;
|
|
347
|
+
delete(id: string): Promise<void>;
|
|
308
348
|
}
|
|
309
349
|
|
|
310
350
|
declare class InMemoryGroupStore implements GroupStoreAdapter {
|
|
@@ -481,10 +521,40 @@ declare function createMediaAttachment(file: File | Blob, filename?: string): Pr
|
|
|
481
521
|
*/
|
|
482
522
|
declare function formatFileSize(bytes: number): string;
|
|
483
523
|
|
|
524
|
+
declare class LocalStorageProvider implements StorageProvider {
|
|
525
|
+
readonly name = "local";
|
|
526
|
+
private storageDir;
|
|
527
|
+
constructor(storageDir?: string);
|
|
528
|
+
upload(data: string | Buffer, filename: string, mimeType: string): Promise<StorageUploadResult>;
|
|
529
|
+
download(storageKey: string): Promise<string>;
|
|
530
|
+
delete(storageKey: string): Promise<void>;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
interface S3Config {
|
|
534
|
+
region: string;
|
|
535
|
+
bucket: string;
|
|
536
|
+
credentials?: {
|
|
537
|
+
accessKeyId: string;
|
|
538
|
+
secretAccessKey: string;
|
|
539
|
+
};
|
|
540
|
+
endpoint?: string;
|
|
541
|
+
forcePathStyle?: boolean;
|
|
542
|
+
}
|
|
543
|
+
declare class S3StorageProvider implements StorageProvider {
|
|
544
|
+
readonly name = "s3";
|
|
545
|
+
private client;
|
|
546
|
+
private bucket;
|
|
547
|
+
constructor(config: S3Config);
|
|
548
|
+
upload(data: string | Buffer, filename: string, mimeType: string): Promise<StorageUploadResult>;
|
|
549
|
+
download(storageKey: string): Promise<string>;
|
|
550
|
+
delete(storageKey: string): Promise<void>;
|
|
551
|
+
}
|
|
552
|
+
|
|
484
553
|
interface ChatSDKConfig {
|
|
485
554
|
userStore: UserStoreAdapter;
|
|
486
555
|
messageStore: MessageStoreAdapter;
|
|
487
556
|
groupStore: GroupStoreAdapter;
|
|
557
|
+
storageProvider?: StorageProvider;
|
|
488
558
|
transport?: TransportAdapter;
|
|
489
559
|
logLevel?: LogLevel;
|
|
490
560
|
}
|
|
@@ -606,4 +676,4 @@ declare class ChatSDK extends EventEmitter {
|
|
|
606
676
|
};
|
|
607
677
|
}
|
|
608
678
|
|
|
609
|
-
export { ALGORITHM, AuthError, CONNECTION_TIMEOUT, ChatSDK, type ChatSDKConfig, ChatSession, ConfigError, ConnectionState, EVENTS, EncryptionError, FILE_SIZE_LIMITS, GROUP_MAX_MEMBERS, GROUP_MIN_MEMBERS, GROUP_NAME_MAX_LENGTH, type Group, GroupSession, type GroupStoreAdapter, HEARTBEAT_INTERVAL, IV_LENGTH, InMemoryGroupStore, InMemoryMessageStore, InMemoryTransport, InMemoryUserStore, KEY_LENGTH, LogLevel, Logger, type LoggerConfig, MAX_QUEUE_SIZE, MESSAGE_MAX_LENGTH, MESSAGE_RETRY_ATTEMPTS, MESSAGE_RETRY_DELAY, type MediaAttachment, type MediaMetadata, MediaType, type Message, MessageStatus, type MessageStoreAdapter, type MessageType, NetworkError, PBKDF2_ITERATIONS, RECONNECT_BASE_DELAY, RECONNECT_MAX_ATTEMPTS, RECONNECT_MAX_DELAY, SALT_LENGTH, SDKError, SUPPORTED_CURVE, SUPPORTED_MIME_TYPES, SessionError, StorageError, type StoredUser, TAG_LENGTH, type TransportAdapter, TransportError, USERNAME_MAX_LENGTH, USERNAME_MIN_LENGTH, type User, type UserStoreAdapter, ValidationError, WebSocketClient, createMediaAttachment, createMediaMetadata, decodeBase64ToBlob, encodeFileToBase64, formatFileSize, getMediaType, logger, validateGroupMembers, validateGroupName, validateMediaFile, validateMessage, validateUserId, validateUsername };
|
|
679
|
+
export { ALGORITHM, AuthError, CONNECTION_TIMEOUT, ChatSDK, type ChatSDKConfig, ChatSession, ConfigError, ConnectionState, EVENTS, EncryptionError, FILE_SIZE_LIMITS, GROUP_MAX_MEMBERS, GROUP_MIN_MEMBERS, GROUP_NAME_MAX_LENGTH, type Group, GroupSession, type GroupStoreAdapter, HEARTBEAT_INTERVAL, IV_LENGTH, InMemoryGroupStore, InMemoryMessageStore, InMemoryTransport, InMemoryUserStore, KEY_LENGTH, LocalStorageProvider, LogLevel, Logger, type LoggerConfig, MAX_QUEUE_SIZE, MESSAGE_MAX_LENGTH, MESSAGE_RETRY_ATTEMPTS, MESSAGE_RETRY_DELAY, type MediaAttachment, type MediaMetadata, MediaType, type Message, MessageStatus, type MessageStoreAdapter, type MessageType, NetworkError, PBKDF2_ITERATIONS, RECONNECT_BASE_DELAY, RECONNECT_MAX_ATTEMPTS, RECONNECT_MAX_DELAY, type S3Config, S3StorageProvider, SALT_LENGTH, SDKError, SUPPORTED_CURVE, SUPPORTED_MIME_TYPES, SessionError, StorageError, type StorageProvider, type StorageUploadResult, type StoredUser, TAG_LENGTH, type TransportAdapter, TransportError, USERNAME_MAX_LENGTH, USERNAME_MIN_LENGTH, type User, type UserStoreAdapter, ValidationError, WebSocketClient, createMediaAttachment, createMediaMetadata, decodeBase64ToBlob, encodeFileToBase64, formatFileSize, getMediaType, logger, validateGroupMembers, validateGroupName, validateMediaFile, validateMessage, validateUserId, validateUsername };
|