@ovok/core 0.4.25 → 0.4.27
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/client/auth/account-storage.d.ts +5 -0
- package/dist/client/auth/account-storage.js +29 -0
- package/dist/client/auth/methods/logout.d.ts +1 -1
- package/dist/client/auth/methods/logout.js +7 -1
- package/dist/client/ovok-client.d.ts +4 -1
- package/dist/client/ovok-client.js +104 -28
- package/dist/conformance/capability-requirements.js +1 -1
- package/dist/hooks/observation-hooks.js +12 -16
- package/package.json +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { IClientStorage } from "@medplum/core";
|
|
2
|
+
export declare const LOGINS_STORAGE_KEY = "logins";
|
|
3
|
+
export declare const EXPIRED_ACCOUNTS_STORAGE_KEY = "ovok.core.expired-accounts";
|
|
4
|
+
export declare const setStoredAccountExpired: (storage: IClientStorage, accountId: string, expired: boolean) => void;
|
|
5
|
+
export declare const removeStoredAccount: (storage: IClientStorage, accountId: string) => void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removeStoredAccount = exports.setStoredAccountExpired = exports.EXPIRED_ACCOUNTS_STORAGE_KEY = exports.LOGINS_STORAGE_KEY = void 0;
|
|
4
|
+
exports.LOGINS_STORAGE_KEY = "logins";
|
|
5
|
+
exports.EXPIRED_ACCOUNTS_STORAGE_KEY = "ovok.core.expired-accounts";
|
|
6
|
+
const setStoredAccountExpired = (storage, accountId, expired) => {
|
|
7
|
+
var _a;
|
|
8
|
+
const expiredAccountIds = new Set((_a = storage.getObject(exports.EXPIRED_ACCOUNTS_STORAGE_KEY)) !== null && _a !== void 0 ? _a : []);
|
|
9
|
+
if (expired) {
|
|
10
|
+
expiredAccountIds.add(accountId);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
expiredAccountIds.delete(accountId);
|
|
14
|
+
}
|
|
15
|
+
if (expiredAccountIds.size > 0) {
|
|
16
|
+
storage.setObject(exports.EXPIRED_ACCOUNTS_STORAGE_KEY, Array.from(expiredAccountIds));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
storage.setString(exports.EXPIRED_ACCOUNTS_STORAGE_KEY, undefined);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
exports.setStoredAccountExpired = setStoredAccountExpired;
|
|
23
|
+
const removeStoredAccount = (storage, accountId) => {
|
|
24
|
+
var _a;
|
|
25
|
+
const logins = (_a = storage.getObject(exports.LOGINS_STORAGE_KEY)) !== null && _a !== void 0 ? _a : [];
|
|
26
|
+
storage.setObject(exports.LOGINS_STORAGE_KEY, logins.filter((login) => { var _a; return ((_a = login.profile) === null || _a === void 0 ? void 0 : _a.reference) !== accountId; }));
|
|
27
|
+
(0, exports.setStoredAccountExpired)(storage, accountId, false);
|
|
28
|
+
};
|
|
29
|
+
exports.removeStoredAccount = removeStoredAccount;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { OvokClient } from "../../ovok-client";
|
|
2
|
-
/** Revokes the current server session and always
|
|
2
|
+
/** Revokes the current server session and always removes its local account. */
|
|
3
3
|
export declare function logout(this: OvokClient): Promise<void>;
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.logout = logout;
|
|
4
|
-
|
|
4
|
+
const account_storage_1 = require("../account-storage");
|
|
5
|
+
/** Revokes the current server session and always removes its local account. */
|
|
5
6
|
async function logout() {
|
|
7
|
+
var _a, _b;
|
|
8
|
+
const accountId = (_b = (_a = this.getActiveLogin()) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
|
|
6
9
|
try {
|
|
7
10
|
await this.delete("auth/session/current");
|
|
8
11
|
}
|
|
9
12
|
finally {
|
|
10
13
|
this.clearActiveLogin();
|
|
14
|
+
if (accountId && this.clientStorage) {
|
|
15
|
+
(0, account_storage_1.removeStoredAccount)(this.clientStorage, accountId);
|
|
16
|
+
}
|
|
11
17
|
}
|
|
12
18
|
}
|
|
@@ -52,7 +52,8 @@ export declare class OvokClient extends MedplumClient {
|
|
|
52
52
|
switchAccount(accountId: string): Promise<void>;
|
|
53
53
|
/**
|
|
54
54
|
* Ends the selected account's current server session and removes its saved login.
|
|
55
|
-
*
|
|
55
|
+
* Accounts with an expired server session are removed locally; network and service
|
|
56
|
+
* failures retain the account so it can be removed after connectivity returns.
|
|
56
57
|
*/
|
|
57
58
|
removeAccount(accountId: string): Promise<void>;
|
|
58
59
|
/** Removes every saved account and expiry marker from client storage. */
|
|
@@ -79,6 +80,8 @@ export declare class OvokClient extends MedplumClient {
|
|
|
79
80
|
private getLogin;
|
|
80
81
|
private getExpiredAccountIds;
|
|
81
82
|
private setAccountExpired;
|
|
83
|
+
private removeSavedAccount;
|
|
84
|
+
private syncSavedAccountProfile;
|
|
82
85
|
private restoreActiveLogin;
|
|
83
86
|
private executeConditionalObservationCreates;
|
|
84
87
|
private executeObservationBundle;
|
|
@@ -38,6 +38,7 @@ exports.OvokClient = exports.MAX_OBSERVATION_BUNDLE_BYTES = exports.MAX_CONDITIO
|
|
|
38
38
|
const core_1 = require("@medplum/core");
|
|
39
39
|
const capability_1 = require("../conformance/capability");
|
|
40
40
|
const aiFhirMethods = __importStar(require("./ai-fhir/methods"));
|
|
41
|
+
const account_storage_1 = require("./auth/account-storage");
|
|
41
42
|
const authMethods = __importStar(require("./auth/methods"));
|
|
42
43
|
const botMethods = __importStar(require("./bot/methods"));
|
|
43
44
|
const changeHistoryMethods = __importStar(require("./change-history/methods"));
|
|
@@ -63,20 +64,51 @@ const slimMethods = __importStar(require("./slim/methods"));
|
|
|
63
64
|
exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
|
|
64
65
|
var observation_bundle_utils_2 = require("./observation/observation-bundle-utils");
|
|
65
66
|
Object.defineProperty(exports, "MAX_OBSERVATION_BUNDLE_BYTES", { enumerable: true, get: function () { return observation_bundle_utils_2.MAX_OBSERVATION_BUNDLE_BYTES; } });
|
|
66
|
-
const
|
|
67
|
-
|
|
67
|
+
const isNetworkError = (error) => {
|
|
68
|
+
if (!(error instanceof Error)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (error.name === "NetworkError" ||
|
|
72
|
+
error.name === "UnknownHostException" ||
|
|
73
|
+
/network request failed|failed to fetch|load failed|unknownhostexception|econn|enotfound/i.test(error.message)) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
return isNetworkError(error.cause);
|
|
77
|
+
};
|
|
68
78
|
class OvokClient extends core_1.MedplumClient {
|
|
69
79
|
/**
|
|
70
80
|
* @param config - The configuration for the client.
|
|
71
81
|
* @param config.socialLoginClientId - The social login client id to use for the client.
|
|
72
82
|
*/
|
|
73
83
|
constructor(config) {
|
|
74
|
-
var _a, _b, _c;
|
|
84
|
+
var _a, _b, _c, _d, _e, _f;
|
|
75
85
|
const storage = (_a = config.storage) !== null && _a !== void 0 ? _a : new core_1.ClientStorage();
|
|
76
|
-
|
|
86
|
+
const fetchImplementation = (_b = config.fetch) !== null && _b !== void 0 ? _b : globalThis.fetch.bind(globalThis);
|
|
87
|
+
const baseUrl = (_c = config.baseUrl) !== null && _c !== void 0 ? _c : "https://api.medplum.com/";
|
|
88
|
+
const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
89
|
+
const tokenUrl = new URL((_d = config.tokenUrl) !== null && _d !== void 0 ? _d : "oauth2/token", normalizedBaseUrl).href;
|
|
90
|
+
super({
|
|
91
|
+
...config,
|
|
92
|
+
storage,
|
|
93
|
+
fetch: async (url, options) => {
|
|
94
|
+
var _a, _b;
|
|
95
|
+
const response = await fetchImplementation(url, options);
|
|
96
|
+
const requestUrl = new URL(url, normalizedBaseUrl);
|
|
97
|
+
const refreshRefused = requestUrl.href === tokenUrl && [400, 401, 403].includes(response.status);
|
|
98
|
+
const sessionEnded = requestUrl.pathname.endsWith("/auth/session/current") && response.status === 401;
|
|
99
|
+
if (refreshRefused || sessionEnded) {
|
|
100
|
+
const accountId = (_b = (_a = storage.getObject("activeLogin")) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
|
|
101
|
+
if (accountId) {
|
|
102
|
+
(0, account_storage_1.setStoredAccountExpired)(storage, accountId, true);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return response;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
77
108
|
this.socialLoginClientId = "";
|
|
78
109
|
this.hasExplicitStorageAdapter = config.storage !== undefined;
|
|
79
|
-
this.socialLoginClientId = (
|
|
110
|
+
this.socialLoginClientId = (_e = config.socialLoginClientId) !== null && _e !== void 0 ? _e : "";
|
|
111
|
+
this.addEventListener("profileRefreshed", () => this.syncSavedAccountProfile());
|
|
80
112
|
this.bindMethods(authMethods);
|
|
81
113
|
this.bindMethods(observationMethods);
|
|
82
114
|
this.bindMethods(questionnaireResponseMethods);
|
|
@@ -101,7 +133,7 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
101
133
|
return (0, errors_1.isSignalsError)(mapped) ? mapped : (0, ovok_error_1.mapOvokApiError)(mapped);
|
|
102
134
|
});
|
|
103
135
|
this.clientStorage = storage;
|
|
104
|
-
this.offlineQueueOptions = (
|
|
136
|
+
this.offlineQueueOptions = (_f = config.offlineQueue) !== null && _f !== void 0 ? _f : {};
|
|
105
137
|
}
|
|
106
138
|
/** Lists saved accounts without exposing their access or refresh tokens. */
|
|
107
139
|
getSavedAccounts() {
|
|
@@ -124,6 +156,7 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
124
156
|
}
|
|
125
157
|
/** Activates a previously saved account without asking for its password again. */
|
|
126
158
|
async switchAccount(accountId) {
|
|
159
|
+
var _a, _b;
|
|
127
160
|
const login = this.getLogin(accountId);
|
|
128
161
|
if (!login) {
|
|
129
162
|
throw new Error(`No saved account found for ${accountId}.`);
|
|
@@ -131,34 +164,68 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
131
164
|
if (this.getExpiredAccountIds().includes(accountId)) {
|
|
132
165
|
throw new Error("This account's session has expired. Sign in again.");
|
|
133
166
|
}
|
|
134
|
-
|
|
167
|
+
const previousLogin = this.getActiveLogin();
|
|
168
|
+
try {
|
|
169
|
+
await this.setActiveLogin(login);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
const activeLogin = this.getActiveLogin();
|
|
173
|
+
if (((_a = activeLogin === null || activeLogin === void 0 ? void 0 : activeLogin.profile) === null || _a === void 0 ? void 0 : _a.reference) === accountId && isNetworkError(error)) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!activeLogin &&
|
|
177
|
+
previousLogin &&
|
|
178
|
+
((_b = previousLogin.profile) === null || _b === void 0 ? void 0 : _b.reference) !== accountId) {
|
|
179
|
+
await this.restoreActiveLogin(previousLogin);
|
|
180
|
+
}
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
135
183
|
}
|
|
136
184
|
/**
|
|
137
185
|
* Ends the selected account's current server session and removes its saved login.
|
|
138
|
-
*
|
|
186
|
+
* Accounts with an expired server session are removed locally; network and service
|
|
187
|
+
* failures retain the account so it can be removed after connectivity returns.
|
|
139
188
|
*/
|
|
140
189
|
async removeAccount(accountId) {
|
|
141
|
-
var _a
|
|
190
|
+
var _a;
|
|
142
191
|
const login = this.getLogin(accountId);
|
|
143
192
|
if (!login) {
|
|
144
193
|
throw new Error(`No saved account found for ${accountId}.`);
|
|
145
194
|
}
|
|
146
195
|
const previousLogin = this.getActiveLogin();
|
|
196
|
+
const previousAccountId = (_a = previousLogin === null || previousLogin === void 0 ? void 0 : previousLogin.profile) === null || _a === void 0 ? void 0 : _a.reference;
|
|
147
197
|
try {
|
|
148
198
|
await this.setActiveLogin(login);
|
|
149
199
|
await this.delete("auth/session/current");
|
|
150
200
|
}
|
|
151
201
|
catch (error) {
|
|
152
|
-
if (
|
|
202
|
+
if (this.getExpiredAccountIds().includes(accountId)) {
|
|
203
|
+
this.removeSavedAccount(accountId);
|
|
204
|
+
if (previousLogin && previousAccountId && previousAccountId !== accountId) {
|
|
205
|
+
await this.restoreActiveLogin(previousLogin);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
this.clearActiveLogin();
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (previousLogin && previousAccountId !== accountId) {
|
|
153
213
|
await this.restoreActiveLogin(previousLogin);
|
|
154
214
|
}
|
|
155
215
|
throw error;
|
|
156
216
|
}
|
|
157
217
|
this.clearActiveLogin();
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
218
|
+
this.removeSavedAccount(accountId);
|
|
219
|
+
const availableAccountIds = new Set(this.getSavedAccounts().filter((account) => !account.expired).map((account) => account.id));
|
|
220
|
+
let nextLogin;
|
|
221
|
+
if (previousAccountId && previousAccountId !== accountId) {
|
|
222
|
+
if (availableAccountIds.has(previousAccountId)) {
|
|
223
|
+
nextLogin = previousLogin;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else if (previousAccountId === accountId) {
|
|
227
|
+
nextLogin = this.getLogins().find((savedLogin) => { var _a, _b; return availableAccountIds.has((_b = (_a = savedLogin.profile) === null || _a === void 0 ? void 0 : _a.reference) !== null && _b !== void 0 ? _b : ""); });
|
|
228
|
+
}
|
|
162
229
|
if (nextLogin) {
|
|
163
230
|
await this.restoreActiveLogin(nextLogin);
|
|
164
231
|
}
|
|
@@ -268,10 +335,10 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
268
335
|
const queuedMeasurements = storage === null || storage === void 0 ? void 0 : storage.getObject(queueKey);
|
|
269
336
|
super.clear();
|
|
270
337
|
if (logins.length > 0) {
|
|
271
|
-
storage === null || storage === void 0 ? void 0 : storage.setObject(LOGINS_STORAGE_KEY, logins);
|
|
338
|
+
storage === null || storage === void 0 ? void 0 : storage.setObject(account_storage_1.LOGINS_STORAGE_KEY, logins);
|
|
272
339
|
}
|
|
273
340
|
if (expiredAccountIds.size > 0) {
|
|
274
|
-
storage === null || storage === void 0 ? void 0 : storage.setObject(EXPIRED_ACCOUNTS_STORAGE_KEY, Array.from(expiredAccountIds));
|
|
341
|
+
storage === null || storage === void 0 ? void 0 : storage.setObject(account_storage_1.EXPIRED_ACCOUNTS_STORAGE_KEY, Array.from(expiredAccountIds));
|
|
275
342
|
}
|
|
276
343
|
if (Array.isArray(queuedMeasurements)) {
|
|
277
344
|
storage === null || storage === void 0 ? void 0 : storage.setObject(queueKey, queuedMeasurements);
|
|
@@ -282,23 +349,32 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
282
349
|
}
|
|
283
350
|
getExpiredAccountIds() {
|
|
284
351
|
var _a, _b;
|
|
285
|
-
return ((_b = (_a = this.clientStorage) === null || _a === void 0 ? void 0 : _a.getObject(EXPIRED_ACCOUNTS_STORAGE_KEY)) !== null && _b !== void 0 ? _b : []);
|
|
352
|
+
return ((_b = (_a = this.clientStorage) === null || _a === void 0 ? void 0 : _a.getObject(account_storage_1.EXPIRED_ACCOUNTS_STORAGE_KEY)) !== null && _b !== void 0 ? _b : []);
|
|
286
353
|
}
|
|
287
354
|
setAccountExpired(accountId, expired) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (expired) {
|
|
291
|
-
expiredAccountIds.add(accountId);
|
|
355
|
+
if (this.clientStorage) {
|
|
356
|
+
(0, account_storage_1.setStoredAccountExpired)(this.clientStorage, accountId, expired);
|
|
292
357
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
(_a = this.clientStorage) === null || _a === void 0 ? void 0 : _a.setObject(EXPIRED_ACCOUNTS_STORAGE_KEY, Array.from(expiredAccountIds));
|
|
358
|
+
}
|
|
359
|
+
removeSavedAccount(accountId) {
|
|
360
|
+
if (this.clientStorage) {
|
|
361
|
+
(0, account_storage_1.removeStoredAccount)(this.clientStorage, accountId);
|
|
298
362
|
}
|
|
299
|
-
|
|
300
|
-
|
|
363
|
+
}
|
|
364
|
+
syncSavedAccountProfile() {
|
|
365
|
+
var _a, _b, _c;
|
|
366
|
+
const login = this.getActiveLogin();
|
|
367
|
+
const profile = this.getProfile();
|
|
368
|
+
const accountId = (_a = login === null || login === void 0 ? void 0 : login.profile) === null || _a === void 0 ? void 0 : _a.reference;
|
|
369
|
+
if (!login || !profile || !accountId) {
|
|
370
|
+
return;
|
|
301
371
|
}
|
|
372
|
+
const updatedLogin = {
|
|
373
|
+
...login,
|
|
374
|
+
profile: { ...login.profile, display: (0, core_1.getDisplayString)(profile) },
|
|
375
|
+
};
|
|
376
|
+
(_b = this.clientStorage) === null || _b === void 0 ? void 0 : _b.setObject("activeLogin", updatedLogin);
|
|
377
|
+
(_c = this.clientStorage) === null || _c === void 0 ? void 0 : _c.setObject(account_storage_1.LOGINS_STORAGE_KEY, this.getLogins().map((savedLogin) => { var _a; return ((_a = savedLogin.profile) === null || _a === void 0 ? void 0 : _a.reference) === accountId ? updatedLogin : savedLogin; }));
|
|
302
378
|
}
|
|
303
379
|
async restoreActiveLogin(login) {
|
|
304
380
|
try {
|
|
@@ -48,7 +48,7 @@ exports.OVOK_CORE_REQUIREMENTS = {
|
|
|
48
48
|
description: "The FHIR surface a server must provide for the @ovok/core client library's own methods to function.",
|
|
49
49
|
software: {
|
|
50
50
|
name: "@ovok/core",
|
|
51
|
-
version: "0.4.
|
|
51
|
+
version: "0.4.27",
|
|
52
52
|
},
|
|
53
53
|
fhirVersion: "4.0.1",
|
|
54
54
|
format: ["json"],
|
|
@@ -45,22 +45,18 @@ const useObservations = ({ types, period, patientId, id, groupBy, timeToleranceM
|
|
|
45
45
|
// A broad search is intentional: one measurement can expand into several
|
|
46
46
|
// observations, and the inverse mapper performs the type-specific grouping.
|
|
47
47
|
const codes = React.useMemo(() => (0, observationsToMeasurements_1.observationCodesForTypes)(types), [types]);
|
|
48
|
-
const query = React.useMemo(() =>
|
|
49
|
-
...(codes ?
|
|
50
|
-
...(patientId ?
|
|
51
|
-
...(id ?
|
|
52
|
-
...((period === null || period === void 0 ? void 0 : period.start)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
...(count !== undefined ? { _count: count } : {}),
|
|
61
|
-
...(sort ? { _sort: sort } : {}),
|
|
62
|
-
...(elements ? { _elements: Array.isArray(elements) ? elements.join(",") : elements } : {}),
|
|
63
|
-
}), [codes, count, elements, id, patientId, period === null || period === void 0 ? void 0 : period.end, period === null || period === void 0 ? void 0 : period.start, sort]);
|
|
48
|
+
const query = React.useMemo(() => [
|
|
49
|
+
...(codes ? [["code", codes]] : []),
|
|
50
|
+
...(patientId ? [["subject", `Patient/${patientId}`]] : []),
|
|
51
|
+
...(id ? [["_id", id]] : []),
|
|
52
|
+
...((period === null || period === void 0 ? void 0 : period.start) ? [["date", `ge${period.start.toISOString()}`]] : []),
|
|
53
|
+
...((period === null || period === void 0 ? void 0 : period.end) ? [["date", `le${period.end.toISOString()}`]] : []),
|
|
54
|
+
...(count !== undefined ? [["_count", String(count)]] : []),
|
|
55
|
+
...(sort ? [["_sort", sort]] : []),
|
|
56
|
+
...(elements
|
|
57
|
+
? [["_elements", typeof elements === "string" ? elements : elements.join(",")]]
|
|
58
|
+
: []),
|
|
59
|
+
], [codes, count, elements, id, patientId, period === null || period === void 0 ? void 0 : period.end, period === null || period === void 0 ? void 0 : period.start, sort]);
|
|
64
60
|
const client = (0, index_1.useClient)();
|
|
65
61
|
const load = React.useCallback(() => client.searchResources("Observation", query), [client, query]);
|
|
66
62
|
const { data: observations = [], reload: refetch, loading, error } = (0, api_hooks_1.useOvokRequest)(load);
|