gdc-common-utils-ts 2.0.17 → 2.1.0
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/README.md +0 -1
- package/dist/constants/verifiable-credentials.d.ts +7 -0
- package/dist/constants/verifiable-credentials.js +7 -0
- package/dist/examples/bundle-didcomm-payload.d.ts +11 -0
- package/dist/examples/bundle-didcomm-payload.js +13 -0
- package/dist/examples/communication-attached-bundle-session.js +2 -1
- package/dist/examples/communication-didcomm-payload.d.ts +35 -0
- package/dist/examples/communication-didcomm-payload.js +38 -0
- package/dist/examples/contract-examples.d.ts +1 -0
- package/dist/examples/contract-examples.js +1 -0
- package/dist/examples/index.d.ts +5 -0
- package/dist/examples/index.js +5 -0
- package/dist/examples/inter-tenant-access-contract.d.ts +124 -0
- package/dist/examples/inter-tenant-access-contract.js +134 -0
- package/dist/examples/profile-manager-mem.d.ts +45 -0
- package/dist/examples/profile-manager-mem.js +29 -0
- package/dist/examples/shared.d.ts +6 -0
- package/dist/examples/shared.js +6 -0
- package/dist/examples/wallet-mem.d.ts +38 -0
- package/dist/examples/wallet-mem.js +40 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/interfaces/IProfileOutboxRepository.d.ts +36 -0
- package/dist/interfaces/IProfileOutboxRepository.js +2 -0
- package/dist/interfaces/IWallet.d.ts +74 -0
- package/dist/interfaces/IWallet.js +2 -0
- package/dist/interfaces/IWalletQueue.d.ts +30 -0
- package/dist/interfaces/IWalletQueue.js +2 -0
- package/dist/interfaces/index.d.ts +8 -0
- package/dist/interfaces/index.js +8 -0
- package/dist/models/communication-attached-bundle-session.d.ts +193 -0
- package/dist/models/communication-attached-bundle-session.js +34 -0
- package/dist/models/index.d.ts +4 -0
- package/dist/models/index.js +4 -0
- package/dist/models/inter-tenant-access-contract.d.ts +105 -0
- package/dist/models/inter-tenant-access-contract.js +78 -0
- package/dist/models/interoperable-claims/diagnostic-report-claims.d.ts +1 -2
- package/dist/models/interoperable-claims/diagnostic-report-claims.js +1 -2
- package/dist/models/profile-manager.d.ts +85 -0
- package/dist/models/profile-manager.js +2 -0
- package/dist/models/wallet.d.ts +59 -0
- package/dist/models/wallet.js +22 -0
- package/dist/utils/backend-message-manager-mem.d.ts +67 -0
- package/dist/utils/backend-message-manager-mem.js +227 -0
- package/dist/utils/bundle-didcomm-payload.d.ts +29 -0
- package/dist/utils/bundle-didcomm-payload.js +48 -0
- package/dist/utils/communication-attached-bundle-session-helpers.d.ts +54 -0
- package/dist/utils/communication-attached-bundle-session-helpers.js +517 -0
- package/dist/utils/communication-attached-bundle-session.d.ts +59 -302
- package/dist/utils/communication-attached-bundle-session.js +114 -839
- package/dist/utils/communication-consent-access-editor.d.ts +85 -0
- package/dist/utils/communication-consent-access-editor.js +244 -0
- package/dist/utils/communication-didcomm-payload.d.ts +33 -0
- package/dist/utils/communication-didcomm-payload.js +75 -0
- package/dist/utils/evidence-blockchain-references.d.ts +2 -0
- package/dist/utils/evidence-blockchain-references.js +4 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/inter-tenant-access-contract.d.ts +44 -0
- package/dist/utils/inter-tenant-access-contract.js +358 -0
- package/dist/utils/organization-authorization-urn.d.ts +43 -0
- package/dist/utils/organization-authorization-urn.js +87 -0
- package/dist/utils/profile-manager-mem.d.ts +69 -0
- package/dist/utils/profile-manager-mem.js +79 -0
- package/dist/utils/profile-outbox-memory-repository.d.ts +34 -0
- package/dist/utils/profile-outbox-memory-repository.js +57 -0
- package/dist/utils/wallet-mem.d.ts +93 -0
- package/dist/utils/wallet-mem.js +277 -0
- package/dist/utils/wallet-memory-queue.d.ts +32 -0
- package/dist/utils/wallet-memory-queue.js +82 -0
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
|
|
2
|
+
/**
|
|
3
|
+
* Canonical flat claim keys used when a frontend captures an inter-tenant
|
|
4
|
+
* access contract form and sends it to a backend before the final FHIR
|
|
5
|
+
* `Contract` resource is assembled.
|
|
6
|
+
*
|
|
7
|
+
* Notes:
|
|
8
|
+
* - these keys are claims-first transport helpers, not the legal source of
|
|
9
|
+
* truth
|
|
10
|
+
* - the canonical legal payload is the FHIR `Contract` placed in
|
|
11
|
+
* `credentialSubject` of the emitted VC
|
|
12
|
+
* - custom flat keys are used only where base FHIR does not define a simple
|
|
13
|
+
* one-claim scalar path suitable for UI transport
|
|
14
|
+
*/
|
|
15
|
+
export var ClaimInterTenantAccessContract;
|
|
16
|
+
(function (ClaimInterTenantAccessContract) {
|
|
17
|
+
/** Stable business identifier of the agreement/contract. */
|
|
18
|
+
ClaimInterTenantAccessContract["identifier"] = "Contract.identifier";
|
|
19
|
+
/** FHIR Contract lifecycle status, for example `executed` or `amended`. */
|
|
20
|
+
ClaimInterTenantAccessContract["status"] = "Contract.status";
|
|
21
|
+
/** When the agreement was formally issued. */
|
|
22
|
+
ClaimInterTenantAccessContract["issued"] = "Contract.issued";
|
|
23
|
+
/** Agreement validity start date/time. */
|
|
24
|
+
ClaimInterTenantAccessContract["appliesStart"] = "Contract.applies-start";
|
|
25
|
+
/** Agreement validity end date/time. */
|
|
26
|
+
ClaimInterTenantAccessContract["appliesEnd"] = "Contract.applies-end";
|
|
27
|
+
/**
|
|
28
|
+
* Provider organization DID.
|
|
29
|
+
*
|
|
30
|
+
* Meaning for programmers:
|
|
31
|
+
* - this is the tenant that owns/exposes the protected data
|
|
32
|
+
* - in the current example this is `did:web:api.acme.org`
|
|
33
|
+
*/
|
|
34
|
+
ClaimInterTenantAccessContract["providerOrganization"] = "Contract.provider-organization";
|
|
35
|
+
/**
|
|
36
|
+
* Consumer organization DID.
|
|
37
|
+
*
|
|
38
|
+
* Meaning for programmers:
|
|
39
|
+
* - this is the foreign tenant requesting access under the agreement
|
|
40
|
+
* - in the current example this is `did:web:api.lab.org`
|
|
41
|
+
*/
|
|
42
|
+
ClaimInterTenantAccessContract["consumerOrganization"] = "Contract.consumer-organization";
|
|
43
|
+
/**
|
|
44
|
+
* DID of the controller who signed on behalf of the provider organization.
|
|
45
|
+
*/
|
|
46
|
+
ClaimInterTenantAccessContract["providerController"] = "Contract.provider-controller";
|
|
47
|
+
/**
|
|
48
|
+
* DID of the controller who signed on behalf of the consumer organization.
|
|
49
|
+
*/
|
|
50
|
+
ClaimInterTenantAccessContract["consumerController"] = "Contract.consumer-controller";
|
|
51
|
+
/**
|
|
52
|
+
* Allowed operational capability, expressed with the same vocabulary used by
|
|
53
|
+
* GW service capabilities and SMART-like root scopes.
|
|
54
|
+
*
|
|
55
|
+
* Example:
|
|
56
|
+
* - `organization/Composition.rs`
|
|
57
|
+
*/
|
|
58
|
+
ClaimInterTenantAccessContract["capability"] = "Contract.security-label";
|
|
59
|
+
/**
|
|
60
|
+
* Allowed business/legal purpose for the access.
|
|
61
|
+
*
|
|
62
|
+
* Example:
|
|
63
|
+
* - `RESEARCH`
|
|
64
|
+
*/
|
|
65
|
+
ClaimInterTenantAccessContract["purpose"] = "Contract.term-type";
|
|
66
|
+
/**
|
|
67
|
+
* External instantiated artifact for the agreement, typically the signed PDF
|
|
68
|
+
* or CID/URL of the contract itself plus direct contractual annexes.
|
|
69
|
+
*
|
|
70
|
+
* Canonical FHIR field:
|
|
71
|
+
* - `Contract.instantiatesUri`
|
|
72
|
+
*
|
|
73
|
+
* Flat claim rule in this stack:
|
|
74
|
+
* - use FHIR-style `instantiates-uri` in claims, then map it to
|
|
75
|
+
* `instantiatesUri` in the actual FHIR resource object
|
|
76
|
+
*/
|
|
77
|
+
ClaimInterTenantAccessContract["instantiatesUri"] = "Contract.instantiates-uri";
|
|
78
|
+
})(ClaimInterTenantAccessContract || (ClaimInterTenantAccessContract = {}));
|
|
@@ -91,6 +91,5 @@ export declare const DiagnosticReportSearchParamToClaimKey: Record<DiagnosticRep
|
|
|
91
91
|
*
|
|
92
92
|
* Keep `DiagnosticReport` paused at the typing/mapping layer until:
|
|
93
93
|
* 1. `claims-helpers-diagnostic-report.ts` exists
|
|
94
|
-
* 2.
|
|
95
|
-
* 3. GW Core readers/tests consume the same shared claim keys
|
|
94
|
+
* 2. GW Core readers/tests consume the same shared claim keys
|
|
96
95
|
*/
|
|
@@ -100,6 +100,5 @@ export const DiagnosticReportSearchParamToClaimKey = {
|
|
|
100
100
|
*
|
|
101
101
|
* Keep `DiagnosticReport` paused at the typing/mapping layer until:
|
|
102
102
|
* 1. `claims-helpers-diagnostic-report.ts` exists
|
|
103
|
-
* 2.
|
|
104
|
-
* 3. GW Core readers/tests consume the same shared claim keys
|
|
103
|
+
* 2. GW Core readers/tests consume the same shared claim keys
|
|
105
104
|
*/
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { WalletMessagePriority, WalletQueuedMessage } from './wallet';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal local profile descriptor for memory-backed wallet/runtime tests.
|
|
4
|
+
*
|
|
5
|
+
* This stays intentionally below actor-specific SDK facades.
|
|
6
|
+
*/
|
|
7
|
+
export type ProfileManagerMemProfile = Readonly<{
|
|
8
|
+
profileId: string;
|
|
9
|
+
displayName?: string;
|
|
10
|
+
did: string;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Result returned by one backend message transport after submitting one local
|
|
14
|
+
* queued message envelope.
|
|
15
|
+
*/
|
|
16
|
+
export type BackendMessageTransportSubmitResult = Readonly<{
|
|
17
|
+
accepted: boolean;
|
|
18
|
+
retryable?: boolean;
|
|
19
|
+
errorMessage?: string;
|
|
20
|
+
responseEnvelope?: string;
|
|
21
|
+
locationUrl?: string;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Result returned by one backend message poll operation after a previous
|
|
25
|
+
* transport submit accepted the request asynchronously.
|
|
26
|
+
*/
|
|
27
|
+
export type BackendMessageTransportPollResult = Readonly<{
|
|
28
|
+
pending: boolean;
|
|
29
|
+
completed?: boolean;
|
|
30
|
+
retryable?: boolean;
|
|
31
|
+
errorMessage?: string;
|
|
32
|
+
responseEnvelope?: string;
|
|
33
|
+
}>;
|
|
34
|
+
/**
|
|
35
|
+
* Pluggable submit-only transport used by the low-level backend message manager.
|
|
36
|
+
*/
|
|
37
|
+
export type BackendMessageTransport = Readonly<{
|
|
38
|
+
submit(input: Readonly<{
|
|
39
|
+
message: WalletQueuedMessage;
|
|
40
|
+
envelope: string;
|
|
41
|
+
}>): Promise<BackendMessageTransportSubmitResult>;
|
|
42
|
+
poll?(input: Readonly<{
|
|
43
|
+
message: ProfileOutboxMessageRecord;
|
|
44
|
+
}>): Promise<BackendMessageTransportPollResult>;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* One decoded inbound response associated with one DIDComm thread.
|
|
48
|
+
*/
|
|
49
|
+
export type BackendMessageResponseRecord = Readonly<{
|
|
50
|
+
thid: string;
|
|
51
|
+
receivedAt: string;
|
|
52
|
+
content: Record<string, unknown>;
|
|
53
|
+
meta: Record<string, unknown>;
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Immutable historical record written after one profile message is submitted.
|
|
57
|
+
*
|
|
58
|
+
* It complements the mutable queue state by preserving:
|
|
59
|
+
* - the final submitted envelope
|
|
60
|
+
* - the last known queue status at submission time
|
|
61
|
+
* - the optional decoded response thread reference
|
|
62
|
+
*/
|
|
63
|
+
export type ProfileOutboxMessageRecord = Readonly<{
|
|
64
|
+
id: string;
|
|
65
|
+
thid?: string;
|
|
66
|
+
messageType?: string;
|
|
67
|
+
priority: string;
|
|
68
|
+
status: string;
|
|
69
|
+
transportStatus?: 'submitted' | 'completed' | 'failed';
|
|
70
|
+
recordedAt: string;
|
|
71
|
+
payload: Record<string, unknown>;
|
|
72
|
+
envelope: string;
|
|
73
|
+
responseThid?: string;
|
|
74
|
+
locationUrl?: string;
|
|
75
|
+
pollCount?: number;
|
|
76
|
+
}>;
|
|
77
|
+
/**
|
|
78
|
+
* Input used by `BackendMessageManagerMem` and `ProfileManagerMem` to queue one
|
|
79
|
+
* outbound business payload.
|
|
80
|
+
*/
|
|
81
|
+
export type QueueProfileMessageInput = Readonly<{
|
|
82
|
+
payload: Record<string, unknown>;
|
|
83
|
+
priority?: WalletMessagePriority;
|
|
84
|
+
recipientEncryptionJwk: Record<string, unknown>;
|
|
85
|
+
}>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { JWK } from './jwk';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical priority levels for wallet-managed outbound messages.
|
|
4
|
+
*
|
|
5
|
+
* Ordering rule:
|
|
6
|
+
* - `emergency` is always processed before all other priorities
|
|
7
|
+
* - for the same priority level, insertion order must be preserved
|
|
8
|
+
*/
|
|
9
|
+
export declare const WalletMessagePriorities: Readonly<{
|
|
10
|
+
readonly Emergency: "emergency";
|
|
11
|
+
readonly High: "high";
|
|
12
|
+
readonly Normal: "normal";
|
|
13
|
+
readonly Low: "low";
|
|
14
|
+
}>;
|
|
15
|
+
export type WalletMessagePriority = typeof WalletMessagePriorities[keyof typeof WalletMessagePriorities];
|
|
16
|
+
/**
|
|
17
|
+
* Lifecycle states for one local wallet queue/outbox message.
|
|
18
|
+
*/
|
|
19
|
+
export declare const WalletQueueStatuses: Readonly<{
|
|
20
|
+
readonly Pending: "pending";
|
|
21
|
+
readonly Delivered: "delivered";
|
|
22
|
+
readonly Failed: "failed";
|
|
23
|
+
}>;
|
|
24
|
+
export type WalletQueueStatus = typeof WalletQueueStatuses[keyof typeof WalletQueueStatuses];
|
|
25
|
+
/**
|
|
26
|
+
* One message tracked by the local wallet queue or outbox.
|
|
27
|
+
*/
|
|
28
|
+
export type WalletQueuedMessage = Readonly<{
|
|
29
|
+
id: string;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
priority: WalletMessagePriority;
|
|
32
|
+
status: WalletQueueStatus;
|
|
33
|
+
sequence: number;
|
|
34
|
+
payload: Record<string, unknown>;
|
|
35
|
+
thid?: string;
|
|
36
|
+
messageType?: string;
|
|
37
|
+
deliveredAt?: string;
|
|
38
|
+
errorMessage?: string;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Input accepted by wallet-local queue implementations when creating one
|
|
42
|
+
* queued outbound message.
|
|
43
|
+
*/
|
|
44
|
+
export type WalletEnqueueMessageInput = Readonly<{
|
|
45
|
+
payload: Record<string, unknown>;
|
|
46
|
+
priority?: WalletMessagePriority;
|
|
47
|
+
thid?: string;
|
|
48
|
+
messageType?: string;
|
|
49
|
+
id?: string;
|
|
50
|
+
createdAt?: string;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Public summary of one wallet-managed entity key set.
|
|
54
|
+
*/
|
|
55
|
+
export type WalletManagedEntityDescriptor = Readonly<{
|
|
56
|
+
entityId: string;
|
|
57
|
+
signingJwk: JWK;
|
|
58
|
+
encryptionJwk: JWK;
|
|
59
|
+
}>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Copyright 2026 Conectate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
|
|
2
|
+
/**
|
|
3
|
+
* Canonical priority levels for wallet-managed outbound messages.
|
|
4
|
+
*
|
|
5
|
+
* Ordering rule:
|
|
6
|
+
* - `emergency` is always processed before all other priorities
|
|
7
|
+
* - for the same priority level, insertion order must be preserved
|
|
8
|
+
*/
|
|
9
|
+
export const WalletMessagePriorities = Object.freeze({
|
|
10
|
+
Emergency: 'emergency',
|
|
11
|
+
High: 'high',
|
|
12
|
+
Normal: 'normal',
|
|
13
|
+
Low: 'low',
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Lifecycle states for one local wallet queue/outbox message.
|
|
17
|
+
*/
|
|
18
|
+
export const WalletQueueStatuses = Object.freeze({
|
|
19
|
+
Pending: 'pending',
|
|
20
|
+
Delivered: 'delivered',
|
|
21
|
+
Failed: 'failed',
|
|
22
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { IWallet } from '../interfaces/IWallet';
|
|
2
|
+
import type { IProfileOutboxRepository } from '../interfaces/IProfileOutboxRepository';
|
|
3
|
+
import type { BackendMessageResponseRecord, BackendMessageTransport, ProfileOutboxMessageRecord, QueueProfileMessageInput } from '../models/profile-manager';
|
|
4
|
+
import { type WalletQueuedMessage } from '../models/wallet';
|
|
5
|
+
export type BackendMessageManagerMemOptions = Readonly<{
|
|
6
|
+
wallet: IWallet;
|
|
7
|
+
entityId: string;
|
|
8
|
+
transport: BackendMessageTransport;
|
|
9
|
+
outboxRepository: IProfileOutboxRepository;
|
|
10
|
+
}>;
|
|
11
|
+
/**
|
|
12
|
+
* Memory-backed backend message manager that:
|
|
13
|
+
* - queues outbound payloads through `WalletMem`
|
|
14
|
+
* - packs the next pending message as DIDComm-like JWS+JWE
|
|
15
|
+
* - submits it through an injected transport
|
|
16
|
+
* - unwraps the response and indexes it by `thid`
|
|
17
|
+
*
|
|
18
|
+
* This is the lowest reusable BFF/proxy slice before actor-specific SDK
|
|
19
|
+
* facades appear.
|
|
20
|
+
*/
|
|
21
|
+
export declare class BackendMessageManagerMem {
|
|
22
|
+
private readonly wallet;
|
|
23
|
+
private readonly entityId;
|
|
24
|
+
private readonly transport;
|
|
25
|
+
private readonly outboxRepository;
|
|
26
|
+
private readonly responsesByThread;
|
|
27
|
+
constructor(options: BackendMessageManagerMemOptions);
|
|
28
|
+
/**
|
|
29
|
+
* Queues one outbound business payload for later submission.
|
|
30
|
+
*/
|
|
31
|
+
queueMessage(input: QueueProfileMessageInput): Promise<WalletQueuedMessage>;
|
|
32
|
+
/**
|
|
33
|
+
* Returns queued messages in current priority/FIFO processing order.
|
|
34
|
+
*/
|
|
35
|
+
listQueuedMessages(): Promise<WalletQueuedMessage[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Submits the next pending message through the injected transport and stores
|
|
38
|
+
* the decoded response by `thid` when one response envelope is returned.
|
|
39
|
+
*/
|
|
40
|
+
submitNextPendingMessage(recipientEncryptionJwk: Record<string, unknown>): Promise<{
|
|
41
|
+
message: WalletQueuedMessage;
|
|
42
|
+
response?: BackendMessageResponseRecord;
|
|
43
|
+
pending?: boolean;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* Returns the latest decoded response for one DIDComm thread.
|
|
47
|
+
*/
|
|
48
|
+
getResponseByThreadId(thid: string): BackendMessageResponseRecord | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Lists submitted message history records preserved by the outbox repository.
|
|
51
|
+
*/
|
|
52
|
+
listOutboxHistory(): Promise<ProfileOutboxMessageRecord[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Polls one previously submitted asynchronous message by id.
|
|
55
|
+
*/
|
|
56
|
+
pollSubmittedMessage(messageId: string): Promise<{
|
|
57
|
+
pending: boolean;
|
|
58
|
+
response?: BackendMessageResponseRecord;
|
|
59
|
+
record: ProfileOutboxMessageRecord;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Reads one decoded response by thread id, first from memory and then from the repository.
|
|
63
|
+
*/
|
|
64
|
+
readResponseByThreadId(thid: string): Promise<BackendMessageResponseRecord | undefined>;
|
|
65
|
+
private persistMessageRecord;
|
|
66
|
+
private requireOutboxRecord;
|
|
67
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// Copyright 2026 Conectate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
|
|
2
|
+
import { WalletQueueStatuses } from '../models/wallet.js';
|
|
3
|
+
function clone(value) {
|
|
4
|
+
return JSON.parse(JSON.stringify(value));
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Memory-backed backend message manager that:
|
|
8
|
+
* - queues outbound payloads through `WalletMem`
|
|
9
|
+
* - packs the next pending message as DIDComm-like JWS+JWE
|
|
10
|
+
* - submits it through an injected transport
|
|
11
|
+
* - unwraps the response and indexes it by `thid`
|
|
12
|
+
*
|
|
13
|
+
* This is the lowest reusable BFF/proxy slice before actor-specific SDK
|
|
14
|
+
* facades appear.
|
|
15
|
+
*/
|
|
16
|
+
export class BackendMessageManagerMem {
|
|
17
|
+
wallet;
|
|
18
|
+
entityId;
|
|
19
|
+
transport;
|
|
20
|
+
outboxRepository;
|
|
21
|
+
responsesByThread = new Map();
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.wallet = options.wallet;
|
|
24
|
+
this.entityId = String(options.entityId || '').trim();
|
|
25
|
+
this.transport = options.transport;
|
|
26
|
+
this.outboxRepository = options.outboxRepository;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Queues one outbound business payload for later submission.
|
|
30
|
+
*/
|
|
31
|
+
async queueMessage(input) {
|
|
32
|
+
if (!this.wallet.enqueueMessage) {
|
|
33
|
+
throw new Error('BackendMessageManagerMem requires wallet.enqueueMessage().');
|
|
34
|
+
}
|
|
35
|
+
const thid = typeof input.payload['thid'] === 'string' ? String(input.payload['thid']) : undefined;
|
|
36
|
+
const messageType = typeof input.payload['type'] === 'string' ? String(input.payload['type']) : undefined;
|
|
37
|
+
return this.wallet.enqueueMessage({
|
|
38
|
+
payload: input.payload,
|
|
39
|
+
priority: input.priority,
|
|
40
|
+
thid,
|
|
41
|
+
messageType,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Returns queued messages in current priority/FIFO processing order.
|
|
46
|
+
*/
|
|
47
|
+
async listQueuedMessages() {
|
|
48
|
+
if (!this.wallet.listMessages) {
|
|
49
|
+
throw new Error('BackendMessageManagerMem requires wallet.listMessages().');
|
|
50
|
+
}
|
|
51
|
+
return this.wallet.listMessages();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Submits the next pending message through the injected transport and stores
|
|
55
|
+
* the decoded response by `thid` when one response envelope is returned.
|
|
56
|
+
*/
|
|
57
|
+
async submitNextPendingMessage(recipientEncryptionJwk) {
|
|
58
|
+
if (!this.wallet.peekNextMessage) {
|
|
59
|
+
throw new Error('BackendMessageManagerMem requires wallet.peekNextMessage().');
|
|
60
|
+
}
|
|
61
|
+
if (!this.wallet.pack) {
|
|
62
|
+
throw new Error('BackendMessageManagerMem requires wallet.pack().');
|
|
63
|
+
}
|
|
64
|
+
if (!this.wallet.markMessageDelivered || !this.wallet.markMessageFailed) {
|
|
65
|
+
throw new Error('BackendMessageManagerMem requires wallet.markMessageDelivered() and wallet.markMessageFailed().');
|
|
66
|
+
}
|
|
67
|
+
const next = await this.wallet.peekNextMessage();
|
|
68
|
+
if (!next || next.status !== WalletQueueStatuses.Pending) {
|
|
69
|
+
throw new Error('BackendMessageManagerMem found no pending queued message.');
|
|
70
|
+
}
|
|
71
|
+
const envelope = await this.wallet.pack(next.payload, recipientEncryptionJwk, this.entityId);
|
|
72
|
+
const transportResult = await this.transport.submit({
|
|
73
|
+
message: next,
|
|
74
|
+
envelope,
|
|
75
|
+
});
|
|
76
|
+
if (!transportResult.accepted) {
|
|
77
|
+
const failed = await this.wallet.markMessageFailed(next.id, String(transportResult.errorMessage || 'transport rejected queued message'));
|
|
78
|
+
await this.persistMessageRecord(failed, envelope, undefined, {
|
|
79
|
+
transportStatus: 'failed',
|
|
80
|
+
});
|
|
81
|
+
return { message: failed };
|
|
82
|
+
}
|
|
83
|
+
const delivered = await this.wallet.markMessageDelivered(next.id);
|
|
84
|
+
if (!transportResult.responseEnvelope) {
|
|
85
|
+
await this.persistMessageRecord(delivered, envelope, undefined, {
|
|
86
|
+
transportStatus: 'submitted',
|
|
87
|
+
locationUrl: transportResult.locationUrl,
|
|
88
|
+
pollCount: 0,
|
|
89
|
+
});
|
|
90
|
+
return { message: delivered, pending: true };
|
|
91
|
+
}
|
|
92
|
+
if (!this.wallet.unpack) {
|
|
93
|
+
throw new Error('BackendMessageManagerMem requires wallet.unpack() to decode response envelopes.');
|
|
94
|
+
}
|
|
95
|
+
const unpacked = await this.wallet.unpack(transportResult.responseEnvelope, this.entityId);
|
|
96
|
+
const responseContent = clone(unpacked.content);
|
|
97
|
+
const thid = String(responseContent['thid'] || delivered.thid || delivered.id).trim();
|
|
98
|
+
const response = {
|
|
99
|
+
thid,
|
|
100
|
+
receivedAt: new Date().toISOString(),
|
|
101
|
+
content: responseContent,
|
|
102
|
+
meta: clone(unpacked.meta),
|
|
103
|
+
};
|
|
104
|
+
this.responsesByThread.set(thid, response);
|
|
105
|
+
await this.outboxRepository.putResponse(response);
|
|
106
|
+
await this.persistMessageRecord(delivered, envelope, response.thid, {
|
|
107
|
+
transportStatus: 'completed',
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
message: delivered,
|
|
111
|
+
response,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Returns the latest decoded response for one DIDComm thread.
|
|
116
|
+
*/
|
|
117
|
+
getResponseByThreadId(thid) {
|
|
118
|
+
const normalizedThreadId = String(thid || '').trim();
|
|
119
|
+
const stored = this.responsesByThread.get(normalizedThreadId);
|
|
120
|
+
return stored ? clone(stored) : undefined;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Lists submitted message history records preserved by the outbox repository.
|
|
124
|
+
*/
|
|
125
|
+
async listOutboxHistory() {
|
|
126
|
+
return this.outboxRepository.listMessages();
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Polls one previously submitted asynchronous message by id.
|
|
130
|
+
*/
|
|
131
|
+
async pollSubmittedMessage(messageId) {
|
|
132
|
+
if (!this.transport.poll) {
|
|
133
|
+
throw new Error('BackendMessageManagerMem requires transport.poll() for asynchronous polling.');
|
|
134
|
+
}
|
|
135
|
+
const currentRecord = await this.requireOutboxRecord(messageId);
|
|
136
|
+
const pollResult = await this.transport.poll({
|
|
137
|
+
message: currentRecord,
|
|
138
|
+
});
|
|
139
|
+
if (pollResult.pending) {
|
|
140
|
+
const pendingRecord = await this.outboxRepository.putMessage({
|
|
141
|
+
...currentRecord,
|
|
142
|
+
transportStatus: 'submitted',
|
|
143
|
+
pollCount: (currentRecord.pollCount || 0) + 1,
|
|
144
|
+
recordedAt: new Date().toISOString(),
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
pending: true,
|
|
148
|
+
record: pendingRecord,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
if (!pollResult.completed || !pollResult.responseEnvelope) {
|
|
152
|
+
const failedRecord = await this.outboxRepository.putMessage({
|
|
153
|
+
...currentRecord,
|
|
154
|
+
transportStatus: pollResult.retryable ? 'submitted' : 'failed',
|
|
155
|
+
pollCount: (currentRecord.pollCount || 0) + 1,
|
|
156
|
+
recordedAt: new Date().toISOString(),
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
pending: Boolean(pollResult.retryable),
|
|
160
|
+
record: failedRecord,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (!this.wallet.unpack) {
|
|
164
|
+
throw new Error('BackendMessageManagerMem requires wallet.unpack() to decode polled response envelopes.');
|
|
165
|
+
}
|
|
166
|
+
const unpacked = await this.wallet.unpack(pollResult.responseEnvelope, this.entityId);
|
|
167
|
+
const responseContent = clone(unpacked.content);
|
|
168
|
+
const thid = String(responseContent['thid'] || currentRecord.thid || currentRecord.id).trim();
|
|
169
|
+
const response = {
|
|
170
|
+
thid,
|
|
171
|
+
receivedAt: new Date().toISOString(),
|
|
172
|
+
content: responseContent,
|
|
173
|
+
meta: clone(unpacked.meta),
|
|
174
|
+
};
|
|
175
|
+
this.responsesByThread.set(thid, response);
|
|
176
|
+
await this.outboxRepository.putResponse(response);
|
|
177
|
+
const completedRecord = await this.outboxRepository.putMessage({
|
|
178
|
+
...currentRecord,
|
|
179
|
+
transportStatus: 'completed',
|
|
180
|
+
responseThid: response.thid,
|
|
181
|
+
pollCount: (currentRecord.pollCount || 0) + 1,
|
|
182
|
+
recordedAt: new Date().toISOString(),
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
pending: false,
|
|
186
|
+
response,
|
|
187
|
+
record: completedRecord,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Reads one decoded response by thread id, first from memory and then from the repository.
|
|
192
|
+
*/
|
|
193
|
+
async readResponseByThreadId(thid) {
|
|
194
|
+
const inMemory = this.getResponseByThreadId(thid);
|
|
195
|
+
if (inMemory)
|
|
196
|
+
return inMemory;
|
|
197
|
+
const stored = await this.outboxRepository.getLatestResponseByThreadId(thid);
|
|
198
|
+
if (stored) {
|
|
199
|
+
this.responsesByThread.set(String(stored.thid || '').trim(), clone(stored));
|
|
200
|
+
}
|
|
201
|
+
return stored;
|
|
202
|
+
}
|
|
203
|
+
async persistMessageRecord(message, envelope, responseThid, options = {}) {
|
|
204
|
+
await this.outboxRepository.putMessage({
|
|
205
|
+
id: message.id,
|
|
206
|
+
thid: message.thid,
|
|
207
|
+
messageType: message.messageType,
|
|
208
|
+
priority: message.priority,
|
|
209
|
+
status: message.status,
|
|
210
|
+
transportStatus: options.transportStatus,
|
|
211
|
+
recordedAt: new Date().toISOString(),
|
|
212
|
+
payload: clone(message.payload),
|
|
213
|
+
envelope,
|
|
214
|
+
...(responseThid ? { responseThid } : {}),
|
|
215
|
+
...(options.locationUrl ? { locationUrl: options.locationUrl } : {}),
|
|
216
|
+
...(typeof options.pollCount === 'number' ? { pollCount: options.pollCount } : {}),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async requireOutboxRecord(messageId) {
|
|
220
|
+
const normalizedMessageId = String(messageId || '').trim();
|
|
221
|
+
const stored = await this.outboxRepository.getMessage(normalizedMessageId);
|
|
222
|
+
if (!stored) {
|
|
223
|
+
throw new Error(`BackendMessageManagerMem outbox record not found: ${normalizedMessageId}`);
|
|
224
|
+
}
|
|
225
|
+
return stored;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IDecodedDidcommPayload } from '../models/confidential-message';
|
|
2
|
+
export declare const BundleDidcommEntryTypes: Readonly<{
|
|
3
|
+
readonly Batch: "Bundle-batch-request-v1.0";
|
|
4
|
+
readonly Document: "Bundle-document-request-v1.0";
|
|
5
|
+
}>;
|
|
6
|
+
/**
|
|
7
|
+
* Input contract for wrapping one bundle-like resource as one low-level
|
|
8
|
+
* DIDComm-style batch payload.
|
|
9
|
+
*/
|
|
10
|
+
export type BuildDidcommPayloadFromBundleInput = Readonly<{
|
|
11
|
+
bundle: Record<string, unknown>;
|
|
12
|
+
iss: string;
|
|
13
|
+
aud: string;
|
|
14
|
+
jti: string;
|
|
15
|
+
thid: string;
|
|
16
|
+
entryType?: string;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Builds one low-level DIDComm-style payload carrying one direct `Bundle`
|
|
20
|
+
* resource in `body.data[0].resource`.
|
|
21
|
+
*
|
|
22
|
+
* This path is meant for operational bundles such as employee/search/purge
|
|
23
|
+
* flows that travel as a bundle itself, not nested inside a `Communication`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildDidcommPayloadFromBundle(input: BuildDidcommPayloadFromBundleInput): IDecodedDidcommPayload;
|
|
26
|
+
/**
|
|
27
|
+
* Reads the first `Bundle` resource carried under `body.data[0].resource`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getFirstBundleResourceFromDidcommPayload(payload: IDecodedDidcommPayload): Record<string, unknown>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Copyright 2026 Conectate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
|
|
2
|
+
export const BundleDidcommEntryTypes = Object.freeze({
|
|
3
|
+
Batch: 'Bundle-batch-request-v1.0',
|
|
4
|
+
Document: 'Bundle-document-request-v1.0',
|
|
5
|
+
});
|
|
6
|
+
function clone(value) {
|
|
7
|
+
return JSON.parse(JSON.stringify(value));
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Builds one low-level DIDComm-style payload carrying one direct `Bundle`
|
|
11
|
+
* resource in `body.data[0].resource`.
|
|
12
|
+
*
|
|
13
|
+
* This path is meant for operational bundles such as employee/search/purge
|
|
14
|
+
* flows that travel as a bundle itself, not nested inside a `Communication`.
|
|
15
|
+
*/
|
|
16
|
+
export function buildDidcommPayloadFromBundle(input) {
|
|
17
|
+
return {
|
|
18
|
+
iss: String(input.iss || '').trim(),
|
|
19
|
+
aud: String(input.aud || '').trim(),
|
|
20
|
+
jti: String(input.jti || '').trim(),
|
|
21
|
+
thid: String(input.thid || '').trim(),
|
|
22
|
+
type: String(input.entryType || BundleDidcommEntryTypes.Batch).trim(),
|
|
23
|
+
body: {
|
|
24
|
+
data: [
|
|
25
|
+
{
|
|
26
|
+
id: String(input.jti || '').trim(),
|
|
27
|
+
type: String(input.entryType || BundleDidcommEntryTypes.Batch).trim(),
|
|
28
|
+
resource: clone(input.bundle),
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reads the first `Bundle` resource carried under `body.data[0].resource`.
|
|
36
|
+
*/
|
|
37
|
+
export function getFirstBundleResourceFromDidcommPayload(payload) {
|
|
38
|
+
const first = Array.isArray(payload?.body?.data)
|
|
39
|
+
? payload.body.data[0]
|
|
40
|
+
: undefined;
|
|
41
|
+
const resource = first?.resource && typeof first.resource === 'object'
|
|
42
|
+
? clone(first.resource)
|
|
43
|
+
: {};
|
|
44
|
+
if (String(resource['resourceType'] || '').trim() !== 'Bundle') {
|
|
45
|
+
throw new Error('getFirstBundleResourceFromDidcommPayload requires body.data[0].resource.resourceType = Bundle.');
|
|
46
|
+
}
|
|
47
|
+
return resource;
|
|
48
|
+
}
|