react-session.manager.sk 2.0.0 → 2.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-session.manager.sk",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "A react component used to manage token sessions with flask backend",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -0,0 +1,128 @@
1
+ import React from "react";
2
+ import { toast } from "react-toastify";
3
+ import { Error as ErrorIcon } from "@mui/icons-material";
4
+
5
+ class ErrorBoundary extends React.Component {
6
+ constructor(props) {
7
+ super(props);
8
+ this.state = { hasError: false, error: null, errorInfo: null };
9
+ }
10
+
11
+ static getDerivedStateFromError(error) {
12
+ // Update state so the next render will show the fallback UI
13
+ return { hasError: true };
14
+ }
15
+
16
+ componentDidCatch(error, errorInfo) {
17
+ console.error("ErrorBoundary caught an error:", error, errorInfo);
18
+
19
+ this.setState({
20
+ error: error,
21
+ errorInfo: errorInfo,
22
+ });
23
+
24
+ // Show a toast notification for the error
25
+ toast.error(
26
+ "Something went wrong. Please refresh the page or try again later.",
27
+ {
28
+ toastId: "error-boundary",
29
+ icon: <ErrorIcon />,
30
+ autoClose: 10000,
31
+ }
32
+ );
33
+
34
+ // Log error to external service if needed
35
+ if (this.props.onError) {
36
+ this.props.onError(error, errorInfo);
37
+ }
38
+ }
39
+
40
+ handleRetry = () => {
41
+ this.setState({ hasError: false, error: null, errorInfo: null });
42
+ };
43
+
44
+ render() {
45
+ if (this.state.hasError) {
46
+ // Custom error UI
47
+ if (this.props.fallback) {
48
+ return this.props.fallback;
49
+ }
50
+
51
+ return (
52
+ <div
53
+ style={{
54
+ padding: "20px",
55
+ textAlign: "center",
56
+ backgroundColor: "#f8f9fa",
57
+ border: "1px solid #dee2e6",
58
+ borderRadius: "8px",
59
+ margin: "20px",
60
+ }}
61
+ >
62
+ <ErrorIcon
63
+ style={{ fontSize: "48px", color: "#dc3545", marginBottom: "16px" }}
64
+ />
65
+ <h2 style={{ color: "#343a40", marginBottom: "16px" }}>
66
+ Something went wrong
67
+ </h2>
68
+ <p style={{ color: "#6c757d", marginBottom: "20px" }}>
69
+ An unexpected error occurred. Please try refreshing the page.
70
+ </p>
71
+ <button
72
+ onClick={this.handleRetry}
73
+ style={{
74
+ backgroundColor: "#007bff",
75
+ color: "white",
76
+ border: "none",
77
+ padding: "10px 20px",
78
+ borderRadius: "4px",
79
+ cursor: "pointer",
80
+ marginRight: "10px",
81
+ }}
82
+ >
83
+ Try Again
84
+ </button>
85
+ <button
86
+ onClick={() => window.location.reload()}
87
+ style={{
88
+ backgroundColor: "#28a745",
89
+ color: "white",
90
+ border: "none",
91
+ padding: "10px 20px",
92
+ borderRadius: "4px",
93
+ cursor: "pointer",
94
+ }}
95
+ >
96
+ Refresh Page
97
+ </button>
98
+
99
+ {process.env.NODE_ENV === "development" && this.state.error && (
100
+ <details style={{ marginTop: "20px", textAlign: "left" }}>
101
+ <summary style={{ cursor: "pointer", fontWeight: "bold" }}>
102
+ Error Details (Development)
103
+ </summary>
104
+ <pre
105
+ style={{
106
+ backgroundColor: "#f8f9fa",
107
+ padding: "10px",
108
+ borderRadius: "4px",
109
+ overflow: "auto",
110
+ fontSize: "12px",
111
+ marginTop: "10px",
112
+ }}
113
+ >
114
+ {this.state.error && this.state.error.toString()}
115
+ <br />
116
+ {this.state.errorInfo?.componentStack}
117
+ </pre>
118
+ </details>
119
+ )}
120
+ </div>
121
+ );
122
+ }
123
+
124
+ return this.props.children;
125
+ }
126
+ }
127
+
128
+ export default ErrorBoundary;
package/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import React, { createContext, useState, useEffect, useCallback } from "react";
2
2
  import versionCompare from "./components/versionCompare";
