UNPKG

@kilpi/react-client

Version:

Kilpi React Client Components · Kilpi is the authorization framework for full-stack TypeScript applications, designed for flexible, powerful, agnostic, intuitive and developer friendly authorization.

190 lines (184 loc) 5.14 kB
// src/plugins/ReactClientPlugin.ts import { createKilpiClientPlugin } from "@kilpi/client"; // src/components/AuthorizeClient.tsx function create_AuthorizeClient(client) { void client; return function AuthorizeClient({ policy, children, Unauthorized, Idle, Pending, Error, isDisabled }) { const query = policy.useAuthorize({ isDisabled }); switch (query.status) { // Handle pending, idle and error states with custom functions case "pending": { return typeof Pending === "function" ? Pending(query) : Pending ?? null; } case "idle": { if (Idle === void 0) { return typeof Pending === "function" ? Pending(query) : Pending ?? null; } return typeof Idle === "function" ? Idle(query) : Idle ?? null; } case "error": { return typeof Error === "function" ? Error(query) : Error ?? null; } // Success: Decide component to render based on granted case "success": { if (query.granted) { return typeof children === "function" ? children(query) : children ?? null; } return typeof Unauthorized === "function" ? Unauthorized(query) : Unauthorized ?? null; } } }; } // src/hooks/useAuthorize.ts import "@kilpi/client"; import { useEffect as useEffect2, useState } from "react"; // src/hooks/useUnmountAbortSignalRef.ts import { useEffect, useRef } from "react"; function useUnmountAbortSignalRef() { const controllerRef = useRef(new AbortController()); const signalRef = useRef(controllerRef.current.signal); useEffect(() => { return () => { controllerRef.current.abort(); }; }, []); return signalRef; } // src/hooks/useAuthorize.ts function create_useAuthorize(Client, policy) { void Client; function getStatusWithFlags(status) { return { status, isIdle: status === "idle", isError: status === "error", isSuccess: status === "success", isPending: status === "pending" }; } return function useAuthorize(options = {}) { const isDisabled = options.isDisabled || false; const [status, setStatus] = useState("idle"); const [error, setError] = useState(null); const [decision, setDecision] = useState(null); const signalRef = useUnmountAbortSignalRef(); useEffect2(() => { return Client.$hooks.onCacheInvalidate(({ matches }) => { if (matches(policy)) { setStatus("idle"); setError(null); setDecision(null); } }); }, []); useEffect2(() => { if (isDisabled) { setStatus("idle"); setError(null); setDecision(null); return; } if (status !== "idle") { return; } setStatus("pending"); policy.authorize({ signal: signalRef.current }).then((value) => { if (signalRef.current.aborted) return; setDecision(value); setStatus("success"); }).catch((err) => { if (signalRef.current.aborted) return; setError(err); setStatus("error"); }); }, [isDisabled, status, signalRef]); switch (status) { case "idle": return { ...getStatusWithFlags(status), decision: null, granted: false, error: null, isDisabled }; case "pending": return { ...getStatusWithFlags(status), decision: null, granted: false, error: null, isDisabled }; case "error": return { ...getStatusWithFlags(status), decision: null, granted: false, error, isDisabled }; case "success": if (decision?.granted) { return { ...getStatusWithFlags(status), decision, granted: true, error: null, isDisabled }; } else { return { ...getStatusWithFlags(status), // `decision` should never be null here, but we need to satisfy TypeScript decision: decision || { granted: false }, granted: false, error: null, isDisabled }; } } }; } // src/plugins/ReactClientPlugin.ts function ReactClientPlugin() { return createKilpiClientPlugin((Client) => { return { /** * Extend the client to allow creating components. */ extendClient() { return { $createReactClientComponents() { const AuthorizeClient = create_AuthorizeClient(Client); return { AuthorizeClient }; } }; }, /** * Extend policies to add the `useAuthorize` hook. Ensure matches extension type * with the satisfies clause. */ extendPolicy(policy) { return { useAuthorize(options) { const useAuthorize = create_useAuthorize(Client, policy); return useAuthorize(options); } }; } }; }); } export { ReactClientPlugin };