UNPKG

@beanreact/permission

Version:
655 lines (521 loc) 18.9 kB
'use strict'; 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;