@tsingroc/tsingroc-components 5.0.0-alpha.20 → 5.0.0-alpha.22

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 (42) hide show
  1. package/dist/components/AutoResizedECharts.js +1 -2
  2. package/dist/components/Calendar.js +8 -4
  3. package/dist/components/ECharts.d.ts +1 -1
  4. package/dist/components/Header.d.ts +1 -1
  5. package/dist/components/Header.js +3 -1
  6. package/dist/components/LineChartEditor.js +3 -3
  7. package/dist/components/LineChartTable.js +319 -123
  8. package/dist/components/LinkedLineChart.js +3 -3
  9. package/dist/components/QuickDateRangePicker.d.ts +1 -1
  10. package/dist/components/QuickDateRangePicker.js +2 -3
  11. package/dist/components/Sidebar.d.ts +1 -1
  12. package/dist/components/Sidebar.js +4 -5
  13. package/dist/components/TsingrocDatePicker.d.ts +2 -2
  14. package/dist/components/TsingrocDatePicker.js +6 -7
  15. package/dist/components/UserButton.d.ts +1 -1
  16. package/dist/components/UserButton.js +24 -18
  17. package/dist/components/WeatherMap.js +1 -1
  18. package/dist/components/auth/AuthProvider.d.ts +29 -0
  19. package/dist/components/auth/AuthProvider.js +138 -0
  20. package/dist/components/auth/AuthService.d.ts +30 -0
  21. package/dist/components/auth/AuthService.js +1 -0
  22. package/dist/components/auth/CasdoorAuth.d.ts +135 -0
  23. package/dist/components/auth/CasdoorAuth.js +156 -0
  24. package/dist/components/auth/EmbeddedAuth.d.ts +16 -0
  25. package/dist/components/auth/EmbeddedAuth.js +53 -0
  26. package/dist/components/auth/Fetcher.d.ts +30 -0
  27. package/dist/components/auth/Fetcher.js +132 -0
  28. package/dist/components/auth/LocalAuth.d.ts +70 -0
  29. package/dist/components/auth/LocalAuth.js +84 -0
  30. package/dist/components/auth/LoginCheck.d.ts +13 -0
  31. package/dist/components/auth/LoginCheck.js +54 -0
  32. package/dist/components/auth/SessionStore.d.ts +68 -0
  33. package/dist/components/auth/SessionStore.js +193 -0
  34. package/dist/deckgl/TiandituLayer.d.ts +1 -1
  35. package/dist/deckgl/TiandituLayer.js +1 -1
  36. package/dist/deckgl/WeatherData.d.ts +1 -1
  37. package/dist/echarts/series/maxBarSeries.d.ts +1 -1
  38. package/dist/index.d.ts +8 -1
  39. package/dist/index.js +7 -1
  40. package/package.json +34 -50
  41. package/dist/components/Auth.d.ts +0 -314
  42. package/dist/components/Auth.js +0 -301
