@0xchain/request 1.1.0-beta.60 → 1.1.0-beta.62

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/index.js CHANGED
@@ -1,23 +1,26 @@
1
1
  import axios, { isAxiosError, AxiosError } from "axios";
2
2
  import qs from "qs";
3
- import Cookies from "js-cookie";
3
+ import jsCookie from "js-cookie";
4
4
  import { logger } from "@0xchain/logger";
5
- import TokenManager, { isTokenExpired, getToken, removeAllToken, setToken } from "@0xchain/token";
5
+ import { getToken, setToken, isTokenExpired } from "@0xchain/token";
6
6
  const isSimplifiedChinese = () => {
7
7
  return /^(zh-hans|zh-cn|zh-sg|zh-my)$/.test(window?.navigator.language.toLowerCase());
8
8
  };
9
9
  const matchSystemLanguage = () => {
10
10
  return isSimplifiedChinese() ? "zh-hans" : "";
11
11
  };
12
+ function isServerRuntime() {
13
+ if (typeof window !== "undefined") {
14
+ return false;
15
+ }
16
+ return true;
17
+ }
12
18
  let httpAgent;
13
19
  let httpsAgent;
14
20
  let serverHeaderProvider;
15
21
  function setServerHeaderProvider(provider) {
16
22
  serverHeaderProvider = provider;
17
23
  }
18
- function isServerRuntime() {
19
- return typeof window === "undefined" || process.env.IS_SERVER === "true";
20
- }
21
24
  function isEdgeRuntime() {
22
25
  return process.env.NEXT_RUNTIME === "edge" || typeof globalThis.EdgeRuntime !== "undefined";
23
26
  }
@@ -76,8 +79,8 @@ async function commonHeader(config) {
76
79
  }
77
80
  }
78
81
  } else {
79
- access_token = Cookies.get("access_token") || "";
80
- language = Cookies.get("NEXT_LOCALE") || matchSystemLanguage() || "en";
82
+ access_token = jsCookie.get("access_token") || "";
83
+ language = jsCookie.get("NEXT_LOCALE") || matchSystemLanguage() || "en";
81
84
  }
82
85
  const isRefreshToken = config.headers?.["isRefreshToken"];
83
86
  setHeader(config, "Accept-Language", language);
@@ -190,15 +193,58 @@ function errorInterceptor(error) {
190
193
  logger.error({ message, details: err.details, errType: "server error" });
191
194
  return Promise.reject(err);
192
195
  }
