@agentunion/fastaun-browser 0.5.0 → 0.5.1

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 (116) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/_packed_docs/CHANGELOG-validators.md +144 -0
  3. package/_packed_docs/CHANGELOG.md +56 -0
  4. package/_packed_docs/INDEX.md +198 -181
  5. package/_packed_docs/KITE_DOCS_GUIDE.md +26 -23
  6. package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +4 -3
  7. package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
  8. package/_packed_docs/protocol/08-AUN-E2EE-Group.md +296 -902
  9. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +64 -114
  10. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +7 -1
  11. 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
  12. package/_packed_docs/protocol/README.md +2 -1
  13. package/_packed_docs/protocol/index.md +8 -3
  14. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +4 -252
  15. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +483 -457
  16. package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -550
  17. package/_packed_docs/sdk/09-group-rpc-manual.md +248 -335
  18. package/_packed_docs/sdk/09-storage-rpc-manual.md +56 -19
  19. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -13
  20. package/_packed_docs/sdk/INDEX.md +14 -14
  21. package/dist/bundle.js +1560 -1159
  22. package/dist/client/delivery.d.ts +13 -2
  23. package/dist/client/delivery.d.ts.map +1 -1
  24. package/dist/client/delivery.js +251 -46
  25. package/dist/client/delivery.js.map +1 -1
  26. package/dist/client/group-state.d.ts.map +1 -1
  27. package/dist/client/group-state.js +36 -14
  28. package/dist/client/group-state.js.map +1 -1
  29. package/dist/client/lifecycle.js +2 -2
  30. package/dist/client/lifecycle.js.map +1 -1
  31. package/dist/client/rpc-pipeline.d.ts +1 -0
  32. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  33. package/dist/client/rpc-pipeline.js +166 -50
  34. package/dist/client/rpc-pipeline.js.map +1 -1
  35. package/dist/client/v2-e2ee.d.ts +14 -1
  36. package/dist/client/v2-e2ee.d.ts.map +1 -1
  37. package/dist/client/v2-e2ee.js +300 -121
  38. package/dist/client/v2-e2ee.js.map +1 -1
  39. package/dist/client.d.ts +5 -4
  40. package/dist/client.d.ts.map +1 -1
  41. package/dist/client.js +187 -46
  42. package/dist/client.js.map +1 -1
  43. package/dist/collab/client.d.ts +8 -0
  44. package/dist/collab/client.d.ts.map +1 -1
  45. package/dist/collab/client.js +12 -0
  46. package/dist/collab/client.js.map +1 -1
  47. package/dist/errors.d.ts +0 -20
  48. package/dist/errors.d.ts.map +1 -1
  49. package/dist/errors.js +8 -51
  50. package/dist/errors.js.map +1 -1
  51. package/dist/facades.d.ts +9 -4
  52. package/dist/facades.d.ts.map +1 -1
  53. package/dist/facades.js +192 -31
  54. package/dist/facades.js.map +1 -1
  55. package/dist/group-fs.d.ts +17 -0
  56. package/dist/group-fs.d.ts.map +1 -1
  57. package/dist/group-fs.js +54 -11
  58. package/dist/group-fs.js.map +1 -1
  59. package/dist/group-id.d.ts +9 -12
  60. package/dist/group-id.d.ts.map +1 -1
  61. package/dist/group-id.js +41 -63
  62. package/dist/group-id.js.map +1 -1
  63. package/dist/index.d.ts +4 -2
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +4 -1
  66. package/dist/index.js.map +1 -1
  67. package/dist/keystore/index.d.ts +2 -54
  68. package/dist/keystore/index.d.ts.map +1 -1
  69. package/dist/keystore/indexeddb-identity-store.d.ts +3 -0
  70. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  71. package/dist/keystore/indexeddb-identity-store.js +65 -0
  72. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  73. package/dist/keystore/indexeddb-shared.d.ts +3 -17
  74. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  75. package/dist/keystore/indexeddb-shared.js +4 -47
  76. package/dist/keystore/indexeddb-shared.js.map +1 -1
  77. package/dist/keystore/indexeddb-token-store.d.ts +1 -64
  78. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  79. package/dist/keystore/indexeddb-token-store.js +45 -774
  80. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  81. package/dist/logger.d.ts +2 -0
  82. package/dist/logger.d.ts.map +1 -1
  83. package/dist/logger.js +4 -0
  84. package/dist/logger.js.map +1 -1
  85. package/dist/storage/lowlevel.d.ts +9 -1
  86. package/dist/storage/lowlevel.d.ts.map +1 -1
  87. package/dist/storage/lowlevel.js +12 -1
  88. package/dist/storage/lowlevel.js.map +1 -1
  89. package/dist/storage/vfs.d.ts +22 -0
  90. package/dist/storage/vfs.d.ts.map +1 -1
  91. package/dist/storage/vfs.js +54 -0
  92. package/dist/storage/vfs.js.map +1 -1
  93. package/dist/tools/cross-sdk-agent.js +336 -49
  94. package/dist/tools/cross-sdk-agent.js.map +1 -1
  95. package/dist/transport.d.ts +2 -0
  96. package/dist/transport.d.ts.map +1 -1
  97. package/dist/transport.js +96 -3
  98. package/dist/transport.js.map +1 -1
  99. package/dist/types.d.ts +39 -56
  100. package/dist/types.d.ts.map +1 -1
  101. package/dist/v2/session/session.d.ts +2 -0
  102. package/dist/v2/session/session.d.ts.map +1 -1
  103. package/dist/v2/session/session.js +58 -22
  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.d.ts.map +1 -1
  107. package/dist/v2/state/commitment.js +5 -3
  108. package/dist/v2/state/commitment.js.map +1 -1
  109. package/dist/validators.d.ts +35 -0
  110. package/dist/validators.d.ts.map +1 -0
  111. package/dist/validators.js +127 -0
  112. package/dist/validators.js.map +1 -0
  113. package/dist/version.d.ts +1 -1
  114. package/dist/version.js +1 -1
  115. package/package.json +1 -1
  116. package/_packed_docs/collab-gateway-boundary-test-report.md +0 -164
