mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 18:55:42 +00:00
fix: session expiry during usage and also login error message
This commit is contained in:
@@ -42,21 +42,41 @@ client.interceptors.request.use((config) => {
|
||||
});
|
||||
|
||||
// Token refresh state
|
||||
let isRefreshing = false;
|
||||
let failedQueue: {
|
||||
resolve: (token: string) => void;
|
||||
reject: (error: unknown) => void;
|
||||
}[] = [];
|
||||
let refreshPromise: Promise<string> | null = null;
|
||||
|
||||
function processQueue(error: unknown, token?: string) {
|
||||
failedQueue.forEach(({ resolve, reject }) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(token!);
|
||||
/**
|
||||
* Single-flight token refresh: concurrent callers (the 401 interceptor and
|
||||
* the proactive scheduler) share one in-flight request so the refresh token
|
||||
* is only rotated once. Throws if no refresh token is stored or the server
|
||||
* rejects it — callers decide how to end the session.
|
||||
*/
|
||||
async function refreshSessionTokens(): Promise<string> {
|
||||
refreshPromise ??= (async () => {
|
||||
const refreshToken = getCookie("refresh-token");
|
||||
if (!refreshToken) {
|
||||
throw new Error("missing refresh token");
|
||||
}
|
||||
|
||||
type TokenPair = { token: string; refreshToken: string };
|
||||
const { data } = await client.post<Partial<TokenPair> & { data?: TokenPair }>(
|
||||
URL_CONSTANTS.AUTH.REFRESH_TOKEN,
|
||||
{ refreshToken },
|
||||
);
|
||||
// The API returns the pair flat ({ success, token, refreshToken }); accept
|
||||
// a { data: { ... } }-wrapped shape too so a transform change can't
|
||||
// silently break refresh again.
|
||||
const payload = data.data ?? data;
|
||||
if (!payload.token || !payload.refreshToken) {
|
||||
throw new Error("malformed refresh-token response");
|
||||
}
|
||||
setCookie("auth-token", payload.token, 7);
|
||||
setCookie("refresh-token", payload.refreshToken, 7);
|
||||
return payload.token;
|
||||
})().finally(() => {
|
||||
refreshPromise = null;
|
||||
});
|
||||
failedQueue = [];
|
||||
|
||||
return refreshPromise;
|
||||
}
|
||||
|
||||
// Handle auth errors globally with token refresh
|
||||
@@ -72,54 +92,41 @@ client.interceptors.response.use(
|
||||
// - status is not 401
|
||||
// - already retried
|
||||
// - it's the refresh endpoint itself
|
||||
// - it's a credential endpoint (401 there = wrong credentials, not an
|
||||
// expired session — refreshing would mask the real error)
|
||||
if (
|
||||
!error.response ||
|
||||
error.response.status !== 401 ||
|
||||
originalRequest._retry ||
|
||||
originalRequest.url === URL_CONSTANTS.AUTH.REFRESH_TOKEN
|
||||
originalRequest.url === URL_CONSTANTS.AUTH.REFRESH_TOKEN ||
|
||||
originalRequest.url === URL_CONSTANTS.AUTH.LOGIN ||
|
||||
originalRequest.url === URL_CONSTANTS.USERS.SIGN_UP
|
||||
) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (isRefreshing) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
failedQueue.push({ resolve, reject });
|
||||
}).then((token) => {
|
||||
originalRequest.headers.Authorization = `Bearer ${token}`;
|
||||
return client(originalRequest);
|
||||
});
|
||||
}
|
||||
|
||||
originalRequest._retry = true;
|
||||
isRefreshing = true;
|
||||
|
||||
const refreshToken = getCookie("refresh-token");
|
||||
|
||||
if (!refreshToken) {
|
||||
isRefreshing = false;
|
||||
clearAuthCookies();
|
||||
// Nothing to refresh with (e.g. not logged in yet) — surface the
|
||||
// original error instead of a confusing refresh failure.
|
||||
if (!getCookie("refresh-token")) {
|
||||
if (getCookie("auth-token")) {
|
||||
// Half-broken cookie state; reset it.
|
||||
clearAuthCookies();
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
originalRequest._retry = true;
|
||||
|
||||
try {
|
||||
const { data } = await client.post<{
|
||||
data: { token: string; refreshToken: string };
|
||||
}>(URL_CONSTANTS.AUTH.REFRESH_TOKEN, { refreshToken });
|
||||
const { token, refreshToken: newRefreshToken } = data.data;
|
||||
setCookie("auth-token", token, 7);
|
||||
setCookie("refresh-token", newRefreshToken, 7);
|
||||
const token = await refreshSessionTokens();
|
||||
originalRequest.headers.Authorization = `Bearer ${token}`;
|
||||
processQueue(null, token);
|
||||
return client(originalRequest);
|
||||
} catch (refreshError) {
|
||||
processQueue(refreshError, undefined);
|
||||
clearAuthCookies();
|
||||
return Promise.reject(refreshError);
|
||||
} finally {
|
||||
isRefreshing = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export { client };
|
||||
export { client, clearAuthCookies, getCookie, refreshSessionTokens };
|
||||
export type { UseQueryOptions, QueryObserverOptions };
|
||||
|
||||
Reference in New Issue
Block a user