193
- function ResponseInterceptor(response) {
194
- const config = response.config;
195
- if (config.returnFullResponse) {
196
- return response;
196
+ const refreshBaseURL = typeof window !== "undefined" ? process.env.NEXT_PUBLIC_AUTH_URL || window.location.origin : process.env.AUTH_INNER_API_URL || process.env.NEXT_PUBLIC_AUTH_URL;
197
+ const refreshClient = axios.create({
198
+ baseURL: refreshBaseURL,
199
+ timeout: 1e4,
200
+ withCredentials: true,
201
+ paramsSerializer(params) {
202
+ return qs.stringify(params, { arrayFormat: "brackets" });
197
203
  }
198
- if (response.status === 401) {
199
- if (typeof window !== "undefined") {
200
- TokenManager.removeAllToken();
204
+ });
205
+ let refreshPromise = null;
206
+ function refreshAccessToken() {
207
+ if (refreshPromise) {
208
+ return refreshPromise;
209
+ }
210
+ const refresh_token = getToken("refresh_token");
211
+ if (!refresh_token) {
212
+ return Promise.resolve(null);
213
+ }
214
+ const pending = refreshClient.get("/api/refresh/token", {
215
+ params: {
216
+ grant_type: "refresh_token",
217
+ refresh_token
218
+ },
219
+ headers: {
220
+ isRefreshToken: true
221
+ }
222
+ }).then((res) => {
223
+ const data = res?.data?.data;
224
+ if (data?.access_token) {
225
+ setToken(data);
226
+ return data.access_token;
201
227
  }
228
+ return null;
229
+ }).catch(() => null).finally(() => {
230
+ refreshPromise = null;
231
+ });
232
+ refreshPromise = pending;
233
+ return pending;
234
+ }
235
+ function getCurrentAccessToken() {
236
+ return jsCookie.get("access_token") || getToken("access_token") || "";
237
+ }
238
+ function shouldSkipRefresh(config) {
239
+ const url = typeof config?.url === "string" ? config.url : "";
240
+ if (url.includes("/api/refresh/token")) return true;
241
+ if (url.includes("/v2/auth/")) return true;
242
+ if (config?.headers?.isRefreshToken) return true;
243
+ return false;
244
+ }
245
+ function handleBusinessResponse(response) {
246
+ const config = response.config;
247
+ if (config.returnFullResponse) {
202
248
  return response;
203
249
  }
204
250
  if (response.status === 200) {
@@ -244,6 +290,23 @@ function ResponseInterceptor(response) {
244
290
  }
245
291
  return response;
246
292
  }
293
+ function createResponseInterceptor(instance) {
294
+ return async function ResponseInterceptor(response) {
295
+ if (response.status === 401) {
296
+ const config = response.config;
297
+ const canRetry = !isServerRuntime() && !shouldSkipRefresh(config) && !config.__isRetryAfterRefresh;
298
+ if (canRetry) {
299
+ const newAccessToken = await refreshAccessToken();
300
+ if (newAccessToken) {
301
+ config.__isRetryAfterRefresh = true;
302
+ return instance(config);
303
+ }
304
+ }
305
+ return response;
306
+ }
307
+ return handleBusinessResponse(response);
308
+ };
309
+ }
247
310
  function setAuthHeader(config, accessToken) {
248
311
  const value = `Bearer ${accessToken}`;
249
312
  const h = config.headers;
@@ -253,30 +316,17 @@ function setAuthHeader(config, accessToken) {
253
316
  h.Authorization = value;
254
317
  }
255
318
  }
256
- let refreshPromise = null;
257
- const axiosInstance$1 = axios.create({
258
- baseURL: process.env.AUTH_INNER_API_URL || process.env.NEXT_PUBLIC_AUTH_URL,
259
- timeout: 1e4,
260
- withCredentials: true,
261
- // Always include credentials by default
262
- paramsSerializer: function(params) {
263
- return qs.stringify(params, { arrayFormat: "brackets" });
264
- }
265
- });
266
319
  async function refreshToken(config) {
267
- if (typeof window === "undefined" || process.env.IS_SERVER === "true") {
320
+ if (isServerRuntime()) {
268
321
  return config;
269
322
  }
270
323
  if (typeof config.url === "string" && config.url.includes("/api/refresh/token")) {
271
324
  return config;
272
325
  }
273
- const headers = config.headers;
274
- if (refreshPromise) {
275
- await refreshPromise;
276
- const accessAfterWait = Cookies.get("access_token") || getToken("access_token");
277
- if (accessAfterWait) {
278
- setAuthHeader(config, accessAfterWait);
279
- }
326
+ if (typeof config.url === "string" && config.url.includes("/v2/auth/")) {
327
+ return config;
328
+ }
329
+ if (config.headers?.isRefreshToken) {
280
330
  return config;
281
331
  }
282
332
  let expired = false;
@@ -286,49 +336,10 @@ async function refreshToken(config) {
286
336
  expired = false;
287
337
  }
288
338
  if (expired) {
289
- const refresh_token = getToken("refresh_token");
290
- if (!refresh_token) {
291
- removeAllToken();
292
- return config;
293
- }
294
- if (refreshPromise) {
295
- await refreshPromise;
296
- const accessAfterWait = Cookies.get("access_token") || getToken("access_token");
297
- if (accessAfterWait) {
298
- setAuthHeader(config, accessAfterWait);
299
- }
300
- return config;
301
- }
302
- const refreshHeaders = { ...headers };
303
- delete refreshHeaders.Authorization;
304
- delete refreshHeaders.authorization;
305
- refreshHeaders.isRefreshToken = true;
306
- refreshPromise = axiosInstance$1.get(`/api/refresh/token`, {
307
- params: {
308
- grant_type: "refresh_token",
309
- refresh_token
310
- },
311
- headers: {
312
- ...refreshHeaders
313
- }
314
- }).then((res) => {
315
- if (res?.data?.data) {
316
- setToken(res.data.data);
317
- return res.data.data;
318
- } else {
319
- removeAllToken();
320
- return null;
321
- }
322
- }).catch(() => {
323
- removeAllToken();
324
- return null;
325
- }).finally(() => {
326
- refreshPromise = null;
327
- });
328
- await refreshPromise;
329
- const accessAfterRefresh = Cookies.get("access_token") || getToken("access_token");
330
- if (accessAfterRefresh) {
331
- setAuthHeader(config, accessAfterRefresh);
339
+ const newAccessToken = await refreshAccessToken();
340
+ const accessToken = newAccessToken || getCurrentAccessToken();
341
+ if (accessToken) {
342
+ setAuthHeader(config, accessToken);
332
343
  }
333
344
  }
334
345
  return config;
@@ -347,7 +358,7 @@ const axiosInstance = axios.create({
347
358
  });
348
359
  axiosInstance.interceptors.request.use(refreshToken);
349
360
  axiosInstance.interceptors.request.use(commonHeader, errorInterceptor);
350
- axiosInstance.interceptors.response.use(ResponseInterceptor, errorInterceptor);
361
+ axiosInstance.interceptors.response.use(createResponseInterceptor(axiosInstance), errorInterceptor);
351
362
  export {
352
363
  axiosInstance as default,
353
364
  setServerHeaderProvider
@@ -1 +1 @@
1
- {"version":3,"file":"commonHeader.d.ts","sourceRoot":"","sources":["../../src/interceptors/commonHeader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,0BAA0B,EAAE,MAAM,OAAO,CAAC;AACrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAOrD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,oBAAoB,QAErE;AA6BD,wBAAsB,YAAY,CAChC,MAAM,EAAE,0BAA0B,4CAwDnC"}
1
+ {"version":3,"file":"commonHeader.d.ts","sourceRoot":"","sources":["../../src/interceptors/commonHeader.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,0BAA0B,EAAE,MAAM,OAAO,CAAC;AACrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAQrD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,oBAAoB,QAErE;AAyBD,wBAAsB,YAAY,CAChC,MAAM,EAAE,0BAA0B,4CAwDnC"}
@@ -1,3 +1,7 @@
1
1
  import { InternalAxiosRequestConfig } from 'axios';
2
+ /**
3
+ * 请求前的「预测式刷新」:若本地判断 access_token 即将/已过期,提前刷新。
4
+ * 这是一层优化;真正的兜底由响应拦截器的 401 自动刷新+重放保证。
5
+ */
2
6
  export default function refreshToken(config: InternalAxiosRequestConfig): Promise<InternalAxiosRequestConfig<any>>;
3
7
  //# sourceMappingURL=refreshToken.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"refreshToken.d.ts","sourceRoot":"","sources":["../../src/interceptors/refreshToken.ts"],"names":[],"mappings":"AAAA,OAAc,EAAoB,0BAA0B,EAAE,MAAM,OAAO,CAAC;AAuB5E,wBAA8B,YAAY,CACxC,MAAM,EAAE,0BAA0B,4CAkFnC"}
1
+ {"version":3,"file":"refreshToken.d.ts","sourceRoot":"","sources":["../../src/interceptors/refreshToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,0BAA0B,EAAE,MAAM,OAAO,CAAC;AAerE;;;GAGG;AACH,wBAA8B,YAAY,CACxC,MAAM,EAAE,0BAA0B,4CAkCnC"}
@@ -1,3 +1,13 @@
1
- import { AxiosResponse } from 'axios';
2
- export default function ResponseInterceptor(response: AxiosResponse): any;
1
+ import { AxiosInstance, AxiosResponse } from 'axios';
2
+ /**
3
+ * 响应拦截器工厂。
4
+ *
5
+ * 业界标准 401 处理:业务接口返回 401 时,用 refresh_token 刷新一次并重放原请求;
6
+ * 仅当刷新失败时把 401 透传给上层(由显式 logout 决定是否清登录态)。
7
+ * - 仅在客户端重试(SSR 无 cookie jar)。
8
+ * - 每个请求最多重试一次,避免死循环。
9
+ * - /v2/auth/*、刷新接口本身不触发重试。
10
+ */
11
+ export declare function createResponseInterceptor(instance: AxiosInstance): (response: AxiosResponse) => Promise<any>;
12
+ export default createResponseInterceptor;
3
13
  //# sourceMappingURL=response.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/interceptors/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,aAAa,EAAE,MAAM,OAAO,CAAC;AAKlD,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,QAAQ,EAAE,aAAa,GAAG,GAAG,CAuDxE"}
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/interceptors/response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,aAAa,EAAE,aAAa,EAA8B,MAAM,OAAO,CAAC;AAqE7F;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,aAAa,IACrB,UAAU,aAAa,KAAG,OAAO,CAAC,GAAG,CAAC,CAoBjF;AAED,eAAe,yBAAyB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 判断是否处于“服务端运行时”。
3
+ *
4
+ * 关键点:只要存在真实浏览器 window,就一律视为客户端,
5
+ * 避免业务方在客户端 bundle 中固化 IS_SERVER=true 时被误判为服务端,
6
+ * 从而走 next/headers 读 cookie 失败、请求不带 Authorization。
7
+ */
8
+ export declare function isServerRuntime(): boolean;
9
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAKzC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 触发一次 token 刷新;并发调用共享同一个在途请求。
3
+ * @returns 新的 access_token;刷新失败返回 null。
4
+ */
5
+ export declare function refreshAccessToken(): Promise<string | null>;
6
+ export declare function getCurrentAccessToken(): string;
7
+ //# sourceMappingURL=tokenRefresh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenRefresh.d.ts","sourceRoot":"","sources":["../src/tokenRefresh.ts"],"names":[],"mappings":"AA+BA;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmC3D;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xchain/request",
3
- "version": "1.1.0-beta.60",
3
+ "version": "1.1.0-beta.62",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -32,8 +32,8 @@
32
32
  "qs": "6.14.0"
33
33
  },
34
34
  "dependencies": {
35
- "@0xchain/logger": "1.1.0-beta.60",
36
- "@0xchain/token": "1.1.0-beta.60"
35
+ "@0xchain/token": "1.1.0-beta.62",
36
+ "@0xchain/logger": "1.1.0-beta.62"
37
37
  },
38
38
  "nx": {
39
39
  "tags": [