@shield-acl/react
Version:
Sistema ACL (Access Control List) inteligente e granular para aplicações React
1,195 lines (1,181 loc) • 34.6 kB
JavaScript
// src/context.tsx
import {
createContext,
useContext,
useState,
useCallback,
useMemo
} from "react";
import { jsx } from "react/jsx-runtime";
var ACLContext = createContext(void 0);
function ACLProvider({
engine,
user: initialUser = null,
children
}) {
const [user, setUser] = useState(initialUser);
const can = useCallback(
(action, resource, context) => {
if (!user)
return false;
return engine.can(user, action, resource, context);
},
[engine, user]
);
const evaluate = useCallback(
(action, resource, context) => {
if (!user) {
return {
allowed: false,
reason: "No user authenticated"
};
}
return engine.evaluate(user, action, resource, context);
},
[engine, user]
);
const value = useMemo(
() => ({
engine,
user,
setUser,
can,
evaluate
}),
[engine, user, can, evaluate]
);
return /* @__PURE__ */ jsx(ACLContext.Provider, { value, children });
}
function useACLContext() {
const context = useContext(ACLContext);
if (!context) {
throw new Error("useACLContext must be used within an ACLProvider");
}
return context;
}
// src/hooks/useACL.ts
import { useCallback as useCallback2 } from "react";
function useACL() {
const { engine, user, setUser, can, evaluate } = useACLContext();
const cannot = useCallback2(
(action, resource, context) => {
return !can(action, resource, context);
},
[can]
);
const permissions = useCallback2(() => {
if (!user)
return [];
return engine.getUserPermissions(user);
}, [engine, user]);
const roles = useCallback2(() => {
if (!user)
return [];
return user.roles;
}, [user]);
const clearCache = useCallback2(() => {
engine.clearCache();
}, [engine]);
return {
// Estado
user,
setUser,
// Verificações
can,
cannot,
evaluate,
// Dados
permissions,
roles,
// Engine
engine,
// Utilidades
clearCache
};
}
// src/hooks/useCan.ts
import { useMemo as useMemo2 } from "react";
function useCan(action, resource, context) {
const { can, user } = useACLContext();
return useMemo2(() => {
return can(action, resource, context);
}, [can, action, resource, context, user]);
}
function useCannot(action, resource, context) {
const canResult = useCan(action, resource, context);
return !canResult;
}
// src/hooks/useCanAny.ts
import { useMemo as useMemo3 } from "react";
function useCanAny(permissions) {
const { can, user } = useACLContext();
return useMemo3(() => {
if (!permissions || permissions.length === 0) {
return false;
}
return permissions.some(
(permission) => can(permission.action, permission.resource, permission.context)
);
}, [can, permissions, user]);
}
function useCanAnyAction(actions) {
const permissions = useMemo3(
() => actions.map((action) => ({ action })),
[actions]
);
return useCanAny(permissions);
}
// src/hooks/useCanAll.ts
import { useMemo as useMemo4 } from "react";
function useCanAll(permissions) {
const { can, user } = useACLContext();
return useMemo4(() => {
if (!permissions || permissions.length === 0) {
return true;
}
return permissions.every(
(permission) => can(permission.action, permission.resource, permission.context)
);
}, [can, permissions, user]);
}
function useCanAllActions(actions) {
const permissions = useMemo4(
() => actions.map((action) => ({ action })),
[actions]
);
return useCanAll(permissions);
}
// src/hooks/usePermissionHelpers.ts
import { useCallback as useCallback3, useMemo as useMemo5 } from "react";
function usePermissionHelpers() {
const { can, user } = useACLContext();
const canRead = useCallback3(
(resource, context) => {
return can(`${resource}.read`, resource, context) || can("read", resource, context) || can(`${resource}.view`, resource, context) || can("view", resource, context);
},
[can]
);
const canCreate = useCallback3(
(resource, context) => {
return can(`${resource}.create`, resource, context) || can("create", resource, context) || can(`${resource}.add`, resource, context) || can("add", resource, context);
},
[can]
);
const canUpdate = useCallback3(
(resource, context) => {
return can(`${resource}.update`, resource, context) || can("update", resource, context) || can(`${resource}.edit`, resource, context) || can("edit", resource, context) || can(`${resource}.modify`, resource, context) || can("modify", resource, context);
},
[can]
);
const canDelete = useCallback3(
(resource, context) => {
return can(`${resource}.delete`, resource, context) || can("delete", resource, context) || can(`${resource}.remove`, resource, context) || can("remove", resource, context);
},
[can]
);
const canManage = useCallback3(
(resource, context) => {
return can(`${resource}.manage`, resource, context) || can("manage", resource, context) || can(`${resource}.*`, resource, context) || can("*", resource, context) || // Se tem todas as permissões CRUD individuais, também pode gerenciar
canRead(resource, context) && canCreate(resource, context) && canUpdate(resource, context) && canDelete(resource, context);
},
[can, canRead, canCreate, canUpdate, canDelete]
);
const canList = useCallback3(
(resource, context) => {
return can(`${resource}.list`, resource, context) || can("list", resource, context) || canRead(resource, context);
},
[can, canRead]
);
const canView = useCallback3(
(resource, context) => {
return can(`${resource}.view`, resource, context) || can("view", resource, context) || canRead(resource, context);
},
[can, canRead]
);
const canEdit = useCallback3(
(resource, context) => {
return can(`${resource}.edit`, resource, context) || can("edit", resource, context) || canUpdate(resource, context);
},
[can, canUpdate]
);
const canModify = useCallback3(
(resource, context) => {
return can(`${resource}.modify`, resource, context) || can("modify", resource, context) || canUpdate(resource, context);
},
[can, canUpdate]
);
const canAccess = useCallback3(
(resource, context) => {
return can(`${resource}.access`, resource, context) || can("access", resource, context) || canRead(resource, context) || canCreate(resource, context) || canUpdate(resource, context) || canDelete(resource, context);
},
[can, canRead, canCreate, canUpdate, canDelete]
);
return useMemo5(
() => ({
canRead,
canCreate,
canUpdate,
canDelete,
canManage,
canList,
canView,
canEdit,
canModify,
canAccess
}),
[
canRead,
canCreate,
canUpdate,
canDelete,
canManage,
canList,
canView,
canEdit,
canModify,
canAccess,
user
]
);
}
function useResourcePermissions(resource) {
const helpers = usePermissionHelpers();
return useMemo5(
() => ({
canRead: (context) => helpers.canRead(resource, context),
canCreate: (context) => helpers.canCreate(resource, context),
canUpdate: (context) => helpers.canUpdate(resource, context),
canDelete: (context) => helpers.canDelete(resource, context),
canManage: (context) => helpers.canManage(resource, context),
canList: (context) => helpers.canList(resource, context),
canView: (context) => helpers.canView(resource, context),
canEdit: (context) => helpers.canEdit(resource, context),
canModify: (context) => helpers.canModify(resource, context),
canAccess: (context) => helpers.canAccess(resource, context)
}),
[helpers, resource]
);
}
// src/hooks/usePermissions.ts
import { useMemo as useMemo6 } from "react";
function usePermissions() {
const { engine, user } = useACLContext();
return useMemo6(() => {
if (!user) {
return {
all: [],
direct: [],
inherited: [],
byRole: {},
denials: [],
allowances: [],
actions: [],
resources: [],
count: 0
};
}
const allPermissions = engine.getUserPermissions(user);
const directPermissions = user.permissions || [];
const inheritedPermissions = allPermissions.filter(
(perm) => !directPermissions.includes(perm)
);
const permissionsByRole = {};
user.roles.forEach((roleName) => {
const role = engine.getRole(roleName);
if (role) {
permissionsByRole[roleName] = role.permissions;
}
});
const denials = allPermissions.filter((perm) => perm.deny === true);
const allowances = allPermissions.filter((perm) => perm.deny !== true);
const uniqueActions = /* @__PURE__ */ new Set();
allPermissions.forEach((perm) => {
if (Array.isArray(perm.action)) {
perm.action.forEach((action) => uniqueActions.add(action));
} else {
uniqueActions.add(perm.action);
}
});
const uniqueResources = /* @__PURE__ */ new Set();
allPermissions.forEach((perm) => {
if (perm.resource) {
if (Array.isArray(perm.resource)) {
perm.resource.forEach((resource) => uniqueResources.add(resource));
} else {
uniqueResources.add(perm.resource);
}
}
});
return {
all: allPermissions,
direct: directPermissions,
inherited: inheritedPermissions,
byRole: permissionsByRole,
denials,
allowances,
actions: Array.from(uniqueActions).sort(),
resources: Array.from(uniqueResources).sort(),
count: allPermissions.length
};
}, [engine, user]);
}
function usePermissionsList() {
const { all } = usePermissions();
return all;
}
function useAvailableActions() {
const { actions } = usePermissions();
return actions;
}
function useAvailableResources() {
const { resources } = usePermissions();
return resources;
}
// src/hooks/useCanMultiple.ts
import { useMemo as useMemo7 } from "react";
function useCanMultiple(permissions, keys) {
const { can, evaluate, user } = useACLContext();
return useMemo7(() => {
const results = {};
const details = {};
const allowed = [];
const denied = [];
permissions.forEach((permission, index) => {
const key = (keys == null ? void 0 : keys[index]) || `${permission.action}${permission.resource ? `.${permission.resource}` : ""}`;
const evaluationResult = evaluate(
permission.action,
permission.resource,
permission.context
);
const isAllowed = evaluationResult.allowed;
results[key] = isAllowed;
details[key] = evaluationResult;
if (isAllowed) {
allowed.push(permission);
} else {
denied.push(permission);
}
});
const allowedCount = allowed.length;
const deniedCount = denied.length;
const allAllowed = allowedCount === permissions.length;
const anyAllowed = allowedCount > 0;
const noneAllowed = allowedCount === 0;
return {
allAllowed,
anyAllowed,
noneAllowed,
allowedCount,
deniedCount,
results,
details,
allowed,
denied
};
}, [can, evaluate, permissions, keys, user]);
}
function useCanMap(permissions, keys) {
const { results } = useCanMultiple(permissions, keys);
return results;
}
function useCanArray(permissions) {
const { can, user } = useACLContext();
return useMemo7(() => {
return permissions.map(
(permission) => can(permission.action, permission.resource, permission.context)
);
}, [can, permissions, user]);
}
// src/hooks/useResourceACL.ts
import { useMemo as useMemo8, useCallback as useCallback4 } from "react";
function useResourceACL(resource) {
const { can: canCheck, user } = useACLContext();
const helpers = usePermissionHelpers();
const canRead = useCallback4(
(context) => helpers.canRead(resource, context),
[helpers, resource]
);
const canCreate = useCallback4(
(context) => helpers.canCreate(resource, context),
[helpers, resource]
);
const canUpdate = useCallback4(
(context) => helpers.canUpdate(resource, context),
[helpers, resource]
);
const canDelete = useCallback4(
(context) => helpers.canDelete(resource, context),
[helpers, resource]
);
const canManage = useCallback4(
(context) => helpers.canManage(resource, context),
[helpers, resource]
);
const canList = useCallback4(
(context) => helpers.canList(resource, context),
[helpers, resource]
);
const canView = useCallback4(
(context) => helpers.canView(resource, context),
[helpers, resource]
);
const canEdit = useCallback4(
(context) => helpers.canEdit(resource, context),
[helpers, resource]
);
const canModify = useCallback4(
(context) => helpers.canModify(resource, context),
[helpers, resource]
);
const canAccess = useCallback4(
(context) => helpers.canAccess(resource, context),
[helpers, resource]
);
const can = useCallback4(
(action, context) => canCheck(action, resource, context),
[canCheck, resource]
);
const cannot = useCallback4(
(action, context) => !canCheck(action, resource, context),
[canCheck, resource]
);
const crudPermissions = useCanMultiple(
[
{ action: `${resource}.read`, resource },
{ action: `${resource}.create`, resource },
{ action: `${resource}.update`, resource },
{ action: `${resource}.delete`, resource },
{ action: `${resource}.manage`, resource }
],
["read", "create", "update", "delete", "manage"]
);
const hasAnyPermission = crudPermissions.anyAllowed;
const hasAllCrudPermissions = crudPermissions.results.read && crudPermissions.results.create && crudPermissions.results.update && crudPermissions.results.delete;
const hasReadOnlyAccess = crudPermissions.results.read && !crudPermissions.results.create && !crudPermissions.results.update && !crudPermissions.results.delete;
const hasWriteAccess = crudPermissions.results.create || crudPermissions.results.update || crudPermissions.results.delete;
const hasFullAccess = crudPermissions.results.manage || hasAllCrudPermissions;
const availableActions = useMemo8(() => {
const actions = [];
const standardActions = [
"read",
"list",
"view",
"create",
"add",
"new",
"update",
"edit",
"modify",
"delete",
"remove",
"manage",
"*"
];
standardActions.forEach((action) => {
if (canCheck(`${resource}.${action}`, resource) || canCheck(action, resource)) {
actions.push(`${resource}.${action}`);
}
});
return [...new Set(actions)].sort();
}, [canCheck, resource, user]);
return useMemo8(
() => ({
resource,
// Funções CRUD
canRead,
canCreate,
canUpdate,
canDelete,
canManage,
// Aliases
canList,
canView,
canEdit,
canModify,
canAccess,
// Verificação customizada
can,
cannot,
// Status de permissões
permissions: {
read: crudPermissions.results.read,
create: crudPermissions.results.create,
update: crudPermissions.results.update,
delete: crudPermissions.results.delete,
manage: crudPermissions.results.manage
},
// Helpers de status
hasAnyPermission,
hasAllCrudPermissions,
hasReadOnlyAccess,
hasWriteAccess,
hasFullAccess,
// Ações disponíveis
availableActions
}),
[
resource,
canRead,
canCreate,
canUpdate,
canDelete,
canManage,
canList,
canView,
canEdit,
canModify,
canAccess,
can,
cannot,
crudPermissions,
hasAnyPermission,
hasAllCrudPermissions,
hasReadOnlyAccess,
hasWriteAccess,
hasFullAccess,
availableActions
]
);
}
function useMultipleResourceACL(resources) {
const acls = resources.map((resource) => ({
resource,
acl: useResourceACL(resource)
}));
return useMemo8(() => {
const map = {};
acls.forEach(({ resource, acl }) => {
map[resource] = acl;
});
return map;
}, [acls]);
}
// src/hooks/usePermissionState.ts
import { useState as useState2, useEffect, useCallback as useCallback5 } from "react";
function usePermissionState(action, resource, context, options = {}) {
const { can, user } = useACLContext();
const {
refreshInterval = 0,
onChange,
enabled = true,
initialValue = false
} = options;
const [state, setState] = useState2({
allowed: initialValue,
loading: true,
error: null,
lastChecked: 0,
refresh: () => {
}
});
const checkPermission = useCallback5(() => {
if (!enabled)
return;
try {
const newValue = can(action, resource, context);
const now = Date.now();
setState((prev) => {
if (prev.allowed !== newValue && !prev.loading && onChange) {
onChange(newValue, prev.allowed);
}
return {
allowed: newValue,
loading: false,
error: null,
lastChecked: now,
refresh: prev.refresh
};
});
} catch (error) {
setState((prev) => ({
...prev,
loading: false,
error,
lastChecked: Date.now()
}));
}
}, [can, action, resource, context, enabled, onChange]);
useEffect(() => {
checkPermission();
}, [checkPermission, user]);
useEffect(() => {
if (refreshInterval > 0 && enabled) {
const interval = setInterval(checkPermission, refreshInterval);
return () => clearInterval(interval);
}
}, [refreshInterval, checkPermission, enabled]);
useEffect(() => {
setState((prev) => ({
...prev,
refresh: checkPermission
}));
}, [checkPermission]);
return state;
}
function useMultiplePermissionStates(permissions, options = {}) {
const { can, user } = useACLContext();
const { refreshInterval = 0, enabled = true, initialValue = false } = options;
const [states, setStates] = useState2(() => {
const initial = {};
permissions.forEach(({ key }) => {
initial[key] = {
allowed: initialValue,
loading: true,
error: null,
lastChecked: 0,
refresh: () => {
}
};
});
return initial;
});
const checkAllPermissions = useCallback5(() => {
if (!enabled)
return;
const newStates = {};
permissions.forEach(({ action, resource, context, key }) => {
try {
const allowed = can(action, resource, context);
newStates[key] = {
allowed,
loading: false,
error: null,
lastChecked: Date.now(),
refresh: () => {
}
};
} catch (error) {
newStates[key] = {
allowed: false,
loading: false,
error,
lastChecked: Date.now(),
refresh: () => {
}
};
}
});
setStates((prev) => {
Object.keys(newStates).forEach((key) => {
if (prev[key]) {
newStates[key].refresh = prev[key].refresh;
}
});
return newStates;
});
}, [can, permissions, enabled]);
useEffect(() => {
checkAllPermissions();
}, [checkAllPermissions, user]);
useEffect(() => {
if (refreshInterval > 0 && enabled) {
const interval = setInterval(checkAllPermissions, refreshInterval);
return () => clearInterval(interval);
}
}, [refreshInterval, checkAllPermissions, enabled]);
useEffect(() => {
setStates((prev) => {
const updated = { ...prev };
Object.keys(updated).forEach((key) => {
updated[key] = {
...updated[key],
refresh: checkAllPermissions
};
});
return updated;
});
}, [checkAllPermissions]);
return states;
}
function usePermissionBoolean(action, resource, context) {
const state = usePermissionState(action, resource, context);
return [state.allowed, state.loading, state.refresh];
}
// src/hooks/useConditionalPermissions.ts
import { useMemo as useMemo10, useCallback as useCallback6 } from "react";
function useConditionalPermissions(permissions) {
const { can, user } = useACLContext();
return useMemo10(() => {
return permissions.map((perm) => {
var _a;
const conditionMet = typeof perm.condition === "function" ? perm.condition() : perm.condition;
if (!conditionMet) {
return {
allowed: (_a = perm.defaultValue) != null ? _a : true,
// Por padrão, permite se condição não for atendida
conditionMet: false,
skipped: true,
reason: "Condition not met - permission check skipped"
};
}
const allowed = can(perm.action, perm.resource, perm.context);
return {
allowed,
conditionMet: true,
skipped: false,
reason: allowed ? "Condition met and permission granted" : "Condition met but permission denied"
};
});
}, [permissions, can, user]);
}
function useConditionalPermission(condition, action, resource, context, defaultValue) {
const results = useConditionalPermissions([
{ condition, action, resource, context, defaultValue }
]);
return results[0];
}
function useConditionalChecker() {
const { can } = useACLContext();
return useCallback6(
(condition, action, resource, context, defaultValue = true) => {
const conditionMet = typeof condition === "function" ? condition() : condition;
if (!conditionMet) {
return {
allowed: defaultValue,
conditionMet: false,
skipped: true,
reason: "Condition not met - permission check skipped"
};
}
const allowed = can(action, resource, context);
return {
allowed,
conditionMet: true,
skipped: false,
reason: allowed ? "Condition met and permission granted" : "Condition met but permission denied"
};
},
[can]
);
}
function useCombinedConditionalPermission(mode, conditions, action, resource, context, defaultValue = true) {
const combinedCondition = useMemo10(() => {
const evaluatedConditions = conditions.map(
(c) => typeof c === "function" ? c() : c
);
return mode === "all" ? evaluatedConditions.every((c) => c) : evaluatedConditions.some((c) => c);
}, [mode, conditions]);
return useConditionalPermission(
combinedCondition,
action,
resource,
context,
defaultValue
);
}
// src/hooks/useEvaluate.ts
import { useCallback as useCallback7, useMemo as useMemo11 } from "react";
function useEvaluate() {
const { evaluate } = useACLContext();
return evaluate;
}
function useEvaluationResult(action, resource, context) {
const { evaluate, user } = useACLContext();
return useMemo11(() => {
return evaluate(action, resource, context);
}, [evaluate, action, resource, context, user]);
}
function usePermissionAnalysis() {
const { evaluate, user } = useACLContext();
const explainDenial = useCallback7(
(action, resource, context) => {
const result = evaluate(action, resource, context);
if (result.allowed) {
return "Permiss\xE3o concedida";
}
return result.reason || "Permiss\xE3o negada por raz\xE3o desconhecida";
},
[evaluate]
);
const findMatchingRule = useCallback7(
(action, resource, context) => {
const result = evaluate(action, resource, context);
return result.matchedRule || null;
},
[evaluate]
);
const testPermission = useCallback7(
(action, resource, contexts) => {
if (!contexts || contexts.length === 0) {
const result = evaluate(action, resource);
return [{ context: void 0, ...result }];
}
return contexts.map((context) => ({
context,
...evaluate(action, resource, context)
}));
},
[evaluate]
);
return useMemo11(
() => ({
explainDenial,
findMatchingRule,
testPermission
}),
[explainDenial, findMatchingRule, testPermission, user]
);
}
// src/hooks/useRoleHierarchy.ts
import { useMemo as useMemo12, useCallback as useCallback8 } from "react";
function useRoleHierarchy() {
const { engine, user } = useACLContext();
const { allRoles, directRoles, inheritedRoles, inheritanceMap } = useMemo12(() => {
if (!user) {
return {
allRoles: [],
directRoles: [],
inheritedRoles: [],
inheritanceMap: {}
};
}
const direct = user.roles;
const all = new Set(direct);
const inheritance = {};
const collectInheritedRoles = (roleName, visited = /* @__PURE__ */ new Set()) => {
if (visited.has(roleName))
return;
visited.add(roleName);
const role = engine.getRole(roleName);
if (!role)
return;
inheritance[roleName] = role.inherits || [];
if (role.inherits) {
role.inherits.forEach((inheritedRole) => {
all.add(inheritedRole);
collectInheritedRoles(inheritedRole, visited);
});
}
};
direct.forEach((roleName) => collectInheritedRoles(roleName));
const allArray = Array.from(all);
const inherited = allArray.filter((role) => !direct.includes(role));
return {
allRoles: allArray,
directRoles: direct,
inheritedRoles: inherited,
inheritanceMap: inheritance
};
}, [engine, user]);
const hierarchyTree = useMemo12(() => {
if (!user)
return [];
const buildNode = (roleName, direct) => {
var _a;
const role = engine.getRole(roleName);
const children = ((_a = role == null ? void 0 : role.inherits) == null ? void 0 : _a.map((child) => buildNode(child, false))) || [];
return {
name: roleName,
direct,
children
};
};
return directRoles.map((roleName) => buildNode(roleName, true));
}, [engine, user, directRoles]);
const hasRole = useCallback8(
(roleName) => {
return allRoles.includes(roleName);
},
[allRoles]
);
const hasAnyRole = useCallback8(
(roleNames) => {
return roleNames.some((role) => allRoles.includes(role));
},
[allRoles]
);
const hasAllRoles = useCallback8(
(roleNames) => {
return roleNames.every((role) => allRoles.includes(role));
},
[allRoles]
);
const getRoleDetails = useCallback8(
(roleName) => {
return engine.getRole(roleName);
},
[engine]
);
return {
allRoles,
directRoles,
inheritedRoles,
inheritanceMap,
hierarchyTree,
hasRole,
hasAnyRole,
hasAllRoles,
getRoleDetails
};
}
function useHasRole(roleNames, mode = "any") {
const { hasRole, hasAnyRole, hasAllRoles } = useRoleHierarchy();
return useMemo12(() => {
const roles = Array.isArray(roleNames) ? roleNames : [roleNames];
if (roles.length === 1) {
return hasRole(roles[0]);
}
return mode === "all" ? hasAllRoles(roles) : hasAnyRole(roles);
}, [roleNames, mode, hasRole, hasAnyRole, hasAllRoles]);
}
function useRoleInfo(roleName) {
const { getRoleDetails } = useRoleHierarchy();
return useMemo12(() => {
return getRoleDetails(roleName);
}, [getRoleDetails, roleName]);
}
// src/hooks/usePermissionChange.ts
import { useEffect as useEffect2, useRef, useCallback as useCallback9 } from "react";
function usePermissionChange(action, resource, callback) {
const { can, user } = useACLContext();
const previousValueRef = useRef(null);
const previousUserRef = useRef(user);
useEffect2(() => {
var _a;
const currentValue = can(action, resource);
if (previousValueRef.current === null) {
previousValueRef.current = currentValue;
previousUserRef.current = user;
return;
}
const hasPermissionChanged = currentValue !== previousValueRef.current;
const hasUserChanged = (user == null ? void 0 : user.id) !== ((_a = previousUserRef.current) == null ? void 0 : _a.id);
if (hasPermissionChanged || hasUserChanged) {
callback(currentValue, previousValueRef.current, {
action,
resource,
user
});
previousValueRef.current = currentValue;
previousUserRef.current = user;
}
}, [can, user, action, resource, callback]);
}
function useMultiplePermissionChange(permissions, callback) {
const { can, user } = useACLContext();
const previousValuesRef = useRef(/* @__PURE__ */ new Map());
const previousUserRef = useRef(user);
useEffect2(() => {
var _a;
const changes = [];
let hasAnyChange = false;
const hasUserChanged = (user == null ? void 0 : user.id) !== ((_a = previousUserRef.current) == null ? void 0 : _a.id);
permissions.forEach(({ action, resource }) => {
const key = `${action}:${resource || "none"}`;
const currentValue = can(action, resource);
const previousValue = previousValuesRef.current.get(key);
if (previousValue === void 0 || hasUserChanged) {
previousValuesRef.current.set(key, currentValue);
if (previousValue !== void 0) {
hasAnyChange = true;
changes.push({
action,
resource,
newValue: currentValue,
previousValue
});
}
} else if (currentValue !== previousValue) {
hasAnyChange = true;
previousValuesRef.current.set(key, currentValue);
changes.push({
action,
resource,
newValue: currentValue,
previousValue
});
}
});
if (hasAnyChange) {
callback(changes);
}
previousUserRef.current = user;
}, [can, user, permissions, callback]);
}
function usePermissionEffect(action, resource, onGain, onLose) {
usePermissionChange(action, resource, (newValue, previousValue) => {
if (newValue && !previousValue && onGain) {
onGain();
} else if (!newValue && previousValue && onLose) {
onLose();
}
});
}
function usePermissionChangeDetector() {
const { can } = useACLContext();
return useCallback9(
(action, resource) => {
const initialValue = can(action, resource);
return {
action,
resource,
previousValue: initialValue,
get currentValue() {
return can(action, resource);
},
hasChanged() {
return this.currentValue !== this.previousValue;
}
};
},
[can]
);
}
// src/components.tsx
import { cloneElement, isValidElement } from "react";
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
function Can({
action,
resource,
context,
children,
fallback = null,
passThrough = false
}) {
const allowed = useCan(action, resource, context);
if (allowed) {
return /* @__PURE__ */ jsx2(Fragment, { children });
}
if (passThrough && isValidElement(children)) {
return cloneElement(children, {
disabled: true,
"data-permission-denied": true,
"aria-disabled": true
});
}
return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
}
function Cannot({
action,
resource,
context,
children,
passThrough = false
}) {
const allowed = useCan(action, resource, context);
if (allowed && !passThrough) {
return null;
}
if (allowed && passThrough && isValidElement(children)) {
return cloneElement(children, {
disabled: false,
"data-permission-granted": true,
"aria-disabled": false
});
}
return /* @__PURE__ */ jsx2(Fragment, { children });
}
function IfCan({
action,
resource,
context,
yes,
no = null
}) {
const allowed = useCan(action, resource, context);
return /* @__PURE__ */ jsx2(Fragment, { children: allowed ? yes : no });
}
function CanAny({
permissions,
children,
fallback = null
}) {
const canAny = permissions.some(
(perm) => useCan(perm.action, perm.resource, perm.context)
);
return /* @__PURE__ */ jsx2(Fragment, { children: canAny ? children : fallback });
}
function CanAll({
permissions,
children,
fallback = null
}) {
const canAll = permissions.every(
(perm) => useCan(perm.action, perm.resource, perm.context)
);
return /* @__PURE__ */ jsx2(Fragment, { children: canAll ? children : fallback });
}
function withCan(Component, action, resource, options) {
return function WithCanComponent(props) {
var _a;
const allowed = useCan(action, resource, props.context);
if (!allowed) {
(_a = options == null ? void 0 : options.onDenied) == null ? void 0 : _a.call(options);
if (options == null ? void 0 : options.fallback) {
const FallbackComponent = options.fallback;
return /* @__PURE__ */ jsx2(FallbackComponent, { ...props });
}
if ((options == null ? void 0 : options.redirectTo) && typeof window !== "undefined") {
window.location.href = options.redirectTo;
}
return null;
}
return /* @__PURE__ */ jsx2(Component, { ...props });
};
}
function PermissionClass({
action,
resource,
context,
className,
deniedClassName = "",
children
}) {
const allowed = useCan(action, resource, context);
if (!isValidElement(children)) {
return null;
}
const appliedClassName = allowed ? className : deniedClassName;
const existingClassName = children.props.className || "";
return cloneElement(children, {
className: `${existingClassName} ${appliedClassName}`.trim(),
"data-permission": allowed ? "granted" : "denied"
});
}
export {
ACLProvider,
Can,
CanAll,
CanAny,
Cannot,
IfCan,
PermissionClass,
useACL,
useACLContext,
useAvailableActions,
useAvailableResources,
useCan,
useCanAll,
useCanAllActions,
useCanAny,
useCanAnyAction,
useCanArray,
useCanMap,
useCanMultiple,
useCannot,
useCombinedConditionalPermission,
useConditionalChecker,
useConditionalPermission,
useConditionalPermissions,
useEvaluate,
useEvaluationResult,
useHasRole,
useMultiplePermissionChange,
useMultiplePermissionStates,
useMultipleResourceACL,
usePermissionAnalysis,
usePermissionBoolean,
usePermissionChange,
usePermissionChangeDetector,
usePermissionEffect,
usePermissionHelpers,
usePermissionState,
usePermissions,
usePermissionsList,
useResourceACL,
useResourcePermissions,
useRoleHierarchy,
useRoleInfo,
withCan
};
//# sourceMappingURL=index.mjs.map