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
|
@@ -42,7 +42,7 @@ function _interopNamespaceDefault(e) {
|
|
|
42
42
|
var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
|
|
43
43
|
|
|
44
44
|
var name = "happy-coder";
|
|
45
|
-
var version = "0.10.0-
|
|
45
|
+
var version = "0.10.0-4";
|
|
46
46
|
var description = "Mobile and Web client for Claude Code and Codex";
|
|
47
47
|
var author = "Kirill Dubovitskiy";
|
|
48
48
|
var license = "MIT";
|
|
@@ -102,7 +102,7 @@ var scripts = {
|
|
|
102
102
|
build: "shx rm -rf dist && npx tsc --noEmit && pkgroll",
|
|
103
103
|
test: "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
|
|
104
104
|
start: "yarn build && ./bin/happy.mjs",
|
|
105
|
-
dev: "
|
|
105
|
+
dev: "tsx --env-file .env.dev src/index.ts",
|
|
106
106
|
"dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
107
107
|
"dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
|
|
108
108
|
prepublishOnly: "yarn build && yarn test",
|
|
@@ -114,6 +114,7 @@ var dependencies = {
|
|
|
114
114
|
"@anthropic-ai/sdk": "^0.56.0",
|
|
115
115
|
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
116
116
|
"@stablelib/base64": "^2.0.1",
|
|
117
|
+
"@stablelib/hex": "^2.0.1",
|
|
117
118
|
"@types/cross-spawn": "^6.0.6",
|
|
118
119
|
"@types/http-proxy": "^1.17.16",
|
|
119
120
|
"@types/ps-list": "^6.2.1",
|
|
@@ -187,6 +188,7 @@ var packageJson = {
|
|
|
187
188
|
|
|
188
189
|
class Configuration {
|
|
189
190
|
serverUrl;
|
|
191
|
+
webappUrl;
|
|
190
192
|
isDaemonProcess;
|
|
191
193
|
// Directories and paths (from persistence)
|
|
192
194
|
happyHomeDir;
|
|
@@ -199,6 +201,7 @@ class Configuration {
|
|
|
199
201
|
isExperimentalEnabled;
|
|
200
202
|
constructor() {
|
|
201
203
|
this.serverUrl = process.env.HAPPY_SERVER_URL || "https://api.cluster-fluster.com";
|
|
204
|
+
this.webappUrl = process.env.HAPPY_WEBAPP_URL || "https://app.happy.engineering";
|
|
202
205
|
const args = process.argv.slice(2);
|
|
203
206
|
this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" && args[1] === "start-sync";
|
|
204
207
|
if (process.env.HAPPY_HOME_DIR) {
|
|
@@ -243,7 +246,17 @@ function decodeBase64(base64, variant = "base64") {
|
|
|
243
246
|
function getRandomBytes(size) {
|
|
244
247
|
return new Uint8Array(node_crypto.randomBytes(size));
|
|
245
248
|
}
|
|
246
|
-
function
|
|
249
|
+
function libsodiumEncryptForPublicKey(data, recipientPublicKey) {
|
|
250
|
+
const ephemeralKeyPair = tweetnacl.box.keyPair();
|
|
251
|
+
const nonce = getRandomBytes(tweetnacl.box.nonceLength);
|
|
252
|
+
const encrypted = tweetnacl.box(data, nonce, recipientPublicKey, ephemeralKeyPair.secretKey);
|
|
253
|
+
const result = new Uint8Array(ephemeralKeyPair.publicKey.length + nonce.length + encrypted.length);
|
|
254
|
+
result.set(ephemeralKeyPair.publicKey, 0);
|
|
255
|
+
result.set(nonce, ephemeralKeyPair.publicKey.length);
|
|
256
|
+
result.set(encrypted, ephemeralKeyPair.publicKey.length + nonce.length);
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
function encryptLegacy(data, secret) {
|
|
247
260
|
const nonce = getRandomBytes(tweetnacl.secretbox.nonceLength);
|
|
248
261
|
const encrypted = tweetnacl.secretbox(new TextEncoder().encode(JSON.stringify(data)), nonce, secret);
|
|
249
262
|
const result = new Uint8Array(nonce.length + encrypted.length);
|
|
@@ -251,7 +264,7 @@ function encrypt(data, secret) {
|
|
|
251
264
|
result.set(encrypted, nonce.length);
|
|
252
265
|
return result;
|
|
253
266
|
}
|
|
254
|
-
function
|
|
267
|
+
function decryptLegacy(data, secret) {
|
|
255
268
|
const nonce = data.slice(0, tweetnacl.secretbox.nonceLength);
|
|
256
269
|
const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
|
|
257
270
|
const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
|
|
@@ -260,6 +273,61 @@ function decrypt(data, secret) {
|
|
|
260
273
|
}
|
|
261
274
|
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
262
275
|
}
|
|
276
|
+
function encryptWithDataKey(data, dataKey) {
|
|
277
|
+
const nonce = getRandomBytes(12);
|
|
278
|
+
const cipher = node_crypto.createCipheriv("aes-256-gcm", dataKey, nonce);
|
|
279
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(data));
|
|
280
|
+
const encrypted = Buffer.concat([
|
|
281
|
+
cipher.update(plaintext),
|
|
282
|
+
cipher.final()
|
|
283
|
+
]);
|
|
284
|
+
const authTag = cipher.getAuthTag();
|
|
285
|
+
const bundle = new Uint8Array(12 + encrypted.length + 16 + 1);
|
|
286
|
+
bundle.set([0], 0);
|
|
287
|
+
bundle.set(nonce, 1);
|
|
288
|
+
bundle.set(new Uint8Array(encrypted), 13);
|
|
289
|
+
bundle.set(new Uint8Array(authTag), 13 + encrypted.length);
|
|
290
|
+
return bundle;
|
|
291
|
+
}
|
|
292
|
+
function decryptWithDataKey(bundle, dataKey) {
|
|
293
|
+
if (bundle.length < 1) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
if (bundle[0] !== 0) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
if (bundle.length < 12 + 16 + 1) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const nonce = bundle.slice(1, 13);
|
|
303
|
+
const authTag = bundle.slice(bundle.length - 16);
|
|
304
|
+
const ciphertext = bundle.slice(13, bundle.length - 16);
|
|
305
|
+
try {
|
|
306
|
+
const decipher = node_crypto.createDecipheriv("aes-256-gcm", dataKey, nonce);
|
|
307
|
+
decipher.setAuthTag(authTag);
|
|
308
|
+
const decrypted = Buffer.concat([
|
|
309
|
+
decipher.update(ciphertext),
|
|
310
|
+
decipher.final()
|
|
311
|
+
]);
|
|
312
|
+
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
313
|
+
} catch (error) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function encrypt(key, variant, data) {
|
|
318
|
+
if (variant === "legacy") {
|
|
319
|
+
return encryptLegacy(data, key);
|
|
320
|
+
} else {
|
|
321
|
+
return encryptWithDataKey(data, key);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function decrypt(key, variant, data) {
|
|
325
|
+
if (variant === "legacy") {
|
|
326
|
+
return decryptLegacy(data, key);
|
|
327
|
+
} else {
|
|
328
|
+
return decryptWithDataKey(data, key);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
263
331
|
|
|
264
332
|
const defaultSettings = {
|
|
265
333
|
onboardingCompleted: false
|
|
@@ -323,8 +391,13 @@ async function updateSettings(updater) {
|
|
|
323
391
|
}
|
|
324
392
|
}
|
|
325
393
|
const credentialsSchema = z__namespace.object({
|
|
326
|
-
|
|
327
|
-
|
|
394
|
+
token: z__namespace.string(),
|
|
395
|
+
secret: z__namespace.string().base64().nullish(),
|
|
396
|
+
// Legacy
|
|
397
|
+
encryption: z__namespace.object({
|
|
398
|
+
publicKey: z__namespace.string().base64(),
|
|
399
|
+
machineKey: z__namespace.string().base64()
|
|
400
|
+
}).nullish()
|
|
328
401
|
});
|
|
329
402
|
async function readCredentials() {
|
|
330
403
|
if (!fs.existsSync(configuration.privateKeyFile)) {
|
|
@@ -333,15 +406,30 @@ async function readCredentials() {
|
|
|
333
406
|
try {
|
|
334
407
|
const keyBase64 = await promises.readFile(configuration.privateKeyFile, "utf8");
|
|
335
408
|
const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
409
|
+
if (credentials.secret) {
|
|
410
|
+
return {
|
|
411
|
+
token: credentials.token,
|
|
412
|
+
encryption: {
|
|
413
|
+
type: "legacy",
|
|
414
|
+
secret: new Uint8Array(Buffer.from(credentials.secret, "base64"))
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
} else if (credentials.encryption) {
|
|
418
|
+
return {
|
|
419
|
+
token: credentials.token,
|
|
420
|
+
encryption: {
|
|
421
|
+
type: "dataKey",
|
|
422
|
+
publicKey: new Uint8Array(Buffer.from(credentials.encryption.publicKey, "base64")),
|
|
423
|
+
machineKey: new Uint8Array(Buffer.from(credentials.encryption.machineKey, "base64"))
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
340
427
|
} catch {
|
|
341
428
|
return null;
|
|
342
429
|
}
|
|
430
|
+
return null;
|
|
343
431
|
}
|
|
344
|
-
async function
|
|
432
|
+
async function writeCredentialsLegacy(credentials) {
|
|
345
433
|
if (!fs.existsSync(configuration.happyHomeDir)) {
|
|
346
434
|
await promises.mkdir(configuration.happyHomeDir, { recursive: true });
|
|
347
435
|
}
|
|
@@ -350,6 +438,15 @@ async function writeCredentials(credentials) {
|
|
|
350
438
|
token: credentials.token
|
|
351
439
|
}, null, 2));
|
|
352
440
|
}
|
|
441
|
+
async function writeCredentialsDataKey(credentials) {
|
|
442
|
+
if (!fs.existsSync(configuration.happyHomeDir)) {
|
|
443
|
+
await promises.mkdir(configuration.happyHomeDir, { recursive: true });
|
|
444
|
+
}
|
|
445
|
+
await promises.writeFile(configuration.privateKeyFile, JSON.stringify({
|
|
446
|
+
encryption: { publicKey: encodeBase64(credentials.publicKey), machineKey: encodeBase64(credentials.machineKey) },
|
|
447
|
+
token: credentials.token
|
|
448
|
+
}, null, 2));
|
|
449
|
+
}
|
|
353
450
|
async function clearCredentials() {
|
|
354
451
|
if (fs.existsSync(configuration.privateKeyFile)) {
|
|
355
452
|
await promises.unlink(configuration.privateKeyFile);
|
|
@@ -684,32 +781,6 @@ z.z.object({
|
|
|
684
781
|
]),
|
|
685
782
|
createdAt: z.z.number()
|
|
686
783
|
});
|
|
687
|
-
z.z.object({
|
|
688
|
-
createdAt: z.z.number(),
|
|
689
|
-
id: z.z.string(),
|
|
690
|
-
seq: z.z.number(),
|
|
691
|
-
updatedAt: z.z.number(),
|
|
692
|
-
metadata: z.z.any(),
|
|
693
|
-
metadataVersion: z.z.number(),
|
|
694
|
-
agentState: z.z.any().nullable(),
|
|
695
|
-
agentStateVersion: z.z.number(),
|
|
696
|
-
// Connectivity tracking (from server)
|
|
697
|
-
connectivityStatus: z.z.union([
|
|
698
|
-
z.z.enum(["neverConnected", "online", "offline"]),
|
|
699
|
-
z.z.string()
|
|
700
|
-
// Forward compatibility
|
|
701
|
-
]).optional(),
|
|
702
|
-
connectivityStatusSince: z.z.number().optional(),
|
|
703
|
-
connectivityStatusReason: z.z.string().optional(),
|
|
704
|
-
// State tracking (from server)
|
|
705
|
-
state: z.z.union([
|
|
706
|
-
z.z.enum(["running", "archiveRequested", "archived"]),
|
|
707
|
-
z.z.string()
|
|
708
|
-
// Forward compatibility
|
|
709
|
-
]).optional(),
|
|
710
|
-
stateSince: z.z.number().optional(),
|
|
711
|
-
stateReason: z.z.string().optional()
|
|
712
|
-
});
|
|
713
784
|
z.z.object({
|
|
714
785
|
host: z.z.string(),
|
|
715
786
|
platform: z.z.string(),
|
|
@@ -734,37 +805,6 @@ z.z.object({
|
|
|
734
805
|
// Forward compatibility
|
|
735
806
|
]).optional()
|
|
736
807
|
});
|
|
737
|
-
z.z.object({
|
|
738
|
-
id: z.z.string(),
|
|
739
|
-
metadata: z.z.any(),
|
|
740
|
-
// Decrypted MachineMetadata
|
|
741
|
-
metadataVersion: z.z.number(),
|
|
742
|
-
daemonState: z.z.any().nullable(),
|
|
743
|
-
// Decrypted DaemonState
|
|
744
|
-
daemonStateVersion: z.z.number(),
|
|
745
|
-
// We don't really care about these on the CLI for now
|
|
746
|
-
// ApiMachineClient will not sync these
|
|
747
|
-
active: z.z.boolean(),
|
|
748
|
-
activeAt: z.z.number(),
|
|
749
|
-
createdAt: z.z.number(),
|
|
750
|
-
updatedAt: z.z.number(),
|
|
751
|
-
// Connectivity tracking (from server)
|
|
752
|
-
connectivityStatus: z.z.union([
|
|
753
|
-
z.z.enum(["neverConnected", "online", "offline"]),
|
|
754
|
-
z.z.string()
|
|
755
|
-
// Forward compatibility
|
|
756
|
-
]).optional(),
|
|
757
|
-
connectivityStatusSince: z.z.number().optional(),
|
|
758
|
-
connectivityStatusReason: z.z.string().optional(),
|
|
759
|
-
// State tracking (from server)
|
|
760
|
-
state: z.z.union([
|
|
761
|
-
z.z.enum(["running", "archiveRequested", "archived"]),
|
|
762
|
-
z.z.string()
|
|
763
|
-
// Forward compatibility
|
|
764
|
-
]).optional(),
|
|
765
|
-
stateSince: z.z.number().optional(),
|
|
766
|
-
stateReason: z.z.string().optional()
|
|
767
|
-
});
|
|
768
808
|
z.z.object({
|
|
769
809
|
content: SessionMessageContentSchema,
|
|
770
810
|
createdAt: z.z.number(),
|
|
@@ -888,12 +928,14 @@ class AsyncLock {
|
|
|
888
928
|
class RpcHandlerManager {
|
|
889
929
|
handlers = /* @__PURE__ */ new Map();
|
|
890
930
|
scopePrefix;
|
|
891
|
-
|
|
931
|
+
encryptionKey;
|
|
932
|
+
encryptionVariant;
|
|
892
933
|
logger;
|
|
893
934
|
socket = null;
|
|
894
935
|
constructor(config) {
|
|
895
936
|
this.scopePrefix = config.scopePrefix;
|
|
896
|
-
this.
|
|
937
|
+
this.encryptionKey = config.encryptionKey;
|
|
938
|
+
this.encryptionVariant = config.encryptionVariant;
|
|
897
939
|
this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
|
|
898
940
|
}
|
|
899
941
|
/**
|
|
@@ -919,20 +961,19 @@ class RpcHandlerManager {
|
|
|
919
961
|
if (!handler) {
|
|
920
962
|
this.logger("[RPC] [ERROR] Method not found", { method: request.method });
|
|
921
963
|
const errorResponse = { error: "Method not found" };
|
|
922
|
-
const encryptedError = encodeBase64(encrypt(
|
|
964
|
+
const encryptedError = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
|
|
923
965
|
return encryptedError;
|
|
924
966
|
}
|
|
925
|
-
const decryptedParams = decrypt(decodeBase64(request.params)
|
|
967
|
+
const decryptedParams = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(request.params));
|
|
926
968
|
const result = await handler(decryptedParams);
|
|
927
|
-
const encryptedResponse = encodeBase64(encrypt(
|
|
969
|
+
const encryptedResponse = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, result));
|
|
928
970
|
return encryptedResponse;
|
|
929
971
|
} catch (error) {
|
|
930
972
|
this.logger("[RPC] [ERROR] Error handling request", { error });
|
|
931
973
|
const errorResponse = {
|
|
932
974
|
error: error instanceof Error ? error.message : "Unknown error"
|
|
933
975
|
};
|
|
934
|
-
|
|
935
|
-
return encryptedError;
|
|
976
|
+
return encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, errorResponse));
|
|
936
977
|
}
|
|
937
978
|
}
|
|
938
979
|
onSocketConnect(socket) {
|
|
@@ -974,7 +1015,7 @@ class RpcHandlerManager {
|
|
|
974
1015
|
}
|
|
975
1016
|
}
|
|
976
1017
|
|
|
977
|
-
const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-
|
|
1018
|
+
const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-BcDnTXMg.cjs', document.baseURI).href))));
|
|
978
1019
|
function projectPath() {
|
|
979
1020
|
const path$1 = path.resolve(__dirname$1, "..");
|
|
980
1021
|
return path$1;
|
|
@@ -1277,7 +1318,6 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1277
1318
|
|
|
1278
1319
|
class ApiSessionClient extends node_events.EventEmitter {
|
|
1279
1320
|
token;
|
|
1280
|
-
secret;
|
|
1281
1321
|
sessionId;
|
|
1282
1322
|
metadata;
|
|
1283
1323
|
metadataVersion;
|
|
@@ -1289,18 +1329,22 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1289
1329
|
rpcHandlerManager;
|
|
1290
1330
|
agentStateLock = new AsyncLock();
|
|
1291
1331
|
metadataLock = new AsyncLock();
|
|
1292
|
-
|
|
1332
|
+
encryptionKey;
|
|
1333
|
+
encryptionVariant;
|
|
1334
|
+
constructor(token, session) {
|
|
1293
1335
|
super();
|
|
1294
1336
|
this.token = token;
|
|
1295
|
-
this.secret = secret;
|
|
1296
1337
|
this.sessionId = session.id;
|
|
1297
1338
|
this.metadata = session.metadata;
|
|
1298
1339
|
this.metadataVersion = session.metadataVersion;
|
|
1299
1340
|
this.agentState = session.agentState;
|
|
1300
1341
|
this.agentStateVersion = session.agentStateVersion;
|
|
1342
|
+
this.encryptionKey = session.encryptionKey;
|
|
1343
|
+
this.encryptionVariant = session.encryptionVariant;
|
|
1301
1344
|
this.rpcHandlerManager = new RpcHandlerManager({
|
|
1302
1345
|
scopePrefix: this.sessionId,
|
|
1303
|
-
|
|
1346
|
+
encryptionKey: this.encryptionKey,
|
|
1347
|
+
encryptionVariant: this.encryptionVariant,
|
|
1304
1348
|
logger: (msg, data) => logger.debug(msg, data)
|
|
1305
1349
|
});
|
|
1306
1350
|
registerCommonHandlers(this.rpcHandlerManager);
|
|
@@ -1342,7 +1386,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1342
1386
|
return;
|
|
1343
1387
|
}
|
|
1344
1388
|
if (data.body.t === "new-message" && data.body.message.content.t === "encrypted") {
|
|
1345
|
-
const body = decrypt(decodeBase64(data.body.message.content.c)
|
|
1389
|
+
const body = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.message.content.c));
|
|
1346
1390
|
logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
|
|
1347
1391
|
const userResult = UserMessageSchema.safeParse(body);
|
|
1348
1392
|
if (userResult.success) {
|
|
@@ -1356,11 +1400,11 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1356
1400
|
}
|
|
1357
1401
|
} else if (data.body.t === "update-session") {
|
|
1358
1402
|
if (data.body.metadata && data.body.metadata.version > this.metadataVersion) {
|
|
1359
|
-
this.metadata = decrypt(decodeBase64(data.body.metadata.value)
|
|
1403
|
+
this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.metadata.value));
|
|
1360
1404
|
this.metadataVersion = data.body.metadata.version;
|
|
1361
1405
|
}
|
|
1362
1406
|
if (data.body.agentState && data.body.agentState.version > this.agentStateVersion) {
|
|
1363
|
-
this.agentState = data.body.agentState.value ? decrypt(decodeBase64(data.body.agentState.value)
|
|
1407
|
+
this.agentState = data.body.agentState.value ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(data.body.agentState.value)) : null;
|
|
1364
1408
|
this.agentStateVersion = data.body.agentState.version;
|
|
1365
1409
|
}
|
|
1366
1410
|
} else if (data.body.t === "update-machine") {
|
|
@@ -1414,7 +1458,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1414
1458
|
};
|
|
1415
1459
|
}
|
|
1416
1460
|
logger.debugLargeJson("[SOCKET] Sending message through socket:", content);
|
|
1417
|
-
const encrypted = encodeBase64(encrypt(
|
|
1461
|
+
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
1418
1462
|
this.socket.emit("message", {
|
|
1419
1463
|
sid: this.sessionId,
|
|
1420
1464
|
message: encrypted
|
|
@@ -1448,7 +1492,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1448
1492
|
sentFrom: "cli"
|
|
1449
1493
|
}
|
|
1450
1494
|
};
|
|
1451
|
-
const encrypted = encodeBase64(encrypt(
|
|
1495
|
+
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
1452
1496
|
this.socket.emit("message", {
|
|
1453
1497
|
sid: this.sessionId,
|
|
1454
1498
|
message: encrypted
|
|
@@ -1463,7 +1507,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1463
1507
|
data: event
|
|
1464
1508
|
}
|
|
1465
1509
|
};
|
|
1466
|
-
const encrypted = encodeBase64(encrypt(
|
|
1510
|
+
const encrypted = encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, content));
|
|
1467
1511
|
this.socket.emit("message", {
|
|
1468
1512
|
sid: this.sessionId,
|
|
1469
1513
|
message: encrypted
|
|
@@ -1523,14 +1567,14 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1523
1567
|
this.metadataLock.inLock(async () => {
|
|
1524
1568
|
await backoff(async () => {
|
|
1525
1569
|
let updated = handler(this.metadata);
|
|
1526
|
-
const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(
|
|
1570
|
+
const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) });
|
|
1527
1571
|
if (answer.result === "success") {
|
|
1528
|
-
this.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1572
|
+
this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
|
|
1529
1573
|
this.metadataVersion = answer.version;
|
|
1530
1574
|
} else if (answer.result === "version-mismatch") {
|
|
1531
1575
|
if (answer.version > this.metadataVersion) {
|
|
1532
1576
|
this.metadataVersion = answer.version;
|
|
1533
|
-
this.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1577
|
+
this.metadata = decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.metadata));
|
|
1534
1578
|
}
|
|
1535
1579
|
throw new Error("Metadata version mismatch");
|
|
1536
1580
|
} else if (answer.result === "error") ;
|
|
@@ -1546,15 +1590,15 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1546
1590
|
this.agentStateLock.inLock(async () => {
|
|
1547
1591
|
await backoff(async () => {
|
|
1548
1592
|
let updated = handler(this.agentState || {});
|
|
1549
|
-
const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(
|
|
1593
|
+
const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(this.encryptionKey, this.encryptionVariant, updated)) : null });
|
|
1550
1594
|
if (answer.result === "success") {
|
|
1551
|
-
this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState)
|
|
1595
|
+
this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
|
|
1552
1596
|
this.agentStateVersion = answer.version;
|
|
1553
1597
|
logger.debug("Agent state updated", this.agentState);
|
|
1554
1598
|
} else if (answer.result === "version-mismatch") {
|
|
1555
1599
|
if (answer.version > this.agentStateVersion) {
|
|
1556
1600
|
this.agentStateVersion = answer.version;
|
|
1557
|
-
this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState)
|
|
1601
|
+
this.agentState = answer.agentState ? decrypt(this.encryptionKey, this.encryptionVariant, decodeBase64(answer.agentState)) : null;
|
|
1558
1602
|
}
|
|
1559
1603
|
throw new Error("Agent state version mismatch");
|
|
1560
1604
|
} else if (answer.result === "error") ;
|
|
@@ -1584,13 +1628,13 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1584
1628
|
}
|
|
1585
1629
|
|
|
1586
1630
|
class ApiMachineClient {
|
|
1587
|
-
constructor(token,
|
|
1631
|
+
constructor(token, machine) {
|
|
1588
1632
|
this.token = token;
|
|
1589
|
-
this.secret = secret;
|
|
1590
1633
|
this.machine = machine;
|
|
1591
1634
|
this.rpcHandlerManager = new RpcHandlerManager({
|
|
1592
1635
|
scopePrefix: this.machine.id,
|
|
1593
|
-
|
|
1636
|
+
encryptionKey: this.machine.encryptionKey,
|
|
1637
|
+
encryptionVariant: this.machine.encryptionVariant,
|
|
1594
1638
|
logger: (msg, data) => logger.debug(msg, data)
|
|
1595
1639
|
});
|
|
1596
1640
|
registerCommonHandlers(this.rpcHandlerManager);
|
|
@@ -1651,17 +1695,17 @@ class ApiMachineClient {
|
|
|
1651
1695
|
const updated = handler(this.machine.metadata);
|
|
1652
1696
|
const answer = await this.socket.emitWithAck("machine-update-metadata", {
|
|
1653
1697
|
machineId: this.machine.id,
|
|
1654
|
-
metadata: encodeBase64(encrypt(
|
|
1698
|
+
metadata: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
|
|
1655
1699
|
expectedVersion: this.machine.metadataVersion
|
|
1656
1700
|
});
|
|
1657
1701
|
if (answer.result === "success") {
|
|
1658
|
-
this.machine.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1702
|
+
this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
|
|
1659
1703
|
this.machine.metadataVersion = answer.version;
|
|
1660
1704
|
logger.debug("[API MACHINE] Metadata updated successfully");
|
|
1661
1705
|
} else if (answer.result === "version-mismatch") {
|
|
1662
1706
|
if (answer.version > this.machine.metadataVersion) {
|
|
1663
1707
|
this.machine.metadataVersion = answer.version;
|
|
1664
|
-
this.machine.metadata = decrypt(decodeBase64(answer.metadata)
|
|
1708
|
+
this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.metadata));
|
|
1665
1709
|
}
|
|
1666
1710
|
throw new Error("Metadata version mismatch");
|
|
1667
1711
|
}
|
|
@@ -1676,17 +1720,17 @@ class ApiMachineClient {
|
|
|
1676
1720
|
const updated = handler(this.machine.daemonState);
|
|
1677
1721
|
const answer = await this.socket.emitWithAck("machine-update-state", {
|
|
1678
1722
|
machineId: this.machine.id,
|
|
1679
|
-
daemonState: encodeBase64(encrypt(
|
|
1723
|
+
daemonState: encodeBase64(encrypt(this.machine.encryptionKey, this.machine.encryptionVariant, updated)),
|
|
1680
1724
|
expectedVersion: this.machine.daemonStateVersion
|
|
1681
1725
|
});
|
|
1682
1726
|
if (answer.result === "success") {
|
|
1683
|
-
this.machine.daemonState = decrypt(decodeBase64(answer.daemonState)
|
|
1727
|
+
this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
|
|
1684
1728
|
this.machine.daemonStateVersion = answer.version;
|
|
1685
1729
|
logger.debug("[API MACHINE] Daemon state updated successfully");
|
|
1686
1730
|
} else if (answer.result === "version-mismatch") {
|
|
1687
1731
|
if (answer.version > this.machine.daemonStateVersion) {
|
|
1688
1732
|
this.machine.daemonStateVersion = answer.version;
|
|
1689
|
-
this.machine.daemonState = decrypt(decodeBase64(answer.daemonState)
|
|
1733
|
+
this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(answer.daemonState));
|
|
1690
1734
|
}
|
|
1691
1735
|
throw new Error("Daemon state version mismatch");
|
|
1692
1736
|
}
|
|
@@ -1733,12 +1777,12 @@ class ApiMachineClient {
|
|
|
1733
1777
|
const update = data.body;
|
|
1734
1778
|
if (update.metadata) {
|
|
1735
1779
|
logger.debug("[API MACHINE] Received external metadata update");
|
|
1736
|
-
this.machine.metadata = decrypt(decodeBase64(update.metadata.value)
|
|
1780
|
+
this.machine.metadata = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.metadata.value));
|
|
1737
1781
|
this.machine.metadataVersion = update.metadata.version;
|
|
1738
1782
|
}
|
|
1739
1783
|
if (update.daemonState) {
|
|
1740
1784
|
logger.debug("[API MACHINE] Received external daemon state update");
|
|
1741
|
-
this.machine.daemonState = decrypt(decodeBase64(update.daemonState.value)
|
|
1785
|
+
this.machine.daemonState = decrypt(this.machine.encryptionKey, this.machine.encryptionVariant, decodeBase64(update.daemonState.value));
|
|
1742
1786
|
this.machine.daemonStateVersion = update.daemonState.version;
|
|
1743
1787
|
}
|
|
1744
1788
|
} else {
|
|
@@ -1910,46 +1954,62 @@ class PushNotificationClient {
|
|
|
1910
1954
|
}
|
|
1911
1955
|
|
|
1912
1956
|
class ApiClient {
|
|
1913
|
-
|
|
1914
|
-
|
|
1957
|
+
static async create(credential) {
|
|
1958
|
+
return new ApiClient(credential);
|
|
1959
|
+
}
|
|
1960
|
+
credential;
|
|
1915
1961
|
pushClient;
|
|
1916
|
-
constructor(
|
|
1917
|
-
this.
|
|
1918
|
-
this.
|
|
1919
|
-
this.pushClient = new PushNotificationClient(token);
|
|
1962
|
+
constructor(credential) {
|
|
1963
|
+
this.credential = credential;
|
|
1964
|
+
this.pushClient = new PushNotificationClient(credential.token, configuration.serverUrl);
|
|
1920
1965
|
}
|
|
1921
1966
|
/**
|
|
1922
1967
|
* Create a new session or load existing one with the given tag
|
|
1923
1968
|
*/
|
|
1924
1969
|
async getOrCreateSession(opts) {
|
|
1970
|
+
let dataEncryptionKey = null;
|
|
1971
|
+
let encryptionKey;
|
|
1972
|
+
let encryptionVariant;
|
|
1973
|
+
if (this.credential.encryption.type === "dataKey") {
|
|
1974
|
+
encryptionKey = getRandomBytes(32);
|
|
1975
|
+
encryptionVariant = "dataKey";
|
|
1976
|
+
let encryptedDataKey = libsodiumEncryptForPublicKey(encryptionKey, this.credential.encryption.publicKey);
|
|
1977
|
+
dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
|
|
1978
|
+
dataEncryptionKey.set([0], 0);
|
|
1979
|
+
dataEncryptionKey.set(encryptedDataKey, 1);
|
|
1980
|
+
} else {
|
|
1981
|
+
encryptionKey = this.credential.encryption.secret;
|
|
1982
|
+
encryptionVariant = "legacy";
|
|
1983
|
+
}
|
|
1925
1984
|
try {
|
|
1926
1985
|
const response = await axios.post(
|
|
1927
1986
|
`${configuration.serverUrl}/v1/sessions`,
|
|
1928
1987
|
{
|
|
1929
1988
|
tag: opts.tag,
|
|
1930
|
-
metadata: encodeBase64(encrypt(opts.metadata
|
|
1931
|
-
agentState: opts.state ? encodeBase64(encrypt(opts.state
|
|
1989
|
+
metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
|
|
1990
|
+
agentState: opts.state ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.state)) : null,
|
|
1991
|
+
dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : null
|
|
1932
1992
|
},
|
|
1933
1993
|
{
|
|
1934
1994
|
headers: {
|
|
1935
|
-
"Authorization": `Bearer ${this.token}`,
|
|
1995
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
1936
1996
|
"Content-Type": "application/json"
|
|
1937
1997
|
},
|
|
1938
|
-
timeout:
|
|
1939
|
-
//
|
|
1998
|
+
timeout: 6e4
|
|
1999
|
+
// 1 minute timeout for very bad network connections
|
|
1940
2000
|
}
|
|
1941
2001
|
);
|
|
1942
2002
|
logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
|
|
1943
2003
|
let raw = response.data.session;
|
|
1944
2004
|
let session = {
|
|
1945
2005
|
id: raw.id,
|
|
1946
|
-
createdAt: raw.createdAt,
|
|
1947
|
-
updatedAt: raw.updatedAt,
|
|
1948
2006
|
seq: raw.seq,
|
|
1949
|
-
metadata: decrypt(decodeBase64(raw.metadata)
|
|
2007
|
+
metadata: decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)),
|
|
1950
2008
|
metadataVersion: raw.metadataVersion,
|
|
1951
|
-
agentState: raw.agentState ? decrypt(decodeBase64(raw.agentState)
|
|
1952
|
-
agentStateVersion: raw.agentStateVersion
|
|
2009
|
+
agentState: raw.agentState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.agentState)) : null,
|
|
2010
|
+
agentStateVersion: raw.agentStateVersion,
|
|
2011
|
+
encryptionKey,
|
|
2012
|
+
encryptionVariant
|
|
1953
2013
|
};
|
|
1954
2014
|
return session;
|
|
1955
2015
|
} catch (error) {
|
|
@@ -1957,54 +2017,40 @@ class ApiClient {
|
|
|
1957
2017
|
throw new Error(`Failed to get or create session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1958
2018
|
}
|
|
1959
2019
|
}
|
|
1960
|
-
/**
|
|
1961
|
-
* Get machine by ID from the server
|
|
1962
|
-
* Returns the current machine state from the server with decrypted metadata and daemonState
|
|
1963
|
-
*/
|
|
1964
|
-
async getMachine(machineId) {
|
|
1965
|
-
const response = await axios.get(`${configuration.serverUrl}/v1/machines/${machineId}`, {
|
|
1966
|
-
headers: {
|
|
1967
|
-
"Authorization": `Bearer ${this.token}`,
|
|
1968
|
-
"Content-Type": "application/json"
|
|
1969
|
-
},
|
|
1970
|
-
timeout: 2e3
|
|
1971
|
-
});
|
|
1972
|
-
const raw = response.data.machine;
|
|
1973
|
-
if (!raw) {
|
|
1974
|
-
return null;
|
|
1975
|
-
}
|
|
1976
|
-
logger.debug(`[API] Machine ${machineId} fetched from server`);
|
|
1977
|
-
const machine = {
|
|
1978
|
-
id: raw.id,
|
|
1979
|
-
metadata: raw.metadata ? decrypt(decodeBase64(raw.metadata), this.secret) : null,
|
|
1980
|
-
metadataVersion: raw.metadataVersion || 0,
|
|
1981
|
-
daemonState: raw.daemonState ? decrypt(decodeBase64(raw.daemonState), this.secret) : null,
|
|
1982
|
-
daemonStateVersion: raw.daemonStateVersion || 0,
|
|
1983
|
-
active: raw.active,
|
|
1984
|
-
activeAt: raw.activeAt,
|
|
1985
|
-
createdAt: raw.createdAt,
|
|
1986
|
-
updatedAt: raw.updatedAt
|
|
1987
|
-
};
|
|
1988
|
-
return machine;
|
|
1989
|
-
}
|
|
1990
2020
|
/**
|
|
1991
2021
|
* Register or update machine with the server
|
|
1992
2022
|
* Returns the current machine state from the server with decrypted metadata and daemonState
|
|
1993
2023
|
*/
|
|
1994
2024
|
async getOrCreateMachine(opts) {
|
|
2025
|
+
let dataEncryptionKey = null;
|
|
2026
|
+
let encryptionKey;
|
|
2027
|
+
let encryptionVariant;
|
|
2028
|
+
if (this.credential.encryption.type === "dataKey") {
|
|
2029
|
+
encryptionVariant = "dataKey";
|
|
2030
|
+
encryptionKey = this.credential.encryption.machineKey;
|
|
2031
|
+
let encryptedDataKey = libsodiumEncryptForPublicKey(this.credential.encryption.machineKey, this.credential.encryption.publicKey);
|
|
2032
|
+
dataEncryptionKey = new Uint8Array(encryptedDataKey.length + 1);
|
|
2033
|
+
dataEncryptionKey.set([0], 0);
|
|
2034
|
+
dataEncryptionKey.set(encryptedDataKey, 1);
|
|
2035
|
+
} else {
|
|
2036
|
+
encryptionKey = this.credential.encryption.secret;
|
|
2037
|
+
encryptionVariant = "legacy";
|
|
2038
|
+
}
|
|
1995
2039
|
const response = await axios.post(
|
|
1996
2040
|
`${configuration.serverUrl}/v1/machines`,
|
|
1997
2041
|
{
|
|
1998
2042
|
id: opts.machineId,
|
|
1999
|
-
metadata: encodeBase64(encrypt(opts.metadata
|
|
2000
|
-
daemonState: opts.daemonState ? encodeBase64(encrypt(opts.daemonState
|
|
2043
|
+
metadata: encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.metadata)),
|
|
2044
|
+
daemonState: opts.daemonState ? encodeBase64(encrypt(encryptionKey, encryptionVariant, opts.daemonState)) : void 0,
|
|
2045
|
+
dataEncryptionKey: dataEncryptionKey ? encodeBase64(dataEncryptionKey) : void 0
|
|
2001
2046
|
},
|
|
2002
2047
|
{
|
|
2003
2048
|
headers: {
|
|
2004
|
-
"Authorization": `Bearer ${this.token}`,
|
|
2049
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
2005
2050
|
"Content-Type": "application/json"
|
|
2006
2051
|
},
|
|
2007
|
-
timeout:
|
|
2052
|
+
timeout: 6e4
|
|
2053
|
+
// 1 minute timeout for very bad network connections
|
|
2008
2054
|
}
|
|
2009
2055
|
);
|
|
2010
2056
|
if (response.status !== 200) {
|
|
@@ -2016,22 +2062,20 @@ class ApiClient {
|
|
|
2016
2062
|
logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
|
|
2017
2063
|
const machine = {
|
|
2018
2064
|
id: raw.id,
|
|
2019
|
-
|
|
2065
|
+
encryptionKey,
|
|
2066
|
+
encryptionVariant,
|
|
2067
|
+
metadata: raw.metadata ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.metadata)) : null,
|
|
2020
2068
|
metadataVersion: raw.metadataVersion || 0,
|
|
2021
|
-
daemonState: raw.daemonState ? decrypt(decodeBase64(raw.daemonState)
|
|
2022
|
-
daemonStateVersion: raw.daemonStateVersion || 0
|
|
2023
|
-
active: raw.active,
|
|
2024
|
-
activeAt: raw.activeAt,
|
|
2025
|
-
createdAt: raw.createdAt,
|
|
2026
|
-
updatedAt: raw.updatedAt
|
|
2069
|
+
daemonState: raw.daemonState ? decrypt(encryptionKey, encryptionVariant, decodeBase64(raw.daemonState)) : null,
|
|
2070
|
+
daemonStateVersion: raw.daemonStateVersion || 0
|
|
2027
2071
|
};
|
|
2028
2072
|
return machine;
|
|
2029
2073
|
}
|
|
2030
2074
|
sessionSyncClient(session) {
|
|
2031
|
-
return new ApiSessionClient(this.token,
|
|
2075
|
+
return new ApiSessionClient(this.credential.token, session);
|
|
2032
2076
|
}
|
|
2033
2077
|
machineSyncClient(machine) {
|
|
2034
|
-
return new ApiMachineClient(this.token,
|
|
2078
|
+
return new ApiMachineClient(this.credential.token, machine);
|
|
2035
2079
|
}
|
|
2036
2080
|
push() {
|
|
2037
2081
|
return this.pushClient;
|
|
@@ -2049,7 +2093,7 @@ class ApiClient {
|
|
|
2049
2093
|
},
|
|
2050
2094
|
{
|
|
2051
2095
|
headers: {
|
|
2052
|
-
"Authorization": `Bearer ${this.token}`,
|
|
2096
|
+
"Authorization": `Bearer ${this.credential.token}`,
|
|
2053
2097
|
"Content-Type": "application/json"
|
|
2054
2098
|
},
|
|
2055
2099
|
timeout: 5e3
|
|
@@ -2137,5 +2181,6 @@ exports.readDaemonState = readDaemonState;
|
|
|
2137
2181
|
exports.readSettings = readSettings;
|
|
2138
2182
|
exports.releaseDaemonLock = releaseDaemonLock;
|
|
2139
2183
|
exports.updateSettings = updateSettings;
|
|
2140
|
-
exports.
|
|
2184
|
+
exports.writeCredentialsDataKey = writeCredentialsDataKey;
|
|
2185
|
+
exports.writeCredentialsLegacy = writeCredentialsLegacy;
|
|
2141
2186
|
exports.writeDaemonState = writeDaemonState;
|