gdc-sdk-node-ts 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 (60) hide show
  1. package/README.md +166 -0
  2. package/dist/async-polling.d.ts +7 -0
  3. package/dist/async-polling.js +26 -0
  4. package/dist/device-activation.d.ts +44 -0
  5. package/dist/device-activation.js +44 -0
  6. package/dist/gdc-session-bridge.d.ts +11 -0
  7. package/dist/gdc-session-bridge.js +74 -0
  8. package/dist/host-onboarding.d.ts +38 -0
  9. package/dist/host-onboarding.js +39 -0
  10. package/dist/identity-bootstrap.d.ts +30 -0
  11. package/dist/identity-bootstrap.js +32 -0
  12. package/dist/index.d.ts +23 -0
  13. package/dist/index.js +24 -0
  14. package/dist/individual-onboarding.d.ts +35 -0
  15. package/dist/individual-onboarding.js +38 -0
  16. package/dist/individual-start.d.ts +87 -0
  17. package/dist/individual-start.js +82 -0
  18. package/dist/legacy-compat.d.ts +22 -0
  19. package/dist/legacy-compat.js +18 -0
  20. package/dist/node-runtime-client.d.ts +195 -0
  21. package/dist/node-runtime-client.js +483 -0
  22. package/dist/orchestration/client-port.d.ts +47 -0
  23. package/dist/orchestration/client-port.js +31 -0
  24. package/dist/orchestration/host-onboarding-sdk.d.ts +14 -0
  25. package/dist/orchestration/host-onboarding-sdk.js +15 -0
  26. package/dist/orchestration/individual-controller-sdk.d.ts +54 -0
  27. package/dist/orchestration/individual-controller-sdk.js +70 -0
  28. package/dist/orchestration/individual-member-sdk.d.ts +17 -0
  29. package/dist/orchestration/individual-member-sdk.js +20 -0
  30. package/dist/orchestration/organization-controller-sdk.d.ts +46 -0
  31. package/dist/orchestration/organization-controller-sdk.js +56 -0
  32. package/dist/orchestration/organization-employee-sdk.d.ts +9 -0
  33. package/dist/orchestration/organization-employee-sdk.js +13 -0
  34. package/dist/orchestration/personal-sdk.d.ts +22 -0
  35. package/dist/orchestration/personal-sdk.js +34 -0
  36. package/dist/orchestration/professional-sdk.d.ts +58 -0
  37. package/dist/orchestration/professional-sdk.js +63 -0
  38. package/dist/poll-options.d.ts +1 -0
  39. package/dist/poll-options.js +2 -0
  40. package/dist/resource-operations.d.ts +225 -0
  41. package/dist/resource-operations.js +184 -0
  42. package/dist/runtime-contracts.d.ts +65 -0
  43. package/dist/runtime-contracts.js +7 -0
  44. package/dist/session.d.ts +64 -0
  45. package/dist/session.js +80 -0
  46. package/dist/simple-device-activation.d.ts +44 -0
  47. package/dist/simple-device-activation.js +44 -0
  48. package/dist/simple-host-onboarding.d.ts +27 -0
  49. package/dist/simple-host-onboarding.js +39 -0
  50. package/dist/simple-individual-onboarding.d.ts +27 -0
  51. package/dist/simple-individual-onboarding.js +38 -0
  52. package/dist/simple-individual-start.d.ts +45 -0
  53. package/dist/simple-individual-start.js +59 -0
  54. package/dist/simple-poll-options.d.ts +5 -0
  55. package/dist/simple-poll-options.js +17 -0
  56. package/dist/simple-smart-token.d.ts +58 -0
  57. package/dist/simple-smart-token.js +85 -0
  58. package/dist/smart-token.d.ts +135 -0
  59. package/dist/smart-token.js +100 -0
  60. package/package.json +26 -0
