133 lines
3.4 KiB
TypeScript
133 lines
3.4 KiB
TypeScript
import { create } from 'zustand';
|
|
import { setAccessToken, clearTokens, restoreCustomerSession } from '@/services/api/client';
|
|
|
|
export interface CustomerAuthUser {
|
|
customer_id: string;
|
|
email: string;
|
|
first_name?: string;
|
|
last_name?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
interface AuthState {
|
|
accessToken: string | null;
|
|
customer: CustomerAuthUser | null;
|
|
isAuthenticated: boolean;
|
|
/** False until bootstrap has tried refresh/sessionStorage restore */
|
|
authReady: boolean;
|
|
setAuth: (accessToken: string, customer: CustomerAuthUser | null) => void;
|
|
clearAuth: () => void;
|
|
setAuthReady: (ready: boolean) => void;
|
|
bootstrapAuth: () => Promise<void>;
|
|
}
|
|
|
|
const SESSION_KEY = 'ifixkart_customer_auth';
|
|
let bootstrapPromise: Promise<void> | null = null;
|
|
|
|
function persistSession(accessToken: string, customer: CustomerAuthUser | null) {
|
|
try {
|
|
sessionStorage.setItem(
|
|
SESSION_KEY,
|
|
JSON.stringify({ accessToken, customer })
|
|
);
|
|
} catch {
|
|
/* ignore quota / private mode */
|
|
}
|
|
}
|
|
|
|
function readPersistedSession(): { accessToken: string; customer: CustomerAuthUser | null } | null {
|
|
try {
|
|
const raw = sessionStorage.getItem(SESSION_KEY);
|
|
if (!raw) return null;
|
|
const parsed = JSON.parse(raw);
|
|
if (!parsed?.accessToken) return null;
|
|
return parsed;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function clearPersistedSession() {
|
|
try {
|
|
sessionStorage.removeItem(SESSION_KEY);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>((set, get) => ({
|
|
accessToken: null,
|
|
customer: null,
|
|
isAuthenticated: false,
|
|
authReady: false,
|
|
|
|
setAuth: (accessToken, customer) => {
|
|
setAccessToken(accessToken);
|
|
persistSession(accessToken, customer);
|
|
set({
|
|
accessToken,
|
|
customer,
|
|
isAuthenticated: true,
|
|
authReady: true,
|
|
});
|
|
},
|
|
|
|
clearAuth: () => {
|
|
clearTokens();
|
|
clearPersistedSession();
|
|
set({
|
|
accessToken: null,
|
|
customer: null,
|
|
isAuthenticated: false,
|
|
authReady: true,
|
|
});
|
|
},
|
|
|
|
setAuthReady: (ready) => set({ authReady: ready }),
|
|
|
|
bootstrapAuth: async () => {
|
|
if (get().authReady) return;
|
|
if (bootstrapPromise) return bootstrapPromise;
|
|
|
|
bootstrapPromise = (async () => {
|
|
try {
|
|
// 1) Restore from tab sessionStorage (survives soft navigations / reloads in same tab)
|
|
const persisted = readPersistedSession();
|
|
if (persisted?.accessToken) {
|
|
setAccessToken(persisted.accessToken);
|
|
set({
|
|
accessToken: persisted.accessToken,
|
|
customer: persisted.customer,
|
|
isAuthenticated: true,
|
|
authReady: true,
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 2) Try HttpOnly refresh cookie → new access token
|
|
const restored = await restoreCustomerSession();
|
|
if (restored) {
|
|
const customer = {
|
|
customer_id: restored.customer_id,
|
|
email: restored.email,
|
|
first_name: restored.first_name,
|
|
};
|
|
set({
|
|
accessToken: restored.access_token,
|
|
customer,
|
|
isAuthenticated: true,
|
|
authReady: true,
|
|
});
|
|
persistSession(restored.access_token, customer);
|
|
return;
|
|
}
|
|
|
|
set({ authReady: true, isAuthenticated: false });
|
|
} finally {
|
|
bootstrapPromise = null;
|
|
}
|
|
})();
|
|
|
|
return bootstrapPromise;
|
|
},
|
|
}));
|