@@ -0,0 +1,156 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import Sdk from "casdoor-js-sdk";
3
+ import { useEffect, useEffectEvent, useRef, useState, useSyncExternalStore } from "react";
4
+ import { useSessionStoreInternal } from "./AuthProvider";
5
+ import { EmbeddedAuthService } from "./EmbeddedAuth";
6
+ import { fetcherWithBearerToken } from "./Fetcher";
7
+ export class CasdoorAuthService {
8
+ #sdk;
9
+ #signinOrigin;
10
+ #signinPath;
11
+ constructor(options) {
12
+ this.#sdk = new Sdk(options.config);
13
+ this.#signinOrigin = options.signinOrigin || location.origin;
14
+ this.#signinPath = options.signinPath;
15
+ }
16
+ getSession() {
17
+ location.href = this.#sdk.getSigninUrl();
18
+ return Promise.reject(new Error("已跳转到登录页面,该错误用于中止登录流程,可以忽略。"));
19
+ }
20
+ async validate(token) {
21
+ // sdk 的类型声明有误
22
+ const resp = await this.#sdk.getUserInfo(token);
23
+ if ("status" in resp) {
24
+ throw new Error("登陆状态无效!");
25
+ }
26
+ return resp;
27
+ }
28
+
29
+ /**
30
+ * CasdoorAuthService 的特有函数。
31
+ * 使用 CasdoorAuthService 时,需要在项目中设置一个回调页面。
32
+ * 当用户完成登录,跳转到回调页面时,可调用此函数获取 access token。
33
+ * 若用户不经过登录流程进入回调页面,或者后端发生错误,则会抛出异常。
34
+ */
35
+ async callback() {
36
+ // sdk 的类型声明有误
37
+ const resp = await this.#sdk.signin(this.#signinOrigin, this.#signinPath);
38
+ const token = resp.token;
39
+ if (!token) {
40
+ throw new Error("登录 API 返回了异常的结果:" + JSON.stringify(resp));
41
+ }
42
+ return token;
43
+ }
44
+ fetcherMiddleware = fetcherWithBearerToken;
45
+ parseUserInfo(info) {
46
+ return info;
47
+ }
48
+ }
49
+ export function CasdoorCallback(props) {
50
+ const $ = _c(8);
51
+ const {
52
+ onSuccess,
53
+ onError,
54
+ success: t0,
55
+ error: t1,
56
+ children: t2
57
+ } = props;
58
+ const success = t0 === undefined ? "\u767B\u5F55\u6210\u529F" : t0;
59
+ const error = t1 === undefined ? "\u767B\u5F55\u8FC7\u7A0B\u53D1\u751F\u9519\u8BEF" : t1;
60
+ const children = t2 === undefined ? "\u6B63\u5728\u8DF3\u8F6C\u2026\u2026" : t2;
61
+ const sessionStore = useSessionStoreInternal("CasdoorCallback");
62
+ const service = sessionStore.service;
63
+ if (!(service instanceof CasdoorAuthService)) {
64
+ throw new Error("CasdoorCallback \u5FC5\u987B\u642D\u914D CasdoorAuthService \u4F7F\u7528\uFF01");
65
+ }
66
+ const [status, setStatus] = useState("loading");
67
+ const hasOngoingCallback = useRef(false);
68
+ let t3;
69
+ if ($[0] !== onError || $[1] !== onSuccess || $[2] !== service || $[3] !== sessionStore) {
70
+ t3 = () => {
71
+ if (hasOngoingCallback.current) {
72
+ return;
73
+ }
74
+ service.callback().then(token => {
75
+ setStatus("success");
76
+ sessionStore.load(token);
77
+ onSuccess?.();
78
+ }, err => {
79
+ setStatus("error");
80
+ onError?.(err);
81
+ });
82
+ hasOngoingCallback.current = true;
83
+ };
84
+ $[0] = onError;
85
+ $[1] = onSuccess;
86
+ $[2] = service;
87
+ $[3] = sessionStore;
88
+ $[4] = t3;
89
+ } else {
90
+ t3 = $[4];
91
+ }
92
+ const callback = useEffectEvent(t3);
93
+ let t4;
94
+ if ($[5] !== callback) {
95
+ t4 = () => {
96
+ callback();
97
+ };
98
+ $[5] = callback;
99
+ $[6] = t4;
100
+ } else {
101
+ t4 = $[6];
102
+ }
103
+ let t5;
104
+ if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
105
+ t5 = [];
106
+ $[7] = t5;
107
+ } else {
108
+ t5 = $[7];
109
+ }
110
+ useEffect(t4, t5);
111
+ return status === "loading" ? children : status === "error" ? error : success;
112
+ }
113
+ export function CasdoorRoleCheck(props) {
114
+ const $ = _c(4);
115
+ const {
116
+ required,
117
+ forbidden,
118
+ error: t0,
119
+ children
120
+ } = props;
121
+ const error = t0 === undefined ? "\u60A8\u65E0\u6743\u8BBF\u95EE\u6B64\u9875\u9762\uFF01" : t0;
122
+ const sessionStore = useSessionStoreInternal("CasdoorRoleCheck");
123
+ if (!(sessionStore.service instanceof CasdoorAuthService || sessionStore.service instanceof EmbeddedAuthService)) {
124
+ throw new Error("CasdoorRoleCheck \u5FC5\u987B\u642D\u914D CasdoorAuthService \u6216 EmbeddedAuthService \u4F7F\u7528\uFF01");
125
+ }
126
+ let t1;
127
+ if ($[0] !== sessionStore) {
128
+ t1 = onChange => {
129
+ sessionStore.addEventListener(onChange);
130
+ return () => sessionStore.addEventListener(onChange);
131
+ };
132
+ $[0] = sessionStore;
133
+ $[1] = t1;
134
+ } else {
135
+ t1 = $[1];
136
+ }
137
+ let t2;
138
+ if ($[2] !== sessionStore.session) {
139
+ t2 = () => sessionStore.session;
140
+ $[2] = sessionStore.session;
141
+ $[3] = t2;
142
+ } else {
143
+ t2 = $[3];
144
+ }
145
+ const session = useSyncExternalStore(t1, t2);
146
+ if (!session) {
147
+ return error;
148
+ }
149
+ const userInfo = session.rawUserInfo;
150
+ const hasRequiredRole = required.some(role => userInfo.roles.includes(role));
151
+ const hasForbiddenRole = forbidden?.some(role_0 => userInfo.roles.includes(role_0));
152
+ if (!hasRequiredRole || hasForbiddenRole) {
153
+ return error;
154
+ }
155
+ return children;
156
+ }
@@ -0,0 +1,16 @@
1
+ import type { SdkConfig } from "casdoor-js-sdk/lib/esm/sdk";
2
+ import type { AuthService, UserInfo } from "./AuthService";
3
+ import type { CasdoorUserInfo } from "./CasdoorAuth";
4
+ import { fetcherWithBearerToken } from "./Fetcher";
5
+ export interface EmbeddedAuthServiceOptions {
6
+ config: SdkConfig;
7
+ timeout?: number;
8
+ }
9
+ export declare class EmbeddedAuthService implements AuthService<string, CasdoorUserInfo> {
10
+ #private;
11
+ constructor(options: EmbeddedAuthServiceOptions);
12
+ getSession(): Promise<string>;
13
+ validate(token: string): Promise<CasdoorUserInfo>;
14
+ fetcherMiddleware: typeof fetcherWithBearerToken;
15
+ parseUserInfo(info: CasdoorUserInfo): UserInfo;
16
+ }
@@ -0,0 +1,53 @@
1
+ import Sdk from "casdoor-js-sdk";
2
+ import { fetcherWithBearerToken } from "./Fetcher";
3
+ export class EmbeddedAuthService {
4
+ #sdk;
5
+ #timeout;
6
+ constructor(options) {
7
+ this.#sdk = new Sdk(options.config);
8
+ this.#timeout = options.timeout || 5000;
9
+ }
10
+ getSession() {
11
+ if (!(window.parent && window.parent !== window)) {
12
+ return Promise.reject(new Error("无法在非嵌入式环境中使用 EmbeddedAuthService"));
13
+ }
14
+ return new Promise((resolve, reject) => {
15
+ const handleMessage = event => {
16
+ window.removeEventListener("message", handleMessage);
17
+ clearTimeout(timeout);
18
+ // 通常来说接收来自父页面的消息需要验证 origin,
19
+ // 但由于我们这里是在接受 access token,而 access token 没法造假,
20
+ // 因此只要对数据进行小心验证,可以不需要验证 origin
21
+ if (event.data && typeof event.data === "object") {
22
+ const data = event.data;
23
+ if (data.type === "TOKEN_RESPONSE" && typeof data.token === "string") {
24
+ resolve(data.token);
25
+ return;
26
+ }
27
+ }
28
+ reject(new Error("从父页面收到无效数据"));
29
+ };
30
+ const handleTimeout = () => {
31
+ window.removeEventListener("message", handleMessage);
32
+ reject(new Error("等待父页面回复超时"));
33
+ };
34
+ window.addEventListener("message", handleMessage);
35
+ window.parent.postMessage({
36
+ type: "REQUEST_TOKEN"
37
+ }, "*");
38
+ const timeout = setTimeout(handleTimeout, this.#timeout);
39
+ });
40
+ }
41
+ async validate(token) {
42
+ // sdk 的类型声明有误
43
+ const resp = await this.#sdk.getUserInfo(token);
44
+ if ("status" in resp) {
45
+ throw new Error("登陆状态无效!");
46
+ }
47
+ return resp;
48
+ }
49
+ fetcherMiddleware = fetcherWithBearerToken;
50
+ parseUserInfo(info) {
51
+ return info;
52
+ }
53
+ }
@@ -0,0 +1,30 @@
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
+ export declare function fetcherWithBearerToken(token: string): (fetcher: Fetcher) => Fetcher;
20
+ export declare function fetcherWithLoginOn401(sessionStore: SessionStore): (fetcher: Fetcher) => Fetcher;
21
+ export declare function useFetcher(): Fetcher;
22
+ export interface FetcherMiddlewareProps {
23
+ middleware: (fetcher: Fetcher) => Fetcher;
24
+ children: ReactNode;
25
+ }
26
+ export declare function FetcherMiddleware(props: FetcherMiddlewareProps): import("react/jsx-runtime").JSX.Element;
27
+ export interface FetcherWithAuthProps {
28
+ children: ReactNode;
29
+ }
30
+ export declare function FetcherWithAuth(props: FetcherWithAuthProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,132 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import { createContext, use, useSyncExternalStore } from "react";
3
+ import { useSessionStoreInternal } from "./AuthProvider";
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
+ export function fetcherWithBearerToken(token) {
20
+ return fetcher => async (resource, options) => {
21
+ const headers = new Headers(options?.headers);
22
+ if (!headers.has("Authorization")) {
23
+ headers.set("Authorization", `Bearer ${token}`);
24
+ }
25
+ const resp = await fetcher(resource, {
26
+ ...options,
27
+ headers
28
+ });
29
+ return resp;
30
+ };
31
+ }
32
+ export function fetcherWithLoginOn401(sessionStore) {
33
+ return fetcher => async (resource, options) => {
34
+ const loggedIn = !!sessionStore.session;
35
+ const resp = await fetcher(resource, options);
36
+ if (resp.status === 401) {
37
+ await sessionStore.clear();
38
+ void sessionStore.get();
39
+ throw new Error(`对 ${resp.url} 的请求需要鉴权,但用户${loggedIn ? "登录状态已失效" : "未登录"}。`);
40
+ }
41
+ return resp;
42
+ };
43
+ }
44
+ const FetcherContext = /*#__PURE__*/createContext(fetch);
45
+ export function useFetcher() {
46
+ return use(FetcherContext);
47
+ }
48
+ export function FetcherMiddleware(props) {
49
+ const $ = _c(6);
50
+ const {
51
+ middleware,
52
+ children
53
+ } = props;
54
+ const fetcher = use(FetcherContext);
55
+ let t0;
56
+ if ($[0] !== fetcher || $[1] !== middleware) {
57
+ t0 = middleware(fetcher);
58
+ $[0] = fetcher;
59
+ $[1] = middleware;
60
+ $[2] = t0;
61
+ } else {
62
+ t0 = $[2];
63
+ }
64
+ let t1;
65
+ if ($[3] !== children || $[4] !== t0) {
66
+ t1 = /*#__PURE__*/_jsx(FetcherContext, {
67
+ value: t0,
68
+ children: children
69
+ });
70
+ $[3] = children;
71
+ $[4] = t0;
72
+ $[5] = t1;
73
+ } else {
74
+ t1 = $[5];
75
+ }
76
+ return t1;
77
+ }
78
+ export function FetcherWithAuth(props) {
79
+ const $ = _c(12);
80
+ const {
81
+ children
82
+ } = props;
83
+ const prevFetcher = use(FetcherContext);
84
+ const sessionStore = useSessionStoreInternal("FetcherWithAuth");
85
+ const service = sessionStore.service;
86
+ let t0;
87
+ if ($[0] !== sessionStore) {
88
+ t0 = onChange => {
89
+ sessionStore.addEventListener(onChange);
90
+ return () => sessionStore.addEventListener(onChange);
91
+ };
92
+ $[0] = sessionStore;
93
+ $[1] = t0;
94
+ } else {
95
+ t0 = $[1];
96
+ }
97
+ let t1;
98
+ if ($[2] !== sessionStore.session) {
99
+ t1 = () => sessionStore.session;
100
+ $[2] = sessionStore.session;
101
+ $[3] = t1;
102
+ } else {
103
+ t1 = $[3];
104
+ }
105
+ const session = useSyncExternalStore(t0, t1);
106
+ let t2;
107
+ if ($[4] !== prevFetcher || $[5] !== service || $[6] !== session || $[7] !== sessionStore) {
108
+ const fetcherWithAuth = session ? service.fetcherMiddleware(session.state)(prevFetcher) : prevFetcher;
109
+ t2 = fetcherWithLoginOn401(sessionStore)(fetcherWithAuth);
110
+ $[4] = prevFetcher;
111
+ $[5] = service;
112
+ $[6] = session;
113
+ $[7] = sessionStore;
114
+ $[8] = t2;
115
+ } else {
116
+ t2 = $[8];
117
+ }
118
+ const fetcher = t2;
119
+ let t3;
120
+ if ($[9] !== children || $[10] !== fetcher) {
121
+ t3 = /*#__PURE__*/_jsx(FetcherContext, {
122
+ value: fetcher,
123
+ children: children
124
+ });
125
+ $[9] = children;
126
+ $[10] = fetcher;
127
+ $[11] = t3;
128
+ } else {
129
+ t3 = $[11];
130
+ }
131
+ return t3;
132
+ }
@@ -0,0 +1,70 @@
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
+ export declare class LocalAuthService implements AuthService<string, LocalUserInfo> {
10
+ #private;
11
+ constructor(options: LocalAuthServiceOptions);
12
+ getSession(): Promise<never>;
13
+ validate(token: string): Promise<LocalUserInfo>;
14
+ /**
15
+ * LocalAuthService 的特有函数。
16
+ * 使用 CasdoorAuthService 时,需要在项目中设置一个登录页面。
17
+ * 在用户在登录页面输入账号密码之后,调用此函数获取 access token。
18
+ * 若账号密码错误,或者后端发生错误,则会抛出异常。
19
+ */
20
+ login(username: string, password: string): Promise<string>;
21
+ fetcherMiddleware: typeof fetcherWithBearerToken;
22
+ parseUserInfo(info: LocalUserInfo): UserInfo;
23
+ }
24
+ export interface LocalTokenPayload extends JwtPayload {
25
+ /** Token 的过期时间(Unix 时间戳)。*/
26
+ exp: number;
27
+ /** Token 的生效时间(Unix 时间戳)。*/
28
+ nbf: number;
29
+ /** Token 的签发时间(Unix 时间戳)。*/
30
+ iat: number;
31
+ /** 用户信息 */
32
+ User: {
33
+ /** 注册时间。*/
34
+ createTime: string;
35
+ /** 用户的 UUID。*/
36
+ id?: string;
37
+ /** 用户名。*/
38
+ username?: string;
39
+ /** 是否已删除。*/
40
+ deleted?: boolean;
41
+ /** 用户的角色。*/
42
+ role: {
43
+ /** 角色的 UUID。*/
44
+ id: string;
45
+ /** 角色名。*/
46
+ name: string;
47
+ /** 角色的显示名。*/
48
+ display_name: string;
49
+ /** 角色拥有的权限。*/
50
+ functions: string[];
51
+ /** 角色的优先级。*/
52
+ priority: number;
53
+ };
54
+ /** 用户的角色的 UUID。*/
55
+ roleId: string;
56
+ };
57
+ }
58
+ export interface LocalUserInfo {
59
+ /** 用户的 UUID。*/
60
+ id?: string;
61
+ /** 用户名。*/
62
+ username?: string;
63
+ /** 显示名。*/
64
+ displayName?: string;
65
+ /** 创建时间。*/
66
+ createdAt: string;
67
+ /** 更新时间。*/
68
+ updatedAt: string;
69
+ }
70
+ export declare function useLocalLogin(): (username: string, password: string) => Promise<import("./SessionStore").Session>;
@@ -0,0 +1,84 @@
1
+ import { c as _c } from "react/compiler-runtime";
2
+ import { useSessionStoreInternal } from "./AuthProvider";
3
+ import { fetcherWithBearerToken } from "./Fetcher";
4
+ export class LocalAuthService {
5
+ #openLoginPage;
6
+ #signinOrigin;
7
+ #signinPath;
8
+ constructor(options) {
9
+ this.#openLoginPage = options.openLoginPage;
10
+ this.#signinOrigin = options.signinOrigin ?? location.origin;
11
+ this.#signinPath = options.signinPath ?? "/api/signin";
12
+ }
13
+ getSession() {
14
+ this.#openLoginPage();
15
+ return Promise.reject(new Error("已跳转到登录页面,该错误用于中止登录流程,可以忽略。"));
16
+ }
17
+ async validate(token) {
18
+ const resp = await fetch(new URL("/api/userInfo", this.#signinOrigin), {
19
+ headers: {
20
+ Authorization: `Bearer ${token}`
21
+ }
22
+ });
23
+ if (resp.status !== 200) {
24
+ throw new Error("登陆状态无效!");
25
+ }
26
+ const json = await resp.json();
27
+ if (!json.data) {
28
+ throw new Error("登陆状态无效!");
29
+ }
30
+ return json.data;
31
+ }
32
+
33
+ /**
34
+ * LocalAuthService 的特有函数。
35
+ * 使用 CasdoorAuthService 时,需要在项目中设置一个登录页面。
36
+ * 在用户在登录页面输入账号密码之后,调用此函数获取 access token。
37
+ * 若账号密码错误,或者后端发生错误,则会抛出异常。
38
+ */
39
+ async login(username, password) {
40
+ const resp = await fetch(new URL(this.#signinPath, this.#signinOrigin), {
41
+ method: "post",
42
+ headers: {
43
+ "Content-Type": "application/json"
44
+ },
45
+ body: JSON.stringify({
46
+ username,
47
+ password
48
+ })
49
+ });
50
+ if (resp.status !== 200) {
51
+ throw new Error("账号或密码错误!");
52
+ }
53
+ const json = await resp.json();
54
+ const token = json.token;
55
+ if (typeof token !== "string") {
56
+ throw new Error("登录 API 返回了异常的结果:" + JSON.stringify(json));
57
+ }
58
+ return token;
59
+ }
60
+ fetcherMiddleware = fetcherWithBearerToken;
61
+ parseUserInfo(info) {
62
+ return {
63
+ name: info.displayName ?? info.username ?? "未命名用户"
64
+ };
65
+ }
66
+ }
67
+ export function useLocalLogin() {
68
+ const $ = _c(3);
69
+ const sessionStore = useSessionStoreInternal("useLocalLogin");
70
+ const service = sessionStore.service;
71
+ if (!(service instanceof LocalAuthService)) {
72
+ throw new Error("useLocalLogin \u5FC5\u987B\u642D\u914D LocalAuthService \u4F7F\u7528\uFF01");
73
+ }
74
+ let t0;
75
+ if ($[0] !== service || $[1] !== sessionStore) {
76
+ t0 = async (username, password) => sessionStore.load(await service.login(username, password));
77
+ $[0] = service;
78
+ $[1] = sessionStore;
79
+ $[2] = t0;
80
+ } else {
81
+ t0 = $[2];
82
+ }
83
+ return t0;
84
+ }
@@ -0,0 +1,13 @@
1
+ import { type ReactNode } from "react";
2
+ import type { Session } from "./SessionStore";
3
+ export interface LoginCheckProps {
4
+ onValidated?: (session: Session) => void;
5
+ onRevalidated?: (session: Session) => void;
6
+ onFailed?: (err: unknown) => void;
7
+ onInvalidated?: (err: unknown) => void;
8
+ onBeforeGetSession?: () => void;
9
+ loading?: ReactNode;
10
+ failed?: ReactNode;
11
+ children: ReactNode;
12
+ }
13
+ export declare function LoginCheck(props: LoginCheckProps): ReactNode;
@@ -0,0 +1,54 @@
1
+ import { useEffect, useEffectEvent, useState, useSyncExternalStore } from "react";
2
+ import { useSessionStoreInternal } from "./AuthProvider";
3
+ export function LoginCheck(props) {
4
+ const {
5
+ onValidated,
6
+ onRevalidated,
7
+ onFailed,
8
+ onInvalidated,
9
+ onBeforeGetSession,
10
+ loading = "正在登录……",
11
+ failed = "登录失败",
12
+ children
13
+ } = props;
14
+ const sessionStore = useSessionStoreInternal("LoginCheck");
15
+ const session = useSyncExternalStore(onChange => {
16
+ sessionStore.addEventListener(onChange);
17
+ return () => sessionStore.addEventListener(onChange);
18
+ }, () => sessionStore.session);
19
+ const [status, setStatus] = useState(session ? "loaded" : "loading");
20
+ const onSessionStoreEvent = useEffectEvent(event => {
21
+ switch (event.type) {
22
+ case "failed":
23
+ onFailed?.(event.reason);
24
+ break;
25
+ case "validated":
26
+ onValidated?.(event.session);
27
+ break;
28
+ case "revalidated":
29
+ onRevalidated?.(event.session);
30
+ break;
31
+ case "invalidated":
32
+ onInvalidated?.(event.reason);
33
+ break;
34
+ }
35
+ });
36
+ useEffect(() => {
37
+ sessionStore.addEventListener(onSessionStoreEvent);
38
+ return () => sessionStore.removeEventListener(onSessionStoreEvent);
39
+ }, [sessionStore]);
40
+ const startLoading = useEffectEvent(async () => {
41
+ try {
42
+ onBeforeGetSession?.();
43
+ void (await sessionStore.get());
44
+ } finally {
45
+ setStatus("loaded");
46
+ }
47
+ });
48
+ useEffect(() => {
49
+ if (status === "loading") {
50
+ startLoading();
51
+ }
52
+ }, [status]);
53
+ return status === "loading" ? loading : session ? children : failed;
54
+ }
@@ -0,0 +1,68 @@
1
+ import type { AuthService, UserInfo } from "./AuthService";
2
+ export interface SessionStoreOptions {
3
+ /**
4
+ * 是否将状态保存到 localStorage,并在初始化时自动恢复。
5
+ * 注意该功能只支持状态为字符串的 AuthService 实现。
6
+ * @default true
7
+ */
8
+ persistent?: boolean;
9
+ /** 将状态保存到 localStorage 时使用的 key。 @default "access_token" */
10
+ persistentKey?: string;
11
+ /** 自动检查登录状态是否有效的间隔时间(单位毫秒),若为 0 则不再定期检查。 @default 0 */
12
+ revalidateInterval?: number;
13
+ /** 是否在窗口/标签页获得焦点时立即检查登录状态是否有效。 @default false */
14
+ revalidateOnFocus?: boolean;
15
+ /** 是否在网络连接恢复时立即检查登录状态是否有效。 @default false */
16
+ revalidateOnReconnect?: boolean;
17
+ }
18
+ export declare class SessionStore {
19
+ #private;
20
+ get service(): AuthService<unknown, unknown>;
21
+ get session(): Session | null;
22
+ constructor(service: AuthService<string>, options?: Readonly<SessionStoreOptions>);
23
+ constructor(service: AuthService, options?: Readonly<SessionStoreOptions & {
24
+ persistent: false;
25
+ }>);
26
+ /**
27
+ * 获取会话。
28
+ *
29
+ * 如果当前已经有会话,那么直接返回会话。
30
+ * 如果当前没有会话,那么获取一个新的会话(通常是登录),失败时抛出异常。
31
+ * 返回的 Promise 可能永远不会 resolve,也可能立即 resolve。
32
+ */
33
+ get(): Promise<Session>;
34
+ /** 加载并验证一个外部的会话状态。有效时更新并返回 Session,无效时抛出异常。 */
35
+ load(state: unknown): Promise<Session>;
36
+ /** 在线验证当前保存的会话是否有效。有效时返回 Session,无效或无会话时清除状态并抛出异常。 */
37
+ validate(): Promise<Session>;
38
+ /** 在线验证当前保存的会话是否有效。无效时清除状态,有效或无会话时不做任何操作。返回会话是否有效。 */
39
+ revalidate(): Promise<boolean>;
40
+ /** 立即结束会话并清除状态。如果当前无会话则不做任何操作。 */
41
+ clear(): Promise<void>;
42
+ addEventListener(listener: SessionStoreEventListener): void;
43
+ removeEventListener(listener: SessionStoreEventListener): void;
44
+ }
45
+ /**
46
+ * SessionStore 发出的事件类型。
47
+ *
48
+ * - validated:会话首次验证成功。
49
+ * - revalidated:会话重新验证成功。
50
+ * - failed: 会话创建或首次验证失败。
51
+ * - invalidated:会话失效。
52
+ */
53
+ export type SessionStoreEvent = {
54
+ type: "validated" | "revalidated";
55
+ session: Session;
56
+ } | {
57
+ type: "failed" | "invalidated";
58
+ reason: unknown;
59
+ };
60
+ export type SessionStoreEventListener = (event: SessionStoreEvent) => void;
61
+ export interface Session {
62
+ /** 特定于实现的会话状态。 */
63
+ state: unknown;
64
+ /** 特定于实现的用户信息。 */
65
+ rawUserInfo: unknown;
66
+ /** 用户的个人信息。*/
67
+ userInfo: UserInfo;
68
+ }