@tsingroc/tsingroc-components 5.0.0-alpha.21 → 5.0.0-alpha.23

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.
Files changed (41) hide show
  1. package/dist/components/Calendar.js +8 -4
  2. package/dist/components/ECharts.d.ts +1 -1
  3. package/dist/components/Header.d.ts +1 -1
  4. package/dist/components/Header.js +3 -1
  5. package/dist/components/LineChartEditor.js +3 -3
  6. package/dist/components/LineChartTable.js +319 -123
  7. package/dist/components/LinkedLineChart.js +3 -3
  8. package/dist/components/QuickDateRangePicker.d.ts +1 -1
  9. package/dist/components/QuickDateRangePicker.js +2 -3
  10. package/dist/components/Sidebar.d.ts +1 -1
  11. package/dist/components/Sidebar.js +4 -5
  12. package/dist/components/TsingrocDatePicker.d.ts +2 -2
  13. package/dist/components/TsingrocDatePicker.js +6 -7
  14. package/dist/components/UserButton.d.ts +1 -1
  15. package/dist/components/UserButton.js +24 -18
  16. package/dist/components/WeatherMap.js +1 -1
  17. package/dist/components/auth/AuthProvider.d.ts +35 -0
  18. package/dist/components/auth/AuthProvider.js +147 -0
  19. package/dist/components/auth/AuthService.d.ts +30 -0
  20. package/dist/components/auth/AuthService.js +1 -0
  21. package/dist/components/auth/CasdoorAuth.d.ts +152 -0
  22. package/dist/components/auth/CasdoorAuth.js +180 -0
  23. package/dist/components/auth/EmbeddedAuth.d.ts +38 -0
  24. package/dist/components/auth/EmbeddedAuth.js +205 -0
  25. package/dist/components/auth/Fetcher.d.ts +38 -0
  26. package/dist/components/auth/Fetcher.js +142 -0
  27. package/dist/components/auth/LocalAuth.d.ts +90 -0
  28. package/dist/components/auth/LocalAuth.js +111 -0
  29. package/dist/components/auth/LoginCheck.d.ts +17 -0
  30. package/dist/components/auth/LoginCheck.js +58 -0
  31. package/dist/components/auth/SessionStore.d.ts +69 -0
  32. package/dist/components/auth/SessionStore.js +194 -0
  33. package/dist/deckgl/TiandituLayer.d.ts +1 -1
  34. package/dist/deckgl/TiandituLayer.js +1 -1
  35. package/dist/deckgl/WeatherData.d.ts +1 -1
  36. package/dist/echarts/series/maxBarSeries.d.ts +1 -1
  37. package/dist/index.d.ts +8 -1
  38. package/dist/index.js +7 -1
  39. package/package.json +34 -50
  40. package/dist/components/Auth.d.ts +0 -314
  41. package/dist/components/Auth.js +0 -296
