@gz-fronted/gz-pc 0.1.0 → 0.1.1-beta.2
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 +122 -6
- package/dist/fetch/index.cjs +148 -4
- package/dist/fetch/index.d.cts +14 -1
- package/dist/fetch/index.d.ts +14 -1
- package/dist/fetch/index.js +149 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,8 +22,9 @@ Axios 和 ahooks 是 gz-pc 的普通 dependencies,安装 gz-pc 时会自动安
|
|
|
22
22
|
|
|
23
23
|
## 请求协议
|
|
24
24
|
|
|
25
|
-
gz-fetch
|
|
26
|
-
|
|
25
|
+
gz-fetch 默认将所有 HTTP `2xx` 状态视为成功,包括 200、201、202、204 和 206。
|
|
26
|
+
非 `2xx` 状态进入统一错误处理并抛出 `GzFetchError`,错误中保留 `status` 和
|
|
27
|
+
`responseData`。
|
|
27
28
|
|
|
28
29
|
成功时直接返回后端原始的 `response.data`,不会返回 `AxiosResponse`,也不会:
|
|
29
30
|
|
|
@@ -31,6 +32,29 @@ gz-fetch **只有在 HTTP Status 为 200 时才认为请求成功**。201、204
|
|
|
31
32
|
- 自动解包 `data`
|
|
32
33
|
- 转换分页、字段、日期或任何服务端数据
|
|
33
34
|
|
|
35
|
+
例如后端返回以下内容时,调用方取得的是包含 `code`、`data`、`message` 的完整
|
|
36
|
+
对象:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
interface CreateOrderResponse {
|
|
40
|
+
code: number;
|
|
41
|
+
data: {
|
|
42
|
+
orderId: string;
|
|
43
|
+
};
|
|
44
|
+
message: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const response = await gzFetch<CreateOrderResponse, CreateOrderParams>({
|
|
48
|
+
url: '/order/create',
|
|
49
|
+
method: 'POST',
|
|
50
|
+
params,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
response.code;
|
|
54
|
+
response.data.orderId;
|
|
55
|
+
response.message;
|
|
56
|
+
```
|
|
57
|
+
|
|
34
58
|
```ts
|
|
35
59
|
import { configureGzFetch, gzFetch } from '@gz-fronted/gz-pc/fetch';
|
|
36
60
|
|
|
@@ -105,6 +129,7 @@ function gzFetch<TResponse, TRequestParams = unknown>(
|
|
|
105
129
|
| `skipAuth` | `boolean` | 否 | 为 `true` 时跳过 Token 注入 |
|
|
106
130
|
| `responseType` | `'json' \| 'blob' \| 'text'` | 否 | 响应数据类型,默认 `json` |
|
|
107
131
|
| `signal` | `AbortSignal` | 否 | 取消请求的标准 AbortSignal |
|
|
132
|
+
| `withCredentials` | `boolean` | 否 | 是否携带跨域 Cookie 等凭证 |
|
|
108
133
|
|
|
109
134
|
请求入参字段统一使用 `params`,业务代码不需要区分 Axios 的 `params` 和 `data`:
|
|
110
135
|
|
|
@@ -112,6 +137,8 @@ function gzFetch<TResponse, TRequestParams = unknown>(
|
|
|
112
137
|
- POST、PUT:`params` 转为 Request Body。
|
|
113
138
|
|
|
114
139
|
业务请求配置不支持 `data`,也不得混用 `params` 和 `data`。
|
|
140
|
+
POST、PUT 的 `params` 支持 JSON 对象、`FormData`、`URLSearchParams` 和 `Blob`;
|
|
141
|
+
自定义请求头通过 `headers` 传入。
|
|
115
142
|
|
|
116
143
|
完整示例:
|
|
117
144
|
|
|
@@ -153,15 +180,38 @@ const result = await gzFetch<SaveResult, SaveParams>({
|
|
|
153
180
|
| ------------------ | ----------------------------------------------------------- | ----------------- | -------------------------- |
|
|
154
181
|
| `baseURL` | `string` | 无 | 所有业务请求的基础地址 |
|
|
155
182
|
| `timeout` | `number` | `15000` | 默认请求超时时间,单位毫秒 |
|
|
183
|
+
| `validateStatus` | `(status: number) => boolean` | 所有 `2xx` | 自定义 HTTP 成功状态范围 |
|
|
156
184
|
| `getToken` | `() => string \| undefined \| Promise<string \| undefined>` | 无 | 每次请求前动态获取 Token |
|
|
157
185
|
| `showErrorMessage` | `boolean` | `true` | 实例级错误提示开关 |
|
|
158
186
|
| `auth.headerName` | `string` | `Authorization` | Token 请求头名称 |
|
|
159
187
|
| `auth.formatToken` | `(token: string) => string` | `Bearer ${token}` | Token 格式化函数 |
|
|
188
|
+
| `unauthorized` | `GzFetchUnauthorizedOptions` | 未开启 | 可选的 HTTP 401 统一处理 |
|
|
160
189
|
| `middlewares` | `readonly GzFetchMiddleware[]` | `[]` | 请求、响应和错误中间件 |
|
|
161
190
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
191
|
+
需要覆盖默认成功状态时,只在应用初始化层配置:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
configureGzFetch({
|
|
195
|
+
validateStatus: (status) => status === 200 || status === 304,
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
当前稳定 API 没有开放 Axios 的 `paramsSerializer`、`onUploadProgress` 等配置,也
|
|
200
|
+
暂不支持 `PATCH`。业务代码只应传入上表列出的字段;如果后续确有通用场景,再
|
|
201
|
+
通过 gz-pc 统一扩展类型和请求核心。
|
|
202
|
+
|
|
203
|
+
### 跨域凭证
|
|
204
|
+
|
|
205
|
+
需要浏览器携带跨域 Cookie 等凭证时,在单次请求中开启 `withCredentials`:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
await gzFetch<QueryPresetResponse, QueryPresetParams>({
|
|
209
|
+
url: '/queryFilterTemplate/list',
|
|
210
|
+
method: 'POST',
|
|
211
|
+
params,
|
|
212
|
+
withCredentials: true,
|
|
213
|
+
});
|
|
214
|
+
```
|
|
165
215
|
|
|
166
216
|
## Token
|
|
167
217
|
|
|
@@ -196,7 +246,7 @@ configureGzFetch({
|
|
|
196
246
|
|
|
197
247
|
## 错误提示
|
|
198
248
|
|
|
199
|
-
HTTP 非
|
|
249
|
+
HTTP 非 `2xx` 时仅尝试读取响应体的 `msg`。没有有效 `msg` 时使用默认文案,最终
|
|
200
250
|
抛出 `GzFetchError`。
|
|
201
251
|
|
|
202
252
|
错误类型包括:
|
|
@@ -235,6 +285,72 @@ controller.abort();
|
|
|
235
285
|
await promise;
|
|
236
286
|
```
|
|
237
287
|
|
|
288
|
+
## HTTP 401 统一处理
|
|
289
|
+
|
|
290
|
+
401 统一处理默认关闭,未显式开启时继续执行原有 HTTP 错误提示和抛错逻辑,不弹窗、
|
|
291
|
+
不跳转。需要启用时,在应用初始化配置中传入 `unauthorized.enabled: true`:
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
configureGzFetch({
|
|
295
|
+
baseURL: '/api',
|
|
296
|
+
getToken: () => runtimeToken,
|
|
297
|
+
unauthorized: {
|
|
298
|
+
enabled: true,
|
|
299
|
+
loginUrl: '/login',
|
|
300
|
+
modalTitle: '登录失效',
|
|
301
|
+
modalMessage: '当前登录状态已失效,请重新登录。',
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
配置默认值如下:
|
|
307
|
+
|
|
308
|
+
| 字段 | 类型 | 默认值 |
|
|
309
|
+
| ---------------- | ------------ | --------------------------------------- |
|
|
310
|
+
| `enabled` | `boolean` | `false` |
|
|
311
|
+
| `loginUrl` | `string` | `/login` |
|
|
312
|
+
| `modalTitle` | `string` | `登录失效` |
|
|
313
|
+
| `modalMessage` | `string` | `当前登录状态已失效,请重新登录。` |
|
|
314
|
+
| `onUnauthorized` | `() => void` | 无;确认后使用 `window.location.assign` |
|
|
315
|
+
|
|
316
|
+
为了让弹窗继承宿主项目当前的 gg-ui 主题,需要在应用根部的 `ConfigProvider` 内挂载一次
|
|
317
|
+
`GzFetchUnauthorizedModal`:
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
import { GzFetchUnauthorizedModal } from '@gz-fronted/gz-pc/fetch';
|
|
321
|
+
import { ConfigProvider } from '@chenhui996/gg-ui';
|
|
322
|
+
|
|
323
|
+
<ConfigProvider themeMode={themeMode}>
|
|
324
|
+
<GzFetchUnauthorizedModal />
|
|
325
|
+
<App />
|
|
326
|
+
</ConfigProvider>;
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
该组件使用受控 gg-ui `Modal`,不会调用脱离 React 上下文的静态
|
|
330
|
+
`Modal.confirm`,因此主题切换时会继续读取宿主 `ConfigProvider` 的主题 Token,
|
|
331
|
+
无需复制亮色或暗色样式。业务页面不需要维护 401 状态或重复渲染弹窗。
|
|
332
|
+
|
|
333
|
+
所有由同一份 gz-pc 包创建的默认客户端和独立客户端共享一个 401 管理器。首个 401
|
|
334
|
+
打开弹窗,后续并发 401 不会创建新弹窗;取消或弹窗关闭动画结束后会释放状态,后续
|
|
335
|
+
独立发生的 401 可以再次打开。确认操作也有同一状态锁,不会重复执行登录跳转。
|
|
336
|
+
|
|
337
|
+
业务项目需要自定义 SSO、登出或状态清理时传入 `onUnauthorized`。用户确认后优先执行
|
|
338
|
+
该回调;只有未提供回调时才通过 `window.location.assign(loginUrl)` 跳转:
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
configureGzFetch({
|
|
342
|
+
unauthorized: {
|
|
343
|
+
enabled: true,
|
|
344
|
+
onUnauthorized: () => {
|
|
345
|
+
authService.logout();
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
开启该能力后,401 仍会作为 `GzFetchError` 抛给调用方,但请求核心不会再为同一个
|
|
352
|
+
401 额外调用普通 `message.error`。其他 HTTP 错误、网络错误、超时和取消请求行为不变。
|
|
353
|
+
|
|
238
354
|
## 中间件
|
|
239
355
|
|
|
240
356
|
中间件的 `onRequest`、`onResponse` 和 `onError` 都按注册顺序执行,不采用洋葱
|
package/dist/fetch/index.cjs
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
var ggUi = require('@chenhui996/gg-ui');
|
|
4
4
|
var axios = require('axios');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
7
|
|
|
6
8
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
9
|
|
|
@@ -96,9 +98,107 @@ async function runErrorMiddlewares(error, context, middlewares) {
|
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
// src/fetch/unauthorized.ts
|
|
102
|
+
var DEFAULT_UNAUTHORIZED_LOGIN_URL = "/login";
|
|
103
|
+
var DEFAULT_UNAUTHORIZED_MODAL_TITLE = "登录失效";
|
|
104
|
+
var DEFAULT_UNAUTHORIZED_MODAL_MESSAGE = "当前登录状态已失效,请重新登录。";
|
|
105
|
+
var IDLE_SNAPSHOT = { phase: "idle" };
|
|
106
|
+
var snapshot = IDLE_SNAPSHOT;
|
|
107
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
108
|
+
var modalHosts = /* @__PURE__ */ new Set();
|
|
109
|
+
var emitChange = () => {
|
|
110
|
+
for (const listener of listeners) {
|
|
111
|
+
listener();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
var setSnapshot = (nextSnapshot) => {
|
|
115
|
+
snapshot = nextSnapshot;
|
|
116
|
+
emitChange();
|
|
117
|
+
};
|
|
118
|
+
var resolveUnauthorizedOptions = (options) => {
|
|
119
|
+
var _a, _b, _c, _d;
|
|
120
|
+
return {
|
|
121
|
+
enabled: (_a = options == null ? void 0 : options.enabled) != null ? _a : false,
|
|
122
|
+
loginUrl: (_b = options == null ? void 0 : options.loginUrl) != null ? _b : DEFAULT_UNAUTHORIZED_LOGIN_URL,
|
|
123
|
+
modalTitle: (_c = options == null ? void 0 : options.modalTitle) != null ? _c : DEFAULT_UNAUTHORIZED_MODAL_TITLE,
|
|
124
|
+
modalMessage: (_d = options == null ? void 0 : options.modalMessage) != null ? _d : DEFAULT_UNAUTHORIZED_MODAL_MESSAGE,
|
|
125
|
+
...(options == null ? void 0 : options.onUnauthorized) === void 0 ? {} : { onUnauthorized: options.onUnauthorized }
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
var getUnauthorizedSnapshot = () => snapshot;
|
|
129
|
+
var getUnauthorizedServerSnapshot = () => IDLE_SNAPSHOT;
|
|
130
|
+
var subscribeUnauthorized = (listener) => {
|
|
131
|
+
listeners.add(listener);
|
|
132
|
+
return () => listeners.delete(listener);
|
|
133
|
+
};
|
|
134
|
+
var registerUnauthorizedModalHost = (hostId) => {
|
|
135
|
+
modalHosts.add(hostId);
|
|
136
|
+
if (snapshot.activeHostId === void 0) {
|
|
137
|
+
setSnapshot({ ...snapshot, activeHostId: hostId });
|
|
138
|
+
}
|
|
139
|
+
return () => {
|
|
140
|
+
modalHosts.delete(hostId);
|
|
141
|
+
if (snapshot.activeHostId !== hostId) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const nextHost = modalHosts.values().next();
|
|
145
|
+
const nextHostId = nextHost.done ? void 0 : nextHost.value;
|
|
146
|
+
if (snapshot.phase === "closing") {
|
|
147
|
+
setSnapshot(
|
|
148
|
+
nextHostId === void 0 ? IDLE_SNAPSHOT : { activeHostId: nextHostId, phase: "idle" }
|
|
149
|
+
);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (nextHostId === void 0) {
|
|
153
|
+
setSnapshot(
|
|
154
|
+
snapshot.options === void 0 ? { phase: snapshot.phase } : { options: snapshot.options, phase: snapshot.phase }
|
|
155
|
+
);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
setSnapshot({ ...snapshot, activeHostId: nextHostId });
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
var openUnauthorizedModal = (options) => {
|
|
162
|
+
if (!options.enabled || snapshot.phase !== "idle") {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
setSnapshot({
|
|
166
|
+
...snapshot.activeHostId === void 0 ? {} : { activeHostId: snapshot.activeHostId },
|
|
167
|
+
options,
|
|
168
|
+
phase: "open"
|
|
169
|
+
});
|
|
170
|
+
return true;
|
|
171
|
+
};
|
|
172
|
+
var beginCloseUnauthorizedModal = () => {
|
|
173
|
+
if (snapshot.phase !== "open" || snapshot.options === void 0) {
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
176
|
+
const { options } = snapshot;
|
|
177
|
+
setSnapshot({ ...snapshot, phase: "closing" });
|
|
178
|
+
return options;
|
|
179
|
+
};
|
|
180
|
+
var cancelUnauthorizedModal = () => beginCloseUnauthorizedModal() !== void 0;
|
|
181
|
+
var confirmUnauthorizedModal = () => beginCloseUnauthorizedModal();
|
|
182
|
+
var completeUnauthorizedModal = () => {
|
|
183
|
+
if (snapshot.phase !== "closing") {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
setSnapshot(
|
|
187
|
+
snapshot.activeHostId === void 0 ? IDLE_SNAPSHOT : { activeHostId: snapshot.activeHostId, phase: "idle" }
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
var executeUnauthorizedAction = (options, navigate = (loginUrl) => window.location.assign(loginUrl)) => {
|
|
191
|
+
if (options.onUnauthorized) {
|
|
192
|
+
options.onUnauthorized();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
navigate(options.loginUrl);
|
|
196
|
+
};
|
|
197
|
+
|
|
99
198
|
// src/fetch/client.ts
|
|
100
199
|
var DEFAULT_TIMEOUT = 15e3;
|
|
101
200
|
var DEFAULT_AUTH_HEADER = "Authorization";
|
|
201
|
+
var DEFAULT_VALIDATE_STATUS = (status) => status >= 200 && status < 300;
|
|
102
202
|
function toHeaderRecord(headers) {
|
|
103
203
|
const result = {};
|
|
104
204
|
for (const [name, value] of Object.entries(headers)) {
|
|
@@ -109,15 +209,16 @@ function toHeaderRecord(headers) {
|
|
|
109
209
|
return result;
|
|
110
210
|
}
|
|
111
211
|
function createGzFetch(options = {}) {
|
|
112
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
212
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
113
213
|
const middlewares = (_a = options.middlewares) != null ? _a : [];
|
|
114
214
|
const showInstanceErrorMessage = (_b = options.showErrorMessage) != null ? _b : true;
|
|
115
215
|
const headerName = (_d = (_c = options.auth) == null ? void 0 : _c.headerName) != null ? _d : DEFAULT_AUTH_HEADER;
|
|
116
216
|
const formatToken = (_f = (_e = options.auth) == null ? void 0 : _e.formatToken) != null ? _f : ((token) => `Bearer ${token}`);
|
|
217
|
+
const unauthorizedOptions = resolveUnauthorizedOptions(options.unauthorized);
|
|
117
218
|
const instance = axios__default.default.create({
|
|
118
219
|
...options.baseURL === void 0 ? {} : { baseURL: options.baseURL },
|
|
119
220
|
timeout: (_g = options.timeout) != null ? _g : DEFAULT_TIMEOUT,
|
|
120
|
-
validateStatus: (
|
|
221
|
+
validateStatus: (_h = options.validateStatus) != null ? _h : DEFAULT_VALIDATE_STATUS
|
|
121
222
|
});
|
|
122
223
|
async function request(initialConfig) {
|
|
123
224
|
var _a2;
|
|
@@ -143,7 +244,8 @@ function createGzFetch(options = {}) {
|
|
|
143
244
|
...usesRequestBody || currentConfig.params === void 0 ? {} : { params: currentConfig.params },
|
|
144
245
|
...currentConfig.timeout === void 0 ? {} : { timeout: currentConfig.timeout },
|
|
145
246
|
...currentConfig.responseType === void 0 ? {} : { responseType: currentConfig.responseType },
|
|
146
|
-
...currentConfig.signal === void 0 ? {} : { signal: currentConfig.signal }
|
|
247
|
+
...currentConfig.signal === void 0 ? {} : { signal: currentConfig.signal },
|
|
248
|
+
...currentConfig.withCredentials === void 0 ? {} : { withCredentials: currentConfig.withCredentials }
|
|
147
249
|
};
|
|
148
250
|
const response = await instance.request(axiosConfig);
|
|
149
251
|
const context = {
|
|
@@ -154,9 +256,13 @@ function createGzFetch(options = {}) {
|
|
|
154
256
|
return await runResponseMiddlewares(response.data, context, middlewares);
|
|
155
257
|
} catch (originalError) {
|
|
156
258
|
const error = toGzFetchError(originalError);
|
|
259
|
+
const shouldHandleUnauthorized = unauthorizedOptions.enabled && error.type === "HTTP_ERROR" && error.status === 401;
|
|
260
|
+
if (shouldHandleUnauthorized) {
|
|
261
|
+
openUnauthorizedModal(unauthorizedOptions);
|
|
262
|
+
}
|
|
157
263
|
await runErrorMiddlewares(error, { config: currentConfig }, middlewares);
|
|
158
264
|
const shouldShowError = (_a2 = currentConfig.showErrorMessage) != null ? _a2 : showInstanceErrorMessage;
|
|
159
|
-
if (shouldShowError && error.type !== "CANCELED_ERROR") {
|
|
265
|
+
if (shouldShowError && error.type !== "CANCELED_ERROR" && !shouldHandleUnauthorized) {
|
|
160
266
|
ggUi.message.error(error.message);
|
|
161
267
|
}
|
|
162
268
|
throw error;
|
|
@@ -192,8 +298,46 @@ async function gzFetch(config) {
|
|
|
192
298
|
}
|
|
193
299
|
return defaultClient(config);
|
|
194
300
|
}
|
|
301
|
+
var GzFetchUnauthorizedModal = () => {
|
|
302
|
+
var _a, _b, _c;
|
|
303
|
+
const hostIdRef = react.useRef(void 0);
|
|
304
|
+
(_a = hostIdRef.current) != null ? _a : hostIdRef.current = /* @__PURE__ */ Symbol("gz-fetch-unauthorized-modal-host");
|
|
305
|
+
const hostId = hostIdRef.current;
|
|
306
|
+
const snapshot2 = react.useSyncExternalStore(
|
|
307
|
+
subscribeUnauthorized,
|
|
308
|
+
getUnauthorizedSnapshot,
|
|
309
|
+
getUnauthorizedServerSnapshot
|
|
310
|
+
);
|
|
311
|
+
react.useEffect(() => registerUnauthorizedModalHost(hostId), [hostId]);
|
|
312
|
+
if (snapshot2.activeHostId !== hostId) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
const handleConfirm = () => {
|
|
316
|
+
const options = confirmUnauthorizedModal();
|
|
317
|
+
if (options) {
|
|
318
|
+
executeUnauthorizedAction(options);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
322
|
+
ggUi.Modal,
|
|
323
|
+
{
|
|
324
|
+
afterClose: completeUnauthorizedModal,
|
|
325
|
+
cancelText: "取消",
|
|
326
|
+
closable: true,
|
|
327
|
+
okText: "重新登录",
|
|
328
|
+
open: snapshot2.phase === "open",
|
|
329
|
+
title: (_b = snapshot2.options) == null ? void 0 : _b.modalTitle,
|
|
330
|
+
onCancel: cancelUnauthorizedModal,
|
|
331
|
+
onOk: handleConfirm,
|
|
332
|
+
children: (_c = snapshot2.options) == null ? void 0 : _c.modalMessage
|
|
333
|
+
}
|
|
334
|
+
);
|
|
335
|
+
};
|
|
336
|
+
GzFetchUnauthorizedModal.displayName = "GzFetchUnauthorizedModal";
|
|
337
|
+
var GzFetchUnauthorizedModal_default = GzFetchUnauthorizedModal;
|
|
195
338
|
|
|
196
339
|
exports.GzFetchError = GzFetchError;
|
|
340
|
+
exports.GzFetchUnauthorizedModal = GzFetchUnauthorizedModal_default;
|
|
197
341
|
exports.configureGzFetch = configureGzFetch;
|
|
198
342
|
exports.createGzFetch = createGzFetch;
|
|
199
343
|
exports.getResponseMessage = getResponseMessage;
|
package/dist/fetch/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AxiosHeaders } from 'axios';
|
|
2
|
+
import { FC } from 'react';
|
|
2
3
|
|
|
3
4
|
type GzFetchErrorType = 'HTTP_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT_ERROR' | 'CANCELED_ERROR' | 'UNKNOWN_ERROR';
|
|
4
5
|
interface GzFetchErrorOptions {
|
|
@@ -31,6 +32,7 @@ interface GzRequestConfig<TRequestParams = unknown> {
|
|
|
31
32
|
skipAuth?: boolean;
|
|
32
33
|
responseType?: GzResponseType;
|
|
33
34
|
signal?: AbortSignal;
|
|
35
|
+
withCredentials?: boolean;
|
|
34
36
|
}
|
|
35
37
|
interface GzInternalRequestConfig extends Omit<GzRequestConfig<unknown>, 'headers'> {
|
|
36
38
|
headers?: GzRequestHeaders;
|
|
@@ -53,12 +55,21 @@ interface GzFetchAuthOptions {
|
|
|
53
55
|
headerName?: string;
|
|
54
56
|
formatToken?: (token: string) => string;
|
|
55
57
|
}
|
|
58
|
+
interface GzFetchUnauthorizedOptions {
|
|
59
|
+
enabled?: boolean;
|
|
60
|
+
loginUrl?: string;
|
|
61
|
+
modalTitle?: string;
|
|
62
|
+
modalMessage?: string;
|
|
63
|
+
onUnauthorized?: () => void;
|
|
64
|
+
}
|
|
56
65
|
interface CreateGzFetchOptions {
|
|
57
66
|
baseURL?: string;
|
|
58
67
|
timeout?: number;
|
|
68
|
+
validateStatus?: (status: number) => boolean;
|
|
59
69
|
getToken?: () => string | undefined | Promise<string | undefined>;
|
|
60
70
|
showErrorMessage?: boolean;
|
|
61
71
|
auth?: GzFetchAuthOptions;
|
|
72
|
+
unauthorized?: GzFetchUnauthorizedOptions;
|
|
62
73
|
middlewares?: readonly GzFetchMiddleware[];
|
|
63
74
|
}
|
|
64
75
|
interface GzFetchClient {
|
|
@@ -75,4 +86,6 @@ declare function createGzFetch(options?: CreateGzFetchOptions): GzFetchClient;
|
|
|
75
86
|
declare function configureGzFetch(options: CreateGzFetchOptions): void;
|
|
76
87
|
declare function gzFetch<TResponse, TRequestParams = unknown>(config: GzRequestConfig<TRequestParams>): Promise<TResponse>;
|
|
77
88
|
|
|
78
|
-
|
|
89
|
+
declare const GzFetchUnauthorizedModal: FC;
|
|
90
|
+
|
|
91
|
+
export { type CreateGzFetchOptions, type GzErrorContext, type GzFetch, type GzFetchAuthOptions, type GzFetchClient, GzFetchError, type GzFetchErrorOptions, type GzFetchErrorType, type GzFetchMiddleware, GzFetchUnauthorizedModal, type GzFetchUnauthorizedOptions, type GzInternalRequestConfig, type GzRequestConfig, type GzRequestHeaders, type GzRequestMethod, type GzRequestOptions, type GzRequestParams, type GzResponseContext, type GzResponseType, configureGzFetch, createGzFetch, getResponseMessage, gzFetch, toGzFetchError };
|
package/dist/fetch/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AxiosHeaders } from 'axios';
|
|
2
|
+
import { FC } from 'react';
|
|
2
3
|
|
|
3
4
|
type GzFetchErrorType = 'HTTP_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT_ERROR' | 'CANCELED_ERROR' | 'UNKNOWN_ERROR';
|
|
4
5
|
interface GzFetchErrorOptions {
|
|
@@ -31,6 +32,7 @@ interface GzRequestConfig<TRequestParams = unknown> {
|
|
|
31
32
|
skipAuth?: boolean;
|
|
32
33
|
responseType?: GzResponseType;
|
|
33
34
|
signal?: AbortSignal;
|
|
35
|
+
withCredentials?: boolean;
|
|
34
36
|
}
|
|
35
37
|
interface GzInternalRequestConfig extends Omit<GzRequestConfig<unknown>, 'headers'> {
|
|
36
38
|
headers?: GzRequestHeaders;
|
|
@@ -53,12 +55,21 @@ interface GzFetchAuthOptions {
|
|
|
53
55
|
headerName?: string;
|
|
54
56
|
formatToken?: (token: string) => string;
|
|
55
57
|
}
|
|
58
|
+
interface GzFetchUnauthorizedOptions {
|
|
59
|
+
enabled?: boolean;
|
|
60
|
+
loginUrl?: string;
|
|
61
|
+
modalTitle?: string;
|
|
62
|
+
modalMessage?: string;
|
|
63
|
+
onUnauthorized?: () => void;
|
|
64
|
+
}
|
|
56
65
|
interface CreateGzFetchOptions {
|
|
57
66
|
baseURL?: string;
|
|
58
67
|
timeout?: number;
|
|
68
|
+
validateStatus?: (status: number) => boolean;
|
|
59
69
|
getToken?: () => string | undefined | Promise<string | undefined>;
|
|
60
70
|
showErrorMessage?: boolean;
|
|
61
71
|
auth?: GzFetchAuthOptions;
|
|
72
|
+
unauthorized?: GzFetchUnauthorizedOptions;
|
|
62
73
|
middlewares?: readonly GzFetchMiddleware[];
|
|
63
74
|
}
|
|
64
75
|
interface GzFetchClient {
|
|
@@ -75,4 +86,6 @@ declare function createGzFetch(options?: CreateGzFetchOptions): GzFetchClient;
|
|
|
75
86
|
declare function configureGzFetch(options: CreateGzFetchOptions): void;
|
|
76
87
|
declare function gzFetch<TResponse, TRequestParams = unknown>(config: GzRequestConfig<TRequestParams>): Promise<TResponse>;
|
|
77
88
|
|
|
78
|
-
|
|
89
|
+
declare const GzFetchUnauthorizedModal: FC;
|
|
90
|
+
|
|
91
|
+
export { type CreateGzFetchOptions, type GzErrorContext, type GzFetch, type GzFetchAuthOptions, type GzFetchClient, GzFetchError, type GzFetchErrorOptions, type GzFetchErrorType, type GzFetchMiddleware, GzFetchUnauthorizedModal, type GzFetchUnauthorizedOptions, type GzInternalRequestConfig, type GzRequestConfig, type GzRequestHeaders, type GzRequestMethod, type GzRequestOptions, type GzRequestParams, type GzResponseContext, type GzResponseType, configureGzFetch, createGzFetch, getResponseMessage, gzFetch, toGzFetchError };
|
package/dist/fetch/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { message } from '@chenhui996/gg-ui';
|
|
1
|
+
import { Modal, message } from '@chenhui996/gg-ui';
|
|
2
2
|
import axios, { AxiosError, AxiosHeaders } from 'axios';
|
|
3
|
+
import { useRef, useSyncExternalStore, useEffect } from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
5
|
|
|
4
6
|
// src/fetch/client.ts
|
|
5
7
|
var GzFetchError = class extends Error {
|
|
@@ -90,9 +92,107 @@ async function runErrorMiddlewares(error, context, middlewares) {
|
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
// src/fetch/unauthorized.ts
|
|
96
|
+
var DEFAULT_UNAUTHORIZED_LOGIN_URL = "/login";
|
|
97
|
+
var DEFAULT_UNAUTHORIZED_MODAL_TITLE = "登录失效";
|
|
98
|
+
var DEFAULT_UNAUTHORIZED_MODAL_MESSAGE = "当前登录状态已失效,请重新登录。";
|
|
99
|
+
var IDLE_SNAPSHOT = { phase: "idle" };
|
|
100
|
+
var snapshot = IDLE_SNAPSHOT;
|
|
101
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
102
|
+
var modalHosts = /* @__PURE__ */ new Set();
|
|
103
|
+
var emitChange = () => {
|
|
104
|
+
for (const listener of listeners) {
|
|
105
|
+
listener();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var setSnapshot = (nextSnapshot) => {
|
|
109
|
+
snapshot = nextSnapshot;
|
|
110
|
+
emitChange();
|
|
111
|
+
};
|
|
112
|
+
var resolveUnauthorizedOptions = (options) => {
|
|
113
|
+
var _a, _b, _c, _d;
|
|
114
|
+
return {
|
|
115
|
+
enabled: (_a = options == null ? void 0 : options.enabled) != null ? _a : false,
|
|
116
|
+
loginUrl: (_b = options == null ? void 0 : options.loginUrl) != null ? _b : DEFAULT_UNAUTHORIZED_LOGIN_URL,
|
|
117
|
+
modalTitle: (_c = options == null ? void 0 : options.modalTitle) != null ? _c : DEFAULT_UNAUTHORIZED_MODAL_TITLE,
|
|
118
|
+
modalMessage: (_d = options == null ? void 0 : options.modalMessage) != null ? _d : DEFAULT_UNAUTHORIZED_MODAL_MESSAGE,
|
|
119
|
+
...(options == null ? void 0 : options.onUnauthorized) === void 0 ? {} : { onUnauthorized: options.onUnauthorized }
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
var getUnauthorizedSnapshot = () => snapshot;
|
|
123
|
+
var getUnauthorizedServerSnapshot = () => IDLE_SNAPSHOT;
|
|
124
|
+
var subscribeUnauthorized = (listener) => {
|
|
125
|
+
listeners.add(listener);
|
|
126
|
+
return () => listeners.delete(listener);
|
|
127
|
+
};
|
|
128
|
+
var registerUnauthorizedModalHost = (hostId) => {
|
|
129
|
+
modalHosts.add(hostId);
|
|
130
|
+
if (snapshot.activeHostId === void 0) {
|
|
131
|
+
setSnapshot({ ...snapshot, activeHostId: hostId });
|
|
132
|
+
}
|
|
133
|
+
return () => {
|
|
134
|
+
modalHosts.delete(hostId);
|
|
135
|
+
if (snapshot.activeHostId !== hostId) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const nextHost = modalHosts.values().next();
|
|
139
|
+
const nextHostId = nextHost.done ? void 0 : nextHost.value;
|
|
140
|
+
if (snapshot.phase === "closing") {
|
|
141
|
+
setSnapshot(
|
|
142
|
+
nextHostId === void 0 ? IDLE_SNAPSHOT : { activeHostId: nextHostId, phase: "idle" }
|
|
143
|
+
);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (nextHostId === void 0) {
|
|
147
|
+
setSnapshot(
|
|
148
|
+
snapshot.options === void 0 ? { phase: snapshot.phase } : { options: snapshot.options, phase: snapshot.phase }
|
|
149
|
+
);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
setSnapshot({ ...snapshot, activeHostId: nextHostId });
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
var openUnauthorizedModal = (options) => {
|
|
156
|
+
if (!options.enabled || snapshot.phase !== "idle") {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
setSnapshot({
|
|
160
|
+
...snapshot.activeHostId === void 0 ? {} : { activeHostId: snapshot.activeHostId },
|
|
161
|
+
options,
|
|
162
|
+
phase: "open"
|
|
163
|
+
});
|
|
164
|
+
return true;
|
|
165
|
+
};
|
|
166
|
+
var beginCloseUnauthorizedModal = () => {
|
|
167
|
+
if (snapshot.phase !== "open" || snapshot.options === void 0) {
|
|
168
|
+
return void 0;
|
|
169
|
+
}
|
|
170
|
+
const { options } = snapshot;
|
|
171
|
+
setSnapshot({ ...snapshot, phase: "closing" });
|
|
172
|
+
return options;
|
|
173
|
+
};
|
|
174
|
+
var cancelUnauthorizedModal = () => beginCloseUnauthorizedModal() !== void 0;
|
|
175
|
+
var confirmUnauthorizedModal = () => beginCloseUnauthorizedModal();
|
|
176
|
+
var completeUnauthorizedModal = () => {
|
|
177
|
+
if (snapshot.phase !== "closing") {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
setSnapshot(
|
|
181
|
+
snapshot.activeHostId === void 0 ? IDLE_SNAPSHOT : { activeHostId: snapshot.activeHostId, phase: "idle" }
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
var executeUnauthorizedAction = (options, navigate = (loginUrl) => window.location.assign(loginUrl)) => {
|
|
185
|
+
if (options.onUnauthorized) {
|
|
186
|
+
options.onUnauthorized();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
navigate(options.loginUrl);
|
|
190
|
+
};
|
|
191
|
+
|
|
93
192
|
// src/fetch/client.ts
|
|
94
193
|
var DEFAULT_TIMEOUT = 15e3;
|
|
95
194
|
var DEFAULT_AUTH_HEADER = "Authorization";
|
|
195
|
+
var DEFAULT_VALIDATE_STATUS = (status) => status >= 200 && status < 300;
|
|
96
196
|
function toHeaderRecord(headers) {
|
|
97
197
|
const result = {};
|
|
98
198
|
for (const [name, value] of Object.entries(headers)) {
|
|
@@ -103,15 +203,16 @@ function toHeaderRecord(headers) {
|
|
|
103
203
|
return result;
|
|
104
204
|
}
|
|
105
205
|
function createGzFetch(options = {}) {
|
|
106
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
206
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
107
207
|
const middlewares = (_a = options.middlewares) != null ? _a : [];
|
|
108
208
|
const showInstanceErrorMessage = (_b = options.showErrorMessage) != null ? _b : true;
|
|
109
209
|
const headerName = (_d = (_c = options.auth) == null ? void 0 : _c.headerName) != null ? _d : DEFAULT_AUTH_HEADER;
|
|
110
210
|
const formatToken = (_f = (_e = options.auth) == null ? void 0 : _e.formatToken) != null ? _f : ((token) => `Bearer ${token}`);
|
|
211
|
+
const unauthorizedOptions = resolveUnauthorizedOptions(options.unauthorized);
|
|
111
212
|
const instance = axios.create({
|
|
112
213
|
...options.baseURL === void 0 ? {} : { baseURL: options.baseURL },
|
|
113
214
|
timeout: (_g = options.timeout) != null ? _g : DEFAULT_TIMEOUT,
|
|
114
|
-
validateStatus: (
|
|
215
|
+
validateStatus: (_h = options.validateStatus) != null ? _h : DEFAULT_VALIDATE_STATUS
|
|
115
216
|
});
|
|
116
217
|
async function request(initialConfig) {
|
|
117
218
|
var _a2;
|
|
@@ -137,7 +238,8 @@ function createGzFetch(options = {}) {
|
|
|
137
238
|
...usesRequestBody || currentConfig.params === void 0 ? {} : { params: currentConfig.params },
|
|
138
239
|
...currentConfig.timeout === void 0 ? {} : { timeout: currentConfig.timeout },
|
|
139
240
|
...currentConfig.responseType === void 0 ? {} : { responseType: currentConfig.responseType },
|
|
140
|
-
...currentConfig.signal === void 0 ? {} : { signal: currentConfig.signal }
|
|
241
|
+
...currentConfig.signal === void 0 ? {} : { signal: currentConfig.signal },
|
|
242
|
+
...currentConfig.withCredentials === void 0 ? {} : { withCredentials: currentConfig.withCredentials }
|
|
141
243
|
};
|
|
142
244
|
const response = await instance.request(axiosConfig);
|
|
143
245
|
const context = {
|
|
@@ -148,9 +250,13 @@ function createGzFetch(options = {}) {
|
|
|
148
250
|
return await runResponseMiddlewares(response.data, context, middlewares);
|
|
149
251
|
} catch (originalError) {
|
|
150
252
|
const error = toGzFetchError(originalError);
|
|
253
|
+
const shouldHandleUnauthorized = unauthorizedOptions.enabled && error.type === "HTTP_ERROR" && error.status === 401;
|
|
254
|
+
if (shouldHandleUnauthorized) {
|
|
255
|
+
openUnauthorizedModal(unauthorizedOptions);
|
|
256
|
+
}
|
|
151
257
|
await runErrorMiddlewares(error, { config: currentConfig }, middlewares);
|
|
152
258
|
const shouldShowError = (_a2 = currentConfig.showErrorMessage) != null ? _a2 : showInstanceErrorMessage;
|
|
153
|
-
if (shouldShowError && error.type !== "CANCELED_ERROR") {
|
|
259
|
+
if (shouldShowError && error.type !== "CANCELED_ERROR" && !shouldHandleUnauthorized) {
|
|
154
260
|
message.error(error.message);
|
|
155
261
|
}
|
|
156
262
|
throw error;
|
|
@@ -186,5 +292,42 @@ async function gzFetch(config) {
|
|
|
186
292
|
}
|
|
187
293
|
return defaultClient(config);
|
|
188
294
|
}
|
|
295
|
+
var GzFetchUnauthorizedModal = () => {
|
|
296
|
+
var _a, _b, _c;
|
|
297
|
+
const hostIdRef = useRef(void 0);
|
|
298
|
+
(_a = hostIdRef.current) != null ? _a : hostIdRef.current = /* @__PURE__ */ Symbol("gz-fetch-unauthorized-modal-host");
|
|
299
|
+
const hostId = hostIdRef.current;
|
|
300
|
+
const snapshot2 = useSyncExternalStore(
|
|
301
|
+
subscribeUnauthorized,
|
|
302
|
+
getUnauthorizedSnapshot,
|
|
303
|
+
getUnauthorizedServerSnapshot
|
|
304
|
+
);
|
|
305
|
+
useEffect(() => registerUnauthorizedModalHost(hostId), [hostId]);
|
|
306
|
+
if (snapshot2.activeHostId !== hostId) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
const handleConfirm = () => {
|
|
310
|
+
const options = confirmUnauthorizedModal();
|
|
311
|
+
if (options) {
|
|
312
|
+
executeUnauthorizedAction(options);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
return /* @__PURE__ */ jsx(
|
|
316
|
+
Modal,
|
|
317
|
+
{
|
|
318
|
+
afterClose: completeUnauthorizedModal,
|
|
319
|
+
cancelText: "取消",
|
|
320
|
+
closable: true,
|
|
321
|
+
okText: "重新登录",
|
|
322
|
+
open: snapshot2.phase === "open",
|
|
323
|
+
title: (_b = snapshot2.options) == null ? void 0 : _b.modalTitle,
|
|
324
|
+
onCancel: cancelUnauthorizedModal,
|
|
325
|
+
onOk: handleConfirm,
|
|
326
|
+
children: (_c = snapshot2.options) == null ? void 0 : _c.modalMessage
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
};
|
|
330
|
+
GzFetchUnauthorizedModal.displayName = "GzFetchUnauthorizedModal";
|
|
331
|
+
var GzFetchUnauthorizedModal_default = GzFetchUnauthorizedModal;
|
|
189
332
|
|
|
190
|
-
export { GzFetchError, configureGzFetch, createGzFetch, getResponseMessage, gzFetch, toGzFetchError };
|
|
333
|
+
export { GzFetchError, GzFetchUnauthorizedModal_default as GzFetchUnauthorizedModal, configureGzFetch, createGzFetch, getResponseMessage, gzFetch, toGzFetchError };
|