gdc-sdk-node-ts 2.4.45 → 2.5.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.
package/README.md CHANGED
@@ -702,7 +702,8 @@ modules below.
702
702
  - [`NodeHttpClient.submitCommunicationAndPoll(...)`](src/node-runtime-client.ts)
703
703
  - [`NodeHttpClient.searchClinicalBundle(...)`](src/node-runtime-client.ts)
704
704
  - [`NodeHttpClient.updateClinicalSection(...)`](src/node-runtime-client.ts) for
705
- one explicit section carried by a batch/collection Communication
705
+ one explicit section carried by a batch Communication; its typed entries may
706
+ mix `.create()`, `.update()` and exact authored `.delete()` operations
706
707
  - [`NodeHttpClient.updateClinicalSummary(...)`](src/node-runtime-client.ts) for
707
708
  a Composition-first multi-section document
708
709
  - [`NodeHttpClient.searchLatestIps(...)`](src/node-runtime-client.ts)
@@ -7,6 +7,9 @@ const CLAIM_ITEM_CONDITION = 'org.schema.IndividualProduct.itemCondition';
7
7
  const CLAIM_PERSON_EMAIL = 'org.schema.Person.email';
8
8
  const CLAIM_PERSON_TELEPHONE = 'org.schema.Person.telephone';
9
9
  const CLAIM_ORGANIZATION_SAME_AS = 'org.schema.Organization.sameAs';
10
+ const CLAIM_ORGANIZATION_OWNER_EMAIL = 'org.schema.Organization.owner.email';
11
+ const CLAIM_ORGANIZATION_OWNER_TELEPHONE = 'org.schema.Organization.owner.telephone';
12
+ const CLAIM_ORGANIZATION_MEMBER_ROLE = 'org.schema.Organization.member.role';
10
13
  const CLAIM_RELATED_PERSON_ROLE = 'RelatedPerson.role';
11
14
  const CLAIM_OCCUPATION_IDENTIFIER = 'org.schema.Person.hasOccupation.identifier.value';
12
15
  /**
@@ -23,6 +26,24 @@ export async function listAuthorizedIndividualSubjectsWithDeps(routeContext, inp
23
26
  if (!email && !telephone) {
24
27
  throw new Error('A verified email or telephone is required to list authorized subjects.');
25
28
  }
29
+ const ownerResult = await deps.submitAndPoll(deps.individualOrganizationSearchPath(routeContext), deps.individualOrganizationSearchPollPath(routeContext), {
30
+ thid: input.requestThid || createThreadId('authorized-subject-owner'),
31
+ body: {
32
+ resourceType: 'Bundle',
33
+ type: 'batch',
34
+ data: [{
35
+ type: 'Family-search-v1.0',
36
+ request: { method: 'POST' },
37
+ meta: { claims: {
38
+ '@context': SCHEMA_CONTEXT,
39
+ ...(email ? { [CLAIM_ORGANIZATION_OWNER_EMAIL]: email } : {}),
40
+ ...(telephone ? { [CLAIM_ORGANIZATION_OWNER_TELEPHONE]: telephone } : {}),
41
+ } },
42
+ }],
43
+ },
44
+ }, input.pollOptions);
45
+ const ownedSubjects = readOwnedSubjects(ownerResult, { email, telephone });
46
+ const subjectsByDid = new Map(ownedSubjects.map((subject) => [subject.subjectDid, subject]));
26
47
  const licenseClaims = {
27
48
  '@context': SCHEMA_CONTEXT,
28
49
  [CLAIM_AUDIENCE_TYPE]: INDIVIDUAL_AUDIENCE_TYPE,
@@ -42,7 +63,7 @@ export async function listAuthorizedIndividualSubjectsWithDeps(routeContext, inp
42
63
  },
43
64
  }, input.pollOptions);
44
65
  const licenses = readAcceptedLicenses(licenseResult);
45
- const resolved = await Promise.all(licenses.map(async (license) => {
66
+ const resolved = await Promise.all(licenses.filter((license) => !subjectsByDid.has(license.subjectDid)).map(async (license) => {
46
67
  const organizationClaims = {
47
68
  '@context': SCHEMA_CONTEXT,
48
69
  [CLAIM_ORGANIZATION_SAME_AS]: license.subjectDid,
@@ -74,7 +95,38 @@ export async function listAuthorizedIndividualSubjectsWithDeps(routeContext, inp
74
95
  subjectClaims,
75
96
  };
76
97
  }));
77
- return resolved.filter((value) => Boolean(value));
98
+ for (const subject of resolved.filter((value) => Boolean(value))) {
99
+ subjectsByDid.set(subject.subjectDid, subject);
100
+ }
101
+ return [...subjectsByDid.values()];
102
+ }
103
+ function readOwnedSubjects(result, verified) {
104
+ const resources = readData(result).flatMap((entry) => {
105
+ if (entry?.resource?.resourceType === 'Bundle' && Array.isArray(entry.resource.entry)) {
106
+ return entry.resource.entry.map((item) => item?.resource).filter(Boolean);
107
+ }
108
+ return [];
109
+ });
110
+ return resources.flatMap((resource) => {
111
+ const claims = resource?.meta?.claims;
112
+ if (!claims || typeof claims !== 'object')
113
+ return [];
114
+ const ownerEmail = String(claims[CLAIM_ORGANIZATION_OWNER_EMAIL] || '').trim().toLowerCase();
115
+ const ownerTelephone = String(claims[CLAIM_ORGANIZATION_OWNER_TELEPHONE] || '').trim();
116
+ if (!((verified.email && ownerEmail === verified.email) || (verified.telephone && ownerTelephone === verified.telephone))) {
117
+ return [];
118
+ }
119
+ const subjectDid = String(claims[CLAIM_ORGANIZATION_SAME_AS] || '').trim();
120
+ if (!subjectDid)
121
+ return [];
122
+ const role = String(claims[CLAIM_ORGANIZATION_MEMBER_ROLE] || '').trim();
123
+ return [{
124
+ subjectDid,
125
+ ...(role ? { role } : {}),
126
+ grantClaims: {},
127
+ subjectClaims: claims,
128
+ }];
129
+ });
78
130
  }
79
131
  function readAcceptedLicenses(result) {
80
132
  const rows = readData(result)[0]?.resource?.data;
@@ -180,6 +180,16 @@ export declare class NodeManagedWallet implements IWallet {
180
180
  * Packs one payload into a transport envelope signed and encrypted by the runtime.
181
181
  */
