@0xchain/request 1.1.0-beta.61 → 1.1.0-beta.63

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,8 +1,8 @@
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
  };
@@ -79,8 +79,8 @@ async function commonHeader(config) {
79
79
  }
80
80
  }
81
81
  } else {
82
- access_token = Cookies.get("access_token") || "";
83
- language = Cookies.get("NEXT_LOCALE") || matchSystemLanguage() || "en";
82
+ access_token = jsCookie.get("access_token") || "";
83
+ language = jsCookie.get("NEXT_LOCALE") || matchSystemLanguage() || "en";
84
84
  }
85
85
  const isRefreshToken = config.headers?.["isRefreshToken"];
86
86
  setHeader(config, "Accept-Language", language);
@@ -193,15 +193,58 @@ function errorInterceptor(error) {
193
193
  logger.error({ message, details: err.details, errType: "server error" });
194
194
  return Promise.reject(err);
195
195
  }
196
- function ResponseInterceptor(response) {
197
- const config = response.config;
198
- if (config.returnFullResponse) {
199
- 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" });
203
+ }
204
+ });
205
+ let refreshPromise = null;
206
+ function refreshAccessToken() {
207
+ if (refreshPromise) {
208
+ return refreshPromise;
200
209
  }
201
- if (response.status === 401) {
202
- if (typeof window !== "undefined") {
203
- TokenManager.removeAllToken();
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;
204
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) {
205
248
  return response;
206
249
  }
207
250
  if (response.status === 200) {
@@ -247,6 +290,23 @@ function ResponseInterceptor(response) {
247
290
  }
248
291
  return response;
249
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
+ }
250
310
  function setAuthHeader(config, accessToken) {
251
311
  const value = `Bearer ${accessToken}`;
252
312
  const h = config.headers;
@@ -256,16 +316,6 @@ function setAuthHeader(config, accessToken) {
256
316
  h.Authorization = value;
257
317
  }
258
318
  }
259
- let refreshPromise = null;
260
- const axiosInstance$1 = axios.create({
261
- baseURL: process.env.AUTH_INNER_API_URL || process.env.NEXT_PUBLIC_AUTH_URL,
262
- timeout: 1e4,
263
- withCredentials: true,
264
- // Always include credentials by default
265
- paramsSerializer: function(params) {
266
- return qs.stringify(params, { arrayFormat: "brackets" });
267
- }
268
- });
269
319
  async function refreshToken(config) {
270
320
  if (isServerRuntime()) {
271
321
  return config;
@@ -273,13 +323,10 @@ async function refreshToken(config) {
273
323
  if (typeof config.url === "string" && config.url.includes("/api/refresh/token")) {
274
324
  return config;
275
325
  }
276
- const headers = config.headers;
277
- if (refreshPromise) {
278
- await refreshPromise;
279
- const accessAfterWait = Cookies.get("access_token") || getToken("access_token");
280
- if (accessAfterWait) {
281
- setAuthHeader(config, accessAfterWait);
282
- }
326
+ if (typeof config.url === "string" && config.url.includes("/v2/auth/")) {
327
+ return config;
328
+ }
329
+ if (config.headers?.isRefreshToken) {
283
330
  return config;
284
331
  }
285
332
  let expired = false;
@@ -289,49 +336,10 @@ async function refreshToken(config) {
289
336
  expired = false;
290
337
  }
291
338
  if (expired) {
292
- const refresh_token = getToken("refresh_token");
293
- if (!refresh_token) {
294
- removeAllToken();
295
- return config;
296
- }
297
- if (refreshPromise) {
298
- await refreshPromise;
299
- const accessAfterWait = Cookies.get("access_token") || getToken("access_token");
300
- if (accessAfterWait) {
301
- setAuthHeader(config, accessAfterWait);
302
- }
303
- return config;
304
- }
305
- const refreshHeaders = { ...headers };
306
- delete refreshHeaders.Authorization;
307
- delete refreshHeaders.authorization;
308
- refreshHeaders.isRefreshToken = true;
309
- refreshPromise = axiosInstance$1.get(`/api/refresh/token`, {
310
- params: {
311
- grant_type: "refresh_token",
312
- refresh_token
313
- },
314
- headers: {
315
- ...refreshHeaders
316
- }
317
- }).then((res) => {
318
- if (res?.data?.data) {
319
- setToken(res.data.data);
320
- return res.data.data;
321
- } else {
322
- removeAllToken();
323
- return null;
324
- }
325
- }).catch(() => {
326
- removeAllToken();
327
- return null;
328
- }).finally(() => {
329
- refreshPromise = null;
330
- });
331
- await refreshPromise;
332
- const accessAfterRefresh = Cookies.get("access_token") || getToken("access_token");
333
- if (accessAfterRefresh) {
334
- setAuthHeader(config, accessAfterRefresh);
339
+ const newAccessToken = await refreshAccessToken();
340
+ const accessToken = newAccessToken || getCurrentAccessToken();
341
+ if (accessToken) {
342
+ setAuthHeader(config, accessToken);
335
343
  }
336
344
  }
337
345
  return config;
@@ -350,7 +358,7 @@ const axiosInstance = axios.create({
350
358
  });
351
359
  axiosInstance.interceptors.request.use(refreshToken);
352
360
  axiosInstance.interceptors.request.use(commonHeader, errorInterceptor);
353
- axiosInstance.interceptors.response.use(ResponseInterceptor, errorInterceptor);
361
+ axiosInstance.interceptors.response.use(createResponseInterceptor(axiosInstance), errorInterceptor);
354
362
  export {
355
363
  axiosInstance as default,
356
364
  setServerHeaderProvider
@@ -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;AAwB5E,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,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.61",
3
+ "version": "1.1.0-beta.63",
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.61",
36
- "@0xchain/token": "1.1.0-beta.61"
35
+ "@0xchain/logger": "1.1.0-beta.63",
36
+ "@0xchain/token": "1.1.0-beta.63"
37
37
  },
38
38
  "nx": {
39
39
  "tags": [