@flowerforce/flower-react 4.0.11-beta.1 → 4.0.11-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -96,7 +96,7 @@ const useInitNodes = ({ one, nodes, name, startId, persist = false, initialData,
96
96
  }, [dispatch, name, nodes, startId, initialData, initialState, persist]);
97
97
  };
98
98
 
99
- const useInitDevtools = ({ devtoolState, setWsDevtools, flowName, }) => {
99
+ const useInitDevtools = ({ devtoolState, setWsDevtools, flowName }) => {
100
100
  const { dispatch } = flowerReactStore.ReduxFlowerProvider.getReduxHooks();
101
101
  React.useEffect(() => {
102
102
  /* istanbul ignore next */
@@ -209,7 +209,10 @@ const useFlowerNavigateEvent = ({ current, flowName, isInitialized, wsDevtools,
209
209
  if (!isInitialized)
210
210
  return;
211
211
  if (isDisabled) {
212
- dispatch({ type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/next`, payload: { flowName, disabled: true } });
212
+ dispatch({
213
+ type: `${flowerCore.REDUCER_NAME.FLOWER_FLOW}/next`,
214
+ payload: { flowName, disabled: true }
215
+ });
213
216
  // eslint-disable-next-line no-underscore-dangle, no-undef
214
217
  /* istanbul ignore next */
215
218
  if (wsDevtools &&
@@ -761,6 +764,75 @@ const createSliceWithFlower = (createSliceOptions) => {
761
764
  return slice;
762
765
  };
763
766
 
767
+ const AbacContext = React.createContext({
768
+ can: () => false,
769
+ allowedActions: () => [],
770
+ loaded: false,
771
+ rawRules: null
772
+ });
773
+ const AbacProvider = ({ children, rules, rulesPath, fetchOptions, denyByDefault = true }) => {
774
+ const [loaded, setLoaded] = React.useState(flowerCore.isAbacInitialized());
775
+ const [localRawRules, setLocalRawRules] = React.useState(flowerCore.getRawRules());
776
+ React.useEffect(() => {
777
+ if (rules) {
778
+ flowerCore.initAbac(rules);
779
+ setLocalRawRules(rules);
780
+ setLoaded(true);
781
+ return;
782
+ }
783
+ if (!rulesPath) {
784
+ setLoaded(false);
785
+ setLocalRawRules(null);
786
+ return;
787
+ }
788
+ let mounted = true;
789
+ const init = async () => {
790
+ try {
791
+ const res = await fetch(rulesPath, fetchOptions);
792
+ if (!res.ok)
793
+ throw new Error(`HTTP ${res.status}`);
794
+ const json = (await res.json());
795
+ if (!mounted)
796
+ return;
797
+ flowerCore.initAbac(json);
798
+ setLocalRawRules(json);
799
+ setLoaded(true);
800
+ }
801
+ catch (err) {
802
+ console.error('ABAC: failed to load rules from', rulesPath, err);
803
+ setLocalRawRules(null);
804
+ setLoaded(false);
805
+ }
806
+ };
807
+ init();
808
+ return () => {
809
+ mounted = false;
810
+ };
811
+ }, [rules, rulesPath, fetchOptions]);
812
+ const can = React.useCallback((ctx) => {
813
+ if (!flowerCore.isAbacInitialized())
814
+ return !denyByDefault;
815
+ const subject = flowerCore.getSubject();
816
+ return (flowerCore.getAbacEngine()?.decide({ subject: subject ?? {}, ...ctx }) === 'Permit');
817
+ }, []);
818
+ const allowedActions = React.useCallback((resource, actions = ['read', 'create', 'update', 'delete']) => {
819
+ if (!flowerCore.isAbacInitialized())
820
+ return denyByDefault ? [] : actions.slice();
821
+ const subject = flowerCore.getSubject();
822
+ return flowerCore.getAbacEngine()?.allowedActions({ subject: subject ?? {}, resource, environment: undefined }, actions);
823
+ }, []);
824
+ const value = React.useMemo(() => ({
825
+ can,
826
+ allowedActions,
827
+ loaded,
828
+ rawRules: localRawRules
829
+ }), [loaded, localRawRules]);
830
+ return React.createElement(AbacContext.Provider, { value: value }, children);
831
+ };
832
+ function useAbac() {
833
+ return React.useContext(AbacContext);
834
+ }
835
+
764
836
  Object.defineProperty(exports, "createApi", {
765
837
  enumerable: true,
766
838
  get: function () { return flowerReactStore.createApi; }
@@ -769,6 +841,7 @@ Object.defineProperty(exports, "HistoryContextProvider", {
769
841
  enumerable: true,
770
842
  get: function () { return flowerReactHistoryContext.HistoryContextProvider; }
771
843
  });
844
+ exports.AbacProvider = AbacProvider;
772
845
  exports.Flower = Flower;
773
846
  exports.FlowerAction = FlowerAction;
774
847
  exports.FlowerFlow = FlowerFlow;
@@ -783,5 +856,6 @@ exports.createStoreWithFlower = createStoreWithFlower;
783
856
  exports.flowerReducers = flowerReducers;
784
857
  exports.makeSelectData = makeSelectData;
785
858
  exports.reducerFlower = reducerFlower;
859
+ exports.useAbac = useAbac;
786
860
  exports.useFlower = useFlower;
787
861
  exports.useFlowerNavigate = useFlowerNavigate;
package/dist/index.esm.js CHANGED
@@ -1,6 +1,6 @@
1
- import React, { useEffect, memo, useRef, useState, useMemo, Children, useContext, useCallback } from 'react';
1
+ import React, { useEffect, memo, useRef, useState, useMemo, Children, useContext, useCallback, createContext } from 'react';
2
2
  import _keyBy from 'lodash/keyBy';
3
- import { FlowUtils, FlowerCoreBaseReducers, REDUCER_NAME, FlowerCoreStateSelectors, FlowerStateUtils, Emitter, devtoolState } from '@flowerforce/flower-core';
3
+ import { FlowUtils, FlowerCoreBaseReducers, REDUCER_NAME, FlowerCoreStateSelectors, FlowerStateUtils, Emitter, devtoolState, isAbacInitialized, getRawRules, initAbac, getSubject, getAbacEngine } from '@flowerforce/flower-core';
4
4
  import { FlowerReactProvider, FlowerReactContext } from '@flowerforce/flower-react-context';
5
5
  import _get from 'lodash/get';
6
6
  import { reducerData, ReduxFlowerProvider, flowerDataActions, middlewares } from '@flowerforce/flower-react-store';
@@ -96,7 +96,7 @@ const useInitNodes = ({ one, nodes, name, startId, persist = false, initialData,
96
96
  }, [dispatch, name, nodes, startId, initialData, initialState, persist]);
97
97
  };
98
98
 
99
- const useInitDevtools = ({ devtoolState, setWsDevtools, flowName, }) => {
99
+ const useInitDevtools = ({ devtoolState, setWsDevtools, flowName }) => {
100
100
  const { dispatch } = ReduxFlowerProvider.getReduxHooks();
101
101
  useEffect(() => {
102
102
  /* istanbul ignore next */
@@ -209,7 +209,10 @@ const useFlowerNavigateEvent = ({ current, flowName, isInitialized, wsDevtools,
209
209
  if (!isInitialized)
210
210
  return;
211
211
  if (isDisabled) {
212
- dispatch({ type: `${REDUCER_NAME.FLOWER_FLOW}/next`, payload: { flowName, disabled: true } });
212
+ dispatch({
213
+ type: `${REDUCER_NAME.FLOWER_FLOW}/next`,
214
+ payload: { flowName, disabled: true }
215
+ });
213
216
  // eslint-disable-next-line no-underscore-dangle, no-undef
214
217
  /* istanbul ignore next */
215
218
  if (wsDevtools &&
@@ -761,4 +764,73 @@ const createSliceWithFlower = (createSliceOptions) => {
761
764
  return slice;
762
765
  };
763
766
 
764
- export { Flower, FlowerAction, FlowerFlow, FlowerNavigate, FlowerNode, FlowerProvider, FlowerRoute, FlowerServer, FlowerStart, createSliceWithFlower, createStoreWithFlower, flowerReducers, makeSelectData, reducerFlower, useFlower, useFlowerNavigate };
767
+ const AbacContext = createContext({
768
+ can: () => false,
769
+ allowedActions: () => [],
770
+ loaded: false,
771
+ rawRules: null
772
+ });
773
+ const AbacProvider = ({ children, rules, rulesPath, fetchOptions, denyByDefault = true }) => {
774
+ const [loaded, setLoaded] = useState(isAbacInitialized());
775
+ const [localRawRules, setLocalRawRules] = useState(getRawRules());
776
+ useEffect(() => {
777
+ if (rules) {
778
+ initAbac(rules);
779
+ setLocalRawRules(rules);
780
+ setLoaded(true);
781
+ return;
782
+ }
783
+ if (!rulesPath) {
784
+ setLoaded(false);
785
+ setLocalRawRules(null);
786
+ return;
787
+ }
788
+ let mounted = true;
789
+ const init = async () => {
790
+ try {
791
+ const res = await fetch(rulesPath, fetchOptions);
792
+ if (!res.ok)
793
+ throw new Error(`HTTP ${res.status}`);
794
+ const json = (await res.json());
795
+ if (!mounted)
796
+ return;
797
+ initAbac(json);
798
+ setLocalRawRules(json);
799
+ setLoaded(true);
800
+ }
801
+ catch (err) {
802
+ console.error('ABAC: failed to load rules from', rulesPath, err);
803
+ setLocalRawRules(null);
804
+ setLoaded(false);
805
+ }
806
+ };
807
+ init();
808
+ return () => {
809
+ mounted = false;
810
+ };
811
+ }, [rules, rulesPath, fetchOptions]);
812
+ const can = useCallback((ctx) => {
813
+ if (!isAbacInitialized())
814
+ return !denyByDefault;
815
+ const subject = getSubject();
816
+ return (getAbacEngine()?.decide({ subject: subject ?? {}, ...ctx }) === 'Permit');
817
+ }, []);
818
+ const allowedActions = useCallback((resource, actions = ['read', 'create', 'update', 'delete']) => {
819
+ if (!isAbacInitialized())
820
+ return denyByDefault ? [] : actions.slice();
821
+ const subject = getSubject();
822
+ return getAbacEngine()?.allowedActions({ subject: subject ?? {}, resource, environment: undefined }, actions);
823
+ }, []);
824
+ const value = useMemo(() => ({
825
+ can,
826
+ allowedActions,
827
+ loaded,
828
+ rawRules: localRawRules
829
+ }), [loaded, localRawRules]);
830
+ return React.createElement(AbacContext.Provider, { value: value }, children);
831
+ };
832
+ function useAbac() {
833
+ return useContext(AbacContext);
834
+ }
835
+
836
+ export { AbacProvider, Flower, FlowerAction, FlowerFlow, FlowerNavigate, FlowerNode, FlowerProvider, FlowerRoute, FlowerServer, FlowerStart, createSliceWithFlower, createStoreWithFlower, flowerReducers, makeSelectData, reducerFlower, useAbac, useFlower, useFlowerNavigate };
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { RuleInput, AbacCtx, Resource, Action } from '@flowerforce/flower-core';
3
+ type AbacProviderProps = {
4
+ children: React.ReactNode;
5
+ rules?: RuleInput[];
6
+ rulesPath?: string;
7
+ fetchOptions?: RequestInit;
8
+ denyByDefault?: boolean;
9
+ };
10
+ type AbacContextValue = {
11
+ can: (args: Omit<AbacCtx, 'subject'>) => boolean;
12
+ allowedActions: (resource: Resource | undefined, actions?: Action[]) => Action[] | undefined;
13
+ loaded: boolean;
14
+ rawRules?: RuleInput[] | null;
15
+ };
16
+ export declare const AbacProvider: React.FC<AbacProviderProps>;
17
+ export declare function useAbac(): AbacContextValue;
18
+ export {};
@@ -0,0 +1 @@
1
+ export * from './abacProvider';
@@ -1,2 +1,2 @@
1
1
  import { UseInitDevtoolsProps } from './types';
2
- export declare const useInitDevtools: ({ devtoolState, setWsDevtools, flowName, }: UseInitDevtoolsProps) => void;
2
+ export declare const useInitDevtools: ({ devtoolState, setWsDevtools, flowName }: UseInitDevtoolsProps) => void;
@@ -2,5 +2,6 @@ export { Flower, FlowerAction, FlowerFlow, FlowerNode, FlowerRoute, FlowerNaviga
2
2
  export * from './provider';
3
3
  export { makeSelectData, reducerFlower, flowerReducers } from './features';
4
4
  export type * from './types';
5
+ export * from './abacProvider';
5
6
  export { createApi } from '@flowerforce/flower-react-store';
6
7
  export { HistoryContextProvider } from '@flowerforce/flower-react-history-context';
@@ -1,4 +1,4 @@
1
- import { PropsWithChildren } from "react";
1
+ import { PropsWithChildren } from 'react';
2
2
  export type FlowerInitalState = {
3
3
  startId?: string;
4
4
  current?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowerforce/flower-react",
3
- "version": "4.0.11-beta.1",
3
+ "version": "4.0.11-beta.2",
4
4
  "description": "FlowerJS components, hooks and utils for React.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,8 +26,8 @@
26
26
  "@reduxjs/toolkit": "^2.2.4",
27
27
  "reselect": "^5.1.0",
28
28
  "@testing-library/react": "15.0.7",
29
- "@testing-library/jest-dom": "6.6.3",
30
- "@flowerforce/flower-react-form": "4.0.11-beta.0",
29
+ "@testing-library/jest-dom": "6.8.0",
30
+ "@flowerforce/flower-react-form": "4.0.11-beta.1",
31
31
  "@testing-library/user-event": "14.6.1",
32
32
  "redux": "5.0.1"
33
33
  },
@@ -41,10 +41,10 @@
41
41
  "typescript": "^5.4.5"
42
42
  },
43
43
  "dependencies": {
44
- "@flowerforce/flower-core": "4.0.1-beta.9",
45
- "@flowerforce/flower-react-context": "4.0.1-beta.9",
46
- "@flowerforce/flower-react-store": "4.0.1-beta.9",
47
- "@flowerforce/flower-react-shared": "4.0.6-beta.5",
44
+ "@flowerforce/flower-core": "4.0.1-beta.10",
45
+ "@flowerforce/flower-react-context": "4.0.1-beta.10",
46
+ "@flowerforce/flower-react-store": "4.0.1-beta.10",
47
+ "@flowerforce/flower-react-shared": "4.0.6-beta.6",
48
48
  "@flowerforce/flower-react-history-context": "4.0.0",
49
49
  "lodash": "^4.17.21"
50
50
  },