mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-29 17:38:14 +00:00
feat: implement concurrent-safe token refreshing with session-aware error handling
This commit is contained in:
@@ -10,21 +10,54 @@ interface RefreshResponse {
|
||||
refreshToken: string;
|
||||
}
|
||||
|
||||
export async function refreshAccessToken(): Promise<string> {
|
||||
/**
|
||||
* Marks the one failure that actually ends a session: the server rejecting the
|
||||
* refresh token. Read structurally by the API layer's 401 handler — a shared
|
||||
* error class would mean `libs/api` importing `libs/auth`, which already
|
||||
* imports `libs/api`.
|
||||
*/
|
||||
const sessionExpired = (message: string) =>
|
||||
Object.assign(new Error(message), { sessionExpired: true });
|
||||
|
||||
let inFlight: Promise<string> | null = null;
|
||||
|
||||
/**
|
||||
* Concurrent 401s must share one refresh. A page fires several requests at
|
||||
* once; without this each one POSTs the same refresh token, the server rotates
|
||||
* on the first and rejects the rest, and the losers tear down the session the
|
||||
* winner just renewed.
|
||||
*/
|
||||
export function refreshAccessToken(): Promise<string> {
|
||||
inFlight ??= runRefresh().finally(() => {
|
||||
inFlight = null;
|
||||
});
|
||||
return inFlight;
|
||||
}
|
||||
|
||||
async function runRefresh(): Promise<string> {
|
||||
const refreshToken = authStorage.getRefreshToken();
|
||||
if (!refreshToken) throw new Error("No refresh token available");
|
||||
if (!refreshToken) throw sessionExpired("No refresh token available");
|
||||
|
||||
// The IAM service exposes this as `refresh-token`; posting to `/auth/refresh`
|
||||
// 404s, which the catch below turns into a silent logout.
|
||||
// 404s, which the caller would turn into a silent logout.
|
||||
const response = await fetch(`${BASE_API_URL}/auth/refresh-token`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
});
|
||||
|
||||
if (
|
||||
response.status === 400 ||
|
||||
response.status === 401 ||
|
||||
response.status === 403
|
||||
) {
|
||||
throw sessionExpired("Refresh token rejected");
|
||||
}
|
||||
|
||||
// Anything else is the API having a bad minute — a 502, a proxy timeout. The
|
||||
// session is still valid, so leave it alone and let the screen report it.
|
||||
if (!response.ok) {
|
||||
authStorage.clear();
|
||||
throw new Error("Token refresh failed");
|
||||
throw new Error(`Token refresh failed (${response.status})`);
|
||||
}
|
||||
|
||||
const data: RefreshResponse = await response.json();
|
||||
|
||||
Reference in New Issue
Block a user