@openmrs/esm-api 10.0.1-pre.5635 → 10.0.1-pre.5642
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 +6 -0
- package/dist/current-user.d.ts.map +1 -1
- package/dist/current-user.js +31 -2
- package/package.json +9 -9
- package/src/current-user.test.ts +58 -7
- package/src/current-user.ts +50 -1
package/.turbo/turbo-build.log
CHANGED
package/dist/current-user.d.ts
CHANGED
|
@@ -9,6 +9,12 @@ export type UnloadedSessionStore = {
|
|
|
9
9
|
session: null;
|
|
10
10
|
/** Set when fetching the session failed before any session had loaded. */
|
|
11
11
|
error?: Error;
|
|
12
|
+
/**
|
|
13
|
+
* Set alongside `error`. `true` when the backend appears to be still starting up rather than broken:
|
|
14
|
+
* it redirected the session request to its initial setup page, or its gateway answered with a 502
|
|
15
|
+
* shortly after the page loaded.
|
|
16
|
+
*/
|
|
17
|
+
initializing?: boolean;
|
|
12
18
|
};
|
|
13
19
|
/** @internal */
|
|
14
20
|
export declare const sessionStore: import("zustand").StoreApi<SessionStore>;
|
|
@@ -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;IACd,0EAA0E;IAC1E,KAAK,CAAC,EAAE,KAAK,CAAC;
|
|
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;IACd;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,gBAAgB;AAChB,eAAO,MAAM,YAAY,0CAGvB,CAAC;AA4CH;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,yBA0BtE;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
|
@@ -12,6 +12,11 @@ import { OpenmrsFetchError, openmrsFetch, restBaseUrl, sessionEndpoint } from ".
|
|
|
12
12
|
* strictly newer than this.
|
|
13
13
|
*/ const sessionMaxAgeMillis = 60 * 1000;
|
|
14
14
|
let lastFetchTimeMillis = 0;
|
|
15
|
+
/**
|
|
16
|
+
* How long after the first session fetch a 502 is taken to mean that the backend is still starting up. A
|
|
17
|
+
* gateway answers with a 502 until the backend has deployed, which can take a few minutes after a restart.
|
|
18
|
+
*/ const startupGracePeriodMillis = 3 * 60 * 1000;
|
|
19
|
+
let firstFetchTimeMillis;
|
|
15
20
|
let inFlightRefresh = null;
|
|
16
21
|
/**
|
|
17
22
|
* Fetches the session if the store is unloaded or its session is older than `sessionMaxAgeMillis`.
|
|
@@ -148,6 +153,7 @@ function isSuperUser(user) {
|
|
|
148
153
|
* ```
|
|
149
154
|
*/ export function refetchCurrentUser(username, password) {
|
|
150
155
|
lastFetchTimeMillis = Date.now();
|
|
156
|
+
firstFetchTimeMillis ?? (firstFetchTimeMillis = lastFetchTimeMillis);
|
|
151
157
|
let headers = {};
|
|
152
158
|
if (username && password) {
|
|
153
159
|
headers['Authorization'] = `Basic ${window.btoa(`${username}:${password}`)}`;
|
|
@@ -434,6 +440,8 @@ function handleSessionResponse(result) {
|
|
|
434
440
|
};
|
|
435
441
|
sessionStore.setState(nextState, true);
|
|
436
442
|
resolve(nextState);
|
|
443
|
+
} else if (isInitialSetupRedirect(res)) {
|
|
444
|
+
reject(recordSessionFailure(Error('The server is still starting up'), true));
|
|
437
445
|
} else {
|
|
438
446
|
reject(recordSessionFailure(Error('The session endpoint did not respond with a session')));
|
|
439
447
|
}
|
|
@@ -451,6 +459,10 @@ function handleSessionResponse(result) {
|
|
|
451
459
|
resolve(sessionStore.getState());
|
|
452
460
|
return;
|
|
453
461
|
}
|
|
462
|
+
if (isGatewayErrorDuringStartup(err)) {
|
|
463
|
+
reject(recordSessionFailure(err, true));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
454
466
|
reportError(`Failed to fetch new session information: ${err}`);
|
|
455
467
|
reject(recordSessionFailure(err));
|
|
456
468
|
});
|
|
@@ -459,18 +471,35 @@ function handleSessionResponse(result) {
|
|
|
459
471
|
function isAuthFailure(err) {
|
|
460
472
|
return err instanceof OpenmrsFetchError && (err.response.status === 401 || err.response.status === 403);
|
|
461
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Until the backend has finished its initial setup, it answers every request with a redirect to its
|
|
476
|
+
* setup page, which `fetch()` follows, so the session request resolves with that page's HTML.
|
|
477
|
+
*/ function isInitialSetupRedirect(res) {
|
|
478
|
+
if (!res?.redirected || !res.url) {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
return new URL(res.url, window.location.href).pathname.replace(/\/+$/, '').endsWith('/initialsetup');
|
|
483
|
+
} catch {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function isGatewayErrorDuringStartup(err) {
|
|
488
|
+
return err instanceof OpenmrsFetchError && err.response.status === 502 && !sessionStore.getState().loaded && firstFetchTimeMillis !== undefined && Date.now() - firstFetchTimeMillis < startupGracePeriodMillis;
|
|
489
|
+
}
|
|
462
490
|
function isUnauthenticatedSession(body) {
|
|
463
491
|
return typeof body === 'object' && body !== null && body.authenticated === false;
|
|
464
492
|
}
|
|
465
493
|
/**
|
|
466
494
|
* A session that has already loaded is kept when a later fetch fails, since it is still the best answer
|
|
467
495
|
* available. With nothing to fall back on, the error is recorded so the app shell can show an error page.
|
|
468
|
-
*/ function recordSessionFailure(err) {
|
|
496
|
+
*/ function recordSessionFailure(err, initializing = false) {
|
|
469
497
|
if (!sessionStore.getState().loaded) {
|
|
470
498
|
sessionStore.setState({
|
|
471
499
|
loaded: false,
|
|
472
500
|
session: null,
|
|
473
|
-
error: err instanceof Error ? err : Error(String(err))
|
|
501
|
+
error: err instanceof Error ? err : Error(String(err)),
|
|
502
|
+
initializing
|
|
474
503
|
}, true);
|
|
475
504
|
}
|
|
476
505
|
return sessionStore.getState();
|
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.5642",
|
|
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.5642",
|
|
61
|
+
"@openmrs/esm-error-handling": "^10.0.1-pre.5642",
|
|
62
|
+
"@openmrs/esm-globals": "^10.0.1-pre.5642",
|
|
63
|
+
"@openmrs/esm-navigation": "^10.0.1-pre.5642"
|
|
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.5642",
|
|
67
|
+
"@openmrs/esm-error-handling": "10.0.1-pre.5642",
|
|
68
|
+
"@openmrs/esm-globals": "10.0.1-pre.5642",
|
|
69
|
+
"@openmrs/esm-navigation": "10.0.1-pre.5642",
|
|
70
70
|
"@swc/cli": "0.8.1",
|
|
71
71
|
"@swc/core": "1.15.21",
|
|
72
72
|
"@vitest/coverage-v8": "^4.1.2",
|
package/src/current-user.test.ts
CHANGED
|
@@ -35,7 +35,7 @@ vi.mock('@openmrs/esm-error-handling', () => ({
|
|
|
35
35
|
const mockOpenmrsFetch = vi.mocked(openmrsFetch);
|
|
36
36
|
const mockReportError = vi.mocked(reportError);
|
|
37
37
|
|
|
38
|
-
function
|
|
38
|
+
function createFetchError(status: number, responseBody: unknown = null) {
|
|
39
39
|
return new OpenmrsFetchError('/openmrs/ws/rest/v1/session', { status } as Response, responseBody as any, Error());
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -592,8 +592,8 @@ describe('refetchCurrentUser', () => {
|
|
|
592
592
|
const error = new Error('Bad gateway');
|
|
593
593
|
mockOpenmrsFetch.mockRejectedValue(error);
|
|
594
594
|
|
|
595
|
-
await expect(refetchCurrentUser()).rejects.toEqual({ loaded: false, session: null, error });
|
|
596
|
-
expect(sessionStore.getState()).toEqual({ loaded: false, session: null, error });
|
|
595
|
+
await expect(refetchCurrentUser()).rejects.toEqual({ loaded: false, session: null, error, initializing: false });
|
|
596
|
+
expect(sessionStore.getState()).toEqual({ loaded: false, session: null, error, initializing: false });
|
|
597
597
|
});
|
|
598
598
|
|
|
599
599
|
it('should keep an already-loaded session when a later fetch fails', async () => {
|
|
@@ -615,11 +615,62 @@ describe('refetchCurrentUser', () => {
|
|
|
615
615
|
loaded: false,
|
|
616
616
|
error: new Error('The session endpoint did not respond with a session'),
|
|
617
617
|
});
|
|
618
|
+
expect(sessionStore.getState()).toMatchObject({ initializing: false });
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it('should record that the server is starting up when the session request is redirected to initial setup', async () => {
|
|
622
|
+
mockOpenmrsFetch.mockResolvedValue({
|
|
623
|
+
...createMockFetchResponse(undefined),
|
|
624
|
+
redirected: true,
|
|
625
|
+
url: 'http://localhost/openmrs/initialsetup',
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
await expect(refetchCurrentUser()).rejects.toMatchObject({ loaded: false, session: null, initializing: true });
|
|
629
|
+
expect(sessionStore.getState()).toMatchObject({
|
|
630
|
+
loaded: false,
|
|
631
|
+
initializing: true,
|
|
632
|
+
error: new Error('The server is still starting up'),
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it('should record that the server is starting up when the gateway responds with a 502 soon after the first fetch', async () => {
|
|
637
|
+
mockReportError.mockClear();
|
|
638
|
+
mockOpenmrsFetch.mockRejectedValue(createFetchError(502));
|
|
639
|
+
|
|
640
|
+
await expect(refetchCurrentUser()).rejects.toMatchObject({ loaded: false, initializing: true });
|
|
641
|
+
expect(mockReportError).not.toHaveBeenCalled();
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it('should record an ordinary error when the gateway still responds with a 502 minutes after the first fetch', async () => {
|
|
645
|
+
vi.useFakeTimers();
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
mockOpenmrsFetch.mockRejectedValue(createFetchError(502));
|
|
649
|
+
await refetchCurrentUser().catch(() => {});
|
|
650
|
+
|
|
651
|
+
vi.setSystemTime(Date.now() + 3 * 60 * 1000);
|
|
652
|
+
mockReportError.mockClear();
|
|
653
|
+
|
|
654
|
+
await expect(refetchCurrentUser()).rejects.toMatchObject({ loaded: false, initializing: false });
|
|
655
|
+
expect(mockReportError).toHaveBeenCalled();
|
|
656
|
+
} finally {
|
|
657
|
+
vi.useRealTimers();
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('should keep an already-loaded session and report the error when the gateway responds with a 502', async () => {
|
|
662
|
+
const mockSession: Session = { authenticated: true, sessionId: 'test-session', user: {} as LoggedInUser };
|
|
663
|
+
sessionStore.setState({ loaded: true, session: mockSession }, true);
|
|
664
|
+
mockReportError.mockClear();
|
|
665
|
+
mockOpenmrsFetch.mockRejectedValue(createFetchError(502));
|
|
666
|
+
|
|
667
|
+
await expect(refetchCurrentUser()).rejects.toEqual({ loaded: true, session: mockSession });
|
|
668
|
+
expect(mockReportError).toHaveBeenCalled();
|
|
618
669
|
});
|
|
619
670
|
|
|
620
671
|
it('should use the unauthenticated session in the body of a 401 response', async () => {
|
|
621
672
|
const unauthenticated = { authenticated: false, sessionId: 'abc', allowedLocales: ['en', 'fr'] };
|
|
622
|
-
mockOpenmrsFetch.mockRejectedValue(
|
|
673
|
+
mockOpenmrsFetch.mockRejectedValue(createFetchError(401, unauthenticated));
|
|
623
674
|
|
|
624
675
|
await expect(refetchCurrentUser()).resolves.toEqual({ loaded: true, session: unauthenticated });
|
|
625
676
|
});
|
|
@@ -628,7 +679,7 @@ describe('refetchCurrentUser', () => {
|
|
|
628
679
|
'should record a logged-out session when the session endpoint responds with %i',
|
|
629
680
|
async (status) => {
|
|
630
681
|
mockReportError.mockClear();
|
|
631
|
-
mockOpenmrsFetch.mockRejectedValue(
|
|
682
|
+
mockOpenmrsFetch.mockRejectedValue(createFetchError(status));
|
|
632
683
|
|
|
633
684
|
await expect(refetchCurrentUser()).resolves.toEqual({
|
|
634
685
|
loaded: true,
|
|
@@ -644,7 +695,7 @@ describe('refetchCurrentUser', () => {
|
|
|
644
695
|
);
|
|
645
696
|
|
|
646
697
|
it('should not leave readers waiting on the failed request after an auth failure', async () => {
|
|
647
|
-
mockOpenmrsFetch.mockRejectedValueOnce(
|
|
698
|
+
mockOpenmrsFetch.mockRejectedValueOnce(createFetchError(401));
|
|
648
699
|
await refetchCurrentUser();
|
|
649
700
|
|
|
650
701
|
await expect(getCurrentUser()).resolves.toEqual({ authenticated: false, sessionId: '' });
|
|
@@ -901,7 +952,7 @@ describe('logout', () => {
|
|
|
901
952
|
});
|
|
902
953
|
|
|
903
954
|
it('should treat a session the server has already ended as logged out', async () => {
|
|
904
|
-
mockOpenmrsFetch.mockRejectedValue(
|
|
955
|
+
mockOpenmrsFetch.mockRejectedValue(createFetchError(401));
|
|
905
956
|
|
|
906
957
|
await expect(logout()).resolves.toBeUndefined();
|
|
907
958
|
expect(sessionStore.getState()).toEqual(loggedOut);
|
package/src/current-user.ts
CHANGED
|
@@ -17,6 +17,12 @@ export type UnloadedSessionStore = {
|
|
|
17
17
|
session: null;
|
|
18
18
|
/** Set when fetching the session failed before any session had loaded. */
|
|
19
19
|
error?: Error;
|
|
20
|
+
/**
|
|
21
|
+
* Set alongside `error`. `true` when the backend appears to be still starting up rather than broken:
|
|
22
|
+
* it redirected the session request to its initial setup page, or its gateway answered with a 502
|
|
23
|
+
* shortly after the page loaded.
|
|
24
|
+
*/
|
|
25
|
+
initializing?: boolean;
|
|
20
26
|
};
|
|
21
27
|
|
|
22
28
|
/** @internal */
|
|
@@ -33,6 +39,14 @@ export const sessionStore = createGlobalStore<SessionStore>('session', {
|
|
|
33
39
|
const sessionMaxAgeMillis = 60 * 1000;
|
|
34
40
|
|
|
35
41
|
let lastFetchTimeMillis = 0;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* How long after the first session fetch a 502 is taken to mean that the backend is still starting up. A
|
|
45
|
+
* gateway answers with a 502 until the backend has deployed, which can take a few minutes after a restart.
|
|
46
|
+
*/
|
|
47
|
+
const startupGracePeriodMillis = 3 * 60 * 1000;
|
|
48
|
+
|
|
49
|
+
let firstFetchTimeMillis: number | undefined;
|
|
36
50
|
let inFlightRefresh: Promise<SessionStore> | null = null;
|
|
37
51
|
|
|
38
52
|
/**
|
|
@@ -236,6 +250,7 @@ function isSuperUser(user: { roles: Array<Role> }) {
|
|
|
236
250
|
*/
|
|
237
251
|
export function refetchCurrentUser(username?: string, password?: string) {
|
|
238
252
|
lastFetchTimeMillis = Date.now();
|
|
253
|
+
firstFetchTimeMillis ??= lastFetchTimeMillis;
|
|
239
254
|
let headers = {};
|
|
240
255
|
if (username && password) {
|
|
241
256
|
headers['Authorization'] = `Basic ${window.btoa(`${username}:${password}`)}`;
|
|
@@ -557,6 +572,8 @@ function handleSessionResponse(result: Promise<FetchResponse<Session>>) {
|
|
|
557
572
|
const nextState: SessionStore = { loaded: true, session: res.data };
|
|
558
573
|
sessionStore.setState(nextState, true);
|
|
559
574
|
resolve(nextState);
|
|
575
|
+
} else if (isInitialSetupRedirect(res)) {
|
|
576
|
+
reject(recordSessionFailure(Error('The server is still starting up'), true));
|
|
560
577
|
} else {
|
|
561
578
|
reject(recordSessionFailure(Error('The session endpoint did not respond with a session')));
|
|
562
579
|
}
|
|
@@ -573,6 +590,11 @@ function handleSessionResponse(result: Promise<FetchResponse<Session>>) {
|
|
|
573
590
|
return;
|
|
574
591
|
}
|
|
575
592
|
|
|
593
|
+
if (isGatewayErrorDuringStartup(err)) {
|
|
594
|
+
reject(recordSessionFailure(err, true));
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
576
598
|
reportError(`Failed to fetch new session information: ${err}`);
|
|
577
599
|
reject(recordSessionFailure(err));
|
|
578
600
|
});
|
|
@@ -583,6 +605,32 @@ function isAuthFailure(err: unknown): err is OpenmrsFetchError {
|
|
|
583
605
|
return err instanceof OpenmrsFetchError && (err.response.status === 401 || err.response.status === 403);
|
|
584
606
|
}
|
|
585
607
|
|
|
608
|
+
/**
|
|
609
|
+
* Until the backend has finished its initial setup, it answers every request with a redirect to its
|
|
610
|
+
* setup page, which `fetch()` follows, so the session request resolves with that page's HTML.
|
|
611
|
+
*/
|
|
612
|
+
function isInitialSetupRedirect(res: FetchResponse | undefined) {
|
|
613
|
+
if (!res?.redirected || !res.url) {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
try {
|
|
618
|
+
return new URL(res.url, window.location.href).pathname.replace(/\/+$/, '').endsWith('/initialsetup');
|
|
619
|
+
} catch {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function isGatewayErrorDuringStartup(err: unknown) {
|
|
625
|
+
return (
|
|
626
|
+
err instanceof OpenmrsFetchError &&
|
|
627
|
+
err.response.status === 502 &&
|
|
628
|
+
!sessionStore.getState().loaded &&
|
|
629
|
+
firstFetchTimeMillis !== undefined &&
|
|
630
|
+
Date.now() - firstFetchTimeMillis < startupGracePeriodMillis
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
586
634
|
function isUnauthenticatedSession(body: unknown): body is Session {
|
|
587
635
|
return typeof body === 'object' && body !== null && (body as Session).authenticated === false;
|
|
588
636
|
}
|
|
@@ -591,13 +639,14 @@ function isUnauthenticatedSession(body: unknown): body is Session {
|
|
|
591
639
|
* A session that has already loaded is kept when a later fetch fails, since it is still the best answer
|
|
592
640
|
* available. With nothing to fall back on, the error is recorded so the app shell can show an error page.
|
|
593
641
|
*/
|
|
594
|
-
function recordSessionFailure(err: unknown): SessionStore {
|
|
642
|
+
function recordSessionFailure(err: unknown, initializing = false): SessionStore {
|
|
595
643
|
if (!sessionStore.getState().loaded) {
|
|
596
644
|
sessionStore.setState(
|
|
597
645
|
{
|
|
598
646
|
loaded: false,
|
|
599
647
|
session: null,
|
|
600
648
|
error: err instanceof Error ? err : Error(String(err)),
|
|
649
|
+
initializing,
|
|
601
650
|
},
|
|
602
651
|
true,
|
|
603
652
|
);
|