@lark-apaas/client-toolkit 1.1.36 → 1.1.37-alpha.31

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/README.md CHANGED
@@ -6,3 +6,71 @@
6
6
 
7
7
  * 导出所有对外暴露的 API 和组件,并通过 npm exports 来控制
8
8
  * 其他目录作为 internal 实现,不对外暴露
9
+
10
+ ### auth 目录
11
+
12
+ * 包含与权限相关的 API 和组件
13
+
14
+
15
+ ### 开发组件 - 使用 CanRole 组件 (推荐)
16
+
17
+ ```tsx
18
+ import { CanRole } from '@lark-apaas/client-toolkit/auth';
19
+
20
+ function Home() {
21
+ return (
22
+ <div>
23
+ <CanRole roles={['role_admin']}>
24
+ <div>管理员按钮</div>
25
+ </CanRole>
26
+ <CanRole roles={['role_admin', 'role_editor']}>
27
+ <div>编辑按钮</div>
28
+ </CanRole>
29
+ </div>
30
+ );
31
+ }
32
+ ```
33
+
34
+ ### 开发组件 - 使用 AbilityContext 处理复杂场景
35
+
36
+ ```tsx
37
+ import { useContext } from 'react';
38
+ import { AbilityContext, ROLE_SUBJECT } from '@lark-apaas/client-toolkit/auth';
39
+
40
+ function Home() {
41
+ const ability = useContext(AbilityContext);
42
+ return (
43
+ <div>
44
+ {ability.can('role_admin', ROLE_SUBJECT) || ability.can('role_editor', ROLE_SUBJECT) ? (
45
+ <div>可见的仪表盘</div>
46
+ ) : null}
47
+ </div>
48
+ );
49
+ }
50
+ ```
51
+
52
+ ### 开发组件 - 进阶示例
53
+
54
+ ### 菜单按权限过滤
55
+
56
+ ```tsx
57
+ import { useContext } from 'react';
58
+ import { AbilityContext, ROLE_SUBJECT } from '@lark-apaas/client-toolkit/auth';
59
+
60
+ const menus = [
61
+ { name: 'Dashboard', path: '/dashboard', p: { action: 'role_admin', subject: ROLE_SUBJECT } },
62
+ { name: 'Users', path: '/users', p: { action: 'role_editor', subject: ROLE_SUBJECT } },
63
+ { name: 'Settings', path: '/settings', p: { action: 'role_admin', subject: ROLE_SUBJECT } },
64
+ ];
65
+
66
+ function Nav() {
67
+ const ability = useContext(AbilityContext);
68
+ return (
69
+ <nav>
70
+ {menus.map(m => ability.can(m.p.action, m.p.subject) && (
71
+ <a key={m.path} href={m.path}>{m.name}</a>
72
+ ))}
73
+ </nav>
74
+ );
75
+ }
76
+ ```
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Avatar components re-exported from ui/avatar for public API.
3
+ */
4
+ export { Avatar, AvatarImage, AvatarFallback } from '../../components/ui/avatar';
@@ -0,0 +1,2 @@
1
+ import { Avatar, AvatarFallback, AvatarImage } from "../../components/ui/avatar.js";
2
+ export { Avatar, AvatarFallback, AvatarImage };
@@ -0,0 +1,9 @@
1
+ import * as React from 'react';
2
+ import { type NavLinkProps } from 'react-router-dom';
3
+ /**
4
+ * Enhanced NavLink component that extends react-router-dom's NavLink
5
+ * with support for hash links (anchor navigation) and smooth scrolling.
6
+ */
7
+ declare const NavLink: React.ForwardRefExoticComponent<NavLinkProps & React.RefAttributes<HTMLAnchorElement>>;
8
+ export { NavLink };
9
+ export type { NavLinkProps };
@@ -0,0 +1,50 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { NavLink, useLocation, useNavigate } from "react-router-dom";
4
+ const NavLink_NavLink = /*#__PURE__*/ forwardRef(({ to, children, className, style, ...props }, ref)=>{
5
+ const isHashLink = 'string' == typeof to && to.startsWith('#');
6
+ const location = useLocation();
7
+ const navigate = useNavigate();
8
+ if (isHashLink) {
9
+ const handleClick = (e)=>{
10
+ e.preventDefault();
11
+ const element = document.querySelector(to);
12
+ if (element) element.scrollIntoView({
13
+ behavior: 'smooth'
14
+ });
15
+ navigate(to);
16
+ };
17
+ const isActive = location.hash === to;
18
+ const renderProps = {
19
+ isActive,
20
+ isPending: false,
21
+ isTransitioning: false
22
+ };
23
+ const resolvedClassName = 'function' == typeof className ? className(renderProps) : className;
24
+ const resolvedStyle = 'function' == typeof style ? style(renderProps) : style;
25
+ const { caseSensitive, end, replace, state, preventScrollReset, relative, viewTransition, ...restProps } = props;
26
+ return /*#__PURE__*/ jsx("a", {
27
+ href: to,
28
+ onClick: handleClick,
29
+ ref: ref,
30
+ className: resolvedClassName,
31
+ style: resolvedStyle,
32
+ ...restProps,
33
+ children: 'function' == typeof children ? children(renderProps) : children
34
+ });
35
+ }
36
+ return /*#__PURE__*/ jsx(NavLink, {
37
+ to: to,
38
+ ref: ref,
39
+ className: className,
40
+ style: style,
41
+ onClick: ()=>(document.getElementById('rootContainer') || window)?.scrollTo({
42
+ top: 0,
43
+ behavior: 'smooth'
44
+ }),
45
+ ...props,
46
+ children: children
47
+ });
48
+ });
49
+ NavLink_NavLink.displayName = 'NavLink';
50
+ export { NavLink_NavLink as NavLink };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * TruncatedTitle component - displays truncated text with a tooltip when overflowed.
3
+ * This is an alias for OverflowTooltipText for semantic naming in layout components.
4
+ */
5
+ export { OverflowTooltipText, OverflowTooltipText as TruncatedTitle, type OverflowTooltipTextProps, type OverflowTooltipTextProps as TruncatedTitleProps, } from '../../components/ui/overflow-tooltip-text';
@@ -0,0 +1,2 @@
1
+ import { OverflowTooltipText } from "../../components/ui/overflow-tooltip-text.js";
2
+ export { OverflowTooltipText, OverflowTooltipText as TruncatedTitle };
package/lib/auth.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { CanRole, AbilityContext, ROLE_SUBJECT } from '@lark-apaas/auth-sdk';
package/lib/auth.js ADDED
@@ -0,0 +1,2 @@
1
+ import { AbilityContext, CanRole, ROLE_SUBJECT } from "@lark-apaas/auth-sdk";
2
+ export { AbilityContext, CanRole, ROLE_SUBJECT };
@@ -1,7 +1,10 @@
1
1
  import React from 'react';
