@gz-fronted/gz-pc 0.1.1-beta.1 → 0.1.1-beta.3
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 +71 -0
- package/dist/fetch/index.cjs +189 -5
- package/dist/fetch/index.d.cts +14 -1
- package/dist/fetch/index.d.ts +14 -1
- package/dist/fetch/index.js +188 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -185,6 +185,7 @@ const result = await gzFetch<SaveResult, SaveParams>({
|
|
|
185
185
|
| `showErrorMessage` | `boolean` | `true` | 实例级错误提示开关 |
|
|
186
186
|
| `auth.headerName` | `string` | `Authorization` | Token 请求头名称 |
|
|
187
187
|
| `auth.formatToken` | `(token: string) => string` | `Bearer ${token}` | Token 格式化函数 |
|
|
188
|
+
| `unauthorized` | `GzFetchUnauthorizedOptions` | 未开启 | 可选的 HTTP 401 统一处理 |
|
|
188
189
|
| `middlewares` | `readonly GzFetchMiddleware[]` | `[]` | 请求、响应和错误中间件 |
|
|
189
190
|
|
|
190
191
|
需要覆盖默认成功状态时,只在应用初始化层配置:
|
|
@@ -284,6 +285,76 @@ controller.abort();
|
|
|
284
285
|
await promise;
|
|
285
286
|
```
|
|
286
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
|
+
为了让普通错误消息和 401 弹窗都继承宿主项目当前的 gg-ui 主题,需要在应用根部的
|
|
317
|
+
`ConfigProvider` 内挂载一次 `GzFetchFeedbackProvider`:
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
import { GzFetchFeedbackProvider } from '@gz-fronted/gz-pc/fetch';
|
|
321
|
+
import { ConfigProvider } from '@chenhui996/gg-ui';
|
|
322
|
+
|
|
323
|
+
<ConfigProvider themeMode={themeMode}>
|
|
324
|
+
<GzFetchFeedbackProvider>
|
|
325
|
+
<App />
|
|
326
|
+
</GzFetchFeedbackProvider>
|
|
327
|
+
</ConfigProvider>;
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
该 Provider 使用 `message.useMessage()` 和受控 gg-ui `Modal`,不会在已接入的项目中
|
|
331
|
+
调用脱离 React 上下文的静态反馈 API。因此主题切换时,普通错误消息和 401 弹窗都会
|
|
332
|
+
继续读取宿主 `ConfigProvider` 的主题 Token,无需复制亮色或暗色样式。业务页面不需要
|
|
333
|
+
维护反馈状态。未挂载该 Provider 的已有项目仍会回退到原来的静态 `message.error`,
|
|
334
|
+
不会产生 breaking change。`GzFetchUnauthorizedModal` 继续保留为兼容 API,但新项目
|
|
335
|
+
优先使用统一 Provider。
|
|
336
|
+
|
|
337
|
+
所有由同一份 gz-pc 包创建的默认客户端和独立客户端共享一个 401 管理器。首个 401
|
|
338
|
+
打开弹窗,后续并发 401 不会创建新弹窗;取消或弹窗关闭动画结束后会释放状态,后续
|
|
339
|
+
独立发生的 401 可以再次打开。确认操作也有同一状态锁,不会重复执行登录跳转。
|
|
340
|
+
|
|
341
|
+
业务项目需要自定义 SSO、登出或状态清理时传入 `onUnauthorized`。用户确认后优先执行
|
|
342
|
+
该回调;只有未提供回调时才通过 `window.location.assign(loginUrl)` 跳转:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
configureGzFetch({
|
|
346
|
+
unauthorized: {
|
|
347
|
+
enabled: true,
|
|
348
|
+
onUnauthorized: () => {
|
|
349
|
+
authService.logout();
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
开启该能力后,401 仍会作为 `GzFetchError` 抛给调用方,但请求核心不会再为同一个
|
|
356
|
+
401 额外调用普通 `message.error`。其他 HTTP 错误、网络错误、超时和取消请求行为不变。
|
|
357
|
+
|
|
287
358
|
## 中间件
|
|
288
359
|
|
|
289
360
|
中间件的 `onRequest`、`onResponse` 和 `onError` 都按注册顺序执行,不采用洋葱
|
package/dist/fetch/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var ggUi = require('@chenhui996/gg-ui');
|
|
4
3
|
var axios = require('axios');
|
|
4
|
+
var ggUi = require('@chenhui996/gg-ui');
|
|
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
|
|
|
@@ -9,8 +11,8 @@ var axios__default = /*#__PURE__*/_interopDefault(axios);
|
|
|
9
11
|
|
|
10
12
|
// src/fetch/client.ts
|
|
11
13
|
var GzFetchError = class extends Error {
|
|
12
|
-
constructor(
|
|
13
|
-
super(
|
|
14
|
+
constructor(message3, options) {
|
|
15
|
+
super(message3);
|
|
14
16
|
this.name = "GzFetchError";
|
|
15
17
|
this.type = options.type;
|
|
16
18
|
this.status = options.status;
|
|
@@ -69,6 +71,28 @@ function toGzFetchError(error) {
|
|
|
69
71
|
originalError: error
|
|
70
72
|
});
|
|
71
73
|
}
|
|
74
|
+
var feedbackHosts = /* @__PURE__ */ new Map();
|
|
75
|
+
var activeHostId;
|
|
76
|
+
var registerFeedbackHost = (hostId, host) => {
|
|
77
|
+
feedbackHosts.set(hostId, host);
|
|
78
|
+
activeHostId != null ? activeHostId : activeHostId = hostId;
|
|
79
|
+
return () => {
|
|
80
|
+
feedbackHosts.delete(hostId);
|
|
81
|
+
if (activeHostId !== hostId) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const nextHost = feedbackHosts.keys().next();
|
|
85
|
+
activeHostId = nextHost.done ? void 0 : nextHost.value;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
var showFetchErrorMessage = (content) => {
|
|
89
|
+
const activeHost = activeHostId === void 0 ? void 0 : feedbackHosts.get(activeHostId);
|
|
90
|
+
if (activeHost) {
|
|
91
|
+
activeHost.showErrorMessage(content);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
ggUi.message.error(content);
|
|
95
|
+
};
|
|
72
96
|
|
|
73
97
|
// src/fetch/middleware.ts
|
|
74
98
|
async function runRequestMiddlewares(initialConfig, middlewares) {
|
|
@@ -96,6 +120,103 @@ async function runErrorMiddlewares(error, context, middlewares) {
|
|
|
96
120
|
}
|
|
97
121
|
}
|
|
98
122
|
|
|
123
|
+
// src/fetch/unauthorized.ts
|
|
124
|
+
var DEFAULT_UNAUTHORIZED_LOGIN_URL = "/login";
|
|
125
|
+
var DEFAULT_UNAUTHORIZED_MODAL_TITLE = "登录失效";
|
|
126
|
+
var DEFAULT_UNAUTHORIZED_MODAL_MESSAGE = "当前登录状态已失效,请重新登录。";
|
|
127
|
+
var IDLE_SNAPSHOT = { phase: "idle" };
|
|
128
|
+
var snapshot = IDLE_SNAPSHOT;
|
|
129
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
130
|
+
var modalHosts = /* @__PURE__ */ new Set();
|
|
131
|
+
var emitChange = () => {
|
|
132
|
+
for (const listener of listeners) {
|
|
133
|
+
listener();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
var setSnapshot = (nextSnapshot) => {
|
|
137
|
+
snapshot = nextSnapshot;
|
|
138
|
+
emitChange();
|
|
139
|
+
};
|
|
140
|
+
var resolveUnauthorizedOptions = (options) => {
|
|
141
|
+
var _a, _b, _c, _d;
|
|
142
|
+
return {
|
|
143
|
+
enabled: (_a = options == null ? void 0 : options.enabled) != null ? _a : false,
|
|
144
|
+
loginUrl: (_b = options == null ? void 0 : options.loginUrl) != null ? _b : DEFAULT_UNAUTHORIZED_LOGIN_URL,
|
|
145
|
+
modalTitle: (_c = options == null ? void 0 : options.modalTitle) != null ? _c : DEFAULT_UNAUTHORIZED_MODAL_TITLE,
|
|
146
|
+
modalMessage: (_d = options == null ? void 0 : options.modalMessage) != null ? _d : DEFAULT_UNAUTHORIZED_MODAL_MESSAGE,
|
|
147
|
+
...(options == null ? void 0 : options.onUnauthorized) === void 0 ? {} : { onUnauthorized: options.onUnauthorized }
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
var getUnauthorizedSnapshot = () => snapshot;
|
|
151
|
+
var getUnauthorizedServerSnapshot = () => IDLE_SNAPSHOT;
|
|
152
|
+
var subscribeUnauthorized = (listener) => {
|
|
153
|
+
listeners.add(listener);
|
|
154
|
+
return () => listeners.delete(listener);
|
|
155
|
+
};
|
|
156
|
+
var registerUnauthorizedModalHost = (hostId) => {
|
|
157
|
+
modalHosts.add(hostId);
|
|
158
|
+
if (snapshot.activeHostId === void 0) {
|
|
159
|
+
setSnapshot({ ...snapshot, activeHostId: hostId });
|
|
160
|
+
}
|
|
161
|
+
return () => {
|
|
162
|
+
modalHosts.delete(hostId);
|
|
163
|
+
if (snapshot.activeHostId !== hostId) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const nextHost = modalHosts.values().next();
|
|
167
|
+
const nextHostId = nextHost.done ? void 0 : nextHost.value;
|
|
168
|
+
if (snapshot.phase === "closing") {
|
|
169
|
+
setSnapshot(
|
|
170
|
+
nextHostId === void 0 ? IDLE_SNAPSHOT : { activeHostId: nextHostId, phase: "idle" }
|
|
171
|
+
);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (nextHostId === void 0) {
|
|
175
|
+
setSnapshot(
|
|
176
|
+
snapshot.options === void 0 ? { phase: snapshot.phase } : { options: snapshot.options, phase: snapshot.phase }
|
|
177
|
+
);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
setSnapshot({ ...snapshot, activeHostId: nextHostId });
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
var openUnauthorizedModal = (options) => {
|
|
184
|
+
if (!options.enabled || snapshot.phase !== "idle") {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
setSnapshot({
|
|
188
|
+
...snapshot.activeHostId === void 0 ? {} : { activeHostId: snapshot.activeHostId },
|
|
189
|
+
options,
|
|
190
|
+
phase: "open"
|
|
191
|
+
});
|
|
192
|
+
return true;
|
|
193
|
+
};
|
|
194
|
+
var beginCloseUnauthorizedModal = () => {
|
|
195
|
+
if (snapshot.phase !== "open" || snapshot.options === void 0) {
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
const { options } = snapshot;
|
|
199
|
+
setSnapshot({ ...snapshot, phase: "closing" });
|
|
200
|
+
return options;
|
|
201
|
+
};
|
|
202
|
+
var cancelUnauthorizedModal = () => beginCloseUnauthorizedModal() !== void 0;
|
|
203
|
+
var confirmUnauthorizedModal = () => beginCloseUnauthorizedModal();
|
|
204
|
+
var completeUnauthorizedModal = () => {
|
|
205
|
+
if (snapshot.phase !== "closing") {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
setSnapshot(
|
|
209
|
+
snapshot.activeHostId === void 0 ? IDLE_SNAPSHOT : { activeHostId: snapshot.activeHostId, phase: "idle" }
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
var executeUnauthorizedAction = (options, navigate = (loginUrl) => window.location.assign(loginUrl)) => {
|
|
213
|
+
if (options.onUnauthorized) {
|
|
214
|
+
options.onUnauthorized();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
navigate(options.loginUrl);
|
|
218
|
+
};
|
|
219
|
+
|
|
99
220
|
// src/fetch/client.ts
|
|
100
221
|
var DEFAULT_TIMEOUT = 15e3;
|
|
101
222
|
var DEFAULT_AUTH_HEADER = "Authorization";
|
|
@@ -115,6 +236,7 @@ function createGzFetch(options = {}) {
|
|
|
115
236
|
const showInstanceErrorMessage = (_b = options.showErrorMessage) != null ? _b : true;
|
|
116
237
|
const headerName = (_d = (_c = options.auth) == null ? void 0 : _c.headerName) != null ? _d : DEFAULT_AUTH_HEADER;
|
|
117
238
|
const formatToken = (_f = (_e = options.auth) == null ? void 0 : _e.formatToken) != null ? _f : ((token) => `Bearer ${token}`);
|
|
239
|
+
const unauthorizedOptions = resolveUnauthorizedOptions(options.unauthorized);
|
|
118
240
|
const instance = axios__default.default.create({
|
|
119
241
|
...options.baseURL === void 0 ? {} : { baseURL: options.baseURL },
|
|
120
242
|
timeout: (_g = options.timeout) != null ? _g : DEFAULT_TIMEOUT,
|
|
@@ -156,10 +278,14 @@ function createGzFetch(options = {}) {
|
|
|
156
278
|
return await runResponseMiddlewares(response.data, context, middlewares);
|
|
157
279
|
} catch (originalError) {
|
|
158
280
|
const error = toGzFetchError(originalError);
|
|
281
|
+
const shouldHandleUnauthorized = unauthorizedOptions.enabled && error.type === "HTTP_ERROR" && error.status === 401;
|
|
282
|
+
if (shouldHandleUnauthorized) {
|
|
283
|
+
openUnauthorizedModal(unauthorizedOptions);
|
|
284
|
+
}
|
|
159
285
|
await runErrorMiddlewares(error, { config: currentConfig }, middlewares);
|
|
160
286
|
const shouldShowError = (_a2 = currentConfig.showErrorMessage) != null ? _a2 : showInstanceErrorMessage;
|
|
161
|
-
if (shouldShowError && error.type !== "CANCELED_ERROR") {
|
|
162
|
-
|
|
287
|
+
if (shouldShowError && error.type !== "CANCELED_ERROR" && !shouldHandleUnauthorized) {
|
|
288
|
+
showFetchErrorMessage(error.message);
|
|
163
289
|
}
|
|
164
290
|
throw error;
|
|
165
291
|
}
|
|
@@ -194,8 +320,66 @@ async function gzFetch(config) {
|
|
|
194
320
|
}
|
|
195
321
|
return defaultClient(config);
|
|
196
322
|
}
|
|
323
|
+
var GzFetchUnauthorizedModal = () => {
|
|
324
|
+
var _a, _b, _c;
|
|
325
|
+
const hostIdRef = react.useRef(void 0);
|
|
326
|
+
(_a = hostIdRef.current) != null ? _a : hostIdRef.current = /* @__PURE__ */ Symbol("gz-fetch-unauthorized-modal-host");
|
|
327
|
+
const hostId = hostIdRef.current;
|
|
328
|
+
const [messageApi, messageContextHolder] = ggUi.message.useMessage();
|
|
329
|
+
const showErrorMessage = react.useCallback(
|
|
330
|
+
(content) => {
|
|
331
|
+
void messageApi.error(content);
|
|
332
|
+
},
|
|
333
|
+
[messageApi]
|
|
334
|
+
);
|
|
335
|
+
const snapshot2 = react.useSyncExternalStore(
|
|
336
|
+
subscribeUnauthorized,
|
|
337
|
+
getUnauthorizedSnapshot,
|
|
338
|
+
getUnauthorizedServerSnapshot
|
|
339
|
+
);
|
|
340
|
+
react.useEffect(() => registerUnauthorizedModalHost(hostId), [hostId]);
|
|
341
|
+
react.useEffect(
|
|
342
|
+
() => registerFeedbackHost(hostId, { showErrorMessage }),
|
|
343
|
+
[hostId, showErrorMessage]
|
|
344
|
+
);
|
|
345
|
+
const handleConfirm = () => {
|
|
346
|
+
const options = confirmUnauthorizedModal();
|
|
347
|
+
if (options) {
|
|
348
|
+
executeUnauthorizedAction(options);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
352
|
+
messageContextHolder,
|
|
353
|
+
snapshot2.activeHostId === hostId && /* @__PURE__ */ jsxRuntime.jsx(
|
|
354
|
+
ggUi.Modal,
|
|
355
|
+
{
|
|
356
|
+
afterClose: completeUnauthorizedModal,
|
|
357
|
+
cancelText: "取消",
|
|
358
|
+
closable: true,
|
|
359
|
+
okText: "重新登录",
|
|
360
|
+
open: snapshot2.phase === "open",
|
|
361
|
+
title: (_b = snapshot2.options) == null ? void 0 : _b.modalTitle,
|
|
362
|
+
onCancel: cancelUnauthorizedModal,
|
|
363
|
+
onOk: handleConfirm,
|
|
364
|
+
children: (_c = snapshot2.options) == null ? void 0 : _c.modalMessage
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
] });
|
|
368
|
+
};
|
|
369
|
+
GzFetchUnauthorizedModal.displayName = "GzFetchUnauthorizedModal";
|
|
370
|
+
var GzFetchUnauthorizedModal_default = GzFetchUnauthorizedModal;
|
|
371
|
+
var GzFetchFeedbackProvider = ({ children }) => {
|
|
372
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
373
|
+
/* @__PURE__ */ jsxRuntime.jsx(GzFetchUnauthorizedModal_default, {}),
|
|
374
|
+
children
|
|
375
|
+
] });
|
|
376
|
+
};
|
|
377
|
+
GzFetchFeedbackProvider.displayName = "GzFetchFeedbackProvider";
|
|
378
|
+
var GzFetchFeedbackProvider_default = GzFetchFeedbackProvider;
|
|
197
379
|
|
|
198
380
|
exports.GzFetchError = GzFetchError;
|
|
381
|
+
exports.GzFetchFeedbackProvider = GzFetchFeedbackProvider_default;
|
|
382
|
+
exports.GzFetchUnauthorizedModal = GzFetchUnauthorizedModal_default;
|
|
199
383
|
exports.configureGzFetch = configureGzFetch;
|
|
200
384
|
exports.createGzFetch = createGzFetch;
|
|
201
385
|
exports.getResponseMessage = getResponseMessage;
|
package/dist/fetch/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AxiosHeaders } from 'axios';
|
|
2
|
+
import { FC, PropsWithChildren } from 'react';
|
|
2
3
|
|
|
3
4
|
type GzFetchErrorType = 'HTTP_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT_ERROR' | 'CANCELED_ERROR' | 'UNKNOWN_ERROR';
|
|
4
5
|
interface GzFetchErrorOptions {
|
|
@@ -54,6 +55,13 @@ interface GzFetchAuthOptions {
|
|
|
54
55
|
headerName?: string;
|
|
55
56
|
formatToken?: (token: string) => string;
|
|
56
57
|
}
|
|
58
|
+
interface GzFetchUnauthorizedOptions {
|
|
59
|
+
enabled?: boolean;
|
|
60
|
+
loginUrl?: string;
|
|
61
|
+
modalTitle?: string;
|
|
62
|
+
modalMessage?: string;
|
|
63
|
+
onUnauthorized?: () => void;
|
|
64
|
+
}
|
|
57
65
|
interface CreateGzFetchOptions {
|
|
58
66
|
baseURL?: string;
|
|
59
67
|
timeout?: number;
|
|
@@ -61,6 +69,7 @@ interface CreateGzFetchOptions {
|
|
|
61
69
|
getToken?: () => string | undefined | Promise<string | undefined>;
|
|
62
70
|
showErrorMessage?: boolean;
|
|
63
71
|
auth?: GzFetchAuthOptions;
|
|
72
|
+
unauthorized?: GzFetchUnauthorizedOptions;
|
|
64
73
|
middlewares?: readonly GzFetchMiddleware[];
|
|
65
74
|
}
|
|
66
75
|
interface GzFetchClient {
|
|
@@ -77,4 +86,8 @@ declare function createGzFetch(options?: CreateGzFetchOptions): GzFetchClient;
|
|
|
77
86
|
declare function configureGzFetch(options: CreateGzFetchOptions): void;
|
|
78
87
|
declare function gzFetch<TResponse, TRequestParams = unknown>(config: GzRequestConfig<TRequestParams>): Promise<TResponse>;
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
declare const GzFetchFeedbackProvider: FC<PropsWithChildren>;
|
|
90
|
+
|
|
91
|
+
declare const GzFetchUnauthorizedModal: FC;
|
|
92
|
+
|
|
93
|
+
export { type CreateGzFetchOptions, type GzErrorContext, type GzFetch, type GzFetchAuthOptions, type GzFetchClient, GzFetchError, type GzFetchErrorOptions, type GzFetchErrorType, GzFetchFeedbackProvider, 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, PropsWithChildren } from 'react';
|
|
2
3
|
|
|
3
4
|
type GzFetchErrorType = 'HTTP_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT_ERROR' | 'CANCELED_ERROR' | 'UNKNOWN_ERROR';
|
|
4
5
|
interface GzFetchErrorOptions {
|
|
@@ -54,6 +55,13 @@ interface GzFetchAuthOptions {
|
|
|
54
55
|
headerName?: string;
|
|
55
56
|
formatToken?: (token: string) => string;
|
|
56
57
|
}
|
|
58
|
+
interface GzFetchUnauthorizedOptions {
|
|
59
|
+
enabled?: boolean;
|
|
60
|
+
loginUrl?: string;
|
|
61
|
+
modalTitle?: string;
|
|
62
|
+
modalMessage?: string;
|
|
63
|
+
onUnauthorized?: () => void;
|
|
64
|
+
}
|
|
57
65
|
interface CreateGzFetchOptions {
|
|
58
66
|
baseURL?: string;
|
|
59
67
|
timeout?: number;
|
|
@@ -61,6 +69,7 @@ interface CreateGzFetchOptions {
|
|
|
61
69
|
getToken?: () => string | undefined | Promise<string | undefined>;
|
|
62
70
|
showErrorMessage?: boolean;
|
|
63
71
|
auth?: GzFetchAuthOptions;
|
|
72
|
+
unauthorized?: GzFetchUnauthorizedOptions;
|
|
64
73
|
middlewares?: readonly GzFetchMiddleware[];
|
|
65
74
|
}
|
|
66
75
|
interface GzFetchClient {
|
|
@@ -77,4 +86,8 @@ declare function createGzFetch(options?: CreateGzFetchOptions): GzFetchClient;
|
|
|
77
86
|
declare function configureGzFetch(options: CreateGzFetchOptions): void;
|
|
78
87
|
declare function gzFetch<TResponse, TRequestParams = unknown>(config: GzRequestConfig<TRequestParams>): Promise<TResponse>;
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
declare const GzFetchFeedbackProvider: FC<PropsWithChildren>;
|
|
90
|
+
|
|
91
|
+
declare const GzFetchUnauthorizedModal: FC;
|
|
92
|
+
|
|
93
|
+
export { type CreateGzFetchOptions, type GzErrorContext, type GzFetch, type GzFetchAuthOptions, type GzFetchClient, GzFetchError, type GzFetchErrorOptions, type GzFetchErrorType, GzFetchFeedbackProvider, 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,10 +1,12 @@
|
|
|
1
|
-
import { message } from '@chenhui996/gg-ui';
|
|
2
1
|
import axios, { AxiosError, AxiosHeaders } from 'axios';
|
|
2
|
+
import { message, Modal } from '@chenhui996/gg-ui';
|
|
3
|
+
import { useRef, useCallback, useSyncExternalStore, useEffect } from 'react';
|
|
4
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
3
5
|
|
|
4
6
|
// src/fetch/client.ts
|
|
5
7
|
var GzFetchError = class extends Error {
|
|
6
|
-
constructor(
|
|
7
|
-
super(
|
|
8
|
+
constructor(message3, options) {
|
|
9
|
+
super(message3);
|
|
8
10
|
this.name = "GzFetchError";
|
|
9
11
|
this.type = options.type;
|
|
10
12
|
this.status = options.status;
|
|
@@ -63,6 +65,28 @@ function toGzFetchError(error) {
|
|
|
63
65
|
originalError: error
|
|
64
66
|
});
|
|
65
67
|
}
|
|
68
|
+
var feedbackHosts = /* @__PURE__ */ new Map();
|
|
69
|
+
var activeHostId;
|
|
70
|
+
var registerFeedbackHost = (hostId, host) => {
|
|
71
|
+
feedbackHosts.set(hostId, host);
|
|
72
|
+
activeHostId != null ? activeHostId : activeHostId = hostId;
|
|
73
|
+
return () => {
|
|
74
|
+
feedbackHosts.delete(hostId);
|
|
75
|
+
if (activeHostId !== hostId) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const nextHost = feedbackHosts.keys().next();
|
|
79
|
+
activeHostId = nextHost.done ? void 0 : nextHost.value;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
var showFetchErrorMessage = (content) => {
|
|
83
|
+
const activeHost = activeHostId === void 0 ? void 0 : feedbackHosts.get(activeHostId);
|
|
84
|
+
if (activeHost) {
|
|
85
|
+
activeHost.showErrorMessage(content);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
message.error(content);
|
|
89
|
+
};
|
|
66
90
|
|
|
67
91
|
// src/fetch/middleware.ts
|
|
68
92
|
async function runRequestMiddlewares(initialConfig, middlewares) {
|
|
@@ -90,6 +114,103 @@ async function runErrorMiddlewares(error, context, middlewares) {
|
|
|
90
114
|
}
|
|
91
115
|
}
|
|
92
116
|
|
|
117
|
+
// src/fetch/unauthorized.ts
|
|
118
|
+
var DEFAULT_UNAUTHORIZED_LOGIN_URL = "/login";
|
|
119
|
+
var DEFAULT_UNAUTHORIZED_MODAL_TITLE = "登录失效";
|
|
120
|
+
var DEFAULT_UNAUTHORIZED_MODAL_MESSAGE = "当前登录状态已失效,请重新登录。";
|
|
121
|
+
var IDLE_SNAPSHOT = { phase: "idle" };
|
|
122
|
+
var snapshot = IDLE_SNAPSHOT;
|
|
123
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
124
|
+
var modalHosts = /* @__PURE__ */ new Set();
|
|
125
|
+
var emitChange = () => {
|
|
126
|
+
for (const listener of listeners) {
|
|
127
|
+
listener();
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
var setSnapshot = (nextSnapshot) => {
|
|
131
|
+
snapshot = nextSnapshot;
|
|
132
|
+
emitChange();
|
|
133
|
+
};
|
|
134
|
+
var resolveUnauthorizedOptions = (options) => {
|
|
135
|
+
var _a, _b, _c, _d;
|
|
136
|
+
return {
|
|
137
|
+
enabled: (_a = options == null ? void 0 : options.enabled) != null ? _a : false,
|
|
138
|
+
loginUrl: (_b = options == null ? void 0 : options.loginUrl) != null ? _b : DEFAULT_UNAUTHORIZED_LOGIN_URL,
|
|
139
|
+
modalTitle: (_c = options == null ? void 0 : options.modalTitle) != null ? _c : DEFAULT_UNAUTHORIZED_MODAL_TITLE,
|
|
140
|
+
modalMessage: (_d = options == null ? void 0 : options.modalMessage) != null ? _d : DEFAULT_UNAUTHORIZED_MODAL_MESSAGE,
|
|
141
|
+
...(options == null ? void 0 : options.onUnauthorized) === void 0 ? {} : { onUnauthorized: options.onUnauthorized }
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
var getUnauthorizedSnapshot = () => snapshot;
|
|
145
|
+
var getUnauthorizedServerSnapshot = () => IDLE_SNAPSHOT;
|
|
146
|
+
var subscribeUnauthorized = (listener) => {
|
|
147
|
+
listeners.add(listener);
|
|
148
|
+
return () => listeners.delete(listener);
|
|
149
|
+
};
|
|
150
|
+
var registerUnauthorizedModalHost = (hostId) => {
|
|
151
|
+
modalHosts.add(hostId);
|
|
152
|
+
if (snapshot.activeHostId === void 0) {
|
|
153
|
+
setSnapshot({ ...snapshot, activeHostId: hostId });
|
|
154
|
+
}
|
|
155
|
+
return () => {
|
|
156
|
+
modalHosts.delete(hostId);
|
|
157
|
+
if (snapshot.activeHostId !== hostId) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const nextHost = modalHosts.values().next();
|
|
161
|
+
const nextHostId = nextHost.done ? void 0 : nextHost.value;
|
|
162
|
+
if (snapshot.phase === "closing") {
|
|
163
|
+
setSnapshot(
|
|
164
|
+
nextHostId === void 0 ? IDLE_SNAPSHOT : { activeHostId: nextHostId, phase: "idle" }
|
|
165
|
+
);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (nextHostId === void 0) {
|
|
169
|
+
setSnapshot(
|
|
170
|
+
snapshot.options === void 0 ? { phase: snapshot.phase } : { options: snapshot.options, phase: snapshot.phase }
|
|
171
|
+
);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
setSnapshot({ ...snapshot, activeHostId: nextHostId });
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
var openUnauthorizedModal = (options) => {
|
|
178
|
+
if (!options.enabled || snapshot.phase !== "idle") {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
setSnapshot({
|
|
182
|
+
...snapshot.activeHostId === void 0 ? {} : { activeHostId: snapshot.activeHostId },
|
|
183
|
+
options,
|
|
184
|
+
phase: "open"
|
|
185
|
+
});
|
|
186
|
+
return true;
|
|
187
|
+
};
|
|
188
|
+
var beginCloseUnauthorizedModal = () => {
|
|
189
|
+
if (snapshot.phase !== "open" || snapshot.options === void 0) {
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
const { options } = snapshot;
|
|
193
|
+
setSnapshot({ ...snapshot, phase: "closing" });
|
|
194
|
+
return options;
|
|
195
|
+
};
|
|
196
|
+
var cancelUnauthorizedModal = () => beginCloseUnauthorizedModal() !== void 0;
|
|
197
|
+
var confirmUnauthorizedModal = () => beginCloseUnauthorizedModal();
|
|
198
|
+
var completeUnauthorizedModal = () => {
|
|
199
|
+
if (snapshot.phase !== "closing") {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
setSnapshot(
|
|
203
|
+
snapshot.activeHostId === void 0 ? IDLE_SNAPSHOT : { activeHostId: snapshot.activeHostId, phase: "idle" }
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
var executeUnauthorizedAction = (options, navigate = (loginUrl) => window.location.assign(loginUrl)) => {
|
|
207
|
+
if (options.onUnauthorized) {
|
|
208
|
+
options.onUnauthorized();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
navigate(options.loginUrl);
|
|
212
|
+
};
|
|
213
|
+
|
|
93
214
|
// src/fetch/client.ts
|
|
94
215
|
var DEFAULT_TIMEOUT = 15e3;
|
|
95
216
|
var DEFAULT_AUTH_HEADER = "Authorization";
|
|
@@ -109,6 +230,7 @@ function createGzFetch(options = {}) {
|
|
|
109
230
|
const showInstanceErrorMessage = (_b = options.showErrorMessage) != null ? _b : true;
|
|
110
231
|
const headerName = (_d = (_c = options.auth) == null ? void 0 : _c.headerName) != null ? _d : DEFAULT_AUTH_HEADER;
|
|
111
232
|
const formatToken = (_f = (_e = options.auth) == null ? void 0 : _e.formatToken) != null ? _f : ((token) => `Bearer ${token}`);
|
|
233
|
+
const unauthorizedOptions = resolveUnauthorizedOptions(options.unauthorized);
|
|
112
234
|
const instance = axios.create({
|
|
113
235
|
...options.baseURL === void 0 ? {} : { baseURL: options.baseURL },
|
|
114
236
|
timeout: (_g = options.timeout) != null ? _g : DEFAULT_TIMEOUT,
|
|
@@ -150,10 +272,14 @@ function createGzFetch(options = {}) {
|
|
|
150
272
|
return await runResponseMiddlewares(response.data, context, middlewares);
|
|
151
273
|
} catch (originalError) {
|
|
152
274
|
const error = toGzFetchError(originalError);
|
|
275
|
+
const shouldHandleUnauthorized = unauthorizedOptions.enabled && error.type === "HTTP_ERROR" && error.status === 401;
|
|
276
|
+
if (shouldHandleUnauthorized) {
|
|
277
|
+
openUnauthorizedModal(unauthorizedOptions);
|
|
278
|
+
}
|
|
153
279
|
await runErrorMiddlewares(error, { config: currentConfig }, middlewares);
|
|
154
280
|
const shouldShowError = (_a2 = currentConfig.showErrorMessage) != null ? _a2 : showInstanceErrorMessage;
|
|
155
|
-
if (shouldShowError && error.type !== "CANCELED_ERROR") {
|
|
156
|
-
|
|
281
|
+
if (shouldShowError && error.type !== "CANCELED_ERROR" && !shouldHandleUnauthorized) {
|
|
282
|
+
showFetchErrorMessage(error.message);
|
|
157
283
|
}
|
|
158
284
|
throw error;
|
|
159
285
|
}
|
|
@@ -188,5 +314,61 @@ async function gzFetch(config) {
|
|
|
188
314
|
}
|
|
189
315
|
return defaultClient(config);
|
|
190
316
|
}
|
|
317
|
+
var GzFetchUnauthorizedModal = () => {
|
|
318
|
+
var _a, _b, _c;
|
|
319
|
+
const hostIdRef = useRef(void 0);
|
|
320
|
+
(_a = hostIdRef.current) != null ? _a : hostIdRef.current = /* @__PURE__ */ Symbol("gz-fetch-unauthorized-modal-host");
|
|
321
|
+
const hostId = hostIdRef.current;
|
|
322
|
+
const [messageApi, messageContextHolder] = message.useMessage();
|
|
323
|
+
const showErrorMessage = useCallback(
|
|
324
|
+
(content) => {
|
|
325
|
+
void messageApi.error(content);
|
|
326
|
+
},
|
|
327
|
+
[messageApi]
|
|
328
|
+
);
|
|
329
|
+
const snapshot2 = useSyncExternalStore(
|
|
330
|
+
subscribeUnauthorized,
|
|
331
|
+
getUnauthorizedSnapshot,
|
|
332
|
+
getUnauthorizedServerSnapshot
|
|
333
|
+
);
|
|
334
|
+
useEffect(() => registerUnauthorizedModalHost(hostId), [hostId]);
|
|
335
|
+
useEffect(
|
|
336
|
+
() => registerFeedbackHost(hostId, { showErrorMessage }),
|
|
337
|
+
[hostId, showErrorMessage]
|
|
338
|
+
);
|
|
339
|
+
const handleConfirm = () => {
|
|
340
|
+
const options = confirmUnauthorizedModal();
|
|
341
|
+
if (options) {
|
|
342
|
+
executeUnauthorizedAction(options);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
346
|
+
messageContextHolder,
|
|
347
|
+
snapshot2.activeHostId === hostId && /* @__PURE__ */ jsx(
|
|
348
|
+
Modal,
|
|
349
|
+
{
|
|
350
|
+
afterClose: completeUnauthorizedModal,
|
|
351
|
+
cancelText: "取消",
|
|
352
|
+
closable: true,
|
|
353
|
+
okText: "重新登录",
|
|
354
|
+
open: snapshot2.phase === "open",
|
|
355
|
+
title: (_b = snapshot2.options) == null ? void 0 : _b.modalTitle,
|
|
356
|
+
onCancel: cancelUnauthorizedModal,
|
|
357
|
+
onOk: handleConfirm,
|
|
358
|
+
children: (_c = snapshot2.options) == null ? void 0 : _c.modalMessage
|
|
359
|
+
}
|
|
360
|
+
)
|
|
361
|
+
] });
|
|
362
|
+
};
|
|
363
|
+
GzFetchUnauthorizedModal.displayName = "GzFetchUnauthorizedModal";
|
|
364
|
+
var GzFetchUnauthorizedModal_default = GzFetchUnauthorizedModal;
|
|
365
|
+
var GzFetchFeedbackProvider = ({ children }) => {
|
|
366
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
367
|
+
/* @__PURE__ */ jsx(GzFetchUnauthorizedModal_default, {}),
|
|
368
|
+
children
|
|
369
|
+
] });
|
|
370
|
+
};
|
|
371
|
+
GzFetchFeedbackProvider.displayName = "GzFetchFeedbackProvider";
|
|
372
|
+
var GzFetchFeedbackProvider_default = GzFetchFeedbackProvider;
|
|
191
373
|
|
|
192
|
-
export { GzFetchError, configureGzFetch, createGzFetch, getResponseMessage, gzFetch, toGzFetchError };
|
|
374
|
+
export { GzFetchError, GzFetchFeedbackProvider_default as GzFetchFeedbackProvider, GzFetchUnauthorizedModal_default as GzFetchUnauthorizedModal, configureGzFetch, createGzFetch, getResponseMessage, gzFetch, toGzFetchError };
|