session timeout

This commit is contained in:
2025-02-09 12:26:49 +08:00
parent 47bdb719c2
commit e7c92f252f
4 changed files with 64 additions and 1 deletions

View File

@@ -0,0 +1,57 @@
import React, { useState, useEffect, useCallback } from "react";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
interface SessionTimeoutProps {
timeout: number;
onLogout: () => void;
}
const SessionTimeout: React.FC<SessionTimeoutProps> = ({
timeout,
onLogout,
}) => {
const [lastActivityTime, setLastActivityTime] = useState<number>(Date.now());
const [notified, setNotified] = useState<boolean>(false);
const resetTimer = useCallback(() => {
setLastActivityTime(Date.now());
setNotified(false);
}, []);
useEffect(() => {
const events = ["click", "mousemove", "keypress", "scroll", "touchstart"];
const eventHandler = () => resetTimer();
events.forEach((event) => window.addEventListener(event, eventHandler));
return () => {
events.forEach((event) =>
window.removeEventListener(event, eventHandler)
);
};
}, [resetTimer]);
useEffect(() => {
const interval = setInterval(() => {
const timeElapsed = Date.now() - lastActivityTime;
if (timeElapsed >= timeout) {
onLogout();
} else if (timeElapsed >= timeout - 30000 && !notified) {
toast.warn("30 more seconds before automatic logout from idling.");
setNotified(true);
}
}, 1000);
return () => clearInterval(interval);
}, [lastActivityTime, timeout, onLogout, notified]);
return (
<>
<ToastContainer />
</>
);
};
export default SessionTimeout;

View File

@@ -48,7 +48,7 @@ export function login(token: string) {
export function logout() { export function logout() {
clearAccessToken(); clearAccessToken();
window.location.reload(); window.location.assign("/");
} }
export function getAccessToken() { export function getAccessToken() {

View File

@@ -6,6 +6,8 @@ import App from "./App.tsx";
import "./index.css"; import "./index.css";
import { ToastContainer } from "react-toastify"; import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css"; import "react-toastify/dist/ReactToastify.css";
import SessionTimeout from "./components/SessionTimeout.tsx";
import { getAccessToken, logout } from "./http.ts";
document.addEventListener("contextmenu", (event) => { document.addEventListener("contextmenu", (event) => {
event.preventDefault(); event.preventDefault();
@@ -18,6 +20,9 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
<main> <main>
<App /> <App />
<ToastContainer /> <ToastContainer />
{getAccessToken() && (
<SessionTimeout timeout={1 * 60 * 1000} onLogout={logout} />
)}
</main> </main>
</HeroUIProvider> </HeroUIProvider>
</BrowserRouter> </BrowserRouter>

View File

@@ -16,6 +16,7 @@ export default function EditProfilePage() {
const accessToken = getAccessToken(); const accessToken = getAccessToken();
if (!accessToken) { if (!accessToken) {
navigate(-1); navigate(-1);
return;
} }
http.get("/User/profile").then((response) => { http.get("/User/profile").then((response) => {
if (response.status !== 200) { if (response.status !== 200) {