@lark-apaas/client-toolkit 1.1.21-alpha.auth.dev.5 → 1.1.21-alpha.auth.dev.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/lib/apis/utils/getAxiosForBackend.d.ts +1 -0
- package/lib/apis/utils/getAxiosForBackend.js +1 -0
- package/lib/components/AppContainer/index.js +40 -1
- package/lib/components/AppContainer/safety.js +16 -16
- package/lib/components/AppContainer/utils/getLarkUser.d.ts +1 -0
- package/lib/components/AppContainer/utils/getLarkUser.js +17 -0
- package/lib/components/AppContainer/utils/tea.d.ts +8 -0
- package/lib/components/AppContainer/utils/tea.js +51 -0
- package/lib/components/ErrorRender/index.js +17 -10
- package/lib/components/User/UserDisplay.d.ts +3 -2
- package/lib/components/User/UserDisplay.js +22 -21
- package/lib/components/User/index.d.ts +1 -1
- package/lib/hooks/useCurrentUserProfile.js +4 -4
- package/lib/server-log/index.d.ts +9 -0
- package/lib/server-log/index.js +2 -0
- package/lib/server-log/poller.d.ts +87 -0
- package/lib/server-log/poller.js +135 -0
- package/lib/server-log/types.d.ts +166 -0
- package/lib/server-log/types.js +0 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/tea.d.ts +7 -0
- package/lib/types/tea.js +5 -0
- package/lib/utils/deviceType.d.ts +3 -0
- package/lib/utils/deviceType.js +13 -0
- package/lib/utils/getAxiosForBackend.d.ts +11 -0
- package/lib/utils/getAxiosForBackend.js +21 -0
- package/lib/utils/getParentOrigin.d.ts +1 -0
- package/lib/utils/getParentOrigin.js +7 -1
- package/lib/utils/requestManager.d.ts +2 -0
- package/lib/utils/requestManager.js +26 -0
- package/lib/utils/userProfileCache.d.ts +12 -0
- package/lib/utils/userProfileCache.js +26 -0
- package/package.json +14 -9
- /package/lib/{auth/index.d.ts → auth.d.ts} +0 -0
- /package/lib/{auth/index.js → auth.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../../utils/getAxiosForBackend';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../utils/getAxiosForBackend.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import { ConfigProvider } from "antd";
|
|
4
4
|
import { MiaodaInspector } from "@lark-apaas/miaoda-inspector";
|
|
5
5
|
import IframeBridge from "./IframeBridge.js";
|
|
@@ -10,8 +10,12 @@ import { findValueByPixel, generateTailwindRadiusToken, themeColorTokenMap, them
|
|
|
10
10
|
import { registerDayjsPlugins } from "./dayjsPlugins.js";
|
|
11
11
|
import "../../index.css";
|
|
12
12
|
import { initAxiosConfig } from "../../utils/axiosConfig.js";
|
|
13
|
+
import { reportTeaEvent } from "./utils/tea.js";
|
|
13
14
|
import { useAppInfo } from "../../hooks/index.js";
|
|
15
|
+
import { TrackKey } from "../../types/tea.js";
|
|
14
16
|
import safety from "./safety.js";
|
|
17
|
+
import { getAppId } from "../../utils/getAppId.js";
|
|
18
|
+
import { ServerLogPoller } from "../../server-log/index.js";
|
|
15
19
|
import { AuthProvider } from "@lark-apaas/auth-sdk";
|
|
16
20
|
registerDayjsPlugins();
|
|
17
21
|
initAxiosConfig();
|
|
@@ -28,6 +32,7 @@ const readCssVarColor = (varName, fallback)=>{
|
|
|
28
32
|
const App = (props)=>{
|
|
29
33
|
const { themeMeta = {}, enableAuth } = props;
|
|
30
34
|
useAppInfo();
|
|
35
|
+
const serverLogPollerRef = useRef(null);
|
|
31
36
|
const { rem } = findValueByPixel(themeMetaOptions.themeRadius, themeMeta.borderRadius) || {
|
|
32
37
|
rem: '0.625'
|
|
33
38
|
};
|
|
@@ -39,6 +44,30 @@ const App = (props)=>{
|
|
|
39
44
|
borderRadius: radiusToken
|
|
40
45
|
}
|
|
41
46
|
};
|
|
47
|
+
useEffect(()=>{
|
|
48
|
+
if ('production' !== process.env.NODE_ENV && window.parent !== window) {
|
|
49
|
+
try {
|
|
50
|
+
const backendUrl = window.location.origin;
|
|
51
|
+
serverLogPollerRef.current = new ServerLogPoller({
|
|
52
|
+
serverUrl: backendUrl,
|
|
53
|
+
apiPath: '/dev/logs/server-logs',
|
|
54
|
+
pollInterval: 2000,
|
|
55
|
+
limit: 100,
|
|
56
|
+
debug: true
|
|
57
|
+
});
|
|
58
|
+
serverLogPollerRef.current.start();
|
|
59
|
+
console.log('[AppContainer] Server log poller started');
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error('[AppContainer] Failed to start server log poller:', error);
|
|
62
|
+
}
|
|
63
|
+
return ()=>{
|
|
64
|
+
if (serverLogPollerRef.current) {
|
|
65
|
+
serverLogPollerRef.current.stop();
|
|
66
|
+
console.log('[AppContainer] Server log poller stopped');
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
42
71
|
useEffect(()=>{
|
|
43
72
|
if (isMiaodaPreview) fetch(`${location.origin}/ai/api/feida_preview/csrf`).then(()=>{
|
|
44
73
|
setTimeout(()=>{
|
|
@@ -47,6 +76,16 @@ const App = (props)=>{
|
|
|
47
76
|
}, 300);
|
|
48
77
|
});
|
|
49
78
|
}, []);
|
|
79
|
+
useEffect(()=>{
|
|
80
|
+
if ('production' === process.env.NODE_ENV) reportTeaEvent({
|
|
81
|
+
trackKey: TrackKey.VIEW,
|
|
82
|
+
trackParams: {
|
|
83
|
+
artifact_uid: getAppId(window.location.pathname),
|
|
84
|
+
agent_id: 'agent_miaoda',
|
|
85
|
+
url: window.location.href
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}, []);
|
|
50
89
|
return /*#__PURE__*/ jsxs(AuthProvider, {
|
|
51
90
|
config: {
|
|
52
91
|
enable: enableAuth
|
|
@@ -44,7 +44,7 @@ const Component = ()=>{
|
|
|
44
44
|
/*#__PURE__*/ jsx(SheetTrigger, {
|
|
45
45
|
asChild: true,
|
|
46
46
|
children: /*#__PURE__*/ jsxs("div", {
|
|
47
|
-
className: "fixed right-
|
|
47
|
+
className: "fixed right-[12px] bottom-[80px] inline-flex items-center gap-x-1 border-solid border-[#ffffff1a] border px-[10px] py-[6px] bg-[#1f2329e5] backdrop-blur-[5px] shadow-[0px_6px_12px_0px_#41444a0a,0px_8px_24px_8px_#41444a0a] rounded-md text-[#ebebeb)] font-['PingFang_SC'] text-xs leading-[20px] tracking-[0px] z-[10000000]",
|
|
48
48
|
onClick: ()=>{
|
|
49
49
|
setOpen(true);
|
|
50
50
|
},
|
|
@@ -64,10 +64,10 @@ const Component = ()=>{
|
|
|
64
64
|
side: "bottom",
|
|
65
65
|
className: "z-[10000001] border-none bg-transparent outline-0!",
|
|
66
66
|
children: /*#__PURE__*/ jsxs("div", {
|
|
67
|
-
className: "flex flex-col bg-white overflow-hidden rounded-t-
|
|
67
|
+
className: "flex flex-col bg-white overflow-hidden rounded-t-[16px] relative",
|
|
68
68
|
children: [
|
|
69
69
|
/*#__PURE__*/ jsx(X, {
|
|
70
|
-
className: "absolute top-
|
|
70
|
+
className: "absolute top-[8px] left-[16px] size-[24px] text-[#2B2F36]",
|
|
71
71
|
onClick: ()=>setOpen(false)
|
|
72
72
|
}),
|
|
73
73
|
/*#__PURE__*/ jsx("img", {
|
|
@@ -79,7 +79,7 @@ const Component = ()=>{
|
|
|
79
79
|
}
|
|
80
80
|
}),
|
|
81
81
|
/*#__PURE__*/ jsxs("div", {
|
|
82
|
-
className: "flex flex-col w-full justify-center items-end gap-y-
|
|
82
|
+
className: "flex flex-col w-full justify-center items-end gap-y-[16px] border-solid border-[#ffffff0d] border px-[20px] pt-[16px] pb-[48px] shadow-(--shadow-2xs,0px_2px_8px_2px_var(--shadow-2xs-1-color,#1f232905),0px_2px_8px_2px_var(--shadow-2xs-1-color,#1f232905),0px_2px_4px_0px_var(--shadow-2xs-1-color,#1f232905)) rounded-t-[12px] text-[#a6a6a6] font-['PingFang_SC'] text-[12px] leading-[20px] tracking-[0px]",
|
|
83
83
|
children: [
|
|
84
84
|
/*#__PURE__*/ jsxs("div", {
|
|
85
85
|
className: "self-stretch shrink-0 flex flex-col items-start gap-y-[4px]",
|
|
@@ -89,7 +89,7 @@ const Component = ()=>{
|
|
|
89
89
|
children: [
|
|
90
90
|
/*#__PURE__*/ jsx("img", {
|
|
91
91
|
src: "https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/icon/icon_company_outlined.svg",
|
|
92
|
-
className: "shrink-0 w-
|
|
92
|
+
className: "shrink-0 w-[14px] h-[14px]"
|
|
93
93
|
}),
|
|
94
94
|
/*#__PURE__*/ jsxs("p", {
|
|
95
95
|
className: "shrink-0 min-w-[96px] m-0! text-[#646A73] text-sm",
|
|
@@ -105,7 +105,7 @@ const Component = ()=>{
|
|
|
105
105
|
children: [
|
|
106
106
|
/*#__PURE__*/ jsx("img", {
|
|
107
107
|
src: "https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/icon/icon_efficiency-ai_outlined.svg",
|
|
108
|
-
className: "shrink-0 w-
|
|
108
|
+
className: "shrink-0 w-[14px] h-[14px]"
|
|
109
109
|
}),
|
|
110
110
|
/*#__PURE__*/ jsx("p", {
|
|
111
111
|
className: "shrink-0 min-w-[163px] m-0! text-[#646A73] text-sm",
|
|
@@ -116,10 +116,10 @@ const Component = ()=>{
|
|
|
116
116
|
]
|
|
117
117
|
}),
|
|
118
118
|
/*#__PURE__*/ jsxs("div", {
|
|
119
|
-
className: "self-stretch shrink-0 flex items-start gap-x-
|
|
119
|
+
className: "self-stretch shrink-0 flex items-start gap-x-[16px]",
|
|
120
120
|
children: [
|
|
121
121
|
/*#__PURE__*/ jsx("div", {
|
|
122
|
-
className: "flex-1 flex rounded-[99px] items-center justify-center border-[0.5px] border-[#D0D3D6] bg-white text-[#1F2329] cursor-pointer text-lg py-
|
|
122
|
+
className: "flex-1 flex rounded-[99px] items-center justify-center border-[0.5px] border-[#D0D3D6] bg-white text-[#1F2329] cursor-pointer text-lg py-[12px]",
|
|
123
123
|
onClick: (e)=>{
|
|
124
124
|
e.stopPropagation();
|
|
125
125
|
e.preventDefault();
|
|
@@ -130,7 +130,7 @@ const Component = ()=>{
|
|
|
130
130
|
children: "不再展示"
|
|
131
131
|
}),
|
|
132
132
|
/*#__PURE__*/ jsx("div", {
|
|
133
|
-
className: "flex-1 flex rounded-[99px] items-center justify-center border-[0.5px] border-black bg-black text-white cursor-pointer text-lg py-
|
|
133
|
+
className: "flex-1 flex rounded-[99px] items-center justify-center border-[0.5px] border-black bg-black text-white cursor-pointer text-lg py-[12px]",
|
|
134
134
|
onClick: ()=>{
|
|
135
135
|
window.open('https://miaoda.feishu.cn/landing', '_blank');
|
|
136
136
|
},
|
|
@@ -152,7 +152,7 @@ const Component = ()=>{
|
|
|
152
152
|
/*#__PURE__*/ jsx(PopoverTrigger, {
|
|
153
153
|
asChild: true,
|
|
154
154
|
children: /*#__PURE__*/ jsxs("div", {
|
|
155
|
-
className: "fixed right-
|
|
155
|
+
className: "fixed right-[12px] bottom-[12px] inline-flex items-center gap-x-1 border-solid border-[#ffffff1a] border px-[10px] py-[6px] bg-[#1f2329e5] backdrop-blur-[5px] shadow-[0px_6px_12px_0px_#41444a0a,0px_8px_24px_8px_#41444a0a] rounded-[6px] text-[#ebebeb)] font-['PingFang_SC'] text-xs leading-[20px] tracking-[0px] z-[10000000] cursor-pointer",
|
|
156
156
|
onMouseEnter: ()=>{
|
|
157
157
|
clearTimeout(timeoutRef.current);
|
|
158
158
|
setOpen(true);
|
|
@@ -173,7 +173,7 @@ const Component = ()=>{
|
|
|
173
173
|
})
|
|
174
174
|
}),
|
|
175
175
|
/*#__PURE__*/ jsx(PopoverContent, {
|
|
176
|
-
className: "overflow-hidden p-0 m-0 border-0 rounded-[12px]!",
|
|
176
|
+
className: "overflow-hidden p-0 m-0 border-0 rounded-[12px]! w-[286px]",
|
|
177
177
|
style: {
|
|
178
178
|
boxShadow: '0 6px 12px 0 #41444a0a, 0 8px 24px 0 #41444a0a'
|
|
179
179
|
},
|
|
@@ -188,18 +188,18 @@ const Component = ()=>{
|
|
|
188
188
|
timeoutRef.current = setTimeout(()=>setOpen(false), 100);
|
|
189
189
|
},
|
|
190
190
|
children: /*#__PURE__*/ jsxs("div", {
|
|
191
|
-
className: "flex flex-col bg-[#
|
|
191
|
+
className: "flex flex-col bg-[#1A1A1A]",
|
|
192
192
|
children: [
|
|
193
193
|
/*#__PURE__*/ jsx("img", {
|
|
194
194
|
src: "https://lf3-static.bytednsdoc.com/obj/eden-cn/LMfspH/ljhwZthlaukjlkulzlp/logo/miaodacover.png",
|
|
195
195
|
alt: "",
|
|
196
|
-
className: "w-
|
|
196
|
+
className: "w-[286px] h-[128px] cursor-pointer",
|
|
197
197
|
onClick: ()=>{
|
|
198
198
|
window.open('https://miaoda.feishu.cn/landing', '_blank');
|
|
199
199
|
}
|
|
200
200
|
}),
|
|
201
201
|
/*#__PURE__*/ jsxs("div", {
|
|
202
|
-
className: "flex flex-col justify-center items-end gap-y-[12px] border-solid border-[#ffffff0d] border pl-[14px] pr-[15px] pt-[11px] pb-[15px] w-[286px] shadow-(--shadow-2xs,0px_2px_8px_2px_var(--shadow-2xs-1-color,#1f232905),0px_2px_8px_2px_var(--shadow-2xs-1-color,#1f232905),0px_2px_4px_0px_var(--shadow-2xs-1-color,#1f232905)) rounded-t-[12px] text-[#a6a6a6] font-['PingFang_SC'] text-[12px] leading-[20px] tracking-[0px]",
|
|
202
|
+
className: "flex flex-col justify-center items-end gap-y-[12px] border-solid border-[#ffffff0d] border pl-[14px] pr-[15px] pt-[11px] pb-[15px] w-[286px] shadow-(--shadow-2xs,0px_2px_8px_2px_var(--shadow-2xs-1-color,#1f232905),0px_2px_8px_2px_var(--shadow-2xs-1-color,#1f232905),0px_2px_4px_0px_var(--shadow-2xs-1-color,#1f232905)) rounded-t-[12px] text-[#a6a6a6] font-['PingFang_SC'] text-[12px] leading-[20px] tracking-[0px] bg-[#1f2021]",
|
|
203
203
|
children: [
|
|
204
204
|
/*#__PURE__*/ jsxs("div", {
|
|
205
205
|
className: "self-stretch shrink-0 flex flex-col items-start gap-y-[4px]",
|
|
@@ -236,10 +236,10 @@ const Component = ()=>{
|
|
|
236
236
|
]
|
|
237
237
|
}),
|
|
238
238
|
/*#__PURE__*/ jsxs("div", {
|
|
239
|
-
className: "self-stretch shrink-0 flex items-start gap-x-[8px]",
|
|
239
|
+
className: "w-full self-stretch shrink-0 flex items-start gap-x-[8px]",
|
|
240
240
|
children: [
|
|
241
241
|
/*#__PURE__*/ jsx("div", {
|
|
242
|
-
className: "flex-1 flex rounded-lg
|
|
242
|
+
className: "flex-1 flex rounded-lg items-center justify-center h-[34px] border-[0.5px] border-solid border-[#ffffff1c] hover:border-[#ffffff33] bg-[#ffffff08] hover:bg-[#ffffff14] cursor-pointer text-[#ebebeb]",
|
|
243
243
|
"data-custom-element": "safety-close",
|
|
244
244
|
onClick: (e)=>{
|
|
245
245
|
e.stopPropagation();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getLarkUserInfo(): Promise<any>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getAppId } from "../../../utils/getAppId.js";
|
|
2
|
+
import { getCsrfToken } from "../../../utils/getCsrfToken.js";
|
|
3
|
+
async function getLarkUserInfo() {
|
|
4
|
+
const appId = getAppId(window.location.pathname);
|
|
5
|
+
if (!appId) return {
|
|
6
|
+
code: 1,
|
|
7
|
+
msg: 'appId is required',
|
|
8
|
+
data: {}
|
|
9
|
+
};
|
|
10
|
+
const response = await fetch(`/spark/b/${appId}/lark/user_info`, {
|
|
11
|
+
headers: {
|
|
12
|
+
'X-Suda-Csrf-Token': getCsrfToken()
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return await response.json();
|
|
16
|
+
}
|
|
17
|
+
export { getLarkUserInfo };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type TrackParams } from '../../../types/tea';
|
|
2
|
+
export declare const encryptTea: (message: any) => string;
|
|
3
|
+
/**
|
|
4
|
+
* 初始化Tea 客户端
|
|
5
|
+
* @export
|
|
6
|
+
*/
|
|
7
|
+
export declare function createTracker(): Promise<void>;
|
|
8
|
+
export declare const reportTeaEvent: ({ trackKey, trackParams }: TrackParams) => Promise<void>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import blueimp_md5 from "blueimp-md5";
|
|
2
|
+
import sha1 from "crypto-js/sha1";
|
|
3
|
+
import { isMobile } from "../../../utils/deviceType.js";
|
|
4
|
+
import { getEnv } from "../../../utils/getParentOrigin.js";
|
|
5
|
+
import { getLarkUserInfo } from "./getLarkUser.js";
|
|
6
|
+
const saltA = '08a441';
|
|
7
|
+
const saltB = '42b91e';
|
|
8
|
+
const encryptTea = (message)=>{
|
|
9
|
+
const msgString = String(message);
|
|
10
|
+
return sha1(saltA + blueimp_md5(msgString + saltB)).toString();
|
|
11
|
+
};
|
|
12
|
+
let teaInstance = false;
|
|
13
|
+
async function createTracker() {
|
|
14
|
+
const { data } = await getLarkUserInfo();
|
|
15
|
+
const { tenantID, userID } = data || {};
|
|
16
|
+
const userIDEncrypt = userID && '0' !== userID ? encryptTea(userID) : void 0;
|
|
17
|
+
const tenantIDEncrypt = tenantID && '0' !== tenantID ? encryptTea(tenantID) : void 0;
|
|
18
|
+
window.collectEvent('init', {
|
|
19
|
+
app_id: 672575,
|
|
20
|
+
channel: 'cn',
|
|
21
|
+
disable_auto_pv: false,
|
|
22
|
+
enable_ab_test: false,
|
|
23
|
+
enable_debug: false,
|
|
24
|
+
enable_stay_duration: true,
|
|
25
|
+
reportTime: 1000
|
|
26
|
+
});
|
|
27
|
+
window.collectEvent('config', {
|
|
28
|
+
user_unique_id: userIDEncrypt,
|
|
29
|
+
device_type: isMobile() ? 'mobile' : 'pc',
|
|
30
|
+
evtParams: {
|
|
31
|
+
open_from: new URLSearchParams(window.location.search).get('open-from') ?? void 0,
|
|
32
|
+
env: getEnv(),
|
|
33
|
+
user_id: userIDEncrypt,
|
|
34
|
+
tenant_id: tenantIDEncrypt
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
teaInstance = true;
|
|
38
|
+
window.collectEvent('start');
|
|
39
|
+
}
|
|
40
|
+
const reportTeaEvent = async ({ trackKey, trackParams = {} })=>{
|
|
41
|
+
try {
|
|
42
|
+
if (!teaInstance) {
|
|
43
|
+
teaInstance = true;
|
|
44
|
+
await createTracker();
|
|
45
|
+
}
|
|
46
|
+
window.collectEvent(trackKey, trackParams);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(err);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
export { createTracker, encryptTea, reportTeaEvent };
|
|
@@ -2,19 +2,26 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect } from "react";
|
|
3
3
|
import { logger } from "../../logger/index.js";
|
|
4
4
|
import { createApplyHandle, getModuleHot } from "../../utils/module-hot.js";
|
|
5
|
+
import { submitPostMessage } from "../../utils/postMessage.js";
|
|
5
6
|
const RenderError = (props)=>{
|
|
6
7
|
const { error, resetErrorBoundary } = props;
|
|
7
8
|
useEffect(()=>{
|
|
8
|
-
if (error)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
if (error) {
|
|
10
|
+
submitPostMessage({
|
|
11
|
+
type: 'RenderError',
|
|
12
|
+
data: error
|
|
13
|
+
});
|
|
14
|
+
logger.log({
|
|
15
|
+
level: 'error',
|
|
16
|
+
args: [
|
|
17
|
+
'Render Error',
|
|
18
|
+
error
|
|
19
|
+
],
|
|
20
|
+
meta: {
|
|
21
|
+
type: 'render-error'
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
18
25
|
}, [
|
|
19
26
|
error
|
|
20
27
|
]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { IUserProfile } from '../../apis/udt-types';
|
|
3
|
-
export interface
|
|
3
|
+
export interface IUserDisplayProps {
|
|
4
4
|
/**
|
|
5
5
|
* 支持传入完整的用户资料(IUserProfile)/ 列表(IUserProfile[])
|
|
6
6
|
* 或仅传入用户 ID(string)/ 列表(string[])。
|
|
@@ -11,5 +11,6 @@ export interface UserDisplayProps {
|
|
|
11
11
|
className?: string;
|
|
12
12
|
style?: React.CSSProperties;
|
|
13
13
|
showLabel?: boolean;
|
|
14
|
+
showUserProfile?: boolean;
|
|
14
15
|
}
|
|
15
|
-
export declare const UserDisplay: React.FC<
|
|
16
|
+
export declare const UserDisplay: React.FC<IUserDisplayProps>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { getCachedUserProfiles, setCachedUserProfiles } from "../../utils/userProfileCache.js";
|
|
4
4
|
import { clsxWithTw } from "../../utils/utils.js";
|
|
5
5
|
import { UserProfile } from "./UserProfile/index.js";
|
|
6
6
|
import { UserWithAvatar } from "./UserWithAvatar.js";
|
|
7
7
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
|
|
8
|
+
import { fetchWithDeduplication } from "../../utils/requestManager.js";
|
|
8
9
|
const UserDisplay = ({ users, size, className, style, showLabel = true })=>{
|
|
9
10
|
const normalizedIds = useMemo(()=>{
|
|
10
11
|
if (!users) return [];
|
|
@@ -71,22 +72,20 @@ const UserDisplay = ({ users, size, className, style, showLabel = true })=>{
|
|
|
71
72
|
const fetchProfiles = async ()=>{
|
|
72
73
|
try {
|
|
73
74
|
setLoadingProfiles(true);
|
|
74
|
-
const
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const response = await dataloom.service.user.getByIds(ids);
|
|
89
|
-
const fetchedList = Array.isArray(response?.data) ? response?.data : Array.isArray(response?.data?.user_list) ? response?.data?.user_list : [];
|
|
75
|
+
const { cachedProfiles, remainingIds } = getCachedUserProfiles(normalizedIds);
|
|
76
|
+
if (cachedProfiles.length > 0) setResolvedUsers((prev)=>{
|
|
77
|
+
const updatedUsers = [
|
|
78
|
+
...prev
|
|
79
|
+
];
|
|
80
|
+
cachedProfiles.forEach((cached)=>{
|
|
81
|
+
const index = updatedUsers.findIndex((u)=>u.user_id === cached.user_id);
|
|
82
|
+
if (-1 !== index) updatedUsers[index] = cached;
|
|
83
|
+
});
|
|
84
|
+
return updatedUsers;
|
|
85
|
+
});
|
|
86
|
+
if (0 === remainingIds.length) return void setLoadingProfiles(false);
|
|
87
|
+
const fetchedList = await fetchWithDeduplication(remainingIds);
|
|
88
|
+
if (isCancelled || !fetchedList) return;
|
|
90
89
|
const fetchedMap = new Map();
|
|
91
90
|
fetchedList.forEach((profile)=>{
|
|
92
91
|
const id = String(profile?.user_id ?? '');
|
|
@@ -99,13 +98,15 @@ const UserDisplay = ({ users, size, className, style, showLabel = true })=>{
|
|
|
99
98
|
status: profile?.status ?? 1
|
|
100
99
|
});
|
|
101
100
|
});
|
|
101
|
+
setCachedUserProfiles(fetchedList);
|
|
102
102
|
const mergedUsers = normalizedIds.map((id)=>{
|
|
103
103
|
const fetched = fetchedMap.get(id);
|
|
104
104
|
const given = inputProfilesMap.get(id);
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
105
|
+
const cached = cachedProfiles.find((p)=>p.user_id === id);
|
|
106
|
+
const name = given?.name?.trim() || fetched?.name || cached?.name || '';
|
|
107
|
+
const avatar = given?.avatar || fetched?.avatar || cached?.avatar || '';
|
|
108
|
+
const email = given?.email || fetched?.email || cached?.email || '';
|
|
109
|
+
const status = given?.status ?? fetched?.status ?? cached?.status ?? 1;
|
|
109
110
|
return {
|
|
110
111
|
user_id: id,
|
|
111
112
|
name,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { UserSelect } from './UserSelect';
|
|
2
2
|
export type { UserSelectProps } from './UserSelect';
|
|
3
3
|
export { UserDisplay } from './UserDisplay';
|
|
4
|
-
export type { UserDisplayProps } from './UserDisplay';
|
|
4
|
+
export type { IUserDisplayProps as UserDisplayProps } from './UserDisplay';
|
|
5
5
|
export { UserWithAvatar } from './UserWithAvatar';
|
|
6
6
|
export type { UserWithAvatarProps } from './type';
|
|
7
7
|
export { UserProfile } from './UserProfile';
|
|
@@ -19,9 +19,9 @@ const useCurrentUserProfile = ()=>{
|
|
|
19
19
|
(async ()=>{
|
|
20
20
|
const dataloom = await getDataloom();
|
|
21
21
|
const result = await dataloom.service.session.getUserInfo();
|
|
22
|
-
const userInfo = result?.data?.
|
|
22
|
+
const userInfo = result?.data?.userInfo;
|
|
23
23
|
setUserInfo({
|
|
24
|
-
user_id: userInfo?.
|
|
24
|
+
user_id: userInfo?.userID?.toString(),
|
|
25
25
|
email: userInfo?.email,
|
|
26
26
|
name: userInfo?.name?.[0]?.text,
|
|
27
27
|
avatar: userInfo?.avatar?.image?.large,
|
|
@@ -32,9 +32,9 @@ const useCurrentUserProfile = ()=>{
|
|
|
32
32
|
handleMetaInfoChanged = async ()=>{
|
|
33
33
|
const dataloom = await getDataloom();
|
|
34
34
|
const result = await dataloom.service.session.getUserInfo();
|
|
35
|
-
const userInfo = result?.data?.
|
|
35
|
+
const userInfo = result?.data?.userInfo;
|
|
36
36
|
const newUserInfo = {
|
|
37
|
-
user_id: userInfo?.
|
|
37
|
+
user_id: userInfo?.userID?.toString(),
|
|
38
38
|
email: userInfo?.email,
|
|
39
39
|
name: userInfo?.name?.[0]?.text,
|
|
40
40
|
avatar: userInfo?.avatar?.image?.large,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Log 模块
|
|
3
|
+
*
|
|
4
|
+
* 通过 HTTP 轮询方式获取服务端日志并转发给父窗口
|
|
5
|
+
*/
|
|
6
|
+
export { ServerLogPoller } from './poller';
|
|
7
|
+
export type { ServerLogPollerOptions } from './poller';
|
|
8
|
+
export type { ServerLog, ServerLogLevel, ServerLogSource, ServerLogMeta, ServerLogPostMessage, ClientToServerMessage, ServerToClientMessage, } from './types';
|
|
9
|
+
export { ServerLogPoller as default } from './poller';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Log Poller (HTTP 轮询版本)
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 定期轮询后端 HTTP API (/dev/logs/server-logs)
|
|
6
|
+
* 2. 获取新的服务端日志
|
|
7
|
+
* 3. 将接收到的日志通过 postMessage 转发给父窗口 (miaoda)
|
|
8
|
+
* 4. 管理连接状态并通知父窗口
|
|
9
|
+
*
|
|
10
|
+
* 与 ServerLogForwarder 的区别:
|
|
11
|
+
* - 使用 HTTP 轮询代替 WebSocket
|
|
12
|
+
* - 无需 socket.io-client 依赖
|
|
13
|
+
* - 更简单、更稳定、更易调试
|
|
14
|
+
*/
|
|
15
|
+
export interface ServerLogPollerOptions {
|
|
16
|
+
/**
|
|
17
|
+
* 后端服务器 URL
|
|
18
|
+
* @example 'http://localhost:3000'
|
|
19
|
+
*/
|
|
20
|
+
serverUrl: string;
|
|
21
|
+
/**
|
|
22
|
+
* API 路径
|
|
23
|
+
* @default '/dev/logs/server-logs'
|
|
24
|
+
*/
|
|
25
|
+
apiPath?: string;
|
|
26
|
+
/**
|
|
27
|
+
* 轮询间隔(毫秒)
|
|
28
|
+
* @default 2000
|
|
29
|
+
*/
|
|
30
|
+
pollInterval?: number;
|
|
31
|
+
/**
|
|
32
|
+
* 每次获取的日志数量
|
|
33
|
+
* @default 100
|
|
34
|
+
*/
|
|
35
|
+
limit?: number;
|
|
36
|
+
/**
|
|
37
|
+
* 是否启用调试日志
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
debug?: boolean;
|
|
41
|
+
}
|
|
42
|
+
type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
|
|
43
|
+
export declare class ServerLogPoller {
|
|
44
|
+
private intervalId;
|
|
45
|
+
private status;
|
|
46
|
+
private lastTimestamp;
|
|
47
|
+
private options;
|
|
48
|
+
private isPolling;
|
|
49
|
+
constructor(options: ServerLogPollerOptions);
|
|
50
|
+
/**
|
|
51
|
+
* 启动轮询器
|
|
52
|
+
*/
|
|
53
|
+
start(): void;
|
|
54
|
+
/**
|
|
55
|
+
* 停止轮询器
|
|
56
|
+
*/
|
|
57
|
+
stop(): void;
|
|
58
|
+
/**
|
|
59
|
+
* 获取当前连接状态
|
|
60
|
+
*/
|
|
61
|
+
getStatus(): ConnectionStatus;
|
|
62
|
+
/**
|
|
63
|
+
* 执行一次轮询
|
|
64
|
+
*/
|
|
65
|
+
private poll;
|
|
66
|
+
/**
|
|
67
|
+
* 转发日志到父窗口
|
|
68
|
+
*/
|
|
69
|
+
private forwardLog;
|
|
70
|
+
/**
|
|
71
|
+
* 更新连接状态并通知父窗口
|
|
72
|
+
*/
|
|
73
|
+
private updateStatus;
|
|
74
|
+
/**
|
|
75
|
+
* 发送消息到父窗口
|
|
76
|
+
*/
|
|
77
|
+
private postToParent;
|
|
78
|
+
/**
|
|
79
|
+
* 调试日志
|
|
80
|
+
*/
|
|
81
|
+
private log;
|
|
82
|
+
/**
|
|
83
|
+
* 错误日志
|
|
84
|
+
*/
|
|
85
|
+
private error;
|
|
86
|
+
}
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
class ServerLogPoller {
|
|
2
|
+
intervalId = null;
|
|
3
|
+
status = 'disconnected';
|
|
4
|
+
lastTimestamp = 0;
|
|
5
|
+
options;
|
|
6
|
+
isPolling = false;
|
|
7
|
+
constructor(options){
|
|
8
|
+
this.options = {
|
|
9
|
+
serverUrl: options.serverUrl,
|
|
10
|
+
apiPath: (process.env.CLIENT_BASE_PATH || '') + (options.apiPath || '/dev/logs/server-logs'),
|
|
11
|
+
pollInterval: options.pollInterval || 2000,
|
|
12
|
+
limit: options.limit || 100,
|
|
13
|
+
debug: options.debug ?? false
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
start() {
|
|
17
|
+
if (this.isPolling) return void this.log('Poller already started');
|
|
18
|
+
this.isPolling = true;
|
|
19
|
+
this.updateStatus('connecting');
|
|
20
|
+
this.log('Starting server log poller...', {
|
|
21
|
+
serverUrl: this.options.serverUrl,
|
|
22
|
+
apiPath: this.options.apiPath,
|
|
23
|
+
pollInterval: this.options.pollInterval
|
|
24
|
+
});
|
|
25
|
+
this.poll();
|
|
26
|
+
this.intervalId = window.setInterval(()=>{
|
|
27
|
+
this.poll();
|
|
28
|
+
}, this.options.pollInterval);
|
|
29
|
+
}
|
|
30
|
+
stop() {
|
|
31
|
+
if (!this.isPolling) return void this.log('Poller not running');
|
|
32
|
+
this.log('Stopping server log poller...');
|
|
33
|
+
if (null !== this.intervalId) {
|
|
34
|
+
window.clearInterval(this.intervalId);
|
|
35
|
+
this.intervalId = null;
|
|
36
|
+
}
|
|
37
|
+
this.isPolling = false;
|
|
38
|
+
this.updateStatus('disconnected');
|
|
39
|
+
}
|
|
40
|
+
getStatus() {
|
|
41
|
+
return this.status;
|
|
42
|
+
}
|
|
43
|
+
async poll() {
|
|
44
|
+
try {
|
|
45
|
+
const url = `${this.options.serverUrl}${this.options.apiPath}?limit=${this.options.limit}&sources=server,trace,server-std,client-std`;
|
|
46
|
+
this.log('Polling...', {
|
|
47
|
+
url
|
|
48
|
+
});
|
|
49
|
+
const response = await fetch(url, {
|
|
50
|
+
method: 'GET',
|
|
51
|
+
headers: {
|
|
52
|
+
Accept: 'application/json'
|
|
53
|
+
},
|
|
54
|
+
signal: AbortSignal.timeout(5000)
|
|
55
|
+
});
|
|
56
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
this.log('Received logs', {
|
|
59
|
+
count: data.logs.length,
|
|
60
|
+
total: data.total,
|
|
61
|
+
hasMore: data.hasMore
|
|
62
|
+
});
|
|
63
|
+
const newLogs = data.logs.filter((log)=>log.timestamp > this.lastTimestamp);
|
|
64
|
+
if (newLogs.length > 0) {
|
|
65
|
+
this.lastTimestamp = Math.max(...newLogs.map((log)=>log.timestamp));
|
|
66
|
+
newLogs.sort((a, b)=>a.timestamp - b.timestamp);
|
|
67
|
+
this.log(`Forwarding ${newLogs.length} new logs`);
|
|
68
|
+
newLogs.forEach((log)=>{
|
|
69
|
+
this.forwardLog(log);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if ('connected' !== this.status) {
|
|
73
|
+
this.updateStatus('connected');
|
|
74
|
+
if (0 === this.lastTimestamp && data.logs.length > 0) this.lastTimestamp = Math.max(...data.logs.map((log)=>log.timestamp));
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
this.error('Poll failed', error);
|
|
78
|
+
if ('error' !== this.status) {
|
|
79
|
+
this.updateStatus('error');
|
|
80
|
+
this.postToParent({
|
|
81
|
+
type: 'SERVER_LOG_CONNECTION',
|
|
82
|
+
status: 'error',
|
|
83
|
+
error: error instanceof Error ? error.message : String(error)
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
forwardLog(log) {
|
|
89
|
+
try {
|
|
90
|
+
this.log('Forwarding log to parent window', {
|
|
91
|
+
type: 'SERVER_LOG',
|
|
92
|
+
logId: log.id,
|
|
93
|
+
level: log.level,
|
|
94
|
+
tags: log.tags
|
|
95
|
+
});
|
|
96
|
+
this.postToParent({
|
|
97
|
+
type: 'SERVER_LOG',
|
|
98
|
+
payload: JSON.stringify(log)
|
|
99
|
+
});
|
|
100
|
+
this.log('Log forwarded successfully');
|
|
101
|
+
} catch (e) {
|
|
102
|
+
this.error('Failed to forward log', e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
updateStatus(status) {
|
|
106
|
+
const previousStatus = this.status;
|
|
107
|
+
this.status = status;
|
|
108
|
+
if (previousStatus !== status) {
|
|
109
|
+
this.log(`Status changed: ${previousStatus} → ${status}`);
|
|
110
|
+
this.postToParent({
|
|
111
|
+
type: 'SERVER_LOG_CONNECTION',
|
|
112
|
+
status
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
postToParent(message) {
|
|
117
|
+
if (window.parent === window) return;
|
|
118
|
+
try {
|
|
119
|
+
window.parent.postMessage(message, '*');
|
|
120
|
+
} catch (e) {
|
|
121
|
+
this.error('postMessage error', e);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
log(message, data) {
|
|
125
|
+
const enableLog = 'true' === localStorage.getItem('debug_server_log_poller');
|
|
126
|
+
if (this.options.debug && enableLog) if (data) console.log(`[ServerLogPoller] ${message}`, data);
|
|
127
|
+
else console.log(`[ServerLogPoller] ${message}`);
|
|
128
|
+
}
|
|
129
|
+
error(message, error) {
|
|
130
|
+
const enableLog = 'true' === localStorage.getItem('debug_server_log_poller');
|
|
131
|
+
if (enableLog) if (error) console.error(`[ServerLogPoller] ${message}`, error);
|
|
132
|
+
else console.error(`[ServerLogPoller] ${message}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export { ServerLogPoller };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Log Stream 类型定义
|
|
3
|
+
*
|
|
4
|
+
* 这些类型与 @lark-apaas/nestjs-logger 保持一致
|
|
5
|
+
* 用于 iframe 端的日志转发功能
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 后端日志级别
|
|
9
|
+
* 与 NestJS LogLevel 对齐,但增加了 fatal
|
|
10
|
+
*/
|
|
11
|
+
export type ServerLogLevel = 'fatal' | 'error' | 'warn' | 'log' | 'debug' | 'verbose';
|
|
12
|
+
/**
|
|
13
|
+
* 日志来源
|
|
14
|
+
*/
|
|
15
|
+
export type ServerLogSource = 'server' | 'trace' | 'server-std' | 'client-std' | 'browser';
|
|
16
|
+
/**
|
|
17
|
+
* 日志元数据
|
|
18
|
+
*/
|
|
19
|
+
export interface ServerLogMeta {
|
|
20
|
+
pid?: number;
|
|
21
|
+
hostname?: string;
|
|
22
|
+
service?: string;
|
|
23
|
+
path?: string;
|
|
24
|
+
method?: string;
|
|
25
|
+
statusCode?: number;
|
|
26
|
+
durationMs?: number;
|
|
27
|
+
ip?: string;
|
|
28
|
+
requestBody?: any;
|
|
29
|
+
responseBody?: any;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 后端日志对象
|
|
34
|
+
* 用于实时推送给前端展示
|
|
35
|
+
*/
|
|
36
|
+
export interface ServerLog {
|
|
37
|
+
/**
|
|
38
|
+
* 唯一标识符(UUID)
|
|
39
|
+
* 用于前端去重和引用
|
|
40
|
+
*/
|
|
41
|
+
id: string;
|
|
42
|
+
/**
|
|
43
|
+
* 日志级别
|
|
44
|
+
*/
|
|
45
|
+
level: ServerLogLevel;
|
|
46
|
+
/**
|
|
47
|
+
* 时间戳(毫秒)
|
|
48
|
+
*/
|
|
49
|
+
timestamp: number;
|
|
50
|
+
/**
|
|
51
|
+
* 日志消息
|
|
52
|
+
*/
|
|
53
|
+
message: string;
|
|
54
|
+
/**
|
|
55
|
+
* 日志上下文(如 Controller 名称、Service 名称)
|
|
56
|
+
*/
|
|
57
|
+
context?: string;
|
|
58
|
+
/**
|
|
59
|
+
* 请求追踪 ID
|
|
60
|
+
* 用于关联同一个请求的所有日志
|
|
61
|
+
*/
|
|
62
|
+
traceId?: string;
|
|
63
|
+
/**
|
|
64
|
+
* 用户 ID
|
|
65
|
+
*/
|
|
66
|
+
userId?: string;
|
|
67
|
+
/**
|
|
68
|
+
* 应用 ID
|
|
69
|
+
*/
|
|
70
|
+
appId?: string;
|
|
71
|
+
/**
|
|
72
|
+
* 租户 ID
|
|
73
|
+
*/
|
|
74
|
+
tenantId?: string;
|
|
75
|
+
/**
|
|
76
|
+
* 错误堆栈(仅 ERROR/FATAL 级别)
|
|
77
|
+
*/
|
|
78
|
+
stack?: string;
|
|
79
|
+
/**
|
|
80
|
+
* 额外的元数据
|
|
81
|
+
*/
|
|
82
|
+
meta?: ServerLogMeta;
|
|
83
|
+
/**
|
|
84
|
+
* 自定义标签
|
|
85
|
+
* 如 ['server', 'bootstrap'] 或 ['trace', 'http']
|
|
86
|
+
*/
|
|
87
|
+
tags?: string[];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* WebSocket 消息类型(客户端 → 服务端)
|
|
91
|
+
*/
|
|
92
|
+
export type ClientToServerMessage = {
|
|
93
|
+
type: 'SUBSCRIBE';
|
|
94
|
+
payload: {
|
|
95
|
+
levels?: ServerLogLevel[];
|
|
96
|
+
tags?: string[];
|
|
97
|
+
sources?: ServerLogSource[];
|
|
98
|
+
};
|
|
99
|
+
} | {
|
|
100
|
+
type: 'UNSUBSCRIBE';
|
|
101
|
+
} | {
|
|
102
|
+
type: 'GET_HISTORY';
|
|
103
|
+
payload: {
|
|
104
|
+
limit?: number;
|
|
105
|
+
offset?: number;
|
|
106
|
+
levels?: ServerLogLevel[];
|
|
107
|
+
sources?: ServerLogSource[];
|
|
108
|
+
};
|
|
109
|
+
} | {
|
|
110
|
+
type: 'CLEAR_LOGS';
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* WebSocket 消息类型(服务端 → 客户端)
|
|
114
|
+
*/
|
|
115
|
+
export type ServerToClientMessage = {
|
|
116
|
+
type: 'CONNECTED';
|
|
117
|
+
payload: {
|
|
118
|
+
clientId: string;
|
|
119
|
+
timestamp: number;
|
|
120
|
+
};
|
|
121
|
+
} | {
|
|
122
|
+
type: 'LOG';
|
|
123
|
+
payload: ServerLog;
|
|
124
|
+
} | {
|
|
125
|
+
type: 'LOGS_BATCH';
|
|
126
|
+
payload: ServerLog[];
|
|
127
|
+
} | {
|
|
128
|
+
type: 'HISTORY';
|
|
129
|
+
payload: {
|
|
130
|
+
logs: ServerLog[];
|
|
131
|
+
total: number;
|
|
132
|
+
hasMore: boolean;
|
|
133
|
+
};
|
|
134
|
+
} | {
|
|
135
|
+
type: 'LOGS_CLEARED';
|
|
136
|
+
} | {
|
|
137
|
+
type: 'SUBSCRIBED';
|
|
138
|
+
payload: {
|
|
139
|
+
levels?: ServerLogLevel[];
|
|
140
|
+
tags?: string[];
|
|
141
|
+
sources?: ServerLogSource[];
|
|
142
|
+
};
|
|
143
|
+
} | {
|
|
144
|
+
type: 'UNSUBSCRIBED';
|
|
145
|
+
} | {
|
|
146
|
+
type: 'ERROR';
|
|
147
|
+
payload: {
|
|
148
|
+
message: string;
|
|
149
|
+
code?: string;
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* PostMessage 类型(iframe → parent)
|
|
154
|
+
*
|
|
155
|
+
* iframe 端通过 postMessage 将接收到的 WebSocket 消息转发给父窗口
|
|
156
|
+
*/
|
|
157
|
+
export type ServerLogPostMessage = {
|
|
158
|
+
type: 'SERVER_LOG';
|
|
159
|
+
payload: string;
|
|
160
|
+
} | {
|
|
161
|
+
type: 'SERVER_LOG_CONNECTION';
|
|
162
|
+
status: 'connected' | 'disconnected' | 'error' | 'connecting';
|
|
163
|
+
error?: string;
|
|
164
|
+
} | {
|
|
165
|
+
type: 'SERVER_LOG_CLEARED';
|
|
166
|
+
};
|
|
File without changes
|
package/lib/types/index.d.ts
CHANGED
package/lib/types/tea.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const isIpad = ()=>{
|
|
2
|
+
const _isIpad = /iPad|Tab|Tablet/i.test(navigator.userAgent) && navigator.maxTouchPoints && navigator.maxTouchPoints > 1;
|
|
3
|
+
return Boolean(_isIpad);
|
|
4
|
+
};
|
|
5
|
+
const isMobile = ()=>{
|
|
6
|
+
const isPhone = /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i.test(navigator.userAgent);
|
|
7
|
+
return isPhone || isIpad();
|
|
8
|
+
};
|
|
9
|
+
const isIOS = ()=>{
|
|
10
|
+
if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) return true;
|
|
11
|
+
return false;
|
|
12
|
+
};
|
|
13
|
+
export { isIOS, isIpad, isMobile };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AxiosInstance } from 'axios';
|
|
2
|
+
declare module 'axios' {
|
|
3
|
+
interface AxiosRequestConfig {
|
|
4
|
+
meta?: {
|
|
5
|
+
autoJumpToLogin?: boolean;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** 获取axios实例 */
|
|
10
|
+
export declare function getAxiosForBackend(): AxiosInstance;
|
|
11
|
+
export declare const axiosForBackend: AxiosInstance;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { initAxiosConfig } from "./axiosConfig.js";
|
|
3
|
+
let axiosInstance;
|
|
4
|
+
function getAxiosForBackend() {
|
|
5
|
+
if (!axiosInstance) {
|
|
6
|
+
axiosInstance = axios.create({
|
|
7
|
+
baseURL: process.env.CLIENT_BASE_PATH || '/'
|
|
8
|
+
});
|
|
9
|
+
axiosInstance.interceptors.response.use(null, (err)=>{
|
|
10
|
+
if (err.config.meta?.autoJumpToLogin !== false) {
|
|
11
|
+
const loginUrl = err.response?.headers?.['x-login-url'];
|
|
12
|
+
if (loginUrl) window.location.href = loginUrl;
|
|
13
|
+
}
|
|
14
|
+
return Promise.reject(err);
|
|
15
|
+
});
|
|
16
|
+
initAxiosConfig(axiosInstance);
|
|
17
|
+
}
|
|
18
|
+
return axiosInstance;
|
|
19
|
+
}
|
|
20
|
+
const axiosForBackend = getAxiosForBackend();
|
|
21
|
+
export { axiosForBackend, getAxiosForBackend };
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
function getEnv() {
|
|
2
|
+
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';
|
|
5
|
+
return 'BOE';
|
|
6
|
+
}
|
|
1
7
|
function getPreviewParentOrigin() {
|
|
2
8
|
const { origin } = window.location;
|
|
3
9
|
if (origin.includes('force.byted.org')) return 'https://force.feishu-boe.cn';
|
|
@@ -5,4 +11,4 @@ function getPreviewParentOrigin() {
|
|
|
5
11
|
if (origin.includes('fsapp.kundou.cn') || origin.includes('miaoda-pre.feishuapp.net')) return 'https://miaoda.feishu-pre.cn';
|
|
6
12
|
return 'https://miaoda.feishu-boe.cn';
|
|
7
13
|
}
|
|
8
|
-
export { getPreviewParentOrigin };
|
|
14
|
+
export { getEnv, getPreviewParentOrigin };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getDataloom } from "../integrations/dataloom.js";
|
|
2
|
+
const pendingRequests = new Map();
|
|
3
|
+
async function fetchUserProfilesByIds(ids) {
|
|
4
|
+
try {
|
|
5
|
+
const dataloom = await getDataloom();
|
|
6
|
+
if (!dataloom) throw new Error('Dataloom client is unavailable');
|
|
7
|
+
const numericIds = ids.map(Number).filter(Number.isFinite);
|
|
8
|
+
if (0 === numericIds.length) return [];
|
|
9
|
+
const response = await dataloom.service.user.getByIds(numericIds);
|
|
10
|
+
return Array.isArray(response?.data) ? response.data : response?.data?.user_list || [];
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error(`Failed to fetch profiles for user IDs ${ids.join(', ')}:`, error);
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function fetchWithDeduplication(ids) {
|
|
17
|
+
const key = ids.sort().join(',');
|
|
18
|
+
if (pendingRequests.has(key)) return pendingRequests.get(key);
|
|
19
|
+
const fetchPromise = fetchUserProfilesByIds(ids);
|
|
20
|
+
pendingRequests.set(key, fetchPromise);
|
|
21
|
+
fetchPromise.finally(()=>{
|
|
22
|
+
pendingRequests.delete(key);
|
|
23
|
+
});
|
|
24
|
+
return fetchPromise;
|
|
25
|
+
}
|
|
26
|
+
export { fetchWithDeduplication };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { IUserProfile } from '../apis/udt-types';
|
|
2
|
+
export interface ICachedUserProfile {
|
|
3
|
+
profile: IUserProfile;
|
|
4
|
+
timestamp: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const cache: Map<string, ICachedUserProfile>;
|
|
7
|
+
export declare const cacheExpiry: number;
|
|
8
|
+
export declare function setCachedUserProfiles(profiles: IUserProfile[]): void;
|
|
9
|
+
export declare function getCachedUserProfiles(ids: string[]): {
|
|
10
|
+
cachedProfiles: IUserProfile[];
|
|
11
|
+
remainingIds: string[];
|
|
12
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const cache = new Map();
|
|
2
|
+
const cacheExpiry = 1 / 0;
|
|
3
|
+
function setCachedUserProfiles(profiles) {
|
|
4
|
+
const now = Date.now();
|
|
5
|
+
profiles.forEach((profile)=>{
|
|
6
|
+
if (profile && profile.user_id) cache.set(String(profile.user_id), {
|
|
7
|
+
profile,
|
|
8
|
+
timestamp: now
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function getCachedUserProfiles(ids) {
|
|
13
|
+
const cachedProfiles = [];
|
|
14
|
+
const remainingIds = [];
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
ids.forEach((id)=>{
|
|
17
|
+
const cachedItem = cache.get(id);
|
|
18
|
+
if (cachedItem && now - cachedItem.timestamp < cacheExpiry) cachedProfiles.push(cachedItem.profile);
|
|
19
|
+
else remainingIds.push(id);
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
cachedProfiles,
|
|
23
|
+
remainingIds
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export { cache, cacheExpiry, getCachedUserProfiles, setCachedUserProfiles };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/client-toolkit",
|
|
3
|
-
"version": "1.1.21-alpha.auth.dev.
|
|
3
|
+
"version": "1.1.21-alpha.auth.dev.7",
|
|
4
4
|
"types": "./lib/index.d.ts",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -61,10 +61,10 @@
|
|
|
61
61
|
"require": "./lib/apis/utils/*.js",
|
|
62
62
|
"types": "./lib/apis/utils/*.d.ts"
|
|
63
63
|
},
|
|
64
|
-
"./auth
|
|
65
|
-
"import": "./lib/auth
|
|
66
|
-
"require": "./lib/auth
|
|
67
|
-
"types": "./lib/auth
|
|
64
|
+
"./auth": {
|
|
65
|
+
"import": "./lib/auth.js",
|
|
66
|
+
"require": "./lib/auth.js",
|
|
67
|
+
"types": "./lib/auth.d.ts"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
@@ -85,17 +85,19 @@
|
|
|
85
85
|
"dependencies": {
|
|
86
86
|
"@ant-design/colors": "^7.2.1",
|
|
87
87
|
"@ant-design/cssinjs": "^1.24.0",
|
|
88
|
-
"@data-loom/js": "
|
|
89
|
-
"@lark-apaas/auth-sdk": "0.1.0-alpha.
|
|
90
|
-
"@lark-apaas/miaoda-inspector": "^1.0.
|
|
88
|
+
"@data-loom/js": "0.4.4-auth-alpha.6",
|
|
89
|
+
"@lark-apaas/auth-sdk": "0.1.0-alpha.13",
|
|
90
|
+
"@lark-apaas/miaoda-inspector": "^1.0.5",
|
|
91
91
|
"@radix-ui/react-avatar": "^1.1.10",
|
|
92
92
|
"@radix-ui/react-popover": "^1.1.15",
|
|
93
93
|
"@radix-ui/react-slot": "^1.2.3",
|
|
94
94
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
95
95
|
"@zumer/snapdom": "^1.9.14",
|
|
96
96
|
"axios": "^1.12.2",
|
|
97
|
+
"blueimp-md5": "2.10.0",
|
|
97
98
|
"class-variance-authority": "^0.7.1",
|
|
98
99
|
"clsx": "~2.0.1",
|
|
100
|
+
"crypto-js": "3.1.9-1",
|
|
99
101
|
"dayjs": "^1.11.13",
|
|
100
102
|
"echarts": "^6.0.0",
|
|
101
103
|
"lodash": "^4.17.21",
|
|
@@ -128,6 +130,8 @@
|
|
|
128
130
|
"@tailwindcss/postcss": "^4.1.0",
|
|
129
131
|
"@testing-library/jest-dom": "^6.6.4",
|
|
130
132
|
"@testing-library/react": "^16.3.0",
|
|
133
|
+
"@types/blueimp-md5": "^2.18.2",
|
|
134
|
+
"@types/crypto-js": "^4.2.2",
|
|
131
135
|
"@types/lodash": "^4.17.20",
|
|
132
136
|
"@types/node": "^22.10.2",
|
|
133
137
|
"@types/react": "^18.3.23",
|
|
@@ -153,6 +157,7 @@
|
|
|
153
157
|
"antd": ">=5.26.6",
|
|
154
158
|
"react": ">=16.14.0",
|
|
155
159
|
"react-dom": ">=16.14.0",
|
|
156
|
-
"react-router-dom": ">=6.26.2"
|
|
160
|
+
"react-router-dom": ">=6.26.2",
|
|
161
|
+
"styled-jsx": ">=5.0.0"
|
|
157
162
|
}
|
|
158
163
|
}
|
|
File without changes
|
|
File without changes
|