@healthcloudai/hc-safe-cdx 0.1.1 → 0.1.3

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
@@ -2,23 +2,28 @@
2
2
 
3
3
  Safe CDX connector provides a thin TypeScript wrapper around Safe CDX API routes.
4
4
 
5
- The connector is designed to work with the shared `HttpClient` from `@healthcloudai/hc-http`, the same way as other Health Cloud connector modules. It does not implement its own transport layer for standard API calls.
5
+ The connector follows the same shared-client pattern used by other Health Cloud connector modules:
6
6
 
7
- Standard Safe CDX API calls are executed through the injected `HttpClient`.
7
+ - `HttpClient` handles HTTP transport.
8
+ - `HCLoginClient` provides the authenticated request headers.
9
+ - `HCSafeCDXClient` builds Safe CDX URLs, wraps request payloads, and exposes one method per Safe CDX route.
10
+
11
+ Safe CDX has its own API host. The connector uses `HCLoginClient` for authentication only; it does not use `loginClient.getBaseUrl()` for Safe CDX routes.
8
12
 
9
13
  Image upload is handled separately because the upload target is a pre-signed S3 URL, not a Safe CDX API route.
10
14
 
11
15
  ## Installation
12
16
 
13
17
  ```sh
14
- npm install @healthcloudai/hc-safe-cdx @healthcloudai/hc-http
18
+ npm install @healthcloudai/hc-safe-cdx @healthcloudai/hc-http @healthcloudai/hc-login-connector
15
19
  ```
16
20
 
17
21
  ## Import
18
22
 
19
23
  ```ts
20
24
  import { FetchClient } from "@healthcloudai/hc-http";
21
- import { SafeCDXClient } from "@healthcloudai/hc-safe-cdx";
25
+ import { HCLoginClient } from "@healthcloudai/hc-login-connector";
26
+ import { HCSafeCDXClient } from "@healthcloudai/hc-safe-cdx";
22
27
  ```
23
28
 
24
29
  ## Basic Setup
@@ -26,47 +31,85 @@ import { SafeCDXClient } from "@healthcloudai/hc-safe-cdx";
26
31
  ```ts
27
32
  const http = new FetchClient();
28
33
 
29
- const safeCdx = new SafeCDXClient(http, {
34
+ const loginClient = new HCLoginClient(http);
35
+
36
+ // Configure and authenticate the login client first.
37
+ // Exact login/configuration calls depend on the login connector setup used by the app.
38
+
39
+ const safeCdx = new HCSafeCDXClient(http, loginClient, {
30
40
  environment: "dev",
31
41
  defaultLanguage: "en",
32
42
  defaultImageType: "jpg",
33
43
  });
44
+ ```
45
+
46
+ The connector does not perform login. The consuming application should authenticate through the Health Cloud login connector first. Safe CDX then reuses the authenticated headers from `loginClient.getAuthHeader()`.
47
+
48
+ There is no `setAccessToken()` step in this version of the client.
49
+
50
+ ## Dependency Notes
51
+
52
+ Because `HCSafeCDXClient` accepts `HCLoginClient` in the constructor, `@healthcloudai/hc-login-connector` should be included as a package dependency.
53
+
54
+ Example package configuration:
34
55
 
35
- safeCdx.setAccessToken(patientAccessToken);
56
+ ```json
57
+ {
58
+ "dependencies": {
59
+ "@healthcloudai/hc-http": "^0.x.x",
60
+ "@healthcloudai/hc-login-connector": "^0.x.x"
61
+ }
62
+ }
36
63
  ```
37
64
 
38
- The connector requires a logged-in patient token.
65
+ In a workspace setup, use the same dependency versioning strategy already used by the other connector packages.
66
+
67
+ Example:
39
68
 
40
- The connector does not perform login. Login should be handled through the patient authentication flow first. After login, the patient token should be passed to Safe CDX by calling `setAccessToken()`.
69
+ ```json
70
+ {
71
+ "dependencies": {
72
+ "@healthcloudai/hc-http": "workspace:*",
73
+ "@healthcloudai/hc-login-connector": "workspace:*"
74
+ }
75
+ }
76
+ ```
41
77
 
42
78
  ## How the Client Works
43
79
 
44
- The client follows the same structure as the other Health Cloud connector modules:
80
+ The client receives both the HTTP client and the login client:
45
81
 
46
82
  ```ts
47
83
  const http = new FetchClient();
48
- const safeCdx = new SafeCDXClient(http, config);
84
+ const loginClient = new HCLoginClient(http);
85
+
86
+ const safeCdx = new HCSafeCDXClient(http, loginClient, {
87
+ environment: "dev",
88
+ });
49
89
  ```
50
90
 
51
91
  The shared `HttpClient` is responsible for executing HTTP requests.
52
92
 
93
+ The `HCLoginClient` is responsible for the authenticated request headers.
94
+
53
95
  The Safe CDX client is responsible for:
54
96
 
97
+ - resolving the Safe CDX environment host
55
98
  - building Safe CDX URLs
56
- - resolving the environment host
57
- - adding the authorization header
99
+ - adding auth headers from `HCLoginClient`
100
+ - adding optional API key headers when configured
58
101
  - wrapping POST request payloads into `{ Data: payload }`
59
102
  - validating `ApiResponse` results where the backend returns `{ IsOK, Data, ErrorMessage }`
60
- - exposing one SDK method per API route
103
+ - exposing one SDK method per Safe CDX route
61
104
 
