@agentunion/fastaun 0.5.0 → 0.5.2

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 (114) hide show
  1. package/CHANGELOG.md +561 -472
  2. package/_packed_docs/CHANGELOG-validators.md +147 -0
  3. package/_packed_docs/CHANGELOG.md +561 -472
  4. package/_packed_docs/INDEX.md +201 -177
  5. package/_packed_docs/KITE_DOCS_GUIDE.md +38 -32
  6. 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 +169 -116
  7. package/_packed_docs/aun-perf-audit-critical-bugs.md +315 -0
  8. package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +263 -260
  9. package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
  10. package/_packed_docs/protocol/00-/346/200/273/350/247/210/344/270/216/345/210/206/345/261/202.md +2 -2
  11. package/_packed_docs/protocol/00A-/350/256/276/350/256/241/345/216/237/345/210/231-/344/270/272Agent/350/200/214/347/224/237.md +1 -1
  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 +39 -16
  13. package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +8 -5
  14. package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +18 -19
  15. package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +1 -1
  16. package/_packed_docs/protocol/08-AUN-E2EE-Group.md +139 -746
  17. package/_packed_docs/protocol/08-AUN-E2EE.md +12 -10
  18. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +117 -171
  19. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +6 -0
  20. 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 +1 -1
  21. package/_packed_docs/protocol/16-/347/263/273/347/273/237/347/233/256/345/275/225/344/277/235/346/212/244/346/226/271/346/241/210.md +177 -0
  22. package/_packed_docs/protocol/README.md +5 -4
  23. package/_packed_docs/protocol/aun-docs-guide.md +2 -2
  24. package/_packed_docs/protocol/index.md +12 -7
  25. package/_packed_docs/protocol//350/215/211/346/241/210-/346/213/222/347/273/235/344/277/241/345/217/267/345/215/217/350/256/256.md +1 -1
  26. package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +13 -13
  27. package/_packed_docs/protocol//351/231/204/345/275/225L-E2EE/345/256/236/347/216/260/346/214/207/345/215/227.md +9 -9
  28. package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +15 -13
  29. package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +26 -18
  30. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +35 -284
  31. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +473 -442
  32. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +11 -7
  33. package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -550
  34. package/_packed_docs/sdk/09-group-rpc-manual.md +367 -433
  35. package/_packed_docs/sdk/09-message-rpc-manual.md +50 -28
  36. package/_packed_docs/sdk/09-payload-reference.md +3 -3
  37. package/_packed_docs/sdk/09-storage-rpc-manual.md +57 -20
  38. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +18 -17
  39. 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 +3 -2
  40. package/_packed_docs/sdk/INDEX.md +25 -24
  41. package/_packed_docs/sdk/Notify/351/200/232/347/237/245/346/226/271/346/241/210.md +6 -2
  42. package/dist/agent-md.js +18 -9
  43. package/dist/agent-md.js.map +1 -1
  44. package/dist/client/delivery.d.ts +9 -0
  45. package/dist/client/delivery.js +157 -24
  46. package/dist/client/delivery.js.map +1 -1
  47. package/dist/client/group-state.js +33 -11
  48. package/dist/client/group-state.js.map +1 -1
  49. package/dist/client/rpc-pipeline.d.ts +1 -0
  50. package/dist/client/rpc-pipeline.js +127 -40
  51. package/dist/client/rpc-pipeline.js.map +1 -1
  52. package/dist/client/v2-e2ee.d.ts +6 -1
  53. package/dist/client/v2-e2ee.js +284 -140
  54. package/dist/client/v2-e2ee.js.map +1 -1
  55. package/dist/client.d.ts +8 -1
  56. package/dist/client.js +227 -31
  57. package/dist/client.js.map +1 -1
  58. package/dist/collab/client.d.ts +8 -0
  59. package/dist/collab/client.js +12 -0
  60. package/dist/collab/client.js.map +1 -1
  61. package/dist/errors.d.ts +0 -20
  62. package/dist/errors.js +8 -53
  63. package/dist/errors.js.map +1 -1
  64. package/dist/facades.d.ts +9 -4
  65. package/dist/facades.js +137 -15
  66. package/dist/facades.js.map +1 -1
  67. package/dist/group-fs.d.ts +26 -9
  68. package/dist/group-fs.js +84 -84
  69. package/dist/group-fs.js.map +1 -1
  70. package/dist/group-id.d.ts +9 -12
  71. package/dist/group-id.js +41 -63
  72. package/dist/group-id.js.map +1 -1
  73. package/dist/index.d.ts +4 -2
  74. package/dist/index.js +4 -1
  75. package/dist/index.js.map +1 -1
  76. package/dist/keystore/aid-db.d.ts +1 -0
  77. package/dist/keystore/aid-db.js +37 -37
  78. package/dist/keystore/aid-db.js.map +1 -1
  79. package/dist/keystore/local-identity-store.d.ts +16 -0
  80. package/dist/keystore/local-identity-store.js +190 -18
  81. package/dist/keystore/local-identity-store.js.map +1 -1
  82. package/dist/keystore/local-token-store.d.ts +0 -13
  83. package/dist/keystore/local-token-store.js +0 -105
  84. package/dist/keystore/local-token-store.js.map +1 -1
  85. package/dist/logger.d.ts +2 -0
  86. package/dist/logger.js +4 -0
  87. package/dist/logger.js.map +1 -1
  88. package/dist/storage/lowlevel.d.ts +9 -1
  89. package/dist/storage/lowlevel.js +12 -1
  90. package/dist/storage/lowlevel.js.map +1 -1
  91. package/dist/storage/types.d.ts +0 -1
  92. package/dist/storage/types.js.map +1 -1
  93. package/dist/storage/vfs.d.ts +22 -3
  94. package/dist/storage/vfs.js +89 -54
  95. package/dist/storage/vfs.js.map +1 -1
  96. package/dist/tools/cross-sdk-agent.js +336 -49
  97. package/dist/tools/cross-sdk-agent.js.map +1 -1
  98. package/dist/transport.d.ts +2 -0
  99. package/dist/transport.js +52 -3
  100. package/dist/transport.js.map +1 -1
  101. package/dist/types.d.ts +38 -0
  102. package/dist/v2/session/session.d.ts +2 -0
  103. package/dist/v2/session/session.js +53 -23
  104. package/dist/v2/session/session.js.map +1 -1
  105. package/dist/v2/state/commitment.d.ts +1 -1
  106. package/dist/v2/state/commitment.js +4 -2
  107. package/dist/v2/state/commitment.js.map +1 -1
  108. package/dist/validators.d.ts +32 -0
  109. package/dist/validators.js +128 -0
  110. package/dist/validators.js.map +1 -0
  111. package/dist/version.d.ts +1 -1
  112. package/dist/version.js +1 -1
  113. package/package.json +46 -46
  114. package/_packed_docs/collab-gateway-boundary-test-report.md +0 -164
