@agentunion/fastaun-browser 0.5.20 → 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 +47 -0
- package/_packed_docs/CHANGELOG.md +47 -0
- package/_packed_docs/INDEX.md +453 -440
- package/_packed_docs/KITE_DOCS_GUIDE.md +164 -161
- 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/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
- package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -658
- 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 +4 -1
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +147 -14
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
- package/_packed_docs/sdk/09-group-rpc-manual.md +5 -2
- package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
- 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 +134 -130
- package/_packed_docs/sdk/INDEX.md +239 -221
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -922
- 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 +106 -43
- 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 +180 -9
- 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 -708
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -476
- 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 +1 -1
- 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 +2 -2
- package/dist/agent-md-schema.d.ts +1 -0
- package/dist/agent-md-schema.d.ts.map +1 -1
- package/dist/agent-md-schema.js +5 -4
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +25 -1
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +148 -71
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +13 -0
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +55 -8
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +2 -1
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +4981 -1219
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +21 -0
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +101 -16
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +1172 -160
- 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 +122 -48
- 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 +1258 -649
- 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 +391 -87
- 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/events.d.ts +19 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +146 -23
- 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 +363 -7
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +5 -3
- 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 +757 -11
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +21 -4
- package/dist/ownership-v21.d.ts.map +1 -1
- package/dist/ownership-v21.js +49 -16
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +30 -0
- package/dist/ownership-workflow.d.ts.map +1 -1
- package/dist/ownership-workflow.js +412 -54
- 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 +60 -3
- package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
- package/dist/tools/cross-sdk-agent.js +433 -37
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +62 -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;
|
|
@@ -982,6 +1151,21 @@ export class AUNClient {
|
|
|
982
1151
|
this._collab = new CollabClient(this);
|
|
983
1152
|
return this._collab;
|
|
984
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
|
+
}
|
|
985
1169
|
/** Message RPC facade 入口 */
|
|
986
1170
|
get message() {
|
|
987
1171
|
if (!this._messageFacade)
|
|
@@ -1240,9 +1424,9 @@ export class AUNClient {
|
|
|
1240
1424
|
return this._currentAid;
|
|
1241
1425
|
},
|
|
1242
1426
|
readAgentMdFresh: (aid) => this._agentMdManager.readOwnershipFresh(aid),
|
|
1243
|
-
submitAgentMdRaw: (aid, raw, condition) => this._agentMdManager.submitOwnershipRaw(aid, raw, condition),
|
|
1427
|
+
submitAgentMdRaw: (aid, raw, condition, guard) => this._agentMdManager.submitOwnershipRaw(aid, raw, condition, guard),
|
|
1244
1428
|
readOwnerMd: (owner, sub) => storage.readOwnerMd(owner, sub),
|
|
1245
|
-
writeOwnerMd: (owner, sub, raw) => storage.writeOwnerMd(owner, sub, raw),
|
|
1429
|
+
writeOwnerMd: (owner, sub, raw, original, guard) => storage.writeOwnerMd(owner, sub, raw, original, guard),
|
|
1246
1430
|
deleteOwnerMd: (owner, sub) => storage.deleteOwnerMd(owner, sub),
|
|
1247
1431
|
resolveCertificate: async (aid, fingerprint, keyPurpose) => this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, keyPurpose),
|
|
1248
1432
|
});
|
|
@@ -1257,6 +1441,7 @@ export class AUNClient {
|
|
|
1257
1441
|
}
|
|
1258
1442
|
async bindOwneraid(ownerAid) { return await this._ownershipV21().bindOwneraid(ownerAid); }
|
|
1259
1443
|
async bindSubaid(subAid) { return await this._ownershipV21().bindSubaid(subAid); }
|
|
1444
|
+
async resetOwnershipTask() { return await this._ownershipV21().resetOwnershipTask(); }
|
|
1260
1445
|
async releaseSubaid(subAid) { return await this._ownershipV21().releaseSubaid(subAid); }
|
|
1261
1446
|
async transferSubaid(subAid, newOwnerAid) { return await this._ownershipV21().transferSubaid(subAid, newOwnerAid); }
|
|
1262
1447
|
async checkOwnershipConsensus(subAid) { return await this._ownershipV21().checkOwnershipConsensus(subAid); }
|
|
@@ -1805,9 +1990,13 @@ export class AUNClient {
|
|
|
1805
1990
|
loadIdentity(aid) {
|
|
1806
1991
|
const previousAid = this._aid;
|
|
1807
1992
|
this._identityRuntime.loadIdentity(aid);
|
|
1993
|
+
this._stopCredentialRenewal();
|
|
1808
1994
|
this._dispatcher.setEventIdentity(this._aid ?? '');
|
|
1809
|
-
if (previousAid !== this._aid)
|
|
1995
|
+
if (previousAid !== this._aid) {
|
|
1810
1996
|
this._serviceEventIdentityVersion++;
|
|
1997
|
+
this._serviceEventIdentityAbort.abort();
|
|
1998
|
+
this._serviceEventIdentityAbort = new AbortController();
|
|
1999
|
+
}
|
|
1811
2000
|
this._ensureReadStateIdentity();
|
|
1812
2001
|
if (previousAid !== this._aid) {
|
|
1813
2002
|
this._ownershipRecords.clear();
|
|
@@ -1882,6 +2071,10 @@ export class AUNClient {
|
|
|
1882
2071
|
? this._ensureReadStateIdentity() : null;
|
|
1883
2072
|
try {
|
|
1884
2073
|
const result = await this._rpcPipeline.call(method, params);
|
|
2074
|
+
try {
|
|
2075
|
+
await this._getCredentialRenewal().observe(method, params ?? {}, result);
|
|
2076
|
+
}
|
|
2077
|
+
catch { }
|
|
1885
2078
|
if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
|
|
1886
2079
|
this._observeReadStateResult(method, result, params ?? {});
|
|
1887
2080
|
return result;
|
|
@@ -2053,12 +2246,46 @@ export class AUNClient {
|
|
|
2053
2246
|
this._groupIdentityOperations.delete(key);
|
|
2054
2247
|
}
|
|
2055
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
|
+
}
|
|
2056
2275
|
async createOrg(params = {}, options = {}) {
|
|
2057
2276
|
const store = options.aidStore;
|
|
2058
2277
|
const owner = this._currentAid;
|
|
2059
2278
|
const orgAid = String(params.org_id ?? '').trim();
|
|
2060
2279
|
if (!store || !owner?.isPrivateKeyValid() || !orgAid)
|
|
2061
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 });
|
|
2062
2289
|
this._aidStore = store;
|
|
2063
2290
|
let identity = await store.load(orgAid);
|
|
2064
2291
|
if (!identity.ok) {
|
|
@@ -2070,19 +2297,13 @@ export class AUNClient {
|
|
|
2070
2297
|
if (!identity.ok)
|
|
2071
2298
|
throw new StateError(identity.error.message, { stringCode: 'unavailable', retryable: true });
|
|
2072
2299
|
const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
|
|
2073
|
-
const tokens = store._tokenStore;
|
|
2074
|
-
const inputHash = await ownershipSha256(canonicalJson(params));
|
|
2075
|
-
const raw = await tokens.getMetadata(orgAid, 'org_identity_creation');
|
|
2076
|
-
let checkpoint = raw ? JSON.parse(raw) : undefined;
|
|
2077
|
-
if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
|
|
2078
|
-
throw new StateError('pending Org creation context changed', { stringCode: 'conflict', retryable: false });
|
|
2079
2300
|
if (!checkpoint) {
|
|
2080
2301
|
const prepared = await this.org.prepareRequest('org.lifecycle.create', { ...params, owner_aid: owner.aid }, target, owner, { orgSigner: identity.data.aid });
|
|
2081
2302
|
checkpoint = { owner_aid: owner.aid, input_hash: inputHash, source_params: structuredClone(params), prepared, phase: 'create' };
|
|
2082
2303
|
await tokens.setMetadata(orgAid, 'org_identity_creation', JSON.stringify(checkpoint));
|
|
2083
2304
|
}
|
|
2084
2305
|
if (checkpoint.phase === 'create') {
|
|
2085
|
-
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));
|
|
2086
2307
|
checkpoint = { ...checkpoint, result, phase: 'upload' };
|
|
2087
2308
|
await tokens.setMetadata(orgAid, 'org_identity_creation', JSON.stringify(checkpoint));
|
|
2088
2309
|
}
|
|
@@ -2097,10 +2318,12 @@ export class AUNClient {
|
|
|
2097
2318
|
return;
|
|
2098
2319
|
for (const aid of await keyStore.listIdentities()) {
|
|
2099
2320
|
try {
|
|
2100
|
-
const creationRaw = await tokens.getMetadata(aid, 'org_identity_creation');
|
|
2101
|
-
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'));
|
|
2102
2323
|
const creation = creationRaw ? JSON.parse(creationRaw) : undefined;
|
|
2103
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' });
|
|
2104
2327
|
if (creation?.owner_aid === this._aid) {
|
|
2105
2328
|
await this.createOrg(creation.source_params, { aidStore: store });
|
|
2106
2329
|
continue;
|
|
@@ -2123,6 +2346,40 @@ export class AUNClient {
|
|
|
2123
2346
|
}
|
|
2124
2347
|
}
|
|
2125
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
|
+
}
|
|
2126
2383
|
async completeOrgTransfer(params = {}, options = {}) {
|
|
2127
2384
|
const store = options.aidStore;
|
|
2128
2385
|
const owner = this._currentAid;
|
|
@@ -2139,10 +2396,14 @@ export class AUNClient {
|
|
|
2139
2396
|
const localStore = store._keystore;
|
|
2140
2397
|
const tokenStore = store._tokenStore;
|
|
2141
2398
|
const inputHash = await ownershipSha256(canonicalJson(params));
|
|
2142
|
-
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'));
|
|
2143
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' });
|
|
2144
2403
|
if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.input_hash !== inputHash))
|
|
2145
2404
|
throw new StateError('pending Org transfer context changed', { stringCode: 'conflict', retryable: false });
|
|
2405
|
+
if (!pending)
|
|
2406
|
+
await this._verifyOrgTransferMaterials(params, owner.aid);
|
|
2146
2407
|
const pendingKey = `org-transfer:${orgAid}:${transferId}`;
|
|
2147
2408
|
let keyPair = await localStore.loadPendingGroupBind(pendingKey);
|
|
2148
2409
|
if (!keyPair) {
|
|
@@ -2164,7 +2425,7 @@ export class AUNClient {
|
|
|
2164
2425
|
await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
|
|
2165
2426
|
}
|
|
2166
2427
|
if (pending.phase === 'rekey') {
|
|
2167
|
-
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));
|
|
2168
2429
|
pending = { ...pending, phase: 'finalize', rekey_result: result };
|
|
2169
2430
|
await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
|
|
2170
2431
|
}
|
|
@@ -2201,7 +2462,7 @@ export class AUNClient {
|
|
|
2201
2462
|
pending = { ...pending, finalize_params: await this.org.prepareRequest('org.lifecycle.transferAccept', final, target, owner, { orgSigner: orgIdentity }) };
|
|
2202
2463
|
await tokenStore.setMetadata(orgAid, 'org_identity_transfer', JSON.stringify(pending));
|
|
2203
2464
|
}
|
|
2204
|
-
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));
|
|
2205
2466
|
await this._uploadOrgAgentMd(store, orgAid, pending.source_params);
|
|
2206
2467
|
await tokenStore.setMetadata(orgAid, 'org_identity_transfer', '');
|
|
2207
2468
|
await localStore.clearPendingGroupBind(pendingKey);
|
|
@@ -2478,6 +2739,7 @@ export class AUNClient {
|
|
|
2478
2739
|
return await manager.resumePendingUpload(groupAid);
|
|
2479
2740
|
}
|
|
2480
2741
|
async _uploadGroupAgentMd(store, groupAid, params, group, uploaderAid) {
|
|
2742
|
+
this._aidStore = store;
|
|
2481
2743
|
if (await this._resumeGroupAgentMdUpload(store, groupAid))
|
|
2482
2744
|
return;
|
|
2483
2745
|
if (typeof store.uploadAgentMd !== 'function') {
|
|
@@ -3162,13 +3424,15 @@ export class AUNClient {
|
|
|
3162
3424
|
async _fillP2pGap() {
|
|
3163
3425
|
return this._delivery.fillP2pGap();
|
|
3164
3426
|
}
|
|
3165
|
-
_markPublishedSeq(ns, seq) {
|
|
3166
|
-
this._delivery.markPublishedSeq(ns, seq);
|
|
3427
|
+
_markPublishedSeq(ns, seq, page) {
|
|
3428
|
+
this._delivery.markPublishedSeq(ns, seq, page);
|
|
3167
3429
|
}
|
|
3168
|
-
async _publishAppEvent(event, payload, source = 'direct', ns = '', batch) {
|
|
3430
|
+
async _publishAppEvent(event, payload, source = 'direct', ns = '', batch, page) {
|
|
3169
3431
|
if (event === 'message.received' && await this._handleOwnershipMessage(payload))
|
|
3170
3432
|
return;
|
|
3171
|
-
|
|
3433
|
+
if (event === 'message.received' && await this._handleCredentialRenewal(payload))
|
|
3434
|
+
return;
|
|
3435
|
+
await this._delivery.publishAppEvent(event, payload, source, ns, batch, page);
|
|
3172
3436
|
}
|
|
3173
3437
|
_echoTimestamp() {
|
|
3174
3438
|
const now = new Date();
|
|
@@ -3209,14 +3473,14 @@ export class AUNClient {
|
|
|
3209
3473
|
const trace = `${this._echoTimestamp()} [AUN-SDK.receive] aid=${this._aid ?? '-'} conn_uptime=${uptime}s`;
|
|
3210
3474
|
msg.payload = { ...payload, text: payload.text + '\n' + trace };
|
|
3211
3475
|
}
|
|
3212
|
-
async _drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, throughSeq) {
|
|
3213
|
-
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);
|
|
3214
3478
|
}
|
|
3215
|
-
async _publishOrderedMessage(event, ns, seq, payload, source = 'push', batch) {
|
|
3216
|
-
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);
|
|
3217
3481
|
}
|
|
3218
|
-
async _publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', batch) {
|
|
3219
|
-
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);
|
|
3220
3484
|
}
|
|
3221
3485
|
_extractGroupIdFromResult(result) {
|
|
3222
3486
|
const group = isJsonObject(result.group) ? result.group : null;
|
|
@@ -3679,9 +3943,17 @@ export class AUNClient {
|
|
|
3679
3943
|
async _verifyIncomingServiceEvent(event, payload) {
|
|
3680
3944
|
const targetAid = this._aid || this._currentAid?.aid || '';
|
|
3681
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;
|
|
3682
3949
|
const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (service, fingerprint) => this._resolveServiceEventCertificate(service, fingerprint), resolveCredentialService: (cid) => this.resolveCredentialService?.(cid) });
|
|
3683
|
-
if (
|
|
3950
|
+
if (!current())
|
|
3684
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
|
+
}
|
|
3685
3957
|
return verified;
|
|
3686
3958
|
}
|
|
3687
3959
|
async _fetchPeerCert(aid, certFingerprint, timeoutMs = 5000, signedAt, agentMdRead = false) {
|
|
@@ -3695,62 +3967,75 @@ export class AUNClient {
|
|
|
3695
3967
|
this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} source=cache`);
|
|
3696
3968
|
return cached.certPem;
|
|
3697
3969
|
}
|
|
3698
|
-
const
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
// 兼容旧浏览器,不使用 AbortSignal.timeout(Chrome 103+ 才支持)
|
|
3707
|
-
const controller = new AbortController();
|
|
3708
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
3709
|
-
try {
|
|
3710
|
-
const resp = await fetch(certUrl, { signal: controller.signal });
|
|
3711
|
-
if (!resp.ok)
|
|
3712
|
-
throw new ValidationError(`failed to fetch peer cert for ${aid}: HTTP ${resp.status}`);
|
|
3713
|
-
certPem = await resp.text();
|
|
3714
|
-
}
|
|
3715
|
-
finally {
|
|
3716
|
-
clearTimeout(timeoutId);
|
|
3717
|
-
}
|
|
3718
|
-
// H7: 严格校验指纹(DER SHA-256 或 SPKI SHA-256 任一匹配即可)
|
|
3719
|
-
if (certFingerprint) {
|
|
3720
|
-
const expectedFP = String(certFingerprint).trim().toLowerCase();
|
|
3721
|
-
if (!normalizeFingerprintHex(expectedFP)) {
|
|
3722
|
-
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');
|
|
3723
3978
|
}
|
|
3724
|
-
|
|
3725
|
-
|
|
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();
|
|
3726
3991
|
}
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
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);
|
|
3745
4033
|
try {
|
|
3746
|
-
|
|
3747
|
-
await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
|
|
4034
|
+
return await request;
|
|
3748
4035
|
}
|
|
3749
|
-
|
|
3750
|
-
this.
|
|
4036
|
+
finally {
|
|
4037
|
+
this._certFetchInFlight.delete(inFlightKey);
|
|
3751
4038
|
}
|
|
3752
|
-
this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} source=fetched`);
|
|
3753
|
-
return certPem;
|
|
3754
4039
|
}
|
|
3755
4040
|
catch (err) {
|
|
3756
4041
|
this._clientLog.debug(`_fetchPeerCert exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -4169,6 +4454,7 @@ export class AUNClient {
|
|
|
4169
4454
|
}
|
|
4170
4455
|
// ── 内部:后台任务 ────────────────────────────────
|
|
4171
4456
|
_startBackgroundTasks() {
|
|
4457
|
+
this._startCredentialRenewal();
|
|
4172
4458
|
this._startOwnershipRecovery();
|
|
4173
4459
|
// WebSocket Ping/Pong 由 Gateway 与传输层处理;SDK 不发送 RPC 心跳。
|
|
4174
4460
|
// 短连接生命周期短,不需要 token 刷新。
|
|
@@ -4198,6 +4484,7 @@ export class AUNClient {
|
|
|
4198
4484
|
}
|
|
4199
4485
|
}
|
|
4200
4486
|
_stopBackgroundTasks() {
|
|
4487
|
+
this._stopCredentialRenewal();
|
|
4201
4488
|
this._ownershipRecoveryGeneration += 1;
|
|
4202
4489
|
if (this._ownershipRecoveryTimer !== null) {
|
|
4203
4490
|
clearTimeout(this._ownershipRecoveryTimer);
|
|
@@ -4951,7 +5238,7 @@ export class AUNClient {
|
|
|
4951
5238
|
* undecryptable 事件、成功消费后触发 SPK 轮换。inline Push 在游标提交和
|
|
4952
5239
|
* Head/正文绑定校验前调用本入口时必须显式关闭这些副作用。
|
|
4953
5240
|
*/
|
|
4954
|
-
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) {
|
|
4955
5242
|
const session = this._v2Session;
|
|
4956
5243
|
if (!session)
|
|
4957
5244
|
return null;
|
|
@@ -4967,8 +5254,12 @@ export class AUNClient {
|
|
|
4967
5254
|
return null;
|
|
4968
5255
|
}
|
|
4969
5256
|
const e2eeMeta = v2E2eeMeta(envelope);
|
|
4970
|
-
if (observeAgentMd)
|
|
4971
|
-
|
|
5257
|
+
if (observeAgentMd) {
|
|
5258
|
+
if (deferredEffects)
|
|
5259
|
+
deferredEffects.push(() => this._observeAgentMdFromEnvelope(envelope));
|
|
5260
|
+
else
|
|
5261
|
+
await this._observeAgentMdFromEnvelope(envelope);
|
|
5262
|
+
}
|
|
4972
5263
|
// 确定 spk_id 和 recipient_key_source
|
|
4973
5264
|
let spkId = '';
|
|
4974
5265
|
let recipientKeySource = '';
|
|
@@ -5053,7 +5344,10 @@ export class AUNClient {
|
|
|
5053
5344
|
source,
|
|
5054
5345
|
};
|
|
5055
5346
|
attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
5056
|
-
|
|
5347
|
+
if (deferredEffects)
|
|
5348
|
+
deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
|
|
5349
|
+
else
|
|
5350
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
5057
5351
|
}
|
|
5058
5352
|
catch { /* publish 异常不影响主流程 */ }
|
|
5059
5353
|
}
|
|
@@ -5094,7 +5388,10 @@ export class AUNClient {
|
|
|
5094
5388
|
source,
|
|
5095
5389
|
};
|
|
5096
5390
|
attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
5097
|
-
|
|
5391
|
+
if (deferredEffects)
|
|
5392
|
+
deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
|
|
5393
|
+
else
|
|
5394
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
5098
5395
|
}
|
|
5099
5396
|
catch { /* publish 异常不影响主流程 */ }
|
|
5100
5397
|
}
|
|
@@ -5126,7 +5423,10 @@ export class AUNClient {
|
|
|
5126
5423
|
source,
|
|
5127
5424
|
};
|
|
5128
5425
|
attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
5129
|
-
|
|
5426
|
+
if (deferredEffects)
|
|
5427
|
+
deferredEffects.push(() => this._dispatcher.enqueue(undecryptableEvent, event));
|
|
5428
|
+
else
|
|
5429
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
5130
5430
|
}
|
|
5131
5431
|
catch { /* publish 异常不影响主流程 */ }
|
|
5132
5432
|
}
|
|
@@ -5135,8 +5435,12 @@ export class AUNClient {
|
|
|
5135
5435
|
if (plaintext == null)
|
|
5136
5436
|
return null;
|
|
5137
5437
|
// 消费触发 SPK 轮换(fire-and-forget,不阻塞消息处理)
|
|
5138
|
-
if (rotateKeys)
|
|
5139
|
-
|
|
5438
|
+
if (rotateKeys) {
|
|
5439
|
+
if (deferredEffects)
|
|
5440
|
+
deferredEffects.push(() => this._scheduleV2KeyRotationForMessage(msg, envelope));
|
|
5441
|
+
else
|
|
5442
|
+
this._scheduleV2KeyRotationForMessage(msg, envelope);
|
|
5443
|
+
}
|
|
5140
5444
|
const e2ee = v2E2eeMeta(envelope);
|
|
5141
5445
|
let result = {
|
|
5142
5446
|
message_id: String(msg.message_id ?? ''),
|