@agentunion/fastaun-browser 0.4.3 → 0.4.5

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 (71) hide show
  1. package/CHANGELOG.md +203 -178
  2. package/_packed_docs/CHANGELOG.md +203 -178
  3. package/_packed_docs/INDEX.md +17 -17
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +11 -11
  5. package/_packed_docs/agent.md/SCHEMA.md +49 -49
  6. package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +22 -22
  7. package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +327 -327
  8. package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -686
  9. package/_packed_docs/design/2026-05-22-aun-rpc-trace-enhancement.md +542 -542
  10. package/_packed_docs/design/E2EE_V2/347/256/200/345/214/226/344/270/2721DH/345/212/240Per-AID_Wrap/346/226/271/346/241/210.md +124 -124
  11. package/_packed_docs/design//350/267/250/350/257/255/350/250/200/345/256/271/345/231/250E2E/346/265/213/350/257/225/346/226/271/346/241/210.md +665 -665
  12. package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +2 -2
  13. package/_packed_docs/protocol/14-/344/272/244/344/272/222/346/234/272/345/210/266-/345/223/215/345/272/224/346/250/241/345/274/217/344/270/216/350/207/252/344/270/273/346/250/241/345/274/217.md +170 -170
  14. 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 +419 -419
  15. package/_packed_docs/protocol/README.md +1 -1
  16. package/_packed_docs/protocol/aun-docs-guide.md +1 -1
  17. package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +15 -15
  18. package/_packed_docs/protocol//351/231/204/345/275/225B-/346/211/251/345/261/225/346/200/247/346/214/207/345/215/227.md +4 -4
  19. package/_packed_docs/protocol//351/231/204/345/275/225J-/345/256/242/346/210/267/347/253/257/346/216/245/345/205/245/347/244/272/344/276/213.md +98 -98
  20. package/_packed_docs/protocol//351/231/204/345/275/225M-JWT/350/256/244/350/257/201/345/256/236/347/216/260/346/214/207/345/215/227.md +46 -46
  21. package/_packed_docs/protocol//351/231/204/345/275/225N-/345/210/206/345/270/203/345/274/217Trace/345/215/217/350/256/256.md +257 -257
  22. package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
  23. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
  24. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1 -0
  25. package/_packed_docs/sdk/09-payload-reference.md +13 -13
  26. package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +171 -171
  27. package/dist/aid-store.d.ts +1 -0
  28. package/dist/aid-store.d.ts.map +1 -1
  29. package/dist/aid-store.js +26 -9
  30. package/dist/aid-store.js.map +1 -1
  31. package/dist/aid.d.ts +2 -1
  32. package/dist/aid.d.ts.map +1 -1
  33. package/dist/aid.js +7 -6
  34. package/dist/aid.js.map +1 -1
  35. package/dist/auth.d.ts +8 -13
  36. package/dist/auth.d.ts.map +1 -1
  37. package/dist/auth.js +38 -127
  38. package/dist/auth.js.map +1 -1
  39. package/dist/bundle.js +872 -350
  40. package/dist/client.d.ts +12 -5
  41. package/dist/client.d.ts.map +1 -1
  42. package/dist/client.js +296 -213
  43. package/dist/client.js.map +1 -1
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +1 -0
  47. package/dist/index.js.map +1 -1
  48. package/dist/keystore/index.d.ts +45 -22
  49. package/dist/keystore/index.d.ts.map +1 -1
  50. package/dist/keystore/index.js +6 -1
  51. package/dist/keystore/index.js.map +1 -1
  52. package/dist/keystore/indexeddb.d.ts +11 -1
  53. package/dist/keystore/indexeddb.d.ts.map +1 -1
  54. package/dist/keystore/indexeddb.js +167 -18
  55. package/dist/keystore/indexeddb.js.map +1 -1
  56. package/dist/register-flow.d.ts +34 -0
  57. package/dist/register-flow.d.ts.map +1 -0
  58. package/dist/register-flow.js +355 -0
  59. package/dist/register-flow.js.map +1 -0
  60. package/dist/v2/session/keystore.d.ts +5 -0
  61. package/dist/v2/session/keystore.d.ts.map +1 -1
  62. package/dist/v2/session/keystore.js +29 -0
  63. package/dist/v2/session/keystore.js.map +1 -1
  64. package/dist/version.d.ts +1 -1
  65. package/dist/version.js +1 -1
  66. package/package.json +1 -1
  67. package/_packed_docs/0.4.0_/345/267/256/345/274/202/346/240/270/345/256/236/345/206/263/347/255/226/350/256/260/345/275/225.md +0 -302
  68. package/_packed_docs/AUN_SDK_0.4.0_/350/256/276/350/256/241/345/257/271/346/257/224/345/210/206/346/236/220.md +0 -194
  69. package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/345/256/236/346/226/275/350/256/241/345/210/222.md +0 -596
  70. package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/350/256/276/350/256/241/346/226/271/346/241/210_v3.md +0 -1697
  71. package/_packed_docs/python-sdk-v2-only-changelog.md +0 -189
package/dist/bundle.js CHANGED
@@ -314,12 +314,12 @@ async function secretPut(storeName, key, value) {
314
314
  tx.oncomplete = () => db.close();
315
315
  });
316
316
  }
