@yeepay/client-utils 3.1.0 → 4.0.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.
package/README.md CHANGED
@@ -41,8 +41,9 @@ const service = serviceFactory(
41
41
  {
42
42
  baseUrl: '/xxx-server',
43
43
  headers: {},
44
- // glob mock 数据文件位置
45
- mockModules: import.meta.glob('./mock/**/*.json', { eager: true }),
44
+ mock: {
45
+ modules: import.meta.glob('./mock/**/*.json', { eager: true }),
46
+ },
46
47
  },
47
48
  successCallback,
48
49
  failCallback,
@@ -81,3 +82,34 @@ export default (config: AxiosRequestConfig) => {
81
82
  }, { 'new-header-hello': 'world' }]
82
83
  }
83
84
  ```
85
+
86
+ ### Debug 日志打印
87
+
88
+ 开发者常常使用 console.log 日志打印来调试代码,且往往会忘记删除这些日志,导致线上环境也打印日志,影响性能与潜在安全风险。
89
+
90
+ `@yeepay/client-utils` 提供了 debug 模块来帮助开发者更好地控制日志打印。
91
+
92
+ #### 基本使用
93
+
94
+ ```javascript
95
+ import { debug } from '@yeepay/client-utils'
96
+
97
+ debug.page1('page1 is loaded')
98
+
99
+ debug.api('fetch user api is called')
100
+ ```
101
+
102
+ ![image](http://gitlab.yeepay.com/fex/client-utils/-/raw/master/static/debug-demo.png)
103
+
104
+ 这些日志默认在开发环境打印,而在生产环境不打印。
105
+
106
+ 如果想要在生产环境打印,可以在浏览器控制台执行:
107
+
108
+ ```javascript
109
+ debug.enableAll()
110
+ // 或者打印特定模块
111
+ debug.enable('page1')
112
+
113
+ // 恢复默认行为
114
+ debug.disable()
115
+ ```
package/dist/index.d.ts CHANGED
@@ -1,7 +1,36 @@
1
- import { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from "axios";
1
+ import debug from "debug";
2
+ import { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from "axios";
2
3
 
4
+ //#region src/debug.d.ts
5
+ declare const logger: Record<string, debug.Debugger> & debug.Debugger;
6
+ declare global {
7
+ interface Window {
8
+ debug: {
9
+ enableAll: () => void;
10
+ enable: (namespace: string) => void;
11
+ disable: () => void;
12
+ };
13
+ }
14
+ }
15
+ //#endregion
16
+ //#region src/is.d.ts
17
+ declare function isTouchDevice(): boolean;
18
+ declare const BREAKPOINTS: {
19
+ MOBILE_MAX: number;
20
+ TABLET_MIN: number;
21
+ TABLET_MAX: number;
22
+ DESKTOP_MIN: number;
23
+ };
24
+ declare const MEDIA_QUERIES: {
25
+ MOBILE: string;
26
+ TABLET: string;
27
+ DESKTOP: string;
28
+ };
29
+ declare function isMobile(): boolean;
30
+ declare function isTablet(): boolean;
31
+ declare function isDesktop(): boolean;
32
+ //#endregion
3
33
  //#region src/request/mock.d.ts
4
-
5
34
  /**
6
35
  * A valid `picomatch` glob pattern, or array of patterns.
7
36
  */
@@ -64,11 +93,10 @@ interface ServiceFactoryCallbacks {
64
93
  notfoundCallback?: (response: AxiosResponse<any> | any) => any;
65
94
  requestInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig;
66
95
  responseInterceptor?: (response: AxiosResponse<any>) => AxiosResponse<any>;
96
+ responseInterceptorFailCallback?: (error: AxiosError) => any;
67
97
  }
68
- type ServiceFactoryCallbacksCompatible = [ServiceFactoryCallbacks['successCallback']?, ServiceFactoryCallbacks['failCallback']?, ServiceFactoryCallbacks['unauthorizedCallback']?, ServiceFactoryCallbacks['forbiddenCallback']?, ServiceFactoryCallbacks['notfoundCallback']?, ServiceFactoryCallbacks['requestInterceptor']?, ServiceFactoryCallbacks['responseInterceptor']?];
69
98
  declare function verifySuccessCode<T extends string | number>(code: T, customCode?: T): boolean;
70
99
  declare function serviceFactory(options: ServiceFactoryOptions, callbacks: ServiceFactoryCallbacks): AxiosInstance;
71
- declare function serviceFactory(options: ServiceFactoryOptions, ...callbacks: ServiceFactoryCallbacksCompatible): AxiosInstance;
72
100
  //#endregion
73
101
  //#region src/request/token.d.ts
74
102
  declare function getToken(): string | undefined;
@@ -81,4 +109,4 @@ declare function removeToken(): void;
81
109
  //#region src/utils.d.ts
82
110
  declare function getQueryObject(url?: string): Record<string, string>;
83
111
  //#endregion
84
- export { getQueryObject, getToken, removeToken, serviceFactory, setTokenFromUrl, verifySuccessCode };
112
+ export { BREAKPOINTS, MEDIA_QUERIES, logger as debug, getQueryObject, getToken, isDesktop, isMobile, isTablet, isTouchDevice, removeToken, serviceFactory, setTokenFromUrl, verifySuccessCode };
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import debug from "debug";
1
2
  import { interopDefault, toArray } from "@imyangyong/utils";
2
3
  import axios from "axios";
3
4
  import { bold, cyan, gray, green, red, yellow } from "ansis";
@@ -5,6 +6,90 @@ import AxiosMockAdapter from "axios-mock-adapter";
5
6
  import picomatch from "picomatch/posix";
6
7
  import Cookie from "js-cookie";
7
8
 
9
+ //#region src/debug.ts
10
+ function createLogger(namespace) {
11
+ return debug(namespace);
12
+ }
13
+ const loggers = {};
14
+ const logger = new Proxy(function() {}, {
15
+ get(_, prop) {
16
+ if (prop in loggers) return loggers[prop];
17
+ const newLogger = createLogger(`${prop}`);
18
+ loggers[prop] = newLogger;
19
+ return newLogger;
20
+ },
21
+ apply(_, thisArg, args) {
22
+ return (loggers.app || (loggers.app = createLogger("app"))).apply(thisArg, args);
23
+ }
24
+ });
25
+ window.debug = {
26
+ enableAll: () => {
27
+ localStorage.setItem("debug", "*");
28
+ location.reload();
29
+ },
30
+ enable: (namespace) => {
31
+ if (!namespace) {
32
+ window.debug.enableAll();
33
+ return;
34
+ }
35
+ const namespaces = (localStorage.getItem("debug") || "").split(",").filter(Boolean);
36
+ namespaces.push(`${namespace}`);
37
+ localStorage.setItem("debug", namespaces.join(","));
38
+ location.reload();
39
+ },
40
+ disable: () => {
41
+ try {
42
+ if (process?.env?.NODE_ENV === "development") localStorage.setItem("debug", "null");
43
+ else localStorage.removeItem("debug");
44
+ } finally {
45
+ location.reload();
46
+ }
47
+ }
48
+ };
49
+ try {
50
+ if (process.env.NODE_ENV === "development") {
51
+ if (!localStorage.getItem("debug")) window.debug.enableAll();
52
+ }
53
+ } catch (error) {
54
+ console.error("Error enabling logger:", error);
55
+ }
56
+
57
+ //#endregion
58
+ //#region src/is.ts
59
+ function isTouchDevice() {
60
+ if (typeof navigator === "undefined" || typeof window === "undefined") return false;
61
+ if ("maxTouchPoints" in navigator) return navigator.maxTouchPoints > 0;
62
+ if (typeof window.matchMedia === "function") return window.matchMedia("(pointer: coarse)").matches;
63
+ const ua = navigator.userAgent || "";
64
+ return /android|iphone|ipad|ipod|windows phone|blackberry|mobile/i.test(ua);
65
+ }
66
+ const BREAKPOINTS = {
67
+ MOBILE_MAX: 767,
68
+ TABLET_MIN: 768,
69
+ TABLET_MAX: 1023,
70
+ DESKTOP_MIN: 1024
71
+ };
72
+ const MEDIA_QUERIES = {
73
+ MOBILE: `(max-width: ${BREAKPOINTS.MOBILE_MAX}px)`,
74
+ TABLET: `(min-width: ${BREAKPOINTS.TABLET_MIN}px) and (max-width: ${BREAKPOINTS.TABLET_MAX}px)`,
75
+ DESKTOP: `(min-width: ${BREAKPOINTS.DESKTOP_MIN}px)`
76
+ };
77
+ function isMobile() {
78
+ if (!isTouchDevice()) return false;
79
+ if (typeof window.matchMedia === "function") return window.matchMedia(MEDIA_QUERIES.MOBILE).matches;
80
+ return false;
81
+ }
82
+ function isTablet() {
83
+ if (!isTouchDevice()) return false;
84
+ if (typeof window.matchMedia === "function") return window.matchMedia(MEDIA_QUERIES.TABLET).matches;
85
+ return false;
86
+ }
87
+ function isDesktop() {
88
+ if (typeof navigator === "undefined" || typeof window === "undefined") return false;
89
+ return window.matchMedia(MEDIA_QUERIES.DESKTOP).matches;
90
+ }
91
+
92
+ //#endregion
8
93
  //#region src/request/mock.ts
9
94
  /**
10
95
  * 查找数组中最长相同前缀连续子序列
@@ -28,8 +113,7 @@ function matchesPattern(str, pattern) {
28
113
  if (pattern instanceof RegExp) return pattern.test(str);
29
114
  if (typeof pattern === "string") {
30
115
  if (pattern === "*") return true;
31
- const isMatch = picomatch(pattern);
32
- return isMatch(str);
116
+ return picomatch(pattern)(str);
33
117
  }
34
118
  return false;
35
119
  }
@@ -38,11 +122,8 @@ function matchesPattern(str, pattern) {
38
122
  */
39
123
  function shouldIncludeModule(uri, includes, excludes) {
40
124
  const includePatterns = includes ? Array.isArray(includes) ? includes : [includes] : ["*"];
41
- const excludePatterns = excludes ? Array.isArray(excludes) ? excludes : [excludes] : [];
42
- const isExcluded = excludePatterns.some((pattern) => pattern && matchesPattern(uri, pattern));
43
- if (isExcluded) return false;
44
- const isIncluded = includePatterns.some((pattern) => pattern && matchesPattern(uri, pattern));
45
- return isIncluded;
125
+ if ((excludes ? Array.isArray(excludes) ? excludes : [excludes] : []).some((pattern) => pattern && matchesPattern(uri, pattern))) return false;
126
+ return includePatterns.some((pattern) => pattern && matchesPattern(uri, pattern));
46
127
  }
47
128
  async function setupMockAdapter(axiosInstance, mock) {
48
129
  if (!mock) return;
@@ -55,16 +136,14 @@ async function setupMockAdapter(axiosInstance, mock) {
55
136
  });
56
137
  const allPaths = Object.keys(modules);
57
138
  const longestPrefix = longestCommonPrefix(allPaths);
58
- const paths = allPaths.map((path) => {
139
+ const filteredPaths = allPaths.map((path) => {
59
140
  const longestPrefixIndex = longestPrefix.lastIndexOf("/");
60
141
  const dotIndex = path.lastIndexOf(".");
61
- const uri = path.slice(longestPrefixIndex, dotIndex);
62
142
  return {
63
- uri,
143
+ uri: path.slice(longestPrefixIndex, dotIndex),
64
144
  path
65
145
  };
66
- });
67
- const filteredPaths = paths.filter(({ uri }) => shouldIncludeModule(uri, includes, excludes));
146
+ }).filter(({ uri }) => shouldIncludeModule(uri, includes, excludes));
68
147
  if (filteredPaths.length === 0) return;
69
148
  filteredPaths.forEach(({ uri, path }) => {
70
149
  const registerMock = (mod) => {
@@ -92,8 +171,7 @@ function getQueryObject(url) {
92
171
  url = !url ? window.location.href : url;
93
172
  const search = url.substring(url.lastIndexOf("?") + 1);
94
173
  const obj = {};
95
- const reg = /([^?&=]+)=([^?&=]*)/g;
96
- search.replace(reg, (rs, $1, $2) => {
174
+ search.replace(/([^?&=]+)=([^?&=]*)/g, (rs, $1, $2) => {
97
175
  const name = decodeURIComponent($1);
98
176
  let val = decodeURIComponent($2);
99
177
  val = String(val);
@@ -134,26 +212,13 @@ function verifySuccessCode(code, customCode) {
134
212
  if (customCode) return toArray(customCode).includes(code) || !code;
135
213
  return code === 0 || code === "0" || code === "000000" || code === 200 || !code;
136
214
  }
137
- function compatibleCallback(compatibleCallbacks = []) {
138
- if (Array.isArray(compatibleCallbacks) && compatibleCallbacks.length && typeof compatibleCallbacks[0] === "function") return {
139
- successCallback: compatibleCallbacks[0] || (() => ({})),
140
- failCallback: compatibleCallbacks[1] || (() => ({})),
141
- unauthorizedCallback: compatibleCallbacks[2] || (() => ({})),
142
- forbiddenCallback: compatibleCallbacks[3] || (() => ({})),
143
- notfoundCallback: compatibleCallbacks[4] || (() => ({})),
144
- requestInterceptor: compatibleCallbacks[5] || ((c) => c),
145
- responseInterceptor: compatibleCallbacks[6] || ((r) => r)
146
- };
147
- else return compatibleCallbacks[0];
148
- }
149
215
  function blobToJson(blob) {
150
216
  return new Promise((resolve, reject) => {
151
217
  const reader = new FileReader();
152
218
  reader.onload = (event) => {
153
219
  try {
154
220
  const jsonString = event.target?.result;
155
- const jsonObject = JSON.parse(jsonString);
156
- resolve(jsonObject);
221
+ resolve(JSON.parse(jsonString));
157
222
  } catch (error) {
158
223
  reject(error);
159
224
  }
@@ -164,16 +229,13 @@ function blobToJson(blob) {
164
229
  reader.readAsText(blob);
165
230
  });
166
231
  }
167
- function serviceFactory(options, ...callbacks) {
168
- const { baseUrl = "/", timeout = 12e5, headers, code, debug, mock } = options;
169
- const { successCallback = (_response) => ({}), failCallback = (_error) => ({}), unauthorizedCallback = (_response) => ({}), forbiddenCallback = (_response) => ({}), notfoundCallback = (_response) => ({}), requestInterceptor = (c) => c, responseInterceptor = (r) => r } = compatibleCallback(callbacks);
232
+ function serviceFactory(options, callbacks) {
233
+ const { baseUrl = "/", timeout = 12e5, headers, code, debug: debug$1, mock } = options;
234
+ const { successCallback = (_response) => ({}), failCallback = (_error) => ({}), unauthorizedCallback = (_response) => ({}), forbiddenCallback = (_response) => ({}), notfoundCallback = (_response) => ({}), requestInterceptor = (c) => c, responseInterceptor = (r) => r, responseInterceptorFailCallback = (error) => Promise.reject(error) } = callbacks;
170
235
  const service = axios.create({
171
236
  baseURL: baseUrl,
172
237
  timeout,
173
- headers,
174
- validateStatus(status) {
175
- return status >= 200 && status < 300 || status === 401 || status === 403 || status === 400 || status === 500;
176
- }
238
+ headers
177
239
  });
178
240
  /**
179
241
  * request拦截器, 先入栈后执行
@@ -186,7 +248,7 @@ function serviceFactory(options, ...callbacks) {
186
248
  config.headers.Authorization = getToken();
187
249
  const { method, url = "" } = config;
188
250
  if (method === "get") {
189
- if (!debug) if (url.includes("?")) config.url = `${url}&_t=${(/* @__PURE__ */ new Date()).getTime()}`;
251
+ if (!debug$1) if (url.includes("?")) config.url = `${url}&_t=${(/* @__PURE__ */ new Date()).getTime()}`;
190
252
  else config.url = `${url}?_t=${(/* @__PURE__ */ new Date()).getTime()}`;
191
253
  }
192
254
  return config;
@@ -205,16 +267,12 @@ function serviceFactory(options, ...callbacks) {
205
267
  }
206
268
  return response;
207
269
  }, async (error) => {
208
- if (error.response.data instanceof Blob && error.response.headers["content-type"]?.split(";")[0]?.trim() === "application/json") error.response.data = await blobToJson(error.response.data);
270
+ try {
271
+ if (error.response.data instanceof Blob && error.response.headers["content-type"]?.split(";")[0]?.trim() === "application/json") error.response.data = await blobToJson(error.response.data);
272
+ } catch (error$1) {}
209
273
  return Promise.reject(error);
210
274
  });
211
275
  service.interceptors.response.use((response) => {
212
- if (response.status && response.status === 401) return unauthorizedCallback(response);
213
- if (response.status && response.status === 403) return forbiddenCallback(response);
214
- if (response.status && (response.status === 400 || response.status === 500)) {
215
- failCallback(response);
216
- return Promise.reject(response);
217
- }
218
276
  if (verifySuccessCode(response.data.code, code)) {
219
277
  successCallback(response);
220
278
  return response;
@@ -224,24 +282,21 @@ function serviceFactory(options, ...callbacks) {
224
282
  }
225
283
  }, (error) => {
226
284
  if (axios.isCancel(error)) return Promise.reject(error);
227
- let status = 0;
228
285
  try {
229
- status = error.response.status || error.response.data.status;
230
- } catch {
231
- if (error.toString().includes("Error: timeout")) return Promise.reject(error);
286
+ const status = error.response.status || error.response.data.status;
287
+ if (status === 401) unauthorizedCallback(error);
288
+ else if (status === 403) forbiddenCallback(error);
289
+ else if (status === 404) notfoundCallback(error);
290
+ else failCallback(error);
291
+ } catch (_) {
292
+ failCallback(error);
232
293
  }
233
- if (status) if (status === 403) forbiddenCallback(error);
234
- else if (status === 404) notfoundCallback(error);
235
- else failCallback(error);
236
- else failCallback(error);
237
- return Promise.reject(error);
238
- });
239
- service.interceptors.response.use(responseInterceptor, (error) => {
240
294
  return Promise.reject(error);
241
295
  });
296
+ service.interceptors.response.use(responseInterceptor, responseInterceptorFailCallback);
242
297
  if (mock) setupMockAdapter(service, mock);
243
298
  return service;
244
299
  }
245
300
 
246
301
  //#endregion
247
- export { getQueryObject, getToken, removeToken, serviceFactory, setTokenFromUrl, verifySuccessCode };
302
+ export { BREAKPOINTS, MEDIA_QUERIES, logger as debug, getQueryObject, getToken, isDesktop, isMobile, isTablet, isTouchDevice, removeToken, serviceFactory, setTokenFromUrl, verifySuccessCode };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yeepay/client-utils",
3
3
  "type": "module",
4
- "version": "3.1.0",
4
+ "version": "4.0.0",
5
5
  "description": "shared utilities for yeepay client packages",
6
6
  "author": "Yong Yang",
7
7
  "homepage": "http://gitlab.yeepay.com/ued/client-utils#readme",
@@ -21,20 +21,24 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "@imyangyong/utils": "^0.8.0",
24
+ "@yeepay/client-utils": "link:",
24
25
  "ansis": "^4.1.0",
25
- "axios": "^1.7.0",
26
+ "axios": "^1.13.2",
26
27
  "axios-mock-adapter": "^2.1.0",
28
+ "debug": "^4.4.3",
27
29
  "js-cookie": "^3.0.5",
28
30
  "picomatch": "^4.0.3"
29
31
  },
30
32
  "devDependencies": {
31
33
  "@antfu/ni": "^0.21.12",
34
+ "@types/debug": "^4.1.12",
32
35
  "@types/js-cookie": "^3.0.6",
33
36
  "@types/picomatch": "^4.0.2",
34
37
  "@yeepay/eslint-config": "^4.17.0",
35
38
  "bumpp": "^9.4.1",
36
39
  "eslint": "^9.32.0",
37
40
  "eslint-plugin-format": "^0.1.1",
41
+ "happy-dom": "^18.0.1",
38
42
  "lint-staged": "^15.2.2",
39
43
  "simple-git-hooks": "^2.11.1",
40
44
  "tsdown": "^0.14.1",
@@ -53,6 +57,7 @@
53
57
  "watch": "tsdown --watch",
54
58
  "lint": "eslint .",
55
59
  "release": "bumpp && pnpm publish",
56
- "test": "vitest"
60
+ "test": "vitest",
61
+ "play": "npm -C playground run dev"
57
62
  }
58
63
  }