dataspace-client-sdk-node 0.1.1

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.
Files changed (56) hide show
  1. package/README.md +310 -0
  2. package/SDK_PARITY_MAP.md +120 -0
  3. package/TODO_PROMPT_NEXT_STEPS.md +185 -0
  4. package/artifacts/update-smart-wallet.js +1016 -0
  5. package/dist/builders.d.ts +12 -0
  6. package/dist/builders.js +17 -0
  7. package/dist/client.d.ts +333 -0
  8. package/dist/client.js +1229 -0
  9. package/dist/consent/pdfSignatureVerification.d.ts +18 -0
  10. package/dist/consent/pdfSignatureVerification.js +23 -0
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.js +8 -0
  13. package/dist/sdk/dataspace-wallet-sdk-node/MultiWalletClient.d.ts +9 -0
  14. package/dist/sdk/dataspace-wallet-sdk-node/MultiWalletClient.js +21 -0
  15. package/dist/sdk/dataspace-wallet-sdk-node/WalletClient.d.ts +26 -0
  16. package/dist/sdk/dataspace-wallet-sdk-node/WalletClient.js +36 -0
  17. package/dist/sdk/dataspace-wallet-sdk-node/index.d.ts +6 -0
  18. package/dist/sdk/dataspace-wallet-sdk-node/index.js +6 -0
  19. package/dist/sdk/dataspace-wallet-sdk-node/provider.d.ts +24 -0
  20. package/dist/sdk/dataspace-wallet-sdk-node/provider.js +1 -0
  21. package/dist/sdk/dataspace-wallet-sdk-node/providers/memory-provider.d.ts +41 -0
  22. package/dist/sdk/dataspace-wallet-sdk-node/providers/memory-provider.js +216 -0
  23. package/dist/sdk/dataspace-wallet-sdk-node/providers/seed-provider.d.ts +22 -0
  24. package/dist/sdk/dataspace-wallet-sdk-node/providers/seed-provider.js +28 -0
  25. package/dist/sdk/dataspace-wallet-sdk-node/types.d.ts +51 -0
  26. package/dist/sdk/dataspace-wallet-sdk-node/types.js +1 -0
  27. package/dist/types.d.ts +445 -0
  28. package/dist/types.js +1 -0
  29. package/docs/API.md +745 -0
  30. package/docs/DATA_MODEL_ALIGNMENT.md +31 -0
  31. package/docs/DATA_PLANES_SCOPE_MATRIX.md +51 -0
  32. package/docs/DEVELOPER_USE_CASES.md +253 -0
  33. package/docs/E2E_BOOTSTRAP.md +54 -0
  34. package/docs/TODO_SMART_EHR_COMPAT.md +58 -0
  35. package/examples/backend-pkce-auth.mjs +119 -0
  36. package/examples/conversion-upload.mjs +52 -0
  37. package/examples/e2e-bootstrap-tenant.mjs +126 -0
  38. package/examples/e2e-individual-flow.mjs +43 -0
  39. package/examples/host-activate-and-employee.mjs +75 -0
  40. package/package.json +26 -0
  41. package/src/builders.ts +28 -0
  42. package/src/client.ts +1626 -0
  43. package/src/consent/pdfSignatureVerification.ts +41 -0
  44. package/src/index.ts +8 -0
  45. package/src/sdk/dataspace-wallet-sdk-node/MultiWalletClient.ts +25 -0
  46. package/src/sdk/dataspace-wallet-sdk-node/WalletClient.ts +63 -0
  47. package/src/sdk/dataspace-wallet-sdk-node/index.ts +6 -0
  48. package/src/sdk/dataspace-wallet-sdk-node/provider.ts +44 -0
  49. package/src/sdk/dataspace-wallet-sdk-node/providers/memory-provider.ts +310 -0
  50. package/src/sdk/dataspace-wallet-sdk-node/providers/seed-provider.ts +31 -0
  51. package/src/sdk/dataspace-wallet-sdk-node/types.ts +61 -0
  52. package/src/types.ts +497 -0
  53. package/tests/client.test.mjs +892 -0
  54. package/tests/uc5-org-onboarding.flow.test.mjs +145 -0
  55. package/tests/uc5-subject-data.flow.test.mjs +198 -0
  56. package/tsconfig.json +13 -0
