@agentunion/fastaun 0.5.19 → 0.5.21
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 +176 -78
- package/_packed_docs/CHANGELOG.md +176 -78
- package/_packed_docs/INDEX.md +288 -269
- package/_packed_docs/KITE_DOCS_GUIDE.md +101 -95
- package/_packed_docs/agent.md/SCHEMA.md +7 -7
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
- package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
- package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
- 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 +11 -11
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +372 -331
- package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +676 -676
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +8 -12
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +445 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
- package/_packed_docs/protocol/agent.md/SCHEMA.md +1 -1
- package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
- package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +70 -63
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +43 -40
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +179 -19
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +50 -49
- package/_packed_docs/sdk/09-group-rpc-manual.md +261 -254
- package/_packed_docs/sdk/09-message-rpc-manual.md +253 -160
- package/_packed_docs/sdk/09-storage-rpc-manual.md +18 -26
- package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -0
- package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +88 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +105 -92
- package/_packed_docs/sdk/INDEX.md +197 -167
- package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -0
- package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
- package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +363 -0
- package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +553 -0
- package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -0
- package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
- package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
- package/dist/agent-md-schema.d.ts +13 -1
- package/dist/agent-md-schema.js +63 -33
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +30 -3
- package/dist/agent-md.js +293 -152
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +42 -0
- package/dist/aid-store.js +224 -44
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.js +18 -13
- package/dist/aid.js.map +1 -1
- package/dist/auth.js +78 -2
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +44 -1
- package/dist/cert-utils.js +157 -56
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +68 -1
- package/dist/client/delivery.js +1196 -135
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +5 -1
- package/dist/client/rpc-pipeline.js +240 -111
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +12 -2
- package/dist/client/v2-e2ee.js +842 -379
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +26 -2
- package/dist/client.js +420 -75
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +23 -4
- package/dist/events.js +171 -15
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +43 -1
- package/dist/facades.js +368 -12
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +7 -2
- package/dist/keystore/aid-db.js +194 -43
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +15 -1
- package/dist/keystore/local-token-store.d.ts +10 -2
- package/dist/keystore/local-token-store.js +81 -16
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +45 -0
- package/dist/org-protocol.js +829 -22
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +40 -9
- package/dist/ownership-v21.js +174 -79
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +22 -0
- package/dist/ownership-workflow.js +861 -66
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/secret-store/file-store.js +84 -0
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.js +2 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +67 -3
- package/dist/tools/cross-sdk-agent.js +575 -42
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +4 -0
- package/dist/transport.js +108 -44
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/v2/e2ee/encrypt-group.js +1 -0
- package/dist/v2/e2ee/encrypt-group.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +1 -0
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
- package/dist/v2/e2ee/types.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -26,7 +26,7 @@ import { normalizeGroupId } from './group-id.js';
|
|
|
26
26
|
import { validateAIDFormat, validateGroupIDFormat } from './validators.js';
|
|
27
27
|
import { RPCTransport } from './transport.js';
|
|
28
28
|
import { AuthFlow } from './auth.js';
|
|
29
|
-
import { AgentMdManager, buildDefaultAgentMd
|
|
29
|
+
import { AgentMdManager, buildDefaultAgentMd } from './agent-md.js';
|
|
30
30
|
import { GroupIndexMetaCache } from './group-index.js';
|
|
31
31
|
import { SeqTracker } from './seq-tracker.js';
|
|
32
32
|
import { ClientRuntime } from './client/runtime.js';
|
|
@@ -37,7 +37,7 @@ import { LifecycleController } from './client/lifecycle.js';
|
|
|
37
37
|
import { PeerDirectory } from './client/peers.js';
|
|
38
38
|
import { RpcPipeline } from './client/rpc-pipeline.js';
|
|
39
39
|
import { CollabClient } from './collab/client.js';
|
|
40
|
-
import { CredentialFacade, OrgFacade, GroupFacade, MailFacade, MessageFacade, StreamFacade } from './facades.js';
|
|
40
|
+
import { DebugFacade, CredentialFacade, OrgFacade, GroupFacade, MailFacade, MessageFacade, StreamFacade } from './facades.js';
|
|
41
41
|
import { StorageVFS } from './storage/vfs.js';
|
|
42
42
|
import { V2E2EECoordinator } from './client/v2-e2ee.js';
|
|
43
43
|
import { GroupStateCoordinator } from './client/group-state.js';
|
|
@@ -47,10 +47,10 @@ import { decryptedOwnershipJsonText } from './v2/e2ee/decrypt.js';
|
|
|
47
47
|
import { isJsonObject, ConnectionState, STATE_TO_PUBLIC, } from './types.js';
|
|
48
48
|
import { AID } from './aid.js';
|
|
49
49
|
import { certFingerprint, certMatchesFingerprint, normalizeFingerprintHex, publicKeyMatchesFingerprint } from './cert-utils.js';
|
|
50
|
-
import { validateAgentMdDocument } from './agent-md-schema.js';
|
|
51
|
-
import { OwnershipFiles, OwnershipStorageAdapter, ownershipFailure
|
|
50
|
+
import { validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
|
|
51
|
+
import { OwnershipFiles, OwnershipStorageAdapter, ownershipFailure } from './ownership-v21.js';
|
|
52
52
|
import { OwnershipWorkflow } from './ownership-workflow.js';
|
|
53
|
-
import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, verifyServiceEvent } from './org-protocol.js';
|
|
53
|
+
import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, digest, validateMembershipBinding, verifyMembershipBinding, verifyCredentialSignature, createRepresentationProof, verifyServiceEvent, buildRequestBinding, validateRequestBinding, CredentialRenewal } from './org-protocol.js';
|
|
54
54
|
function isPromiseLike(value) {
|
|
55
55
|
return Boolean(value && typeof value.then === 'function');
|
|
56
56
|
}
|
|
@@ -440,7 +440,7 @@ function createAgentMdManagerForRuntime(opts) {
|
|
|
440
440
|
const target = String(aid ?? '').trim();
|
|
441
441
|
const expectedFp = String(certFingerprint ?? '').trim().toLowerCase();
|
|
442
442
|
const current = opts.currentAid();
|
|
443
|
-
if (current?.aid === target) {
|
|
443
|
+
if (signedAt === undefined && current?.aid === target) {
|
|
444
444
|
if (!expectedFp || certMatchesFingerprint(current.certPem, expectedFp))
|
|
445
445
|
return current;
|
|
446
446
|
}
|
|
@@ -478,6 +478,7 @@ export class AUNClient {
|
|
|
478
478
|
/** 当前 AID 值对象(新 API) */
|
|
479
479
|
_currentAid = null;
|
|
480
480
|
_serviceEventIdentityVersion = 0;
|
|
481
|
+
_serviceEventIdentityAbort = new AbortController();
|
|
481
482
|
/** ownership 关系只在 SDK 内存中编排;服务端不保存状态机。 */
|
|
482
483
|
_ownershipRecords = new Map();
|
|
483
484
|
_ownershipType = 'agent';
|
|
@@ -485,7 +486,7 @@ export class AUNClient {
|
|
|
485
486
|
_ownershipFiles = null;
|
|
486
487
|
_ownershipWorkflow = null;
|
|
487
488
|
_getOwnershipStorage() {
|
|
488
|
-
return this._ownershipStorage ??= new OwnershipStorageAdapter({ currentAid: () => this._aid, call: (method, params) => this.call(method, params) });
|
|
489
|
+
return this._ownershipStorage ??= new OwnershipStorageAdapter({ currentAid: () => this._aid, call: (method, params) => this.call(method, params), httpPut: (url, data, headers) => this.storage.lowlevel.httpPut(url, data, headers) });
|
|
489
490
|
}
|
|
490
491
|
_getOwnershipFiles() {
|
|
491
492
|
return this._ownershipFiles ??= new OwnershipFiles({
|
|
@@ -494,10 +495,10 @@ export class AUNClient {
|
|
|
494
495
|
currentType: () => this.getType(),
|
|
495
496
|
now: () => Math.trunc(Date.now() / 1000),
|
|
496
497
|
readAgentMdFresh: (subAid) => this._agentMdManager.readOwnershipFresh(subAid),
|
|
497
|
-
submitAgentMdRaw: (subAid, raw, condition) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition),
|
|
498
|
+
submitAgentMdRaw: (subAid, raw, condition, assertActive) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition, assertActive),
|
|
498
499
|
readOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().readOwnerMd(ownerAid, subAid),
|
|
499
|
-
writeOwnerMd: (ownerAid, subAid, raw,
|
|
500
|
-
deleteOwnerMd: (ownerAid, subAid
|
|
500
|
+
writeOwnerMd: (ownerAid, subAid, raw, original, assertActive) => this._getOwnershipStorage().writeOwnerMd(ownerAid, subAid, raw, original, assertActive),
|
|
501
|
+
deleteOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().deleteOwnerMd(ownerAid, subAid),
|
|
501
502
|
resolveCertificate: async (aid, fingerprint, purpose) => this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, purpose),
|
|
502
503
|
});
|
|
503
504
|
}
|
|
@@ -549,6 +550,188 @@ export class AUNClient {
|
|
|
549
550
|
await this._resumeRegistrationIntent();
|
|
550
551
|
await this._resumeOwnership();
|
|
551
552
|
}
|
|
553
|
+
_credentialRenewal;
|
|
554
|
+
_credentialRenewalTimer = null;
|
|
555
|
+
_credentialRenewalAbort = null;
|
|
556
|
+
_credentialRenewalScan = Promise.resolve();
|
|
557
|
+
_getCredentialRenewal() {
|
|
558
|
+
return this._credentialRenewal ??= new CredentialRenewal({
|
|
559
|
+
currentAid: () => this._aid ?? '',
|
|
560
|
+
atomicCheckpointSave: () => this._tokenStore instanceof LocalTokenStore,
|
|
561
|
+
load: (aid) => (this._tokenStore._loadMetadataStrict?.(aid) ?? this._tokenStore.loadMetadata?.(aid))?.credential_renewals_v1 ?? {},
|
|
562
|
+
...(typeof this._tokenStore._loadMetadataStrict === 'function' ? {
|
|
563
|
+
loadRevocations: (aid) => this._tokenStore._loadMetadataStrict(aid)?.credential_revocations_v1 ?? {},
|
|
564
|
+
saveRevocations: (aid, state, guard) => {
|
|
565
|
+
if (guard && !guard.current())
|
|
566
|
+
throw new ValidationError('credential revocation synchronization was superseded');
|
|
567
|
+
this._tokenStore.saveMetadata?.(aid, { credential_revocations_v1: state });
|
|
568
|
+
},
|
|
569
|
+
} : {}),
|
|
570
|
+
save: (aid, records, guard) => {
|
|
571
|
+
const version = this._serviceEventIdentityVersion;
|
|
572
|
+
const current = () => version === this._serviceEventIdentityVersion && aid === this._aid && (!guard || guard.current());
|
|
573
|
+
if (!current())
|
|
574
|
+
throw new ValidationError('membership event receiver identity changed');
|
|
575
|
+
const saved = this._tokenStore instanceof LocalTokenStore ? records : Object.fromEntries(Object.entries(records).map(([cid, value]) => {
|
|
576
|
+
const record = { ...value };
|
|
577
|
+
for (const key of ['submitted_at', 'submission_hash', 'registration_submitted_at', 'registration_submission_hash'])
|
|
578
|
+
delete record[key];
|
|
579
|
+
return [cid, record];
|
|
580
|
+
}));
|
|
581
|
+
this._tokenStore.saveMetadata?.(aid, { credential_renewals_v1: saved });
|
|
582
|
+
if (!current())
|
|
583
|
+
throw new ValidationError('membership event receiver identity changed');
|
|
584
|
+
},
|
|
585
|
+
signer: (aid, guard) => this._credentialRenewalSigner(aid, guard),
|
|
586
|
+
certificate: (aid, fingerprint, signedAt) => signedAt === undefined
|
|
587
|
+
? this._fetchServiceEventCertificate(aid, fingerprint) : this._fetchPeerCert(aid, fingerprint, undefined, signedAt),
|
|
588
|
+
call: (method, params, onSent) => {
|
|
589
|
+
if (!onSent)
|
|
590
|
+
return this._rpcPipeline.call(method, params);
|
|
591
|
+
const version = this._serviceEventIdentityVersion, aid = this._aid;
|
|
592
|
+
return this._rpcPipeline['_callWithSent'](method, params, async () => {
|
|
593
|
+
if (version !== this._serviceEventIdentityVersion || aid !== this._aid)
|
|
594
|
+
throw new ValidationError('credential sender identity changed');
|
|
595
|
+
await onSent();
|
|
596
|
+
});
|
|
597
|
+
},
|
|
598
|
+
send: (to, payload) => this.call('message.send', { to, encrypt: true, payload }),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
_assertCredentialNotRevoked(credential, proofChain) {
|
|
602
|
+
const version = this._serviceEventIdentityVersion, aid = this._aid;
|
|
603
|
+
const current = () => { if (aid !== this._aid || version !== this._serviceEventIdentityVersion)
|
|
604
|
+
throw new ValidationError('credential receiver identity changed'); };
|
|
605
|
+
this._getCredentialRenewal()._assertNotRevoked(credential, proofChain);
|
|
606
|
+
current();
|
|
607
|
+
return current;
|
|
608
|
+
}
|
|
609
|
+
async _credentialRenewalSigner(aid, guard) {
|
|
610
|
+
const check = () => { if (guard && !guard.current())
|
|
611
|
+
throw new ValidationError('credential renewal was superseded'); };
|
|
612
|
+
check();
|
|
613
|
+
const stored = this._aidStore?.load(aid);
|
|
614
|
+
let identity = aid === this._aid && this._identity?.private_key_pem
|
|
615
|
+
? AID._create({ aid, aunPath: this._configModel.aunPath, certPem: String(this._identity.cert ?? this._currentAid?.certPem ?? ''), privateKeyPem: String(this._identity.private_key_pem), certValid: true, privateKeyValid: true })
|
|
616
|
+
: stored?.ok ? stored.data.aid : undefined;
|
|
617
|
+
if (!identity?.isPrivateKeyValid())
|
|
618
|
+
return null;
|
|
619
|
+
const certificate = new crypto.X509Certificate(identity.certPem);
|
|
620
|
+
const end = Date.parse(certificate.validTo), start = Date.parse(certificate.validFrom);
|
|
621
|
+
if (end > Date.now() && end - Date.now() < (end - start) / 2 && this._aidStore) {
|
|
622
|
+
const renewed = await (guard ? this._aidStore._renewCert(aid, guard) : this._aidStore.renewCert(aid));
|
|
623
|
+
check();
|
|
624
|
+
if (!renewed.ok)
|
|
625
|
+
throw new ValidationError(renewed.error.message);
|
|
626
|
+
const loaded = this._aidStore.load(aid);
|
|
627
|
+
if (!loaded.ok)
|
|
628
|
+
throw new ValidationError(loaded.error.message);
|
|
629
|
+
identity = loaded.data.aid;
|
|
630
|
+
if (aid === this._aid && this._identity) {
|
|
631
|
+
this._identity.cert = identity.certPem;
|
|
632
|
+
this._currentAid = identity;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return identity;
|
|
636
|
+
}
|
|
637
|
+
_currentCredential(input) {
|
|
638
|
+
const records = this._tokenStore.loadMetadata?.(this._aid ?? '')?.credential_renewals_v1 ?? {};
|
|
639
|
+
let cid = typeof input === 'string' ? input : String(input.cid);
|
|
640
|
+
const seen = new Set();
|
|
641
|
+
while (records[cid]?.successor_cid && !seen.has(cid)) {
|
|
642
|
+
seen.add(cid);
|
|
643
|
+
cid = String(records[cid].successor_cid);
|
|
644
|
+
}
|
|
645
|
+
return records[cid] ? structuredClone(records[cid]) : null;
|
|
646
|
+
}
|
|
647
|
+
_credentialCurrent(input) {
|
|
648
|
+
return this._getCredentialRenewal().current(input);
|
|
649
|
+
}
|
|
650
|
+
async _resumeCredentialRenewalAfterConnect(guard) {
|
|
651
|
+
if (!this._aid || (guard && !guard.current()))
|
|
652
|
+
return;
|
|
653
|
+
await this._safeAsync(guard ? this._credentialRenewalSigner(this._aid, guard) : this._credentialRenewalSigner(this._aid));
|
|
654
|
+
if (!guard || guard.current())
|
|
655
|
+
await this._safeAsync(this._getCredentialRenewal()._syncRevocations?.(guard) ?? Promise.resolve());
|
|
656
|
+
if (!guard || guard.current())
|
|
657
|
+
await this._getCredentialRenewal().resume(guard);
|
|
658
|
+
}
|
|
659
|
+
_startCredentialRenewal() {
|
|
660
|
+
this._stopCredentialRenewal();
|
|
661
|
+
const aid = this._aid, version = this._serviceEventIdentityVersion;
|
|
662
|
+
if (!aid || !['ready', 'connected'].includes(this._state) || this._closing || this._disconnecting)
|
|
663
|
+
return;
|
|
664
|
+
const abort = new AbortController();
|
|
665
|
+
this._credentialRenewalAbort = abort;
|
|
666
|
+
const current = () => this._credentialRenewalAbort === abort && !abort.signal.aborted && aid === this._aid && version === this._serviceEventIdentityVersion && ['ready', 'connected'].includes(this._state) && !this._closing && !this._disconnecting;
|
|
667
|
+
const guard = { current, signal: abort.signal };
|
|
668
|
+
const scan = async () => {
|
|
669
|
+
const task = this._credentialRenewalScan.then(async () => {
|
|
670
|
+
if (!current())
|
|
671
|
+
return;
|
|
672
|
+
await this._resumeCredentialRenewalAfterConnect(guard);
|
|
673
|
+
});
|
|
674
|
+
this._credentialRenewalScan = task.catch(() => { });
|
|
675
|
+
await this._safeAsync(task);
|
|
676
|
+
if (current())
|
|
677
|
+
this._credentialRenewalTimer = setTimeout(() => { void scan(); }, 60_000);
|
|
678
|
+
};
|
|
679
|
+
void scan();
|
|
680
|
+
}
|
|
681
|
+
_stopCredentialRenewal() {
|
|
682
|
+
this._credentialRenewalAbort?.abort();
|
|
683
|
+
this._credentialRenewalAbort = null;
|
|
684
|
+
if (this._credentialRenewalTimer !== null)
|
|
685
|
+
clearTimeout(this._credentialRenewalTimer);
|
|
686
|
+
this._credentialRenewalTimer = null;
|
|
687
|
+
}
|
|
688
|
+
_rememberCredential(credential, material, certificate) {
|
|
689
|
+
if (!material.long_term && certificate) {
|
|
690
|
+
const cert = new crypto.X509Certificate(typeof certificate === 'string' && !certificate.includes('-----BEGIN CERTIFICATE-----') ? Buffer.from(certificate, 'base64') : certificate);
|
|
691
|
+
const parent = Array.isArray(credential.proof_chain) ? credential.proof_chain[0] : undefined;
|
|
692
|
+
const certificateEnd = Math.floor(Date.parse(cert.validTo) / 1000);
|
|
693
|
+
const defaultEnd = credential.parent != null && isJsonObject(parent) && typeof parent.exp === 'number'
|
|
694
|
+
? Math.min(certificateEnd, Number(parent.exp)) : certificateEnd;
|
|
695
|
+
material = { ...material, long_term: credential.exp === defaultEnd };
|
|
696
|
+
}
|
|
697
|
+
this._safeAsync(this._getCredentialRenewal().remember(credential, material));
|
|
698
|
+
}
|
|
699
|
+
async _verifyIncomingServiceEvent(event, payload) {
|
|
700
|
+
const targetAid = this._identity?.aid ?? '';
|
|
701
|
+
const version = this._serviceEventIdentityVersion;
|
|
702
|
+
const signal = this._serviceEventIdentityAbort.signal;
|
|
703
|
+
const expiresAt = Number(payload?.expires_at);
|
|
704
|
+
const current = () => version === this._serviceEventIdentityVersion && targetAid === (this._identity?.aid ?? '') && Date.now() / 1000 < expiresAt;
|
|
705
|
+
const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (aid, fingerprint) => this._fetchServiceEventCertificate(aid, fingerprint), resolveCredentialService: this.resolveCredentialService });
|
|
706
|
+
if (!current())
|
|
707
|
+
throw new ValidationError('service event receiver identity changed');
|
|
708
|
+
if (event.replace(/^event\//, '') === 'org.member.changed' && isJsonObject(payload) && Object.hasOwn(payload, 'membership_package')) {
|
|
709
|
+
await this._getCredentialRenewal().importMembershipPackage(payload, { current, signal });
|
|
710
|
+
if (!current())
|
|
711
|
+
throw new ValidationError('service event receiver identity changed');
|
|
712
|
+
}
|
|
713
|
+
return verified;
|
|
714
|
+
}
|
|
715
|
+
async _handleCredentialRenewal(message) {
|
|
716
|
+
if (!isJsonObject(message))
|
|
717
|
+
return false;
|
|
718
|
+
const envelope = message;
|
|
719
|
+
const content = 'payload' in envelope ? envelope.payload : envelope;
|
|
720
|
+
const payload = decryptedOwnershipJsonText(content) ?? content;
|
|
721
|
+
let parsed = payload;
|
|
722
|
+
if (typeof payload === 'string') {
|
|
723
|
+
try {
|
|
724
|
+
parsed = JSON.parse(payload);
|
|
725
|
+
}
|
|
726
|
+
catch {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (!isJsonObject(parsed) || !['credential.renewal.request', 'credential.renewal.result'].includes(String(parsed.type)))
|
|
731
|
+
return false;
|
|
732
|
+
await this._safeAsync(this._getCredentialRenewal().receive(String(envelope.from ?? envelope.from_aid ?? envelope.sender_aid ?? ''), parsed));
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
552
735
|
async _resumeRegistrationIntent() {
|
|
553
736
|
const intent = this._tokenStore.loadMetadata?.(this._aid)?.registration_intent;
|
|
554
737
|
if (['group', 'org'].includes(String(intent?.type ?? this.getType())))
|
|
@@ -598,11 +781,13 @@ export class AUNClient {
|
|
|
598
781
|
_defaultConnectDeliveryMode;
|
|
599
782
|
/** peer 证书缓存 */
|
|
600
783
|
_certCache = new Map();
|
|
784
|
+
_certFetchInFlight = new Map();
|
|
601
785
|
_agentMdManager;
|
|
602
786
|
_aidStore;
|
|
603
787
|
_groupIdentityOperations = new Map();
|
|
604
788
|
_groupIndexMetaCache = new GroupIndexMetaCache();
|
|
605
789
|
/** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
|
|
790
|
+
_p2pPeerPullEnabled = true;
|
|
606
791
|
_seqTracker = new SeqTracker();
|
|
607
792
|
_seqTrackerContext = null;
|
|
608
793
|
/** 惰性群同步:已同步过的 group_id 集合 */
|
|
@@ -685,6 +870,7 @@ export class AUNClient {
|
|
|
685
870
|
_storage;
|
|
686
871
|
_collab;
|
|
687
872
|
_message;
|
|
873
|
+
_debug;
|
|
688
874
|
_mail;
|
|
689
875
|
_group;
|
|
690
876
|
_stream;
|
|
@@ -725,7 +911,7 @@ export class AUNClient {
|
|
|
725
911
|
if (debugFlag) {
|
|
726
912
|
this._clientLog.info(`AUNClient initialized (debug=true, aunPath=${this._configModel.aunPath})`);
|
|
727
913
|
}
|
|
728
|
-
this._dispatcher = new EventDispatcher(this._logger.for('aun_core.events'), () => this._identity?.aid ?? '');
|
|
914
|
+
this._dispatcher = new EventDispatcher(this._logger.for('aun_core.events'), () => this._identity?.aid ?? '', () => this._ensureReadStateIdentity());
|
|
729
915
|
const dnsNet = new DnsResilientNet({
|
|
730
916
|
verifySsl: this._configModel.verifySsl,
|
|
731
917
|
logger: this._clientLog,
|
|
@@ -771,7 +957,7 @@ export class AUNClient {
|
|
|
771
957
|
},
|
|
772
958
|
auth: () => this._auth,
|
|
773
959
|
tokenStore: () => this._tokenStore,
|
|
774
|
-
fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt),
|
|
960
|
+
fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
|
|
775
961
|
resolveEntityOwner: (target, type) => this._resolveEntityAgentMdOwner(target, type),
|
|
776
962
|
fetchEntityIdentity: (target, type) => this._freshEntityCertificate(target, type),
|
|
777
963
|
fetchEntitySigner: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
|
|
@@ -784,9 +970,10 @@ export class AUNClient {
|
|
|
784
970
|
dnsNet,
|
|
785
971
|
});
|
|
786
972
|
this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
|
|
787
|
-
this._transport.setServiceEventVerifier((event, payload) =>
|
|
973
|
+
this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
|
|
788
974
|
this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
|
|
789
975
|
this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
|
|
976
|
+
this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
|
|
790
977
|
this._runtime = new ClientRuntime(this);
|
|
791
978
|
this._identityRuntime = new IdentityRuntimeManager(this._runtime);
|
|
792
979
|
this._peerDirectory = new PeerDirectory(this._runtime);
|
|
@@ -816,7 +1003,8 @@ export class AUNClient {
|
|
|
816
1003
|
this._dispatcher.subscribeProtocol('_raw.message.read_changed', (data) => this._onRawReadChanged(data, 'p2p'));
|
|
817
1004
|
// V2 P2P 推送通知:收到通知后自动走 message.v2.pull 拉取并解密
|
|
818
1005
|
this._dispatcher.subscribeProtocol('_raw.peer.v2.message_received', (data) => {
|
|
819
|
-
const
|
|
1006
|
+
const sender = isJsonObject(data) ? String(data.sender_aid ?? '') : '';
|
|
1007
|
+
const ns = this._p2pPeerPullEnabled ? (sender ? this._delivery.peerNamespace(sender) : '') : this._aid ? `p2p:${this._aid}` : '';
|
|
820
1008
|
const hasInline = isJsonObject(data)
|
|
821
1009
|
&& isJsonObject(data.inline_message);
|
|
822
1010
|
const inlineGeneration = hasInline ? this._delivery.captureInlineAckGeneration() : undefined;
|
|
@@ -895,6 +1083,21 @@ export class AUNClient {
|
|
|
895
1083
|
this._collab = new CollabClient(this);
|
|
896
1084
|
return this._collab;
|
|
897
1085
|
}
|
|
1086
|
+
get debug() {
|
|
1087
|
+
if (!this._debug)
|
|
1088
|
+
this._debug = new DebugFacade(this, async (operation) => {
|
|
1089
|
+
const transport = this._transport;
|
|
1090
|
+
const previous = transport.getTraceMode();
|
|
1091
|
+
transport.setTraceMode('diag');
|
|
1092
|
+
try {
|
|
1093
|
+
return await operation();
|
|
1094
|
+
}
|
|
1095
|
+
finally {
|
|
1096
|
+
transport.setTraceMode(previous);
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
return this._debug;
|
|
1100
|
+
}
|
|
898
1101
|
/** Message facade 入口 */
|
|
899
1102
|
get message() {
|
|
900
1103
|
if (!this._message)
|
|
@@ -973,6 +1176,7 @@ export class AUNClient {
|
|
|
973
1176
|
}
|
|
974
1177
|
async bindOwneraid(ownerAid) { return this._getOwnershipWorkflow().bindOwneraid(ownerAid); }
|
|
975
1178
|
async bindSubaid(subAid) { return this._getOwnershipWorkflow().bindSubaid(subAid); }
|
|
1179
|
+
async resetOwnershipTask() { return this._getOwnershipWorkflow().resetOwnershipTask(); }
|
|
976
1180
|
async releaseSubaid(subAid) { return this._getOwnershipWorkflow().releaseSubaid(subAid); }
|
|
977
1181
|
async transferSubaid(subAid, newOwnerAid) { return this._getOwnershipWorkflow().transferSubaid(subAid, newOwnerAid); }
|
|
978
1182
|
async checkOwnershipConsensus(subAid) { return this._getOwnershipWorkflow().checkOwnershipConsensus(subAid); }
|
|
@@ -1159,7 +1363,7 @@ export class AUNClient {
|
|
|
1159
1363
|
},
|
|
1160
1364
|
auth: () => this._auth,
|
|
1161
1365
|
tokenStore: () => this._tokenStore,
|
|
1162
|
-
fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt),
|
|
1366
|
+
fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
|
|
1163
1367
|
resolveEntityOwner: (target, type) => this._resolveEntityAgentMdOwner(target, type),
|
|
1164
1368
|
fetchEntityIdentity: (target, type) => this._freshEntityCertificate(target, type),
|
|
1165
1369
|
fetchEntitySigner: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
|
|
@@ -1172,14 +1376,18 @@ export class AUNClient {
|
|
|
1172
1376
|
dnsNet,
|
|
1173
1377
|
});
|
|
1174
1378
|
this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
|
|
1175
|
-
this._transport.setServiceEventVerifier((event, payload) =>
|
|
1379
|
+
this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
|
|
1176
1380
|
this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
|
|
1177
1381
|
this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
|
|
1382
|
+
this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
|
|
1178
1383
|
}
|
|
1179
1384
|
loadIdentity(aid) {
|
|
1180
1385
|
const previousAid = this._aid;
|
|
1181
1386
|
this._identityRuntime.loadIdentity(aid);
|
|
1387
|
+
this._stopCredentialRenewal();
|
|
1182
1388
|
this._serviceEventIdentityVersion++;
|
|
1389
|
+
this._serviceEventIdentityAbort.abort();
|
|
1390
|
+
this._serviceEventIdentityAbort = new AbortController();
|
|
1183
1391
|
this._ensureReadStateIdentity();
|
|
1184
1392
|
if (previousAid !== this._aid) {
|
|
1185
1393
|
this._ownershipRecords.clear();
|
|
@@ -1287,6 +1495,7 @@ export class AUNClient {
|
|
|
1287
1495
|
? this._ensureReadStateIdentity() : null;
|
|
1288
1496
|
try {
|
|
1289
1497
|
const result = await this._rpcPipeline.call(method, params);
|
|
1498
|
+
await this._safeAsync(this._getCredentialRenewal().observe(method, params ?? {}, result));
|
|
1290
1499
|
if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
|
|
1291
1500
|
this._observeReadStateResult(method, result, params ?? {});
|
|
1292
1501
|
return result;
|
|
@@ -1316,6 +1525,13 @@ export class AUNClient {
|
|
|
1316
1525
|
const groupId = String(params.group_id ?? params.groupId ?? params.group_aid ?? params.groupAid ?? fallback).trim();
|
|
1317
1526
|
if (!groupId || !store)
|
|
1318
1527
|
return false;
|
|
1528
|
+
const groupManager = store._agentMdManager ?? this._agentMdManager;
|
|
1529
|
+
const keystore = store._keystore;
|
|
1530
|
+
let pendingBind = keystore?.loadPendingGroupBind?.(`bind:${groupId}`);
|
|
1531
|
+
if (!pendingBind?.public_key_der_b64 || !pendingBind?.private_key_pem)
|
|
1532
|
+
pendingBind = keystore?.loadPendingGroupBind?.(groupId);
|
|
1533
|
+
if (groupManager.pendingUpload(fallback) && (!pendingBind?.public_key_der_b64 || !pendingBind?.private_key_pem))
|
|
1534
|
+
return false;
|
|
1319
1535
|
try {
|
|
1320
1536
|
await this._runGroupIdentityOperation(groupId, () => this.bindGroupAid({ group_id: groupId }, { aidStore: store }));
|
|
1321
1537
|
}
|
|
@@ -1333,6 +1549,11 @@ export class AUNClient {
|
|
|
1333
1549
|
const loaded = store.load(groupAid);
|
|
1334
1550
|
if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid())
|
|
1335
1551
|
return false;
|
|
1552
|
+
const groupManager = store._agentMdManager ?? this._agentMdManager;
|
|
1553
|
+
if (groupManager.pendingUpload(groupAid))
|
|
1554
|
+
return false;
|
|
1555
|
+
if (String((groupManager._loadRecord?.(groupAid, true) ?? groupManager.loadRecord(groupAid))?.local_draft_content ?? ''))
|
|
1556
|
+
return false;
|
|
1336
1557
|
await this._runGroupIdentityOperation(groupAid, () => this._uploadGroupAgentMd(store, groupAid, {}, {}));
|
|
1337
1558
|
return true;
|
|
1338
1559
|
}
|
|
@@ -1348,17 +1569,15 @@ export class AUNClient {
|
|
|
1348
1569
|
const aid = this._aid;
|
|
1349
1570
|
if (!aid || !this._currentAid)
|
|
1350
1571
|
return false;
|
|
1351
|
-
const observed = await this.
|
|
1352
|
-
if (observed.exists)
|
|
1572
|
+
const observed = await this._agentMdManager.readAgentMdFresh(aid);
|
|
1573
|
+
if (observed.exists || String(this._agentMdManager._loadRecord?.(aid, true)?.local_draft_content ?? this._agentMdManager.loadRecord(aid)?.local_draft_content ?? '') || this._agentMdManager.pendingUpload(aid))
|
|
1353
1574
|
return false;
|
|
1354
1575
|
let agentMd = buildDefaultAgentMd(aid, { type: this.getType() });
|
|
1355
1576
|
try {
|
|
1356
1577
|
agentMd = this._agentMdManager.readContent(aid) || agentMd;
|
|
1357
1578
|
}
|
|
1358
1579
|
catch { }
|
|
1359
|
-
|
|
1360
|
-
const raw = signOwnershipFile(this._currentAid, payload.endsWith('\n') || payload.endsWith('\r') ? payload : `${payload}\n`, 'agent');
|
|
1361
|
-
await this._agentMdManager.submitOwnershipRaw(aid, raw, { if_none_match: '*' });
|
|
1580
|
+
await this._agentMdManager.upload(agentMd, { if_none_match: '*' });
|
|
1362
1581
|
return true;
|
|
1363
1582
|
}
|
|
1364
1583
|
async _checkIdentityAdmissionAfterConnect() {
|
|
@@ -1412,13 +1631,18 @@ export class AUNClient {
|
|
|
1412
1631
|
await this.completeGroupTransfer({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
|
|
1413
1632
|
return;
|
|
1414
1633
|
}
|
|
1634
|
+
const groupManager = store._agentMdManager ?? this._agentMdManager;
|
|
1635
|
+
if (groupManager.pendingUpload(groupAid))
|
|
1636
|
+
return;
|
|
1637
|
+
if (String((groupManager._loadRecord?.(groupAid, true) ?? groupManager.loadRecord(groupAid))?.local_draft_content ?? '')) {
|
|
1638
|
+
await this._agentMdManager.check(groupAid);
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1415
1641
|
const loaded = store.load(groupAid);
|
|
1416
1642
|
if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid()) {
|
|
1417
1643
|
await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
|
|
1418
1644
|
return;
|
|
1419
1645
|
}
|
|
1420
|
-
if (await this._resumeGroupAgentMdUpload(store, groupAid))
|
|
1421
|
-
return;
|
|
1422
1646
|
const checked = await this._agentMdManager.check(groupAid);
|
|
1423
1647
|
if (!checked.remote_found) {
|
|
1424
1648
|
await this._uploadGroupAgentMd(store, groupAid, {}, {});
|
|
@@ -1449,12 +1673,45 @@ export class AUNClient {
|
|
|
1449
1673
|
this._groupIdentityOperations.delete(key);
|
|
1450
1674
|
}
|
|
1451
1675
|
}
|
|
1676
|
+
async _resumeOrgRequest(method, params, target) {
|
|
1677
|
+
const binding = params.request_binding;
|
|
1678
|
+
const now = Math.trunc(Date.now() / 1000);
|
|
1679
|
+
if (!isJsonObject(binding) || now < Number(binding.exp))
|
|
1680
|
+
return params;
|
|
1681
|
+
const credentials = isJsonObject(params.credentials) ? params.credentials : {};
|
|
1682
|
+
const credential = ['member_credential', 'approval_credential', 'audit_credential'].map((field) => credentials[field]).find(isJsonObject) ?? params.credential_template;
|
|
1683
|
+
const cid = isJsonObject(credential) ? String(credential.cid) : '';
|
|
1684
|
+
validateRequestBinding(binding, method, target, params, cid, String(params.nonce), String(params.request_id), Number(binding.iat));
|
|
1685
|
+
let exp = now + 300;
|
|
1686
|
+
for (const material of [credential, ...Object.values(credentials).filter((value) => isJsonObject(value) && value.ver === 'aun-cap/1'), ...[params, params.previous_owner, params.new_owner].filter(isJsonObject).flatMap((value) => ['membership_binding', 'credential_template', 'registration_authorization'].map((field) => value[field]))]) {
|
|
1687
|
+
if (isJsonObject(material)) {
|
|
1688
|
+
if (!Number.isInteger(material.exp) || Number(material.exp) <= now)
|
|
1689
|
+
throw new ValidationError('Org recovery authorization material has expired');
|
|
1690
|
+
exp = Math.min(exp, Number(material.exp));
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
const refreshed = { ...params, request_binding: buildRequestBinding(method, target, params, cid, String(params.nonce), String(params.request_id), now, exp) };
|
|
1694
|
+
delete refreshed.client_signature;
|
|
1695
|
+
if (isJsonObject(credentials.representation_proof)) {
|
|
1696
|
+
if (!this._currentAid || !isJsonObject(credential))
|
|
1697
|
+
throw new ValidationError('Org recovery representation signer is unavailable');
|
|
1698
|
+
refreshed.credentials = { ...credentials, representation_proof: createRepresentationProof(credential, refreshed, refreshed.request_binding, this._currentAid, refreshed.proof_chain) };
|
|
1699
|
+
}
|
|
1700
|
+
return refreshed;
|
|
1701
|
+
}
|
|
1452
1702
|
async createOrg(params = {}, options = {}) {
|
|
1453
1703
|
const store = options.aidStore;
|
|
1454
1704
|
const owner = this._currentAid;
|
|
1455
1705
|
const orgAid = String(params.org_id ?? '').trim();
|
|
1456
1706
|
if (!store || !owner?.isPrivateKeyValid() || !orgAid)
|
|
1457
1707
|
throw new ValidationError('createOrg requires aidStore, org_id and current owner identity');
|
|
1708
|
+
const tokens = store._tokenStore;
|
|
1709
|
+
const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
|
|
1710
|
+
let checkpoint = (typeof tokens._loadMetadataStrict === 'function' ? tokens._loadMetadataStrict(orgAid) : tokens.loadMetadata(orgAid))?.org_identity_creation;
|
|
1711
|
+
if (checkpoint != null && !isJsonObject(checkpoint))
|
|
1712
|
+
throw ownershipFailure('persistence_unavailable', 'Org creation checkpoint is invalid');
|
|
1713
|
+
if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
|
|
1714
|
+
throw ownershipFailure('conflict', 'pending Org creation context changed');
|
|
1458
1715
|
this._aidStore = store;
|
|
1459
1716
|
let identity = store.load(orgAid);
|
|
1460
1717
|
if (!identity.ok) {
|
|
@@ -1466,18 +1723,13 @@ export class AUNClient {
|
|
|
1466
1723
|
if (!identity.ok)
|
|
1467
1724
|
throw ownershipFailure('unavailable', identity.error.message, true);
|
|
1468
1725
|
const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
|
|
1469
|
-
const tokens = store._tokenStore;
|
|
1470
|
-
const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
|
|
1471
|
-
let checkpoint = tokens.loadMetadata(orgAid)?.org_identity_creation;
|
|
1472
|
-
if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
|
|
1473
|
-
throw ownershipFailure('conflict', 'pending Org creation context changed');
|
|
1474
1726
|
if (!checkpoint) {
|
|
1475
1727
|
const prepared = this.org.prepareRequest('org.lifecycle.create', { ...params, owner_aid: owner.aid }, target, owner, { orgSigner: identity.data.aid });
|
|
1476
1728
|
checkpoint = { owner_aid: owner.aid, input_hash: inputHash, source_params: params, prepared, phase: 'create' };
|
|
1477
1729
|
tokens.saveMetadata(orgAid, { org_identity_creation: checkpoint });
|
|
1478
1730
|
}
|
|
1479
1731
|
if (checkpoint.phase === 'create') {
|
|
1480
|
-
const result = await this.call('org.lifecycle.create', checkpoint.prepared);
|
|
1732
|
+
const result = await this.call('org.lifecycle.create', await this._resumeOrgRequest('org.lifecycle.create', checkpoint.prepared, target));
|
|
1481
1733
|
checkpoint = { ...checkpoint, result, phase: 'upload' };
|
|
1482
1734
|
tokens.saveMetadata(orgAid, { org_identity_creation: checkpoint });
|
|
1483
1735
|
}
|
|
@@ -1491,9 +1743,11 @@ export class AUNClient {
|
|
|
1491
1743
|
if (typeof keyStore?.listIdentities !== 'function' || typeof tokens?.loadMetadata !== 'function')
|
|
1492
1744
|
return;
|
|
1493
1745
|
for (const aid of keyStore.listIdentities()) {
|
|
1494
|
-
const metadata = tokens.loadMetadata(aid);
|
|
1746
|
+
const metadata = typeof tokens._loadMetadataStrict === 'function' ? tokens._loadMetadataStrict(aid) : tokens.loadMetadata(aid);
|
|
1495
1747
|
const creation = metadata?.org_identity_creation;
|
|
1496
1748
|
const transfer = metadata?.org_identity_transfer;
|
|
1749
|
+
if (creation != null && !isJsonObject(creation) || transfer != null && !isJsonObject(transfer))
|
|
1750
|
+
throw ownershipFailure('persistence_unavailable', 'Org identity checkpoint is invalid');
|
|
1497
1751
|
try {
|
|
1498
1752
|
if (creation?.owner_aid === this._aid) {
|
|
1499
1753
|
await this.createOrg(creation.source_params, { aidStore: store });
|
|
@@ -1503,18 +1757,46 @@ export class AUNClient {
|
|
|
1503
1757
|
await this.completeOrgTransfer(transfer.source_params, { aidStore: store });
|
|
1504
1758
|
continue;
|
|
1505
1759
|
}
|
|
1506
|
-
const pending = store._agentMdManager.pendingUpload(aid);
|
|
1507
|
-
if (!pending)
|
|
1508
|
-
continue;
|
|
1509
|
-
const fields = validateAgentMdDocument(pending.content, { expectedAid: aid }).frontmatter;
|
|
1510
|
-
if (fields.type === 'org' && fields.owner === this._aid)
|
|
1511
|
-
await this._resumeGroupAgentMdUpload(store, aid);
|
|
1512
1760
|
}
|
|
1513
1761
|
catch (error) {
|
|
1514
1762
|
this._clientLog.warn(`post-connect Org identity recovery failed: aid=${aid} err=${formatCaughtError(error)}`);
|
|
1515
1763
|
}
|
|
1516
1764
|
}
|
|
1517
1765
|
}
|
|
1766
|
+
async _verifyOrgTransferMaterials(params, ownerAid) {
|
|
1767
|
+
const orgAid = String(params.org_id);
|
|
1768
|
+
const previous = params.previous_owner;
|
|
1769
|
+
const next = params.new_owner;
|
|
1770
|
+
const binding = previous.membership_binding;
|
|
1771
|
+
const nextBinding = next.membership_binding;
|
|
1772
|
+
const credential = previous.credential_template;
|
|
1773
|
+
const ext = isJsonObject(credential.ext) ? credential.ext : {};
|
|
1774
|
+
const capability = Array.isArray(credential.cap) && credential.cap.length === 1 && isJsonObject(credential.cap[0]) ? credential.cap[0] : {};
|
|
1775
|
+
const now = Math.trunc(Date.now() / 1000);
|
|
1776
|
+
if (binding.org_id !== orgAid || nextBinding.org_id !== orgAid || binding.member_aid === ownerAid || nextBinding.member_aid !== ownerAid
|
|
1777
|
+
|| binding.role !== 'member' || nextBinding.role !== 'owner' || digest(previous.terms) !== binding.terms_hash || digest(next.terms) !== nextBinding.terms_hash
|
|
1778
|
+
|| !Number.isSafeInteger(nextBinding.iat) || !Number.isSafeInteger(nextBinding.exp) || Number(nextBinding.iat) > now || Number(nextBinding.exp) <= now
|
|
1779
|
+
|| credential.iss !== orgAid || credential.sub !== binding.member_aid || credential.parent != null || credential.delegate !== false
|
|
1780
|
+
|| ext.credential_type !== 'member' || ext.bound_to_membership !== true || ext.org_id !== orgAid || ext.membership_id !== binding.membership_id
|
|
1781
|
+
|| ext.binding_version !== binding.binding_version || ext.membership_terms_hash !== binding.terms_hash
|
|
1782
|
+
|| capability.ns !== 'org' || capability.action !== 'role.member' || capability.resource !== orgAid)
|
|
1783
|
+
throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
|
|
1784
|
+
validateMembershipBinding(binding);
|
|
1785
|
+
const certificates = new Map();
|
|
1786
|
+
for (const [aid, signature] of [[binding.member_aid, binding.member_signature], [orgAid, binding.org_signature], [orgAid, credential.signature]]) {
|
|
1787
|
+
if (!isJsonObject(signature))
|
|
1788
|
+
throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
|
|
1789
|
+
const fingerprint = String(signature.cert_fingerprint);
|
|
1790
|
+
const certificate = await this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(String(aid)), String(aid), fingerprint);
|
|
1791
|
+
certificates.set(fingerprint, certificate.cert_pem);
|
|
1792
|
+
}
|
|
1793
|
+
const resolve = (fingerprint) => certificates.get(fingerprint);
|
|
1794
|
+
const checkedAt = Math.trunc(Date.now() / 1000);
|
|
1795
|
+
verifyMembershipBinding(binding, resolve, checkedAt, orgAid);
|
|
1796
|
+
verifyCredentialSignature(credential, resolve, checkedAt, orgAid);
|
|
1797
|
+
if (Number(nextBinding.exp) <= checkedAt)
|
|
1798
|
+
throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
|
|
1799
|
+
}
|
|
1518
1800
|
async completeOrgTransfer(params = {}, options = {}) {
|
|
1519
1801
|
const store = options.aidStore;
|
|
1520
1802
|
const owner = this._currentAid;
|
|
@@ -1522,20 +1804,33 @@ export class AUNClient {
|
|
|
1522
1804
|
const transferId = String(params.transfer_id ?? '').trim();
|
|
1523
1805
|
if (!store || !owner?.isPrivateKeyValid() || !orgAid || !transferId)
|
|
1524
1806
|
throw new ValidationError('completeOrgTransfer requires aidStore, org_id, transfer_id and current accepting owner');
|
|
1807
|
+
for (const field of ['previous_owner', 'new_owner']) {
|
|
1808
|
+
const material = params[field];
|
|
1809
|
+
if (!isJsonObject(material) || !isJsonObject(material.membership_binding) || !isJsonObject(material.terms) || field === 'previous_owner' && !isJsonObject(material.credential_template))
|
|
1810
|
+
throw ownershipFailure('invalid', 'Org transfer requires both replacement membership bindings, terms and previous-owner credential');
|
|
1811
|
+
}
|
|
1525
1812
|
this._aidStore = store;
|
|
1526
1813
|
const localStore = store._keystore;
|
|
1527
1814
|
const tokenStore = store._tokenStore;
|
|
1528
1815
|
const pendingKey = `org-transfer:${orgAid}:${transferId}`;
|
|
1529
1816
|
const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
|
|
1817
|
+
let pending = (typeof tokenStore._loadMetadataStrict === 'function' ? tokenStore._loadMetadataStrict(orgAid) : tokenStore.loadMetadata(orgAid))?.org_identity_transfer;
|
|
1818
|
+
if (pending != null && !isJsonObject(pending))
|
|
1819
|
+
throw ownershipFailure('persistence_unavailable', 'Org transfer checkpoint is invalid');
|
|
1820
|
+
if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.input_hash !== inputHash))
|
|
1821
|
+
throw ownershipFailure('conflict', 'pending Org transfer context changed');
|
|
1822
|
+
if (!pending)
|
|
1823
|
+
await this._verifyOrgTransferMaterials(params, owner.aid);
|
|
1530
1824
|
let keyPair = localStore.loadPendingGroupBind(pendingKey);
|
|
1531
1825
|
if (!keyPair) {
|
|
1826
|
+
if (pending)
|
|
1827
|
+
throw ownershipFailure('persistence_unavailable', 'Org transfer private key checkpoint is unavailable');
|
|
1532
1828
|
keyPair = new CryptoProvider().generateIdentity();
|
|
1533
1829
|
localStore.savePendingGroupBind(pendingKey, keyPair);
|
|
1534
1830
|
}
|
|
1535
1831
|
const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
|
|
1536
1832
|
const publicKey = crypto.createPublicKey({ key: Buffer.from(keyPair.public_key_der_b64, 'base64'), format: 'der', type: 'spki' }).export({ format: 'pem', type: 'spki' }).toString();
|
|
1537
|
-
|
|
1538
|
-
if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.public_key !== publicKey || pending.input_hash !== inputHash))
|
|
1833
|
+
if (pending && pending.public_key !== publicKey)
|
|
1539
1834
|
throw ownershipFailure('conflict', 'pending Org transfer context changed');
|
|
1540
1835
|
if (!pending) {
|
|
1541
1836
|
const rekey = { ...params, stage: 'rekey', public_key: publicKey, curve: 'P-256' };
|
|
@@ -1546,7 +1841,7 @@ export class AUNClient {
|
|
|
1546
1841
|
tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
|
|
1547
1842
|
}
|
|
1548
1843
|
if (pending.phase === 'rekey') {
|
|
1549
|
-
const result = await this.call('org.lifecycle.transferAccept', pending.rekey_params);
|
|
1844
|
+
const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.rekey_params, target));
|
|
1550
1845
|
pending = { ...pending, phase: 'finalize', rekey_result: result };
|
|
1551
1846
|
tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
|
|
1552
1847
|
}
|
|
@@ -1582,10 +1877,10 @@ export class AUNClient {
|
|
|
1582
1877
|
pending = { ...pending, finalize_params: this.org.prepareRequest('org.lifecycle.transferAccept', final, target, owner, { orgSigner: orgIdentity }) };
|
|
1583
1878
|
tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
|
|
1584
1879
|
}
|
|
1585
|
-
const result = await this.call('org.lifecycle.transferAccept', pending.finalize_params);
|
|
1880
|
+
const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.finalize_params, target));
|
|
1586
1881
|
await this._uploadOrgAgentMd(store, orgAid, pending.source_params);
|
|
1587
|
-
localStore.clearPendingGroupBind(pendingKey);
|
|
1588
1882
|
tokenStore.saveMetadata(orgAid, { org_identity_transfer: null });
|
|
1883
|
+
localStore.clearPendingGroupBind(pendingKey);
|
|
1589
1884
|
return result;
|
|
1590
1885
|
}
|
|
1591
1886
|
async dissolveOrg(params = {}, options = {}) {
|
|
@@ -2443,7 +2738,8 @@ export class AUNClient {
|
|
|
2443
2738
|
return;
|
|
2444
2739
|
this._readStateCache.set(key, { method, params: query, state: { ...eventData } });
|
|
2445
2740
|
}
|
|
2446
|
-
|
|
2741
|
+
const identity = this._ensureReadStateIdentity();
|
|
2742
|
+
this._dispatcher.enqueue(scope === 'group' ? 'group.read_changed' : 'message.read_changed', eventData, () => identity === this._ensureReadStateIdentity());
|
|
2447
2743
|
}
|
|
2448
2744
|
async _reconcileReadStates() {
|
|
2449
2745
|
const identity = this._ensureReadStateIdentity();
|
|
@@ -2475,7 +2771,7 @@ export class AUNClient {
|
|
|
2475
2771
|
owner_aid: this._aid ?? '',
|
|
2476
2772
|
states: [...keys].map((key) => ({ ...this._readStateCache.get(key).state })),
|
|
2477
2773
|
errors,
|
|
2478
|
-
});
|
|
2774
|
+
}, () => identity === this._ensureReadStateIdentity());
|
|
2479
2775
|
}
|
|
2480
2776
|
async _callRawV2Rpc(method, params, pullGateKey = '') {
|
|
2481
2777
|
const p = { ...(params ?? {}) };
|
|
@@ -2640,7 +2936,10 @@ export class AUNClient {
|
|
|
2640
2936
|
if (event === 'message.received') {
|
|
2641
2937
|
return this._handleOwnershipMessage(payload).then((handled) => {
|
|
2642
2938
|
if (!handled)
|
|
2643
|
-
return this.
|
|
2939
|
+
return this._handleCredentialRenewal(payload).then((renewed) => {
|
|
2940
|
+
if (!renewed)
|
|
2941
|
+
return this._delivery.publishAppEvent(event, payload, source, deliveryContext);
|
|
2942
|
+
});
|
|
2644
2943
|
return undefined;
|
|
2645
2944
|
});
|
|
2646
2945
|
}
|
|
@@ -3015,8 +3314,13 @@ export class AUNClient {
|
|
|
3015
3314
|
try {
|
|
3016
3315
|
if (opts.signedAt === undefined)
|
|
3017
3316
|
await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
|
|
3018
|
-
else
|
|
3317
|
+
else if (opts.agentMdRead) {
|
|
3318
|
+
validateAgentMdCertificate(certPem, aid, opts.signedAt, { requireCurrentValidity: false, readCompatibility: true });
|
|
3319
|
+
await this._auth._verifyAuthCertChain(peerGatewayUrl, new crypto.X509Certificate(certPem), aid, opts.signedAt);
|
|
3320
|
+
}
|
|
3321
|
+
else {
|
|
3019
3322
|
await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, opts.signedAt);
|
|
3323
|
+
}
|
|
3020
3324
|
}
|
|
3021
3325
|
catch (exc) {
|
|
3022
3326
|
if (cachedBootstrapChain) {
|
|
@@ -3030,7 +3334,7 @@ export class AUNClient {
|
|
|
3030
3334
|
validatedAt: nowSec,
|
|
3031
3335
|
refreshAfter: nowSec + PEER_CERT_CACHE_TTL,
|
|
3032
3336
|
};
|
|
3033
|
-
const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (opts.signedAt === undefined ? '' : `:signed:${opts.signedAt}`);
|
|
3337
|
+
const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (opts.signedAt === undefined ? '' : `:signed:${opts.signedAt}${opts.agentMdRead ? ':agentmd' : ''}`);
|
|
3034
3338
|
this._certCache.set(cacheKey, entry);
|
|
3035
3339
|
if (!certFingerprint && opts.signedAt === undefined) {
|
|
3036
3340
|
const bareKey = AUNClient._certCacheKey(aid);
|
|
@@ -3040,7 +3344,7 @@ export class AUNClient {
|
|
|
3040
3344
|
}
|
|
3041
3345
|
try {
|
|
3042
3346
|
// peer 证书只存版本目录,不覆盖 cert.pem
|
|
3043
|
-
this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
|
|
3347
|
+
this._tokenStore.saveCert(aid, certPem, opts.agentMdRead && opts.signedAt !== undefined ? `sha256:${x509Cert.fingerprint256.replace(/:/g, '').toLowerCase()}` : certFingerprint, { makeActive: false });
|
|
3044
3348
|
}
|
|
3045
3349
|
catch (exc) {
|
|
3046
3350
|
this._clientLog.error(`failed to write cert to keystore (aid=${aid}, fp=${certFingerprint ?? ''}): ${formatCaughtError(exc)}`, exc instanceof Error ? exc : undefined);
|
|
@@ -3055,32 +3359,47 @@ export class AUNClient {
|
|
|
3055
3359
|
return certificate;
|
|
3056
3360
|
}
|
|
3057
3361
|
/** 获取对方证书(带缓存 + 完整 PKI 验证),跨域时自动路由到 peer 所在域。 */
|
|
3058
|
-
async _fetchPeerCert(aid, certFingerprint, timeoutMs = 30_000, signedAt) {
|
|
3362
|
+
async _fetchPeerCert(aid, certFingerprint, timeoutMs = 30_000, signedAt, agentMdRead = false) {
|
|
3059
3363
|
const tStart = Date.now();
|
|
3060
3364
|
this._clientLog.debug(`_fetchPeerCert enter: aid=${aid}, fp=${certFingerprint ?? ''}`);
|
|
3061
3365
|
try {
|
|
3062
|
-
const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}`);
|
|
3366
|
+
const cacheKey = AUNClient._certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}${agentMdRead ? ':agentmd' : ''}`);
|
|
3063
3367
|
const cached = this._certCache.get(cacheKey);
|
|
3064
3368
|
const now = Date.now() / 1000;
|
|
3065
3369
|
if (cached && now < cached.refreshAfter) {
|
|
3066
3370
|
this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (cache_hit)`);
|
|
3067
3371
|
return cached.certPem;
|
|
3068
3372
|
}
|
|
3069
|
-
const
|
|
3070
|
-
|
|
3071
|
-
|
|
3373
|
+
const inFlightKey = `${cacheKey}:timeout:${timeoutMs}`;
|
|
3374
|
+
const pending = this._certFetchInFlight.get(inFlightKey);
|
|
3375
|
+
if (pending)
|
|
3376
|
+
return await pending;
|
|
3377
|
+
const request = (async () => {
|
|
3378
|
+
const gatewayUrl = this._gatewayUrl;
|
|
3379
|
+
if (!gatewayUrl) {
|
|
3380
|
+
throw new ValidationError('gateway url unavailable for e2ee cert fetch');
|
|
3381
|
+
}
|
|
3382
|
+
const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
|
|
3383
|
+
const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
|
|
3384
|
+
const validated = await this._validateAndCachePeerCert({
|
|
3385
|
+
aid,
|
|
3386
|
+
certPem,
|
|
3387
|
+
certFingerprint,
|
|
3388
|
+
source: 'fetch',
|
|
3389
|
+
signedAt,
|
|
3390
|
+
agentMdRead,
|
|
3391
|
+
});
|
|
3392
|
+
this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (fetched)`);
|
|
3393
|
+
return validated;
|
|
3394
|
+
})();
|
|
3395
|
+
this._certFetchInFlight.set(inFlightKey, request);
|
|
3396
|
+
try {
|
|
3397
|
+
return await request;
|
|
3398
|
+
}
|
|
3399
|
+
finally {
|
|
3400
|
+
if (this._certFetchInFlight.get(inFlightKey) === request)
|
|
3401
|
+
this._certFetchInFlight.delete(inFlightKey);
|
|
3072
3402
|
}
|
|
3073
|
-
const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
|
|
3074
|
-
const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
|
|
3075
|
-
const validated = await this._validateAndCachePeerCert({
|
|
3076
|
-
aid,
|
|
3077
|
-
certPem,
|
|
3078
|
-
certFingerprint,
|
|
3079
|
-
source: 'fetch',
|
|
3080
|
-
signedAt,
|
|
3081
|
-
});
|
|
3082
|
-
this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (fetched)`);
|
|
3083
|
-
return validated;
|
|
3084
3403
|
}
|
|
3085
3404
|
catch (err) {
|
|
3086
3405
|
this._clientLog.debug(`_fetchPeerCert exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3656,7 +3975,7 @@ export class AUNClient {
|
|
|
3656
3975
|
}
|
|
3657
3976
|
/** 后台 Promise 统一兜底,避免事件回调里的异步异常变成未处理拒绝。 */
|
|
3658
3977
|
_safeAsync(promise) {
|
|
3659
|
-
promise.catch((exc) => {
|
|
3978
|
+
return promise.then(() => undefined).catch((exc) => {
|
|
3660
3979
|
this._clientLog.warn(`background task exception: ${formatCaughtError(exc)}`);
|
|
3661
3980
|
});
|
|
3662
3981
|
}
|
|
@@ -3851,7 +4170,7 @@ export class AUNClient {
|
|
|
3851
4170
|
return await this._v2E2EE.ackGroupV2(groupId, upToSeq, groupAid);
|
|
3852
4171
|
}
|
|
3853
4172
|
/** 解密单条 V2 pull 消息。缺 sender IK 时先入 pending,后台补齐后重试。 */
|
|
3854
|
-
async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull') {
|
|
4173
|
+
async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull', deferredEffects) {
|
|
3855
4174
|
const session = this._v2Session;
|
|
3856
4175
|
if (!session)
|
|
3857
4176
|
return null;
|
|
@@ -3867,8 +4186,12 @@ export class AUNClient {
|
|
|
3867
4186
|
return null;
|
|
3868
4187
|
}
|
|
3869
4188
|
const e2eeMeta = this._v2E2eeMeta(envelope);
|
|
3870
|
-
if (observeAgentMd)
|
|
3871
|
-
|
|
4189
|
+
if (observeAgentMd) {
|
|
4190
|
+
if (deferredEffects)
|
|
4191
|
+
deferredEffects.push(() => this._agentMdManager.observeEnvelope(envelope));
|
|
4192
|
+
else
|
|
4193
|
+
this._agentMdManager.observeEnvelope(envelope);
|
|
4194
|
+
}
|
|
3872
4195
|
let spkId = '';
|
|
3873
4196
|
let recipientKeySource = '';
|
|
3874
4197
|
if (isJsonObject(envelope.recipient)) {
|
|
@@ -3947,8 +4270,12 @@ export class AUNClient {
|
|
|
3947
4270
|
};
|
|
3948
4271
|
this._attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
3949
4272
|
this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
|
|
3950
|
-
if (emitUndecryptable)
|
|
3951
|
-
|
|
4273
|
+
if (emitUndecryptable) {
|
|
4274
|
+
if (deferredEffects)
|
|
4275
|
+
deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
|
|
4276
|
+
else
|
|
4277
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
4278
|
+
}
|
|
3952
4279
|
return null;
|
|
3953
4280
|
}
|
|
3954
4281
|
this._clientLog.debug(`V2 decrypt key lookup ok: seq=${String(msg.seq ?? '')}, group=${groupIdForKeys || '<p2p>'}, ik_len=${ikPriv.byteLength}, spk_len=${spkPriv?.byteLength ?? 0}`);
|
|
@@ -3986,8 +4313,12 @@ export class AUNClient {
|
|
|
3986
4313
|
};
|
|
3987
4314
|
this._attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
3988
4315
|
this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
|
|
3989
|
-
if (emitUndecryptable)
|
|
3990
|
-
|
|
4316
|
+
if (emitUndecryptable) {
|
|
4317
|
+
if (deferredEffects)
|
|
4318
|
+
deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
|
|
4319
|
+
else
|
|
4320
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
4321
|
+
}
|
|
3991
4322
|
return null;
|
|
3992
4323
|
}
|
|
3993
4324
|
let plaintext;
|
|
@@ -4015,16 +4346,24 @@ export class AUNClient {
|
|
|
4015
4346
|
};
|
|
4016
4347
|
this._attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
4017
4348
|
this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
|
|
4018
|
-
if (emitUndecryptable)
|
|
4019
|
-
|
|
4349
|
+
if (emitUndecryptable) {
|
|
4350
|
+
if (deferredEffects)
|
|
4351
|
+
deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
|
|
4352
|
+
else
|
|
4353
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
4354
|
+
}
|
|
4020
4355
|
return null;
|
|
4021
4356
|
}
|
|
4022
4357
|
if (plaintext === null) {
|
|
4023
4358
|
this._clientLog.debug(`V2 decrypt returned null plaintext: seq=${String(msg.seq ?? '')}, group=${groupIdForKeys || '<p2p>'}`);
|
|
4024
4359
|
return null;
|
|
4025
4360
|
}
|
|
4026
|
-
if (rotateKeys)
|
|
4027
|
-
|
|
4361
|
+
if (rotateKeys) {
|
|
4362
|
+
if (deferredEffects)
|
|
4363
|
+
deferredEffects.push(() => this._scheduleV2KeyRotationForMessage(msg, envelope));
|
|
4364
|
+
else
|
|
4365
|
+
this._scheduleV2KeyRotationForMessage(msg, envelope);
|
|
4366
|
+
}
|
|
4028
4367
|
const e2ee = this._v2E2eeMeta(envelope);
|
|
4029
4368
|
let result = {
|
|
4030
4369
|
message_id: String(msg.message_id ?? ''),
|
|
@@ -4120,6 +4459,8 @@ export class AUNClient {
|
|
|
4120
4459
|
if (protectedHeaders && Object.keys(protectedHeaders).length > 0) {
|
|
4121
4460
|
meta.protected_headers = protectedHeaders;
|
|
4122
4461
|
}
|
|
4462
|
+
if (typeof envelope.need_render === 'boolean')
|
|
4463
|
+
meta.need_render = envelope.need_render;
|
|
4123
4464
|
const payloadType = String(envelope.payload_type ?? protectedHeaders?.payload_type ?? '').trim();
|
|
4124
4465
|
if (payloadType) {
|
|
4125
4466
|
meta.payload_type = payloadType;
|
|
@@ -4135,6 +4476,8 @@ export class AUNClient {
|
|
|
4135
4476
|
return meta;
|
|
4136
4477
|
}
|
|
4137
4478
|
_attachV2EnvelopeMetadata(message, meta) {
|
|
4479
|
+
if (typeof meta.need_render === 'boolean')
|
|
4480
|
+
message.need_render = meta.need_render;
|
|
4138
4481
|
const payloadType = typeof meta.payload_type === 'string' ? meta.payload_type.trim() : '';
|
|
4139
4482
|
if (payloadType)
|
|
4140
4483
|
message.payload_type = payloadType;
|
|
@@ -4492,6 +4835,7 @@ export class AUNClient {
|
|
|
4492
4835
|
// ── 内部:后台任务 ────────────────────────────────────────
|
|
4493
4836
|
/** 启动所有后台任务 */
|
|
4494
4837
|
_startBackgroundTasks() {
|
|
4838
|
+
this._startCredentialRenewal();
|
|
4495
4839
|
// WebSocket Ping/Pong 由 Gateway 与传输层处理;SDK 不发送 RPC 心跳。
|
|
4496
4840
|
// 短连接生命周期短,不需要 token 刷新。
|
|
4497
4841
|
if (this._sessionOptions.connection_kind !== 'short') {
|
|
@@ -4501,6 +4845,7 @@ export class AUNClient {
|
|
|
4501
4845
|
}
|
|
4502
4846
|
/** 停止所有后台任务 */
|
|
4503
4847
|
_stopBackgroundTasks() {
|
|
4848
|
+
this._stopCredentialRenewal();
|
|
4504
4849
|
if (this._heartbeatTimer !== null) {
|
|
4505
4850
|
clearInterval(this._heartbeatTimer);
|
|
4506
4851
|
this._heartbeatTimer = null;
|