@lark-apaas/client-toolkit 1.0.9 → 1.0.11

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.
@@ -35,6 +35,10 @@ export interface ApiResponse<T = any> {
35
35
  status: number;
36
36
  /** 状态文本 */
37
37
  statusText: string;
38
+ /** 请求是否成功 (2xx 状态码) */
39
+ success: boolean;
40
+ /** 响应头(序列化为普通对象,可通过 postMessage 传递) */
41
+ headers?: Record<string, string>;
38
42
  }
39
43
  /**
40
44
  * API 错误类
@@ -45,39 +49,28 @@ export declare class ApiError extends Error {
45
49
  response?: Response;
46
50
  config?: RequestConfig;
47
51
  code?: string;
48
- constructor(message: string, config?: RequestConfig, response?: Response, code?: string);
52
+ /** 业务错误响应数据 */
53
+ responseData?: any;
54
+ constructor(message: string, config?: RequestConfig, response?: Response, code?: string, responseData?: any);
55
+ /**
56
+ * 序列化为可传递的 JSON 对象(用于 postMessage 等场景)
57
+ * 移除不可序列化的对象(如 Response、Request 等)
58
+ */
59
+ toJSON(): object;
49
60
  }
50
- /**
51
- * 请求拦截器类型
52
- */
53
- export type RequestInterceptor = (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;
54
- /**
55
- * 响应拦截器类型
56
- */
57
- export type ResponseInterceptor = <T = any>(response: ApiResponse<T>) => ApiResponse<T> | Promise<ApiResponse<T>>;
58
- /**
59
- * 错误拦截器类型
60
- */
61
- export type ErrorInterceptor = (error: ApiError) => any;
62
61
  /**
63
62
  * 通用接口测试 API 代理类
64
63
  *
65
64
  * 特性:
66
65
  * - 支持所有 RESTful 请求方法 (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
67
66
  * - 完善的错误处理和边界检查
68
- * - 请求/响应拦截器
69
67
  * - 超时控制和请求取消
70
68
  * - 支持多种响应类型
69
+ * - 所有响应数据可序列化(可通过 postMessage 传递)
71
70
  */
72
71
  declare class ApiProxy {
73
72
  /** 默认配置 */
74
73
  private defaultConfig;
75
- /** 请求拦截器队列 */
76
- private requestInterceptors;
77
- /** 响应拦截器队列 */
78
- private responseInterceptors;
79
- /** 错误拦截器队列 */
80
- private errorInterceptors;
81
74
  /** 活跃的请求控制器 Map */
82
75
  private activeRequests;
83
76
  /**
@@ -85,18 +78,6 @@ declare class ApiProxy {
85
78
  * @param baseConfig 基础配置
86
79
  */
87
80
  constructor(baseConfig?: Partial<RequestConfig>);
88
- /**
89
- * 添加请求拦截器
90
- */
91
- useRequestInterceptor(interceptor: RequestInterceptor): void;
92
- /**
93
- * 添加响应拦截器
94
- */
95
- useResponseInterceptor(interceptor: ResponseInterceptor): void;
96
- /**
97
- * 添加错误拦截器
98
- */
99
- useErrorInterceptor(interceptor: ErrorInterceptor): void;
100
81
  /**
101
82
  * 验证 URL 格式
102
83
  */
@@ -114,17 +95,9 @@ declare class ApiProxy {
114
95
  */
115
96
  private parseResponse;
116
97
  /**
117
- * 执行请求拦截器
118
- */
119
- private runRequestInterceptors;
120
- /**
121
- * 执行响应拦截器
122
- */
123
- private runResponseInterceptors;
124
- /**
125
- * 执行错误拦截器
98
+ * 将 Headers 对象序列化为普通对象(可通过 postMessage 传递)
126
99
  */
127
- private runErrorInterceptors;
100
+ private serializeHeaders;
128
101
  /**
129
102
  * 生成请求唯一键
130
103
  */
@@ -4,7 +4,8 @@ class ApiError extends Error {
4
4
  response;
5
5
  config;
6
6
  code;
7
- constructor(message, config, response, code){
7
+ responseData;
8
+ constructor(message, config, response, code, responseData){
8
9
  super(message);
9
10
  this.name = 'ApiError';
10
11
  this.config = config;
@@ -12,8 +13,27 @@ class ApiError extends Error {
12
13
  this.status = response?.status;
13
14
  this.statusText = response?.statusText;
14
15
  this.code = code;
16
+ this.responseData = responseData;
15
17
  Object.setPrototypeOf(this, ApiError.prototype);
16
18
  }
19
+ toJSON() {
20
+ return {
21
+ name: this.name,
22
+ message: this.message,
23
+ code: this.code,
24
+ status: this.status,
25
+ statusText: this.statusText,
26
+ responseData: this.responseData,
27
+ config: this.config ? {
28
+ url: this.config.url,
29
+ method: this.config.method,
30
+ headers: this.config.headers,
31
+ params: this.config.params,
32
+ timeout: this.config.timeout,
33
+ responseType: this.config.responseType
34
+ } : void 0
35
+ };
36
+ }
17
37
  }
18
38
  class ApiProxy {
19
39
  defaultConfig = {
@@ -23,12 +43,9 @@ class ApiProxy {
23
43
  headers: {
24
44
  'Content-Type': 'application/json',
25
45
  'X-Suda-Csrf-Token': window.csrfToken || '',
26
- 'Cache-Control': 'no-store'
46
+ 'Cache-Control': 'no-store, no-cache'
27
47
  }
28
48
  };
29
- requestInterceptors = [];
30
- responseInterceptors = [];
31
- errorInterceptors = [];
32
49
  activeRequests = new Map();
33
50
  constructor(baseConfig){
34
51
  if (baseConfig) this.defaultConfig = {
@@ -36,18 +53,6 @@ class ApiProxy {
36
53
  ...baseConfig
37
54
  };
38
55
  }
39
- useRequestInterceptor(interceptor) {
40
- if ('function' != typeof interceptor) throw new TypeError('Request interceptor must be a function');
41
- this.requestInterceptors.push(interceptor);
42
- }
43
- useResponseInterceptor(interceptor) {
44
- if ('function' != typeof interceptor) throw new TypeError('Response interceptor must be a function');
45
- this.responseInterceptors.push(interceptor);
46
- }
47
- useErrorInterceptor(interceptor) {
48
- if ('function' != typeof interceptor) throw new TypeError('Error interceptor must be a function');
49
- this.errorInterceptors.push(interceptor);
50
- }
51
56
  validateUrl(url) {
52
57
  if (!url || 'string' != typeof url) return false;
53
58
  if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) return true;
@@ -115,35 +120,12 @@ class ApiProxy {
115
120
  throw new ApiError(`Failed to parse response as ${responseType}`, void 0, response, 'PARSE_ERROR');
116
121
  }
117
122
  }
118
- async runRequestInterceptors(config) {
119
- let modifiedConfig = {
120
- ...config
121
- };
122
- for (const interceptor of this.requestInterceptors)try {
123
- modifiedConfig = await interceptor(modifiedConfig);
124
- } catch (error) {
125
- console.error('Request interceptor error:', error);
126
- throw new ApiError('Request interceptor failed', modifiedConfig, void 0, 'INTERCEPTOR_ERROR');
127
- }
128
- return modifiedConfig;
129
- }
130
- async runResponseInterceptors(response) {
131
- let modifiedResponse = response;
132
- for (const interceptor of this.responseInterceptors)try {
133
- modifiedResponse = await interceptor(modifiedResponse);
134
- } catch (error) {
135
- console.error('Response interceptor error:', error);
136
- }
137
- return modifiedResponse;
138
- }
139
- async runErrorInterceptors(error) {
140
- for (const interceptor of this.errorInterceptors)try {
141
- const result = await interceptor(error);
142
- if (void 0 !== result) return result;
143
- } catch (err) {
144
- console.error('Error interceptor failed:', err);
145
- }
146
- throw error;
123
+ serializeHeaders(headers) {
124
+ const serialized = {};
125
+ headers.forEach((value, key)=>{
126
+ serialized[key] = value;
127
+ });
128
+ return serialized;
147
129
  }
148
130
  generateRequestKey(config) {
149
131
  return `${config.method || 'GET'}_${config.url}_${Date.now()}_${Math.random()}`;
@@ -160,15 +142,8 @@ class ApiProxy {
160
142
  ...config.headers
161
143
  }
162
144
  };
163
- const finalConfig = await this.runRequestInterceptors(mergedConfig);
164
- const requestKey = this.generateRequestKey(finalConfig);
165
- try {
166
- const response = await this.executeRequest(finalConfig, requestKey);
167
- return response;
168
- } catch (error) {
169
- const apiError = error instanceof ApiError ? error : new ApiError(error instanceof Error ? error.message : 'Unknown error', finalConfig, void 0, 'REQUEST_FAILED');
170
- return await this.runErrorInterceptors(apiError);
171
- }
145
+ const requestKey = this.generateRequestKey(mergedConfig);
146
+ return await this.executeRequest(mergedConfig, requestKey);
172
147
  }
173
148
  async executeRequest(config, requestKey) {
174
149
  let abortController;
@@ -197,27 +172,28 @@ class ApiProxy {
197
172
  const response = await fetch(fullUrl, fetchOptions);
198
173
  if (timeoutId) clearTimeout(timeoutId);
199
174
  this.activeRequests.delete(requestKey);
200
- if (!response.ok) throw new ApiError(`HTTP Error: ${response.status} ${response.statusText}`, config, response, 'HTTP_ERROR');
201
- const data = await this.parseResponse(response, config.responseType || 'json');
175
+ let data;
176
+ try {
177
+ data = 204 === response.status || 304 === response.status ? null : await this.parseResponse(response, config.responseType || 'json');
178
+ } catch (parseError) {
179
+ console.error('Failed to parse response:', parseError);
180
+ data = null;
181
+ }
202
182
  const apiResponse = {
203
183
  data,
204
184
  status: response.status,
205
- statusText: response.statusText
185
+ statusText: response.statusText,
186
+ success: response.ok,
187
+ headers: this.serializeHeaders(response.headers)
206
188
  };
207
- return await this.runResponseInterceptors(apiResponse);
189
+ return apiResponse;
208
190
  } catch (error) {
209
- let errorResponse = null, errorResponseString = '';
210
- if (error.response && error.config.responseType) errorResponse = await this.parseResponse(error.response, error.config.responseType || 'json');
211
- try {
212
- errorResponseString = JSON.stringify(errorResponse);
213
- } catch (error) {
214
- throw new ApiError(error instanceof Error ? error.message : 'Response parse error', config, void 0, 'UNKNOWN_ERROR');
215
- }
216
191
  if (timeoutId) clearTimeout(timeoutId);
217
192
  this.activeRequests.delete(requestKey);
193
+ if (error instanceof ApiError) throw error;
218
194
  if (error instanceof Error && 'AbortError' === error.name) throw new ApiError(config.timeout ? 'Request timeout' : 'Request cancelled', config, void 0, 'ABORT_ERROR');
219
195
  if (error instanceof TypeError) throw new ApiError('Network error or CORS issue', config, void 0, 'NETWORK_ERROR');
220
- throw new ApiError(errorResponseString, config, errorResponse, 'UNKNOWN_ERROR');
196
+ throw new ApiError(error instanceof Error ? error.message : 'Unknown error', config, void 0, 'UNKNOWN_ERROR');
221
197
  }
222
198
  }
223
199
  async get(url, config) {
@@ -5,5 +5,6 @@ export interface UserDisplayProps {
5
5
  size?: 'small' | 'medium' | 'large';
6
6
  className?: string;
7
7
  style?: React.CSSProperties;
8
+ showLabel?: boolean;
8
9
  }
9
10
  export declare const UserDisplay: React.FC<UserDisplayProps>;
@@ -5,7 +5,7 @@ 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
- const UserDisplay = ({ users, size, className, style })=>{
8
+ const UserDisplay = ({ users, size, className, style, showLabel = true })=>{
9
9
  const normalizedUsers = useMemo(()=>Array.isArray(users) ? users : [
10
10
  users
11
11
  ].filter(Boolean), [
@@ -96,6 +96,7 @@ const UserDisplay = ({ users, size, className, style })=>{
96
96
  children: /*#__PURE__*/ jsx(UserWithAvatar, {
97
97
  data: user,
98
98
  size: size,
99
+ showLabel: showLabel,
99
100
  className: "cursor-pointer hover:bg-[rgba(31,35,41,0.15)] active:bg-[rgba(31,35,41,0.2)]"
100
101
  })
101
102
  })
@@ -1,3 +1,3 @@
1
1
  import * as React from 'react';
2
2
  import type { UserWithAvatarProps } from './type';
3
- export declare function UserWithAvatar({ data, size, mode, className, }: UserWithAvatarProps): React.JSX.Element;
3
+ export declare function UserWithAvatar({ data, size, mode, className, showLabel, }: UserWithAvatarProps): React.JSX.Element;
@@ -23,7 +23,7 @@ const textVariantMap = {
23
23
  medium: 'text-[14px] leading-[20px]',
24
24
  large: 'text-[16px] leading-[24px]'
25
25
  };
26
- function UserWithAvatar({ data, size = 'medium', mode = 'tag', className }) {
26
+ function UserWithAvatar({ data, size = 'medium', mode = 'tag', className, showLabel = true }) {
27
27
  const { avatar, name } = data;
28
28
  const displayName = name?.trim() || '无效人员';
29
29
  const formatSize = [
@@ -48,7 +48,7 @@ function UserWithAvatar({ data, size = 'medium', mode = 'tag', className }) {
48
48
  })
49
49
  ]
50
50
  }),
51
- /*#__PURE__*/ jsx(OverflowTooltipText, {
51
+ showLabel && /*#__PURE__*/ jsx(OverflowTooltipText, {
52
52
  text: displayName,
53
53
  className: clsxWithTw('text-card-foreground', textVariantMap[formatSize])
54
54
  })
@@ -10,4 +10,8 @@ export interface UserWithAvatarProps {
10
10
  */
11
11
  mode?: 'tag' | 'plain';
12
12
  className?: string;
13
+ /**
14
+ * @default true
15
+ */
16
+ showLabel?: boolean;
13
17
  }
@@ -13,7 +13,7 @@ class BatchLogger {
13
13
  userId,
14
14
  tenantId,
15
15
  appId,
16
- endpoint: '/dev/logs/collect-batch',
16
+ endpoint: (process.env.CLIENT_BASE_PATH || '') + '/dev/logs/collect-batch',
17
17
  sizeThreshold: 20,
18
18
  flushInterval: 1000,
19
19
  maxRetries: 3,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/client-toolkit",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "types": "./lib/index.d.ts",
5
5
  "main": "./lib/index.js",
6
6
  "files": [