@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 +87 -76
- package/dist/interceptors/commonHeader.d.ts.map +1 -1
- package/dist/interceptors/refreshToken.d.ts +4 -0
- package/dist/interceptors/refreshToken.d.ts.map +1 -1
- package/dist/interceptors/response.d.ts +12 -2
- package/dist/interceptors/response.d.ts.map +1 -1
- package/dist/runtime.d.ts +9 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/tokenRefresh.d.ts +7 -0
- package/dist/tokenRefresh.d.ts.map +1 -0
- package/package.json +3 -3
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
|
|
3
|
+
import jsCookie from "js-cookie";
|
|
4
4
|
import { logger } from "@0xchain/logger";
|
|
5
|
-
import
|
|
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 =
|
|
80
|
-
language =
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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 (
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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(
|
|
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;
|
|
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,
|
|
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
|
-
|
|
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;
|
|
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 @@
|
|
|
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.
|
|
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/
|
|
36
|
-
"@0xchain/
|
|
35
|
+
"@0xchain/token": "1.1.0-beta.62",
|
|
36
|
+
"@0xchain/logger": "1.1.0-beta.62"
|
|
37
37
|
},
|
|
38
38
|
"nx": {
|
|
39
39
|
"tags": [
|