@agentunion/fastaun 0.5.4 → 0.5.6

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.
Files changed (133) hide show
  1. package/CHANGELOG.md +126 -90
  2. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/216/245/345/205/245/344/270/216/346/274/224/347/244/272/346/214/207/345/215/227.md +290 -0
  3. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/346/236/266/346/236/204/344/270/216/350/257/246/347/273/206/345/256/236/347/216/260/350/256/241/345/210/222-codex.md +994 -0
  4. package/_packed_docs/AUN/347/246/273/347/272/277/346/216/250/351/200/201/346/234/215/345/212/241/350/277/220/347/273/264/344/270/216/345/217/221/345/270/203/346/214/207/345/215/227.md +144 -0
  5. package/_packed_docs/CHANGELOG.md +126 -90
  6. package/_packed_docs/INDEX.md +127 -69
  7. package/_packed_docs/KITE_DOCS_GUIDE.md +63 -29
  8. package/_packed_docs/audit/AUN/346/234/215/345/212/241Go/345/214/226/351/207/215/346/236/204/347/262/276/347/273/206/345/214/226/346/272/220/347/240/201/345/256/241/346/237/245-20260718.md +366 -0
  9. package/_packed_docs/aun/345/205/254/347/275/221/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +227 -0
  10. package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +1004 -0
  11. package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +999 -0
  12. package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +6 -4
  13. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +58 -20
  14. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +219 -247
  15. package/_packed_docs/protocol/12-Stream-/345/255/220/345/215/217/350/256/256.md +14 -14
  16. package/_packed_docs/protocol/13-Agent/350/241/214/344/270/272/350/247/204/350/214/203.md +3 -3
  17. package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +165 -421
  18. package/_packed_docs/protocol/README.md +1 -0
  19. package/_packed_docs/protocol/aun-docs-guide.md +7 -4
  20. package/_packed_docs/protocol/index.md +25 -19
  21. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +62 -17
  22. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +22 -0
  23. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +67 -78
  24. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +6 -2
  25. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +50 -14
  26. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +18 -5
  27. package/_packed_docs/sdk/09-group-rpc-manual.md +312 -282
  28. package/_packed_docs/sdk/09-message-rpc-manual.md +142 -103
  29. package/_packed_docs/sdk/09-payload-reference.md +1 -1
  30. package/_packed_docs/sdk/09-stream-rpc-manual.md +8 -8
  31. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +26 -17
  32. package/_packed_docs/sdk/INDEX.md +42 -34
  33. package/_packed_docs/sdk/README.md +7 -6
  34. package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/344/273/243/347/240/201/345/256/241/346/237/245/351/227/256/351/242/230/346/270/205/345/215/225.md +229 -0
  35. package/_packed_docs//346/266/210/346/201/257/345/220/214/346/255/245/344/270/216/345/216/206/345/217/262/346/213/211/345/217/226/346/224/271/351/200/240/346/226/271/346/241/210.md +750 -0
  36. package/dist/agent-md.js +8 -2
  37. package/dist/agent-md.js.map +1 -1
  38. package/dist/aid-store.d.ts +2 -0
  39. package/dist/aid-store.js +43 -7
  40. package/dist/aid-store.js.map +1 -1
  41. package/dist/auth.d.ts +0 -2
  42. package/dist/auth.js +97 -50
  43. package/dist/auth.js.map +1 -1
  44. package/dist/client/delivery.d.ts +70 -7
  45. package/dist/client/delivery.js +1332 -236
  46. package/dist/client/delivery.js.map +1 -1
  47. package/dist/client/group-state.js +25 -22
  48. package/dist/client/group-state.js.map +1 -1
  49. package/dist/client/lifecycle.js +46 -3
  50. package/dist/client/lifecycle.js.map +1 -1
  51. package/dist/client/mention-mode.d.ts +6 -0
  52. package/dist/client/mention-mode.js +184 -0
  53. package/dist/client/mention-mode.js.map +1 -0
  54. package/dist/client/peers.d.ts +1 -1
  55. package/dist/client/peers.js +26 -3
  56. package/dist/client/peers.js.map +1 -1
  57. package/dist/client/rpc-pipeline.d.ts +20 -1
  58. package/dist/client/rpc-pipeline.js +332 -73
  59. package/dist/client/rpc-pipeline.js.map +1 -1
  60. package/dist/client/v2-e2ee.d.ts +20 -1
  61. package/dist/client/v2-e2ee.js +625 -105
  62. package/dist/client/v2-e2ee.js.map +1 -1
  63. package/dist/client.d.ts +17 -5
  64. package/dist/client.js +332 -167
  65. package/dist/client.js.map +1 -1
  66. package/dist/errors.js +4 -1
  67. package/dist/errors.js.map +1 -1
  68. package/dist/facades.d.ts +3 -0
  69. package/dist/facades.js +109 -53
  70. package/dist/facades.js.map +1 -1
  71. package/dist/group-index.d.ts +5 -0
  72. package/dist/group-index.js +65 -30
  73. package/dist/group-index.js.map +1 -1
  74. package/dist/keystore/aid-db.d.ts +12 -0
  75. package/dist/keystore/aid-db.js +68 -4
  76. package/dist/keystore/aid-db.js.map +1 -1
  77. package/dist/keystore/index.d.ts +12 -0
  78. package/dist/keystore/local-identity-store.d.ts +10 -0
  79. package/dist/keystore/local-identity-store.js +8 -2
  80. package/dist/keystore/local-identity-store.js.map +1 -1
  81. package/dist/keystore/local-token-store.d.ts +10 -0
  82. package/dist/keystore/local-token-store.js +8 -2
  83. package/dist/keystore/local-token-store.js.map +1 -1
  84. package/dist/register-flow.d.ts +0 -1
  85. package/dist/register-flow.js +96 -38
  86. package/dist/register-flow.js.map +1 -1
  87. package/dist/secret-store/file-store.d.ts +3 -14
  88. package/dist/secret-store/file-store.js +7 -98
  89. package/dist/secret-store/file-store.js.map +1 -1
  90. package/dist/secret-store/index.d.ts +1 -4
  91. package/dist/secret-store/index.js +2 -2
  92. package/dist/secret-store/index.js.map +1 -1
  93. package/dist/seq-tracker.d.ts +29 -9
  94. package/dist/seq-tracker.js +233 -64
  95. package/dist/seq-tracker.js.map +1 -1
  96. package/dist/storage/vfs.d.ts +1 -0
  97. package/dist/storage/vfs.js +28 -4
  98. package/dist/storage/vfs.js.map +1 -1
  99. package/dist/tools/cross-sdk-agent.js +360 -8
  100. package/dist/tools/cross-sdk-agent.js.map +1 -1
  101. package/dist/transport.d.ts +19 -0
  102. package/dist/transport.js +197 -14
  103. package/dist/transport.js.map +1 -1
  104. package/dist/v2/e2ee/decrypt.js +26 -23
  105. package/dist/v2/e2ee/decrypt.js.map +1 -1
  106. package/dist/v2/session/session.js +3 -4
  107. package/dist/v2/session/session.js.map +1 -1
  108. package/dist/v2/state/commitment.js +1 -2
  109. package/dist/v2/state/commitment.js.map +1 -1
  110. package/dist/version.d.ts +1 -1
  111. package/dist/version.js +1 -1
  112. package/package.json +5 -4
  113. package/dist/e2ee-group.d.ts +0 -249
  114. package/dist/e2ee-group.js +0 -1567
  115. package/dist/e2ee-group.js.map +0 -1
  116. package/dist/group-resources.d.ts +0 -31
  117. package/dist/group-resources.js +0 -84
  118. package/dist/group-resources.js.map +0 -1
  119. package/dist/keystore/file.d.ts +0 -109
  120. package/dist/keystore/file.js +0 -772
  121. package/dist/keystore/file.js.map +0 -1
  122. package/dist/keystore/sqlite-backup.d.ts +0 -44
  123. package/dist/keystore/sqlite-backup.js +0 -382
  124. package/dist/keystore/sqlite-backup.js.map +0 -1
  125. package/dist/namespaces/auth.d.ts +0 -118
  126. package/dist/namespaces/auth.js +0 -968
  127. package/dist/namespaces/auth.js.map +0 -1
  128. package/dist/namespaces/custody.d.ts +0 -48
  129. package/dist/namespaces/custody.js +0 -313
  130. package/dist/namespaces/custody.js.map +0 -1
  131. package/dist/namespaces/meta.d.ts +0 -76
  132. package/dist/namespaces/meta.js +0 -490
  133. package/dist/namespaces/meta.js.map +0 -1
