@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
JavaScript
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;
}