@agentunion/fastaun 0.5.10 → 0.5.12
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 +62 -0
- package/_packed_docs/CHANGELOG.md +62 -0
- package/_packed_docs/INDEX.md +3 -3
- package/_packed_docs/KITE_DOCS_GUIDE.md +1 -1
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +21 -3
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +59 -0
- package/_packed_docs/sdk/09-group-rpc-manual.md +29 -2
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +3 -2
- package/_packed_docs/sdk/INDEX.md +2 -1
- package/dist/client/delivery.d.ts +20 -7
- package/dist/client/delivery.js +248 -251
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +1 -0
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/rpc-pipeline.js +36 -12
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +2 -1
- package/dist/client/v2-e2ee.js +229 -97
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +18 -0
- package/dist/client.js +332 -19
- package/dist/client.js.map +1 -1
- package/dist/facades.d.ts +6 -0
- package/dist/facades.js +66 -0
- package/dist/facades.js.map +1 -1
- package/dist/seq-tracker.d.ts +2 -2
- package/dist/seq-tracker.js +11 -17
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +50 -20
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +4 -0
- package/dist/transport.js +141 -44
- package/dist/transport.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -124,7 +124,7 @@ const MAX_NOTIFY_PAYLOAD_SIZE = 64 * 1024;
|
|
|
124
124
|
// P1-23: 非幂等方法使用更长超时(35s),避免 SDK 10s 超时 < gateway 30s 处理时间
|
|
125
125
|
const NON_IDEMPOTENT_TIMEOUT_MS = 35_000;
|
|
126
126
|
const NON_IDEMPOTENT_METHODS = new Set([
|
|
127
|
-
'message.send', 'mail.send', 'group.send', 'group.create',
|
|
127
|
+
'message.send', 'mail.send', 'group.send', 'group.create',
|
|
128
128
|
'group.kick', 'group.remove_member', 'group.leave', 'group.dissolve',
|
|
129
129
|
'group.set_settings',
|
|
130
130
|
'group.update_announcement', 'group.update_rules',
|
|
@@ -150,6 +150,7 @@ const NON_IDEMPOTENT_METHODS = new Set([
|
|
|
150
150
|
'auth.create_aid', 'auth.renew_cert', 'auth.rekey',
|
|
151
151
|
'message.thought.put', 'group.thought.put',
|
|
152
152
|
'group.add_member', 'group.bind_group_aid', 'group.complete_transfer',
|
|
153
|
+
'group.accept_invite', 'group.reject_invite', 'group.revoke_invite',
|
|
153
154
|
'collab.create', 'collab.commit', 'collab.clone',
|
|
154
155
|
'collab.prune', 'collab.unregister',
|
|
155
156
|
'collab.tag.create', 'collab.tag.restore',
|
|
@@ -174,6 +175,8 @@ const SIGNED_METHODS = new Set([
|
|
|
174
175
|
'group.review_join_request',
|
|
175
176
|
'group.batch_review_join_request',
|
|
176
177
|
'group.request_join', 'group.use_invite_code',
|
|
178
|
+
'group.invite_member', 'group.list_my_invites', 'group.accept_invite',
|
|
179
|
+
'group.reject_invite', 'group.revoke_invite',
|
|
177
180
|
'group.thought.put',
|
|
178
181
|
'message.thought.put',
|
|
179
182
|
'group.set_settings',
|
|
@@ -468,6 +471,9 @@ export class AUNClient {
|
|
|
468
471
|
_seqTrackerContext = null;
|
|
469
472
|
/** 惰性群同步:已同步过的 group_id 集合 */
|
|
470
473
|
_groupSynced = new Set();
|
|
474
|
+
/** 群定向邀请按 group_id + invitee_aid 聚合去重。 */
|
|
475
|
+
_groupInviteAggregates = new Map();
|
|
476
|
+
_groupInviteAggregatesLoaded = false;
|
|
471
477
|
/** P2P 撤回去重:原始 message_id -> 时间戳,保证应用层只回调一次 */
|
|
472
478
|
_messageRecallSeen = new Map();
|
|
473
479
|
/** 群撤回去重:group_id|sorted(message_ids) -> 时间戳,保证应用层只回调一次 */
|
|
@@ -680,6 +686,9 @@ export class AUNClient {
|
|
|
680
686
|
this._dispatcher.subscribe('_raw.group.message_recalled', (data) => this._safeAsync(this._onRawGroupMessageRecalled(data)));
|
|
681
687
|
// 群组变更事件:透传并触发 V2 state/SPK 维护
|
|
682
688
|
this._dispatcher.subscribe('_raw.group.changed', (data) => this._onRawGroupChanged(data));
|
|
689
|
+
// 定向邀请底层事件:按群 + 目标 AID 聚合后向应用层只提示一次。
|
|
690
|
+
this._dispatcher.subscribe('_raw.group.invite_created', (data) => this._onRawGroupInviteCreated(data));
|
|
691
|
+
this._dispatcher.subscribe('_raw.group.invite_finalized', (data) => this._onRawGroupInviteFinalized(data));
|
|
683
692
|
// V2 state proposal 服务平面事件:owner/admin 负责确认或重新提案
|
|
684
693
|
this._dispatcher.subscribe('_raw.group.v2.state_proposed', (data) => this._safeAsync(this._onV2StateProposed(data)));
|
|
685
694
|
this._dispatcher.subscribe('_raw.group.v2.state_retry_needed', (data) => this._safeAsync(this._onV2StateRetryNeeded(data)));
|
|
@@ -1068,6 +1077,62 @@ export class AUNClient {
|
|
|
1068
1077
|
await this._agentMdManager.upload(agentMd);
|
|
1069
1078
|
return true;
|
|
1070
1079
|
}
|
|
1080
|
+
async _checkIdentityAdmissionAfterConnect() {
|
|
1081
|
+
const aid = String(this._aid ?? this._currentAid?.aid ?? '').trim();
|
|
1082
|
+
if (!aid)
|
|
1083
|
+
return;
|
|
1084
|
+
try {
|
|
1085
|
+
const checked = await this._agentMdManager.check(aid);
|
|
1086
|
+
if (!checked.remote_found) {
|
|
1087
|
+
const content = this._agentMdManager.readContent(aid) || buildDefaultAgentMd(aid);
|
|
1088
|
+
await this._agentMdManager.upload(content);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
catch (exc) {
|
|
1092
|
+
this._clientLog.warn(`post-connect agent.md check failed: ${formatCaughtError(exc)}`);
|
|
1093
|
+
}
|
|
1094
|
+
const store = this._aidStore;
|
|
1095
|
+
if (!store)
|
|
1096
|
+
return;
|
|
1097
|
+
let listed;
|
|
1098
|
+
try {
|
|
1099
|
+
listed = await this.call('group.list_my', {});
|
|
1100
|
+
}
|
|
1101
|
+
catch (exc) {
|
|
1102
|
+
this._clientLog.warn(`post-connect group identity check failed: ${formatCaughtError(exc)}`);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const result = isJsonObject(listed) ? listed : {};
|
|
1106
|
+
const groups = Array.isArray(result.groups) ? result.groups : (Array.isArray(result.items) ? result.items : []);
|
|
1107
|
+
for (const raw of groups) {
|
|
1108
|
+
if (!isJsonObject(raw))
|
|
1109
|
+
continue;
|
|
1110
|
+
const group = raw;
|
|
1111
|
+
const role = String(group.role ?? group.my_role ?? '').trim().toLowerCase();
|
|
1112
|
+
if (role !== 'owner')
|
|
1113
|
+
continue;
|
|
1114
|
+
const groupId = String(group.group_id ?? group.groupId ?? group.group_aid ?? group.groupAid ?? '').trim();
|
|
1115
|
+
const groupAid = String(group.group_aid ?? group.groupAid ?? groupId).trim();
|
|
1116
|
+
if (!groupId || !groupAid)
|
|
1117
|
+
continue;
|
|
1118
|
+
try {
|
|
1119
|
+
await this._runGroupIdentityOperation(groupId, async () => {
|
|
1120
|
+
const loaded = store.load(groupAid);
|
|
1121
|
+
if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid()) {
|
|
1122
|
+
await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
const checked = await this._agentMdManager.check(groupAid);
|
|
1126
|
+
if (!checked.remote_found) {
|
|
1127
|
+
await this._uploadGroupAgentMd(store, groupAid, {}, {});
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
catch (exc) {
|
|
1132
|
+
this._clientLog.warn(`post-connect group identity check failed: group=${groupId} err=${formatCaughtError(exc)}`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1071
1136
|
async _runGroupIdentityOperation(groupId, operation) {
|
|
1072
1137
|
const aid = String(this._aid ?? '').trim();
|
|
1073
1138
|
const dot = aid.indexOf('.');
|
|
@@ -1097,6 +1162,8 @@ export class AUNClient {
|
|
|
1097
1162
|
}
|
|
1098
1163
|
this._aidStore = store;
|
|
1099
1164
|
const payload = { ...params };
|
|
1165
|
+
const initialMembers = payload.members;
|
|
1166
|
+
delete payload.members;
|
|
1100
1167
|
if (payload.group_name === undefined && payload.groupName !== undefined) {
|
|
1101
1168
|
payload.group_name = payload.groupName;
|
|
1102
1169
|
delete payload.groupName;
|
|
@@ -1124,7 +1191,8 @@ export class AUNClient {
|
|
|
1124
1191
|
const result = { ...createdMap, ...boundMap, group: { ...createdGroup, ...boundGroup } };
|
|
1125
1192
|
const postprocessParams = { ...payload };
|
|
1126
1193
|
delete postprocessParams._defer_group_ready_postprocess;
|
|
1127
|
-
|
|
1194
|
+
const processed = await this._groupState.postprocessResult('group.create', postprocessParams, result);
|
|
1195
|
+
return await this._inviteInitialGroupMembers(processed, groupId, initialMembers);
|
|
1128
1196
|
}
|
|
1129
1197
|
const pendingKey = `create:${String(payload.group_name).trim().toLowerCase()}`;
|
|
1130
1198
|
const keystore = store._keystore;
|
|
@@ -1167,7 +1235,32 @@ export class AUNClient {
|
|
|
1167
1235
|
}
|
|
1168
1236
|
const postprocessParams = { ...payload };
|
|
1169
1237
|
delete postprocessParams._defer_group_ready_postprocess;
|
|
1170
|
-
|
|
1238
|
+
const processed = await this._groupState.postprocessResult('group.create', postprocessParams, result);
|
|
1239
|
+
return await this._inviteInitialGroupMembers(processed, String(group.group_id ?? groupAid).trim(), initialMembers);
|
|
1240
|
+
}
|
|
1241
|
+
async _inviteInitialGroupMembers(result, groupId, members) {
|
|
1242
|
+
const invites = [];
|
|
1243
|
+
const inviteErrors = [];
|
|
1244
|
+
const seen = new Set();
|
|
1245
|
+
if (Array.isArray(members)) {
|
|
1246
|
+
for (const item of members) {
|
|
1247
|
+
const aid = typeof item === 'string' ? item.trim() : (isJsonObject(item) ? String(item.aid ?? '').trim() : '');
|
|
1248
|
+
const memberType = isJsonObject(item) ? String(item.member_type ?? '').trim() : '';
|
|
1249
|
+
if (!aid || aid === String(this._aid ?? '').trim() || seen.has(aid))
|
|
1250
|
+
continue;
|
|
1251
|
+
seen.add(aid);
|
|
1252
|
+
const params = { group_id: groupId, invitee_aid: aid };
|
|
1253
|
+
if (memberType)
|
|
1254
|
+
params.member_type = memberType;
|
|
1255
|
+
try {
|
|
1256
|
+
invites.push(await this.group.inviteMember(params));
|
|
1257
|
+
}
|
|
1258
|
+
catch (err) {
|
|
1259
|
+
inviteErrors.push({ aid, error: err instanceof Error ? err.message : String(err) });
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return { ...(isJsonObject(result) ? result : {}), invites, invite_errors: inviteErrors };
|
|
1171
1264
|
}
|
|
1172
1265
|
_groupAgentMdContent(groupAid, params, group) {
|
|
1173
1266
|
const explicit = params.group_agent_md ?? params.groupAgentMd ?? params.content;
|
|
@@ -1949,8 +2042,8 @@ export class AUNClient {
|
|
|
1949
2042
|
async _publishOrderedMessage(event, ns, seq, payload, source = 'push') {
|
|
1950
2043
|
return this._delivery.publishOrderedMessage(event, ns, seq, payload, source);
|
|
1951
2044
|
}
|
|
1952
|
-
async _publishPulledMessage(event, ns, seq, payload, persist = true) {
|
|
1953
|
-
return this._delivery.publishPulledMessage(event, ns, seq, payload, persist);
|
|
2045
|
+
async _publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull') {
|
|
2046
|
+
return this._delivery.publishPulledMessage(event, ns, seq, payload, persist, source);
|
|
1954
2047
|
}
|
|
1955
2048
|
_markOrderedSeqDelivered(ns, seq) {
|
|
1956
2049
|
if (!ns || !Number.isFinite(seq) || !Number.isInteger(seq) || seq <= 0)
|
|
@@ -2325,6 +2418,225 @@ export class AUNClient {
|
|
|
2325
2418
|
_persistSeq(ns, forceSeq) {
|
|
2326
2419
|
return this._delivery.persistSeq(ns, forceSeq);
|
|
2327
2420
|
}
|
|
2421
|
+
static GROUP_INVITE_METADATA_KEY = 'group_invites_pending_v1';
|
|
2422
|
+
_groupInviteArray(value) {
|
|
2423
|
+
if (Array.isArray(value))
|
|
2424
|
+
return value.map((item) => String(item ?? '').trim()).filter(Boolean);
|
|
2425
|
+
const item = String(value ?? '').trim();
|
|
2426
|
+
return item ? [item] : [];
|
|
2427
|
+
}
|
|
2428
|
+
_groupInvitePayload(data) {
|
|
2429
|
+
if (!isJsonObject(data))
|
|
2430
|
+
return null;
|
|
2431
|
+
const source = data;
|
|
2432
|
+
const groupId = normalizeGroupId(source.group_id ?? source.group_aid ?? source.groupId ?? source.groupAid);
|
|
2433
|
+
const inviteeAid = String(source.invitee_aid ?? source.inviteeAid ?? this._aid ?? '').trim();
|
|
2434
|
+
if (!groupId || !inviteeAid)
|
|
2435
|
+
return null;
|
|
2436
|
+
if (this._aid && inviteeAid.toLowerCase() !== this._aid.toLowerCase())
|
|
2437
|
+
return null;
|
|
2438
|
+
const inviteIds = this._groupInviteArray(source.invite_ids ?? source.invite_id ?? source.inviteIds);
|
|
2439
|
+
const inviterAids = this._groupInviteArray(source.inviter_aids ?? source.inviter_aid ?? source.inviterAids);
|
|
2440
|
+
return {
|
|
2441
|
+
...source,
|
|
2442
|
+
group_id: groupId,
|
|
2443
|
+
group_aid: String(source.group_aid ?? groupId).trim() || groupId,
|
|
2444
|
+
invite_ids: inviteIds,
|
|
2445
|
+
inviter_aids: inviterAids,
|
|
2446
|
+
invitee_aid: inviteeAid,
|
|
2447
|
+
role: String(source.role ?? 'member'),
|
|
2448
|
+
member_type: String(source.member_type ?? 'human'),
|
|
2449
|
+
status: String(source.status ?? 'pending').toLowerCase() || 'pending',
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
_groupInviteKey(payload) {
|
|
2453
|
+
return `${String(payload.group_id ?? '').trim().toLowerCase()}\u0000${String(payload.invitee_aid ?? '').trim().toLowerCase()}`;
|
|
2454
|
+
}
|
|
2455
|
+
_loadGroupInviteAggregates() {
|
|
2456
|
+
if (this._groupInviteAggregatesLoaded)
|
|
2457
|
+
return;
|
|
2458
|
+
this._groupInviteAggregatesLoaded = true;
|
|
2459
|
+
const aid = String(this._aid ?? '').trim();
|
|
2460
|
+
const store = this._tokenStore;
|
|
2461
|
+
if (!aid || !store || typeof store.loadMetadata !== 'function')
|
|
2462
|
+
return;
|
|
2463
|
+
try {
|
|
2464
|
+
const raw = store.loadMetadata(aid)?.[AUNClient.GROUP_INVITE_METADATA_KEY];
|
|
2465
|
+
const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
2466
|
+
if (!isJsonObject(parsed))
|
|
2467
|
+
return;
|
|
2468
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
2469
|
+
if (isJsonObject(value) && String(value.status ?? 'pending').toLowerCase() === 'pending') {
|
|
2470
|
+
this._groupInviteAggregates.set(key, value);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
catch (exc) {
|
|
2475
|
+
this._clientLog.debug(`group invite metadata restore failed: ${formatCaughtError(exc)}`);
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
_persistGroupInviteAggregates() {
|
|
2479
|
+
const aid = String(this._aid ?? '').trim();
|
|
2480
|
+
const store = this._tokenStore;
|
|
2481
|
+
if (!aid || !store || typeof store.saveMetadata !== 'function')
|
|
2482
|
+
return;
|
|
2483
|
+
try {
|
|
2484
|
+
const values = {};
|
|
2485
|
+
for (const [key, value] of this._groupInviteAggregates)
|
|
2486
|
+
values[key] = value;
|
|
2487
|
+
store.saveMetadata(aid, { [AUNClient.GROUP_INVITE_METADATA_KEY]: JSON.stringify(values) });
|
|
2488
|
+
}
|
|
2489
|
+
catch (exc) {
|
|
2490
|
+
this._clientLog.debug(`group invite metadata persist failed: ${formatCaughtError(exc)}`);
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
async _onRawGroupInviteCreated(data) {
|
|
2494
|
+
await this._handleGroupInviteAggregate(data);
|
|
2495
|
+
}
|
|
2496
|
+
async _onRawGroupInviteFinalized(data) {
|
|
2497
|
+
const payload = this._groupInvitePayload(data);
|
|
2498
|
+
if (!payload)
|
|
2499
|
+
return;
|
|
2500
|
+
this._loadGroupInviteAggregates();
|
|
2501
|
+
const key = this._groupInviteKey(payload);
|
|
2502
|
+
const existing = this._groupInviteAggregates.get(key);
|
|
2503
|
+
if (!existing)
|
|
2504
|
+
return;
|
|
2505
|
+
const source = data;
|
|
2506
|
+
const status = String(payload.status ?? '').toLowerCase();
|
|
2507
|
+
const aggregateStatus = String(source.aggregate_status ?? source.aggregateStatus ?? '').toLowerCase();
|
|
2508
|
+
const remainingPending = source.remaining_pending === true
|
|
2509
|
+
|| source.remaining_pending === 1
|
|
2510
|
+
|| String(source.remaining_pending ?? '').toLowerCase() === 'true';
|
|
2511
|
+
const pending = source.pending === true
|
|
2512
|
+
|| source.pending === 1
|
|
2513
|
+
|| String(source.pending ?? '').toLowerCase() === 'true';
|
|
2514
|
+
const pendingIds = this._groupInviteArray(source.pending_invite_ids ?? source.pendingInviteIds);
|
|
2515
|
+
const pendingInviters = this._groupInviteArray(source.pending_inviter_aids ?? source.pendingInviterAids);
|
|
2516
|
+
const currentIds = this._groupInviteArray(existing.invite_ids);
|
|
2517
|
+
const inviteId = String(source.invite_id ?? '').trim();
|
|
2518
|
+
const inferredPartialRevoke = status === 'revoked' && inviteId
|
|
2519
|
+
&& currentIds.length > 1 && currentIds.includes(inviteId);
|
|
2520
|
+
const leavesPending = remainingPending || pending || status === 'pending'
|
|
2521
|
+
|| aggregateStatus === 'pending' || pendingIds.length > 0 || inferredPartialRevoke;
|
|
2522
|
+
if (!leavesPending) {
|
|
2523
|
+
this._groupInviteAggregates.delete(key);
|
|
2524
|
+
}
|
|
2525
|
+
else {
|
|
2526
|
+
const finalizedIds = new Set(remainingPending && inviteId
|
|
2527
|
+
? [inviteId]
|
|
2528
|
+
: this._groupInviteArray(payload.invite_ids));
|
|
2529
|
+
const nextIds = pendingIds.length > 0
|
|
2530
|
+
? pendingIds
|
|
2531
|
+
: this._groupInviteArray(existing.invite_ids).filter((id) => !finalizedIds.has(id));
|
|
2532
|
+
if (nextIds.length === 0) {
|
|
2533
|
+
this._groupInviteAggregates.delete(key);
|
|
2534
|
+
}
|
|
2535
|
+
else {
|
|
2536
|
+
existing.invite_ids = nextIds;
|
|
2537
|
+
existing.inviter_aids = pendingInviters.length > 0
|
|
2538
|
+
? pendingInviters
|
|
2539
|
+
: this._groupInviteArray(existing.inviter_aids);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
this._persistGroupInviteAggregates();
|
|
2543
|
+
}
|
|
2544
|
+
async _handleGroupInviteAggregate(data) {
|
|
2545
|
+
const payload = this._groupInvitePayload(data);
|
|
2546
|
+
if (!payload)
|
|
2547
|
+
return;
|
|
2548
|
+
this._loadGroupInviteAggregates();
|
|
2549
|
+
const status = String(payload.status ?? 'pending').toLowerCase();
|
|
2550
|
+
const key = this._groupInviteKey(payload);
|
|
2551
|
+
if (status !== 'pending') {
|
|
2552
|
+
if (this._groupInviteAggregates.delete(key))
|
|
2553
|
+
this._persistGroupInviteAggregates();
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
const existing = this._groupInviteAggregates.get(key);
|
|
2557
|
+
if (existing) {
|
|
2558
|
+
existing.invite_ids = [...new Set([
|
|
2559
|
+
...this._groupInviteArray(existing.invite_ids),
|
|
2560
|
+
...this._groupInviteArray(payload.invite_ids),
|
|
2561
|
+
])];
|
|
2562
|
+
existing.inviter_aids = [...new Set([
|
|
2563
|
+
...this._groupInviteArray(existing.inviter_aids),
|
|
2564
|
+
...this._groupInviteArray(payload.inviter_aids),
|
|
2565
|
+
])];
|
|
2566
|
+
this._persistGroupInviteAggregates();
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
const snapshot = {
|
|
2570
|
+
...payload,
|
|
2571
|
+
invite_ids: this._groupInviteArray(payload.invite_ids),
|
|
2572
|
+
inviter_aids: this._groupInviteArray(payload.inviter_aids),
|
|
2573
|
+
};
|
|
2574
|
+
this._groupInviteAggregates.set(key, snapshot);
|
|
2575
|
+
this._persistGroupInviteAggregates();
|
|
2576
|
+
this._dispatcher.enqueue('group.invite_received', { ...snapshot, invite_ids: [...this._groupInviteArray(snapshot.invite_ids)], inviter_aids: [...this._groupInviteArray(snapshot.inviter_aids)] });
|
|
2577
|
+
}
|
|
2578
|
+
/** 连接建立/恢复后从事实列表补发尚未提示的群级邀请。 */
|
|
2579
|
+
async _recoverGroupInvites(result) {
|
|
2580
|
+
let response = result;
|
|
2581
|
+
let cursor = '';
|
|
2582
|
+
const seen = new Set();
|
|
2583
|
+
do {
|
|
2584
|
+
if (response === undefined) {
|
|
2585
|
+
response = await this.call('group.list_my_invites', cursor ? { cursor } : {});
|
|
2586
|
+
}
|
|
2587
|
+
let items = [];
|
|
2588
|
+
let nextCursor = '';
|
|
2589
|
+
if (Array.isArray(response))
|
|
2590
|
+
items = response;
|
|
2591
|
+
else if (isJsonObject(response)) {
|
|
2592
|
+
const responseObject = response;
|
|
2593
|
+
if (Array.isArray(responseObject.items))
|
|
2594
|
+
items = responseObject.items;
|
|
2595
|
+
else if (Array.isArray(responseObject.invites))
|
|
2596
|
+
items = responseObject.invites;
|
|
2597
|
+
else if (isJsonObject(responseObject.data) && Array.isArray(responseObject.data.items))
|
|
2598
|
+
items = responseObject.data.items;
|
|
2599
|
+
nextCursor = String(responseObject.next_cursor ?? responseObject.nextCursor ?? responseObject.cursor ?? '');
|
|
2600
|
+
}
|
|
2601
|
+
for (const item of items) {
|
|
2602
|
+
const payload = this._groupInvitePayload(item);
|
|
2603
|
+
if (payload)
|
|
2604
|
+
seen.add(this._groupInviteKey(payload));
|
|
2605
|
+
await this._handleGroupInviteAggregate(item);
|
|
2606
|
+
}
|
|
2607
|
+
cursor = nextCursor;
|
|
2608
|
+
response = undefined;
|
|
2609
|
+
} while (cursor);
|
|
2610
|
+
this._loadGroupInviteAggregates();
|
|
2611
|
+
let stale = false;
|
|
2612
|
+
for (const key of this._groupInviteAggregates.keys()) {
|
|
2613
|
+
if (!seen.has(key)) {
|
|
2614
|
+
this._groupInviteAggregates.delete(key);
|
|
2615
|
+
stale = true;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
if (stale)
|
|
2619
|
+
this._persistGroupInviteAggregates();
|
|
2620
|
+
}
|
|
2621
|
+
/** 接受/拒绝/撤回成功后清理本地待处理聚合项。 */
|
|
2622
|
+
clearGroupInviteAggregate(params) {
|
|
2623
|
+
this._loadGroupInviteAggregates();
|
|
2624
|
+
const input = params ?? {};
|
|
2625
|
+
const inviteId = String(input.invite_id ?? input.inviteId ?? '').trim();
|
|
2626
|
+
const groupId = normalizeGroupId(input.group_id ?? input.group_aid ?? input.groupId ?? input.groupAid);
|
|
2627
|
+
const inviteeAid = String(input.invitee_aid ?? input.inviteeAid ?? this._aid ?? '').trim();
|
|
2628
|
+
let removed = false;
|
|
2629
|
+
for (const [key, value] of this._groupInviteAggregates) {
|
|
2630
|
+
const matchesKey = groupId && inviteeAid && key === `${groupId.toLowerCase()}\u0000${inviteeAid.toLowerCase()}`;
|
|
2631
|
+
const matchesInvite = inviteId && this._groupInviteArray(value.invite_ids).includes(inviteId);
|
|
2632
|
+
if (matchesKey || matchesInvite) {
|
|
2633
|
+
this._groupInviteAggregates.delete(key);
|
|
2634
|
+
removed = true;
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
if (removed)
|
|
2638
|
+
this._persistGroupInviteAggregates();
|
|
2639
|
+
}
|
|
2328
2640
|
_commitSeqTrackerState(ns) {
|
|
2329
2641
|
return this._delivery.commitSeqTrackerState(ns);
|
|
2330
2642
|
}
|
|
@@ -2338,19 +2650,10 @@ export class AUNClient {
|
|
|
2338
2650
|
return this._delivery.clampAckParams(method, params);
|
|
2339
2651
|
}
|
|
2340
2652
|
_repairPushContiguousBound(ns, pushSeq, hasPayload, label) {
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
const shouldRepair = contig > pushSeq;
|
|
2346
|
-
if (!shouldRepair)
|
|
2347
|
-
return contig;
|
|
2348
|
-
const repairedTo = Math.max(0, pushSeq - 1);
|
|
2349
|
-
this._seqTracker.repairContiguousSeq(ns, repairedTo);
|
|
2350
|
-
const repaired = this._seqTracker.getContiguousSeq(ns);
|
|
2351
|
-
this._persistRepairedSeq(ns);
|
|
2352
|
-
this._clientLog.warn(`${label} push repaired contiguous_seq: ns=${ns} payload=${hasPayload} push_seq=${pushSeq} contiguous=${contig}->${repaired}`);
|
|
2353
|
-
return repaired;
|
|
2653
|
+
void pushSeq;
|
|
2654
|
+
void hasPayload;
|
|
2655
|
+
void label;
|
|
2656
|
+
return ns ? this._seqTracker.getContiguousSeq(ns) : 0;
|
|
2354
2657
|
}
|
|
2355
2658
|
// ── URL 辅助 ──────────────────────────────────────────────
|
|
2356
2659
|
/** 跨域时将 Gateway URL 替换为 peer 所在域的 Gateway URL */
|
|
@@ -2502,6 +2805,12 @@ export class AUNClient {
|
|
|
2502
2805
|
const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params, 'background_sync');
|
|
2503
2806
|
const backgroundSyncEnabled = this._sessionOptions.background_sync !== false
|
|
2504
2807
|
&& (!isShortConnection || hasExplicitBackgroundSync);
|
|
2808
|
+
if (backgroundSyncEnabled) {
|
|
2809
|
+
// 连接建立/恢复后先从事实列表恢复定向邀请提示;失败不影响连接本身。
|
|
2810
|
+
this._safeAsync(this._recoverGroupInvites().catch((exc) => {
|
|
2811
|
+
this._clientLog.debug(`group invite recovery failed: ${formatCaughtError(exc)}`);
|
|
2812
|
+
}));
|
|
2813
|
+
}
|
|
2505
2814
|
if (!isShortConnection) {
|
|
2506
2815
|
await this._v2E2EE.onConnected({ backgroundSync: backgroundSyncEnabled });
|
|
2507
2816
|
this._assertReconnectOwner(reconnectOwner);
|
|
@@ -2517,6 +2826,7 @@ export class AUNClient {
|
|
|
2517
2826
|
this._clientLog.warn(`schedule post-connect P2P gap fill failed: ${formatCaughtError(exc)}`);
|
|
2518
2827
|
});
|
|
2519
2828
|
}
|
|
2829
|
+
this._safeAsync(this._checkIdentityAdmissionAfterConnect());
|
|
2520
2830
|
this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms gateway=${gatewayUrl}, aid=${this._aid ?? ''}`);
|
|
2521
2831
|
}
|
|
2522
2832
|
catch (err) {
|
|
@@ -2730,7 +3040,7 @@ export class AUNClient {
|
|
|
2730
3040
|
return await this._v2E2EE.ackGroupV2(groupId, upToSeq, groupAid);
|
|
2731
3041
|
}
|
|
2732
3042
|
/** 解密单条 V2 pull 消息。缺 sender IK 时先入 pending,后台补齐后重试。 */
|
|
2733
|
-
async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false) {
|
|
3043
|
+
async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull') {
|
|
2734
3044
|
const session = this._v2Session;
|
|
2735
3045
|
if (!session)
|
|
2736
3046
|
return null;
|
|
@@ -2823,6 +3133,7 @@ export class AUNClient {
|
|
|
2823
3133
|
_envelope_type: String(envelope.type ?? ''),
|
|
2824
3134
|
_suite: String(envelope.suite ?? ''),
|
|
2825
3135
|
_spk_id: spkId,
|
|
3136
|
+
source,
|
|
2826
3137
|
};
|
|
2827
3138
|
this._attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
2828
3139
|
this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
|
|
@@ -2862,6 +3173,7 @@ export class AUNClient {
|
|
|
2862
3173
|
_decrypt_stage: 'sender_ik',
|
|
2863
3174
|
_envelope_type: String(envelope.type ?? ''),
|
|
2864
3175
|
_suite: String(envelope.suite ?? ''),
|
|
3176
|
+
source,
|
|
2865
3177
|
};
|
|
2866
3178
|
this._attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
2867
3179
|
this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
|
|
@@ -2891,6 +3203,7 @@ export class AUNClient {
|
|
|
2891
3203
|
_decrypt_stage: 'decrypt',
|
|
2892
3204
|
_envelope_type: String(envelope.type ?? ''),
|
|
2893
3205
|
_suite: String(envelope.suite ?? ''),
|
|
3206
|
+
source,
|
|
2894
3207
|
};
|
|
2895
3208
|
this._attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
2896
3209
|
this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
|