react-session.manager.sk 2.0.1 → 2.0.2
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/index.js +1 -1
- package/dist/index.js.LICENSE.txt +9 -0
- package/package.json +1 -1
- package/src/components/ErrorBoundary.js +27 -12
- package/src/components/ThemeWrapper.js +36 -0
- package/src/index.js +232 -30
|
@@ -17,3 +17,12 @@
|
|
|
17
17
|
* This source code is licensed under the MIT license found in the
|
|
18
18
|
* LICENSE file in the root directory of this source tree.
|
|
19
19
|
*/
|
|
20
|
+
|
|
21
|
+
/** @license React v16.13.1
|
|
22
|
+
* react-is.production.min.js
|
|
23
|
+
*
|
|
24
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
25
|
+
*
|
|
26
|
+
* This source code is licensed under the MIT license found in the
|
|
27
|
+
* LICENSE file in the root directory of this source tree.
|
|
28
|
+
*/
|
package/package.json
CHANGED
|
@@ -21,15 +21,23 @@ class ErrorBoundary extends React.Component {
|
|
|
21
21
|
errorInfo: errorInfo,
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
// Check if this is a theme-related error
|
|
25
|
+
const isThemeError =
|
|
26
|
+
error?.message?.includes("Cannot convert undefined or null to object") &&
|
|
27
|
+
(error?.stack?.includes("Object.entries") ||
|
|
28
|
+
error?.stack?.includes("theme") ||
|
|
29
|
+
error?.stack?.includes("Styled"));
|
|
30
|
+
|
|
31
|
+
const errorMessage = isThemeError
|
|
32
|
+
? "A theme error occurred. This might be due to connection issues. Please refresh the page."
|
|
33
|
+
: "Something went wrong. Please refresh the page or try again later.";
|
|
34
|
+
|
|
24
35
|
// Show a toast notification for the error
|
|
25
|
-
toast.error(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
autoClose: 10000,
|
|
31
|
-
}
|
|
32
|
-
);
|
|
36
|
+
toast.error(errorMessage, {
|
|
37
|
+
toastId: "error-boundary",
|
|
38
|
+
icon: <ErrorIcon />,
|
|
39
|
+
autoClose: 10000,
|
|
40
|
+
});
|
|
33
41
|
|
|
34
42
|
// Log error to external service if needed
|
|
35
43
|
if (this.props.onError) {
|
|
@@ -57,11 +65,14 @@ class ErrorBoundary extends React.Component {
|
|
|
57
65
|
border: "1px solid #dee2e6",
|
|
58
66
|
borderRadius: "8px",
|
|
59
67
|
margin: "20px",
|
|
68
|
+
minHeight: "200px",
|
|
69
|
+
display: "flex",
|
|
70
|
+
flexDirection: "column",
|
|
71
|
+
justifyContent: "center",
|
|
72
|
+
alignItems: "center",
|
|
60
73
|
}}
|
|
61
74
|
>
|
|
62
|
-
<
|
|
63
|
-
style={{ fontSize: "48px", color: "#dc3545", marginBottom: "16px" }}
|
|
64
|
-
/>
|
|
75
|
+
<div style={{ fontSize: "48px", marginBottom: "16px" }}>⚠️</div>
|
|
65
76
|
<h2 style={{ color: "#343a40", marginBottom: "16px" }}>
|
|
66
77
|
Something went wrong
|
|
67
78
|
</h2>
|
|
@@ -97,7 +108,9 @@ class ErrorBoundary extends React.Component {
|
|
|
97
108
|
</button>
|
|
98
109
|
|
|
99
110
|
{process.env.NODE_ENV === "development" && this.state.error && (
|
|
100
|
-
<details
|
|
111
|
+
<details
|
|
112
|
+
style={{ marginTop: "20px", textAlign: "left", width: "100%" }}
|
|
113
|
+
>
|
|
101
114
|
<summary style={{ cursor: "pointer", fontWeight: "bold" }}>
|
|
102
115
|
Error Details (Development)
|
|
103
116
|
</summary>
|
|
@@ -109,6 +122,8 @@ class ErrorBoundary extends React.Component {
|
|
|
109
122
|
overflow: "auto",
|
|
110
123
|
fontSize: "12px",
|
|
111
124
|
marginTop: "10px",
|
|
125
|
+
maxHeight: "300px",
|
|
126
|
+
textAlign: "left",
|
|
112
127
|
}}
|
|
113
128
|
>
|
|
114
129
|
{this.state.error && this.state.error.toString()}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ThemeProvider, createTheme } from "@mui/material/styles";
|
|
3
|
+
|
|
4
|
+
// Fallback theme for when the main app theme is not available
|
|
5
|
+
const fallbackTheme = createTheme({
|
|
6
|
+
palette: {
|
|
7
|
+
mode: "light",
|
|
8
|
+
primary: {
|
|
9
|
+
main: "#1976d2",
|
|
10
|
+
},
|
|
11
|
+
secondary: {
|
|
12
|
+
main: "#dc3545",
|
|
13
|
+
},
|
|
14
|
+
error: {
|
|
15
|
+
main: "#d32f2f",
|
|
16
|
+
},
|
|
17
|
+
warning: {
|
|
18
|
+
main: "#ed6c02",
|
|
19
|
+
},
|
|
20
|
+
info: {
|
|
21
|
+
main: "#0288d1",
|
|
22
|
+
},
|
|
23
|
+
success: {
|
|
24
|
+
main: "#2e7d32",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const ThemeWrapper = ({ children, theme }) => {
|
|
30
|
+
// Use the provided theme or fallback theme
|
|
31
|
+
const safeTheme = theme || fallbackTheme;
|
|
32
|
+
|
|
33
|
+
return <ThemeProvider theme={safeTheme}>{children}</ThemeProvider>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default ThemeWrapper;
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import React, { createContext, useState, useEffect, useCallback } from "react";
|
|
|
2
2
|
import versionCompare from "./components/versionCompare";
|
|
3
3
|
import getDeviceFingerprint from "./components/FingerPrint";
|
|
4
4
|
import ErrorBoundary from "./components/ErrorBoundary";
|
|
5
|
+
import ThemeWrapper from "./components/ThemeWrapper";
|
|
5
6
|
// Notifications
|
|
6
7
|
import { ToastContainer, toast } from "react-toastify";
|
|
7
8
|
import "react-toastify/dist/ReactToastify.css";
|
|
@@ -90,6 +91,12 @@ const SessionManagerProvider = ({
|
|
|
90
91
|
})
|
|
91
92
|
.catch((err) => {
|
|
92
93
|
console.error("Token refresh failed:", err);
|
|
94
|
+
|
|
95
|
+
// Track auth failures
|
|
96
|
+
if (err?.code === "ERR_NETWORK" || err?.code === "ERR_CONNECTION_REFUSED") {
|
|
97
|
+
setAuthError(true);
|
|
98
|
+
}
|
|
99
|
+
|
|
93
100
|
// Clear invalid tokens
|
|
94
101
|
localStorage.removeItem("Authorization");
|
|
95
102
|
sessionStorage.removeItem("Authorization");
|
|
@@ -98,13 +105,19 @@ const SessionManagerProvider = ({
|
|
|
98
105
|
|
|
99
106
|
// Only show toast for actual network/server errors, not auth failures
|
|
100
107
|
if (err?.code === "ERR_NETWORK" || err?.response?.status >= 500) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
// Don't spam with toasts during retries
|
|
109
|
+
const lastToast = sessionStorage.getItem("lastRefreshErrorToast");
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
if (!lastToast || now - parseInt(lastToast) > 30000) { // 30 seconds
|
|
112
|
+
toast.error(
|
|
113
|
+
"Unable to refresh your session. Please login again.",
|
|
114
|
+
{
|
|
115
|
+
toastId: "token-refresh-error",
|
|
116
|
+
icon: <Logout />,
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
sessionStorage.setItem("lastRefreshErrorToast", now.toString());
|
|
120
|
+
}
|
|
108
121
|
}
|
|
109
122
|
});
|
|
110
123
|
}, 100);
|
|
@@ -130,6 +143,7 @@ const SessionManagerProvider = ({
|
|
|
130
143
|
const [isAdmin, setIsAdmin] = useState(false);
|
|
131
144
|
const [userInfo, setUserInfo] = useState({});
|
|
132
145
|
const [loadingUser, setLoadingUser] = useState(true);
|
|
146
|
+
const [authError, setAuthError] = useState(false);
|
|
133
147
|
|
|
134
148
|
useEffect(() => {
|
|
135
149
|
setTimeout(() => {
|
|
@@ -140,13 +154,18 @@ const SessionManagerProvider = ({
|
|
|
140
154
|
setCurrentLoggedIn(data.logged_in);
|
|
141
155
|
setIsAdmin(data.is_admin);
|
|
142
156
|
setUserInfo(data.Info || {});
|
|
157
|
+
setAuthError(false);
|
|
143
158
|
}
|
|
144
159
|
})
|
|
145
160
|
.catch((err) => {
|
|
146
161
|
console.error("User data loading failed:", err);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
162
|
+
|
|
163
|
+
// Check if this is a persistent auth error
|
|
164
|
+
if (err?.code === "ERR_NETWORK" || err?.code === "ERR_CONNECTION_REFUSED") {
|
|
165
|
+
setAuthError(true);
|
|
166
|
+
// Don't show toast immediately, let retries happen first
|
|
167
|
+
} else if (err?.response?.status !== 401 && err?.response?.status !== 403) {
|
|
168
|
+
if (err?.response?.status >= 500) {
|
|
150
169
|
toast.error(
|
|
151
170
|
"Unable to load user information. Some features may be limited.",
|
|
152
171
|
{
|
|
@@ -193,6 +212,8 @@ const SessionManagerProvider = ({
|
|
|
193
212
|
const customInterceptor = () => {
|
|
194
213
|
AuthenticatedAxiosObject.interceptors.response.use(
|
|
195
214
|
(response) => {
|
|
215
|
+
// Reset connection failure count on successful response
|
|
216
|
+
sessionStorage.setItem("connectionFailures", "0");
|
|
196
217
|
return response;
|
|
197
218
|
},
|
|
198
219
|
(error) => {
|
|
@@ -262,10 +283,18 @@ const SessionManagerProvider = ({
|
|
|
262
283
|
? "Unable to connect to the server. Please check your internet connection and try again."
|
|
263
284
|
: "The server is not responding, please reload or try again later.";
|
|
264
285
|
|
|
265
|
-
toast
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
286
|
+
// Only show toast after multiple failures to avoid spam
|
|
287
|
+
const failureCount = parseInt(sessionStorage.getItem("connectionFailures") || "0");
|
|
288
|
+
sessionStorage.setItem("connectionFailures", (failureCount + 1).toString());
|
|
289
|
+
|
|
290
|
+
if (failureCount >= 3) {
|
|
291
|
+
toast.error(errorMessage, {
|
|
292
|
+
toastId: `${error?.code || "ERR_CONNECTION_REFUSED"}`,
|
|
293
|
+
icon: <GppBad />,
|
|
294
|
+
});
|
|
295
|
+
// Reset counter after showing toast
|
|
296
|
+
sessionStorage.setItem("connectionFailures", "0");
|
|
297
|
+
}
|
|
269
298
|
}
|
|
270
299
|
return Promise.reject(error);
|
|
271
300
|
}
|
|
@@ -353,25 +382,198 @@ const SessionManagerProvider = ({
|
|
|
353
382
|
hasRole: hasRole,
|
|
354
383
|
deviceUID: deviceUID,
|
|
355
384
|
loadingUser: loadingUser,
|
|
385
|
+
authError: authError,
|
|
356
386
|
};
|
|
357
387
|
|
|
388
|
+
// Show loading state while initializing
|
|
389
|
+
if (loadingUser) {
|
|
390
|
+
return (
|
|
391
|
+
<ThemeWrapper>
|
|
392
|
+
<ErrorBoundary>
|
|
393
|
+
<SessionManager.Provider value={contextValue}>
|
|
394
|
+
<div style={{
|
|
395
|
+
display: 'flex',
|
|
396
|
+
flexDirection: 'column',
|
|
397
|
+
alignItems: 'center',
|
|
398
|
+
justifyContent: 'center',
|
|
399
|
+
minHeight: '100vh',
|
|
400
|
+
padding: '20px',
|
|
401
|
+
backgroundColor: '#f5f5f5',
|
|
402
|
+
fontFamily: 'Arial, sans-serif'
|
|
403
|
+
}}>
|
|
404
|
+
<div style={{
|
|
405
|
+
backgroundColor: 'white',
|
|
406
|
+
padding: '40px',
|
|
407
|
+
borderRadius: '8px',
|
|
408
|
+
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
|
|
409
|
+
textAlign: 'center',
|
|
410
|
+
maxWidth: '400px'
|
|
411
|
+
}}>
|
|
412
|
+
<div style={{
|
|
413
|
+
width: '40px',
|
|
414
|
+
height: '40px',
|
|
415
|
+
border: '4px solid #f3f3f3',
|
|
416
|
+
borderTop: '4px solid #1976d2',
|
|
417
|
+
borderRadius: '50%',
|
|
418
|
+
animation: 'spin 1s linear infinite',
|
|
419
|
+
margin: '0 auto 16px'
|
|
420
|
+
}} />
|
|
421
|
+
<h3 style={{ color: '#333', marginBottom: '8px' }}>Loading...</h3>
|
|
422
|
+
<p style={{ color: '#666', margin: 0 }}>
|
|
423
|
+
Initializing session manager
|
|
424
|
+
</p>
|
|
425
|
+
</div>
|
|
426
|
+
<style>{`
|
|
427
|
+
@keyframes spin {
|
|
428
|
+
0% { transform: rotate(0deg); }
|
|
429
|
+
100% { transform: rotate(360deg); }
|
|
430
|
+
}
|
|
431
|
+
`}</style>
|
|
432
|
+
</div>
|
|
433
|
+
</SessionManager.Provider>
|
|
434
|
+
</ErrorBoundary>
|
|
435
|
+
</ThemeWrapper>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
return (
|
|
439
|
+
<ThemeWrapper>
|
|
440
|
+
<ErrorBoundary>
|
|
441
|
+
<SessionManager.Provider value={contextValue}>
|
|
442
|
+
<ToastContainer
|
|
443
|
+
position="top-left"
|
|
444
|
+
autoClose={5000}
|
|
445
|
+
closeOnClick
|
|
446
|
+
pauseOnFocusLoss
|
|
447
|
+
pauseOnHover
|
|
448
|
+
newestOnTop={false}
|
|
449
|
+
toastClassName={"custToast materialToast"}
|
|
450
|
+
{...toastOptions}
|
|
451
|
+
/>
|
|
452
|
+
<div style={{
|
|
453
|
+
display: 'flex',
|
|
454
|
+
flexDirection: 'column',
|
|
455
|
+
alignItems: 'center',
|
|
456
|
+
justifyContent: 'center',
|
|
457
|
+
minHeight: '100vh',
|
|
458
|
+
padding: '20px',
|
|
459
|
+
backgroundColor: '#f5f5f5',
|
|
460
|
+
fontFamily: 'Arial, sans-serif'
|
|
461
|
+
}}>
|
|
462
|
+
<div style={{
|
|
463
|
+
backgroundColor: 'white',
|
|
464
|
+
padding: '40px',
|
|
465
|
+
borderRadius: '8px',
|
|
466
|
+
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
|
|
467
|
+
textAlign: 'center',
|
|
468
|
+
maxWidth: '400px'
|
|
469
|
+
}}>
|
|
470
|
+
<div style={{ fontSize: '48px', marginBottom: '16px' }}>⚠️</div>
|
|
471
|
+
<h2 style={{ color: '#d32f2f', marginBottom: '16px' }}>Connection Error</h2>
|
|
472
|
+
<p style={{ color: '#666', marginBottom: '24px' }}>
|
|
473
|
+
Unable to connect to the server. Please check your internet connection and try again.
|
|
474
|
+
</p>
|
|
475
|
+
<button
|
|
476
|
+
onClick={() => window.location.reload()}
|
|
477
|
+
style={{
|
|
478
|
+
backgroundColor: '#1976d2',
|
|
479
|
+
color: 'white',
|
|
480
|
+
border: 'none',
|
|
481
|
+
padding: '12px 24px',
|
|
482
|
+
borderRadius: '4px',
|
|
483
|
+
cursor: 'pointer',
|
|
484
|
+
fontSize: '16px'
|
|
485
|
+
}}
|
|
486
|
+
>
|
|
487
|
+
Retry
|
|
488
|
+
</button>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
</SessionManager.Provider>
|
|
492
|
+
</ErrorBoundary>
|
|
493
|
+
</ThemeWrapper>
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// If there's a persistent auth error, render a minimal error state
|
|
498
|
+
if (authError && !loadingUser) {
|
|
499
|
+
return (
|
|
500
|
+
<ThemeWrapper>
|
|
501
|
+
<ErrorBoundary>
|
|
502
|
+
<SessionManager.Provider value={contextValue}>
|
|
503
|
+
<ToastContainer
|
|
504
|
+
position="top-left"
|
|
505
|
+
autoClose={5000}
|
|
506
|
+
closeOnClick
|
|
507
|
+
pauseOnFocusLoss
|
|
508
|
+
pauseOnHover
|
|
509
|
+
newestOnTop={false}
|
|
510
|
+
toastClassName={"custToast materialToast"}
|
|
511
|
+
{...toastOptions}
|
|
512
|
+
/>
|
|
513
|
+
<div style={{
|
|
514
|
+
display: 'flex',
|
|
515
|
+
flexDirection: 'column',
|
|
516
|
+
alignItems: 'center',
|
|
517
|
+
justifyContent: 'center',
|
|
518
|
+
minHeight: '100vh',
|
|
519
|
+
padding: '20px',
|
|
520
|
+
backgroundColor: '#f5f5f5',
|
|
521
|
+
fontFamily: 'Arial, sans-serif'
|
|
522
|
+
}}>
|
|
523
|
+
<div style={{
|
|
524
|
+
backgroundColor: 'white',
|
|
525
|
+
padding: '40px',
|
|
526
|
+
borderRadius: '8px',
|
|
527
|
+
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
|
|
528
|
+
textAlign: 'center',
|
|
529
|
+
maxWidth: '400px'
|
|
530
|
+
}}>
|
|
531
|
+
<div style={{ fontSize: '48px', marginBottom: '16px' }}>⚠️</div>
|
|
532
|
+
<h2 style={{ color: '#d32f2f', marginBottom: '16px' }}>Connection Error</h2>
|
|
533
|
+
<p style={{ color: '#666', marginBottom: '24px' }}>
|
|
534
|
+
Unable to connect to the server. Please check your internet connection and try again.
|
|
535
|
+
</p>
|
|
536
|
+
<button
|
|
537
|
+
onClick={() => window.location.reload()}
|
|
538
|
+
style={{
|
|
539
|
+
backgroundColor: '#1976d2',
|
|
540
|
+
color: 'white',
|
|
541
|
+
border: 'none',
|
|
542
|
+
padding: '12px 24px',
|
|
543
|
+
borderRadius: '4px',
|
|
544
|
+
cursor: 'pointer',
|
|
545
|
+
fontSize: '16px'
|
|
546
|
+
}}
|
|
547
|
+
>
|
|
548
|
+
Retry
|
|
549
|
+
</button>
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
</SessionManager.Provider>
|
|
553
|
+
</ErrorBoundary>
|
|
554
|
+
</ThemeWrapper>
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
358
558
|
return (
|
|
359
|
-
<
|
|
360
|
-
<
|
|
361
|
-
<
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
559
|
+
<ThemeWrapper>
|
|
560
|
+
<ErrorBoundary>
|
|
561
|
+
<SessionManager.Provider value={contextValue}>
|
|
562
|
+
<ToastContainer
|
|
563
|
+
position="top-left"
|
|
564
|
+
autoClose={5000}
|
|
565
|
+
closeOnClick
|
|
566
|
+
pauseOnFocusLoss
|
|
567
|
+
pauseOnHover
|
|
568
|
+
newestOnTop={false}
|
|
569
|
+
toastClassName={"custToast materialToast"}
|
|
570
|
+
{...toastOptions}
|
|
571
|
+
/>
|
|
572
|
+
<VersionProtection appVersion={appVersion} />
|
|
573
|
+
{children}
|
|
574
|
+
</SessionManager.Provider>
|
|
575
|
+
</ErrorBoundary>
|
|
576
|
+
</ThemeWrapper>
|
|
375
577
|
);
|
|
376
578
|
};
|
|
377
579
|
|