| @@ -14,6 +14,7 @@ import {useNavigate} from "react-router-dom"; | |||
| import {useDispatch} from "react-redux"; | |||
| import { LOGOUT, REFRESH_TOKEN } from 'utils/ApiPathConst'; | |||
| import { getMessage } from 'utils/getI18nMessage'; | |||
| import { beginRefresh } from 'auth/refreshGate'; | |||
| // Guard so we only register interceptors once (ThemeRoutes re-renders add duplicate handlers otherwise) | |||
| let axiosInterceptorsSetup = false; | |||
| @@ -192,10 +193,10 @@ export const SetupAxiosInterceptors = () => { | |||
| return Promise.reject(error); | |||
| } | |||
| isRefreshToken = true; | |||
| return axios | |||
| return beginRefresh(() => axios | |||
| .post(`${apiPath}${REFRESH_TOKEN}`, { | |||
| refreshToken: refreshToken // Replace with your refresh token | |||
| }) | |||
| refreshToken: refreshToken | |||
| })) | |||
| .then((response) => { | |||
| if (response.status === 200) { | |||
| const newAccessToken = response.data.accessToken; | |||
| @@ -261,36 +262,17 @@ export const SetupAxiosInterceptors = () => { | |||
| } | |||
| export const handleRefreshTokenFunction = () => { | |||
| // const token = localStorage.getItem('accessToken'); | |||
| let isRefresh = false; | |||
| if (!isRefresh) { | |||
| const refreshToken = localStorage.getItem('refreshToken'); | |||
| isRefresh = true; | |||
| axios.post(`${apiPath}${REFRESH_TOKEN}`, { | |||
| refreshToken: refreshToken | |||
| }).then((response) => { | |||
| if (response.status === 200) { | |||
| const newAccessToken = response.data.accessToken; | |||
| const newRefreshToken = response.data.refreshToken; | |||
| localStorage.setItem('accessToken', newAccessToken); | |||
| localStorage.setItem('refreshToken', newRefreshToken); | |||
| // token = newAccessToken; | |||
| isRefresh = false; | |||
| } else { | |||
| // token = null; | |||
| isRefresh = false; | |||
| } | |||
| }) | |||
| .catch((refreshError) => { | |||
| console.log('Failed to refresh token'); | |||
| if (refreshError != undefined){ | |||
| console.log(refreshError) | |||
| } | |||
| // token = null | |||
| isRefresh = false; | |||
| }); | |||
| const refreshToken = localStorage.getItem('refreshToken'); | |||
| if (!refreshToken) { | |||
| return false; | |||
| } | |||
| return isRefresh; | |||
| beginRefresh(() => axios.post(`${apiPath}${REFRESH_TOKEN}`, { | |||
| refreshToken: refreshToken | |||
| })).catch((refreshError) => { | |||
| console.log('Failed to refresh token'); | |||
| if (refreshError != undefined) { | |||
| console.log(refreshError) | |||
| } | |||
| }); | |||
| return true; | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| import axios from 'axios' | |||
| import jwtDefaultConfig from './jwtDefaultConfig' | |||
| import { beginRefresh } from '../refreshGate' | |||
| export default class JwtService { | |||
| // ** jwtConfig <= Will be used by this service | |||
| @@ -126,8 +127,8 @@ export default class JwtService { | |||
| } | |||
| refreshToken() { | |||
| return axios.post(this.jwtConfig.refreshEndpoint, { | |||
| return beginRefresh(() => axios.post(this.jwtConfig.refreshEndpoint, { | |||
| refreshToken: this.getRefreshToken() | |||
| }) | |||
| })) | |||
| } | |||
| } | |||
| @@ -0,0 +1,71 @@ | |||
| const LOCK_KEY = 'pnspsRefreshLock'; | |||
| const LOCK_MS = 15000; | |||
| let inFlight = null; | |||
| function waitForStoredToken(previousRefreshToken) { | |||
| return new Promise((resolve, reject) => { | |||
| const started = Date.now(); | |||
| const timer = setInterval(() => { | |||
| const refreshToken = localStorage.getItem('refreshToken'); | |||
| const accessToken = localStorage.getItem('accessToken'); | |||
| if (refreshToken && accessToken && refreshToken !== previousRefreshToken) { | |||
| clearInterval(timer); | |||
| resolve({ | |||
| status: 200, | |||
| data: { accessToken, refreshToken } | |||
| }); | |||
| return; | |||
| } | |||
| const lockGone = !localStorage.getItem(LOCK_KEY); | |||
| if ((lockGone && Date.now() - started > 500) || Date.now() - started > LOCK_MS) { | |||
| clearInterval(timer); | |||
| reject(new Error('refresh-lock-released')); | |||
| } | |||
| }, 200); | |||
| }); | |||
| } | |||
| export function storeRefreshedTokens(accessToken, refreshToken) { | |||
| if (accessToken) { | |||
| localStorage.setItem('accessToken', accessToken); | |||
| localStorage.setItem('axiosToken', 'Bearer ' + accessToken); | |||
| } | |||
| if (refreshToken) { | |||
| localStorage.setItem('refreshToken', refreshToken); | |||
| } | |||
| } | |||
| /** | |||
| * One refresh at a time in this tab, and a short lock so a second tab waits | |||
| * for the new token instead of posting the same refresh token again. | |||
| */ | |||
| export function beginRefresh(run) { | |||
| if (inFlight) { | |||
| return inFlight; | |||
| } | |||
| const previousRefreshToken = localStorage.getItem('refreshToken'); | |||
| inFlight = (async () => { | |||
| const lockedAt = Number(localStorage.getItem(LOCK_KEY) || 0); | |||
| if (lockedAt && Date.now() - lockedAt < LOCK_MS) { | |||
| try { | |||
| return await waitForStoredToken(previousRefreshToken); | |||
| } catch (e) { | |||
| // The other tab did not store a new token. Refresh from here. | |||
| } | |||
| } | |||
| localStorage.setItem(LOCK_KEY, String(Date.now())); | |||
| try { | |||
| const response = await run(); | |||
| if (response?.status === 200 && response.data) { | |||
| storeRefreshedTokens(response.data.accessToken, response.data.refreshToken); | |||
| } | |||
| return response; | |||
| } finally { | |||
| localStorage.removeItem(LOCK_KEY); | |||
| } | |||
| })().finally(() => { | |||
| inFlight = null; | |||
| }); | |||
| return inFlight; | |||
| } | |||
| @@ -2,6 +2,7 @@ import { createContext, useEffect, useRef, useCallback } from 'react'; | |||
| import { apiPath } from 'auth/utils'; | |||
| import { REFRESH_TOKEN } from 'utils/ApiPathConst'; | |||
| import axios from 'axios'; | |||
| import { beginRefresh } from 'auth/refreshGate'; | |||
| import { | |||
| isUserLoggedIn, | |||
| } from "utils/Utils"; | |||
| @@ -16,10 +17,10 @@ const RefreshTokenProvider = ({ children }) => { | |||
| if (!isRefresh.current) { | |||
| const refreshToken = localStorage.getItem('refreshToken'); | |||
| isRefresh.current = true; | |||
| axios | |||
| beginRefresh(() => axios | |||
| .post(`${apiPath}${REFRESH_TOKEN}`, { | |||
| refreshToken: refreshToken | |||
| }) | |||
| })) | |||
| .then((response) => { | |||
| if (response.status === 200) { | |||
| const newAccessToken = response.data.accessToken; | |||