@@ -0,0 +1,59 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { resolvePollOptionsFromSeconds } from './simple-poll-options.js';
3
+ export async function startIndividualOrganizationWithDeps(deps) {
4
+ const alternateName = String(deps.input.alternateName || '').trim();
5
+ if (!alternateName) {
6
+ throw new Error('bootstrapIndividualOrganization requires alternateName.');
7
+ }
8
+ const controllerEmail = String(deps.input.controllerEmail || '').trim();
9
+ const controllerTelephone = String(deps.input.controllerTelephone || '').trim();
10
+ if (!controllerEmail && !controllerTelephone) {
11
+ throw new Error('bootstrapIndividualOrganization requires controllerEmail or controllerTelephone.');
12
+ }
13
+ const controllerRole = String(deps.input.controllerRole || 'RESPRSN').trim();
14
+ const claims = {
15
+ '@context': 'org.schema',
16
+ 'org.schema.Organization.alternateName': alternateName,
17
+ 'org.schema.Service.category': deps.routeCtx.sector,
18
+ 'org.schema.Person.hasOccupation': controllerRole,
19
+ ...(controllerEmail ? { 'org.schema.Person.email': controllerEmail } : {}),
20
+ ...(controllerTelephone ? { 'org.schema.Person.telephone': controllerTelephone } : {}),
21
+ ...(deps.input.additionalClaims || {}),
22
+ };
23
+ const registrationPayload = {
24
+ jti: `jti-${createRuntimeUuid()}`,
25
+ iss: deps.routeCtx.tenantId,
26
+ aud: deps.routeCtx.tenantId,
27
+ type: 'application/didcomm-plain+json',
28
+ thid: `family-org-${createRuntimeUuid()}`,
29
+ body: {
30
+ data: [{
31
+ type: 'SubjectOrg-registration-form-v1.0',
32
+ meta: { claims },
33
+ resource: { meta: { claims } },
34
+ }],
35
+ },
36
+ };
37
+ const pollOptions = resolvePollOptionsFromSeconds(deps.input.timeoutSeconds, deps.input.intervalSeconds, {
38
+ timeoutMs: deps.defaultTimeoutMs,
39
+ intervalMs: deps.defaultIntervalMs,
40
+ });
41
+ const registration = await deps.submitAndPoll(deps.individualFamilyOrganizationBatchPath(deps.routeCtx), deps.individualFamilyOrganizationPollPath(deps.routeCtx), registrationPayload, pollOptions);
42
+ deps.assertFirstDidcommEntrySuccess?.(registration, 'startIndividualOrganization.registration');
43
+ const offerId = deps.getOfferIdFromResponse(registration);
44
+ if (!offerId) {
45
+ throw new Error('startIndividualOrganization failed: missing offerId in registration response.');
46
+ }
47
+ return {
48
+ registration,
49
+ offerId,
50
+ offerPreview: deps.getOfferPreviewFromResponse(registration),
51
+ };
52
+ }
53
+ function createRuntimeUuid() {
54
+ const fromCrypto = globalThis.crypto?.randomUUID?.();
55
+ if (fromCrypto) {
56
+ return fromCrypto;
57
+ }
58
+ return `fallback-${Date.now()}-${Math.random().toString(16).slice(2)}`;
59
+ }
@@ -0,0 +1,5 @@
1
+ import type { PollOptions } from './orchestration/client-port.js';
2
+ export declare function resolvePollOptionsFromSeconds(timeoutSeconds?: number, intervalSeconds?: number, defaults?: {
3
+ timeoutMs?: number;
4
+ intervalMs?: number;
5
+ }): PollOptions | undefined;
@@ -0,0 +1,17 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ export function resolvePollOptionsFromSeconds(timeoutSeconds, intervalSeconds, defaults) {
3
+ const pollOptions = {};
4
+ if (Number.isFinite(Number(timeoutSeconds))) {
5
+ pollOptions.timeoutMs = Math.max(1, Math.floor(Number(timeoutSeconds) * 1000));
6
+ }
7
+ else if (defaults?.timeoutMs) {
8
+ pollOptions.timeoutMs = defaults.timeoutMs;
9
+ }
10
+ if (Number.isFinite(Number(intervalSeconds))) {
11
+ pollOptions.intervalMs = Math.max(1, Math.floor(Number(intervalSeconds) * 1000));
12
+ }
13
+ else if (defaults?.intervalMs) {
14
+ pollOptions.intervalMs = defaults.intervalMs;
15
+ }
16
+ return Object.keys(pollOptions).length ? pollOptions : undefined;
17
+ }
@@ -0,0 +1,58 @@
1
+ import type { PollOptions, SubmitAndPollResult } from './orchestration/client-port.js';
2
+ import type { RouteContext } from './simple-individual-onboarding.js';
3
+ export type SmartTokenRequestInput = {
4
+ tenantId?: string;
5
+ jurisdiction?: string;
6
+ sector?: string;
7
+ idToken: string;
8
+ scopes: string[];
9
+ subjectDid?: string;
10
+ vpToken?: string;
11
+ clientId?: string;
12
+ audience?: string;
13
+ issuer?: string;
14
+ redirectUri?: string;
15
+ acrValues?: string;
16
+ codeChallenge?: string;
17
+ codeChallengeMethod?: 'S256';
18
+ presentationSubmission?: Record<string, unknown>;
19
+ purpose?: string;
20
+ requestBodyClaims?: Record<string, unknown>;
21
+ smartTokenKind?: 'token-exchange' | 'openid-smart';
22
+ tokenCacheKey?: string;
23
+ endpointId?: string;
24
+ timeoutSeconds?: number;
25
+ intervalSeconds?: number;
26
+ additionalClaims?: Record<string, unknown>;
27
+ };
28
+ export type SmartTokenExchangeResult = {
29
+ status: 'fetched' | 'cached' | 'failed';
30
+ accessToken?: string;
31
+ tokenType?: string;
32
+ scopes?: string[];
33
+ statusCode?: number;
34
+ response?: unknown;
35
+ };
36
+ type CachedTokenWrite = {
37
+ accessToken: string;
38
+ tokenType: string;
39
+ scopes: string[];
40
+ expiresAt: number;
41
+ };
42
+ type RequestSmartTokenDeps = {
43
+ input: SmartTokenRequestInput;
44
+ routeCtx: RouteContext;
45
+ baseUrl: string;
46
+ defaultTimeoutMs?: number;
47
+ defaultIntervalMs?: number;
48
+ identityTokenExchangePath: (ctx: RouteContext) => string;
49
+ identityTokenExchangePollPath: (ctx: RouteContext) => string;
50
+ identityOpenIdSmartTokenPath: (ctx: RouteContext) => string;
51
+ identityOpenIdSmartTokenPollPath: (ctx: RouteContext) => string;
52
+ submitAndPoll: (submitPath: string, pollPath: string, payload: {
53
+ thid?: string;
54
+ } & Record<string, unknown>, options?: PollOptions) => Promise<SubmitAndPollResult>;
55
+ setTokenCache: (tokenCacheKey: string, token: CachedTokenWrite) => void;
56
+ };
57
+ export declare function requestSmartTokenWithDeps(deps: RequestSmartTokenDeps): Promise<SmartTokenExchangeResult>;
58
+ export {};
@@ -0,0 +1,85 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { resolvePollOptionsFromSeconds } from './simple-poll-options.js';
3
+ export async function requestSmartTokenWithDeps(deps) {
4
+ const normalizedScopes = Array.from(new Set((deps.input.scopes || []).filter(Boolean))).sort();
5
+ const tokenCacheKey = String(deps.input.tokenCacheKey || deps.input.endpointId || `smart:${deps.routeCtx.tenantId}:${normalizedScopes.join(',')}`).trim();
6
+ if (!tokenCacheKey) {
7
+ throw new Error('requestSmartToken requires tokenCacheKey (or non-empty scopes).');
8
+ }
9
+ const pollOptions = resolvePollOptionsFromSeconds(deps.input.timeoutSeconds, deps.input.intervalSeconds, {
10
+ timeoutMs: deps.defaultTimeoutMs,
11
+ intervalMs: deps.defaultIntervalMs,
12
+ });
13
+ if (deps.input.smartTokenKind === 'openid-smart') {
14
+ const smartPayload = {
15
+ thid: `smart-${createRuntimeUuid()}`,
16
+ iss: deps.input.issuer || deps.input.clientId || deps.routeCtx.tenantId,
17
+ aud: deps.input.audience || deps.routeCtx.tenantId,
18
+ body: {
19
+ client_id: deps.input.clientId || deps.input.subjectDid || deps.routeCtx.tenantId,
20
+ redirect_uri: deps.input.redirectUri || `${deps.baseUrl}/callback`,
21
+ code_challenge: deps.input.codeChallenge || 'demo-code-challenge',
22
+ code_challenge_method: deps.input.codeChallengeMethod || 'S256',
23
+ acr_values: deps.input.acrValues || 'urn:antifraud:acr:openid4vp:employee',
24
+ vp_token: deps.input.vpToken || deps.input.idToken,
25
+ presentation_submission: deps.input.presentationSubmission,
26
+ expires_in: 300,
27
+ token_type: 'Bearer',
28
+ sub: deps.input.subjectDid || deps.input.issuer || deps.input.clientId || deps.routeCtx.tenantId,
29
+ purpose: deps.input.purpose || 'TREAT',
30
+ scope: normalizedScopes.join(' '),
31
+ ...(deps.input.requestBodyClaims || {}),
32
+ },
33
+ ...(deps.input.additionalClaims || {}),
34
+ };
35
+ const exchange = await deps.submitAndPoll(deps.identityOpenIdSmartTokenPath(deps.routeCtx), deps.identityOpenIdSmartTokenPollPath(deps.routeCtx), smartPayload, pollOptions);
36
+ return resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, deps.setTokenCache);
37
+ }
38
+ const payload = {
39
+ thid: `exchange-${createRuntimeUuid()}`,
40
+ grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
41
+ subject_token_type: 'urn:ietf:params:oauth:token-type:id_token',
42
+ subject_token: deps.input.idToken,
43
+ scope: normalizedScopes.join(' '),
44
+ organization: deps.routeCtx.tenantId,
45
+ ...(deps.input.additionalClaims || {}),
46
+ };
47
+ const exchange = await deps.submitAndPoll(deps.identityTokenExchangePath(deps.routeCtx), deps.identityTokenExchangePollPath(deps.routeCtx), payload, pollOptions);
48
+ return resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, deps.setTokenCache);
49
+ }
50
+ function resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, setTokenCache) {
51
+ const exchangeBody = exchange.poll.body ?? {};
52
+ const accessToken = String(exchangeBody.access_token || '').trim();
53
+ if (exchange.poll.status >= 400 || !accessToken) {
54
+ return {
55
+ status: 'failed',
56
+ statusCode: exchange.poll.status,
57
+ response: exchange.poll.body,
58
+ };
59
+ }
60
+ const tokenType = String(exchangeBody.token_type || 'Bearer');
61
+ const grantedScopes = String(exchangeBody.scope || '').trim().split(' ').filter(Boolean);
62
+ const resolvedScopes = grantedScopes.length ? grantedScopes : normalizedScopes;
63
+ const expiresIn = Number(exchangeBody.expires_in ?? 0);
64
+ setTokenCache(tokenCacheKey, {
65
+ accessToken,
66
+ tokenType,
67
+ scopes: resolvedScopes,
68
+ expiresAt: Date.now() + expiresIn * 1000,
69
+ });
70
+ return {
71
+ status: 'fetched',
72
+ accessToken,
73
+ tokenType,
74
+ scopes: resolvedScopes,
75
+ statusCode: exchange.poll.status,
76
+ response: exchange.poll.body,
77
+ };
78
+ }
79
+ function createRuntimeUuid() {
80
+ const fromCrypto = globalThis.crypto?.randomUUID?.();
81
+ if (fromCrypto) {
82
+ return fromCrypto;
83
+ }
84
+ return `fallback-${Date.now()}-${Math.random().toString(16).slice(2)}`;
85
+ }
@@ -0,0 +1,135 @@
1
+ import type { PollOptions, SubmitAndPollResult } from './orchestration/client-port.js';
2
+ import type { RouteContext } from './individual-onboarding.js';
3
+ /**
4
+ * Public SDK input for GW SMART/OpenID token requests.
5
+ *
6
+ * Separation of concerns:
7
+ * - actor identity is carried by `actorDid`
8
+ * - subject access is carried by `subjectDid` plus requested `scopes`
9
+ * - tenant/jurisdiction/sector are only route hints
10
+ * for the current GW transport contract when the client does not already
11
+ * have a default route context configured
12
+ *
13
+ * Why this route context still exists:
14
+ * the current CORE GW token endpoint is tenant-scoped in the URL itself, for
15
+ * example:
16
+ * `/{tenantId}/cds-{jurisdiction}/v1/{sector}/identity/openid/smart/token`
17
+ *
18
+ * So the route context selects which gateway tenant, consent store, and token
19
+ * issuer is answering the request. It is not the same thing as the actor DID
20
+ * carried inside the request body as `sub`.
21
+ *
22
+ * Current implementation status:
23
+ * - actor DID, subject DID, and client/device identity are separated at input level
24
+ * - the runtime still depends on tenant-scoped GW URLs
25
+ *
26
+ * Pending convergence work:
27
+ * - resolve token endpoints from provider DID documents / discovery metadata
28
+ * - avoid route-context fallback when resolved provider metadata is already available
29
+ *
30
+ * Canonical payload examples for both token-exchange and OpenID4VP SMART flows
31
+ * live in `gdc-common-utils-ts/examples`.
32
+ */
33
+ export type SmartTokenRequestInput = {
34
+ /**
35
+ * @deprecated Prefer configuring `NodeHttpClient({ ctx })`.
36
+ */
37
+ tenantId?: string;
38
+ /**
39
+ * @deprecated Prefer configuring `NodeHttpClient({ ctx })`.
40
+ */
41
+ jurisdiction?: string;
42
+ /**
43
+ * @deprecated Prefer configuring `NodeHttpClient({ ctx })`.
44
+ */
45
+ sector?: string;
46
+ /**
47
+ * OpenID token or subject token already obtained by the caller.
48
+ */
49
+ idToken: string;
50
+ /**
51
+ * Requested SMART/GW scopes.
52
+ *
53
+ * Under the current CORE GW contract this should normally include the pinned
54
+ * `organization/Composition...` root scope for the target subject, and may
55
+ * include additional items such as `organization/Consent.cruds`.
56
+ */
57
+ scopes: string[];
58
+ /**
59
+ * Actor/profile DID that is requesting access.
60
+ *
61
+ * Typical values are a professional DID or a `RelatedPerson` DID. This is
62
+ * distinct from the `subjectDid` whose data is being accessed.
63
+ */
64
+ actorDid?: string;
65
+ /**
66
+ * Subject/individual DID whose data is being requested.
67
+ */
68
+ subjectDid?: string;
69
+ /**
70
+ * Optional VP token used by the OpenID4VP-based GW smart token flow.
71
+ */
72
+ vpToken?: string;
73
+ /**
74
+ * Client/device/portal DID used as OAuth/OpenID `client_id`.
75
+ */
76
+ clientId?: string;
77
+ audience?: string;
78
+ issuer?: string;
79
+ redirectUri?: string;
80
+ acrValues?: string;
81
+ codeChallenge?: string;
82
+ codeChallengeMethod?: 'S256';
83
+ presentationSubmission?: Record<string, unknown>;
84
+ purpose?: string;
85
+ requestBodyClaims?: Record<string, unknown>;
86
+ smartTokenKind?: 'token-exchange' | 'openid-smart';
87
+ tokenCacheKey?: string;
88
+ endpointId?: string;
89
+ timeoutSeconds?: number;
90
+ intervalSeconds?: number;
91
+ additionalClaims?: Record<string, unknown>;
92
+ };
93
+ export type SmartTokenExchangeResult = {
94
+ status: 'fetched' | 'cached' | 'failed';
95
+ accessToken?: string;
96
+ tokenType?: string;
97
+ scopes?: string[];
98
+ statusCode?: number;
99
+ response?: unknown;
100
+ };
101
+ type CachedTokenWrite = {
102
+ accessToken: string;
103
+ tokenType: string;
104
+ scopes: string[];
105
+ expiresAt: number;
106
+ };
107
+ type RequestSmartTokenDeps = {
108
+ input: SmartTokenRequestInput;
109
+ routeCtx: RouteContext;
110
+ baseUrl: string;
111
+ defaultTimeoutMs?: number;
112
+ defaultIntervalMs?: number;
113
+ identityTokenExchangePath: (ctx: RouteContext) => string;
114
+ identityTokenExchangePollPath: (ctx: RouteContext) => string;
115
+ identityOpenIdSmartTokenPath: (ctx: RouteContext) => string;
116
+ identityOpenIdSmartTokenPollPath: (ctx: RouteContext) => string;
117
+ submitAndPoll: (submitPath: string, pollPath: string, payload: {
118
+ thid?: string;
119
+ } & Record<string, unknown>, options?: PollOptions) => Promise<SubmitAndPollResult>;
120
+ setTokenCache: (tokenCacheKey: string, token: CachedTokenWrite) => void;
121
+ };
122
+ /**
123
+ * Executes the current GW token flow using injected transport dependencies.
124
+ *
125
+ * Implemented today:
126
+ * - token-exchange flow
127
+ * - OpenID4VP/SMART flow with `vp_token`
128
+ * - normalized polling and token-cache writeback
129
+ *
130
+ * Still pending:
131
+ * - endpoint resolution from provider DID metadata instead of route concatenation
132
+ * - first-class runtime use of `DiscoveryFacade`/`IdentityStore`
133
+ */
134
+ export declare function requestSmartTokenWithDeps(deps: RequestSmartTokenDeps): Promise<SmartTokenExchangeResult>;
135
+ export {};
@@ -0,0 +1,100 @@
1
+ // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
+ import { HealthcareConsentPurposes } from 'gdc-common-utils-ts/constants';
3
+ import { resolvePollOptionsFromSeconds } from './poll-options.js';
4
+ /**
5
+ * Executes the current GW token flow using injected transport dependencies.
6
+ *
7
+ * Implemented today:
8
+ * - token-exchange flow
9
+ * - OpenID4VP/SMART flow with `vp_token`
10
+ * - normalized polling and token-cache writeback
11
+ *
12
+ * Still pending:
13
+ * - endpoint resolution from provider DID metadata instead of route concatenation
14
+ * - first-class runtime use of `DiscoveryFacade`/`IdentityStore`
15
+ */
16
+ export async function requestSmartTokenWithDeps(deps) {
17
+ const normalizedScopes = Array.from(new Set((deps.input.scopes || []).filter(Boolean))).sort();
18
+ const tokenCacheKey = String(deps.input.tokenCacheKey || deps.input.endpointId || `smart:${deps.routeCtx.tenantId}:${normalizedScopes.join(',')}`).trim();
19
+ if (!tokenCacheKey) {
20
+ throw new Error('requestSmartToken requires tokenCacheKey (or non-empty scopes).');
21
+ }
22
+ const pollOptions = resolvePollOptionsFromSeconds(deps.input.timeoutSeconds, deps.input.intervalSeconds, {
23
+ timeoutMs: deps.defaultTimeoutMs,
24
+ intervalMs: deps.defaultIntervalMs,
25
+ });
26
+ if (deps.input.smartTokenKind === 'openid-smart') {
27
+ const actorDid = String(deps.input.actorDid || '').trim() || undefined;
28
+ const smartPayload = {
29
+ thid: `smart-${createRuntimeUuid()}`,
30
+ iss: deps.input.issuer || deps.input.clientId || deps.routeCtx.tenantId,
31
+ aud: deps.input.audience || deps.routeCtx.tenantId,
32
+ body: {
33
+ client_id: deps.input.clientId || actorDid || deps.input.subjectDid || deps.routeCtx.tenantId,
34
+ redirect_uri: deps.input.redirectUri || `${deps.baseUrl}/callback`,
35
+ code_challenge: deps.input.codeChallenge || 'demo-code-challenge',
36
+ code_challenge_method: deps.input.codeChallengeMethod || 'S256',
37
+ acr_values: deps.input.acrValues || 'urn:antifraud:acr:openid4vp:employee',
38
+ vp_token: deps.input.vpToken || deps.input.idToken,
39
+ presentation_submission: deps.input.presentationSubmission,
40
+ expires_in: 300,
41
+ token_type: 'Bearer',
42
+ sub: actorDid || deps.input.issuer || deps.input.clientId || deps.routeCtx.tenantId,
43
+ purpose: deps.input.purpose || HealthcareConsentPurposes.Treatment,
44
+ scope: normalizedScopes.join(' '),
45
+ ...(deps.input.requestBodyClaims || {}),
46
+ },
47
+ ...(deps.input.additionalClaims || {}),
48
+ };
49
+ const exchange = await deps.submitAndPoll(deps.identityOpenIdSmartTokenPath(deps.routeCtx), deps.identityOpenIdSmartTokenPollPath(deps.routeCtx), smartPayload, pollOptions);
50
+ return resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, deps.setTokenCache);
51
+ }
52
+ const payload = {
53
+ thid: `exchange-${createRuntimeUuid()}`,
54
+ grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
55
+ subject_token_type: 'urn:ietf:params:oauth:token-type:id_token',
56
+ subject_token: deps.input.idToken,
57
+ scope: normalizedScopes.join(' '),
58
+ organization: deps.routeCtx.tenantId,
59
+ ...(deps.input.actorDid ? { sub: deps.input.actorDid } : {}),
60
+ ...(deps.input.additionalClaims || {}),
61
+ };
62
+ const exchange = await deps.submitAndPoll(deps.identityTokenExchangePath(deps.routeCtx), deps.identityTokenExchangePollPath(deps.routeCtx), payload, pollOptions);
63
+ return resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, deps.setTokenCache);
64
+ }
65
+ function resolveTokenExchangeResult(exchange, normalizedScopes, tokenCacheKey, setTokenCache) {
66
+ const exchangeBody = exchange.poll.body ?? {};
67
+ const accessToken = String(exchangeBody.access_token || '').trim();
68
+ if (exchange.poll.status >= 400 || !accessToken) {
69
+ return {
70
+ status: 'failed',
71
+ statusCode: exchange.poll.status,
72
+ response: exchange.poll.body,
73
+ };
74
+ }
75
+ const tokenType = String(exchangeBody.token_type || 'Bearer');
76
+ const grantedScopes = String(exchangeBody.scope || '').trim().split(' ').filter(Boolean);
77
+ const resolvedScopes = grantedScopes.length ? grantedScopes : normalizedScopes;
78
+ const expiresIn = Number(exchangeBody.expires_in ?? 0);
79
+ setTokenCache(tokenCacheKey, {
80
+ accessToken,
81
+ tokenType,
82
+ scopes: resolvedScopes,
83
+ expiresAt: Date.now() + expiresIn * 1000,
84
+ });
85
+ return {
86
+ status: 'fetched',
87
+ accessToken,
88
+ tokenType,
89
+ scopes: resolvedScopes,
90
+ statusCode: exchange.poll.status,
91
+ response: exchange.poll.body,
92
+ };
93
+ }
94
+ function createRuntimeUuid() {
95
+ const fromCrypto = globalThis.crypto?.randomUUID?.();
96
+ if (fromCrypto) {
97
+ return fromCrypto;
98
+ }
99
+ return `fallback-${Date.now()}-${Math.random().toString(16).slice(2)}`;
100
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "gdc-sdk-node-ts",
3
+ "version": "0.1.1",
4
+ "description": "Next-generation Node runtime package for the GDC SDK family",
5
+ "license": "Apache-2.0",
6
+ "author": "Antifraud Services Inc.",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc -p tsconfig.json",
15
+ "type-check": "tsc -p tsconfig.json --noEmit",
16
+ "test": "npm run build && node --test tests/*.test.mjs",
17
+ "test:e2e:live-gw": "npm run build && RUN_LIVE_GW_E2E=1 node --test tests/live-gw-node-runtime.e2e.test.mjs"
18
+ },
19
+ "dependencies": {
20
+ "gdc-common-utils-ts": "^1.4.22"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^20.14.10",
24
+ "typescript": "^5.5.4"
25
+ }
26
+ }