happy-coder 0.10.0-3 → 0.10.0-4
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 +10 -1
- package/dist/{index-tqOLc1Il.cjs → index-67rskwL7.cjs} +61 -84
- package/dist/{index-DPVbp4Yx.mjs → index-Dw96QD4T.mjs} +60 -83
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +42 -110
- package/dist/lib.d.mts +42 -110
- package/dist/lib.mjs +1 -1
- package/dist/{runCodex-BxLD6H6G.cjs → runCodex-BLNf5zb1.cjs} +3 -3
- package/dist/{runCodex-C07HQlsW.mjs → runCodex-BNH8w4O9.mjs} +3 -3
- package/dist/{types-xds_c-JJ.mjs → types-2wHnX7UW.mjs} +208 -164
- package/dist/{types-CsJGQvQ3.cjs → types-BcDnTXMg.cjs} +209 -164
- package/package.json +3 -2
|
@@ -7,7 +7,7 @@ import { join, basename } from 'node:path';
|
|
|
7
7
|
import { readFile, open, stat, unlink, mkdir, writeFile, rename } from 'node:fs/promises';
|
|
8
8
|
import * as z from 'zod';
|
|
9
9
|
import { z as z$1 } from 'zod';
|
|
10
|
-
import { randomBytes, randomUUID } from 'node:crypto';
|
|
10
|
+
import { randomBytes, createCipheriv, createDecipheriv, randomUUID } from 'node:crypto';
|
|
11
11
|
import tweetnacl from 'tweetnacl';
|
|
12
12
|
import { EventEmitter } from 'node:events';
|
|
13
13
|
import { io } from 'socket.io-client';
|
|
@@ -21,7 +21,7 @@ import { platform } from 'os';
|
|
|
21
21
|
import { Expo } from 'expo-server-sdk';
|
|
22
22
|
|
|
23
23
|
var name = "happy-coder";
|
|
24
|
-
var version = "0.10.0-
|
|
24
|
+
var version = "0.10.0-4";
|
|
25
25
|
var description = "Mobile and Web client for Claude Code and Codex";
|
|
26
26
|
var author = "Kirill Dubovitskiy";
|
|
27
27
|
var license = "MIT";
|
|
@@ -81,7 +81,7 @@ var scripts = {
|
|
|
81
81
|
build: "shx rm -rf dist && npx tsc --noEmit && pkgroll",
|
|
82
82
|
test: "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
|
|
83
83
|
start: "yarn build && ./bin/happy.mjs",
|
|
84
|
-
dev: "
|
|
84
|
+
dev: "tsx --env-file .env.dev src/index.ts",
|
|
85
85
|
"dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
86
86
|
"dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
|
|
87
87
|
prepublishOnly: "yarn build && yarn test",
|
|
@@ -93,6 +93,7 @@ var dependencies = {
|
|
|
93
93
|
"@anthropic-ai/sdk": "^0.56.0",
|
|
94
94
|
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
95
95
|
"@stablelib/base64": "^2.0.1",
|
|
96
|
+
"@stablelib/hex": "^2.0.1",
|
|
96
97
|
"@types/cross-spawn": "^6.0.6",
|
|
97
98
|
"@types/http-proxy": "^1.17.16",
|
|
98
99
|
"@types/ps-list": "^6.2.1",
|
|
@@ -166,6 +167,7 @@ var packageJson = {
|
|
|
166
167
|
|
|
167
168
|
class Configuration {
|
|
168
169
|
serverUrl;
|
|
170
|
+
webappUrl;
|
|
169
171
|
isDaemonProcess;
|
|
170
172
|
// Directories and paths (from persistence)
|
|
171
173
|
happyHomeDir;
|
|
@@ -178,6 +180,7 @@ class Configuration {
|
|
|
178
180
|
isExperimentalEnabled;
|
|
179
181
|
constructor() {
|
|
180
182
|
this.serverUrl = process.env.HAPPY_SERVER_URL || "https://api.cluster-fluster.com";
|
|
183
|
+
this.webappUrl = process.env.HAPPY_WEBAPP_URL || "https://app.happy.engineering";
|
|
181
184
|
const args = process.argv.slice(2);
|
|
182
185
|
this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" && args[1] === "start-sync";
|
|
183
186
|
if (process.env.HAPPY_HOME_DIR) {
|
|
@@ -222,7 +225,17 @@ function decodeBase64(base64, variant = "base64") {
|
|
|
222
225
|
function getRandomBytes(size) {
|
|
223
226
|
return new Uint8Array(randomBytes(size));
|
|
224
227
|
}
|
|
225
|
-
function
|
|
228
|
+
function libsodiumEncryptForPublicKey(data, recipientPublicKey) {
|
|
229
|
+
const ephemeralKeyPair = tweetnacl.box.keyPair();
|
|
230
|
+
const nonce = getRandomBytes(tweetnacl.box.nonceLength);
|
|
231
|
+
const encrypted = tweetnacl.box(data, nonce, recipientPublicKey, ephemeralKeyPair.secretKey);
|
|
232
|
+
const result = new Uint8Array(ephemeralKeyPair.publicKey.length + nonce.length + encrypted.length);
|
|
233
|
+
result.set(ephemeralKeyPair.publicKey, 0);
|
|
234
|
+
result.set(nonce, ephemeralKeyPair.publicKey.length);
|
|
235
|
+
result.set(encrypted, ephemeralKeyPair.publicKey.length + nonce.length);
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
function encryptLegacy(data, secret) {
|
|
226
239
|
const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
|
|
227
240
|
const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
|
|
228
241
|
const result = new Uint8Array(nonce.length + encrypted.length);
|
|
@@ -230,7 +243,7 @@ function encrypt(data, secret) {
|
|
|
230
243
|
result.set(encrypted, nonce.length);
|
|
231
244
|
return result;
|
|
232
245
|
}
|
|
233
|
-
function
|
|
246
|
+
function decryptLegacy(data, secret) {
|
|
234
247
|
const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
|
|
235
248
|
const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
|
|
236
249
|
const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
|
|
@@ -239,6 +252,61 @@ function decrypt(data, secret) {
|
|
|
239
252
|
}
|
|
240
253
|
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
241
254
|
}
|
|
255
|
+
function encryptWithDataKey(data, dataKey) {
|
|
256
|
+
const nonce = getRandomBytes(12);
|
|
257
|
+
const cipher = createCipheriv("aes-256-gcm", dataKey, nonce);
|
|
258
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(data));
|
|
259
|
+
const encrypted = Buffer.concat([
|
|
260
|
+
cipher.update(plaintext),
|
|
261
|
+
cipher.final()
|
|
262
|
+
]);
|
|
263
|
+
const authTag = cipher.getAuthTag();
|
|
264
|
+
const bundle = new Uint8Array(12 + encrypted.length + 16 + 1);
|
|
265
|
+
bundle.set([0], 0);
|
|
266
|
+
bundle.set(nonce, 1);
|
|
267
|
+
bundle.set(new Uint8Array(encrypted), 13);
|
|
268
|
+
bundle.set(new Uint8Array(authTag), 13 + encrypted.length);
|
|
269
|
+
return bundle;
|
|
270
|
+
}
|
|
271
|
+
function decryptWithDataKey(bundle, dataKey) {
|
|
272
|
+
if (bundle.length < 1) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
if (bundle[0] !== 0) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
if (bundle.length < 12 + 16 + 1) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
const nonce = bundle.slice(1, 13);
|
|
282
|
+
const authTag = bundle.slice(bundle.length - 16);
|
|
283
|
+
const ciphertext = bundle.slice(13, bundle.length - 16);
|
|
284
|
+
try {
|
|
285
|
+
const decipher = createDecipheriv("aes-256-gcm", dataKey, nonce);
|
|
286
|
+
decipher.setAuthTag(authTag);
|
|
287
|
+
const decrypted = Buffer.concat([
|
|
288
|
+
decipher.update(ciphertext),
|
|
289
|
+
decipher.final()
|
|
290
|
+
]);
|
|
291
|
+
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
292
|
+
} catch (error) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function encrypt(key, variant, data) {
|
|
297
|
+
if (variant === "legacy") {
|
|
298
|
+
return encryptLegacy(data, key);
|
|
299
|
+
} else {
|
|
300
|
+
return encryptWithDataKey(data, key);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function decrypt(key, variant, data) {
|
|
304
|
+
if (variant === "legacy") {
|
|
305
|
+
return decryptLegacy(data, key);
|
|
306
|
+
} else {
|
|
307
|
+
return decryptWithDataKey(data, key);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
242
310
|
|
|
243
311
|
const defaultSettings = {
|
|
244
312
|
onboardingCompleted: false
|
|
@@ -302,8 +370,13 @@ async function updateSettings(updater) {
|
|
|
302
370
|
}
|
|
303
371
|
}
|
|
304
372
|
const credentialsSchema = z.object({
|
|
305
|
-
|
|
306
|
-
|
|
373
|
+
token: z.string(),
|
|
374
|
+
secret: z.string().base64().nullish(),
|
|
375
|
+
// Legacy
|
|
376
|
+
encryption: z.object({
|
|
377
|
+
publicKey: z.string().base64(),
|
|
378
|
+
machineKey: z.string().base64()
|
|
379
|
+
}).nullish()
|
|
307
380
|
});
|
|
308
381
|
async function readCredentials() {
|
|
309
382
|
if (!existsSync(configuration.privateKeyFile)) {
|
|
@@ -312,15 +385,30 @@ async function readCredentials() {
|
|
|
312
385
|
try {
|
|
313
386
|
const keyBase64 = await readFile(configuration.privateKeyFile, "utf8");
|
|
314
387
|
const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
388
|
+
if (credentials.secret) {
|
|
389
|
+
return {
|
|
390
|
+
token: credentials.token,
|
|
391
|
+
encryption: {
|
|
392
|
+
type: "legacy",
|
|
393
|
+
secret: new Uint8Array(Buffer.from(credentials.secret, "base64"))
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
} else if (credentials.encryption) {
|
|
397
|
+
return {
|
|
398
|
+
token: credentials.token,
|
|
399
|
+
encryption: {
|
|
400
|
+
type: "dataKey",
|
|
401
|
+
publicKey: new Uint8Array(Buffer.from(credentials.encryption.publicKey, "base64")),
|
|
402
|
+
machineKey: new Uint8Array(Buffer.from(credentials.encryption.machineKey, "base64"))
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
319
406
|
} catch {
|
|
320
407
|
return null;
|
|
321
408
|
}
|
|
409
|
+
return null;
|
|
322
410
|
}
|
|
323
|
-
async function
|
|
411
|
+
async function writeCredentialsLegacy(credentials) {
|
|
324
412
|
if (!existsSync(configuration.happyHomeDir)) {
|
|
325
413
|
await mkdir(configuration.happyHomeDir, { recursive: true });
|
|
326
414
|
}
|
|
@@ -329,6 +417,15 @@ async function writeCredentials(credentials) {
|
|
|
329
417
|
token: credentials.token
|
|
330
418
|
}, null, 2));
|
|
331
419
|
}
|
|
420
|
+
async function writeCredentialsDataKey(credentials) {
|
|
421
|
+
if (!existsSync(configuration.happyHomeDir)) {
|
|
422
|
+
await mkdir(configuration.happyHomeDir, { recursive: true });
|
|
423
|
+
}
|
|
424
|
+
await writeFile(configuration.privateKeyFile, JSON.stringify({
|
|
425
|
+
encryption: { publicKey: encodeBase64(credentials.publicKey), machineKey: encodeBase64(credentials.machineKey) },
|
|
426
|
+
token: credentials.token
|
|
427
|
+
}, null, 2));
|
|
428
|
+
}
|
|
332
429
|
async function clearCredentials() {
|
|
333
430
|
if (existsSync(configuration.privateKeyFile)) {
|
|
334
431
|
await unlink(configuration.privateKeyFile);
|
|
@@ -663,32 +760,6 @@ z$1.object({
|
|
|
663
760
|
]),
|
|
664
761
|
createdAt: z$1.number()
|
|
665
762
|
});
|
|
666
|
-
z$1.object({
|
|
667
|
-
createdAt: z$1.number(),
|
|
668
|
-
id: z$1.string(),
|
|
669
|
-
seq: z$1.number(),
|
|
670
|
-
updatedAt: z$1.number(),
|
|
671
|
-
metadata: z$1.any(),
|
|
672
|
-
metadataVersion: z$1.number(),
|
|
673
|
-
agentState: z$1.any().nullable(),
|
|
674
|
-
agentStateVersion: z$1.number(),
|
|
675
|
-
// Connectivity tracking (from server)
|
|
676
|
-
connectivityStatus: z$1.union([
|
|
677
|
-
z$1.enum(["neverConnected", "online", "offline"]),
|
|
678
|
-
z$1.string()
|
|
679
|
-
// Forward compatibility
|
|
680
|
-
]).optional(),
|
|
681
|
-
connectivityStatusSince: z$1.number().optional(),
|
|
682
|
-
connectivityStatusReason: z$1.string().optional(),
|
|
683
|
-
// State tracking (from server)
|
|
684
|
-
state: z$1.union([
|
|
685
|
-
z$1.enum(["running", "archiveRequested", "archived"]),
|
|
686
|
-
z$1.string()
|
|
687
|
-
// Forward compatibility
|
|
688
|
-
]).optional(),
|
|
689
|
-
stateSince: z$1.number().optional(),
|
|
690
|
-
stateReason: z$1.string().optional()
|
|
691
|
-
});
|
|
692
763
|
z$1.object({
|
|
693
764
|
host: z$1.string(),
|
|
694
765
|
platform: z$1.string(),
|
|
@@ -713,37 +784,6 @@ z$1.object({
|
|
|
713
784
|
// Forward compatibility
|
|
714
785
|
]).optional()
|
|
715
786
|
});
|
|
716
|
-
z$1.object({
|
|
717
|
-
id: z$1.string(),
|
|
718
|
-
metadata: z$1.any(),
|
|
719
|
-
// Decrypted MachineMetadata
|
|
720
|
-
metadataVersion: z$1.number(),
|
|
721
|
-
daemonState: z$1.any().nullable(),
|
|
722
|
-
// Decrypted DaemonState
|
|
723
|
-
daemonStateVersion: z$1.number(),
|
|
724
|
-
// We don't really care about these on the CLI for now
|
|
725
|
-
// ApiMachineClient will not sync these
|
|
726
|
-
active: z$1.boolean(),
|
|
727
|
-
activeAt: z$1.number(),
|
|
728
|
-
createdAt: z$1.number(),
|
|
729
|
-
updatedAt: z$1.number(),
|
|
730
|
-
// Connectivity tracking (from server)
|
|
731
|
-
connectivityStatus: z$1.union([
|
|
732
|
-
z$1.enum(["neverConnected", "online", "offline"]),
|
|
733
|
-
z$1.string()
|
|
734
|
-
// Forward compatibility
|
|
735
|
-
]).optional(),
|
|
736
|
-
connectivityStatusSince: z$1.number().optional(),
|
|
737
|
-
connectivityStatusReason: z$1.string().optional(),
|
|
738
|
-
// State tracking (from server)
|
|
739
|
-
state: z$1.union([
|
|
740
|
-
z$1.enum(["running", "archiveRequested", "archived"]),
|
|
741
|
-
z$1.string()
|
|
742
|
-
// Forward compatibility
|
|
743
|
-
]).optional(),
|
|
744
|
-
stateSince: z$1.number().optional(),
|
|
745
|
-
stateReason: z$1.string().optional()
|
|
746
|
-
});
|
|
747
787
|
z$1.object({
|
|
748
788
|
content: SessionMessageContentSchema,
|
|
749
789
|
createdAt: z$1.number(),
|
|
@@ -867,12 +907,14 @@ class AsyncLock {
|
|
|
867
907
|
class RpcHandlerManager {
|
|
868
908
|
handlers = /* @__PURE__ */ new Map();
|
|
869
909
|
scopePrefix;
|
|
870
|
-
|
|
910
|
+
encryptionKey;
|
|
911
|
+
encryptionVariant;
|
|
871
912
|
logger;
|
|
872
913
|
socket = null;
|
|
873
914
|
constructor(config) {
|
|
874
915
|
this.scopePrefix = config.scopePrefix;
|
|
875
|
-
this.
|
|
916
|
+
this.encryptionKey = config.encryptionKey;
|
|
917
|
+
this.encryptionVariant = config.encryptionVariant;
|
|
876
918
|
this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
|
|
877
919
|
}
|
|
878
920
|
/**
|
|
@@ -898,20 +940,19 @@ class RpcHandlerManager {
|
|
|
898
940
|
if (!handler) {
|
|
899
941
|
this.logger("[RPC] [ERROR] Method not found", { method: request.method });
|
|
900
942
|
const errorResponse = { error: "Method not found" };
|
|
901
|
-
const encryptedError = encodeBase64(encrypt(
|
|
943
|
+
const encryptedError = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
|
|
902
944
|
return encryptedError;
|
|
903
945
|
}
|
|
904
|
-
const decryptedParams = decrypt(decodeBase64(request.params)
|
|
946
|
+
const decryptedParams = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(request.params));
|
|
905
947
|
const result = await handler(decryptedParams);
|
|
906
|
-
const encryptedResponse = encodeBase64(encrypt(
|
|
948
|
+
const encryptedResponse = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, result));
|
|
907
949
|
return encryptedResponse;
|
|
908
950
|
} catch (error) {
|
|
909
951
|
this.logger("[RPC] [ERROR] Error handling request", { error });
|
|
910
952
|
const errorResponse = {
|
|
911
953
|
error: error instanceof Error ? error.message : "Unknown error"
|
|
912
954
|
};
|
|
913
|
-
|
|
914
|
-
return encryptedError;
|
|
955
|
+
return encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
|
|
915
956
|
}
|
|
916
957
|
}
|
|
917
958
|
onSocketConnect(socket) {
|
|
@@ -1256,7 +1297,6 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1256
1297
|
|
|
1257
1298
|
class ApiSessionClient extends EventEmitter {
|
|
1258
1299
|
token;
|
|
1259
|
-
secret;
|
|
1260
1300
|
sessionId;
|
|
1261
1301
|
metadata;
|
|
1262
1302
|
metadataVersion;
|
|
@@ -1268,18 +1308,22 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1268
1308
|
rpcHandlerManager;
|
|
1269
1309
|
agentStateLock = new AsyncLock();
|
|
1270
1310
|
metadataLock = new AsyncLock();
|
|
1271
|
-
|
|
1311
|
+
encryptionKey;
|
|
1312
|
+
encryptionVariant;
|
|
1313
|
+
constructor(token, session) {
|
|
1272
1314
|
super();
|
|
1273
1315
|
this.token = token;
|
|
1274
|
-
this.secret = secret;
|
|
1275
1316
|
this.sessionId = session.id;
|
|
1276
1317
|
this.metadata = session.metadata;
|
|
1277
1318
|
this.metadataVersion = session.metadataVersion;
|
|
1278
1319
|
this.agentState = session.agentState;
|
|
1279
1320
|
this.agentStateVersion = session.agentStateVersion;
|
|
1321
|
+
this.encryptionKey = session.encryptionKey;
|
|
1322
|
+
this.encryptionVariant = session.encryptionVariant;
|
|
1280
1323
|
this.rpcHandlerManager = new RpcHandlerManager({
|
|
1281
1324
|
scopePrefix: this.sessionId,
|
|
1282
|
-
|
|
1325
|
+
encryptionKey: this.encryptionKey,
|
|
1326
|
+
encryptionVariant: this.encryptionVariant,
|
|
1283
1327
|
logger: (msg, data) => logger.debug(msg, data)
|
|
1284
1328
|
});
|
|
1285
1329
|
registerCommonHandlers(this.rpcHandlerManager);
|
|
@@ -1321,7 +1365,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1321
1365
|
return;
|
|
1322
1366
|
}
|
|
1323
1367
|
if (data.body.t === "new-message" && data.body.message.content.t === "encrypted") {
|
|
1324
|
-
const body = decrypt(decodeBase64(data.body.message.content.c)
|
|
1368
|
+
const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.message.content.c));
|
|
1325
1369
|
logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
|
|
1326
1370
|
const userResult = UserMessageSchema.safeParse(body);
|
|
1327
1371
|
if (userResult.success) {
|
|
@@ -1335,11 +1379,11 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1335
1379
|
}
|
|
1336
1380
|
} else if (data.body.t === "update-session") {
|
|
1337
1381
|
if (data.body.metadata && data.body.metadata.version > this.metadataVersion) {
|
|
1338
|
-
this.metadata = decrypt(decodeBase64(data.body.metadata.value)
|
|
1382
|
+
this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.metadata.value));
|
|
1339
1383
|
this.metadataVersion = data.body.metadata.version;
|
|
1340
1384
|
}
|
|
1341
1385
|
if (data.body.agentState && data.body.agentState.version > this.agentStateVersion) {
|
|
1342
|
-
this.agentState = data.body.agentState.value ? decrypt(decodeBase64(data.body.agentState.value)
|
|
1386
|
+
this.agentState = data.body.agentState.value ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.agentState.value)) : null;
|
|
1343
1387
|
this.agentStateVersion = data.body.agentState.version;
|
|
1344
1388
|
}
|
|
1345
1389
|
} else if (data.body.t === "update-machine") {
|
|
@@ -1393,7 +1437,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1393
1437
|
};
|
|
1394
1438
|
}
|
|
1395
1439
|
logger.debugLargeJson("[SOCKET] Sending message through socket:", content);
|
|
1396
|
-
const encrypted = encodeBase64(encrypt(
|
|
1440
|
+
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
1397
1441
|
this.socket.emit("message", {
|
|
1398
1442
|
sid: this.sessionId,
|
|
1399
1443
|
message: encrypted
|
|
@@ -1427,7 +1471,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1427
1471
|
sentFrom: "cli"
|
|
1428
1472
|
}
|
|
1429
1473
|
};
|
|
1430
|
-
const encrypted = encodeBase64(encrypt(
|
|
1474
|
+
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
1431
1475
|
this.socket.emit("message", {
|
|
1432
1476
|
sid: this.sessionId,
|
|
1433
1477
|
message: encrypted
|
|
@@ -1442,7 +1486,7 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1442
1486
|
data: event
|
|
1443
1487
|
}
|
|
1444
1488
|
};
|
|
1445
|
-
const encrypted = encodeBase64(encrypt(
|
|
1489
|
+
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
1446
1490
|
this.socket.emit("message", {
|
|
1447
1491
|
sid: this.sessionId,
|
|
1448
1492
|
message: encrypted
|
|
@@ -1502,14 +1546,14 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1502
1546
|
this.metadataLock.inLock(async () => {
|
|
1503
1547
|
await backoff(async () => {
|
|
1504
1548
|
let updated = handler(this.metadata);
|
|
1505
|
-
const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(
|
|
1549
|
+
const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) });
|
|
1506
1550
|
if (answer.result === "success") {
|
|
1507
|
-
this.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1551
|
+
this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
|
|
1508
1552
|
this.metadataVersion = answer.version;
|
|
1509
1553
|
} else if (answer.result === "version-mismatch") {
|
|
1510
1554
|
if (answer.version > this.metadataVersion) {
|
|
1511
1555
|
this.metadataVersion = answer.version;
|
|
1512
|
-
this.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1556
|
+
this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
|
|
1513
1557
|
}
|
|
1514
1558
|
throw new Error("Metadata version mismatch");
|
|
1515
1559
|
} else if (answer.result === "error") ;
|
|
@@ -1525,15 +1569,15 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1525
1569
|
this.agentStateLock.inLock(async () => {
|
|
1526
1570
|
await backoff(async () => {
|
|
1527
1571
|
let updated = handler(this.agentState || {});
|
|
1528
|
-
const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(
|
|
1572
|
+
const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
|
|
1529
1573
|
if (answer.result === "success") {
|
|
1530
|
-
this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState)
|
|
1574
|
+
this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
|
|
1531
1575
|
this.agentStateVersion = answer.version;
|
|
1532
1576
|
logger.debug("Agent state updated", this.agentState);
|
|
1533
1577
|
} else if (answer.result === "version-mismatch") {
|
|
1534
1578
|
if (answer.version > this.agentStateVersion) {
|
|
1535
1579
|
this.agentStateVersion = answer.version;
|
|
1536
|
-
this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState)
|
|
1580
|
+
this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
|
|
1537
1581
|
}
|
|
1538
1582
|
throw new Error("Agent state version mismatch");
|
|
1539
1583
|
} else if (answer.result === "error") ;
|
|
@@ -1563,13 +1607,13 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1563
1607
|
}
|
|
1564
1608
|
|
|
1565
1609
|
class ApiMachineClient {
|
|
1566
|
-
constructor(token,
|
|
1610
|
+
constructor(token, machine) {
|
|
1567
1611
|
this.token = token;
|
|
1568
|
-
this.secret = secret;
|
|
1569
1612
|
this.machine = machine;
|
|
1570
1613
|
this.rpcHandlerManager = new RpcHandlerManager({
|
|
1571
1614
|
scopePrefix: this.machine.id,
|
|
1572
|
-
|
|
1615
|
+
encryptionKey: this.machine.encryptionKey,
|
|
1616
|
+
encryptionVariant: this.machine.encryptionVariant,
|
|
1573
1617
|
logger: (msg, data) => logger.debug(msg, data)
|
|
1574
1618
|
});
|
|
1575
1619
|
registerCommonHandlers(this.rpcHandlerManager);
|
|
@@ -1630,17 +1674,17 @@ class ApiMachineClient {
|
|
|
1630
1674
|
const updated = handler(this.machine.metadata);
|
|
1631
1675
|
const answer = await this.socket.emitWithAck("machine-update-metadata", {
|
|
1632
1676
|
machineId: this.machine.id,
|
|
1633
|
-
metadata: encodeBase64(encrypt(
|
|
1677
|
+
metadata: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
|
|
1634
1678
|
expectedVersion: this.machine.metadataVersion
|
|
1635
1679
|
});
|
|
1636
1680
|
if (answer.result === "success") {
|
|
1637
|
-
this.machine.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1681
|
+
this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
|
|
1638
1682
|
this.machine.metadataVersion = answer.version;
|
|
1639
1683
|
logger.debug("[API MACHINE] Metadata updated successfully");
|
|
1640
1684
|
} else if (answer.result === "version-mismatch") {
|
|
1641
1685
|
if (answer.version > this.machine.metadataVersion) {
|
|
1642
1686
|
this.machine.metadataVersion = answer.version;
|
|
1643
|
-
this.machine.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1687
|
+
this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
|
|
1644
1688
|
}
|
|
1645
1689
|
throw new Error("Metadata version mismatch");
|
|
1646
1690
|
}
|
|
@@ -1655,17 +1699,17 @@ class ApiMachineClient {
|
|
|
1655
1699
|
const updated = handler(this.machine.daemonState);
|
|
1656
1700
|
const answer = await this.socket.emitWithAck("machine-update-state", {
|
|
1657
1701
|
machineId: this.machine.id,
|
|
1658
|
-
daemonState: encodeBase64(encrypt(
|
|
1702
|
+
daemonState: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
|
|
1659
1703
|
expectedVersion: this.machine.daemonStateVersion
|
|
1660
1704
|
});
|
|
1661
1705
|
if (answer.result === "success") {
|
|
1662
|
-
this.machine.daemonState = decrypt(decodeBase64(answer.daemonState)
|
|
1706
|
+
this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
|
|
1663
1707
|
this.machine.daemonStateVersion = answer.version;
|
|
1664
1708
|
logger.debug("[API MACHINE] Daemon state updated successfully");
|
|
1665
1709
|
} else if (answer.result === "version-mismatch") {
|
|
1666
1710
|
if (answer.version > this.machine.daemonStateVersion) {
|
|
1667
1711
|
this.machine.daemonStateVersion = answer.version;
|
|
1668
|
-
this.machine.daemonState = decrypt(decodeBase64(answer.daemonState)
|
|
1712
|
+
this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
|
|
1669
1713
|
}
|
|
1670
1714
|
throw new Error("Daemon state version mismatch");
|
|
1671
1715
|
}
|
|
@@ -1712,12 +1756,12 @@ class ApiMachineClient {
|
|
|
1712
1756
|
const update = data.body;
|
|
1713
1757
|
if (update.metadata) {
|
|
1714
1758
|
logger.debug("[API MACHINE] Received external metadata update");
|
|
1715
|
-
this.machine.metadata = decrypt(decodeBase64(update.metadata.value)
|
|
1759
|
+
this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.metadata.value));
|
|
1716
1760
|
this.machine.metadataVersion = update.metadata.version;
|
|
1717
1761
|
}
|
|
1718
1762
|
if (update.daemonState) {
|
|
1719
1763
|
logger.debug("[API MACHINE] Received external daemon state update");
|
|
1720
|
-
this.machine.daemonState = decrypt(decodeBase64(update.daemonState.value)
|
|
1764
|
+
this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.daemonState.value));
|
|
1721
1765
|
this.machine.daemonStateVersion = update.daemonState.version;
|
|
1722
1766
|
}
|
|
1723
1767
|
} else {
|
|
@@ -1889,46 +1933,62 @@ class PushNotificationClient {
|
|
|
1889
1933
|
}
|
|
1890
1934
|
|
|
1891
1935
|
class ApiClient {
|
|
1892
|
-
|
|
1893
|
-
|
|
1936
|
+
static async create(credential) {
|
|
1937
|
+
return new ApiClient(credential);
|
|
1938
|
+
}
|
|
1939
|
+
credential;
|
|
1894
1940
|
pushClient;
|
|
1895
|
-
constructor(
|
|
1896
|
-
this.
|
|
1897
|
-
this.
|
|
1898
|
-
this.pushClient = new PushNotificationClient(token);
|
|
1941
|
+
constructor(credential) {
|
|
1942
|
+
this.credential = credential;
|
|
1943
|
+
this.pushClient = new PushNotificationClient(credential.token, configuration.serverUrl);
|
|
1899
1944
|
}
|
|
1900
1945
|
/**
|
|
1901
1946
|
* Create a new session or load existing one with the given tag
|
|
1902
1947
|
*/
|
|
1903
1948
|
async getOrCreateSession(opts) {
|
|
1949
|
+
let dataEncryptionKey = null;
|
|
1950
|
+
let encryptionKey;
|
|
1951
|
+
let encryptionVariant;
|
|
1952
|
+
if (this.credential.encryption.type === "dataKey") {
|
|
1953
|
+
encryptionKey = getRandomBytes(32);
|
|
1954
|
+
encryptionVariant = "dataKey";
|
|
1955
|
+
let encryptedDataKey = libsodiumEncryptForPublicKey(encryptionKey, this.credential.encryption.publicKey);
|
|
1956
|
+
dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
|
|
1957
|
+
dataEncryptionKey.set([0], 0);
|
|
1958
|
+
dataEncryptionKey.set(encryptedDataKey, 1);
|
|
1959
|
+
} else {
|
|
1960
|
+
encryptionKey = this.credential.encryption.secret;
|
|
1961
|
+
encryptionVariant = "legacy";
|
|
1962
|
+
}
|
|
1904
1963
|
try {
|
|
1905
1964
|
const response = await axios.post(
|
|
1906
1965
|
`${configuration.serverUrl}/v1/sessions`,
|
|
1907
1966
|
{
|
|
1908
1967
|
tag: opts.tag,
|
|
1909
|
-
metadata: encodeBase64(encrypt(opts.metadata
|
|
1910
|
-
agentState: opts.state ? encodeBase64(encrypt(opts.state
|
|
1968
|
+
metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
|
|
1969
|
+
agentState: opts.state ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.state)) : null,
|
|
1970
|
+
dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : null
|
|
1911
1971
|
},
|
|
1912
1972
|
{
|
|
1913
1973
|
headers: {
|
|
1914
|
-
"Authorization": `Bearer ${this.token}`,
|
|
1974
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
1915
1975
|
"Content-Type": "application/json"
|
|
1916
1976
|
},
|
|
1917
|
-
timeout:
|
|
1918
|
-
//
|
|
1977
|
+
timeout: 6e4
|
|
1978
|
+
// 1 minute timeout for very bad network connections
|
|
1919
1979
|
}
|
|
1920
1980
|
);
|
|
1921
1981
|
logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
|
|
1922
1982
|
let raw = response.data.session;
|
|
1923
1983
|
let session = {
|
|
1924
1984
|
id: raw.id,
|
|
1925
|
-
createdAt: raw.createdAt,
|
|
1926
|
-
updatedAt: raw.updatedAt,
|
|
1927
1985
|
seq: raw.seq,
|
|
1928
|
-
metadata: decrypt(decodeBase64(raw.metadata)
|
|
1986
|
+
metadata: decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)),
|
|
1929
1987
|
metadataVersion: raw.metadataVersion,
|
|
1930
|
-
agentState: raw.agentState ? decrypt(decodeBase64(raw.agentState)
|
|
1931
|
-
agentStateVersion: raw.agentStateVersion
|
|
1988
|
+
agentState: raw.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.agentState)) : null,
|
|
1989
|
+
agentStateVersion: raw.agentStateVersion,
|
|
1990
|
+
encryptionKey,
|
|
1991
|
+
encryptionVariant
|
|
1932
1992
|
};
|
|
1933
1993
|
return session;
|
|
1934
1994
|
} catch (error) {
|
|
@@ -1936,54 +1996,40 @@ class ApiClient {
|
|
|
1936
1996
|
throw new Error(`Failed to get or create session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1937
1997
|
}
|
|
1938
1998
|
}
|
|
1939
|
-
/**
|
|
1940
|
-
* Get machine by ID from the server
|
|
1941
|
-
* Returns the current machine state from the server with decrypted metadata and daemonState
|
|
1942
|
-
*/
|
|
1943
|
-
async getMachine(machineId) {
|
|
1944
|
-
const response = await axios.get(`${configuration.serverUrl}/v1/machines/${machineId}`, {
|
|
1945
|
-
headers: {
|
|
1946
|
-
"Authorization": `Bearer ${this.token}`,
|
|
1947
|
-
"Content-Type": "application/json"
|
|
1948
|
-
},
|
|
1949
|
-
timeout: 2e3
|
|
1950
|
-
});
|
|
1951
|
-
const raw = response.data.machine;
|
|
1952
|
-
if (!raw) {
|
|
1953
|
-
return null;
|
|
1954
|
-
}
|
|
1955
|
-
logger.debug(`[API] Machine ${machineId} fetched from server`);
|
|
1956
|
-
const machine = {
|
|
1957
|
-
id: raw.id,
|
|
1958
|
-
metadata: raw.metadata ? decrypt(decodeBase64(raw.metadata), this.secret) : null,
|
|
1959
|
-
metadataVersion: raw.metadataVersion || 0,
|
|
1960
|
-
daemonState: raw.daemonState ? decrypt(decodeBase64(raw.daemonState), this.secret) : null,
|
|
1961
|
-
daemonStateVersion: raw.daemonStateVersion || 0,
|
|
1962
|
-
active: raw.active,
|
|
1963
|
-
activeAt: raw.activeAt,
|
|
1964
|
-
createdAt: raw.createdAt,
|
|
1965
|
-
updatedAt: raw.updatedAt
|
|
1966
|
-
};
|
|
1967
|
-
return machine;
|
|
1968
|
-
}
|
|
1969
1999
|
/**
|
|
1970
2000
|
* Register or update machine with the server
|
|
1971
2001
|
* Returns the current machine state from the server with decrypted metadata and daemonState
|
|
1972
2002
|
*/
|
|
1973
2003
|
async getOrCreateMachine(opts) {
|
|
2004
|
+
let dataEncryptionKey = null;
|
|
2005
|
+
let encryptionKey;
|
|
2006
|
+
let encryptionVariant;
|
|
2007
|
+
if (this.credential.encryption.type === "dataKey") {
|
|
2008
|
+
encryptionVariant = "dataKey";
|
|
2009
|
+
encryptionKey = this.credential.encryption.machineKey;
|
|
2010
|
+
let encryptedDataKey = libsodiumEncryptForPublicKey(this.credential.encryption.machineKey, this.credential.encryption.publicKey);
|
|
2011
|
+
dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
|
|
2012
|
+
dataEncryptionKey.set([0], 0);
|
|
2013
|
+
dataEncryptionKey.set(encryptedDataKey, 1);
|
|
2014
|
+
} else {
|
|
2015
|
+
encryptionKey = this.credential.encryption.secret;
|
|
2016
|
+
encryptionVariant = "legacy";
|
|
2017
|
+
}
|
|
1974
2018
|
const response = await axios.post(
|
|
1975
2019
|
`${configuration.serverUrl}/v1/machines`,
|
|
1976
2020
|
{
|
|
1977
2021
|
id: opts.machineId,
|
|
1978
|
-
metadata: encodeBase64(encrypt(opts.metadata
|
|
1979
|
-
daemonState: opts.daemonState ? encodeBase64(encrypt(opts.daemonState
|
|
2022
|
+
metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
|
|
2023
|
+
daemonState: opts.daemonState ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.daemonState)) : void 0,
|
|
2024
|
+
dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : void 0
|
|
1980
2025
|
},
|
|
1981
2026
|
{
|
|
1982
2027
|
headers: {
|
|
1983
|
-
"Authorization": `Bearer ${this.token}`,
|
|
2028
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
1984
2029
|
"Content-Type": "application/json"
|
|
1985
2030
|
},
|
|
1986
|
-
timeout:
|
|
2031
|
+
timeout: 6e4
|
|
2032
|
+
// 1 minute timeout for very bad network connections
|
|
1987
2033
|
}
|
|
1988
2034
|
);
|
|
1989
2035
|
if (response.status !== 200) {
|
|
@@ -1995,22 +2041,20 @@ class ApiClient {
|
|
|
1995
2041
|
logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
|
|
1996
2042
|
const machine = {
|
|
1997
2043
|
id: raw.id,
|
|
1998
|
-
|
|
2044
|
+
encryptionKey,
|
|
2045
|
+
encryptionVariant,
|
|
2046
|
+
metadata: raw.metadata ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)) : null,
|
|
1999
2047
|
metadataVersion: raw.metadataVersion || 0,
|
|
2000
|
-
daemonState: raw.daemonState ? decrypt(decodeBase64(raw.daemonState)
|
|
2001
|
-
daemonStateVersion: raw.daemonStateVersion || 0
|
|
2002
|
-
active: raw.active,
|
|
2003
|
-
activeAt: raw.activeAt,
|
|
2004
|
-
createdAt: raw.createdAt,
|
|
2005
|
-
updatedAt: raw.updatedAt
|
|
2048
|
+
daemonState: raw.daemonState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.daemonState)) : null,
|
|
2049
|
+
daemonStateVersion: raw.daemonStateVersion || 0
|
|
2006
2050
|
};
|
|
2007
2051
|
return machine;
|
|
2008
2052
|
}
|
|
2009
2053
|
sessionSyncClient(session) {
|
|
2010
|
-
return new ApiSessionClient(this.token,
|
|
2054
|
+
return new ApiSessionClient(this.credential.token, session);
|
|
2011
2055
|
}
|
|
2012
2056
|
machineSyncClient(machine) {
|
|
2013
|
-
return new ApiMachineClient(this.token,
|
|
2057
|
+
return new ApiMachineClient(this.credential.token, machine);
|
|
2014
2058
|
}
|
|
2015
2059
|
push() {
|
|
2016
2060
|
return this.pushClient;
|
|
@@ -2028,7 +2072,7 @@ class ApiClient {
|
|
|
2028
2072
|
},
|
|
2029
2073
|
{
|
|
2030
2074
|
headers: {
|
|
2031
|
-
"Authorization": `Bearer ${this.token}`,
|
|
2075
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
2032
2076
|
"Content-Type": "application/json"
|
|
2033
2077
|
},
|
|
2034
2078
|
timeout: 5e3
|
|
@@ -2093,4 +2137,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
|
|
|
2093
2137
|
}).passthrough()
|
|
2094
2138
|
]);
|
|
2095
2139
|
|
|
2096
|
-
export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, packageJson as b, configuration as c, backoff as d, delay as e, AsyncLock as f, readDaemonState as g, clearDaemonState as h, readCredentials as i, encodeBase64 as j, encodeBase64Url as k, logger as l, decodeBase64 as m,
|
|
2140
|
+
export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, packageJson as b, configuration as c, backoff as d, delay as e, AsyncLock as f, readDaemonState as g, clearDaemonState as h, readCredentials as i, encodeBase64 as j, encodeBase64Url as k, logger as l, decodeBase64 as m, writeCredentialsDataKey as n, acquireDaemonLock as o, projectPath as p, writeDaemonState as q, readSettings as r, releaseDaemonLock as s, clearCredentials as t, updateSettings as u, clearMachineId as v, writeCredentialsLegacy as w, getLatestDaemonLog as x };
|