@lark-apaas/client-toolkit 1.2.6 → 1.2.7
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 +68 -0
- package/lib/apis/components/ActiveLink.d.ts +26 -0
- package/lib/apis/components/ActiveLink.js +67 -0
- package/lib/apis/trace.d.ts +1 -0
- package/lib/apis/trace.js +1 -0
- package/lib/apis/utils/getEnv.d.ts +1 -0
- package/lib/apis/utils/getEnv.js +2 -0
- package/lib/auth.d.ts +1 -0
- package/lib/auth.js +2 -0
- package/lib/components/AppContainer/IframeBridge.js +6 -0
- package/lib/components/AppContainer/index.d.ts +4 -1
- package/lib/components/AppContainer/index.js +7 -2
- package/lib/components/AppContainer/utils/listenHot.js +31 -9
- package/lib/components/AppContainer/utils/observable.js +5 -2
- package/lib/logger/batch-logger.js +3 -2
- package/lib/logger/selected-logs.js +1 -2
- package/lib/trace/index.d.ts +13 -0
- package/lib/trace/index.js +6 -0
- package/lib/utils/axiosConfig.d.ts +10 -3
- package/lib/utils/axiosConfig.js +102 -7
- package/lib/utils/getParentOrigin.js +12 -2
- package/package.json +15 -5
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,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type NavLinkProps } from 'react-router-dom';
|
|
3
|
+
export interface ActiveLinkProps extends Omit<NavLinkProps, 'to'> {
|
|
4
|
+
to: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* ActiveLink 组件
|
|
8
|
+
*
|
|
9
|
+
* 替换 react-router-dom 的 NavLink,扩展支持 hash 路由跳转和激活态功能
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // 普通路由跳转
|
|
13
|
+
* <ActiveLink to="/dashboard">Dashboard</ActiveLink>
|
|
14
|
+
*
|
|
15
|
+
* // hash 路由跳转
|
|
16
|
+
* <ActiveLink to="#section1">Section 1</ActiveLink>
|
|
17
|
+
*
|
|
18
|
+
* // 使用函数式 className 显示激活态
|
|
19
|
+
* <ActiveLink
|
|
20
|
+
* to="#features"
|
|
21
|
+
* className={({isActive}) => isActive ? 'active' : ''}
|
|
22
|
+
* >
|
|
23
|
+
* Features
|
|
24
|
+
* </ActiveLink>
|
|
25
|
+
*/
|
|
26
|
+
export declare const ActiveLink: React.ForwardRefExoticComponent<ActiveLinkProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useState } from "react";
|
|
3
|
+
import { NavLink } from "react-router-dom";
|
|
4
|
+
const ActiveLink = /*#__PURE__*/ forwardRef(({ to, onClick, className, style, children, ...rest }, ref)=>{
|
|
5
|
+
const [currentHash, setCurrentHash] = useState(()=>'undefined' != typeof window ? window.location.hash : '');
|
|
6
|
+
const isHashRoute = 'string' == typeof to && to.startsWith('#');
|
|
7
|
+
useEffect(()=>{
|
|
8
|
+
if (!isHashRoute) return;
|
|
9
|
+
const handleHashChange = ()=>{
|
|
10
|
+
setCurrentHash(window.location.hash);
|
|
11
|
+
};
|
|
12
|
+
window.addEventListener('hashchange', handleHashChange);
|
|
13
|
+
return ()=>window.removeEventListener('hashchange', handleHashChange);
|
|
14
|
+
}, [
|
|
15
|
+
isHashRoute
|
|
16
|
+
]);
|
|
17
|
+
const isActive = isHashRoute && currentHash === to;
|
|
18
|
+
if (!isHashRoute) return /*#__PURE__*/ jsx(NavLink, {
|
|
19
|
+
ref: ref,
|
|
20
|
+
to: to,
|
|
21
|
+
onClick: onClick,
|
|
22
|
+
className: className,
|
|
23
|
+
style: style,
|
|
24
|
+
...rest,
|
|
25
|
+
children: children
|
|
26
|
+
});
|
|
27
|
+
const handleHashClick = (e)=>{
|
|
28
|
+
onClick?.(e);
|
|
29
|
+
if (e.defaultPrevented) return;
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
const hash = to;
|
|
32
|
+
window.location.hash = hash;
|
|
33
|
+
setCurrentHash(hash);
|
|
34
|
+
window.dispatchEvent(new Event('hashchange'));
|
|
35
|
+
const targetId = hash.slice(1);
|
|
36
|
+
const targetElement = document.getElementById(targetId);
|
|
37
|
+
if (targetElement && 'function' == typeof targetElement.scrollIntoView) targetElement.scrollIntoView({
|
|
38
|
+
behavior: 'smooth'
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const computedClassName = 'function' == typeof className ? className({
|
|
42
|
+
isActive,
|
|
43
|
+
isPending: false,
|
|
44
|
+
isTransitioning: false
|
|
45
|
+
}) : className;
|
|
46
|
+
const computedStyle = 'function' == typeof style ? style({
|
|
47
|
+
isActive,
|
|
48
|
+
isPending: false,
|
|
49
|
+
isTransitioning: false
|
|
50
|
+
}) : style;
|
|
51
|
+
const computedChildren = 'function' == typeof children ? children({
|
|
52
|
+
isActive,
|
|
53
|
+
isPending: false,
|
|
54
|
+
isTransitioning: false
|
|
55
|
+
}) : children;
|
|
56
|
+
return /*#__PURE__*/ jsx("a", {
|
|
57
|
+
ref: ref,
|
|
58
|
+
href: to,
|
|
59
|
+
onClick: handleHashClick,
|
|
60
|
+
className: computedClassName,
|
|
61
|
+
style: computedStyle,
|
|
62
|
+
...rest,
|
|
63
|
+
children: computedChildren
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
ActiveLink.displayName = 'ActiveLink';
|
|
67
|
+
export { ActiveLink };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../trace';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../trace/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getEnv } from '../../utils/getParentOrigin';
|
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
|
@@ -5,6 +5,7 @@ import { connectToParent } from "penpal";
|
|
|
5
5
|
import { useUpdatingRef } from "../../hooks/useUpdatingRef.js";
|
|
6
6
|
import { resolveParentOrigin, submitPostMessage } from "../../utils/postMessage.js";
|
|
7
7
|
import { childApi } from "./utils/childApi.js";
|
|
8
|
+
import { batchLogInfo } from "../../logger/batch-logger.js";
|
|
8
9
|
import "./utils/listenHot.js";
|
|
9
10
|
var IframeBridge_RouteMessageType = /*#__PURE__*/ function(RouteMessageType) {
|
|
10
11
|
RouteMessageType["RouteChange"] = "RouteChange";
|
|
@@ -20,6 +21,11 @@ async function connectParent() {
|
|
|
20
21
|
type: 'PreviewReady',
|
|
21
22
|
data: {}
|
|
22
23
|
});
|
|
24
|
+
batchLogInfo('info', JSON.stringify({
|
|
25
|
+
type: 'PreviewReady',
|
|
26
|
+
timestamp: Date.now(),
|
|
27
|
+
url: window.location.href
|
|
28
|
+
}));
|
|
23
29
|
const parentOrigin = resolveParentOrigin();
|
|
24
30
|
if (!parentOrigin) return;
|
|
25
31
|
const connection = connectToParent({
|
|
@@ -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;
|
|
@@ -19,6 +19,7 @@ import { getAppId } from "../../utils/getAppId.js";
|
|
|
19
19
|
import { ServerLogSSEClient } from "../../server-log/index.js";
|
|
20
20
|
import QueryProvider from "../QueryProvider/index.js";
|
|
21
21
|
import { initObservable } from "./utils/observable.js";
|
|
22
|
+
import { AuthProvider } from "@lark-apaas/auth-sdk";
|
|
22
23
|
registerDayjsPlugins();
|
|
23
24
|
initAxiosConfig();
|
|
24
25
|
initObservable();
|
|
@@ -33,7 +34,7 @@ const readCssVarColor = (varName, fallback)=>{
|
|
|
33
34
|
}
|
|
34
35
|
};
|
|
35
36
|
const App = (props)=>{
|
|
36
|
-
const { themeMeta = {} } = props;
|
|
37
|
+
const { themeMeta = {}, enableAuth } = props;
|
|
37
38
|
useAppInfo();
|
|
38
39
|
const serverLogClientRef = useRef(null);
|
|
39
40
|
const { rem } = findValueByPixel(themeMetaOptions.themeRadius, themeMeta.borderRadius) || {
|
|
@@ -99,7 +100,10 @@ const App = (props)=>{
|
|
|
99
100
|
}
|
|
100
101
|
});
|
|
101
102
|
}, []);
|
|
102
|
-
return /*#__PURE__*/ jsxs(
|
|
103
|
+
return /*#__PURE__*/ jsxs(AuthProvider, {
|
|
104
|
+
config: {
|
|
105
|
+
enable: enableAuth
|
|
106
|
+
},
|
|
103
107
|
children: [
|
|
104
108
|
/*#__PURE__*/ jsx(Toaster, {}),
|
|
105
109
|
'production' !== process.env.NODE_ENV && /*#__PURE__*/ jsx(MiaodaInspector, {
|
|
@@ -247,6 +251,7 @@ const AppContainer_AppContainer = (props)=>{
|
|
|
247
251
|
},
|
|
248
252
|
children: /*#__PURE__*/ jsx(App, {
|
|
249
253
|
themeMeta: props.themeMeta,
|
|
254
|
+
enableAuth: props.enableAuth,
|
|
250
255
|
children: children
|
|
251
256
|
})
|
|
252
257
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import sockjs_client from "sockjs-client";
|
|
2
2
|
import { submitPostMessage, submitSlardarEvent } from "../../../utils/postMessage.js";
|
|
3
|
+
import { batchLogInfo } from "../../../logger/batch-logger.js";
|
|
3
4
|
import { getWsPath } from "../../../utils/utils.js";
|
|
4
5
|
let hotInited = false;
|
|
5
6
|
function handleDevServerMessage(msg) {
|
|
@@ -15,15 +16,28 @@ function handleDevServerMessage(msg) {
|
|
|
15
16
|
},
|
|
16
17
|
data: null
|
|
17
18
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
batchLogInfo('info', JSON.stringify({
|
|
20
|
+
type: 'HmrMessage',
|
|
21
|
+
subType: 'hot',
|
|
22
|
+
timestamp: Date.now(),
|
|
23
|
+
hash: msg.data
|
|
24
|
+
}));
|
|
25
|
+
} else if ('errors' === msg.type) {
|
|
26
|
+
submitPostMessage({
|
|
27
|
+
type: 'HmrMessage',
|
|
28
|
+
msg: {
|
|
29
|
+
type: 'errors',
|
|
30
|
+
data: JSON.stringify(msg.data)
|
|
31
|
+
},
|
|
32
|
+
data: null
|
|
33
|
+
});
|
|
34
|
+
batchLogInfo('info', JSON.stringify({
|
|
35
|
+
type: 'HmrMessage',
|
|
36
|
+
subType: 'errors',
|
|
37
|
+
timestamp: Date.now(),
|
|
38
|
+
errorCount: msg.data?.length
|
|
39
|
+
}));
|
|
40
|
+
} else if ('hmr-timing' === msg.type) {
|
|
27
41
|
const { duration, fileCount, fileTotalSize } = msg.data;
|
|
28
42
|
submitSlardarEvent({
|
|
29
43
|
name: 'runTiming',
|
|
@@ -36,6 +50,14 @@ function handleDevServerMessage(msg) {
|
|
|
36
50
|
fileTotalSize
|
|
37
51
|
}
|
|
38
52
|
});
|
|
53
|
+
batchLogInfo('info', JSON.stringify({
|
|
54
|
+
type: 'HmrMessage',
|
|
55
|
+
subType: 'hmr-timing',
|
|
56
|
+
timestamp: Date.now(),
|
|
57
|
+
duration,
|
|
58
|
+
fileCount,
|
|
59
|
+
fileTotalSize
|
|
60
|
+
}));
|
|
39
61
|
}
|
|
40
62
|
}
|
|
41
63
|
function connectDevServer() {
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { AppEnv, observable } from "@lark-apaas/observable-web";
|
|
2
2
|
const initObservable = ()=>{
|
|
3
3
|
try {
|
|
4
|
+
const appId = window.appId;
|
|
4
5
|
observable.start({
|
|
5
6
|
serviceName: "app",
|
|
6
7
|
env: 'development' === process.env.NODE_ENV ? AppEnv.Dev : AppEnv.Prod,
|
|
7
8
|
collectorUrl: {
|
|
8
|
-
log: `/spark/app/${
|
|
9
|
-
|
|
9
|
+
log: `/spark/app/${appId}/runtime/api/v1/observability/logs/collect`,
|
|
10
|
+
trace: `/spark/app/${appId}/runtime/api/v1/observability/traces/collect`,
|
|
11
|
+
metric: `/spark/app/${appId}/runtime/api/v1/observability/metrics/collect`,
|
|
12
|
+
time: `/spark/api/v1/observability/app/${appId}/current_server_timestamp`
|
|
10
13
|
}
|
|
11
14
|
});
|
|
12
15
|
} catch (error) {
|
|
@@ -4,9 +4,9 @@ class BatchLogger {
|
|
|
4
4
|
flushTimer = null;
|
|
5
5
|
isProcessing = false;
|
|
6
6
|
originConsole;
|
|
7
|
-
constructor(
|
|
7
|
+
constructor(console1, config){
|
|
8
8
|
this.originConsole = {
|
|
9
|
-
...
|
|
9
|
+
...console1
|
|
10
10
|
};
|
|
11
11
|
const { userId = '', tenantId = '', appId = '' } = window || {};
|
|
12
12
|
this.config = {
|
|
@@ -131,4 +131,5 @@ function destroyBatchLogger() {
|
|
|
131
131
|
defaultBatchLogger = null;
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
+
if ('production' !== process.env.NODE_ENV && 'undefined' != typeof window) defaultBatchLogger = new BatchLogger(console);
|
|
134
135
|
export { BatchLogger, batchLogInfo, destroyBatchLogger, getBatchLogger, initBatchLogger };
|
|
@@ -2,7 +2,7 @@ import { SourceMapConsumer as external_source_map_SourceMapConsumer } from "sour
|
|
|
2
2
|
import "../utils/utils.js";
|
|
3
3
|
import { mappingText } from "./source-map-mappings-wasm.js";
|
|
4
4
|
import stacktrace_js from "stacktrace-js";
|
|
5
|
-
import { batchLogInfo
|
|
5
|
+
import { batchLogInfo } from "./batch-logger.js";
|
|
6
6
|
function hexToBuffer(hexString) {
|
|
7
7
|
const hex = hexString.replace(/\s/g, '').toUpperCase();
|
|
8
8
|
if (!/^[0-9A-F]+$/.test(hex)) throw new Error('Invalid hex string');
|
|
@@ -213,7 +213,6 @@ const typedLogInterceptor = (logger)=>({
|
|
|
213
213
|
});
|
|
214
214
|
}
|
|
215
215
|
});
|
|
216
|
-
'production' !== process.env.NODE_ENV && initBatchLogger(console);
|
|
217
216
|
const interceptors = 'production' !== process.env.NODE_ENV ? [
|
|
218
217
|
typedLogInterceptor
|
|
219
218
|
] : [];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { observable } from "@lark-apaas/observable-web";
|
|
2
|
+
/**
|
|
3
|
+
* 核心 Trace 方法:用于执行并追踪异步函数
|
|
4
|
+
* 使用代理模式而非直接 bind,确保在调用时获取最新的 observable 实例状态
|
|
5
|
+
*/
|
|
6
|
+
export declare const trace: (...args: Parameters<typeof observable.trace>) => Promise<unknown>;
|
|
7
|
+
/**
|
|
8
|
+
* 默认导出追踪套件
|
|
9
|
+
*/
|
|
10
|
+
declare const _default: {
|
|
11
|
+
trace: (name: string, fn: (span: import("@opentelemetry/api").Span) => Promise<unknown>, options?: import("@opentelemetry/api").SpanOptions, span?: import("@opentelemetry/api").Span) => Promise<unknown>;
|
|
12
|
+
};
|
|
13
|
+
export default _default;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { AxiosInstance } from 'axios';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*/
|
|
2
|
+
declare module 'axios' {
|
|
3
|
+
interface AxiosRequestConfig {
|
|
4
|
+
/** @internal 存储 Span 实例 */
|
|
5
|
+
__span?: any;
|
|
6
|
+
/** @internal 请求开始时间 */
|
|
7
|
+
_startTime?: number;
|
|
8
|
+
/** @internal 请求唯一标识 */
|
|
9
|
+
_requestUUID?: string;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
5
12
|
export declare function initAxiosConfig(axiosInstance?: AxiosInstance): void;
|
package/lib/utils/axiosConfig.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
|
+
import { observable } from "@lark-apaas/observable-web";
|
|
2
3
|
import { logger } from "../apis/logger.js";
|
|
3
4
|
import { getStacktrace } from "../logger/selected-logs.js";
|
|
5
|
+
import { safeStringify } from "./safeStringify.js";
|
|
4
6
|
const isValidResponse = (resp)=>null != resp && 'object' == typeof resp && 'config' in resp && null !== resp.config && void 0 !== resp.config && 'object' == typeof resp.config && 'status' in resp && 'number' == typeof resp.status && 'data' in resp;
|
|
5
7
|
async function logResponse(ok, responseOrError) {
|
|
6
8
|
if (isValidResponse(responseOrError)) {
|
|
@@ -52,7 +54,7 @@ async function logResponse(ok, responseOrError) {
|
|
|
52
54
|
logTraceID
|
|
53
55
|
};
|
|
54
56
|
if (stacktrace) logMeta.stacktrace = stacktrace;
|
|
55
|
-
|
|
57
|
+
logger.debug({
|
|
56
58
|
level: ok,
|
|
57
59
|
args: [
|
|
58
60
|
parts.join(''),
|
|
@@ -108,7 +110,7 @@ async function logResponse(ok, responseOrError) {
|
|
|
108
110
|
const stacktrace = await requestStacktraceMap.get(requestUUID);
|
|
109
111
|
const logMeta = {};
|
|
110
112
|
if (stacktrace) logMeta.stacktrace = stacktrace;
|
|
111
|
-
logger.
|
|
113
|
+
logger.debug({
|
|
112
114
|
level: 'error',
|
|
113
115
|
args: [
|
|
114
116
|
parts.join(''),
|
|
@@ -118,7 +120,7 @@ async function logResponse(ok, responseOrError) {
|
|
|
118
120
|
});
|
|
119
121
|
return;
|
|
120
122
|
}
|
|
121
|
-
logger.
|
|
123
|
+
logger.debug({
|
|
122
124
|
level: 'error',
|
|
123
125
|
args: [
|
|
124
126
|
'请求失败:无响应对象或配置信息'
|
|
@@ -127,21 +129,114 @@ async function logResponse(ok, responseOrError) {
|
|
|
127
129
|
});
|
|
128
130
|
}
|
|
129
131
|
const requestStacktraceMap = new Map();
|
|
132
|
+
function handleSpanEnd(cfg, response, error) {
|
|
133
|
+
try {
|
|
134
|
+
const currentSpan = cfg?.__span;
|
|
135
|
+
if (!currentSpan) return;
|
|
136
|
+
const startTime = cfg._startTime;
|
|
137
|
+
const errorResponse = error?.response || {};
|
|
138
|
+
const errorMessage = error?.message || '未知错误';
|
|
139
|
+
const url = response?.request?.responseURL || errorResponse?.request?.responseURL || cfg.url || "";
|
|
140
|
+
const method = (cfg.method || 'GET').toUpperCase();
|
|
141
|
+
const path = url.split('?')[0].replace(/^https?:\/\/[^/]+/, '') || '/';
|
|
142
|
+
const logData = {
|
|
143
|
+
method,
|
|
144
|
+
path,
|
|
145
|
+
url,
|
|
146
|
+
duration_ms: startTime ? Date.now() - startTime : void 0,
|
|
147
|
+
status: response ? response.status : errorResponse.status || 0
|
|
148
|
+
};
|
|
149
|
+
if (error) logData.error_message = errorMessage;
|
|
150
|
+
if ('undefined' != typeof navigator) logData.user_agent = navigator.userAgent;
|
|
151
|
+
if (cfg.data) if ('string' == typeof cfg.data) try {
|
|
152
|
+
logData.request_body = JSON.parse(cfg.data);
|
|
153
|
+
} catch {
|
|
154
|
+
logData.request_body = cfg.data;
|
|
155
|
+
}
|
|
156
|
+
else cfg.data, logData.request_body = cfg.data;
|
|
157
|
+
const responseData = response?.data || errorResponse?.data;
|
|
158
|
+
if (responseData) logData.response = responseData;
|
|
159
|
+
const level = error ? 'ERROR' : 'INFO';
|
|
160
|
+
observable.log(level, safeStringify(logData), {}, currentSpan);
|
|
161
|
+
'function' == typeof currentSpan.end && currentSpan.end();
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error('[AxiosTrace] Log span failed:', e);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const AxiosProto = axios.Axios.prototype;
|
|
167
|
+
const originalAxiosRequest = AxiosProto.request;
|
|
168
|
+
AxiosProto.request = function(configOrUrl, config) {
|
|
169
|
+
let finalConfig;
|
|
170
|
+
if ('string' == typeof configOrUrl) {
|
|
171
|
+
finalConfig = config || {};
|
|
172
|
+
finalConfig.url = configOrUrl;
|
|
173
|
+
} else finalConfig = configOrUrl || {};
|
|
174
|
+
if (finalConfig.__span) return originalAxiosRequest.call(this, finalConfig);
|
|
175
|
+
const fullUrl = this.getUri ? this.getUri(finalConfig) : finalConfig.url || "";
|
|
176
|
+
const actualMethod = (finalConfig.method || 'GET').toUpperCase();
|
|
177
|
+
const cleanedPath = fullUrl.split('?')[0].replace(/^https?:\/\/[^/]+/, '') || '/';
|
|
178
|
+
const span = observable.startSpan(`${actualMethod} ${cleanedPath}`);
|
|
179
|
+
if (span) try {
|
|
180
|
+
const spanContext = span.spanContext();
|
|
181
|
+
if (spanContext && spanContext.traceId) {
|
|
182
|
+
const traceInfo = `${spanContext.traceId}-${spanContext.spanId}`;
|
|
183
|
+
if (!finalConfig.headers) finalConfig.headers = {};
|
|
184
|
+
finalConfig.headers['X-Tt-TraceInfo'] = traceInfo;
|
|
185
|
+
finalConfig.__span = span;
|
|
186
|
+
finalConfig._startTime = finalConfig._startTime || Date.now();
|
|
187
|
+
}
|
|
188
|
+
} catch (e) {
|
|
189
|
+
console.error('[AxiosTrace] Setup trace info failed:', e);
|
|
190
|
+
}
|
|
191
|
+
return originalAxiosRequest.call(this, finalConfig).then((response)=>{
|
|
192
|
+
handleSpanEnd(response.config || finalConfig, response, null);
|
|
193
|
+
return response;
|
|
194
|
+
}, (error)=>{
|
|
195
|
+
handleSpanEnd(error.config || finalConfig, null, error);
|
|
196
|
+
throw error;
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
const originalCreate = axios.create;
|
|
200
|
+
axios.create = function(config) {
|
|
201
|
+
const instance = originalCreate.apply(axios, [
|
|
202
|
+
config
|
|
203
|
+
]);
|
|
204
|
+
return instance;
|
|
205
|
+
};
|
|
206
|
+
const methods = [
|
|
207
|
+
'request',
|
|
208
|
+
'get',
|
|
209
|
+
'post',
|
|
210
|
+
'put',
|
|
211
|
+
'delete',
|
|
212
|
+
'patch',
|
|
213
|
+
'head',
|
|
214
|
+
'options',
|
|
215
|
+
'postForm',
|
|
216
|
+
'putForm',
|
|
217
|
+
'patchForm'
|
|
218
|
+
];
|
|
219
|
+
methods.forEach((method)=>{
|
|
220
|
+
const originalMethod = axios[method];
|
|
221
|
+
if ('function' == typeof originalMethod) axios[method] = function(...args) {
|
|
222
|
+
return AxiosProto.request.apply(axios, args);
|
|
223
|
+
};
|
|
224
|
+
});
|
|
130
225
|
function initAxiosConfig(axiosInstance) {
|
|
131
|
-
|
|
132
|
-
|
|
226
|
+
const instance = axiosInstance || axios;
|
|
227
|
+
instance.interceptors.request.use((config)=>{
|
|
133
228
|
const requestUUID = crypto.randomUUID();
|
|
134
229
|
if ('production' !== process.env.NODE_ENV) {
|
|
135
230
|
const stacktrace = getStacktrace();
|
|
136
231
|
requestStacktraceMap.set(requestUUID, stacktrace);
|
|
137
232
|
}
|
|
138
233
|
config._requestUUID = requestUUID;
|
|
139
|
-
config._startTime = Date.now();
|
|
234
|
+
config._startTime = config._startTime || Date.now();
|
|
140
235
|
const csrfToken = window.csrfToken;
|
|
141
236
|
if (csrfToken) config.headers['X-Suda-Csrf-Token'] = csrfToken;
|
|
142
237
|
return config;
|
|
143
238
|
}, (error)=>Promise.reject(error));
|
|
144
|
-
'production' !== process.env.NODE_ENV &&
|
|
239
|
+
'production' !== process.env.NODE_ENV && instance.interceptors.response.use((response)=>{
|
|
145
240
|
logResponse('success', response);
|
|
146
241
|
return response;
|
|
147
242
|
}, (error)=>{
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
function getEnv() {
|
|
2
|
+
switch(globalThis.ENVIRONMENT){
|
|
3
|
+
case 'staging':
|
|
4
|
+
return 'BOE';
|
|
5
|
+
case 'gray':
|
|
6
|
+
return 'PRE';
|
|
7
|
+
case 'online':
|
|
8
|
+
return 'ONLINE';
|
|
9
|
+
default:
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
2
12
|
const { origin } = window.location;
|
|
3
|
-
if (origin.includes('feishuapp.cn') || origin.includes('miaoda.feishuapp.net')) return 'ONLINE';
|
|
4
|
-
if (origin.includes('fsapp.kundou.cn') || origin.includes('miaoda-pre.feishuapp.net')) return 'PRE';
|
|
13
|
+
if (origin.includes('feishuapp.cn') || origin.includes('miaoda.feishuapp.net') || origin.includes('aiforce.cloud') || origin.includes('aiforce.run')) return 'ONLINE';
|
|
14
|
+
if (origin.includes('fsapp.kundou.cn') || origin.includes('miaoda-pre.feishuapp.net') || origin.includes('aiforce-pre.bytedance.net') || origin.includes('aiforce-pre-preview.bytedance.net') || origin.includes('aiforce-pre.cloud') || origin.includes('aiforce-pre.run')) return 'PRE';
|
|
5
15
|
return 'BOE';
|
|
6
16
|
}
|
|
7
17
|
export { getEnv };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/client-toolkit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"types": "./lib/index.d.ts",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
"require": "./lib/apis/logger.js",
|
|
32
32
|
"types": "./lib/apis/logger.d.ts"
|
|
33
33
|
},
|
|
34
|
+
"./trace": {
|
|
35
|
+
"import": "./lib/apis/trace.js",
|
|
36
|
+
"require": "./lib/apis/trace.js",
|
|
37
|
+
"types": "./lib/apis/trace.d.ts"
|
|
38
|
+
},
|
|
34
39
|
"./udt-types": {
|
|
35
40
|
"import": "./lib/apis/udt-types.js",
|
|
36
41
|
"require": "./lib/apis/udt-types.js",
|
|
@@ -60,6 +65,11 @@
|
|
|
60
65
|
"import": "./lib/apis/utils/*.js",
|
|
61
66
|
"require": "./lib/apis/utils/*.js",
|
|
62
67
|
"types": "./lib/apis/utils/*.d.ts"
|
|
68
|
+
},
|
|
69
|
+
"./auth": {
|
|
70
|
+
"import": "./lib/auth.js",
|
|
71
|
+
"require": "./lib/auth.js",
|
|
72
|
+
"types": "./lib/auth.d.ts"
|
|
63
73
|
}
|
|
64
74
|
},
|
|
65
75
|
"scripts": {
|
|
@@ -81,9 +91,10 @@
|
|
|
81
91
|
"@ant-design/colors": "^7.2.1",
|
|
82
92
|
"@ant-design/cssinjs": "^1.24.0",
|
|
83
93
|
"@data-loom/js": "^0.4.3",
|
|
94
|
+
"@lark-apaas/auth-sdk": "^0.1.0",
|
|
84
95
|
"@lark-apaas/client-capability": "^0.1.2",
|
|
85
|
-
"@lark-apaas/miaoda-inspector": "^1.0.
|
|
86
|
-
"@lark-apaas/observable-web": "^1.0.
|
|
96
|
+
"@lark-apaas/miaoda-inspector": "^1.0.13",
|
|
97
|
+
"@lark-apaas/observable-web": "^1.0.1",
|
|
87
98
|
"@radix-ui/react-avatar": "^1.1.10",
|
|
88
99
|
"@radix-ui/react-popover": "^1.1.15",
|
|
89
100
|
"@radix-ui/react-slot": "^1.2.3",
|
|
@@ -156,6 +167,5 @@
|
|
|
156
167
|
"react-dom": ">=16.14.0",
|
|
157
168
|
"react-router-dom": ">=6.26.2",
|
|
158
169
|
"styled-jsx": ">=5.0.0"
|
|
159
|
-
}
|
|
160
|
-
"gitHead": "10f20a5f5cd6b6fa12524008f8c68c82bc729cc7"
|
|
170
|
+
}
|
|
161
171
|
}
|