2
2
  import { IBaseThemeProviderProps } from '../theme';
3
3
  import '../../index.css';
4
+ interface IBaseAuthProviderProps {
5
+ enableAuth?: boolean;
6
+ }
4
7
  declare const AppContainer: React.FC<{
5
8
  children: React.ReactNode;
6
- } & IBaseThemeProviderProps>;
9
+ } & IBaseThemeProviderProps & IBaseAuthProviderProps>;
7
10
  export default AppContainer;
@@ -17,8 +17,11 @@ import safety from "./safety.js";
17
17
  import { getAppId } from "../../utils/getAppId.js";
18
18
  import { ServerLogPoller } from "../../server-log/index.js";
19
19
  import QueryProvider from "../QueryProvider/index.js";
20
+ import { initObservable } from "./utils/observable.js";
21
+ import { AuthProvider } from "@lark-apaas/auth-sdk";
20
22
  registerDayjsPlugins();
21
23
  initAxiosConfig();
24
+ initObservable();
22
25
  const isMiaodaPreview = window.IS_MIAODA_PREVIEW;
23
26
  const readCssVarColor = (varName, fallback)=>{
24
27
  try {
@@ -30,7 +33,7 @@ const readCssVarColor = (varName, fallback)=>{
30
33
  }
31
34
  };
32
35
  const App = (props)=>{
33
- const { themeMeta = {} } = props;
36
+ const { themeMeta = {}, enableAuth } = props;
34
37
  useAppInfo();
35
38
  const serverLogPollerRef = useRef(null);
36
39
  const { rem } = findValueByPixel(themeMetaOptions.themeRadius, themeMeta.borderRadius) || {
@@ -86,7 +89,10 @@ const App = (props)=>{
86
89
  }
87
90
  });
88
91
  }, []);
