gdc-sdk-client-ts 1.0.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 (135) hide show
  1. package/README.md +392 -0
  2. package/__tests__/BaseApiService.test.ts +158 -0
  3. package/__tests__/BaseProfessionalService.test.ts +26 -0
  4. package/__tests__/ClientSDK.test.ts +114 -0
  5. package/__tests__/CommonAuthService.test.ts +170 -0
  6. package/__tests__/ConversationToObservations.test.ts +231 -0
  7. package/__tests__/IStorageAdapter.ts +39 -0
  8. package/__tests__/IVaultRepository-backend.ts +55 -0
  9. package/__tests__/IndividualService.test.ts +126 -0
  10. package/__tests__/JobManager.test.ts +338 -0
  11. package/__tests__/OrgAdminService.test.ts +157 -0
  12. package/__tests__/ProfileManager.test.ts +137 -0
  13. package/__tests__/Services.test.ts +148 -0
  14. package/__tests__/SmartTokenManager.test.ts +110 -0
  15. package/__tests__/StorageAdapter-mem.ts +46 -0
  16. package/__tests__/VaultRepository-mem-frontend.ts +95 -0
  17. package/__tests__/VaultRepository.mem.ts +122 -0
  18. package/__tests__/VerifierService.test.ts +89 -0
  19. package/__tests__/capabilityMapper.test.ts +67 -0
  20. package/__tests__/integration/apiIntegratorsGuide.e2e.test.ts +215 -0
  21. package/__tests__/mockData.test.ts +43 -0
  22. package/__tests__/repository.ts +14 -0
  23. package/__tests__/rule.test.ts +23 -0
  24. package/__tests__/scopeBuilder.test.ts +72 -0
  25. package/__tests__/stubs/multibasehash.ts +8 -0
  26. package/__tests__/stubs/stablelib-base64.ts +40 -0
  27. package/__tests__/stubs/stablelib-utf8.ts +23 -0
  28. package/__tests__/testUtils.ts +94 -0
  29. package/data/demo/credential.data.ts +89 -0
  30. package/data/demo/didProvider.data.ts +68 -0
  31. package/data/demo/entityKeys.data.ts +15 -0
  32. package/data/demo/example-payloads.data.ts +369 -0
  33. package/data/demo/offer.data.ts +19 -0
  34. package/data/demo/org-base.data.ts +118 -0
  35. package/data/demo/trust.registry.data.ts +56 -0
  36. package/data/demo/urn.data.ts +10 -0
  37. package/jest.config.ts +38 -0
  38. package/package.json +16 -0
  39. package/scripts/run-e2e-external.sh +60 -0
  40. package/src/ClientSDK.ts +265 -0
  41. package/src/JobManager.ts +315 -0
  42. package/src/ProfileManager.ts +177 -0
  43. package/src/ProfileRegistry.ts +59 -0
  44. package/src/SmartTokenManager.ts +288 -0
  45. package/src/VerifierService.ts +140 -0
  46. package/src/frontend-services/BaseApiService.ts +282 -0
  47. package/src/frontend-services/base/BaseProfessionalService.ts +67 -0
  48. package/src/frontend-services/capabilities/AppointmentService.ts +58 -0
  49. package/src/frontend-services/capabilities/MedicationService.ts +88 -0
  50. package/src/frontend-services/capabilities/TaskService.ts +66 -0
  51. package/src/frontend-services/common/CommonAuthService.ts +217 -0
  52. package/src/frontend-services/family-admin/FamilyAdminService.ts +124 -0
  53. package/src/frontend-services/family-admin/FamilyItService.ts +53 -0
  54. package/src/frontend-services/individual/IndividualService.ts +46 -0
  55. package/src/frontend-services/org-admin/OrgAdminService.ts +131 -0
  56. package/src/frontend-services/org-admin/OrgItService.ts +59 -0
  57. package/src/frontend-services/professional/emergencies/ParamedicService.ts +28 -0
  58. package/src/frontend-services/professional/health-care/PhysicianService.ts +56 -0
  59. package/src/index.ts +31 -0
  60. package/src/interfaces/IJobManager.ts +31 -0
  61. package/src/interfaces/IParams.ts +142 -0
  62. package/src/interfaces/IProfile.ts +23 -0
  63. package/src/interfaces/ISchemaorg.ts +192 -0
  64. package/src/interfaces/ISmartTokenManager.ts +44 -0
  65. package/src/interfaces/ITrustRegistry.ts +48 -0
  66. package/src/interfaces/IVaultRepository.ts +47 -0
  67. package/src/interfaces/index.ts +13 -0
  68. package/src/interfaces/others.ts +100 -0
  69. package/src/models/claims/ClaimsAllergyIntolerance.model.ts +62 -0
  70. package/src/models/claims/ClaimsBase.common.model.ts +37 -0
  71. package/src/models/claims/ClaimsCondition.model.ts +55 -0
  72. package/src/models/claims/ClaimsConsent.model.ts +148 -0
  73. package/src/models/claims/ClaimsDevice.model.ts +93 -0
  74. package/src/models/claims/ClaimsDeviceAssociation.model.ts +78 -0
  75. package/src/models/claims/ClaimsDeviceUsage.model.ts +93 -0
  76. package/src/models/claims/ClaimsMedicationStatement.model.ts +55 -0
  77. package/src/models/claims/ClaimsObservation.model.ts +55 -0
  78. package/src/models/claims/ClaimsRelatedPerson.model.ts +63 -0
  79. package/src/models/claims/index.ts +13 -0
  80. package/src/models/hl7-personal-relationship.ts +227 -0
  81. package/src/models/index.ts +6 -0
  82. package/src/models/profileRegistry.ts +16 -0
  83. package/src/models/roleIsco.ts +45 -0
  84. package/src/models/rolesHL7.ts +36 -0
  85. package/src/roleRegistry.ts +120 -0
  86. package/src/serviceSelectorRegistry.ts +297 -0
  87. package/src/uhc-fhir-utils-ts/models/AtcModel.ts +20 -0
  88. package/src/uhc-fhir-utils-ts/models/CommonModels.ts +82 -0
  89. package/src/uhc-fhir-utils-ts/models/DicomModels.ts +36 -0
  90. package/src/uhc-fhir-utils-ts/models/FhirModels.ts +70 -0
  91. package/src/uhc-fhir-utils-ts/models/LoincModels.ts +30 -0
  92. package/src/uhc-fhir-utils-ts/models/SnomedModels.ts +13 -0
  93. package/src/uhc-fhir-utils-ts/models/index.ts +11 -0
  94. package/src/uhc-fhir-utils-ts/models/params/AdverseEvent.params.model.ts +16 -0
  95. package/src/uhc-fhir-utils-ts/models/params/AllergyIntolerance.params.model.ts +27 -0
  96. package/src/uhc-fhir-utils-ts/models/params/Appointment.params.model.ts +14 -0
  97. package/src/uhc-fhir-utils-ts/models/params/ClinicalImpression.params.model.ts +15 -0
  98. package/src/uhc-fhir-utils-ts/models/params/Condition.params.model.ts +17 -0
  99. package/src/uhc-fhir-utils-ts/models/params/DetectedIssue.params.model.ts +13 -0
  100. package/src/uhc-fhir-utils-ts/models/params/DiagnosticReport.params.model.ts +14 -0
  101. package/src/uhc-fhir-utils-ts/models/params/Encounter.params.model.ts +19 -0
  102. package/src/uhc-fhir-utils-ts/models/params/Immunization.params.model.ts +17 -0
  103. package/src/uhc-fhir-utils-ts/models/params/Medication.params.model.ts +45 -0
  104. package/src/uhc-fhir-utils-ts/models/params/Observation.params.model.ts +399 -0
  105. package/src/uhc-fhir-utils-ts/models/params/Procedure.params.model.ts +17 -0
  106. package/src/uhc-fhir-utils-ts/models/params/Search.params.model.ts +164 -0
  107. package/src/uhc-fhir-utils-ts/models/params/ServiceRequest.params.model.ts +16 -0
  108. package/src/uhc-fhir-utils-ts/models/params/Specimen.params.model.ts +12 -0
  109. package/src/uhc-fhir-utils-ts/models/params/index.ts +16 -0
  110. package/src/uhc-fhir-utils-ts/models/templates/AdverseEvent.template.model.ts +24 -0
  111. package/src/uhc-fhir-utils-ts/models/templates/AllergyIntolerance.template.model.ts +28 -0
  112. package/src/uhc-fhir-utils-ts/models/templates/Appointment.template.model.ts +17 -0
  113. package/src/uhc-fhir-utils-ts/models/templates/ClinicalImpression.template.model.ts +19 -0
  114. package/src/uhc-fhir-utils-ts/models/templates/Condition.template.model.ts +20 -0
  115. package/src/uhc-fhir-utils-ts/models/templates/DetectedIssue.template.model.ts +14 -0
  116. package/src/uhc-fhir-utils-ts/models/templates/DiagnosticReport.template.model.ts +19 -0
  117. package/src/uhc-fhir-utils-ts/models/templates/Encounter.template.model.ts +20 -0
  118. package/src/uhc-fhir-utils-ts/models/templates/Immunization.template.model.ts +21 -0
  119. package/src/uhc-fhir-utils-ts/models/templates/Medication.template.model.ts +52 -0
  120. package/src/uhc-fhir-utils-ts/models/templates/Observation.template.model.ts +53 -0
  121. package/src/uhc-fhir-utils-ts/models/templates/Procedure.template.model.ts +20 -0
  122. package/src/uhc-fhir-utils-ts/models/templates/ServiceRequest.template.model.ts +19 -0
  123. package/src/uhc-fhir-utils-ts/models/templates/Specimen.template.model.ts +15 -0
  124. package/src/uhc-fhir-utils-ts/models/templates/index.ts +15 -0
  125. package/src/uhc-fhir-utils-ts/utils/fhirRelatedPerson.ts +59 -0
  126. package/src/uhc-fhir-utils-ts/utils/fhirToken.ts +34 -0
  127. package/src/uhc-fhir-utils-ts/utils/index.ts +3 -0
  128. package/src/uhc-fhir-utils-ts/utils/telecom.ts +12 -0
  129. package/src/utils/capabilityMapper.ts +137 -0
  130. package/src/utils/deriveObservations.ts +73 -0
  131. package/src/utils/index.ts +7 -0
  132. package/src/utils/mockData.ts +88 -0
  133. package/src/utils/rule.ts +55 -0
  134. package/src/utils/scopeBuilder.ts +109 -0
  135. package/tsconfig.json +13 -0