package/dist/client.js CHANGED
@@ -7,6 +7,7 @@
7
7
  import { createConfig, getDeviceId, normalizeSlotId } from './config.js';
8
8
  import { EventDispatcher } from './events.js';
9
9
  import { normalizeGroupId } from './group-id.js';
10
+ import { validateAIDFormat, validateGroupIDFormat } from './validators.js';
10
11
  import { GatewayDiscovery } from './discovery.js';
11
12
  import { AIDStore } from './aid-store.js';
12
13
  import { RPCTransport } from './transport.js';
@@ -30,7 +31,7 @@ import { ecdsaVerifyRaw } from './v2/crypto/ecdsa.js';
30
31
  import { AUNLogger } from './logger.js';
31
32
  import { AgentMdManager } from './agent-md.js';
32
33
  import { certMatchesFingerprint, normalizeFingerprintHex } from './cert-utils.js';
33
- import { AUNError, AuthError, ConnectionError, E2EEError, NotFoundError, PermissionError, StateError, ValidationError, } from './errors.js';
34
+ import { AUNError, AuthError, ConnectionError, E2EEError, NotFoundError, PermissionError, StateError, TimeoutError, ValidationError, } from './errors.js';
34
35
  import { isJsonObject, ConnectionState, STATE_TO_PUBLIC, } from './types.js';
35
36
  import { AID } from './aid.js';