182
182
  packForRecipientWithContext(content: any, recipientDidOrJwk: string | JWK, options: WalletPackOptions): Promise<string>;
183
+ /**
184
+ * Packs the first activation/DCR requests before GW can resolve this
185
+ * installation's communication keys from a registered DID document.
186
+ *
187
+ * The embedded public keys are bootstrap response and signature material;
188
+ * callers must still authenticate the HTTP request with the trusted OIDC
189
+ * token and one server-authorized activation code. Post-DCR operations must
190
+ * use `packForRecipientWithContext` so registered key custody is resolved.
191
+ */
192
+ packProfileBootstrapForRecipientWithContext(content: any, recipientDidOrJwk: string | JWK, options: WalletPackOptions): Promise<string>;
183
193
  /**
184
194
  * Legacy unpack shape retained for app compatibility.
185
195
  */
@@ -467,6 +467,46 @@ export class NodeManagedWallet {
467
467
  key: encryptionKey,
468
468
  });
469
469
  }
470
+ /**
471
+ * Packs the first activation/DCR requests before GW can resolve this
472
+ * installation's communication keys from a registered DID document.
473
+ *
474
+ * The embedded public keys are bootstrap response and signature material;
475
+ * callers must still authenticate the HTTP request with the trusted OIDC
476
+ * token and one server-authorized activation code. Post-DCR operations must
477
+ * use `packForRecipientWithContext` so registered key custody is resolved.
478
+ */
479
+ async packProfileBootstrapForRecipientWithContext(content, recipientDidOrJwk, options) {
480
+ const recipientJwk = typeof recipientDidOrJwk === 'string'
481
+ ? await this.resolveRecipientPublicJwk(recipientDidOrJwk)
482
+ : recipientDidOrJwk;
483
+ const signingKey = this.requireManagedKey(options.context, {
484
+ ownerScope: 'runtime',
485
+ purpose: 'comm-signing',
486
+ }, 'sig');
487
+ const encryptionKey = this.requireManagedKey(options.context, {
488
+ ownerScope: 'runtime',
489
+ purpose: 'comm-encryption',
490
+ }, 'enc');
491
+ const compactJws = await this.signCompactJws(options.context, {
492
+ header: {
493
+ typ: 'JWS',
494
+ jwk: signingKey.descriptor.publicJwk,
495
+ },
496
+ claims: content,
497
+ key: { keyId: signingKey.descriptor.kid },
498
+ });
499
+ const senderEncryptionKey = {
500
+ ...encryptionKey.descriptor.publicJwk,
501
+ dBytes: encryptionKey.privateMaterial,
502
+ };
503
+ return this.cryptography.encryptJweToCompact(compactJws, {
504
+ enc: 'A256GCM',
505
+ skid: encryptionKey.descriptor.kid,
506
+ jwk: encryptionKey.descriptor.publicJwk,
507
+ cty: 'JWS',
508
+ }, senderEncryptionKey, recipientJwk);
509
+ }
470
510
  /**
471
511
  * Legacy unpack shape retained for app compatibility.
472
512
  */
