@blueking/chat-helper 0.0.2 → 0.0.3

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.
@@ -3,11 +3,14 @@ export interface ApiResponse<T = unknown> {
3
3
  data: T;
4
4
  message: string;
5
5
  }
6
+ /** 单次请求配置,优先级高于 `requestData` 全局默认值 */
6
7
  export interface IRequestConfig {
7
8
  baseURL?: string;
8
9
  controller?: AbortController;
9
10
  credentials?: 'include' | 'omit' | 'same-origin';
11
+ /** 请求体;可传函数以便延迟求值(在 `prepareRequest` 中通过 `getValue` 解析) */
10
12
  data?: (() => unknown) | unknown;
13
+ /** 请求头;可传函数以便延迟求值,便于动态注入(如 token) */
11
14
  headers?: (() => Record<string, string>) | Record<string, string>;
12
15
  method?: string;
13
16
  mode?: 'cors' | 'no-cors' | 'same-origin';
@@ -132,7 +132,6 @@ class InterceptorManager {
132
132
  }
133
133
  }
134
134
  export class FetchClient {
135
- // 应用响应拦截器的错误处理
136
135
  applyResponseErrorInterceptors(error) {
137
136
  let rejectedError = error;
138
137
  this.interceptors.response.forEach((interceptor)=>{
@@ -142,7 +141,6 @@ export class FetchClient {
142
141
  });
143
142
  return rejectedError;
144
143
  }
145
- // 创建新实例
146
144
  create(config) {
147
145
  return new FetchClient(mergeConfig(this.defaults, config || {}));
148
146
  }
@@ -188,13 +186,9 @@ export class FetchClient {
188
186
  data
189
187
  }));
190
188
  }