62
- The Safe CDX client does not create its own internal `fetch` helpers for standard API routes.
105
+ The Safe CDX client does not create its own internal `fetch` helpers for standard Safe CDX API routes.
63
106
 
64
107
  ## Environment Configuration
65
108
 
66
109
  The connector supports the following environments:
67
110
 
68
111
  ```ts
69
- const safeCdx = new SafeCDXClient(http, {
112
+ const safeCdx = new HCSafeCDXClient(http, loginClient, {
70
113
  environment: "dev",
71
114
  });
72
115
  ```
@@ -77,35 +120,54 @@ Available environments:
77
120
  "dev" | "uat" | "prod"
78
121
  ```
79
122
 
80
- Environment host mapping is handled internally by the client.
123
+ Safe CDX environment host mapping is handled internally by the connector:
81
124
 
82
- ## Authorization
125
+ ```txt
126
+ dev -> dev-api-hcs.healthcloud-services.com
127
+ uat -> uat-api-hcs.healthcloud-services.com
128
+ prod -> api-hcs.healthcloud-services.com
129
+ ```
83
130
 
84
- Safe CDX API calls require a Bearer token.
131
+ These hosts are separate from the login connector base URL.
85
132
 
86
- ```ts
87
- safeCdx.setAccessToken(patientAccessToken);
88
- ```
133
+ ## Authentication
89
134
 
90
- The token may be passed with or without the `Bearer` prefix.
135
+ Safe CDX API calls require authenticated Health Cloud headers.
91
136
 
92
- Both examples are valid:
137
+ The connector gets those headers from:
93
138
 
94
139
  ```ts
95
- safeCdx.setAccessToken("eyJ...");
140
+ loginClient.getAuthHeader()
96
141
  ```
97
142
 
143
+ This means the login connector must be configured and authenticated before Safe CDX methods are called.
144
+
145
+ Example flow:
146
+
98
147
  ```ts
99
- safeCdx.setAccessToken("Bearer eyJ...");
148
+ const http = new FetchClient();
149
+
150
+ const loginClient = new HCLoginClient(http);
151
+
152
+ // loginClient.configure(...)
153
+ // await loginClient.loginPatient(...)
154
+
155
+ const safeCdx = new HCSafeCDXClient(http, loginClient, {
156
+ environment: "dev",
157
+ });
100
158
  ```
101
159
 
102
- The client normalizes the value internally and sends it as:
160
+ Safe CDX does not manually receive a token. It does not expose `setAccessToken()`. Auth is inherited from the configured login client.
161
+
162
+ ## Optional API Key Header
163
+
164
+ If an API key header is required by the environment, it can be set on the Safe CDX client:
103
165
 
104
166
  ```ts
105
- Authorization: Bearer <token>
167
+ safeCdx.setApiKey("x-api-key", apiKeyValue);
106
168
  ```
107
169
 
108
- If no token is set before calling an API method, the client throws a `SafeCDXError`.
170
+ When configured, the API key header is added to Safe CDX API calls together with the login auth headers.
109
171
 
110
172
  ## Request Payload Wrapping
111
173
 
@@ -115,29 +177,38 @@ The SDK wraps the payload internally as:
115
177
 
116
178
  ```ts
117
179
  {
118
- "Data": payload
180
+ Data: payload
119
181
  }
120
182
  ```
121
183
 
122
184
  Correct:
123
185
 
124
186
  ```ts
