react-session.manager.sk 1.1.3 → 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/src/index.js CHANGED
@@ -1,295 +1,415 @@
1
- import React, { createContext, useState, useEffect, useCallback } from "react";
2
- import versionCompare from "./components/versionCompare";
3
- import getDeviceFingerprint from "./components/FingerPrint";
4
- // Notifications
5
- import { ToastContainer, toast } from "react-toastify";
6
- import "react-toastify/dist/ReactToastify.css";
7
- import "./styling/toast.css";
8
-
9
- // Icons
10
- import { GppBad, Update, BrowserUpdated, Logout } from "@mui/icons-material";
11
-
12
- export const SessionManager = createContext({
13
- isLoggedIn: null,
14
- header: null,
15
- isAdmin: null,
16
- userInfo: null,
17
- refreshData: null,
18
- setHeader: null,
19
- setLoggedin: null,
20
- setRefreshData: null,
21
- hasRole: null,
22
- deviceUID: null,
23
- loadingUser: null,
24
- });
25
-
26
- const SessionManagerProvider = ({
27
- AuthenticatedAxiosObject,
28
- refreshTimer,
29
- dataRefresh,
30
- userLoader,
31
- refreshToken,
32
- appVersion,
33
- toastOptions,
34
- children,
35
- }) => {
36
- // Set deivce UID
37
- const [deviceUID, setDeviceUID] = useState(
38
- localStorage.getItem("deviceUID") || false
39
- );
40
-
41
- useEffect(() => {
42
- if (!deviceUID) {
43
- const uid = getDeviceFingerprint();
44
- setDeviceUID(uid);
45
- AuthenticatedAxiosObject.defaults.headers.common["deviceUID"] = uid;
46
- }
47
- }, [deviceUID, AuthenticatedAxiosObject]);
48
-
49
- // State to hold the selected header name
50
- const [current, setCurrent] = useState("");
51
-
52
- AuthenticatedAxiosObject.defaults.withCredentials = true;
53
- AuthenticatedAxiosObject.defaults.headers.common["Authorization"] = current;
54
- AuthenticatedAxiosObject.defaults.headers.common["deviceUID"] = deviceUID;
55
- AuthenticatedAxiosObject.defaults.headers.common["appVersion"] = appVersion;
56
-
57
- const fromPrevious = useCallback(
58
- (auth, remember) => {
59
- setCurrent(auth);
60
- setTimeout(() => {
61
- refreshToken()
62
- .then((data) => {
63
- if (data.refreshed) {
64
- const token = `Bearer ${data.access_token}`;
65
- setCurrent(token);
66
- AuthenticatedAxiosObject.defaults.headers.common[
67
- "Authorization"
68
- ] = token;
69
- if (remember) localStorage.setItem("Authorization", token);
70
- sessionStorage.setItem("Authorization", token);
71
- }
72
- })
73
- .catch((err) => {
74
- console.log(err);
75
- console.log("Missing/Invalid Token");
76
- localStorage.removeItem("Authorization");
77
- sessionStorage.removeItem("Authorization");
78
- });
79
- }, 100);
80
- },
81
- [AuthenticatedAxiosObject, refreshToken]
82
- );
83
-
84
- useEffect(() => {
85
- const local_auth = localStorage.getItem("Authorization");
86
- const sess_auth = sessionStorage.getItem("Authorization");
87
-
88
- if (local_auth) fromPrevious(local_auth, true);
89
- else if (sess_auth) fromPrevious(sess_auth, false);
90
- return () => clearInterval(fromPrevious);
91
- }, [current, fromPrevious]);
92
-
93
- // Wrap setHeader to store new header names in localStorage
94
- const setHeader = (header) => {
95
- setCurrent(header);
96
- };
97
-
98
- const [currentLoggin, setCurrentLoggedIn] = useState(false);
99
- const [isAdmin, setIsAdmin] = useState(false);
100
- const [userInfo, setUserInfo] = useState({});
101
- const [loadingUser, setLoadingUser] = useState(true);
102
-
103
- useEffect(() => {
104
- setTimeout(() => {
105
- userLoader()
106
- .then((res) => {
107
- const data = res.data;
108
- setCurrentLoggedIn(data.logged_in);
109
- setIsAdmin(data.is_admin);
110
- setUserInfo(data.Info);
111
- })
112
- .catch((err) => {
113
- console.log(err);
114
- })
115
- .finally(() => {
116
- setLoadingUser(false);
117
- });
118
- }, 100);
119
- }, [current, currentLoggin, userLoader]);
120
-
121
- const setLoggedin = (status) => {
122
- setCurrentLoggedIn(status);
123
- };
124
-
125
- useEffect(() => {
126
- const tokenRefreshTimer = () => {
127
- const mins = refreshTimer || 60;
128
- return mins * 60 * 1000 || 10000;
129
- };
130
-
131
- let interval = null;
132
- if (currentLoggin) {
133
- const remember = localStorage.getItem("Authorization") ? true : false;
134
- interval = setInterval(() => {
135
- fromPrevious(sessionStorage.getItem("Authorization"), remember);
136
- }, tokenRefreshTimer());
137
- } else {
138
- clearInterval(interval);
139
- }
140
- return () => clearInterval(interval);
141
- }, [current, fromPrevious, currentLoggin, refreshTimer]);
142
-
143
- useEffect(() => {
144
- const customInterceptor = () => {
145
- AuthenticatedAxiosObject.interceptors.response.use(
146
- (response) => {
147
- return response;
148
- },
149
- (error) => {
150
- if (error?.response?.status === 455) {
151
- try {
152
- const status = error.response.data.logged_in || false;
153
- if (!status) {
154
- console.log("Session ended");
155
- setCurrentLoggedIn(false);
156
- AuthenticatedAxiosObject.defaults.headers.common[
157
- "Authorization"
158
- ] = ``;
159
- toast.info(
160
- "Your session in no longer valid, please login again.",
161
- { toastId: "Forced_log_out", icon: <Logout /> }
162
- );
163
- }
164
- } catch (err) {
165
- throw err;
166
- }
167
- }
168
- 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
- }
187
- );
188
- }
189
- }
190
-
191
- 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
- );
197
- }
198
- throw error;
199
- }
200
- );
201
- };
202
- return customInterceptor;
203
- }, [AuthenticatedAxiosObject, currentLoggin]);
204
-
205
- // We will use the below to refresh our data about the user when ever we flag refreshData as true
206
- const [refreshData, setRefreshFlag] = useState(false);
207
-
208
- useEffect(() => {
209
- const refreshDelay = () => {
210
- const mins = dataRefresh || 60;
211
- return mins * 60 * 1000 || 10000;
212
- };
213
-
214
- const timer = setTimeout(() => setRefreshFlag(true), refreshDelay());
215
- return () => clearTimeout(timer);
216
- }, [dataRefresh]);
217
-
218
- useEffect(() => {
219
- if (refreshData) {
220
- userLoader().then((res) => {
221
- const data = res.data;
222
- setUserInfo(data.Info);
223
- setRefreshData(false);
224
- });
225
- }
226
- }, [refreshData, userLoader]);
227
-
228
- const setRefreshData = (status) => {
229
- setRefreshFlag(status);
230
- };
231
-
232
- // Check if user has specific role
233
-
234
- const hasRole = (roles) => {
235
- const found = roles.some((r) => userInfo?.roles?.indexOf(r) >= 0);
236
- return found;
237
- };
238
-
239
- const contextValue = {
240
- isLoggedIn: currentLoggin,
241
- header: current,
242
- isAdmin: isAdmin,
243
- userInfo: userInfo,
244
- refreshData: refreshData,
245
- setHeader: setHeader,
246
- setLoggedin: setLoggedin,
247
- setRefreshData: setRefreshData,
248
- hasRole: hasRole,
249
- deviceUID: deviceUID,
250
- loadingUser: loadingUser,
251
- };
252
-
253
- 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>
268
- );
269
- };
270
-
271
- function VersionProtection({ appVersion }) {
272
- const oldVersion = sessionStorage.getItem("appVersionOld") || false;
273
- 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
- });
289
- }
290
- }, [oldVersion]);
291
-
292
- return <></>;
293
- }
294
-
295
- export default SessionManagerProvider;
1
+ import React, { createContext, useState, useEffect, useCallback } from "react";
2
+ import versionCompare from "./components/versionCompare";
3
+ import getDeviceFingerprint from "./components/FingerPrint";
4
+ import ErrorBoundary from "./components/ErrorBoundary";
5
+ // Notifications
6
+ import { ToastContainer, toast } from "react-toastify";
7
+ import "react-toastify/dist/ReactToastify.css";
8
+ import "./styling/toast.css";
9
+
10
+ // Icons
11
+ import { GppBad, Update, BrowserUpdated, Logout } from "@mui/icons-material";
12
+
13
+ export const SessionManager = createContext({
14
+ isLoggedIn: null,
15
+ header: null,
16
+ isAdmin: null,
17
+ userInfo: null,
18
+ refreshData: null,
19
+ setHeader: null,
20
+ setLoggedin: null,
21
+ setRefreshData: null,
22
+ hasRole: null,
23
+ deviceUID: null,
24
+ loadingUser: null,
25
+ });
26
+
27
+ const SessionManagerProvider = ({
28
+ AuthenticatedAxiosObject,
29
+ refreshTimer,
30
+ dataRefresh,
31
+ userLoader,
32
+ refreshToken,
33
+ appVersion,
34
+ toastOptions,
35
+ children,
36
+ }) => {
37
+ // Set deivce UID
38
+ const [deviceUID, setDeviceUID] = useState(
39
+ localStorage.getItem("deviceUID") || false
40
+ );
41
+
42
+ useEffect(() => {
43
+ if (!deviceUID) {
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
+ }
64
+ }
65
+ }, [deviceUID, AuthenticatedAxiosObject]);
66
+
67
+ // State to hold the selected header name
68
+ const [current, setCurrent] = useState("");
69
+
70
+ AuthenticatedAxiosObject.defaults.withCredentials = true;
71
+ AuthenticatedAxiosObject.defaults.headers.common["Authorization"] = current;
72
+ AuthenticatedAxiosObject.defaults.headers.common["deviceUID"] = deviceUID;
73
+ AuthenticatedAxiosObject.defaults.headers.common["appVersion"] = appVersion;
74
+
75
+ const fromPrevious = useCallback(
76
+ (auth, remember) => {
77
+ setCurrent(auth);
78
+ setTimeout(() => {
79
+ refreshToken()
80
+ .then((data) => {
81
+ if (data?.refreshed) {
82
+ const token = `Bearer ${data.access_token}`;
83
+ setCurrent(token);
84
+ AuthenticatedAxiosObject.defaults.headers.common[
85
+ "Authorization"
86
+ ] = token;
87
+ if (remember) localStorage.setItem("Authorization", token);
88
+ sessionStorage.setItem("Authorization", token);
89
+ }
90
+ })
91
+ .catch((err) => {
92
+ console.error("Token refresh failed:", err);
93
+ // Clear invalid tokens
94
+ localStorage.removeItem("Authorization");
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
+ }
109
+ });
110
+ }, 100);
111
+ },
112
+ [AuthenticatedAxiosObject, refreshToken]
113
+ );
114
+
115
+ useEffect(() => {
116
+ const local_auth = localStorage.getItem("Authorization");
117
+ const sess_auth = sessionStorage.getItem("Authorization");
118
+
119
+ if (local_auth) fromPrevious(local_auth, true);
120
+ else if (sess_auth) fromPrevious(sess_auth, false);
121
+ // No cleanup needed here since fromPrevious doesn't create intervals
122
+ }, [fromPrevious]);
123
+
124
+ // Wrap setHeader to store new header names in localStorage
125
+ const setHeader = (header) => {
126
+ setCurrent(header);
127
+ };
128
+
129
+ const [currentLoggin, setCurrentLoggedIn] = useState(false);
130
+ const [isAdmin, setIsAdmin] = useState(false);
131
+ const [userInfo, setUserInfo] = useState({});
132
+ const [loadingUser, setLoadingUser] = useState(true);
133
+
134
+ useEffect(() => {
135
+ setTimeout(() => {
136
+ userLoader()
137
+ .then((res) => {
138
+ const data = res?.data;
139
+ if (data) {
140
+ setCurrentLoggedIn(data.logged_in);
141
+ setIsAdmin(data.is_admin);
142
+ setUserInfo(data.Info || {});
143
+ }
144
+ })
145
+ .catch((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({});
163
+ })
164
+ .finally(() => {
165
+ setLoadingUser(false);
166
+ });
167
+ }, 100);
168
+ }, [current, currentLoggin, userLoader]);
169
+
170
+ const setLoggedin = (status) => {
171
+ setCurrentLoggedIn(status);
172
+ };
173
+
174
+ useEffect(() => {
175
+ const tokenRefreshTimer = () => {
176
+ const mins = refreshTimer || 60;
177
+ return mins * 60 * 1000 || 10000;
178
+ };
179
+
180
+ let interval = null;
181
+ if (currentLoggin) {
182
+ const remember = localStorage.getItem("Authorization") ? true : false;
183
+ interval = setInterval(() => {
184
+ fromPrevious(sessionStorage.getItem("Authorization"), remember);
185
+ }, tokenRefreshTimer());
186
+ } else {
187
+ clearInterval(interval);
188
+ }
189
+ return () => clearInterval(interval);
190
+ }, [fromPrevious, currentLoggin, refreshTimer]);
191
+
192
+ useEffect(() => {
193
+ const customInterceptor = () => {
194
+ AuthenticatedAxiosObject.interceptors.response.use(
195
+ (response) => {
196
+ return response;
197
+ },
198
+ (error) => {
199
+ console.error("Axios interceptor caught error:", error);
200
+
201
+ // Handle session expiry (455)
202
+ if (error?.response?.status === 455) {
203
+ try {
204
+ const status = error.response.data?.logged_in || false;
205
+ if (!status) {
206
+ console.log("Session ended");
207
+ setCurrentLoggedIn(false);
208
+ AuthenticatedAxiosObject.defaults.headers.common[
209
+ "Authorization"
210
+ ] = ``;
211
+ toast.info(
212
+ "Your session is no longer valid, please login again.",
213
+ { toastId: "Forced_log_out", icon: <Logout /> }
214
+ );
215
+ }
216
+ } catch (err) {
217
+ console.error("Error handling session expiry:", err);
218
+ // Don't throw here - just log and continue
219
+ }
220
+ return Promise.reject(error);
221
+ }
222
+
223
+ // Handle version mismatch (426)
224
+ if (error?.response?.status === 426) {
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"
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);
250
+ }
251
+ return Promise.reject(error);
252
+ }
253
+
254
+ // Handle network errors and connection issues
255
+ if (!error?.response?.status) {
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);
271
+ }
272
+
273
+ // For all other errors, just reject without throwing
274
+ return Promise.reject(error);
275
+ }
276
+ );
277
+ };
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]);
289
+
290
+ // We will use the below to refresh our data about the user when ever we flag refreshData as true
291
+ const [refreshData, setRefreshFlag] = useState(false);
292
+
293
+ useEffect(() => {
294
+ const refreshDelay = () => {
295
+ const mins = dataRefresh || 60;
296
+ return mins * 60 * 1000 || 10000;
297
+ };
298
+
299
+ const timer = setTimeout(() => setRefreshFlag(true), refreshDelay());
300
+ return () => clearTimeout(timer);
301
+ }, [dataRefresh]);
302
+
303
+ useEffect(() => {
304
+ if (refreshData) {
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
+ });
319
+ }
320
+ }, [refreshData, userLoader]);
321
+
322
+ const setRefreshData = (status) => {
323
+ setRefreshFlag(status);
324
+ };
325
+
326
+ // Check if user has specific role
327
+ const hasRole = (roles) => {
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
+ }
342
+ };
343
+
344
+ const contextValue = {
345
+ isLoggedIn: currentLoggin,
346
+ header: current,
347
+ isAdmin: isAdmin,
348
+ userInfo: userInfo,
349
+ refreshData: refreshData,
350
+ setHeader: setHeader,
351
+ setLoggedin: setLoggedin,
352
+ setRefreshData: setRefreshData,
353
+ hasRole: hasRole,
354
+ deviceUID: deviceUID,
355
+ loadingUser: loadingUser,
356
+ };
357
+
358
+ return (
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>
375
+ );
376
+ };
377
+
378
+ function VersionProtection({ appVersion }) {
379
+ const [oldVersion, setOldVersion] = useState(false);
380
+
381
+ useEffect(() => {
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);
409
+ }
410
+ }, [appVersion]);
411
+
412
+ return <></>;
413
+ }
414
+
415
+ export default SessionManagerProvider;