@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@badgerclaw/connect",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "BadgerClaw channel plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -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
- const deviceId = whoami.device_id ?? null;
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 client not available, skipping key backup setup");
35
+ LogService.info("MatrixKeyBackup", "Crypto not available, skipping backup setup");
47
36
  return;
48
37
  }
49
38
 
50
39
  try {
51
- const backupInfo = await client.getKeyBackupVersion();
40
+ let backupInfo = await client.getKeyBackupVersion();
41
+
52
42
  if (!backupInfo) {
53
- LogService.info(
54
- "MatrixKeyBackup",
55
- "No key backup version found on server skipping backup setup",
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
- "MatrixKeyBackup",
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", "Key backup setup failed:", err);
80
+ LogService.warn("MatrixKeyBackup", "Backup setup failed:", err);
68
81
  }
69
82
  }
70
83
 
71
- function _persistBackupRecord(version: string, deviceId: string): void {
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
- const path = join(dir, "key-backup-record.log");
76
- const entry =
77
- JSON.stringify({ version, deviceId, timestamp: new Date().toISOString() }) + "\n";
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
  }