gdc-sdk-client-ts 1.0.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -141,14 +141,14 @@ The `SmartTokenManager` within the SDK handles this flow automatically. API serv
141
141
 
142
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
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).
144
+ 3. **Specific Methods are Simple, The Engine is Smart:** A specific API method (e.g., `startOrganizationRegistration`) 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
145
 
146
146
  ## End-to-End Onboarding Flow
147
147
 
148
148
  The SDK is designed to follow a specific, multi-step business process for onboarding a new organization.
149
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.
150
+ 1. **`orgAdmin.admin.startOrganizationRegistration()`**: A legal representative, authenticated with an external OIDC `id_token`, submits the organization's details. The SDK automatically resolves and targets the operator host DID for this onboarding call. The async response will contain an `Offer`.
151
+ 2. **`orgAdmin.admin.confirmOrganizationRegistration()`**: The representative accepts the offer by submitting an order. The SDK again resolves the operator host DID automatically. The async response will contain a set of license codes.
152
152
  3. **Payment (External)**: If required, the user completes the payment flow.
153
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
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`.
@@ -264,6 +264,31 @@ const sdk = useMemo(() => {
264
264
  }, []);
265
265
  ```
266
266
 
267
+ ### API Config Discovery For Frontend Forms
268
+
269
+ When a provider API publishes `/.well-known/api-config.json`, the SDK can fetch it and return the form fields in a UI-friendly shape.
270
+
271
+ ```typescript
272
+ const apiConfig = await sdk.fetchWellKnownApiConfig('http://34.8.62.21');
273
+ // or: await sdk.fetchWellKnownApiConfig('did:web:api.example.com');
274
+
275
+ console.log(apiConfig.language); // "es"
276
+ console.log(apiConfig.fields);
277
+ // [
278
+ // { code: 'section', display: 'Departamento o sección: ...' },
279
+ // { code: 'coverage_insurer', display: 'Identificador o nombre de la aseguradora' },
280
+ // ]
281
+
282
+ const supportedFields = await sdk.fetchSupportedFields('http://34.8.62.21');
283
+ ```
284
+
285
+ The resolved object includes:
286
+
287
+ - `language`
288
+ - `supportedFields` as the raw `code -> display` map
289
+ - `fields` as `{ code, display }[]` ready for selects/tables
290
+ - `endpoints` with backend route templates such as `create`, `createResponse`, `upload`, and `uploadResponse`
291
+
267
292
  ### Constructing the Provider DID
268
293
 
269
294
  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:
