@healthcloudai/hc-safe-cdx 0.1.4 → 0.2.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/dist/index.js CHANGED
@@ -13,10 +13,15 @@ var ENV_HOST = {
13
13
  prod: "api-hcs.healthcloud-services.com"
14
14
  };
15
15
  var ROUTE_PREFIX = "api/console/hcservice/safecdx";
16
- function buildUrl(host, route, query) {
17
- const url = new URL(
18
- `https://${host}/${ROUTE_PREFIX}/${route.replace(/^\/+/, "")}`
19
- );
16
+ function buildSafeCdxBaseUrl(environment) {
17
+ const host = ENV_HOST[environment];
18
+ if (!host) {
19
+ throw new SafeCDXError("Invalid Safe CDX environment.");
20
+ }
21
+ return `https://${host}/${ROUTE_PREFIX}`;
22
+ }
23
+ function buildUrl(baseUrl, route, query) {
24
+ const url = new URL(`${baseUrl}/${route.replace(/^\/+/, "")}`);
20
25
  if (query) {
21
26
  for (const [key, value] of Object.entries(query)) {
22
27
  if (value !== void 0 && value !== "") {
@@ -26,30 +31,11 @@ function buildUrl(host, route, query) {
26
31
  }
27
32
  return url.toString();
28
33
  }
29
- function wrapData(payload) {
30
- return { Data: payload };
31
- }
32
- function assertApiResponse(envelope, route) {
33
- var _a;
34
- if (!envelope.IsOK) {
35
- throw new SafeCDXError(
36
- (_a = envelope.ErrorMessage) != null ? _a : `${route} returned IsOK=false.`,
37
- envelope
38
- );
39
- }
40
- return envelope;
41
- }
42
34
  var HCSafeCDXClient = class {
43
- constructor(httpClient, authClient, config) {
44
- var _a;
45
- if (!ENV_HOST[config.environment]) {
46
- throw new SafeCDXError(`Invalid environment: ${config.environment}`);
47
- }
35
+ constructor(httpClient, loginClient) {
48
36
  this.http = httpClient;
49
- this.auth = authClient;
50
- this.host = ENV_HOST[config.environment];
51
- this.defaultLanguage = config.defaultLanguage;
52
- this.defaultImageType = (_a = config.defaultImageType) != null ? _a : "jpg";
37
+ this.loginClient = loginClient;
38
+ this.baseUrl = buildSafeCdxBaseUrl(loginClient.getEnvironment());
53
39
  }
54
40
  setApiKey(headerName, value) {
55
41
  const trimmedHeaderName = headerName == null ? void 0 : headerName.trim();
@@ -63,106 +49,66 @@ var HCSafeCDXClient = class {
63
49
  this.apiKeyHeaderName = trimmedHeaderName;
64
50
  this.apiKeyValue = trimmedValue;
65
51
  }
66
- // ---------------------------------------------------------------------------
67
- // Private helpers
68
- // ---------------------------------------------------------------------------
69
- getAuthHeaders() {
70
- return {
71
- ...this.auth.getAuthHeader(),
72
- ...this.getApiKeyHeader()
73
- };
74
- }
75
- getJsonHeaders() {
76
- return {
77
- ...this.getAuthHeaders(),
78
- "Content-Type": "application/json"
79
- };
80
- }
81
- getApiKeyHeader() {
82
- if (!this.apiKeyHeaderName || !this.apiKeyValue) {
83
- return {};
84
- }
85
- return {
86
- [this.apiKeyHeaderName]: this.apiKeyValue
87
- };
88
- }
89
- url(route, query) {
90
- return buildUrl(this.host, route, query);
91
- }
92
- // ---------------------------------------------------------------------------
93
- // Scan
94
- // ---------------------------------------------------------------------------
52
+ // -------------------------------------------------------------------------
53
+ // Test Profiles
54
+ // -------------------------------------------------------------------------
95
55
  /**
96
56
  * GET gs1/:gtin
97
- * Resolves a test profile by GTIN barcode and creates a UserTestResultId.
57
+ * Resolves the SafeCDX test profile associated with a GTIN barcode.
98
58
  */
99
- async getTestProfileByGTIN(gtin, language) {
100
- const envelope = await this.http.get(
101
- this.url(`gs1/${encodeURIComponent(gtin)}`, {
102
- language: language != null ? language : this.defaultLanguage
103
- }),
59
+ async getTestProfileByGTIN(gtin) {
60
+ return this.http.get(
61
+ this.url(`gs1/${encodeURIComponent(gtin)}`),
104
62
  this.getAuthHeaders()
105
63
  );
106
- return assertApiResponse(envelope, "gs1/:gtin");
107
- }
108
- // ---------------------------------------------------------------------------
109
- // Test Profiles
110
- // ---------------------------------------------------------------------------
111
- /**
112
- * POST test/profile
113
- * Returns the full test profile for a given GTIN.
114
- */
115
- async getTestProfile(payload) {
116
- const envelope = await this.http.post(
117
- this.url("test/profile"),
118
- wrapData(payload),
119
- this.getJsonHeaders()
120
- );
121
- return assertApiResponse(envelope, "test/profile");
122
64
  }
123
65
  /**
124
66
  * POST test/profiles/by-account
125
- * Lists all test profiles available to the authenticated account.
67
+ * Lists test profiles available to the authenticated patient account.
68
+ *
69
+ * TenantId is resolved by the backend from the authenticated patient context.
126
70
  */
127
- async getTestProfilesByAccount(payload = {
128
- IncludeRegisterTestDetails: true
129
- }) {
130
- const envelope = await this.http.post(
71
+ async getTestProfilesByAccount(includeRegisterTestDetails = true) {
72
+ const request = {
73
+ Data: {
74
+ IncludeRegisterTestDetails: includeRegisterTestDetails
75
+ }
76
+ };
77
+ return this.http.post(
131
78
  this.url("test/profiles/by-account"),
132
- wrapData(payload),
79
+ request,
133
80
  this.getJsonHeaders()
134
81
  );
135
- return assertApiResponse(envelope, "test/profiles/by-account");
136
82
  }
137
- // ---------------------------------------------------------------------------
138
- // Upload
139
- // ---------------------------------------------------------------------------
83
+ // -------------------------------------------------------------------------
84
+ // Image Upload
85
+ // -------------------------------------------------------------------------
140
86
  /**
141
87
  * POST upload/url
142
- * Requests a pre-signed S3 URL for image upload.
143
- * Use the returned Data.preSignedURL and Data.Metadata.UploadId in the next steps.
88
+ * Requests a pre-signed URL used to upload a test image.
144
89
  */
145
- async createUploadUrl(userTestResultId, gtin, imageType) {
146
- const payload = {
147
- Gtin: gtin,
148
- UserTestResultId: userTestResultId,
149
- Metadata: {
150
- ImageType: imageType != null ? imageType : this.defaultImageType
90
+ async createUploadUrl(userTestResultId, gtin, imageType = "jpg") {
91
+ const request = {
92
+ Data: {
93
+ Gtin: gtin,
94
+ UserTestResultId: userTestResultId,
95
+ Metadata: {
96
+ ImageType: imageType
97
+ }
151
98
  }
152
99
  };
153
- const envelope = await this.http.post(
100
+ return this.http.post(
154
101
  this.url("upload/url"),
155
- wrapData(payload),
102
+ request,
156
103
  this.getJsonHeaders()
157
104
  );
158
- return assertApiResponse(envelope, "upload/url");
159
105
  }
160
106
  /**
161
- * PUT preSignedURL — direct S3 upload, not a SafeCDX API route.
162
- * preSignedURL comes from createUploadUrl() response: Data.preSignedURL.
107
+ * PUT preSignedURL
108
+ * Uploads the image directly to the pre-signed storage URL.
163
109
  *
164
- * This intentionally does not use the shared HttpClient because the existing
165
- * HttpClient.put() is JSON-oriented and stringifies the request body.
110
+ * This request does not call a SafeCDX API route and does not send
111
+ * authenticated Health Cloud headers.
166
112
  */
167
113
  async uploadImage(preSignedURL, image, contentType = "image/jpeg") {
168
114
  const response = await fetch(preSignedURL, {
@@ -177,185 +123,154 @@ var HCSafeCDXClient = class {
177
123
  try {
178
124
  body = (await response.text()).slice(0, 500);
179
125
  } catch {
126
+ body = void 0;
180
127
  }
181
- throw new SafeCDXError(
182
- `Image upload failed with HTTP ${response.status}.`,
183
- {
184
- status: response.status,
185
- body
186
- }
187
- );
128
+ throw new SafeCDXError(`Image upload failed with HTTP ${response.status}.`, {
129
+ status: response.status,
130
+ body
131
+ });
188
132
  }
189
133
  }
190
- // ---------------------------------------------------------------------------
134
+ // -------------------------------------------------------------------------
191
135
  // CVML
192
- // ---------------------------------------------------------------------------
136
+ // -------------------------------------------------------------------------
193
137
  /**
194
138
  * POST cvml/status
195
- * Updates the ML processing status for a captured image.
196
- * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
139
+ * Returns the direct SafeCDX response without an outer APIResponse wrapper.
197
140
  */
198
141
  async updateCvmlStatus(imageOfCaptureId, cvmlStatus) {
199
- const payload = {
200
- ImageOfCaptureId: imageOfCaptureId,
201
- CvmlStatus: cvmlStatus
142
+ const request = {
143
+ Data: {
144
+ ImageOfCaptureId: imageOfCaptureId,
145
+ CvmlStatus: cvmlStatus
146
+ }
202
147
  };
203
- return this.http.post(
204
- this.url("cvml/status"),
205
- wrapData(payload),
206
- this.getJsonHeaders()
207
- );
148
+ return this.http.post(this.url("cvml/status"), request, this.getJsonHeaders());
208
149
  }
209
150
  /**
210
151
  * GET cvml/results
211
- * Polls ML analysis results for a captured image.
212
- * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
152
+ * Returns the direct SafeCDX response without an outer APIResponse wrapper.
213
153
  */
214
154
  async getCvmlResults(imageOfCaptureId) {
215
- return this.http.get(
216
- this.url("cvml/results", {
217
- imageOfCaptureId
218
- }),
219
- this.getAuthHeaders()
220
- );
155
+ return this.http.get(this.url("cvml/results", { imageOfCaptureId }), this.getAuthHeaders());
221
156
  }
222
- // ---------------------------------------------------------------------------
157
+ // -------------------------------------------------------------------------
223
158
  // Test Results
224
- // ---------------------------------------------------------------------------
225
- /**
226
- * POST test/result/pending
227
- * Returns test results in a pending/incomplete state.
228
- */
229
- async getPendingResults(payload = {
230
- ExcludeStatus: "Started"
231
- }) {
232
- const envelope = await this.http.post(
233
- this.url("test/result/pending"),
234
- wrapData(payload),
235
- this.getJsonHeaders()
236
- );
237
- return assertApiResponse(envelope, "test/result/pending");
159
+ // -------------------------------------------------------------------------
160
+ /** POST test/result/pending */
161
+ async getPendingResults(excludeStatus = "Invalid,Canceled") {
162
+ const request = {
163
+ Data: {
164
+ ExcludeStatus: excludeStatus
165
+ }
166
+ };
167
+ return this.http.post(this.url("test/result/pending"), request, this.getJsonHeaders());
238
168
  }
239
- /**
240
- * POST test/result/last
241
- * Returns the most recent test result for the authenticated patient.
242
- */
243
- async getLastResults(payload = {}) {
244
- const envelope = await this.http.post(
245
- this.url("test/result/last"),
246
- wrapData(payload),
247
- this.getJsonHeaders()
248
- );
249
- return assertApiResponse(envelope, "test/result/last");
169
+ /** POST test/result/last */
170
+ async getLastResults(excludeStatus = "Invalid") {
171
+ const request = {
172
+ Data: {
173
+ ExcludeStatus: excludeStatus
174
+ }
175
+ };
176
+ return this.http.post(this.url("test/result/last"), request, this.getJsonHeaders());
250
177
  }
251
- /**
252
- * POST test/result/history
253
- * Returns the full test history for the authenticated patient.
254
- */
255
- async getTestHistory(payload = {}) {
256
- const envelope = await this.http.post(
257
- this.url("test/result/history"),
258
- wrapData(payload),
259
- this.getJsonHeaders()
260
- );
261
- return assertApiResponse(envelope, "test/result/history");
178
+ /** POST test/result/history */
179
+ async getTestHistory(excludeStatus = "Invalid") {
180
+ const request = {
181
+ Data: {
182
+ ExcludeStatus: excludeStatus
183
+ }
184
+ };
185
+ return this.http.post(this.url("test/result/history"), request, this.getJsonHeaders());
262
186
  }
263
187
  /**
264
188
  * POST test/result/details
265
- * Returns detailed result data for a specific test attempt.
266
- * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
189
+ * Returns the direct SafeCDX response without an outer APIResponse wrapper.
267
190
  */
268
191
  async getResultDetails(userTestResultId) {
269
- const payload = {
270
- UserTestResultId: userTestResultId
192
+ const request = {
193
+ Data: {
194
+ UserTestResultId: userTestResultId
195
+ }
271
196
  };
272
- return this.http.post(
273
- this.url("test/result/details"),
274
- wrapData(payload),
275
- this.getJsonHeaders()
276
- );
197
+ return this.http.post(this.url("test/result/details"), request, this.getJsonHeaders());
277
198
  }
278
- /**
279
- * POST test/result/pdf
280
- * Generates a PDF report for a specific test result.
281
- */
282
- async getResultPdf(payload) {
283
- const envelope = await this.http.post(
284
- this.url("test/result/pdf"),
285
- wrapData(payload),
286
- this.getJsonHeaders()
287
- );
288
- return assertApiResponse(envelope, "test/result/pdf");
199
+ /** POST test/result/pdf */
200
+ async getResultPdf(userTestResultId) {
201
+ const request = {
202
+ Data: {
203
+ UserTestResultId: userTestResultId
204
+ }
205
+ };
206
+ return this.http.post(this.url("test/result/pdf"), request, this.getJsonHeaders());
289
207
  }
290
208
  /**
291
209
  * POST test/result/image/capture
292
- * Returns the image capture URL for a specific test result.
293
- * NOTE: Returns raw service result — no ApiResponse wrapper on this endpoint.
210
+ * Returns the direct SafeCDX response without an outer APIResponse wrapper.
294
211
  */
295
212
  async getImageCaptureUrl(userTestResultId) {
296
- const payload = {
297
- UserTestResultId: userTestResultId
213
+ const request = {
214
+ Data: {
215
+ UserTestResultId: userTestResultId
216
+ }
298
217
  };
299
- return this.http.post(
300
- this.url("test/result/image/capture"),
301
- wrapData(payload),
302
- this.getJsonHeaders()
303
- );
218
+ return this.http.post(this.url("test/result/image/capture"), request, this.getJsonHeaders());
304
219
  }
305
- /**
306
- * POST test/result/analytics
307
- * Returns analytics data for test results.
308
- */
309
- async getAnalytics(payload = {}) {
310
- const envelope = await this.http.post(
311
- this.url("test/result/analytics"),
312
- wrapData(payload),
313
- this.getJsonHeaders()
314
- );
315
- return assertApiResponse(envelope, "test/result/analytics");
316
- }
317
- // ---------------------------------------------------------------------------
220
+ // -------------------------------------------------------------------------
318
221
  // Resume / Answers / Finalize
319
- // ---------------------------------------------------------------------------
320
- /**
321
- * POST test/resume
322
- * Marks a test attempt as resumed (or not).
323
- */
222
+ // -------------------------------------------------------------------------
223
+ /** POST test/resume */
324
224
  async resumeFlow(userTestResultId, resumed = true) {
325
- const payload = {
326
- UserTestResultId: userTestResultId,
327
- Resumed: resumed
225
+ const request = {
226
+ Data: {
227
+ UserTestResultId: userTestResultId,
228
+ Resumed: resumed
229
+ }
328
230
  };
329
- const envelope = await this.http.post(
330
- this.url("test/resume"),
331
- wrapData(payload),
332
- this.getJsonHeaders()
333
- );
334
- return assertApiResponse(envelope, "test/resume");
231
+ return this.http.post(this.url("test/resume"), request, this.getJsonHeaders());
335
232
  }
336
- /**
337
- * POST test/answers
338
- * Submits analyte answers for a test attempt.
339
- */
340
- async submitAnswers(payload) {
341
- const envelope = await this.http.post(
342
- this.url("test/answers"),
343
- wrapData(payload),
344
- this.getJsonHeaders()
345
- );
346
- return assertApiResponse(envelope, "test/answers");
233
+ /** POST test/answers */
234
+ async submitAnswers(submission) {
235
+ const request = {
236
+ Data: submission
237
+ };
238
+ return this.http.post(this.url("test/answers"), request, this.getJsonHeaders());
347
239
  }
348
- /**
349
- * POST test/finalize
350
- * Finalizes a test attempt with CTA, indication, and decision results.
351
- */
352
- async finalizeTest(payload) {
353
- const envelope = await this.http.post(
354
- this.url("test/finalize"),
355
- wrapData(payload),
356
- this.getJsonHeaders()
357
- );
358
- return assertApiResponse(envelope, "test/finalize");
240
+ /** POST test/finalize */
241
+ async finalizeTest(userTestResultId) {
242
+ const request = {
243
+ Data: {
244
+ UserTestResultId: userTestResultId
245
+ }
246
+ };
247
+ return this.http.post(this.url("test/finalize"), request, this.getJsonHeaders());
248
+ }
249
+ // -------------------------------------------------------------------------
250
+ // Private Helpers
251
+ // -------------------------------------------------------------------------
252
+ getAuthHeaders() {
253
+ return {
254
+ ...this.loginClient.getAuthHeader(),
255
+ ...this.getApiKeyHeader()
256
+ };
257
+ }
258
+ getJsonHeaders() {
259
+ return {
260
+ ...this.getAuthHeaders(),
261
+ "Content-Type": "application/json"
262
+ };
263
+ }
264
+ getApiKeyHeader() {
265
+ if (!this.apiKeyHeaderName || !this.apiKeyValue) {
266
+ return {};
267
+ }
268
+ return {
269
+ [this.apiKeyHeaderName]: this.apiKeyValue
270
+ };
271
+ }
272
+ url(route, query) {
273
+ return buildUrl(this.baseUrl, route, query);
359
274
  }
360
275
  };
361
276
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@healthcloudai/hc-safe-cdx",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "Healthcheck Safe CDX connector.",
5
5
  "author": "Healthcheck Systems Inc",
6
6
  "license": "MIT",