import { browser } from '$app/environment'; import { readable, writable } from 'svelte/store'; export type AppSession = { name: string; email: string; role: string; token?: string | null; tenant_id?: string | null; client_role?: string | null; user_id?: number | null; client_account_id?: number | null; module_permissions?: Record; // Permission-key array, populated when the user signed in via the internal // Hunter Stock Feeds /api/access/login endpoint. Drives feature gating. permissions?: string[]; role_name?: string | null; }; const ACCESS_LEVEL_ORDER: Record = { none: 0, view: 1, edit: 2, manage: 3 }; const CLIENT_STORAGE_KEY = 'data-entry-app-client-session'; const ADMIN_STORAGE_KEY = 'data-entry-app-admin-session'; function readStoredSession(storageKey: string): AppSession | null { if (!browser) { return null; } const value = localStorage.getItem(storageKey); if (!value) { return null; } try { return JSON.parse(value) as AppSession; } catch { localStorage.removeItem(storageKey); return null; } } function createSessionStore(storageKey: string) { const store = writable(readStoredSession(storageKey)); if (browser) { window.addEventListener('storage', (event) => { if (event.key === storageKey) { store.set(readStoredSession(storageKey)); } }); } return { subscribe: store.subscribe, set(session: AppSession) { const storedSession = { ...session, token: null }; if (browser) { localStorage.setItem(storageKey, JSON.stringify(storedSession)); } store.set(storedSession); }, clear() { if (browser) { localStorage.removeItem(storageKey); // Drop any cached API responses keyed to the old session identity. // Imported lazily so this module stays free of api.ts side-effects. import('$lib/api').then(({ clearApiCache }) => clearApiCache()).catch(() => {}); } store.set(null); } }; } export function getStoredClientSession() { return readStoredSession(CLIENT_STORAGE_KEY); } export function getStoredAdminSession() { return readStoredSession(ADMIN_STORAGE_KEY); } export function hasStoredClientSession() { return getStoredClientSession() !== null; } export function hasStoredAdminSession() { return getStoredAdminSession() !== null; } export function hasModuleAccess( session: AppSession | null | undefined, moduleKey: string, minimumLevel: 'view' | 'edit' | 'manage' = 'view' ) { if (!session) { return false; } if (session.role === 'admin') { return true; } const currentLevel = session.module_permissions?.[moduleKey] ?? 'none'; return (ACCESS_LEVEL_ORDER[currentLevel] ?? 0) >= ACCESS_LEVEL_ORDER[minimumLevel]; } // Permission-key check for the internal access-control system. Returns false // for legacy sessions that don't carry a permissions array. UI gating only — // every privileged backend route still enforces permissions itself. export function hasPermission(session: AppSession | null | undefined, permissionKey: string) { if (!session?.permissions) { return false; } return session.permissions.includes(permissionKey); } export function hasAnyPermission(session: AppSession | null | undefined, permissionKeys: string[]) { if (!session?.permissions) { return false; } return permissionKeys.some((key) => session.permissions!.includes(key)); } export const sessionHydrated = readable(false, (set) => { if (!browser) { return undefined; } const frame = window.requestAnimationFrame(() => { set(true); }); return () => { window.cancelAnimationFrame(frame); }; }); export const clientSession = createSessionStore(CLIENT_STORAGE_KEY); export const adminSession = createSessionStore(ADMIN_STORAGE_KEY);