@@ -0,0 +1,180 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import Sdk from "casdoor-js-sdk";
3
+ import { jwtDecode } from "jwt-decode";
4
+ import { useEffect, useEffectEvent, useRef, useState, useSyncExternalStore } from "react";
5
+ import { useSessionStoreInternal } from "./AuthProvider"; // oxlint-disable-line no-unused-vars
6
+
7
+ import { EmbeddedAuthService } from "./EmbeddedAuth";
8
+ import { fetcherWithBearerToken } from "./Fetcher";
9
+ // oxlint-disable-line no-unused-vars
10
+
11
+ /**
12
+ * 基于 Casdoor 的鉴权服务。该鉴权服务的登录流程如下:
13
+ *
14
+ * 1. 调用 {@linkcode CasdoorAuthService.getSession | getSession()} 跳转到 SSO 登录页面。
15
+ * 2. 用户在登录页面完成登录,跳转到回调页面。
16
+ * 3. 回调页面调用 {@linkcode CasdoorAuthService.callback | callback()} 获取 token。
17
+ *
18
+ * 当使用 {@linkcode AuthProvider} 时,第 1 步由 {@linkcode LoginCheck} 完成,
19
+ * 第 3 步由 {@linkcode CasdoorCallback} 完成。
20
+ */
21
+ export class CasdoorAuthService {
22
+ #sdk;
23
+ #signinOrigin;
24
+ #signinPath;
25
+ constructor(options) {
26
+ this.#sdk = new Sdk(options.config);
27
+ this.#signinOrigin = options.signinOrigin || location.origin;
28
+ this.#signinPath = options.signinPath;
29
+ }
30
+ getSession() {
31
+ location.href = this.#sdk.getSigninUrl();
32
+ return Promise.reject(new Error("已跳转到登录页面,该错误用于中止登录流程,可以忽略。"));
33
+ }
34
+ async validate(token) {
35
+ // sdk 的类型声明有误
36
+ const resp = await this.#sdk.getUserInfo(token);
37
+ if ("status" in resp) {
38
+ throw new Error("登陆状态无效!");
39
+ }
40
+ return resp;
41
+ }
42
+
43
+ /**
44
+ * CasdoorAuthService 的特有函数。
45
+ * 使用 CasdoorAuthService 时,需要在项目中设置一个回调页面。
46
+ * 当用户完成登录,跳转到回调页面时,可调用此函数获取 access token。
47
+ * 若用户不经过登录流程进入回调页面,或者后端发生错误,则会抛出异常。
48
+ */
49
+ async callback() {
50
+ // sdk 的类型声明有误
51
+ const resp = await this.#sdk.signin(this.#signinOrigin, this.#signinPath);
52
+ const token = resp.token;
53
+ if (!token) {
54
+ throw new Error("登录 API 返回了异常的结果:" + JSON.stringify(resp));
55
+ }
56
+ return token;
57
+ }
58
+ fetcherMiddleware = fetcherWithBearerToken;
59
+ parseUserInfo(info) {
60
+ return info;
61
+ }
62
+
63
+ /**
64
+ * 解析 token 中的内容。
65
+ * Token 可以通过 `useSession().state` 获取。
66
+ */
67
+ decodeToken(token) {
68
+ return jwtDecode(token);
69
+ }
70
+ }
71
+ /** Casdoor 回调组件。请将该组件放置在 {@linkcode CasdoorAuthService} 中设定的回调路径上。 */
72
+ export function CasdoorCallback(props) {
73
+ const $ = _c(8);
74
+ const {
75
+ onSuccess,
76
+ onError,
77
+ success: t0,
78
+ error: t1,
79
+ children: t2
80
+ } = props;
81
+ const success = t0 === undefined ? "\u767B\u5F55\u6210\u529F" : t0;
82
+ const error = t1 === undefined ? "\u767B\u5F55\u8FC7\u7A0B\u53D1\u751F\u9519\u8BEF" : t1;
83
+ const children = t2 === undefined ? "\u6B63\u5728\u8DF3\u8F6C\u2026\u2026" : t2;
84
+ const sessionStore = useSessionStoreInternal("CasdoorCallback");
85
+ const service = sessionStore.service;
86
+ if (!(service instanceof CasdoorAuthService)) {
87
+ throw new Error("CasdoorCallback \u5FC5\u987B\u642D\u914D CasdoorAuthService \u4F7F\u7528\uFF01");
88
+ }
89
+ const [status, setStatus] = useState("loading");
90
+ const hasOngoingCallback = useRef(false);
91
+ let t3;
92
+ if ($[0] !== onError || $[1] !== onSuccess || $[2] !== service || $[3] !== sessionStore) {
93
+ t3 = () => {
94
+ if (hasOngoingCallback.current) {
95
+ return;
96
+ }
97
+ service.callback().then(token => {
98
+ setStatus("success");
99
+ sessionStore.load(token);
100
+ onSuccess?.();
101
+ }, err => {
102
+ setStatus("error");
103
+ onError?.(err);
104
+ });
105
+ hasOngoingCallback.current = true;
106
+ };
107
+ $[0] = onError;
108
+ $[1] = onSuccess;
109
+ $[2] = service;
110
+ $[3] = sessionStore;
111
+ $[4] = t3;
112
+ } else {
113
+ t3 = $[4];
114
+ }
115
+ const callback = useEffectEvent(t3);
116
+ let t4;
117
+ if ($[5] !== callback) {
118
+ t4 = () => {
119
+ callback();
120
+ };
121
+ $[5] = callback;
122
+ $[6] = t4;
123
+ } else {
124
+ t4 = $[6];
125
+ }
126
+ let t5;
127
+ if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
128
+ t5 = [];
129
+ $[7] = t5;
130
+ } else {
131
+ t5 = $[7];
132
+ }
133
+ useEffect(t4, t5);
134
+ return status === "loading" ? children : status === "error" ? error : success;
135
+ }
136
+ /** 检查 Casdoor 用户的角色(role),限制只有特定角色的用户才能访问内部的组件。 */
137
+ export function CasdoorRoleCheck(props) {
138
+ const $ = _c(4);
139
+ const {
140
+ required,
141
+ forbidden,
142
+ error: t0,
143
+ children
144
+ } = props;
145
+ const error = t0 === undefined ? "\u60A8\u65E0\u6743\u8BBF\u95EE\u6B64\u9875\u9762\uFF01" : t0;
146
+ const sessionStore = useSessionStoreInternal("CasdoorRoleCheck");
147
+ if (!(sessionStore.service instanceof CasdoorAuthService || sessionStore.service instanceof EmbeddedAuthService)) {
148
+ throw new Error("CasdoorRoleCheck \u5FC5\u987B\u642D\u914D CasdoorAuthService \u6216 EmbeddedAuthService \u4F7F\u7528\uFF01");
149
+ }
150
+ let t1;
151
+ if ($[0] !== sessionStore) {
152
+ t1 = onChange => {
153
+ sessionStore.addEventListener(onChange);
154
+ return () => sessionStore.addEventListener(onChange);
155
+ };
156
+ $[0] = sessionStore;
157
+ $[1] = t1;
158
+ } else {
159
+ t1 = $[1];
160
+ }
161
+ let t2;
162
+ if ($[2] !== sessionStore.session) {
163
+ t2 = () => sessionStore.session;
164
+ $[2] = sessionStore.session;
165
+ $[3] = t2;
166
+ } else {
167
+ t2 = $[3];
168
+ }
169
+ const session = useSyncExternalStore(t1, t2);
170
+ if (!session) {
171
+ return error;
172
+ }
173
+ const userInfo = session.rawUserInfo;
174
+ const hasRequiredRole = required.some(role => userInfo.roles.includes(role));
175
+ const hasForbiddenRole = forbidden?.some(role_0 => userInfo.roles.includes(role_0));
176
+ if (!hasRequiredRole || hasForbiddenRole) {
177
+ return error;
178
+ }
179
+ return children;
180
+ }
@@ -0,0 +1,38 @@
1
+ import type { SdkConfig } from "casdoor-js-sdk/lib/esm/sdk";
2
+ import { type IframeHTMLAttributes, type ReactNode, type RefAttributes } from "react";
3
+ import type { AuthService, UserInfo } from "./AuthService";
4
+ import { type CasdoorTokenPayload, type CasdoorUserInfo } from "./CasdoorAuth";
5
+ import { fetcherWithBearerToken } from "./Fetcher";
6
+ export interface EmbeddedAuthServiceOptions {
7
+ config: SdkConfig;
8
+ timeout?: number;
9
+ }
10
+ /**
11
+ * 嵌入式鉴权服务,只能在 iframe 内使用。该鉴权服务的登录流程如下:
12
+ *
13
+ * 1. 调用 {@linkcode EmbeddedAuthService.getSession | getSession()} 向父页面获取 token。
14
+ * 2. 父页面响应请求,提供 token。
15
+ * 3. {@linkcode EmbeddedAuthService.getSession | getSession()} 返回 token。
16
+ *
17
+ * 当使用 {@linkcode AuthProvider} 时,第 1、3 步由 {@linkcode LoginCheck} 完成,
18
+ * 第 2 步由父页面中的 {@linkcode Embed} 完成。
19
+ */
20
+ export declare class EmbeddedAuthService implements AuthService<string, CasdoorUserInfo> {
21
+ #private;
22
+ constructor(options: EmbeddedAuthServiceOptions);
23
+ getSession(): Promise<string>;
24
+ validate(token: string): Promise<CasdoorUserInfo>;
25
+ readonly fetcherMiddleware: typeof fetcherWithBearerToken;
26
+ parseUserInfo(info: CasdoorUserInfo): UserInfo;
27
+ /**
28
+ * 解析 token 中的内容。
29
+ * Token 可以通过 `useSession().state` 获取。
30
+ */
31
+ decodeToken(token: string): CasdoorTokenPayload;
32
+ }
33
+ /**
34
+ * 嵌入一个使用了 {@linkcode EmbeddedAuthService} 的页面。
35
+ * 该组件必须搭配 {@linkcode CasdoorAuthService} 或 {@linkcode EmbeddedAuthService},并且在 {@linkcode LoginCheck} 内部使用。
36
+ * 该组件只负责向子页面提供 token,其它方面的用法和 `<iframe>` 完全相同。
37
+ */
38
+ export declare function Embed(props: IframeHTMLAttributes<HTMLIFrameElement> & RefAttributes<HTMLIFrameElement>): ReactNode;
@@ -0,0 +1,205 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import Sdk from "casdoor-js-sdk";
3
+ import { jwtDecode } from "jwt-decode";
4
+ import { useEffectEvent, useSyncExternalStore } from "react";
5
+ import { useSessionStoreInternal } from "./AuthProvider"; // oxlint-disable-line no-unused-vars
6
+
7
+ import { CasdoorAuthService } from "./CasdoorAuth";
8
+ import { fetcherWithBearerToken } from "./Fetcher";
9
+ // oxlint-disable-line no-unused-vars
10
+ import { jsx as _jsx } from "react/jsx-runtime";
11
+ /**
12
+ * 嵌入式鉴权服务,只能在 iframe 内使用。该鉴权服务的登录流程如下:
13
+ *
14
+ * 1. 调用 {@linkcode EmbeddedAuthService.getSession | getSession()} 向父页面获取 token。
15
+ * 2. 父页面响应请求,提供 token。
16
+ * 3. {@linkcode EmbeddedAuthService.getSession | getSession()} 返回 token。
17
+ *
18
+ * 当使用 {@linkcode AuthProvider} 时,第 1、3 步由 {@linkcode LoginCheck} 完成,
19
+ * 第 2 步由父页面中的 {@linkcode Embed} 完成。
20
+ */
21
+ export class EmbeddedAuthService {
22
+ #sdk;
23
+ #timeout;
24
+ constructor(options) {
25
+ this.#sdk = new Sdk(options.config);
26
+ this.#timeout = options.timeout || 5000;
27
+ }
28
+ getSession() {
29
+ if (!(window.parent && window.parent !== window)) {
30
+ return Promise.reject(new Error("无法在非嵌入式环境中使用 EmbeddedAuthService"));
31
+ }
32
+ return new Promise((resolve, reject) => {
33
+ const handleMessage = event => {
34
+ window.removeEventListener("message", handleMessage);
35
+ clearTimeout(timeout);
36
+ // 通常来说接收来自父页面的消息需要验证 origin,
37
+ // 但由于我们这里是在接受 access token,而 access token 没法造假,
38
+ // 因此只要对数据进行小心验证,可以不需要验证 origin
39
+ if (event.data && typeof event.data === "object") {
40
+ const data = event.data;
41
+ if (data.type === "TOKEN_RESPONSE" && typeof data.token === "string") {
42
+ resolve(data.token);
43
+ return;
44
+ }
45
+ }
46
+ reject(new Error("从父页面收到无效数据"));
47
+ };
48
+ const handleTimeout = () => {
49
+ window.removeEventListener("message", handleMessage);
50
+ reject(new Error("等待父页面回复超时"));
51
+ };
52
+ window.addEventListener("message", handleMessage);
53
+ window.parent.postMessage({
54
+ type: "REQUEST_TOKEN"
55
+ }, "*");
56
+ const timeout = setTimeout(handleTimeout, this.#timeout);
57
+ });
58
+ }
59
+ async validate(token) {
60
+ // sdk 的类型声明有误
61
+ const resp = await this.#sdk.getUserInfo(token);
62
+ if ("status" in resp) {
63
+ throw new Error("登陆状态无效!");
64
+ }
65
+ return resp;
66
+ }
67
+ fetcherMiddleware = fetcherWithBearerToken;
68
+ parseUserInfo(info) {
69
+ return info;
70
+ }
71
+
72
+ /**
73
+ * 解析 token 中的内容。
74
+ * Token 可以通过 `useSession().state` 获取。
75
+ */
76
+ decodeToken(token) {
77
+ return jwtDecode(token);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * 嵌入一个使用了 {@linkcode EmbeddedAuthService} 的页面。
83
+ * 该组件必须搭配 {@linkcode CasdoorAuthService} 或 {@linkcode EmbeddedAuthService},并且在 {@linkcode LoginCheck} 内部使用。
84
+ * 该组件只负责向子页面提供 token,其它方面的用法和 `<iframe>` 完全相同。
85
+ */
86
+ export function Embed(props) {
87
+ const $ = _c(16);
88
+ const sessionStore = useSessionStoreInternal("useSession");
89
+ if (!(sessionStore.service instanceof CasdoorAuthService || sessionStore.service instanceof EmbeddedAuthService)) {
90
+ throw new Error("CasdoorRoleCheck \u5FC5\u987B\u642D\u914D CasdoorAuthService \u6216 EmbeddedAuthService \u4F7F\u7528\uFF01");
91
+ }
92
+ let t0;
93
+ if ($[0] !== sessionStore) {
94
+ t0 = onChange => {
95
+ sessionStore.addEventListener(onChange);
96
+ return () => sessionStore.addEventListener(onChange);
97
+ };
98
+ $[0] = sessionStore;
99
+ $[1] = t0;
100
+ } else {
101
+ t0 = $[1];
102
+ }
103
+ let t1;
104
+ if ($[2] !== sessionStore.session) {
105
+ t1 = () => sessionStore.session;
106
+ $[2] = sessionStore.session;
107
+ $[3] = t1;
108
+ } else {
109
+ t1 = $[3];
110
+ }
111
+ const session = useSyncExternalStore(t0, t1);
112
+ if (!session) {
113
+ throw new Error("\u5F53\u524D\u7528\u6237\u672A\u767B\u5F55\uFF0C\u65E0\u6CD5\u83B7\u53D6\u4F1A\u8BDD\u3002\u8BF7\u786E\u4FDD\u53EA\u5728 LoginCheck \u5185\u90E8\u4F7F\u7528 Embed\u3002");
114
+ }
115
+ const {
116
+ state: token
117
+ } = session;
118
+ let t2;
119
+ if ($[4] !== props.src || $[5] !== token) {
120
+ t2 = (event, elem) => {
121
+ if (!props.src) {
122
+ return;
123
+ }
124
+ if (event.source !== elem.contentWindow) {
125
+ return;
126
+ }
127
+ const data = event.data;
128
+ if (typeof data !== "object" || !data || !("type" in data) || typeof data.type !== "string") {
129
+ return;
130
+ }
131
+ if (data.type === "REQUEST_TOKEN") {
132
+ elem.contentWindow?.postMessage({
133
+ type: "TOKEN_RESPONSE",
134
+ token
135
+ }, new URL(props.src).origin);
136
+ }
137
+ };
138
+ $[4] = props.src;
139
+ $[5] = token;
140
+ $[6] = t2;
141
+ } else {
142
+ t2 = $[6];
143
+ }
144
+ const onMessage = useEffectEvent(t2);
145
+ let ref;
146
+ let rest;
147
+ if ($[7] !== props) {
148
+ ({
149
+ ref,
150
+ ...rest
151
+ } = props);
152
+ $[7] = props;
153
+ $[8] = ref;
154
+ $[9] = rest;
155
+ } else {
156
+ ref = $[8];
157
+ rest = $[9];
158
+ }
159
+ let t3;
160
+ if ($[10] !== onMessage || $[11] !== ref) {
161
+ t3 = elem_0 => {
162
+ if (!elem_0) {
163
+ return;
164
+ }
165
+ const listener = event_0 => onMessage(event_0, elem_0);
166
+ window.addEventListener("message", listener);
167
+ let cleanup = undefined;
168
+ if (typeof ref === "function") {
169
+ const cleanupOrUndefined = ref(elem_0);
170
+ if (typeof cleanupOrUndefined === "function") {
171
+ cleanup = cleanupOrUndefined;
172
+ } else {
173
+ cleanup = () => ref(null);
174
+ }
175
+ } else {
176
+ if (ref) {
177
+ ref.current = elem_0;
178
+ cleanup = () => ref.current = null;
179
+ }
180
+ }
181
+ return () => {
182
+ cleanup?.();
183
+ window.removeEventListener("message", listener);
184
+ };
185
+ };
186
+ $[10] = onMessage;
187
+ $[11] = ref;
188
+ $[12] = t3;
189
+ } else {
190
+ t3 = $[12];
191
+ }
192
+ let t4;
193
+ if ($[13] !== rest || $[14] !== t3) {
194
+ t4 = /*#__PURE__*/_jsx("iframe", {
195
+ ref: t3,
196
+ ...rest
197
+ });
198
+ $[13] = rest;
199
+ $[14] = t3;
200
+ $[15] = t4;
201
+ } else {
202
+ t4 = $[15];
203
+ }
204
+ return t4;
205
+ }
@@ -0,0 +1,38 @@
1
+ import { type ReactNode } from "react";
2
+ import type { SessionStore } from "./SessionStore";
3
+ /** 一个与 {@linkcode fetch} 兼容的 fetcher 函数 */
4
+ export type Fetcher = (resource: RequestInfo | URL, options?: RequestInit & FetcherExtraOptions) => Promise<Response>;
5
+ /**
6
+ * Fetcher 类型可接受的额外选项。可以使用 TypeScript 向这个类型中加入额外内容:
7
+ *
8
+ * ```ts
9
+ * declare module "@tsingroc/tsingroc-components" {
10
+ * interface FetcherExtraOptions {
11
+ * customOption?: object; // 注意必须是可选参数
12
+ * }
13
+ * }
14
+ * ```
15
+ */
16
+ export interface FetcherExtraOptions {
17
+ loginOn401?: boolean;
18
+ }
19
+ /** 在请求中添加 Authorization 头的 fetcher 中间件。 */
20
+ export declare function fetcherWithBearerToken(token: string): (fetcher: Fetcher) => Fetcher;
21
+ /** 在请求返回 401 错误时自动尝试登录的 fetcher 中间件。 */
22
+ export declare function fetcherWithLoginOn401(sessionStore: SessionStore): (fetcher: Fetcher) => Fetcher;
23
+ /** 获取当前上下文的 fetcher。 */
24
+ export declare function useFetcher(): Fetcher;
25
+ export interface FetcherMiddlewareProps {
26
+ middleware: (fetcher: Fetcher) => Fetcher;
27
+ children: ReactNode;
28
+ }
29
+ /** 为内部的组件添加额外的 fetcher 中间件,例如在发生错误时弹出警告。 */
30
+ export declare function FetcherMiddleware(props: FetcherMiddlewareProps): ReactNode;
31
+ export interface FetcherWithAuthProps {
32
+ children: ReactNode;
33
+ }
34
+ /**
35
+ * 为内部的组件的 fetcher 添加 Authorization 头。
36
+ * 默认设置下,{@linkcode AuthProvider} 已经提供了这个中间件,不需要另外添加。
37
+ */
38
+ export declare function FetcherWithAuth(props: FetcherWithAuthProps): ReactNode;
@@ -0,0 +1,142 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import { createContext, use, useSyncExternalStore } from "react";
3
+ import { useSessionStoreInternal } from "./AuthProvider"; // oxlint-disable-line no-unused-vars
4
+
5
+ /** 一个与 {@linkcode fetch} 兼容的 fetcher 函数 */
6
+
7
+ /**
8
+ * Fetcher 类型可接受的额外选项。可以使用 TypeScript 向这个类型中加入额外内容:
9
+ *
10
+ * ```ts
11
+ * declare module "@tsingroc/tsingroc-components" {
12
+ * interface FetcherExtraOptions {
13
+ * customOption?: object; // 注意必须是可选参数
14
+ * }
15
+ * }
16
+ * ```
17
+ */
18
+ import { jsx as _jsx } from "react/jsx-runtime";
19
+ /** 在请求中添加 Authorization 头的 fetcher 中间件。 */
20
+ export function fetcherWithBearerToken(token) {
21
+ return fetcher => async (resource, options) => {
22
+ const headers = new Headers(options?.headers);
23
+ if (!headers.has("Authorization")) {
24
+ headers.set("Authorization", `Bearer ${token}`);
25
+ }
26
+ const resp = await fetcher(resource, {
27
+ ...options,
28
+ headers
29
+ });
30
+ return resp;
31
+ };
32
+ }
33
+
34
+ /** 在请求返回 401 错误时自动尝试登录的 fetcher 中间件。 */
35
+ export function fetcherWithLoginOn401(sessionStore) {
36
+ return fetcher => async (resource, options) => {
37
+ const loggedIn = !!sessionStore.session;
38
+ const resp = await fetcher(resource, options);
39
+ if (resp.status === 401) {
40
+ await sessionStore.clear();
41
+ void sessionStore.get();
42
+ throw new Error(`对 ${resp.url} 的请求需要鉴权,但用户${loggedIn ? "登录状态已失效" : "未登录"}。`);
43
+ }
44
+ return resp;
45
+ };
46
+ }
47
+ const FetcherContext = /*#__PURE__*/createContext(fetch);
48
+
49
+ /** 获取当前上下文的 fetcher。 */
50
+ export function useFetcher() {
51
+ return use(FetcherContext);
52
+ }
53
+ /** 为内部的组件添加额外的 fetcher 中间件,例如在发生错误时弹出警告。 */
54
+ export function FetcherMiddleware(props) {
55
+ const $ = _c(6);
56
+ const {
57
+ middleware,
58
+ children
59
+ } = props;
60
+ const fetcher = use(FetcherContext);
61
+ let t0;
62
+ if ($[0] !== fetcher || $[1] !== middleware) {
63
+ t0 = middleware(fetcher);
64
+ $[0] = fetcher;
65
+ $[1] = middleware;
66
+ $[2] = t0;
67
+ } else {
68
+ t0 = $[2];
69
+ }
70
+ let t1;
71
+ if ($[3] !== children || $[4] !== t0) {
72
+ t1 = /*#__PURE__*/_jsx(FetcherContext, {
73
+ value: t0,
74
+ children: children
75
+ });
76
+ $[3] = children;
77
+ $[4] = t0;
78
+ $[5] = t1;
79
+ } else {
80
+ t1 = $[5];
81
+ }
82
+ return t1;
83
+ }
84
+ /**
85
+ * 为内部的组件的 fetcher 添加 Authorization 头。
86
+ * 默认设置下,{@linkcode AuthProvider} 已经提供了这个中间件,不需要另外添加。
87
+ */
88
+ export function FetcherWithAuth(props) {
89
+ const $ = _c(12);
90
+ const {
91
+ children
92
+ } = props;
93
+ const prevFetcher = use(FetcherContext);
94
+ const sessionStore = useSessionStoreInternal("FetcherWithAuth");
95
+ const service = sessionStore.service;
96
+ let t0;
97
+ if ($[0] !== sessionStore) {
98
+ t0 = onChange => {
99
+ sessionStore.addEventListener(onChange);
100
+ return () => sessionStore.addEventListener(onChange);
101
+ };
102
+ $[0] = sessionStore;
103
+ $[1] = t0;
104
+ } else {
105
+ t0 = $[1];
106
+ }
107
+ let t1;
108
+ if ($[2] !== sessionStore.session) {
109
+ t1 = () => sessionStore.session;
110
+ $[2] = sessionStore.session;
111
+ $[3] = t1;
112
+ } else {
113
+ t1 = $[3];
114
+ }
115
+ const session = useSyncExternalStore(t0, t1);
116
+ let t2;
117
+ if ($[4] !== prevFetcher || $[5] !== service || $[6] !== session || $[7] !== sessionStore) {
118
+ const fetcherWithAuth = session ? service.fetcherMiddleware(session.state)(prevFetcher) : prevFetcher;
119
+ t2 = fetcherWithLoginOn401(sessionStore)(fetcherWithAuth);
120
+ $[4] = prevFetcher;
121
+ $[5] = service;
122
+ $[6] = session;
123
+ $[7] = sessionStore;
124
+ $[8] = t2;
125
+ } else {
126
+ t2 = $[8];
127
+ }
128
+ const fetcher = t2;
129
+ let t3;
130
+ if ($[9] !== children || $[10] !== fetcher) {
131
+ t3 = /*#__PURE__*/_jsx(FetcherContext, {
132
+ value: fetcher,
133
+ children: children
134
+ });
135
+ $[9] = children;
136
+ $[10] = fetcher;
137
+ $[11] = t3;
138
+ } else {
139
+ t3 = $[11];
140
+ }
141
+ return t3;
142
+ }
@@ -0,0 +1,90 @@
1
+ import { type JwtPayload } from "jwt-decode";
2
+ import type { AuthService, UserInfo } from "./AuthService";
3
+ import { fetcherWithBearerToken } from "./Fetcher";
4
+ export interface LocalAuthServiceOptions {
5
+ openLoginPage: () => void;
6
+ signinOrigin?: string;
7
+ signinPath?: string;
8
+ }
9
+ /**
10
+ * 用于内网使用的鉴权服务,目前只有风功率预测系统在使用这个鉴权服务。该鉴权服务的登录流程如下:
11
+ *
12
+ * 1. 调用 {@linkcode LocalAuthService.getSession | getSession()} 跳转到登录页面。
13
+ * 这个操作的具体实现由初始化时的设置项 {@linkcode LocalAuthServiceOptions.openLoginPage | openLoginPage} 提供。
14
+ * 用户需要自行实现跳转操作,可以直接修改 {@linkcode window.location},也可以使用 router 提供的导航功能。
15
+ * 2. 用户在登录页面输入账号密码,调用 {@linkcode LocalAuthService.login | login()} 得到 token。
16
+ *
17
+ * 当使用 {@linkcode AuthProvider} 时,第 1 步会由 {@linkcode LoginCheck} 自动调用,
18
+ * 第 2 步可以通过 {@linkcode useLocalLogin} 获取到登录函数。
19
+ */
20
+ export declare class LocalAuthService implements AuthService<string, LocalUserInfo> {
21
+ #private;
22
+ constructor(options: LocalAuthServiceOptions);
23
+ getSession(): Promise<never>;
24
+ validate(token: string): Promise<LocalUserInfo>;
25
+ /**
26
+ * LocalAuthService 的特有函数。
27
+ * 使用 CasdoorAuthService 时,需要在项目中设置一个登录页面。
28
+ * 在用户在登录页面输入账号密码之后,调用此函数获取 access token。
29
+ * 若账号密码错误,或者后端发生错误,则会抛出异常。
30
+ */
31
+ login(username: string, password: string): Promise<string>;
32
+ readonly fetcherMiddleware: typeof fetcherWithBearerToken;
33
+ parseUserInfo(info: LocalUserInfo): UserInfo;
34
+ /**
35
+ * 解析 token 中的内容。
36
+ * Token 可以通过 `useSession().state` 获取。
37
+ */
38
+ decodeToken(token: string): LocalTokenPayload;
39
+ }
40
+ export interface LocalTokenPayload extends JwtPayload {
41
+ /** Token 的过期时间(Unix 时间戳)。*/
42
+ exp: number;
43
+ /** Token 的生效时间(Unix 时间戳)。*/
44
+ nbf: number;
45
+ /** Token 的签发时间(Unix 时间戳)。*/
46
+ iat: number;
47
+ /** 用户信息 */
48
+ User: {
49
+ /** 注册时间。*/
50
+ createTime: string;
51
+ /** 用户的 UUID。*/
52
+ id?: string;
53
+ /** 用户名。*/
54
+ username?: string;
55
+ /** 是否已删除。*/
56
+ deleted?: boolean;
57
+ /** 用户的角色。*/
58
+ role: {
59
+ /** 角色的 UUID。*/
60
+ id: string;
61
+ /** 角色名。*/
62
+ name: string;
63
+ /** 角色的显示名。*/
64
+ display_name: string;
65
+ /** 角色拥有的权限。*/
66
+ functions: string[];
67
+ /** 角色的优先级。*/
68
+ priority: number;
69
+ };
70
+ /** 用户的角色的 UUID。*/
71
+ roleId: string;
72
+ };
73
+ }
74
+ export interface LocalUserInfo {
75
+ /** 用户的 UUID。*/
76
+ id?: string;
77
+ /** 用户名。*/
78
+ username?: string;
79
+ /** 显示名。*/
80
+ displayName?: string;
81
+ /** 创建时间。*/
82
+ createdAt: string;
83
+ /** 更新时间。*/
84
+ updatedAt: string;
85
+ }
86
+ /**
87
+ * 获取 {@linkcode LocalAuthService} 的登录函数。
88
+ * 返回的函数只负责使用账号密码登录并将得到的 token 存入 session store,用户需要自行实现登录完成后的跳转。
89
+ */
90
+ export declare function useLocalLogin(): (username: string, password: string) => Promise<import("./SessionStore").Session>;