79 lines
2.3 KiB
TypeScript
79 lines
2.3 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, refreshToken, user } = await authApi.login({ email, password });
|
|
tokenStorage.set(accessToken, refreshToken);
|
|
set({ user, isInitializing: false });
|
|
},
|
|
|
|
logout: async () => {
|
|
try {
|
|
await authApi.logout(tokenStorage.getRefreshToken() ?? '');
|
|
} catch {
|
|
// Best-effort: always clear the local session, even if the API call fails.
|
|
} finally {
|
|
tokenStorage.clear();
|
|
set({ user: null, isInitializing: false });
|
|
}
|
|
},
|
|
|
|
init: async () => {
|
|
const refreshToken = tokenStorage.getRefreshToken();
|
|
if (!refreshToken) {
|
|
tokenStorage.clear();
|
|
set({ user: null, isInitializing: false });
|
|
return;
|
|
}
|
|
try {
|
|
const { accessToken, refreshToken: nextRefreshToken, user } = await authApi.refresh(refreshToken);
|
|
tokenStorage.set(accessToken, nextRefreshToken);
|
|
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 () => {
|
|
const refreshToken = tokenStorage.getRefreshToken();
|
|
if (!refreshToken) return null;
|
|
try {
|
|
const { accessToken, refreshToken: nextRefreshToken } = await authApi.refresh(refreshToken);
|
|
tokenStorage.set(accessToken, nextRefreshToken);
|
|
return accessToken;
|
|
} catch {
|
|
return null;
|
|
}
|
|
},
|
|
() => {
|
|
tokenStorage.clear();
|
|
useAuthStore.setState({ user: null, isInitializing: false });
|
|
},
|
|
);
|