125
- await safeCdx.scan2Ddm({
126
- // scan payload
187
+ await safeCdx.submitAnswers({
188
+ UserTestResultId: userTestResultId,
189
+ Result: [
190
+ {
191
+ Analyte: "Purchase",
192
+ ReportedValue: "drug store",
193
+ StoredValue: "drug store",
194
+ Score: "0",
195
+ },
196
+ ],
127
197
  });
128
198
  ```
129
199
 
130
200
  Incorrect:
131
201
 
132
202
  ```ts
133
- await safeCdx.scan2Ddm({
203
+ await safeCdx.submitAnswers({
134
204
  Data: {
135
- // scan payload
136
- }
205
+ UserTestResultId: userTestResultId,
206
+ Result: [],
207
+ },
137
208
  });
138
209
  ```
139
210
 
140
- The caller should not manually add the `Data` wrapper unless a specific method type explicitly requires it.
211
+ The caller should not manually add the `Data` wrapper.
141
212
 
142
213
  ## API Response Handling
143
214
 
@@ -155,6 +226,8 @@ For these endpoints, the client validates `IsOK`.
155
226
 
156
227
  If `IsOK` is `false`, the client throws `SafeCDXError`.
157
228
 
229
+ This validation exists because an HTTP `200` response does not always mean the backend operation succeeded. The HTTP client validates the transport layer, while the Safe CDX client validates the API response envelope.
230
+
158
231
  Some endpoints return a raw service result instead of the standard `ApiResponse` envelope. Those methods return the raw result directly.
159
232
 
160
233
  Raw result methods include:
@@ -189,13 +262,15 @@ A typical flow looks like this:
189
262
  ```ts
190
263
  const http = new FetchClient();
191
264
 
192
- const safeCdx = new SafeCDXClient(http, {
265
+ const loginClient = new HCLoginClient(http);
266
+
267
+ // Configure and authenticate loginClient first.
268
+
269
+ const safeCdx = new HCSafeCDXClient(http, loginClient, {
193
270
  environment: "dev",
194
271
  defaultLanguage: "en",
195
272
  defaultImageType: "jpg",
196
273
  });
197
-
198
- safeCdx.setAccessToken(patientAccessToken);
199
274
  ```
200
275
 
201
276
  ### 1. Get available test profiles
@@ -336,13 +411,6 @@ Resolves a test profile by GTIN barcode.
336
411
 
337
412
  `language` is optional. If not provided, the client uses `defaultLanguage` from config when available.
338
413
 
339
- ### scan2Ddm
340
-
341
- ```ts
342
- const scan = await safeCdx.scan2Ddm(payload);
343
- ```
344
-
345
- Scans a 2D data matrix barcode and resolves the related test data.
346
414
 
347
415
  ### getTestProfile
348
416
 
@@ -513,6 +581,8 @@ const result = await safeCdx.submitAnswers(payload);
513
581
 
514
582
  Submits analyte answers for a test attempt.
515
583
 
584
+ `Result` should contain at least one answer item. The connector does not generate analyte answers automatically.
585
+
516
586
  ### finalizeTest
517
587
 
518
588
  ```ts
@@ -521,38 +591,20 @@ const result = await safeCdx.finalizeTest(payload);
521
591
 
522
592
  Finalizes a test attempt with CTA, indication, and decision results.
523
593
 
524
- ## Error Handling
525
-
526
- The connector throws `SafeCDXError` for connector-level errors.
527
-
528
- Examples:
529
-
530
- - invalid environment
531
- - missing access token
532
- - failed `ApiResponse` validation
533
- - failed image upload
534
-
535
- ```ts
536
- try {
537
- const profile = await safeCdx.getTestProfileByGTIN("850024942325");
538
- } catch (error) {
539
- if (error instanceof SafeCDXError) {
540
- console.error(error.message);
541
- console.error(error.response);
542
- }
543
-
544
- throw error;
545
- }
546
- ```
594
+ The payload should include:
547
595
 
548
- HTTP-level errors from the shared `HttpClient` may also be thrown depending on the `HttpClient` implementation.
596
+ - `UserTestResultId`
597
+ - `ctaResult`
598
+ - `indicationResult`
599
+ - `decisionResult`
549
600
 
550
- For example, `FetchClient` from `@healthcloudai/hc-http` throws an HTTP error when the response status is not successful.
551
601
 
552
602
  ## Notes
553
603
 
554
604
  - The connector does not perform login.
555
- - The connector expects a valid patient token to be set before API calls.
605
+ - The connector uses `HCLoginClient` for authenticated request headers.
606
+ - The connector does not use `loginClient.getBaseUrl()` for Safe CDX API routes.
607
+ - Safe CDX routes use the Safe CDX environment host from `SafeCDXConfig`.
556
608
  - Standard Safe CDX API methods use the shared `HttpClient`.
557
609
  - POST payloads are wrapped internally as `{ Data: payload }`.
558
610
  - The caller should pass only the inner payload to SDK methods.
package/dist/index.cjs CHANGED
@@ -67,46 +67,52 @@ function assertApiResponse(envelope, route) {
67
67
  return envelope;
68
68
  }
69
69
  var HCSafeCDXClient = class {
70
- constructor(httpClient, config) {
70
+ constructor(httpClient, authClient, config) {
71
71
  var _a;
72
72
  if (!ENV_HOST[config.environment]) {
73
73
  throw new SafeCDXError(`Invalid environment: ${config.environment}`);
74
74
  }
75
75
  this.http = httpClient;
76
+ this.auth = authClient;
76
77
  this.host = ENV_HOST[config.environment];
77
78
  this.defaultLanguage = config.defaultLanguage;
78
79
  this.defaultImageType = (_a = config.defaultImageType) != null ? _a : "jpg";
79
80
  }
80
- setAccessToken(token) {
81
- const trimmedToken = token == null ? void 0 : token.trim();
82
- if (!trimmedToken) {
83
- throw new SafeCDXError("Access token is required.");
81
+ setApiKey(headerName, value) {
82
+ const trimmedHeaderName = headerName == null ? void 0 : headerName.trim();
83
+ const trimmedValue = value == null ? void 0 : value.trim();
84
+ if (!trimmedHeaderName) {
85
+ throw new SafeCDXError("API key header name is required.");
84
86
  }
85
- this.accessToken = trimmedToken;
87
+ if (!trimmedValue) {
88
+ throw new SafeCDXError("API key value is required.");
89
+ }
90
+ this.apiKeyHeaderName = trimmedHeaderName;
91
+ this.apiKeyValue = trimmedValue;
86
92
  }
87
93
  // ---------------------------------------------------------------------------
88
94
  // Private helpers
89
95
  // ---------------------------------------------------------------------------
90
- getAuthorizationHeader() {
91
- if (!this.accessToken) {
92
- throw new SafeCDXError(
93
- "SafeCDX requires an access token. Call setAccessToken() first."
94
- );
95
- }
96
- const token = this.accessToken.replace(/^Bearer\s+/i, "").trim();
97
- return `Bearer ${token}`;
98
- }
99
96
  getAuthHeaders() {
100
97
  return {
101
- Authorization: this.getAuthorizationHeader()
98
+ ...this.auth.getAuthHeader(),
99
+ ...this.getApiKeyHeader()
102
100
  };
103
101
  }
104
- getJsonAuthHeaders() {
102
+ getJsonHeaders() {
105
103
  return {
106
- Authorization: this.getAuthorizationHeader(),
104
+ ...this.getAuthHeaders(),
107
105
  "Content-Type": "application/json"
108
106
  };
109
107
  }
108
+ getApiKeyHeader() {
109
+ if (!this.apiKeyHeaderName || !this.apiKeyValue) {
110
+ return {};
111
+ }
112
+ return {
113
+ [this.apiKeyHeaderName]: this.apiKeyValue
114
+ };
115
+ }
110
116
  url(route, query) {
111
117
  return buildUrl(this.host, route, query);
112
118
  }
@@ -126,18 +132,6 @@ var HCSafeCDXClient = class {
126
132
  );
127
133
  return assertApiResponse(envelope, "gs1/:gtin");
128
134
  }
129
- /**
130
- * POST scan/2ddm
131
- * Scans a 2D data matrix barcode and resolves a UserTestResultId.
132
- */
133
- async scan2Ddm(payload) {
134
- const envelope = await this.http.post(
135
- this.url("scan/2ddm"),
136
- wrapData(payload),
137
- this.getJsonAuthHeaders()
138
- );
139
- return assertApiResponse(envelope, "scan/2ddm");
140
- }
141
135
  // ---------------------------------------------------------------------------
142
136
  // Test Profiles
143
137
  // ---------------------------------------------------------------------------
@@ -149,7 +143,7 @@ var HCSafeCDXClient = class {
149
143
  const envelope = await this.http.post(
150
144
  this.url("test/profile"),
151
145
  wrapData(payload),
152
- this.getJsonAuthHeaders()
146
+ this.getJsonHeaders()
153
147
  );
154
148
  return assertApiResponse(envelope, "test/profile");
155
149
  }
@@ -163,7 +157,7 @@ var HCSafeCDXClient = class {
163
157
  const envelope = await this.http.post(
164
158
  this.url("test/profiles/by-account"),
165
159
  wrapData(payload),
166
- this.getJsonAuthHeaders()
160
+ this.getJsonHeaders()
167
161
  );
168
162
  return assertApiResponse(envelope, "test/profiles/by-account");
169
163
  }
@@ -186,14 +180,16 @@ var HCSafeCDXClient = class {
186
180
  const envelope = await this.http.post(
187
181
  this.url("upload/url"),
188
182
  wrapData(payload),
189
- this.getJsonAuthHeaders()
183
+ this.getJsonHeaders()
190
184
  );
191
185
  return assertApiResponse(envelope, "upload/url");
192
186
  }
193
187
  /**
194
188
  * PUT preSignedURL — direct S3 upload, not a SafeCDX API route.
195
189
  * preSignedURL comes from createUploadUrl() response: Data.preSignedURL.
196
-
190
+ *
191
+ * This intentionally does not use the shared HttpClient because the existing
192
+ * HttpClient.put() is JSON-oriented and stringifies the request body.
197
193
  */
198
194
  async uploadImage(preSignedURL, image, contentType = "image/jpeg") {
199
195
  const response = await fetch(preSignedURL, {
@@ -234,7 +230,7 @@ var HCSafeCDXClient = class {
234
230
  return this.http.post(
235
231
  this.url("cvml/status"),
236
232
  wrapData(payload),
237
- this.getJsonAuthHeaders()
233
+ this.getJsonHeaders()
238
234
  );
239
235
  }
240
236
  /**
@@ -263,7 +259,7 @@ var HCSafeCDXClient = class {
263
259
  const envelope = await this.http.post(
264
260
  this.url("test/result/pending"),
265
261
  wrapData(payload),
266
- this.getJsonAuthHeaders()
262
+ this.getJsonHeaders()
267
263
  );
268
264
  return assertApiResponse(envelope, "test/result/pending");
269
265
  }
@@ -275,7 +271,7 @@ var HCSafeCDXClient = class {
275
271
  const envelope = await this.http.post(
276
272
  this.url("test/result/last"),
277
273
  wrapData(payload),
278
- this.getJsonAuthHeaders()
274
+ this.getJsonHeaders()
279
275
  );
280
276
  return assertApiResponse(envelope, "test/result/last");
281
277
  }
@@ -287,7 +283,7 @@ var HCSafeCDXClient = class {
287
283
  const envelope = await this.http.post(
288
284
  this.url("test/result/history"),
289
285
  wrapData(payload),
290
- this.getJsonAuthHeaders()
286
+ this.getJsonHeaders()
291
287
  );
292
288
  return assertApiResponse(envelope, "test/result/history");
293
289
  }
@@ -303,7 +299,7 @@ var HCSafeCDXClient = class {
303
299
  return this.http.post(
304
300
  this.url("test/result/details"),
305
301
  wrapData(payload),
306
- this.getJsonAuthHeaders()
302
+ this.getJsonHeaders()
307
303
  );
308
304
  }
309
305
  /**
@@ -314,7 +310,7 @@ var HCSafeCDXClient = class {
314
310
  const envelope = await this.http.post(
315
311
  this.url("test/result/pdf"),
316
312
  wrapData(payload),
317
- this.getJsonAuthHeaders()
313
+ this.getJsonHeaders()
318
314
  );
319
315
  return assertApiResponse(envelope, "test/result/pdf");
320
316
  }
@@ -330,7 +326,7 @@ var HCSafeCDXClient = class {
330
326
  return this.http.post(
331
327
  this.url("test/result/image/capture"),
332
328
  wrapData(payload),
333
- this.getJsonAuthHeaders()
329
+ this.getJsonHeaders()
334
330
  );
335
331
  }
336
332
  /**
@@ -341,7 +337,7 @@ var HCSafeCDXClient = class {
341
337
  const envelope = await this.http.post(
342
338
  this.url("test/result/analytics"),
343
339
  wrapData(payload),
344
- this.getJsonAuthHeaders()
340
+ this.getJsonHeaders()
345
341
  );
346
342
  return assertApiResponse(envelope, "test/result/analytics");
347
343
  }
@@ -360,7 +356,7 @@ var HCSafeCDXClient = class {
360
356
  const envelope = await this.http.post(
361
357
  this.url("test/resume"),
362
358
  wrapData(payload),
363
- this.getJsonAuthHeaders()
359
+ this.getJsonHeaders()
364
360
  );
365
361
  return assertApiResponse(envelope, "test/resume");
366
362
  }
@@ -372,7 +368,7 @@ var HCSafeCDXClient = class {
372
368
  const envelope = await this.http.post(
373
369
  this.url("test/answers"),
374
370
  wrapData(payload),
375
- this.getJsonAuthHeaders()
371
+ this.getJsonHeaders()
376
372
  );
377
373
  return assertApiResponse(envelope, "test/answers");
378
374
  }
@@ -384,7 +380,7 @@ var HCSafeCDXClient = class {
384
380
  const envelope = await this.http.post(
385
381
  this.url("test/finalize"),
386
382
  wrapData(payload),
387
- this.getJsonAuthHeaders()
383
+ this.getJsonHeaders()
388
384
  );
389
385
  return assertApiResponse(envelope, "test/finalize");
390
386
  }
package/dist/index.d.cts CHANGED
@@ -1,3 +1,4 @@
1
+ import { HCLoginClient } from '@healthcloudai/hc-login-connector';
1
2
  import { HttpClient } from '@healthcloudai/hc-http';
2
3
 
3
4
  type Environment = "dev" | "uat" | "prod";
@@ -66,10 +67,6 @@ interface ScanUpcAnalyte {
66
67
  Image: string | null;
67
68
  Responses: unknown[];
68
69
  }
69
- interface Scan2DdmRequest {
70
- Code: string;
71
- ExternalQuestionnaireResultId?: string;
72
- }
73
70
  interface ScanUpcOrderableData {
74
71
  _id: string | null;
75
72
  ShortName: string | null;
@@ -256,26 +253,23 @@ interface FinalizeTestRequest {
256
253
 
257
254
  declare class HCSafeCDXClient {
258
255
  private readonly http;
256
+ private readonly auth;
259
257
  private readonly host;
260
- private accessToken;
261
- private defaultLanguage;
262
- private defaultImageType;
263
- constructor(httpClient: HttpClient, config: SafeCDXConfig);
264
- setAccessToken(token: string): void;
265
- private getAuthorizationHeader;
258
+ private readonly defaultLanguage;
259
+ private readonly defaultImageType;
260
+ private apiKeyHeaderName?;
261
+ private apiKeyValue?;
262
+ constructor(httpClient: HttpClient, authClient: HCLoginClient, config: SafeCDXConfig);
263
+ setApiKey(headerName: string, value: string): void;
266
264
  private getAuthHeaders;
267
- private getJsonAuthHeaders;
265
+ private getJsonHeaders;
266
+ private getApiKeyHeader;
268
267
  private url;
269
268
  /**
270
269
  * GET gs1/:gtin
271
270
  * Resolves a test profile by GTIN barcode and creates a UserTestResultId.
272
271
  */
273
272
  getTestProfileByGTIN(gtin: string, language?: string): Promise<ApiResponse<GetTestProfileByGTINData>>;
274
- /**
275
- * POST scan/2ddm
276
- * Scans a 2D data matrix barcode and resolves a UserTestResultId.
277
- */
278
- scan2Ddm(payload: Scan2DdmRequest): Promise<ApiResponse<ScanUpcResponseData>>;
279
273
  /**
280
274
  * POST test/profile
281
275
  * Returns the full test profile for a given GTIN.
@@ -295,7 +289,9 @@ declare class HCSafeCDXClient {
295
289
  /**
296
290
  * PUT preSignedURL — direct S3 upload, not a SafeCDX API route.
297
291
  * preSignedURL comes from createUploadUrl() response: Data.preSignedURL.
298
-
292
+ *
293
+ * This intentionally does not use the shared HttpClient because the existing
294
+ * HttpClient.put() is JSON-oriented and stringifies the request body.
299
295
  */
300
296
  uploadImage(preSignedURL: string, image: BodyInit, contentType?: string): Promise<void>;
301
297
  /**
@@ -368,4 +364,4 @@ declare class ConfigError extends Error {
368
364
  constructor(message: string);
369
365
  }
370
366
 
371
- export { type AnswerResult, type ApiResponse, ConfigError, type CreateUploadUrlData, type CreateUploadUrlRequest, type CtaResult, type DecisionResult, type DecisionResultItem, type Environment, type FinalizeTestRequest, type GetAnalyticsRequest, type GetImageCaptureUrlRequest, type GetLastResultsRequest, type GetPendingResultsRequest, type GetResultDetailsRequest, type GetResultPdfRequest, type GetTestHistoryRequest, type GetTestProfileByGTINData, type GetTestProfileRequest, type GetTestProfilesByAccountRequest, HCSafeCDXClient, type IndicationResult, type ResumeFlowRequest, type SafeCDXConfig, type Scan2DdmRequest, type ScanUpcAnalyte, type ScanUpcArticle, type ScanUpcBillingInfo, type ScanUpcInstruction, type ScanUpcOrderableData, type ScanUpcResponseData, type ScanUpcScanImageDetail, type ScanUpcTerritory, type ScanUpcTestResult, type SubmitAnswersRequest, type UpdateCvmlStatusRequest };
367
+ export { type AnswerResult, type ApiResponse, ConfigError, type CreateUploadUrlData, type CreateUploadUrlRequest, type CtaResult, type DecisionResult, type DecisionResultItem, type Environment, type FinalizeTestRequest, type GetAnalyticsRequest, type GetImageCaptureUrlRequest, type GetLastResultsRequest, type GetPendingResultsRequest, type GetResultDetailsRequest, type GetResultPdfRequest, type GetTestHistoryRequest, type GetTestProfileByGTINData, type GetTestProfileRequest, type GetTestProfilesByAccountRequest, HCSafeCDXClient, type IndicationResult, type ResumeFlowRequest, type SafeCDXConfig, type ScanUpcAnalyte, type ScanUpcArticle, type ScanUpcBillingInfo, type ScanUpcInstruction, type ScanUpcOrderableData, type ScanUpcResponseData, type ScanUpcScanImageDetail, type ScanUpcTerritory, type ScanUpcTestResult, type SubmitAnswersRequest, type UpdateCvmlStatusRequest };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { HCLoginClient } from '@healthcloudai/hc-login-connector';
1
2
  import { HttpClient } from '@healthcloudai/hc-http';
2
3
 
3
4
  type Environment = "dev" | "uat" | "prod";
@@ -66,10 +67,6 @@ interface ScanUpcAnalyte {
66
67
  Image: string | null;
67
68
  Responses: unknown[];
68
69
  }
69
- interface Scan2DdmRequest {
70
- Code: string;
71
- ExternalQuestionnaireResultId?: string;
72
- }
73
70
  interface ScanUpcOrderableData {
74
71
  _id: string | null;
75
72
  ShortName: string | null;
@@ -256,26 +253,23 @@ interface FinalizeTestRequest {
256
253
 
257
254
  declare class HCSafeCDXClient {
258
255
  private readonly http;
256
+ private readonly auth;
259
257
  private readonly host;
260
- private accessToken;
261
- private defaultLanguage;
262
- private defaultImageType;
263
- constructor(httpClient: HttpClient, config: SafeCDXConfig);
264
- setAccessToken(token: string): void;
265
- private getAuthorizationHeader;
258
+ private readonly defaultLanguage;
259
+ private readonly defaultImageType;
260
+ private apiKeyHeaderName?;
261
+ private apiKeyValue?;
262
+ constructor(httpClient: HttpClient, authClient: HCLoginClient, config: SafeCDXConfig);
263
+ setApiKey(headerName: string, value: string): void;
266
264
  private getAuthHeaders;
267
- private getJsonAuthHeaders;
265
+ private getJsonHeaders;
266
+ private getApiKeyHeader;
268
267
  private url;
269
268
  /**
270
269
  * GET gs1/:gtin
271
270
  * Resolves a test profile by GTIN barcode and creates a UserTestResultId.
272
271
  */
273
272
  getTestProfileByGTIN(gtin: string, language?: string): Promise<ApiResponse<GetTestProfileByGTINData>>;
274
- /**
275
- * POST scan/2ddm
276
- * Scans a 2D data matrix barcode and resolves a UserTestResultId.
277
- */
278
- scan2Ddm(payload: Scan2DdmRequest): Promise<ApiResponse<ScanUpcResponseData>>;
279
273
  /**
280
274
  * POST test/profile
281
275
  * Returns the full test profile for a given GTIN.
@@ -295,7 +289,9 @@ declare class HCSafeCDXClient {
295
289
  /**
296
290
  * PUT preSignedURL — direct S3 upload, not a SafeCDX API route.
297
291
  * preSignedURL comes from createUploadUrl() response: Data.preSignedURL.
298
-
292
+ *
293
+ * This intentionally does not use the shared HttpClient because the existing
294
+ * HttpClient.put() is JSON-oriented and stringifies the request body.
299
295
  */
300
296
  uploadImage(preSignedURL: string, image: BodyInit, contentType?: string): Promise<void>;
301
297
  /**
@@ -368,4 +364,4 @@ declare class ConfigError extends Error {
368
364
  constructor(message: string);
369
365
  }
370
366
 
371
- export { type AnswerResult, type ApiResponse, ConfigError, type CreateUploadUrlData, type CreateUploadUrlRequest, type CtaResult, type DecisionResult, type DecisionResultItem, type Environment, type FinalizeTestRequest, type GetAnalyticsRequest, type GetImageCaptureUrlRequest, type GetLastResultsRequest, type GetPendingResultsRequest, type GetResultDetailsRequest, type GetResultPdfRequest, type GetTestHistoryRequest, type GetTestProfileByGTINData, type GetTestProfileRequest, type GetTestProfilesByAccountRequest, HCSafeCDXClient, type IndicationResult, type ResumeFlowRequest, type SafeCDXConfig, type Scan2DdmRequest, type ScanUpcAnalyte, type ScanUpcArticle, type ScanUpcBillingInfo, type ScanUpcInstruction, type ScanUpcOrderableData, type ScanUpcResponseData, type ScanUpcScanImageDetail, type ScanUpcTerritory, type ScanUpcTestResult, type SubmitAnswersRequest, type UpdateCvmlStatusRequest };
367
+ export { type AnswerResult, type ApiResponse, ConfigError, type CreateUploadUrlData, type CreateUploadUrlRequest, type CtaResult, type DecisionResult, type DecisionResultItem, type Environment, type FinalizeTestRequest, type GetAnalyticsRequest, type GetImageCaptureUrlRequest, type GetLastResultsRequest, type GetPendingResultsRequest, type GetResultDetailsRequest, type GetResultPdfRequest, type GetTestHistoryRequest, type GetTestProfileByGTINData, type GetTestProfileRequest, type GetTestProfilesByAccountRequest, HCSafeCDXClient, type IndicationResult, type ResumeFlowRequest, type SafeCDXConfig, type ScanUpcAnalyte, type ScanUpcArticle, type ScanUpcBillingInfo, type ScanUpcInstruction, type ScanUpcOrderableData, type ScanUpcResponseData, type ScanUpcScanImageDetail, type ScanUpcTerritory, type ScanUpcTestResult, type SubmitAnswersRequest, type UpdateCvmlStatusRequest };
package/dist/index.js CHANGED
@@ -40,46 +40,52 @@ function assertApiResponse(envelope, route) {
40
40
  return envelope;
41
41
  }
42
42
  var HCSafeCDXClient = class {
43
- constructor(httpClient, config) {
43
+ constructor(httpClient, authClient, config) {
44
44
  var _a;
45
45
  if (!ENV_HOST[config.environment]) {
46
46
  throw new SafeCDXError(`Invalid environment: ${config.environment}`);
47
47
  }
48
48
  this.http = httpClient;
49
+ this.auth = authClient;
49
50
  this.host = ENV_HOST[config.environment];
50
51
  this.defaultLanguage = config.defaultLanguage;
51
52
  this.defaultImageType = (_a = config.defaultImageType) != null ? _a : "jpg";
52
53
  }
53
- setAccessToken(token) {
54
- const trimmedToken = token == null ? void 0 : token.trim();
55
- if (!trimmedToken) {
56
- throw new SafeCDXError("Access token is required.");
54
+ setApiKey(headerName, value) {
55
+ const trimmedHeaderName = headerName == null ? void 0 : headerName.trim();
56
+ const trimmedValue = value == null ? void 0 : value.trim();
57
+ if (!trimmedHeaderName) {
58
+ throw new SafeCDXError("API key header name is required.");
57
59
  }
58
- this.accessToken = trimmedToken;
60
+ if (!trimmedValue) {
61
+ throw new SafeCDXError("API key value is required.");
62
+ }
63
+ this.apiKeyHeaderName = trimmedHeaderName;
64
+ this.apiKeyValue = trimmedValue;
59
65
  }
60
66
  // ---------------------------------------------------------------------------
61
67
  // Private helpers
62
68
  // ---------------------------------------------------------------------------
63
- getAuthorizationHeader() {
64
- if (!this.accessToken) {
65
- throw new SafeCDXError(
66
- "SafeCDX requires an access token. Call setAccessToken() first."
67
- );
68
- }
69
- const token = this.accessToken.replace(/^Bearer\s+/i, "").trim();
70
- return `Bearer ${token}`;
71
- }
72
69
  getAuthHeaders() {
73
70
  return {
74
- Authorization: this.getAuthorizationHeader()
71
+ ...this.auth.getAuthHeader(),
72
+ ...this.getApiKeyHeader()
75
73
  };
76
74
  }
77
- getJsonAuthHeaders() {
75
+ getJsonHeaders() {
78
76
  return {
79
- Authorization: this.getAuthorizationHeader(),
77
+ ...this.getAuthHeaders(),
80
78
  "Content-Type": "application/json"
81
79
  };
82
80
  }
81
+ getApiKeyHeader() {
82
+ if (!this.apiKeyHeaderName || !this.apiKeyValue) {
83
+ return {};
84
+ }
85
+ return {
86
+ [this.apiKeyHeaderName]: this.apiKeyValue
87
+ };
88
+ }
83
89
  url(route, query) {
84
90
  return buildUrl(this.host, route, query);
85
91
  }
@@ -99,18 +105,6 @@ var HCSafeCDXClient = class {
99
105
  );
100
106
  return assertApiResponse(envelope, "gs1/:gtin");
101
107
  }
102
- /**
103
- * POST scan/2ddm
104
- * Scans a 2D data matrix barcode and resolves a UserTestResultId.
105
- */
106
- async scan2Ddm(payload) {
107
- const envelope = await this.http.post(
108
- this.url("scan/2ddm"),
109
- wrapData(payload),
110
- this.getJsonAuthHeaders()
111
- );
112
- return assertApiResponse(envelope, "scan/2ddm");
113
- }
114
108
  // ---------------------------------------------------------------------------
115
109
  // Test Profiles
116
110
  // ---------------------------------------------------------------------------
@@ -122,7 +116,7 @@ var HCSafeCDXClient = class {
122
116
  const envelope = await this.http.post(
123
117
  this.url("test/profile"),
124
118
  wrapData(payload),
125
- this.getJsonAuthHeaders()
119
+ this.getJsonHeaders()
126
120
  );
127
121
  return assertApiResponse(envelope, "test/profile");
128
122
  }
@@ -136,7 +130,7 @@ var HCSafeCDXClient = class {
136
130
  const envelope = await this.http.post(
137
131
  this.url("test/profiles/by-account"),
138
132
  wrapData(payload),
139
- this.getJsonAuthHeaders()
133
+ this.getJsonHeaders()
140
134
  );
141
135
  return assertApiResponse(envelope, "test/profiles/by-account");
142
136
  }
@@ -159,14 +153,16 @@ var HCSafeCDXClient = class {
159
153
  const envelope = await this.http.post(
160
154
  this.url("upload/url"),
161
155
  wrapData(payload),
162
- this.getJsonAuthHeaders()
156
+ this.getJsonHeaders()
163
157
  );
164
158
  return assertApiResponse(envelope, "upload/url");
165
159
  }
166
160
  /**
167
161
  * PUT preSignedURL — direct S3 upload, not a SafeCDX API route.
168
162
  * preSignedURL comes from createUploadUrl() response: Data.preSignedURL.
169
-
163
+ *
164
+ * This intentionally does not use the shared HttpClient because the existing
165
+ * HttpClient.put() is JSON-oriented and stringifies the request body.
170
166
  */
171
167
  async uploadImage(preSignedURL, image, contentType = "image/jpeg") {
172
168
  const response = await fetch(preSignedURL, {
@@ -207,7 +203,7 @@ var HCSafeCDXClient = class {
207
203
  return this.http.post(
208
204
  this.url("cvml/status"),
209
205
  wrapData(payload),
210
- this.getJsonAuthHeaders()
206
+ this.getJsonHeaders()
211
207
  );
212
208
  }
213
209
  /**
@@ -236,7 +232,7 @@ var HCSafeCDXClient = class {
236
232
  const envelope = await this.http.post(
237
233
  this.url("test/result/pending"),
238
234
  wrapData(payload),
239
- this.getJsonAuthHeaders()
235
+ this.getJsonHeaders()
240
236
  );
241
237
  return assertApiResponse(envelope, "test/result/pending");
242
238
  }
@@ -248,7 +244,7 @@ var HCSafeCDXClient = class {
248
244
  const envelope = await this.http.post(
249
245
  this.url("test/result/last"),
250
246
  wrapData(payload),
251
- this.getJsonAuthHeaders()
247
+ this.getJsonHeaders()
252
248
  );
253
249
  return assertApiResponse(envelope, "test/result/last");
254
250
  }
@@ -260,7 +256,7 @@ var HCSafeCDXClient = class {
260
256
  const envelope = await this.http.post(
261
257
  this.url("test/result/history"),
262
258
  wrapData(payload),
263
- this.getJsonAuthHeaders()
259
+ this.getJsonHeaders()
264
260
  );
265
261
  return assertApiResponse(envelope, "test/result/history");
266
262
  }
@@ -276,7 +272,7 @@ var HCSafeCDXClient = class {
276
272
  return this.http.post(
277
273
  this.url("test/result/details"),
278
274
  wrapData(payload),
279
- this.getJsonAuthHeaders()
275
+ this.getJsonHeaders()
280
276
  );
281
277
  }
282
278
  /**
@@ -287,7 +283,7 @@ var HCSafeCDXClient = class {
287
283
  const envelope = await this.http.post(
288
284
  this.url("test/result/pdf"),
289
285
  wrapData(payload),
290
- this.getJsonAuthHeaders()
286
+ this.getJsonHeaders()
291
287
  );
292
288
  return assertApiResponse(envelope, "test/result/pdf");
293
289
  }
@@ -303,7 +299,7 @@ var HCSafeCDXClient = class {
303
299
  return this.http.post(
304
300
  this.url("test/result/image/capture"),
305
301
  wrapData(payload),
306
- this.getJsonAuthHeaders()
302
+ this.getJsonHeaders()
307
303
  );
308
304
  }
309
305
  /**
@@ -314,7 +310,7 @@ var HCSafeCDXClient = class {
314
310
  const envelope = await this.http.post(
315
311
  this.url("test/result/analytics"),
316
312
  wrapData(payload),
317
- this.getJsonAuthHeaders()
313
+ this.getJsonHeaders()
318
314
  );
319
315
  return assertApiResponse(envelope, "test/result/analytics");
320
316
  }
@@ -333,7 +329,7 @@ var HCSafeCDXClient = class {
333
329
  const envelope = await this.http.post(
334
330
  this.url("test/resume"),
335
331
  wrapData(payload),
336
- this.getJsonAuthHeaders()
332
+ this.getJsonHeaders()
337
333
  );
338
334
  return assertApiResponse(envelope, "test/resume");
339
335
  }
@@ -345,7 +341,7 @@ var HCSafeCDXClient = class {
345
341
  const envelope = await this.http.post(
346
342
  this.url("test/answers"),
347
343
  wrapData(payload),
348
- this.getJsonAuthHeaders()
344
+ this.getJsonHeaders()
349
345
  );
350
346
  return assertApiResponse(envelope, "test/answers");
351
347
  }
@@ -357,7 +353,7 @@ var HCSafeCDXClient = class {
357
353
  const envelope = await this.http.post(
358
354
  this.url("test/finalize"),
359
355
  wrapData(payload),
360
- this.getJsonAuthHeaders()
356
+ this.getJsonHeaders()
361
357
  );
362
358
  return assertApiResponse(envelope, "test/finalize");
363
359
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@healthcloudai/hc-safe-cdx",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Healthcheck Safe CDX connector.",
5
5
  "author": "Healthcheck Systems Inc",
6
6
  "license": "MIT",
@@ -33,7 +33,8 @@
33
33
  "prepublishOnly": "npm run build"
34
34
  },
35
35
  "dependencies": {
36
- "@healthcloudai/hc-http": "^0.0.6"
36
+ "@healthcloudai/hc-http": "^0.0.6",
37
+ "@healthcloudai/hc-login-connector": "^0.0.15"
37
38
  },
38
39
  "peerDependencies": {
39
40
  "react-native": ">=0.70.0"