@beanreact/permission
Version:
Easy to process permission of Component
655 lines (521 loc) • 18.9 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var React = require('react');
var React__default = _interopDefault(React);
function _typeof(obj) {
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _possibleConstructorReturn(self, call) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
function _superPropBase(object, property) {
while (!Object.prototype.hasOwnProperty.call(object, property)) {
object = _getPrototypeOf(object);
if (object === null) break;
}
return object;
}
function _get(target, property, receiver) {
if (typeof Reflect !== "undefined" && Reflect.get) {
_get = Reflect.get;
} else {
_get = function _get(target, property, receiver) {
var base = _superPropBase(target, property);
if (!base) return;
var desc = Object.getOwnPropertyDescriptor(base, property);
if (desc.get) {
return desc.get.call(receiver);
}
return desc.value;
};
}
return _get(target, property, receiver || target);
}
// 用户权限状态
var UserStatus = {
UNSET: 'unset',
PENDING: 'pending',
ERROR: 'error',
DONE: 'done'
}; // 组件的校验状态
var CheckStatus = {
AUTHORIZED: 'authorized',
DENIED: 'denied'
};
function SetPermissionException() {
var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
this.title = 'set permission error: ';
this.message = message;
this.toString = function () {
return this.title + this.message;
};
}
/**
* @desc 封装了一些项目常用方法.
*/
// 内部函数, 用于判断对象类型
function _getClass(object) {
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
}
function isArray(obj) {
return _getClass(obj).toLowerCase() === 'array';
}
function isString(obj) {
return _getClass(obj).toLowerCase() === 'string';
}
function isObject(obj) {
return _getClass(obj).toLowerCase() === 'object';
}
function isNumber(obj) {
return _getClass(obj).toLowerCase() === 'number';
}
/**
* @desc 判断参数是否为空, 包括null, undefined, [], '', {}
* @param {object} obj 需判断的对象
*/
function isEmpty(obj) {
var empty = false;
if (obj === null || obj === undefined) {
// null and undefined
empty = true;
} else if ((isArray(obj) || isString(obj)) && obj.length === 0) {
empty = true;
} else if (isObject(obj)) {
var hasProp = false;
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
hasProp = true;
break;
}
}
if (!hasProp) {
empty = true;
}
} else if (isNumber(obj) && isNaN(obj)) {
empty = true;
}
return empty;
}
/**
* @desc 判断参数是否不为空
*/
function isNotEmpty(obj) {
return !isEmpty(obj);
}
function isPromise(obj) {
return _typeof(obj) === 'object' && obj.then && obj.catch && obj.finally;
}
function trim(value) {
if (isString(value)) {
return value.trim();
}
return value;
}
/*
$$typeof: Symbol(react.element)
key: "1"
props: {onClick: ƒ, children: Array(2)}
ref: null
type: "div"
_owner: FiberNode {tag: 1, key: null, elementType: ƒ, type: ƒ, stateNode: _class, …}
_store: {validated: false}
_self: null
_source: null
*/
function isReactDOMElement(node) {
return node && typeof node.type === 'string';
}
/*
$$typeof: Symbol(react.element)
key: "1"
props: {name: "stephen", children: Array(3)}
ref: null
type: ƒ MyComponent(props)
_owner: FiberNode {tag: 1, key: null, elementType: ƒ, type: ƒ, stateNode: _class, …}
_store: {validated: false}
_self: null
_source: null
*/
function isReactComponentElement(node) {
return node && typeof node.type === 'function';
}
/*
$$typeof: Symbol(react.element)
key: null
props: {name: "stephen", onClick: ƒ, children: Array(3)}
ref: null
type: ƒ _class()
_owner: FiberNode {tag: 1, key: null, elementType: ƒ, type: ƒ, stateNode: _class, …}
_store: {validated: false}
_self: null
_source: null
*/
function isReactClass(node) {
return isReactComponentElement(node) && node.type.name === '_class';
}
/*
$$typeof: Symbol(react.element)
key: "1"
props: {children: Array(3)}
ref: null
type: Symbol(react.fragment)
_owner: FiberNode {tag: 1, key: null, elementType: ƒ, type: ƒ, stateNode: _class, …}
_store: {validated: false}
_self: null
_source: null
*/
function isReactFragment(node) {
return node && node.type === Symbol.for('react.fragment');
}
/*
$$typeof: Symbol(react.portal)
children: (3) [{…}, {…}, {…}]
containerInfo: div#app2
implementation: null
key: null
*/
function isReactPortal(node) {
return node && _typeof(node.containerInfo) === 'object';
}
function formatPermissionValue(value) {
var permissions = [];
if (isArray(value)) {
permissions = value;
} else if (isNumber(value)) {
permissions.push(value);
} else if (isString(value)) {
if (value.includes(',')) {
// TODO: 移除开头和结尾的 ","
permissions = value.split(',');
} else {
permissions.push(value);
}
}
return permissions;
}
var _userStatus = UserStatus.UNSET;
var _userPromise;
var _userPermissions;
var _updateComponentQueue = [];
var _defaults = {
onDenied: null,
transformData: null,
comparePermission: function comparePermission() {
var requiredPermissions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var userPermissions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var _loop = function _loop(i) {
var requiredPermission = requiredPermissions[i]; // Compare permission
var allow = userPermissions.some(function (userPermission) {
return trim(requiredPermission) == trim(userPermission);
});
if (!allow) {
return {
v: false
};
}
};
for (var i = 0; i < requiredPermissions.length; i++) {
var _ret = _loop(i);
if (_typeof(_ret) === "object") return _ret.v;
}
return true;
}
}; // 生成一个key
function generateKey(element) {
var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var key = '';
if (isReactDOMElement(element)) {
key = element.type;
} else if (isReactComponentElement(element)) {
key = element.type.name;
} // TODO: key有点多余, 先这样吧
return "permission__".concat(index);
}
function checkPermission(permissions, userPermissions) {
// 必要的权限
if (isEmpty(permissions)) {
return true;
} // 用户的权限
if (isEmpty(userPermissions)) {
return false;
}
var requiredPermissions = formatPermissionValue(permissions);
return _defaults.comparePermission(requiredPermissions, userPermissions);
} // 接收到用户的权限数据后进行处理
function handleUserPermissions(data) {
var _permissions;
if (_defaults.transformData) {
_permissions = _defaults.transformData(data);
} else {
_permissions = data;
} // 加载完后 _userPermissions 由 Promise 转为真正的权限列表
return formatPermissionValue(_permissions);
} // TODO: index 默认值为0可能有问题
function handleDeniedHook(permission, element, onDenied) {
var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
// 用户权限还未加载完成, 默认隐藏所有元素不显示, 不执行 onDenied 方法
if (_userStatus !== UserStatus.DONE) {
return;
} // TODO: 这里可能会有性能问题, 如果 onDenied 方法直接执行页面跳转, 可能来不及清除内存占用.
var newElement = onDenied && onDenied(permission, element, index);
if (React__default.isValidElement(newElement)) {
return newElement;
}
return;
} // 递归遍历 Virtual Tree
function filterPermission(element, userPermissions, onDenied, index) {
if (!element) {
return;
} // 处理 DOMElement, ComponentElement, ClassElement
if (isReactDOMElement(element) || isReactComponentElement(element) || isReactClass(element)) {
var permission = element.props['data-permission'] || element.props['data-permissions'] || element.props['permission'] || element.props['permissions']; // TODO: 返回缺失的权限数组
if (checkPermission(permission, userPermissions)) {
var newChildren = [];
var children = element.props.children;
if (children) {
React.Children.forEach(children, function (child, _index) {
var checkedChild = filterPermission(child, userPermissions, onDenied, _index);
checkedChild && newChildren.push(checkedChild);
});
} // children 为数组时 react会检测 key 是否为空, 为空会报警告.
if (newChildren.length === 0) {
newChildren = null; // 确保 newChildren 和 children 数据类型保持一致(object 或 array), 如果类型被修改,
// 会导致 react 以为子组件被替换了, 将卸载已 render 的子组件(componentWillUnmount)并且重新渲染新的子组件(componentDidMount).
} else if (newChildren.length === 1 && children.length === 1) {
newChildren = newChildren[0];
} // cloneElement(element, props, children), 第二个, 第三个参数用于覆盖拷贝的 element 属性, 如果不输入默认使用原 element 的.
// key and ref from the original element will be preserved. 第二个参数可以覆盖 key 和 ref.
var newElement = React__default.cloneElement(element, {
key: element.key || generateKey(element, index)
}, newChildren); // 返回权限过滤后的元素.
return newElement;
}
return handleDeniedHook(permission, element, onDenied, index); // 处理 Array
} else if (isArray(element) || isReactFragment(element) || isReactPortal(element)) {
var _element$props;
var _children = (element === null || element === void 0 ? void 0 : (_element$props = element.props) === null || _element$props === void 0 ? void 0 : _element$props.children) || element.children || element;
return React.Children.map(_children, function (el, _index) {
return filterPermission(el, userPermissions, onDenied, _index);
});
} // 其他元素类型暂不处理
return element;
}
function updateQueue() {
var componentQueue = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var component;
while (component = componentQueue.shift()) {
component.forceUpdate();
}
}
function permission(permissions, onDenied) {
var _permissions;
var _onDenied; // 当前组件无必要权限, 只校验子组件.
if (typeof permissions === 'function' && arguments.length === 1) {
_onDenied = permissions;
} else {
_permissions = permissions;
_onDenied = onDenied;
} // 为 null 表示用户不想使用回调, 包括默认的 onDenied
if (!_onDenied && _onDenied !== null) {
_onDenied = _defaults.onDenied;
}
return function (WrappedComponent) {
return (
/*#__PURE__*/
function (_WrappedComponent) {
_inherits(_class, _WrappedComponent);
function _class() {
_classCallCheck(this, _class);
return _possibleConstructorReturn(this, _getPrototypeOf(_class).apply(this, arguments));
}
_createClass(_class, [{
key: "componentDidMount",
value: function componentDidMount() {
_get(_getPrototypeOf(_class.prototype), "componentDidMount", this) && _get(_getPrototypeOf(_class.prototype), "componentDidMount", this).call(this); // TODO: 目前只能延迟刷新 Class Component, 考虑纯函数组件: Hooks 和 stateless Component
// 用户权限未加载完成时将组件加入刷新队列, 待用户权限加载后重新校验.
if (_userStatus !== UserStatus.DONE) {
_updateComponentQueue.push(this);
}
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
var _this = this;
// 组件被销毁时, 从更新队列中移除
var index = _updateComponentQueue.findIndex(function (component) {
return component === _this;
});
if (index !== -1) {
_updateComponentQueue.splice(index, 1);
}
_get(_getPrototypeOf(_class.prototype), "componentWillUnmount", this) && _get(_getPrototypeOf(_class.prototype), "componentWillUnmount", this).call(this);
}
}, {
key: "render",
value: function render() {
var newElement = null;
var AUTHORIZED = CheckStatus.AUTHORIZED,
DENIED = CheckStatus.DENIED; // 校验当前 Component 是否满足权限
var status = checkPermission(_permissions, _userPermissions) ? AUTHORIZED : DENIED;
switch (status) {
case AUTHORIZED:
// 认证通过
var originElement = _get(_getPrototypeOf(_class.prototype), "render", this).call(this); // 校验子组件是否满足权限
newElement = filterPermission(originElement, _userPermissions, _onDenied);
break;
case DENIED:
// 拒绝
// 调用 denied 回调方法
newElement = handleDeniedHook(_permissions, this, _onDenied);
break;
}
return newElement || null; // 不能返回 undefined, 要报错.
}
}]);
return _class;
}(WrappedComponent)
);
};
} // 设置默认配置
permission.settings = function (options) {
Object.assign(_defaults, options);
}; // 设置用户权限
permission.setGlobalPermissions = function (permissions) {
_userPermissions = handleUserPermissions(permissions); // 用户权限设置完成
_userStatus = UserStatus.DONE; // 拿到用户权限后刷新队列里的组件, 重新检测它们的权限
if (isNotEmpty(_updateComponentQueue)) {
updateQueue(_updateComponentQueue);
}
}; // lazy load
permission.setGlobalPermissionsAsync = function (permissions) {
if (isPromise(permissions)) {
_userPromise = permissions; // 用户权限状态改为 pending 状态
_userStatus = UserStatus.PENDING;
_userPromise.then(function (data) {
permission.setGlobalPermissions(data);
}, function (error) {
// 设置出错恢复到未设置状态
_userStatus = UserStatus.ERROR;
_userPermissions = null;
throw new SetPermissionException(error);
}).finally(function () {
// 接收数据后清除 _userPromise
_userPromise = null;
});
}
};
permission.getGlobalPermissions = function () {
return _userPermissions;
};
permission.getGlobalPermissionsAsync = function (cb) {
// 延迟到下一个宏任务执行, 确保异步保存可以拿到数据
setTimeout(function () {
if (_userPromise) {
_userPromise.then(function (data) {
cb(handleUserPermissions(data));
}, cb);
} else {
cb(_userPermissions);
}
}, 0);
};
function withPermission(WrappedComponent, permissions, onDenied) {
var _dec, _class;
return _dec = permission(permissions, onDenied), _dec(_class =
/*#__PURE__*/
function (_React$PureComponent) {
_inherits(_class, _React$PureComponent);
function _class() {
_classCallCheck(this, _class);
return _possibleConstructorReturn(this, _getPrototypeOf(_class).apply(this, arguments));
}
_createClass(_class, [{
key: "render",
value: function render() {
return WrappedComponent(this.props);
}
}]);
return _class;
}(React__default.PureComponent)) || _class;
} // TODO: for Hooks Component
// export function hooksWrapper(WrappedComponent, permissions, onDenied) {
// return @permission(permissions, onDenied) class extends Component {
// render() {
// return WrappedComponent;
// }
// };
// }
var setGlobalPermissions = permission.setGlobalPermissions,
setGlobalPermissionsAsync = permission.setGlobalPermissionsAsync,
getGlobalPermissions = permission.getGlobalPermissions,
getGlobalPermissionsAsync = permission.getGlobalPermissionsAsync;
exports.default = permission;
exports.getGlobalPermissions = getGlobalPermissions;
exports.getGlobalPermissionsAsync = getGlobalPermissionsAsync;
exports.setGlobalPermissions = setGlobalPermissions;
exports.setGlobalPermissionsAsync = setGlobalPermissionsAsync;
exports.withPermission = withPermission;