You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
86 lines
2.0 KiB
86 lines
2.0 KiB
import { request, unwrapApiBody, type ApiResponse } from "../utils/http/factory";
|
|
|
|
export type AuthUser = {
|
|
id: number;
|
|
username: string;
|
|
role: string;
|
|
publicSlug: string | null;
|
|
nickname: string | null;
|
|
avatar: string | null;
|
|
};
|
|
|
|
type MeResult = {
|
|
user: AuthUser;
|
|
};
|
|
|
|
export type AuthSessionState = {
|
|
initialized: boolean;
|
|
pending: boolean;
|
|
loggedIn: boolean;
|
|
user: AuthUser | null;
|
|
};
|
|
|
|
export const AUTH_SESSION_STATE_KEY = "auth:session";
|
|
export const DEFAULT_AUTH_SESSION_STATE: AuthSessionState = {
|
|
initialized: false,
|
|
pending: false,
|
|
loggedIn: false,
|
|
user: null,
|
|
};
|
|
|
|
function isUnauthorized(error: unknown) {
|
|
if (typeof error !== "object" || error === null) {
|
|
return false;
|
|
}
|
|
return "statusCode" in error && (error as { statusCode?: number }).statusCode === 401;
|
|
}
|
|
|
|
export function useAuthSession() {
|
|
const state = useState<AuthSessionState>(AUTH_SESSION_STATE_KEY, () => ({
|
|
...DEFAULT_AUTH_SESSION_STATE,
|
|
}));
|
|
|
|
const applyUser = (user: AuthUser | null) => {
|
|
state.value.user = user;
|
|
state.value.loggedIn = Boolean(user);
|
|
state.value.initialized = true;
|
|
};
|
|
|
|
const clear = () => {
|
|
applyUser(null);
|
|
};
|
|
|
|
const refresh = async (force = false) => {
|
|
if (state.value.initialized && !force) {
|
|
return state.value.user;
|
|
}
|
|
if (state.value.pending) {
|
|
return state.value.user;
|
|
}
|
|
state.value.pending = true;
|
|
try {
|
|
const fetcher = import.meta.server ? useRequestFetch() : request;
|
|
const payload = await fetcher<ApiResponse<MeResult>>("/api/auth/me");
|
|
const data = unwrapApiBody(payload);
|
|
applyUser(data.user);
|
|
return data.user;
|
|
} catch (error: unknown) {
|
|
if (isUnauthorized(error)) {
|
|
clear();
|
|
return null;
|
|
}
|
|
throw error;
|
|
} finally {
|
|
state.value.pending = false;
|
|
}
|
|
};
|
|
|
|
return {
|
|
initialized: computed(() => state.value.initialized),
|
|
loggedIn: computed(() => state.value.loggedIn),
|
|
user: computed(() => state.value.user),
|
|
pending: computed(() => state.value.pending),
|
|
refresh,
|
|
clear,
|
|
};
|
|
}
|
|
|