@@ -316,16 +341,15 @@ const session = await sdk.initializeSession({
316
341
  const orgAdminService = session.orgAdmin.admin;
317
342
 
318
343
  // --- Step 2: Create the Organization ---
319
- // This call is directed at the HOST's DID.
320
- const hostDid = 'did:web:provider.example.com';
344
+ // The SDK derives the operator host DID from `providerDid`.
321
345
  const orgClaims = { /* ... organization registration data ... */ };
322
- const { thid: createOrgThid } = await orgAdminService.createOrganization(orgClaims, hostDid, legalRepIdToken);
346
+ const { thid: createOrgThid } = await orgAdminService.startOrganizationRegistration(orgClaims, legalRepIdToken);
323
347
  // Poll for the result, which will contain an Offer...
324
348
 
325
349
  // --- Step 3: Confirm the Order ---
326
- // The user accepts the offer. This call is also to the HOST's DID.
350
+ // The user accepts the offer. The SDK keeps targeting the operator host DID internally.
327
351
  const offerId = '...'; // From the result of the previous step
328
- const { thid: confirmOrderThid } = await orgAdminService.confirmOrder(offerId, hostDid, legalRepIdToken);
352
+ const { thid: confirmOrderThid } = await orgAdminService.confirmOrganizationRegistration(offerId, legalRepIdToken);
329
353
  // Poll for the result, which will contain a license code...
330
354
 
331
355
  // --- Step 4: Activate the First Device ---
@@ -337,7 +361,7 @@ const { thid: activateThid } = await commonAuthService.activateDevice(licenseCod
337
361
  ```
338
362
 
339
363
  ### 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.
364
+ The self-service `startOrganizationRegistration` 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
365
 
342
366
  ## Testing
343
367
 
@@ -1,7 +1,7 @@
1
1
  import { IWallet } from 'gdc-common-utils-ts/interfaces/IWallet';
2
2
  import { IVaultRepository } from './interfaces/IVaultRepository';
3
3
  import { ProfileManager } from './ProfileManager';
4
- import { SdkConfig } from './interfaces/others';
4
+ import { SdkConfig, WellKnownApiConfigResolved, WellKnownApiSupportedField } from './interfaces/others';
5
5
  import { DidDocument } from 'gdc-common-utils-ts/models/did';
6
6
  import { IVerifier } from './interfaces/ITrustRegistry';
7
7
  import { AppInfo } from './interfaces/others';
@@ -15,6 +15,7 @@ export interface InitializeSessionParams {
15
15
  role: string;
16
16
  providerDid: string;
17
17
  appType?: AppInfo['appType'];
18
+ familyId?: string;
18
19
  }
19
20
  /**
20
21
  * @class ClientSDK
@@ -30,6 +31,10 @@ export declare class ClientSDK {
30
31
  currentSession: ProfileManager | null;
31
32
  profileRegistry: ProfileRegistry | null;
32
33
  get operationMode(): string;
34
+ private resolveApiConfigBaseUrl;
35
+ private normalizeWellKnownApiConfig;
36
+ fetchWellKnownApiConfig(source: string): Promise<WellKnownApiConfigResolved>;
37
+ fetchSupportedFields(source: string): Promise<WellKnownApiSupportedField[]>;
33
38
  /**
34
39
  * Dynamically adds a mock DID Document to the SDK's configuration for DEMO mode.
35
40
  * This allows the UI to inject mock data at runtime to bypass network failures.
@@ -49,6 +54,7 @@ export declare class ClientSDK {
49
54
  */
50
55
  initializeProfileRegistry(createVault: (registryId: string) => IVaultRepository, registryId?: string): Promise<ProfileRegistry>;
51
56
  shutdownSession(): void;
57
+ private normalizeRoleCodeForDid;
52
58
  private constructEmployeeDid;
53
59
  private constructIndividualDid;
54
60
  }
package/dist/ClientSDK.js CHANGED
@@ -11,6 +11,55 @@ export class ClientSDK {
11
11
  get operationMode() {
12
12
  return this.sdkConfig.api.operationMode;
13
13
  }
14
+ resolveApiConfigBaseUrl(source) {
15
+ const normalized = String(source || '').trim();
16
+ if (!normalized) {
17
+ throw new Error('fetchWellKnownApiConfig requires a provider did:web or a base URL.');
18
+ }
19
+ if (normalized.startsWith('did:web:')) {
20
+ return getBaseUrlFromDidWeb(normalizeDidWeb(normalized));
21
+ }
22
+ return normalized.endsWith('/') ? normalized : `${normalized}/`;
23
+ }
24
+ normalizeWellKnownApiConfig(payload) {
25
+ const raw = (payload && typeof payload === 'object') ? payload : {};
26
+ const supportedFieldsSource = raw.supportedFields && typeof raw.supportedFields === 'object'
27
+ ? raw.supportedFields
28
+ : {};
29
+ const endpointsSource = raw.endpoints && typeof raw.endpoints === 'object'
30
+ ? raw.endpoints
31
+ : {};
32
+ const supportedFields = Object.fromEntries(Object.entries(supportedFieldsSource)
33
+ .filter(([key, value]) => String(key || '').trim() && String(value || '').trim())
34
+ .map(([key, value]) => [String(key).trim(), String(value).trim()]));
35
+ const endpoints = Object.fromEntries(Object.entries(endpointsSource)
36
+ .filter(([key, value]) => String(key || '').trim() && String(value || '').trim())
37
+ .map(([key, value]) => [String(key).trim(), String(value).trim()]));
38
+ const fields = Object.entries(supportedFields).map(([code, display]) => ({
39
+ code,
40
+ display,
41
+ }));
42
+ return {
43
+ language: String(raw.language || '').trim(),
44
+ supportedFields,
45
+ endpoints,
46
+ fields,
47
+ };
48
+ }
49
+ async fetchWellKnownApiConfig(source) {
50
+ const baseUrl = this.resolveApiConfigBaseUrl(source);
51
+ const url = new URL('.well-known/api-config.json', baseUrl).href;
52
+ const response = await this._fetch(url);
53
+ if (!response.ok) {
54
+ throw new Error(`Failed to fetch API config from ${url}`);
55
+ }
56
+ const payload = await response.json();
57
+ return this.normalizeWellKnownApiConfig(payload);
58
+ }
59
+ async fetchSupportedFields(source) {
60
+ const config = await this.fetchWellKnownApiConfig(source);
61
+ return config.fields;
62
+ }
14
63
  /**
15
64
  * Dynamically adds a mock DID Document to the SDK's configuration for DEMO mode.
16
65
  * This allows the UI to inject mock data at runtime to bypass network failures.
@@ -130,7 +179,7 @@ export class ClientSDK {
130
179
  const finalProfileId = params.profileId || this.sdkConfig.crypto.randomUUID();
131
180
  const userDid = appType === 'Organization'
132
181
  ? this.constructEmployeeDid(providerDid, email, role)
133
- : this.constructIndividualDid(providerDid, finalProfileId);
182
+ : await this.constructIndividualDid(providerDid, finalProfileId, email, role, params.familyId);
134
183
  const publicKeys = await this.wallet.provisionKeys(finalProfileId);
135
184
  const newProfile = {
136
185
  id: finalProfileId,
@@ -174,25 +223,29 @@ export class ClientSDK {
174
223
  this.currentSession = null;
175
224
  }
176
225
  }
177
- constructEmployeeDid(connectorDid, email, role) {
178
- // The user's role is expected in the FHIR-like format "system|code", e.g., "ISCO-08|1120"
179
- const roleParts = role.split('|');
180
- if (roleParts.length !== 2) {
181
- console.warn(`[ClientSDK] Invalid role format for Employee DID construction: ${role}. Expected "system|code".`);
182
- return `${connectorDid}:employee:invalid-role-format`;
226
+ normalizeRoleCodeForDid(role, fallback = 'unknown-role') {
227
+ const raw = (role || '').trim();
228
+ if (!raw)
229
+ return fallback;
230
+ const parts = raw.split('|');
231
+ if (parts.length === 2) {
232
+ return (parts[1] || '').trim() || fallback;
183
233
  }
184
- // Construct the DID according to the official spec in docs/did_generation.md
185
- const rawDid = `${connectorDid}:employee:${email}:${role}`;
234
+ return raw;
235
+ }
236
+ constructEmployeeDid(connectorDid, email, role) {
237
+ const roleCode = this.normalizeRoleCodeForDid(role, 'invalid-role');
238
+ const rawDid = `${connectorDid}:employee:${email}:${roleCode}`;
186
239
  // Normalize it to ensure canonical representation
187
240
  return normalizeDidWeb(rawDid);
188
241
  }
189
- // TODO: Per conversation, evolve this to align with the new path structure,
190
- // potentially incorporating a familyId. e.g., did:web:<provider>:family:<id>:individual:<uuid_base58>
191
- constructIndividualDid(connectorDid, profileId) {
192
- // For now, using a simplified version of the multibase approach.
193
- // A real implementation would use a proper base58btc encoder.
194
- const multibaseId = `z${profileId.replace(/-/g, '')}`;
195
- const rawDid = `${connectorDid}:individual:multibase:${multibaseId}`;
242
+ async constructIndividualDid(connectorDid, profileId, email, role, familyId) {
243
+ const normalizedEmail = (email || '').trim().toLowerCase();
244
+ const emailHash = await this.sdkConfig.crypto.digestString(normalizedEmail, 'SHA-256');
245
+ const multibaseEmailHash = `z${emailHash}`;
246
+ const normalizedFamilyId = (familyId || `profile-${profileId}`).trim();
247
+ const normalizedRole = this.normalizeRoleCodeForDid(role, 'ONESELF').toUpperCase();
248
+ const rawDid = `${connectorDid}:family:${normalizedFamilyId}:${multibaseEmailHash}:${normalizedRole}`;
196
249
  return normalizeDidWeb(rawDid);
197
250
  }
198
251
  }
@@ -34,6 +34,7 @@ export declare abstract class BaseApiService {
34
34
  protected readonly appInfo: AppInfo;
35
35
  protected readonly cryptoHelper: ICryptoHelper;
36
36
  constructor(context: ServiceContext);
37
+ private withRetryPolicy;
37
38
  /**
38
39
  * The generic "engine" for all API calls. It takes the endpoint ingredients,
39
40
  * builds the selector, finds the service, constructs the message, and executes the job.
@@ -25,6 +25,26 @@ export class BaseApiService {
25
25
  this.appInfo = context.appInfo; // Initialize AppInfo
26
26
  this.cryptoHelper = context.cryptoHelper;
27
27
  }
28
+ async withRetryPolicy(operationKey, fn) {
29
+ var _a, _b, _c, _d;
30
+ const policy = (_b = (_a = this.sdkConfig.api).getRetryPolicy) === null || _b === void 0 ? void 0 : _b.call(_a, operationKey);
31
+ const retries = Math.max(0, (_c = policy === null || policy === void 0 ? void 0 : policy.retries) !== null && _c !== void 0 ? _c : 0);
32
+ const delayMs = Math.max(0, (_d = policy === null || policy === void 0 ? void 0 : policy.delayMs) !== null && _d !== void 0 ? _d : 0);
33
+ const attempts = retries + 1;
34
+ let lastError;
35
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
36
+ try {
37
+ return await fn();
38
+ }
39
+ catch (error) {
40
+ lastError = error;
41
+ if (attempt < attempts && delayMs > 0) {
42
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
43
+ }
44
+ }
45
+ }
46
+ throw lastError instanceof Error ? lastError : new Error(`Operation failed: ${operationKey}`);
47
+ }
28
48
  /**
29
49
  * The generic "engine" for all API calls. It takes the endpoint ingredients,
30
50
  * builds the selector, finds the service, constructs the message, and executes the job.
@@ -69,16 +89,17 @@ export class BaseApiService {
69
89
  jti: this.jobManager.generateId(),
70
90
  thid: thid,
71
91
  };
92
+ const operationKey = `${serviceSelector.section || 'default'}/${serviceSelector.format}/${serviceSelector.resourceType}/${serviceSelector.action}`;
72
93
  // 6. Call the appropriate execution method.
73
94
  if (isPublic) {
74
95
  if (!idToken)
75
96
  throw new Error("idToken is required for public jobs.");
76
- await this.executePublicJob(endpointUrl, idToken, didcommMessage, serviceSelector);
97
+ await this.withRetryPolicy(operationKey, () => this.executePublicJob(endpointUrl, idToken, didcommMessage, serviceSelector));
77
98
  }
78
99
  else {
79
100
  if (!requiredScope || !idToken)
80
101
  throw new Error("requiredScope and idToken are required for authorized jobs.");
81
- await this.executeAuthorizedJob(targetDid, endpointUrl, requiredScope, idToken, didcommMessage, serviceSelector);
102
+ await this.withRetryPolicy(operationKey, () => this.executeAuthorizedJob(targetDid, endpointUrl, requiredScope, idToken, didcommMessage, serviceSelector));
82
103
  }
83
104
  return { thid };
84
105
  }
@@ -3,15 +3,20 @@ import { BaseApiService } from "../BaseApiService";
3
3
  * Service class for handling organization-related administrative actions.
4
4
  */
5
5
  export declare class OrgAdminService extends BaseApiService {
6
+ /**
7
+ * Resolves the operator host DID from the current session provider DID.
8
+ * For hosted organizations (`did:web:host:tenant:...`) this returns `did:web:host`.
9
+ * For self-hosted organizations (`did:web:org.example.com`) this returns the same DID.
10
+ */
11
+ private resolveOperatorDidFromSession;
6
12
  /**
7
13
  * Registers a new organization with the gateway using an external OIDC token.
8
14
  *
9
15
  * @param dataClaims The organization's registration form data.
10
- * @param gatewayDid The DID of the target gateway/host.
11
16
  * @param idToken The OIDC token of the legal representative.
12
17
  * @returns The thread ID (thid) of the submitted job for polling.
13
18
  */
14
- createOrganization(dataClaims: object, gatewayDid: string, idToken: string, targetNetwork: string): Promise<{
19
+ startOrganizationRegistration(dataClaims: object, idToken: string, targetNetwork?: string): Promise<{
15
20
  thid: string;
16
21
  }>;
17
22
  /**
@@ -19,11 +24,10 @@ export declare class OrgAdminService extends BaseApiService {
19
24
  * This is the second step in the organization onboarding flow.
20
25
  *
21
26
  * @param acceptedOfferId The URN identifier of the offer being accepted.
22
- * @param gatewayDid The DID of the target gateway/host.
23
27
  * @param idToken The OIDC token of the legal representative.
24
28
  * @returns The thread ID (thid) of the submitted job for polling.
25
29
  */
26
- confirmOrder(acceptedOfferId: string, gatewayDid: string, idToken: string, targetNetwork: string): Promise<{
30
+ confirmOrganizationRegistration(acceptedOfferId: string, idToken: string, targetNetwork?: string): Promise<{
27
31
  thid: string;
28
32
  }>;
29
33
  /**
@@ -1,21 +1,37 @@
1
1
  // Copyright 2025 Antifraud Services Inc. under the Apache License, Version 2.0.
2
2
  // File: client-sdk-ts/src/services/org-admin/OrgAdminService.ts
3
3
  import { BaseApiService } from "../BaseApiService.js";
4
- import { EndpointKey, ENDPOINT_REGISTRY } from "../../serviceSelectorRegistry.js";
4
+ import { EndpointKey, ENDPOINT_REGISTRY, defaultNetworkIdentifier } from "../../serviceSelectorRegistry.js";
5
5
  /**
6
6
  * Service class for handling organization-related administrative actions.
7
7
  */
8
8
  export class OrgAdminService extends BaseApiService {
9
+ /**
10
+ * Resolves the operator host DID from the current session provider DID.
11
+ * For hosted organizations (`did:web:host:tenant:...`) this returns `did:web:host`.
12
+ * For self-hosted organizations (`did:web:org.example.com`) this returns the same DID.
13
+ */
14
+ resolveOperatorDidFromSession() {
15
+ const providerDid = this.profile.providerDid;
16
+ if (!providerDid || !providerDid.startsWith('did:web:')) {
17
+ throw new Error(`[OrgAdminService] Invalid provider DID in session: '${providerDid}'.`);
18
+ }
19
+ const parts = providerDid.split(':');
20
+ if (parts.length < 3) {
21
+ throw new Error(`[OrgAdminService] Invalid provider DID format: '${providerDid}'.`);
22
+ }
23
+ return `did:web:${parts[2]}`;
24
+ }
9
25
  /**
10
26
  * Registers a new organization with the gateway using an external OIDC token.
11
27
  *
12
28
  * @param dataClaims The organization's registration form data.
13
- * @param gatewayDid The DID of the target gateway/host.
14
29
  * @param idToken The OIDC token of the legal representative.
15
30
  * @returns The thread ID (thid) of the submitted job for polling.
16
31
  */
17
- async createOrganization(dataClaims, gatewayDid, idToken, targetNetwork) {
18
- console.log(`[OrgAdminService] Calling createOrganization on network: ${targetNetwork}.`);
32
+ async startOrganizationRegistration(dataClaims, idToken, targetNetwork = defaultNetworkIdentifier) {
33
+ console.log(`[OrgAdminService] Calling startOrganizationRegistration on network: ${targetNetwork}.`);
34
+ const operatorDid = this.resolveOperatorDidFromSession();
19
35
  const serviceSelector = Object.assign({}, ENDPOINT_REGISTRY[EndpointKey.REGISTER_TENANT]);
20
36
  serviceSelector.sector = targetNetwork;
21
37
  const payloadBody = {
@@ -24,7 +40,7 @@ export class OrgAdminService extends BaseApiService {
24
40
  meta: { claims: dataClaims },
25
41
  }],
26
42
  };
27
- return this.resolveAndExecute(gatewayDid, serviceSelector, payloadBody, true, // isPublic
43
+ return this.resolveAndExecute(operatorDid, serviceSelector, payloadBody, true, // isPublic
28
44
  undefined, idToken);
29
45
  }
30
46
  /**
@@ -32,12 +48,12 @@ export class OrgAdminService extends BaseApiService {
32
48
  * This is the second step in the organization onboarding flow.
33
49
  *
34
50
  * @param acceptedOfferId The URN identifier of the offer being accepted.
35
- * @param gatewayDid The DID of the target gateway/host.
36
51
  * @param idToken The OIDC token of the legal representative.
37
52
  * @returns The thread ID (thid) of the submitted job for polling.
38
53
  */
39
- async confirmOrder(acceptedOfferId, gatewayDid, idToken, targetNetwork) {
40
- console.log(`[OrgAdminService] Calling confirmOrder for offer: ${acceptedOfferId} on network: ${targetNetwork}.`);
54
+ async confirmOrganizationRegistration(acceptedOfferId, idToken, targetNetwork = defaultNetworkIdentifier) {
55
+ console.log(`[OrgAdminService] Calling confirmOrganizationRegistration for offer: ${acceptedOfferId} on network: ${targetNetwork}.`);
56
+ const operatorDid = this.resolveOperatorDidFromSession();
41
57
  const serviceSelector = Object.assign({}, ENDPOINT_REGISTRY[EndpointKey.CONFIRM_ORDER]);
42
58
  serviceSelector.sector = targetNetwork;
43
59
  const payloadBody = {
@@ -51,7 +67,7 @@ export class OrgAdminService extends BaseApiService {
51
67
  }
52
68
  }],
53
69
  };
54
- return this.resolveAndExecute(gatewayDid, serviceSelector, payloadBody, true, // isPublic
70
+ return this.resolveAndExecute(operatorDid, serviceSelector, payloadBody, true, // isPublic
55
71
  undefined, idToken);
56
72
  }
57
73
  /**
@@ -39,6 +39,22 @@ export interface INetwork {
39
39
  export interface IApiConfig {
40
40
  operationMode: 'DEMO' | 'FAPI';
41
41
  legacyFhirEnabled: boolean;
42
+ getRetryPolicy?: (operationKey: string) => {
43
+ retries: number;
44
+ delayMs: number;
45
+ } | undefined;
46
+ }
47
+ export interface WellKnownApiSupportedField {
48
+ code: string;
49
+ display: string;
50
+ }
51
+ export interface WellKnownApiConfigDocument {
52
+ language: string;
53
+ supportedFields: Record<string, string>;
54
+ endpoints: Record<string, string>;
55
+ }
56
+ export interface WellKnownApiConfigResolved extends WellKnownApiConfigDocument {
57
+ fields: WellKnownApiSupportedField[];
42
58
  }
43
59
  /**
44
60
  * Defines the set of optional parameters for enabling the SDK's mock mode.
@@ -0,0 +1,10 @@
1
+ export type LicenseClass = 'member' | 'employee';
2
+ export type OfferEntitlement = {
3
+ offerId?: string;
4
+ checkoutUrl?: string;
5
+ quantity?: number;
6
+ unitPrice?: string;
7
+ totalPrice?: string;
8
+ taxes?: string;
9
+ };
10
+ export declare const parseOfferEntitlementFromClaims: (claims: Record<string, any> | undefined) => OfferEntitlement;
@@ -0,0 +1,21 @@
1
+ const findClaimBySuffix = (claims, suffix) => {
2
+ if (!claims)
3
+ return undefined;
4
+ for (const [key, value] of Object.entries(claims)) {
5
+ if (typeof value === 'string' && key.endsWith(suffix))
6
+ return value;
7
+ }
8
+ return undefined;
9
+ };
10
+ export const parseOfferEntitlementFromClaims = (claims) => {
11
+ const quantityRaw = findClaimBySuffix(claims, 'Offer.eligibleQuantity.value');
12
+ const quantity = typeof quantityRaw === 'string' && quantityRaw.trim().length > 0 ? Number(quantityRaw) : undefined;
13
+ return {
14
+ offerId: findClaimBySuffix(claims, 'Offer.identifier'),
15
+ checkoutUrl: findClaimBySuffix(claims, 'Offer.checkoutPageURLTemplate'),
16
+ quantity: Number.isFinite(quantity) ? quantity : undefined,
17
+ unitPrice: findClaimBySuffix(claims, 'Offer.unitPrice.value'),
18
+ totalPrice: findClaimBySuffix(claims, 'Offer.price.value'),
19
+ taxes: findClaimBySuffix(claims, 'Offer.tax.value'),
20
+ };
21
+ };
@@ -1,3 +1,4 @@
1
1
  export * from './mockData';
2
2
  export * from './scopeBuilder';
3
3
  export * from './rule';
4
+ export * from './entitlements';
@@ -4,3 +4,4 @@
4
4
  export * from './mockData.js';
5
5
  export * from './scopeBuilder.js';
6
6
  export * from './rule.js';
7
+ export * from './entitlements.js';
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "gdc-sdk-client-ts",
3
- "version": "1.0.4",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "dependencies": {
7
7
  "@noble/hashes": "^1.8.0",
8
- "gdc-common-utils-ts": "^1.0.7"
8
+ "gdc-common-utils-ts": "^1.1.0"
9
9
  },
10
10
  "scripts": {
11
11
  "test": "jest",