updates on the login
This commit is contained in:
@@ -10,6 +10,7 @@ import { useNavigate } from 'react-router';
|
||||
import { useEffect } from 'react';
|
||||
import { generateToken, initFirebaseNotificationListener } from 'firebase_notification/notification';
|
||||
import InternetStatus from 'components/updateNetworkStatus';
|
||||
import useInactivityLogout from 'hooks/useInactivityLogout';
|
||||
|
||||
// auth-provider
|
||||
// import { JWTProvider as AuthProvider } from 'contexts/JWTContext';
|
||||
@@ -25,6 +26,8 @@ const App = () => {
|
||||
}, [navigate]);
|
||||
|
||||
const AppContent = () => {
|
||||
useInactivityLogout();
|
||||
|
||||
useEffect(() => {
|
||||
generateToken();
|
||||
initFirebaseNotificationListener();
|
||||
|
||||
96
src/hooks/useInactivityLogout.js
Normal file
96
src/hooks/useInactivityLogout.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { clearFcmToken } from 'store/reducers/fcmSlice';
|
||||
import { logoutUser } from 'store/reducers/loginUserSlice';
|
||||
import {
|
||||
ABSOLUTE_SESSION_TIMEOUT_MS,
|
||||
ACTIVITY_STORAGE_KEY,
|
||||
AUTH_PRESENCE_KEY,
|
||||
INACTIVITY_TIMEOUT_MS,
|
||||
SESSION_START_STORAGE_KEY,
|
||||
isSessionActive,
|
||||
markActivity,
|
||||
markSessionStart,
|
||||
performSessionLogout
|
||||
} from 'utils/session';
|
||||
|
||||
const ACTIVITY_EVENTS = ['mousemove', 'mousedown', 'keydown', 'scroll', 'touchstart', 'wheel'];
|
||||
const ACTIVITY_WRITE_THROTTLE_MS = 5000;
|
||||
const IDLE_CHECK_INTERVAL_MS = 15000;
|
||||
|
||||
// Two independent timers, both enforced from localStorage so every tab on the
|
||||
// origin agrees:
|
||||
// - INACTIVITY_TIMEOUT_MS: logs out after 15 minutes with no interaction, so
|
||||
// a laptop left unlocked doesn't leave the console open indefinitely.
|
||||
// - ABSOLUTE_SESSION_TIMEOUT_MS: a hard 30-minute cap since login, even if
|
||||
// the user has been continuously active, so localStorage (auth keys, FCM
|
||||
// token, cached zone list) never lingers on disk longer than that.
|
||||
// A logout in one tab (auth key cleared) is picked up by the others via the
|
||||
// 'storage' event.
|
||||
const useInactivityLogout = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dispatch = useDispatch();
|
||||
const lastWriteRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
const doLogout = () => performSessionLogout({ queryClient, dispatch, clearFcmToken, logoutUser });
|
||||
|
||||
const handleActivity = () => {
|
||||
const now = Date.now();
|
||||
if (now - lastWriteRef.current < ACTIVITY_WRITE_THROTTLE_MS) return;
|
||||
lastWriteRef.current = now;
|
||||
markActivity();
|
||||
};
|
||||
|
||||
const handleStorage = (event) => {
|
||||
if (event.key === AUTH_PRESENCE_KEY && !event.newValue) {
|
||||
doLogout();
|
||||
}
|
||||
};
|
||||
|
||||
// Guards against the browser restoring a cached (bfcache) copy of a
|
||||
// protected page via the back/forward button after logout happened.
|
||||
const handlePageShow = (event) => {
|
||||
if (event.persisted && !isSessionActive()) {
|
||||
window.location.replace('/login');
|
||||
}
|
||||
};
|
||||
|
||||
if (isSessionActive()) {
|
||||
if (!localStorage.getItem(ACTIVITY_STORAGE_KEY)) markActivity();
|
||||
// Sessions that were already open before this feature shipped won't have
|
||||
// a start time yet — give them a fresh 30-minute window instead of
|
||||
// treating them as already expired.
|
||||
if (!localStorage.getItem(SESSION_START_STORAGE_KEY)) markSessionStart();
|
||||
}
|
||||
|
||||
ACTIVITY_EVENTS.forEach((eventName) => window.addEventListener(eventName, handleActivity, { passive: true }));
|
||||
window.addEventListener('storage', handleStorage);
|
||||
window.addEventListener('pageshow', handlePageShow);
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
if (!isSessionActive()) return;
|
||||
|
||||
const lastActivity = Number(localStorage.getItem(ACTIVITY_STORAGE_KEY)) || Date.now();
|
||||
if (Date.now() - lastActivity >= INACTIVITY_TIMEOUT_MS) {
|
||||
doLogout();
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionStart = Number(localStorage.getItem(SESSION_START_STORAGE_KEY)) || Date.now();
|
||||
if (Date.now() - sessionStart >= ABSOLUTE_SESSION_TIMEOUT_MS) {
|
||||
doLogout();
|
||||
}
|
||||
}, IDLE_CHECK_INTERVAL_MS);
|
||||
|
||||
return () => {
|
||||
ACTIVITY_EVENTS.forEach((eventName) => window.removeEventListener(eventName, handleActivity));
|
||||
window.removeEventListener('storage', handleStorage);
|
||||
window.removeEventListener('pageshow', handlePageShow);
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, [queryClient, dispatch]);
|
||||
};
|
||||
|
||||
export default useInactivityLogout;
|
||||
@@ -7,14 +7,10 @@ import { List, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'
|
||||
// assets
|
||||
import { EditOutlined, LogoutOutlined, CommentOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { clearFcmToken } from 'store/reducers/fcmSlice';
|
||||
import { logoutUser } from 'store/reducers/loginUserSlice';
|
||||
|
||||
// ==============================|| HEADER PROFILE - PROFILE TAB ||============================== //
|
||||
|
||||
const ProfileTab = ({ handleLogout }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const navigate = useNavigate();
|
||||
const handleListItemClick = (event, index) => {
|
||||
@@ -48,18 +44,7 @@ const ProfileTab = ({ handleLogout }) => {
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Billing" />
|
||||
</ListItemButton> */}
|
||||
<ListItemButton
|
||||
selected={selectedIndex === 3}
|
||||
// onClick={handleLogout}
|
||||
onClick={() => {
|
||||
handleLogout();
|
||||
dispatch(clearFcmToken()); // ✅ dispatch the action
|
||||
dispatch(logoutUser()); // ✅ dispatch logout user as initial state
|
||||
}}
|
||||
// onClick={()=>{
|
||||
// navigate('/login')
|
||||
// }}
|
||||
>
|
||||
<ListItemButton selected={selectedIndex === 3} onClick={handleLogout}>
|
||||
<ListItemIcon>
|
||||
<LogoutOutlined />
|
||||
</ListItemIcon>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
@@ -23,6 +23,7 @@ import { LogoutOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { clearFcmToken } from 'store/reducers/fcmSlice';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { logoutUser } from 'store/reducers/loginUserSlice';
|
||||
import { performSessionLogout } from 'utils/session';
|
||||
|
||||
// tab panel wrapper
|
||||
function TabPanel({ children, value, index, ...other }) {
|
||||
@@ -50,30 +51,11 @@ function a11yProps(index) {
|
||||
|
||||
const Profile = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// const { logout, user } = useAuth();
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
// await logout();
|
||||
|
||||
// navigate(`/login`, {
|
||||
// state: {
|
||||
// from: ''
|
||||
// }
|
||||
// });
|
||||
localStorage.removeItem('firstname');
|
||||
localStorage.removeItem('appuserid');
|
||||
localStorage.removeItem('authname');
|
||||
localStorage.removeItem('roleid');
|
||||
localStorage.removeItem('tenantid');
|
||||
localStorage.clear();
|
||||
|
||||
navigate('/login');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
const handleLogout = () => {
|
||||
performSessionLogout({ queryClient, dispatch, clearFcmToken, logoutUser });
|
||||
};
|
||||
|
||||
const anchorRef = useRef(null);
|
||||
@@ -173,16 +155,7 @@ const Profile = () => {
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Tooltip title="Logout">
|
||||
<IconButton
|
||||
size="large"
|
||||
sx={{ color: 'text.primary' }}
|
||||
// onClick={handleLogout}>
|
||||
onClick={() => {
|
||||
handleLogout();
|
||||
dispatch(clearFcmToken()); // ✅ dispatch the action dispatch(logoutUser()); // ✅ dispatch logout user as initial state dispatch(logoutUser()); // ✅ dispatch logout user as initial state
|
||||
dispatch(logoutUser()); // ✅ dispatch logout user as initial state
|
||||
}}
|
||||
>
|
||||
<IconButton size="large" sx={{ color: 'text.primary' }} onClick={handleLogout}>
|
||||
<LogoutOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { closeGlobalToast, GlobalToast } from 'components/nearle_components/Glob
|
||||
import Visibility from '@mui/icons-material/Visibility';
|
||||
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||
import { setLoginUser } from 'store/reducers/loginUserSlice';
|
||||
import { markSessionStart } from 'utils/session';
|
||||
|
||||
const Login = () => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -96,6 +97,7 @@ const Login = () => {
|
||||
localStorage.setItem('applocationid', userinfo.applocationid);
|
||||
localStorage.setItem('userid', userinfo.userid);
|
||||
localStorage.setItem('userfcmtoken', userinfo.userfcmtoken);
|
||||
markSessionStart();
|
||||
fetchAppLocations(userinfo.userid);
|
||||
navigate('/nearle/dispatch');
|
||||
} else {
|
||||
@@ -117,6 +119,7 @@ const Login = () => {
|
||||
localStorage.setItem('applocationid', userinfo.applocationid);
|
||||
localStorage.setItem('userid', userinfo.userid);
|
||||
localStorage.setItem('userfcmtoken', userinfo.userfcmtoken);
|
||||
markSessionStart();
|
||||
closeGlobalToast(); // to close the pin snackbar
|
||||
|
||||
navigate('/nearle/dispatch');
|
||||
|
||||
38
src/utils/session.js
Normal file
38
src/utils/session.js
Normal file
@@ -0,0 +1,38 @@
|
||||
// Shared session/logout contract used by both the manual "Logout" buttons
|
||||
// and the automatic inactivity logout (see hooks/useInactivityLogout.js).
|
||||
// Keeping this in one place means every logout path clears the same things
|
||||
// the same way, instead of each caller hand-rolling its own localStorage cleanup.
|
||||
|
||||
export const ACTIVITY_STORAGE_KEY = 'lastActivityTime';
|
||||
export const SESSION_START_STORAGE_KEY = 'sessionStartTime';
|
||||
export const AUTH_PRESENCE_KEY = 'authname';
|
||||
export const INACTIVITY_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes, resets on user activity
|
||||
export const ABSOLUTE_SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes, hard cap since login regardless of activity
|
||||
|
||||
export const markActivity = () => {
|
||||
localStorage.setItem(ACTIVITY_STORAGE_KEY, String(Date.now()));
|
||||
};
|
||||
|
||||
// Called once at login to start the absolute-lifetime clock for the session.
|
||||
export const markSessionStart = () => {
|
||||
localStorage.setItem(SESSION_START_STORAGE_KEY, String(Date.now()));
|
||||
};
|
||||
|
||||
export const isSessionActive = () => Boolean(localStorage.getItem(AUTH_PRESENCE_KEY));
|
||||
|
||||
// Wipes local auth state + the in-memory query cache, then hard-navigates to
|
||||
// /login. A real navigation (not react-router) is deliberate: it throws away
|
||||
// the entire JS heap (Redux store, component state, any variable holding
|
||||
// fetched data) so nothing sensitive survives in memory after logout, and it
|
||||
// prevents the back button from resurrecting a stale authenticated page from
|
||||
// the render tree.
|
||||
export const performSessionLogout = ({ queryClient, dispatch, clearFcmToken, logoutUser } = {}) => {
|
||||
try {
|
||||
queryClient?.clear();
|
||||
if (dispatch && clearFcmToken) dispatch(clearFcmToken());
|
||||
if (dispatch && logoutUser) dispatch(logoutUser());
|
||||
} finally {
|
||||
localStorage.clear();
|
||||
window.location.replace('/login');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user