@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.
- package/CHANGELOG.md +56 -0
- package/_packed_docs/CHANGELOG-validators.md +144 -0
- package/_packed_docs/CHANGELOG.md +56 -0
- package/_packed_docs/INDEX.md +198 -181
- package/_packed_docs/KITE_DOCS_GUIDE.md +26 -23
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +4 -3
- package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
- package/_packed_docs/protocol/08-AUN-E2EE-Group.md +296 -902
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +64 -114
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +7 -1
- 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
- package/_packed_docs/protocol/README.md +2 -1
- package/_packed_docs/protocol/index.md +8 -3
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +4 -252
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +483 -457
- package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -550
- package/_packed_docs/sdk/09-group-rpc-manual.md +248 -335
- package/_packed_docs/sdk/09-storage-rpc-manual.md +56 -19
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -13
- package/_packed_docs/sdk/INDEX.md +14 -14
- package/dist/bundle.js +1560 -1159
- package/dist/client/delivery.d.ts +13 -2
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +251 -46
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.d.ts.map +1 -1
- package/dist/client/group-state.js +36 -14
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/lifecycle.js +2 -2
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +1 -0
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +166 -50
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +14 -1
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +300 -121
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +5 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +187 -46
- package/dist/client.js.map +1 -1
- package/dist/collab/client.d.ts +8 -0
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +12 -0
- package/dist/collab/client.js.map +1 -1
- package/dist/errors.d.ts +0 -20
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +8 -51
- package/dist/errors.js.map +1 -1
- package/dist/facades.d.ts +9 -4
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +192 -31
- package/dist/facades.js.map +1 -1
- package/dist/group-fs.d.ts +17 -0
- package/dist/group-fs.d.ts.map +1 -1
- package/dist/group-fs.js +54 -11
- package/dist/group-fs.js.map +1 -1
- package/dist/group-id.d.ts +9 -12
- package/dist/group-id.d.ts.map +1 -1
- package/dist/group-id.js +41 -63
- package/dist/group-id.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +2 -54
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.d.ts +3 -0
- package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.js +65 -0
- package/dist/keystore/indexeddb-identity-store.js.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +3 -17
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +4 -47
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +1 -64
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +45 -774
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -1
- package/dist/storage/lowlevel.d.ts +9 -1
- package/dist/storage/lowlevel.d.ts.map +1 -1
- package/dist/storage/lowlevel.js +12 -1
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/storage/vfs.d.ts +22 -0
- package/dist/storage/vfs.d.ts.map +1 -1
- package/dist/storage/vfs.js +54 -0
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +336 -49
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +2 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +96 -3
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +39 -56
- package/dist/types.d.ts.map +1 -1
- package/dist/v2/session/session.d.ts +2 -0
- package/dist/v2/session/session.d.ts.map +1 -1
- package/dist/v2/session/session.js +58 -22
- package/dist/v2/session/session.js.map +1 -1
- package/dist/v2/state/commitment.d.ts +1 -1
- package/dist/v2/state/commitment.d.ts.map +1 -1
- package/dist/v2/state/commitment.js +5 -3
- package/dist/v2/state/commitment.js.map +1 -1
- package/dist/validators.d.ts +35 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +127 -0
- package/dist/validators.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- 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.
|
|
109
|
-
'group.
|
|
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',
|
|
150
|
-
'group.update',
|
|
151
|
-
'group.
|
|
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
|
-
|
|
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
|
|
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.
|
|
1144
|
-
|
|
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.
|
|
1194
|
-
|
|
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.
|
|
1339
|
-
p.
|
|
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
|
-
|
|
1513
|
-
|
|
1514
|
-
this.
|
|
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(
|
|
1523
|
-
this._pushedSeqs.delete(
|
|
1524
|
-
this._pendingOrderedMsgs.delete(
|
|
1525
|
-
this._pendingOrderedMsgs.delete(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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) => {
|