@openmrs/esm-api 10.0.1-pre.5607 → 10.0.1-pre.5619
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/.turbo/turbo-build.log +1 -1
- package/dist/current-user.d.ts +65 -3
- package/dist/current-user.d.ts.map +1 -1
- package/dist/current-user.js +146 -23
- package/dist/openmrs-fetch.d.ts +5 -0
- package/dist/openmrs-fetch.d.ts.map +1 -1
- package/dist/openmrs-fetch.js +53 -57
- package/dist/public.d.ts +1 -1
- package/dist/public.d.ts.map +1 -1
- package/dist/public.js +1 -1
- package/mock-jest.ts +3 -0
- package/mock.ts +3 -0
- package/package.json +9 -9
- package/src/current-user.test.ts +230 -25
- package/src/current-user.ts +170 -21
- package/src/openmrs-fetch.test.ts +11 -1
- package/src/openmrs-fetch.ts +62 -70
- package/src/public.ts +12 -1
package/.turbo/turbo-build.log
CHANGED
package/dist/current-user.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type LoadedSessionStore = {
|
|
|
7
7
|
export type UnloadedSessionStore = {
|
|
8
8
|
loaded: false;
|
|
9
9
|
session: null;
|
|
10
|
+
/** Set when fetching the session failed before any session had loaded. */
|
|
11
|
+
error?: Error;
|
|
10
12
|
};
|
|
11
13
|
/** @internal */
|
|
12
14
|
export declare const sessionStore: import("zustand").StoreApi<SessionStore>;
|
|
@@ -15,7 +17,8 @@ export declare const sessionStore: import("zustand").StoreApi<SessionStore>;
|
|
|
15
17
|
* current user's session. If the session hasn't been loaded, was loaded more than
|
|
16
18
|
* a minute ago, or is in the middle of being refetched, the Promise waits for the
|
|
17
19
|
* fetch in question rather than resolving with data that may be out of date. The
|
|
18
|
-
* session it resolves with is therefore never more than a minute old
|
|
20
|
+
* session it resolves with is therefore never more than a minute old, unless that fetch fails,
|
|
21
|
+
* in which case it resolves with the last session that loaded.
|
|
19
22
|
*
|
|
20
23
|
* The function accepts an optional `opts` object with an `includeAuthStatus` boolean
|
|
21
24
|
* property that defaults to `true`. When `true`, the entire {@link Session} object
|
|
@@ -94,7 +97,12 @@ export declare function setUserLanguage(data: Session): void;
|
|
|
94
97
|
* the user. All subscribers to the session store will be notified of the
|
|
95
98
|
* new user once the new version of the user object is downloaded.
|
|
96
99
|
*
|
|
97
|
-
*
|
|
100
|
+
* If the server rejects the request as unauthenticated (401 or 403), the store records a
|
|
101
|
+
* logged-out session. If the request fails for any other reason, a session that has already
|
|
102
|
+
* loaded is kept; otherwise the store records the error.
|
|
103
|
+
*
|
|
104
|
+
* @returns A Promise resolving to the updated session store state. It rejects with the
|
|
105
|
+
* store state if the request fails for a reason other than authentication.
|
|
98
106
|
*
|
|
99
107
|
* @example
|
|
100
108
|
* ```js
|
|
@@ -189,7 +197,25 @@ export declare function getSessionLocation(): Promise<SessionLocation | undefine
|
|
|
189
197
|
* await setSessionLocation('location-uuid-here', abortController);
|
|
190
198
|
* ```
|
|
191
199
|
*/
|
|
192
|
-
export declare function setSessionLocation(locationUuid: string, abortController
|
|
200
|
+
export declare function setSessionLocation(locationUuid: string, abortController?: AbortController): Promise<any>;
|
|
201
|
+
/**
|
|
202
|
+
* Sets the locale for the current session only, leaving the user's default locale unchanged.
|
|
203
|
+
* Use {@link setUserProperties} with a `defaultLocale` property to change the default instead.
|
|
204
|
+
*
|
|
205
|
+
* @param locale The locale in Java's `Locale#toString()` form, e.g. `en_GB`.
|
|
206
|
+
* @param abortController Optional AbortController to allow cancellation of the request.
|
|
207
|
+
* @returns A Promise that resolves with the updated SessionStore after refetching the current user.
|
|
208
|
+
* It rejects with an {@link OpenmrsFetchError} if the server does not accept the locale.
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* import { setSessionLocale } from '@openmrs/esm-api';
|
|
213
|
+
* await setSessionLocale('fr');
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* @internal
|
|
217
|
+
*/
|
|
218
|
+
export declare function setSessionLocale(locale: string, abortController?: AbortController): Promise<SessionStore>;
|
|
193
219
|
/**
|
|
194
220
|
* Updates the user properties for a specific user. User properties are key-value
|
|
195
221
|
* pairs that store user-specific settings and preferences. After updating the
|
|
@@ -216,4 +242,40 @@ export declare function setSessionLocation(locationUuid: string, abortController
|
|
|
216
242
|
export declare function setUserProperties(userUuid: string, userProperties: {
|
|
217
243
|
[x: string]: string;
|
|
218
244
|
}, abortController?: AbortController): Promise<SessionStore>;
|
|
245
|
+
/**
|
|
246
|
+
* Submits a TOTP code to complete a two-factor login challenge. When the code is accepted, the
|
|
247
|
+
* session store is refreshed with the now-authenticated session.
|
|
248
|
+
*
|
|
249
|
+
* @param code The code from the user's authenticator app.
|
|
250
|
+
* @param rememberDevice Whether the server should skip the challenge on this device in future.
|
|
251
|
+
* @returns A Promise resolving to the session. Its `authenticated` property is `false` if the
|
|
252
|
+
* code was not accepted. It rejects with an {@link OpenmrsFetchError} if the server rejects the
|
|
253
|
+
* request, in which case `responseBody` may explain why.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* import { verifyTotpCode } from '@openmrs/esm-api';
|
|
258
|
+
* const session = await verifyTotpCode('123456', true);
|
|
259
|
+
* ```
|
|
260
|
+
*
|
|
261
|
+
* @internal
|
|
262
|
+
*/
|
|
263
|
+
export declare function verifyTotpCode(code: string, rememberDevice?: boolean): Promise<Session>;
|
|
264
|
+
/**
|
|
265
|
+
* Ends the current session on the server and records a logged-out session in the session store.
|
|
266
|
+
* A server that has already ended the session (401 or 403) counts as a successful logout.
|
|
267
|
+
*
|
|
268
|
+
* Other data cached for the logged-in user, such as SWR caches, is the caller's to clear.
|
|
269
|
+
*
|
|
270
|
+
* @returns A Promise that resolves once the session store holds a logged-out session.
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```ts
|
|
274
|
+
* import { logout } from '@openmrs/esm-api';
|
|
275
|
+
* await logout();
|
|
276
|
+
* ```
|
|
277
|
+
*
|
|
278
|
+
* @internal
|
|
279
|
+
*/
|
|
280
|
+
export declare function logout(): Promise<void>;
|
|
219
281
|
//# sourceMappingURL=current-user.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"current-user.d.ts","sourceRoot":"","sources":["../src/current-user.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAiB,MAAM,SAAS,CAAC;AAEtG,MAAM,MAAM,YAAY,GAAG,kBAAkB,GAAG,oBAAoB,CAAC;AAErE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,KAAK,CAAC;IACd,OAAO,EAAE,IAAI,CAAC;CACf,CAAC;AAEF,gBAAgB;AAChB,eAAO,MAAM,YAAY,0CAGvB,CAAC;AAoCH
|
|
1
|
+
{"version":3,"file":"current-user.d.ts","sourceRoot":"","sources":["../src/current-user.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAiB,MAAM,SAAS,CAAC;AAEtG,MAAM,MAAM,YAAY,GAAG,kBAAkB,GAAG,oBAAoB,CAAC;AAErE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,KAAK,CAAC;IACd,OAAO,EAAE,IAAI,CAAC;IACd,0EAA0E;IAC1E,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,CAAC;AAEF,gBAAgB;AAChB,eAAO,MAAM,YAAY,0CAGvB,CAAC;AAoCH;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,iBAAS,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;AAC5C;;;;;GAKG;AACH,iBAAS,cAAc,CAAC,IAAI,EAAE;IAAE,iBAAiB,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAC7E;;;;;GAKG;AACH,iBAAS,cAAc,CAAC,IAAI,EAAE;IAAE,iBAAiB,EAAE,KAAK,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;AAyBnF,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,6CAG9B;AAiBD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,QAc5C;AAwBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,yBAyBtE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,SAQ/B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAC3B,iBAAiB,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,EACzC,IAAI,EAAE;IAAE,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;CAAE,WAa3D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,0BAmB9B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAG/E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,CAY9G;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC,CAW/G;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE;IACd,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACrB,EACD,eAAe,CAAC,EAAE,eAAe,GAChC,OAAO,CAAC,YAAY,CAAC,CAcvB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,UAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAiB3F;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAY5C"}
|
package/dist/current-user.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @module @category API */ import { reportError } from "@openmrs/esm-error-handling";
|
|
2
2
|
import { createGlobalStore } from "@openmrs/esm-state";
|
|
3
3
|
import { isUndefined } from "lodash-es";
|
|
4
|
-
import { openmrsFetch, restBaseUrl, sessionEndpoint } from "./openmrs-fetch.js";
|
|
4
|
+
import { OpenmrsFetchError, openmrsFetch, restBaseUrl, sessionEndpoint } from "./openmrs-fetch.js";
|
|
5
5
|
/** @internal */ export const sessionStore = createGlobalStore('session', {
|
|
6
6
|
loaded: false,
|
|
7
7
|
session: null
|
|
@@ -36,16 +36,22 @@ let inFlightRefresh = null;
|
|
|
36
36
|
function getCurrentUser(opts) {
|
|
37
37
|
const includeAuthStatus = opts?.includeAuthStatus ?? true;
|
|
38
38
|
const select = (session)=>includeAuthStatus ? session : session.user;
|
|
39
|
-
|
|
39
|
+
const refresh = refreshSessionIfStale();
|
|
40
|
+
if (!refresh) {
|
|
40
41
|
return Promise.resolve(select(sessionStore.getState().session));
|
|
41
42
|
}
|
|
42
43
|
return new Promise((resolve)=>{
|
|
43
|
-
const
|
|
44
|
+
const resolveIfLoaded = ()=>{
|
|
45
|
+
const state = sessionStore.getState();
|
|
44
46
|
if (state.loaded) {
|
|
45
47
|
unsubscribe();
|
|
46
48
|
resolve(select(state.session));
|
|
47
49
|
}
|
|
48
|
-
}
|
|
50
|
+
};
|
|
51
|
+
const unsubscribe = sessionStore.subscribe(resolveIfLoaded);
|
|
52
|
+
// A failed refresh keeps an already-loaded session without updating the store, so the
|
|
53
|
+
// subscription alone would never fire.
|
|
54
|
+
refresh.then(resolveIfLoaded, resolveIfLoaded);
|
|
49
55
|
});
|
|
50
56
|
}
|
|
51
57
|
export { getCurrentUser };
|
|
@@ -128,7 +134,12 @@ function isSuperUser(user) {
|
|
|
128
134
|
* the user. All subscribers to the session store will be notified of the
|
|
129
135
|
* new user once the new version of the user object is downloaded.
|
|
130
136
|
*
|
|
131
|
-
*
|
|
137
|
+
* If the server rejects the request as unauthenticated (401 or 403), the store records a
|
|
138
|
+
* logged-out session. If the request fails for any other reason, a session that has already
|
|
139
|
+
* loaded is kept; otherwise the store records the error.
|
|
140
|
+
*
|
|
141
|
+
* @returns A Promise resolving to the updated session store state. It rejects with the
|
|
142
|
+
* store state if the request fails for a reason other than authentication.
|
|
132
143
|
*
|
|
133
144
|
* @example
|
|
134
145
|
* ```js
|
|
@@ -142,7 +153,8 @@ function isSuperUser(user) {
|
|
|
142
153
|
headers['Authorization'] = `Basic ${window.btoa(`${username}:${password}`)}`;
|
|
143
154
|
}
|
|
144
155
|
const refresh = handleSessionResponse(openmrsFetch(sessionEndpoint, {
|
|
145
|
-
headers
|
|
156
|
+
headers,
|
|
157
|
+
rejectAuthFailure: true
|
|
146
158
|
}));
|
|
147
159
|
// Publish the request so that readers can wait on it instead of reading the session it is about to
|
|
148
160
|
// replace. Each call still issues its own request; this only tracks the most recent one.
|
|
@@ -173,7 +185,7 @@ function isSuperUser(user) {
|
|
|
173
185
|
authenticated: false,
|
|
174
186
|
sessionId: ''
|
|
175
187
|
}
|
|
176
|
-
});
|
|
188
|
+
}, true);
|
|
177
189
|
}
|
|
178
190
|
/**
|
|
179
191
|
* Checks whether the given user has access based on the required privilege(s).
|
|
@@ -281,9 +293,39 @@ function isSuperUser(user) {
|
|
|
281
293
|
headers: {
|
|
282
294
|
'Content-Type': 'application/json'
|
|
283
295
|
},
|
|
284
|
-
|
|
296
|
+
rejectAuthFailure: true,
|
|
297
|
+
signal: abortController?.signal
|
|
285
298
|
}));
|
|
286
299
|
}
|
|
300
|
+
/**
|
|
301
|
+
* Sets the locale for the current session only, leaving the user's default locale unchanged.
|
|
302
|
+
* Use {@link setUserProperties} with a `defaultLocale` property to change the default instead.
|
|
303
|
+
*
|
|
304
|
+
* @param locale The locale in Java's `Locale#toString()` form, e.g. `en_GB`.
|
|
305
|
+
* @param abortController Optional AbortController to allow cancellation of the request.
|
|
306
|
+
* @returns A Promise that resolves with the updated SessionStore after refetching the current user.
|
|
307
|
+
* It rejects with an {@link OpenmrsFetchError} if the server does not accept the locale.
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```ts
|
|
311
|
+
* import { setSessionLocale } from '@openmrs/esm-api';
|
|
312
|
+
* await setSessionLocale('fr');
|
|
313
|
+
* ```
|
|
314
|
+
*
|
|
315
|
+
* @internal
|
|
316
|
+
*/ export async function setSessionLocale(locale, abortController) {
|
|
317
|
+
await openmrsFetch(sessionEndpoint, {
|
|
318
|
+
method: 'POST',
|
|
319
|
+
body: {
|
|
320
|
+
locale
|
|
321
|
+
},
|
|
322
|
+
headers: {
|
|
323
|
+
'Content-Type': 'application/json'
|
|
324
|
+
},
|
|
325
|
+
signal: abortController?.signal
|
|
326
|
+
});
|
|
327
|
+
return refetchCurrentUser();
|
|
328
|
+
}
|
|
287
329
|
/**
|
|
288
330
|
* Updates the user properties for a specific user. User properties are key-value
|
|
289
331
|
* pairs that store user-specific settings and preferences. After updating the
|
|
@@ -322,33 +364,114 @@ function isSuperUser(user) {
|
|
|
322
364
|
});
|
|
323
365
|
return refetchCurrentUser();
|
|
324
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Submits a TOTP code to complete a two-factor login challenge. When the code is accepted, the
|
|
369
|
+
* session store is refreshed with the now-authenticated session.
|
|
370
|
+
*
|
|
371
|
+
* @param code The code from the user's authenticator app.
|
|
372
|
+
* @param rememberDevice Whether the server should skip the challenge on this device in future.
|
|
373
|
+
* @returns A Promise resolving to the session. Its `authenticated` property is `false` if the
|
|
374
|
+
* code was not accepted. It rejects with an {@link OpenmrsFetchError} if the server rejects the
|
|
375
|
+
* request, in which case `responseBody` may explain why.
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```ts
|
|
379
|
+
* import { verifyTotpCode } from '@openmrs/esm-api';
|
|
380
|
+
* const session = await verifyTotpCode('123456', true);
|
|
381
|
+
* ```
|
|
382
|
+
*
|
|
383
|
+
* @internal
|
|
384
|
+
*/ export async function verifyTotpCode(code, rememberDevice = false) {
|
|
385
|
+
const response = await openmrsFetch(rememberDevice ? `${sessionEndpoint}?rememberMe=true` : sessionEndpoint, {
|
|
386
|
+
headers: {
|
|
387
|
+
'X-Totp-Code': code
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
if (response.data?.authenticated) {
|
|
391
|
+
// `refetchCurrentUser` only resolves once the store holds a session.
|
|
392
|
+
const store = await refetchCurrentUser();
|
|
393
|
+
return store.session;
|
|
394
|
+
}
|
|
395
|
+
return response.data;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Ends the current session on the server and records a logged-out session in the session store.
|
|
399
|
+
* A server that has already ended the session (401 or 403) counts as a successful logout.
|
|
400
|
+
*
|
|
401
|
+
* Other data cached for the logged-in user, such as SWR caches, is the caller's to clear.
|
|
402
|
+
*
|
|
403
|
+
* @returns A Promise that resolves once the session store holds a logged-out session.
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```ts
|
|
407
|
+
* import { logout } from '@openmrs/esm-api';
|
|
408
|
+
* await logout();
|
|
409
|
+
* ```
|
|
410
|
+
*
|
|
411
|
+
* @internal
|
|
412
|
+
*/ export async function logout() {
|
|
413
|
+
try {
|
|
414
|
+
await openmrsFetch(sessionEndpoint, {
|
|
415
|
+
method: 'DELETE',
|
|
416
|
+
rejectAuthFailure: true
|
|
417
|
+
});
|
|
418
|
+
} catch (err) {
|
|
419
|
+
if (!isAuthFailure(err)) {
|
|
420
|
+
throw err;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
clearCurrentUser();
|
|
424
|
+
// The session just cleared is already correct, so a failed refresh loses nothing.
|
|
425
|
+
await refetchCurrentUser().catch(()=>{});
|
|
426
|
+
}
|
|
325
427
|
function handleSessionResponse(result) {
|
|
326
428
|
return new Promise((resolve, reject)=>{
|
|
327
429
|
result.then((res)=>{
|
|
328
|
-
let nextState;
|
|
329
430
|
if (typeof res?.data === 'object') {
|
|
330
|
-
nextState = {
|
|
431
|
+
const nextState = {
|
|
331
432
|
loaded: true,
|
|
332
433
|
session: res.data
|
|
333
434
|
};
|
|
334
|
-
sessionStore.setState(nextState);
|
|
435
|
+
sessionStore.setState(nextState, true);
|
|
335
436
|
resolve(nextState);
|
|
336
437
|
} else {
|
|
337
|
-
|
|
338
|
-
loaded: false,
|
|
339
|
-
session: null
|
|
340
|
-
};
|
|
341
|
-
sessionStore.setState(nextState);
|
|
342
|
-
reject(nextState);
|
|
438
|
+
reject(recordSessionFailure(Error('The session endpoint did not respond with a session')));
|
|
343
439
|
}
|
|
344
440
|
}).catch((err)=>{
|
|
441
|
+
// An auth failure is the session endpoint's answer for "not logged in", not a failed lookup.
|
|
442
|
+
if (isAuthFailure(err)) {
|
|
443
|
+
if (isUnauthenticatedSession(err.responseBody)) {
|
|
444
|
+
sessionStore.setState({
|
|
445
|
+
loaded: true,
|
|
446
|
+
session: err.responseBody
|
|
447
|
+
}, true);
|
|
448
|
+
} else {
|
|
449
|
+
clearCurrentUser();
|
|
450
|
+
}
|
|
451
|
+
resolve(sessionStore.getState());
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
345
454
|
reportError(`Failed to fetch new session information: ${err}`);
|
|
346
|
-
|
|
347
|
-
loaded: false,
|
|
348
|
-
session: null
|
|
349
|
-
};
|
|
350
|
-
sessionStore.setState(nextState);
|
|
351
|
-
reject(nextState);
|
|
455
|
+
reject(recordSessionFailure(err));
|
|
352
456
|
});
|
|
353
457
|
});
|
|
354
458
|
}
|
|
459
|
+
function isAuthFailure(err) {
|
|
460
|
+
return err instanceof OpenmrsFetchError && (err.response.status === 401 || err.response.status === 403);
|
|
461
|
+
}
|
|
462
|
+
function isUnauthenticatedSession(body) {
|
|
463
|
+
return typeof body === 'object' && body !== null && body.authenticated === false;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* A session that has already loaded is kept when a later fetch fails, since it is still the best answer
|
|
467
|
+
* available. With nothing to fall back on, the error is recorded so the app shell can show an error page.
|
|
468
|
+
*/ function recordSessionFailure(err) {
|
|
469
|
+
if (!sessionStore.getState().loaded) {
|
|
470
|
+
sessionStore.setState({
|
|
471
|
+
loaded: false,
|
|
472
|
+
session: null,
|
|
473
|
+
error: err instanceof Error ? err : Error(String(err))
|
|
474
|
+
}, true);
|
|
475
|
+
}
|
|
476
|
+
return sessionStore.getState();
|
|
477
|
+
}
|
package/dist/openmrs-fetch.d.ts
CHANGED
|
@@ -78,6 +78,11 @@ export declare class OpenmrsFetchError extends Error implements FetchError {
|
|
|
78
78
|
export interface FetchConfig extends Omit<RequestInit, 'body' | 'headers'> {
|
|
79
79
|
headers?: FetchHeaders;
|
|
80
80
|
body?: FetchBody | string;
|
|
81
|
+
/**
|
|
82
|
+
* When `true`, a response that triggers the auth-failure redirect still navigates, but the
|
|
83
|
+
* returned promise rejects with an `OpenmrsFetchError` instead of staying pending.
|
|
84
|
+
*/
|
|
85
|
+
rejectAuthFailure?: boolean;
|
|
81
86
|
}
|
|
82
87
|
type ResponseBody = string | FetchResponseJson;
|
|
83
88
|
export interface FetchHeaders {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openmrs-fetch.d.ts","sourceRoot":"","sources":["../src/openmrs-fetch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,mEAAmE;AACnE,eAAO,MAAM,WAAW,gBAAgB,CAAC;AACzC,uEAAuE;AACvE,eAAO,MAAM,WAAW,iBAAiB,CAAC;AAC1C,sDAAsD;AACtD,eAAO,MAAM,eAAe,wBAA2B,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,UASnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,YAAY,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,WAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"openmrs-fetch.d.ts","sourceRoot":"","sources":["../src/openmrs-fetch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,mEAAmE;AACnE,eAAO,MAAM,WAAW,gBAAgB,CAAC;AACzC,uEAAuE;AACvE,eAAO,MAAM,WAAW,iBAAiB,CAAC;AAC1C,sDAAsD;AACtD,eAAO,MAAM,eAAe,wBAA2B,CAAC;AAExD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,UASnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,YAAY,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,WAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAqL1G;AAED,qBAAa,iBAAkB,SAAQ,KAAM,YAAW,UAAU;gBACpD,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,GAAG,IAAI,EAAE,iBAAiB,EAAE,KAAK;IAQxG,QAAQ,EAAE,QAAQ,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IACxE,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,IAAI,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,KAAK,YAAY,GAAG,MAAM,GAAG,iBAAiB,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,UAAU,SAAS;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,QAAQ,CAAC;IACnB,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;CACnC"}
|
package/dist/openmrs-fetch.js
CHANGED
|
@@ -104,26 +104,26 @@ import { defaultRedirectAuthFailureUrl } from "./config-schema.js";
|
|
|
104
104
|
if (!fetchInit.headers) {
|
|
105
105
|
fetchInit.headers = {};
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
// Automatically stringify javascript objects being sent in the
|
|
108
|
+
// request body.
|
|
109
|
+
if (isPlainObject(fetchInit.body)) {
|
|
110
110
|
fetchInit.body = JSON.stringify(fetchInit.body);
|
|
111
111
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
// Add a request header to tell the server to respond with json,
|
|
113
|
+
// since frontend code almost always wants json and the OpenMRS
|
|
114
|
+
// server won't give you json unless you explicitly ask for it.
|
|
115
|
+
// If a different Accept header is preferred, pass it into the fetchInit.
|
|
116
|
+
// If no Accept header is desired, pass it in explicitly as null.
|
|
117
|
+
if (typeof fetchInit.headers.Accept === 'undefined') {
|
|
118
118
|
fetchInit.headers.Accept = 'application/json';
|
|
119
119
|
}
|
|
120
120
|
if (fetchInit.headers.Accept === null) {
|
|
121
121
|
delete fetchInit.headers.Accept;
|
|
122
122
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
// This tells the OpenMRS REST API not to return a WWW-Authenticate
|
|
124
|
+
// header. Returning that header is useful when using the API, but
|
|
125
|
+
// not from a UI.
|
|
126
|
+
let isRestRequest = false;
|
|
127
127
|
try {
|
|
128
128
|
const requestUrl = new URL(url, window.location.href);
|
|
129
129
|
const restUrl = new URL(makeUrl(restBaseUrl), window.location.href);
|
|
@@ -158,22 +158,20 @@ import { defaultRedirectAuthFailureUrl } from "./config-schema.js";
|
|
|
158
158
|
url = urlUrl.toString();
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
161
|
+
// We capture the stacktrace before making the request, so that if an error occurs we can
|
|
162
|
+
// log a full stacktrace that includes the code that made the request and handled the response
|
|
163
|
+
// Otherwise, we could run into situations where the stacktrace doesn't even show which code
|
|
164
|
+
// called @openmrs/api.
|
|
165
|
+
const requestStacktrace = Error();
|
|
166
166
|
return window.fetch(url, fetchInit).then(async (r)=>{
|
|
167
167
|
const response = r;
|
|
168
168
|
const { redirectAuthFailure, followRedirects } = await getConfig('@openmrs/esm-api');
|
|
169
169
|
if (response.ok) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
* - HTTP 204: Logout redirect URLs (e.g. Keycloak IdP logout). Refer: OA-41 #1231
|
|
176
|
-
*/ const location = response.headers.get('location');
|
|
170
|
+
// Backend modules can trigger SPA redirects by returning a `Location` header.
|
|
171
|
+
// This is required because `fetch()` hides HTTP redirects from the application.
|
|
172
|
+
// Session endpoint (2xx): Authentication challenge URLs (e.g. TOTP,2FA).
|
|
173
|
+
// HTTP 204: Logout redirect URLs (e.g. Keycloak IdP logout). Refer: OA-41 #1231
|
|
174
|
+
const location = response.headers.get('location');
|
|
177
175
|
const shouldRedirect = followRedirects && location && (url === makeUrl(sessionEndpoint) || response.status === 204);
|
|
178
176
|
if (shouldRedirect) {
|
|
179
177
|
navigate({
|
|
@@ -181,10 +179,10 @@ import { defaultRedirectAuthFailureUrl } from "./config-schema.js";
|
|
|
181
179
|
});
|
|
182
180
|
}
|
|
183
181
|
if (response.status === 204) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
// HTTP 204 - No Content
|
|
183
|
+
// We should not try to download the empty response as json. Instead,
|
|
184
|
+
// we return null since there is no response body.
|
|
185
|
+
response.data = null;
|
|
188
186
|
return response;
|
|
189
187
|
} else {
|
|
190
188
|
// HTTP 200s - The request succeeded
|
|
@@ -200,13 +198,12 @@ import { defaultRedirectAuthFailureUrl } from "./config-schema.js";
|
|
|
200
198
|
});
|
|
201
199
|
}
|
|
202
200
|
} else {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
*/ if (url === makeUrl(sessionEndpoint) && response.status === 403 || redirectAuthFailure.enabled && redirectAuthFailure.errors.includes(response.status)) {
|
|
201
|
+
// HTTP response status is not in 200s. Usually this will mean
|
|
202
|
+
// either HTTP 400s (bad request from browser) or HTTP 500s (server error)
|
|
203
|
+
// Our goal is to come up with best possible stacktrace and error message
|
|
204
|
+
// to help developers understand the problem and debug
|
|
205
|
+
// Redirect to given url when redirect on auth failure is enabled
|
|
206
|
+
if (url === makeUrl(sessionEndpoint) && response.status === 403 || redirectAuthFailure.enabled && redirectAuthFailure.errors.includes(response.status)) {
|
|
210
207
|
clearHistory();
|
|
211
208
|
// by default, redirect to the url specified in the config.
|
|
212
209
|
// If blank, use the location header from the response.
|
|
@@ -215,28 +212,27 @@ import { defaultRedirectAuthFailureUrl } from "./config-schema.js";
|
|
|
215
212
|
navigate({
|
|
216
213
|
to: location
|
|
217
214
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
} else {
|
|
223
|
-
// Attempt to download a response body, if it has one
|
|
224
|
-
return response.clone().text().then((responseText)=>{
|
|
225
|
-
let responseBody = responseText;
|
|
226
|
-
try {
|
|
227
|
-
responseBody = JSON.parse(responseText);
|
|
228
|
-
} catch (err) {
|
|
229
|
-
// Server didn't respond with json, so just go with the response text string
|
|
230
|
-
}
|
|
231
|
-
/* Make the fetch promise go into "rejected" status, with the best
|
|
232
|
-
* possible stacktrace and error message.
|
|
233
|
-
*/ throw new OpenmrsFetchError(url, response, responseBody, requestStacktrace);
|
|
234
|
-
}, (err)=>{
|
|
235
|
-
/* We weren't able to download a response body for this error.
|
|
236
|
-
* Time to just give the best possible stacktrace and error message.
|
|
237
|
-
*/ throw new OpenmrsFetchError(url, response, null, requestStacktrace);
|
|
238
|
-
});
|
|
215
|
+
const rejectAuthFailures = fetchInit.rejectAuthFailure === true;
|
|
216
|
+
if (!rejectAuthFailures) {
|
|
217
|
+
return redirectAuthFailure.resolvePromise ? Promise.resolve() : new Promise(()=>{});
|
|
218
|
+
}
|
|
239
219
|
}
|
|
220
|
+
// Attempt to download a response body, if it has one
|
|
221
|
+
return response.clone().text().then((responseText)=>{
|
|
222
|
+
let responseBody = responseText;
|
|
223
|
+
try {
|
|
224
|
+
responseBody = JSON.parse(responseText);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
// Server didn't respond with json, so just go with the response text string
|
|
227
|
+
}
|
|
228
|
+
// Make the fetch promise go into "rejected" status, with the best
|
|
229
|
+
// possible stacktrace and error message.
|
|
230
|
+
throw new OpenmrsFetchError(url, response, responseBody, requestStacktrace);
|
|
231
|
+
}, ()=>{
|
|
232
|
+
// We weren't able to download a response body for this error.
|
|
233
|
+
// Time to just give the best possible stacktrace and error message.
|
|
234
|
+
throw new OpenmrsFetchError(url, response, null, requestStacktrace);
|
|
235
|
+
});
|
|
240
236
|
}
|
|
241
237
|
});
|
|
242
238
|
}
|
package/dist/public.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { clearCurrentUser, getCurrentUser, getLoggedInUser, getSessionStore, getSessionLocation, refetchCurrentUser, setSessionLocation, setUserLanguage, setUserProperties, userHasAccess, type LoadedSessionStore, type SessionStore, type UnloadedSessionStore, } from './current-user';
|
|
2
2
|
export * from './environment';
|
|
3
3
|
export * from './types';
|
|
4
|
-
export
|
|
4
|
+
export { fhirBaseUrl, makeUrl, openmrsFetch, OpenmrsFetchError, restBaseUrl, sessionEndpoint, type FetchConfig, type FetchError, type FetchHeaders, type FetchResponseJson, } from './openmrs-fetch';
|
|
5
5
|
export * from './openmrs-backend-dependencies';
|
|
6
6
|
//# sourceMappingURL=public.d.ts.map
|
package/dist/public.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../src/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,oBAAoB,GAC1B,MAAM,gBAAgB,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../src/public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,oBAAoB,GAC1B,MAAM,gBAAgB,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,OAAO,EACL,WAAW,EACX,OAAO,EACP,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC;AACzB,cAAc,gCAAgC,CAAC"}
|
package/dist/public.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { clearCurrentUser, getCurrentUser, getLoggedInUser, getSessionStore, getSessionLocation, refetchCurrentUser, setSessionLocation, setUserLanguage, setUserProperties, userHasAccess } from "./current-user.js";
|
|
2
2
|
export * from "./environment.js";
|
|
3
3
|
export * from "./types/index.js";
|
|
4
|
-
export
|
|
4
|
+
export { fhirBaseUrl, makeUrl, openmrsFetch, OpenmrsFetchError, restBaseUrl, sessionEndpoint } from "./openmrs-fetch.js";
|
|
5
5
|
export * from "./openmrs-backend-dependencies.js";
|
package/mock-jest.ts
CHANGED
|
@@ -17,5 +17,8 @@ export const clearCurrentUser = jest.fn();
|
|
|
17
17
|
export const refetchCurrentUser = jest.fn();
|
|
18
18
|
export const setUserLanguage = jest.fn();
|
|
19
19
|
export const setUserProperties = jest.fn();
|
|
20
|
+
export const setSessionLocale = jest.fn(() => Promise.resolve());
|
|
21
|
+
export const verifyTotpCode = jest.fn();
|
|
22
|
+
export const logout = jest.fn(() => Promise.resolve());
|
|
20
23
|
export const userHasAccess = jest.fn();
|
|
21
24
|
export { OpenmrsFetchError } from './src/openmrs-fetch';
|
package/mock.ts
CHANGED
|
@@ -18,5 +18,8 @@ export const clearCurrentUser = vi.fn();
|
|
|
18
18
|
export const refetchCurrentUser = vi.fn();
|
|
19
19
|
export const setUserLanguage = vi.fn();
|
|
20
20
|
export const setUserProperties = vi.fn();
|
|
21
|
+
export const setSessionLocale = vi.fn(() => Promise.resolve());
|
|
22
|
+
export const verifyTotpCode = vi.fn();
|
|
23
|
+
export const logout = vi.fn(() => Promise.resolve());
|
|
21
24
|
export const userHasAccess = vi.fn();
|
|
22
25
|
export { OpenmrsFetchError } from './src/openmrs-fetch';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-api",
|
|
3
|
-
"version": "10.0.1-pre.
|
|
3
|
+
"version": "10.0.1-pre.5619",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "The javascript module for interacting with the OpenMRS API",
|
|
6
6
|
"type": "module",
|
|
@@ -57,16 +57,16 @@
|
|
|
57
57
|
"lodash-es": "^4.17.21"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@openmrs/esm-config": "^10.0.1-pre.
|
|
61
|
-
"@openmrs/esm-error-handling": "^10.0.1-pre.
|
|
62
|
-
"@openmrs/esm-globals": "^10.0.1-pre.
|
|
63
|
-
"@openmrs/esm-navigation": "^10.0.1-pre.
|
|
60
|
+
"@openmrs/esm-config": "^10.0.1-pre.5619",
|
|
61
|
+
"@openmrs/esm-error-handling": "^10.0.1-pre.5619",
|
|
62
|
+
"@openmrs/esm-globals": "^10.0.1-pre.5619",
|
|
63
|
+
"@openmrs/esm-navigation": "^10.0.1-pre.5619"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@openmrs/esm-config": "10.0.1-pre.
|
|
67
|
-
"@openmrs/esm-error-handling": "10.0.1-pre.
|
|
68
|
-
"@openmrs/esm-globals": "10.0.1-pre.
|
|
69
|
-
"@openmrs/esm-navigation": "10.0.1-pre.
|
|
66
|
+
"@openmrs/esm-config": "10.0.1-pre.5619",
|
|
67
|
+
"@openmrs/esm-error-handling": "10.0.1-pre.5619",
|
|
68
|
+
"@openmrs/esm-globals": "10.0.1-pre.5619",
|
|
69
|
+
"@openmrs/esm-navigation": "10.0.1-pre.5619",
|
|
70
70
|
"@swc/cli": "0.8.1",
|
|
71
71
|
"@swc/core": "1.15.21",
|
|
72
72
|
"@vitest/coverage-v8": "^4.1.2",
|