etherreq 1.2.5 → 1.2.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/dist/etherreq.js CHANGED
@@ -1,68 +1,101 @@
1
+ /**
2
+ * 创建一个可配置的 HTTP 客户端
3
+ * @param defaultConfig - 默认配置(支持 baseURL, headers 等)
4
+ */
1
5
  export const create = (defaultConfig = {}) => {
2
- // 拦截器存储
3
6
  const requestInterceptors = [];
4
7
  const responseInterceptors = [];
8
+ /**
9
+ * 核心请求函数
10
+ */
5
11
  const client = async (config) => {
6
- // 合并默认配置
7
- const mergedConfig = { ...defaultConfig, ...config };
12
+ // 合并配置:传入 config > defaultConfig,确保url始终存在
13
+ const { url, method: inputMethod, ...configWithoutUrl } = config; // 分离url和其他配置
14
+ const mergedConfig = {
15
+ url, // 确保url存在
16
+ method: (inputMethod ?? defaultConfig.method ?? 'GET'), // 使用输入的method或默认GET,并添加类型断言
17
+ headers: {
18
+ ...defaultConfig?.headers,
19
+ ...config.headers,
20
+ },
21
+ baseURL: config.baseURL ?? defaultConfig.baseURL,
22
+ body: config.body ?? defaultConfig.body,
23
+ params: config.params ?? defaultConfig.params,
24
+ cache: config.cache ?? defaultConfig.cache,
25
+ ...configWithoutUrl, // 使用分离后的配置,避免defaultConfig覆盖已明确设置的值
26
+ };
27
+ // 🔧 自动拼接 baseURL(如果提供了)
28
+ let finalUrl = mergedConfig.url;
29
+ if (mergedConfig.baseURL && mergedConfig.url && !mergedConfig.url.startsWith('http')) {
30
+ try {
31
+ finalUrl = new URL(mergedConfig.url, mergedConfig.baseURL).toString();
32
+ }
33
+ catch (err) {
34
+ throw new Error(`[etherreq] Invalid URL: ${mergedConfig.url} with base ${mergedConfig.baseURL}`);
35
+ }
36
+ }
8
37
  // 应用请求拦截器
9
- let processedConfig = { ...mergedConfig };
38
+ let processedConfig = { ...mergedConfig, url: finalUrl };
10
39
  for (const interceptor of requestInterceptors) {
11
- processedConfig = await interceptor(processedConfig);
40
+ processedConfig = await Promise.resolve(interceptor(processedConfig));
12
41
  }
42
+ const { url: processedUrl, method: processedMethod, headers, body, ...rest } = processedConfig;
43
+ // ✅ 关键:只在非 GET/HEAD/OPTIONS 时发送 body
44
+ const hasBody = body != null &&
45
+ processedMethod !== 'GET' &&
46
+ processedMethod !== 'HEAD' &&
47
+ processedMethod !== 'OPTIONS';
13
48
  try {
14
- // 确定是否需要发送请求体
15
- const method = processedConfig.method || 'GET';
16
- const hasBody = processedConfig.body &&
17
- method !== 'GET' &&
18
- method !== 'HEAD' &&
19
- method !== 'OPTIONS';
20
- // 发送请求
21
- const response = await fetch(processedConfig.url, {
22
- method,
23
- headers: {
24
- ...(processedConfig.headers || {}),
25
- },
26
- // 只有非GET/HEAD/OPTIONS请求才发送body
27
- body: hasBody ? JSON.stringify(processedConfig.body) : undefined,
28
- });
29
- // 检查响应状态
49
+ const fetchOptions = {
50
+ method: processedMethod,
51
+ headers: headers || {},
52
+ };
53
+ if (hasBody) {
54
+ // 自动序列化 JSON(可扩展支持 FormData 等)
55
+ fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
56
+ }
57
+ const response = await fetch(processedUrl, fetchOptions);
30
58
  if (!response.ok) {
31
59
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
32
60
  }
33
- // 解析响应数据
34
- const data = await response.json();
61
+ // 尝试解析 JSON,失败则返回文本
62
+ let data;
63
+ const contentType = response.headers.get('content-type');
64
+ if (contentType && contentType.includes('application/json')) {
65
+ data = await response.json();
66
+ }
67
+ else {
68
+ data = await response.text();
69
+ }
35
70
  let responseObject = {
36
71
  data,
37
72
  status: response.status,
38
73
  statusText: response.statusText,
39
- headers: response.headers,
74
+ headers: Object.fromEntries(response.headers.entries()),
40
75
  config: processedConfig,
41
76
  };
42
77
  // 应用响应拦截器
43
78
  for (const interceptor of responseInterceptors) {
44
- responseObject = await interceptor(responseObject);
79
+ responseObject = await Promise.resolve(interceptor(responseObject));
45
80
  }
46
81
  return responseObject;
47
82
  }
48
83
  catch (error) {
49
- // 如果是网络错误或其他类型的错误
50
84
  throw new Error(`Request failed: ${error.message}`);
51
85
  }
52
86
  };
53
- // 拦截器接口
87
+ // 拦截器 API
54
88
  client.interceptors = {
55
89
  request: {
56
- use: (fulfilled, rejected) => {
57
- requestInterceptors.push(fulfilled);
90
+ use: (onFulfilled, onRejected) => {
91
+ requestInterceptors.push(onFulfilled);
58
92
  },
59
93
  },
60
94
  response: {
61
- use: (fulfilled, rejected) => {
62
- responseInterceptors.push(fulfilled);
95
+ use: (onFulfilled, onRejected) => {
96
+ responseInterceptors.push(onFulfilled);
63
97
  },
64
98
  },
65
99
  };
66
100
  return client;
67
101
  };
68
- export {};
package/dist/index.js CHANGED
@@ -2,33 +2,30 @@
2
2
  import { request, baseURL as _baseURL, setBaseURL } from './request.js';
3
3
  // 示例封装方法
4
4
  const createMethod = (method) => (url, data, callback) => {
5
- let options;
5
+ let options = { method }; // 修复:排除URL字段,明确指定method类型
6
6
  if (data &&
7
7
  typeof data === 'object' &&
8
8
  !Array.isArray(data) &&
9
- !(data instanceof FormData) &&
10
- !data.method &&
11
- !data.headers &&
12
- !data.params &&
13
- !data.baseURL) {
14
- // 对于GET请求,不应发送body,而是将数据作为params处理
15
- if (method.toUpperCase() === 'GET') {
16
- options = { params: data, method: method };
17
- }
18
- else {
19
- options = { body: data, method: method };
20
- }
9
+ method in { POST: 1, PUT: 1, PATCH: 1 }) {
10
+ // 有数据对象且为 POST/PUT/PATCH 请求
11
+ options = { ...options, params: data };
12
+ }
13
+ else if (data && typeof data !== 'function') {
14
+ // 有数据但不是对象(如原始类型)
15
+ options = { ...options, body: data };
21
16
  }
22
17
  else {
23
- options = { ...data, method: method };
18
+ // 没有数据或数据是回调函数
19
+ if (typeof data === 'function')
20
+ callback = data;
24
21
  }
25
22
  const promise = request(url, options);
26
- if (typeof callback === 'function') {
27
- promise.then(data => callback(null, data)).catch(err => callback(err, null));
28
- }
29
- else {
30
- return promise;
23
+ if (callback) {
24
+ promise
25
+ .then(data => callback(null, data))
26
+ .catch(err => callback(err, null));
31
27
  }
28
+ return promise;
32
29
  };
33
30
  export const etherreq = Object.assign((url, options, callback) => createMethod('GET')(url, options, callback), {
34
31
  get: createMethod('GET'),
package/dist/request.js CHANGED
@@ -3,11 +3,11 @@ import { create } from './etherreq.js';
3
3
  import { requestCache, getCacheKey } from './cache.js';
4
4
  // 创建一个带有默认配置的请求实例
5
5
  const instance = create({
6
- baseURL: 'https://api.example.com', // 默认基础 URL
6
+ baseURL: 'https://api.example.com', // 默认基础 URL(可被覆盖)
7
7
  });
8
8
  // 请求拦截器
9
9
  instance.interceptors.request.use((config) => {
10
- const token = localStorage.getItem('token');
10
+ const token = typeof localStorage !== 'undefined' ? localStorage.getItem('token') : null;
11
11
  const headers = {
12
12
  ...(config.headers || {}),
13
13
  Authorization: token ? `Bearer ${token}` : undefined,
@@ -56,51 +56,63 @@ let _baseURL = 'https://api.example.com'; // 内部变量用于保存 base URL
56
56
  * 判断是否应该禁用缓存
57
57
  */
58
58
  function shouldDisableCache(config) {
59
- // 检查请求级别的缓存禁用
60
59
  if (config.cache === false || config.disableCache) {
61
60
  return true;
62
61
  }
63
- // 检查缓存配置
64
62
  if (config.cache && config.cache.enabled === false) {
65
63
  return true;
66
64
  }
67
65
  return false;
68
66
  }
69
67
  /**
70
- * 获取缓存过期时间
68
+ * 获取缓存过期时间(毫秒)
71
69
  */
72
70
  function getCacheTTL(config) {
73
71
  if (config.cache && typeof config.cache.ttl === 'number') {
74
72
  return config.cache.ttl;
75
73
  }
76
- return 5 * 60 * 1000; // 默认5分钟
74
+ return 5 * 60 * 1000; // 默认 5 分钟
77
75
  }
78
76
  /**
79
- * 封装 request 函数,支持 baseURL 拼接等逻辑
80
- * @param url - 请求路径
77
+ * 封装 request 函数,支持 baseURL 拼接、URL 校验等
78
+ * @param url - 请求路径(必须是非空字符串)
81
79
  * @param options - 请求配置
82
80
  */
83
81
  export const request = (url, options = {}) => {
84
- // 如果 options.baseURL 存在则使用它,否则使用全局 _baseURL,如果都不存在则使用当前页面的 origin
85
- const baseURL = options.baseURL || _baseURL || (typeof window !== 'undefined' ? window.location.origin : '');
86
- // 构造最终的 URL,如果 baseURL 为空则直接使用传入的 url
82
+ // 🔴 关键:严格校验 url
83
+ if (typeof url !== 'string' || url.trim() === '') {
84
+ throw new Error(`[etherreq] Invalid URL: "${String(url)}". Expected a non-empty string.`);
85
+ }
86
+ // 确定 baseURL:优先使用 options.baseURL,其次全局 _baseURL,最后 fallback 到当前页面 origin(仅浏览器)
87
+ let baseURL = options.baseURL || _baseURL;
88
+ if (!baseURL && typeof window !== 'undefined') {
89
+ baseURL = window.location.origin;
90
+ }
91
+ // 构造最终 URL
87
92
  let finalURL;
88
93
  if (baseURL && !url.startsWith('http')) {
89
- // 只有当 url 不是绝对 URL 时才拼接 baseURL
90
- finalURL = new URL(url, baseURL).toString();
94
+ try {
95
+ finalURL = new URL(url, baseURL).toString();
96
+ }
97
+ catch (err) {
98
+ throw new Error(`[etherreq] Invalid URL combination: base="${baseURL}", path="${url}"`);
99
+ }
91
100
  }
92
101
  else {
93
- // 如果 url 是绝对 URL 或者 baseURL 为空,则直接使用 url
94
102
  finalURL = url;
95
103
  }
96
104
  return instance({
105
+ url: finalURL, // 明确指定url属性
97
106
  ...options,
98
- url: finalURL,
99
107
  });
100
108
  };
101
- // 导出可读写的 baseURL 变量
102
- export { _baseURL as baseURL };
103
- // 允许外部设置 baseURL
109
+ // 导出 baseURL 供读取(只读)
110
+ export const baseURL = _baseURL;
111
+ // 允许外部设置全局 baseURL
104
112
  export const setBaseURL = (newBaseURL) => {
105
- _baseURL = newBaseURL;
113
+ if (typeof newBaseURL !== 'string' || newBaseURL.trim() === '') {
114
+ console.warn('[etherreq] setBaseURL received invalid value:', newBaseURL);
115
+ return;
116
+ }
117
+ _baseURL = newBaseURL.trim();
106
118
  };
@@ -1,18 +1,17 @@
1
- export declare const create: (defaultConfig?: any) => {
2
- (config: any): Promise<{
3
- data: any;
4
- status: number;
5
- statusText: string;
6
- headers: Headers;
7
- config: any;
8
- }>;
1
+ import type { EtherRequestOptions } from './types/cache.js';
2
+ import type { RequestConfig as EtherRequestConfig } from './types/config.js';
3
+ /**
4
+ * 创建一个可配置的 HTTP 客户端
5
+ * @param defaultConfig - 默认配置(支持 baseURL, headers 等)
6
+ */
7
+ export declare const create: (defaultConfig?: Partial<EtherRequestConfig>) => {
8
+ (config: EtherRequestOptions): Promise<any>;
9
9
  interceptors: {
10
10
  request: {
11
- use: (fulfilled: any, rejected: any) => void;
11
+ use: (onFulfilled: (config: any) => any, onRejected?: any) => void;
12
12
  };
13
13
  response: {
14
- use: (fulfilled: any, rejected: any) => void;
14
+ use: (onFulfilled: (response: any) => any, onRejected?: any) => void;
15
15
  };
16
16
  };
17
17
  };
18
- export {};
@@ -1,14 +1,14 @@
1
1
  import { baseURL as _baseURL, setBaseURL } from './request.js';
2
2
  import type { EtherRequestOptions } from './types/cache.js';
3
- export declare const etherreq: ((url: string, options?: EtherRequestOptions, callback?: (error: Error | null, data: any) => void) => Promise<any> | undefined) & {
4
- get: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any> | undefined;
5
- post: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any> | undefined;
6
- put: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any> | undefined;
7
- delete: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any> | undefined;
8
- del: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any> | undefined;
9
- head: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any> | undefined;
10
- options: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any> | undefined;
11
- patch: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any> | undefined;
3
+ export declare const etherreq: ((url: string, options?: EtherRequestOptions, callback?: (error: Error | null, data: any) => void) => Promise<any>) & {
4
+ get: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any>;
5
+ post: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any>;
6
+ put: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any>;
7
+ delete: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any>;
8
+ del: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any>;
9
+ head: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any>;
10
+ options: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any>;
11
+ patch: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any>;
12
12
  login: (url: string, data?: any, callback?: (error: Error | null, data: any) => void) => Promise<any> | undefined;
13
13
  };
14
14
  export { setBaseURL, _baseURL as baseURL };
@@ -1,10 +1,18 @@
1
- import type { EtherRequestOptions } from './types/cache.js';
2
- declare let _baseURL: string;
1
+ import type { CacheOptions } from './types/cache.js';
2
+ interface RequestConfigWithoutUrl {
3
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
4
+ headers?: Record<string, string>;
5
+ body?: any;
6
+ params?: Record<string, string | number | boolean>;
7
+ baseURL?: string;
8
+ cache?: CacheOptions | false;
9
+ }
3
10
  /**
4
- * 封装 request 函数,支持 baseURL 拼接等逻辑
5
- * @param url - 请求路径
11
+ * 封装 request 函数,支持 baseURL 拼接、URL 校验等
12
+ * @param url - 请求路径(必须是非空字符串)
6
13
  * @param options - 请求配置
7
14
  */
8
- export declare const request: (url: string, options?: EtherRequestOptions) => Promise<any>;
9
- export { _baseURL as baseURL };
15
+ export declare const request: (url: string, options?: RequestConfigWithoutUrl) => Promise<any>;
16
+ export declare const baseURL: string;
10
17
  export declare const setBaseURL: (newBaseURL: string) => void;
18
+ export {};
@@ -37,6 +37,7 @@ export interface CacheOptions {
37
37
  * 请求级别的完整选项(包含缓存)
38
38
  */
39
39
  export interface EtherRequestOptions {
40
+ url: string;
40
41
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
41
42
  headers?: Record<string, string>;
42
43
  body?: any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "etherreq",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "A lightweight custom HTTP request library with TypeScript support.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/types/index.d.ts",