From 9bd190d21fa343f4175df8d3e5d4e0c84258d448 Mon Sep 17 00:00:00 2001 From: BlueSky Date: Sat, 2 Dec 2023 19:08:09 +0800 Subject: [PATCH] :sparkles: Auto refresh token if tab was active --- web/src/components/Menu/index.tsx | 14 +++++++ web/src/utils/refreshToken.ts | 63 +++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 web/src/utils/refreshToken.ts diff --git a/web/src/components/Menu/index.tsx b/web/src/components/Menu/index.tsx index 95e108b8..427ae02a 100644 --- a/web/src/components/Menu/index.tsx +++ b/web/src/components/Menu/index.tsx @@ -24,6 +24,7 @@ import { Link, useSearchParams, useNavigate } from 'react-router-dom'; import Regex from "../../utils/regex"; import Toast from "../../components/Notification"; import { INamespaceItem, INamespaceList, IHTTPError, IUserSelf, IEndpoint, IVersion } from "../../interfaces"; +import { setupAutoRefreshToken, teardownAutoRefreshToken } from "../../utils/refreshToken" export default function ({ localServer, item, namespace, repository, tag, selfClick }: { localServer: string, item: string, namespace?: string, repository?: string, tag?: string, selfClick?: boolean }) { const [showProfileMenu, setShowProfileMenu] = useState(false); @@ -62,6 +63,19 @@ export default function ({ localServer, item, namespace, repository, tag, selfCl const errorcode = error.response.data as IHTTPError; Toast({ level: "warning", title: errorcode.title, message: errorcode.description }); }); + setupAutoRefreshToken(localServer, logout); + const visibilitychangeHandler = () => { + if (document.hidden) { + teardownAutoRefreshToken(); + } else { + setupAutoRefreshToken(localServer, logout); + } + } + document.addEventListener("visibilitychange", visibilitychangeHandler); + + return () => { + document.removeEventListener("visibilitychange", visibilitychangeHandler); + } } }, [refresh]) diff --git a/web/src/utils/refreshToken.ts b/web/src/utils/refreshToken.ts new file mode 100644 index 00000000..f9f738cb --- /dev/null +++ b/web/src/utils/refreshToken.ts @@ -0,0 +1,63 @@ +/** + * Copyright 2023 sigma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import axios from "axios"; + +let REFRESH_TOKEN_INTERVAL: ReturnType | null; + +const REFRESH_TOKEN_INTERVAL_TIMEOUT = 1000; // 10 mins + +import { IUserLoginResponse } from "../interfaces"; + + +export function refreshToken( + localServer: string, + onFailed: () => void +) { + let headers: { [key: string]: any } = { + "Authorization": "Bearer " + localStorage.getItem('refresh_token'), + }; + let url = localServer + `/api/v1/users/login`; + axios.post(url, {}, { + headers: headers, + }) + .then(response => { + if (response?.status === 200) { + const resp = response.data as IUserLoginResponse; + localStorage.setItem("token", resp.token); + localStorage.setItem("refresh_token", resp.refresh_token); + } else { + onFailed() + } + }).catch(err => { + onFailed() + }) +} + +export function setupAutoRefreshToken( + localServer: string, + onFailed: () => void +) { + if (REFRESH_TOKEN_INTERVAL) return; + REFRESH_TOKEN_INTERVAL = REFRESH_TOKEN_INTERVAL = setInterval(() => { + refreshToken(localServer, onFailed); + }, REFRESH_TOKEN_INTERVAL_TIMEOUT); +} + +export function teardownAutoRefreshToken() { + if (!REFRESH_TOKEN_INTERVAL) return; + clearInterval(REFRESH_TOKEN_INTERVAL); + REFRESH_TOKEN_INTERVAL = null; +} \ No newline at end of file