@lark-apaas/client-toolkit 1.1.23-dataloom-test.2 → 1.1.23-serverlog-test.0

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.
@@ -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";
@@ -15,6 +15,7 @@ import { useAppInfo } from "../../hooks/index.js";
15
15
  import { TrackKey } from "../../types/tea.js";
16
16
  import safety from "./safety.js";
17
17
  import { getAppId } from "../../utils/getAppId.js";
18
+ import { ServerLogForwarder } from "../../server-log/index.js";
18
19
  registerDayjsPlugins();
19
20
  initAxiosConfig();
20
21
  const isMiaodaPreview = window.IS_MIAODA_PREVIEW;
@@ -30,6 +31,7 @@ const readCssVarColor = (varName, fallback)=>{
30
31
  const App = (props)=>{
31
32
  const { themeMeta = {} } = props;
32
33
  useAppInfo();
34
+ const serverLogForwarderRef = useRef(null);
33
35
  const { rem } = findValueByPixel(themeMetaOptions.themeRadius, themeMeta.borderRadius) || {
34
36
  rem: '0.625'
35
37
  };
@@ -41,6 +43,29 @@ const App = (props)=>{
41
43
  borderRadius: radiusToken
42
44
  }
43
45
  };
46
+ useEffect(()=>{
47
+ if ('production' !== process.env.NODE_ENV && window.parent !== window) {
48
+ try {
49
+ const backendUrl = window.location.origin;
50
+ serverLogForwarderRef.current = new ServerLogForwarder({
51
+ serverUrl: backendUrl,
52
+ path: '/api/server-log/socket.io',
53
+ namespace: '/dev/logs/server',
54
+ debug: true
55
+ });
56
+ serverLogForwarderRef.current.start();
57
+ console.log('[AppContainer] Server log forwarder started');
58
+ } catch (error) {
59
+ console.error('[AppContainer] Failed to start server log forwarder:', error);
60
+ }
61
+ return ()=>{
62
+ if (serverLogForwarderRef.current) {
63
+ serverLogForwarderRef.current.stop();
64
+ console.log('[AppContainer] Server log forwarder stopped');
65
+ }
66
+ };
67
+ }
68
+ }, []);
44
69
  useEffect(()=>{
45
70
  if (isMiaodaPreview) fetch(`${location.origin}/ai/api/feida_preview/csrf`).then(()=>{
46
71
  setTimeout(()=>{
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Server Log Forwarder
3
+ *
4
+ * 职责:
5
+ * 1. 连接到后端 WebSocket Server (/dev/logs/server)
6
+ * 2. 订阅所有服务端日志
7
+ * 3. 将接收到的日志通过 postMessage 转发给父窗口 (miaoda)
8
+ * 4. 管理连接状态并通知父窗口
9
+ */
10
+ export interface ServerLogForwarderOptions {
11
+ /**
12
+ * 后端服务器 URL
13
+ * @example 'http://localhost:3000'
14
+ */
15
+ serverUrl: string;
16
+ /**
17
+ * Socket.io 服务器路径
18
+ * @default '/socket.io'
19
+ * @example '/api/server-log/socket.io'
20
+ */
21
+ path?: string;
22
+ /**
23
+ * WebSocket 命名空间
24
+ * @default '/dev/logs/server'
25
+ */
26
+ namespace?: string;
27
+ /**
28
+ * 是否启用调试日志
29
+ * @default false
30
+ */
31
+ debug?: boolean;
32
+ /**
33
+ * 重连配置
34
+ */
35
+ reconnection?: {
36
+ enabled: boolean;
37
+ attempts: number;
38
+ delay: number;
39
+ };
40
+ }
41
+ type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
42
+ export declare class ServerLogForwarder {
43
+ private socket;
44
+ private status;
45
+ private options;
46
+ constructor(options: ServerLogForwarderOptions);
47
+ /**
48
+ * 启动转发器
49
+ */
50
+ start(): void;
51
+ /**
52
+ * 停止转发器
53
+ */
54
+ stop(): void;
55
+ /**
56
+ * 获取当前连接状态
57
+ */
58
+ getStatus(): ConnectionStatus;
59
+ /**
60
+ * 绑定 Socket.io 事件监听器
61
+ */
62
+ private attachEventListeners;
63
+ /**
64
+ * 处理服务端消息
65
+ */
66
+ private handleServerMessage;
67
+ /**
68
+ * 转发日志到父窗口
69
+ */
70
+ private forwardLog;
71
+ /**
72
+ * 更新连接状态并通知父窗口
73
+ */
74
+ private updateStatus;
75
+ /**
76
+ * 发送消息到父窗口
77
+ */
78
+ private postToParent;
79
+ /**
80
+ * 调试日志
81
+ */
82
+ private log;
83
+ /**
84
+ * 错误日志
85
+ */
86
+ private error;
87
+ }
88
+ export {};
@@ -0,0 +1,169 @@
1
+ import { io } from "socket.io-client";
2
+ class ServerLogForwarder {
3
+ socket = null;
4
+ status = 'disconnected';
5
+ options;
6
+ constructor(options){
7
+ this.options = {
8
+ serverUrl: options.serverUrl,
9
+ path: options.path || '/socket.io',
10
+ namespace: options.namespace || '/dev/logs/server',
11
+ debug: options.debug ?? false,
12
+ reconnection: options.reconnection || {
13
+ enabled: true,
14
+ attempts: 5,
15
+ delay: 1000
16
+ }
17
+ };
18
+ }
19
+ start() {
20
+ if (this.socket) return void this.log('Forwarder already started');
21
+ this.updateStatus('connecting');
22
+ this.log('Starting server log forwarder...', {
23
+ serverUrl: this.options.serverUrl,
24
+ path: this.options.path,
25
+ namespace: this.options.namespace
26
+ });
27
+ this.socket = io(`${this.options.serverUrl}${this.options.namespace}`, {
28
+ path: this.options.path,
29
+ transports: [
30
+ 'websocket'
31
+ ],
32
+ reconnection: this.options.reconnection.enabled,
33
+ reconnectionAttempts: this.options.reconnection.attempts,
34
+ reconnectionDelay: this.options.reconnection.delay
35
+ });
36
+ this.attachEventListeners();
37
+ }
38
+ stop() {
39
+ if (!this.socket) return void this.log('Forwarder not running');
40
+ this.log('Stopping server log forwarder...');
41
+ this.socket.emit('UNSUBSCRIBE');
42
+ this.socket.disconnect();
43
+ this.socket = null;
44
+ this.updateStatus('disconnected');
45
+ }
46
+ getStatus() {
47
+ return this.status;
48
+ }
49
+ attachEventListeners() {
50
+ if (!this.socket) return;
51
+ this.socket.on('connect', ()=>{
52
+ this.log('Connected to server log stream');
53
+ this.updateStatus('connected');
54
+ this.socket?.emit('SUBSCRIBE', {});
55
+ });
56
+ this.socket.on('disconnect', (reason)=>{
57
+ this.log('Disconnected from server log stream', {
58
+ reason
59
+ });
60
+ this.updateStatus('disconnected');
61
+ });
62
+ this.socket.on('connect_error', (error)=>{
63
+ this.error('Connection error', error);
64
+ this.updateStatus('error');
65
+ });
66
+ this.socket.on('message', (message)=>{
67
+ this.handleServerMessage(message);
68
+ });
69
+ this.socket.on('reconnect_attempt', (attemptNumber)=>{
70
+ this.log(`Reconnection attempt ${attemptNumber}...`);
71
+ this.updateStatus('connecting');
72
+ });
73
+ this.socket.on('reconnect', (attemptNumber)=>{
74
+ this.log(`Reconnected after ${attemptNumber} attempts`);
75
+ this.updateStatus('connected');
76
+ });
77
+ this.socket.on('reconnect_failed', ()=>{
78
+ this.error('Reconnection failed after all attempts');
79
+ this.updateStatus('error');
80
+ });
81
+ }
82
+ handleServerMessage(message) {
83
+ switch(message.type){
84
+ case 'CONNECTED':
85
+ this.log('Server confirmed connection', message.payload);
86
+ break;
87
+ case 'SUBSCRIBED':
88
+ this.log("Subscription confirmed", message.payload);
89
+ break;
90
+ case 'LOG':
91
+ this.log('Received LOG message, forwarding...', {
92
+ level: message.payload.level,
93
+ message: message.payload.message?.substring(0, 50),
94
+ tags: message.payload.tags
95
+ });
96
+ this.forwardLog(message.payload);
97
+ break;
98
+ case 'LOGS_BATCH':
99
+ this.log(`Received ${message.payload.length} logs in batch`);
100
+ message.payload.forEach((log)=>this.forwardLog(log));
101
+ break;
102
+ case 'HISTORY':
103
+ this.log('Received history logs', {
104
+ count: message.payload.logs.length,
105
+ total: message.payload.total,
106
+ hasMore: message.payload.hasMore
107
+ });
108
+ break;
109
+ case 'LOGS_CLEARED':
110
+ this.log('Server logs cleared');
111
+ this.postToParent({
112
+ type: 'SERVER_LOG_CLEARED'
113
+ });
114
+ break;
115
+ case 'UNSUBSCRIBED':
116
+ this.log('Unsubscribed from server logs');
117
+ break;
118
+ case 'ERROR':
119
+ this.error('Server error', message.payload);
120
+ break;
121
+ default:
122
+ this.log('Unknown message type', message);
123
+ }
124
+ }
125
+ forwardLog(log) {
126
+ try {
127
+ this.log('Forwarding log to parent window', {
128
+ type: 'SERVER_LOG',
129
+ logId: log.id,
130
+ tags: log.tags
131
+ });
132
+ this.postToParent({
133
+ type: 'SERVER_LOG',
134
+ payload: JSON.stringify(log)
135
+ });
136
+ this.log('Log forwarded successfully');
137
+ } catch (e) {
138
+ this.error('Failed to forward log', e);
139
+ }
140
+ }
141
+ updateStatus(status) {
142
+ const previousStatus = this.status;
143
+ this.status = status;
144
+ if (previousStatus !== status) {
145
+ this.log(`Status changed: ${previousStatus} → ${status}`);
146
+ this.postToParent({
147
+ type: 'SERVER_LOG_CONNECTION',
148
+ status
149
+ });
150
+ }
151
+ }
152
+ postToParent(message) {
153
+ if (window.parent === window) return;
154
+ try {
155
+ window.parent.postMessage(message, '*');
156
+ } catch (e) {
157
+ this.error('postMessage error', e);
158
+ }
159
+ }
160
+ log(message, data) {
161
+ if (this.options.debug) if (data) console.log(`[ServerLogForwarder] ${message}`, data);
162
+ else console.log(`[ServerLogForwarder] ${message}`);
163
+ }
164
+ error(message, error) {
165
+ if (error) console.error(`[ServerLogForwarder] ${message}`, error);
166
+ else console.error(`[ServerLogForwarder] ${message}`);
167
+ }
168
+ }
169
+ export { ServerLogForwarder };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Server Log Stream 模块
3
+ *
4
+ * 提供 iframe 端的服务端日志转发功能
5
+ */
6
+ export { ServerLogForwarder } from './forwarder';
7
+ export type { ServerLogForwarderOptions } from './forwarder';
8
+ export type { ServerLog, ServerLogLevel, ServerLogSource, ServerLogMeta, ServerLogPostMessage, ClientToServerMessage, ServerToClientMessage, } from './types';
@@ -0,0 +1,2 @@
1
+ import { ServerLogForwarder } from "./forwarder.js";
2
+ export { ServerLogForwarder };
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/client-toolkit",
3
- "version": "1.1.23-dataloom-test.2",
3
+ "version": "1.1.23-serverlog-test.0",
4
4
  "types": "./lib/index.d.ts",
5
5
  "main": "./lib/index.js",
6
6
  "files": [
@@ -80,7 +80,7 @@
80
80
  "dependencies": {
81
81
  "@ant-design/colors": "^7.2.1",
82
82
  "@ant-design/cssinjs": "^1.24.0",
83
- "@data-loom/js": "^0.4.2-alpha.2",
83
+ "@data-loom/js": "^0.4.0",
84
84
  "@lark-apaas/miaoda-inspector": "^1.0.5",
85
85
  "@radix-ui/react-avatar": "^1.1.10",
86
86
  "@radix-ui/react-popover": "^1.1.15",
@@ -97,6 +97,7 @@
97
97
  "lodash": "^4.17.21",
98
98
  "network-idle": "^0.2.0",
99
99
  "penpal": "^6.2.2",
100
+ "socket.io-client": "^4.8.1",
100
101
  "sockjs-client": "^1.6.1",
101
102
  "sonner": "~2.0.0",
102
103
  "source-map": "^0.7.6",
@@ -124,6 +125,8 @@
124
125
  "@tailwindcss/postcss": "^4.1.0",
125
126
  "@testing-library/jest-dom": "^6.6.4",
126
127
  "@testing-library/react": "^16.3.0",
128
+ "@types/blueimp-md5": "^2.18.2",
129
+ "@types/crypto-js": "^4.2.2",
127
130
  "@types/lodash": "^4.17.20",
128
131
  "@types/node": "^22.10.2",
129
132
  "@types/react": "^18.3.23",