import { defineStore } from "pinia"; import { ApiError } from "@vmall/shared"; import type { AuthTokens, User } from "@vmall/shared"; const USER_KEY = "vmall.user"; const TOKEN_KEY = "vmall.token"; function isUser(v: unknown): v is User { if (!v || typeof v !== "object") return false; const o = v as Record; return ( typeof o.id === "string" && typeof o.email === "string" && typeof o.display_name === "string" && typeof o.role === "string" ); } export function readStoredUser(): User | null { if (!import.meta.client) return null; try { const raw = localStorage.getItem(USER_KEY); if (!raw) return null; const parsed: unknown = JSON.parse(raw); return isUser(parsed) ? parsed : null; } catch { return null; } } interface SessionState { user: User | null; token: string | null; /** Set once the session is trusted: by a fresh sign-in or a successful validate(). */ validated: boolean; } export const useSessionStore = defineStore("session", { state: (): SessionState => ({ user: null, token: null, validated: false }), getters: { isLoggedIn: (s): boolean => s.token !== null, }, actions: { hydrate(): void { if (!import.meta.client) return; this.token = localStorage.getItem(TOKEN_KEY); this.user = readStoredUser(); }, setAuth(auth: AuthTokens): void { this.token = auth.token; this.user = auth.user; this.validated = true; if (import.meta.client) { localStorage.setItem(TOKEN_KEY, auth.token); localStorage.setItem(USER_KEY, JSON.stringify(auth.user)); } }, clear(): void { this.token = null; this.user = null; this.validated = false; if (import.meta.client) { localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(USER_KEY); } }, /** * Check a token restored from storage against the auth API, replacing the * previous blind trust in localStorage. Only a rejected token clears the * session - an unreachable API must not sign the shopper out. Runs at most * once per load, because setAuth() marks a fresh sign-in as already trusted. */ async validate(): Promise { if (this.validated) return true; if (!import.meta.client || !this.token) return false; const { $api } = useNuxtApp(); try { const user = await $api.me(); this.user = user; this.validated = true; localStorage.setItem(USER_KEY, JSON.stringify(user)); return true; } catch (error) { if (error instanceof ApiError && (error.status === 401 || error.status === 403)) { this.clear(); return false; } // Network or server failure: keep the session rather than guess. return this.token !== null; } }, logout(): void { this.clear(); navigateTo("/login"); }, }, });