forkoff 1.0.8 → 1.0.10
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/E2EE-COMPLETE.md +290 -0
- package/README.md +11 -0
- package/dist/__tests__/crypto/e2e-integration.test.d.ts +17 -0
- package/dist/__tests__/crypto/e2e-integration.test.d.ts.map +1 -0
- package/dist/__tests__/crypto/e2e-integration.test.js +338 -0
- package/dist/__tests__/crypto/e2e-integration.test.js.map +1 -0
- package/dist/__tests__/crypto/e2eeManager.test.d.ts +2 -0
- package/dist/__tests__/crypto/e2eeManager.test.d.ts.map +1 -0
- package/dist/__tests__/crypto/e2eeManager.test.js +242 -0
- package/dist/__tests__/crypto/e2eeManager.test.js.map +1 -0
- package/dist/__tests__/crypto/encryption.test.d.ts +2 -0
- package/dist/__tests__/crypto/encryption.test.d.ts.map +1 -0
- package/dist/__tests__/crypto/encryption.test.js +116 -0
- package/dist/__tests__/crypto/encryption.test.js.map +1 -0
- package/dist/__tests__/crypto/keyExchange.test.d.ts +2 -0
- package/dist/__tests__/crypto/keyExchange.test.d.ts.map +1 -0
- package/dist/__tests__/crypto/keyExchange.test.js +84 -0
- package/dist/__tests__/crypto/keyExchange.test.js.map +1 -0
- package/dist/__tests__/crypto/keyGeneration.test.d.ts +2 -0
- package/dist/__tests__/crypto/keyGeneration.test.d.ts.map +1 -0
- package/dist/__tests__/crypto/keyGeneration.test.js +61 -0
- package/dist/__tests__/crypto/keyGeneration.test.js.map +1 -0
- package/dist/__tests__/crypto/keyStorage.test.d.ts +2 -0
- package/dist/__tests__/crypto/keyStorage.test.d.ts.map +1 -0
- package/dist/__tests__/crypto/keyStorage.test.js +133 -0
- package/dist/__tests__/crypto/keyStorage.test.js.map +1 -0
- package/dist/__tests__/crypto/websocketIntegration.test.d.ts +2 -0
- package/dist/__tests__/crypto/websocketIntegration.test.d.ts.map +1 -0
- package/dist/__tests__/crypto/websocketIntegration.test.js +259 -0
- package/dist/__tests__/crypto/websocketIntegration.test.js.map +1 -0
- package/dist/__tests__/tools/claude-process.test.d.ts +8 -0
- package/dist/__tests__/tools/claude-process.test.d.ts.map +1 -0
- package/dist/__tests__/tools/claude-process.test.js +224 -0
- package/dist/__tests__/tools/claude-process.test.js.map +1 -0
- package/dist/crypto/e2eeManager.d.ts +82 -0
- package/dist/crypto/e2eeManager.d.ts.map +1 -0
- package/dist/crypto/e2eeManager.js +270 -0
- package/dist/crypto/e2eeManager.js.map +1 -0
- package/dist/crypto/encryption.d.ts +19 -0
- package/dist/crypto/encryption.d.ts.map +1 -0
- package/dist/crypto/encryption.js +111 -0
- package/dist/crypto/encryption.js.map +1 -0
- package/dist/crypto/keyExchange.d.ts +24 -0
- package/dist/crypto/keyExchange.d.ts.map +1 -0
- package/dist/crypto/keyExchange.js +119 -0
- package/dist/crypto/keyExchange.js.map +1 -0
- package/dist/crypto/keyGeneration.d.ts +18 -0
- package/dist/crypto/keyGeneration.d.ts.map +1 -0
- package/dist/crypto/keyGeneration.js +99 -0
- package/dist/crypto/keyGeneration.js.map +1 -0
- package/dist/crypto/keyStorage.d.ts +39 -0
- package/dist/crypto/keyStorage.d.ts.map +1 -0
- package/dist/crypto/keyStorage.js +117 -0
- package/dist/crypto/keyStorage.js.map +1 -0
- package/dist/crypto/sessionPersistence.d.ts +33 -0
- package/dist/crypto/sessionPersistence.d.ts.map +1 -0
- package/dist/crypto/sessionPersistence.js +173 -0
- package/dist/crypto/sessionPersistence.js.map +1 -0
- package/dist/crypto/types.d.ts +35 -0
- package/dist/crypto/types.d.ts.map +1 -0
- package/dist/crypto/types.js +8 -0
- package/dist/crypto/types.js.map +1 -0
- package/dist/crypto/websocketE2EE.d.ts +47 -0
- package/dist/crypto/websocketE2EE.d.ts.map +1 -0
- package/dist/crypto/websocketE2EE.js +144 -0
- package/dist/crypto/websocketE2EE.js.map +1 -0
- package/dist/index.js +103 -2
- package/dist/index.js.map +1 -1
- package/dist/tools/claude-process.d.ts +19 -3
- package/dist/tools/claude-process.d.ts.map +1 -1
- package/dist/tools/claude-process.js +107 -164
- package/dist/tools/claude-process.js.map +1 -1
- package/dist/websocket.d.ts +33 -1
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +30 -1
- package/dist/websocket.js.map +1 -1
- package/jest.config.js +15 -0
- package/package.json +9 -2
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.persistSessionKey = persistSessionKey;
|
|
37
|
+
exports.loadPersistedSessionKey = loadPersistedSessionKey;
|
|
38
|
+
exports.deletePersistedSession = deletePersistedSession;
|
|
39
|
+
exports.deleteAllPersistedSessions = deleteAllPersistedSessions;
|
|
40
|
+
exports.listPersistedSessions = listPersistedSessions;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
/**
|
|
45
|
+
* Session Persistence
|
|
46
|
+
* Stores session keys to disk so they survive reconnections after IP changes
|
|
47
|
+
*/
|
|
48
|
+
const SESSION_STORE_DIR = path.join(os.homedir(), '.forkoff-cli', 'sessions');
|
|
49
|
+
/**
|
|
50
|
+
* Ensures the session store directory exists
|
|
51
|
+
*/
|
|
52
|
+
function ensureSessionStoreExists() {
|
|
53
|
+
if (!fs.existsSync(SESSION_STORE_DIR)) {
|
|
54
|
+
fs.mkdirSync(SESSION_STORE_DIR, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Gets the file path for a device's session
|
|
59
|
+
*/
|
|
60
|
+
function getSessionFilePath(deviceId, targetDeviceId) {
|
|
61
|
+
return path.join(SESSION_STORE_DIR, `${deviceId}-${targetDeviceId}.json`);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Persists a session key to disk
|
|
65
|
+
* @param deviceId - Current device ID
|
|
66
|
+
* @param targetDeviceId - Target device ID
|
|
67
|
+
* @param sessionKeys - Session encryption keys
|
|
68
|
+
*/
|
|
69
|
+
function persistSessionKey(deviceId, targetDeviceId, sessionKeys) {
|
|
70
|
+
try {
|
|
71
|
+
ensureSessionStoreExists();
|
|
72
|
+
const data = {
|
|
73
|
+
encryptionKey: Array.from(sessionKeys.encryptionKey), // Convert Uint8Array to Array for JSON
|
|
74
|
+
sessionId: sessionKeys.sessionId,
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
76
|
+
};
|
|
77
|
+
const filePath = getSessionFilePath(deviceId, targetDeviceId);
|
|
78
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
console.error('Failed to persist session key:', error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Loads a persisted session key from disk
|
|
86
|
+
* @param deviceId - Current device ID
|
|
87
|
+
* @param targetDeviceId - Target device ID
|
|
88
|
+
* @returns SessionKeys or null if not found
|
|
89
|
+
*/
|
|
90
|
+
function loadPersistedSessionKey(deviceId, targetDeviceId) {
|
|
91
|
+
try {
|
|
92
|
+
const filePath = getSessionFilePath(deviceId, targetDeviceId);
|
|
93
|
+
if (!fs.existsSync(filePath)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
97
|
+
// Check if session is expired (older than 24 hours)
|
|
98
|
+
const timestamp = new Date(data.timestamp);
|
|
99
|
+
const now = new Date();
|
|
100
|
+
const hoursSinceCreation = (now.getTime() - timestamp.getTime()) / (1000 * 60 * 60);
|
|
101
|
+
if (hoursSinceCreation > 24) {
|
|
102
|
+
// Session expired, delete it
|
|
103
|
+
fs.unlinkSync(filePath);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
encryptionKey: new Uint8Array(data.encryptionKey),
|
|
108
|
+
sessionId: data.sessionId,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.error('Failed to load persisted session key:', error);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Deletes a persisted session
|
|
118
|
+
* @param deviceId - Current device ID
|
|
119
|
+
* @param targetDeviceId - Target device ID
|
|
120
|
+
*/
|
|
121
|
+
function deletePersistedSession(deviceId, targetDeviceId) {
|
|
122
|
+
try {
|
|
123
|
+
const filePath = getSessionFilePath(deviceId, targetDeviceId);
|
|
124
|
+
if (fs.existsSync(filePath)) {
|
|
125
|
+
fs.unlinkSync(filePath);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error('Failed to delete persisted session:', error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Deletes all persisted sessions for a device
|
|
134
|
+
* @param deviceId - Current device ID
|
|
135
|
+
*/
|
|
136
|
+
function deleteAllPersistedSessions(deviceId) {
|
|
137
|
+
try {
|
|
138
|
+
if (!fs.existsSync(SESSION_STORE_DIR)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const files = fs.readdirSync(SESSION_STORE_DIR);
|
|
142
|
+
for (const file of files) {
|
|
143
|
+
if (file.startsWith(`${deviceId}-`)) {
|
|
144
|
+
fs.unlinkSync(path.join(SESSION_STORE_DIR, file));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
console.error('Failed to delete all persisted sessions:', error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Lists all persisted sessions for a device
|
|
154
|
+
* @param deviceId - Current device ID
|
|
155
|
+
* @returns Array of target device IDs with active sessions
|
|
156
|
+
*/
|
|
157
|
+
function listPersistedSessions(deviceId) {
|
|
158
|
+
try {
|
|
159
|
+
if (!fs.existsSync(SESSION_STORE_DIR)) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
const files = fs.readdirSync(SESSION_STORE_DIR);
|
|
163
|
+
const prefix = `${deviceId}-`;
|
|
164
|
+
return files
|
|
165
|
+
.filter((file) => file.startsWith(prefix) && file.endsWith('.json'))
|
|
166
|
+
.map((file) => file.substring(prefix.length, file.length - 5)); // Remove prefix and .json
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.error('Failed to list persisted sessions:', error);
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=sessionPersistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionPersistence.js","sourceRoot":"","sources":["../../src/crypto/sessionPersistence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2EE Type Definitions for ForkOff CLI
|
|
3
|
+
*
|
|
4
|
+
* X25519 key exchange + AES-256-GCM encryption
|
|
5
|
+
*/
|
|
6
|
+
export interface E2EEKeyPair {
|
|
7
|
+
publicKey: string;
|
|
8
|
+
privateKey: string;
|
|
9
|
+
}
|
|
10
|
+
export interface EncryptedPayload {
|
|
11
|
+
ciphertext: string;
|
|
12
|
+
nonce: string;
|
|
13
|
+
authTag: string;
|
|
14
|
+
}
|
|
15
|
+
export interface EncryptedMessage {
|
|
16
|
+
senderDeviceId: string;
|
|
17
|
+
recipientDeviceId: string;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
payload: EncryptedPayload;
|
|
20
|
+
messageCounter: number;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SessionKeys {
|
|
24
|
+
encryptionKey: Uint8Array;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
}
|
|
27
|
+
export interface KeyExchangeInit {
|
|
28
|
+
senderDeviceId: string;
|
|
29
|
+
ephemeralPublicKey: string;
|
|
30
|
+
}
|
|
31
|
+
export interface KeyExchangeAck {
|
|
32
|
+
recipientDeviceId: string;
|
|
33
|
+
ephemeralPublicKey: string;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/crypto/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,UAAU,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;CAC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/crypto/types.ts"],"names":[],"mappings":";AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { WebSocketClient } from '../websocket';
|
|
2
|
+
/**
|
|
3
|
+
* WebSocket E2EE Integration
|
|
4
|
+
* Adds end-to-end encryption capabilities to WebSocket client
|
|
5
|
+
*/
|
|
6
|
+
export declare class WebSocketE2EEIntegration {
|
|
7
|
+
private wsClient;
|
|
8
|
+
private e2eeManager;
|
|
9
|
+
private enabled;
|
|
10
|
+
constructor(wsClient: WebSocketClient);
|
|
11
|
+
/**
|
|
12
|
+
* Initializes E2EE and sets up event handlers
|
|
13
|
+
*/
|
|
14
|
+
initialize(deviceId: string, apiUrl: string, authToken: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Sets up WebSocket event handlers for E2EE
|
|
17
|
+
*/
|
|
18
|
+
private setupEventHandlers;
|
|
19
|
+
/**
|
|
20
|
+
* Initiates key exchange with a target device
|
|
21
|
+
*/
|
|
22
|
+
initiateKeyExchange(targetDeviceId: string): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Encrypts and sends a message to a target device
|
|
25
|
+
*/
|
|
26
|
+
sendEncryptedMessage(plaintext: string, targetDeviceId: string, sessionId: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Checks if E2EE session exists for a device
|
|
29
|
+
*/
|
|
30
|
+
hasSession(deviceId: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Checks if E2EE is enabled
|
|
33
|
+
*/
|
|
34
|
+
isEnabled(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Cleans up E2EE sessions
|
|
37
|
+
*/
|
|
38
|
+
cleanup(): void;
|
|
39
|
+
private emitKeyExchangeInit;
|
|
40
|
+
private emitKeyExchangeAck;
|
|
41
|
+
private emitEncryptedMessage;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Factory function to create E2EE-enabled WebSocket client
|
|
45
|
+
*/
|
|
46
|
+
export declare function createE2EEWebSocketClient(wsClient: WebSocketClient, deviceId: string, apiUrl: string, authToken: string): Promise<WebSocketE2EEIntegration>;
|
|
47
|
+
//# sourceMappingURL=websocketE2EE.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocketE2EE.d.ts","sourceRoot":"","sources":["../../src/crypto/websocketE2EE.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAI/C;;;GAGG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,OAAO,CAAS;gBAEZ,QAAQ,EAAE,eAAe;IAIrC;;OAEG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpF;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA6D1B;;OAEG;IACG,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhE;;OAEG;IACH,oBAAoB,CAClB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,GAChB,IAAI;IAqBP;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIrC;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,oBAAoB;CAG7B;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,wBAAwB,CAAC,CAGnC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebSocketE2EEIntegration = void 0;
|
|
4
|
+
exports.createE2EEWebSocketClient = createE2EEWebSocketClient;
|
|
5
|
+
const e2eeManager_1 = require("./e2eeManager");
|
|
6
|
+
/**
|
|
7
|
+
* WebSocket E2EE Integration
|
|
8
|
+
* Adds end-to-end encryption capabilities to WebSocket client
|
|
9
|
+
*/
|
|
10
|
+
class WebSocketE2EEIntegration {
|
|
11
|
+
constructor(wsClient) {
|
|
12
|
+
this.e2eeManager = null;
|
|
13
|
+
this.enabled = false;
|
|
14
|
+
this.wsClient = wsClient;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Initializes E2EE and sets up event handlers
|
|
18
|
+
*/
|
|
19
|
+
async initialize(deviceId, apiUrl, authToken) {
|
|
20
|
+
this.e2eeManager = new e2eeManager_1.E2EEManager(deviceId, apiUrl, authToken);
|
|
21
|
+
await this.e2eeManager.initialize();
|
|
22
|
+
this.setupEventHandlers();
|
|
23
|
+
this.enabled = true;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sets up WebSocket event handlers for E2EE
|
|
27
|
+
*/
|
|
28
|
+
setupEventHandlers() {
|
|
29
|
+
if (!this.e2eeManager)
|
|
30
|
+
return;
|
|
31
|
+
// Handle incoming key exchange init
|
|
32
|
+
this.wsClient.on('encrypted_key_exchange_init', async (data) => {
|
|
33
|
+
if (!this.e2eeManager)
|
|
34
|
+
return;
|
|
35
|
+
try {
|
|
36
|
+
// Handle key exchange and send ack
|
|
37
|
+
const ackPayload = await this.e2eeManager.handleKeyExchangeInit(data.senderDeviceId, data.ephemeralPublicKey);
|
|
38
|
+
// Emit ack back to sender
|
|
39
|
+
this.emitKeyExchangeAck(data.senderDeviceId, ackPayload.ephemeralPublicKey);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error('[E2EE] Failed to handle key exchange init:', error);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
// Handle incoming key exchange ack
|
|
46
|
+
this.wsClient.on('encrypted_key_exchange_ack', async (data) => {
|
|
47
|
+
if (!this.e2eeManager)
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
await this.e2eeManager.handleKeyExchangeAck(data.senderDeviceId, data.ephemeralPublicKey);
|
|
51
|
+
console.log(`[E2EE] Key exchange completed with ${data.senderDeviceId}`);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error('[E2EE] Failed to handle key exchange ack:', error);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
// Handle incoming encrypted messages
|
|
58
|
+
this.wsClient.on('encrypted_message', (data) => {
|
|
59
|
+
if (!this.e2eeManager)
|
|
60
|
+
return;
|
|
61
|
+
try {
|
|
62
|
+
const plaintext = this.e2eeManager.decryptMessage(data, data.senderDeviceId);
|
|
63
|
+
// Emit decrypted message as a regular user_message event
|
|
64
|
+
this.wsClient.emit('decrypted_message', {
|
|
65
|
+
...data,
|
|
66
|
+
decryptedContent: plaintext,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error('[E2EE] Failed to decrypt message:', error);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Initiates key exchange with a target device
|
|
76
|
+
*/
|
|
77
|
+
async initiateKeyExchange(targetDeviceId) {
|
|
78
|
+
if (!this.e2eeManager) {
|
|
79
|
+
throw new Error('E2EE not initialized');
|
|
80
|
+
}
|
|
81
|
+
const initPayload = await this.e2eeManager.initiateKeyExchange(targetDeviceId);
|
|
82
|
+
// Emit key exchange init via WebSocket
|
|
83
|
+
this.emitKeyExchangeInit(targetDeviceId, initPayload.ephemeralPublicKey);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Encrypts and sends a message to a target device
|
|
87
|
+
*/
|
|
88
|
+
sendEncryptedMessage(plaintext, targetDeviceId, sessionId) {
|
|
89
|
+
if (!this.e2eeManager) {
|
|
90
|
+
throw new Error('E2EE not initialized');
|
|
91
|
+
}
|
|
92
|
+
if (!this.e2eeManager.hasSessionKey(targetDeviceId)) {
|
|
93
|
+
throw new Error(`No E2EE session with ${targetDeviceId}. Must complete key exchange first.`);
|
|
94
|
+
}
|
|
95
|
+
const encryptedMessage = this.e2eeManager.encryptMessage(plaintext, targetDeviceId, sessionId);
|
|
96
|
+
// Emit encrypted message via WebSocket
|
|
97
|
+
this.emitEncryptedMessage(encryptedMessage);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Checks if E2EE session exists for a device
|
|
101
|
+
*/
|
|
102
|
+
hasSession(deviceId) {
|
|
103
|
+
return this.e2eeManager?.hasSessionKey(deviceId) ?? false;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Checks if E2EE is enabled
|
|
107
|
+
*/
|
|
108
|
+
isEnabled() {
|
|
109
|
+
return this.enabled && this.e2eeManager !== null;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Cleans up E2EE sessions
|
|
113
|
+
*/
|
|
114
|
+
cleanup() {
|
|
115
|
+
this.e2eeManager?.cleanup();
|
|
116
|
+
}
|
|
117
|
+
// Private WebSocket emit methods
|
|
118
|
+
emitKeyExchangeInit(recipientDeviceId, ephemeralPublicKey) {
|
|
119
|
+
this.wsClient.emitKeyExchangeInit({
|
|
120
|
+
recipientDeviceId,
|
|
121
|
+
senderDeviceId: this.e2eeManager?.['deviceId'] || '',
|
|
122
|
+
ephemeralPublicKey,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
emitKeyExchangeAck(recipientDeviceId, ephemeralPublicKey) {
|
|
126
|
+
this.wsClient.emitKeyExchangeAck({
|
|
127
|
+
recipientDeviceId,
|
|
128
|
+
senderDeviceId: this.e2eeManager?.['deviceId'] || '',
|
|
129
|
+
ephemeralPublicKey,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
emitEncryptedMessage(message) {
|
|
133
|
+
this.wsClient.emitEncryptedMessage(message);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.WebSocketE2EEIntegration = WebSocketE2EEIntegration;
|
|
137
|
+
/**
|
|
138
|
+
* Factory function to create E2EE-enabled WebSocket client
|
|
139
|
+
*/
|
|
140
|
+
function createE2EEWebSocketClient(wsClient, deviceId, apiUrl, authToken) {
|
|
141
|
+
const integration = new WebSocketE2EEIntegration(wsClient);
|
|
142
|
+
return integration.initialize(deviceId, apiUrl, authToken).then(() => integration);
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=websocketE2EE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocketE2EE.js","sourceRoot":"","sources":["../../src/crypto/websocketE2EE.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -542,9 +542,14 @@ async function startConnection() {
|
|
|
542
542
|
websocket_1.wsClient.on('claude_start_session', async (data) => {
|
|
543
543
|
console.log(chalk_1.default.cyan(`[Claude] Start session request: ${data.directory}`));
|
|
544
544
|
try {
|
|
545
|
-
const result = await tools_1.claudeProcessManager.startSession(data.directory, data.terminalSessionId);
|
|
545
|
+
const result = await tools_1.claudeProcessManager.startSession(data.directory, data.terminalSessionId, data.dangerouslySkipPermissions);
|
|
546
546
|
websocket_1.wsClient.sendToolStatusUpdate('claude_code', 'active');
|
|
547
547
|
websocket_1.wsClient.sendTerminalCwd({ terminalSessionId: data.terminalSessionId, cwd: result.cwd });
|
|
548
|
+
// Notify mobile that the session is ready for input
|
|
549
|
+
websocket_1.wsClient.sendClaudeSessionEvent({
|
|
550
|
+
sessionKey: data.terminalSessionId,
|
|
551
|
+
event: { type: 'ready' },
|
|
552
|
+
});
|
|
548
553
|
console.log(chalk_1.default.green(`[Claude] Session started: ${data.terminalSessionId}`));
|
|
549
554
|
}
|
|
550
555
|
catch (error) {
|
|
@@ -570,7 +575,7 @@ async function startConnection() {
|
|
|
570
575
|
}
|
|
571
576
|
resolvedDir = path.resolve(resolvedDir);
|
|
572
577
|
// Register session info for later use when message is sent (don't spawn yet)
|
|
573
|
-
tools_1.claudeProcessManager.registerSession(data.sessionKey, resolvedDir, data.terminalSessionId);
|
|
578
|
+
tools_1.claudeProcessManager.registerSession(data.sessionKey, resolvedDir, data.terminalSessionId, data.dangerouslySkipPermissions);
|
|
574
579
|
websocket_1.wsClient.sendToolStatusUpdate('claude_code', 'active');
|
|
575
580
|
websocket_1.wsClient.sendClaudeSessionUpdate({
|
|
576
581
|
sessionKey: data.sessionKey,
|
|
@@ -618,6 +623,79 @@ async function startConnection() {
|
|
|
618
623
|
websocket_1.wsClient.sendDirectoryListResponse({ requestId: data.requestId, entries: [], currentPath: data.path });
|
|
619
624
|
}
|
|
620
625
|
});
|
|
626
|
+
// Handle read file requests from mobile (e.g., CLAUDE.md)
|
|
627
|
+
websocket_1.wsClient.on('read_file', async (data) => {
|
|
628
|
+
console.log(chalk_1.default.dim(`[File] Read request: ${data.filePath}`));
|
|
629
|
+
try {
|
|
630
|
+
// SECURITY: Whitelist of allowed filenames
|
|
631
|
+
const allowedFiles = ['CLAUDE.md', 'README.md', 'package.json'];
|
|
632
|
+
const fileName = path.basename(data.filePath);
|
|
633
|
+
if (!allowedFiles.includes(fileName)) {
|
|
634
|
+
console.warn(chalk_1.default.yellow(`[File] Access denied - file not in whitelist: ${fileName}`));
|
|
635
|
+
websocket_1.wsClient.sendReadFileResponse({
|
|
636
|
+
requestId: data.requestId,
|
|
637
|
+
exists: false,
|
|
638
|
+
fileName,
|
|
639
|
+
error: 'File not allowed',
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
// Resolve path
|
|
644
|
+
let resolvedPath = data.filePath;
|
|
645
|
+
if (resolvedPath === '~' || resolvedPath.startsWith('~/')) {
|
|
646
|
+
resolvedPath = resolvedPath === '~' ? os.homedir() : resolvedPath.replace('~', os.homedir());
|
|
647
|
+
}
|
|
648
|
+
resolvedPath = path.resolve(resolvedPath);
|
|
649
|
+
// SECURITY: Only allow access under home directory
|
|
650
|
+
const homeDir = os.homedir();
|
|
651
|
+
if (!resolvedPath.startsWith(homeDir)) {
|
|
652
|
+
console.warn(chalk_1.default.yellow(`[File] Access denied - path outside home directory: ${resolvedPath}`));
|
|
653
|
+
websocket_1.wsClient.sendReadFileResponse({
|
|
654
|
+
requestId: data.requestId,
|
|
655
|
+
exists: false,
|
|
656
|
+
fileName,
|
|
657
|
+
error: 'Path outside home directory',
|
|
658
|
+
});
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
// Check if file exists
|
|
662
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
663
|
+
websocket_1.wsClient.sendReadFileResponse({
|
|
664
|
+
requestId: data.requestId,
|
|
665
|
+
exists: false,
|
|
666
|
+
fileName,
|
|
667
|
+
});
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
// SECURITY: Check file size (max 100KB)
|
|
671
|
+
const stats = fs.statSync(resolvedPath);
|
|
672
|
+
if (stats.size > 100 * 1024) {
|
|
673
|
+
websocket_1.wsClient.sendReadFileResponse({
|
|
674
|
+
requestId: data.requestId,
|
|
675
|
+
exists: true,
|
|
676
|
+
fileName,
|
|
677
|
+
error: 'File too large (max 100KB)',
|
|
678
|
+
});
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const content = fs.readFileSync(resolvedPath, 'utf-8');
|
|
682
|
+
websocket_1.wsClient.sendReadFileResponse({
|
|
683
|
+
requestId: data.requestId,
|
|
684
|
+
content,
|
|
685
|
+
exists: true,
|
|
686
|
+
fileName,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
catch (error) {
|
|
690
|
+
console.error(chalk_1.default.red(`[File] Error: ${error.message}`));
|
|
691
|
+
websocket_1.wsClient.sendReadFileResponse({
|
|
692
|
+
requestId: data.requestId,
|
|
693
|
+
exists: false,
|
|
694
|
+
fileName: path.basename(data.filePath),
|
|
695
|
+
error: error.message,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
});
|
|
621
699
|
// Handle transcript fetch requests from mobile
|
|
622
700
|
websocket_1.wsClient.on('transcript_fetch', async (data) => {
|
|
623
701
|
console.log(chalk_1.default.dim(`[Transcript] Fetching: ${data.sessionKey}, offset: ${data.offset}, limit: ${data.limit}, reverse: ${data.reverse}`));
|
|
@@ -817,6 +895,11 @@ async function startConnection() {
|
|
|
817
895
|
console.log(chalk_1.default.yellow(`[Claude] Approval request: ${data.approvalId}`));
|
|
818
896
|
websocket_1.wsClient.sendClaudeApprovalRequest(data);
|
|
819
897
|
});
|
|
898
|
+
// Forward tool activity events to mobile (non-blocking notifications)
|
|
899
|
+
tools_1.claudeProcessManager.on('tool_activity', (data) => {
|
|
900
|
+
console.log(chalk_1.default.dim(`[Claude] Tool activity: ${data.toolName} - ${data.inputSummary?.substring(0, 60)}`));
|
|
901
|
+
websocket_1.wsClient.sendToolActivity(data);
|
|
902
|
+
});
|
|
820
903
|
// Handle Claude approval responses from mobile
|
|
821
904
|
websocket_1.wsClient.on('claude_approval_response', (data) => {
|
|
822
905
|
console.log(chalk_1.default.green(`[Claude] Approval response: ${data.approvalId} -> ${data.response}`));
|
|
@@ -835,6 +918,24 @@ async function startConnection() {
|
|
|
835
918
|
}
|
|
836
919
|
// Check if this session is registered (either active or registered for later spawn)
|
|
837
920
|
if (!tools_1.claudeProcessManager.isClaudeSession(terminalSessionId)) {
|
|
921
|
+
// Fresh session from auto-prompt (quick action): start new session with directory
|
|
922
|
+
if (data.directory) {
|
|
923
|
+
console.log(chalk_1.default.cyan(`[Claude] Starting fresh session for auto-prompt in ${data.directory}`));
|
|
924
|
+
const sent = await tools_1.claudeProcessManager.startAndSendMessage(data.directory, terminalSessionId, data.message);
|
|
925
|
+
if (sent) {
|
|
926
|
+
// Notify mobile that session is ready
|
|
927
|
+
websocket_1.wsClient.sendClaudeSessionUpdate({
|
|
928
|
+
sessionKey: terminalSessionId,
|
|
929
|
+
directory: data.directory,
|
|
930
|
+
state: 'active',
|
|
931
|
+
lastUsedAt: new Date().toISOString(),
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
console.log(chalk_1.default.yellow(`[Claude] Failed to start fresh session`));
|
|
936
|
+
}
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
838
939
|
console.log(chalk_1.default.yellow(`[Claude] Session not registered: ${terminalSessionId}`));
|
|
839
940
|
console.log(chalk_1.default.dim(`[Claude] Hint: Mobile should send claude_resume_session first`));
|
|
840
941
|
return;
|