3
3
  import getDeviceFingerprint from "./components/FingerPrint";
4
+ import ErrorBoundary from "./components/ErrorBoundary";
4
5
  // Notifications
5
6
  import { ToastContainer, toast } from "react-toastify";
6
7
  import "react-toastify/dist/ReactToastify.css";
@@ -40,9 +41,26 @@ const SessionManagerProvider = ({
40
41
 
41
42
  useEffect(() => {
42
43
  if (!deviceUID) {
43
- const uid = getDeviceFingerprint();
44
- setDeviceUID(uid);
45
- AuthenticatedAxiosObject.defaults.headers.common["deviceUID"] = uid;
44
+ try {
45
+ const uid = getDeviceFingerprint();
46
+ if (uid) {
47
+ setDeviceUID(uid);
48
+ localStorage.setItem("deviceUID", uid);
49
+ AuthenticatedAxiosObject.defaults.headers.common["deviceUID"] = uid;
50
+ }
51
+ } catch (err) {
52
+ console.error("Failed to generate device fingerprint:", err);
53
+ // Generate a fallback UUID
54
+ const fallbackUID =
55
+ "fallback-" +
56
+ Date.now() +
57
+ "-" +
58
+ Math.random().toString(36).substr(2, 9);
59
+ setDeviceUID(fallbackUID);
60
+ localStorage.setItem("deviceUID", fallbackUID);
61
+ AuthenticatedAxiosObject.defaults.headers.common["deviceUID"] =
62
+ fallbackUID;
63
+ }
46
64
  }
47
65
  }, [deviceUID, AuthenticatedAxiosObject]);
48
66
 
@@ -60,7 +78,7 @@ const SessionManagerProvider = ({
60
78
  setTimeout(() => {
61
79
  refreshToken()
62
80
  .then((data) => {
63
- if (data.refreshed) {
81
+ if (data?.refreshed) {
64
82
  const token = `Bearer ${data.access_token}`;
65
83
  setCurrent(token);
66
84
  AuthenticatedAxiosObject.defaults.headers.common[
@@ -71,10 +89,23 @@ const SessionManagerProvider = ({
71
89
  }
72
90
  })
73
91
  .catch((err) => {
74
- console.log(err);
75
- console.log("Missing/Invalid Token");
92
+ console.error("Token refresh failed:", err);
93
+ // Clear invalid tokens
76
94
  localStorage.removeItem("Authorization");
77
95
  sessionStorage.removeItem("Authorization");
96
+ setCurrent("");
97
+ setCurrentLoggedIn(false);
98
+
99
+ // Only show toast for actual network/server errors, not auth failures
100
+ if (err?.code === "ERR_NETWORK" || err?.response?.status >= 500) {
101
+ toast.error(
102
+ "Unable to refresh your session. Please login again.",
103
+ {
104
+ toastId: "token-refresh-error",
105
+ icon: <Logout />,
106
+ }
107
+ );
108
+ }
78
109
  });
79
110
  }, 100);
80
111
  },
@@ -87,8 +118,8 @@ const SessionManagerProvider = ({
87
118
 
88
119
  if (local_auth) fromPrevious(local_auth, true);
89
120
  else if (sess_auth) fromPrevious(sess_auth, false);
90
- return () => clearInterval(fromPrevious);
91
- }, [current, fromPrevious]);
121
+ // No cleanup needed here since fromPrevious doesn't create intervals
122
+ }, [fromPrevious]);
92
123
 
93
124
  // Wrap setHeader to store new header names in localStorage
94
125
  const setHeader = (header) => {
@@ -104,13 +135,31 @@ const SessionManagerProvider = ({
104
135
  setTimeout(() => {
105
136
  userLoader()
106
137
  .then((res) => {
107
- const data = res.data;
108
- setCurrentLoggedIn(data.logged_in);
109
- setIsAdmin(data.is_admin);
110
- setUserInfo(data.Info);
138
+ const data = res?.data;
139
+ if (data) {
140
+ setCurrentLoggedIn(data.logged_in);
141
+ setIsAdmin(data.is_admin);
142
+ setUserInfo(data.Info || {});
143
+ }
111
144
  })
112
145
  .catch((err) => {
113
- console.log(err);
146
+ console.error("User data loading failed:", err);
147
+ // Don't show toast for auth errors (401, 403)
148
+ if (err?.response?.status !== 401 && err?.response?.status !== 403) {
149
+ if (err?.code === "ERR_NETWORK" || err?.response?.status >= 500) {
150
+ toast.error(
151
+ "Unable to load user information. Some features may be limited.",
152
+ {
153
+ toastId: "user-load-error",
154
+ icon: <GppBad />,
155
+ }
156
+ );
157
+ }
158
+ }
159
+ // Set safe defaults
160
+ setCurrentLoggedIn(false);
161
+ setIsAdmin(false);
162
+ setUserInfo({});
114
163
  })
115
164
  .finally(() => {
116
165
  setLoadingUser(false);
@@ -138,7 +187,7 @@ const SessionManagerProvider = ({
138
187
  clearInterval(interval);
139
188
  }
140
189
  return () => clearInterval(interval);
141
- }, [current, fromPrevious, currentLoggin, refreshTimer]);
190
+ }, [fromPrevious, currentLoggin, refreshTimer]);
142
191
 
143
192
  useEffect(() => {
144
193
  const customInterceptor = () => {
@@ -147,9 +196,12 @@ const SessionManagerProvider = ({
147
196
  return response;
148
197
  },
149
198
  (error) => {
199
+ console.error("Axios interceptor caught error:", error);
200
+
201
+ // Handle session expiry (455)
150
202
  if (error?.response?.status === 455) {
151
203
  try {
152
- const status = error.response.data.logged_in || false;
204
+ const status = error.response.data?.logged_in || false;
153
205
  if (!status) {
154
206
  console.log("Session ended");
155
207
  setCurrentLoggedIn(false);
@@ -157,50 +209,83 @@ const SessionManagerProvider = ({
157
209
  "Authorization"
158
210
  ] = ``;
159
211
  toast.info(
160
- "Your session in no longer valid, please login again.",
212
+ "Your session is no longer valid, please login again.",
161
213
  { toastId: "Forced_log_out", icon: <Logout /> }
162
214
  );
163
215
  }
164
216
  } catch (err) {
165
- throw err;
217
+ console.error("Error handling session expiry:", err);
218
+ // Don't throw here - just log and continue
166
219
  }
220
+ return Promise.reject(error);
167
221
  }
222
+
223
+ // Handle version mismatch (426)
168
224
  if (error?.response?.status === 426) {
169
- sessionStorage.setItem("appVersionOld", true);
170
- sessionStorage.setItem(
171
- "requiredVersion",
172
- error.response.data.minVersion
173
- );
174
- let reloads = sessionStorage.getItem("appReloads") || 0;
175
- if (reloads < 2) {
176
- sessionStorage.setItem("appReloads", reloads + 1);
177
- setTimeout(() => {
178
- window.location.reload();
179
- }, 1000 * reloads);
180
- } else {
181
- toast.warning(
182
- "The application needs to be updated please wait for some time then reload the page.",
183
- {
184
- toastId: "appReloadError",
185
- icon: <Update />,
186
- }
225
+ try {
226
+ sessionStorage.setItem("appVersionOld", true);
227
+ sessionStorage.setItem(
228
+ "requiredVersion",
229
+ error.response.data?.minVersion || "unknown"
230
+ );
231
+ let reloads = parseInt(
232
+ sessionStorage.getItem("appReloads") || "0"
187
233
  );
234
+ if (reloads < 2) {
235
+ sessionStorage.setItem("appReloads", (reloads + 1).toString());
236
+ setTimeout(() => {
237
+ window.location.reload();
238
+ }, 1000 * reloads);
239
+ } else {
240
+ toast.warning(
241
+ "The application needs to be updated. Please wait for some time then reload the page.",
242
+ {
243
+ toastId: "appReloadError",
244
+ icon: <Update />,
245
+ }
246
+ );
247
+ }
248
+ } catch (err) {
249
+ console.error("Error handling version mismatch:", err);
188
250
  }
251
+ return Promise.reject(error);
189
252
  }
190
253
 
254
+ // Handle network errors and connection issues
191
255
  if (!error?.response?.status) {
192
- if (error?.code !== "ERR_CANCELED" && error?.message !== "canceled")
193
- toast.error(
194
- "The server is not responding, please reload or try again later.",
195
- { toastId: "ERR_CONNECTION_REFUSED", icon: <GppBad /> }
196
- );
256
+ if (
257
+ error?.code !== "ERR_CANCELED" &&
258
+ error?.message !== "canceled"
259
+ ) {
260
+ const errorMessage =
261
+ error?.code === "ERR_NETWORK"
262
+ ? "Unable to connect to the server. Please check your internet connection and try again."
263
+ : "The server is not responding, please reload or try again later.";
264
+
265
+ toast.error(errorMessage, {
266
+ toastId: `${error?.code || "ERR_CONNECTION_REFUSED"}`,
267
+ icon: <GppBad />,
268
+ });
269
+ }
270
+ return Promise.reject(error);
197
271
  }
198
- throw error;
272
+
273
+ // For all other errors, just reject without throwing
274
+ return Promise.reject(error);
199
275
  }
200
276
  );
201
277
  };
202
- return customInterceptor;
203
- }, [AuthenticatedAxiosObject, currentLoggin]);
278
+
279
+ // Call the interceptor setup
280
+ customInterceptor();
281
+
282
+ // Cleanup function to remove interceptors
283
+ return () => {
284
+ if (AuthenticatedAxiosObject?.interceptors?.response) {
285
+ AuthenticatedAxiosObject.interceptors.response.clear();
286
+ }
287
+ };
288
+ }, [AuthenticatedAxiosObject, currentLoggin, setCurrentLoggedIn]);
204
289
 
205
290
  // We will use the below to refresh our data about the user when ever we flag refreshData as true
206
291
  const [refreshData, setRefreshFlag] = useState(false);
@@ -217,11 +302,20 @@ const SessionManagerProvider = ({
217
302
 
218
303
  useEffect(() => {
219
304
  if (refreshData) {
220
- userLoader().then((res) => {
221
- const data = res.data;
222
- setUserInfo(data.Info);
223
- setRefreshData(false);
224
- });
305
+ userLoader()
306
+ .then((res) => {
307
+ const data = res?.data;
308
+ if (data) {
309
+ setUserInfo(data.Info || {});
310
+ }
311
+ })
312
+ .catch((err) => {
313
+ console.error("User data refresh failed:", err);
314
+ // Silently fail for data refresh - don't overwhelm user with toasts
315
+ })
316
+ .finally(() => {
317
+ setRefreshData(false);
318
+ });
225
319
  }
226
320
  }, [refreshData, userLoader]);
227
321
 
@@ -230,10 +324,21 @@ const SessionManagerProvider = ({
230
324
  };
231
325
 
232
326
  // Check if user has specific role
233
-
234
327
  const hasRole = (roles) => {
235
- const found = roles.some((r) => userInfo?.roles?.indexOf(r) >= 0);
236
- return found;
328
+ if (
329
+ !roles ||
330
+ !Array.isArray(roles) ||
331
+ !userInfo?.roles ||
332
+ !Array.isArray(userInfo.roles)
333
+ ) {
334
+ return false;
335
+ }
336
+ try {
337
+ return roles.some((r) => userInfo.roles.indexOf(r) >= 0);
338
+ } catch (err) {
339
+ console.error("Error checking user roles:", err);
340
+ return false;
341
+ }
237
342
  };
238
343
 
239
344
  const contextValue = {
@@ -251,43 +356,58 @@ const SessionManagerProvider = ({
251
356
  };
252
357
 
253
358
  return (
254
- <SessionManager.Provider value={contextValue}>
255
- <ToastContainer
256
- position="top-left"
257
- autoClose={5000}
258
- closeOnClick
259
- pauseOnFocusLoss
260
- pauseOnHover
261
- newestOnTop={false}
262
- toastClassName={"custToast materialToast"}
263
- {...toastOptions}
264
- />
265
- <VersionProtection appVersion={appVersion} />
266
- {children}
267
- </SessionManager.Provider>
359
+ <ErrorBoundary>
360
+ <SessionManager.Provider value={contextValue}>
361
+ <ToastContainer
362
+ position="top-left"
363
+ autoClose={5000}
364
+ closeOnClick
365
+ pauseOnFocusLoss
366
+ pauseOnHover
367
+ newestOnTop={false}
368
+ toastClassName={"custToast materialToast"}
369
+ {...toastOptions}
370
+ />
371
+ <VersionProtection appVersion={appVersion} />
372
+ {children}
373
+ </SessionManager.Provider>
374
+ </ErrorBoundary>
268
375
  );
269
376
  };
270
377
 
271
378
  function VersionProtection({ appVersion }) {
272
- const oldVersion = sessionStorage.getItem("appVersionOld") || false;
379
+ const [oldVersion, setOldVersion] = useState(false);
380
+
273
381
  useEffect(() => {
274
- if (
275
- oldVersion &&
276
- sessionStorage.getItem("requiredVersion") &&
277
- versionCompare(appVersion, sessionStorage.getItem("requiredVersion"))
278
- ) {
279
- console.log("Update Success Toast");
280
- toast.success("Your application has been updated", {
281
- toastId: "appReload",
282
- icon: <BrowserUpdated />,
283
- onClose: () => {
284
- sessionStorage.removeItem("appVersionOld");
285
- sessionStorage.removeItem("requiredVersion");
286
- sessionStorage.removeItem("appReloads");
287
- },
288
- });
382
+ try {
383
+ const isOldVersion = sessionStorage.getItem("appVersionOld") === "true";
384
+ setOldVersion(isOldVersion);
385
+
386
+ if (
387
+ isOldVersion &&
388
+ sessionStorage.getItem("requiredVersion") &&
389
+ appVersion &&
390
+ versionCompare(appVersion, sessionStorage.getItem("requiredVersion"))
391
+ ) {
392
+ console.log("Update Success Toast");
393
+ toast.success("Your application has been updated", {
394
+ toastId: "appReload",
395
+ icon: <BrowserUpdated />,
396
+ onClose: () => {
397
+ try {
398
+ sessionStorage.removeItem("appVersionOld");
399
+ sessionStorage.removeItem("requiredVersion");
400
+ sessionStorage.removeItem("appReloads");
401
+ } catch (err) {
402
+ console.error("Error cleaning up version storage:", err);
403
+ }
404
+ },
405
+ });
406
+ }
407
+ } catch (err) {
408
+ console.error("Error in version protection:", err);
289
409
  }
290
- }, [oldVersion]);
410
+ }, [appVersion]);
291
411
 
292
412
  return <></>;
293
413
  }