UNPKG

@styra/opa-react

Version:

Styra-supported React hooks and components for frontend authorization based on @styra/opa

104 lines 4.2 kB
"use strict"; 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