89
- return /*#__PURE__*/ jsxs(Fragment, {
92
+ return /*#__PURE__*/ jsxs(AuthProvider, {
93
+ config: {
94
+ enable: enableAuth
95
+ },
90
96
  children: [
91
97
  /*#__PURE__*/ jsx(Toaster, {}),
92
98
  'production' !== process.env.NODE_ENV && /*#__PURE__*/ jsx(MiaodaInspector, {
@@ -233,6 +239,7 @@ const AppContainer_AppContainer = (props)=>{
233
239
  },
234
240
  children: /*#__PURE__*/ jsx(App, {
235
241
  themeMeta: props.themeMeta,
242
+ enableAuth: props.enableAuth,
236
243
  children: children
237
244
  })
238
245
  })
@@ -0,0 +1 @@
1
+ export declare const initObservable: () => void;
@@ -0,0 +1,16 @@
1
+ import { AppEnv, observable } from "@lark-apaas/observable-web";
2
+ const initObservable = ()=>{
3
+ try {
4
+ observable.start({
5
+ serviceName: "app",
6
+ env: 'development' === process.env.NODE_ENV ? AppEnv.Dev : AppEnv.Prod,
7
+ collectorUrl: {
8
+ log: `/spark/app/${window.appId}/runtime/api/v1/observability/logs/collect`,
9
+ metric: `/spark/app/${window.appId}/runtime/api/v1/observability/metrics/collect`
10
+ }
11
+ });
12
+ } catch (error) {
13
+ console.error('Failed to start WebObservableSdk:', error);
14
+ }
15
+ };
16
+ export { initObservable };
@@ -3,6 +3,11 @@ import { logger } from "../logger/index.js";
3
3
  import { getCurrentUserProfile } from "../integrations/getCurrentUserProfile.js";
4
4
  import { getDataloom } from "../integrations/dataloom.js";
5
5
  import { isSparkRuntime } from "../utils/utils.js";
6
+ function getNameFromArray(nameArray) {
7
+ if (!nameArray || 0 === nameArray.length) return;
8
+ const chineseName = nameArray.find((item)=>2052 === item.language_code);
9
+ return chineseName?.text ?? nameArray[0]?.text;
10
+ }
6
11
  function getCompatibilityUserProfile() {
7
12
  const userInfo = getCurrentUserProfile();
8
13
  return {
@@ -18,34 +23,36 @@ const useCurrentUserProfile = ()=>{
18
23
  if (isSparkRuntime()) {
19
24
  (async ()=>{
20
25
  const dataloom = await getDataloom();
21
- const result = await dataloom.service.session.getUserInfo();
26
+ const result = await dataloom?.service?.session?.getUserInfo();
22
27
  const userInfo = result?.data?.user_info;
28
+ const userName = getNameFromArray(userInfo?.name);
23
29
  setUserInfo({
24
30
  user_id: userInfo?.user_id?.toString(),
25
31
  email: userInfo?.email,
26
- name: userInfo?.name?.[0]?.text,
32
+ name: userName,
27
33
  avatar: userInfo?.avatar?.image?.large,
28
- userName: userInfo?.name?.[0]?.text,
34
+ userName: userName,
29
35
  userAvatar: userInfo?.avatar?.image?.large
30
36
  });
31
37
  })();
32
38
  handleMetaInfoChanged = async ()=>{
33
39
  const dataloom = await getDataloom();
34
- const result = await dataloom.service.session.getUserInfo();
40
+ const result = await dataloom?.service?.session?.getUserInfo();
35
41
  const userInfo = result?.data?.user_info;
42
+ const userName = getNameFromArray(userInfo?.name);
36
43
  const newUserInfo = {
37
44
  user_id: userInfo?.user_id?.toString(),
38
45
  email: userInfo?.email,
39
- name: userInfo?.name?.[0]?.text,
46
+ name: userName,
40
47
  avatar: userInfo?.avatar?.image?.large,
41
- userName: userInfo?.name?.[0]?.text,
48
+ userName: userName,
42
49
  userAvatar: userInfo?.avatar?.image?.large
43
50
  };
44
- logger.info('MiaoDaMetaInfoChanged', newUserInfo);
51
+ if ('development' === process.env.NODE_ENV) logger.info('MiaoDaMetaInfoChanged', newUserInfo);
45
52
  setUserInfo(newUserInfo);
46
53
  };
47
54
  } else handleMetaInfoChanged = ()=>{
48
- logger.info('MiaoDaMetaInfoChanged', getCompatibilityUserProfile());
55
+ if ('development' === process.env.NODE_ENV) logger.info('MiaoDaMetaInfoChanged', getCompatibilityUserProfile());
49
56
  setUserInfo(getCompatibilityUserProfile());
50
57
  };
51
58
  window.addEventListener('MiaoDaMetaInfoChanged', handleMetaInfoChanged);
@@ -1,5 +1,8 @@
1
+ import { observable } from "@lark-apaas/observable-web";
1
2
  import { interceptors } from "./selected-logs.js";
2
3
  import { interceptErrors } from "./intercept-global-error.js";
4
+ import { mapLogLevel, processLogParams } from "../utils/safeStringify.js";
5
+ const shouldReportToObservable = 'production' === process.env.NODE_ENV;
3
6
  const LOG_LEVELS = [
4
7
  'debug',
5
8
  'info',
@@ -45,18 +48,35 @@ let logger = {
45
48
  },
46
49
  info (message, ...args) {
47
50
  if (shouldLog('info')) console.log(...getFormattedPrefix('info'), message, ...args);
51
+ if (shouldReportToObservable) observable.log('INFO', processLogParams([
52
+ message,
53
+ ...args
54
+ ]));
48
55
  },
49
56
  warn (message, ...args) {
50
57
  if (shouldLog('warn')) console.log(...getFormattedPrefix('warn'), message, ...args);
58
+ if (shouldReportToObservable) observable.log('WARN', processLogParams([
59
+ message,
60
+ ...args
61
+ ]));
51
62
  },
52
63
  error (message, ...args) {
53
64
  if (shouldLog('error')) console.error(...getFormattedPrefix('error'), message, ...args);
65
+ if (shouldReportToObservable) observable.log('ERROR', processLogParams([
66
+ message,
67
+ ...args
68
+ ]));
54
69
  },
55
70
  success (message, ...args) {
56
71
  if (shouldLog('success')) console.log(...getFormattedPrefix('success'), message, ...args);
72
+ if (shouldReportToObservable) observable.log('INFO', processLogParams([
73
+ message,
74
+ ...args
75
+ ]));
57
76
  },
58
77
  log ({ level, args }) {
59
78
  if (shouldLog(level)) console.log(...getFormattedPrefix(level), ...args);
79
+ if (shouldReportToObservable && "debug" !== level) observable.log(mapLogLevel(level), processLogParams(args));
60
80
  }
61
81
  };
62
82
  if ('production' !== process.env.NODE_ENV) window.__RUNTIME_LOGGER__ = {
@@ -52,7 +52,7 @@ async function logResponse(ok, responseOrError) {
52
52
  logTraceID
53
53
  };
54
54
  if (stacktrace) logMeta.stacktrace = stacktrace;
55
- logger.log({
55
+ if ('development' === process.env.NODE_ENV) logger.log({
56
56
  level: ok,
57
57
  args: [
58
58
  parts.join(''),
@@ -0,0 +1,4 @@
1
+ import { LogLevel } from "../logger/log-types";
2
+ export declare function safeStringify(obj: unknown): string;
3
+ export declare function processLogParams(args: unknown[]): string;
4
+ export declare function mapLogLevel(level: LogLevel): "INFO" | "WARN" | "ERROR";
@@ -0,0 +1,42 @@
1
+ function safeStringify(obj) {
2
+ const seen = new Set();
3
+ try {
4
+ return JSON.stringify(obj, (key, value)=>{
5
+ if ('object' == typeof value && null !== value) {
6
+ if (seen.has(value)) return '[Circular]';
7
+ seen.add(value);
8
+ }
9
+ if ('bigint' == typeof value) return value.toString();
10
+ if (value instanceof Date) return value.toISOString();
11
+ if (value instanceof Map) return Object.fromEntries(value);
12
+ if (value instanceof Set) return Array.from(value);
13
+ if (void 0 === value) return 'undefined';
14
+ if ('symbol' == typeof value) return value.toString();
15
+ return value;
16
+ });
17
+ } catch {
18
+ return '';
19
+ } finally{
20
+ seen.clear();
21
+ }
22
+ }
23
+ function processLogParams(args) {
24
+ if (1 === args.length) return safeStringify(args[0]);
25
+ const obj = {};
26
+ for(let i = 0; i < args.length; i++)obj[i.toString()] = args[i];
27
+ return safeStringify(obj);
28
+ }
29
+ function mapLogLevel(level) {
30
+ switch(level){
31
+ case "error":
32
+ return "ERROR";
33
+ case "info":
34
+ case "success":
35
+ return "INFO";
36
+ case "warn":
37
+ return "WARN";
38
+ default:
39
+ return "INFO";
40
+ }
41
+ }
42
+ export { mapLogLevel, processLogParams, safeStringify };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/client-toolkit",
3
- "version": "1.1.36",
3
+ "version": "1.1.37-alpha.31",
4
4
  "types": "./lib/index.d.ts",
5
5
  "main": "./lib/index.js",
6
6
  "files": [
@@ -60,6 +60,11 @@
60
60
  "import": "./lib/apis/utils/*.js",
61
61
  "require": "./lib/apis/utils/*.js",
62
62
  "types": "./lib/apis/utils/*.d.ts"
63
+ },
64
+ "./auth": {
65
+ "import": "./lib/auth.js",
66
+ "require": "./lib/auth.js",
67
+ "types": "./lib/auth.d.ts"
63
68
  }
64
69
  },
65
70
  "scripts": {
@@ -81,7 +86,9 @@
81
86
  "@ant-design/colors": "^7.2.1",
82
87
  "@ant-design/cssinjs": "^1.24.0",
83
88
  "@data-loom/js": "^0.4.3",
89
+ "@lark-apaas/auth-sdk": "0.1.0-alpha.31",
84
90
  "@lark-apaas/miaoda-inspector": "^1.0.9",
91
+ "@lark-apaas/observable-web": "^1.0.0",
85
92
  "@radix-ui/react-avatar": "^1.1.10",
86
93
  "@radix-ui/react-popover": "^1.1.15",
87
94
  "@radix-ui/react-slot": "^1.2.3",