@styra/opa-react
Version: 
Styra-supported React hooks and components for frontend authorization based on @styra/opa
104 lines • 4.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthzContext = void 0;
exports.default = AuthzProvider;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const react_query_1 = require("@tanstack/react-query");
const batshit_1 = require("@yornaath/batshit");
const table = new WeakMap();
// key is used to index the individual Batch API batches when splitting
// an incoming batch of EvalQuery. The same `x` is later passed to the
// batch item resolver in the results object. We use the WeakMap to
// ensure that the same `x` gets the same number.
function key(x) {
    const r = table.get(x);
    if (r)
        return r;
    const num = new Uint32Array(1);
    crypto.getRandomValues(num);
    const rand = num.toString();
    table.set(x, rand);
    return rand;
}
const evals = (sdk) => (0, batshit_1.create)({
    fetcher: async (evals) => {
        const evs = evals.map((x) => ({ ...x, k: key(x) }));
        const groups = Object.groupBy(evs, ({ path }) => path); // group by path
        return Promise.all(Object.entries(groups).map(([path, inputs]) => {
            const inps = {};
            const fromRs = {};
            inputs.forEach(({ k, input, fromResult }) => {
                inps[k] = input;
                fromRs[k] = fromResult;
            });
            return sdk
                .evaluateBatch(path, inps, { rejectMixed: true })
                .then((res) => Object.fromEntries(Object.entries(res).map(([k, res]) => [
                k,
                fromRs[k] ? fromRs[k](res) : res,
            ])));
        })).then((all) => 
        // combine result arrays of objects (if there's more than one)
        all.length == 1 ? all[0] : Object.assign({}, ...all));
    },
    resolver: (results, query) => results[key(query)] ?? null,
    scheduler: (0, batshit_1.windowScheduler)(10),
    name: "@styra/opa-react",
});
// Reference: https://reacttraining.com/blog/react-context-with-typescript
exports.AuthzContext = (0, react_1.createContext)(undefined);
/**
 * Configures the authorization SDK, with default path/input of applicable.
 * The `<AuthzProvider/>` wrapper needs to be as high as possible in the component tree,
 * since `<Authz/>` or `useAuthz` may only be used inside that wrapper.
 *
 * @example
 *
 * ```tsx
 * <AuthzProvider sdk={sdk} defaultPath="tickets" defaultInput={{tenant: 'acme-corp'}}>
 *   <App/>
 * </AuthzProvider>
 * ```
 */
function AuthzProvider({ children, opaClient, defaultPath, defaultInput, defaultFromResult, retry = 0, batch = false, }) {
    const batcher = (0, react_1.useMemo)(() => batch && opaClient && evals(opaClient), [opaClient, batch]);
    const defaultQueryFn = (0, react_1.useCallback)(async ({ queryKey, meta = {}, signal, }) => {
        const [path, input] = queryKey;
        const fromResult = meta["fromResult"];
        if (!batcher) {
            // use the default, unbatched queries backed by react-query
            return path
                ? opaClient.evaluate(path, input, {
                    fromResult,
                    fetchOptions: { signal },
                })
                : opaClient.evaluateDefault(input, {
                    fromResult,
                    fetchOptions: { signal },
                });
        }
        if (!path)
            throw new Error("batch requests need to have a defined query path");
        return batcher.fetch({ path, input, fromResult });
    }, [batcher, batch]);
    const queryClient = (0, react_1.useMemo)(() => new react_query_1.QueryClient({
        defaultOptions: {
            queries: {
                queryFn: defaultQueryFn,
                retry,
            },
        },
    }), [defaultQueryFn, retry]);
    const context = (0, react_1.useMemo)(() => ({
        opaClient,
        defaultPath,
        defaultInput,
        defaultFromResult,
        queryClient: queryClient,
    }), [opaClient, defaultPath, defaultInput, defaultFromResult, queryClient]);
    if (!queryClient)
        return null;
    return ((0, jsx_runtime_1.jsx)(exports.AuthzContext.Provider, { value: context, children: children }));
}
//# sourceMappingURL=authz-provider.js.map