@lark-apaas/client-toolkit 1.0.8-alpha.1 → 1.0.9

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,10 +35,6 @@ 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>;
42
38
  }
43
39
  /**
44
40
  * API 错误类
@@ -49,28 +45,39 @@ export declare class ApiError extends Error {
49
45
  response?: Response;
50
46
  config?: RequestConfig;
51
47
  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;
48
+ constructor(message: string, config?: RequestConfig, response?: Response, code?: string);
60
49
  }
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;
61
62
  /**
62
63
  * 通用接口测试 API 代理类
63
64
  *
64
65
  * 特性:
65
66
  * - 支持所有 RESTful 请求方法 (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
66
67
  * - 完善的错误处理和边界检查
68
+ * - 请求/响应拦截器
67
69
  * - 超时控制和请求取消
68
70
  * - 支持多种响应类型
69
- * - 所有响应数据可序列化(可通过 postMessage 传递)
70
71
  */
71
72
  declare class ApiProxy {
72
73
  /** 默认配置 */
73
74
  private defaultConfig;
75
+ /** 请求拦截器队列 */
76
+ private requestInterceptors;
77
+ /** 响应拦截器队列 */
78
+ private responseInterceptors;
79
+ /** 错误拦截器队列 */
80
+ private errorInterceptors;
74
81
  /** 活跃的请求控制器 Map */
75
82
  private activeRequests;
76
83
  /**
@@ -78,6 +85,18 @@ declare class ApiProxy {
78
85
  * @param baseConfig 基础配置
79
86
  */
80
87
  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;
81
100
  /**
82
101
  * 验证 URL 格式
83
102
  */
@@ -95,9 +114,17 @@ declare class ApiProxy {
95
114
  */
96
115
  private parseResponse;
97
116
  /**
98
- * 将 Headers 对象序列化为普通对象(可通过 postMessage 传递)
117
+ * 执行请求拦截器
118
+ */
119
+ private runRequestInterceptors;
120
+ /**
121
+ * 执行响应拦截器
122
+ */
123
+ private runResponseInterceptors;
124
+ /**
125
+ * 执行错误拦截器
99
126
  */
100
- private serializeHeaders;
127
+ private runErrorInterceptors;
101
128
  /**
102
129
  * 生成请求唯一键
103
130
  */
@@ -4,8 +4,7 @@ class ApiError extends Error {
4
4
  response;
5
5
  config;
6
6
  code;
7
- responseData;
8
- constructor(message, config, response, code, responseData){
7
+ constructor(message, config, response, code){
9
8
  super(message);
10
9
  this.name = 'ApiError';
11
10
  this.config = config;
@@ -13,27 +12,8 @@ class ApiError extends Error {
13
12
  this.status = response?.status;
14
13
  this.statusText = response?.statusText;
15
14
  this.code = code;
16
- this.responseData = responseData;
17
15
  Object.setPrototypeOf(this, ApiError.prototype);
18
16
  }
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
- }
37
17
  }
38
18
  class ApiProxy {
39
19
  defaultConfig = {
@@ -43,9 +23,12 @@ class ApiProxy {
43
23
  headers: {
44
24
  'Content-Type': 'application/json',
45
25
  'X-Suda-Csrf-Token': window.csrfToken || '',
46
- 'Cache-Control': 'no-store, no-cache'
26
+ 'Cache-Control': 'no-store'
47
27
  }
48
28
  };
29
+ requestInterceptors = [];
30
+ responseInterceptors = [];
31
+ errorInterceptors = [];
49
32
  activeRequests = new Map();
50
33
  constructor(baseConfig){
51
34
  if (baseConfig) this.defaultConfig = {
@@ -53,6 +36,18 @@ class ApiProxy {
53
36
  ...baseConfig
54
37
  };
55
38
  }
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
+ }
56
51
  validateUrl(url) {
57
52
  if (!url || 'string' != typeof url) return false;
58
53
  if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) return true;
@@ -120,12 +115,35 @@ class ApiProxy {
120
115
  throw new ApiError(`Failed to parse response as ${responseType}`, void 0, response, 'PARSE_ERROR');
121
116
  }
122
117
  }
123
- serializeHeaders(headers) {
124
- const serialized = {};
125
- headers.forEach((value, key)=>{
126
- serialized[key] = value;
127
- });
128
- return serialized;
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;
129
147
  }
130
148
  generateRequestKey(config) {
131
149
  return `${config.method || 'GET'}_${config.url}_${Date.now()}_${Math.random()}`;
@@ -142,8 +160,15 @@ class ApiProxy {
142
160
  ...config.headers
143
161
  }
144
162
  };
145
- const requestKey = this.generateRequestKey(mergedConfig);
146
- return await this.executeRequest(mergedConfig, requestKey);
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
+ }
147
172
  }
148
173
  async executeRequest(config, requestKey) {
149
174
  let abortController;
@@ -172,28 +197,27 @@ class ApiProxy {
172
197
  const response = await fetch(fullUrl, fetchOptions);
173
198
  if (timeoutId) clearTimeout(timeoutId);
174
199
  this.activeRequests.delete(requestKey);
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
- }
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');
182
202
  const apiResponse = {
183
203
  data,
184
204
  status: response.status,
185
- statusText: response.statusText,
186
- success: response.ok,
187
- headers: this.serializeHeaders(response.headers)
205
+ statusText: response.statusText
188
206
  };
189
- return apiResponse;
207
+ return await this.runResponseInterceptors(apiResponse);
190
208
  } 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
+ }
191
216
  if (timeoutId) clearTimeout(timeoutId);
192
217
  this.activeRequests.delete(requestKey);
193
- if (error instanceof ApiError) throw error;
194
218
  if (error instanceof Error && 'AbortError' === error.name) throw new ApiError(config.timeout ? 'Request timeout' : 'Request cancelled', config, void 0, 'ABORT_ERROR');
195
219
  if (error instanceof TypeError) throw new ApiError('Network error or CORS issue', config, void 0, 'NETWORK_ERROR');
196
- throw new ApiError(error instanceof Error ? error.message : 'Unknown error', config, void 0, 'UNKNOWN_ERROR');
220
+ throw new ApiError(errorResponseString, config, errorResponse, 'UNKNOWN_ERROR');
197
221
  }
198
222
  }
199
223
  async get(url, config) {
@@ -2,10 +2,11 @@ function getAppId(path) {
2
2
  if (window.MIAODA_APP_ID) return window.MIAODA_APP_ID;
3
3
  let prefix;
4
4
  prefix = path.includes('/ai/feida/runtime/') ? '/ai/feida/runtime/' : path.includes('/spark/r/') ? '/spark/r/' : '/ai/miaoda/';
5
- if (!path.startsWith(prefix)) return null;
5
+ const windowAppId = window.appId || null;
6
+ if (!path.startsWith(prefix)) return windowAppId;
6
7
  const remainder = path.substring(prefix.length);
7
8
  const nextSlashIndex = remainder.indexOf('/');
8
- if (-1 === nextSlashIndex) return remainder;
9
- return remainder.substring(0, nextSlashIndex);
9
+ if (-1 === nextSlashIndex) return remainder || windowAppId;
10
+ return remainder.substring(0, nextSlashIndex) || windowAppId;
10
11
  }
11
12
  export { getAppId };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/client-toolkit",
3
- "version": "1.0.8-alpha.1",
3
+ "version": "1.0.9",
4
4
  "types": "./lib/index.d.ts",
5
5
  "main": "./lib/index.js",
6
6
  "files": [