ifixkart-storefront/store/authStore.ts

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;
},
}));