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