@@ -94,7 +94,12 @@ export declare class IndividualControllerSdk {
94
94
  * Ingests a FHIR `Communication` and waits for indexing.
95
95
  */
96
96
  ingestCommunicationAndUpdateIndex(ctx: RouteContext, input: CommunicationIngestionInput): Promise<SubmitAndPollResult>;
97
- /** Updates one exact clinical section through a section-scoped batch/collection. */
97
+ /**
98
+ * Updates one exact clinical section through a section-scoped batch.
99
+ * Entries choose their own operation, so one call may create one resource
100
+ * and delete another with `.delete()` and optional `.ifMatch(versionId)`.
101
+ * DELETE entries contain no resource body.
102
+ */
98
103
  updateClinicalSection(ctx: RouteContext, input: ClinicalSectionUpdateInput): Promise<SubmitAndPollResult>;
99
104
  /** Updates the multi-section summary through a Composition-first document. */
100
105
  updateClinicalSummary(ctx: RouteContext, input: ClinicalSummaryUpdateInput): Promise<SubmitAndPollResult>;
@@ -142,7 +142,12 @@ export class IndividualControllerSdk {
142
142
  assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualIngestCommunication, ActorKinds.IndividualController, 'ingestCommunicationAndUpdateIndex');
143
143
  return requireClientMethod(this.client, 'ingestCommunicationAndUpdateIndex')(ctx, input);
144
144
  }
145
- /** Updates one exact clinical section through a section-scoped batch/collection. */
145
+ /**
146
+ * Updates one exact clinical section through a section-scoped batch.
147
+ * Entries choose their own operation, so one call may create one resource
148
+ * and delete another with `.delete()` and optional `.ifMatch(versionId)`.
149
+ * DELETE entries contain no resource body.
150
+ */
146
151
  updateClinicalSection(ctx, input) {
147
152
  assertFacadeCapability(this.capabilities, ActorCapabilities.IndividualIngestCommunication, ActorKinds.IndividualController, 'updateClinicalSection');
148
153
  return requireClientMethod(this.client, 'updateClinicalSection')(ctx, input);
@@ -291,7 +291,14 @@ type ClinicalUpdateRuntimeOptions = Readonly<{
291
291
  intervalMs?: number;
292
292
  };
293
293
  }>;
294
- /** Updates exactly one clinical section through a scoped batch/collection. */
294
+ /**
295
+ * Updates exactly one clinical section through a scoped batch/collection.
296
+ * A `Bundle.type = batch` may mix typed create, update, and delete entries.
297
+ * A delete targets `ResourceType/id`, carries no resource body, and may use
298
+ * `ifMatch` for optimistic concurrency. GW authorizes it against the creator
299
+ * DID plus the caller's linked verified login identity; contact identifiers
300
+ * are never written into the clinical resource.
301
+ */
295
302
  export type ClinicalSectionUpdateInput = ClinicalSectionUpdateCommunicationInput & ClinicalUpdateRuntimeOptions;
296
303
  /** Updates one complete multi-section clinical summary document. */
297
304
  export type ClinicalSummaryUpdateInput = ClinicalUpdateCommunicationInput & ClinicalUpdateRuntimeOptions;
@@ -44,7 +44,18 @@ export class ServerProfileSessionManager {
44
44
  const idToken = String(input.idToken || '').trim();
45
45
  if (!idToken)
46
46
  throw new Error('Profile enrollment requires a signed OIDC idToken for account/email binding.');
47
- const client = this.createClient(input.routeContext, idToken);
47
+ const client = new NodeHttpClient({
48
+ baseUrl: this.options.gatewayBaseUrl,
49
+ ctx: input.routeContext,
50
+ bearerToken: idToken,
51
+ appInfo: this.options.appInfo,
52
+ fetchImpl: this.options.fetchImpl,
53
+ transportProfile: TransportProfiles.DidcommEncryptedForm,
54
+ secureTransportAdapter: {
55
+ pack: (message) => wallet.packProfileBootstrapForRecipientWithContext({ ...message, iss: input.actorDid }, input.providerDid, { context }),
56
+ unpack: async (jwe) => (await wallet.unpackWithContext(jwe, { context })).content,
57
+ },
58
+ });
48
59
  const redirectUris = input.redirectUris || input.dcrRedirectUris || [];
49
60
  const clientName = String(input.clientName || input.dcrClientName || '').trim();
50
61
  const activationRequest = createProfileDeviceActivationRequest({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-sdk-node-ts",
3
- "version": "2.4.45",
3
+ "version": "2.5.1",
4
4
  "description": "Next-generation Node runtime package for the GDC SDK family",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Antifraud Services Inc.",
@@ -38,8 +38,8 @@
38
38
  "test:e2e:live-gw:clean": "bash ./scripts/run-live-gw-clean.sh"
39
39
  },
40
40
  "dependencies": {
41
- "gdc-common-utils-ts": "2.5.20",
42
- "gdc-sdk-core-ts": "~2.3.18"
41
+ "gdc-common-utils-ts": "2.6.0",
42
+ "gdc-sdk-core-ts": "2.4.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^20.14.10",