317
- var _noopLog6, SECRET_DB_NAME, SECRET_DB_VERSION, STORE_SECRETS, STORE_MASTER, IndexedDBSecretStore;
317
+ var _noopLog7, SECRET_DB_NAME, SECRET_DB_VERSION, STORE_SECRETS, STORE_MASTER, IndexedDBSecretStore;
318
318
  var init_indexeddb_store = __esm({
319
319
  "src/secret-store/indexeddb-store.ts"() {
320
320
  "use strict";
321
321
  init_crypto();
322
- _noopLog6 = { error: () => {
322
+ _noopLog7 = { error: () => {
323
323
  }, warn: () => {
324
324
  }, info: () => {
325
325
  }, debug: () => {
@@ -332,7 +332,7 @@ var init_indexeddb_store = __esm({
332
332
  constructor(encryptionSeed) {
333
333
  __publicField(this, "_encryptionSeed");
334
334
  __publicField(this, "_masterKeyPromise", null);
335
- __publicField(this, "_log", _noopLog6);
335
+ __publicField(this, "_log", _noopLog7);
336
336
  this._encryptionSeed = encryptionSeed;
337
337
  }
338
338
  setLogger(log) {
@@ -454,7 +454,7 @@ var init_indexeddb_store = __esm({
454
454
  });
455
455
 
456
456
  // src/version.ts
457
- var VERSION = "0.4.3";
457
+ var VERSION = "0.4.5";
458
458
 
459
459
  // src/types.ts
460
460
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
@@ -2031,13 +2031,14 @@ function gatewayHttpUrl(gatewayUrl, path) {
2031
2031
  var _AuthFlow = class _AuthFlow {
2032
2032
  constructor(opts) {
2033
2033
  __publicField(this, "_log", _noopLog4);
2034
- __publicField(this, "_keystore");
2034
+ __publicField(this, "_tokenStore");
2035
2035
  __publicField(this, "_crypto");
2036
2036
  __publicField(this, "_aid");
2037
2037
  __publicField(this, "_deviceId");
2038
2038
  __publicField(this, "_slotId");
2039
2039
  __publicField(this, "_rootCaPem");
2040
2040
  __publicField(this, "_verifySsl");
2041
+ __publicField(this, "_memIdentity", null);
2041
2042
  // 缓存
2042
2043
  __publicField(this, "_rootCerts", null);
2043
2044
  __publicField(this, "_gatewayChainCache", /* @__PURE__ */ new Map());
@@ -2046,7 +2047,7 @@ var _AuthFlow = class _AuthFlow {
2046
2047
  __publicField(this, "_chainVerifiedCache", /* @__PURE__ */ new Map());
2047
2048
  __publicField(this, "_chainCacheTtl");
2048
2049
  __publicField(this, "_gatewayCaVerified", /* @__PURE__ */ new Map());
2049
- this._keystore = opts.keystore;
2050
+ this._tokenStore = opts.tokenStore;
2050
2051
  this._crypto = opts.crypto;
2051
2052
  this._aid = opts.aid ?? null;
2052
2053
  this._deviceId = String(opts.deviceId ?? "").trim();
@@ -2059,13 +2060,18 @@ var _AuthFlow = class _AuthFlow {
2059
2060
  this._log = log;
2060
2061
  }
2061
2062
  // ── 公开 API ──────────────────────────────────────
2063
+ /** 注入内存私钥,禁止 AuthFlow 内部再走 tokenStore 解密 */
2064
+ setIdentity(identity) {
2065
+ this._memIdentity = identity;
2066
+ if (identity?.aid) this._aid = String(identity.aid);
2067
+ }
2062
2068
  /** 加载本地身份信息 */
2063
2069
  async loadIdentity(aid) {
2064
2070
  const tStart = Date.now();
2065
2071
  this._log.debug(`loadIdentity enter: aid=${aid ?? "<current>"}`);
2066
2072
  try {
2067
2073
  const identity = await this._loadIdentityOrRaise(aid);
2068
- const cert = await this._keystore.loadCert(identity.aid);
2074
+ const cert = await this._tokenStore.loadCert(identity.aid);
2069
2075
  if (cert) identity.cert = cert;
2070
2076
  const instanceState = await this._loadInstanceState(identity.aid);
2071
2077
  if (instanceState) {
@@ -2104,93 +2110,10 @@ var _AuthFlow = class _AuthFlow {
2104
2110
  this._deviceId = String(opts.deviceId ?? "").trim();
2105
2111
  this._slotId = String(opts.slotId ?? "").trim();
2106
2112
  }
2107
- /**
2108
- * 严格注册新 AID(对齐 TS registerAid / Go RegisterAID)。
2109
- *
2110
- * 注册与认证彻底分离:此方法绝不被 SDK 内部自动调用,
2111
- * 必须由应用层显式调用。
2112
- */
2113
- async registerAid(gatewayUrl, aid) {
2114
- const tStart = Date.now();
2115
- this._log.debug(`registerAid enter: aid=${aid} gateway=${gatewayUrl}`);
2116
- _AuthFlow._validateAidName(aid);
2117
- try {
2118
- const existing = await this._keystore.loadIdentity(aid);
2119
- if (existing && existing.private_key_pem && existing.public_key_der_b64) {
2120
- this._log.debug(`registerAid: local keypair exists, checking server: aid=${aid}`);
2121
- const localPubB642 = String(existing.public_key_der_b64);
2122
- const serverCertPem2 = await this._downloadRegisteredCert(gatewayUrl, aid);
2123
- if (serverCertPem2) {
2124
- const serverCert = parseCertDer(serverCertPem2);
2125
- const serverPubB64 = uint8ToBase64(serverCert.spkiBytes);
2126
- if (serverPubB64 !== localPubB642) {
2127
- throw new IdentityConflictError(
2128
- `AID '${aid}' is registered by another party on server (public key mismatch). Choose a different name.`
2129
- );
2130
- }
2131
- this._log.info(`registerAid: idempotent return for already-registered AID: aid=${aid}`);
2132
- if (!existing.cert) {
2133
- existing.cert = serverCertPem2;
2134
- await this._persistIdentity(existing);
2135
- }
2136
- this._aid = aid;
2137
- return { aid, cert: serverCertPem2 };
2138
- } else {
2139
- this._log.debug(`registerAid: server has no record, registering with existing keypair: aid=${aid}`);
2140
- const created2 = await this._createAid(gatewayUrl, existing);
2141
- const certPem2 = String(created2.cert ?? "");
2142
- if (!certPem2) {
2143
- throw new AuthError(`registerAid: server response missing cert for ${aid}`);
2144
- }
2145
- existing.cert = certPem2;
2146
- const returnedCert2 = parseCertDer(certPem2);
2147
- const certPubB642 = uint8ToBase64(returnedCert2.spkiBytes);
2148
- if (certPubB642 !== localPubB642) {
2149
- throw new AuthError(
2150
- `registerAid: server returned certificate with mismatched public key for ${aid}`
2151
- );
2152
- }
2153
- await this._persistIdentity(existing);
2154
- this._aid = aid;
2155
- this._log.debug(`registerAid exit (recovered): elapsed=${Date.now() - tStart}ms aid=${aid}`);
2156
- return { aid, cert: certPem2 };
2157
- }
2158
- }
2159
- const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
2160
- if (serverCertPem) {
2161
- throw new IdentityConflictError(
2162
- `AID '${aid}' is already registered on server. Choose a different name, or if you own the keypair use a recovery flow.`
2163
- );
2164
- }
2165
- const identity = await this._crypto.generateIdentity();
2166
- identity.aid = aid;
2167
- const created = await this._createAid(gatewayUrl, identity);
2168
- const certPem = String(created.cert ?? "");
2169
- if (!certPem) {
2170
- throw new AuthError(`registerAid: server response missing cert for ${aid}`);
2171
- }
2172
- identity.cert = certPem;
2173
- const returnedCert = parseCertDer(certPem);
2174
- const certPubB64 = uint8ToBase64(returnedCert.spkiBytes);
2175
- const localPubB64 = String(identity.public_key_der_b64);
2176
- if (certPubB64 !== localPubB64) {
2177
- throw new AuthError(
2178
- `registerAid: server returned certificate with mismatched public key for ${aid}`
2179
- );
2180
- }
2181
- await this._persistIdentity(identity);
2182
- this._aid = aid;
2183
- this._log.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
2184
- return { aid: identity.aid, cert: identity.cert };
2185
- } catch (err) {
2186
- this._log.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
2187
- throw err;
2188
- }
2189
- }
2190
2113
  /**
2191
2114
  * 认证已有 AID — login1/login2 双阶段流程。
2192
2115
  *
2193
- * 优先复用 keystore 里的 cached access_token(未过期且有 refresh_token),
2116
+ * 优先复用 tokenStore 里的 cached access_token(未过期且有 refresh_token),
2194
2117
  * 避免每次 authenticate 都走两阶段重登的网络往返。与 Python SDK 行为对齐。
2195
2118
  */
2196
2119
  async authenticate(gatewayUrl, aid) {
@@ -2584,15 +2507,6 @@ var _AuthFlow = class _AuthFlow {
2584
2507
  clearTimeout(timeoutId);
2585
2508
  }
2586
2509
  }
2587
- // ── 内部方法:AID 创建 ───────────────────────────
2588
- async _createAid(gatewayUrl, identity) {
2589
- const response = await this._shortRpc(gatewayUrl, "auth.create_aid", {
2590
- aid: identity.aid,
2591
- public_key: identity.public_key_der_b64,
2592
- curve: identity.curve ?? "P-256"
2593
- });
2594
- return { cert: response.cert };
2595
- }
2596
2510
  /**
2597
2511
  * 从服务端下载指定 AID 的证书(公开 API)。
2598
2512
  *
@@ -3104,49 +3018,53 @@ var _AuthFlow = class _AuthFlow {
3104
3018
  /** 加载身份,不存在或半成品时抛出异常 */
3105
3019
  async _loadIdentityOrRaise(aid) {
3106
3020
  const requestedAid = aid ?? this._aid;
3107
- if (requestedAid) {
3108
- const existing = await this._keystore.loadIdentity(requestedAid);
3109
- if (!existing) {
3110
- throw new StateError(`identity not found for aid: ${requestedAid}`);
3021
+ if (this._memIdentity) {
3022
+ const mem = this._memIdentity;
3023
+ if (requestedAid && String(mem.aid ?? "") !== requestedAid) {
3024
+ throw new StateError(`identity mismatch: requested ${requestedAid}, loaded ${mem.aid}`);
3111
3025
  }
3112
- if (!existing.private_key_pem || !existing.public_key_der_b64) {
3113
- throw new StateError(
3114
- `local identity for aid ${requestedAid} is incomplete (missing keypair); call auth.registerAid() first`
3115
- );
3026
+ if (!mem.private_key_pem || !mem.public_key_der_b64) {
3027
+ throw new StateError(`injected identity for aid ${mem.aid} is incomplete (missing keypair)`);
3116
3028
  }
3117
- this._aid = requestedAid;
3118
- if (!existing.aid) existing.aid = requestedAid;
3119
- return existing;
3029
+ if (requestedAid) this._aid = requestedAid;
3030
+ return { ...mem };
3031
+ }
3032
+ if (requestedAid) {
3033
+ throw new StateError(
3034
+ `no injected identity for aid ${requestedAid}; call AUNClient.loadIdentity(aid) first`
3035
+ );
3120
3036
  }
3121
- throw new StateError("no local identity found, call auth.registerAid() first");
3037
+ throw new StateError("no local identity found, call AUNClient.loadIdentity(aid) first");
3122
3038
  }
3123
3039
  // (_ensureIdentity 已移除:注册和登录彻底分离)
3124
3040
  async _loadInstanceState(aid) {
3125
- if (typeof this._keystore.loadInstanceState !== "function") {
3041
+ if (typeof this._tokenStore.loadInstanceState !== "function") {
3126
3042
  return null;
3127
3043
  }
3128
- return await this._keystore.loadInstanceState(aid, this._deviceId, this._slotId);
3044
+ return await this._tokenStore.loadInstanceState(aid, this._deviceId, this._slotId);
3129
3045
  }
3130
3046
  async _persistIdentity(identity) {
3131
3047
  const aid = String(identity.aid ?? "");
3132
3048
  if (!aid) {
3133
3049
  throw new StateError("identity missing aid");
3134
3050
  }
3135
- const persisted = { ...identity };
3136
3051
  const instanceState = {};
3137
3052
  const instanceStateRecord = instanceState;
3138
- const persistedRecord = persisted;
3053
+ const persistedRecord = { ...identity };
3139
3054
  for (const key of _AuthFlow._INSTANCE_STATE_FIELDS) {
3140
- if (key in persisted) {
3055
+ if (key in persistedRecord) {
3141
3056
  instanceStateRecord[key] = persistedRecord[key];
3142
3057
  delete persistedRecord[key];
3143
3058
  }
3144
3059
  }
3145
- await this._keystore.saveIdentity(aid, persisted);
3146
- if (Object.keys(instanceState).length === 0 || typeof this._keystore.updateInstanceState !== "function") {
3060
+ const certPem = String(persistedRecord.cert ?? "");
3061
+ if (certPem) {
3062
+ await this._tokenStore.saveCert(aid, certPem);
3063
+ }
3064
+ if (Object.keys(instanceState).length === 0 || typeof this._tokenStore.updateInstanceState !== "function") {
3147
3065
  return;
3148
3066
  }
3149
- await this._keystore.updateInstanceState(aid, this._deviceId, this._slotId, (current) => {
3067
+ await this._tokenStore.updateInstanceState(aid, this._deviceId, this._slotId, (current) => {
3150
3068
  Object.assign(current, instanceState);
3151
3069
  return current;
3152
3070
  });
@@ -3568,7 +3486,7 @@ function hasEncryptionSeed(seed) {
3568
3486
  return seed !== void 0;
3569
3487
  }
3570
3488
  var DB_NAME = "aun-keystore";
3571
- var DB_VERSION = 6;
3489
+ var DB_VERSION = 7;
3572
3490
  var STORE_KEY_PAIRS = "key_pairs";
3573
3491
  var STORE_CERTS = "certs";
3574
3492
  var STORE_METADATA = "metadata";
@@ -3579,10 +3497,18 @@ var STORE_GROUP_OLD_EPOCHS = "group_old_epochs";
3579
3497
  var STORE_SESSIONS = "e2ee_sessions";
3580
3498
  var STORE_GROUP_STATE = "group_state";
3581
3499
  var STORE_AGENT_MD_CACHE = "agent_md_cache";
3500
+ var STORE_PENDING_IDENTITIES = "pending_identities";
3582
3501
  var STRUCTURED_RECOVERY_RETENTION_MS = 7 * 24 * 3600 * 1e3;
3583
3502
  function metadataStoreKey(aid) {
3584
3503
  return safeAid(aid);
3585
3504
  }
3505
+ function randomHex(byteLength) {
3506
+ const bytes = crypto.getRandomValues(new Uint8Array(byteLength));
3507
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
3508
+ }
3509
+ function pendingIdentityPrefix(aid) {
3510
+ return `${safeAid(aid)}-`;
3511
+ }
3586
3512
  function normalizeCertFingerprint(certFingerprint) {
3587
3513
  const normalized = String(certFingerprint ?? "").trim().toLowerCase();
3588
3514
  if (!normalized) return "";
@@ -3748,6 +3674,9 @@ function openDB() {
3748
3674
  if (!db.objectStoreNames.contains(STORE_AGENT_MD_CACHE)) {
3749
3675
  db.createObjectStore(STORE_AGENT_MD_CACHE);
3750
3676
  }
3677
+ if (!db.objectStoreNames.contains(STORE_PENDING_IDENTITIES)) {
3678
+ db.createObjectStore(STORE_PENDING_IDENTITIES);
3679
+ }
3751
3680
  };
3752
3681
  request.onsuccess = () => {
3753
3682
  const db = request.result;
@@ -3882,9 +3811,9 @@ async function idbDelete(storeName, key) {
3882
3811
  var _IndexedDBKeyStore = class _IndexedDBKeyStore {
3883
3812
  constructor(opts) {
3884
3813
  __publicField(this, "_log", _noopLog5);
3885
- /** 私钥加密种子;为空时降级为明文存储(向后兼容) */
3814
+ /** 私钥加密种子;空字符串也是有效 seed,默认不再写入明文私钥。 */
3886
3815
  __publicField(this, "_encryptionSeed");
3887
- this._encryptionSeed = opts?.encryptionSeed;
3816
+ this._encryptionSeed = opts?.encryptionSeed ?? "";
3888
3817
  }
3889
3818
  setLogger(log) {
3890
3819
  this._log = log;
@@ -3898,7 +3827,13 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
3898
3827
  for (const row of rows) {
3899
3828
  if (!isRecord(row.value)) continue;
3900
3829
  const envelope = row.value._encrypted_pk;
3901
- if (!isRecord(envelope)) continue;
3830
+ if (!isRecord(envelope)) {
3831
+ const plain = row.value.private_key_pem;
3832
+ if (typeof plain === "string" && plain) {
3833
+ migrations.push({ key: row.key, value: deepClone(row.value), privateKeyPem: plain });
3834
+ }
3835
+ continue;
3836
+ }
3902
3837
  try {
3903
3838
  const privateKeyPem = await _decryptPEM(envelope, oldSeed);
3904
3839
  migrations.push({ key: row.key, value: deepClone(row.value), privateKeyPem });
@@ -3997,16 +3932,14 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
3997
3932
  result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
3998
3933
  delete result._encrypted_pk;
3999
3934
  } catch {
4000
- this._log.error(`[keystore] decrypt ${aid} private keyfailed, maybe encryptionSeed mismatch`);
3935
+ this._log.error(`[keystore] decrypt ${aid} private key failed, maybe encryptionSeed mismatch`);
3936
+ throw new Error(`private key decrypt failed for aid ${aid}: seed_password mismatch or IndexedDB record corrupted`);
4001
3937
  }
4002
3938
  } else if (
4003
3939
  // 透明迁移:旧版明文数据自动加密回写
4004
3940
  !epk && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
4005
3941
  ) {
4006
- try {
4007
- await this.saveKeyPair(aid, result);
4008
- } catch {
4009
- }
3942
+ await this.saveKeyPair(aid, result);
4010
3943
  }
4011
3944
  return result;
4012
3945
  }
@@ -4018,6 +3951,123 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
4018
3951
  }
4019
3952
  await idbPut(STORE_KEY_PAIRS, metadataStoreKey(aid), record);
4020
3953
  }
3954
+ // ── RegisterAID pending 身份 ─────────────────────────
3955
+ async pendingIdentityDir(aid) {
3956
+ const handle = `${pendingIdentityPrefix(aid)}${randomHex(4)}-${Math.floor(Date.now() / 1e3)}`;
3957
+ const now = Date.now();
3958
+ await idbPut(STORE_PENDING_IDENTITIES, handle, {
3959
+ aid,
3960
+ created_at: now,
3961
+ updated_at: now
3962
+ });
3963
+ return handle;
3964
+ }
3965
+ async listPendingIdentityDirs(aid) {
3966
+ const prefix = pendingIdentityPrefix(aid);
3967
+ const rows = await idbGetAllByPrefix(STORE_PENDING_IDENTITIES, prefix);
3968
+ return rows.filter((row) => row.key.startsWith(prefix) && isRecord(row.value) && String(row.value.aid ?? "") === aid).sort((a, b) => Number(b.value.updated_at ?? 0) - Number(a.value.updated_at ?? 0)).map((row) => row.key);
3969
+ }
3970
+ async savePendingKeyPair(handle, aid, keyPair) {
3971
+ const current = await this._loadPendingRecord(handle, aid);
3972
+ if (!current) throw new Error(`pending identity not found: ${handle}`);
3973
+ const record = deepClone(keyPair);
3974
+ const privateKeyPem = record.private_key_pem;
3975
+ if (typeof privateKeyPem !== "string" || !privateKeyPem) {
3976
+ throw new Error("savePendingKeyPair requires private_key_pem");
3977
+ }
3978
+ record._encrypted_pk = await _encryptPEM(privateKeyPem, this._encryptionSeed ?? "");
3979
+ delete record.private_key_pem;
3980
+ await idbPut(STORE_PENDING_IDENTITIES, handle, {
3981
+ ...current,
3982
+ aid,
3983
+ key_pair: record,
3984
+ updated_at: Date.now()
3985
+ });
3986
+ }
3987
+ async loadPendingKeyPair(handle, aid) {
3988
+ const current = await this._loadPendingRecord(handle, aid, false);
3989
+ if (!current || !isRecord(current.key_pair)) return null;
3990
+ const result = deepClone(current.key_pair);
3991
+ const encrypted = result._encrypted_pk;
3992
+ if (isRecord(encrypted)) {
3993
+ try {
3994
+ const envelope = encrypted;
3995
+ result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed ?? "");
3996
+ delete result._encrypted_pk;
3997
+ return result;
3998
+ } catch {
3999
+ this._log.error(`[keystore] decrypt pending ${aid} private key failed, maybe encryptionSeed mismatch`);
4000
+ throw new Error(`pending identity private key decrypt failed for aid ${aid}: seed_password mismatch or IndexedDB record corrupted`);
4001
+ }
4002
+ }
4003
+ if (typeof result.private_key_pem === "string" && result.private_key_pem) {
4004
+ await this.savePendingKeyPair(handle, aid, result);
4005
+ return result;
4006
+ }
4007
+ return result;
4008
+ }
4009
+ async savePendingCert(handle, certPem) {
4010
+ const current = await this._loadPendingRecord(handle, void 0);
4011
+ if (!current) throw new Error(`pending identity not found: ${handle}`);
4012
+ await idbPut(STORE_PENDING_IDENTITIES, handle, {
4013
+ ...current,
4014
+ cert: certPem,
4015
+ updated_at: Date.now()
4016
+ });
4017
+ }
4018
+ async promotePendingIdentity(handle, aid) {
4019
+ const current = await this._loadPendingRecord(handle, aid);
4020
+ if (!current) throw new Error(`pending identity not found: ${handle}`);
4021
+ if (!isRecord(current.key_pair)) {
4022
+ throw new Error(`promotePendingIdentity: missing pending key pair: ${handle}`);
4023
+ }
4024
+ const targetKey = metadataStoreKey(aid);
4025
+ const [existingKeyPair, existingCert, existingMetadata] = await Promise.all([
4026
+ idbGet(STORE_KEY_PAIRS, targetKey),
4027
+ idbGet(STORE_CERTS, certStoreKey(aid)),
4028
+ idbGet(STORE_METADATA, targetKey)
4029
+ ]);
4030
+ if (existingKeyPair || existingCert || existingMetadata) {
4031
+ throw new Error(`promotePendingIdentity: target exists: ${targetKey}`);
4032
+ }
4033
+ const keyPair = await this._protectedPendingKeyPair(current, aid);
4034
+ await idbPut(STORE_KEY_PAIRS, targetKey, keyPair);
4035
+ if (typeof current.cert === "string" && current.cert) {
4036
+ await idbPut(STORE_CERTS, certStoreKey(aid), current.cert);
4037
+ }
4038
+ await idbDelete(STORE_PENDING_IDENTITIES, handle);
4039
+ return targetKey;
4040
+ }
4041
+ async _protectedPendingKeyPair(current, aid) {
4042
+ if (!isRecord(current.key_pair)) {
4043
+ throw new Error(`pending identity missing key pair for ${aid}`);
4044
+ }
4045
+ const keyPair = deepClone(current.key_pair);
4046
+ const privateKeyPem = keyPair.private_key_pem;
4047
+ if (typeof privateKeyPem === "string" && privateKeyPem) {
4048
+ throw new Error(`pending identity private key is plaintext for ${aid}`);
4049
+ }
4050
+ if (!isRecord(keyPair._encrypted_pk)) {
4051
+ throw new Error(`pending identity private key is not encrypted for ${aid}`);
4052
+ }
4053
+ return keyPair;
4054
+ }
4055
+ async cleanupPendingDirs(maxAgeMs = 6e5) {
4056
+ const rows = await idbGetAll(STORE_PENDING_IDENTITIES);
4057
+ const now = Date.now();
4058
+ let removed = 0;
4059
+ for (const row of rows) {
4060
+ if (!isRecord(row.value)) continue;
4061
+ const updatedAt = Number(row.value.updated_at ?? row.value.created_at ?? 0);
4062
+ if (updatedAt && now - updatedAt < maxAgeMs) continue;
4063
+ await idbDelete(STORE_PENDING_IDENTITIES, row.key);
4064
+ removed++;
4065
+ }
4066
+ return removed;
4067
+ }
4068
+ async discardPendingIdentity(handle) {
4069
+ await idbDelete(STORE_PENDING_IDENTITIES, handle);
4070
+ }
4021
4071
  // ── 证书 ──────────────────────────────────────────
4022
4072
  async loadCert(aid, certFingerprint) {
4023
4073
  const tStart = Date.now();
@@ -4384,6 +4434,18 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
4384
4434
  });
4385
4435
  }
4386
4436
  // ── 内部辅助 ─────────────────────────────────────────
4437
+ async _loadPendingRecord(handle, aid, required = true) {
4438
+ const data = await idbGet(STORE_PENDING_IDENTITIES, handle);
4439
+ if (!isRecord(data)) {
4440
+ if (required) throw new Error(`pending identity not found: ${handle}`);
4441
+ return null;
4442
+ }
4443
+ const record = deepClone(data);
4444
+ if (aid !== void 0 && String(record.aid ?? "") !== aid) {
4445
+ throw new Error(`pending identity aid mismatch: ${handle}`);
4446
+ }
4447
+ return record;
4448
+ }
4387
4449
  async _loadKeyPairUnlocked(aid) {
4388
4450
  const data = await idbGet(STORE_KEY_PAIRS, metadataStoreKey(aid));
4389
4451
  if (!isRecord(data)) return null;
@@ -4394,16 +4456,14 @@ var _IndexedDBKeyStore = class _IndexedDBKeyStore {
4394
4456
  result.private_key_pem = await _decryptPEM(envelope, this._encryptionSeed);
4395
4457
  delete result._encrypted_pk;
4396
4458
  } catch {
4397
- this._log.error(`[keystore] decrypt ${aid} private keyfailed, maybe encryptionSeed mismatch`);
4459
+ this._log.error(`[keystore] decrypt ${aid} private key failed, maybe encryptionSeed mismatch`);
4460
+ throw new Error(`private key decrypt failed for aid ${aid}: seed_password mismatch or IndexedDB record corrupted`);
4398
4461
  }
4399
4462
  } else if (
4400
4463
  // 透明迁移:旧版明文数据自动加密回写
4401
4464
  !isRecord(result._encrypted_pk) && typeof result.private_key_pem === "string" && hasEncryptionSeed(this._encryptionSeed)
4402
4465
  ) {
4403
- try {
4404
- await this._saveKeyPairUnlocked(aid, result);
4405
- } catch {
4406
- }
4466
+ await this._saveKeyPairUnlocked(aid, result);
4407
4467
  }
4408
4468
  return result;
4409
4469
  }
@@ -5462,6 +5522,35 @@ var V2KeyStore = class _V2KeyStore {
5462
5522
  req.onerror = () => reject(req.error);
5463
5523
  });
5464
5524
  }
5525
+ async saveGroupIdentity(deviceId, groupAid, privateKeyPem, pubDer) {
5526
+ const record = {
5527
+ device_id: deviceId,
5528
+ key_type: "group_identity",
5529
+ group_id: groupAid,
5530
+ key_id: "",
5531
+ private_key: new TextEncoder().encode(privateKeyPem),
5532
+ public_key: pubDer,
5533
+ created_at: Date.now()
5534
+ };
5535
+ return new Promise((resolve, reject) => {
5536
+ const req = this.store("readwrite").put(record);
5537
+ req.onsuccess = () => resolve();
5538
+ req.onerror = () => reject(req.error);
5539
+ });
5540
+ }
5541
+ async loadGroupIdentity(deviceId, groupAid) {
5542
+ return new Promise((resolve, reject) => {
5543
+ const req = this.store("readonly").get([deviceId, "group_identity", groupAid, ""]);
5544
+ req.onsuccess = () => {
5545
+ const r = req.result;
5546
+ resolve(r ? {
5547
+ privateKeyPem: new TextDecoder().decode(r.private_key),
5548
+ pubDer: new Uint8Array(r.public_key)
5549
+ } : null);
5550
+ };
5551
+ req.onerror = () => reject(req.error);
5552
+ });
5553
+ }
5465
5554
  async markGroupSPKUploaded(deviceId, groupId, spkId) {
5466
5555
  const record = {
5467
5556
  device_id: deviceId,
@@ -9703,7 +9792,8 @@ var AID = class _AID {
9703
9792
  __publicField(this, "verifySsl");
9704
9793
  __publicField(this, "rootCaPath");
9705
9794
  __publicField(this, "debug");
9706
- __publicField(this, "_privateKeyPem");
9795
+ /** AIDStore 加载时注入的明文私钥 PEM,供 AUNClient 直接使用(无需 seed)。*/
9796
+ __publicField(this, "privateKeyPem");
9707
9797
  __publicField(this, "_certValid");
9708
9798
  __publicField(this, "_privateKeyValid");
9709
9799
  __publicField(this, "_certFingerprint", "");
@@ -9721,7 +9811,7 @@ var AID = class _AID {
9721
9811
  this.certIssuer = meta.issuer;
9722
9812
  this.certNotBefore = meta.notBefore;
9723
9813
  this.certNotAfter = meta.notAfter;
9724
- this._privateKeyPem = params.privateKeyPem;
9814
+ this.privateKeyPem = params.privateKeyPem ?? "";
9725
9815
  this._certValid = params.certValid;
9726
9816
  this._privateKeyValid = params.privateKeyValid;
9727
9817
  }
@@ -9740,10 +9830,10 @@ var AID = class _AID {
9740
9830
  return this._privateKeyValid;
9741
9831
  }
9742
9832
  async sign(payload) {
9743
- if (!this._privateKeyValid || !this._privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
9833
+ if (!this._privateKeyValid || !this.privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
9744
9834
  try {
9745
9835
  const data = typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
9746
- return resultOk({ signature: await signBytes(this._privateKeyPem, data) });
9836
+ return resultOk({ signature: await signBytes(this.privateKeyPem, data) });
9747
9837
  } catch (exc) {
9748
9838
  return resultErr(SIGNATURE_OPERATION_ERROR, String(exc), exc);
9749
9839
  }
@@ -9758,10 +9848,10 @@ var AID = class _AID {
9758
9848
  }
9759
9849
  }
9760
9850
  async signAgentMd(content) {
9761
- if (!this._privateKeyValid || !this._privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
9851
+ if (!this._privateKeyValid || !this.privateKeyPem) return resultErr(PRIVATE_KEY_NOT_VALID, "private key is not valid");
9762
9852
  try {
9763
9853
  const payload = normalizeAgentMdPayload(content);
9764
- const signature = await signBytes(this._privateKeyPem, new TextEncoder().encode(payload));
9854
+ const signature = await signBytes(this.privateKeyPem, new TextEncoder().encode(payload));
9765
9855
  return resultOk({ signed: payload + buildAgentMdSignatureBlock(this.certFingerprint, Date.now() / 1e3, signature) });
9766
9856
  } catch (exc) {
9767
9857
  return resultErr(SIGNATURE_OPERATION_ERROR, String(exc), exc);
@@ -9809,6 +9899,12 @@ function getV2DeviceId(dev) {
9809
9899
  }
9810
9900
  return { present: false, value: "" };
9811
9901
  }
9902
+ function isAIDObject(value) {
9903
+ const candidate = value;
9904
+ return Boolean(
9905
+ candidate && typeof candidate === "object" && typeof candidate.aid === "string" && typeof candidate.aunPath === "string" && typeof candidate.isPrivateKeyValid === "function"
9906
+ );
9907
+ }
9812
9908
  function sortObjectKeys(obj) {
9813
9909
  if (obj === null || obj === void 0 || typeof obj !== "object") return obj;
9814
9910
  if (Array.isArray(obj)) return obj.map(sortObjectKeys);
@@ -9941,6 +10037,20 @@ var DEFAULT_SESSION_OPTIONS = {
9941
10037
  http: 30
9942
10038
  }
9943
10039
  };
10040
+ var PUBLIC_CONNECTION_OPTION_KEYS = /* @__PURE__ */ new Set([
10041
+ "auto_reconnect",
10042
+ "connect_timeout",
10043
+ "retry_initial_delay",
10044
+ "retry_max_delay",
10045
+ "retry_max_attempts",
10046
+ "heartbeat_interval",
10047
+ "call_timeout",
10048
+ "connection_kind",
10049
+ "short_ttl_ms",
10050
+ "delivery_mode",
10051
+ "extra_info",
10052
+ "background_sync"
10053
+ ]);
9944
10054
  var PROTECTED_HEADERS_METHODS = /* @__PURE__ */ new Set([
9945
10055
  "message.send",
9946
10056
  "group.send",
@@ -10344,7 +10454,7 @@ var _AUNClient = class _AUNClient {
10344
10454
  __publicField(this, "_sessionOptions", { ...DEFAULT_SESSION_OPTIONS });
10345
10455
  __publicField(this, "_dispatcher");
10346
10456
  __publicField(this, "_discovery");
10347
- __publicField(this, "_keystore");
10457
+ __publicField(this, "_tokenStore");
10348
10458
  __publicField(this, "_auth");
10349
10459
  __publicField(this, "_transport");
10350
10460
  // E2EE 编排状态(内存缓存)
@@ -10358,6 +10468,7 @@ var _AUNClient = class _AUNClient {
10358
10468
  // V2 E2EE 状态
10359
10469
  __publicField(this, "_v2Session");
10360
10470
  __publicField(this, "_v2KeyStore");
10471
+ __publicField(this, "_v2SessionInitInFlight", null);
10361
10472
  __publicField(this, "_v2BootstrapCache", /* @__PURE__ */ new Map());
10362
10473
  __publicField(this, "_v2SenderIKPending", /* @__PURE__ */ new Map());
10363
10474
  __publicField(this, "_v2SenderIKFetching", /* @__PURE__ */ new Set());
@@ -10425,7 +10536,7 @@ var _AUNClient = class _AUNClient {
10425
10536
  __publicField(this, "_clientLog");
10426
10537
  __publicField(this, "_logAuth");
10427
10538
  __publicField(this, "_logTransport");
10428
- __publicField(this, "_logKeystore");
10539
+ __publicField(this, "_tokenStoreLog");
10429
10540
  __publicField(this, "_logDiscovery");
10430
10541
  __publicField(this, "_logEvents");
10431
10542
  /**
@@ -10433,10 +10544,10 @@ var _AUNClient = class _AUNClient {
10433
10544
  */
10434
10545
  __publicField(this, "_v2PullInflight", false);
10435
10546
  __publicField(this, "_v2PullPending", false);
10436
- const inputAid = aid !== null && aid !== void 0 && typeof aid.aunPath === "string" && typeof aid.isPrivateKeyValid === "function" ? aid : null;
10437
- if (typeof aid === "string") {
10438
- throw new ValidationError("AUNClient aid must be an AID object, not a string");
10547
+ if (aid !== null && aid !== void 0 && !isAIDObject(aid)) {
10548
+ throw new ValidationError("AUNClient only accepts an AID object or no argument");
10439
10549
  }
10550
+ const inputAid = aid ?? null;
10440
10551
  const rawConfig = {};
10441
10552
  if (inputAid) rawConfig.aun_path = inputAid.aunPath;
10442
10553
  const _debug = inputAid ? inputAid.debug : false;
@@ -10454,18 +10565,18 @@ var _AUNClient = class _AUNClient {
10454
10565
  this._clientLog = this._logger.for("aun_core.client");
10455
10566
  this._logAuth = this._logger.for("aun_core.auth");
10456
10567
  this._logTransport = this._logger.for("aun_core.transport");
10457
- this._logKeystore = this._logger.for("aun_core.keystore");
10568
+ this._tokenStoreLog = this._logger.for("aun_core.keystore");
10458
10569
  this._logDiscovery = this._logger.for("aun_core.discovery");
10459
10570
  this._logEvents = this._logger.for("aun_core.events");
10460
10571
  this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? "-"}`);
10461
10572
  this._dispatcher = new EventDispatcher();
10462
10573
  this._discovery = new GatewayDiscovery();
10463
- this._keystore = new IndexedDBKeyStore({ encryptionSeed: this.configModel.seedPassword ?? void 0 });
10574
+ this._tokenStore = new IndexedDBKeyStore({});
10464
10575
  this._slotId = inputAid?.slotId || "default";
10465
10576
  this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: "fanout" });
10466
10577
  this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
10467
10578
  this._auth = new AuthFlow({
10468
- keystore: this._keystore,
10579
+ tokenStore: this._tokenStore,
10469
10580
  crypto: new CryptoProvider(),
10470
10581
  aid: initAid,
10471
10582
  deviceId: this._deviceId,
@@ -10489,10 +10600,11 @@ var _AUNClient = class _AUNClient {
10489
10600
  this._currentAid = inputAid;
10490
10601
  this._identity = {
10491
10602
  aid: inputAid.aid,
10492
- private_key_pem: inputAid._privateKeyPem ?? "",
10603
+ private_key_pem: inputAid.privateKeyPem,
10493
10604
  public_key_der_b64: inputAid.publicKey,
10494
10605
  cert: inputAid.certPem
10495
10606
  };
10607
+ this._auth.setIdentity(this._identity);
10496
10608
  this._state = "disconnected";
10497
10609
  }
10498
10610
  }
@@ -10502,8 +10614,8 @@ var _AUNClient = class _AUNClient {
10502
10614
  if (typeof this._discovery.setLogger === "function") {
10503
10615
  this._discovery.setLogger(this._logger.for("aun_core.discovery"));
10504
10616
  }
10505
- if (typeof this._keystore.setLogger === "function") {
10506
- this._keystore.setLogger(this._logKeystore);
10617
+ if (typeof this._tokenStore.setLogger === "function") {
10618
+ this._tokenStore.setLogger(this._tokenStoreLog);
10507
10619
  }
10508
10620
  this._dispatcher.subscribe("_raw.message.received", (data) => {
10509
10621
  this._onRawMessageReceived(data);
@@ -10694,7 +10806,7 @@ var _AUNClient = class _AUNClient {
10694
10806
  if (!target) throw new ValidationError("verifyAgentMd requires non-empty aid");
10695
10807
  let peer = target === this._currentAid?.aid ? this._currentAid : null;
10696
10808
  if (!peer) {
10697
- let certPem = String(await this._keystore.loadCert(target) ?? "").trim();
10809
+ let certPem = String(await this._tokenStore.loadCert(target) ?? "").trim();
10698
10810
  if (!certPem) {
10699
10811
  certPem = String(await this._fetchPeerCert(target) ?? "").trim();
10700
10812
  }
@@ -10841,11 +10953,11 @@ var _AUNClient = class _AUNClient {
10841
10953
  async _readAgentMdStorage(logicalKey) {
10842
10954
  const key = String(logicalKey ?? "").trim();
10843
10955
  if (!key) return null;
10844
- const load = this._keystore.loadAgentMdCache;
10956
+ const load = this._tokenStore.loadAgentMdCache;
10845
10957
  if (typeof load !== "function") {
10846
10958
  throw new Error("IndexedDB agent.md storage unavailable");
10847
10959
  }
10848
- const record = await load.call(this._keystore, this._agentMdRoot(), key);
10960
+ const record = await load.call(this._tokenStore, this._agentMdRoot(), key);
10849
10961
  if (record && Object.prototype.hasOwnProperty.call(record, "content")) {
10850
10962
  return String(record.content ?? "");
10851
10963
  }
@@ -10854,12 +10966,12 @@ var _AUNClient = class _AUNClient {
10854
10966
  async _writeAgentMdStorage(logicalKey, content) {
10855
10967
  const key = String(logicalKey ?? "").trim();
10856
10968
  if (!key) return;
10857
- const save = this._keystore.upsertAgentMdCache;
10969
+ const save = this._tokenStore.upsertAgentMdCache;
10858
10970
  if (typeof save !== "function") {
10859
10971
  throw new Error("IndexedDB agent.md storage unavailable");
10860
10972
  }
10861
10973
  const text = String(content ?? "");
10862
- await save.call(this._keystore, this._agentMdRoot(), key, {
10974
+ await save.call(this._tokenStore, this._agentMdRoot(), key, {
10863
10975
  content: text,
10864
10976
  local_etag: await this._agentMdContentEtag(text),
10865
10977
  fetched_at: Date.now()
@@ -11215,22 +11327,79 @@ var _AUNClient = class _AUNClient {
11215
11327
  get lastErrorCode() {
11216
11328
  return this._lastErrorCode;
11217
11329
  }
11330
+ _applyAidRuntimeContext(aid) {
11331
+ const nextConfig = createConfig({
11332
+ aunPath: aid.aunPath,
11333
+ rootCaPem: aid.rootCaPath,
11334
+ verifySsl: aid.verifySsl
11335
+ });
11336
+ Object.assign(this.configModel, nextConfig);
11337
+ this.config.aun_path = nextConfig.aunPath;
11338
+ this.config.root_ca_path = nextConfig.rootCaPem;
11339
+ this.config.seed_password = nextConfig.seedPassword;
11340
+ this._agentMdPath = this._agentMdDefaultRoot();
11341
+ this._agentMdCache.clear();
11342
+ this._agentMdFetchInflight.clear();
11343
+ this._peerCache.clear();
11344
+ this._certCache.clear();
11345
+ this._gatewayUrl = null;
11346
+ this._deviceId = aid.deviceId || getDeviceId();
11347
+ this._slotId = aid.slotId || "default";
11348
+ this._logger = new AUNLogger({ debug: aid.debug, aunPath: nextConfig.aunPath });
11349
+ this._logger.bindDeviceId(this._deviceId);
11350
+ this._clientLog = this._logger.for("aun_core.client");
11351
+ this._logAuth = this._logger.for("aun_core.auth");
11352
+ this._logTransport = this._logger.for("aun_core.transport");
11353
+ this._tokenStoreLog = this._logger.for("aun_core.keystore");
11354
+ this._logDiscovery = this._logger.for("aun_core.discovery");
11355
+ this._logEvents = this._logger.for("aun_core.events");
11356
+ this._discovery = new GatewayDiscovery();
11357
+ this._tokenStore = new IndexedDBKeyStore({});
11358
+ this._auth = new AuthFlow({
11359
+ tokenStore: this._tokenStore,
11360
+ crypto: new CryptoProvider(),
11361
+ aid: aid.aid,
11362
+ deviceId: this._deviceId,
11363
+ slotId: this._slotId,
11364
+ rootCaPem: nextConfig.rootCaPem,
11365
+ verifySsl: nextConfig.verifySsl
11366
+ });
11367
+ this._transport = new RPCTransport({
11368
+ eventDispatcher: this._dispatcher,
11369
+ timeout: DEFAULT_SESSION_OPTIONS.timeouts.call,
11370
+ onDisconnect: (error, closeCode) => this._handleTransportDisconnect(error, closeCode)
11371
+ });
11372
+ this._transport.setMetaObserver((meta) => {
11373
+ void this._observeRpcMeta(meta).catch((exc) => {
11374
+ this._clientLog.debug(`agent.md meta observer skipped: ${String(exc)}`);
11375
+ });
11376
+ });
11377
+ this._auth.setLogger(this._logAuth);
11378
+ this._transport.setLogger(this._logTransport);
11379
+ this._dispatcher.setLogger(this._logEvents);
11380
+ if (typeof this._discovery.setLogger === "function") {
11381
+ this._discovery.setLogger(this._logDiscovery);
11382
+ }
11383
+ if (typeof this._tokenStore.setLogger === "function") {
11384
+ this._tokenStore.setLogger(this._tokenStoreLog);
11385
+ }
11386
+ }
11218
11387
  loadIdentity(aid) {
11219
11388
  if (!aid?.isPrivateKeyValid()) throw new StateError("loadIdentity requires an AID with a valid private key");
11220
11389
  const publicState = this.state;
11221
11390
  if (publicState !== "no_identity" /* NO_IDENTITY */ && publicState !== "closed" /* CLOSED */) {
11222
11391
  throw new StateError(`loadIdentity not allowed in state ${publicState}`);
11223
11392
  }
11393
+ this._applyAidRuntimeContext(aid);
11224
11394
  this._currentAid = aid;
11225
11395
  this._aid = aid.aid;
11226
11396
  this._identity = {
11227
11397
  aid: aid.aid,
11228
- private_key_pem: aid._privateKeyPem ?? "",
11398
+ private_key_pem: aid.privateKeyPem,
11229
11399
  public_key_der_b64: aid.publicKey,
11230
11400
  cert: aid.certPem
11231
11401
  };
11232
- this._auth._aid = aid.aid;
11233
- this._slotId = aid.slotId || "default";
11402
+ this._auth.setIdentity(this._identity);
11234
11403
  this._state = "disconnected";
11235
11404
  this._closing = false;
11236
11405
  }
@@ -11274,9 +11443,6 @@ var _AUNClient = class _AUNClient {
11274
11443
  get gatewayUrl() {
11275
11444
  return this._gatewayUrl;
11276
11445
  }
11277
- set gatewayUrl(url) {
11278
- this._gatewayUrl = url;
11279
- }
11280
11446
  get discovery() {
11281
11447
  return this._discovery;
11282
11448
  }
@@ -11317,10 +11483,11 @@ var _AUNClient = class _AUNClient {
11317
11483
  /** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
11318
11484
  async connect(opts) {
11319
11485
  const tStart = Date.now();
11320
- if (opts !== void 0 && typeof opts === "object") {
11486
+ if (opts !== void 0 && opts !== null && typeof opts === "object") {
11321
11487
  const raw = opts;
11322
- if ("access_token" in raw || "aid" in raw || "token" in raw) {
11323
- throw new ValidationError("connect options must not include access_token/aid; these are managed internally");
11488
+ const invalid = Object.keys(raw).filter((key) => !PUBLIC_CONNECTION_OPTION_KEYS.has(key)).sort();
11489
+ if (invalid.length > 0) {
11490
+ throw new ValidationError(`connect options contain unsupported field(s): ${invalid.join(", ")}`);
11324
11491
  }
11325
11492
  }
11326
11493
  const target = this._currentAid?.aid ?? this._aid ?? "";
@@ -11482,6 +11649,12 @@ var _AUNClient = class _AUNClient {
11482
11649
  }
11483
11650
  this._validateOutboundCall(method, p);
11484
11651
  this._injectMessageCursorContext(method, p);
11652
+ if (method.startsWith("group.") && !("_group_cursor_params" in p) && !Boolean(p._pull_gate_locked)) {
11653
+ const explicitCursorParams = this._groupCursorParams(p);
11654
+ if (Object.keys(explicitCursorParams).length > 0) {
11655
+ p._group_cursor_params = explicitCursorParams;
11656
+ }
11657
+ }
11485
11658
  if (method.startsWith("group.") && p.group_id !== void 0 && p.group_id !== null) {
11486
11659
  const rawGroupId = String(p.group_id);
11487
11660
  const normalizedGroupId = normalizeGroupId(rawGroupId);
@@ -11500,9 +11673,10 @@ var _AUNClient = class _AUNClient {
11500
11673
  const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
11501
11674
  delete p.encrypt;
11502
11675
  if (encrypt) {
11503
- if (!this._v2Session) {
11504
- throw new StateError("V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)");
11505
- }
11676
+ await this._ensureV2SessionReady(
11677
+ "message.send",
11678
+ "V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)"
11679
+ );
11506
11680
  this._clientLog.debug("call route: message.send \u2192 V2 encrypted send");
11507
11681
  return await this._sendV2(String(p.to ?? ""), p.payload ?? {}, {
11508
11682
  messageId: String(p.message_id ?? "") || void 0,
@@ -11517,9 +11691,10 @@ var _AUNClient = class _AUNClient {
11517
11691
  const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
11518
11692
  delete p.encrypt;
11519
11693
  if (encrypt) {
11520
- if (!this._v2Session) {
11521
- throw new StateError("V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)");
11522
- }
11694
+ await this._ensureV2SessionReady(
11695
+ "group.send",
11696
+ "V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)"
11697
+ );
11523
11698
  this._clientLog.debug("call route: group.send \u2192 V2 encrypted send");
11524
11699
  return await this._sendGroupV2(String(p.group_id ?? ""), p.payload ?? {}, {
11525
11700
  messageId: String(p.message_id ?? "") || void 0,
@@ -11534,9 +11709,10 @@ var _AUNClient = class _AUNClient {
11534
11709
  const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
11535
11710
  delete p.encrypt;
11536
11711
  if (encrypt) {
11537
- if (!this._v2Session) {
11538
- throw new StateError("V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)");
11539
- }
11712
+ await this._ensureV2SessionReady(
11713
+ "group.thought.put",
11714
+ "V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)"
11715
+ );
11540
11716
  this._clientLog.debug("call route: group.thought.put \u2192 V2 encrypted put");
11541
11717
  return this._putGroupThoughtEncryptedV2(p);
11542
11718
  }
@@ -11545,9 +11721,10 @@ var _AUNClient = class _AUNClient {
11545
11721
  const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
11546
11722
  delete p.encrypt;
11547
11723
  if (encrypt) {
11548
- if (!this._v2Session) {
11549
- throw new StateError("V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)");
11550
- }
11724
+ await this._ensureV2SessionReady(
11725
+ "message.thought.put",
11726
+ "V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)"
11727
+ );
11551
11728
  this._clientLog.debug("call route: message.thought.put \u2192 V2 encrypted put");
11552
11729
  return this._putMessageThoughtEncryptedV2(p);
11553
11730
  }
@@ -11565,26 +11742,43 @@ var _AUNClient = class _AUNClient {
11565
11742
  * 拆分出来以便 pull gate 包裹整个操作。
11566
11743
  */
11567
11744
  async _callImplInner(method, p) {
11568
- if (method === "message.pull" && this._v2Session) {
11745
+ if (method === "message.pull") {
11746
+ await this._ensureV2SessionReady("message.pull");
11569
11747
  this._clientLog.debug("call route: message.pull \u2192 V2 pull");
11570
11748
  const messages = await this._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
11571
11749
  return { messages };
11572
11750
  }
11573
- if (method === "message.ack" && this._v2Session) {
11751
+ if (method === "message.ack") {
11752
+ await this._ensureV2SessionReady("message.ack");
11574
11753
  this._clientLog.debug("call route: message.ack \u2192 V2 ack");
11575
11754
  return await this._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || void 0);
11576
11755
  }
11577
- if (method === "group.pull" && this._v2Session && p.group_id) {
11756
+ if (method === "group.pull" && p.group_id) {
11757
+ await this._ensureV2SessionReady("group.pull");
11578
11758
  this._clientLog.debug("call route: group.pull \u2192 V2 pull");
11759
+ const hasExplicitAfterSeq = "after_seq" in p || "after_message_seq" in p;
11760
+ const cursorParams = this._explicitGroupCursorParams(p);
11761
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
11762
+ const pullOpts = {};
11763
+ if (hasExplicitAfterSeq) pullOpts.explicitAfterSeq = true;
11764
+ if (Object.keys(cursorParams).length > 0) pullOpts.cursorParams = cursorParams;
11765
+ if (!ownsCursor) pullOpts.ownsCursor = false;
11579
11766
  const messages = await this._pullGroupV2(
11580
11767
  String(p.group_id),
11581
11768
  Number(p.after_seq ?? p.after_message_seq ?? 0) || 0,
11582
- Number(p.limit ?? 50) || 50
11769
+ Number(p.limit ?? 50) || 50,
11770
+ Object.keys(pullOpts).length > 0 ? pullOpts : void 0
11583
11771
  );
11584
11772
  return { messages };
11585
11773
  }
11586
- if (method === "group.ack_messages" && this._v2Session && p.group_id) {
11774
+ if (method === "group.ack_messages" && p.group_id) {
11775
+ await this._ensureV2SessionReady("group.ack_messages");
11587
11776
  this._clientLog.debug("call route: group.ack_messages \u2192 V2 ack");
11777
+ const cursorParams = this._explicitGroupCursorParams(p);
11778
+ const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
11779
+ if (!ownsCursor) {
11780
+ return await this._rawGroupAckMessages(p);
11781
+ }
11588
11782
  return await this._ackGroupV2(
11589
11783
  String(p.group_id),
11590
11784
  Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || void 0
@@ -11685,6 +11879,7 @@ var _AUNClient = class _AUNClient {
11685
11879
  delete p._pull_gate_locked;
11686
11880
  delete p._skip_auto_ack;
11687
11881
  delete p.skip_auto_ack;
11882
+ delete p._group_cursor_params;
11688
11883
  if (method.startsWith("group.") && p.group_id !== void 0 && p.group_id !== null) {
11689
11884
  p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
11690
11885
  }
@@ -12014,54 +12209,27 @@ var _AUNClient = class _AUNClient {
12014
12209
  /** 后台补齐群消息空洞 */
12015
12210
  async _fillGroupGap(groupId) {
12016
12211
  if (this._state !== "connected" || this._closing) return;
12212
+ groupId = normalizeGroupId(groupId) || String(groupId ?? "").trim();
12213
+ if (!groupId) return;
12017
12214
  const ns = `group:${groupId}`;
12018
12215
  const afterSeq = this._seqTracker.getContiguousSeq(ns);
12019
12216
  const dedupKey = `group_pull:${ns}`;
12020
12217
  if (this._gapFillDone.has(dedupKey)) return;
12021
12218
  this._gapFillDone.add(dedupKey);
12022
12219
  this._gapFillActive = true;
12220
+ let filled = 0;
12023
12221
  try {
12024
- const result = await this.call("group.pull", {
12025
- group_id: groupId,
12026
- after_message_seq: afterSeq,
12027
- device_id: this._deviceId,
12028
- limit: 50
12029
- });
12030
- if (isJsonObject(result)) {
12031
- const messages = result.messages;
12032
- if (Array.isArray(messages)) {
12033
- const pushed = this._pushedSeqs.get(ns);
12034
- for (const msg of messages) {
12035
- if (isJsonObject(msg)) {
12036
- const s = msg.seq;
12037
- if (pushed && s !== void 0 && s !== null && pushed.has(s)) continue;
12038
- if (s !== void 0 && s !== null) {
12039
- await this._publishPulledMessage("group.message_created", ns, s, msg);
12040
- } else {
12041
- await this._publishAppEvent("group.message_created", msg);
12042
- }
12043
- }
12044
- }
12045
- this._prunePushedSeqs(ns);
12046
- const contig = this._seqTracker.getContiguousSeq(ns);
12047
- if (contig > 0) {
12048
- const gid = groupId;
12049
- this._transport.call("group.ack_messages", {
12050
- group_id: gid,
12051
- msg_seq: contig,
12052
- device_id: this._deviceId,
12053
- slot_id: this._slotId
12054
- }).catch((e) => {
12055
- this._clientLog.warn(`group gap-fill auto-ack failed: group=${gid}`, e);
12056
- });
12057
- }
12058
- }
12059
- }
12222
+ const messages = await this._pullGroupV2(groupId, afterSeq, 50);
12223
+ filled = messages.length;
12224
+ this._prunePushedSeqs(ns);
12060
12225
  } catch (exc) {
12061
12226
  this._clientLog.warn(`group message gap-fill failed:${String(exc)}`);
12062
12227
  } finally {
12063
12228
  this._gapFillDone.delete(dedupKey);
12064
12229
  this._gapFillActive = false;
12230
+ if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
12231
+ this._safeAsync(this._fillGroupGap(groupId));
12232
+ }
12065
12233
  }
12066
12234
  }
12067
12235
  /** 后台补齐群事件空洞 */
@@ -12159,44 +12327,19 @@ var _AUNClient = class _AUNClient {
12159
12327
  if (this._gapFillDone.has(dedupKey)) return;
12160
12328
  this._gapFillDone.add(dedupKey);
12161
12329
  this._gapFillActive = true;
12330
+ let filled = 0;
12162
12331
  try {
12163
- const result = await this.call("message.pull", {
12164
- after_seq: afterSeq,
12165
- limit: 50
12166
- });
12167
- if (isJsonObject(result)) {
12168
- const messages = result.messages;
12169
- if (Array.isArray(messages)) {
12170
- const pushed = this._pushedSeqs.get(ns);
12171
- for (const msg of messages) {
12172
- if (isJsonObject(msg)) {
12173
- const s = msg.seq;
12174
- if (pushed && s !== void 0 && s !== null && pushed.has(s)) continue;
12175
- if (s !== void 0 && s !== null) {
12176
- await this._publishPulledMessage("message.received", ns, s, msg);
12177
- } else {
12178
- await this._publishAppEvent("message.received", msg);
12179
- }
12180
- }
12181
- }
12182
- this._prunePushedSeqs(ns);
12183
- const contig = this._seqTracker.getContiguousSeq(ns);
12184
- if (contig > 0) {
12185
- this._transport.call("message.ack", {
12186
- seq: contig,
12187
- device_id: this._deviceId,
12188
- slot_id: this._slotId
12189
- }).catch((e) => {
12190
- this._clientLog.warn(`P2P gap-fill auto-ack failed:${String(e)}`);
12191
- });
12192
- }
12193
- }
12194
- }
12332
+ const messages = await this._pullV2(afterSeq, 50);
12333
+ filled = messages.length;
12334
+ this._prunePushedSeqs(ns);
12195
12335
  } catch (exc) {
12196
12336
  this._clientLog.warn(`P2P message gap-fill failed:${String(exc)}`);
12197
12337
  } finally {
12198
12338
  this._gapFillDone.delete(dedupKey);
12199
12339
  this._gapFillActive = false;
12340
+ if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
12341
+ this._safeAsync(this._fillP2pGap());
12342
+ }
12200
12343
  }
12201
12344
  }
12202
12345
  /** 只按硬上限裁剪 published guard,不能按 contiguousSeq 清理。 */
@@ -12516,8 +12659,8 @@ var _AUNClient = class _AUNClient {
12516
12659
  const keyEpoch = Number(d.key_epoch ?? 0);
12517
12660
  const membershipSnapshot = String(d.membership_snapshot ?? "").trim();
12518
12661
  const policySnapshot = String(d.policy_snapshot ?? "").trim();
12519
- const loadFn = this._keystore.loadGroupState;
12520
- const localState = loadFn ? await loadFn.call(this._keystore, groupId) : null;
12662
+ const loadFn = this._tokenStore.loadGroupState;
12663
+ const localState = loadFn ? await loadFn.call(this._tokenStore, groupId) : null;
12521
12664
  if (localState && localState.state_hash && localState.state_hash !== prevStateHash) {
12522
12665
  this._clientLog.warn(
12523
12666
  "[aun_core] state_hash \u94FE\u4E0D\u8FDE\u7EED group=%s local_sv=%d event_sv=%d",
@@ -12556,9 +12699,9 @@ var _AUNClient = class _AUNClient {
12556
12699
  return;
12557
12700
  }
12558
12701
  }
12559
- const saveFn2 = this._keystore.saveGroupState;
12702
+ const saveFn2 = this._tokenStore.saveGroupState;
12560
12703
  if (saveFn2) {
12561
- await saveFn2.call(this._keystore, groupId, {
12704
+ await saveFn2.call(this._tokenStore, groupId, {
12562
12705
  group_id: groupId,
12563
12706
  state_version: sv,
12564
12707
  state_hash: sHash,
@@ -12594,9 +12737,9 @@ var _AUNClient = class _AUNClient {
12594
12737
  );
12595
12738
  return;
12596
12739
  }
12597
- const saveFn = this._keystore.saveGroupState;
12740
+ const saveFn = this._tokenStore.saveGroupState;
12598
12741
  if (saveFn) {
12599
- await saveFn.call(this._keystore, groupId, {
12742
+ await saveFn.call(this._tokenStore, groupId, {
12600
12743
  group_id: groupId,
12601
12744
  state_version: stateVersion,
12602
12745
  state_hash: stateHash,
@@ -12967,7 +13110,7 @@ var _AUNClient = class _AUNClient {
12967
13110
  refreshAfter: now + PEER_CERT_CACHE_TTL
12968
13111
  });
12969
13112
  try {
12970
- await this._keystore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
13113
+ await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
12971
13114
  } catch (exc) {
12972
13115
  this._clientLog.error(`write cert to keystore failed (aid=${aid}): ${String(exc)}`, exc instanceof Error ? exc : void 0);
12973
13116
  }
@@ -12984,7 +13127,7 @@ var _AUNClient = class _AUNClient {
12984
13127
  const cached = this._certCache.get(cacheKey);
12985
13128
  const now = Date.now() / 1e3;
12986
13129
  if (cached && now < cached.refreshAfter) return true;
12987
- const localCert = await this._keystore.loadCert(aid, certFingerprint);
13130
+ const localCert = await this._tokenStore.loadCert(aid, certFingerprint);
12988
13131
  if (localCert) {
12989
13132
  if (certFingerprint) {
12990
13133
  const actualFingerprint = await this._certFingerprint(localCert);
@@ -13007,7 +13150,7 @@ var _AUNClient = class _AUNClient {
13007
13150
  }
13008
13151
  try {
13009
13152
  const certPem = await this._fetchPeerCert(aid, certFingerprint);
13010
- await this._keystore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
13153
+ await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
13011
13154
  return true;
13012
13155
  } catch (exc) {
13013
13156
  if (cached && now < cached.validatedAt + PEER_CERT_CACHE_TTL * 2) {
@@ -13039,10 +13182,10 @@ var _AUNClient = class _AUNClient {
13039
13182
  * 使用 SubtleCrypto 异步签名。
13040
13183
  */
13041
13184
  async _signClientOperation(method, params) {
13042
- const identity = this._identity;
13043
- if (!identity || !identity.private_key_pem) return;
13185
+ const currentAid = this._currentAid;
13186
+ if (!currentAid?.privateKeyPem) return;
13044
13187
  try {
13045
- const aid = identity.aid ?? "";
13188
+ const aid = currentAid.aid;
13046
13189
  const ts = String(Math.floor(Date.now() / 1e3));
13047
13190
  const paramsForHash = {};
13048
13191
  for (const [k, v] of Object.entries(params)) {
@@ -13057,7 +13200,7 @@ var _AUNClient = class _AUNClient {
13057
13200
  );
13058
13201
  const paramsHash = Array.from(new Uint8Array(paramsHashBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
13059
13202
  const signData = new TextEncoder().encode(`${method}|${aid}|${ts}|${paramsHash}`);
13060
- const pkcs8 = pemToArrayBuffer(identity.private_key_pem);
13203
+ const pkcs8 = pemToArrayBuffer(currentAid.privateKeyPem);
13061
13204
  const cryptoKey = await crypto.subtle.importKey(
13062
13205
  "pkcs8",
13063
13206
  pkcs8,
@@ -13072,7 +13215,7 @@ var _AUNClient = class _AUNClient {
13072
13215
  );
13073
13216
  const sigDer = p1363ToDer(new Uint8Array(sigP1363));
13074
13217
  let certFingerprint = "";
13075
- const certPem = identity.cert ?? "";
13218
+ const certPem = currentAid.certPem;
13076
13219
  if (certPem) {
13077
13220
  const certDer = pemToArrayBuffer(certPem);
13078
13221
  const fpBuf = await crypto.subtle.digest("SHA-256", certDer);
@@ -13172,12 +13315,20 @@ var _AUNClient = class _AUNClient {
13172
13315
  await this._restoreSeqTrackerState();
13173
13316
  }
13174
13317
  this._startBackgroundTasks();
13175
- try {
13176
- await this._initV2Session();
13177
- } catch (exc) {
13178
- this._clientLog.warn(`V2 session init failed (non-fatal): ${String(exc)}`);
13318
+ const connectionKind = String(params.connection_kind ?? "long");
13319
+ const isShortConnection = connectionKind === "short";
13320
+ if (!isShortConnection) {
13321
+ try {
13322
+ await this._initV2Session();
13323
+ } catch (exc) {
13324
+ this._clientLog.warn(`V2 session init failed (non-fatal): ${String(exc)}`);
13325
+ }
13326
+ } else {
13327
+ this._clientLog.debug("V2 session init deferred for short connection");
13179
13328
  }
13180
- if (this._sessionOptions?.background_sync !== false) {
13329
+ const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params, "background_sync");
13330
+ const backgroundSyncEnabled = this._sessionOptions?.background_sync !== false && (!isShortConnection || hasExplicitBackgroundSync);
13331
+ if (backgroundSyncEnabled) {
13181
13332
  this._safeAsync(this._fillP2pGap());
13182
13333
  }
13183
13334
  this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms aid=${this._aid ?? "-"}`);
@@ -13195,8 +13346,8 @@ var _AUNClient = class _AUNClient {
13195
13346
  if (!target) throw new StateError("gateway discovery requires a loaded AID");
13196
13347
  if (this._gatewayUrl) return this._gatewayUrl;
13197
13348
  try {
13198
- const getMetadata = this._keystore.getMetadata;
13199
- const raw = typeof getMetadata === "function" ? String(await getMetadata.call(this._keystore, target, "gateway_url") ?? "").trim() : "";
13349
+ const getMetadata = this._tokenStore.getMetadata;
13350
+ const raw = typeof getMetadata === "function" ? String(await getMetadata.call(this._tokenStore, target, "gateway_url") ?? "").trim() : "";
13200
13351
  if (raw) {
13201
13352
  const gateway = raw.startsWith('"') && raw.endsWith('"') ? String(JSON.parse(raw)).trim() : raw;
13202
13353
  if (gateway) {
@@ -13219,9 +13370,9 @@ var _AUNClient = class _AUNClient {
13219
13370
  const gateway = await this._discovery.discover(url);
13220
13371
  this._gatewayUrl = gateway;
13221
13372
  try {
13222
- const setMetadata = this._keystore.setMetadata;
13373
+ const setMetadata = this._tokenStore.setMetadata;
13223
13374
  if (typeof setMetadata === "function") {
13224
- await setMetadata.call(this._keystore, target, "gateway_url", gateway);
13375
+ await setMetadata.call(this._tokenStore, target, "gateway_url", gateway);
13225
13376
  }
13226
13377
  } catch {
13227
13378
  }
@@ -13253,13 +13404,8 @@ var _AUNClient = class _AUNClient {
13253
13404
  return [gateway];
13254
13405
  }
13255
13406
  async _syncIdentityAfterConnect(accessToken) {
13256
- let identity = null;
13257
- try {
13258
- identity = await this._auth.loadIdentityOrNone(this._aid ?? void 0);
13259
- } catch {
13260
- }
13407
+ const identity = this._identity;
13261
13408
  if (!identity) {
13262
- this._identity = null;
13263
13409
  return;
13264
13410
  }
13265
13411
  identity.access_token = accessToken;
@@ -13269,8 +13415,6 @@ var _AUNClient = class _AUNClient {
13269
13415
  const persistIdentity = this._auth._persistIdentity;
13270
13416
  if (typeof persistIdentity === "function") {
13271
13417
  await persistIdentity.call(this._auth, identity);
13272
- } else {
13273
- await this._keystore.saveIdentity(String(identity.aid), identity);
13274
13418
  }
13275
13419
  }
13276
13420
  }
@@ -13740,8 +13884,7 @@ var _AUNClient = class _AUNClient {
13740
13884
  }
13741
13885
  // ── Named Group(命名群)高层 API ────────────────────────────
13742
13886
  /**
13743
- * 创建命名群:本地生成 P-256 keypair,调用 group.create 传入 public_key,
13744
- * 服务端签发群 AID 证书,返回后将证书和私钥存入 keystore。
13887
+ * 创建命名群:群/P2P 私钥由 V2 数据库存储,不写入 AID 身份私钥存储。
13745
13888
  */
13746
13889
  async createNamedGroup(groupName, opts = {}) {
13747
13890
  const tStart = Date.now();
@@ -13761,15 +13904,10 @@ var _AUNClient = class _AUNClient {
13761
13904
  const aidCert = result?.aid_cert;
13762
13905
  const groupAid = String(groupInfo?.group_aid ?? "");
13763
13906
  if (groupAid && aidCert) {
13764
- await this._keystore.saveIdentity(groupAid, {
13765
- private_key_pem: identity.private_key_pem,
13766
- public_key: identity.public_key_der_b64,
13767
- curve: "P-256",
13768
- type: "group_identity"
13769
- });
13907
+ await this._saveGroupIdentityToV2(groupAid, identity);
13770
13908
  const certPem = String(aidCert.cert ?? "");
13771
13909
  if (certPem) {
13772
- await this._keystore.saveCert(groupAid, certPem);
13910
+ await this._tokenStore.saveCert(groupAid, certPem);
13773
13911
  }
13774
13912
  }
13775
13913
  this._clientLog.debug(`createNamedGroup exit: elapsed=${Date.now() - tStart}ms group_aid=${groupAid}`);
@@ -13799,15 +13937,10 @@ var _AUNClient = class _AUNClient {
13799
13937
  const aidCert = result?.aid_cert;
13800
13938
  const groupAid = String(groupInfo?.group_aid ?? "");
13801
13939
  if (groupAid && aidCert) {
13802
- await this._keystore.saveIdentity(groupAid, {
13803
- private_key_pem: identity.private_key_pem,
13804
- public_key: identity.public_key_der_b64,
13805
- curve: "P-256",
13806
- type: "group_identity"
13807
- });
13940
+ await this._saveGroupIdentityToV2(groupAid, identity);
13808
13941
  const certPem = String(aidCert.cert ?? "");
13809
13942
  if (certPem) {
13810
- await this._keystore.saveCert(groupAid, certPem);
13943
+ await this._tokenStore.saveCert(groupAid, certPem);
13811
13944
  }
13812
13945
  }
13813
13946
  this._clientLog.debug(`bindGroupAid exit: elapsed=${Date.now() - tStart}ms group_aid=${groupAid}`);
@@ -13843,7 +13976,7 @@ var _AUNClient = class _AUNClient {
13843
13976
  const deviceId = this._deviceId;
13844
13977
  const slotId = this._slotId;
13845
13978
  try {
13846
- const loadAll = this._keystore.loadAllSeqs?.bind(this._keystore);
13979
+ const loadAll = this._tokenStore.loadAllSeqs?.bind(this._tokenStore);
13847
13980
  if (typeof loadAll === "function") {
13848
13981
  let state = await loadAll(aid, deviceId, slotId);
13849
13982
  if (this._seqTrackerContext !== context) return;
@@ -13853,7 +13986,7 @@ var _AUNClient = class _AUNClient {
13853
13986
  }
13854
13987
  return;
13855
13988
  }
13856
- const loader = this._keystore.loadInstanceState?.bind(this._keystore);
13989
+ const loader = this._tokenStore.loadInstanceState?.bind(this._tokenStore);
13857
13990
  if (typeof loader !== "function") return;
13858
13991
  const stateHolder = await loader(aid, deviceId, slotId);
13859
13992
  if (this._seqTrackerContext !== context) return;
@@ -13906,8 +14039,8 @@ var _AUNClient = class _AUNClient {
13906
14039
  const aid = this._aid;
13907
14040
  const deviceId = this._deviceId;
13908
14041
  const slotId = this._slotId;
13909
- const saver = this._keystore.saveSeq?.bind(this._keystore);
13910
- const deleter = this._keystore.deleteSeq?.bind(this._keystore);
14042
+ const saver = this._tokenStore.saveSeq?.bind(this._tokenStore);
14043
+ const deleter = this._tokenStore.deleteSeq?.bind(this._tokenStore);
13911
14044
  if (typeof saver === "function") {
13912
14045
  for (const [oldNs, newNs] of Object.entries(renameMap)) {
13913
14046
  if (typeof deleter === "function") {
@@ -13974,7 +14107,7 @@ var _AUNClient = class _AUNClient {
13974
14107
  const state = this._seqTracker.exportState();
13975
14108
  if (Object.keys(state).length === 0) return;
13976
14109
  try {
13977
- const saveFn = this._keystore.saveSeq?.bind(this._keystore);
14110
+ const saveFn = this._tokenStore.saveSeq?.bind(this._tokenStore);
13978
14111
  if (typeof saveFn === "function") {
13979
14112
  for (const [ns, seq] of Object.entries(state)) {
13980
14113
  saveFn(this._aid, this._deviceId, this._slotId, ns, seq).catch((exc) => {
@@ -13990,8 +14123,8 @@ var _AUNClient = class _AUNClient {
13990
14123
  }
13991
14124
  return;
13992
14125
  }
13993
- if (typeof this._keystore.updateInstanceState === "function") {
13994
- this._keystore.updateInstanceState(this._aid, this._deviceId, this._slotId, (current) => {
14126
+ if (typeof this._tokenStore.updateInstanceState === "function") {
14127
+ this._tokenStore.updateInstanceState(this._aid, this._deviceId, this._slotId, (current) => {
13995
14128
  current.seq_tracker_state = state;
13996
14129
  return current;
13997
14130
  }).catch((exc) => {
@@ -14020,15 +14153,15 @@ var _AUNClient = class _AUNClient {
14020
14153
  if (!this._aid || !ns) return;
14021
14154
  const seq = this._seqTracker.getContiguousSeq(ns);
14022
14155
  try {
14023
- if (seq > 0 && typeof this._keystore.saveSeq === "function") {
14024
- this._keystore.saveSeq(this._aid, this._deviceId, this._slotId, ns, seq).catch((exc) => {
14156
+ if (seq > 0 && typeof this._tokenStore.saveSeq === "function") {
14157
+ this._tokenStore.saveSeq(this._aid, this._deviceId, this._slotId, ns, seq).catch((exc) => {
14025
14158
  this._clientLog.debug(`persist repaired seq failed: ns=${ns} err=${formatCaughtError(exc)}`);
14026
14159
  });
14027
14160
  return;
14028
14161
  }
14029
- const deleteSeq = this._keystore.deleteSeq;
14162
+ const deleteSeq = this._tokenStore.deleteSeq;
14030
14163
  if (seq <= 0 && typeof deleteSeq === "function") {
14031
- deleteSeq.call(this._keystore, this._aid, this._deviceId, this._slotId, ns).catch((exc) => {
14164
+ deleteSeq.call(this._tokenStore, this._aid, this._deviceId, this._slotId, ns).catch((exc) => {
14032
14165
  this._clientLog.debug(`delete repaired seq failed: ns=${ns} err=${formatCaughtError(exc)}`);
14033
14166
  });
14034
14167
  return;
@@ -14056,6 +14189,19 @@ var _AUNClient = class _AUNClient {
14056
14189
  );
14057
14190
  return repaired;
14058
14191
  }
14192
+ async _ensureV2SessionReady(method, errorMessage) {
14193
+ if (!this._v2Session) {
14194
+ if (!this._v2SessionInitInFlight) {
14195
+ this._v2SessionInitInFlight = this._initV2Session().finally(() => {
14196
+ this._v2SessionInitInFlight = null;
14197
+ });
14198
+ }
14199
+ await this._v2SessionInitInFlight;
14200
+ }
14201
+ if (!this._v2Session) {
14202
+ throw new StateError(errorMessage ?? `V2 session not initialized; encrypted ${method} requires E2EE V2`);
14203
+ }
14204
+ }
14059
14205
  // ── V2 E2EE API(async,与 Python `client.py` `_init_v2_session` / `send_v2` / `pull_v2` / `ack_v2` 对齐) ──
14060
14206
  /**
14061
14207
  * 初始化 V2 session:从 AID PEM 私钥提取 raw scalar + DER 公钥,
@@ -14065,31 +14211,13 @@ var _AUNClient = class _AUNClient {
14065
14211
  */
14066
14212
  async _initV2Session() {
14067
14213
  if (!this._aid) return;
14068
- let identity = this._identity;
14069
- if (!identity?.private_key_pem) {
14070
- try {
14071
- const reloaded = await this._keystore.loadIdentity(this._aid);
14072
- if (reloaded?.private_key_pem) {
14073
- this._identity = reloaded;
14074
- identity = reloaded;
14075
- this._clientLog.warn("V2 session init: identity cache was stale, reloaded from keystore");
14076
- try {
14077
- const persistIdentity = this._auth._persistIdentity;
14078
- if (typeof persistIdentity === "function") {
14079
- await persistIdentity.call(this._auth, reloaded);
14080
- }
14081
- } catch {
14082
- }
14083
- }
14084
- } catch {
14085
- }
14086
- }
14087
- if (!identity?.private_key_pem) {
14214
+ const currentAid = this._currentAid;
14215
+ if (!currentAid?.privateKeyPem) {
14088
14216
  this._clientLog.warn("V2 session init skipped: no AID private key");
14089
14217
  return;
14090
14218
  }
14091
14219
  if (this._v2Session) return;
14092
- const pem = String(identity.private_key_pem).trim();
14220
+ const pem = currentAid.privateKeyPem.trim();
14093
14221
  const pemBody = pem.replace(/-----BEGIN [^-]+-----/g, "").replace(/-----END [^-]+-----/g, "").replace(/\s+/g, "");
14094
14222
  const pkcs8Der = _v2B64ToBytes(pemBody);
14095
14223
  const privKey = await crypto.subtle.importKey(
@@ -14132,6 +14260,25 @@ var _AUNClient = class _AUNClient {
14132
14260
  this._clientLog.debug(`V2 session initialized aid=${this._aid} device=${this._deviceId}`);
14133
14261
  this._safeAsync(this._v2AutoConfirmPendingProposals());
14134
14262
  }
14263
+ _v2StoreDeviceId() {
14264
+ return `aid:${encodeURIComponent(String(this._aid ?? ""))}|device:${encodeURIComponent(String(this._deviceId ?? ""))}`;
14265
+ }
14266
+ async _saveGroupIdentityToV2(groupAid, identity) {
14267
+ const privateKeyPem = String(identity.private_key_pem ?? "").trim();
14268
+ const publicKeyDerB642 = String(identity.public_key_der_b64 ?? "").trim();
14269
+ if (!groupAid || !privateKeyPem || !publicKeyDerB642) {
14270
+ throw new StateError("group identity is incomplete");
14271
+ }
14272
+ if (!this._v2KeyStore) {
14273
+ this._v2KeyStore = await V2KeyStore.open();
14274
+ }
14275
+ await this._v2KeyStore.saveGroupIdentity(
14276
+ this._v2StoreDeviceId(),
14277
+ groupAid,
14278
+ privateKeyPem,
14279
+ base64ToUint8(publicKeyDerB642)
14280
+ );
14281
+ }
14135
14282
  async _v2TrustedIKPubDer(aid) {
14136
14283
  const normalizedAid = String(aid ?? "").trim();
14137
14284
  if (!normalizedAid) throw new E2EEError("spk_aid_missing");
@@ -14461,6 +14608,7 @@ var _AUNClient = class _AUNClient {
14461
14608
  decrypted.push(plaintext);
14462
14609
  }
14463
14610
  }
14611
+ const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, "server_ack_seq");
14464
14612
  const serverAckSeq = Number(result.server_ack_seq ?? 0);
14465
14613
  if (ns && Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
14466
14614
  const contig = this._seqTracker.getContiguousSeq(ns);
@@ -14476,7 +14624,8 @@ var _AUNClient = class _AUNClient {
14476
14624
  await this._drainOrderedMessages(ns);
14477
14625
  this._saveSeqTrackerState();
14478
14626
  }
14479
- if (messages.length > 0 && contigAdvanced && ackSeq > 0) {
14627
+ const ackNeeded = messages.length > 0 && ackSeq > 0 && (contigAdvanced || hasServerAckSeq && ackSeq > serverAckSeq);
14628
+ if (ackNeeded) {
14480
14629
  this._safeAsync(this._ackV2(ackSeq).then(() => void 0));
14481
14630
  }
14482
14631
  }
@@ -14505,7 +14654,7 @@ var _AUNClient = class _AUNClient {
14505
14654
  seq = maxSeen;
14506
14655
  }
14507
14656
  }
14508
- const raw = await this.call("message.v2.ack", { up_to_seq: seq });
14657
+ const raw = await this._callRawV2Rpc("message.v2.ack", { up_to_seq: seq });
14509
14658
  const result = isJsonObject(raw) ? { ...raw } : { result: raw };
14510
14659
  let actualAckSeq = seq;
14511
14660
  if ("effective_ack_seq" in result) actualAckSeq = Number(result.effective_ack_seq ?? 0);
@@ -14775,7 +14924,7 @@ var _AUNClient = class _AUNClient {
14775
14924
  * @param afterSeq 从此 seq 之后开始拉取(0/省略 = 从当前 contiguous 开始)
14776
14925
  * @param limit 最多拉取条数
14777
14926
  */
14778
- async _pullGroupV2(groupId, afterSeq = 0, limit = 50) {
14927
+ async _pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
14779
14928
  if (!this._v2Session) {
14780
14929
  throw new StateError("V2 session not initialized (not connected?)");
14781
14930
  }
@@ -14783,15 +14932,18 @@ var _AUNClient = class _AUNClient {
14783
14932
  if (!gid) throw new ValidationError("group.pull requires group_id");
14784
14933
  const ns = `group:${gid}`;
14785
14934
  const decrypted = [];
14786
- let nextAfterSeq = afterSeq || this._seqTracker.getContiguousSeq(ns);
14935
+ const cursorParams = opts?.cursorParams ?? {};
14936
+ const ownsCursor = opts?.ownsCursor !== false;
14937
+ let nextAfterSeq = opts?.explicitAfterSeq ? afterSeq : afterSeq || this._seqTracker.getContiguousSeq(ns);
14787
14938
  let pageCount = 0;
14788
14939
  const maxPages = 100;
14789
14940
  while (pageCount < maxPages) {
14790
14941
  pageCount += 1;
14791
- const result = await this.call("group.v2.pull", {
14942
+ const result = await this._callRawV2Rpc("group.v2.pull", {
14792
14943
  group_id: gid,
14793
14944
  after_seq: nextAfterSeq,
14794
- limit
14945
+ limit,
14946
+ ...cursorParams
14795
14947
  });
14796
14948
  const messages = Array.isArray(result?.messages) ? result.messages : [];
14797
14949
  const seqs = messages.map((msg) => Number(msg.seq ?? 0)).filter((seq) => Number.isFinite(seq) && seq > 0);
@@ -14853,6 +15005,7 @@ var _AUNClient = class _AUNClient {
14853
15005
  decrypted.push(plaintext);
14854
15006
  }
14855
15007
  const cursor = isJsonObject(result.cursor) ? result.cursor : null;
15008
+ const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, "current_seq");
14856
15009
  const serverAckSeq = Number(cursor?.current_seq ?? 0);
14857
15010
  if (Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
14858
15011
  const contig = this._seqTracker.getContiguousSeq(ns);
@@ -14867,10 +15020,12 @@ var _AUNClient = class _AUNClient {
14867
15020
  await this._drainOrderedMessages(ns);
14868
15021
  this._saveSeqTrackerState();
14869
15022
  }
14870
- if (messages.length > 0 && contigAdvanced && ackSeq > 0) {
15023
+ const ackNeeded = messages.length > 0 && ackSeq > 0 && ownsCursor && (contigAdvanced || hasServerCursor && ackSeq > serverAckSeq);
15024
+ if (ackNeeded) {
14871
15025
  this._safeAsync(this._ackGroupV2(gid, ackSeq).then(() => void 0));
14872
15026
  }
14873
15027
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
15028
+ if (!ownsCursor) break;
14874
15029
  if (messages.length === 0 || nextAfter <= nextAfterSeq || result.has_more === false) break;
14875
15030
  nextAfterSeq = nextAfter;
14876
15031
  }
@@ -14879,6 +15034,28 @@ var _AUNClient = class _AUNClient {
14879
15034
  }
14880
15035
  return decrypted;
14881
15036
  }
15037
+ _groupCursorParams(params) {
15038
+ const cursorParams = {};
15039
+ for (const key of ["device_id", "slot_id", "device_name", "device_type"]) {
15040
+ const value = params[key];
15041
+ if (value !== void 0 && value !== null) cursorParams[key] = value;
15042
+ }
15043
+ return cursorParams;
15044
+ }
15045
+ _explicitGroupCursorParams(params) {
15046
+ const value = params._group_cursor_params;
15047
+ if (!isJsonObject(value)) return {};
15048
+ return { ...value };
15049
+ }
15050
+ _groupCursorTargetsCurrentInstance(params) {
15051
+ const deviceId = String(params.device_id ?? "").trim();
15052
+ const slotId = String(params.slot_id ?? "").trim();
15053
+ return (!deviceId || deviceId === (this._deviceId ?? "")) && (!slotId || slotId === (this._slotId ?? ""));
15054
+ }
15055
+ async _rawGroupAckMessages(params) {
15056
+ const p = { ...params };
15057
+ return await this._callRawV2Rpc("group.ack_messages", p);
15058
+ }
14882
15059
  /**
14883
15060
  * 确认 V2 群消息已消费。
14884
15061
  *
@@ -14896,7 +15073,7 @@ var _AUNClient = class _AUNClient {
14896
15073
  this._clientLog.warn(`ackGroupV2 clamp: group=${gid} up_to_seq=${seq} > max_seen=${maxSeen}, clamp`);
14897
15074
  seq = maxSeen;
14898
15075
  }
14899
- return this.call("group.v2.ack", { group_id: gid, up_to_seq: seq });
15076
+ return this._callRawV2Rpc("group.v2.ack", { group_id: gid, up_to_seq: seq });
14900
15077
  }
14901
15078
  // ── V2 thought(per-device wrap,服务端透传,不持久化)──────────
14902
15079
  /**
@@ -15655,8 +15832,8 @@ var _AUNClient = class _AUNClient {
15655
15832
  return;
15656
15833
  }
15657
15834
  let signature = "";
15658
- const identity = this._identity;
15659
- if (identity?.private_key_pem) {
15835
+ const currentAid = this._currentAid;
15836
+ if (currentAid?.privateKeyPem) {
15660
15837
  try {
15661
15838
  const signPayloadObj = {
15662
15839
  group_id: groupId,
@@ -15666,7 +15843,7 @@ var _AUNClient = class _AUNClient {
15666
15843
  };
15667
15844
  const signPayload = stableStringify(signPayloadObj);
15668
15845
  const signPayloadBytes = new TextEncoder().encode(signPayload);
15669
- const privKey = await importPrivateKeyEcdsa(identity.private_key_pem);
15846
+ const privKey = await importPrivateKeyEcdsa(currentAid.privateKeyPem);
15670
15847
  const sigBytes = await ecdsaSignDer(privKey, signPayloadBytes);
15671
15848
  signature = uint8ToBase64(sigBytes);
15672
15849
  } catch (sigExc) {
@@ -15840,7 +16017,7 @@ var _AUNClient = class _AUNClient {
15840
16017
  if (newContig > 0 && newContig !== contigBefore) {
15841
16018
  const maxSeen = this._seqTracker.getMaxSeenSeq(ns);
15842
16019
  const ackSeq = maxSeen > 0 ? Math.min(newContig, maxSeen) : newContig;
15843
- this.call("message.v2.ack", { up_to_seq: ackSeq }).catch((e) => this._clientLog.debug(`V2 P2P push-ack failed: ${e}`));
16020
+ this._callRawV2Rpc("message.v2.ack", { up_to_seq: ackSeq }).catch((e) => this._clientLog.debug(`V2 P2P push-ack failed: ${e}`));
15844
16021
  }
15845
16022
  this._clientLog.debug(
15846
16023
  `_onV2PushNotification: push \u5E26 payload \u89E3\u5BC6\u6210\u529F, contiguous_seq=${contigBefore}->${newContig} push_seq=${pushSeq}`
@@ -15998,6 +16175,334 @@ function _uuidV4() {
15998
16175
 
15999
16176
  // src/aid-store.ts
16000
16177
  init_crypto();
16178
+
16179
+ // src/register-flow.ts
16180
+ init_crypto();
16181
+ var _noopLog6 = { error: () => {
16182
+ }, warn: () => {
16183
+ }, info: () => {
16184
+ }, debug: () => {
16185
+ } };
16186
+ function _gatewayHttpUrl(gatewayUrl, path) {
16187
+ try {
16188
+ const parsed = new URL(gatewayUrl);
16189
+ const scheme = parsed.protocol === "wss:" ? "https:" : "http:";
16190
+ return `${scheme}//${parsed.host}${path}`;
16191
+ } catch {
16192
+ return gatewayUrl.replace(/^wss:/, "https:").replace(/^ws:/, "http:") + path;
16193
+ }
16194
+ }
16195
+ function _extractSpkiB64FromPem(certPem) {
16196
+ const der = new Uint8Array(pemToArrayBuffer(certPem));
16197
+ function readLen(data, offset) {
16198
+ const first = data[offset];
16199
+ if (!(first & 128)) return { value: first, lenBytes: 1 };
16200
+ const n = first & 127;
16201
+ let v = 0;
16202
+ for (let i = 0; i < n; i++) v = v << 8 | data[offset + 1 + i];
16203
+ return { value: v, lenBytes: 1 + n };
16204
+ }
16205
+ function skipTlv(data, offset) {
16206
+ const len = readLen(data, offset + 1);
16207
+ return offset + 1 + len.lenBytes + len.value;
16208
+ }
16209
+ const certLen = readLen(der, 1);
16210
+ const tbsStart = 1 + certLen.lenBytes;
16211
+ const tbsLen = readLen(der, tbsStart + 1);
16212
+ let pos = tbsStart + 1 + tbsLen.lenBytes;
16213
+ if (der[pos] === 160) pos = skipTlv(der, pos);
16214
+ pos = skipTlv(der, pos);
16215
+ pos = skipTlv(der, pos);
16216
+ pos = skipTlv(der, pos);
16217
+ pos = skipTlv(der, pos);
16218
+ pos = skipTlv(der, pos);
16219
+ const spkiLen = readLen(der, pos + 1);
16220
+ const spkiEnd = pos + 1 + spkiLen.lenBytes + spkiLen.value;
16221
+ return uint8ToBase64(der.slice(pos, spkiEnd));
16222
+ }
16223
+ var RegisterFlow = class {
16224
+ constructor(opts) {
16225
+ __publicField(this, "_keystore");
16226
+ __publicField(this, "_crypto");
16227
+ __publicField(this, "_logger");
16228
+ this._keystore = opts.keystore;
16229
+ this._crypto = opts.crypto;
16230
+ this._logger = opts.logger ?? _noopLog6;
16231
+ }
16232
+ async registerAid(gatewayUrl, aid) {
16233
+ const tStart = Date.now();
16234
+ AuthFlow._validateAidName(aid);
16235
+ this._logger.debug(`registerAid enter: aid=${aid}, gateway=${gatewayUrl}`);
16236
+ try {
16237
+ const existing = await this._keystore.loadIdentity(aid);
16238
+ if (existing && existing.private_key_pem && existing.public_key_der_b64) {
16239
+ this._logger.debug(`registerAid: local keypair exists, checking server: aid=${aid}`);
16240
+ const localPubB64 = String(existing.public_key_der_b64);
16241
+ const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
16242
+ if (serverCertPem) {
16243
+ const serverPubB64 = _extractSpkiB64FromPem(serverCertPem);
16244
+ if (serverPubB64 !== localPubB64) {
16245
+ throw new IdentityConflictError(
16246
+ `AID '${aid}' is registered by another party on server (public key mismatch). Choose a different name.`
16247
+ );
16248
+ }
16249
+ this._logger.info(`registerAid: idempotent return for already-registered AID: aid=${aid}`);
16250
+ if (!existing.cert) {
16251
+ existing.cert = serverCertPem;
16252
+ await this._persistIdentity(existing);
16253
+ }
16254
+ this._logger.debug(`registerAid exit (idempotent): elapsed=${Date.now() - tStart}ms aid=${aid}`);
16255
+ return {
16256
+ aid,
16257
+ cert: serverCertPem,
16258
+ private_key_pem: String(existing.private_key_pem),
16259
+ public_key_der_b64: localPubB64,
16260
+ curve: String(existing.curve ?? "P-256")
16261
+ };
16262
+ } else {
16263
+ this._logger.debug(`registerAid: server has no record, registering with existing keypair: aid=${aid}`);
16264
+ const created2 = await this._createAid(gatewayUrl, existing);
16265
+ const certPem2 = String(created2.cert ?? "");
16266
+ if (!certPem2) throw new AuthError(`registerAid: server response missing cert for ${aid}`);
16267
+ existing.cert = certPem2;
16268
+ this._assertCertMatchesLocalKeypair(existing);
16269
+ await this._persistIdentity(existing);
16270
+ this._logger.debug(`registerAid exit (recovered): elapsed=${Date.now() - tStart}ms aid=${aid}`);
16271
+ return {
16272
+ aid,
16273
+ cert: certPem2,
16274
+ private_key_pem: String(existing.private_key_pem),
16275
+ public_key_der_b64: localPubB64,
16276
+ curve: String(existing.curve ?? "P-256")
16277
+ };
16278
+ }
16279
+ }
16280
+ const recovered = await this._tryRecoverPendingRegistration(gatewayUrl, aid);
16281
+ if (recovered !== null) {
16282
+ this._logger.info(`registerAid recovered from pending: aid=${aid}`);
16283
+ return recovered;
16284
+ }
16285
+ const existingCert = await this._downloadRegisteredCert(gatewayUrl, aid);
16286
+ if (existingCert) {
16287
+ throw new IdentityConflictError(
16288
+ `AID '${aid}' is already registered on server. Choose a different name, or if you own the keypair use a recovery flow.`
16289
+ );
16290
+ }
16291
+ const identity = await this._crypto.generateIdentity();
16292
+ identity.aid = aid;
16293
+ const pendingStore = this._pendingStore();
16294
+ const pendingHandle = await pendingStore.pendingIdentityDir(aid);
16295
+ await pendingStore.savePendingKeyPair(pendingHandle, aid, identity);
16296
+ let created;
16297
+ try {
16298
+ created = await this._createAid(gatewayUrl, identity);
16299
+ } catch (e) {
16300
+ this._logger.warn(`registerAid RPC failed (pending kept for recovery): aid=${aid}, pending=${pendingHandle}, error=${e instanceof Error ? e.message : String(e)}`);
16301
+ throw e;
16302
+ }
16303
+ const certPem = String(created.cert ?? "");
16304
+ if (!certPem) throw new AuthError(`registerAid: server response missing cert for ${aid}`);
16305
+ identity.cert = certPem;
16306
+ this._assertCertMatchesLocalKeypair(identity);
16307
+ await pendingStore.savePendingCert(pendingHandle, certPem);
16308
+ try {
16309
+ await pendingStore.promotePendingIdentity(pendingHandle, aid);
16310
+ } catch (e) {
16311
+ throw new IdentityConflictError(
16312
+ `AID '${aid}' was created by another process during registration; pending record kept for cleanup.`
16313
+ );
16314
+ }
16315
+ await this._persistIdentity(identity);
16316
+ this._logger.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
16317
+ return {
16318
+ aid,
16319
+ cert: certPem,
16320
+ private_key_pem: String(identity.private_key_pem ?? ""),
16321
+ public_key_der_b64: String(identity.public_key_der_b64 ?? ""),
16322
+ curve: String(identity.curve ?? "P-256")
16323
+ };
16324
+ } catch (err) {
16325
+ this._logger.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
16326
+ throw err;
16327
+ }
16328
+ }
16329
+ _pendingStore() {
16330
+ const store = this._keystore;
16331
+ for (const name of [
16332
+ "pendingIdentityDir",
16333
+ "listPendingIdentityDirs",
16334
+ "savePendingKeyPair",
16335
+ "loadPendingKeyPair",
16336
+ "savePendingCert",
16337
+ "promotePendingIdentity"
16338
+ ]) {
16339
+ if (typeof store[name] !== "function") {
16340
+ throw new AuthError(`keystore does not support pending registration: ${name}`);
16341
+ }
16342
+ }
16343
+ return store;
16344
+ }
16345
+ async _tryRecoverPendingRegistration(gatewayUrl, aid) {
16346
+ const pendingStore = this._pendingStore();
16347
+ const handles = await pendingStore.listPendingIdentityDirs(aid);
16348
+ for (const handle of handles) {
16349
+ let keyPair;
16350
+ try {
16351
+ keyPair = await pendingStore.loadPendingKeyPair(handle, aid);
16352
+ } catch (exc) {
16353
+ this._logger.warn(`pending identity keypair load failed; keeping pending for retry: aid=${aid}, pending=${handle}, error=${exc instanceof Error ? exc.message : String(exc)}`);
16354
+ throw exc;
16355
+ }
16356
+ const privateKeyPem = String(keyPair?.private_key_pem ?? "");
16357
+ const publicKeyDerB642 = String(keyPair?.public_key_der_b64 ?? "");
16358
+ if (!privateKeyPem || !publicKeyDerB642) {
16359
+ await pendingStore.discardPendingIdentity?.(handle);
16360
+ continue;
16361
+ }
16362
+ const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
16363
+ if (!serverCertPem) {
16364
+ this._logger.info(`pending record found but server has no registration; cleaning up: ${handle}`);
16365
+ await pendingStore.discardPendingIdentity?.(handle);
16366
+ return null;
16367
+ }
16368
+ const serverPubB64 = _extractSpkiB64FromPem(serverCertPem);
16369
+ if (serverPubB64 !== publicKeyDerB642) {
16370
+ await pendingStore.discardPendingIdentity?.(handle);
16371
+ throw new IdentityConflictError(
16372
+ `AID '${aid}' has been registered by another party while local pending registration was incomplete; local pending key discarded.`
16373
+ );
16374
+ }
16375
+ const identity = {
16376
+ aid,
16377
+ cert: serverCertPem,
16378
+ private_key_pem: privateKeyPem,
16379
+ public_key_der_b64: publicKeyDerB642,
16380
+ curve: String(keyPair?.curve ?? "P-256")
16381
+ };
16382
+ await pendingStore.savePendingKeyPair(handle, aid, identity);
16383
+ await pendingStore.savePendingCert(handle, serverCertPem);
16384
+ try {
16385
+ await pendingStore.promotePendingIdentity(handle, aid);
16386
+ } catch (e) {
16387
+ throw new IdentityConflictError(
16388
+ `AID '${aid}' was created by another process during recovery; pending record kept for cleanup.`
16389
+ );
16390
+ }
16391
+ await this._persistIdentity(identity);
16392
+ return {
16393
+ aid,
16394
+ cert: serverCertPem,
16395
+ private_key_pem: privateKeyPem,
16396
+ public_key_der_b64: publicKeyDerB642,
16397
+ curve: String(keyPair?.curve ?? "P-256")
16398
+ };
16399
+ }
16400
+ return null;
16401
+ }
16402
+ async _downloadRegisteredCert(gatewayUrl, aid) {
16403
+ const certUrl = _gatewayHttpUrl(gatewayUrl, `/pki/cert/${aid}`);
16404
+ const controller = new AbortController();
16405
+ const timer = setTimeout(() => controller.abort(), 5e3);
16406
+ try {
16407
+ const resp = await fetch(certUrl, { signal: controller.signal });
16408
+ if (!resp.ok) return null;
16409
+ const certPem = await resp.text();
16410
+ if (!certPem || !certPem.includes("BEGIN CERTIFICATE")) return null;
16411
+ return certPem;
16412
+ } catch {
16413
+ return null;
16414
+ } finally {
16415
+ clearTimeout(timer);
16416
+ }
16417
+ }
16418
+ async _createAid(gatewayUrl, identity) {
16419
+ const response = await this._shortRpc(gatewayUrl, "auth.create_aid", {
16420
+ aid: identity.aid,
16421
+ public_key: identity.public_key_der_b64,
16422
+ curve: identity.curve ?? "P-256"
16423
+ });
16424
+ return { cert: response.cert };
16425
+ }
16426
+ _shortRpc(gatewayUrl, method, params) {
16427
+ return new Promise((resolve, reject) => {
16428
+ let ws;
16429
+ try {
16430
+ ws = new WebSocket(gatewayUrl);
16431
+ } catch {
16432
+ reject(new AuthError(`WebSocket \u8FDE\u63A5\u5931\u8D25: ${gatewayUrl}`));
16433
+ return;
16434
+ }
16435
+ let receivedChallenge = false;
16436
+ const timeout = globalThis.setTimeout(() => {
16437
+ try {
16438
+ ws.close();
16439
+ } catch {
16440
+ }
16441
+ reject(new AuthError(`shortRpc \u8D85\u65F6: ${method}`));
16442
+ }, 1e4);
16443
+ ws.onerror = () => {
16444
+ globalThis.clearTimeout(timeout);
16445
+ reject(new AuthError(`WebSocket \u8FDE\u63A5\u9519\u8BEF: ${gatewayUrl}`));
16446
+ };
16447
+ ws.onmessage = (event) => {
16448
+ try {
16449
+ const msg = typeof event.data === "string" ? JSON.parse(event.data) : event.data;
16450
+ if (!receivedChallenge) {
16451
+ receivedChallenge = true;
16452
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id: `pre-${method}`, method, params }));
16453
+ return;
16454
+ }
16455
+ globalThis.clearTimeout(timeout);
16456
+ try {
16457
+ ws.close();
16458
+ } catch {
16459
+ }
16460
+ if (msg.error) {
16461
+ reject(mapRemoteError(msg.error));
16462
+ return;
16463
+ }
16464
+ const result = msg.result;
16465
+ if (!isJsonObject(result)) {
16466
+ reject(new ValidationError(`invalid pre-auth response for ${method}`));
16467
+ return;
16468
+ }
16469
+ if (result.success === false) {
16470
+ reject(new AuthError(String(result.error ?? `${method} failed`)));
16471
+ return;
16472
+ }
16473
+ resolve(result);
16474
+ } catch (e) {
16475
+ globalThis.clearTimeout(timeout);
16476
+ try {
16477
+ ws.close();
16478
+ } catch {
16479
+ }
16480
+ reject(e instanceof Error ? e : new AuthError(String(e)));
16481
+ }
16482
+ };
16483
+ });
16484
+ }
16485
+ _assertCertMatchesLocalKeypair(identity) {
16486
+ const aid = identity.aid ?? "?";
16487
+ const certPem = identity.cert;
16488
+ const localPubB64 = identity.public_key_der_b64;
16489
+ if (!certPem || !localPubB64) {
16490
+ throw new AuthError(`identity for aid ${aid} missing cert or public key`);
16491
+ }
16492
+ const certPubB64 = _extractSpkiB64FromPem(certPem);
16493
+ if (certPubB64 !== localPubB64) {
16494
+ throw new AuthError(`local certificate public key does not match local keypair for aid ${aid}`);
16495
+ }
16496
+ }
16497
+ async _persistIdentity(identity) {
16498
+ const aid = String(identity.aid ?? "");
16499
+ if (!aid) return;
16500
+ const certPem = String(identity.cert ?? "");
16501
+ if (certPem) await this._keystore.saveCert(aid, certPem);
16502
+ }
16503
+ };
16504
+
16505
+ // src/aid-store.ts
16001
16506
  function _derReadLength(data, offset) {
16002
16507
  if (offset >= data.length) return null;
16003
16508
  const first = data[offset];
@@ -16223,6 +16728,7 @@ var AIDStore = class {
16223
16728
  __publicField(this, "_encryptionSeed");
16224
16729
  __publicField(this, "_keystore");
16225
16730
  __publicField(this, "_auth");
16731
+ __publicField(this, "_registerFlow");
16226
16732
  __publicField(this, "_crypto");
16227
16733
  __publicField(this, "_discovery");
16228
16734
  __publicField(this, "_verifySsl");
@@ -16240,13 +16746,18 @@ var AIDStore = class {
16240
16746
  this._crypto = new CryptoProvider();
16241
16747
  this._discovery = new GatewayDiscovery();
16242
16748
  this._auth = new AuthFlow({
16243
- keystore: this._keystore,
16749
+ tokenStore: this._keystore,
16244
16750
  crypto: this._crypto,
16245
16751
  deviceId: this.deviceId,
16246
16752
  slotId: this.slotId,
16247
16753
  rootCaPem: opts.rootCaPem ?? null,
16248
16754
  verifySsl: this._verifySsl
16249
16755
  });
16756
+ this._registerFlow = new RegisterFlow({
16757
+ keystore: this._keystore,
16758
+ crypto: this._crypto,
16759
+ verifySsl: this._verifySsl
16760
+ });
16250
16761
  }
16251
16762
  close() {
16252
16763
  const close = this._keystore.close;
@@ -16365,7 +16876,17 @@ var AIDStore = class {
16365
16876
  try {
16366
16877
  validateRegisterAidName(target);
16367
16878
  const gatewayUrl = await this._resolveGateway(target);
16368
- await this._auth.registerAid(gatewayUrl, target);
16879
+ const result = await this._registerFlow.registerAid(gatewayUrl, target);
16880
+ if (result.cert) {
16881
+ await this._keystore.saveCert(target, result.cert);
16882
+ }
16883
+ if (result.private_key_pem || result.public_key_der_b64) {
16884
+ await this._keystore.saveKeyPair(target, {
16885
+ private_key_pem: result.private_key_pem,
16886
+ public_key_der_b64: result.public_key_der_b64,
16887
+ curve: result.curve
16888
+ });
16889
+ }
16369
16890
  return resultOk({ registered: true });
16370
16891
  } catch (exc) {
16371
16892
  if (exc instanceof IdentityConflictError) {
@@ -16568,18 +17089,18 @@ var AIDStore = class {
16568
17089
  return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
16569
17090
  }
16570
17091
  try {
16571
- const identity = await this._auth.loadIdentityOrNone(target);
16572
- if (!identity?.cert || !identity.private_key_pem) {
17092
+ const aidObj = loaded.data.aid;
17093
+ if (!aidObj.certPem || !aidObj.privateKeyPem) {
16573
17094
  return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
16574
17095
  }
16575
17096
  const gatewayUrl = await this._resolveGateway(target);
16576
17097
  const clientNonce = this._crypto.newClientNonce();
16577
17098
  const phase1 = await this._auth._shortRpc(gatewayUrl, "auth.aid_login1", {
16578
17099
  aid: target,
16579
- cert: identity.cert,
17100
+ cert: aidObj.certPem,
16580
17101
  client_nonce: clientNonce
16581
17102
  });
16582
- const [signature, clientTime] = await this._crypto.signLoginNonce(identity.private_key_pem, String(phase1.nonce ?? ""));
17103
+ const [signature, clientTime] = await this._crypto.signLoginNonce(aidObj.privateKeyPem, String(phase1.nonce ?? ""));
16583
17104
  const response = await this._auth._shortRpc(gatewayUrl, "auth.renew_cert", {
16584
17105
  aid: target,
16585
17106
  request_id: String(phase1.request_id ?? ""),
@@ -16608,8 +17129,8 @@ var AIDStore = class {
16608
17129
  return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
16609
17130
  }
16610
17131
  try {
16611
- const identity = await this._auth.loadIdentityOrNone(target);
16612
- if (!identity?.cert || !identity.private_key_pem) {
17132
+ const oldAid = loaded.data.aid;
17133
+ if (!oldAid.certPem || !oldAid.privateKeyPem) {
16613
17134
  return resultErr(PRIVATE_KEY_REQUIRED, `private key required for aid: ${target}`);
16614
17135
  }
16615
17136
  const gatewayUrl = await this._resolveGateway(target);
@@ -16617,7 +17138,7 @@ var AIDStore = class {
16617
17138
  const clientNonce = this._crypto.newClientNonce();
16618
17139
  const phase1 = await this._auth._shortRpc(gatewayUrl, "auth.aid_login1", {
16619
17140
  aid: target,
16620
- cert: identity.cert,
17141
+ cert: oldAid.certPem,
16621
17142
  client_nonce: clientNonce
16622
17143
  });
16623
17144
  const signPayload = `${String(phase1.nonce ?? "")}${newIdentity.public_key_der_b64}`;
@@ -16798,6 +17319,7 @@ export {
16798
17319
  ROOT_CA_PEM,
16799
17320
  RPCTransport,
16800
17321
  RateLimitError,
17322
+ RegisterFlow,
16801
17323
  STATE_PREFIX,
16802
17324
  SeedMigrationError,
16803
17325
  SerializationError,