dataspace-client-sdk-node 0.1.1 → 0.1.2
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 +28 -0
- package/README.md +40 -0
- package/dist/client.d.ts +114 -32
- package/dist/client.js +482 -30
- package/dist/types.d.ts +92 -0
- package/docs/BACKEND_NODE_INTEGRATION.md +122 -0
- package/docs/DATA_MODEL_ALIGNMENT.md +37 -13
- package/docs/PORTAL_BACKEND_INTEGRATION_HANDOVER.md +335 -0
- package/docs/REACT_WEB_INTEGRATION.md +72 -0
- package/examples/smoke-legal-org-local.mjs +40 -0
- package/package.json +1 -1
- package/src/client.ts +628 -66
- package/src/types.ts +101 -0
- package/tests/client.test.mjs +491 -0
- package/SDK_PARITY_MAP.md +0 -120
package/dist/client.js
CHANGED
|
@@ -8,21 +8,131 @@ function trimTrailingSlash(value) {
|
|
|
8
8
|
function encode(value) {
|
|
9
9
|
return encodeURIComponent(value);
|
|
10
10
|
}
|
|
11
|
+
function toDidWebFromUrlOrHost(raw) {
|
|
12
|
+
const v = String(raw || '').trim();
|
|
13
|
+
if (!v)
|
|
14
|
+
return undefined;
|
|
15
|
+
if (v.startsWith('did:web:'))
|
|
16
|
+
return v;
|
|
17
|
+
const host = v
|
|
18
|
+
.replace(/^https?:\/\//i, '')
|
|
19
|
+
.replace(/\/.*$/, '')
|
|
20
|
+
.trim()
|
|
21
|
+
.toLowerCase();
|
|
22
|
+
if (!host)
|
|
23
|
+
return undefined;
|
|
24
|
+
return `did:web:${host}`;
|
|
25
|
+
}
|
|
26
|
+
function parseRetryAfterMs(header) {
|
|
27
|
+
if (!header)
|
|
28
|
+
return undefined;
|
|
29
|
+
const raw = header.trim();
|
|
30
|
+
if (!raw)
|
|
31
|
+
return undefined;
|
|
32
|
+
const seconds = Number(raw);
|
|
33
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
34
|
+
return Math.floor(seconds * 1000);
|
|
35
|
+
}
|
|
36
|
+
const epochMs = Date.parse(raw);
|
|
37
|
+
if (Number.isFinite(epochMs)) {
|
|
38
|
+
const delta = epochMs - Date.now();
|
|
39
|
+
return delta > 0 ? delta : 0;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
11
43
|
export class DataspaceNodeClient {
|
|
12
44
|
baseUrl;
|
|
13
45
|
bearerToken;
|
|
14
46
|
defaultHeaders;
|
|
15
47
|
wallet;
|
|
48
|
+
defaultCtx;
|
|
49
|
+
defaultTimeoutMs;
|
|
50
|
+
defaultIntervalMs;
|
|
16
51
|
_tokenCache = new Map();
|
|
17
52
|
constructor(options) {
|
|
18
53
|
this.baseUrl = trimTrailingSlash(options.baseUrl);
|
|
19
54
|
this.bearerToken = options.bearerToken;
|
|
20
55
|
this.defaultHeaders = options.defaultHeaders ?? {};
|
|
21
56
|
this.wallet = options.wallet;
|
|
57
|
+
this.defaultCtx = options.ctx;
|
|
22
58
|
}
|
|
23
59
|
getWallet() {
|
|
24
60
|
return this.wallet;
|
|
25
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Set default route context for subsequent calls.
|
|
64
|
+
*/
|
|
65
|
+
setContext(ctx) {
|
|
66
|
+
this.defaultCtx = { ...ctx };
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Preferred alias for organization/tenant integration context.
|
|
71
|
+
*/
|
|
72
|
+
setContextOrg(ctx) {
|
|
73
|
+
return this.setContext(ctx);
|
|
74
|
+
}
|
|
75
|
+
setTenantId(tenantId) {
|
|
76
|
+
const current = this.defaultCtx ?? { tenantId: '', jurisdiction: '', sector: '' };
|
|
77
|
+
this.defaultCtx = { ...current, tenantId };
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
setJurisdiction(jurisdiction) {
|
|
81
|
+
const current = this.defaultCtx ?? { tenantId: '', jurisdiction: '', sector: '' };
|
|
82
|
+
this.defaultCtx = { ...current, jurisdiction };
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
setSector(sector) {
|
|
86
|
+
const current = this.defaultCtx ?? { tenantId: '', jurisdiction: '', sector: '' };
|
|
87
|
+
this.defaultCtx = { ...current, sector };
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
setDefaultTimeoutSeconds(seconds) {
|
|
91
|
+
if (Number.isFinite(Number(seconds))) {
|
|
92
|
+
this.defaultTimeoutMs = Math.max(1, Math.floor(Number(seconds) * 1000));
|
|
93
|
+
}
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
setDefaultIntervalSeconds(seconds) {
|
|
97
|
+
if (Number.isFinite(Number(seconds))) {
|
|
98
|
+
this.defaultIntervalMs = Math.max(1, Math.floor(Number(seconds) * 1000));
|
|
99
|
+
}
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
resolveSimplePollOptions(timeoutSeconds, intervalSeconds) {
|
|
103
|
+
const pollOptions = {};
|
|
104
|
+
if (Number.isFinite(Number(timeoutSeconds))) {
|
|
105
|
+
pollOptions.timeoutMs = Math.max(1, Math.floor(Number(timeoutSeconds) * 1000));
|
|
106
|
+
}
|
|
107
|
+
else if (this.defaultTimeoutMs) {
|
|
108
|
+
pollOptions.timeoutMs = this.defaultTimeoutMs;
|
|
109
|
+
}
|
|
110
|
+
if (Number.isFinite(Number(intervalSeconds))) {
|
|
111
|
+
pollOptions.intervalMs = Math.max(1, Math.floor(Number(intervalSeconds) * 1000));
|
|
112
|
+
}
|
|
113
|
+
else if (this.defaultIntervalMs) {
|
|
114
|
+
pollOptions.intervalMs = this.defaultIntervalMs;
|
|
115
|
+
}
|
|
116
|
+
return Object.keys(pollOptions).length ? pollOptions : undefined;
|
|
117
|
+
}
|
|
118
|
+
requireRouteContext(ctx) {
|
|
119
|
+
const resolved = ctx ?? this.defaultCtx;
|
|
120
|
+
const tenantId = String(resolved?.tenantId || '').trim();
|
|
121
|
+
const jurisdiction = String(resolved?.jurisdiction || '').trim();
|
|
122
|
+
const sector = String(resolved?.sector || '').trim();
|
|
123
|
+
if (!tenantId || !jurisdiction || !sector) {
|
|
124
|
+
throw new Error('Route context is required. Provide `ctx` in method call or constructor options.');
|
|
125
|
+
}
|
|
126
|
+
return { tenantId, jurisdiction, sector };
|
|
127
|
+
}
|
|
128
|
+
requireHostRouteContext(ctx) {
|
|
129
|
+
const jurisdiction = String(ctx?.jurisdiction || this.defaultCtx?.jurisdiction || '').trim();
|
|
130
|
+
const sector = String(ctx?.sector || this.defaultCtx?.sector || '').trim();
|
|
131
|
+
if (jurisdiction && sector) {
|
|
132
|
+
return { jurisdiction, sector };
|
|
133
|
+
}
|
|
134
|
+
throw new Error('Host route context is required. Provide `ctx` in method call or constructor options.ctx.');
|
|
135
|
+
}
|
|
26
136
|
// ---- Path helpers -------------------------------------------------------
|
|
27
137
|
/**
|
|
28
138
|
* Generic GW v1 tenant route builder.
|
|
@@ -36,7 +146,8 @@ export class DataspaceNodeClient {
|
|
|
36
146
|
* // → /acme/cds-ES/v1/health-care/individual/org.schema/Organization/_batch
|
|
37
147
|
*/
|
|
38
148
|
v1Path(ctx, section, format, resourceType, action) {
|
|
39
|
-
|
|
149
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
150
|
+
return `/${encode(routeCtx.tenantId)}/cds-${encode(routeCtx.jurisdiction)}/v1/${encode(routeCtx.sector)}/${encode(section)}/${encode(format)}/${encode(resourceType)}/${encode(action)}`;
|
|
40
151
|
}
|
|
41
152
|
/**
|
|
42
153
|
* Generic tenant-scoped identity route builder.
|
|
@@ -46,7 +157,8 @@ export class DataspaceNodeClient {
|
|
|
46
157
|
* Dedicated path methods in this SDK use `host` (GW convention).
|
|
47
158
|
*/
|
|
48
159
|
tenantIdentityPath(ctx, prefix, action) {
|
|
49
|
-
|
|
160
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
161
|
+
return `/${encode(prefix)}/cds-${encode(routeCtx.jurisdiction)}/v1/${encode(routeCtx.sector)}/${encode(routeCtx.tenantId)}/identity/auth/${encode(action)}`;
|
|
50
162
|
}
|
|
51
163
|
/**
|
|
52
164
|
* Generic host registry route builder (tenant-agnostic, `host/` prefix).
|
|
@@ -55,7 +167,8 @@ export class DataspaceNodeClient {
|
|
|
55
167
|
* Pattern: `/host/cds-{jurisdiction}/v1/{sector}/registry/org.schema/{resourceType}/{action}`
|
|
56
168
|
*/
|
|
57
169
|
hostRegistryPath(ctx, resourceType, action) {
|
|
58
|
-
|
|
170
|
+
const hostCtx = this.requireHostRouteContext(ctx);
|
|
171
|
+
return `/host/cds-${encode(hostCtx.jurisdiction)}/v1/${encode(hostCtx.sector)}/registry/org.schema/${encode(resourceType)}/${encode(action)}`;
|
|
59
172
|
}
|
|
60
173
|
/** Submit path: host registry Organization batch (controller-level org registration). */
|
|
61
174
|
hostRegistryOrganizationBatchPath(ctx) {
|
|
@@ -86,11 +199,13 @@ export class DataspaceNodeClient {
|
|
|
86
199
|
* Use for `family-registration/_create-or-resume` DIDComm payloads.
|
|
87
200
|
*/
|
|
88
201
|
individualFamilyOrganizationBatchPath(ctx) {
|
|
89
|
-
|
|
202
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
203
|
+
return `/${encode(routeCtx.tenantId)}/cds-${encode(routeCtx.jurisdiction)}/v1/${encode(routeCtx.sector)}/individual/org.schema/Organization/_batch`;
|
|
90
204
|
}
|
|
91
205
|
/** Poll path: individual/family Organization. Pair with `individualFamilyOrganizationBatchPath`. */
|
|
92
206
|
individualFamilyOrganizationPollPath(ctx) {
|
|
93
|
-
|
|
207
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
208
|
+
return `/${encode(routeCtx.tenantId)}/cds-${encode(routeCtx.jurisdiction)}/v1/${encode(routeCtx.sector)}/individual/org.schema/Organization/_batch-response`;
|
|
94
209
|
}
|
|
95
210
|
/** Submit path: individual/family Organization search (`org.schema/Organization/_search`). */
|
|
96
211
|
individualFamilyOrganizationSearchPath(ctx) {
|
|
@@ -102,11 +217,13 @@ export class DataspaceNodeClient {
|
|
|
102
217
|
}
|
|
103
218
|
/** Submit path: individual/family Order batch (`org.schema/Order/_batch`). */
|
|
104
219
|
individualFamilyOrderBatchPath(ctx) {
|
|
105
|
-
|
|
220
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
221
|
+
return `/${encode(routeCtx.tenantId)}/cds-${encode(routeCtx.jurisdiction)}/v1/${encode(routeCtx.sector)}/individual/org.schema/Order/_batch`;
|
|
106
222
|
}
|
|
107
223
|
/** Poll path: individual/family Order. Pair with `individualFamilyOrderBatchPath`. */
|
|
108
224
|
individualFamilyOrderPollPath(ctx) {
|
|
109
|
-
|
|
225
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
226
|
+
return `/${encode(routeCtx.tenantId)}/cds-${encode(routeCtx.jurisdiction)}/v1/${encode(routeCtx.sector)}/individual/org.schema/Order/_batch-response`;
|
|
110
227
|
}
|
|
111
228
|
/** Submit path: individual RelatedPerson (FHIR R4 API, `org.hl7.fhir.api/RelatedPerson/_batch`). */
|
|
112
229
|
individualRelatedPersonBatchPath(ctx) {
|
|
@@ -477,6 +594,58 @@ export class DataspaceNodeClient {
|
|
|
477
594
|
response,
|
|
478
595
|
};
|
|
479
596
|
}
|
|
597
|
+
/**
|
|
598
|
+
* Friendly wrapper for SMART token request via GW identity/auth token-exchange route.
|
|
599
|
+
* Uses one object, seconds-based polling, and constructor ctx fallback.
|
|
600
|
+
*/
|
|
601
|
+
async requestSmartTokenSimple(input) {
|
|
602
|
+
const routeCtx = this.requireRouteContext(input.tenantId && input.jurisdiction && input.sector
|
|
603
|
+
? { tenantId: input.tenantId, jurisdiction: input.jurisdiction, sector: input.sector }
|
|
604
|
+
: undefined);
|
|
605
|
+
const normalizedScopes = Array.from(new Set((input.scopes || []).filter(Boolean))).sort();
|
|
606
|
+
const endpointId = String(input.endpointId || `smart:${routeCtx.tenantId}:${normalizedScopes.join(',')}`).trim();
|
|
607
|
+
if (!endpointId) {
|
|
608
|
+
throw new Error('requestSmartTokenSimple requires endpointId (or non-empty scopes).');
|
|
609
|
+
}
|
|
610
|
+
const pollOptions = this.resolveSimplePollOptions(input.timeoutSeconds, input.intervalSeconds);
|
|
611
|
+
const payload = {
|
|
612
|
+
thid: `exchange-${randomUUID()}`,
|
|
613
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
|
|
614
|
+
subject_token_type: 'urn:ietf:params:oauth:token-type:id_token',
|
|
615
|
+
subject_token: input.idToken,
|
|
616
|
+
scope: normalizedScopes.join(' '),
|
|
617
|
+
organization: routeCtx.tenantId,
|
|
618
|
+
...(input.additionalClaims || {}),
|
|
619
|
+
};
|
|
620
|
+
const exchange = await this.submitAndPoll(this.identityTokenExchangePath(routeCtx), this.identityTokenExchangePollPath(routeCtx), payload, pollOptions);
|
|
621
|
+
const exchangeBody = exchange.poll.body ?? {};
|
|
622
|
+
const accessToken = String(exchangeBody.access_token || '').trim();
|
|
623
|
+
if (exchange.poll.status >= 400 || !accessToken) {
|
|
624
|
+
return {
|
|
625
|
+
status: 'failed',
|
|
626
|
+
statusCode: exchange.poll.status,
|
|
627
|
+
response: exchange.poll.body,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
const tokenType = String(exchangeBody.token_type || 'Bearer');
|
|
631
|
+
const grantedScopes = String(exchangeBody.scope || '').trim().split(' ').filter(Boolean);
|
|
632
|
+
const resolvedScopes = grantedScopes.length ? grantedScopes : normalizedScopes;
|
|
633
|
+
const expiresIn = Number(exchangeBody.expires_in ?? 0);
|
|
634
|
+
this._tokenCache.set(endpointId, {
|
|
635
|
+
accessToken,
|
|
636
|
+
tokenType,
|
|
637
|
+
scopes: resolvedScopes,
|
|
638
|
+
expiresAt: Date.now() + expiresIn * 1000,
|
|
639
|
+
});
|
|
640
|
+
return {
|
|
641
|
+
status: 'fetched',
|
|
642
|
+
accessToken,
|
|
643
|
+
tokenType,
|
|
644
|
+
scopes: resolvedScopes,
|
|
645
|
+
statusCode: exchange.poll.status,
|
|
646
|
+
response: exchange.poll.body,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
480
649
|
// ---- Private auth helpers ----------------------------------------------
|
|
481
650
|
_pkceS256Challenge(verifier) {
|
|
482
651
|
return createHash('sha256').update(verifier).digest().toString('base64url');
|
|
@@ -729,6 +898,7 @@ export class DataspaceNodeClient {
|
|
|
729
898
|
return {
|
|
730
899
|
status: response.status,
|
|
731
900
|
body: await this.parseResponseBody(response),
|
|
901
|
+
retryAfterMs: parseRetryAfterMs(response.headers.get('retry-after')),
|
|
732
902
|
};
|
|
733
903
|
}
|
|
734
904
|
/**
|
|
@@ -766,6 +936,7 @@ export class DataspaceNodeClient {
|
|
|
766
936
|
* (appointment, medication schedule, or another event), mapped to `based-on-display`.
|
|
767
937
|
*/
|
|
768
938
|
async createPhoneReminderTasks(ctx, input, options) {
|
|
939
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
769
940
|
const windows = Array.isArray(input.windows) ? input.windows : [];
|
|
770
941
|
if (!windows.length) {
|
|
771
942
|
throw new Error('createPhoneReminderTasks requires at least one reminder window.');
|
|
@@ -785,9 +956,9 @@ export class DataspaceNodeClient {
|
|
|
785
956
|
throw new Error('createPhoneReminderTasks requires remindAt in every window.');
|
|
786
957
|
}
|
|
787
958
|
const taskIdSeed = [
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
959
|
+
routeCtx.tenantId,
|
|
960
|
+
routeCtx.jurisdiction,
|
|
961
|
+
routeCtx.sector,
|
|
791
962
|
input.subjectRef,
|
|
792
963
|
input.ownerRef,
|
|
793
964
|
input.focusRef,
|
|
@@ -832,12 +1003,12 @@ export class DataspaceNodeClient {
|
|
|
832
1003
|
};
|
|
833
1004
|
});
|
|
834
1005
|
const payload = createDidcommPlainMessage({
|
|
835
|
-
iss:
|
|
836
|
-
aud:
|
|
1006
|
+
iss: routeCtx.tenantId,
|
|
1007
|
+
aud: routeCtx.tenantId,
|
|
837
1008
|
thid,
|
|
838
1009
|
body: { data },
|
|
839
1010
|
});
|
|
840
|
-
return this.submitAndPoll(this.individualTaskBatchPath(
|
|
1011
|
+
return this.submitAndPoll(this.individualTaskBatchPath(routeCtx), this.individualTaskPollPath(routeCtx), payload, options ?? { timeoutMs: 20_000, intervalMs: 1_000 });
|
|
841
1012
|
}
|
|
842
1013
|
/** Endpoint path for medication overlap pre-check (planned GW contract). */
|
|
843
1014
|
individualMedicationOverlapCheckPath(ctx) {
|
|
@@ -934,11 +1105,12 @@ export class DataspaceNodeClient {
|
|
|
934
1105
|
* Returns `null` when no matching registration exists.
|
|
935
1106
|
*/
|
|
936
1107
|
async searchFamilyOrganization(ctx, filters, options) {
|
|
1108
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
937
1109
|
const thid = `search-${randomUUID()}`;
|
|
938
1110
|
const claims = {
|
|
939
1111
|
'org.schema.Organization.owner.telephone': filters.controllerPhone,
|
|
940
1112
|
'org.schema.Organization.alternateName': filters.usualname,
|
|
941
|
-
'org.schema.Service.category':
|
|
1113
|
+
'org.schema.Service.category': routeCtx.sector,
|
|
942
1114
|
};
|
|
943
1115
|
if (filters.birthDate) {
|
|
944
1116
|
claims['org.schema.Organization.foundingDate'] = filters.birthDate;
|
|
@@ -946,8 +1118,8 @@ export class DataspaceNodeClient {
|
|
|
946
1118
|
const payload = {
|
|
947
1119
|
jti: randomUUID(),
|
|
948
1120
|
thid,
|
|
949
|
-
iss:
|
|
950
|
-
aud:
|
|
1121
|
+
iss: routeCtx.tenantId,
|
|
1122
|
+
aud: routeCtx.tenantId,
|
|
951
1123
|
type: 'application/api+json',
|
|
952
1124
|
body: {
|
|
953
1125
|
data: [{
|
|
@@ -957,7 +1129,7 @@ export class DataspaceNodeClient {
|
|
|
957
1129
|
}],
|
|
958
1130
|
},
|
|
959
1131
|
};
|
|
960
|
-
const result = await this.submitAndPoll(this.individualFamilyOrganizationSearchPath(
|
|
1132
|
+
const result = await this.submitAndPoll(this.individualFamilyOrganizationSearchPath(routeCtx), this.individualFamilyOrganizationSearchPollPath(routeCtx), payload, options ?? { timeoutMs: 20_000, intervalMs: 1_000 });
|
|
961
1133
|
if (result.poll.status !== 200)
|
|
962
1134
|
return null;
|
|
963
1135
|
const entry = result.poll.body?.body?.data?.[0];
|
|
@@ -992,6 +1164,11 @@ export class DataspaceNodeClient {
|
|
|
992
1164
|
vp_token: input.vpToken,
|
|
993
1165
|
...(input.additionalClaims || {}),
|
|
994
1166
|
};
|
|
1167
|
+
const requestedMembers = Number.isFinite(Number(input.numberOfMembers))
|
|
1168
|
+
? Math.max(1, Math.floor(Number(input.numberOfMembers)))
|
|
1169
|
+
: 2;
|
|
1170
|
+
// Keep gateway-facing claim stable while exposing a generic SDK input.
|
|
1171
|
+
claims['org.schema.Organization.numberOfEmployees'] = requestedMembers;
|
|
995
1172
|
if (input.organizationVc)
|
|
996
1173
|
claims['org.schema.OrganizationCredential.jwt'] = input.organizationVc;
|
|
997
1174
|
if (input.legalRepresentativeVc) {
|
|
@@ -1003,6 +1180,10 @@ export class DataspaceNodeClient {
|
|
|
1003
1180
|
iss: 'did:web:controller.example.com',
|
|
1004
1181
|
aud: 'did:web:host.example.com',
|
|
1005
1182
|
body: {
|
|
1183
|
+
// GW activation parser expects proof material at top-level DIDComm body.
|
|
1184
|
+
vp_token: input.vpToken,
|
|
1185
|
+
...(input.organizationVc ? { organizationCredential: input.organizationVc } : {}),
|
|
1186
|
+
...(input.legalRepresentativeVc ? { representativeCredential: input.legalRepresentativeVc } : {}),
|
|
1006
1187
|
data: [
|
|
1007
1188
|
{
|
|
1008
1189
|
type: 'Organization-activation-request-v1.0',
|
|
@@ -1014,6 +1195,157 @@ export class DataspaceNodeClient {
|
|
|
1014
1195
|
});
|
|
1015
1196
|
return this.submitAndPoll(this.hostRegistryOrganizationActivatePath(ctx), this.hostRegistryOrganizationActivatePollPath(ctx), payload, options);
|
|
1016
1197
|
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Friendly wrapper for legal organization activation.
|
|
1200
|
+
* Accepts one object and seconds-based polling options for integrator ergonomics.
|
|
1201
|
+
*/
|
|
1202
|
+
async activateOrganizationInGatewaySimple(input) {
|
|
1203
|
+
const serviceProviderDidWeb = String(input.serviceProviderDidWeb || '').trim();
|
|
1204
|
+
const serviceProviderUrl = String(input.serviceProviderUrl || '').trim();
|
|
1205
|
+
const controllerEmail = String(input.controllerEmail || '').trim();
|
|
1206
|
+
const controllerTelephone = String(input.controllerTelephone || '').trim();
|
|
1207
|
+
const controllerRole = String(input.controllerRole || '').trim();
|
|
1208
|
+
const resolvedServiceDid = toDidWebFromUrlOrHost(serviceProviderDidWeb || serviceProviderUrl);
|
|
1209
|
+
if (!resolvedServiceDid) {
|
|
1210
|
+
throw new Error('activateOrganizationInGatewaySimple requires serviceProviderDidWeb or serviceProviderUrl.');
|
|
1211
|
+
}
|
|
1212
|
+
if (!controllerEmail && !controllerTelephone) {
|
|
1213
|
+
throw new Error('activateOrganizationInGatewaySimple requires controllerEmail or controllerTelephone.');
|
|
1214
|
+
}
|
|
1215
|
+
if (!controllerRole) {
|
|
1216
|
+
throw new Error('activateOrganizationInGatewaySimple requires controllerRole.');
|
|
1217
|
+
}
|
|
1218
|
+
const pollOptions = this.resolveSimplePollOptions(input.timeoutSeconds, input.intervalSeconds);
|
|
1219
|
+
const hostCtx = this.requireHostRouteContext(input.jurisdiction && input.sector
|
|
1220
|
+
? { jurisdiction: input.jurisdiction, sector: input.sector }
|
|
1221
|
+
: undefined);
|
|
1222
|
+
const implicitClaims = {
|
|
1223
|
+
'org.schema.Service.category': hostCtx.sector,
|
|
1224
|
+
'org.schema.Service.identifier': resolvedServiceDid,
|
|
1225
|
+
...(serviceProviderUrl ? { 'org.schema.Service.url': serviceProviderUrl } : {}),
|
|
1226
|
+
'org.schema.Person.hasOccupation': controllerRole,
|
|
1227
|
+
...(controllerEmail ? { 'org.schema.Person.email': controllerEmail } : {}),
|
|
1228
|
+
...(controllerTelephone ? { 'org.schema.Person.telephone': controllerTelephone } : {}),
|
|
1229
|
+
};
|
|
1230
|
+
const activation = await this.activateOrganizationInGatewayFromIcaProof(hostCtx, {
|
|
1231
|
+
vpToken: input.vpToken,
|
|
1232
|
+
numberOfMembers: input.numberOfMembers,
|
|
1233
|
+
organizationVc: input.organizationVc,
|
|
1234
|
+
legalRepresentativeVc: input.legalRepresentativeVc,
|
|
1235
|
+
regulatoryEvidence: input.regulatoryEvidence,
|
|
1236
|
+
additionalClaims: { ...implicitClaims, ...(input.additionalClaims || {}) },
|
|
1237
|
+
}, pollOptions);
|
|
1238
|
+
this.assertFirstDidcommEntrySuccess(activation, 'activateOrganizationInGatewaySimple');
|
|
1239
|
+
return activation;
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Friendly wrapper for legal organization Order confirmation.
|
|
1243
|
+
* Accepts one object and builds payload/paths internally.
|
|
1244
|
+
*/
|
|
1245
|
+
async confirmLegalOrganizationOrderSimple(input) {
|
|
1246
|
+
if (!String(input.offerId || '').trim()) {
|
|
1247
|
+
throw new Error('confirmLegalOrganizationOrderSimple requires offerId.');
|
|
1248
|
+
}
|
|
1249
|
+
const pollOptions = this.resolveSimplePollOptions(input.timeoutSeconds, input.intervalSeconds);
|
|
1250
|
+
const hostCtx = this.requireHostRouteContext(input.jurisdiction && input.sector
|
|
1251
|
+
? { jurisdiction: input.jurisdiction, sector: input.sector }
|
|
1252
|
+
: undefined);
|
|
1253
|
+
const claims = {
|
|
1254
|
+
'@context': 'org.schema',
|
|
1255
|
+
'Order.acceptedOffer.identifier': input.offerId,
|
|
1256
|
+
...(input.additionalClaims || {}),
|
|
1257
|
+
};
|
|
1258
|
+
const payload = createDidcommPlainMessage({
|
|
1259
|
+
iss: 'did:web:controller.example.com',
|
|
1260
|
+
aud: 'did:web:host.example.com',
|
|
1261
|
+
thid: `order-${randomUUID()}`,
|
|
1262
|
+
body: {
|
|
1263
|
+
data: [{
|
|
1264
|
+
type: input.dataType || 'Organization-order-request-v1.0',
|
|
1265
|
+
meta: { claims }, // legacy compatibility
|
|
1266
|
+
resource: { meta: { claims } },
|
|
1267
|
+
}],
|
|
1268
|
+
},
|
|
1269
|
+
});
|
|
1270
|
+
const order = await this.submitAndPoll(this.hostRegistryOrderBatchPath(hostCtx), this.hostRegistryOrderPollPath(hostCtx), payload, pollOptions);
|
|
1271
|
+
this.assertFirstDidcommEntrySuccess(order, 'confirmLegalOrganizationOrderSimple');
|
|
1272
|
+
return order;
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Normalize GW async response into DIDComm message body.
|
|
1276
|
+
*
|
|
1277
|
+
* Transport note:
|
|
1278
|
+
* - GW poll responses are HTTP JSON envelopes
|
|
1279
|
+
* - business payload lives inside DIDComm `body`
|
|
1280
|
+
*
|
|
1281
|
+
* This helper abstracts envelope differences so consumers do not depend on
|
|
1282
|
+
* raw `poll.body.body` paths.
|
|
1283
|
+
*/
|
|
1284
|
+
getDidcommMessageBodyFromResponse(result) {
|
|
1285
|
+
const pollBody = result?.poll?.body ?? result?.body ?? result;
|
|
1286
|
+
const didcommBody = pollBody?.body;
|
|
1287
|
+
if (didcommBody && typeof didcommBody === 'object')
|
|
1288
|
+
return didcommBody;
|
|
1289
|
+
if (pollBody && typeof pollBody === 'object' && Array.isArray(pollBody?.data)) {
|
|
1290
|
+
return pollBody;
|
|
1291
|
+
}
|
|
1292
|
+
return undefined;
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Return first DIDComm business entry from a submit/poll result.
|
|
1296
|
+
*/
|
|
1297
|
+
getFirstDidcommDataEntryFromResponse(result) {
|
|
1298
|
+
const body = this.getDidcommMessageBodyFromResponse(result);
|
|
1299
|
+
const entry = body?.data?.[0];
|
|
1300
|
+
return entry && typeof entry === 'object' ? entry : undefined;
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Extract `org.schema.Offer.identifier` from a submit/poll result.
|
|
1304
|
+
*
|
|
1305
|
+
* This helper normalizes canonical and legacy claim locations.
|
|
1306
|
+
*/
|
|
1307
|
+
getOfferIdFromResponse(result) {
|
|
1308
|
+
const entry = this.getFirstDidcommDataEntryFromResponse(result);
|
|
1309
|
+
const offerId = String(entry?.meta?.claims?.['org.schema.Offer.identifier']
|
|
1310
|
+
|| entry?.resource?.meta?.claims?.['org.schema.Offer.identifier']
|
|
1311
|
+
|| '').trim();
|
|
1312
|
+
return offerId || undefined;
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Extract a UI-ready Offer preview from activation/registration responses.
|
|
1316
|
+
*/
|
|
1317
|
+
getOfferPreviewFromResponse(result) {
|
|
1318
|
+
const entry = this.getFirstDidcommDataEntryFromResponse(result);
|
|
1319
|
+
const claims = entry?.meta?.claims || entry?.resource?.meta?.claims || {};
|
|
1320
|
+
const seatsRaw = claims['org.schema.Offer.eligibleQuantity.value'];
|
|
1321
|
+
const seats = typeof seatsRaw === 'number'
|
|
1322
|
+
? seatsRaw
|
|
1323
|
+
: (typeof seatsRaw === 'string' && seatsRaw.trim() ? Number(seatsRaw) : undefined);
|
|
1324
|
+
return {
|
|
1325
|
+
offerId: this.getOfferIdFromResponse(result),
|
|
1326
|
+
amount: claims['org.schema.Offer.price'],
|
|
1327
|
+
currency: claims['org.schema.Offer.priceCurrency'],
|
|
1328
|
+
seats: Number.isFinite(seats) ? seats : undefined,
|
|
1329
|
+
planName: claims['org.schema.Offer.itemOffered.name'],
|
|
1330
|
+
sku: claims['org.schema.Offer.itemOffered.sku'],
|
|
1331
|
+
paymentMethod: claims['org.schema.Offer.acceptedPaymentMethod'],
|
|
1332
|
+
checkoutUrl: claims['org.schema.Offer.checkoutPageURLTemplate'],
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* Throws when first DIDComm entry contains a business-level error status.
|
|
1337
|
+
*/
|
|
1338
|
+
assertFirstDidcommEntrySuccess(result, contextLabel) {
|
|
1339
|
+
const entry = this.getFirstDidcommDataEntryFromResponse(result);
|
|
1340
|
+
const responseStatusRaw = entry?.response?.status;
|
|
1341
|
+
const responseStatus = Number(responseStatusRaw);
|
|
1342
|
+
if (!Number.isFinite(responseStatus) || responseStatus < 400)
|
|
1343
|
+
return;
|
|
1344
|
+
const diagnostics = String(entry?.response?.outcome?.issue?.[0]?.diagnostics
|
|
1345
|
+
|| entry?.response?.outcome?.issue?.[0]?.details?.text
|
|
1346
|
+
|| '').trim();
|
|
1347
|
+
throw new Error(`${contextLabel} failed (business status=${responseStatus})${diagnostics ? `: ${diagnostics}` : ''}`);
|
|
1348
|
+
}
|
|
1017
1349
|
/**
|
|
1018
1350
|
* Activate employee/member device by activation code exchange + DCR registration.
|
|
1019
1351
|
*
|
|
@@ -1054,13 +1386,30 @@ export class DataspaceNodeClient {
|
|
|
1054
1386
|
dcr,
|
|
1055
1387
|
};
|
|
1056
1388
|
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Friendly wrapper for employee/member device activation.
|
|
1391
|
+
* Uses one object, seconds-based polling, and constructor ctx fallback.
|
|
1392
|
+
*/
|
|
1393
|
+
async activateEmployeeDeviceWithActivationCodeSimple(input) {
|
|
1394
|
+
const routeCtx = this.requireRouteContext(input.tenantId && input.jurisdiction && input.sector
|
|
1395
|
+
? { tenantId: input.tenantId, jurisdiction: input.jurisdiction, sector: input.sector }
|
|
1396
|
+
: undefined);
|
|
1397
|
+
const pollOptions = this.resolveSimplePollOptions(input.timeoutSeconds, input.intervalSeconds);
|
|
1398
|
+
return this.activateEmployeeDeviceWithActivationCode(routeCtx, {
|
|
1399
|
+
activationCode: input.activationCode,
|
|
1400
|
+
idToken: input.idToken,
|
|
1401
|
+
dcrPayload: input.dcrPayload,
|
|
1402
|
+
pollOptions,
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1057
1405
|
/**
|
|
1058
1406
|
* UC 5.3 wrapper: create organization employee in entity Employee batch route.
|
|
1059
1407
|
*/
|
|
1060
1408
|
async createOrganizationEmployee(ctx, input, options) {
|
|
1409
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
1061
1410
|
const payload = createDidcommPlainMessage({
|
|
1062
|
-
iss:
|
|
1063
|
-
aud:
|
|
1411
|
+
iss: routeCtx.tenantId,
|
|
1412
|
+
aud: routeCtx.tenantId,
|
|
1064
1413
|
thid: `employee-${randomUUID()}`,
|
|
1065
1414
|
body: {
|
|
1066
1415
|
data: [
|
|
@@ -1072,7 +1421,7 @@ export class DataspaceNodeClient {
|
|
|
1072
1421
|
],
|
|
1073
1422
|
},
|
|
1074
1423
|
});
|
|
1075
|
-
return this.submitAndPoll(this.employeeBatchPath(
|
|
1424
|
+
return this.submitAndPoll(this.employeeBatchPath(routeCtx), this.employeePollPath(routeCtx), payload, options);
|
|
1076
1425
|
}
|
|
1077
1426
|
/**
|
|
1078
1427
|
* UC 5.1 wrapper: bootstrap subject organization context via registration + optional order confirmation.
|
|
@@ -1093,20 +1442,120 @@ export class DataspaceNodeClient {
|
|
|
1093
1442
|
const confirmation = await this.submitAndPoll(this.individualFamilyOrderBatchPath(ctx), this.individualFamilyOrderPollPath(ctx), confirmationPayload, input.pollOptions);
|
|
1094
1443
|
return { registration, confirmation };
|
|
1095
1444
|
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Friendly wrapper (recommended step 1): register individual organization and return Offer.
|
|
1447
|
+
*/
|
|
1448
|
+
async startIndividualOrganizationSimple(input) {
|
|
1449
|
+
const routeCtx = this.requireRouteContext(input.tenantId && input.jurisdiction && input.sector
|
|
1450
|
+
? { tenantId: input.tenantId, jurisdiction: input.jurisdiction, sector: input.sector }
|
|
1451
|
+
: undefined);
|
|
1452
|
+
const alternateName = String(input.alternateName || '').trim();
|
|
1453
|
+
if (!alternateName) {
|
|
1454
|
+
throw new Error('bootstrapIndividualOrganizationSimple requires alternateName.');
|
|
1455
|
+
}
|
|
1456
|
+
const controllerEmail = String(input.controllerEmail || '').trim();
|
|
1457
|
+
const controllerTelephone = String(input.controllerTelephone || '').trim();
|
|
1458
|
+
if (!controllerEmail && !controllerTelephone) {
|
|
1459
|
+
throw new Error('bootstrapIndividualOrganizationSimple requires controllerEmail or controllerTelephone.');
|
|
1460
|
+
}
|
|
1461
|
+
const controllerRole = String(input.controllerRole || 'org.hl7.v3.RoleCode|RESPRSN').trim();
|
|
1462
|
+
const claims = {
|
|
1463
|
+
'@context': 'org.schema',
|
|
1464
|
+
'org.schema.Organization.alternateName': alternateName,
|
|
1465
|
+
'org.schema.Service.category': routeCtx.sector,
|
|
1466
|
+
'org.schema.Person.hasOccupation': controllerRole,
|
|
1467
|
+
...(controllerEmail ? { 'org.schema.Person.email': controllerEmail } : {}),
|
|
1468
|
+
...(controllerTelephone ? { 'org.schema.Person.telephone': controllerTelephone } : {}),
|
|
1469
|
+
...(input.additionalClaims || {}),
|
|
1470
|
+
};
|
|
1471
|
+
const pollOptions = this.resolveSimplePollOptions(input.timeoutSeconds, input.intervalSeconds);
|
|
1472
|
+
const registrationPayload = createDidcommPlainMessage({
|
|
1473
|
+
iss: routeCtx.tenantId,
|
|
1474
|
+
aud: routeCtx.tenantId,
|
|
1475
|
+
thid: `family-org-${randomUUID()}`,
|
|
1476
|
+
body: {
|
|
1477
|
+
data: [{
|
|
1478
|
+
type: 'SubjectOrg-registration-form-v1.0',
|
|
1479
|
+
meta: { claims },
|
|
1480
|
+
resource: { meta: { claims } },
|
|
1481
|
+
}],
|
|
1482
|
+
},
|
|
1483
|
+
});
|
|
1484
|
+
const registration = await this.submitAndPoll(this.individualFamilyOrganizationBatchPath(routeCtx), this.individualFamilyOrganizationPollPath(routeCtx), registrationPayload, pollOptions);
|
|
1485
|
+
this.assertFirstDidcommEntrySuccess(registration, 'startIndividualOrganizationSimple.registration');
|
|
1486
|
+
const offerId = this.getOfferIdFromResponse(registration);
|
|
1487
|
+
if (!offerId) {
|
|
1488
|
+
throw new Error('startIndividualOrganizationSimple failed: missing offerId in registration response.');
|
|
1489
|
+
}
|
|
1490
|
+
return { registration, offerId, offerPreview: this.getOfferPreviewFromResponse(registration) };
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Friendly wrapper (recommended step 2): confirm individual/family order from accepted offerId.
|
|
1494
|
+
*/
|
|
1495
|
+
async confirmIndividualOrganizationOrderSimple(input) {
|
|
1496
|
+
const routeCtx = this.requireRouteContext(input.tenantId && input.jurisdiction && input.sector
|
|
1497
|
+
? { tenantId: input.tenantId, jurisdiction: input.jurisdiction, sector: input.sector }
|
|
1498
|
+
: undefined);
|
|
1499
|
+
const offerId = String(input.offerId || '').trim();
|
|
1500
|
+
if (!offerId) {
|
|
1501
|
+
throw new Error('confirmIndividualOrganizationOrderSimple requires offerId.');
|
|
1502
|
+
}
|
|
1503
|
+
const pollOptions = this.resolveSimplePollOptions(input.timeoutSeconds, input.intervalSeconds);
|
|
1504
|
+
const orderClaims = {
|
|
1505
|
+
'@context': 'org.schema',
|
|
1506
|
+
'Order.acceptedOffer.identifier': offerId,
|
|
1507
|
+
};
|
|
1508
|
+
const confirmationPayload = createDidcommPlainMessage({
|
|
1509
|
+
iss: routeCtx.tenantId,
|
|
1510
|
+
aud: routeCtx.tenantId,
|
|
1511
|
+
thid: `family-order-${randomUUID()}`,
|
|
1512
|
+
body: {
|
|
1513
|
+
data: [{
|
|
1514
|
+
type: 'Family-order-request-v1.0',
|
|
1515
|
+
meta: { claims: orderClaims },
|
|
1516
|
+
resource: { meta: { claims: orderClaims } },
|
|
1517
|
+
}],
|
|
1518
|
+
},
|
|
1519
|
+
});
|
|
1520
|
+
const confirmation = await this.submitAndPoll(this.individualFamilyOrderBatchPath(routeCtx), this.individualFamilyOrderPollPath(routeCtx), confirmationPayload, pollOptions);
|
|
1521
|
+
this.assertFirstDidcommEntrySuccess(confirmation, 'confirmIndividualOrganizationOrderSimple');
|
|
1522
|
+
return confirmation;
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Friendly wrapper (provisional): register + auto-confirm individual order.
|
|
1526
|
+
* Prefer `startIndividualOrganizationSimple` + `confirmIndividualOrganizationOrderSimple`.
|
|
1527
|
+
*/
|
|
1528
|
+
async bootstrapIndividualOrganizationSimple(input) {
|
|
1529
|
+
const started = await this.startIndividualOrganizationSimple(input);
|
|
1530
|
+
const confirmation = await this.confirmIndividualOrganizationOrderSimple({
|
|
1531
|
+
tenantId: input.tenantId,
|
|
1532
|
+
jurisdiction: input.jurisdiction,
|
|
1533
|
+
sector: input.sector,
|
|
1534
|
+
offerId: started.offerId,
|
|
1535
|
+
timeoutSeconds: input.timeoutSeconds,
|
|
1536
|
+
intervalSeconds: input.intervalSeconds,
|
|
1537
|
+
});
|
|
1538
|
+
return {
|
|
1539
|
+
registration: started.registration,
|
|
1540
|
+
offerId: started.offerId,
|
|
1541
|
+
confirmation,
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1096
1544
|
/**
|
|
1097
1545
|
* UC 5.5 wrapper: import IPS/FHIR composition and update subject index context.
|
|
1098
1546
|
*/
|
|
1099
1547
|
async importIpsOrFhirAndUpdateIndex(ctx, input) {
|
|
1548
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
1100
1549
|
const payload = {
|
|
1101
1550
|
thid: input.compositionPayload.thid || `composition-${randomUUID()}`,
|
|
1102
1551
|
...input.compositionPayload,
|
|
1103
1552
|
};
|
|
1104
1553
|
const submitPath = (input.format || 'r4') === 'api'
|
|
1105
|
-
? this.individualCompositionR4BatchPath(
|
|
1106
|
-
: this.individualCompositionR4BatchPath(
|
|
1554
|
+
? this.individualCompositionR4BatchPath(routeCtx).replace('/org.hl7.fhir.r4/', '/org.hl7.fhir.api/')
|
|
1555
|
+
: this.individualCompositionR4BatchPath(routeCtx);
|
|
1107
1556
|
const pollPath = (input.format || 'r4') === 'api'
|
|
1108
|
-
? this.individualCompositionR4PollPath(
|
|
1109
|
-
: this.individualCompositionR4PollPath(
|
|
1557
|
+
? this.individualCompositionR4PollPath(routeCtx).replace('/org.hl7.fhir.r4/', '/org.hl7.fhir.api/')
|
|
1558
|
+
: this.individualCompositionR4PollPath(routeCtx);
|
|
1110
1559
|
return this.submitAndPoll(submitPath, pollPath, payload, input.pollOptions);
|
|
1111
1560
|
}
|
|
1112
1561
|
/**
|
|
@@ -1114,6 +1563,7 @@ export class DataspaceNodeClient {
|
|
|
1114
1563
|
* Builds canonical Consent claims and submits/polls the Consent batch.
|
|
1115
1564
|
*/
|
|
1116
1565
|
async grantProfessionalAccessSimple(ctx, input) {
|
|
1566
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
1117
1567
|
const built = buildConsentClaimsSimpleWithCid({
|
|
1118
1568
|
subjectDid: input.subjectDid,
|
|
1119
1569
|
subjectPhone: input.subjectPhone,
|
|
@@ -1143,7 +1593,7 @@ export class DataspaceNodeClient {
|
|
|
1143
1593
|
],
|
|
1144
1594
|
},
|
|
1145
1595
|
};
|
|
1146
|
-
const consent = await this.submitAndPoll(this.individualConsentR4BatchPath(
|
|
1596
|
+
const consent = await this.submitAndPoll(this.individualConsentR4BatchPath(routeCtx), this.individualConsentR4PollPath(routeCtx), consentPayload, input.pollOptions);
|
|
1147
1597
|
return {
|
|
1148
1598
|
thid,
|
|
1149
1599
|
consent,
|
|
@@ -1157,16 +1607,17 @@ export class DataspaceNodeClient {
|
|
|
1157
1607
|
* UC 5.7 wrapper: generate digital twin composition from subject data.
|
|
1158
1608
|
*/
|
|
1159
1609
|
async generateDigitalTwinFromSubjectData(ctx, input) {
|
|
1610
|
+
const routeCtx = this.requireRouteContext(ctx);
|
|
1160
1611
|
const payload = {
|
|
1161
1612
|
thid: input.compositionPayload.thid || `digital-twin-${randomUUID()}`,
|
|
1162
1613
|
...input.compositionPayload,
|
|
1163
1614
|
};
|
|
1164
1615
|
const submitPath = (input.format || 'r4') === 'api'
|
|
1165
|
-
? this.digitalTwinCompositionApiBatchPath(
|
|
1166
|
-
: this.digitalTwinCompositionR4BatchPath(
|
|
1616
|
+
? this.digitalTwinCompositionApiBatchPath(routeCtx)
|
|
1617
|
+
: this.digitalTwinCompositionR4BatchPath(routeCtx);
|
|
1167
1618
|
const pollPath = (input.format || 'r4') === 'api'
|
|
1168
|
-
? this.digitalTwinCompositionApiPollPath(
|
|
1169
|
-
: this.digitalTwinCompositionR4PollPath(
|
|
1619
|
+
? this.digitalTwinCompositionApiPollPath(routeCtx)
|
|
1620
|
+
: this.digitalTwinCompositionR4PollPath(routeCtx);
|
|
1170
1621
|
return this.submitAndPoll(submitPath, pollPath, payload, input.pollOptions);
|
|
1171
1622
|
}
|
|
1172
1623
|
/**
|
|
@@ -1196,7 +1647,8 @@ export class DataspaceNodeClient {
|
|
|
1196
1647
|
if (Date.now() - startedAt > timeoutMs) {
|
|
1197
1648
|
throw new Error(`Polling timeout after ${attempts} attempts (${timeoutMs}ms).`);
|
|
1198
1649
|
}
|
|
1199
|
-
|
|
1650
|
+
const waitMs = options?.intervalMs ?? result.retryAfterMs ?? intervalMs;
|
|
1651
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
1200
1652
|
}
|
|
1201
1653
|
}
|
|
1202
1654
|
// ---- Internal HTTP helpers ---------------------------------------------
|