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.
 
 
 
 

82 lines
2.0 KiB

import { request, unwrapApiBody, type ApiResponse } from "../utils/http/factory";
export type AuthUser = {
id: number;
username: string;
};
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,
};
}