package/README.md ADDED
@@ -0,0 +1,392 @@
1
+ # Client SDK (`client-sdk-ts`)
2
+
3
+ This SDK provides a platform-agnostic, secure interface for interacting with the backend services. It encapsulates the complexity of cryptographic operations, secure storage, and the two-phase authentication model required by the API.
4
+
5
+ ## Core Architectural Concepts
6
+
7
+ ### Data Integrity & Conflict Resolution Model
8
+
9
+ The SDK uses a sophisticated model to ensure data integrity and manage versioning, especially in scenarios where a draft might be edited on multiple devices before being submitted.
10
+
11
+ - **`job.id` / `job.content.jti` (Stable Job/Draft ID):**
12
+ - **Purpose:** This is the persistent, primary identifier for the entire job or "draft."
13
+ - **Implementation:** It's a `UUIDv4`, created **once** when the draft is first initiated.
14
+ - **Behavior:** It **never changes**, even when the content is edited. It serves as the primary key in the local `JobManager` vault and is the public-facing identifier used for anti-replay protection, as required by FAPI.
15
+
16
+ - **`job.versionId` / `job.content.id` (Ephemeral Content Version ID):**
17
+ - **Purpose:** This is a verifiable "fingerprint" of the *current version* of the draft's content.
18
+ - **Implementation:** It is a `SHA` hash of the canonicalized `content` object (with `meta` and `id` fields excluded).
19
+ - **Behavior:** It is **recalculated and changes every time** the draft is saved. Its sole purpose is internal version tracking and integrity verification. Crucially, this `id` field within the payload is **removed before the message is sent**, as the digital signature covers the integrity of the sent content.
20
+
21
+ - **`job.sequence` (Ordering Timestamp):**
22
+ - **Purpose:** To provide a definitive order for different versions of a draft.
23
+ - **Implementation:** An epoch timestamp (number) generated whenever a draft is saved.
24
+ - **Behavior:** This is the primary mechanism for conflict resolution. The version with the highest `sequence` number is considered the most recent.
25
+
26
+ - **`job.previousSequence` (Version History Link):**
27
+ - **Purpose:** To create a linked list or "change history" of a draft's versions.
28
+ - **Implementation:** When a draft is updated, the `sequence` number of the *previous* version is stored in this field.
29
+ - **Behavior:** This allows the system to trace the history of edits. It is the key to detecting **uncoordinated simultaneous edits**. If a device tries to sync a version whose `previousSequence` does not match the `sequence` of the current latest version on the server, a conflict has occurred. The default strategy is **last-write-wins** (based on the highest `sequence`), but this history makes more complex merging strategies possible in the future.
30
+
31
+ ### User vs. Device Identity
32
+
33
+ ### 1. Host vs. Gateway Providers
34
+
35
+ The SDK's data model and service resolution are built on a clear distinction between two entity types:
36
+
37
+ - **Host Provider:** The central infrastructure provider. It is responsible for onboarding new organizations. Its DID Document contains **`registry`** services, such as `createOrganization` and `confirmOrder`. All onboarding operations are directed at the Host's DID.
38
+ - **Gateway Provider (Tenant):** An individual organization that has been onboarded. Its DID Document contains **`entity`** and **`individual`** services for day-to-day operations (e.g., `createEmployee`, `activateDevice`). Once onboarded, an organization's services are resolved against its own DID.
39
+
40
+ ### 2. The `roleRegistry` as the Single Source of Truth
41
+
42
+ The SDK is designed to be highly scalable and maintainable by centralizing all business logic for role capabilities into one place: `client-sdk-ts/src/roleRegistry.ts`.
43
+
44
+ - **`roleRegistry.ts`** defines which services (e.g., `PhysicianService`) and capabilities (e.g., `MedicationService`) are available to a user based on their ISCO-08 role code.
45
+ - **`capabilityMapper.ts`** is a pure, agnostic engine that reads this registry at runtime. It dynamically assembles and injects the correct services when a user session is created.
46
+ - **To add or modify a role's capabilities, a developer only needs to edit the `roleRegistry.ts` file.** The rest of the system adapts automatically.
47
+
48
+ The SDK's security model is built on two distinct layers of identity and authentication, which are managed automatically.
49
+
50
+ ## Service-Layer Architecture: Hierarchical & Composable Capability Services
51
+
52
+ ### Fundamental Principle
53
+
54
+ The API must reflect a user's real-world **capabilities**, which are a combination of their role, their context, and their authorizations. The architecture is built on two core principles: **Inheritance** for "is-a" relationships (a Physician *is-a* Paramedic in terms of skills) and **Composition** for "has-a" capabilities (a Physician *has-a* medication administration capability).
55
+
56
+ ### Core Concepts
57
+
58
+ * **Context:** The environment of the interaction: **Organization** or **Family**.
59
+ * **Role:** The user's specific professional function, aligned with an ISCO-08 code (e.g., `Physician`, `Paramedic`).
60
+ * **Shared Capability:** A function that multiple roles can perform, managed by a dedicated **Capability Service** (e.g., `MedicationService`).
61
+ * **Explicit Authorization Principle:** Significant actions **must** be preceded by a signed, verifiable authorization object (e.g., a `MedicationRequest` signed as a JWS). The API does not allow for optional or implicit authorizations for such actions.
62
+
63
+ ### Key Components & Design Patterns
64
+
65
+ 1. **Role Services (using Inheritance):**
66
+ * Represent the user's job in a logical inheritance chain that models skill sets. A more specialized role extends a more foundational one. This relationship is for code reuse and does **not** dictate the directory structure.
67
+ * **Example:** `PhysicianService` (in `health-care`) can **extend** `ParamedicService` (in `emergencies`) to inherit emergency response capabilities.
68
+
69
+ 2. **Capability Services (using Composition):**
70
+ * Specialized classes that manage a single, shared capability (e.g., `MedicationService` in `src/services/capabilities`). They are **not** part of the role inheritance chain.
71
+ * **Usage:** Role services (`PhysicianService`, `NurseService`) hold an **instance** of the relevant capability service and delegate tasks to it.
72
+ * **Explicit Authorization:** Methods in a capability service **require** a signed authorization object.
73
+ * `MedicationService.administer(patientDid: string, signedMedicationRequest: Jws)`
74
+
75
+ 3. **The Smart Hub (`ProfileManager` and `capabilityMapper`):**
76
+ * The `capabilityMapper` is the "brain". It receives the user's role and the entity's `DidDocument`.
77
+ * It **instantiates the final, most specific role service** (e.g., `new PhysicianService(...)`).
78
+ * It also **instantiates and injects** the required capability services (e.g., `new MedicationService(...)`) into the role service's constructor.
79
+ * `ProfileManager` then exposes the final, fully-formed service object (e.g., `session.professional.physician`).
80
+
81
+ ### Example Directory & Class Structure
82
+
83
+ This architecture translates into the following physical directory structure. Note how the directory structure is organized by **sector/domain**, while class inheritance can cross these boundaries.
84
+
85
+ ```
86
+ src/services/
87
+ ├── base/
88
+ │ ├── BaseProfessionalService.ts
89
+ │ ├── BaseOrgAdminService.ts
90
+ │ └── BaseFamilyAdminService.ts
91
+
92
+ ├── capabilities/
93
+ │ └── MedicationService.ts
94
+
95
+ ├── org-admin/
96
+ │ └── OrgAdminService.ts
97
+
98
+ ├── family-admin/
99
+ │ └── FamilyAdminService.ts
100
+
101
+ ├── professional/
102
+ │ ├── health-care/
103
+ │ │ ├── PhysicianService.ts // Extends ParamedicService
104
+ │ │ ├── NurseService.ts
105
+ │ │ └── PhysiotherapistService.ts
106
+ │ │
107
+ │ ├── emergencies/
108
+ │ │ └── ParamedicService.ts // Extends BaseProfessionalService
109
+ │ │
110
+ │ ├── health-insurance/
111
+ │ │ └── InsuranceAgentService.ts
112
+ │ │
113
+ │ └── care/
114
+ │ └── CaregiverService.ts
115
+
116
+ └── individual/
117
+ └── IndividualAccountService.ts
118
+ ```
119
+
120
+
121
+ ### 1. The User's Identity (via `id_token`)
122
+ - **What it is:** A proof of **who the human is**. It's a standard OIDC `id_token` obtained from an external identity provider (e.g., Google, Apple, eIDAS).
123
+ - **When it's used:** This token is used for initial user authentication and for "public" API calls that happen **before** a device profile is officially registered in the system (Pre-DCR).
124
+ - **Examples:** `registerOrganization`, `acceptLicenseOffer`.
125
+ - **How the SDK uses it:** The `id_token` is passed to "public" service methods (e.g., `executePublicJob`) to prove the legal representative's identity. It's also used during the acquisition of a `smartToken`.
126
+
127
+ ### 2. The Device's Identity (via `private_key_jwt` Client Assertion)
128
+ - **What it is:** A proof of **what the client application/device is**. The device's identity is its `client_id`, which is a `did:web` identifier created by the backend connector upon activation.
129
+ - **When it's used:** It is used **every time** the SDK needs to request a `smartToken` from our own Authorization Server (`/token` endpoint). This happens for all secure, data-access calls **after** the device is registered (Post-DCR).
130
+ - **How it works:** The SDK uses the device's private JWK (stored securely by the `IWallet`) to sign a JWT. This signed JWT is the "Client Assertion," and it proves to the token endpoint that the request is coming from a legitimate, registered client.
131
+
132
+ ### The Bridge: Acquiring a `smartToken`
133
+
134
+ For all Post-DCR operations, the SDK must acquire a short-lived `smartToken` (an OAuth 2.0 Access Token). To do this, our Authorization Server needs proof of both the **device** and the **user**.
135
+ - **Proof of Device:** The `private_key_jwt` client assertion.
136
+ - **Proof of User:** The user's current, active `id_token`.
137
+
138
+ The `SmartTokenManager` within the SDK handles this flow automatically. API service methods simply declare the scopes they need, and the manager orchestrates the acquisition and caching of the required `smartToken`.
139
+
140
+ ## Guiding Principles
141
+
142
+ 1. **The `API_INTEGRATORS_GUIDE.md` is the Source of Truth:** All API method implementations, service selectors, and payload structures **must** be based on the official documentation available at `https://raw.githubusercontent.com/Global-DataCare/docs/refs/heads/main/API_INTEGRATORS_GUIDE.md`. The SDK should not invent or assume logic.
143
+ 2. **Test Data is the Second Source of Truth:** The mock data files in `client-sdk-ts/__tests__/data/` are sourced from the backend's own test suite and documentation. They represent the ground truth for API responses and **must** be used for all Jest tests and for the in-app `DEMO_MODE`.
144
+ 3. **Specific Methods are Simple, The Engine is Smart:** A specific API method (e.g., `createOrganization`) is responsible for knowing the "what" (the endpoint selector, the payload). The `BaseApiService` engine (`resolveAndExecute`) is responsible for the "how" (DID resolution, message construction, job submission).
145
+
146
+ ## End-to-End Onboarding Flow
147
+
148
+ The SDK is designed to follow a specific, multi-step business process for onboarding a new organization.
149
+
150
+ 1. **`orgAdmin.admin.createOrganization()`**: A legal representative, authenticated with an external OIDC `id_token`, submits the organization's details to the **Host Provider's DID**. The async response will contain an `Offer`.
151
+ 2. **`orgAdmin.admin.confirmOrder()`**: The representative accepts the offer by submitting an order, again to the **Host Provider's DID**. The async response will contain a set of license codes.
152
+ 3. **Payment (External)**: If required, the user completes the payment flow.
153
+ 4. **`common.auth.activateDevice()`**: The representative (or another employee) uses a valid license code to register their device. This is a DCR (Dynamic Client Registration) call made to the **Gateway's (Tenant's) DID**. This call creates the `client_id` (`did:web`) for the device and marks it as active.
154
+ 5. **`smartToken` Acquisition**: From this point forward, all API calls are authorized (Post-DCR) and must acquire a `smartToken`. The SDK handles this automatically using the device's identity (via `private_key_jwt`) and the user's `id_token`.
155
+
156
+ ## Quick Start & Core Usage
157
+
158
+ This guide demonstrates how to implement the primary business flows using the SDK.
159
+
160
+ ### The Core Principle: The SDK is a Platform-Agnostic Engine
161
+
162
+ Think of the SDK as a powerful engine. It is designed to run in any environment but it cannot directly perform platform-specific tasks like storing a cryptographic key on a device. Instead, the SDK defines **interfaces** (or "ports") for these tasks, such as `IWallet` for secure storage or `ICryptoHelper` for cryptographic operations.
163
+
164
+ Your application is responsible for providing the concrete **implementations** (the "adapters" or "platform services") that connect to these ports.
165
+
166
+ For a detailed explanation of the overall multi-package architecture and how the different pieces (like `adapters-sdk-expo` and `platformServices`) fit together, please see the **[main project README](../../README.md)**.
167
+
168
+ ### Implementing the Platform Adapters (`IWallet` & `IVaultRepository`)
169
+
170
+ The two most important interfaces you must implement when bringing the SDK to a new platform are:
171
+
172
+ 1. **`IWallet`**: This interface defines the contract for secure cryptographic key storage. Your app must provide an object that implements this interface. For an Expo app, this is typically a class that uses `expo-secure-store` to interact with the device's Keychain or Keystore.
173
+
174
+ 2. **`IVaultRepository`**: This defines the contract for storing user data (like profiles and job requests). The SDK requires a **factory function** that can create a separate, isolated storage vault for each user profile.
175
+
176
+ **Conceptual Example (`platformServices.ts` for a new platform):**
177
+ ```typescript
178
+ // In a file like `platformServices.ts` in your new platform's source.
179
+
180
+ import { IWallet, IVaultRepository } from '@gdc/client-sdk'; // Assuming published package
181
+ import { MySecureStorage } from './my-platform-secure-storage';
182
+ import { MyDatabase } from './my-platform-database';
183
+
184
+ // 1. Your platform's implementation of IWallet
185
+ class MyPlatformWallet implements IWallet {
186
+ // ... implement all methods of IWallet using your platform's secure storage ...
187
+ async provisionKeys(entityId: string): Promise<JwkSet> {
188
+ const privateKey = await MySecureStorage.generateAndStoreKey(entityId);
189
+ return getPublicKey(privateKey);
190
+ }
191
+ // ... other methods like digest, protectConfidentialData, etc.
192
+ }
193
+
194
+ // 2. Create a single, app-wide instance of your wallet.
195
+ export const appWallet = new MyPlatformWallet();
196
+
197
+ // 3. Your platform's factory function for creating user vaults.
198
+ export function createVaultForProfile(profileId: string): IVaultRepository {
199
+ return new MyDatabase(profileId);
200
+ }
201
+ ```
202
+
203
+ ### Using the Pre-built Expo Implementation
204
+
205
+ **This project already provides the necessary platform-specific implementations for Expo.** You do not need to build them from scratch.
206
+
207
+ - **Low-Level Adapters:** The primitive adapters can be found in the `adapters-sdk-expo/` directory.
208
+ - **High-Level Services:** The concrete implementations are located in the `platformServices/` directory.
209
+ - **`platformServices/ExpoWallet.ts`** is the implementation of the `IWallet` interface.
210
+ - **`platformServices/index.ts`** is where the singleton `appWallet` is instantiated and the `createVaultForProfile` function is defined.
211
+
212
+ Therefore, to use the pre-built services for Expo, you simply import them:
213
+
214
+ ```typescript
215
+ // In your ProfileContext.tsx or equivalent setup file:
216
+ import { appWallet, createVaultForProfile } from '../platformServices';
217
+ import { ClientSDK } from '../client-sdk-ts';
218
+
219
+ // ... then, when you initialize the SDK, you pass these directly.
220
+ const sdk = new ClientSDK(sdkConfig, appInfo, appWallet, verifierService, icaDid);
221
+
222
+ // ... and when you create a session, you pass the factory function.
223
+ await sdk.initializeSession(params, createVaultForProfile);
224
+ ```
225
+
226
+ ### SDK Initialization (Platform-Specific)
227
+
228
+ The first step is to instantiate the `ClientSDK`. This requires providing the platform-specific "adapters" and implementations discussed above.
229
+
230
+ ```typescript
231
+ // In a central provider file, e.g., ProfileContext.tsx
232
+
233
+ import { ClientSDK, InitializeSessionParams, IscoCode } from '../client-sdk-ts';
234
+ import { appWallet, createVaultForProfile } from '../platformServices';
235
+ import { AdapterCryptoSdkExpo, AdapterNetworkSdkExpo, AdapterApiConfigSdkExpo } from '../adapters-sdk-expo';
236
+ import { VerifierService } from '../client-sdk-ts/src/VerifierService';
237
+ import { CryptographyService } from '../crypto-ts/CryptographyService';
238
+
239
+ // This initialization should happen once in your application's lifecycle.
240
+ const sdk = useMemo(() => {
241
+ // 1. Instantiate platform-specific adapters.
242
+ const cryptoAdapter = new AdapterCryptoSdkExpo();
243
+ const sdkConfig = {
244
+ crypto: cryptoAdapter,
245
+ network: new AdapterNetworkSdkExpo(),
246
+ api: new AdapterApiConfigSdkExpo(),
247
+ fetcher: fetch.bind(window),
248
+ // ... mockOptions for DEMO mode
249
+ };
250
+
251
+ // 2. Set up the Verifier Service with your trust anchors.
252
+ const cryptoService = new CryptographyService(cryptoAdapter);
253
+ const rootGoverningKeyPub = process.env.EXPO_PUBLIC_ROOT_GOVERNING_KEY_PUB; // From environment
254
+ const verifierService = new VerifierService(cryptoService, rootGoverningKeyPub, sdkConfig.fetcher);
255
+
256
+ // 3. Define your application's static info.
257
+ const appInfo = { /* ... device info, app type, etc. ... */ };
258
+
259
+ // 4. The trusted Intermediate CA DID.
260
+ const icaDid = process.env.EXPO_PUBLIC_ICA_DID; // From environment
261
+
262
+ // 5. Create the SDK instance, injecting your platform-specific wallet.
263
+ return new ClientSDK(sdkConfig, appInfo, appWallet, verifierService, icaDid);
264
+ }, []);
265
+ ```
266
+
267
+ ### Constructing the Provider DID
268
+
269
+ Before initializing a session, your application must determine the canonical DID of the provider (the organization or host) the user wants to connect to. The SDK supports two scenarios:
270
+
271
+ #### Case A: The organization has its own domain
272
+
273
+ If the user provides a dedicated domain for their organization (e.g., `identity.acme.com`), constructing the DID is straightforward.
274
+
275
+ **Example:**
276
+ ```typescript
277
+ const userInputDomain = 'identity.acme.com';
278
+ const providerDid = `did:web:${userInputDomain.toLowerCase()}`;
279
+ ```
280
+
281
+ #### Case B: The organization is hosted by a provider
282
+
283
+ If the user's organization is hosted on a shared platform, you must construct a more complex "hosted" DID. The SDK provides a utility function for this.
284
+
285
+ **Example:**
286
+ ```typescript
287
+ import { buildHostedDidDetails } from '../client-sdk-ts';
288
+
289
+ const hostDomain = 'provider.example.com';
290
+ const tenantName = 'acme-health'; // Provided by the user in the UI
291
+
292
+ const { did: providerDid } = buildHostedDidDetails({
293
+ host: hostDomain,
294
+ alternateName: tenantName,
295
+ jurisdiction: 'ES', // Or another value from the UI
296
+ });
297
+ // providerDid -> "did:web:provider.example.com:acme-health:cds-ES:v1:health-care"
298
+ ```
299
+
300
+ ### Core Flow: Onboarding a New Organization
301
+
302
+ This flow uses the `providerDid` constructed in the previous step. It involves a human legal representative authenticating themselves to bootstrap the organization's identity and its first device.
303
+
304
+ ```typescript
305
+ // Assume you have the `providerDid` from the previous step.
306
+ // Assume you have the legal rep's idToken from an OIDC login.
307
+ const legalRepIdToken = '...';
308
+
309
+ // --- Step 1: Initialize a session for the Legal Representative ---
310
+ const session = await sdk.initializeSession({
311
+ email: 'rep@acme.com',
312
+ role: `ISCO-08|${IscoCode.ManagingDirector}`,
313
+ providerDid: providerDid, // The DID is the entry point
314
+ }, createVaultForProfile); // Pass the vault factory function here
315
+
316
+ const orgAdminService = session.orgAdmin.admin;
317
+
318
+ // --- Step 2: Create the Organization ---
319
+ // This call is directed at the HOST's DID.
320
+ const hostDid = 'did:web:provider.example.com';
321
+ const orgClaims = { /* ... organization registration data ... */ };
322
+ const { thid: createOrgThid } = await orgAdminService.createOrganization(orgClaims, hostDid, legalRepIdToken);
323
+ // Poll for the result, which will contain an Offer...
324
+
325
+ // --- Step 3: Confirm the Order ---
326
+ // The user accepts the offer. This call is also to the HOST's DID.
327
+ const offerId = '...'; // From the result of the previous step
328
+ const { thid: confirmOrderThid } = await orgAdminService.confirmOrder(offerId, hostDid, legalRepIdToken);
329
+ // Poll for the result, which will contain a license code...
330
+
331
+ // --- Step 4: Activate the First Device ---
332
+ // This call is directed at the new TENANT's DID (the one we used in initializeSession).
333
+ const licenseCode = '...'; // From the result of the previous step
334
+ const commonAuthService = session.common.auth;
335
+ const { thid: activateThid } = await commonAuthService.activateDevice(licenseCode, providerDid);
336
+ // Poll for the result. The device is now registered.
337
+ ```
338
+
339
+ ### Note on Production vs. Test Onboarding
340
+ The self-service `createOrganization` flow detailed above is intended for the **`test-network`**. Onboarding a new organization in the **production** environment involves a manual verification process by the host provider to ensure the legitimacy of the legal entity.
341
+
342
+ ## Testing
343
+
344
+ Unit tests:
345
+ ```bash
346
+ npm test
347
+ ```
348
+
349
+ E2E (SDK against external gateway):
350
+ ```bash
351
+ scripts/run-e2e-external.sh
352
+ ```
353
+
354
+ Override the backend env file:
355
+ ```bash
356
+ scripts/run-e2e-external.sh --env-file /path/to/.env
357
+ ```
358
+
359
+
360
+ ### Core Flow: Day-to-Day Authenticated Operations
361
+
362
+ Once a device is registered, subsequent sessions are initialized using the same logic. The SDK handles token management automatically.
363
+
364
+ ```typescript
365
+ // Assume a registered employee logs in.
366
+ // The app first constructs the correct `providerDid` as shown above.
367
+
368
+ const providerDid = '...'; // Constructed using Case A or Case B.
369
+
370
+ // --- Step 1: Initialize Session ---
371
+ const session = await sdk.initializeSession({
372
+ email: 'physician@acme.com',
373
+ role: `ISCO-08|${IscoCode.GeneralistMedicalPractitioner}`,
374
+ providerDid: providerDid,
375
+ }, createVaultForProfile); // Pass the vault factory function
376
+
377
+ // --- Step 2: Perform an Authenticated Action ---
378
+ const physicianService = session.professional.physician;
379
+ if (physicianService) {
380
+ // The SDK will automatically handle acquiring a smartToken for this call.
381
+ const { thid } = await physicianService.createEmployee(...);
382
+ }
383
+ ```
384
+
385
+ ### Other Business Flows
386
+
387
+ Flows for managing families, adding members, managing consent, and sending communications follow a similar pattern:
388
+ 1. Initialize a session for the authenticated user.
389
+ 2. Access the appropriate service from the `session` object (e.g., `session.familyAdmin.admin`).
390
+ 3. Call the method for the desired action.
391
+
392
+ For the specific service methods and the exact structure of the data payloads required, always consult the **`API_INTEGRATORS_GUIDE.md`**.
@@ -0,0 +1,158 @@
1
+ import { BaseApiService, ServiceContext } from '../src/frontend-services/BaseApiService';
2
+ import { createMockServiceContext } from './testUtils';
3
+ import { generateServiceId } from 'gdc-common-utils-ts/utils/did';
4
+
5
+ class TestApiService extends BaseApiService {
6
+ public callResolveAndExecute(
7
+ targetDid: string,
8
+ selector: any,
9
+ body: object,
10
+ isPublic: boolean,
11
+ scope?: string,
12
+ idToken?: string,
13
+ ) {
14
+ return this.resolveAndExecute(targetDid, selector, body, isPublic, scope, idToken);
15
+ }
16
+
17
+ public callSendCommunication(
18
+ recipientDid: string,
19
+ providerDid: string,
20
+ idToken: string,
21
+ sector: string,
22
+ components: any,
23
+ ) {
24
+ return this.sendCommunication(recipientDid, providerDid, idToken, sector, components);
25
+ }
26
+
27
+ public callResolveDid(did: string) {
28
+ return this.resolveDid(did);
29
+ }
30
+ }
31
+
32
+ describe('BaseApiService', () => {
33
+ it('resolves DID documents from mocked config', async () => {
34
+ const context = createMockServiceContext({
35
+ sdkConfig: {
36
+ ...createMockServiceContext().sdkConfig,
37
+ mockOptions: {
38
+ resolvedDidDocs: {
39
+ 'did:web:api.example.com': { id: 'did:web:api.example.com', service: [] },
40
+ },
41
+ },
42
+ } as any,
43
+ });
44
+
45
+ const service = new TestApiService(context);
46
+ const didDoc = await service.callResolveDid('did:web:api.example.com');
47
+
48
+ expect(didDoc.id).toBe('did:web:api.example.com');
49
+ });
50
+
51
+ it('executes public jobs with resolved endpoint', async () => {
52
+ const selector = { section: 'test', format: 'org.schema', resourceType: 'Device', action: '_search' };
53
+ const serviceId = `did:web:api.example.com${generateServiceId(selector)}`;
54
+ const context = createMockServiceContext({
55
+ sdkConfig: {
56
+ ...createMockServiceContext().sdkConfig,
57
+ mockOptions: {
58
+ resolvedDidDocs: {
59
+ 'did:web:api.example.com': {
60
+ id: 'did:web:api.example.com',
61
+ service: [{ id: serviceId, serviceEndpoint: 'https://api.example.com/search' }],
62
+ },
63
+ },
64
+ },
65
+ } as any,
66
+ });
67
+
68
+ const service = new TestApiService(context);
69
+
70
+ await service.callResolveAndExecute(
71
+ 'did:web:api.example.com',
72
+ selector,
73
+ { resourceType: 'Device' },
74
+ true,
75
+ undefined,
76
+ 'id-token',
77
+ );
78
+
79
+ expect(context.jobManager.createOrUpdateDraftJob).toHaveBeenCalled();
80
+ expect(context.jobManager.sealJobWithToken).toHaveBeenCalled();
81
+ expect(context.jobManager.submitJob).toHaveBeenCalled();
82
+ });
83
+
84
+ it('executes authorized jobs using SMART tokens', async () => {
85
+ const selector = { section: 'clinical', format: 'org.hl7.fhir.r4', resourceType: 'Observation', action: '_batch' };
86
+ const serviceId = `did:web:provider${generateServiceId(selector)}`;
87
+ const context = createMockServiceContext({
88
+ sdkConfig: {
89
+ ...createMockServiceContext().sdkConfig,
90
+ mockOptions: {
91
+ resolvedDidDocs: {
92
+ 'did:web:provider': {
93
+ id: 'did:web:provider',
94
+ service: [{ id: serviceId, serviceEndpoint: 'https://provider.example.com/fhir' }],
95
+ },
96
+ },
97
+ },
98
+ } as any,
99
+ });
100
+
101
+ const service = new TestApiService(context);
102
+
103
+ await service.callResolveAndExecute(
104
+ 'did:web:provider',
105
+ selector,
106
+ { resourceType: 'Bundle' },
107
+ false,
108
+ 'scope-1',
109
+ 'id-token',
110
+ );
111
+
112
+ expect(context.smartTokenManager.getToken).toHaveBeenCalledWith(
113
+ 'did:web:provider',
114
+ ['scope-1'],
115
+ 'id-token',
116
+ context.profile.did,
117
+ );
118
+ expect(context.jobManager.submitJob).toHaveBeenCalled();
119
+ });
120
+
121
+ it('sends communication payloads with required components', async () => {
122
+ const context = createMockServiceContext();
123
+ const service = new TestApiService(context);
124
+ const resolveSpy = jest.spyOn(service as any, 'resolveAndExecute').mockResolvedValue({ thid: 'thid-1' });
125
+
126
+ await service.callSendCommunication(
127
+ 'did:web:recipient',
128
+ 'did:web:provider',
129
+ 'id-token',
130
+ 'health-care',
131
+ {
132
+ category: 'system|code',
133
+ note: 'Hello',
134
+ referenceUrl: 'https://example.com',
135
+ referenceType: 'Appointment',
136
+ attachment: { contentType: 'text/plain', data: 'ZGF0YQ==', title: 'file' },
137
+ },
138
+ );
139
+
140
+ expect(resolveSpy).toHaveBeenCalled();
141
+ resolveSpy.mockRestore();
142
+ });
143
+
144
+ it('rejects communication requests without content', async () => {
145
+ const context = createMockServiceContext();
146
+ const service = new TestApiService(context);
147
+
148
+ await expect(service.callSendCommunication(
149
+ 'did:web:recipient',
150
+ 'did:web:provider',
151
+ 'id-token',
152
+ 'health-care',
153
+ { category: 'system|code' },
154
+ ))
155
+ .rejects
156
+ .toThrow('Communication must have at least one component');
157
+ });
158
+ });
@@ -0,0 +1,26 @@
1
+ import { BaseProfessionalService } from '../src/frontend-services/base/BaseProfessionalService';
2
+ import { createMockServiceContext } from './testUtils';
3
+
4
+ class TestProfessionalService extends BaseProfessionalService {
5
+ public callDiscoverPerson() {
6
+ return this.discoverPerson([
7
+ { '@context': 'org.schema', 'schema:identifier': '123' } as any,
8
+ ], 'did:web:provider', 'id-token', 'health-care');
9
+ }
10
+ }
11
+
12
+ describe('BaseProfessionalService', () => {
13
+ it('builds discovery payloads and delegates to resolveAndExecute', async () => {
14
+ const context = createMockServiceContext();
15
+ const service = new TestProfessionalService(context);
16
+ const resolveSpy = jest.spyOn(service as any, 'resolveAndExecute').mockResolvedValue({ thid: 'thid-1' });
17
+
18
+ await service.callDiscoverPerson();
19
+
20
+ expect(resolveSpy).toHaveBeenCalled();
21
+ const [, , payload, , scope] = resolveSpy.mock.calls[0];
22
+ expect(payload).toMatchObject({ data: expect.any(Array) });
23
+ expect(scope).toBe('professional.health-care.person.discovery');
24
+ resolveSpy.mockRestore();
25
+ });
26
+ });