@@ -1,772 +0,0 @@
1
- /**
2
- * 基于文件(key.json/cert.pem)+ AIDDatabase(SQLite)的 KeyStore 实现。
3
- *
4
- * 与 Python SDK FileKeyStore / Go FileKeyStore 对齐:
5
- * - key.json / cert.pem 仍用文件存储
6
- * - tokens / instance_state / metadata_kv / group_state 全部存 SQLite
7
- * - 废弃 meta.json
8
- */
9
- import * as crypto from 'node:crypto';
10
- import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, chmodSync, renameSync, unlinkSync, rmSync as fsRmSync, renameSync as fsRenameSync, statSync, } from 'node:fs';
11
- import { join, dirname } from 'node:path';
12
- import { homedir } from 'node:os';
13
- import { AIDDatabase } from './aid-db.js';
14
- import { V2KeyStore } from '../v2/session/keystore.js';
15
- import { getDeviceId } from '../config.js';
16
- import { certificateSha256Fingerprint } from '../crypto.js';
17
- import { createDefaultSecretStore } from '../secret-store/index.js';
18
- import { FileSecretStore } from '../secret-store/file-store.js';
19
- import { isJsonObject, } from '../types.js';
20
- const _noopLogger = { error: () => { }, warn: () => { }, info: () => { }, debug: () => { } };
21
- const TOKEN_FIELDS = new Set(['access_token', 'refresh_token', 'kite_token']);
22
- function secureFilePermissions(path) {
23
- if (process.platform !== 'win32') {
24
- try {
25
- chmodSync(path, 0o600);
26
- }
27
- catch { /* ignore */ }
28
- }
29
- }
30
- function replaceFileSync(tmpPath, targetPath) {
31
- try {
32
- renameSync(tmpPath, targetPath);
33
- return;
34
- }
35
- catch (renameErr) {
36
- try {
37
- unlinkSync(targetPath);
38
- }
39
- catch (unlinkErr) {
40
- if (unlinkErr.code !== 'ENOENT') {
41
- throw new Error(`replace target cleanup failed: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}; original rename error: ${renameErr instanceof Error ? renameErr.message : String(renameErr)}`);
42
- }
43
- }
44
- renameSync(tmpPath, targetPath);
45
- }
46
- }
47
- function deepClone(value) {
48
- return JSON.parse(JSON.stringify(value));
49
- }
50
- function fingerprintFromCertPem(certPem) {
51
- // 委托给 crypto.ts 的统一实现(ISSUE-SDK-TS-008: 消除两套指纹计算)
52
- return certificateSha256Fingerprint(certPem);
53
- }
54
- function normalizeCertFingerprint(fp) {
55
- const v = String(fp ?? '').trim().toLowerCase();
56
- if (!v.startsWith('sha256:') || v.length !== 71)
57
- return '';
58
- if (/[^0-9a-f]/.test(v.slice(7)))
59
- return '';
60
- return v;
61
- }
62
- function safeAid(aid) {
63
- return aid.replace(/[/\\:]/g, '_');
64
- }
65
- export class FileKeyStore {
66
- _root;
67
- _aidsRoot;
68
- _secretStore;
69
- _aidDBs = new Map();
70
- _deviceId;
71
- _logger;
72
- constructor(root, opts) {
73
- this._logger = opts?.logger ?? _noopLogger;
74
- const preferred = root ?? join(homedir(), '.aun');
75
- const fallback = join(process.cwd(), '.aun');
76
- this._root = this._prepareRoot(preferred, fallback);
77
- this._secretStore = opts?.secretStore ?? createDefaultSecretStore(this._root, opts?.encryptionSeed, undefined, { logger: opts?.secretStoreLogger ?? this._logger });
78
- this._aidsRoot = join(this._root, 'AIDs');
79
- mkdirSync(this._aidsRoot, { recursive: true });
80
- this._deviceId = getDeviceId(this._root);
81
- }
82
- close() {
83
- this._logger.debug(`FileKeyStore close: closing ${this._aidDBs.size} AID databases`);
84
- for (const db of this._aidDBs.values())
85
- db.close();
86
- this._aidDBs.clear();
87
- }
88
- static ChangeSeed(root, oldSeed, newSeed) {
89
- return FileSecretStore.changeSeed(root, oldSeed, newSeed);
90
- }
91
- changeSeed(oldSeed, newSeed) {
92
- this.close();
93
- const result = FileSecretStore.changeSeed(this._root, oldSeed, newSeed, { logger: this._logger });
94
- this._secretStore = createDefaultSecretStore(this._root, newSeed, undefined, { logger: this._logger });
95
- return result;
96
- }
97
- _getDB(aid) {
98
- const safe = safeAid(aid);
99
- let db = this._aidDBs.get(safe);
100
- if (!db) {
101
- const dbPath = join(this._aidsRoot, safe, 'aun.db');
102
- try {
103
- db = new AIDDatabase(dbPath, this._secretStore, safe, this._logger);
104
- }
105
- catch (exc) {
106
- // DB 损坏:备份后重建
107
- this._logger.warn(`database corrupted, backing up and rebuilding: aid=${aid} err=${exc instanceof Error ? exc.message : String(exc)}`);
108
- const ts = new Date().toISOString().replace(/[:.]/g, '-');
109
- const bakPath = dbPath + `.corrupt_${ts}.bak`;
110
- try {
111
- renameSync(dbPath, bakPath);
112
- }
113
- catch (renameErr) {
114
- this._logger.warn(`backup rename failed: ${renameErr instanceof Error ? renameErr.message : String(renameErr)}`);
115
- }
116
- // 清理 WAL/SHM
117
- for (const suffix of ['-wal', '-shm', '-journal']) {
118
- try {
119
- unlinkSync(dbPath + suffix);
120
- }
121
- catch { /* 文件不存在/无权限均可忽略,下方重建会重新创建 */ }
122
- }
123
- db = new AIDDatabase(dbPath, this._secretStore, safe, this._logger);
124
- }
125
- this._aidDBs.set(safe, db);
126
- }
127
- return db;
128
- }
129
- _prepareRoot(preferred, fallback) {
130
- try {
131
- mkdirSync(preferred, { recursive: true });
132
- return preferred;
133
- }
134
- catch {
135
- // ISSUE-TS-003: 回退时警告用户数据存储位置变更
136
- this._logger.warn(`preferred path ${preferred} unavailable, falling back to ${fallback}`);
137
- mkdirSync(fallback, { recursive: true });
138
- return fallback;
139
- }
140
- }
141
- // ── KeyPair ──────────────────────────────────────────────
142
- loadKeyPair(aid) {
143
- const path = this._keyPairPath(aid);
144
- if (!existsSync(path))
145
- return null;
146
- let raw;
147
- try {
148
- raw = JSON.parse(readFileSync(path, 'utf-8'));
149
- }
150
- catch (exc) {
151
- this._logger.warn('key.json read or parse failed, treating as non-existent');
152
- return null;
153
- }
154
- return this._restoreKeyPair(aid, raw, path);
155
- }
156
- saveKeyPair(aid, keyPair) {
157
- const path = this._keyPairPath(aid);
158
- this._saveKeyPairAtPath(aid, path, keyPair);
159
- }
160
- _saveKeyPairAtPath(aid, path, keyPair) {
161
- mkdirSync(dirname(path), { recursive: true });
162
- const protected_ = deepClone(keyPair);
163
- const pem = protected_.private_key_pem;
164
- if (typeof pem === 'string' && pem) {
165
- delete protected_.private_key_pem;
166
- const rec = this._secretStore.protect(safeAid(aid), 'identity/private_key', Buffer.from(pem, 'utf-8'));
167
- protected_.private_key_protection = rec;
168
- }
169
- const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
170
- writeFileSync(tmpPath, JSON.stringify(protected_, null, 2), { mode: 0o600 });
171
- secureFilePermissions(tmpPath);
172
- try {
173
- replaceFileSync(tmpPath, path);
174
- }
175
- catch (exc) {
176
- try {
177
- unlinkSync(tmpPath);
178
- }
179
- catch { /* ignore tmp cleanup failure */ }
180
- throw exc;
181
- }
182
- secureFilePermissions(path);
183
- }
184
- _restoreKeyPair(aid, kp, persistPath) {
185
- const out = deepClone(kp);
186
- const rec = out.private_key_protection;
187
- if (isJsonObject(rec)) {
188
- const plain = this._secretStore.reveal(safeAid(aid), 'identity/private_key', rec);
189
- if (!plain) {
190
- throw new Error(`private key decrypt failed for aid ${aid}: seed_password mismatch or key.json corrupted`);
191
- }
192
- out.private_key_pem = plain.toString('utf-8');
193
- return out;
194
- }
195
- if (persistPath && typeof out.private_key_pem === 'string' && out.private_key_pem) {
196
- // 兼容历史明文 key.json:首次加载成功后立即用当前 seed_password 加密回写。
197
- this._saveKeyPairAtPath(aid, persistPath, out);
198
- }
199
- return out;
200
- }
201
- // ── Cert ─────────────────────────────────────────────────
202
- loadCert(aid, certFingerprint) {
203
- try {
204
- const norm = normalizeCertFingerprint(certFingerprint);
205
- if (norm) {
206
- const vp = this._certVersionPath(aid, norm);
207
- if (existsSync(vp))
208
- return readFileSync(vp, 'utf-8');
209
- const active = this._certPath(aid);
210
- if (existsSync(active)) {
211
- const certPem = readFileSync(active, 'utf-8');
212
- if (fingerprintFromCertPem(certPem) === norm)
213
- return certPem;
214
- }
215
- return null;
216
- }
217
- const path = this._certPath(aid);
218
- return existsSync(path) ? readFileSync(path, 'utf-8') : null;
219
- }
220
- catch (exc) {
221
- // 文件被锁定、无权限、目录冲突等异常时降级返回 null
222
- this._logger.warn('cert.pem read failed, treating as non-existent');
223
- return null;
224
- }
225
- }
226
- saveCert(aid, certPem, certFingerprint, opts) {
227
- const norm = normalizeCertFingerprint(certFingerprint);
228
- if (norm) {
229
- const vp = this._certVersionPath(aid, norm);
230
- mkdirSync(dirname(vp), { recursive: true });
231
- writeFileSync(vp, certPem);
232
- if (!opts?.makeActive)
233
- return;
234
- }
235
- const path = this._certPath(aid);
236
- mkdirSync(dirname(path), { recursive: true });
237
- writeFileSync(path, certPem);
238
- }
239
- // ── Identity ─────────────────────────────────────────────
240
- loadIdentity(aid) {
241
- // 只读语义保护:身份目录不存在时直接返回 null,绝不通过 _getDB 触发
242
- // `~/.aun/AIDs/{aid}/aun.db` 的副作用 mkdir。这避免了"用对端 AID
243
- // 误调 loadIdentity"导致本地落下空目录的安全事故。
244
- const identityDir = join(this._aidsRoot, safeAid(aid));
245
- if (!existsSync(identityDir))
246
- return null;
247
- const kp = this.loadKeyPair(aid);
248
- const cert = this.loadCert(aid);
249
- // 直接从 DB 读取 tokens + KV
250
- const db = this._getDB(aid);
251
- const tokens = db.getAllTokens();
252
- const kv = db.getAllMetadata();
253
- const hasMeta = Object.keys(tokens).length > 0 || Object.keys(kv).length > 0;
254
- if (!kp && !cert && !hasMeta)
255
- return null;
256
- const identity = {};
257
- for (const [k, v] of Object.entries(kv)) {
258
- try {
259
- identity[k] = JSON.parse(v);
260
- }
261
- catch {
262
- identity[k] = v;
263
- }
264
- }
265
- Object.assign(identity, tokens);
266
- if (kp)
267
- Object.assign(identity, kp);
268
- if (cert) {
269
- // key/cert 公钥一致性校验:防止 cert.pem 被意外覆盖
270
- const localPubB64 = kp?.public_key_der_b64;
271
- if (typeof localPubB64 === 'string' && localPubB64) {
272
- try {
273
- const x = new crypto.X509Certificate(cert);
274
- const certPubDer = x.publicKey.export({ type: 'spki', format: 'der' });
275
- const localPubDer = Buffer.from(localPubB64, 'base64');
276
- if (!certPubDer.equals(localPubDer)) {
277
- this._logger.error('key.json public key does not match cert.pem public key, discarding cert');
278
- }
279
- else {
280
- identity.cert = cert;
281
- }
282
- }
283
- catch {
284
- identity.cert = cert;
285
- }
286
- }
287
- else {
288
- identity.cert = cert;
289
- }
290
- }
291
- return identity;
292
- }
293
- saveIdentity(aid, identity) {
294
- const kp = {};
295
- for (const k of ['private_key_pem', 'public_key_der_b64', 'curve']) {
296
- if (k in identity)
297
- kp[k] = identity[k];
298
- }
299
- if (Object.keys(kp).length > 0)
300
- this.saveKeyPair(aid, kp);
301
- if (typeof identity.cert === 'string' && identity.cert)
302
- this.saveCert(aid, identity.cert);
303
- // 直接写入 tokens + KV
304
- const db = this._getDB(aid);
305
- const skip = new Set([...TOKEN_FIELDS, 'private_key_pem', 'public_key_der_b64', 'curve', 'cert']);
306
- for (const field of TOKEN_FIELDS) {
307
- if (!(field in identity))
308
- continue;
309
- const v = identity[field];
310
- if (typeof v === 'string' && v)
311
- db.setToken(field, v);
312
- else
313
- db.deleteToken(field);
314
- }
315
- for (const [k, v] of Object.entries(identity)) {
316
- if (skip.has(k))
317
- continue;
318
- db.setMetadata(k, JSON.stringify(v));
319
- }
320
- }
321
- loadAnyIdentity() {
322
- if (!existsSync(this._aidsRoot))
323
- return null;
324
- for (const entry of readdirSync(this._aidsRoot, { withFileTypes: true })) {
325
- if (!entry.isDirectory())
326
- continue;
327
- if (entry.name.startsWith('_'))
328
- continue;
329
- const identity = this.loadIdentity(entry.name);
330
- if (identity)
331
- return identity;
332
- }
333
- return null;
334
- }
335
- // ── Instance State ───────────────────────────────────────
336
- loadInstanceState(aid, deviceId, slotId = '') {
337
- return this._getDB(aid).loadInstanceState(deviceId, slotId);
338
- }
339
- saveInstanceState(aid, deviceId, slotId, state) {
340
- this._getDB(aid).saveInstanceState(deviceId, slotId, state);
341
- }
342
- updateInstanceState(aid, deviceId, slotId, updater) {
343
- const db = this._getDB(aid);
344
- const current = (db.loadInstanceState(deviceId, slotId) ?? {});
345
- const working = deepClone(current);
346
- const updated = (updater(working) ?? working);
347
- db.saveInstanceState(deviceId, slotId, updated);
348
- return deepClone(updated);
349
- }
350
- // ── Seq Tracker ───────────────────────────────────────────
351
- saveSeq(aid, deviceId, slotId, namespace, contiguousSeq) {
352
- this._getDB(aid).saveSeq(deviceId, slotId, namespace, contiguousSeq);
353
- }
354
- loadSeq(aid, deviceId, slotId, namespace) {
355
- return this._getDB(aid).loadSeq(deviceId, slotId, namespace);
356
- }
357
- loadAllSeqs(aid, deviceId, slotId) {
358
- return this._getDB(aid).loadAllSeqs(deviceId, slotId);
359
- }
360
- deleteSeq(aid, deviceId, slotId, namespace) {
361
- this._getDB(aid).deleteSeq(deviceId, slotId, namespace);
362
- }
363
- // ── 身份列表 ───────────────────────────────────────────
364
- /** 列出所有已存储的 AID(对齐 Python list_identities) */
365
- listIdentities() {
366
- if (!existsSync(this._aidsRoot))
367
- return [];
368
- const aids = [];
369
- for (const entry of readdirSync(this._aidsRoot, { withFileTypes: true })) {
370
- if (!entry.isDirectory())
371
- continue;
372
- // 跳过保留前缀的特殊目录(_pending 等)
373
- if (entry.name.startsWith('_'))
374
- continue;
375
- aids.push(entry.name);
376
- }
377
- return aids;
378
- }
379
- /** 加载指定 AID 的元数据(对齐 Python load_metadata) */
380
- loadMetadata(aid) {
381
- try {
382
- const db = this._getDB(aid);
383
- const kv = db.getAllMetadata();
384
- if (Object.keys(kv).length === 0)
385
- return null;
386
- const result = {};
387
- for (const [k, v] of Object.entries(kv)) {
388
- try {
389
- result[k] = JSON.parse(v);
390
- }
391
- catch {
392
- result[k] = v;
393
- }
394
- }
395
- return result;
396
- }
397
- catch {
398
- return null;
399
- }
400
- }
401
- /** 保存指定 AID 的元数据;只覆盖传入字段,不清理其它 metadata。 */
402
- saveMetadata(aid, metadata) {
403
- const db = this._getDB(aid);
404
- for (const [k, v] of Object.entries(metadata)) {
405
- if (v === undefined) {
406
- db.deleteMetadata(k);
407
- continue;
408
- }
409
- db.setMetadata(k, JSON.stringify(v));
410
- }
411
- }
412
- // ── 旧 E2EE 存储互操作 ───────────────────────────────────
413
- async saveE2EEPrekey(aid, prekeyId, prekeyData, deviceId = '') {
414
- const now = Date.now();
415
- const privateKey = String(prekeyData.private_key_pem ?? '');
416
- const extra = {};
417
- for (const [key, value] of Object.entries(prekeyData)) {
418
- if (!['private_key_pem', 'created_at', 'updated_at', 'expires_at'].includes(key))
419
- extra[key] = value;
420
- }
421
- this._getDB(aid).getSqliteHandle().prepare(`INSERT INTO prekeys (prekey_id, device_id, private_key_enc, data, created_at, updated_at, expires_at)
422
- VALUES (?, ?, ?, ?, ?, ?, ?)
423
- ON CONFLICT(prekey_id, device_id) DO UPDATE SET
424
- private_key_enc=excluded.private_key_enc,
425
- data=excluded.data,
426
- updated_at=excluded.updated_at,
427
- expires_at=excluded.expires_at`).run(prekeyId, String(deviceId ?? ''), privateKey, // 阶段6:明文写入,IK 私钥除外
428
- JSON.stringify(extra), Number(prekeyData.created_at ?? now), now, prekeyData.expires_at == null ? null : Number(prekeyData.expires_at));
429
- }
430
- async loadE2EEPrekeys(aid, deviceId = '') {
431
- const rows = this._getDB(aid).getSqliteHandle().prepare(`SELECT prekey_id, private_key_enc, data, created_at, updated_at, expires_at
432
- FROM prekeys WHERE device_id = ?`).all(String(deviceId ?? ''));
433
- const result = {};
434
- for (const row of rows) {
435
- const id = String(row.prekey_id ?? '');
436
- if (!id)
437
- continue;
438
- const entry = {
439
- private_key_pem: String(row.private_key_enc ?? ''),
440
- created_at: row.created_at,
441
- updated_at: row.updated_at,
442
- expires_at: row.expires_at,
443
- };
444
- try {
445
- const extra = JSON.parse(String(row.data ?? '{}'));
446
- if (extra && typeof extra === 'object' && !Array.isArray(extra))
447
- Object.assign(entry, extra);
448
- }
449
- catch { /* ignore damaged extra payload */ }
450
- result[id] = entry;
451
- }
452
- return result;
453
- }
454
- async listGroupSecretIds(aid) {
455
- const db = this._getDB(aid).getSqliteHandle();
456
- const ids = new Set();
457
- for (const row of db.prepare('SELECT group_id FROM group_current').all()) {
458
- ids.add(String(row.group_id ?? ''));
459
- }
460
- for (const row of db.prepare('SELECT DISTINCT group_id FROM group_old_epochs').all()) {
461
- ids.add(String(row.group_id ?? ''));
462
- }
463
- return Array.from(ids).filter(Boolean).sort();
464
- }
465
- async loadGroupSecretEpoch(aid, groupId, epoch) {
466
- const db = this._getDB(aid).getSqliteHandle();
467
- const current = db.prepare('SELECT epoch, secret_enc, data, updated_at FROM group_current WHERE group_id = ?').get(groupId);
468
- if (current && (epoch == null || Number(current.epoch ?? 0) === Number(epoch))) {
469
- const entry = {
470
- group_id: groupId,
471
- epoch: current.epoch,
472
- secret: String(current.secret_enc ?? ''),
473
- updated_at: current.updated_at,
474
- };
475
- try {
476
- const extra = JSON.parse(String(current.data ?? '{}'));
477
- if (extra && typeof extra === 'object' && !Array.isArray(extra))
478
- Object.assign(entry, extra);
479
- }
480
- catch { /* ignore damaged extra payload */ }
481
- return entry;
482
- }
483
- if (epoch == null)
484
- return null;
485
- const old = db.prepare('SELECT epoch, secret_enc, data, updated_at, expires_at FROM group_old_epochs WHERE group_id = ? AND epoch = ?').get(groupId, Number(epoch));
486
- if (!old)
487
- return null;
488
- const entry = {
489
- epoch: old.epoch,
490
- secret: String(old.secret_enc ?? ''),
491
- updated_at: old.updated_at,
492
- };
493
- if (old.expires_at != null)
494
- entry.expires_at = old.expires_at;
495
- try {
496
- const extra = JSON.parse(String(old.data ?? '{}'));
497
- if (extra && typeof extra === 'object' && !Array.isArray(extra))
498
- Object.assign(entry, extra);
499
- }
500
- catch { /* ignore damaged extra payload */ }
501
- return entry;
502
- }
503
- async storeGroupSecretTransition(aid, groupId, opts) {
504
- const db = this._getDB(aid).getSqliteHandle();
505
- const now = Date.now();
506
- const epoch = Number(opts.epoch ?? 0);
507
- const current = db.prepare('SELECT epoch, secret_enc, data, updated_at FROM group_current WHERE group_id = ?').get(groupId);
508
- if (current && Number(current.epoch ?? 0) > epoch)
509
- return false;
510
- if (current && Number(current.epoch ?? 0) !== epoch) {
511
- const oldEpoch = Number(current.epoch ?? 0);
512
- const oldSecret = String(current.secret_enc ?? '');
513
- db.prepare(`INSERT INTO group_old_epochs (group_id, epoch, secret_enc, data, updated_at, expires_at)
514
- VALUES (?, ?, ?, ?, ?, ?)
515
- ON CONFLICT(group_id, epoch) DO UPDATE SET
516
- secret_enc=excluded.secret_enc,
517
- data=excluded.data,
518
- updated_at=excluded.updated_at,
519
- expires_at=excluded.expires_at`).run(groupId, oldEpoch, oldSecret, String(current.data ?? '{}'), Number(current.updated_at ?? now), Number(current.updated_at ?? now) + Number(opts.oldEpochRetentionMs ?? 0));
520
- }
521
- const data = {
522
- commitment: String(opts.commitment ?? ''),
523
- member_aids: Array.isArray(opts.memberAids) ? [...opts.memberAids].map(String).sort() : [],
524
- };
525
- db.prepare(`INSERT INTO group_current (group_id, epoch, secret_enc, data, updated_at)
526
- VALUES (?, ?, ?, ?, ?)
527
- ON CONFLICT(group_id) DO UPDATE SET
528
- epoch=excluded.epoch,
529
- secret_enc=excluded.secret_enc,
530
- data=excluded.data,
531
- updated_at=excluded.updated_at`).run(groupId, epoch, String(opts.secret ?? ''), // 阶段6:明文写入
532
- JSON.stringify(data), now);
533
- return true;
534
- }
535
- async saveE2EESession(aid, sessionId, data) {
536
- const dataJson = JSON.stringify(data ?? {});
537
- this._getDB(aid).getSqliteHandle().prepare(`INSERT INTO e2ee_sessions (session_id, data_enc, updated_at) VALUES (?, ?, ?)
538
- ON CONFLICT(session_id) DO UPDATE SET data_enc=excluded.data_enc, updated_at=excluded.updated_at`).run(sessionId, dataJson, Date.now()); // 阶段6:明文写入
539
- }
540
- async loadE2EESessions(aid) {
541
- const rows = this._getDB(aid).getSqliteHandle().prepare('SELECT session_id, data_enc, updated_at FROM e2ee_sessions').all();
542
- const result = [];
543
- for (const row of rows) {
544
- const sessionId = String(row.session_id ?? '');
545
- if (!sessionId)
546
- continue;
547
- try {
548
- const entry = JSON.parse(String(row.data_enc ?? '{}'));
549
- if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
550
- result.push({ ...entry, session_id: sessionId, updated_at: row.updated_at });
551
- }
552
- }
553
- catch { /* ignore damaged session payload */ }
554
- }
555
- return result;
556
- }
557
- // ── 信任根管理 ─────────────────────────────────────────────
558
- trustRootDir() {
559
- const dir = join(this._root, 'CA', 'root');
560
- mkdirSync(dir, { recursive: true });
561
- return dir;
562
- }
563
- trustRootBundlePath() {
564
- return join(this.trustRootDir(), 'trust-roots.pem');
565
- }
566
- saveTrustRoots(trustList, rootCerts) {
567
- const dir = this.trustRootDir();
568
- // 保存每个根证书
569
- for (let i = 0; i < rootCerts.length; i++) {
570
- const item = rootCerts[i];
571
- const certId = item.id || item.fingerprint_sha256 || `root-${i + 1}`;
572
- const safeName = certId.replace(/[^A-Za-z0-9_.-]+/g, '_').slice(0, 120);
573
- writeFileSync(join(dir, `${safeName}.crt`), item.cert_pem, 'utf-8');
574
- }
575
- // 生成合并 bundle
576
- const bundlePath = this.trustRootBundlePath();
577
- const bundle = rootCerts.map(i => i.cert_pem.trim()).join('\n') + '\n';
578
- writeFileSync(bundlePath, bundle, 'utf-8');
579
- // 保存元数据 JSON
580
- writeFileSync(join(dir, 'trust-roots.json'), JSON.stringify(trustList, null, 2), 'utf-8');
581
- return bundlePath;
582
- }
583
- saveIssuerRootCert(issuer, certPem, fingerprintSha256 = '') {
584
- const dir = this.trustRootDir();
585
- const issuersDir = join(dir, 'issuers');
586
- mkdirSync(issuersDir, { recursive: true });
587
- // 保存 issuer 根证书
588
- const safeIssuer = (issuer || 'issuer').replace(/[^A-Za-z0-9_.-]+/g, '_').slice(0, 120);
589
- const certPath = join(issuersDir, `${safeIssuer}.root.crt`);
590
- const normalizedPem = certPem.trim() + '\n';
591
- writeFileSync(certPath, normalizedPem, 'utf-8');
592
- // 读取已有 bundle 并合并(按指纹去重)
593
- const bundlePath = this.trustRootBundlePath();
594
- const existingPems = new Map();
595
- try {
596
- const bundleText = readFileSync(bundlePath, 'utf-8');
597
- for (const pem of this._splitPemBundle(bundleText)) {
598
- const fp = this._pemFingerprint(pem);
599
- existingPems.set(fp, pem);
600
- }
601
- }
602
- catch { /* bundle 不存在时忽略 */ }
603
- // 新证书的指纹
604
- let newFp = this._pemFingerprint(normalizedPem);
605
- if (fingerprintSha256) {
606
- newFp = fingerprintSha256.toLowerCase().replace(/^sha256:/, '');
607
- }
608
- existingPems.set(newFp, normalizedPem);
609
- // 重写 bundle
610
- const merged = Array.from(existingPems.values()).map(p => p.trim()).join('\n') + '\n';
611
- writeFileSync(bundlePath, merged, 'utf-8');
612
- return [certPath, bundlePath];
613
- }
614
- _splitPemBundle(bundleText) {
615
- return bundleText
616
- .split(/(?<=-----END CERTIFICATE-----)\s*/)
617
- .map(s => s.trim())
618
- .filter(s => s.startsWith('-----BEGIN CERTIFICATE-----'));
619
- }
620
- _pemFingerprint(pem) {
621
- try {
622
- const der = pem.replace(/-----[A-Z ]+-----/g, '').replace(/\s/g, '');
623
- const hash = crypto.createHash('sha256').update(Buffer.from(der, 'base64')).digest('hex');
624
- return hash;
625
- }
626
- catch {
627
- return crypto.createHash('sha256').update(pem, 'utf-8').digest('hex');
628
- }
629
- }
630
- // ── 路径辅助 ─────────────────────────────────────────────
631
- _keyPairPath(aid) {
632
- return join(this._aidsRoot, safeAid(aid), 'private', 'key.json');
633
- }
634
- _certPath(aid) {
635
- return join(this._aidsRoot, safeAid(aid), 'public', 'cert.pem');
636
- }
637
- _certVersionPath(aid, fp) {
638
- return join(this._aidsRoot, safeAid(aid), 'public', 'certs', `${fp.replace(/:/g, '_')}.pem`);
639
- }
640
- /** RegisterAID 临时目录根:AIDs/_pending/ */
641
- _pendingRoot() {
642
- return join(this._aidsRoot, '_pending');
643
- }
644
- /**
645
- * 为 RegisterAID 分配一个临时目录 AIDs/_pending/{aid}-{nonce}-{ts}/。
646
- * 子目录 private/ public/ 一并创建。
647
- */
648
- pendingIdentityDir(aid) {
649
- const nonce = crypto.randomBytes(4).toString('hex');
650
- const ts = Math.floor(Date.now() / 1000);
651
- const dir = join(this._pendingRoot(), `${safeAid(aid)}-${nonce}-${ts}`);
652
- mkdirSync(join(dir, 'private'), { recursive: true });
653
- mkdirSync(join(dir, 'public'), { recursive: true });
654
- return dir;
655
- }
656
- listPendingIdentityDirs(aid) {
657
- const root = this._pendingRoot();
658
- if (!existsSync(root))
659
- return [];
660
- const prefix = `${safeAid(aid)}-`;
661
- const items = [];
662
- for (const entry of readdirSync(root, { withFileTypes: true })) {
663
- if (!entry.isDirectory() || !entry.name.startsWith(prefix))
664
- continue;
665
- const path = join(root, entry.name);
666
- try {
667
- items.push({ path, mtimeMs: statSync(path).mtimeMs });
668
- }
669
- catch { /* ignore unreadable pending entry */ }
670
- }
671
- return items.sort((a, b) => b.mtimeMs - a.mtimeMs).map((item) => item.path);
672
- }
673
- savePendingKeyPair(pendingDir, aid, keyPair) {
674
- this._saveKeyPairAtPath(aid, join(pendingDir, 'private', 'key.json'), keyPair);
675
- }
676
- loadPendingKeyPair(pendingDir, aid) {
677
- const keyPath = join(pendingDir, 'private', 'key.json');
678
- if (!existsSync(keyPath))
679
- return null;
680
- let raw;
681
- try {
682
- raw = JSON.parse(readFileSync(keyPath, 'utf-8'));
683
- }
684
- catch {
685
- return null;
686
- }
687
- return this._restoreKeyPair(aid, raw, keyPath);
688
- }
689
- savePendingCert(pendingDir, certPem) {
690
- const certPath = join(pendingDir, 'public', 'cert.pem');
691
- mkdirSync(dirname(certPath), { recursive: true });
692
- writeFileSync(certPath, certPem, { encoding: 'utf-8', mode: 0o600 });
693
- }
694
- /**
695
- * 把临时目录原子 rename 到正式 AIDs/{aid}/。
696
- * 目标已存在 → 抛错(调用方应当返回 IdentityConflictError)。
697
- */
698
- promotePendingIdentity(pendingDir, aid) {
699
- this._ensurePendingKeyPairProtected(pendingDir, aid);
700
- const target = join(this._aidsRoot, safeAid(aid));
701
- if (existsSync(target)) {
702
- throw new Error(`promotePendingIdentity: target exists: ${target}`);
703
- }
704
- // 关闭 lazy 打开的 db 句柄(如果有),避免句柄占用旧路径
705
- const safe = safeAid(aid);
706
- const db = this._aidDBs.get(safe);
707
- if (db) {
708
- try {
709
- db.close();
710
- }
711
- catch { /* ignore */ }
712
- this._aidDBs.delete(safe);
713
- }
714
- mkdirSync(this._aidsRoot, { recursive: true });
715
- fsRenameSync(pendingDir, target);
716
- return target;
717
- }
718
- _ensurePendingKeyPairProtected(pendingDir, aid) {
719
- const keyPath = join(pendingDir, 'private', 'key.json');
720
- if (!existsSync(keyPath)) {
721
- throw new Error(`pending identity missing key pair for ${aid}`);
722
- }
723
- const raw = JSON.parse(readFileSync(keyPath, 'utf-8'));
724
- if (typeof raw.private_key_pem === 'string' && raw.private_key_pem) {
725
- throw new Error(`pending identity private key is plaintext for ${aid}`);
726
- }
727
- if (!isJsonObject(raw.private_key_protection)) {
728
- throw new Error(`pending identity private key is not encrypted for ${aid}`);
729
- }
730
- }
731
- /**
732
- * 删除 AIDs/_pending/ 下 mtime 超过 maxAgeMs 的子目录。
733
- * 失败仅记 warn,不抛错。返回被清理的目录数量。
734
- */
735
- cleanupPendingDirs(maxAgeMs = 600_000) {
736
- const root = this._pendingRoot();
737
- if (!existsSync(root))
738
- return 0;
739
- let removed = 0;
740
- const now = Date.now();
741
- try {
742
- for (const entry of readdirSync(root, { withFileTypes: true })) {
743
- if (!entry.isDirectory())
744
- continue;
745
- const path = join(root, entry.name);
746
- try {
747
- const st = statSync(path);
748
- if (now - st.mtimeMs < maxAgeMs)
749
- continue;
750
- fsRmSync(path, { recursive: true, force: true });
751
- removed++;
752
- }
753
- catch (e) {
754
- this._logger.warn(`cleanupPendingDirs entry failed: ${path} err=${e instanceof Error ? e.message : String(e)}`);
755
- }
756
- }
757
- }
758
- catch (e) {
759
- this._logger.warn(`cleanupPendingDirs read root failed: ${root} err=${e instanceof Error ? e.message : String(e)}`);
760
- }
761
- return removed;
762
- }
763
- discardPendingIdentity(pendingDir) {
764
- fsRmSync(pendingDir, { recursive: true, force: true });
765
- }
766
- /** 获取指定 AID 的 V2KeyStore(共享同一 SQLite 连接)。 */
767
- getV2KeyStore(aid) {
768
- const db = this._getDB(aid);
769
- return new V2KeyStore(db.getSqliteHandle());
770
- }
771
- }
772
- //# sourceMappingURL=file.js.map