@badgerclaw/connect 1.1.1 → 1.1.3
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/package.json +1 -1
- package/src/matrix/client/backup.ts +44 -35
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { MatrixClient } from "@vector-im/matrix-bot-sdk";
|
|
2
|
-
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { appendFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { generateKeyPairSync } from "node:crypto";
|
|
5
6
|
import { getMatrixLogService } from "../sdk-runtime.js";
|
|
6
7
|
|
|
7
8
|
export type KeyBackupStatus = {
|
|
@@ -10,19 +11,12 @@ export type KeyBackupStatus = {
|
|
|
10
11
|
deviceId: string | null;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
export async function getEncryptionKeyBackupStatus(
|
|
14
|
-
client: MatrixClient,
|
|
15
|
-
): Promise<KeyBackupStatus> {
|
|
14
|
+
export async function getEncryptionKeyBackupStatus(client: MatrixClient): Promise<KeyBackupStatus> {
|
|
16
15
|
const LogService = getMatrixLogService();
|
|
17
16
|
try {
|
|
18
17
|
const backupInfo = await client.getKeyBackupVersion();
|
|
19
18
|
const whoami = await client.getWhoAmI();
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
enabled: backupInfo !== null,
|
|
23
|
-
version: backupInfo?.version ?? null,
|
|
24
|
-
deviceId,
|
|
25
|
-
};
|
|
19
|
+
return { enabled: backupInfo !== null, version: backupInfo?.version ?? null, deviceId: whoami.device_id ?? null };
|
|
26
20
|
} catch (err) {
|
|
27
21
|
LogService.warn("MatrixKeyBackup", "Failed to check key backup status:", err);
|
|
28
22
|
return { enabled: false, version: null, deviceId: null };
|
|
@@ -33,50 +27,65 @@ export async function setupKeyBackup(client: MatrixClient): Promise<void> {
|
|
|
33
27
|
const LogService = getMatrixLogService();
|
|
34
28
|
|
|
35
29
|
let deviceId = "(unknown)";
|
|
36
|
-
try {
|
|
37
|
-
const whoami = await client.getWhoAmI();
|
|
38
|
-
deviceId = whoami.device_id ?? deviceId;
|
|
39
|
-
} catch {
|
|
40
|
-
// Non-fatal
|
|
41
|
-
}
|
|
30
|
+
try { deviceId = (await client.getWhoAmI()).device_id ?? deviceId; } catch { /* non-fatal */ }
|
|
42
31
|
|
|
43
32
|
LogService.info("MatrixKeyBackup", `Crypto ready — device ID: ${deviceId}`);
|
|
44
33
|
|
|
45
34
|
if (!client.crypto) {
|
|
46
|
-
LogService.info("MatrixKeyBackup", "Crypto
|
|
35
|
+
LogService.info("MatrixKeyBackup", "Crypto not available, skipping backup setup");
|
|
47
36
|
return;
|
|
48
37
|
}
|
|
49
38
|
|
|
50
39
|
try {
|
|
51
|
-
|
|
40
|
+
let backupInfo = await client.getKeyBackupVersion();
|
|
41
|
+
|
|
52
42
|
if (!backupInfo) {
|
|
53
|
-
LogService.info(
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
LogService.info("MatrixKeyBackup", "No backup found — generating Curve25519 key pair and creating backup version");
|
|
44
|
+
|
|
45
|
+
// Generate X25519 (Curve25519) key pair using Node crypto
|
|
46
|
+
const { publicKey: pubKeyObj, privateKey: privKeyObj } = generateKeyPairSync("x25519");
|
|
47
|
+
const pubKeyRaw = pubKeyObj.export({ type: "spki", format: "der" }).slice(-32);
|
|
48
|
+
const privKeyRaw = privKeyObj.export({ type: "pkcs8", format: "der" }).slice(-32);
|
|
49
|
+
const publicKeyBase64 = pubKeyRaw.toString("base64");
|
|
50
|
+
const privateKeyBase64 = privKeyRaw.toString("base64");
|
|
51
|
+
|
|
52
|
+
// Create the backup version on the server
|
|
53
|
+
await client.signAndCreateKeyBackupVersion({
|
|
54
|
+
algorithm: "m.megolm_backup.v1.curve25519-aes-sha2",
|
|
55
|
+
auth_data: { public_key: publicKeyBase64 },
|
|
56
|
+
} as any);
|
|
57
|
+
|
|
58
|
+
// Save private key for cross-device restore
|
|
59
|
+
const backupDir = join(homedir(), ".openclaw", "backup");
|
|
60
|
+
mkdirSync(backupDir, { recursive: true });
|
|
61
|
+
writeFileSync(
|
|
62
|
+
join(backupDir, `private-key-${deviceId}.json`),
|
|
63
|
+
JSON.stringify({ privateKey: privateKeyBase64, deviceId, created: new Date().toISOString() }),
|
|
64
|
+
{ mode: 0o600 }
|
|
56
65
|
);
|
|
66
|
+
|
|
67
|
+
backupInfo = await client.getKeyBackupVersion();
|
|
68
|
+
LogService.info("MatrixKeyBackup", `Created backup version ${backupInfo?.version}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!backupInfo) {
|
|
72
|
+
LogService.warn("MatrixKeyBackup", "No backup info after creation attempt");
|
|
57
73
|
return;
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
await client.crypto.enableKeyBackup(backupInfo);
|
|
61
|
-
LogService.info(
|
|
62
|
-
|
|
63
|
-
`Key backup enabled (version ${backupInfo.version}) on device ${deviceId}`,
|
|
64
|
-
);
|
|
65
|
-
_persistBackupRecord(backupInfo.version, deviceId);
|
|
77
|
+
LogService.info("MatrixKeyBackup", `Backup enabled v${backupInfo.version} on ${deviceId} — uploading room keys`);
|
|
78
|
+
_persistRecord(backupInfo.version, deviceId);
|
|
66
79
|
} catch (err) {
|
|
67
|
-
LogService.warn("MatrixKeyBackup", "
|
|
80
|
+
LogService.warn("MatrixKeyBackup", "Backup setup failed:", err);
|
|
68
81
|
}
|
|
69
82
|
}
|
|
70
83
|
|
|
71
|
-
function
|
|
84
|
+
function _persistRecord(version: string, deviceId: string): void {
|
|
72
85
|
try {
|
|
73
86
|
const dir = join(homedir(), ".openclaw", "backup");
|
|
74
87
|
mkdirSync(dir, { recursive: true });
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
appendFileSync(path, entry, "utf8");
|
|
79
|
-
} catch {
|
|
80
|
-
// Non-fatal — backup is enabled, record persistence is best-effort
|
|
81
|
-
}
|
|
88
|
+
appendFileSync(join(dir, "key-backup-record.log"),
|
|
89
|
+
JSON.stringify({ version, deviceId, timestamp: new Date().toISOString() }) + "\n");
|
|
90
|
+
} catch { /* non-fatal */ }
|
|
82
91
|
}
|