191
- // 准备请求:合并配置、应用拦截器、构建 URL 和请求体
192
189
  prepareRequest(config, isStream = false) {
193
- // 合并配置
194
190
  const mergedConfig = mergeConfig(this.defaults, config);
195
- // 总的请求配置
196
191
  let requestConfig = mergedConfig;
197
- // 应用请求拦截器
198
192
  this.interceptors.request.forEach((interceptor)=>{
199
193
  if (interceptor.fulfilled) {
200
194
  try {
@@ -207,20 +201,16 @@ export class FetchClient {
207
201
  }
208
202
  }
209
203
  });
210
- // 构建完整 URL
211
204
  let url = requestConfig.url || '';
212
205
  if (requestConfig.baseURL && !url.startsWith('http')) {
213
206
  url = requestConfig.baseURL + url;
214
207
  }
215
208
  url = buildURL(url, requestConfig.params);
216
- // 处理请求体
217
209
  let body = getValue(requestConfig.data);
218
210
  const headers = new Headers(getValue(requestConfig.headers));
219
- // 流式请求设置 Accept 头
220
211
  if (isStream && !headers.has('Accept')) {
221
212
  headers.set('Accept', 'text/event-stream');
222
213
  }
223
- // 处理请求体
224
214
  if (body !== undefined && body !== null) {
225
215
  var _headers_get;
226
216
  if (requestConfig.transformRequest) {
@@ -229,9 +219,8 @@ export class FetchClient {
229
219
  body = JSON.stringify(body);
230
220
  }
231
221
  }
232
- // 创建 AbortController
233
- const controller = requestConfig.controller ? requestConfig.controller : new AbortController();
234
- // 请求配置
222
+ var _requestConfig_controller;
223
+ const controller = (_requestConfig_controller = requestConfig.controller) !== null && _requestConfig_controller !== void 0 ? _requestConfig_controller : new AbortController();
235
224
  const fetchConfig = {
236
225
  method: requestConfig.method,
237
226
  credentials: requestConfig.credentials,
@@ -257,18 +246,13 @@ export class FetchClient {
257
246
  request(config) {
258
247
  var _this = this;
259
248
  return _async_to_generator(function*() {
260
- // 准备请求
261
249
  const { url, fetchConfig, requestConfig, controller } = _this.prepareRequest(config);
262
- // 创建超时控制
263
250
  const timeoutId = requestConfig.timeout && requestConfig.timeout > 0 ? setTimeout(()=>controller.abort(), requestConfig.timeout) : undefined;
264
251
  try {
265
- // 发送请求
266
252
  const fetchResponse = yield fetch(url, fetchConfig);
267
- // 清除超时定时器
268
253
  if (timeoutId) {
269
254
  clearTimeout(timeoutId);
270
255
  }
271
- // 解析响应数据
272
256
  let data;
273
257
  const responseType = requestConfig.responseType || 'json';
274
258
  try {
@@ -294,11 +278,9 @@ export class FetchClient {
294
278
  } catch (_error) {
295
279
  data = null;
296
280
  }
297
- // 应用响应转换
298
281
  if (requestConfig.transformResponse) {
299
282
  data = requestConfig.transformResponse(data);
300
283
  }
301
- // 构建响应对象
302
284
  const response = {
303
285
  data: data,
304
286
  status: fetchResponse.status,
@@ -306,14 +288,12 @@ export class FetchClient {
306
288
  headers: fetchResponse.headers,
307
289
  config: requestConfig
308
290
  };
309
- // 验证状态码
310
291
  const validateStatus = requestConfig.validateStatus || _this.defaults.validateStatus;
311
292
  if (!validateStatus(fetchResponse.status)) {
312
293
  var _response_data_error, _response_data;
313
294
  const message = ((_response_data = response.data) === null || _response_data === void 0 ? void 0 : (_response_data_error = _response_data.error) === null || _response_data_error === void 0 ? void 0 : _response_data_error.message) || `Request failed with status code ${fetchResponse.status}`;
314
295
  throw createError(message, requestConfig, `ERR_BAD_RESPONSE`, response);
315
296
  }
316
- // 应用响应拦截器
317
297
  let finalResponse = response;
318
298
  _this.interceptors.response.forEach((interceptor)=>{
319
299
  if (interceptor.fulfilled) {
@@ -327,11 +307,10 @@ export class FetchClient {
327
307
  }
328
308
  }
329
309
  });
330
- // 等待所有异步拦截器完成
331
310
  if (finalResponse instanceof Promise) {
332
311
  finalResponse = yield finalResponse;
333
312
  }
334
- // 检查业务逻辑状态码
313
+ // 检查业务状态码(区别于 HTTP 状态码)
335
314
  const apiResponse = finalResponse.data;
336
315
  if (![
337
316
  0,
@@ -341,39 +320,30 @@ export class FetchClient {
341
320
  }
342
321
  return apiResponse.data;
343
322
  } catch (error) {
344
- // 清除超时定时器
345
323
  if (timeoutId) {
346
324
  clearTimeout(timeoutId);
347
325
  }
348
- // 处理中断错误
349
326
  if (error instanceof Error && error.name === 'AbortError') {
350
327
  const requestError = createError('Request timeout', requestConfig, 'ECONNABORTED', undefined);
351
328
  throw _this.applyResponseErrorInterceptors(requestError);
352
329
  }
353
- // 处理其他错误
354
330
  const requestError = error.isAxiosError === true ? error : createError(error.message, requestConfig, error.code, undefined);
355
331
  throw _this.applyResponseErrorInterceptors(requestError);
356
332
  }
357
333
  })();
358
334
  }
359
- // SSE 流式请求便捷方法
360
335
  stream(url, config) {
361
336
  return this.streamRequest(_object_spread_props(_object_spread({}, config), {
362
337
  url
363
338
  }));
364
339
  }
365
- // SSE 流式请求
366
340
  streamRequest(config) {
367
341
  var _this = this;
368
342
  return _async_to_generator(function*() {
369
- // 准备请求(标记为流式请求)
370
343
  const { url, fetchConfig, requestConfig } = _this.prepareRequest(config, true);
371
344
  try {
372
- var // 触发 onStart 回调
373
- _config_onStart, _fetchResponse_body;
374
- // 发送请求
345
+ var _config_onStart, _fetchResponse_body;
375
346
  const fetchResponse = yield fetch(url, fetchConfig);
376
- // 验证状态码
377
347
  const validateStatus = requestConfig.validateStatus || _this.defaults.validateStatus;
378
348
  if (!validateStatus(fetchResponse.status)) {
379
349
  var _config_onError;
@@ -392,7 +362,6 @@ export class FetchClient {
392
362
  return;
393
363
  }
394
364
  (_config_onStart = config.onStart) === null || _config_onStart === void 0 ? void 0 : _config_onStart.call(config);
395
- // 获取 reader
396
365
  const reader = (_fetchResponse_body = fetchResponse.body) === null || _fetchResponse_body === void 0 ? void 0 : _fetchResponse_body.pipeThrough(new window.TextDecoderStream()).getReader();
397
366
  if (!reader) {
398
367
  var _config_onError1;
@@ -400,9 +369,8 @@ export class FetchClient {
400
369
  (_config_onError1 = config.onError) === null || _config_onError1 === void 0 ? void 0 : _config_onError1.call(config, error);
401
370
  return;
402
371
  }
403
- // 临时存储数据
372
+ // 缓存跨行分片数据
404
373
  let temp = '';
405
- // 判断是否为 JSON 字符串
406
374
  const isJson = (str)=>{
407
375
  try {
408
376
  JSON.parse(str);
@@ -414,7 +382,6 @@ export class FetchClient {
414
382
  // eslint-disable-next-line no-constant-condition
415
383
  while(true){
416
384
  const { value, done } = yield reader.read();
417
- // 接口完成
418
385
  if (done) {
419
386
  var _config_onDone;
420
387
  (_config_onDone = config.onDone) === null || _config_onDone === void 0 ? void 0 : _config_onDone.call(config);
@@ -467,7 +434,6 @@ export class FetchClient {
467
434
  };
468
435
  }
469
436
  }
470
- // 构建完整 URL
471
437
  function buildURL(url, params) {
472
438
  if (!params) return url;
473
439
  const searchParams = new URLSearchParams();
@@ -488,7 +454,6 @@ function buildURL(url, params) {
488
454
  }
489
455
  return url;
490
456
  }
491
- // 创建错误对象
492
457
  function createError(message, config, code, response) {
493
458
  const error = new Error(message);
494
459
  error.config = config;
@@ -497,22 +462,18 @@ function createError(message, config, code, response) {
497
462
  error.isAxiosError = true;
498
463
  return error;
499
464
  }
500
- // 获取值
501
465
  function getValue(value) {
502
466
  return typeof value === 'function' ? value() : value;
503
467
  }
504
- // 判断是否是普通对象(排除类实例如 AbortController、Headers 等)
505
- function isPlainObject(value) {
468
+ /** 排除 AbortController、Headers 等类实例,只对普通对象深度合并 */ function isPlainObject(value) {
506
469
  if (!value || typeof value !== 'object') return false;
507
470
  const proto = Object.getPrototypeOf(value);
508
471
  return proto === Object.prototype || proto === null;
509
472
  }
510
- // 合并配置
511
473
  function mergeConfig(config1, config2) {
512
474
  const output = _object_spread({}, config1);
513
475
  for(const key in config2){
514
476
  const value2 = config2[key];
515
- // 只对普通对象进行深度合并,类实例(如 AbortController)直接赋值
516
477
  if (isPlainObject(value2)) {
517
478
  const value1 = config1[key];
518
479
  output[key] = mergeConfig(value1 || {}, value2);
@@ -522,7 +483,5 @@ function mergeConfig(config1, config2) {
522
483
  }
523
484
  return output;
524
485
  }
525
- // 创建默认实例
526
486
  const fetchClient = new FetchClient();
527
- // 导出默认实例
528
487
  export default fetchClient;
@@ -22,13 +22,127 @@
22
22
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23
23
  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
24
  * IN THE SOFTWARE.
25
- */ import { FetchClient } from './fetch.ts.js';
25
+ */ function _define_property(obj, key, value) {
26
+ if (key in obj) {
27
+ Object.defineProperty(obj, key, {
28
+ value: value,
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true
32
+ });
33
+ } else {
34
+ obj[key] = value;
35
+ }
36
+ return obj;
37
+ }
38
+ function _object_spread(target) {
39
+ for(var i = 1; i < arguments.length; i++){
40
+ var source = arguments[i] != null ? arguments[i] : {};
41
+ var ownKeys = Object.keys(source);
42
+ if (typeof Object.getOwnPropertySymbols === "function") {
43
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
44
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
45
+ }));
46
+ }
47
+ ownKeys.forEach(function(key) {
48
+ _define_property(target, key, source[key]);
49
+ });
50
+ }
51
+ return target;
52
+ }
53
+ function ownKeys(object, enumerableOnly) {
54
+ var keys = Object.keys(object);
55
+ if (Object.getOwnPropertySymbols) {
56
+ var symbols = Object.getOwnPropertySymbols(object);
57
+ if (enumerableOnly) {
58
+ symbols = symbols.filter(function(sym) {
59
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
60
+ });
61
+ }
62
+ keys.push.apply(keys, symbols);
63
+ }
64
+ return keys;
65
+ }
66
+ function _object_spread_props(target, source) {
67
+ source = source != null ? source : {};
68
+ if (Object.getOwnPropertyDescriptors) {
69
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
70
+ } else {
71
+ ownKeys(Object(source)).forEach(function(key) {
72
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
73
+ });
74
+ }
75
+ return target;
76
+ }
77
+ import { FetchClient } from './fetch.ts.js';
78
+ /** 仅支持 body 的 HTTP 方法 */ const BODY_METHODS = new Set([
79
+ 'POST',
80
+ 'PUT',
81
+ 'PATCH',
82
+ 'DELETE'
83
+ ]);
84
+ /** 排除 FormData / Blob / ArrayBuffer 等,只对普通对象展开合并 */ function isPlainObject(value) {
85
+ if (!value || typeof value !== 'object') return false;
86
+ const proto = Object.getPrototypeOf(value);
87
+ return proto === Object.prototype || proto === null;
88
+ }
89
+ /** 解析「函数 | 值」两种形式,支持延迟求值(如响应式 token) */ function resolveValue(value) {
90
+ return typeof value === 'function' ? value() : value;
91
+ }
92
+ /**
93
+ * 注册全局默认请求配置拦截器,将 `requestData.headers/data` 自动合并到每次请求。
94
+ * - `headers` 注入所有请求;`data` 仅注入 POST/PUT/PATCH/DELETE,避免 GET/HEAD body 报错。
95
+ * - 优先级最低(单次 IRequestConfig > 用户拦截器 > 本拦截器)。
96
+ */ function registerRequestDataInterceptor(client, opts) {
97
+ const { headers: extraHeadersFn, data: extraDataFn } = opts.requestData;
98
+ if (!extraHeadersFn && !extraDataFn) return;
99
+ client.interceptors.request.use((config)=>{
100
+ let result = config;
101
+ if (extraHeadersFn) {
102
+ const extra = resolveValue(extraHeadersFn);
103
+ if (extra && Object.keys(extra).length > 0) {
104
+ var _resolveValue;
105
+ const existing = (_resolveValue = resolveValue(config.headers)) !== null && _resolveValue !== void 0 ? _resolveValue : {};
106
+ result = _object_spread_props(_object_spread({}, result), {
107
+ headers: _object_spread({}, existing, extra)
108
+ });
109
+ }
110
+ }
111
+ var _config_method;
112
+ const method = ((_config_method = config.method) !== null && _config_method !== void 0 ? _config_method : 'GET').toUpperCase();
113
+ if (extraDataFn && BODY_METHODS.has(method)) {
114
+ const extra = resolveValue(extraDataFn);
115
+ if (extra && Object.keys(extra).length > 0) {
116
+ const existing = resolveValue(config.data);
117
+ if (existing == null) {
118
+ // body 为空(如 clearSession POST undefined):直接用 extra 作为 body
119
+ result = _object_spread_props(_object_spread({}, result), {
120
+ data: extra
121
+ });
122
+ } else if (isPlainObject(existing)) {
123
+ // body 是普通对象:浅合并,extra 字段优先级更低(existing 已有同名字段时保留)
124
+ result = _object_spread_props(_object_spread({}, result), {
125
+ data: _object_spread({}, extra, existing)
126
+ });
127
+ } else {
128
+ // body 是 FormData / Blob / string 等:跳过注入,避免破坏原始 body
129
+ console.warn('[chat-helper] requestData.data 无法注入:当前请求体不是普通对象(FormData/Blob/string 等),已跳过合并。', {
130
+ method,
131
+ existingType: typeof existing
132
+ });
133
+ }
134
+ }
135
+ }
136
+ return result;
137
+ });
138
+ }
26
139
  export const useFetch = (options)=>{
27
140
  var _options_interceptors, _options_interceptors1;
28
141
  const fetchClient = new FetchClient({
29
142
  baseURL: options.requestData.urlPrefix
30
143
  });
31
- // 用户定义拦截器
144
+ // 先注册默认拦截器(优先级低),再注册用户拦截器(优先级高)
145
+ registerRequestDataInterceptor(fetchClient, options);
32
146
  if ((_options_interceptors = options.interceptors) === null || _options_interceptors === void 0 ? void 0 : _options_interceptors.request) {
33
147
  fetchClient.interceptors.request.use(options.interceptors.request);
34
148
  }
@@ -38,11 +152,11 @@ export const useFetch = (options)=>{
38
152
  // 重置 fetchClient 配置
39
153
  const reset = (newOptions)=>{
40
154
  var _newOptions_interceptors, _newOptions_interceptors1;
41
- // 更新 baseURL
42
155
  fetchClient.defaults.baseURL = newOptions.requestData.urlPrefix;
43
- // 清空并重新注册拦截器
156
+ // 清空并重新注册所有拦截器
44
157
  fetchClient.interceptors.request.clear();
45
158
  fetchClient.interceptors.response.clear();
159
+ registerRequestDataInterceptor(fetchClient, newOptions);
46
160
  if ((_newOptions_interceptors = newOptions.interceptors) === null || _newOptions_interceptors === void 0 ? void 0 : _newOptions_interceptors.request) {
47
161
  fetchClient.interceptors.request.use(newOptions.interceptors.request);
48
162
  }
package/dist/type.d.ts CHANGED
@@ -2,10 +2,12 @@ import type { IRequestConfig, ISSEProtocol } from './http/fetch';
2
2
  import type { IResponse } from './http/fetch';
3
3
  export interface IUseChatHelperOptions {
4
4
  protocol?: ISSEProtocol;
5
+ /** 自定义拦截器,优先级高于内置 requestData 拦截器 */
5
6
  interceptors?: {
6
7
  request?: (config: IRequestConfig) => IRequestConfig;
7
8
  response?: (response: IResponse) => IResponse;
8
9
  };
10
+ /** 全局默认请求配置,通过内置拦截器自动合并到每次请求(函数形式可延迟求值) */
9
11
  requestData: {
10
12
  data?: (() => Record<string, unknown>) | Record<string, unknown>;
11
13
  headers?: (() => Record<string, string>) | Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueking/chat-helper",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "",
5
5
  "main": "./dist/index.ts.js",
6
6
  "types": "./dist/index.d.ts",