71 lines
1.9 KiB
TypeScript
71 lines
1.9 KiB
TypeScript
import { create } from 'zustand';
|
|||
|
|
|
||
|
|
import { registerAuthHandlers } from '@/api/client';
|
||
|
|
import { tokenStorage } from '@/api/tokenStorage';
|
||
|
|
import { authApi } from '../api/authApi';
|
||
|
|
import type { AuthUser } from '../types';
|
||
|
|
|
||
|
|
interface AuthState {
|
||
|
|
/** Authenticated user, or null when signed out. */
|
||
|
|
user: AuthUser | null;
|
||
|
|
/** Whether a session bootstrap is in progress. */
|
||
|
|
isInitializing: boolean;
|
||
|
|
login: (email: string, password: string) => Promise<void>;
|
||
|
|
logout: () => Promise<void>;
|
||
|
|
/** Bootstrap session: try refresh on app start. */
|
||
|
|
init: () => Promise<void>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const useAuthStore = create<AuthState>()((set) => ({
|
||
|
|
user: null,
|
||
|
|
isInitializing: true,
|
||
|
|
|
||
|
|
login: async (email, password) => {
|
||
|
|
const { accessToken, user } = await authApi.login({ email, password });
|
||
|
|
tokenStorage.set(accessToken);
|
||
|
|
set({ user, isInitializing: false });
|
||
|
|
},
|
||
|
|
|
||
|
|
logout: async () => {
|
||
|
|
try {
|
||
|
|
await authApi.logout();
|
||
|
|
} catch {
|
||
|
|
// Best-effort: always clear the local session, even if the API call fails.
|
||
|
|
} finally {
|
||
|
|
tokenStorage.clear();
|
||
|
|
set({ user: null, isInitializing: false });
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
init: async () => {
|
||
|
|
try {
|
||
|
|
const { accessToken, user } = await authApi.refresh();
|
||
|
|
tokenStorage.set(accessToken);
|
||
|
|
set({ user, isInitializing: false });
|
||
|
|
} catch {
|
||
|
|
tokenStorage.clear();
|
||
|
|
set({ user: null, isInitializing: false });
|
||
|
|
}
|
||
|
|
},
|
||
|
|
}));
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Wire the API client to this store: refresh access token and clear session on
|
||
|
|
* hard 401 (unrecoverable). Keeps the auth concern centralized, not scattered.
|
||
|
|
*/
|
||
|
|
registerAuthHandlers(
|
||
|
|
async () => {
|
||
|
|
try {
|
||
|
|
const { accessToken } = await authApi.refresh();
|
||
|
|
tokenStorage.set(accessToken);
|
||
|
|
return accessToken;
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
},
|
||
|
|
() => {
|
||
|
|
tokenStorage.clear();
|
||
|
|
useAuthStore.setState({ user: null, isInitializing: false });
|
||
|
|
},
|
||
|
|
);
|