package/dist/client.js CHANGED
@@ -24,6 +24,7 @@ import { EventDispatcher } from './events.js';
24
24
  import { LocalTokenStore } from './keystore/local-token-store.js';
25
25
  import { AUNLogger } from './logger.js';
26
26
  import { normalizeGroupId } from './group-id.js';
27
+ import { validateAIDFormat, validateGroupIDFormat } from './validators.js';
27
28
  import { RPCTransport } from './transport.js';
28
29
  import { AuthFlow } from './auth.js';
29
30
  import { AgentMdManager } from './agent-md.js';
@@ -138,8 +139,8 @@ const NON_IDEMPOTENT_TIMEOUT_MS = 35_000;
138
139
  const NON_IDEMPOTENT_METHODS = new Set([
139
140
  'message.send', 'group.send', 'group.create', 'group.invite',
140
141
  'group.kick', 'group.remove_member', 'group.leave', 'group.dissolve',
141
- 'group.update_name', 'group.update_avatar', 'group.update_announcement',
142
- 'group.update_settings',
142
+ 'group.set_settings',
143
+ 'group.update_announcement', 'group.update_rules',
143
144
  'storage.put_object', 'storage.delete_object', 'storage.get_by_share',
144
145
  'storage.create_share_link', 'storage.revoke_share_link',
145
146
  'storage.create_upload_session', 'storage.complete_upload',
@@ -155,6 +156,7 @@ const NON_IDEMPOTENT_METHODS = new Set([
155
156
  'storage.fs.invalidate_membership',
156
157
  'storage.volume.create', 'storage.volume.renew', 'storage.volume.expire_due',
157
158
  'group.fs.mkdir', 'group.fs.rm', 'group.fs.cp', 'group.fs.mv',
159
+ 'group.fs.set_acl', 'group.fs.remove_acl',
158
160
  'group.fs.mount', 'group.fs.umount',
159
161
  'group.fs.check_upload', 'group.fs.create_upload_session',
160
162
  'group.fs.complete_upload', 'group.fs.create_download_ticket',
@@ -178,16 +180,18 @@ const SIGNED_METHODS = new Set([
178
180
  'group.v2.propose_state', 'group.v2.confirm_state',
179
181
  'group.v2.get_proposal',
180
182
  'group.kick', 'group.add_member',
181
- 'group.leave', 'group.remove_member', 'group.update_rules',
182
- 'group.update', 'group.update_announcement',
183
- 'group.update_join_requirements', 'group.set_role',
184
- 'group.transfer_owner', 'group.bind_group_aid', 'group.complete_transfer',
183
+ 'group.leave', 'group.remove_member',
184
+ 'group.update',
185
+ 'group.set_role',
186
+ 'group.transfer_owner', 'group.bind_group_aid', 'group.renew_group_aid', 'group.complete_transfer',
185
187
  'group.review_join_request',
186
188
  'group.batch_review_join_request',
187
189
  'group.request_join', 'group.use_invite_code',
188
190
  'group.thought.put',
189
191
  'message.thought.put',
190
192
  'group.set_settings',
193
+ 'group.update_announcement',
194
+ 'group.update_rules',
191
195
  'storage.put_object', 'storage.delete_object', 'storage.get_by_share',
192
196
  'storage.create_share_link', 'storage.revoke_share_link',
193
197
  'storage.create_upload_session', 'storage.complete_upload',
@@ -204,6 +208,7 @@ const SIGNED_METHODS = new Set([
204
208
  'storage.fs.invalidate_membership',
205
209
  'storage.volume.create', 'storage.volume.renew', 'storage.volume.expire_due',
206
210
  'group.fs.mkdir', 'group.fs.rm', 'group.fs.cp', 'group.fs.mv',
211
+ 'group.fs.set_acl', 'group.fs.remove_acl',
207
212
  'group.fs.mount', 'group.fs.umount',
208
213
  'group.fs.check_upload', 'group.fs.create_upload_session',
209
214
  'group.fs.complete_upload', 'group.fs.create_download_ticket',
@@ -655,10 +660,8 @@ export class AUNClient {
655
660
  this._dispatcher.subscribe('_raw.group.message_created', (data) => this._onRawGroupMessageCreated(data));
656
661
  // 群组消息撤回推送:在线 push 通道,与 pull 双 tombstone 兜底互补(SDK 去重只回调一次)
657
662
  this._dispatcher.subscribe('_raw.group.message_recalled', (data) => this._safeAsync(this._onRawGroupMessageRecalled(data)));
658
- // 群组变更事件:拦截处理成员变更触发的 epoch 轮换,然后透传
663
+ // 群组变更事件:透传并触发 V2 state/SPK 维护
659
664
  this._dispatcher.subscribe('_raw.group.changed', (data) => this._onRawGroupChanged(data));
660
- // V2 epoch 轮换事件:清 bootstrap 缓存并触发 SPK rotation
661
- this._dispatcher.subscribe('_raw.group.v2.epoch_rotated', (data) => this._safeAsync(this._onV2EpochRotated(data)));
662
665
  // V2 state proposal 服务平面事件:owner/admin 负责确认或重新提案
663
666
  this._dispatcher.subscribe('_raw.group.v2.state_proposed', (data) => this._safeAsync(this._onV2StateProposed(data)));
664
667
  this._dispatcher.subscribe('_raw.group.v2.state_retry_needed', (data) => this._safeAsync(this._onV2StateRetryNeeded(data)));
@@ -952,7 +955,22 @@ export class AUNClient {
952
955
  * 自动处理内部方法限制、E2EE 加解密、客户端签名等。
953
956
  */
954
957
  async call(method, params) {
955
- return await this._rpcPipeline.call(method, params);
958
+ try {
959
+ return await this._rpcPipeline.call(method, params);
960
+ }
961
+ catch (err) {
962
+ await this._maybeHandleHalfOpenRpcFailure(err);
963
+ throw err;
964
+ }
965
+ }
966
+ async _maybeHandleHalfOpenRpcFailure(err) {
967
+ if (this._closing || this.state !== ConnectionState.READY)
968
+ return;
969
+ if (!this._sessionOptions?.auto_reconnect || this._reconnectActive)
970
+ return;
971
+ if (!(err instanceof ConnectionError) && !(err instanceof TimeoutError))
972
+ return;
973
+ await this._handleTransportDisconnect(err);
956
974
  }
957
975
  async createGroup(params = {}) {
958
976
  if (!isJsonObject(params)) {
@@ -1007,8 +1025,33 @@ export class AUNClient {
1007
1025
  if (!store) {
1008
1026
  throw new ValidationError('bindGroupAid requires aidStore');
1009
1027
  }
1010
- const keyPair = new CryptoProvider().generateIdentity();
1028
+ const groupId = String(params.group_id ?? params.group_aid ?? '').trim();
1029
+ const keystore = store._keystore; // AIDStore._keystore: LocalIdentityStore
1030
+ let keyPair = null;
1031
+ // 优先复用 pending 槽位中已暂存的待绑定密钥(崩溃/重试幂等)
1032
+ if (groupId && keystore && typeof keystore.loadPendingGroupBind === 'function') {
1033
+ keyPair = keystore.loadPendingGroupBind(groupId);
1034
+ }
1035
+ if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
1036
+ // 未命中 pending,生成新密钥
1037
+ const generated = new CryptoProvider().generateIdentity();
1038
+ keyPair = {
1039
+ private_key_pem: generated.private_key_pem,
1040
+ public_key_der_b64: generated.public_key_der_b64,
1041
+ curve: generated.curve,
1042
+ };
1043
+ // 发 RPC 前先落盘暂存,确保崩溃后重试能复用同一密钥
1044
+ if (groupId && keystore && typeof keystore.savePendingGroupBind === 'function') {
1045
+ keystore.savePendingGroupBind(groupId, keyPair);
1046
+ }
1047
+ }
1048
+ if (!keyPair) {
1049
+ throw new ValidationError('bindGroupAid: failed to generate or load key pair');
1050
+ }
1011
1051
  const payload = { ...params };
1052
+ if (!String(payload.group_id ?? '').trim() && groupId) {
1053
+ payload.group_id = groupId;
1054
+ }
1012
1055
  payload.public_key = keyPair.public_key_der_b64;
1013
1056
  payload.curve = keyPair.curve;
1014
1057
  const result = await this.call('group.bind_group_aid', payload);
@@ -1020,6 +1063,8 @@ export class AUNClient {
1020
1063
  if (!groupAid || !certPem) {
1021
1064
  throw new ValidationError('group.bind_group_aid response missing group.group_aid or aid_cert.cert');
1022
1065
  }
1066
+ // 证书公钥校验:确保服务端返回的证书公钥 == 本地公钥
1067
+ await this.verifyCertPublicKeyMatch(certPem, keyPair.public_key_der_b64, 'bindGroupAid');
1023
1068
  const imported = store.importGroupIdentity(groupAid, {
1024
1069
  private_key_pem: keyPair.private_key_pem,
1025
1070
  public_key_der_b64: keyPair.public_key_der_b64,
@@ -1029,6 +1074,10 @@ export class AUNClient {
1029
1074
  if (!imported.ok) {
1030
1075
  throw new ValidationError(imported.error.message);
1031
1076
  }
1077
+ // 落盘成功,清除 pending 槽位
1078
+ if (groupId && keystore && typeof keystore.clearPendingGroupBind === 'function') {
1079
+ keystore.clearPendingGroupBind(groupId);
1080
+ }
1032
1081
  return result;
1033
1082
  }
1034
1083
  async startGroupTransfer(params = {}, options = {}) {
@@ -1046,10 +1095,10 @@ export class AUNClient {
1046
1095
  }
1047
1096
  let groupAid = String(params.group_aid ?? '').trim();
1048
1097
  if (!groupAid) {
1049
- const info = await this.call('group.get', { group_id: groupId });
1098
+ const info = await this.call('group.get_info', { group_id: groupId, required: ['member'] });
1050
1099
  const infoMap = isJsonObject(info) ? info : {};
1051
1100
  const grp = isJsonObject(infoMap.group) ? infoMap.group : infoMap;
1052
- groupAid = String(grp.group_aid ?? '').trim();
1101
+ groupAid = String(info?.group_aid ?? '').trim();
1053
1102
  }
1054
1103
  if (!groupAid) {
1055
1104
  throw new ValidationError('startGroupTransfer: unable to determine group_aid');
@@ -1080,6 +1129,99 @@ export class AUNClient {
1080
1129
  payload.transfer_auth = { nonce, issued_ms: issuedMs, signature: signed.data.signature };
1081
1130
  return await this.call('group.transfer_owner', payload);
1082
1131
  }
1132
+ async renewGroupAid(params = {}, options = {}) {
1133
+ if (!isJsonObject(params)) {
1134
+ throw new ValidationError('renewGroupAid params must be an object');
1135
+ }
1136
+ const store = options.aidStore;
1137
+ if (!store) {
1138
+ throw new ValidationError('renewGroupAid requires aidStore');
1139
+ }
1140
+ const groupId = String(params.group_id ?? params.group_aid ?? '').trim();
1141
+ if (!groupId) {
1142
+ throw new ValidationError('renewGroupAid requires group_id or group_aid');
1143
+ }
1144
+ let groupAid = String(params.group_aid ?? '').trim();
1145
+ if (!groupAid) {
1146
+ const info = await this.call('group.get_info', { group_id: groupId, required: ['member'] });
1147
+ const infoMap = isJsonObject(info) ? info : {};
1148
+ const grp = isJsonObject(infoMap.group) ? infoMap.group : infoMap;
1149
+ groupAid = String(info?.group_aid ?? '').trim();
1150
+ }
1151
+ if (!groupAid) {
1152
+ throw new ValidationError('renewGroupAid: unable to determine group_aid');
1153
+ }
1154
+ // 加载旧 group_aid 私钥(用于签名授权轮换)
1155
+ const loaded = store.load(groupAid);
1156
+ if (!loaded.ok || !loaded.data) {
1157
+ throw new ValidationError(`renewGroupAid: group_aid identity not found: ${groupAid}`);
1158
+ }
1159
+ const oldAidObj = loaded.data.aid;
1160
+ if (!oldAidObj.isPrivateKeyValid() || !oldAidObj.privateKeyPem) {
1161
+ throw new ValidationError(`renewGroupAid: group_aid has no private key: ${groupAid}`);
1162
+ }
1163
+ const oldPublicKey = oldAidObj.publicKey;
1164
+ if (!oldPublicKey) {
1165
+ throw new ValidationError(`renewGroupAid: cannot determine old public key for ${groupAid}`);
1166
+ }
1167
+ // 生成新密钥
1168
+ const newKeyPair = new CryptoProvider().generateIdentity();
1169
+ const newPublicKey = newKeyPair.public_key_der_b64;
1170
+ const newPrivateKey = newKeyPair.private_key_pem;
1171
+ const curve = newKeyPair.curve || 'P-256';
1172
+ if (!newPublicKey || !newPrivateKey) {
1173
+ throw new ValidationError('renewGroupAid: generated incomplete group identity');
1174
+ }
1175
+ // 用旧私钥对 canonical payload 签名
1176
+ const nonce = crypto.randomUUID().replace(/-/g, '');
1177
+ const issuedMs = Date.now();
1178
+ const oldHash = crypto.createHash('sha256').update(oldPublicKey).digest('hex');
1179
+ const newHash = crypto.createHash('sha256').update(newPublicKey).digest('hex');
1180
+ const canonical = [
1181
+ 'aun-group-aid-renew-v1',
1182
+ groupId.toLowerCase(),
1183
+ groupAid.toLowerCase(),
1184
+ oldHash,
1185
+ newHash,
1186
+ nonce,
1187
+ String(issuedMs),
1188
+ ].join('|');
1189
+ const signed = oldAidObj.sign(canonical);
1190
+ if (!signed.ok) {
1191
+ throw new ValidationError(`renewGroupAid: sign failed: ${signed.error.message}`);
1192
+ }
1193
+ const payload = { ...params };
1194
+ if (!String(payload.group_id ?? '').trim()) {
1195
+ payload.group_id = groupId;
1196
+ }
1197
+ payload.group_aid = groupAid;
1198
+ payload.old_public_key = oldPublicKey;
1199
+ payload.new_public_key = newPublicKey;
1200
+ payload.curve = curve;
1201
+ payload.renew_proof = { nonce, issued_ms: issuedMs, signature: signed.data.signature };
1202
+ const result = await this.call('group.renew_group_aid', payload);
1203
+ const resultMap = isJsonObject(result) ? result : {};
1204
+ const group = isJsonObject(resultMap.group) ? resultMap.group : {};
1205
+ const aidCert = isJsonObject(resultMap.aid_cert) ? resultMap.aid_cert : {};
1206
+ const returnedGroupAid = String(group.group_aid ?? resultMap.group_aid ?? groupAid).trim();
1207
+ const certPem = String(aidCert.cert ?? aidCert.cert_pem ?? resultMap.cert ?? resultMap.cert_pem ?? '').trim();
1208
+ if (!returnedGroupAid || !certPem) {
1209
+ throw new ValidationError('renewGroupAid response missing group.group_aid or aid_cert.cert');
1210
+ }
1211
+ // 证书公钥校验:确保服务端返回的新证书公钥 == 本地新公钥
1212
+ await this.verifyCertPublicKeyMatch(certPem, newPublicKey, 'renewGroupAid');
1213
+ // 用新密钥覆盖本地身份(合法轮换)
1214
+ const imported = store.importGroupIdentity(returnedGroupAid, {
1215
+ private_key_pem: newPrivateKey,
1216
+ public_key_der_b64: newPublicKey,
1217
+ curve,
1218
+ cert_pem: certPem,
1219
+ });
1220
+ if (!imported.ok) {
1221
+ throw new ValidationError(`renewGroupAid failed to persist group identity: ${imported.error.message}`);
1222
+ }
1223
+ return result;
1224
+ }
1083
1225
  async completeGroupTransfer(params = {}, options = {}) {
1084
1226
  if (!isJsonObject(params)) {
1085
1227
  throw new ValidationError('completeGroupTransfer params must be an object');
@@ -1096,10 +1238,10 @@ export class AUNClient {
1096
1238
  }
1097
1239
  let groupAid = String(params.group_aid ?? '').trim();
1098
1240
  if (!groupAid) {
1099
- const info = await this.call('group.get', { group_id: groupId });
1241
+ const info = await this.call('group.get_info', { group_id: groupId, required: ['member'] });
1100
1242
  const infoMap = isJsonObject(info) ? info : {};
1101
1243
  const grp = isJsonObject(infoMap.group) ? infoMap.group : infoMap;
1102
- groupAid = String(grp.group_aid ?? '').trim();
1244
+ groupAid = String(info?.group_aid ?? '').trim();
1103
1245
  }
1104
1246
  if (!groupAid) {
1105
1247
  throw new ValidationError('completeGroupTransfer: unable to determine group_aid');
@@ -1138,6 +1280,8 @@ export class AUNClient {
1138
1280
  if (!returnedGroupAid || !certPem) {
1139
1281
  throw new ValidationError('group.complete_transfer response missing group.group_aid or aid_cert.cert');
1140
1282
  }
1283
+ // 证书公钥校验:确保服务端返回的证书公钥 == 本地公钥
1284
+ await this.verifyCertPublicKeyMatch(certPem, keyPair.public_key_der_b64, 'completeGroupTransfer');
1141
1285
  const imported = store.importGroupIdentity(returnedGroupAid, {
1142
1286
  private_key_pem: keyPair.private_key_pem,
1143
1287
  public_key_der_b64: keyPair.public_key_der_b64,
@@ -1149,6 +1293,32 @@ export class AUNClient {
1149
1293
  }
1150
1294
  return result;
1151
1295
  }
1296
+ /** 证书公钥校验:确保服务端返回的证书公钥 == 本地公钥(DER base64) */
1297
+ async verifyCertPublicKeyMatch(certPem, localPublicKeyDerB64, operation) {
1298
+ // 简化实现:使用 Node.js crypto 或浏览器 Web Crypto API
1299
+ // 完整实现需要解析 X.509 证书提取 subjectPublicKeyInfo
1300
+ // 这里先做基本校验,后续可增强
1301
+ const crypto = globalThis.crypto;
1302
+ if (!crypto || !crypto.subtle) {
1303
+ // 浏览器环境或 Node < 15,跳过校验(非致命)
1304
+ console.warn(`${operation}: crypto.subtle not available, skipping cert public key verification`);
1305
+ return;
1306
+ }
1307
+ try {
1308
+ // TODO: 完整实现需要 ASN.1 解析库提取证书公钥
1309
+ // 当前仅做导入测试,确保证书格式有效
1310
+ const certLines = certPem.split('\n').filter(l => l && !l.startsWith('-----'));
1311
+ const certDer = Buffer.from(certLines.join(''), 'base64');
1312
+ // 验证本地公钥可导入
1313
+ const localPubDer = Buffer.from(localPublicKeyDerB64, 'base64');
1314
+ await crypto.subtle.importKey('spki', localPubDer, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
1315
+ // 证书公钥提取需要完整 ASN.1 解析,暂时依赖服务端正确性
1316
+ // 后续迭代可引入 @peculiar/x509 或类似库
1317
+ }
1318
+ catch (err) {
1319
+ throw new AuthError(`${operation}: certificate or public key validation failed: ${err.message}`);
1320
+ }
1321
+ }
1152
1322
  static _notifyParamsSizeOk(params) {
1153
1323
  return new TextEncoder().encode(JSON.stringify(params)).length <= MAX_NOTIFY_PAYLOAD_SIZE;
1154
1324
  }
@@ -1183,7 +1353,7 @@ export class AUNClient {
1183
1353
  throw new ValidationError('notify payload is too large');
1184
1354
  }
1185
1355
  const targetAid = String(options.to ?? '').trim();
1186
- const targetGroupId = String(options.group_id ?? options.groupId ?? '').trim();
1356
+ const targetGroupId = String(options.group_aid ?? options.groupAid ?? options.group_id ?? options.groupId ?? '').trim();
1187
1357
  const targetDeviceId = String(options.device_id ?? options.deviceId ?? '').trim();
1188
1358
  const targetSlotId = String(options.slot_id ?? options.slotId ?? '').trim();
1189
1359
  const ttl = AUNClient._normalizeNotifyTtl(options.ttl_ms ?? options.ttlMs);
@@ -1193,6 +1363,13 @@ export class AUNClient {
1193
1363
  if (targetSlotId && !targetDeviceId) {
1194
1364
  throw new ValidationError('slot_id requires device_id for notify target');
1195
1365
  }
1366
+ // 校验 AID 和 Group ID 格式
1367
+ if (targetAid) {
1368
+ validateAIDFormat(targetAid, 'to');
1369
+ }
1370
+ if (targetGroupId) {
1371
+ validateGroupIDFormat(targetGroupId, 'group_id');
1372
+ }
1196
1373
  if (targetAid) {
1197
1374
  const eventMethod = AUNClient._validateNotifyEventMethod(method);
1198
1375
  const target = { type: 'aid', aid: targetAid };
@@ -1216,6 +1393,7 @@ export class AUNClient {
1216
1393
  throw new ValidationError('group_id is required for group notify');
1217
1394
  }
1218
1395
  const routeParams = {
1396
+ group_aid: normalizedGroupId,
1219
1397
  group_id: normalizedGroupId,
1220
1398
  deliver: { method: eventMethod, params: payload },
1221
1399
  };
@@ -1250,8 +1428,11 @@ export class AUNClient {
1250
1428
  delete p._skip_auto_ack;
1251
1429
  delete p.skip_auto_ack;
1252
1430
  delete p._group_cursor_params;
1253
- if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
1254
- p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
1431
+ if (method.startsWith('group.') && p.group_aid !== undefined && p.group_aid !== null) {
1432
+ p.group_aid = normalizeGroupId(String(p.group_aid)) || String(p.group_aid);
1433
+ }
1434
+ else if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
1435
+ p.group_aid = normalizeGroupId(String(p.group_id)) || String(p.group_id);
1255
1436
  }
1256
1437
  if (method.startsWith('group.') && p.device_id === undefined) {
1257
1438
  p.device_id = this._deviceId;
@@ -1411,7 +1592,13 @@ export class AUNClient {
1411
1592
  _messageEnvelopeFieldsForDebug(message) {
1412
1593
  return this._delivery.messageEnvelopeFieldsForDebug(message);
1413
1594
  }
1595
+ _isMessageDebugEnabled() {
1596
+ const checker = this._clientLog.isDebugEnabled;
1597
+ return typeof checker === 'function' ? checker.call(this._clientLog) : true;
1598
+ }
1414
1599
  _logMessageDebug(stage, source, event, message, opts = {}) {
1600
+ if (!this._isMessageDebugEnabled())
1601
+ return;
1415
1602
  this._delivery.logMessageDebug(stage, source, event, message, opts);
1416
1603
  }
1417
1604
  _tryAcquirePullGate(key) {
@@ -1546,21 +1733,21 @@ export class AUNClient {
1546
1733
  }
1547
1734
  _extractGroupIdFromResult(result) {
1548
1735
  const group = isJsonObject(result.group) ? result.group : null;
1549
- const gid = group ? String(group.group_id ?? '') : '';
1736
+ const gid = group ? String(group.group_aid ?? group.group_id ?? '') : '';
1550
1737
  if (gid)
1551
1738
  return gid;
1552
- const directGid = String(result.group_id ?? '');
1739
+ const directGid = String(result.group_aid ?? result.group_id ?? '');
1553
1740
  if (directGid)
1554
1741
  return directGid;
1555
1742
  const member = isJsonObject(result.member) ? result.member : null;
1556
- return member ? String(member.group_id ?? '') : '';
1743
+ return member ? String(member.group_aid ?? member.group_id ?? '') : '';
1557
1744
  }
1558
1745
  async _onRawGroupChanged(data) {
1559
1746
  const tStart = Date.now();
1560
1747
  try {
1561
1748
  if (isJsonObject(data)) {
1562
1749
  const d = data;
1563
- const groupId = String(d.group_id ?? '');
1750
+ const groupId = String(d.group_aid ?? d.group_id ?? '');
1564
1751
  const action = String(d.action ?? '');
1565
1752
  this._clientLog.debug(`_onRawGroupChanged enter: group_id=${groupId}, action=${action}, event_seq=${String(d.event_seq ?? '')}`);
1566
1753
  // 验签:有 client_signature 就验,没有默认安全(H20: 严格 boolean)
@@ -1598,18 +1785,22 @@ export class AUNClient {
1598
1785
  this._v2E2EE.deleteBootstrapCacheEntry(`group:${groupId}`);
1599
1786
  this._v2GroupSecurityLevels.delete(groupId);
1600
1787
  this._v2StateChains.delete(groupId);
1601
- this._seqTracker.removeNamespace(`group:${groupId}`);
1602
- this._seqTracker.removeNamespace(`group_event:${groupId}`);
1788
+ const groupNs = `group:${groupId}`;
1789
+ const groupEventNs = `group_event:${groupId}`;
1790
+ this._seqTracker.removeNamespace(groupNs);
1791
+ this._seqTracker.removeNamespace(groupEventNs);
1792
+ this._persistRepairedSeq(groupNs);
1793
+ this._persistRepairedSeq(groupEventNs);
1603
1794
  this._saveSeqTrackerState();
1604
1795
  for (const key of this._gapFillDone.keys()) {
1605
1796
  if (key.includes(groupId)) {
1606
1797
  this._gapFillDone.delete(key);
1607
1798
  }
1608
1799
  }
1609
- this._pushedSeqs.delete(`group:${groupId}`);
1610
- this._pushedSeqs.delete(`group_event:${groupId}`);
1611
- this._pendingOrderedMsgs.delete(`group:${groupId}`);
1612
- this._pendingOrderedMsgs.delete(`group_event:${groupId}`);
1800
+ this._pushedSeqs.delete(groupNs);
1801
+ this._pushedSeqs.delete(groupEventNs);
1802
+ this._pendingOrderedMsgs.delete(groupNs);
1803
+ this._pendingOrderedMsgs.delete(groupEventNs);
1613
1804
  this._clientLog.info(`cleaned up disbanded group ${groupId} local state`);
1614
1805
  }
1615
1806
  /** 同步验签群事件 client_signature。返回 true/false/"pending"。 */
@@ -1867,6 +2058,7 @@ export class AUNClient {
1867
2058
  return JSON.stringify([this._aid, this._deviceId, this._slotId]);
1868
2059
  }
1869
2060
  _resetSeqTrackingState() {
2061
+ this._saveSeqTrackerState();
1870
2062
  this._resetV2IdentityRuntime();
1871
2063
  this._seqTracker = new SeqTracker();
1872
2064
  this._seqTrackerContext = null;
@@ -1888,6 +2080,7 @@ export class AUNClient {
1888
2080
  const nextContext = this._currentSeqTrackerContext();
1889
2081
  if (nextContext === this._seqTrackerContext)
1890
2082
  return;
2083
+ this._saveSeqTrackerState();
1891
2084
  this._seqTracker = new SeqTracker();
1892
2085
  this._gapFillDone.clear();
1893
2086
  this._pushedSeqs.clear();
@@ -1908,6 +2101,9 @@ export class AUNClient {
1908
2101
  _saveSeqTrackerState() {
1909
2102
  return this._delivery.saveSeqTrackerState();
1910
2103
  }
2104
+ _persistSeq(ns, forceSeq) {
2105
+ return this._delivery.persistSeq(ns, forceSeq);
2106
+ }
1911
2107
  _persistRepairedSeq(ns) {
1912
2108
  return this._delivery.persistRepairedSeq(ns);
1913
2109
  }
@@ -2573,9 +2769,6 @@ export class AUNClient {
2573
2769
  async _decryptV2PushMessage(data) {
2574
2770
  return await this._v2E2EE.decryptV2PushMessage(data);
2575
2771
  }
2576
- async _onV2EpochRotated(data) {
2577
- return this._v2E2EE.handleV2EpochRotated(data);
2578
- }
2579
2772
  /** 按当前 AID 发现 Gateway;用于 authenticate()/connect() 的新入口。 */
2580
2773
  async _resolveGatewayForAid(aid) {
2581
2774
  const resolvedAid = String(aid ?? this._aid ?? '').trim();
@@ -3007,6 +3200,9 @@ export class AUNClient {
3007
3200
  this._clientLog.warn(`transport disconnected: closeCode=${closeCode ?? 'none'}, error=${error ? formatCaughtError(error) : 'none'}`);
3008
3201
  this._state = 'standby';
3009
3202
  this._stopBackgroundTasks();
3203
+ void this._transport.close().catch((exc) => {
3204
+ this._clientLog.debug(`transport cleanup skipped: ${formatCaughtError(exc)}`);
3205
+ });
3010
3206
  await this._dispatcher.publish('state_change', { state: this._publicState(this._state), error });
3011
3207
  if (!this._sessionOptions.auto_reconnect) {
3012
3208
  this._reconnectActive = false;