@agentunion/fastaun-browser 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 +16 -4
- package/dist/agent-md-schema.d.ts.map +1 -1
- package/dist/agent-md-schema.js +65 -30
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +35 -3
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +384 -150
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +59 -0
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +202 -16
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +4 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +24 -42
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +89 -18
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +6410 -1848
- package/dist/cert-utils.d.ts +50 -1
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +123 -56
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +108 -16
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +1411 -184
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +203 -117
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +22 -3
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +1312 -684
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +23 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +419 -101
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +8 -6
- package/dist/crypto.js.map +1 -1
- package/dist/events.d.ts +24 -3
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +171 -29
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +42 -1
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +368 -12
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +14 -1
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.d.ts +4 -0
- package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.js +30 -13
- package/dist/keystore/indexeddb-identity-store.js.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +19 -2
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +193 -6
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +15 -2
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +308 -27
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +45 -0
- package/dist/org-protocol.d.ts.map +1 -1
- package/dist/org-protocol.js +827 -21
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +43 -9
- package/dist/ownership-v21.d.ts.map +1 -1
- package/dist/ownership-v21.js +183 -81
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +36 -1
- package/dist/ownership-workflow.d.ts.map +1 -1
- package/dist/ownership-workflow.js +610 -89
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/seq-tracker.d.ts +2 -0
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +3 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +61 -3
- package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
- package/dist/tools/cross-sdk-agent.js +524 -45
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +4 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +64 -30
- 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
|
@@ -20,7 +20,7 @@ import { IdentityRuntimeManager } from './client/identity.js';
|
|
|
20
20
|
import { LifecycleController } from './client/lifecycle.js';
|
|
21
21
|
import { PeerDirectory } from './client/peers.js';
|
|
22
22
|
import { RpcPipeline } from './client/rpc-pipeline.js';
|
|
23
|
-
import { CredentialFacade, GroupFacade, MailFacade, MessageFacade, OrgFacade, StreamFacade } from './facades.js';
|
|
23
|
+
import { DebugFacade, CredentialFacade, GroupFacade, MailFacade, MessageFacade, OrgFacade, StreamFacade } from './facades.js';
|
|
24
24
|
import { StorageVFS } from './storage/vfs.js';
|
|
25
25
|
import { CollabClient } from './collab/client.js';
|
|
26
26
|
import { V2E2EECoordinator } from './client/v2-e2ee.js';
|
|
@@ -39,7 +39,7 @@ import { AUNError, AuthError, ConnectionError, E2EEError, NotFoundError, Permiss
|
|
|
39
39
|
import { isJsonObject, ConnectionState, STATE_TO_PUBLIC, } from './types.js';
|
|
40
40
|
import { AID } from './aid.js';
|
|
41
41
|
import { OwnershipFiles, OwnershipStorageAdapter, ownershipSha256 } from './ownership-v21.js';
|
|
42
|
-
import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, verifyServiceEvent } from './org-protocol.js';
|
|
42
|
+
import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, digest, validateMembershipBinding, verifyMembershipBinding, verifyCredentialSignature, createRepresentationProof, verifyServiceEvent, buildRequestBinding, validateRequestBinding, CredentialRenewal } from './org-protocol.js';
|
|
43
43
|
import { OwnershipWorkflow, parseOwnershipRegistrationIntent } from './ownership-workflow.js';
|
|
44
44
|
import { buildOwnershipMessage, encryptIdentity, decryptIdentity, } from './ownership-proof.js';
|
|
45
45
|
function getV2DeviceId(dev) {
|
|
@@ -466,6 +466,7 @@ function v2E2eeMeta(envelope) {
|
|
|
466
466
|
suite,
|
|
467
467
|
encryption_mode: `v2_${modeSuite}`,
|
|
468
468
|
forward_secrecy: true,
|
|
469
|
+
need_render: envelope.need_render !== false,
|
|
469
470
|
};
|
|
470
471
|
const protectedHeaders = metadataWithoutAuth(envelope.protected_headers);
|
|
471
472
|
if (protectedHeaders && Object.keys(protectedHeaders).length > 0) {
|
|
@@ -488,6 +489,8 @@ function v2E2eeMeta(envelope) {
|
|
|
488
489
|
function attachV2EnvelopeMetadata(message, meta) {
|
|
489
490
|
if (!meta)
|
|
490
491
|
return;
|
|
492
|
+
if (typeof meta.need_render === 'boolean')
|
|
493
|
+
message.need_render = meta.need_render;
|
|
491
494
|
const payloadType = typeof meta.payload_type === 'string' ? meta.payload_type.trim() : '';
|
|
492
495
|
if (payloadType)
|
|
493
496
|
message.payload_type = payloadType;
|
|
@@ -589,6 +592,7 @@ export class AUNClient {
|
|
|
589
592
|
config;
|
|
590
593
|
_aid = null;
|
|
591
594
|
_serviceEventIdentityVersion = 0;
|
|
595
|
+
_serviceEventIdentityAbort = new AbortController();
|
|
592
596
|
_identity = null;
|
|
593
597
|
_state = 'idle';
|
|
594
598
|
_currentAid = null;
|
|
@@ -674,6 +678,7 @@ export class AUNClient {
|
|
|
674
678
|
_transport;
|
|
675
679
|
// E2EE 编排状态(内存缓存)
|
|
676
680
|
_certCache = new Map();
|
|
681
|
+
_certFetchInFlight = new Map();
|
|
677
682
|
// 后台任务 handle(浏览器 setInterval/setTimeout)
|
|
678
683
|
_heartbeatTimer = null;
|
|
679
684
|
_heartbeatCount = 0;
|
|
@@ -714,6 +719,7 @@ export class AUNClient {
|
|
|
714
719
|
_groupIndexCacheLoaded = new Set();
|
|
715
720
|
/** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
|
|
716
721
|
_seqTracker = new SeqTracker();
|
|
722
|
+
_p2pPeerPullEnabled = true;
|
|
717
723
|
_seqTrackerContext = null;
|
|
718
724
|
/** 补洞去重:已完成/进行中的 key 集合,防止重复 pull 同一区间 */
|
|
719
725
|
_gapFillDone = new Set();
|
|
@@ -772,10 +778,173 @@ export class AUNClient {
|
|
|
772
778
|
_storage;
|
|
773
779
|
_collab;
|
|
774
780
|
_messageFacade;
|
|
781
|
+
_debugFacade;
|
|
775
782
|
_mailFacade;
|
|
776
783
|
_groupFacade;
|
|
777
784
|
_streamFacade;
|
|
778
785
|
_credentialFacade;
|
|
786
|
+
_credentialRenewal;
|
|
787
|
+
_credentialRenewalTimer = null;
|
|
788
|
+
_credentialRenewalAbort = null;
|
|
789
|
+
_credentialRenewalScan = Promise.resolve();
|
|
790
|
+
_getCredentialRenewal() {
|
|
791
|
+
return this._credentialRenewal ??= new CredentialRenewal({
|
|
792
|
+
currentAid: () => this._aid ?? '',
|
|
793
|
+
atomicCheckpointSave: () => this._tokenStore instanceof IndexedDBTokenStore,
|
|
794
|
+
load: async (aid) => JSON.parse(await (this._tokenStore._getMetadataStrict?.(aid, 'credential_renewals_v1') ?? this._tokenStore.getMetadata?.(aid, 'credential_renewals_v1')) || '{}'),
|
|
795
|
+
...(typeof this._tokenStore._getMetadataStrict === 'function' ? {
|
|
796
|
+
loadRevocations: async (aid) => JSON.parse(await this._tokenStore._getMetadataStrict(aid, 'credential_revocations_v1') || '{}'),
|
|
797
|
+
saveRevocations: async (aid, state, guard) => {
|
|
798
|
+
if (guard)
|
|
799
|
+
await this._tokenStore._setMetadataIfCurrent(aid, 'credential_revocations_v1', JSON.stringify(state), guard);
|
|
800
|
+
else
|
|
801
|
+
await this._tokenStore.setMetadata?.(aid, 'credential_revocations_v1', JSON.stringify(state));
|
|
802
|
+
},
|
|
803
|
+
} : {}),
|
|
804
|
+
save: async (aid, records, guard) => {
|
|
805
|
+
const version = this._serviceEventIdentityVersion;
|
|
806
|
+
const current = () => version === this._serviceEventIdentityVersion && aid === this._aid && (!guard || guard.current());
|
|
807
|
+
if (!current())
|
|
808
|
+
throw new ValidationError('membership event receiver identity changed');
|
|
809
|
+
const saved = this._tokenStore instanceof IndexedDBTokenStore ? records : Object.fromEntries(Object.entries(records).map(([cid, value]) => {
|
|
810
|
+
const record = { ...value };
|
|
811
|
+
for (const key of ['submitted_at', 'submission_hash', 'registration_submitted_at', 'registration_submission_hash'])
|
|
812
|
+
delete record[key];
|
|
813
|
+
return [cid, record];
|
|
814
|
+
}));
|
|
815
|
+
if (typeof this._tokenStore._setMetadataIfCurrent === 'function')
|
|
816
|
+
await this._tokenStore._setMetadataIfCurrent(aid, 'credential_renewals_v1', JSON.stringify(saved), { current, signal: guard?.signal });
|
|
817
|
+
else
|
|
818
|
+
await this._tokenStore.setMetadata?.(aid, 'credential_renewals_v1', JSON.stringify(saved));
|
|
819
|
+
if (!current())
|
|
820
|
+
throw new ValidationError('membership event receiver identity changed');
|
|
821
|
+
},
|
|
822
|
+
signer: (aid, guard) => this._credentialSigningIdentity(aid, guard),
|
|
823
|
+
certificate: (aid, fingerprint, signedAt) => signedAt === undefined
|
|
824
|
+
? this._resolveServiceEventCertificate(aid, fingerprint)
|
|
825
|
+
: this._fetchPeerCert(aid, fingerprint, undefined, signedAt),
|
|
826
|
+
call: (method, params, onSent) => {
|
|
827
|
+
if (!onSent)
|
|
828
|
+
return this._rpcPipeline.call(method, params);
|
|
829
|
+
const version = this._serviceEventIdentityVersion, aid = this._aid;
|
|
830
|
+
return this._rpcPipeline['_callWithSent'](method, params, async () => {
|
|
831
|
+
if (version !== this._serviceEventIdentityVersion || aid !== this._aid)
|
|
832
|
+
throw new ValidationError('credential sender identity changed');
|
|
833
|
+
await onSent();
|
|
834
|
+
});
|
|
835
|
+
},
|
|
836
|
+
send: (to, payload) => this.call('message.send', { to, encrypt: true, payload }),
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
async _assertCredentialNotRevoked(credential, proofChain) {
|
|
840
|
+
const version = this._serviceEventIdentityVersion, aid = this._aid;
|
|
841
|
+
const current = () => { if (aid !== this._aid || version !== this._serviceEventIdentityVersion)
|
|
842
|
+
throw new ValidationError('credential receiver identity changed'); };
|
|
843
|
+
await this._getCredentialRenewal()._assertNotRevoked(credential, proofChain);
|
|
844
|
+
current();
|
|
845
|
+
return current;
|
|
846
|
+
}
|
|
847
|
+
async _credentialSigningIdentity(aid, guard) {
|
|
848
|
+
const check = () => { if (guard && !guard.current())
|
|
849
|
+
throw new ValidationError('credential renewal was superseded'); };
|
|
850
|
+
check();
|
|
851
|
+
const stored = await this._aidStore?.load(aid);
|
|
852
|
+
check();
|
|
853
|
+
let identity = aid === this._aid && this._identity?.private_key_pem
|
|
854
|
+
? await 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 })
|
|
855
|
+
: stored?.ok ? stored.data.aid : undefined;
|
|
856
|
+
if (!identity?.isPrivateKeyValid())
|
|
857
|
+
return null;
|
|
858
|
+
check();
|
|
859
|
+
const certificate = parseCertMetadata(identity.certPem);
|
|
860
|
+
const end = certificate.notAfter.getTime(), start = certificate.notBefore.getTime();
|
|
861
|
+
if (end > Date.now() && end - Date.now() < (end - start) / 2 && this._aidStore) {
|
|
862
|
+
const renewed = await (guard ? this._aidStore._renewCert(aid, guard) : this._aidStore.renewCert(aid));
|
|
863
|
+
check();
|
|
864
|
+
if (!renewed.ok)
|
|
865
|
+
throw new ValidationError(renewed.error.message);
|
|
866
|
+
const loaded = await this._aidStore.load(aid);
|
|
867
|
+
check();
|
|
868
|
+
if (!loaded.ok)
|
|
869
|
+
throw new ValidationError(loaded.error.message);
|
|
870
|
+
identity = loaded.data.aid;
|
|
871
|
+
if (aid === this._aid && this._identity) {
|
|
872
|
+
this._identity.cert = identity.certPem;
|
|
873
|
+
this._currentAid = identity;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return identity;
|
|
877
|
+
}
|
|
878
|
+
_startCredentialRenewal() {
|
|
879
|
+
this._stopCredentialRenewal();
|
|
880
|
+
const aid = this._aid, version = this._serviceEventIdentityVersion;
|
|
881
|
+
if (!aid || this._state !== 'connected' || this._closing || this._disconnecting)
|
|
882
|
+
return;
|
|
883
|
+
const abort = new AbortController();
|
|
884
|
+
this._credentialRenewalAbort = abort;
|
|
885
|
+
const current = () => this._credentialRenewalAbort === abort && !abort.signal.aborted && aid === this._aid && version === this._serviceEventIdentityVersion && this._state === 'connected' && !this._closing && !this._disconnecting;
|
|
886
|
+
const guard = { current, signal: abort.signal };
|
|
887
|
+
const scan = async () => {
|
|
888
|
+
const task = this._credentialRenewalScan.then(async () => {
|
|
889
|
+
if (!current())
|
|
890
|
+
return;
|
|
891
|
+
await this._credentialSigningIdentity(aid, guard);
|
|
892
|
+
if (current())
|
|
893
|
+
await this._safeAsync(this._getCredentialRenewal()._syncRevocations?.(guard) ?? Promise.resolve());
|
|
894
|
+
if (current())
|
|
895
|
+
await this._getCredentialRenewal().resume(guard);
|
|
896
|
+
});
|
|
897
|
+
this._credentialRenewalScan = task.catch(() => { });
|
|
898
|
+
this._safeAsync(task);
|
|
899
|
+
await task.catch(() => { });
|
|
900
|
+
if (current())
|
|
901
|
+
this._credentialRenewalTimer = setTimeout(() => { void scan(); }, 60_000);
|
|
902
|
+
};
|
|
903
|
+
void scan();
|
|
904
|
+
}
|
|
905
|
+
_stopCredentialRenewal() {
|
|
906
|
+
this._credentialRenewalAbort?.abort();
|
|
907
|
+
this._credentialRenewalAbort = null;
|
|
908
|
+
if (this._credentialRenewalTimer !== null)
|
|
909
|
+
clearTimeout(this._credentialRenewalTimer);
|
|
910
|
+
this._credentialRenewalTimer = null;
|
|
911
|
+
}
|
|
912
|
+
_rememberCredential(credential, material, certificate) {
|
|
913
|
+
if (material.long_term === undefined && certificate) {
|
|
914
|
+
const pem = typeof certificate === 'string' ? certificate : arrayBufferToPem(exactArrayBuffer(certificate), 'CERTIFICATE');
|
|
915
|
+
const parent = Array.isArray(credential.proof_chain) ? credential.proof_chain[0] : undefined;
|
|
916
|
+
material = { ...material, long_term: credential.exp === Math.min(Math.floor(parseCertMetadata(pem).notAfter.getTime() / 1000), credential.parent != null && parent ? Number(parent.exp) : Infinity) };
|
|
917
|
+
}
|
|
918
|
+
this._safeAsync(this._getCredentialRenewal().remember(credential, material));
|
|
919
|
+
}
|
|
920
|
+
_credentialCurrent(input) {
|
|
921
|
+
return this._getCredentialRenewal().current(input);
|
|
922
|
+
}
|
|
923
|
+
async _handleCredentialRenewal(message) {
|
|
924
|
+
if (!isJsonObject(message))
|
|
925
|
+
return false;
|
|
926
|
+
const envelope = message;
|
|
927
|
+
const content = 'payload' in envelope ? envelope.payload : envelope;
|
|
928
|
+
const payload = decryptedOwnershipJsonText(content) ?? content;
|
|
929
|
+
let parsed = payload;
|
|
930
|
+
if (typeof payload === 'string') {
|
|
931
|
+
try {
|
|
932
|
+
parsed = JSON.parse(payload);
|
|
933
|
+
}
|
|
934
|
+
catch {
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
if (!isJsonObject(parsed) || !['credential.renewal.request', 'credential.renewal.result'].includes(String(parsed.type)))
|
|
939
|
+
return false;
|
|
940
|
+
if (Number(parsed.exp) <= Date.now() / 1000)
|
|
941
|
+
return true;
|
|
942
|
+
try {
|
|
943
|
+
await this._getCredentialRenewal().receive(String(envelope.from ?? envelope.from_aid ?? envelope.sender_aid ?? ''), parsed);
|
|
944
|
+
}
|
|
945
|
+
catch { }
|
|
946
|
+
return true;
|
|
947
|
+
}
|
|
779
948
|
_orgFacade;
|
|
780
949
|
_groupInvitePending = new Map();
|
|
781
950
|
_groupInviteStateAid = null;
|
|
@@ -809,7 +978,7 @@ export class AUNClient {
|
|
|
809
978
|
this._logDiscovery = this._logger.for('aun_core.discovery');
|
|
810
979
|
this._logEvents = this._logger.for('aun_core.events');
|
|
811
980
|
this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? '-'}`);
|
|
812
|
-
this._dispatcher = new EventDispatcher();
|
|
981
|
+
this._dispatcher = new EventDispatcher(() => this._ensureReadStateIdentity());
|
|
813
982
|
this._discovery = new GatewayDiscovery();
|
|
814
983
|
this._tokenStore = new IndexedDBTokenStore();
|
|
815
984
|
this._slotId = inputAid?.slotId || 'default';
|
|
@@ -845,7 +1014,7 @@ export class AUNClient {
|
|
|
845
1014
|
},
|
|
846
1015
|
auth: () => this._auth,
|
|
847
1016
|
tokenStore: () => this._tokenStore,
|
|
848
|
-
fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt),
|
|
1017
|
+
fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
|
|
849
1018
|
entityOwnerResolver: (target, type) => this._resolveEntityAgentMdOwner(target, type),
|
|
850
1019
|
entityIdentityGetter: (target, type) => this._freshEntityCertificate(target, type),
|
|
851
1020
|
entitySignerResolver: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
|
|
@@ -857,6 +1026,7 @@ export class AUNClient {
|
|
|
857
1026
|
this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload));
|
|
858
1027
|
this._transport.setProtocolDisconnectCallback((error, closeCode) => this._handleTransportDisconnect(error, closeCode));
|
|
859
1028
|
this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
|
|
1029
|
+
this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
|
|
860
1030
|
this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta).catch((exc) => {
|
|
861
1031
|
this._clientLog.debug(`rpc meta observer skipped: ${String(exc)}`);
|
|
862
1032
|
}));
|
|
@@ -981,6 +1151,21 @@ export class AUNClient {
|
|
|
981
1151
|
this._collab = new CollabClient(this);
|
|
982
1152
|
return this._collab;
|
|
983
1153
|
}
|
|
1154
|
+
get debug() {
|
|
1155
|
+
if (!this._debugFacade)
|
|
1156
|
+
this._debugFacade = new DebugFacade(this, async (operation) => {
|
|
1157
|
+
const transport = this._transport;
|
|
1158
|
+
const previous = transport.getTraceMode();
|
|
1159
|
+
transport.setTraceMode('diag');
|
|
1160
|
+
try {
|
|
1161
|
+
return await operation();
|
|
1162
|
+
}
|
|
1163
|
+
finally {
|
|
1164
|
+
transport.setTraceMode(previous);
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
return this._debugFacade;
|
|
1168
|
+
}
|
|
984
1169
|
/** Message RPC facade 入口 */
|
|
985
1170
|
get message() {
|
|
986
1171
|
if (!this._messageFacade)
|
|
@@ -1230,7 +1415,7 @@ export class AUNClient {
|
|
|
1230
1415
|
}
|
|
1231
1416
|
_ownershipV21() {
|
|
1232
1417
|
if (!this._ownershipWorkflow) {
|
|
1233
|
-
const storage = new OwnershipStorageAdapter({ currentAid: () => this._aid ?? '', call: (method, params) => this.call(method, params) });
|
|
1418
|
+
const storage = new OwnershipStorageAdapter({ currentAid: () => this._aid ?? '', call: (method, params) => this.call(method, params), httpPut: (url, data, headers) => this.storage.lowlevel.httpPut(url, data, headers) });
|
|
1234
1419
|
const files = new OwnershipFiles({
|
|
1235
1420
|
currentAidType: () => this.getType(),
|
|
1236
1421
|
currentAid: () => {
|
|
@@ -1239,10 +1424,10 @@ export class AUNClient {
|
|
|
1239
1424
|
return this._currentAid;
|
|
1240
1425
|
},
|
|
1241
1426
|
readAgentMdFresh: (aid) => this._agentMdManager.readOwnershipFresh(aid),
|
|
1242
|
-
submitAgentMdRaw: (aid, raw, condition) => this._agentMdManager.submitOwnershipRaw(aid, raw, condition),
|
|
1427
|
+
submitAgentMdRaw: (aid, raw, condition, guard) => this._agentMdManager.submitOwnershipRaw(aid, raw, condition, guard),
|
|
1243
1428
|
readOwnerMd: (owner, sub) => storage.readOwnerMd(owner, sub),
|
|
1244
|
-
writeOwnerMd: (owner, sub, raw,
|
|
1245
|
-
deleteOwnerMd: (owner, sub
|
|
1429
|
+
writeOwnerMd: (owner, sub, raw, original, guard) => storage.writeOwnerMd(owner, sub, raw, original, guard),
|
|
1430
|
+
deleteOwnerMd: (owner, sub) => storage.deleteOwnerMd(owner, sub),
|
|
1246
1431
|
resolveCertificate: async (aid, fingerprint, keyPurpose) => this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, keyPurpose),
|
|
1247
1432
|
});
|
|
1248
1433
|
this._ownershipWorkflow = new OwnershipWorkflow({
|
|
@@ -1256,6 +1441,7 @@ export class AUNClient {
|
|
|
1256
1441
|
}
|
|
1257
1442
|
async bindOwneraid(ownerAid) { return await this._ownershipV21().bindOwneraid(ownerAid); }
|
|
1258
1443
|
async bindSubaid(subAid) { return await this._ownershipV21().bindSubaid(subAid); }
|
|
1444
|
+
async resetOwnershipTask() { return await this._ownershipV21().resetOwnershipTask(); }
|
|
1259
1445
|
async releaseSubaid(subAid) { return await this._ownershipV21().releaseSubaid(subAid); }
|
|
1260
1446
|
async transferSubaid(subAid, newOwnerAid) { return await this._ownershipV21().transferSubaid(subAid, newOwnerAid); }
|
|
1261
1447
|
async checkOwnershipConsensus(subAid) { return await this._ownershipV21().checkOwnershipConsensus(subAid); }
|
|
@@ -1775,7 +1961,7 @@ export class AUNClient {
|
|
|
1775
1961
|
},
|
|
1776
1962
|
auth: () => this._auth,
|
|
1777
1963
|
tokenStore: () => this._tokenStore,
|
|
1778
|
-
fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt),
|
|
1964
|
+
fetchPeerCert: (target, certFingerprint, signedAt) => this._fetchPeerCert(target, certFingerprint || undefined, undefined, signedAt, true),
|
|
1779
1965
|
entityOwnerResolver: (target, type) => this._resolveEntityAgentMdOwner(target, type),
|
|
1780
1966
|
entityIdentityGetter: (target, type) => this._freshEntityCertificate(target, type),
|
|
1781
1967
|
entitySignerResolver: (target, fingerprint) => this._freshEntitySigner(target, fingerprint),
|
|
@@ -1787,6 +1973,7 @@ export class AUNClient {
|
|
|
1787
1973
|
this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload));
|
|
1788
1974
|
this._transport.setProtocolDisconnectCallback((error, closeCode) => this._handleTransportDisconnect(error, closeCode));
|
|
1789
1975
|
this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
|
|
1976
|
+
this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
|
|
1790
1977
|
this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta).catch((exc) => {
|
|
1791
1978
|
this._clientLog.debug(`rpc meta observer skipped: ${String(exc)}`);
|
|
1792
1979
|
}));
|
|
@@ -1803,9 +1990,13 @@ export class AUNClient {
|
|
|
1803
1990
|
loadIdentity(aid) {
|
|
1804
1991
|
const previousAid = this._aid;
|
|
1805
1992
|
this._identityRuntime.loadIdentity(aid);
|
|
1993
|
+
this._stopCredentialRenewal();
|
|
1806
1994
|
this._dispatcher.setEventIdentity(this._aid ?? '');
|
|
1807
|
-
if (previousAid !== this._aid)
|
|
1995
|
+
if (previousAid !== this._aid) {
|
|
1808
1996
|
this._serviceEventIdentityVersion++;
|
|
1997
|
+
this._serviceEventIdentityAbort.abort();
|
|
1998
|
+
this._serviceEventIdentityAbort = new AbortController();
|
|
1999
|
+
}
|
|
1809
2000
|
this._ensureReadStateIdentity();
|
|
1810
2001
|
if (previousAid !== this._aid) {
|
|
1811
2002
|
this._ownershipRecords.clear();
|
|
@@ -1880,6 +2071,10 @@ export class AUNClient {
|
|
|
1880
2071
|
? this._ensureReadStateIdentity() : null;
|
|
1881
2072
|
try {
|
|
1882
2073
|
const result = await this._rpcPipeline.call(method, params);
|
|
2074
|
+
try {
|
|
2075
|
+
await this._getCredentialRenewal().observe(method, params ?? {}, result);
|
|
2076
|
+
}
|
|
2077
|
+
catch { }
|
|
1883
2078
|
if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
|
|
1884
2079
|
this._observeReadStateResult(method, result, params ?? {});
|
|
1885
2080
|
return result;
|
|
@@ -1923,6 +2118,8 @@ export class AUNClient {
|
|
|
1923
2118
|
const store = this._aidStore;
|
|
1924
2119
|
if (!groupAid || !store)
|
|
1925
2120
|
return false;
|
|
2121
|
+
if (String((await (store._agentMdManager ?? this._agentMdManager).loadRecord(groupAid, true))?.local_draft_content ?? '').trim())
|
|
2122
|
+
return false;
|
|
1926
2123
|
const loaded = await store.load(groupAid);
|
|
1927
2124
|
if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid())
|
|
1928
2125
|
return false;
|
|
@@ -1938,6 +2135,8 @@ export class AUNClient {
|
|
|
1938
2135
|
const target = message.slice(agentPrefix.length).trim();
|
|
1939
2136
|
if (!target || target.toLowerCase() !== String(this._aid ?? '').trim().toLowerCase())
|
|
1940
2137
|
return false;
|
|
2138
|
+
if (String((await this._agentMdManager.loadRecord(target, true))?.local_draft_content ?? '').trim())
|
|
2139
|
+
return false;
|
|
1941
2140
|
const content = await this._agentMdManager.readContent(target) ?? buildDefaultAgentMd(target);
|
|
1942
2141
|
if (!this._ordinaryAdmissionAgentMd(content, target))
|
|
1943
2142
|
return false;
|
|
@@ -1953,12 +2152,13 @@ export class AUNClient {
|
|
|
1953
2152
|
if (!aid)
|
|
1954
2153
|
return;
|
|
1955
2154
|
try {
|
|
1956
|
-
const
|
|
1957
|
-
|
|
2155
|
+
const draft = String((await this._agentMdManager.loadRecord(aid, true))?.local_draft_content ?? '').trim();
|
|
2156
|
+
const pending = draft ? null : await this._agentMdManager.pendingUpload(aid);
|
|
2157
|
+
if (!draft && pending) {
|
|
1958
2158
|
if (this._ordinaryAdmissionAgentMd(pending.content, aid))
|
|
1959
2159
|
await this._agentMdManager.resumePendingUpload(aid);
|
|
1960
2160
|
}
|
|
1961
|
-
else {
|
|
2161
|
+
else if (!draft) {
|
|
1962
2162
|
const checked = await this._agentMdManager.check(aid, 0);
|
|
1963
2163
|
if (!checked.remote_found) {
|
|
1964
2164
|
const content = await this._agentMdManager.readContent(aid) ?? buildDefaultAgentMd(aid);
|
|
@@ -2012,6 +2212,8 @@ export class AUNClient {
|
|
|
2012
2212
|
await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
|
|
2013
2213
|
return;
|
|
2014
2214
|
}
|
|
2215
|
+
if (String((await (store._agentMdManager ?? this._agentMdManager).loadRecord(groupAid, true))?.local_draft_content ?? '').trim())
|
|
2216
|
+
return;
|
|
2015
2217
|
if (await this._resumeGroupAgentMdUpload(store, groupAid))
|
|
2016
2218
|
return;
|
|
2017
2219
|
const checked = await this._agentMdManager.check(groupAid);
|
|
@@ -2044,12 +2246,46 @@ export class AUNClient {
|
|
|
2044
2246
|
this._groupIdentityOperations.delete(key);
|
|
2045
2247
|
}
|
|
2046
2248
|
}
|
|
2249
|
+
async _resumeOrgRequest(method, params, target) {
|
|
2250
|
+
const binding = params.request_binding;
|
|
2251
|
+
const now = Math.trunc(Date.now() / 1000);
|
|
2252
|
+
if (!isJsonObject(binding) || now < Number(binding.exp))
|
|
2253
|
+
return params;
|
|
2254
|
+
const credentials = isJsonObject(params.credentials) ? params.credentials : {};
|
|
2255
|
+
const credential = ['member_credential', 'approval_credential', 'audit_credential'].map((field) => credentials[field]).find(isJsonObject) ?? params.credential_template;
|
|
2256
|
+
const cid = isJsonObject(credential) ? String(credential.cid) : '';
|
|
2257
|
+
validateRequestBinding(binding, method, target, params, cid, String(params.nonce), String(params.request_id), Number(binding.iat));
|
|
2258
|
+
let exp = now + 300;
|
|
2259
|
+
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]))]) {
|
|
2260
|
+
if (isJsonObject(material)) {
|
|
2261
|
+
if (!Number.isInteger(material.exp) || Number(material.exp) <= now)
|
|
2262
|
+
throw new ValidationError('Org recovery authorization material has expired');
|
|
2263
|
+
exp = Math.min(exp, Number(material.exp));
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
const refreshed = { ...params, request_binding: buildRequestBinding(method, target, params, cid, String(params.nonce), String(params.request_id), now, exp) };
|
|
2267
|
+
delete refreshed.client_signature;
|
|
2268
|
+
if (isJsonObject(credentials.representation_proof)) {
|
|
2269
|
+
if (!this._currentAid || !isJsonObject(credential))
|
|
2270
|
+
throw new ValidationError('Org recovery representation signer is unavailable');
|
|
2271
|
+
refreshed.credentials = { ...credentials, representation_proof: await createRepresentationProof(credential, refreshed, refreshed.request_binding, this._currentAid, refreshed.proof_chain) };
|
|
2272
|
+
}
|
|
2273
|
+
return refreshed;
|
|
2274
|
+
}
|
|
2047
2275
|
async createOrg(params = {}, options = {}) {
|
|
2048
2276
|
const store = options.aidStore;
|
|
2049
2277
|
const owner = this._currentAid;
|
|
2050
2278
|
const orgAid = String(params.org_id ?? '').trim();
|
|
2051
2279
|
if (!store || !owner?.isPrivateKeyValid() || !orgAid)
|
|
2052
2280
|
throw new ValidationError('createOrg requires aidStore, org_id and current owner identity');
|
|
2281
|
+
const tokens = store._tokenStore;
|
|
2282
|
+
const inputHash = await ownershipSha256(canonicalJson(params));
|
|
2283
|
+
const raw = await (typeof tokens._getMetadataStrict === 'function' ? tokens._getMetadataStrict(orgAid, 'org_identity_creation') : tokens.getMetadata(orgAid, 'org_identity_creation'));
|
|
2284
|
+
let checkpoint = raw ? JSON.parse(raw) : undefined;
|
|
2285
|
+
if (raw && !isJsonObject(checkpoint))
|
|
2286
|
+
throw new StateError('Org creation checkpoint is invalid', { stringCode: 'persistence_unavailable' });
|
|
2287
|
+
if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
|
|
2288
|
+
throw new StateError('pending Org creation context changed', { stringCode: 'conflict', retryable: false });
|
|
2053
2289
|
this._aidStore = store;
|
|
2054
2290
|
let identity = await store.load(orgAid);
|
|
2055
2291
|
if (!identity.ok) {
|
|
@@ -2061,19 +2297,13 @@ export class AUNClient {
|
|
|
2061
2297
|
if (!identity.ok)
|
|
2062
2298
|
throw new StateError(identity.error.message, { stringCode: 'unavailable', retryable: true });
|
|
2063
2299
|
const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
|
|
2064
|
-
const tokens = store._tokenStore;
|
|
2065
|
-
const inputHash = await ownershipSha256(canonicalJson(params));
|
|
2066
|
-
const raw = await tokens.getMetadata(orgAid, 'org_identity_creation');
|
|
2067
|
-
let checkpoint = raw ? JSON.parse(raw) : undefined;
|
|
2068
|
-
if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
|
|
2069
|
-
throw new StateError('pending Org creation context changed', { stringCode: 'conflict', retryable: false });
|
|
2070
2300
|
if (!checkpoint) {
|
|
2071
2301
|
const prepared = await this.org.prepareRequest('org.lifecycle.create', { ...params, owner_aid: owner.aid }, target, owner, { orgSigner: identity.data.aid });
|
|
2072
2302
|
checkpoint = { owner_aid: owner.aid, input_hash: inputHash, source_params: structuredClone(params), prepared, phase: 'create' };
|
|
2073
2303
|
await tokens.setMetadata(orgAid, 'org_identity_creation', JSON.stringify(checkpoint));
|
|
2074
2304
|
}
|
|
2075
2305
|
if (checkpoint.phase === 'create') {
|
|
2076
|
-
const result = await this.call('org.lifecycle.create', checkpoint.prepared);
|
|
2306
|
+
const result = await this.call('org.lifecycle.create', await this._resumeOrgRequest('org.lifecycle.create', checkpoint.prepared, target));
|
|
2077
2307
|
checkpoint = { ...checkpoint, result, phase: 'upload' };
|
|
2078
2308
|
await tokens.setMetadata(orgAid, 'org_identity_creation', JSON.stringify(checkpoint));
|
|
2079
2309
|
}
|
|
@@ -2088,10 +2318,12 @@ export class AUNClient {
|
|
|
2088
2318
|
return;
|
|
2089
2319
|
for (const aid of await keyStore.listIdentities()) {
|
|
2090
2320
|
try {
|
|
2091
|
-
const creationRaw = await tokens.getMetadata(aid, 'org_identity_creation');
|
|
2092
|
-
const transferRaw = await tokens.getMetadata(aid, 'org_identity_transfer');
|
|
2321
|
+
const creationRaw = await (typeof tokens._getMetadataStrict === 'function' ? tokens._getMetadataStrict(aid, 'org_identity_creation') : tokens.getMetadata(aid, 'org_identity_creation'));
|
|
2322
|
+
const transferRaw = await (typeof tokens._getMetadataStrict === 'function' ? tokens._getMetadataStrict(aid, 'org_identity_transfer') : tokens.getMetadata(aid, 'org_identity_transfer'));
|
|
2093
2323
|
const creation = creationRaw ? JSON.parse(creationRaw) : undefined;
|
|
2094
2324
|
const transfer = transferRaw ? JSON.parse(transferRaw) : undefined;
|
|
2325
|
+
if (creationRaw && !isJsonObject(creation) || transferRaw && !isJsonObject(transfer))
|
|
2326
|
+
throw new StateError('Org identity checkpoint is invalid', { stringCode: 'persistence_unavailable' });
|
|
2095
2327
|
if (creation?.owner_aid === this._aid) {
|
|
2096
2328
|
await this.createOrg(creation.source_params, { aidStore: store });
|
|
2097
2329
|
continue;
|
|
@@ -2100,6 +2332,8 @@ export class AUNClient {
|
|
|
2100
2332
|
await this.completeOrgTransfer(transfer.source_params, { aidStore: store });
|
|
2101
2333
|
continue;
|
|
2102
2334
|
}
|
|
2335
|
+
if (String((await (store._agentMdManager ?? this._agentMdManager).loadRecord(aid, true))?.local_draft_content ?? '').trim())
|
|
2336
|
+
continue;
|
|
2103
2337
|
const pending = await store._agentMdManager.pendingUpload(aid);
|
|
2104
2338
|
if (!pending)
|
|
2105
2339
|
continue;
|
|
@@ -2112,6 +2346,40 @@ export class AUNClient {
|
|
|
2112
2346
|
}
|
|
2113
2347
|
}
|
|
2114
2348
|
}
|
|
2349
|
+
async _verifyOrgTransferMaterials(params, ownerAid) {
|
|
2350
|
+
const orgAid = String(params.org_id);
|
|
2351
|
+
const previous = params.previous_owner;
|
|
2352
|
+
const next = params.new_owner;
|
|
2353
|
+
const binding = previous.membership_binding;
|
|
2354
|
+
const nextBinding = next.membership_binding;
|
|
2355
|
+
const credential = previous.credential_template;
|
|
2356
|
+
const ext = isJsonObject(credential.ext) ? credential.ext : {};
|
|
2357
|
+
const capability = Array.isArray(credential.cap) && credential.cap.length === 1 && isJsonObject(credential.cap[0]) ? credential.cap[0] : {};
|
|
2358
|
+
const now = Math.trunc(Date.now() / 1000);
|
|
2359
|
+
if (binding.org_id !== orgAid || nextBinding.org_id !== orgAid || binding.member_aid === ownerAid || nextBinding.member_aid !== ownerAid
|
|
2360
|
+
|| binding.role !== 'member' || nextBinding.role !== 'owner' || digest(previous.terms) !== binding.terms_hash || digest(next.terms) !== nextBinding.terms_hash
|
|
2361
|
+
|| !Number.isSafeInteger(nextBinding.iat) || !Number.isSafeInteger(nextBinding.exp) || Number(nextBinding.iat) > now || Number(nextBinding.exp) <= now
|
|
2362
|
+
|| credential.iss !== orgAid || credential.sub !== binding.member_aid || credential.parent != null || credential.delegate !== false
|
|
2363
|
+
|| ext.credential_type !== 'member' || ext.bound_to_membership !== true || ext.org_id !== orgAid || ext.membership_id !== binding.membership_id
|
|
2364
|
+
|| ext.binding_version !== binding.binding_version || ext.membership_terms_hash !== binding.terms_hash
|
|
2365
|
+
|| capability.ns !== 'org' || capability.action !== 'role.member' || capability.resource !== orgAid)
|
|
2366
|
+
throw new StateError('Org transfer membership materials do not match', { stringCode: 'invalid', retryable: false });
|
|
2367
|
+
validateMembershipBinding(binding);
|
|
2368
|
+
const certificates = new Map();
|
|
2369
|
+
for (const [aid, signature] of [[binding.member_aid, binding.member_signature], [orgAid, binding.org_signature], [orgAid, credential.signature]]) {
|
|
2370
|
+
if (!isJsonObject(signature))
|
|
2371
|
+
throw new StateError('Org transfer membership materials do not match', { stringCode: 'invalid', retryable: false });
|
|
2372
|
+
const fingerprint = String(signature.cert_fingerprint);
|
|
2373
|
+
const certificate = await this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(String(aid)), String(aid), fingerprint);
|
|
2374
|
+
certificates.set(fingerprint, certificate.cert_pem);
|
|
2375
|
+
}
|
|
2376
|
+
const resolve = (fingerprint) => certificates.get(fingerprint);
|
|
2377
|
+
const checkedAt = Math.trunc(Date.now() / 1000);
|
|
2378
|
+
await verifyMembershipBinding(binding, resolve, checkedAt, orgAid);
|
|
2379
|
+
await verifyCredentialSignature(credential, resolve, checkedAt, orgAid);
|
|
2380
|
+
if (Number(nextBinding.exp) <= checkedAt)
|
|
2381
|
+
throw new StateError('Org transfer membership materials do not match', { stringCode: 'invalid', retryable: false });
|
|
2382
|
+
}
|
|
2115
2383
|
async completeOrgTransfer(params = {}, options = {}) {
|
|
2116
2384
|
const store = options.aidStore;
|
|
2117
2385
|
const owner = this._currentAid;
|
|
@@ -2119,19 +2387,23 @@ export class AUNClient {
|
|
|
2119
2387
|
const transferId = String(params.transfer_id ?? '').trim();
|
|
2120
2388
|
if (!store || !owner?.isPrivateKeyValid() || !orgAid || !transferId)
|
|
2121
2389
|
throw new ValidationError('completeOrgTransfer requires aidStore, org_id, transfer_id and current accepting owner');
|
|
2390
|
+
for (const field of ['previous_owner', 'new_owner']) {
|
|
2391
|
+
const material = params[field];
|
|
2392
|
+
if (!isJsonObject(material) || !isJsonObject(material.membership_binding) || !isJsonObject(material.terms) || field === 'previous_owner' && !isJsonObject(material.credential_template))
|
|
2393
|
+
throw new StateError('Org transfer requires both replacement membership bindings, terms and previous-owner credential', { stringCode: 'invalid', retryable: false });
|
|
2394
|
+
}
|
|
2122
2395
|
this._aidStore = store;
|
|
2123
2396
|
const localStore = store._keystore;
|
|
2124
2397
|
const tokenStore = store._tokenStore;
|
|
2125
2398
|
const inputHash = await ownershipSha256(canonicalJson(params));
|
|
2126
|
-
const raw = await tokenStore.getMetadata(orgAid, 'org_identity_transfer');
|
|
2399
|
+
const raw = await (typeof tokenStore._getMetadataStrict === 'function' ? tokenStore._getMetadataStrict(orgAid, 'org_identity_transfer') : tokenStore.getMetadata(orgAid, 'org_identity_transfer'));
|
|
2127
2400
|
let pending = raw ? JSON.parse(raw) : undefined;
|
|
2401
|
+
if (raw && !isJsonObject(pending))
|
|
2402
|
+
throw new StateError('Org transfer checkpoint is invalid', { stringCode: 'persistence_unavailable' });
|
|
2128
2403
|
if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.input_hash !== inputHash))
|
|
2129
2404
|
throw new StateError('pending Org transfer context changed', { stringCode: 'conflict', retryable: false });
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
if (!isJsonObject(material) || !isJsonObject(material.membership_binding) || !isJsonObject(material.terms) || field === 'previous_owner' && !isJsonObject(material.credential_template))
|
|
2133
|
-
throw new StateError('Org transfer requires both replacement membership bindings, terms and previous-owner credential', { stringCode: 'invalid', retryable: false });
|
|
2134
|
-
}
|
|
2405
|
+
if (!pending)
|
|
2406
|
+
await this._verifyOrgTransferMaterials(params, owner.aid);
|
|
2135
2407
|
const pendingKey = `org-transfer:${orgAid}:${transferId}`;
|
|
2136
2408
|
let keyPair = await localStore.loadPendingGroupBind(pendingKey);
|
|
2137
2409
|
if (!keyPair) {
|
|
@@ -2153,7 +2425,7 @@ export class AUNClient {
|
|
|
2153
2425
|
await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
|
|
2154
2426
|
}
|
|
2155
2427
|
if (pending.phase === 'rekey') {
|
|
2156
|
-
const result = await this.call('org.lifecycle.transferAccept', pending.rekey_params);
|
|
2428
|
+
const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.rekey_params, target));
|
|
2157
2429
|
pending = { ...pending, phase: 'finalize', rekey_result: result };
|
|
2158
2430
|
await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
|
|
2159
2431
|
}
|
|
@@ -2190,7 +2462,7 @@ export class AUNClient {
|
|
|
2190
2462
|
pending = { ...pending, finalize_params: await this.org.prepareRequest('org.lifecycle.transferAccept', final, target, owner, { orgSigner: orgIdentity }) };
|
|
2191
2463
|
await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
|
|
2192
2464
|
}
|
|
2193
|
-
const result = await this.call('org.lifecycle.transferAccept', pending.finalize_params);
|
|
2465
|
+
const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.finalize_params, target));
|
|
2194
2466
|
await this._uploadOrgAgentMd(store, orgAid, pending.source_params);
|
|
2195
2467
|
await tokenStore.setMetadata(orgAid, 'org_identity_transfer', '');
|
|
2196
2468
|
await localStore.clearPendingGroupBind(pendingKey);
|
|
@@ -2467,6 +2739,7 @@ export class AUNClient {
|
|
|
2467
2739
|
return await manager.resumePendingUpload(groupAid);
|
|
2468
2740
|
}
|
|
2469
2741
|
async _uploadGroupAgentMd(store, groupAid, params, group, uploaderAid) {
|
|
2742
|
+
this._aidStore = store;
|
|
2470
2743
|
if (await this._resumeGroupAgentMdUpload(store, groupAid))
|
|
2471
2744
|
return;
|
|
2472
2745
|
if (typeof store.uploadAgentMd !== 'function') {
|
|
@@ -2474,7 +2747,8 @@ export class AUNClient {
|
|
|
2474
2747
|
}
|
|
2475
2748
|
const uploaded = await store.uploadAgentMd(groupAid, await this._groupAgentMdContent(groupAid, params, group), { uploaderAid, entityOwnerResolver: (aid, type) => this._resolveEntityAgentMdOwner(aid, type), entityIdentityGetter: (aid, type) => this._freshEntityCertificate(aid, type), entitySignerResolver: (aid, fingerprint) => this._freshEntitySigner(aid, fingerprint) });
|
|
2476
2749
|
if (!uploaded.ok) {
|
|
2477
|
-
|
|
2750
|
+
const code = ['invalid', 'unavailable', 'forbidden', 'conflict', 'persistence_unavailable'].includes(uploaded.error.code) ? uploaded.error.code : uploaded.error.code === 'NETWORK_ERROR' ? 'unavailable' : 'invalid';
|
|
2751
|
+
throw new StateError(`group agent.md upload failed: ${uploaded.error.message}`, { stringCode: code, retryable: code === 'unavailable' });
|
|
2478
2752
|
}
|
|
2479
2753
|
}
|
|
2480
2754
|
async _freshEntityCertificate(aid, type = 'group') {
|
|
@@ -3150,13 +3424,15 @@ export class AUNClient {
|
|
|
3150
3424
|
async _fillP2pGap() {
|
|
3151
3425
|
return this._delivery.fillP2pGap();
|
|
3152
3426
|
}
|
|
3153
|
-
_markPublishedSeq(ns, seq) {
|
|
3154
|
-
this._delivery.markPublishedSeq(ns, seq);
|
|
3427
|
+
_markPublishedSeq(ns, seq, page) {
|
|
3428
|
+
this._delivery.markPublishedSeq(ns, seq, page);
|
|
3155
3429
|
}
|
|
3156
|
-
async _publishAppEvent(event, payload, source = 'direct', ns = '', batch) {
|
|
3430
|
+
async _publishAppEvent(event, payload, source = 'direct', ns = '', batch, page) {
|
|
3157
3431
|
if (event === 'message.received' && await this._handleOwnershipMessage(payload))
|
|
3158
3432
|
return;
|
|
3159
|
-
|
|
3433
|
+
if (event === 'message.received' && await this._handleCredentialRenewal(payload))
|
|
3434
|
+
return;
|
|
3435
|
+
await this._delivery.publishAppEvent(event, payload, source, ns, batch, page);
|
|
3160
3436
|
}
|
|
3161
3437
|
_echoTimestamp() {
|
|
3162
3438
|
const now = new Date();
|
|
@@ -3197,14 +3473,14 @@ export class AUNClient {
|
|
|
3197
3473
|
const trace = `${this._echoTimestamp()} [AUN-SDK.receive] aid=${this._aid ?? '-'} conn_uptime=${uptime}s`;
|
|
3198
3474
|
msg.payload = { ...payload, text: payload.text + '\n' + trace };
|
|
3199
3475
|
}
|
|
3200
|
-
async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, throughSeq) {
|
|
3201
|
-
await this._delivery.drainOrderedMessages(ns, beforeSeq, pullResponse, persist, undefined, 'pull', throughSeq);
|
|
3476
|
+
async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, throughSeq, page) {
|
|
3477
|
+
await this._delivery.drainOrderedMessages(ns, beforeSeq, pullResponse, persist, undefined, 'pull', throughSeq, page);
|
|
3202
3478
|
}
|
|
3203
|
-
async _publishOrderedMessage(event, ns, seq, payload, source = 'push', batch) {
|
|
3204
|
-
return this._delivery.publishOrderedMessage(event, ns, seq, payload, source, batch);
|
|
3479
|
+
async _publishOrderedMessage(event, ns, seq, payload, source = 'push', batch, page) {
|
|
3480
|
+
return this._delivery.publishOrderedMessage(event, ns, seq, payload, source, batch, page);
|
|
3205
3481
|
}
|
|
3206
|
-
async _publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', batch) {
|
|
3207
|
-
return this._delivery.publishPulledMessage(event, ns, seq, payload, persist, source, batch);
|
|
3482
|
+
async _publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', batch, page) {
|
|
3483
|
+
return this._delivery.publishPulledMessage(event, ns, seq, payload, persist, source, batch, page);
|
|
3208
3484
|
}
|
|
3209
3485
|
_extractGroupIdFromResult(result) {
|
|
3210
3486
|
const group = isJsonObject(result.group) ? result.group : null;
|
|
@@ -3667,76 +3943,99 @@ export class AUNClient {
|
|
|
3667
3943
|
async _verifyIncomingServiceEvent(event, payload) {
|
|
3668
3944
|
const targetAid = this._aid || this._currentAid?.aid || '';
|
|
3669
3945
|
const version = this._serviceEventIdentityVersion;
|
|
3946
|
+
const signal = this._serviceEventIdentityAbort.signal;
|
|
3947
|
+
const expiresAt = Number(payload?.expires_at);
|
|
3948
|
+
const current = () => version === this._serviceEventIdentityVersion && targetAid === (this._aid || this._currentAid?.aid || '') && Date.now() / 1000 < expiresAt;
|
|
3670
3949
|
const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (service, fingerprint) => this._resolveServiceEventCertificate(service, fingerprint), resolveCredentialService: (cid) => this.resolveCredentialService?.(cid) });
|
|
3671
|
-
if (
|
|
3950
|
+
if (!current())
|
|
3672
3951
|
throw new ValidationError('service event receiver identity changed');
|
|
3952
|
+
if (event.replace(/^event\//, '') === 'org.member.changed' && isJsonObject(payload) && Object.hasOwn(payload, 'membership_package')) {
|
|
3953
|
+
await this._getCredentialRenewal().importMembershipPackage(payload, { current, signal });
|
|
3954
|
+
if (!current())
|
|
3955
|
+
throw new ValidationError('service event receiver identity changed');
|
|
3956
|
+
}
|
|
3673
3957
|
return verified;
|
|
3674
3958
|
}
|
|
3675
|
-
async _fetchPeerCert(aid, certFingerprint, timeoutMs = 5000, signedAt) {
|
|
3959
|
+
async _fetchPeerCert(aid, certFingerprint, timeoutMs = 5000, signedAt, agentMdRead = false) {
|
|
3676
3960
|
const tStart = Date.now();
|
|
3677
3961
|
this._clientLog.debug(`_fetchPeerCert enter: aid=${aid} fingerprint=${certFingerprint ?? '<none>'}`);
|
|
3678
3962
|
try {
|
|
3679
|
-
const cacheKey = certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}`);
|
|
3963
|
+
const cacheKey = certCacheKey(aid, certFingerprint) + (signedAt === undefined ? '' : `:signed:${signedAt}`) + (agentMdRead ? ':agentmd' : '');
|
|
3680
3964
|
const cached = this._certCache.get(cacheKey);
|
|
3681
3965
|
const now = Date.now() / 1000;
|
|
3682
3966
|
if (cached && now < cached.refreshAfter) {
|
|
3683
3967
|
this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} source=cache`);
|
|
3684
3968
|
return cached.certPem;
|
|
3685
3969
|
}
|
|
3686
|
-
const
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
// 兼容旧浏览器,不使用 AbortSignal.timeout(Chrome 103+ 才支持)
|
|
3695
|
-
const controller = new AbortController();
|
|
3696
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
3697
|
-
try {
|
|
3698
|
-
const resp = await fetch(certUrl, { signal: controller.signal });
|
|
3699
|
-
if (!resp.ok)
|
|
3700
|
-
throw new ValidationError(`failed to fetch peer cert for ${aid}: HTTP ${resp.status}`);
|
|
3701
|
-
certPem = await resp.text();
|
|
3702
|
-
}
|
|
3703
|
-
finally {
|
|
3704
|
-
clearTimeout(timeoutId);
|
|
3705
|
-
}
|
|
3706
|
-
// H7: 严格校验指纹(DER SHA-256 或 SPKI SHA-256 任一匹配即可)
|
|
3707
|
-
if (certFingerprint) {
|
|
3708
|
-
const expectedFP = String(certFingerprint).trim().toLowerCase();
|
|
3709
|
-
if (!normalizeFingerprintHex(expectedFP)) {
|
|
3710
|
-
throw new ValidationError(`unsupported cert_fingerprint format for ${aid}: ${expectedFP.slice(0, 24)}`);
|
|
3970
|
+
const inFlightKey = `${cacheKey}:timeout:${timeoutMs}`;
|
|
3971
|
+
const pending = this._certFetchInFlight.get(inFlightKey);
|
|
3972
|
+
if (pending)
|
|
3973
|
+
return await pending;
|
|
3974
|
+
const request = (async () => {
|
|
3975
|
+
const gatewayUrl = this._gatewayUrl;
|
|
3976
|
+
if (!gatewayUrl) {
|
|
3977
|
+
throw new ValidationError('gateway url unavailable for e2ee cert fetch');
|
|
3711
3978
|
}
|
|
3712
|
-
|
|
3713
|
-
|
|
3979
|
+
// 跨域时用 peer 所在域的 Gateway URL
|
|
3980
|
+
const peerGatewayUrl = resolvePeerGatewayUrl(gatewayUrl, aid);
|
|
3981
|
+
let certPem;
|
|
3982
|
+
const certUrl = buildCertUrl(peerGatewayUrl, aid, certFingerprint);
|
|
3983
|
+
// 兼容旧浏览器,不使用 AbortSignal.timeout(Chrome 103+ 才支持)
|
|
3984
|
+
const controller = new AbortController();
|
|
3985
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
3986
|
+
try {
|
|
3987
|
+
const resp = await fetch(certUrl, { signal: controller.signal });
|
|
3988
|
+
if (!resp.ok)
|
|
3989
|
+
throw new ValidationError(`failed to fetch peer cert for ${aid}: HTTP ${resp.status}`);
|
|
3990
|
+
certPem = await resp.text();
|
|
3714
3991
|
}
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3992
|
+
finally {
|
|
3993
|
+
clearTimeout(timeoutId);
|
|
3994
|
+
}
|
|
3995
|
+
// H7: 严格校验指纹(DER SHA-256 或 SPKI SHA-256 任一匹配即可)
|
|
3996
|
+
if (certFingerprint) {
|
|
3997
|
+
const expectedFP = String(certFingerprint).trim().toLowerCase();
|
|
3998
|
+
if (!normalizeFingerprintHex(expectedFP)) {
|
|
3999
|
+
throw new ValidationError(`unsupported cert_fingerprint format for ${aid}: ${expectedFP.slice(0, 24)}`);
|
|
4000
|
+
}
|
|
4001
|
+
if (!(await certMatchesFingerprint(certPem, expectedFP))) {
|
|
4002
|
+
throw new ValidationError(`peer cert fingerprint mismatch for ${aid}: expected=${expectedFP.slice(0, 24)}...`);
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
// 完整 PKI 验证:链 + CRL + OCSP + AID 绑定
|
|
4006
|
+
try {
|
|
4007
|
+
if (signedAt === undefined)
|
|
4008
|
+
await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid);
|
|
4009
|
+
else if (agentMdRead)
|
|
4010
|
+
await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, signedAt, false, true);
|
|
4011
|
+
else
|
|
4012
|
+
await this._auth.verifyPeerCertificate(peerGatewayUrl, certPem, aid, signedAt);
|
|
4013
|
+
}
|
|
4014
|
+
catch (exc) {
|
|
4015
|
+
throw new ValidationError(`peer cert verification failed for ${aid}: ${exc}`);
|
|
4016
|
+
}
|
|
4017
|
+
this._certCache.set(cacheKey, {
|
|
4018
|
+
certPem,
|
|
4019
|
+
validatedAt: now,
|
|
4020
|
+
refreshAfter: now + PEER_CERT_CACHE_TTL,
|
|
4021
|
+
});
|
|
4022
|
+
try {
|
|
4023
|
+
// peer 证书只存版本目录,不覆盖 cert.pem
|
|
4024
|
+
await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
|
|
4025
|
+
}
|
|
4026
|
+
catch (exc) {
|
|
4027
|
+
this._clientLog.error(`write cert to keystore failed (aid=${aid}): ${String(exc)}`, exc instanceof Error ? exc : undefined);
|
|
4028
|
+
}
|
|
4029
|
+
this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} source=fetched`);
|
|
4030
|
+
return certPem;
|
|
4031
|
+
})();
|
|
4032
|
+
this._certFetchInFlight.set(inFlightKey, request);
|
|
3731
4033
|
try {
|
|
3732
|
-
|
|
3733
|
-
await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
|
|
4034
|
+
return await request;
|
|
3734
4035
|
}
|
|
3735
|
-
|
|
3736
|
-
this.
|
|
4036
|
+
finally {
|
|
4037
|
+
this._certFetchInFlight.delete(inFlightKey);
|
|
3737
4038
|
}
|
|
3738
|
-
this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} source=fetched`);
|
|
3739
|
-
return certPem;
|
|
3740
4039
|
}
|
|
3741
4040
|
catch (err) {
|
|
3742
4041
|
this._clientLog.debug(`_fetchPeerCert exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4155,6 +4454,7 @@ export class AUNClient {
|
|
|
4155
4454
|
}
|
|
4156
4455
|
// ── 内部:后台任务 ────────────────────────────────
|
|
4157
4456
|
_startBackgroundTasks() {
|
|
4457
|
+
this._startCredentialRenewal();
|
|
4158
4458
|
this._startOwnershipRecovery();
|
|
4159
4459
|
// WebSocket Ping/Pong 由 Gateway 与传输层处理;SDK 不发送 RPC 心跳。
|
|
4160
4460
|
// 短连接生命周期短,不需要 token 刷新。
|
|
@@ -4184,6 +4484,7 @@ export class AUNClient {
|
|
|
4184
4484
|
}
|
|
4185
4485
|
}
|
|
4186
4486
|
_stopBackgroundTasks() {
|
|
4487
|
+
this._stopCredentialRenewal();
|
|
4187
4488
|
this._ownershipRecoveryGeneration += 1;
|
|
4188
4489
|
if (this._ownershipRecoveryTimer !== null) {
|
|
4189
4490
|
clearTimeout(this._ownershipRecoveryTimer);
|
|
@@ -4937,7 +5238,7 @@ export class AUNClient {
|
|
|
4937
5238
|
* undecryptable 事件、成功消费后触发 SPK 轮换。inline Push 在游标提交和
|
|
4938
5239
|
* Head/正文绑定校验前调用本入口时必须显式关闭这些副作用。
|
|
4939
5240
|
*/
|
|
4940
|
-
async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull') {
|
|
5241
|
+
async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull', deferredEffects) {
|
|
4941
5242
|
const session = this._v2Session;
|
|
4942
5243
|
if (!session)
|
|
4943
5244
|
return null;
|
|
@@ -4953,8 +5254,12 @@ export class AUNClient {
|
|
|
4953
5254
|
return null;
|
|
4954
5255
|
}
|
|
4955
5256
|
const e2eeMeta = v2E2eeMeta(envelope);
|
|
4956
|
-
if (observeAgentMd)
|
|
4957
|
-
|
|
5257
|
+
if (observeAgentMd) {
|
|
5258
|
+
if (deferredEffects)
|
|
5259
|
+
deferredEffects.push(() => this._observeAgentMdFromEnvelope(envelope));
|
|
5260
|
+
else
|
|
5261
|
+
await this._observeAgentMdFromEnvelope(envelope);
|
|
5262
|
+
}
|
|
4958
5263
|
// 确定 spk_id 和 recipient_key_source
|
|
4959
5264
|
let spkId = '';
|
|
4960
5265
|
let recipientKeySource = '';
|
|
@@ -5039,7 +5344,10 @@ export class AUNClient {
|
|
|
5039
5344
|
source,
|
|
5040
5345
|
};
|
|
5041
5346
|
attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
5042
|
-
|
|
5347
|
+
if (deferredEffects)
|
|
5348
|
+
deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
|
|
5349
|
+
else
|
|
5350
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
5043
5351
|
}
|
|
5044
5352
|
catch { /* publish 异常不影响主流程 */ }
|
|
5045
5353
|
}
|
|
@@ -5080,7 +5388,10 @@ export class AUNClient {
|
|
|
5080
5388
|
source,
|
|
5081
5389
|
};
|
|
5082
5390
|
attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
5083
|
-
|
|
5391
|
+
if (deferredEffects)
|
|
5392
|
+
deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
|
|
5393
|
+
else
|
|
5394
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
5084
5395
|
}
|
|
5085
5396
|
catch { /* publish 异常不影响主流程 */ }
|
|
5086
5397
|
}
|
|
@@ -5112,7 +5423,10 @@ export class AUNClient {
|
|
|
5112
5423
|
source,
|
|
5113
5424
|
};
|
|
5114
5425
|
attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
5115
|
-
|
|
5426
|
+
if (deferredEffects)
|
|
5427
|
+
deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
|
|
5428
|
+
else
|
|
5429
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
5116
5430
|
}
|
|
5117
5431
|
catch { /* publish 异常不影响主流程 */ }
|
|
5118
5432
|
}
|
|
@@ -5121,8 +5435,12 @@ export class AUNClient {
|
|
|
5121
5435
|
if (plaintext == null)
|
|
5122
5436
|
return null;
|
|
5123
5437
|
// 消费触发 SPK 轮换(fire-and-forget,不阻塞消息处理)
|
|
5124
|
-
if (rotateKeys)
|
|
5125
|
-
|
|
5438
|
+
if (rotateKeys) {
|
|
5439
|
+
if (deferredEffects)
|
|
5440
|
+
deferredEffects.push(() => this._scheduleV2KeyRotationForMessage(msg, envelope));
|
|
5441
|
+
else
|
|
5442
|
+
this._scheduleV2KeyRotationForMessage(msg, envelope);
|
|
5443
|
+
}
|
|
5126
5444
|
const e2ee = v2E2eeMeta(envelope);
|
|
5127
5445
|
let result = {
|
|
5128
5446
|
message_id: String(msg.message_id ?? ''),
|