36
37
  function getV2DeviceId(dev) {
@@ -105,8 +106,8 @@ const NON_IDEMPOTENT_TIMEOUT = 35;
105
106
  const NON_IDEMPOTENT_METHODS = new Set([
106
107
  'message.send', 'group.send', 'group.create', 'group.invite',
107
108
  'group.kick', 'group.remove_member', 'group.leave', 'group.dissolve',
108
- 'group.update_name', 'group.update_avatar', 'group.update_announcement',
109
- 'group.update_settings',
109
+ 'group.set_settings',
110
+ 'group.update_announcement', 'group.update_rules',
110
111
  'storage.put_object', 'storage.delete_object',
111
112
  'storage.create_share_link', 'storage.revoke_share_link',
112
113
  'storage.get_by_share',
@@ -126,6 +127,7 @@ const NON_IDEMPOTENT_METHODS = new Set([
126
127
  'message.thought.put', 'group.thought.put',
127
128
  'group.add_member', 'group.bind_group_aid', 'group.complete_transfer',
128
129
  'group.fs.mkdir', 'group.fs.rm', 'group.fs.cp', 'group.fs.mv',
130
+ 'group.fs.set_acl', 'group.fs.remove_acl',
129
131
  'group.fs.mount', 'group.fs.umount',
130
132
  'group.fs.check_upload', 'group.fs.create_upload_session',
131
133
  'group.fs.complete_upload', 'group.fs.create_download_ticket',
@@ -146,9 +148,9 @@ const SIGNED_METHODS = new Set([
146
148
  'group.v2.propose_state', 'group.v2.confirm_state',
147
149
  'group.v2.get_proposal',
148
150
  'group.kick', 'group.add_member',
149
- 'group.leave', 'group.remove_member', 'group.update_rules',
150
- 'group.update', 'group.update_announcement',
151
- 'group.update_join_requirements', 'group.set_role',
151
+ 'group.leave', 'group.remove_member',
152
+ 'group.update',
153
+ 'group.set_role',
152
154
  'group.transfer_owner', 'group.bind_group_aid', 'group.complete_transfer',
153
155
  'group.review_join_request',
154
156
  'group.batch_review_join_request',
@@ -156,7 +158,10 @@ const SIGNED_METHODS = new Set([
156
158
  'group.thought.put',
157
159
  'message.thought.put',
158
160
  'group.set_settings',
161
+ 'group.update_announcement',
162
+ 'group.update_rules',
159
163
  'group.fs.mkdir', 'group.fs.rm', 'group.fs.cp', 'group.fs.mv',
164
+ 'group.fs.set_acl', 'group.fs.remove_acl',
160
165
  'group.fs.mount', 'group.fs.umount',
161
166
  'group.fs.check_upload', 'group.fs.create_upload_session',
162
167
  'group.fs.complete_upload', 'group.fs.create_download_ticket',
@@ -736,7 +741,7 @@ export class AUNClient {
736
741
  this._dispatcher.subscribe('_raw.group.message_recalled', (data) => {
737
742
  this._safeAsync(this._onRawGroupMessageRecalled(data));
738
743
  });
739
- // 群组变更事件:验签 + 透传 + gap 检测
744
+ // 群组变更事件:验签 + 透传 + gap 检测 + V2 state/SPK 维护
740
745
  this._dispatcher.subscribe('_raw.group.changed', (data) => {
741
746
  this._onRawGroupChanged(data);
742
747
  });
@@ -747,9 +752,6 @@ export class AUNClient {
747
752
  this._dispatcher.subscribe('_raw.group.v2.message_created', (data) => {
748
753
  this._safeAsync(this._onRawGroupV2MessageCreated(data));
749
754
  });
750
- this._dispatcher.subscribe('_raw.group.v2.epoch_rotated', (data) => {
751
- this._safeAsync(this._onV2EpochRotated(data));
752
- });
753
755
  this._dispatcher.subscribe('_raw.group.v2.state_proposed', (data) => {
754
756
  this._safeAsync(this._onV2StateProposed(data));
755
757
  });
@@ -1047,7 +1049,22 @@ export class AUNClient {
1047
1049
  * 自动解密 message.pull/group.pull、Group E2EE 生命周期编排。
1048
1050
  */
1049
1051
  async call(method, params) {
1050
- return await this._rpcPipeline.call(method, params);
1052
+ try {
1053
+ return await this._rpcPipeline.call(method, params);
1054
+ }
1055
+ catch (err) {
1056
+ await this._maybeHandleHalfOpenRpcFailure(err);
1057
+ throw err;
1058
+ }
1059
+ }
1060
+ async _maybeHandleHalfOpenRpcFailure(err) {
1061
+ if (this._closing || this.state !== ConnectionState.READY)
1062
+ return;
1063
+ if (!this._sessionOptions?.auto_reconnect || this._reconnectActive)
1064
+ return;
1065
+ if (!(err instanceof ConnectionError) && !(err instanceof TimeoutError))
1066
+ return;
1067
+ await this._handleTransportDisconnect(err);
1051
1068
  }
1052
1069
  async createGroup(params = {}) {
1053
1070
  if (!isJsonObject(params)) {
@@ -1101,8 +1118,33 @@ export class AUNClient {
1101
1118
  if (!store) {
1102
1119
  throw new ValidationError('bindGroupAid requires aidStore');
1103
1120
  }
1104
- const keyPair = await new CryptoProvider().generateIdentity();
1121
+ const groupId = String(params.group_id ?? params.group_aid ?? '').trim();
1122
+ const keystore = store._keystore;
1123
+ let keyPair = null;
1124
+ // 优先复用 pending 槽位中已暂存的待绑定密钥(崩溃/重试幂等)
1125
+ if (groupId && keystore && typeof keystore.loadPendingGroupBind === 'function') {
1126
+ keyPair = await keystore.loadPendingGroupBind(groupId);
1127
+ }
1128
+ if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
1129
+ // 未命中 pending,生成新密钥
1130
+ const generated = await new CryptoProvider().generateIdentity();
1131
+ keyPair = {
1132
+ private_key_pem: generated.private_key_pem,
1133
+ public_key_der_b64: generated.public_key_der_b64,
1134
+ curve: generated.curve,
1135
+ };
1136
+ // 发 RPC 前先落盘暂存,确保崩溃后重试能复用同一密钥
1137
+ if (groupId && keystore && typeof keystore.savePendingGroupBind === 'function') {
1138
+ await keystore.savePendingGroupBind(groupId, keyPair);
1139
+ }
1140
+ }
1141
+ if (!keyPair) {
1142
+ throw new ValidationError('bindGroupAid: failed to generate or load key pair');
1143
+ }
1105
1144
  const payload = { ...params };
1145
+ if (!String(payload.group_id ?? '').trim() && groupId) {
1146
+ payload.group_id = groupId;
1147
+ }
1106
1148
  payload.public_key = keyPair.public_key_der_b64;
1107
1149
  payload.curve = keyPair.curve;
1108
1150
  const result = await this.call('group.bind_group_aid', payload);
@@ -1123,6 +1165,95 @@ export class AUNClient {
1123
1165
  if (!imported.ok) {
1124
1166
  throw new ValidationError(imported.error.message);
1125
1167
  }
1168
+ // 落盘成功,清除 pending 槽位
1169
+ if (groupId && keystore && typeof keystore.clearPendingGroupBind === 'function') {
1170
+ await keystore.clearPendingGroupBind(groupId);
1171
+ }
1172
+ return result;
1173
+ }
1174
+ async renewGroupAid(params = {}, options = {}) {
1175
+ if (!isJsonObject(params)) {
1176
+ throw new ValidationError('renewGroupAid params must be an object');
1177
+ }
1178
+ const store = options.aidStore;
1179
+ if (!store) {
1180
+ throw new ValidationError('renewGroupAid requires aidStore');
1181
+ }
1182
+ const groupId = String(params.group_id ?? params.group_aid ?? '').trim();
1183
+ if (!groupId) {
1184
+ throw new ValidationError('renewGroupAid requires group_id or group_aid');
1185
+ }
1186
+ let groupAid = String(params.group_aid ?? '').trim();
1187
+ if (!groupAid) {
1188
+ const info = await this.call('group.get_info', { group_id: groupId, required: ['member'] });
1189
+ groupAid = String(info?.group_aid ?? '').trim();
1190
+ }
1191
+ if (!groupAid) {
1192
+ throw new ValidationError('renewGroupAid: unable to determine group_aid');
1193
+ }
1194
+ const loaded = await store.load(groupAid);
1195
+ if (!loaded.ok || !loaded.data) {
1196
+ throw new ValidationError(`renewGroupAid: group_aid identity not found: ${groupAid}`);
1197
+ }
1198
+ const oldAidObj = loaded.data.aid;
1199
+ if (!oldAidObj.isPrivateKeyValid() || !oldAidObj.privateKeyPem) {
1200
+ throw new ValidationError(`renewGroupAid: group_aid has no private key: ${groupAid}`);
1201
+ }
1202
+ const oldPublicKey = oldAidObj.publicKey;
1203
+ if (!oldPublicKey) {
1204
+ throw new ValidationError(`renewGroupAid: cannot determine old public key for ${groupAid}`);
1205
+ }
1206
+ const newKeyPair = await new CryptoProvider().generateIdentity();
1207
+ const newPublicKey = newKeyPair.public_key_der_b64;
1208
+ const newPrivateKey = newKeyPair.private_key_pem;
1209
+ const curve = newKeyPair.curve || 'P-256';
1210
+ if (!newPublicKey || !newPrivateKey) {
1211
+ throw new ValidationError('renewGroupAid: generated incomplete group identity');
1212
+ }
1213
+ const nonce = globalThis.crypto.randomUUID().replace(/-/g, '');
1214
+ const issuedMs = Date.now();
1215
+ const oldHash = bytesToHex(new Uint8Array(await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(oldPublicKey))));
1216
+ const newHash = bytesToHex(new Uint8Array(await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(newPublicKey))));
1217
+ const canonical = [
1218
+ 'aun-group-aid-renew-v1',
1219
+ groupId.toLowerCase(),
1220
+ groupAid.toLowerCase(),
1221
+ oldHash,
1222
+ newHash,
1223
+ nonce,
1224
+ String(issuedMs),
1225
+ ].join('|');
1226
+ const signed = await oldAidObj.sign(canonical);
1227
+ if (!signed.ok) {
1228
+ throw new ValidationError(`renewGroupAid: sign failed: ${signed.error.message}`);
1229
+ }
1230
+ const payload = { ...params };
1231
+ if (!String(payload.group_id ?? '').trim()) {
1232
+ payload.group_id = groupId;
1233
+ }
1234
+ payload.group_aid = groupAid;
1235
+ payload.old_public_key = oldPublicKey;
1236
+ payload.new_public_key = newPublicKey;
1237
+ payload.curve = curve;
1238
+ payload.renew_proof = { nonce, issued_ms: issuedMs, signature: signed.data.signature };
1239
+ const result = await this.call('group.renew_group_aid', payload);
1240
+ const resultMap = isJsonObject(result) ? result : {};
1241
+ const group = isJsonObject(resultMap.group) ? resultMap.group : {};
1242
+ const aidCert = isJsonObject(resultMap.aid_cert) ? resultMap.aid_cert : {};
1243
+ const returnedGroupAid = String(group.group_aid ?? resultMap.group_aid ?? groupAid).trim();
1244
+ const certPem = String(aidCert.cert ?? aidCert.cert_pem ?? resultMap.cert ?? resultMap.cert_pem ?? '').trim();
1245
+ if (!returnedGroupAid || !certPem) {
1246
+ throw new ValidationError('renewGroupAid response missing group.group_aid or aid_cert.cert');
1247
+ }
1248
+ const imported = await store.importGroupIdentity(returnedGroupAid, {
1249
+ private_key_pem: newPrivateKey,
1250
+ public_key_der_b64: newPublicKey,
1251
+ curve,
1252
+ cert_pem: certPem,
1253
+ });
1254
+ if (!imported.ok) {
1255
+ throw new ValidationError(`renewGroupAid failed to persist group identity: ${imported.error.message}`);
1256
+ }
1126
1257
  return result;
1127
1258
  }
1128
1259
  async startGroupTransfer(params = {}, options = {}) {
@@ -1140,10 +1271,8 @@ export class AUNClient {
1140
1271
  }
1141
1272
  let groupAid = String(params.group_aid ?? '').trim();
1142
1273
  if (!groupAid) {
1143
- const info = await this.call('group.get', { group_id: groupId });
1144
- const infoMap = isJsonObject(info) ? info : {};
1145
- const grp = isJsonObject(infoMap.group) ? infoMap.group : infoMap;
1146
- groupAid = String(grp.group_aid ?? '').trim();
1274
+ const info = await this.call('group.get_info', { group_id: groupId, required: ['member'] });
1275
+ groupAid = String(info?.group_aid ?? '').trim();
1147
1276
  }
1148
1277
  if (!groupAid) {
1149
1278
  throw new ValidationError('startGroupTransfer: unable to determine group_aid');
@@ -1190,10 +1319,8 @@ export class AUNClient {
1190
1319
  }
1191
1320
  let groupAid = String(params.group_aid ?? '').trim();
1192
1321
  if (!groupAid) {
1193
- const info = await this.call('group.get', { group_id: groupId });
1194
- const infoMap = isJsonObject(info) ? info : {};
1195
- const grp = isJsonObject(infoMap.group) ? infoMap.group : infoMap;
1196
- groupAid = String(grp.group_aid ?? '').trim();
1322
+ const info = await this.call('group.get_info', { group_id: groupId, required: ['member'] });
1323
+ groupAid = String(info?.group_aid ?? '').trim();
1197
1324
  }
1198
1325
  if (!groupAid) {
1199
1326
  throw new ValidationError('completeGroupTransfer: unable to determine group_aid');
@@ -1277,7 +1404,7 @@ export class AUNClient {
1277
1404
  throw new ValidationError('notify payload is too large');
1278
1405
  }
1279
1406
  const targetAid = String(options.to ?? '').trim();
1280
- const targetGroupId = String(options.group_id ?? options.groupId ?? '').trim();
1407
+ const targetGroupId = String(options.group_aid ?? options.groupAid ?? options.group_id ?? options.groupId ?? '').trim();
1281
1408
  const targetDeviceId = String(options.device_id ?? options.deviceId ?? '').trim();
1282
1409
  const targetSlotId = String(options.slot_id ?? options.slotId ?? '').trim();
1283
1410
  const ttl = AUNClient._normalizeNotifyTtl(options.ttl_ms ?? options.ttlMs);
@@ -1289,6 +1416,8 @@ export class AUNClient {
1289
1416
  }
1290
1417
  if (targetAid) {
1291
1418
  const eventMethod = AUNClient._validateNotifyEventMethod(method);
1419
+ // 校验目标 AID 格式(拒绝 __system__ 等非法格式)
1420
+ validateAIDFormat(targetAid, 'notify.to');
1292
1421
  const target = { type: 'aid', aid: targetAid };
1293
1422
  if (targetDeviceId)
1294
1423
  target.device_id = targetDeviceId;
@@ -1305,11 +1434,14 @@ export class AUNClient {
1305
1434
  }
1306
1435
  if (targetGroupId) {
1307
1436
  const eventMethod = AUNClient._validateNotifyEventMethod(method);
1437
+ // 校验目标 Group ID 格式
1438
+ validateGroupIDFormat(targetGroupId, 'notify.group_id');
1308
1439
  const normalizedGroupId = normalizeGroupId(targetGroupId);
1309
1440
  if (!normalizedGroupId) {
1310
1441
  throw new ValidationError('group_id is required for group notify');
1311
1442
  }
1312
1443
  const routeParams = {
1444
+ group_aid: normalizedGroupId,
1313
1445
  group_id: normalizedGroupId,
1314
1446
  deliver: { method: eventMethod, params: payload },
1315
1447
  };
@@ -1335,8 +1467,11 @@ export class AUNClient {
1335
1467
  delete p._skip_auto_ack;
1336
1468
  delete p.skip_auto_ack;
1337
1469
  delete p._group_cursor_params;
1338
- if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
1339
- p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
1470
+ if (method.startsWith('group.') && p.group_aid !== undefined && p.group_aid !== null) {
1471
+ p.group_aid = normalizeGroupId(String(p.group_aid)) || String(p.group_aid);
1472
+ }
1473
+ else if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
1474
+ p.group_aid = normalizeGroupId(String(p.group_id)) || String(p.group_id);
1340
1475
  }
1341
1476
  if (method.startsWith('group.') && p.device_id === undefined) {
1342
1477
  p.device_id = this._deviceId;
@@ -1448,19 +1583,19 @@ export class AUNClient {
1448
1583
  }
1449
1584
  _extractGroupIdFromResult(result) {
1450
1585
  const group = isJsonObject(result.group) ? result.group : null;
1451
- const gid = group ? String(group.group_id ?? '') : '';
1586
+ const gid = group ? String(group.group_aid ?? group.group_id ?? '') : '';
1452
1587
  if (gid)
1453
1588
  return gid;
1454
- const directGid = String(result.group_id ?? '');
1589
+ const directGid = String(result.group_aid ?? result.group_id ?? '');
1455
1590
  if (directGid)
1456
1591
  return directGid;
1457
1592
  const member = isJsonObject(result.member) ? result.member : null;
1458
- return member ? String(member.group_id ?? '') : '';
1593
+ return member ? String(member.group_aid ?? member.group_id ?? '') : '';
1459
1594
  }
1460
1595
  async _onRawGroupChanged(data) {
1461
1596
  const tStart = Date.now();
1462
1597
  const action = String(data?.action ?? '');
1463
- const groupIdInit = String(data?.group_id ?? '');
1598
+ const groupIdInit = String(data?.group_aid ?? data?.group_id ?? '');
1464
1599
  this._clientLog.debug(`_onRawGroupChanged enter: group_id=${groupIdInit} action=${action}`);
1465
1600
  try {
1466
1601
  if (isJsonObject(data)) {
@@ -1476,7 +1611,7 @@ export class AUNClient {
1476
1611
  d._verified = this._isEventSignatureVerified(verified);
1477
1612
  }
1478
1613
  }
1479
- const groupId = (d.group_id ?? '');
1614
+ const groupId = (d.group_aid ?? d.group_id ?? '');
1480
1615
  await this._delivery.handleGroupChangedEventSeq(d, groupId);
1481
1616
  }
1482
1617
  else {
@@ -1509,9 +1644,13 @@ export class AUNClient {
1509
1644
  this._v2E2EE.deleteBootstrapCacheEntry(`group:${groupId}`);
1510
1645
  this._v2GroupSecurityLevels.delete(groupId);
1511
1646
  this._v2StateChains.delete(groupId);
1512
- this._seqTracker.removeNamespace(`group:${groupId}`);
1513
- this._seqTracker.removeNamespace(`group_event:${groupId}`);
1514
- this._saveSeqTrackerState();
1647
+ const groupNs = `group:${groupId}`;
1648
+ const groupEventNs = `group_event:${groupId}`;
1649
+ this._seqTracker.removeNamespace(groupNs);
1650
+ this._seqTracker.removeNamespace(groupEventNs);
1651
+ this._persistRepairedSeq(groupNs);
1652
+ this._persistRepairedSeq(groupEventNs);
1653
+ void this._saveSeqTrackerState();
1515
1654
  // 2. 清理补洞去重缓存中的相关条目
1516
1655
  for (const key of this._gapFillDone.keys()) {
1517
1656
  if (key.includes(groupId)) {
@@ -1519,10 +1658,10 @@ export class AUNClient {
1519
1658
  }
1520
1659
  }
1521
1660
  // 3. 清理推送 seq 去重缓存
1522
- this._pushedSeqs.delete(`group:${groupId}`);
1523
- this._pushedSeqs.delete(`group_event:${groupId}`);
1524
- this._pendingOrderedMsgs.delete(`group:${groupId}`);
1525
- this._pendingOrderedMsgs.delete(`group_event:${groupId}`);
1661
+ this._pushedSeqs.delete(groupNs);
1662
+ this._pushedSeqs.delete(groupEventNs);
1663
+ this._pendingOrderedMsgs.delete(groupNs);
1664
+ this._pendingOrderedMsgs.delete(groupEventNs);
1526
1665
  this._clientLog.info(`cleanup dissolved group ${groupId} local state`);
1527
1666
  }
1528
1667
  async _verifyEventSignature(_event, cs) {
@@ -2019,7 +2158,7 @@ export class AUNClient {
2019
2158
  let consecutiveFailures = 0;
2020
2159
  const maxFailures = 2;
2021
2160
  this._heartbeatTimer = setInterval(async () => {
2022
- if (this._state !== 'connected' || this._closing)
2161
+ if (this.state !== ConnectionState.READY || this._closing)
2023
2162
  return;
2024
2163
  try {
2025
2164
  const pong = await this._transport.call('meta.ping', {});
@@ -2054,7 +2193,7 @@ export class AUNClient {
2054
2193
  clearInterval(this._heartbeatTimer);
2055
2194
  this._heartbeatTimer = null;
2056
2195
  }
2057
- if (newInterval > 0 && this._state === 'connected' && !this._closing) {
2196
+ if (newInterval > 0 && this.state === ConnectionState.READY && !this._closing) {
2058
2197
  this._startHeartbeat();
2059
2198
  }
2060
2199
  }
@@ -2073,7 +2212,7 @@ export class AUNClient {
2073
2212
  if (this._closing)
2074
2213
  return;
2075
2214
  this._tokenRefreshTimer = null;
2076
- if (this._state !== 'connected' || !this._gatewayUrl) {
2215
+ if (this.state !== ConnectionState.READY || !this._gatewayUrl) {
2077
2216
  scheduleRefresh();
2078
2217
  return;
2079
2218
  }
@@ -2091,14 +2230,14 @@ export class AUNClient {
2091
2230
  scheduleRefresh();
2092
2231
  return;
2093
2232
  }
2094
- if (this._state !== 'connected' || !this._gatewayUrl || this._closing) {
2233
+ if (this.state !== ConnectionState.READY || !this._gatewayUrl || this._closing) {
2095
2234
  scheduleRefresh();
2096
2235
  return;
2097
2236
  }
2098
2237
  try {
2099
2238
  identity = await this._auth.refreshCachedTokens(this._gatewayUrl, identity);
2100
2239
  // 刷新期间可能已断线,复检状态,避免写回 stale identity
2101
- if (this._state !== 'connected') {
2240
+ if (this.state !== ConnectionState.READY) {
2102
2241
  scheduleRefresh();
2103
2242
  return;
2104
2243
  }
@@ -2183,6 +2322,9 @@ export class AUNClient {
2183
2322
  this._state = 'standby';
2184
2323
  // 先停止后台任务,避免心跳/token刷新在重连期间继续触发
2185
2324
  this._stopBackgroundTasks();
2325
+ void this._transport.close().catch((exc) => {
2326
+ this._clientLog.debug(`transport cleanup skipped: ${formatCaughtError(exc)}`);
2327
+ });
2186
2328
  await this._dispatcher.publish('state_change', {
2187
2329
  state: this._publicState(this._state),
2188
2330
  error,
@@ -2361,6 +2503,7 @@ export class AUNClient {
2361
2503
  return JSON.stringify([this._aid, this._deviceId, this._slotId]);
2362
2504
  }
2363
2505
  _resetSeqTrackingState() {
2506
+ void this._saveSeqTrackerState();
2364
2507
  this._resetV2IdentityRuntime();
2365
2508
  this._seqTracker = new SeqTracker();
2366
2509
  this._seqTrackerContext = null;
@@ -2381,6 +2524,7 @@ export class AUNClient {
2381
2524
  const nextContext = this._currentSeqTrackerContext();
2382
2525
  if (nextContext === this._seqTrackerContext)
2383
2526
  return;
2527
+ void this._saveSeqTrackerState();
2384
2528
  this._seqTracker = new SeqTracker();
2385
2529
  this._gapFillDone.clear();
2386
2530
  this._pushedSeqs.clear();
@@ -2400,6 +2544,9 @@ export class AUNClient {
2400
2544
  _saveSeqTrackerState() {
2401
2545
  return this._delivery.saveSeqTrackerState();
2402
2546
  }
2547
+ _persistSeq(ns, forceSeq) {
2548
+ return this._delivery.persistSeq(ns, forceSeq);
2549
+ }
2403
2550
  _persistRepairedSeq(ns) {
2404
2551
  return this._delivery.persistRepairedSeq(ns);
2405
2552
  }
@@ -2916,12 +3063,6 @@ export class AUNClient {
2916
3063
  async _onV2PushNotification(data) {
2917
3064
  return this._delivery.onV2PushNotification(data);
2918
3065
  }
2919
- /**
2920
- * 处理 V2 epoch 轮换事件:清除 bootstrap 缓存 + 触发 SPK rotation。
2921
- */
2922
- async _onV2EpochRotated(data) {
2923
- return this._v2E2EE.handleV2EpochRotated(data);
2924
- }
2925
3066
  /** 安全执行异步操作(不阻塞调用方,错误打 warning 便于排障) */
2926
3067
  _safeAsync(promise) {
2927
3068
  promise.catch((exc) => {