@ovok/core 0.4.22 → 0.4.23
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/types/Account.d.ts +10 -0
- package/dist/client/auth/types/Account.js +2 -0
- package/dist/client/ovok-client.d.ts +22 -2
- package/dist/client/ovok-client.js +137 -9
- package/dist/conformance/capability-requirements.js +1 -1
- package/dist/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ProfileResource } from "@medplum/core";
|
|
2
|
+
import { Project, Reference } from "@medplum/fhirtypes";
|
|
3
|
+
/** Safe account metadata for account pickers; authentication tokens are omitted. */
|
|
4
|
+
export interface OvokSavedAccount {
|
|
5
|
+
id: string;
|
|
6
|
+
profile: Reference<ProfileResource>;
|
|
7
|
+
project: Reference<Project>;
|
|
8
|
+
active: boolean;
|
|
9
|
+
expired: boolean;
|
|
10
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { IClientStorage, MedplumClient, MedplumClientOptions, SubscriptionManager } from "@medplum/core";
|
|
1
|
+
import { IClientStorage, LoginState, MedplumClient, MedplumClientOptions, SubscriptionManager } from "@medplum/core";
|
|
2
2
|
import { Bundle, CapabilityStatement, Observation } from "@medplum/fhirtypes";
|
|
3
3
|
import { OvokPublicApiEndpoint } from "../conformance/capability";
|
|
4
4
|
import * as aiFhirMethods from "./ai-fhir/methods";
|
|
5
5
|
import * as authMethods from "./auth/methods";
|
|
6
|
+
import { OvokSavedAccount } from "./auth/types/Account";
|
|
6
7
|
import * as botMethods from "./bot/methods";
|
|
7
8
|
import * as changeHistoryMethods from "./change-history/methods";
|
|
8
9
|
import * as aiMethods from "./chat/ai/methods";
|
|
@@ -34,6 +35,7 @@ export type OvokFhirVersion = "R4" | "R5";
|
|
|
34
35
|
export declare class OvokClient extends MedplumClient {
|
|
35
36
|
protected socialLoginClientId: string;
|
|
36
37
|
clientStorage: IClientStorage | undefined;
|
|
38
|
+
private readonly hasExplicitStorageAdapter;
|
|
37
39
|
private readonly offlineQueueOptions;
|
|
38
40
|
private offlineMeasurementQueue;
|
|
39
41
|
/**
|
|
@@ -44,6 +46,19 @@ export declare class OvokClient extends MedplumClient {
|
|
|
44
46
|
socialLoginClientId?: string;
|
|
45
47
|
offlineQueue?: OfflineMeasurementQueueOptions;
|
|
46
48
|
});
|
|
49
|
+
/** Lists saved accounts without exposing their access or refresh tokens. */
|
|
50
|
+
getSavedAccounts(): OvokSavedAccount[];
|
|
51
|
+
/** Activates a previously saved account without asking for its password again. */
|
|
52
|
+
switchAccount(accountId: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Ends the selected account's current server session and removes its saved login.
|
|
55
|
+
* The account is retained locally if the server cannot confirm the logout.
|
|
56
|
+
*/
|
|
57
|
+
removeAccount(accountId: string): Promise<void>;
|
|
58
|
+
/** Removes every saved account and expiry marker from client storage. */
|
|
59
|
+
clearAllAccounts(): void;
|
|
60
|
+
/** Reauthentication clears the expired marker for the account that signed in. */
|
|
61
|
+
setActiveLogin(login: LoginState): Promise<void>;
|
|
47
62
|
/** Saves a measurement immediately, or durably queues it when the opt-in queue is enabled. */
|
|
48
63
|
saveMeasurement(params: GenerateObservationBodyParams): Promise<SaveObservationsResult>;
|
|
49
64
|
/** Reads Ovok's composed FHIR and public-route capability statement. */
|
|
@@ -57,9 +72,14 @@ export declare class OvokClient extends MedplumClient {
|
|
|
57
72
|
private getOfflineMeasurementQueue;
|
|
58
73
|
/**
|
|
59
74
|
* Medplum clears its entire client storage after an unrecoverable 401. Keep
|
|
60
|
-
*
|
|
75
|
+
* saved accounts (marking only the active one expired) and queued measurements.
|
|
76
|
+
* Use clearAllAccounts() when the caller intends to remove all saved accounts.
|
|
61
77
|
*/
|
|
62
78
|
clear(): void;
|
|
79
|
+
private getLogin;
|
|
80
|
+
private getExpiredAccountIds;
|
|
81
|
+
private setAccountExpired;
|
|
82
|
+
private restoreActiveLogin;
|
|
63
83
|
private executeConditionalObservationCreates;
|
|
64
84
|
private executeObservationBundle;
|
|
65
85
|
private bindMethods;
|
|
@@ -63,16 +63,20 @@ const slimMethods = __importStar(require("./slim/methods"));
|
|
|
63
63
|
exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
|
|
64
64
|
var observation_bundle_utils_2 = require("./observation/observation-bundle-utils");
|
|
65
65
|
Object.defineProperty(exports, "MAX_OBSERVATION_BUNDLE_BYTES", { enumerable: true, get: function () { return observation_bundle_utils_2.MAX_OBSERVATION_BUNDLE_BYTES; } });
|
|
66
|
+
const LOGINS_STORAGE_KEY = "logins";
|
|
67
|
+
const EXPIRED_ACCOUNTS_STORAGE_KEY = "ovok.core.expired-accounts";
|
|
66
68
|
class OvokClient extends core_1.MedplumClient {
|
|
67
69
|
/**
|
|
68
70
|
* @param config - The configuration for the client.
|
|
69
71
|
* @param config.socialLoginClientId - The social login client id to use for the client.
|
|
70
72
|
*/
|
|
71
73
|
constructor(config) {
|
|
72
|
-
var _a, _b;
|
|
73
|
-
|
|
74
|
+
var _a, _b, _c;
|
|
75
|
+
const storage = (_a = config.storage) !== null && _a !== void 0 ? _a : new core_1.ClientStorage();
|
|
76
|
+
super({ ...config, storage });
|
|
74
77
|
this.socialLoginClientId = "";
|
|
75
|
-
this.
|
|
78
|
+
this.hasExplicitStorageAdapter = config.storage !== undefined;
|
|
79
|
+
this.socialLoginClientId = (_b = config.socialLoginClientId) !== null && _b !== void 0 ? _b : "";
|
|
76
80
|
this.bindMethods(authMethods);
|
|
77
81
|
this.bindMethods(observationMethods);
|
|
78
82
|
this.bindMethods(questionnaireResponseMethods);
|
|
@@ -96,8 +100,88 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
96
100
|
});
|
|
97
101
|
return (0, errors_1.isSignalsError)(mapped) ? mapped : (0, ovok_error_1.mapOvokApiError)(mapped);
|
|
98
102
|
});
|
|
99
|
-
this.clientStorage =
|
|
100
|
-
this.offlineQueueOptions = (
|
|
103
|
+
this.clientStorage = storage;
|
|
104
|
+
this.offlineQueueOptions = (_c = config.offlineQueue) !== null && _c !== void 0 ? _c : {};
|
|
105
|
+
}
|
|
106
|
+
/** Lists saved accounts without exposing their access or refresh tokens. */
|
|
107
|
+
getSavedAccounts() {
|
|
108
|
+
var _a, _b;
|
|
109
|
+
const activeAccountId = (_b = (_a = this.getActiveLogin()) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
|
|
110
|
+
const expiredAccountIds = new Set(this.getExpiredAccountIds());
|
|
111
|
+
return this.getLogins().flatMap((login) => {
|
|
112
|
+
var _a;
|
|
113
|
+
const id = (_a = login.profile) === null || _a === void 0 ? void 0 : _a.reference;
|
|
114
|
+
return id
|
|
115
|
+
? [{
|
|
116
|
+
id,
|
|
117
|
+
profile: login.profile,
|
|
118
|
+
project: login.project,
|
|
119
|
+
active: id === activeAccountId,
|
|
120
|
+
expired: expiredAccountIds.has(id),
|
|
121
|
+
}]
|
|
122
|
+
: [];
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** Activates a previously saved account without asking for its password again. */
|
|
126
|
+
async switchAccount(accountId) {
|
|
127
|
+
const login = this.getLogin(accountId);
|
|
128
|
+
if (!login) {
|
|
129
|
+
throw new Error(`No saved account found for ${accountId}.`);
|
|
130
|
+
}
|
|
131
|
+
if (this.getExpiredAccountIds().includes(accountId)) {
|
|
132
|
+
throw new Error("This account's session has expired. Sign in again.");
|
|
133
|
+
}
|
|
134
|
+
await this.setActiveLogin(login);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Ends the selected account's current server session and removes its saved login.
|
|
138
|
+
* The account is retained locally if the server cannot confirm the logout.
|
|
139
|
+
*/
|
|
140
|
+
async removeAccount(accountId) {
|
|
141
|
+
var _a, _b;
|
|
142
|
+
const login = this.getLogin(accountId);
|
|
143
|
+
if (!login) {
|
|
144
|
+
throw new Error(`No saved account found for ${accountId}.`);
|
|
145
|
+
}
|
|
146
|
+
const previousLogin = this.getActiveLogin();
|
|
147
|
+
try {
|
|
148
|
+
await this.setActiveLogin(login);
|
|
149
|
+
await this.delete("auth/session/current");
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (previousLogin && previousLogin.profile.reference !== accountId) {
|
|
153
|
+
await this.restoreActiveLogin(previousLogin);
|
|
154
|
+
}
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
this.clearActiveLogin();
|
|
158
|
+
const remainingLogins = this.getLogins().filter((savedLogin) => savedLogin.profile.reference !== accountId);
|
|
159
|
+
(_a = this.clientStorage) === null || _a === void 0 ? void 0 : _a.setObject(LOGINS_STORAGE_KEY, remainingLogins);
|
|
160
|
+
this.setAccountExpired(accountId, false);
|
|
161
|
+
const nextLogin = (_b = ((previousLogin === null || previousLogin === void 0 ? void 0 : previousLogin.profile.reference) !== accountId ? previousLogin : undefined)) !== null && _b !== void 0 ? _b : remainingLogins[0];
|
|
162
|
+
if (nextLogin) {
|
|
163
|
+
await this.restoreActiveLogin(nextLogin);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Removes every saved account and expiry marker from client storage. */
|
|
167
|
+
clearAllAccounts() {
|
|
168
|
+
var _a;
|
|
169
|
+
const storage = this.clientStorage;
|
|
170
|
+
const queueKey = (_a = this.offlineQueueOptions.storageKey) !== null && _a !== void 0 ? _a : offline_1.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
|
|
171
|
+
const queuedMeasurements = storage === null || storage === void 0 ? void 0 : storage.getObject(queueKey);
|
|
172
|
+
super.clear();
|
|
173
|
+
if (Array.isArray(queuedMeasurements)) {
|
|
174
|
+
storage === null || storage === void 0 ? void 0 : storage.setObject(queueKey, queuedMeasurements);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** Reauthentication clears the expired marker for the account that signed in. */
|
|
178
|
+
async setActiveLogin(login) {
|
|
179
|
+
var _a;
|
|
180
|
+
await super.setActiveLogin(login);
|
|
181
|
+
const accountId = (_a = login.profile) === null || _a === void 0 ? void 0 : _a.reference;
|
|
182
|
+
if (accountId) {
|
|
183
|
+
this.setAccountExpired(accountId, false);
|
|
184
|
+
}
|
|
101
185
|
}
|
|
102
186
|
/** Saves a measurement immediately, or durably queues it when the opt-in queue is enabled. */
|
|
103
187
|
async saveMeasurement(params) {
|
|
@@ -145,7 +229,7 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
145
229
|
if (this.offlineQueueOptions.enabled !== true) {
|
|
146
230
|
return undefined;
|
|
147
231
|
}
|
|
148
|
-
if (!this.clientStorage) {
|
|
232
|
+
if (!this.clientStorage || !this.hasExplicitStorageAdapter) {
|
|
149
233
|
throw new Error("Offline measurement queue requires a durable client storage adapter.");
|
|
150
234
|
}
|
|
151
235
|
(_a = this.offlineMeasurementQueue) !== null && _a !== void 0 ? _a : (this.offlineMeasurementQueue = new offline_1.OfflineMeasurementQueue(this.clientStorage, (bundle) => this.executeObservationBundle(bundle), {
|
|
@@ -168,18 +252,62 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
168
252
|
}
|
|
169
253
|
/**
|
|
170
254
|
* Medplum clears its entire client storage after an unrecoverable 401. Keep
|
|
171
|
-
*
|
|
255
|
+
* saved accounts (marking only the active one expired) and queued measurements.
|
|
256
|
+
* Use clearAllAccounts() when the caller intends to remove all saved accounts.
|
|
172
257
|
*/
|
|
173
258
|
clear() {
|
|
174
|
-
var _a;
|
|
259
|
+
var _a, _b, _c;
|
|
175
260
|
const storage = this.clientStorage;
|
|
176
|
-
const
|
|
261
|
+
const logins = this.getLogins();
|
|
262
|
+
const activeAccountId = (_b = (_a = this.getActiveLogin()) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.reference;
|
|
263
|
+
const expiredAccountIds = new Set(this.getExpiredAccountIds());
|
|
264
|
+
if (activeAccountId) {
|
|
265
|
+
expiredAccountIds.add(activeAccountId);
|
|
266
|
+
}
|
|
267
|
+
const queueKey = (_c = this.offlineQueueOptions.storageKey) !== null && _c !== void 0 ? _c : offline_1.OFFLINE_MEASUREMENT_QUEUE_STORAGE_KEY;
|
|
177
268
|
const queuedMeasurements = storage === null || storage === void 0 ? void 0 : storage.getObject(queueKey);
|
|
178
269
|
super.clear();
|
|
270
|
+
if (logins.length > 0) {
|
|
271
|
+
storage === null || storage === void 0 ? void 0 : storage.setObject(LOGINS_STORAGE_KEY, logins);
|
|
272
|
+
}
|
|
273
|
+
if (expiredAccountIds.size > 0) {
|
|
274
|
+
storage === null || storage === void 0 ? void 0 : storage.setObject(EXPIRED_ACCOUNTS_STORAGE_KEY, Array.from(expiredAccountIds));
|
|
275
|
+
}
|
|
179
276
|
if (Array.isArray(queuedMeasurements)) {
|
|
180
277
|
storage === null || storage === void 0 ? void 0 : storage.setObject(queueKey, queuedMeasurements);
|
|
181
278
|
}
|
|
182
279
|
}
|
|
280
|
+
getLogin(accountId) {
|
|
281
|
+
return this.getLogins().find((login) => { var _a; return ((_a = login.profile) === null || _a === void 0 ? void 0 : _a.reference) === accountId; });
|
|
282
|
+
}
|
|
283
|
+
getExpiredAccountIds() {
|
|
284
|
+
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 : []);
|
|
286
|
+
}
|
|
287
|
+
setAccountExpired(accountId, expired) {
|
|
288
|
+
var _a, _b;
|
|
289
|
+
const expiredAccountIds = new Set(this.getExpiredAccountIds());
|
|
290
|
+
if (expired) {
|
|
291
|
+
expiredAccountIds.add(accountId);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
expiredAccountIds.delete(accountId);
|
|
295
|
+
}
|
|
296
|
+
if (expiredAccountIds.size > 0) {
|
|
297
|
+
(_a = this.clientStorage) === null || _a === void 0 ? void 0 : _a.setObject(EXPIRED_ACCOUNTS_STORAGE_KEY, Array.from(expiredAccountIds));
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
(_b = this.clientStorage) === null || _b === void 0 ? void 0 : _b.setString(EXPIRED_ACCOUNTS_STORAGE_KEY, undefined);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async restoreActiveLogin(login) {
|
|
304
|
+
try {
|
|
305
|
+
await this.setActiveLogin(login);
|
|
306
|
+
}
|
|
307
|
+
catch (_a) {
|
|
308
|
+
// Keep the saved login available for a later retry if restoration is offline.
|
|
309
|
+
}
|
|
310
|
+
}
|
|
183
311
|
async executeConditionalObservationCreates(observations) {
|
|
184
312
|
const entries = observations.map(offline_1.entryForObservation);
|
|
185
313
|
return this.executeObservationBundle({
|
|
@@ -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.23",
|
|
52
52
|
},
|
|
53
53
|
fhirVersion: "4.0.1",
|
|
54
54
|
format: ["json"],
|
package/dist/index.d.ts
CHANGED
|
@@ -929,6 +929,7 @@ export * from "./client/errors/ovok-error";
|
|
|
929
929
|
export * from "./client/auth/errors";
|
|
930
930
|
export type { RegisterBody } from "./client/auth/types/RegisterBody";
|
|
931
931
|
export type { RegisterPractitionerBody } from "./client/auth/types/RegisterPractitionerBody";
|
|
932
|
+
export type { OvokSavedAccount } from "./client/auth/types/Account";
|
|
932
933
|
export { MAX_CONDITIONAL_OBSERVATION_ENTRIES, MAX_OBSERVATION_BUNDLE_BYTES, } from "./client/ovok-client";
|
|
933
934
|
export type { SaveObservationsResult } from "./client/ovok-client";
|
|
934
935
|
export * from "./client/offline";
|