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.
- package/CONTRIBUTING.md +658 -0
- package/IMPROVEMENTS.md +402 -0
- package/README.md +1539 -162
- package/dist/index.d.ts +430 -9
- package/dist/index.js +1420 -63
- package/examples/01-basic-chat/README.md +61 -0
- package/examples/01-basic-chat/index.js +58 -0
- package/examples/01-basic-chat/package.json +13 -0
- package/examples/02-group-chat/README.md +78 -0
- package/examples/02-group-chat/index.js +76 -0
- package/examples/02-group-chat/package.json +13 -0
- package/examples/03-offline-messaging/README.md +73 -0
- package/examples/03-offline-messaging/index.js +80 -0
- package/examples/03-offline-messaging/package.json +13 -0
- package/examples/04-live-chat/README.md +80 -0
- package/examples/04-live-chat/index.js +114 -0
- package/examples/04-live-chat/package.json +13 -0
- package/examples/05-hybrid-messaging/README.md +71 -0
- package/examples/05-hybrid-messaging/index.js +106 -0
- package/examples/05-hybrid-messaging/package.json +13 -0
- package/examples/06-postgresql-integration/README.md +101 -0
- package/examples/06-postgresql-integration/adapters/groupStore.js +73 -0
- package/examples/06-postgresql-integration/adapters/messageStore.js +47 -0
- package/examples/06-postgresql-integration/adapters/userStore.js +40 -0
- package/examples/06-postgresql-integration/index.js +92 -0
- package/examples/06-postgresql-integration/package.json +14 -0
- package/examples/06-postgresql-integration/schema.sql +58 -0
- package/examples/08-customer-support/README.md +70 -0
- package/examples/08-customer-support/index.js +104 -0
- package/examples/08-customer-support/package.json +13 -0
- package/examples/README.md +105 -0
- package/jest.config.cjs +28 -0
- package/package.json +12 -8
- package/src/chat/ChatSession.ts +81 -0
- package/src/chat/GroupSession.ts +79 -0
- package/src/constants.ts +61 -0
- package/src/crypto/e2e.ts +0 -20
- package/src/index.ts +525 -63
- package/src/models/mediaTypes.ts +58 -0
- package/src/models/message.ts +4 -1
- package/src/transport/adapters.ts +51 -1
- package/src/transport/memoryTransport.ts +75 -13
- package/src/transport/websocketClient.ts +269 -21
- package/src/transport/websocketServer.ts +26 -26
- package/src/utils/errors.ts +97 -0
- package/src/utils/logger.ts +96 -0
- package/src/utils/mediaUtils.ts +235 -0
- package/src/utils/messageQueue.ts +162 -0
- package/src/utils/validation.ts +99 -0
- package/test/crypto.test.ts +122 -35
- package/test/sdk.test.ts +276 -0
- package/test/validation.test.ts +64 -0
- package/tsconfig.json +11 -10
- package/tsconfig.test.json +11 -0
- package/src/ChatManager.ts +0 -103
- package/src/crypto/keyManager.ts +0 -28
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChatSDK,
|
|
3
|
+
InMemoryUserStore,
|
|
4
|
+
InMemoryMessageStore,
|
|
5
|
+
InMemoryGroupStore,
|
|
6
|
+
MemoryTransport,
|
|
7
|
+
EVENTS,
|
|
8
|
+
LogLevel
|
|
9
|
+
} from 'chatly-sdk';
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
console.log('🎧 Chatly SDK - Customer Support Example');
|
|
13
|
+
console.log('========================================\n');
|
|
14
|
+
|
|
15
|
+
// Initialize support system
|
|
16
|
+
const transport = new MemoryTransport();
|
|
17
|
+
const sdk = new ChatSDK({
|
|
18
|
+
userStore: new InMemoryUserStore(),
|
|
19
|
+
messageStore: new InMemoryMessageStore(),
|
|
20
|
+
groupStore: new InMemoryGroupStore(),
|
|
21
|
+
transport,
|
|
22
|
+
logLevel: LogLevel.NONE,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
console.log('Setting up support system...');
|
|
26
|
+
|
|
27
|
+
// Create support agent and customer
|
|
28
|
+
const agent = await sdk.createUser('support-agent');
|
|
29
|
+
const customer = await sdk.createUser('customer-john');
|
|
30
|
+
console.log('✅ Support agent created');
|
|
31
|
+
console.log('✅ Customer created\n');
|
|
32
|
+
|
|
33
|
+
// Create support session
|
|
34
|
+
const session = await sdk.startSession(customer, agent);
|
|
35
|
+
|
|
36
|
+
// Scenario 1: Customer sends message (agent offline)
|
|
37
|
+
console.log('Scenario 1: Customer sends message (agent offline)');
|
|
38
|
+
sdk.setCurrentUser(customer);
|
|
39
|
+
|
|
40
|
+
await sdk.sendMessage(session, 'I need help with my order');
|
|
41
|
+
console.log('📝 Customer: I need help with my order');
|
|
42
|
+
console.log('⏳ Message queued (no agents available)\n');
|
|
43
|
+
|
|
44
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
45
|
+
|
|
46
|
+
// Scenario 2: Agent comes online and sees pending messages
|
|
47
|
+
console.log('Scenario 2: Agent comes online');
|
|
48
|
+
sdk.setCurrentUser(agent);
|
|
49
|
+
await sdk.setCurrentUser(agent); // Connect agent
|
|
50
|
+
|
|
51
|
+
console.log('🟢 Agent online');
|
|
52
|
+
|
|
53
|
+
const pendingMessages = await sdk.getMessagesForUser(agent.id);
|
|
54
|
+
console.log(`📬 Agent has ${pendingMessages.length} pending message(s)`);
|
|
55
|
+
|
|
56
|
+
for (const msg of pendingMessages) {
|
|
57
|
+
const text = await sdk.decryptMessage(msg, agent);
|
|
58
|
+
console.log(`📨 Agent sees: ${text}`);
|
|
59
|
+
}
|
|
60
|
+
console.log();
|
|
61
|
+
|
|
62
|
+
// Scenario 3: Live chat (both online)
|
|
63
|
+
console.log('Scenario 3: Live chat (both online)');
|
|
64
|
+
console.log('💬 Real-time conversation started\n');
|
|
65
|
+
|
|
66
|
+
// Set up real-time event listeners
|
|
67
|
+
sdk.on(EVENTS.MESSAGE_RECEIVED, async (message) => {
|
|
68
|
+
const currentUser = sdk.getCurrentUser();
|
|
69
|
+
if (currentUser) {
|
|
70
|
+
const plaintext = await sdk.decryptMessage(message, currentUser);
|
|
71
|
+
console.log(`📨 ${currentUser.username} received: ${plaintext}`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Agent responds
|
|
76
|
+
sdk.setCurrentUser(agent);
|
|
77
|
+
await sdk.sendMessage(session, 'Hi! How can I help?');
|
|
78
|
+
console.log('📤 Agent: Hi! How can I help?');
|
|
79
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
80
|
+
|
|
81
|
+
// Customer replies
|
|
82
|
+
sdk.setCurrentUser(customer);
|
|
83
|
+
await sdk.sendMessage(session, 'My order #12345 is delayed');
|
|
84
|
+
console.log('📤 Customer: My order #12345 is delayed');
|
|
85
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
86
|
+
|
|
87
|
+
// Agent helps
|
|
88
|
+
sdk.setCurrentUser(agent);
|
|
89
|
+
await sdk.sendMessage(session, 'Let me check that for you');
|
|
90
|
+
console.log('📤 Agent: Let me check that for you');
|
|
91
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
92
|
+
|
|
93
|
+
console.log();
|
|
94
|
+
console.log('✅ Support system works perfectly!');
|
|
95
|
+
console.log('\n💡 Key Features:');
|
|
96
|
+
console.log(' - 24/7 availability (customers message anytime)');
|
|
97
|
+
console.log(' - Offline queue (messages saved for agents)');
|
|
98
|
+
console.log(' - Real-time chat when both online');
|
|
99
|
+
console.log(' - Full conversation history');
|
|
100
|
+
|
|
101
|
+
await sdk.disconnect();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chatly-sdk-customer-support-example",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Customer support chat system example using Chatly SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node index.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"chatly-sdk": "^0.0.5"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Chatly SDK - Examples
|
|
2
|
+
|
|
3
|
+
This directory contains practical examples demonstrating different use cases for the Chatly SDK.
|
|
4
|
+
|
|
5
|
+
## 📚 Examples
|
|
6
|
+
|
|
7
|
+
### 1. [Basic Chat](./01-basic-chat)
|
|
8
|
+
**Difficulty**: Beginner
|
|
9
|
+
**Topics**: User creation, sessions, encryption, message sending
|
|
10
|
+
|
|
11
|
+
Simple 1:1 encrypted chat between two users.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### 2. [Group Chat](./02-group-chat)
|
|
16
|
+
**Difficulty**: Beginner
|
|
17
|
+
**Topics**: Group creation, multi-user messaging, group encryption
|
|
18
|
+
|
|
19
|
+
Multi-user encrypted group messaging with 3+ participants.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### 3. [Offline Messaging](./03-offline-messaging)
|
|
24
|
+
Asynchronous messaging without WebSocket
|
|
25
|
+
|
|
26
|
+
### 2. **Real-time Examples**
|
|
27
|
+
- [`04-live-chat/`](./04-live-chat/) - Live chat with WebSocket (WhatsApp-style)
|
|
28
|
+
- [`05-hybrid-messaging/`](./05-hybrid-messaging/) - Hybrid online/offline messaging
|
|
29
|
+
|
|
30
|
+
### 3. **Database Integration**
|
|
31
|
+
- [`06-postgresql-integration/`](./06-postgresql-integration/) - PostgreSQL storage adapter
|
|
32
|
+
- [`07-mongodb-integration/`](./07-mongodb-integration/) - MongoDB storage adapter
|
|
33
|
+
|
|
34
|
+
### 4. **Real-world Applications**
|
|
35
|
+
- [`08-customer-support/`](./08-customer-support/) - Customer support chat system
|
|
36
|
+
- [`09-react-chat-app/`](./09-react-chat-app/) - Full React chat application
|
|
37
|
+
- [`10-websocket-server/`](./10-websocket-server/) - Node.js WebSocket server
|
|
38
|
+
|
|
39
|
+
## 🚀 Quick Start
|
|
40
|
+
|
|
41
|
+
Each example includes:
|
|
42
|
+
- ✅ Complete source code
|
|
43
|
+
- ✅ README with setup instructions
|
|
44
|
+
- ✅ package.json for dependencies
|
|
45
|
+
- ✅ Comments explaining key concepts
|
|
46
|
+
|
|
47
|
+
### Running an Example
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Navigate to an example
|
|
51
|
+
cd examples/01-basic-chat
|
|
52
|
+
|
|
53
|
+
# Install dependencies
|
|
54
|
+
npm install
|
|
55
|
+
|
|
56
|
+
# Run the example
|
|
57
|
+
npm start
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 📚 Learning Path
|
|
61
|
+
|
|
62
|
+
**Beginner**: Start here
|
|
63
|
+
1. `01-basic-chat` - Learn the basics
|
|
64
|
+
2. `02-group-chat` - Understand group messaging
|
|
65
|
+
3. `03-offline-messaging` - See how offline works
|
|
66
|
+
|
|
67
|
+
**Intermediate**: Real-time features
|
|
68
|
+
4. `04-live-chat` - Add WebSocket support
|
|
69
|
+
5. `05-hybrid-messaging` - Combine online/offline
|
|
70
|
+
|
|
71
|
+
**Advanced**: Production setup
|
|
72
|
+
6. `06-postgresql-integration` - Database persistence
|
|
73
|
+
7. `08-customer-support` - Real-world application
|
|
74
|
+
8. `09-react-chat-app` - Full frontend integration
|
|
75
|
+
|
|
76
|
+
## 🎯 Use Case Guide
|
|
77
|
+
|
|
78
|
+
| Use Case | Example | Description |
|
|
79
|
+
|----------|---------|-------------|
|
|
80
|
+
| **Messaging App** | `04-live-chat` | WhatsApp-style real-time chat |
|
|
81
|
+
| **Customer Support** | `08-customer-support` | Live support with offline fallback |
|
|
82
|
+
| **Team Collaboration** | `02-group-chat` + `04-live-chat` | Slack-style team chat |
|
|
83
|
+
| **Social Media DMs** | `05-hybrid-messaging` | Instagram-style direct messages |
|
|
84
|
+
| **Email-like System** | `03-offline-messaging` | Asynchronous messaging |
|
|
85
|
+
|
|
86
|
+
## 💡 Key Concepts Demonstrated
|
|
87
|
+
|
|
88
|
+
- ✅ **End-to-end encryption** - All examples use E2E encryption
|
|
89
|
+
- ✅ **Message queue** - Automatic retry and offline support
|
|
90
|
+
- ✅ **Event handling** - Real-time UI updates
|
|
91
|
+
- ✅ **Database integration** - PostgreSQL and MongoDB
|
|
92
|
+
- ✅ **WebSocket** - Real-time bidirectional communication
|
|
93
|
+
- ✅ **React integration** - Hooks and context providers
|
|
94
|
+
|
|
95
|
+
## 🔧 Prerequisites
|
|
96
|
+
|
|
97
|
+
- Node.js >= 16.x
|
|
98
|
+
- npm >= 8.x
|
|
99
|
+
- (Optional) PostgreSQL or MongoDB for database examples
|
|
100
|
+
|
|
101
|
+
## 📞 Need Help?
|
|
102
|
+
|
|
103
|
+
- Check the main [README](../README.md)
|
|
104
|
+
- Read [CONTRIBUTING.md](../CONTRIBUTING.md)
|
|
105
|
+
- Open an [issue](https://github.com/bharath-arch/chatly-sdk/issues)
|
package/jest.config.cjs
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
roots: ['<rootDir>/test'],
|
|
5
|
+
testMatch: ['**/*.test.ts'],
|
|
6
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
7
|
+
transform: {
|
|
8
|
+
'^.+\\.ts$': ['ts-jest', {
|
|
9
|
+
tsconfig: 'tsconfig.test.json',
|
|
10
|
+
}],
|
|
11
|
+
},
|
|
12
|
+
collectCoverageFrom: [
|
|
13
|
+
'src/**/*.ts',
|
|
14
|
+
'!src/**/*.d.ts',
|
|
15
|
+
'!src/**/index.ts',
|
|
16
|
+
],
|
|
17
|
+
coverageThreshold: {
|
|
18
|
+
global: {
|
|
19
|
+
branches: 70,
|
|
20
|
+
functions: 70,
|
|
21
|
+
lines: 70,
|
|
22
|
+
statements: 70,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
moduleNameMapper: {
|
|
26
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
27
|
+
},
|
|
28
|
+
};
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chatly-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsup src/index.ts --dts --format esm",
|
|
9
9
|
"start": "ts-node src/index.ts",
|
|
10
|
-
"test": "
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"test:watch": "jest --watch",
|
|
12
|
+
"test:coverage": "jest --coverage"
|
|
11
13
|
},
|
|
12
|
-
|
|
13
14
|
"publishConfig": {
|
|
14
15
|
"access": "public"
|
|
15
16
|
},
|
|
16
17
|
"repository": {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
},
|
|
20
|
-
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/bharath-arch/chatly-sdk.git"
|
|
20
|
+
},
|
|
21
21
|
"keywords": [
|
|
22
22
|
"chat",
|
|
23
23
|
"sdk",
|
|
@@ -30,10 +30,14 @@
|
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"description": "Production-ready end-to-end encrypted chat SDK with WhatsApp-style features",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"buffer": "^6.0.3"
|
|
33
|
+
"buffer": "^6.0.3",
|
|
34
|
+
"events": "^3.3.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
37
|
+
"@types/jest": "^29.5.14",
|
|
36
38
|
"@types/node": "^24.10.1",
|
|
39
|
+
"jest": "^29.5.0",
|
|
40
|
+
"ts-jest": "^29.1.0",
|
|
37
41
|
"ts-node": "^10.9.2",
|
|
38
42
|
"tsup": "^8.0.0",
|
|
39
43
|
"typescript": "^5.9.3"
|
package/src/chat/ChatSession.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { User } from "../models/user.js";
|
|
2
2
|
import type { Message } from "../models/message.js";
|
|
3
|
+
import type { MediaAttachment } from "../models/mediaTypes.js";
|
|
3
4
|
import { deriveSharedSecret, encryptMessage, decryptMessage } from "../crypto/e2e.js";
|
|
4
5
|
import type { KeyPair } from "../crypto/keys.js";
|
|
5
6
|
import { generateUUID } from "../crypto/uuid.js";
|
|
@@ -68,6 +69,49 @@ export class ChatSession {
|
|
|
68
69
|
};
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Encrypt a media message for this session
|
|
74
|
+
*/
|
|
75
|
+
async encryptMedia(
|
|
76
|
+
plaintext: string,
|
|
77
|
+
media: MediaAttachment,
|
|
78
|
+
senderId: string
|
|
79
|
+
): Promise<Message> {
|
|
80
|
+
if (!this.sharedSecret) {
|
|
81
|
+
await this.initialize();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!this.sharedSecret) {
|
|
85
|
+
throw new Error("Failed to initialize session");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Encrypt the message text (could be caption)
|
|
89
|
+
const { ciphertext, iv } = encryptMessage(plaintext, this.sharedSecret);
|
|
90
|
+
|
|
91
|
+
// Encrypt the media data
|
|
92
|
+
const { ciphertext: encryptedMediaData } = encryptMessage(
|
|
93
|
+
media.data,
|
|
94
|
+
this.sharedSecret
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Create encrypted media attachment
|
|
98
|
+
const encryptedMedia: MediaAttachment = {
|
|
99
|
+
...media,
|
|
100
|
+
data: encryptedMediaData,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
id: generateUUID(),
|
|
105
|
+
senderId,
|
|
106
|
+
receiverId: senderId === this.userA.id ? this.userB.id : this.userA.id,
|
|
107
|
+
ciphertext,
|
|
108
|
+
iv,
|
|
109
|
+
timestamp: Date.now(),
|
|
110
|
+
type: "media",
|
|
111
|
+
media: encryptedMedia,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
71
115
|
/**
|
|
72
116
|
* Decrypt a message in this session
|
|
73
117
|
*/
|
|
@@ -85,4 +129,41 @@ export class ChatSession {
|
|
|
85
129
|
|
|
86
130
|
return decryptMessage(message.ciphertext, message.iv, this.sharedSecret);
|
|
87
131
|
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Decrypt a media message in this session
|
|
135
|
+
*/
|
|
136
|
+
async decryptMedia(message: Message, user: User): Promise<{ text: string; media: MediaAttachment }> {
|
|
137
|
+
if (!message.media) {
|
|
138
|
+
throw new Error("Message does not contain media");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Re-initialize if needed
|
|
142
|
+
if (!this.sharedSecret ||
|
|
143
|
+
(user.id !== this.userA.id && user.id !== this.userB.id)) {
|
|
144
|
+
await this.initializeForUser(user);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!this.sharedSecret) {
|
|
148
|
+
throw new Error("Failed to initialize session");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Decrypt the message text
|
|
152
|
+
const text = decryptMessage(message.ciphertext, message.iv, this.sharedSecret);
|
|
153
|
+
|
|
154
|
+
// Decrypt the media data
|
|
155
|
+
const decryptedMediaData = decryptMessage(
|
|
156
|
+
message.media.data,
|
|
157
|
+
message.iv,
|
|
158
|
+
this.sharedSecret
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Create decrypted media attachment
|
|
162
|
+
const decryptedMedia: MediaAttachment = {
|
|
163
|
+
...message.media,
|
|
164
|
+
data: decryptedMediaData,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
return { text, media: decryptedMedia };
|
|
168
|
+
}
|
|
88
169
|
}
|
package/src/chat/GroupSession.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Group } from "../models/group.js";
|
|
2
2
|
import type { Message } from "../models/message.js";
|
|
3
|
+
import type { MediaAttachment } from "../models/mediaTypes.js";
|
|
3
4
|
import { deriveGroupKey } from "../crypto/group.js";
|
|
4
5
|
import { encryptMessage, decryptMessage } from "../crypto/e2e.js";
|
|
5
6
|
import { base64ToBuffer } from "../crypto/utils.js";
|
|
@@ -43,6 +44,49 @@ export class GroupSession {
|
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Encrypt a media message for this group
|
|
49
|
+
*/
|
|
50
|
+
async encryptMedia(
|
|
51
|
+
plaintext: string,
|
|
52
|
+
media: MediaAttachment,
|
|
53
|
+
senderId: string
|
|
54
|
+
): Promise<Message> {
|
|
55
|
+
if (!this.groupKey) {
|
|
56
|
+
await this.initialize();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!this.groupKey) {
|
|
60
|
+
throw new Error("Failed to initialize group session");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Encrypt the message text (could be caption)
|
|
64
|
+
const { ciphertext, iv } = encryptMessage(plaintext, this.groupKey);
|
|
65
|
+
|
|
66
|
+
// Encrypt the media data
|
|
67
|
+
const { ciphertext: encryptedMediaData } = encryptMessage(
|
|
68
|
+
media.data,
|
|
69
|
+
this.groupKey
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Create encrypted media attachment
|
|
73
|
+
const encryptedMedia: MediaAttachment = {
|
|
74
|
+
...media,
|
|
75
|
+
data: encryptedMediaData,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
id: generateUUID(),
|
|
80
|
+
senderId,
|
|
81
|
+
groupId: this.group.id,
|
|
82
|
+
ciphertext,
|
|
83
|
+
iv,
|
|
84
|
+
timestamp: Date.now(),
|
|
85
|
+
type: "media",
|
|
86
|
+
media: encryptedMedia,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
46
90
|
/**
|
|
47
91
|
* Decrypt a message in this group
|
|
48
92
|
*/
|
|
@@ -57,4 +101,39 @@ export class GroupSession {
|
|
|
57
101
|
|
|
58
102
|
return decryptMessage(message.ciphertext, message.iv, this.groupKey);
|
|
59
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Decrypt a media message in this group
|
|
107
|
+
*/
|
|
108
|
+
async decryptMedia(message: Message): Promise<{ text: string; media: MediaAttachment }> {
|
|
109
|
+
if (!message.media) {
|
|
110
|
+
throw new Error("Message does not contain media");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!this.groupKey) {
|
|
114
|
+
await this.initialize();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!this.groupKey) {
|
|
118
|
+
throw new Error("Failed to initialize group session");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Decrypt the message text
|
|
122
|
+
const text = decryptMessage(message.ciphertext, message.iv, this.groupKey);
|
|
123
|
+
|
|
124
|
+
// Decrypt the media data
|
|
125
|
+
const decryptedMediaData = decryptMessage(
|
|
126
|
+
message.media.data,
|
|
127
|
+
message.iv,
|
|
128
|
+
this.groupKey
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Create decrypted media attachment
|
|
132
|
+
const decryptedMedia: MediaAttachment = {
|
|
133
|
+
...message.media,
|
|
134
|
+
data: decryptedMediaData,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return { text, media: decryptedMedia };
|
|
138
|
+
}
|
|
60
139
|
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Configuration Constants
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Cryptography
|
|
6
|
+
export const SUPPORTED_CURVE = "prime256v1";
|
|
7
|
+
export const ALGORITHM = "aes-256-gcm";
|
|
8
|
+
export const IV_LENGTH = 12; // 96 bits for GCM
|
|
9
|
+
export const SALT_LENGTH = 16;
|
|
10
|
+
export const KEY_LENGTH = 32; // 256 bits
|
|
11
|
+
export const TAG_LENGTH = 16;
|
|
12
|
+
export const PBKDF2_ITERATIONS = 100000;
|
|
13
|
+
|
|
14
|
+
// Validation
|
|
15
|
+
export const USERNAME_MIN_LENGTH = 3;
|
|
16
|
+
export const USERNAME_MAX_LENGTH = 20;
|
|
17
|
+
export const MESSAGE_MAX_LENGTH = 10000;
|
|
18
|
+
export const GROUP_NAME_MAX_LENGTH = 100;
|
|
19
|
+
export const GROUP_MIN_MEMBERS = 2;
|
|
20
|
+
export const GROUP_MAX_MEMBERS = 256;
|
|
21
|
+
|
|
22
|
+
// Transport
|
|
23
|
+
export const RECONNECT_MAX_ATTEMPTS = 5;
|
|
24
|
+
export const RECONNECT_BASE_DELAY = 1000; // 1 second
|
|
25
|
+
export const RECONNECT_MAX_DELAY = 30000; // 30 seconds
|
|
26
|
+
export const HEARTBEAT_INTERVAL = 30000; // 30 seconds
|
|
27
|
+
export const CONNECTION_TIMEOUT = 10000; // 10 seconds
|
|
28
|
+
|
|
29
|
+
// Message Queue
|
|
30
|
+
export const MAX_QUEUE_SIZE = 1000;
|
|
31
|
+
export const MESSAGE_RETRY_ATTEMPTS = 3;
|
|
32
|
+
export const MESSAGE_RETRY_DELAY = 2000; // 2 seconds
|
|
33
|
+
|
|
34
|
+
// Events
|
|
35
|
+
export const EVENTS = {
|
|
36
|
+
MESSAGE_SENT: 'message:sent',
|
|
37
|
+
MESSAGE_RECEIVED: 'message:received',
|
|
38
|
+
MESSAGE_FAILED: 'message:failed',
|
|
39
|
+
CONNECTION_STATE_CHANGED: 'connection:state',
|
|
40
|
+
SESSION_CREATED: 'session:created',
|
|
41
|
+
GROUP_CREATED: 'group:created',
|
|
42
|
+
ERROR: 'error',
|
|
43
|
+
USER_CREATED: 'user:created',
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
// Connection States
|
|
47
|
+
export enum ConnectionState {
|
|
48
|
+
DISCONNECTED = 'disconnected',
|
|
49
|
+
CONNECTING = 'connecting',
|
|
50
|
+
CONNECTED = 'connected',
|
|
51
|
+
RECONNECTING = 'reconnecting',
|
|
52
|
+
FAILED = 'failed',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Message Status
|
|
56
|
+
export enum MessageStatus {
|
|
57
|
+
PENDING = 'pending',
|
|
58
|
+
SENT = 'sent',
|
|
59
|
+
DELIVERED = 'delivered',
|
|
60
|
+
FAILED = 'failed',
|
|
61
|
+
}
|
package/src/crypto/e2e.ts
CHANGED
|
@@ -11,26 +11,6 @@
|
|
|
11
11
|
const TAG_LENGTH = 16;
|
|
12
12
|
const PBKDF2_ITERATIONS = 100000;
|
|
13
13
|
|
|
14
|
-
/**
|
|
15
|
-
* Derive a shared secret using ECDH key exchange
|
|
16
|
-
*/
|
|
17
|
-
// export function deriveSharedSecret(local: KeyPair, remotePublicKey: string): Buffer {
|
|
18
|
-
// const ecdh = createECDH(SUPPORTED_CURVE);
|
|
19
|
-
// ecdh.setPrivateKey(base64ToBuffer(local.privateKey));
|
|
20
|
-
|
|
21
|
-
// const remotePublicKeyBuffer = base64ToBuffer(remotePublicKey);
|
|
22
|
-
// const sharedSecret = ecdh.computeSecret(remotePublicKeyBuffer);
|
|
23
|
-
|
|
24
|
-
// // Derive a symmetric key from the shared secret using PBKDF2
|
|
25
|
-
// // Use a deterministic salt based on both public keys for consistency
|
|
26
|
-
// const salt = Buffer.concat([
|
|
27
|
-
// base64ToBuffer(local.publicKey),
|
|
28
|
-
// base64ToBuffer(remotePublicKey)
|
|
29
|
-
// ]).slice(0, SALT_LENGTH);
|
|
30
|
-
// const derivedKey = pbkdf2Sync(sharedSecret, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
|
|
31
|
-
|
|
32
|
-
// return derivedKey;
|
|
33
|
-
// }
|
|
34
14
|
|
|
35
15
|
export function deriveSharedSecret(local: KeyPair, remotePublicKey: string): Buffer {
|
|
36
16
|
const ecdh = createECDH(SUPPORTED_CURVE);
|