UNPKG

@adonis-agora/authkit-react

Version:

Frontend ergonomics over AuthKit for AdonisJS + Inertia + React apps: a typed useAuth() hook, role-gating hooks and gating components.

92 lines (91 loc) 3.07 kB
import { usePage } from '@inertiajs/react'; import { useContext, useEffect, useState } from 'react'; import { useAuthkitConfig } from '../config.js'; import { AuthContext } from '../provider.js'; import { jsonRequest } from './use_resource.js'; const ANON_PRINCIPAL = 'anon'; function usePrincipalId() { const fromContext = useContext(AuthContext); let fromPage; try { fromPage = usePage().props?.authkit; } catch { fromPage = undefined; } return (fromContext ?? fromPage)?.user?.id ?? ANON_PRINCIPAL; } function cacheKey(path, principal, permission, resource) { return [principal, path, permission, resource ?? ''].join('|'); } export const canCache = { resolved: new Map(), inflight: new Map(), clear() { this.resolved.clear(); this.inflight.clear(); }, }; export function invalidateCanCache() { canCache.clear(); } export async function checkCan(path, permission, resource, csrfToken, principal = ANON_PRINCIPAL) { const key = cacheKey(path, principal, permission, resource); const cached = canCache.resolved.get(key); if (cached !== undefined) return cached; const pending = canCache.inflight.get(key); if (pending) return pending; const promise = jsonRequest(path, { method: 'POST', csrfToken, body: JSON.stringify({ permission, ...(resource ? { resource } : {}) }), }) .then((res) => { const allowed = res?.allowed === true; canCache.resolved.set(key, allowed); return allowed; }) .finally(() => { canCache.inflight.delete(key); }); canCache.inflight.set(key, promise); return promise; } export function useCan(permission, resource) { const config = useAuthkitConfig(); const principal = usePrincipalId(); const path = config.endpoints.can; const key = cacheKey(path, principal, permission, resource); const [state, setState] = useState(() => { const cached = canCache.resolved.get(key); return cached !== undefined ? { allowed: cached, loading: false } : { allowed: false, loading: true }; }); useEffect(() => { let cancelled = false; const hit = canCache.resolved.get(key); if (hit !== undefined) { setState((s) => !s.loading && s.allowed === hit && s.error === undefined ? s : { allowed: hit, loading: false }); return; } setState((s) => (s.loading ? s : { ...s, loading: true })); checkCan(path, permission, resource, config.csrfToken, principal) .then((allowed) => { if (!cancelled) setState({ allowed, loading: false }); }) .catch((err) => { if (!cancelled) setState({ allowed: false, loading: false, error: err }); }); return () => { cancelled = true; }; }, [key, path, permission, resource, config.csrfToken, principal]); return state; }