TorbatYar/frontend/hooks/useAuth.ts
2026-07-28 20:39:10 +03:30

118 lines
3.1 KiB
TypeScript

"use client";
import { useCallback, useEffect, useState } from "react";
import {
fetchAuthConfig,
loginWithRedirect,
fetchMe,
getValidAccessToken,
clearTokens,
logout,
type AuthConfig,
} from "@/lib/auth";
interface AuthUser {
user_id: string;
email: string | null;
username: string | null;
roles: string[];
}
export function useAuth() {
const [config, setConfig] = useState<AuthConfig | null>(null);
const [user, setUser] = useState<AuthUser | null>(null);
const [loading, setLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const load = useCallback(async () => {
setLoading(true);
try {
try {
const cfg = await fetchAuthConfig();
setConfig(cfg);
} catch {
// Config blip must not wipe an existing session.
}
const token = await getValidAccessToken();
if (!token) {
setUser(null);
setIsAuthenticated(false);
return;
}
try {
const me = await fetchMe(token);
setUser(me);
setIsAuthenticated(true);
} catch {
// Access may have expired mid-flight — try one refresh path again.
const retry = await getValidAccessToken();
if (!retry || retry === token) {
clearTokens();
setUser(null);
setIsAuthenticated(false);
return;
}
try {
const me = await fetchMe(retry);
setUser(me);
setIsAuthenticated(true);
} catch {
clearTokens();
setUser(null);
setIsAuthenticated(false);
}
}
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
load();
// Keep session alive while tab is open (proactive refresh).
const id = window.setInterval(async () => {
const token = await getValidAccessToken();
if (!token) {
setUser(null);
setIsAuthenticated(false);
return;
}
}, 45_000);
const onFocus = async () => {
const token = await getValidAccessToken();
if (!token) {
setUser(null);
setIsAuthenticated(false);
}
};
const onVis = async () => {
if (document.visibilityState !== "visible") return;
const token = await getValidAccessToken();
if (!token) {
setUser(null);
setIsAuthenticated(false);
}
};
window.addEventListener("focus", onFocus);
document.addEventListener("visibilitychange", onVis);
return () => {
window.clearInterval(id);
window.removeEventListener("focus", onFocus);
document.removeEventListener("visibilitychange", onVis);
};
}, [load]);
const login = async (redirectPath?: string) => {
await loginWithRedirect(redirectPath);
};
const signOut = async () => {
const cfg = config || (await fetchAuthConfig());
await logout(cfg);
};
return { user, config, loading, isAuthenticated, login, signOut, reload: load };
}