@jcoder-stack/abp-react 0.1.0
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/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/application-configuration-DhOZRqtz.d.ts +170 -0
- package/dist/auth.d.ts +56 -0
- package/dist/auth.js +58 -0
- package/dist/chunk-HWT6PBBS.js +155 -0
- package/dist/chunk-OK6PUP2E.js +21 -0
- package/dist/chunk-QQFH53SC.js +700 -0
- package/dist/chunk-UDMHSDXZ.js +161 -0
- package/dist/chunk-XXJLSSG3.js +116 -0
- package/dist/core.d.ts +65 -0
- package/dist/core.js +24 -0
- package/dist/i18n.d.ts +18 -0
- package/dist/i18n.js +12 -0
- package/dist/is-granted-C0-1wvoW.d.ts +10 -0
- package/dist/logger-BSnS65IC.d.ts +60 -0
- package/dist/logger.d.ts +26 -0
- package/dist/logger.js +26 -0
- package/dist/oidc-7fu5kKVF.d.ts +202 -0
- package/dist/permissions.d.ts +18 -0
- package/dist/permissions.js +8 -0
- package/dist/proxy.d.ts +201 -0
- package/dist/proxy.js +535 -0
- package/dist/react.d.ts +125 -0
- package/dist/react.js +236 -0
- package/dist/router.d.ts +39 -0
- package/dist/router.js +41 -0
- package/dist/translator-B3hyoZmK.d.ts +40 -0
- package/dist/types-Bj0MpXtI.d.ts +97 -0
- package/package.json +116 -0
package/dist/react.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTranslator
|
|
3
|
+
} from "./chunk-XXJLSSG3.js";
|
|
4
|
+
import {
|
|
5
|
+
isAbpTrue
|
|
6
|
+
} from "./chunk-HWT6PBBS.js";
|
|
7
|
+
import {
|
|
8
|
+
createPermissionChecker,
|
|
9
|
+
isGranted
|
|
10
|
+
} from "./chunk-OK6PUP2E.js";
|
|
11
|
+
|
|
12
|
+
// src/react/app-config.tsx
|
|
13
|
+
import { createContext, useContext, useMemo, useRef } from "react";
|
|
14
|
+
import { jsx } from "react/jsx-runtime";
|
|
15
|
+
var AppConfigContext = createContext(null);
|
|
16
|
+
function AppConfigProvider(props) {
|
|
17
|
+
const { config, messages, fallbackCulture, children } = props;
|
|
18
|
+
const callbacksRef = useRef(props);
|
|
19
|
+
callbacksRef.current = props;
|
|
20
|
+
const value = useMemo(() => {
|
|
21
|
+
const makeTranslator = callbacksRef.current.createTranslator ?? createTranslator;
|
|
22
|
+
return {
|
|
23
|
+
config,
|
|
24
|
+
translator: makeTranslator({
|
|
25
|
+
culture: config.localization.currentCulture.name,
|
|
26
|
+
backend: config.localization.values,
|
|
27
|
+
frontend: messages,
|
|
28
|
+
fallbackCulture,
|
|
29
|
+
defaultResourceName: config.localization.defaultResourceName ?? void 0,
|
|
30
|
+
onMissing: (key) => callbacksRef.current.onMissingKey?.(key)
|
|
31
|
+
})
|
|
32
|
+
};
|
|
33
|
+
}, [config, messages, fallbackCulture]);
|
|
34
|
+
return /* @__PURE__ */ jsx(AppConfigContext.Provider, { value, children });
|
|
35
|
+
}
|
|
36
|
+
function useAppConfigContext() {
|
|
37
|
+
const ctx = useContext(AppConfigContext);
|
|
38
|
+
if (ctx === null) throw new Error("useAppConfig* hooks must be used within <AppConfigProvider>");
|
|
39
|
+
return ctx;
|
|
40
|
+
}
|
|
41
|
+
function useAppConfig() {
|
|
42
|
+
return useAppConfigContext().config;
|
|
43
|
+
}
|
|
44
|
+
function useLocalization() {
|
|
45
|
+
const { translator } = useAppConfigContext();
|
|
46
|
+
return useMemo(() => {
|
|
47
|
+
const L = ((key, ...args) => translator.t(key, ...args));
|
|
48
|
+
L.plural = (key, count, ...args) => translator.plural(key, count, ...args);
|
|
49
|
+
L.has = (key) => translator.has(key);
|
|
50
|
+
return L;
|
|
51
|
+
}, [translator]);
|
|
52
|
+
}
|
|
53
|
+
function useCulture() {
|
|
54
|
+
return useAppConfigContext().config.localization.currentCulture.name;
|
|
55
|
+
}
|
|
56
|
+
function useSetting(name) {
|
|
57
|
+
return useAppConfigContext().config.setting.values[name];
|
|
58
|
+
}
|
|
59
|
+
function useSettingBoolean(name) {
|
|
60
|
+
return isAbpTrue(useSetting(name));
|
|
61
|
+
}
|
|
62
|
+
function useFeature(name) {
|
|
63
|
+
return useAppConfigContext().config.features.values[name];
|
|
64
|
+
}
|
|
65
|
+
function useFeatureEnabled(name) {
|
|
66
|
+
return isAbpTrue(useFeature(name));
|
|
67
|
+
}
|
|
68
|
+
function FeatureGuard(props) {
|
|
69
|
+
const { feature, fallback = null, children } = props;
|
|
70
|
+
return useFeatureEnabled(feature) ? children : fallback;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/react/menu.ts
|
|
74
|
+
import { useMemo as useMemo3 } from "react";
|
|
75
|
+
|
|
76
|
+
// src/react/session.tsx
|
|
77
|
+
import {
|
|
78
|
+
createContext as createContext2,
|
|
79
|
+
useCallback,
|
|
80
|
+
useContext as useContext2,
|
|
81
|
+
useMemo as useMemo2,
|
|
82
|
+
useRef as useRef2,
|
|
83
|
+
useState
|
|
84
|
+
} from "react";
|
|
85
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
86
|
+
var SessionContext = createContext2(null);
|
|
87
|
+
function SessionProvider(props) {
|
|
88
|
+
const { identity: hydrated, fetchIdentity, children } = props;
|
|
89
|
+
const [identity, setIdentity] = useState(hydrated);
|
|
90
|
+
const [prevHydrated, setPrevHydrated] = useState(hydrated);
|
|
91
|
+
const generation = useRef2(0);
|
|
92
|
+
if (hydrated !== prevHydrated) {
|
|
93
|
+
setPrevHydrated(hydrated);
|
|
94
|
+
setIdentity(hydrated);
|
|
95
|
+
generation.current += 1;
|
|
96
|
+
}
|
|
97
|
+
const reload = useCallback(async () => {
|
|
98
|
+
if (fetchIdentity === void 0) return;
|
|
99
|
+
const started = ++generation.current;
|
|
100
|
+
const next = await fetchIdentity();
|
|
101
|
+
if (started === generation.current) setIdentity(next);
|
|
102
|
+
}, [fetchIdentity]);
|
|
103
|
+
const value = useMemo2(
|
|
104
|
+
() => ({
|
|
105
|
+
identity,
|
|
106
|
+
status: identity.isAuthenticated ? "authenticated" : "anonymous",
|
|
107
|
+
can: createPermissionChecker(identity.grantedPolicies),
|
|
108
|
+
reload
|
|
109
|
+
}),
|
|
110
|
+
[identity, reload]
|
|
111
|
+
);
|
|
112
|
+
return /* @__PURE__ */ jsx2(SessionContext.Provider, { value, children });
|
|
113
|
+
}
|
|
114
|
+
function useSession() {
|
|
115
|
+
const ctx = useContext2(SessionContext);
|
|
116
|
+
if (ctx === null) throw new Error("useSession must be used within <SessionProvider>");
|
|
117
|
+
return ctx;
|
|
118
|
+
}
|
|
119
|
+
function useCurrentUser() {
|
|
120
|
+
return useSession().identity.user;
|
|
121
|
+
}
|
|
122
|
+
function useGrantedPolicies() {
|
|
123
|
+
return useSession().identity.grantedPolicies;
|
|
124
|
+
}
|
|
125
|
+
function usePermissionChecker() {
|
|
126
|
+
return useSession().can;
|
|
127
|
+
}
|
|
128
|
+
function usePermission(policy) {
|
|
129
|
+
return useSession().can(policy);
|
|
130
|
+
}
|
|
131
|
+
function PermissionGuard(props) {
|
|
132
|
+
const { policy, all, any, requireAuth, fallback = null, children } = props;
|
|
133
|
+
const { identity, can } = useSession();
|
|
134
|
+
let granted = true;
|
|
135
|
+
if (policy !== void 0) granted = can(policy);
|
|
136
|
+
else if (all !== void 0) granted = can.all(all);
|
|
137
|
+
else if (any !== void 0) granted = can.any(any);
|
|
138
|
+
if (granted && requireAuth === true) granted = identity.isAuthenticated;
|
|
139
|
+
return granted ? children : fallback;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/react/menu.ts
|
|
143
|
+
function itemAllowed(item, ctx) {
|
|
144
|
+
if (item.requireAuth === true && ctx.isAuthenticated !== true) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
if (item.requiredPolicy !== void 0 && !isGranted(ctx.grantedPolicies, item.requiredPolicy)) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
if (item.requiredFeature !== void 0 && !isAbpTrue(ctx.features?.[item.requiredFeature])) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
function buildMenu(items, ctx) {
|
|
156
|
+
const result = [];
|
|
157
|
+
for (const item of items) {
|
|
158
|
+
if (!itemAllowed(item, ctx)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const children = item.children ? buildMenu(item.children, ctx) : void 0;
|
|
162
|
+
if (item.children !== void 0 && item.to === void 0 && (children === void 0 || children.length === 0)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (children === void 0) {
|
|
166
|
+
result.push(item);
|
|
167
|
+
} else if (children.length === 0) {
|
|
168
|
+
const { children: emptied, ...withoutChildren } = item;
|
|
169
|
+
result.push(withoutChildren);
|
|
170
|
+
} else {
|
|
171
|
+
result.push({ ...item, children });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return result.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
175
|
+
}
|
|
176
|
+
function mergeMenu(...lists) {
|
|
177
|
+
return lists.flat();
|
|
178
|
+
}
|
|
179
|
+
function findBreadcrumbs(items, pathname) {
|
|
180
|
+
let best = [];
|
|
181
|
+
let bestLength = -1;
|
|
182
|
+
const walk = (nodes, trail) => {
|
|
183
|
+
for (const node of nodes) {
|
|
184
|
+
const chain = [...trail, node];
|
|
185
|
+
if (node.to === pathname) return chain;
|
|
186
|
+
if (node.to !== void 0 && node.to.length > bestLength && (node.to === "/" ? pathname === "/" : pathname === node.to || pathname.startsWith(`${node.to}/`))) {
|
|
187
|
+
best = chain;
|
|
188
|
+
bestLength = node.to.length;
|
|
189
|
+
}
|
|
190
|
+
if (node.children) {
|
|
191
|
+
const found = walk(node.children, chain);
|
|
192
|
+
if (found) return found;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
};
|
|
197
|
+
return walk(items, []) ?? best;
|
|
198
|
+
}
|
|
199
|
+
function useBreadcrumbs(items, pathname) {
|
|
200
|
+
return useMemo3(() => findBreadcrumbs(items, pathname), [items, pathname]);
|
|
201
|
+
}
|
|
202
|
+
function useMenu(items) {
|
|
203
|
+
const { identity } = useSession();
|
|
204
|
+
const config = useAppConfig();
|
|
205
|
+
return useMemo3(
|
|
206
|
+
() => buildMenu(items, {
|
|
207
|
+
grantedPolicies: identity.grantedPolicies,
|
|
208
|
+
features: config.features.values,
|
|
209
|
+
isAuthenticated: identity.isAuthenticated
|
|
210
|
+
}),
|
|
211
|
+
[items, identity, config]
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
export {
|
|
215
|
+
AppConfigProvider,
|
|
216
|
+
FeatureGuard,
|
|
217
|
+
PermissionGuard,
|
|
218
|
+
SessionProvider,
|
|
219
|
+
buildMenu,
|
|
220
|
+
findBreadcrumbs,
|
|
221
|
+
mergeMenu,
|
|
222
|
+
useAppConfig,
|
|
223
|
+
useBreadcrumbs,
|
|
224
|
+
useCulture,
|
|
225
|
+
useCurrentUser,
|
|
226
|
+
useFeature,
|
|
227
|
+
useFeatureEnabled,
|
|
228
|
+
useGrantedPolicies,
|
|
229
|
+
useLocalization,
|
|
230
|
+
useMenu,
|
|
231
|
+
usePermission,
|
|
232
|
+
usePermissionChecker,
|
|
233
|
+
useSession,
|
|
234
|
+
useSetting,
|
|
235
|
+
useSettingBoolean
|
|
236
|
+
};
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { I as Identity } from './types-Bj0MpXtI.js';
|
|
2
|
+
import { P as PermissionStrategy } from './is-granted-C0-1wvoW.js';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/** guard 期望的路由上下文:祖先路由(__root beforeLoad)已放入 identity。 */
|
|
6
|
+
interface GuardContext {
|
|
7
|
+
identity: Identity;
|
|
8
|
+
}
|
|
9
|
+
interface RequirePermissionOptions {
|
|
10
|
+
/** 缺权限时跳转目标(默认 "/forbidden")。 */
|
|
11
|
+
redirectTo?: string;
|
|
12
|
+
/** 多策略组合方式(默认 "all")。 */
|
|
13
|
+
strategy?: PermissionStrategy;
|
|
14
|
+
/** 给出时,未认证访客先被送去登录(带 returnUrl)而非 403;不给则匿名一律 403。 */
|
|
15
|
+
loginPath?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* beforeLoad 守卫工厂:缺权限时 redirect(默认 /forbidden)。纯 UX,安全判定在 ABP。
|
|
19
|
+
* 匿名访客的 grantedPolicies 恒为空,默认因此得到 403;推荐把整个受保护子树挂在
|
|
20
|
+
* `requireAuth()` 父路由下,或给本守卫传 `loginPath` 让匿名先去登录。
|
|
21
|
+
* context 缺 identity 时抛 Error。
|
|
22
|
+
*/
|
|
23
|
+
declare function requirePermission(policy: string | string[], opts?: RequirePermissionOptions): ({ context, location }: {
|
|
24
|
+
context: GuardContext;
|
|
25
|
+
location?: {
|
|
26
|
+
href: string;
|
|
27
|
+
};
|
|
28
|
+
}) => void;
|
|
29
|
+
/** beforeLoad 守卫工厂:未认证时 redirect 到 OIDC 登录(带 returnUrl)。loginPath 自带 query 时用 & 续接。context 缺 identity 时抛 Error。 */
|
|
30
|
+
declare function requireAuth(opts?: {
|
|
31
|
+
loginPath?: string;
|
|
32
|
+
}): ({ context, location }: {
|
|
33
|
+
context: GuardContext;
|
|
34
|
+
location: {
|
|
35
|
+
href: string;
|
|
36
|
+
};
|
|
37
|
+
}) => void;
|
|
38
|
+
|
|
39
|
+
export { type GuardContext, type RequirePermissionOptions, requireAuth, requirePermission };
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isGranted
|
|
3
|
+
} from "./chunk-OK6PUP2E.js";
|
|
4
|
+
|
|
5
|
+
// src/router/guards.ts
|
|
6
|
+
import { redirect } from "@tanstack/react-router";
|
|
7
|
+
function requireIdentity(context, guardName) {
|
|
8
|
+
const identity = context.identity;
|
|
9
|
+
if (identity === void 0) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
`${guardName}: context.identity missing \u2014 inject identity in an ancestor route's beforeLoad (see __root)`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
return identity;
|
|
15
|
+
}
|
|
16
|
+
function requirePermission(policy, opts = {}) {
|
|
17
|
+
return ({ context, location }) => {
|
|
18
|
+
const identity = requireIdentity(context, "requirePermission");
|
|
19
|
+
if (isGranted(identity.grantedPolicies, policy, { strategy: opts.strategy })) return;
|
|
20
|
+
if (opts.loginPath !== void 0 && !identity.isAuthenticated) {
|
|
21
|
+
throw redirect({ href: withReturnUrl(opts.loginPath, location?.href ?? "/") });
|
|
22
|
+
}
|
|
23
|
+
throw redirect({ to: opts.redirectTo ?? "/forbidden" });
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function withReturnUrl(loginPath, href) {
|
|
27
|
+
const separator = loginPath.includes("?") ? "&" : "?";
|
|
28
|
+
return `${loginPath}${separator}returnUrl=${encodeURIComponent(href)}`;
|
|
29
|
+
}
|
|
30
|
+
function requireAuth(opts = {}) {
|
|
31
|
+
return ({ context, location }) => {
|
|
32
|
+
const identity = requireIdentity(context, "requireAuth");
|
|
33
|
+
if (!identity.isAuthenticated) {
|
|
34
|
+
throw redirect({ href: withReturnUrl(opts.loginPath ?? "/api/auth/login", location.href) });
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
requireAuth,
|
|
40
|
+
requirePermission
|
|
41
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Plural form mapping: category name to localized string. */
|
|
2
|
+
type PluralForms = Record<string, string>;
|
|
3
|
+
/** Picks the CLDR plural category via Intl.PluralRules for locale and count; invalid locale tags fall back to 'other', then ''. */
|
|
4
|
+
declare function selectPluralForm(count: number, forms: PluralForms, locale: string): string;
|
|
5
|
+
|
|
6
|
+
/** Backend ABP resources: resource name to key-value map for the current culture. */
|
|
7
|
+
type BackendResources = Record<string, Record<string, string>>;
|
|
8
|
+
/** Frontend i18n catalog: culture to resource to key to string or plural-forms map. */
|
|
9
|
+
type FrontendCatalog = Record<string, Record<string, Record<string, unknown>>>;
|
|
10
|
+
/** Options for translator creation: culture, backend/frontend resources, fallback culture, the resource a key without an explicit `Resource::` prefix belongs to, missing-key callback, and optional interpolation/plural overrides (e.g. to swap in an ICU MessageFormat engine). */
|
|
11
|
+
interface TranslatorOptions {
|
|
12
|
+
culture: string;
|
|
13
|
+
backend?: BackendResources;
|
|
14
|
+
frontend?: FrontendCatalog;
|
|
15
|
+
fallbackCulture?: string;
|
|
16
|
+
/** ABP's `localization.defaultResourceName`; keys written as `::Key` resolve against it. Callers pass it in so this module stays free of any dependency on the configuration package. */
|
|
17
|
+
defaultResourceName?: string;
|
|
18
|
+
onMissing?: (key: string) => void;
|
|
19
|
+
interpolate?: (template: string, args: unknown[]) => string;
|
|
20
|
+
selectPluralForm?: (count: number, forms: PluralForms, culture: string) => string;
|
|
21
|
+
}
|
|
22
|
+
/** Translator interface: t (string interpolation), plural (plural form selection), and has (resolvability check). */
|
|
23
|
+
interface Translator {
|
|
24
|
+
/** Translates `key`, falling back to a plural entry's `other` form; returns the key itself when unresolved. */
|
|
25
|
+
t(key: string, ...args: unknown[]): string;
|
|
26
|
+
/** Picks the plural form for `count`; a single plain-object arg interpolates by name with `count` merged in, otherwise args are positional with the count at `{0}`. */
|
|
27
|
+
plural(key: string, count: number, ...args: unknown[]): string;
|
|
28
|
+
/** True exactly when `t(key)` would return a translation rather than the key.
|
|
29
|
+
* A plural entry counts only if it carries an `other` form. */
|
|
30
|
+
has(key: string): boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Creates a translator. Resolution order: backend (current culture, overrides) → frontend
|
|
33
|
+
* (current culture) → frontend (culture's primary subtag) → frontend (fallback culture).
|
|
34
|
+
* An unresolved key calls `onMissing` and comes back as-is.
|
|
35
|
+
*
|
|
36
|
+
* `::Key` resolves against `defaultResourceName`. A key with no `::` at all stays on the empty
|
|
37
|
+
* resource, which is where frontend catalogs live. */
|
|
38
|
+
declare function createTranslator(opts: TranslatorOptions): Translator;
|
|
39
|
+
|
|
40
|
+
export { type BackendResources as B, type FrontendCatalog as F, type PluralForms as P, type Translator as T, type TranslatorOptions as a, createTranslator as c, selectPluralForm as s };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare const authTokensSchema: z.ZodObject<{
|
|
4
|
+
accessToken: z.ZodString;
|
|
5
|
+
refreshToken: z.ZodOptional<z.ZodString>;
|
|
6
|
+
idToken: z.ZodOptional<z.ZodString>;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
/** 认证会话:token 集 + 绝对过期(ms)+ 租户/文化上下文。绝密,server-only。 */
|
|
9
|
+
declare const authSessionSchema: z.ZodObject<{
|
|
10
|
+
tokens: z.ZodObject<{
|
|
11
|
+
accessToken: z.ZodString;
|
|
12
|
+
refreshToken: z.ZodOptional<z.ZodString>;
|
|
13
|
+
idToken: z.ZodOptional<z.ZodString>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
expiresAt: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
tenant: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
17
|
+
culture: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
type AuthSession = z.infer<typeof authSessionSchema>;
|
|
20
|
+
/** 身份视图:可注水到客户端;结构上不含 token。授权判定仍在后端,这里只是能力转述。 */
|
|
21
|
+
interface Identity {
|
|
22
|
+
isAuthenticated: boolean;
|
|
23
|
+
user: {
|
|
24
|
+
id: string;
|
|
25
|
+
userName: string;
|
|
26
|
+
email?: string;
|
|
27
|
+
roles: string[];
|
|
28
|
+
} | null;
|
|
29
|
+
grantedPolicies: Record<string, boolean>;
|
|
30
|
+
tenant: {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string | null;
|
|
33
|
+
} | null;
|
|
34
|
+
}
|
|
35
|
+
/** 策略统一产出:SessionManager.establish 的输入。expiresAt 缺省表示 IdP 未返回 expires_in。 */
|
|
36
|
+
interface TokenResult {
|
|
37
|
+
tokens: {
|
|
38
|
+
accessToken: string;
|
|
39
|
+
refreshToken?: string;
|
|
40
|
+
idToken?: string;
|
|
41
|
+
};
|
|
42
|
+
expiresAt?: number;
|
|
43
|
+
}
|
|
44
|
+
interface BeginInput {
|
|
45
|
+
returnUrl: string;
|
|
46
|
+
tenant?: string | null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 重定向式握手暂存态:适配层密封进短命 cookie,callback 时开出交还策略。
|
|
50
|
+
* `issuedAt`(ms)让策略在服务端判寿命。只靠 cookie 的 maxAge 是浏览器侧约束,密文本身不会因此失效。
|
|
51
|
+
*/
|
|
52
|
+
declare const handshakeSchema: z.ZodObject<{
|
|
53
|
+
state: z.ZodString;
|
|
54
|
+
nonce: z.ZodString;
|
|
55
|
+
codeVerifier: z.ZodString;
|
|
56
|
+
returnUrl: z.ZodString;
|
|
57
|
+
issuedAt: z.ZodNumber;
|
|
58
|
+
}, z.core.$strip>;
|
|
59
|
+
type Handshake = z.infer<typeof handshakeSchema>;
|
|
60
|
+
type CompleteInput = {
|
|
61
|
+
kind: "callback";
|
|
62
|
+
params: URLSearchParams;
|
|
63
|
+
handshake: Handshake;
|
|
64
|
+
} | {
|
|
65
|
+
kind: "credentials";
|
|
66
|
+
userName: string;
|
|
67
|
+
password: string;
|
|
68
|
+
tenant?: string | null;
|
|
69
|
+
};
|
|
70
|
+
/** 一个登录策略:证明你是谁 → 产出 TokenResult。协议细节内化在实现里。 */
|
|
71
|
+
interface AuthStrategy {
|
|
72
|
+
readonly name: string;
|
|
73
|
+
begin?(input: BeginInput): Promise<{
|
|
74
|
+
redirectUrl: string;
|
|
75
|
+
handshake: Handshake;
|
|
76
|
+
}>;
|
|
77
|
+
complete(input: CompleteInput): Promise<TokenResult>;
|
|
78
|
+
}
|
|
79
|
+
/** 存储 seam:默认实现是加密分块 cookie(自包含);有状态 store 只需换实现。save/clear 需要请求 Cookie 头才能清掉当前存在的所有旧分块(save 不传则只多清一个尾块)。 */
|
|
80
|
+
interface SessionStore {
|
|
81
|
+
load(cookieHeader: string | null): Promise<AuthSession | null>;
|
|
82
|
+
save(session: AuthSession, cookieHeader?: string | null): Promise<string[]>;
|
|
83
|
+
clear(cookieHeader: string | null): Promise<string[]>;
|
|
84
|
+
}
|
|
85
|
+
/** 身份解析的请求上下文;`cookieHeader` 携带匿名访客的租户/文化选择,无请求上下文时传 null。 */
|
|
86
|
+
interface IdentityContext {
|
|
87
|
+
cookieHeader: string | null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 后端无关的身份解析 seam;ABP 实现在 proxy 域经代理拉 application-configuration。
|
|
91
|
+
* 权限与本地化随租户变化,故解析必须能看到请求上下文,只认 session 的实现会把匿名访客
|
|
92
|
+
* 一律按 host 租户解析。
|
|
93
|
+
*/
|
|
94
|
+
type IdentityResolver = (session: AuthSession | null, ctx: IdentityContext) => Promise<Identity>;
|
|
95
|
+
type FetchFn = typeof fetch;
|
|
96
|
+
|
|
97
|
+
export { type AuthSession as A, type BeginInput as B, type CompleteInput as C, type FetchFn as F, type Handshake as H, type Identity as I, type SessionStore as S, type TokenResult as T, type IdentityResolver as a, type AuthStrategy as b, type IdentityContext as c, authSessionSchema as d, authTokensSchema as e, handshakeSchema as h };
|
package/package.json
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jcoder-stack/abp-react",
|
|
3
|
+
"description": "纯 React ABP 前端框架运行时:日志、ABP 类型与 zod 解析、fetch client、认证会话、ABP 代理网关、权限、i18n、React Provider/hooks、TanStack Router 守卫;按子路径分域导出",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/jcoder-stack/abp-react-start.git",
|
|
10
|
+
"directory": "packages/abp-react"
|
|
11
|
+
},
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
"./core": {
|
|
18
|
+
"types": "./dist/core.d.ts",
|
|
19
|
+
"default": "./dist/core.js"
|
|
20
|
+
},
|
|
21
|
+
"./permissions": {
|
|
22
|
+
"types": "./dist/permissions.d.ts",
|
|
23
|
+
"default": "./dist/permissions.js"
|
|
24
|
+
},
|
|
25
|
+
"./i18n": {
|
|
26
|
+
"types": "./dist/i18n.d.ts",
|
|
27
|
+
"default": "./dist/i18n.js"
|
|
28
|
+
},
|
|
29
|
+
"./logger": {
|
|
30
|
+
"types": "./dist/logger.d.ts",
|
|
31
|
+
"default": "./dist/logger.js"
|
|
32
|
+
},
|
|
33
|
+
"./auth": {
|
|
34
|
+
"types": "./dist/auth.d.ts",
|
|
35
|
+
"default": "./dist/auth.js"
|
|
36
|
+
},
|
|
37
|
+
"./proxy": {
|
|
38
|
+
"types": "./dist/proxy.d.ts",
|
|
39
|
+
"default": "./dist/proxy.js"
|
|
40
|
+
},
|
|
41
|
+
"./react": {
|
|
42
|
+
"types": "./dist/react.d.ts",
|
|
43
|
+
"default": "./dist/react.js"
|
|
44
|
+
},
|
|
45
|
+
"./router": {
|
|
46
|
+
"types": "./dist/router.d.ts",
|
|
47
|
+
"default": "./dist/router.js"
|
|
48
|
+
},
|
|
49
|
+
"./package.json": "./package.json"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@tanstack/react-router": "^1.170.18",
|
|
53
|
+
"react": "^18 || ^19",
|
|
54
|
+
"zod": "^4.0.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"@tanstack/react-router": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"react": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@tanstack/react-router": "^1.0.0",
|
|
66
|
+
"react": "^19.0.0",
|
|
67
|
+
"zod": "^4.0.0"
|
|
68
|
+
},
|
|
69
|
+
"files": [
|
|
70
|
+
"dist"
|
|
71
|
+
],
|
|
72
|
+
"publishConfig": {
|
|
73
|
+
"access": "public",
|
|
74
|
+
"exports": {
|
|
75
|
+
"./core": {
|
|
76
|
+
"types": "./dist/core.d.ts",
|
|
77
|
+
"default": "./dist/core.js"
|
|
78
|
+
},
|
|
79
|
+
"./permissions": {
|
|
80
|
+
"types": "./dist/permissions.d.ts",
|
|
81
|
+
"default": "./dist/permissions.js"
|
|
82
|
+
},
|
|
83
|
+
"./i18n": {
|
|
84
|
+
"types": "./dist/i18n.d.ts",
|
|
85
|
+
"default": "./dist/i18n.js"
|
|
86
|
+
},
|
|
87
|
+
"./logger": {
|
|
88
|
+
"types": "./dist/logger.d.ts",
|
|
89
|
+
"default": "./dist/logger.js"
|
|
90
|
+
},
|
|
91
|
+
"./auth": {
|
|
92
|
+
"types": "./dist/auth.d.ts",
|
|
93
|
+
"default": "./dist/auth.js"
|
|
94
|
+
},
|
|
95
|
+
"./proxy": {
|
|
96
|
+
"types": "./dist/proxy.d.ts",
|
|
97
|
+
"default": "./dist/proxy.js"
|
|
98
|
+
},
|
|
99
|
+
"./react": {
|
|
100
|
+
"types": "./dist/react.d.ts",
|
|
101
|
+
"default": "./dist/react.js"
|
|
102
|
+
},
|
|
103
|
+
"./router": {
|
|
104
|
+
"types": "./dist/router.d.ts",
|
|
105
|
+
"default": "./dist/router.js"
|
|
106
|
+
},
|
|
107
|
+
"./package.json": "./package.json"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"scripts": {
|
|
111
|
+
"build": "tsup",
|
|
112
|
+
"prepublishOnly": "npm exec --no -- tsup",
|
|
113
|
+
"prepack": "node ../../scripts/apply-publish-config.mjs",
|
|
114
|
+
"postpack": "node ../../scripts/restore-publish-config.mjs"
|
|
115
|
+
}
|
|
116
|
+
}
|