forkoff 1.1.0 โ 1.1.2
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/README.md +27 -11
- package/dist/index.js +8 -0
- package/dist/tools/claude-process.d.ts +5 -0
- package/dist/tools/claude-process.js +44 -0
- package/dist/websocket.js +1 -1
- package/package.json +9 -4
- package/E2EE-COMPLETE.md +0 -290
package/README.md
CHANGED
|
@@ -27,9 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
ForkOff CLI connects [Claude Code](https://claude.ai/code) on your laptop to the ForkOff mobile app, giving you real-time monitoring, interactive approvals, and usage analytics from anywhere.
|
|
29
29
|
|
|
30
|
-
> **Open Source** —
|
|
31
|
-
>
|
|
32
|
-
> **Open Beta** — [Join the iOS TestFlight](https://testflight.apple.com/join/dhh5FrN7)
|
|
30
|
+
> **Open Source** — MIT licensed. Contributions welcome!
|
|
33
31
|
|
|
34
32
|
## Installation
|
|
35
33
|
|
|
@@ -66,29 +64,47 @@ Keep this running to stream sessions to your phone in real-time.
|
|
|
66
64
|
- **Usage analytics** — Track token usage, session counts, and streaks across devices
|
|
67
65
|
- **Multi-device support** — Connect multiple CLI instances, analytics aggregate automatically
|
|
68
66
|
- **Auto-start** — Optionally launch on login so your phone is always connected
|
|
69
|
-
- **
|
|
67
|
+
- **Local relay option** — Run with `--local` for a direct P2P connection without cloud dependency
|
|
70
68
|
|
|
71
69
|
## Commands
|
|
72
70
|
|
|
73
71
|
| Command | Description |
|
|
74
72
|
|---------|-------------|
|
|
75
|
-
| `forkoff pair` | Generate QR code to pair with mobile app |
|
|
76
|
-
| `forkoff connect` |
|
|
73
|
+
| `forkoff pair [--local]` | Generate QR code to pair with mobile app |
|
|
74
|
+
| `forkoff connect [--local]` | Reconnect to ForkOff (for previously paired devices) |
|
|
77
75
|
| `forkoff status` | Check connection status |
|
|
78
76
|
| `forkoff disconnect` | Disconnect and unpair device |
|
|
79
77
|
| `forkoff config` | View/modify configuration |
|
|
80
78
|
| `forkoff startup` | Manage automatic startup on login |
|
|
81
|
-
| `forkoff tools` | Detect AI
|
|
82
|
-
| `forkoff logs` | List
|
|
79
|
+
| `forkoff tools` | Detect AI tools, install/uninstall hooks |
|
|
80
|
+
| `forkoff logs` | List, view, or clean debug logs |
|
|
83
81
|
|
|
84
82
|
### Configuration
|
|
85
83
|
|
|
86
84
|
```bash
|
|
87
85
|
forkoff config --show # Show current config
|
|
88
86
|
forkoff config --name "My MBP" # Set device name
|
|
87
|
+
forkoff config --port 8080 # Set relay server port
|
|
89
88
|
forkoff config --reset # Reset to defaults
|
|
90
89
|
```
|
|
91
90
|
|
|
91
|
+
### Tools
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
forkoff tools --detect # Detect installed AI tools
|
|
95
|
+
forkoff tools --install-hooks # Install ForkOff hooks for Claude Code
|
|
96
|
+
forkoff tools --uninstall-hooks # Remove ForkOff hooks
|
|
97
|
+
forkoff tools --watch # Watch tool status changes
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Logs
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
forkoff logs # List debug log files
|
|
104
|
+
forkoff logs --latest # Print path to most recent log
|
|
105
|
+
forkoff logs --clean # Delete all log files
|
|
106
|
+
```
|
|
107
|
+
|
|
92
108
|
### Global Options
|
|
93
109
|
|
|
94
110
|
| Option | Description |
|
|
@@ -126,7 +142,7 @@ ForkOff uses end-to-end encryption (X25519 ECDH + XSalsa20-Poly1305) so the rela
|
|
|
126
142
|
| **Key storage** | OS keychain (macOS Keychain, Windows Credential Manager, Linux libsecret) |
|
|
127
143
|
| **Enforcement** | 24 sensitive event types encrypted; plaintext fallback only when E2EE unavailable |
|
|
128
144
|
|
|
129
|
-
No additional setup required — E2EE is enabled automatically when you pair.
|
|
145
|
+
No additional setup required — E2EE is enabled automatically when you pair.
|
|
130
146
|
|
|
131
147
|
---
|
|
132
148
|
|
|
@@ -158,7 +174,7 @@ forkoff.sendTerminalOutput(sessionId, '> npm install\nDone', 'stdout');
|
|
|
158
174
|
| Platform | Location |
|
|
159
175
|
|----------|----------|
|
|
160
176
|
| **Windows** | `%APPDATA%\forkoff-cli\config.json` |
|
|
161
|
-
| **macOS** |
|
|
177
|
+
| **macOS** | `~/.config/forkoff-cli/config.json` |
|
|
162
178
|
| **Linux** | `~/.config/forkoff-cli/config.json` |
|
|
163
179
|
|
|
164
180
|
## Development
|
|
@@ -175,7 +191,7 @@ npm test # Run tests
|
|
|
175
191
|
## Requirements
|
|
176
192
|
|
|
177
193
|
- Node.js 18+
|
|
178
|
-
- [ForkOff mobile app](https://
|
|
194
|
+
- [ForkOff mobile app](https://github.com/Forkoff-app/forkoff-react-native) (iOS/Android)
|
|
179
195
|
|
|
180
196
|
## License
|
|
181
197
|
|
package/dist/index.js
CHANGED
|
@@ -454,6 +454,7 @@ function createProgram() {
|
|
|
454
454
|
const spinner = (0, logger_1.createSpinner)('Initializing...').start();
|
|
455
455
|
try {
|
|
456
456
|
tools_1.PermissionIpcManager.cleanupStaleTempFiles();
|
|
457
|
+
tools_1.claudeProcessManager.cleanupAllPermissionState();
|
|
457
458
|
spinner.succeed('Ready!\n');
|
|
458
459
|
// Detect connected tools
|
|
459
460
|
spinner.start('Detecting AI coding tools...');
|
|
@@ -1097,6 +1098,13 @@ function createProgram() {
|
|
|
1097
1098
|
websocket_1.wsClient.on('disconnected', (reason) => {
|
|
1098
1099
|
console.log(chalk_1.default.yellow(`\nMobile disconnected: ${reason}`));
|
|
1099
1100
|
console.log(chalk_1.default.dim('Waiting for mobile to reconnect...'));
|
|
1101
|
+
tools_1.claudeProcessManager.autoAllowAllPendingPrompts();
|
|
1102
|
+
tools_1.claudeProcessManager.cleanupAllPermissionState();
|
|
1103
|
+
tools_1.claudeProcessManager.clearAllTakenOver();
|
|
1104
|
+
});
|
|
1105
|
+
websocket_1.wsClient.on('session_release', (data) => {
|
|
1106
|
+
console.log(chalk_1.default.dim(`[Session] Mobile released session: ${data.sessionKey}`));
|
|
1107
|
+
tools_1.claudeProcessManager.releaseSession(data.sessionKey);
|
|
1100
1108
|
});
|
|
1101
1109
|
websocket_1.wsClient.on('error', (error) => {
|
|
1102
1110
|
console.error(chalk_1.default.red(`Connection error: ${error.message}`));
|
|
@@ -241,6 +241,11 @@ declare class ClaudeProcessManager extends EventEmitter {
|
|
|
241
241
|
* Clear all taken-over sessions (e.g., when mobile disconnects).
|
|
242
242
|
*/
|
|
243
243
|
clearAllTakenOver(): void;
|
|
244
|
+
/**
|
|
245
|
+
* Release a single session โ clean up its hooks and IPC.
|
|
246
|
+
* Called when mobile navigates away from the session screen after taking over.
|
|
247
|
+
*/
|
|
248
|
+
releaseSession(sessionKey: string): void;
|
|
244
249
|
/**
|
|
245
250
|
* Get all pending permission prompts across all IPC managers.
|
|
246
251
|
* Used to sync pending permissions to mobile on take-over.
|
|
@@ -822,6 +822,50 @@ class ClaudeProcessManager extends events_1.EventEmitter {
|
|
|
822
822
|
this.takenOverSessions.clear();
|
|
823
823
|
console.log(`[Claude Process] All taken-over sessions cleared`);
|
|
824
824
|
}
|
|
825
|
+
/**
|
|
826
|
+
* Release a single session โ clean up its hooks and IPC.
|
|
827
|
+
* Called when mobile navigates away from the session screen after taking over.
|
|
828
|
+
*/
|
|
829
|
+
releaseSession(sessionKey) {
|
|
830
|
+
// Find the process by sessionKey or terminalSessionId
|
|
831
|
+
let terminalSessionId;
|
|
832
|
+
let directory;
|
|
833
|
+
for (const [id, info] of this.processes) {
|
|
834
|
+
if (id === sessionKey || info.sessionKey === sessionKey) {
|
|
835
|
+
terminalSessionId = id;
|
|
836
|
+
directory = info.directory;
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
// Also check closed sessions in case the process already exited
|
|
841
|
+
if (!terminalSessionId) {
|
|
842
|
+
for (const [id, info] of this.closedSessions) {
|
|
843
|
+
if (id === sessionKey || info.sessionKey === sessionKey) {
|
|
844
|
+
terminalSessionId = id;
|
|
845
|
+
directory = info.directory;
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
if (terminalSessionId) {
|
|
851
|
+
// Stop permission IPC for this session
|
|
852
|
+
this.stopPermissionIpc(terminalSessionId);
|
|
853
|
+
// Remove hook from directory if no other active sessions use it
|
|
854
|
+
if (directory) {
|
|
855
|
+
const otherSessionsInDir = Array.from(this.processes.values())
|
|
856
|
+
.filter(p => p.directory === directory && p.terminalSessionId !== terminalSessionId);
|
|
857
|
+
if (otherSessionsInDir.length === 0) {
|
|
858
|
+
this.removeHook(directory);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
// Clear taken-over state
|
|
862
|
+
this.takenOverSessions.delete(terminalSessionId);
|
|
863
|
+
console.log(`[Claude Process] Session released: ${terminalSessionId}`);
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
console.log(`[Claude Process] Session release: no matching session found for ${sessionKey}`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
825
869
|
/**
|
|
826
870
|
* Get all pending permission prompts across all IPC managers.
|
|
827
871
|
* Used to sync pending permissions to mobile on take-over.
|
package/dist/websocket.js
CHANGED
|
@@ -89,7 +89,7 @@ const PLAINTEXT_DROP_EVENTS = [
|
|
|
89
89
|
'directory_list', 'transcript_fetch', 'transcript_subscribe',
|
|
90
90
|
'read_file', 'claude_approval_response', 'permission_response',
|
|
91
91
|
'permission_rules_sync', 'claude_abort', 'tab_complete',
|
|
92
|
-
'usage_stats_request', 'sdk_session_history',
|
|
92
|
+
'usage_stats_request', 'sdk_session_history', 'session_release',
|
|
93
93
|
];
|
|
94
94
|
/** Events forwarded from server that do NOT need plaintext-drop checking */
|
|
95
95
|
const PASSTHROUGH_EVENTS = [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forkoff",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "CLI tool to connect
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"description": "CLI tool to connect Claude Code to the ForkOff mobile app for remote monitoring and approvals",
|
|
5
5
|
"main": "dist/integration.js",
|
|
6
6
|
"types": "dist/integration.d.ts",
|
|
7
7
|
"homepage": "https://forkoff.app",
|
|
@@ -39,10 +39,15 @@
|
|
|
39
39
|
"coding",
|
|
40
40
|
"mobile",
|
|
41
41
|
"claude",
|
|
42
|
+
"claude-code",
|
|
42
43
|
"forkoff",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
44
|
+
"remote-control",
|
|
45
|
+
"developer-tools",
|
|
46
|
+
"websocket"
|
|
45
47
|
],
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18.0.0"
|
|
50
|
+
},
|
|
46
51
|
"author": {
|
|
47
52
|
"name": "ForkOff",
|
|
48
53
|
"email": "support@forkoff.app",
|
package/E2EE-COMPLETE.md
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
# โ
E2EE Implementation Complete
|
|
2
|
-
|
|
3
|
-
## ๐ฏ Final Status: PRODUCTION READY
|
|
4
|
-
|
|
5
|
-
**Total Test Coverage: 185 tests passing**
|
|
6
|
-
- Mobile (React Native): 90 tests โ
|
|
7
|
-
- Backend (NestJS): 23 tests โ
|
|
8
|
-
- CLI (Node.js): 72 tests โ
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## ๐ Security Features
|
|
13
|
-
|
|
14
|
-
### Encryption
|
|
15
|
-
- **Algorithm**: X25519 (ECDH) + AES-256-GCM
|
|
16
|
-
- **Key Exchange**: Ephemeral Diffie-Hellman with HKDF key derivation
|
|
17
|
-
- **Authentication**: Authenticated encryption with 16-byte auth tags
|
|
18
|
-
- **Forward Secrecy**: Each session uses unique ephemeral keys
|
|
19
|
-
|
|
20
|
-
### Security Properties
|
|
21
|
-
โ
End-to-end encryption (backend cannot decrypt)
|
|
22
|
-
โ
Perfect forward secrecy (ephemeral keys)
|
|
23
|
-
โ
Authenticated encryption (tamper detection)
|
|
24
|
-
โ
Replay protection (message counters)
|
|
25
|
-
โ
Key rotation support (version tracking)
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## ๐ Network Resilience (IP Change Handling)
|
|
30
|
-
|
|
31
|
-
### Problem
|
|
32
|
-
When a device's IP changes (WiFi โ cellular, network switch), the WebSocket connection drops.
|
|
33
|
-
|
|
34
|
-
### Solution Implemented
|
|
35
|
-
|
|
36
|
-
#### 1. **Persistent Session Storage**
|
|
37
|
-
- Session keys stored to disk: `~/.forkoff-cli/sessions/`
|
|
38
|
-
- Survives process restarts and IP changes
|
|
39
|
-
- Automatic 24-hour expiration
|
|
40
|
-
|
|
41
|
-
#### 2. **Session Restoration API**
|
|
42
|
-
```typescript
|
|
43
|
-
// After reconnection, restore previous E2EE sessions
|
|
44
|
-
await e2eeManager.restorePersistedSession(targetDeviceId);
|
|
45
|
-
|
|
46
|
-
// List all devices with active sessions
|
|
47
|
-
const devices = e2eeManager.listPersistedDevices();
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
#### 3. **Automatic Reconnection Flow**
|
|
51
|
-
1. IP changes โ WebSocket disconnects
|
|
52
|
-
2. WebSocket automatically reconnects (socket.io)
|
|
53
|
-
3. E2EE manager restores persisted sessions
|
|
54
|
-
4. Encrypted communication resumes seamlessly
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## ๐ File Structure
|
|
59
|
-
|
|
60
|
-
### CLI (`forkoff-cli/src/crypto/`)
|
|
61
|
-
```
|
|
62
|
-
crypto/
|
|
63
|
-
โโโ types.ts # Shared E2EE type definitions
|
|
64
|
-
โโโ keyGeneration.ts # X25519 key pair generation (8 tests)
|
|
65
|
-
โโโ keyStorage.ts # OS keychain + in-memory storage (10 tests)
|
|
66
|
-
โโโ encryption.ts # AES-256-GCM encrypt/decrypt (13 tests)
|
|
67
|
-
โโโ keyExchange.ts # ECDH + HKDF key derivation (9 tests)
|
|
68
|
-
โโโ e2eeManager.ts # Orchestration layer (12 tests)
|
|
69
|
-
โโโ sessionPersistence.ts # Disk-based session storage (NEW)
|
|
70
|
-
โโโ websocketE2EE.ts # WebSocket integration (11 tests)
|
|
71
|
-
|
|
72
|
-
__tests__/crypto/
|
|
73
|
-
โโโ keyGeneration.test.ts
|
|
74
|
-
โโโ keyStorage.test.ts
|
|
75
|
-
โโโ encryption.test.ts
|
|
76
|
-
โโโ keyExchange.test.ts
|
|
77
|
-
โโโ e2eeManager.test.ts
|
|
78
|
-
โโโ websocketIntegration.test.ts
|
|
79
|
-
โโโ e2e-integration.test.ts # End-to-end flow verification (9 tests)
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### Backend (`forkoff-api/src/crypto/`)
|
|
83
|
-
```
|
|
84
|
-
crypto/
|
|
85
|
-
โโโ crypto.service.ts # Public key storage & retrieval (9 tests)
|
|
86
|
-
โโโ crypto.controller.ts # REST API endpoints (7 tests)
|
|
87
|
-
โโโ dto/
|
|
88
|
-
|
|
89
|
-
websocket/
|
|
90
|
-
โโโ websocket.gateway.ts # E2EE message forwarding (7 tests)
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Mobile (`forkoff/services/crypto/`)
|
|
94
|
-
```
|
|
95
|
-
crypto/
|
|
96
|
-
โโโ keyGeneration.ts # Key pair generation
|
|
97
|
-
โโโ keyStorage.ts # Secure store + session keys
|
|
98
|
-
โโโ encryption.ts # AES-256-GCM encryption
|
|
99
|
-
โโโ keyExchange.ts # X25519 key exchange
|
|
100
|
-
โโโ e2eeManager.ts # E2EE orchestration
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## ๐ How It Works
|
|
106
|
-
|
|
107
|
-
### Initial Setup
|
|
108
|
-
1. **Device registers**: Generates X25519 key pair, stores private key in OS keychain
|
|
109
|
-
2. **Upload public key**: Sends public key to backend API
|
|
110
|
-
3. **Backend stores**: Public key stored in PostgreSQL (Device table)
|
|
111
|
-
|
|
112
|
-
### Key Exchange Flow
|
|
113
|
-
```
|
|
114
|
-
Mobile Backend CLI
|
|
115
|
-
| | |
|
|
116
|
-
|--[init: ephemeral_pk]--->|---[forward]------------>|
|
|
117
|
-
| | |
|
|
118
|
-
|<-[ack: ephemeral_pk]-----|<--[forward]-------------|
|
|
119
|
-
| | |
|
|
120
|
-
Both sides derive shared secret using ECDH
|
|
121
|
-
Both sides derive session key using HKDF
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Encrypted Messaging
|
|
125
|
-
```
|
|
126
|
-
Mobile Backend CLI
|
|
127
|
-
| | |
|
|
128
|
-
|--[encrypted_message]---->|---[forward]------------>|
|
|
129
|
-
| {ciphertext, nonce, | |
|
|
130
|
-
| authTag, counter} | |
|
|
131
|
-
| | |
|
|
132
|
-
|<-[encrypted_message]-----|<--[forward]-------------|
|
|
133
|
-
| | |
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Reconnection After IP Change
|
|
137
|
-
```
|
|
138
|
-
CLI Reconnects Backend Mobile
|
|
139
|
-
| | |
|
|
140
|
-
| WebSocket reconnects | |
|
|
141
|
-
|<--------------------------->| |
|
|
142
|
-
| | |
|
|
143
|
-
| Restore persisted sessions | |
|
|
144
|
-
| (load from disk) | |
|
|
145
|
-
| | |
|
|
146
|
-
|--[encrypted_message]------->|---[forward]---------->|
|
|
147
|
-
| Communication resumes! | |
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## ๐งช Test Verification
|
|
153
|
-
|
|
154
|
-
### Unit Tests (Crypto Primitives)
|
|
155
|
-
- โ
Key generation produces valid X25519 keys
|
|
156
|
-
- โ
Keys are properly Base64-encoded
|
|
157
|
-
- โ
Encryption/decryption round-trip works
|
|
158
|
-
- โ
Unicode and emoji preserved
|
|
159
|
-
- โ
Large messages (10KB+) supported
|
|
160
|
-
- โ
ECDH produces matching shared secrets
|
|
161
|
-
- โ
HKDF derives correct session keys
|
|
162
|
-
|
|
163
|
-
### Integration Tests (E2EE Flow)
|
|
164
|
-
- โ
Full bidirectional encrypted communication
|
|
165
|
-
- โ
Key exchange completes successfully
|
|
166
|
-
- โ
Messages encrypted/decrypted correctly
|
|
167
|
-
- โ
Replay attacks detected and rejected
|
|
168
|
-
- โ
Tampered messages rejected
|
|
169
|
-
- โ
Multiple concurrent sessions supported
|
|
170
|
-
- โ
Session persistence across reconnections
|
|
171
|
-
|
|
172
|
-
### Security Tests
|
|
173
|
-
- โ
Tampered ciphertext rejected
|
|
174
|
-
- โ
Tampered nonce rejected
|
|
175
|
-
- โ
Tampered auth tag rejected
|
|
176
|
-
- โ
Wrong key cannot decrypt
|
|
177
|
-
- โ
Message counters prevent replay attacks
|
|
178
|
-
|
|
179
|
-
---
|
|
180
|
-
|
|
181
|
-
## ๐ Usage Example
|
|
182
|
-
|
|
183
|
-
### Initialize E2EE
|
|
184
|
-
```typescript
|
|
185
|
-
import { E2EEManager } from './crypto/e2eeManager';
|
|
186
|
-
import { WebSocketE2EEIntegration } from './crypto/websocketE2EE';
|
|
187
|
-
|
|
188
|
-
// Initialize E2EE manager
|
|
189
|
-
const e2ee = new E2EEManager(deviceId, apiUrl, authToken);
|
|
190
|
-
await e2ee.initialize();
|
|
191
|
-
|
|
192
|
-
// Integrate with WebSocket
|
|
193
|
-
const wsIntegration = new WebSocketE2EEIntegration(wsClient);
|
|
194
|
-
await wsIntegration.initialize(deviceId, apiUrl, authToken);
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Send Encrypted Message
|
|
198
|
-
```typescript
|
|
199
|
-
// Initiate key exchange (if not already done)
|
|
200
|
-
if (!e2ee.hasSessionKey(targetDeviceId)) {
|
|
201
|
-
await wsIntegration.initiateKeyExchange(targetDeviceId);
|
|
202
|
-
// Wait for key exchange to complete...
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Send encrypted message
|
|
206
|
-
wsIntegration.sendEncryptedMessage(
|
|
207
|
-
'Secret message',
|
|
208
|
-
targetDeviceId,
|
|
209
|
-
sessionId
|
|
210
|
-
);
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### Handle Reconnection After IP Change
|
|
214
|
-
```typescript
|
|
215
|
-
wsClient.on('reconnect', async () => {
|
|
216
|
-
// Restore all persisted sessions
|
|
217
|
-
const devices = e2ee.listPersistedDevices();
|
|
218
|
-
|
|
219
|
-
for (const deviceId of devices) {
|
|
220
|
-
const restored = await e2ee.restorePersistedSession(deviceId);
|
|
221
|
-
if (restored) {
|
|
222
|
-
console.log(`Restored E2EE session with ${deviceId}`);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## ๐ Performance Characteristics
|
|
231
|
-
|
|
232
|
-
- **Key Generation**: ~50ms (X25519)
|
|
233
|
-
- **Encryption**: <5ms (AES-256-GCM, typical message)
|
|
234
|
-
- **Decryption**: <5ms (AES-256-GCM, typical message)
|
|
235
|
-
- **Key Exchange**: ~100ms (includes API calls)
|
|
236
|
-
- **Session Restoration**: <10ms (load from disk)
|
|
237
|
-
|
|
238
|
-
---
|
|
239
|
-
|
|
240
|
-
## ๐ง Configuration
|
|
241
|
-
|
|
242
|
-
### Session Expiration
|
|
243
|
-
Sessions automatically expire after 24 hours. Configurable in `sessionPersistence.ts`:
|
|
244
|
-
```typescript
|
|
245
|
-
const hoursSinceCreation = (now.getTime() - timestamp.getTime()) / (1000 * 60 * 60);
|
|
246
|
-
if (hoursSinceCreation > 24) { // Change this value
|
|
247
|
-
// Session expired
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### Storage Location
|
|
252
|
-
Sessions stored at: `~/.forkoff-cli/sessions/`
|
|
253
|
-
|
|
254
|
-
Private keys stored in OS keychain via `keytar`
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
## ๐ What We Learned
|
|
259
|
-
|
|
260
|
-
1. **TDD Works**: Writing tests first caught numerous edge cases
|
|
261
|
-
2. **Message Counters Matter**: Replay protection is crucial
|
|
262
|
-
3. **Persistence Is Hard**: Mocking file system for tests is complex
|
|
263
|
-
4. **Network Resilience**: IP changes are common, plan for them
|
|
264
|
-
5. **Security Trade-offs**: Perfect forward secrecy vs. session resumption
|
|
265
|
-
|
|
266
|
-
---
|
|
267
|
-
|
|
268
|
-
## โจ Future Enhancements (Not Implemented)
|
|
269
|
-
|
|
270
|
-
- [ ] Double Ratchet Algorithm (Signal Protocol)
|
|
271
|
-
- [ ] Group messaging encryption
|
|
272
|
-
- [ ] Key rotation on schedule
|
|
273
|
-
- [ ] Post-quantum cryptography (Kyber)
|
|
274
|
-
- [ ] Hardware security module integration
|
|
275
|
-
- [ ] Encrypted file transfers
|
|
276
|
-
|
|
277
|
-
---
|
|
278
|
-
|
|
279
|
-
## ๐ References
|
|
280
|
-
|
|
281
|
-
- X25519: RFC 7748
|
|
282
|
-
- AES-GCM: NIST SP 800-38D
|
|
283
|
-
- HKDF: RFC 5869
|
|
284
|
-
- Signal Protocol: https://signal.org/docs/
|
|
285
|
-
|
|
286
|
-
---
|
|
287
|
-
|
|
288
|
-
**Built with โค๏ธ using Test-Driven Development**
|
|
289
|
-
|
|
290
|
-
**Status**: โ
PRODUCTION READY - All 185 tests passing
|