@ovok/core 0.4.32 → 0.4.33
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/errors.d.ts +6 -0
- package/dist/client/auth/errors.js +12 -1
- package/dist/client/invites/methods.js +8 -2
- package/dist/client/offline/offline-measurement-queue.d.ts +6 -0
- package/dist/client/offline/offline-measurement-queue.js +40 -25
- package/dist/client/ovok-client.d.ts +9 -1
- package/dist/client/ovok-client.js +88 -22
- package/dist/client/patient-sharing/methods.js +5 -1
- package/dist/conformance/capability-requirements.js +1 -1
- package/dist/hooks/invites-hooks.js +7 -1
- package/dist/hooks/notification-invalidation.d.ts +2 -0
- package/dist/hooks/patient-sharing-hooks.js +17 -3
- package/package.json +1 -1
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export declare const REGISTRATION_FAILED_MESSAGE = "Registration failed.";
|
|
2
|
+
export type OvokAccountErrorCode = "account_not_found" | "account_expired";
|
|
3
|
+
/** A safe, stable account error that does not include the account reference. */
|
|
4
|
+
export declare class OvokAccountError extends Error {
|
|
5
|
+
readonly code: OvokAccountErrorCode;
|
|
6
|
+
constructor(code: OvokAccountErrorCode);
|
|
7
|
+
}
|
|
2
8
|
/** Returns true when signup was refused because the address already has an account. */
|
|
3
9
|
export declare function isRegistrationFailed(error: unknown): boolean;
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.REGISTRATION_FAILED_MESSAGE = void 0;
|
|
3
|
+
exports.OvokAccountError = exports.REGISTRATION_FAILED_MESSAGE = void 0;
|
|
4
4
|
exports.isRegistrationFailed = isRegistrationFailed;
|
|
5
5
|
const ovok_error_1 = require("../errors/ovok-error");
|
|
6
6
|
exports.REGISTRATION_FAILED_MESSAGE = "Registration failed.";
|
|
7
|
+
/** A safe, stable account error that does not include the account reference. */
|
|
8
|
+
class OvokAccountError extends Error {
|
|
9
|
+
constructor(code) {
|
|
10
|
+
super(code === "account_not_found"
|
|
11
|
+
? "Saved account was not found."
|
|
12
|
+
: "This account's session has expired. Sign in again.");
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.name = "OvokAccountError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.OvokAccountError = OvokAccountError;
|
|
7
18
|
/** Returns true when signup was refused because the address already has an account. */
|
|
8
19
|
function isRegistrationFailed(error) {
|
|
9
20
|
const details = (0, ovok_error_1.readOvokError)(error);
|
|
@@ -21,10 +21,16 @@ function acceptInvite(body, requestOptions) {
|
|
|
21
21
|
var _a;
|
|
22
22
|
const resourceType = (_a = this.getProfile()) === null || _a === void 0 ? void 0 : _a.resourceType;
|
|
23
23
|
if (resourceType === "Practitioner") {
|
|
24
|
-
return this.acceptPractitionerInvite(body, ...(requestOptions === undefined ? [] : [requestOptions]))
|
|
24
|
+
return this.acceptPractitionerInvite(body, ...(requestOptions === undefined ? [] : [requestOptions])).then((result) => {
|
|
25
|
+
this.invalidateUrl("/v1/patient-sharing/shares");
|
|
26
|
+
return result;
|
|
27
|
+
});
|
|
25
28
|
}
|
|
26
29
|
if (resourceType === "Patient") {
|
|
27
|
-
return this.acceptPatientInvite(body, ...(requestOptions === undefined ? [] : [requestOptions]))
|
|
30
|
+
return this.acceptPatientInvite(body, ...(requestOptions === undefined ? [] : [requestOptions])).then((result) => {
|
|
31
|
+
this.invalidateUrl("/v1/patient-sharing/shares");
|
|
32
|
+
return result;
|
|
33
|
+
});
|
|
28
34
|
}
|
|
29
35
|
throw new Error("Accepting an invite requires a signed-in Patient or Practitioner profile.");
|
|
30
36
|
}
|
|
@@ -22,6 +22,12 @@ export type OfflineMeasurementFlushResult = {
|
|
|
22
22
|
queued: number;
|
|
23
23
|
exhausted: number;
|
|
24
24
|
responses: Record<string, Bundle>;
|
|
25
|
+
failures: Record<string, OfflineMeasurementFailure>;
|
|
26
|
+
};
|
|
27
|
+
export type OfflineMeasurementFailure = {
|
|
28
|
+
name: string;
|
|
29
|
+
code?: string;
|
|
30
|
+
status?: number;
|
|
25
31
|
};
|
|
26
32
|
type MeasurementTransport = (bundle: Bundle, requestOptions?: MedplumRequestOptions) => Promise<Bundle>;
|
|
27
33
|
declare const stableMeasurementId: (observation: Observation) => string;
|
|
@@ -81,29 +81,32 @@ class OfflineMeasurementQueue {
|
|
|
81
81
|
this.storage = storage;
|
|
82
82
|
this.transport = transport;
|
|
83
83
|
this.operation = Promise.resolve();
|
|
84
|
-
this.enqueueAndFlush = (observations, requestOptions) =>
|
|
85
|
-
var _a
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
84
|
+
this.enqueueAndFlush = (observations, requestOptions) => {
|
|
85
|
+
var _a;
|
|
86
|
+
const ownerKey = (_a = this.getOwnerKey) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
87
|
+
return this.runExclusive(async () => {
|
|
88
|
+
var _a, _b;
|
|
89
|
+
await ((_b = (_a = this.storage).getInitPromise) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
90
|
+
const queue = this.readQueue();
|
|
91
|
+
const entries = observations.map(entryForObservation);
|
|
92
|
+
if (this.getOwnerKey && !ownerKey) {
|
|
93
|
+
throw new Error("Offline measurements require an active account to prevent cross-account delivery.");
|
|
94
|
+
}
|
|
95
|
+
const id = stableValueId({ ownerKey, entries });
|
|
96
|
+
if (!queue.some((item) => item.id === id)) {
|
|
97
|
+
queue.push({
|
|
98
|
+
id,
|
|
99
|
+
ownerKey,
|
|
100
|
+
queuedAt: new Date().toISOString(),
|
|
101
|
+
attempt: 0,
|
|
102
|
+
nextAttemptAt: 0,
|
|
103
|
+
entries,
|
|
104
|
+
});
|
|
105
|
+
this.writeQueue(queue);
|
|
106
|
+
}
|
|
107
|
+
return { id, flush: await this.flushQueue(queue, {}, ...(requestOptions === undefined ? [] : [requestOptions])) };
|
|
108
|
+
});
|
|
109
|
+
};
|
|
107
110
|
this.flush = (options = {}, requestOptions) => this.runExclusive(async () => {
|
|
108
111
|
var _a, _b;
|
|
109
112
|
await ((_b = (_a = this.storage).getInitPromise) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
@@ -142,11 +145,14 @@ class OfflineMeasurementQueue {
|
|
|
142
145
|
queued: 0,
|
|
143
146
|
exhausted: 0,
|
|
144
147
|
responses: {},
|
|
148
|
+
failures: {},
|
|
145
149
|
};
|
|
146
150
|
const now = Date.now();
|
|
147
|
-
const ownerKey = (_a = this.getOwnerKey) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
148
151
|
let changed = false;
|
|
149
152
|
for (const item of [...queue]) {
|
|
153
|
+
if (item.lastFailure) {
|
|
154
|
+
result.failures[item.id] = item.lastFailure;
|
|
155
|
+
}
|
|
150
156
|
if (item.exhausted && options.resetExhausted) {
|
|
151
157
|
item.attempt = 0;
|
|
152
158
|
item.exhausted = false;
|
|
@@ -163,6 +169,7 @@ class OfflineMeasurementQueue {
|
|
|
163
169
|
}
|
|
164
170
|
continue;
|
|
165
171
|
}
|
|
172
|
+
const ownerKey = (_a = this.getOwnerKey) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
166
173
|
if (this.getOwnerKey && (!ownerKey || item.ownerKey !== ownerKey)) {
|
|
167
174
|
result.queued++;
|
|
168
175
|
continue;
|
|
@@ -183,7 +190,7 @@ class OfflineMeasurementQueue {
|
|
|
183
190
|
result.responses[item.id] = response;
|
|
184
191
|
changed = true;
|
|
185
192
|
}
|
|
186
|
-
catch (
|
|
193
|
+
catch (error) {
|
|
187
194
|
item.attempt = attempt;
|
|
188
195
|
item.exhausted = attempt >= this.maxRetries;
|
|
189
196
|
item.nextAttemptAt = item.exhausted
|
|
@@ -191,6 +198,14 @@ class OfflineMeasurementQueue {
|
|
|
191
198
|
: Date.now() +
|
|
192
199
|
Math.min(this.maxRetryBackoffMs, this.retryBackoffMs * 2 ** (attempt - 1));
|
|
193
200
|
changed = true;
|
|
201
|
+
const failure = error;
|
|
202
|
+
const lastFailure = {
|
|
203
|
+
name: typeof (failure === null || failure === void 0 ? void 0 : failure.name) === "string" ? failure.name : "Error",
|
|
204
|
+
...(typeof (failure === null || failure === void 0 ? void 0 : failure.code) === "string" ? { code: failure.code } : {}),
|
|
205
|
+
...(typeof (failure === null || failure === void 0 ? void 0 : failure.status) === "number" ? { status: failure.status } : {}),
|
|
206
|
+
};
|
|
207
|
+
item.lastFailure = lastFailure;
|
|
208
|
+
result.failures[item.id] = lastFailure;
|
|
194
209
|
if (item.exhausted) {
|
|
195
210
|
result.exhausted++;
|
|
196
211
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IClientStorage, LoginState, MedplumClient, MedplumClientOptions, MedplumRequestOptions, SubscriptionManager } from "@medplum/core";
|
|
1
|
+
import { IClientStorage, LoginState, MedplumClient, MedplumClientOptions, MedplumRequestOptions, ReadablePromise, 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";
|
|
@@ -50,18 +50,25 @@ export type OvokClientOptions = Omit<MedplumClientOptions, "onUnauthenticated">
|
|
|
50
50
|
socialLoginClientId?: string;
|
|
51
51
|
requestTimeoutMs?: number;
|
|
52
52
|
offlineQueue?: OfflineMeasurementQueueOptions;
|
|
53
|
+
/** Keep the saved login when a refresh cannot reach the server. */
|
|
54
|
+
keepSessionOnRefreshUnavailable?: boolean;
|
|
53
55
|
};
|
|
54
56
|
export declare class OvokClient extends MedplumClient {
|
|
55
57
|
protected socialLoginClientId: string;
|
|
56
58
|
clientStorage: IClientStorage | undefined;
|
|
57
59
|
private readonly hasExplicitStorageAdapter;
|
|
58
60
|
private readonly offlineQueueOptions;
|
|
61
|
+
private readonly clientOptions;
|
|
62
|
+
private readonly keepSessionOnRefreshUnavailable;
|
|
63
|
+
private readonly refreshState;
|
|
59
64
|
private offlineMeasurementQueue;
|
|
60
65
|
/**
|
|
61
66
|
* @param config - The configuration for the client.
|
|
62
67
|
* @param config.socialLoginClientId - The social login client id to use for the client.
|
|
63
68
|
*/
|
|
64
69
|
constructor(config: OvokClientOptions);
|
|
70
|
+
get<T = unknown>(url: URL | string, options?: MedplumRequestOptions): ReadablePromise<T>;
|
|
71
|
+
refreshIfExpired(gracePeriod?: number): Promise<void>;
|
|
65
72
|
post<T = unknown>(url: URL | string, body?: unknown, contentType?: string, options?: MedplumRequestOptions): Promise<T>;
|
|
66
73
|
/** Lists saved accounts without exposing their access or refresh tokens. */
|
|
67
74
|
getSavedAccounts(): OvokSavedAccount[];
|
|
@@ -96,6 +103,7 @@ export declare class OvokClient extends MedplumClient {
|
|
|
96
103
|
clear(): void;
|
|
97
104
|
private getLogin;
|
|
98
105
|
private getExpiredAccountIds;
|
|
106
|
+
private createAccountSessionClient;
|
|
99
107
|
private setAccountExpired;
|
|
100
108
|
private removeSavedAccount;
|
|
101
109
|
private syncSavedAccountProfile;
|
|
@@ -39,6 +39,7 @@ const core_1 = require("@medplum/core");
|
|
|
39
39
|
const capability_1 = require("../conformance/capability");
|
|
40
40
|
const aiFhirMethods = __importStar(require("./ai-fhir/methods"));
|
|
41
41
|
const account_storage_1 = require("./auth/account-storage");
|
|
42
|
+
const errors_1 = require("./auth/errors");
|
|
42
43
|
const authMethods = __importStar(require("./auth/methods"));
|
|
43
44
|
const botMethods = __importStar(require("./bot/methods"));
|
|
44
45
|
const changeHistoryMethods = __importStar(require("./change-history/methods"));
|
|
@@ -59,7 +60,7 @@ const patientMethods = __importStar(require("./patient/methods"));
|
|
|
59
60
|
const patientSharingMethods = __importStar(require("./patient-sharing/methods"));
|
|
60
61
|
const platformMethods = __importStar(require("./platform/methods"));
|
|
61
62
|
const questionnaireResponseMethods = __importStar(require("./questionnaire-response/methods"));
|
|
62
|
-
const
|
|
63
|
+
const errors_2 = require("./signals/errors");
|
|
63
64
|
const signalsMethods = __importStar(require("./signals/methods"));
|
|
64
65
|
const slimMethods = __importStar(require("./slim/methods"));
|
|
65
66
|
exports.MAX_CONDITIONAL_OBSERVATION_ENTRIES = 8;
|
|
@@ -217,6 +218,7 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
217
218
|
let clearActiveLogin = () => undefined;
|
|
218
219
|
let terminalRefreshToken;
|
|
219
220
|
let terminalRefreshError;
|
|
221
|
+
const refreshState = { unavailable: false };
|
|
220
222
|
const isTokenRefresh = (url, options) => new URL(url, normalizedBaseUrl).href === tokenUrl &&
|
|
221
223
|
(options === null || options === void 0 ? void 0 : options.method) === "POST" &&
|
|
222
224
|
typeof options.body === "string" &&
|
|
@@ -338,6 +340,9 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
338
340
|
const response = await fetchOnce(url, options);
|
|
339
341
|
if (isTemporaryRefreshFailure(response.status)) {
|
|
340
342
|
void ((_a = response.body) === null || _a === void 0 ? void 0 : _a.cancel().catch(() => undefined));
|
|
343
|
+
if (config.keepSessionOnRefreshUnavailable) {
|
|
344
|
+
refreshState.unavailable = true;
|
|
345
|
+
}
|
|
341
346
|
throw refreshUnavailableError(response);
|
|
342
347
|
}
|
|
343
348
|
return response;
|
|
@@ -355,6 +360,11 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
355
360
|
if ((signal === null || signal === void 0 ? void 0 : signal.aborted) || !isNetworkError(error)) {
|
|
356
361
|
throw error;
|
|
357
362
|
}
|
|
363
|
+
if (config.keepSessionOnRefreshUnavailable) {
|
|
364
|
+
refreshState.unavailable = true;
|
|
365
|
+
lastNetworkError = error;
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
358
368
|
if (Date.now() >= expiresAt) {
|
|
359
369
|
lastNetworkError = error;
|
|
360
370
|
break;
|
|
@@ -369,6 +379,10 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
369
379
|
if (!isTemporaryRefreshFailure(lastResponse.status)) {
|
|
370
380
|
return lastResponse;
|
|
371
381
|
}
|
|
382
|
+
if (config.keepSessionOnRefreshUnavailable) {
|
|
383
|
+
refreshState.unavailable = true;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
372
386
|
const remainingMs = expiresAt - Date.now();
|
|
373
387
|
if (remainingMs <= 0) {
|
|
374
388
|
break;
|
|
@@ -388,6 +402,13 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
388
402
|
type: "refresh-unavailable",
|
|
389
403
|
...(status === undefined ? {} : { status }),
|
|
390
404
|
};
|
|
405
|
+
if (config.keepSessionOnRefreshUnavailable) {
|
|
406
|
+
refreshState.unavailable = true;
|
|
407
|
+
if (lastResponse) {
|
|
408
|
+
return lastResponse;
|
|
409
|
+
}
|
|
410
|
+
throw lastNetworkError !== null && lastNetworkError !== void 0 ? lastNetworkError : refreshUnavailableError(undefined);
|
|
411
|
+
}
|
|
391
412
|
unauthenticatedReason = reason;
|
|
392
413
|
markActiveAccountExpired();
|
|
393
414
|
if (lastResponse) {
|
|
@@ -405,6 +426,9 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
405
426
|
storage,
|
|
406
427
|
onUnauthenticated: () => {
|
|
407
428
|
var _a;
|
|
429
|
+
if (refreshState.unavailable) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
408
432
|
const reason = unauthenticatedReason !== null && unauthenticatedReason !== void 0 ? unauthenticatedReason : {
|
|
409
433
|
type: "request-unauthorized",
|
|
410
434
|
status: 401,
|
|
@@ -417,6 +441,9 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
417
441
|
this.socialLoginClientId = "";
|
|
418
442
|
clearActiveLogin = this.clearActiveLogin.bind(this);
|
|
419
443
|
this.hasExplicitStorageAdapter = config.storage !== undefined;
|
|
444
|
+
this.clientOptions = config;
|
|
445
|
+
this.keepSessionOnRefreshUnavailable = config.keepSessionOnRefreshUnavailable === true;
|
|
446
|
+
this.refreshState = refreshState;
|
|
420
447
|
this.socialLoginClientId = (_e = config.socialLoginClientId) !== null && _e !== void 0 ? _e : "";
|
|
421
448
|
this.addEventListener("profileRefreshed", () => this.syncSavedAccountProfile());
|
|
422
449
|
this.bindMethods(authMethods);
|
|
@@ -437,14 +464,46 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
437
464
|
this.bindMethods(patientMethods);
|
|
438
465
|
this.bindMethods(patientSharingMethods);
|
|
439
466
|
this.bindMethods(signalsMethods, (error) => {
|
|
440
|
-
const mapped = (0,
|
|
467
|
+
const mapped = (0, errors_2.mapSignalsError)((0, rate_limit_error_1.mapRateLimitError)(error), {
|
|
441
468
|
signalsRequest: true,
|
|
442
469
|
});
|
|
443
|
-
return (0,
|
|
470
|
+
return (0, errors_2.isSignalsError)(mapped) ? mapped : (0, ovok_error_1.mapOvokApiError)(mapped);
|
|
444
471
|
});
|
|
445
472
|
this.clientStorage = storage;
|
|
446
473
|
this.offlineQueueOptions = (_f = config.offlineQueue) !== null && _f !== void 0 ? _f : {};
|
|
447
474
|
}
|
|
475
|
+
get(url, options) {
|
|
476
|
+
const request = super.get(url, options);
|
|
477
|
+
void request.catch(() => this.invalidateUrl(url));
|
|
478
|
+
return request;
|
|
479
|
+
}
|
|
480
|
+
async refreshIfExpired(gracePeriod) {
|
|
481
|
+
var _a;
|
|
482
|
+
const activeLogin = this.getActiveLogin();
|
|
483
|
+
const accessToken = this.getAccessToken();
|
|
484
|
+
const expiresAt = accessToken ? (0, core_1.tryGetJwtExpiration)(accessToken) : undefined;
|
|
485
|
+
try {
|
|
486
|
+
await super.refreshIfExpired(gracePeriod);
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
const activeAccount = this.getSavedAccounts().find((account) => { var _a; return account.id === ((_a = activeLogin === null || activeLogin === void 0 ? void 0 : activeLogin.profile) === null || _a === void 0 ? void 0 : _a.reference); });
|
|
490
|
+
if (this.keepSessionOnRefreshUnavailable &&
|
|
491
|
+
this.refreshState.unavailable &&
|
|
492
|
+
activeLogin &&
|
|
493
|
+
accessToken &&
|
|
494
|
+
!(activeAccount === null || activeAccount === void 0 ? void 0 : activeAccount.expired) &&
|
|
495
|
+
expiresAt !== undefined &&
|
|
496
|
+
expiresAt > Date.now()) {
|
|
497
|
+
(_a = this.clientStorage) === null || _a === void 0 ? void 0 : _a.setObject("activeLogin", activeLogin);
|
|
498
|
+
this.setAccessToken(accessToken, activeLogin.refreshToken);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
throw error;
|
|
502
|
+
}
|
|
503
|
+
finally {
|
|
504
|
+
this.refreshState.unavailable = false;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
448
507
|
post(url, body, contentType, options) {
|
|
449
508
|
const pathname = new URL(url.toString(), this.getBaseUrl()).pathname;
|
|
450
509
|
if (!isRetrySafePost(pathname)) {
|
|
@@ -476,10 +535,10 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
476
535
|
var _a, _b;
|
|
477
536
|
const login = this.getLogin(accountId);
|
|
478
537
|
if (!login) {
|
|
479
|
-
throw new
|
|
538
|
+
throw new errors_1.OvokAccountError("account_not_found");
|
|
480
539
|
}
|
|
481
540
|
if (this.getExpiredAccountIds().includes(accountId)) {
|
|
482
|
-
throw new
|
|
541
|
+
throw new errors_1.OvokAccountError("account_expired");
|
|
483
542
|
}
|
|
484
543
|
const previousLogin = this.getActiveLogin();
|
|
485
544
|
try {
|
|
@@ -507,32 +566,25 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
507
566
|
var _a;
|
|
508
567
|
const login = this.getLogin(accountId);
|
|
509
568
|
if (!login) {
|
|
510
|
-
throw new
|
|
569
|
+
throw new errors_1.OvokAccountError("account_not_found");
|
|
511
570
|
}
|
|
512
571
|
const previousLogin = this.getActiveLogin();
|
|
513
572
|
const previousAccountId = (_a = previousLogin === null || previousLogin === void 0 ? void 0 : previousLogin.profile) === null || _a === void 0 ? void 0 : _a.reference;
|
|
573
|
+
const wasActive = previousAccountId === accountId;
|
|
574
|
+
const sessionClient = this.createAccountSessionClient(login);
|
|
514
575
|
try {
|
|
515
|
-
await
|
|
516
|
-
await this.delete("auth/session/current", ...(requestOptions === undefined ? [] : [requestOptions]));
|
|
576
|
+
await sessionClient.delete("auth/session/current", ...(requestOptions === undefined ? [] : [requestOptions]));
|
|
517
577
|
}
|
|
518
578
|
catch (error) {
|
|
519
|
-
if (
|
|
520
|
-
|
|
521
|
-
if (previousLogin && previousAccountId && previousAccountId !== accountId) {
|
|
522
|
-
await this.restoreActiveLogin(previousLogin);
|
|
523
|
-
}
|
|
524
|
-
else {
|
|
525
|
-
this.clearActiveLogin();
|
|
526
|
-
}
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
if (previousLogin && previousAccountId !== accountId) {
|
|
530
|
-
await this.restoreActiveLogin(previousLogin);
|
|
579
|
+
if (!sessionClient.getSavedAccounts().some((account) => account.id === accountId && account.expired)) {
|
|
580
|
+
throw error;
|
|
531
581
|
}
|
|
532
|
-
throw error;
|
|
533
582
|
}
|
|
534
|
-
this.clearActiveLogin();
|
|
535
583
|
this.removeSavedAccount(accountId);
|
|
584
|
+
if (!wasActive) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
this.clearActiveLogin();
|
|
536
588
|
const availableAccountIds = new Set(this.getSavedAccounts().filter((account) => !account.expired).map((account) => account.id));
|
|
537
589
|
let nextLogin;
|
|
538
590
|
if (previousAccountId && previousAccountId !== accountId) {
|
|
@@ -604,6 +656,7 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
604
656
|
queued: 0,
|
|
605
657
|
exhausted: 0,
|
|
606
658
|
responses: {},
|
|
659
|
+
failures: {},
|
|
607
660
|
};
|
|
608
661
|
}
|
|
609
662
|
return queue.flush(options, ...(requestOptions === undefined ? [] : [requestOptions]));
|
|
@@ -673,6 +726,19 @@ class OvokClient extends core_1.MedplumClient {
|
|
|
673
726
|
var _a, _b;
|
|
674
727
|
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 : []);
|
|
675
728
|
}
|
|
729
|
+
createAccountSessionClient(login) {
|
|
730
|
+
const storage = new core_1.ClientStorage(new core_1.MemoryStorage(), this.clientOptions.storagePrefix);
|
|
731
|
+
storage.setObject("activeLogin", login);
|
|
732
|
+
storage.setObject(account_storage_1.LOGINS_STORAGE_KEY, [login]);
|
|
733
|
+
const client = new OvokClient({
|
|
734
|
+
...this.clientOptions,
|
|
735
|
+
storage,
|
|
736
|
+
offlineQueue: undefined,
|
|
737
|
+
onUnauthenticated: undefined,
|
|
738
|
+
});
|
|
739
|
+
client.setAccessToken(login.accessToken, login.refreshToken);
|
|
740
|
+
return client;
|
|
741
|
+
}
|
|
676
742
|
setAccountExpired(accountId, expired) {
|
|
677
743
|
if (this.clientStorage) {
|
|
678
744
|
(0, account_storage_1.setStoredAccountExpired)(this.clientStorage, accountId, expired);
|
|
@@ -11,7 +11,10 @@ function createPatientSharingInvite(body, requestOptions) {
|
|
|
11
11
|
return this.post("/v1/patient-sharing/invites", body, undefined, requestOptions);
|
|
12
12
|
}
|
|
13
13
|
function redeemPatientSharingInvite(body, requestOptions) {
|
|
14
|
-
return this.post("/v1/patient-sharing/invites/redeem", body, undefined, requestOptions)
|
|
14
|
+
return this.post("/v1/patient-sharing/invites/redeem", body, undefined, requestOptions).then((share) => {
|
|
15
|
+
this.invalidateUrl("/v1/patient-sharing/shares");
|
|
16
|
+
return share;
|
|
17
|
+
});
|
|
15
18
|
}
|
|
16
19
|
function listPatientSharing(requestOptions) {
|
|
17
20
|
return this.get("/v1/patient-sharing/shares", ...(requestOptions === undefined ? [] : [requestOptions])).then((shares) => {
|
|
@@ -24,6 +27,7 @@ function listPatientSharing(requestOptions) {
|
|
|
24
27
|
}
|
|
25
28
|
async function revokePatientSharing(consentId, requestOptions) {
|
|
26
29
|
await this.delete(`/v1/patient-sharing/shares/${segment(consentId)}`, ...(requestOptions === undefined ? [] : [requestOptions]));
|
|
30
|
+
this.invalidateUrl("/v1/patient-sharing/shares");
|
|
27
31
|
}
|
|
28
32
|
function getPatientSharingSettings(requestOptions) {
|
|
29
33
|
return this.get("/v1/patient-sharing/settings", ...(requestOptions === undefined ? [] : [requestOptions]));
|
|
@@ -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.33",
|
|
52
52
|
},
|
|
53
53
|
fhirVersion: "4.0.1",
|
|
54
54
|
format: ["json"],
|
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.useAcceptInvite = exports.useInvitePatient = exports.useInvitePractitioner = void 0;
|
|
5
5
|
const api_hooks_1 = require("./api-hooks");
|
|
6
|
+
const notification_invalidation_1 = require("./notification-invalidation");
|
|
6
7
|
const useInvitePractitioner = () => (0, api_hooks_1.useOvokMutation)((client, body) => client.invitePractitioner(body));
|
|
7
8
|
exports.useInvitePractitioner = useInvitePractitioner;
|
|
8
9
|
const useInvitePatient = () => (0, api_hooks_1.useOvokMutation)((client, body) => client.invitePatient(body));
|
|
9
10
|
exports.useInvitePatient = useInvitePatient;
|
|
10
|
-
const useAcceptInvite = () => (0, api_hooks_1.useOvokMutation)((client, body) =>
|
|
11
|
+
const useAcceptInvite = () => (0, api_hooks_1.useOvokMutation)(async (client, body) => {
|
|
12
|
+
const result = await client.acceptInvite(body);
|
|
13
|
+
client.invalidateUrl("/v1/patient-sharing/shares");
|
|
14
|
+
(0, notification_invalidation_1.notifyNotificationChanges)(client, { type: "patient-sharing" });
|
|
15
|
+
return result;
|
|
16
|
+
});
|
|
11
17
|
exports.useAcceptInvite = useAcceptInvite;
|
|
@@ -3,6 +3,8 @@ import { OvokClient } from "../client/ovok-client";
|
|
|
3
3
|
import { SignalsAlert, SignalsRawAlert } from "../client/signals/types";
|
|
4
4
|
import { SlimNotification } from "../client/slim/types";
|
|
5
5
|
export type NotificationChange = {
|
|
6
|
+
type: "patient-sharing";
|
|
7
|
+
} | {
|
|
6
8
|
type: "slim";
|
|
7
9
|
notification: SlimNotification | SlimMedicalNotificationRow;
|
|
8
10
|
} | {
|
|
@@ -37,18 +37,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
37
37
|
exports.useUpdatePatientSharingSettings = exports.usePatientSharingSettings = exports.useRevokePatientSharing = exports.useRedeemPatientSharingInvite = exports.useCreatePatientSharingInvite = exports.usePatientSharing = void 0;
|
|
38
38
|
const React = __importStar(require("react"));
|
|
39
39
|
const api_hooks_1 = require("./api-hooks");
|
|
40
|
+
const notification_invalidation_1 = require("./notification-invalidation");
|
|
40
41
|
const index_1 = require("./index");
|
|
41
42
|
const usePatientSharing = () => {
|
|
42
43
|
const client = (0, index_1.useClient)();
|
|
43
44
|
const load = React.useCallback((signal) => client.listPatientSharing({ signal }), [client]);
|
|
44
|
-
|
|
45
|
+
const state = (0, api_hooks_1.useOvokRequestWithUpdater)(load);
|
|
46
|
+
React.useEffect(() => (0, notification_invalidation_1.subscribeToNotificationChanges)(client, (change) => {
|
|
47
|
+
if ((change === null || change === void 0 ? void 0 : change.type) === "patient-sharing") {
|
|
48
|
+
state.reload();
|
|
49
|
+
}
|
|
50
|
+
}), [client, state.reload]);
|
|
51
|
+
return state;
|
|
45
52
|
};
|
|
46
53
|
exports.usePatientSharing = usePatientSharing;
|
|
47
54
|
const useCreatePatientSharingInvite = () => (0, api_hooks_1.useOvokMutation)((client, params) => client.createPatientSharingInvite(params));
|
|
48
55
|
exports.useCreatePatientSharingInvite = useCreatePatientSharingInvite;
|
|
49
|
-
const useRedeemPatientSharingInvite = () => (0, api_hooks_1.useOvokMutation)((client, params) =>
|
|
56
|
+
const useRedeemPatientSharingInvite = () => (0, api_hooks_1.useOvokMutation)(async (client, params) => {
|
|
57
|
+
const share = await client.redeemPatientSharingInvite(params);
|
|
58
|
+
(0, notification_invalidation_1.notifyNotificationChanges)(client, { type: "patient-sharing" });
|
|
59
|
+
return share;
|
|
60
|
+
});
|
|
50
61
|
exports.useRedeemPatientSharingInvite = useRedeemPatientSharingInvite;
|
|
51
|
-
const useRevokePatientSharing = () => (0, api_hooks_1.useOvokMutation)((client, consentId) =>
|
|
62
|
+
const useRevokePatientSharing = () => (0, api_hooks_1.useOvokMutation)(async (client, consentId) => {
|
|
63
|
+
await client.revokePatientSharing(consentId);
|
|
64
|
+
(0, notification_invalidation_1.notifyNotificationChanges)(client, { type: "patient-sharing" });
|
|
65
|
+
});
|
|
52
66
|
exports.useRevokePatientSharing = useRevokePatientSharing;
|
|
53
67
|
const usePatientSharingSettings = () => {
|
|
54
68
|
const client = (0, index_1.useClient)();
|