@@ -0,0 +1,31 @@
1
+ # Data Model Alignment (GW + Chat + SDK)
2
+
3
+ Alignment reference for SDK consumers and backend integrators.
4
+
5
+ ## Business vs Infra Sector
6
+
7
+ - Infra host onboarding uses network routing sector (`HOST_REGISTRY_SECTOR`).
8
+ - Tenant runtime model uses business sector (`SECTOR`).
9
+ - Canonical tenant vault ID:
10
+ - `<SECTOR>_<tenantId>`
11
+ - Example: `health-care_acme`
12
+
13
+ ## Individual Organization Model
14
+
15
+ - Section: `<prefix>_individual`
16
+ - Doc ID: `org.schema.Organization.identifier.value` (UUID)
17
+
18
+ ## Search Contract (`searchFamilyOrganization`)
19
+
20
+ - Input mapping:
21
+ - `controllerPhone` -> `org.schema.Organization.owner.telephone`
22
+ - `usualname` -> `org.schema.Organization.alternateName`
23
+ - `birthDate` -> `org.schema.Organization.foundingDate` (optional)
24
+
25
+ ## Voice Phone Contract
26
+
27
+ - Preferred claim for subject call target:
28
+ - `org.schema.Organization.telephone`
29
+ - Fallback claim:
30
+ - `org.schema.Organization.owner.telephone`
31
+
@@ -0,0 +1,51 @@
1
+ # Data Planes And Scope Matrix (SDK Guide)
2
+
3
+ Canonical reference for scope semantics and resource placement in the Node SDK.
4
+
5
+ Primary backend source: `gdc-unid-node-ts/docs/02-API-AND-ENDPOINTS/02.F-DATA-PLANES-SCOPE-MATRIX.md`.
6
+
7
+ ## Quick rules
8
+
9
+ 1. Admin/legal plane
10
+ - Scopes: `org.schema/Organization.<cruds>`, `org.schema/Person.<cruds>`.
11
+ - Use for legal/admin governance and registry/discovery metadata.
12
+
13
+ 2. Subject data plane
14
+ - Scopes: `organization/<ResourceType>.<cruds|rs|rus>`.
15
+ - Use for private subject operations (`Person`, `RelatedPerson`, `Appointment`, `AppointmentResponse`, `Composition`, etc.).
16
+
17
+ 3. Catalog publication
18
+ - Public/legal metadata: `org.schema` plane.
19
+ - Private subject data: never publish raw records in catalog.
20
+
21
+ ## Resource matrix
22
+
23
+ | Intent | Scope |
24
+ |---|---|
25
+ | Subject private person profile | `organization/Person.rus` |
26
+ | Emergency contacts | `organization/RelatedPerson.cruds` |
27
+ | Appointments | `organization/Appointment.cruds` |
28
+ | Appointment responses | `organization/AppointmentResponse.cruds` |
29
+ | Subject index composition read | `organization/Composition.rs` |
30
+
31
+ ## Notification phone policy
32
+
33
+ 1. Subject notification routing (caregiver/spouse/multi-contact) belongs to private data plane and should be stored in `organization/Organization`.
34
+ 2. Subject own phone belongs to `organization/Person`.
35
+ 3. Emergency contacts belong to `organization/RelatedPerson`.
36
+ 4. Do not model these private routing contacts as legal/public `org.schema` publication data.
37
+
38
+ ## Recommended default scope bundle for subject apps
39
+
40
+ - `organization/Person.rus`
41
+ - `organization/RelatedPerson.cruds`
42
+ - `organization/Appointment.cruds`
43
+ - `organization/AppointmentResponse.cruds`
44
+ - `organization/Composition.rs`
45
+
46
+ ## Test checklist
47
+
48
+ 1. Token minted with only `org.schema/*` cannot operate on `organization/*` resources.
49
+ 2. Token minted with only `organization/*` cannot mutate legal/admin `org.schema/*` resources.
50
+ 3. Scope propagation in flow tests:
51
+ - bootstrap -> consent -> request token -> subject operation.
@@ -0,0 +1,253 @@
1
+ # Developer Use-Case Cookbook (Node SDK)
2
+
3
+ This guide provides exact method calls for real integration flows.
4
+
5
+ Normative scope/resource placement matrix:
6
+ - [DATA_PLANES_SCOPE_MATRIX.md](DATA_PLANES_SCOPE_MATRIX.md)
7
+
8
+ SDK: `dataspace-client-sdk-node` (`DataspaceNodeClient`)
9
+
10
+ ## Base Setup
11
+
12
+ ```ts
13
+ import { DataspaceNodeClient } from 'dataspace-client-sdk-node';
14
+
15
+ const client = new DataspaceNodeClient({
16
+ baseUrl: process.env.GW_BASE_URL!,
17
+ bearerToken: process.env.GW_BEARER_TOKEN, // controller/professional token depending on step
18
+ });
19
+
20
+ const ctx = {
21
+ tenantId: 'acme',
22
+ jurisdiction: 'ES',
23
+ sector: 'health-care',
24
+ };
25
+ ```
26
+
27
+ ## UC5.1 Subject bootstrap (personal organization)
28
+
29
+ Terminology note: `subject` names in methods/claims refer to the member (person/patient) orchestrated by a personal organization with controller/manager and associated members.
30
+
31
+ Method: `bootstrapSubjectOrganizationIndex(ctx, input)`
32
+
33
+ ```ts
34
+ const result = await client.bootstrapSubjectOrganizationIndex(ctx, {
35
+ registrationPayload: {
36
+ body: {
37
+ data: [{ type: 'Family-registration-form-v1.0', meta: { claims: { '@context': 'org.schema' } } }],
38
+ },
39
+ },
40
+ confirmationPayload: {
41
+ body: {
42
+ data: [{ type: 'Family-order-request-v1.0', meta: { claims: { 'Order.acceptedOffer.identifier': 'urn:offer:123' } } }],
43
+ },
44
+ },
45
+ });
46
+ ```
47
+
48
+ ## UC5.2 Legal organization activation in GW (from ICA proof)
49
+
50
+ Method: `activateOrganizationInGatewayFromIcaProof(hostCtx, input)`
51
+
52
+ ```ts
53
+ const activation = await client.activateOrganizationInGatewayFromIcaProof(
54
+ { jurisdiction: 'ES', sector: 'health-care' },
55
+ {
56
+ vpToken: process.env.ICA_VP_TOKEN!,
57
+ organizationVc: process.env.ICA_ORG_VC_JWT,
58
+ legalRepresentativeVc: process.env.ICA_LEGAL_REP_VC_JWT,
59
+ regulatoryEvidence: { sanitaryRegister: 'REG-123' },
60
+ },
61
+ );
62
+ ```
63
+
64
+ ## UC5.3 Create employee / professional license
65
+
66
+ Method: `createOrganizationEmployee(ctx, input)`
67
+
68
+ ```ts
69
+ await client.createOrganizationEmployee(ctx, {
70
+ employeeClaims: {
71
+ '@context': 'org.schema',
72
+ 'org.schema.Person.email': 'doctor@example.com',
73
+ 'org.schema.Person.hasOccupation': 'ISCO-08|2211',
74
+ },
75
+ });
76
+ ```
77
+
78
+ ## UC5.4 Activate employee device
79
+
80
+ Method: `activateEmployeeDeviceWithActivationCode(ctx, input)`
81
+
82
+ ```ts
83
+ const device = await client.activateEmployeeDeviceWithActivationCode(ctx, {
84
+ activationCode: 'ACT-123456',
85
+ idToken: process.env.USER_ID_TOKEN!,
86
+ dcrPayload: {
87
+ application_type: 'web',
88
+ token_endpoint_auth_method: 'private_key_jwt',
89
+ jwks: { keys: [] },
90
+ },
91
+ });
92
+ ```
93
+
94
+ ## UC5.5 Import IPS/FHIR and update index
95
+
96
+ Method: `importIpsOrFhirAndUpdateIndex(ctx, input)`
97
+
98
+ ```ts
99
+ await client.importIpsOrFhirAndUpdateIndex(ctx, {
100
+ format: 'r4',
101
+ compositionPayload: {
102
+ body: {
103
+ data: [{ type: 'Composition-import-request-v1.0', meta: { claims: { subject: 'did:web:subject.example.com' } } }],
104
+ },
105
+ },
106
+ });
107
+ ```
108
+
109
+ ## UC5.6 Consent then SMART token (real decoupled flow)
110
+
111
+ Step 1 method: `grantProfessionalAccessSimple(ctx, input)`
112
+ Step 2 method: `requestSmartToken(input)`
113
+
114
+ Domain note: the protected context is modeled as organization (including personal organizations with controller + subject/person/patient members).
115
+
116
+ Scope namespace note:
117
+ - Organization administration (non-FHIR): `org.schema/Organization.<cruds>`, `org.schema/Person.<cruds>`
118
+ - Subject/personal-organization data (FHIR): `organization/<ResourceType>.<cruds|rs>` with optional `?subject=<did:web:...>`
119
+
120
+ ```ts
121
+ const consent = await client.grantProfessionalAccessSimple(ctx, {
122
+ subjectPhone: '+34600111222',
123
+ subjectGivenName: 'Ana',
124
+ actor: { identifier: 'did:web:hospital.example.com' },
125
+ actorRole: 'Practitioner',
126
+ purpose: 'TREAT',
127
+ actions: ['organization/Composition.rs'],
128
+ });
129
+
130
+ const token = await client.requestSmartToken({
131
+ endpointId: 'professional-app',
132
+ scopes: ['organization/Composition.rs'],
133
+ exchangePayload: { grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange' },
134
+ path: '/token',
135
+ });
136
+ ```
137
+
138
+ ## UC5.7 Generate digital twin
139
+
140
+ Method: `generateDigitalTwinFromSubjectData(ctx, input)`
141
+
142
+ ```ts
143
+ await client.generateDigitalTwinFromSubjectData(ctx, {
144
+ format: 'r4',
145
+ compositionPayload: {
146
+ body: {
147
+ data: [{ type: 'DigitalTwin-composition-request-v1.0', meta: { claims: { source: 'subject-index' } } }],
148
+ },
149
+ },
150
+ });
151
+ ```
152
+
153
+ ## Additional consent actor patterns
154
+
155
+ Important: canonical claim key is `Consent.actor-identifier`.
156
+ `grantProfessionalAccessSimple` accepts convenience aliases and resolves them into that canonical identifier.
157
+
158
+ Action semantics note:
159
+ - Avoid generic actions like `access` or `read`.
160
+ - Use canonical operation strings aligned with protected resource contracts/scopes (for example `organization/Composition.rs`, `organization/Appointment.cruds`).
161
+
162
+ ### A) Actor by canonical `identifier` + role (recommended)
163
+
164
+ ```ts
165
+ await client.grantProfessionalAccessSimple(ctx, {
166
+ subjectDid: 'did:web:subject.example.com',
167
+ actor: { identifier: 'did:web:org.example.com' },
168
+ actorRole: 'Practitioner',
169
+ purpose: 'TREAT',
170
+ actions: ['organization/Composition.rs'],
171
+ });
172
+ ```
173
+
174
+ ### B) Actor by organization URL/domain or NIF + role
175
+
176
+ ```ts
177
+ await client.grantProfessionalAccessSimple(ctx, {
178
+ subjectDid: 'did:web:subject.example.com',
179
+ actor: { url: 'org.example.com' }, // accepts bare domain or full URL; resolves to did:web:org.example.com
180
+ actorRole: 'Practitioner',
181
+ purpose: 'CARE',
182
+ actions: ['organization/Appointment.cruds'],
183
+ });
184
+ ```
185
+
186
+ ```ts
187
+ await client.grantProfessionalAccessSimple(ctx, {
188
+ subjectDid: 'did:web:subject.example.com',
189
+ actor: { organizationTaxId: 'B12345678' }, // maps to urn:taxid:B12345678
190
+ actorRole: 'Practitioner',
191
+ purpose: 'CARE',
192
+ actions: ['organization/RelatedPerson.cruds'],
193
+ });
194
+ ```
195
+
196
+ ### C) Actor by email/phone + role
197
+
198
+ ```ts
199
+ await client.grantProfessionalAccessSimple(ctx, {
200
+ subjectDid: 'did:web:subject.example.com',
201
+ actor: { email: 'doctor@example.com' },
202
+ actorRole: 'Practitioner',
203
+ purpose: 'TREAT',
204
+ actions: ['organization/Composition.rs'],
205
+ });
206
+ ```
207
+
208
+ ```ts
209
+ await client.grantProfessionalAccessSimple(ctx, {
210
+ subjectDid: 'did:web:subject.example.com',
211
+ actor: { phone: '+34600111222' }, // maps to urn:tel:+34600111222
212
+ actorRole: 'Practitioner',
213
+ purpose: 'TREAT',
214
+ actions: ['organization/AppointmentResponse.cruds'],
215
+ });
216
+ ```
217
+
218
+ ### D) Jurisdiction + role (explicit claims)
219
+
220
+ `grantProfessionalAccessSimple` does not currently expose a `jurisdiction` field.
221
+ For jurisdiction-based actor identifiers, submit explicit claims:
222
+
223
+ ```ts
224
+ await client.submitAndPoll(
225
+ client.individualConsentR4BatchPath(ctx),
226
+ client.individualConsentR4PollPath(ctx),
227
+ {
228
+ thid: 'consent-jurisdiction-001',
229
+ body: {
230
+ data: [{
231
+ type: 'Consent-grant-request-v1.0',
232
+ meta: {
233
+ claims: {
234
+ '@context': 'org.hl7.fhir.api',
235
+ 'Consent.decision': 'permit',
236
+ 'Consent.subject': 'did:web:subject.example.com',
237
+ 'Consent.actor-identifier': 'urn:jurisdiction:ES',
238
+ 'Consent.actor-role': 'Practitioner',
239
+ 'Consent.purpose': 'TREAT',
240
+ 'Consent.action': 'LOINC|48765-2,LOINC|10160-0',
241
+ },
242
+ },
243
+ }],
244
+ },
245
+ },
246
+ );
247
+ ```
248
+
249
+ ## Traceability contract (FHIR + claims)
250
+
251
+ - FHIR resource identity: `resource.id` remains UUID.
252
+ - FHIR version traceability: `resource.meta.versionId` stores CID of canonical FHIR resource version.
253
+ - Claims traceability: `grantProfessionalAccessSimple(...)` now emits `resource.meta.claims["@id"]` as CID of canonical claims (excluding `@context`, `@type`, `@id`).
@@ -0,0 +1,54 @@
1
+ # E2E Bootstrap (Tenant + Controller)
2
+
3
+ This flow prepares a tenant for frontend E2E tests (`apptemplate`) after ICA proof is available.
4
+
5
+ ## 1) What the script does
6
+
7
+ Script: `examples/e2e-bootstrap-tenant.mjs`
8
+
9
+ 1. Authenticates to GW (`AUTH_MODE=demo|pkce`).
10
+ 2. Calls host activation endpoint (`Organization/_activate`) with `vp_token` and optional ICA JWT VCs.
11
+ 3. Optionally creates a controller employee in the tenant (`Employee/_batch`).
12
+
13
+ ## 2) Run command
14
+
15
+ ```bash
16
+ npm run example:e2e-bootstrap-tenant
17
+ ```
18
+
19
+ ## 3) Required env
20
+
21
+ - `BASE_URL` (default `http://localhost:3000`)
22
+ - `VP_TOKEN` (required)
23
+
24
+ Auth-specific:
25
+
26
+ - Demo:
27
+ - `AUTH_MODE=demo` (default)
28
+ - `AUTH_BEARER` (optional, default `demo-token`)
29
+ - PKCE:
30
+ - `AUTH_MODE=pkce`
31
+ - `GW_API_KEY`
32
+ - `GW_CONTROLLER_PUBLIC_JWK_SIGN` (JSON object)
33
+
34
+ Routing:
35
+
36
+ - `JURISDICTION` (default `ES`)
37
+ - `HOST_REGISTRY_SECTOR` (default `test`)
38
+ - `TENANT_ID` (default `acme`)
39
+ - `SECTOR` (default `health-care`)
40
+
41
+ Optional:
42
+
43
+ - `ORGANIZATION_VC_JWT`
44
+ - `LEGAL_REPRESENTATIVE_VC_JWT`
45
+ - `CREATE_CONTROLLER_EMPLOYEE=true`
46
+ - `CONTROLLER_EMAIL`
47
+ - `CONTROLLER_ROLE`
48
+
49
+ ## 4) Typical sequence
50
+
51
+ 1. Start local stack (GW + ICA + dependencies).
52
+ 2. Obtain `vp_token` from ICA flow (frontend or node).
53
+ 3. Run this bootstrap script.
54
+ 4. Run `apptemplate` integration profile (`local-demo`, `local-docker`, or `cloud-staging`).
@@ -0,0 +1,58 @@
1
+ # TODO: SMART EHR Compatibility (patient scope alias)
2
+
3
+ Status: pending
4
+ Owner: TBD
5
+ Priority: medium
6
+
7
+ ## Goal
8
+ Support EHR integrations that request SMART scopes using `patient/*` without custom `?subject=...`, while preserving the current gateway profile based on subject-pinned scopes.
9
+
10
+ ## Current state (today)
11
+ - Gateway profile expects subject pinning through scope query parameter (`?subject=...`).
12
+ - Current token flow uses actor identity in `sub` and subject context extracted from scope.
13
+ - This differs from SMART App Launch standard patient context behavior.
14
+
15
+ ## Target behavior
16
+ 1. Keep current gateway profile (`organization/*?subject=...`) for existing clients.
17
+ 2. Add compatibility profile for SMART EHR (`patient/*`) without requiring query `subject`.
18
+ 3. Resolve patient context via SMART launch context (authorize -> token `patient` context), not via custom query-only requirement.
19
+ 4. Keep single-subject enforcement per token request.
20
+
21
+ ## Proposed switch
22
+ - Environment flag (default enabled compatibility):
23
+ - `DISABLE_PATIENT_SCOPE_ALIAS=false` (default)
24
+ - `DISABLE_PATIENT_SCOPE_ALIAS=true` disables alias compatibility
25
+
26
+ ## Implementation TODO
27
+ 1. Scope parser
28
+ - Accept `patient/*` root scopes when alias compatibility is enabled.
29
+ - Normalize internally to canonical authorization checks.
30
+
31
+ 2. Subject/patient context resolution
32
+ - Priority order:
33
+ 1) launch context patient id (SMART standard)
34
+ 2) explicit `?subject=...` (gateway profile)
35
+ - Reject when no resolvable subject context exists.
36
+
37
+ 3. Token payload contract
38
+ - Include/propagate patient context field consistent with SMART expectations.
39
+ - Preserve actor identity semantics already used by gateway.
40
+
41
+ 4. Policy checks
42
+ - Ensure consent/rule lookup uses resolved subject context.
43
+ - Enforce single-subject invariant.
44
+
45
+ 5. Documentation
46
+ - Add compatibility matrix:
47
+ - SMART standard mode (launch/patient context)
48
+ - Gateway mode (scope subject pinning)
49
+ - Clarify required fields per mode.
50
+
51
+ 6. Tests
52
+ - Unit tests: scope parsing + subject resolution matrix.
53
+ - Integration tests: EHR-style `patient/*` and gateway-style `organization/*?subject=...`.
54
+ - Negative tests: missing subject context, mixed-subject scopes.
55
+
56
+ ## Out of scope (for this TODO)
57
+ - Full redesign of authorization model.
58
+ - Breaking changes in existing gateway clients.
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Example: identity-exchange.v1 backend PKCE auth (Node SDK)
3
+ *
4
+ * Demonstrates authenticateBackendPkceAndExchange — the full B2B auth flow:
5
+ * 1. ICA DCR: bind API key to service public JWK
6
+ * 2. PKCE code: S256 challenge
7
+ * 3. PKCE token: code + verifier → id_token
8
+ * 4. Exchange: id_token → SMART bearer
9
+ *
10
+ * Node.js equivalent of Python connector_sdk `authenticate_backend_pkce_and_exchange`.
11
+ *
12
+ * Prerequisites:
13
+ * - Tenant org activated in the GW (registry/org.schema/Organization/_activate with ICA VC)
14
+ * - API key issued by ICA with required scopes
15
+ * - Service key pair (EC P-384 recommended); only the public JWK is sent to the GW
16
+ *
17
+ * Environment variables:
18
+ * BASE_URL GW base URL (e.g. http://localhost:3000)
19
+ * GW_API_KEY API key issued by ICA for this service
20
+ * GW_PUBLIC_JWK JSON-serialised service public JWK (EC P-384)
21
+ * TENANT_ID Tenant org id (e.g. acme)
22
+ * JURISDICTION Jurisdiction code (e.g. ES)
23
+ * SECTOR Sector code (e.g. health-care)
24
+ * GW_AUTH_BEARER (optional) Static bearer for SECURITY_MODE=demo — skips the full flow
25
+ *
26
+ * Run (production flow):
27
+ * BASE_URL="https://gw.example.com" \
28
+ * GW_API_KEY="<api-key>" \
29
+ * GW_PUBLIC_JWK='{"kty":"EC","crv":"P-384","x":"...","y":"..."}' \
30
+ * TENANT_ID="acme" JURISDICTION="ES" SECTOR="health-care" \
31
+ * node examples/backend-pkce-auth.mjs
32
+ *
33
+ * Run (demo/local bypass — SECURITY_MODE=demo):
34
+ * BASE_URL="http://localhost:3000" GW_AUTH_BEARER="demo-token" \
35
+ * node examples/backend-pkce-auth.mjs
36
+ */
37
+
38
+ import { DataspaceNodeClient } from '../dist/index.js';
39
+
40
+ // ---- Demo bypass --------------------------------------------------------
41
+ // If GW_AUTH_BEARER is set (SECURITY_MODE=demo), skip the full PKCE flow.
42
+ const staticBearer = process.env.GW_AUTH_BEARER;
43
+ if (staticBearer) {
44
+ console.log('[demo] Using static GW_AUTH_BEARER — skipping identity-exchange.v1 flow.');
45
+ const client = new DataspaceNodeClient({
46
+ baseUrl: process.env.BASE_URL || 'http://localhost:3000',
47
+ bearerToken: staticBearer,
48
+ });
49
+ console.log('[demo] Client ready. bearerToken set from env.');
50
+ // eslint-disable-next-line no-process-exit
51
+ process.exit(0);
52
+ }
53
+
54
+ // ---- Production flow ----------------------------------------------------
55
+ const baseUrl = process.env.BASE_URL;
56
+ const apiKey = process.env.GW_API_KEY;
57
+ const publicJwkRaw = process.env.GW_PUBLIC_JWK;
58
+
59
+ if (!baseUrl || !apiKey || !publicJwkRaw) {
60
+ console.error('Required env vars: BASE_URL, GW_API_KEY, GW_PUBLIC_JWK');
61
+ console.error('Or set GW_AUTH_BEARER for demo/local bypass.');
62
+ process.exit(1);
63
+ }
64
+
65
+ const controllerPublicJwk = JSON.parse(publicJwkRaw);
66
+
67
+ const ctx = {
68
+ tenantId: process.env.TENANT_ID || 'acme',
69
+ jurisdiction: process.env.JURISDICTION || 'ES',
70
+ sector: process.env.SECTOR || 'health-care',
71
+ };
72
+
73
+ // Client without bearer — auth flow will obtain it.
74
+ const client = new DataspaceNodeClient({ baseUrl });
75
+
76
+ console.log('[auth] Starting identity-exchange.v1 backend PKCE flow...');
77
+ console.log('[auth] ctx:', ctx);
78
+ console.log('[auth] apiKey prefix:', apiKey.slice(0, 8) + '...');
79
+
80
+ const auth = await client.authenticateBackendPkceAndExchange({
81
+ ctx,
82
+ apiKey,
83
+ controllerPublicJwk,
84
+ scopes: ['onboarding', 'family-registration', 'license-order'],
85
+ endpointId: 'chatbot-main',
86
+ // codeVerifier: optional — auto-generated as randomUUID() if not provided
87
+ pollOptions: { timeoutMs: 60_000, intervalMs: 2_000 },
88
+ });
89
+
90
+ if (auth.status === 'failed') {
91
+ console.error(`[auth] FAILED at step: ${auth.step}`);
92
+ process.exit(1);
93
+ }
94
+
95
+ console.log(`[auth] Status: ${auth.status}`); // 'fetched' or 'cached'
96
+ console.log(`[auth] Token type: ${auth.tokenType}`);
97
+ console.log(`[auth] Scopes granted: ${auth.scopes.join(' ')}`);
98
+ console.log(`[auth] Bearer (truncated): ${auth.accessToken.slice(0, 20)}...`);
99
+
100
+ // ---- Use the bearer for subsequent SDK calls ----------------------------
101
+ const authedClient = new DataspaceNodeClient({
102
+ baseUrl,
103
+ bearerToken: auth.accessToken,
104
+ });
105
+
106
+ // Example: submit a family registration draft (DIDComm plain)
107
+ // import { createDidcommPlainMessage } from '../dist/index.js';
108
+ // const payload = createDidcommPlainMessage({ iss: ..., aud: ..., body: { data: [...] } });
109
+ // const result = await authedClient.submitAndPoll(
110
+ // authedClient.individualFamilyOrganizationBatchPath(ctx),
111
+ // authedClient.individualFamilyOrganizationPollPath(ctx),
112
+ // payload,
113
+ // );
114
+ // console.log(result.poll.status, result.poll.body);
115
+
116
+ // ---- Cache check --------------------------------------------------------
117
+ // On subsequent calls, getCachedBearerToken avoids re-running the flow:
118
+ const cached = client.getCachedBearerToken('chatbot-main');
119
+ console.log(`[cache] Cached bearer available: ${cached !== undefined}`);
@@ -0,0 +1,52 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { basename } from 'node:path';
3
+ import { DataspaceNodeClient } from '../dist/index.js';
4
+
5
+ const baseUrl = process.env.BASE_URL || 'http://localhost:3000';
6
+ const bearerToken = process.env.AUTH_BEARER || 'demo-token';
7
+
8
+ const tenantId = process.env.TENANT_ID || 'acme';
9
+ const jurisdiction = process.env.JURISDICTION || 'ES';
10
+ const sector = process.env.SECTOR || 'animal-care';
11
+ const softwareId = process.env.SOFTWARE_ID || 'excel-adapter';
12
+ const sourceFormat = process.env.SOURCE_FORMAT || 'xlsx';
13
+ const filePath = process.env.SOURCE_FILE;
14
+
15
+ if (!filePath) {
16
+ throw new Error('Missing SOURCE_FILE env var (path to excel/csv file).');
17
+ }
18
+
19
+ const client = new DataspaceNodeClient({ baseUrl, bearerToken });
20
+ const ctx = { tenantId, jurisdiction, sector };
21
+
22
+ const uploadPath = client.conversionUploadPath(ctx, softwareId, sourceFormat);
23
+ const pollPath = client.conversionUploadPollPath(ctx, softwareId, sourceFormat);
24
+
25
+ const fileName = basename(filePath);
26
+ const bytes = await readFile(filePath);
27
+
28
+ const submit = await client.uploadConversionFile({
29
+ path: uploadPath,
30
+ fileName,
31
+ fileContent: bytes,
32
+ fields: {
33
+ // Optional provider-specific fields. Keep/remove depending on DataConv API contract.
34
+ mode: process.env.CONVERSION_MODE || 'didcomm-plain',
35
+ },
36
+ });
37
+
38
+ console.log('Upload submit:', JSON.stringify(submit, null, 2));
39
+
40
+ const thid =
41
+ process.env.THID ||
42
+ submit.body?.thid ||
43
+ submit.body?.body?.thid ||
44
+ submit.body?.data?.thid;
45
+
46
+ if (!thid) {
47
+ console.log('No thid found in upload response. If your DataConv API is synchronous, stop here.');
48
+ process.exit(0);
49
+ }
50
+
51
+ const poll = await client.pollUntilComplete(pollPath, { thid }, { timeoutMs: 120000, intervalMs: 5000 });
52
+ console.log('Upload poll:', JSON.stringify(poll, null, 2));