openxiangda 1.0.84 → 1.0.85
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 +2 -0
- package/lib/cli.js +214 -0
- package/openxiangda-skills/SKILL.md +4 -1
- package/openxiangda-skills/references/architecture-design.md +38 -4
- package/openxiangda-skills/references/connector-resources.md +3 -0
- package/openxiangda-skills/references/pages/page-sdk.md +39 -0
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +81 -0
- package/package.json +1 -1
- package/packages/sdk/dist/openxiangdaProvider-CaXMpsnK.d.mts +328 -0
- package/packages/sdk/dist/openxiangdaProvider-CaXMpsnK.d.ts +328 -0
- package/packages/sdk/dist/runtime/index.cjs +4250 -3641
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.d.mts +1 -1
- package/packages/sdk/dist/runtime/index.d.ts +1 -1
- package/packages/sdk/dist/runtime/index.mjs +3845 -3219
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/react.cjs +645 -38
- package/packages/sdk/dist/runtime/react.cjs.map +1 -1
- package/packages/sdk/dist/runtime/react.d.mts +2 -162
- package/packages/sdk/dist/runtime/react.d.ts +2 -162
- package/packages/sdk/dist/runtime/react.mjs +667 -39
- package/packages/sdk/dist/runtime/react.mjs.map +1 -1
- package/templates/openxiangda-react-spa/src/app/router.tsx +2 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// packages/sdk/src/runtime/react/openxiangdaProvider.tsx
|
|
2
2
|
import {
|
|
3
3
|
createContext,
|
|
4
|
-
useCallback,
|
|
4
|
+
useCallback as useCallback2,
|
|
5
5
|
useContext,
|
|
6
|
-
useEffect,
|
|
7
|
-
useMemo,
|
|
8
|
-
useState
|
|
6
|
+
useEffect as useEffect2,
|
|
7
|
+
useMemo as useMemo2,
|
|
8
|
+
useState as useState2
|
|
9
9
|
} from "react";
|
|
10
10
|
|
|
11
11
|
// packages/sdk/src/runtime/core/fetch.ts
|
|
@@ -14,8 +14,631 @@ var createBoundFetch = (fetchImpl) => {
|
|
|
14
14
|
return ((input, init) => baseFetch.call(globalThis, input, init));
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
// packages/sdk/src/runtime/react/auth.tsx
|
|
18
|
+
import {
|
|
19
|
+
useCallback,
|
|
20
|
+
useEffect,
|
|
21
|
+
useMemo,
|
|
22
|
+
useState
|
|
23
|
+
} from "react";
|
|
24
|
+
import {
|
|
25
|
+
Alert,
|
|
26
|
+
Button,
|
|
27
|
+
Card,
|
|
28
|
+
Empty,
|
|
29
|
+
Form,
|
|
30
|
+
Input,
|
|
31
|
+
Space,
|
|
32
|
+
Tabs,
|
|
33
|
+
Typography
|
|
34
|
+
} from "antd";
|
|
35
|
+
import {
|
|
36
|
+
LoginOutlined,
|
|
37
|
+
MobileOutlined,
|
|
38
|
+
QrcodeOutlined,
|
|
39
|
+
SafetyCertificateOutlined,
|
|
40
|
+
UserOutlined
|
|
41
|
+
} from "@ant-design/icons";
|
|
42
|
+
|
|
43
|
+
// packages/sdk/src/runtime/core/auth.ts
|
|
44
|
+
var AuthClientError = class extends Error {
|
|
45
|
+
constructor(message, options = {}) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = "AuthClientError";
|
|
48
|
+
this.status = options.status;
|
|
49
|
+
this.code = options.code;
|
|
50
|
+
this.payload = options.payload;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var createAuthClient = ({
|
|
54
|
+
appType,
|
|
55
|
+
servicePrefix = "/service",
|
|
56
|
+
fetchImpl
|
|
57
|
+
}) => {
|
|
58
|
+
const normalizedAppType = String(appType || "").trim();
|
|
59
|
+
if (!normalizedAppType) {
|
|
60
|
+
throw new Error("appType \u4E0D\u80FD\u4E3A\u7A7A");
|
|
61
|
+
}
|
|
62
|
+
const boundFetch = createBoundFetch(fetchImpl);
|
|
63
|
+
const request = async (path, options = {}) => {
|
|
64
|
+
const response = await boundFetch(buildServiceUrl(servicePrefix, path), {
|
|
65
|
+
credentials: "include",
|
|
66
|
+
...options,
|
|
67
|
+
headers: {
|
|
68
|
+
accept: "application/json",
|
|
69
|
+
...options.body ? { "content-type": "application/json" } : {},
|
|
70
|
+
...options.headers || {}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const payload = await readPayload(response);
|
|
74
|
+
const code = getRecordValue(payload, "code");
|
|
75
|
+
if (!response.ok || typeof code === "number" && code >= 400) {
|
|
76
|
+
throw new AuthClientError(
|
|
77
|
+
String(getRecordValue(payload, "message") || `Auth request failed: ${response.status}`),
|
|
78
|
+
{ status: response.status, code, payload }
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return unwrapPayload(payload);
|
|
82
|
+
};
|
|
83
|
+
const appAuthPath = (suffix) => `/openxiangda-api/v1/apps/${encodeURIComponent(normalizedAppType)}/auth${suffix}`;
|
|
84
|
+
return {
|
|
85
|
+
appType: normalizedAppType,
|
|
86
|
+
servicePrefix,
|
|
87
|
+
getMethods: () => request(appAuthPath("/methods")),
|
|
88
|
+
passwordLogin: (input) => request(appAuthPath("/password/login"), postJson(input)),
|
|
89
|
+
dingtalkLogin: (input) => request(appAuthPath("/dingtalk/login"), postJson(input)),
|
|
90
|
+
guestLogin: (input) => request(appAuthPath("/guest/login"), postJson(input || {})),
|
|
91
|
+
sendPhoneCode: (input) => request(appAuthPath("/phone-code/send"), postJson(input)),
|
|
92
|
+
phoneCodeLogin: (input) => request(appAuthPath("/phone-code/login"), postJson(input)),
|
|
93
|
+
registerWithPhoneCode: (input) => request(appAuthPath("/phone-code/register"), postJson(input)),
|
|
94
|
+
getSsoLoginUrl: (input) => request(appAuthPath("/sso/login-url"), postJson(input || {})),
|
|
95
|
+
refresh: (input) => request(appAuthPath("/refresh"), postJson(input || {})),
|
|
96
|
+
logout: async () => {
|
|
97
|
+
await request(appAuthPath("/logout"), { method: "POST" });
|
|
98
|
+
},
|
|
99
|
+
resolveLoginUrl: (input) => resolveLoginUrl(normalizedAppType, input || {})
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
var postJson = (body) => ({
|
|
103
|
+
method: "POST",
|
|
104
|
+
body: JSON.stringify(body || {})
|
|
105
|
+
});
|
|
106
|
+
var buildServiceUrl = (servicePrefix, path) => {
|
|
107
|
+
const prefix = servicePrefix.endsWith("/") ? servicePrefix.slice(0, -1) : servicePrefix;
|
|
108
|
+
const suffix = path.startsWith("/") ? path : `/${path}`;
|
|
109
|
+
return `${prefix}${suffix}`;
|
|
110
|
+
};
|
|
111
|
+
var readPayload = async (response) => {
|
|
112
|
+
try {
|
|
113
|
+
return await response.json();
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var unwrapPayload = (payload) => {
|
|
119
|
+
if (!payload || typeof payload !== "object") return payload;
|
|
120
|
+
const record = payload;
|
|
121
|
+
if ("data" in record) return record.data;
|
|
122
|
+
return payload;
|
|
123
|
+
};
|
|
124
|
+
var getRecordValue = (value, key) => {
|
|
125
|
+
if (!value || typeof value !== "object") return void 0;
|
|
126
|
+
return value[key];
|
|
127
|
+
};
|
|
128
|
+
var resolveLoginUrl = (appType, {
|
|
129
|
+
callbackUrl = getCurrentHref(),
|
|
130
|
+
callbackParamName = "callback",
|
|
131
|
+
loginUrl
|
|
132
|
+
}) => {
|
|
133
|
+
const target = loginUrl || `/view/${encodeURIComponent(appType)}/login`;
|
|
134
|
+
if (!callbackUrl) return target;
|
|
135
|
+
try {
|
|
136
|
+
const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
137
|
+
const url = new URL(target, base);
|
|
138
|
+
if (!url.searchParams.has(callbackParamName)) {
|
|
139
|
+
url.searchParams.set(callbackParamName, callbackUrl);
|
|
140
|
+
}
|
|
141
|
+
if (target.startsWith("http://") || target.startsWith("https://")) {
|
|
142
|
+
return url.toString();
|
|
143
|
+
}
|
|
144
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
145
|
+
} catch {
|
|
146
|
+
const separator = target.includes("?") ? "&" : "?";
|
|
147
|
+
return `${target}${separator}${callbackParamName}=${encodeURIComponent(callbackUrl)}`;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
var getCurrentHref = () => typeof window === "undefined" ? "" : window.location.href;
|
|
151
|
+
|
|
152
|
+
// packages/sdk/src/runtime/react/auth.tsx
|
|
153
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
154
|
+
var useAuth = (options = {}) => {
|
|
155
|
+
const runtime = useOpenXiangda();
|
|
156
|
+
const client = useMemo(
|
|
157
|
+
() => createAuthClient({
|
|
158
|
+
appType: options.appType || runtime.appType,
|
|
159
|
+
servicePrefix: options.servicePrefix || runtime.servicePrefix,
|
|
160
|
+
fetchImpl: options.fetchImpl || runtime.fetchImpl
|
|
161
|
+
}),
|
|
162
|
+
[
|
|
163
|
+
options.appType,
|
|
164
|
+
options.fetchImpl,
|
|
165
|
+
options.servicePrefix,
|
|
166
|
+
runtime.appType,
|
|
167
|
+
runtime.fetchImpl,
|
|
168
|
+
runtime.servicePrefix
|
|
169
|
+
]
|
|
170
|
+
);
|
|
171
|
+
return useMemo(
|
|
172
|
+
() => ({
|
|
173
|
+
client,
|
|
174
|
+
getMethods: client.getMethods,
|
|
175
|
+
passwordLogin: client.passwordLogin,
|
|
176
|
+
dingtalkLogin: client.dingtalkLogin,
|
|
177
|
+
guestLogin: client.guestLogin,
|
|
178
|
+
sendPhoneCode: client.sendPhoneCode,
|
|
179
|
+
phoneCodeLogin: client.phoneCodeLogin,
|
|
180
|
+
registerWithPhoneCode: client.registerWithPhoneCode,
|
|
181
|
+
getSsoLoginUrl: client.getSsoLoginUrl,
|
|
182
|
+
refresh: client.refresh,
|
|
183
|
+
logout: client.logout,
|
|
184
|
+
resolveLoginUrl: client.resolveLoginUrl
|
|
185
|
+
}),
|
|
186
|
+
[client]
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
var useLoginMethods = (options = {}) => {
|
|
190
|
+
const auth = useAuth(options);
|
|
191
|
+
const [state, setState] = useState({
|
|
192
|
+
data: null,
|
|
193
|
+
loading: true,
|
|
194
|
+
error: null
|
|
195
|
+
});
|
|
196
|
+
const reload = useCallback(async () => {
|
|
197
|
+
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
198
|
+
try {
|
|
199
|
+
const data = await auth.getMethods();
|
|
200
|
+
setState({ data, loading: false, error: null });
|
|
201
|
+
} catch (error) {
|
|
202
|
+
setState({
|
|
203
|
+
data: null,
|
|
204
|
+
loading: false,
|
|
205
|
+
error: normalizeError(error)
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}, [auth]);
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
let disposed = false;
|
|
211
|
+
const run = async () => {
|
|
212
|
+
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
213
|
+
try {
|
|
214
|
+
const data = await auth.getMethods();
|
|
215
|
+
if (!disposed) setState({ data, loading: false, error: null });
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (!disposed) {
|
|
218
|
+
setState({
|
|
219
|
+
data: null,
|
|
220
|
+
loading: false,
|
|
221
|
+
error: normalizeError(error)
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
void run();
|
|
227
|
+
return () => {
|
|
228
|
+
disposed = true;
|
|
229
|
+
};
|
|
230
|
+
}, [auth]);
|
|
231
|
+
return {
|
|
232
|
+
...state,
|
|
233
|
+
methods: state.data?.methods || [],
|
|
234
|
+
reload
|
|
235
|
+
};
|
|
236
|
+
};
|
|
237
|
+
var LoginPage = ({
|
|
238
|
+
title,
|
|
239
|
+
subtitle,
|
|
240
|
+
className,
|
|
241
|
+
style,
|
|
242
|
+
defaultMethod,
|
|
243
|
+
redirectUrl,
|
|
244
|
+
redirectOnSuccess = true,
|
|
245
|
+
onSuccess,
|
|
246
|
+
...authOptions
|
|
247
|
+
}) => {
|
|
248
|
+
const runtime = useOpenXiangda();
|
|
249
|
+
const auth = useAuth(authOptions);
|
|
250
|
+
const methodsState = useLoginMethods(authOptions);
|
|
251
|
+
const [passwordForm] = Form.useForm();
|
|
252
|
+
const [phoneForm] = Form.useForm();
|
|
253
|
+
const [activeMethod, setActiveMethod] = useState(
|
|
254
|
+
defaultMethod || "password"
|
|
255
|
+
);
|
|
256
|
+
const [phonePurpose, setPhonePurpose] = useState(
|
|
257
|
+
"login"
|
|
258
|
+
);
|
|
259
|
+
const [phoneChallengeId, setPhoneChallengeId] = useState("");
|
|
260
|
+
const [submitting, setSubmitting] = useState(false);
|
|
261
|
+
const [sendingCode, setSendingCode] = useState(false);
|
|
262
|
+
const [error, setError] = useState(null);
|
|
263
|
+
const methods = methodsState.methods.filter((method) => method.enabled !== false);
|
|
264
|
+
const passwordMethod = findMethod(methods, "password");
|
|
265
|
+
const phoneMethod = findMethod(methods, "phone_code");
|
|
266
|
+
const dingtalkMethod = findMethod(methods, "dingtalk");
|
|
267
|
+
const ssoMethod = findMethod(methods, "sso");
|
|
268
|
+
const guestMethod = findMethod(methods, "guest");
|
|
269
|
+
const allowRegister = methodsState.data?.registration?.mode !== "reject";
|
|
270
|
+
const tabItems = useMemo(() => {
|
|
271
|
+
const items = [];
|
|
272
|
+
if (passwordMethod) {
|
|
273
|
+
items.push({
|
|
274
|
+
key: "password",
|
|
275
|
+
label: /* @__PURE__ */ jsxs(Space, { size: 6, children: [
|
|
276
|
+
/* @__PURE__ */ jsx(UserOutlined, {}),
|
|
277
|
+
/* @__PURE__ */ jsx("span", { children: passwordMethod.label || "\u8D26\u53F7\u5BC6\u7801" })
|
|
278
|
+
] }),
|
|
279
|
+
children: /* @__PURE__ */ jsx(
|
|
280
|
+
PasswordLoginForm,
|
|
281
|
+
{
|
|
282
|
+
form: passwordForm,
|
|
283
|
+
loading: submitting,
|
|
284
|
+
onFinish: async (values) => {
|
|
285
|
+
setSubmitting(true);
|
|
286
|
+
setError(null);
|
|
287
|
+
try {
|
|
288
|
+
await handleSuccess(
|
|
289
|
+
await auth.passwordLogin({
|
|
290
|
+
username: values.username,
|
|
291
|
+
password: values.password
|
|
292
|
+
})
|
|
293
|
+
);
|
|
294
|
+
} catch (loginError) {
|
|
295
|
+
setError(normalizeError(loginError).message);
|
|
296
|
+
} finally {
|
|
297
|
+
setSubmitting(false);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
)
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
if (phoneMethod) {
|
|
305
|
+
items.push({
|
|
306
|
+
key: "phone_code",
|
|
307
|
+
label: /* @__PURE__ */ jsxs(Space, { size: 6, children: [
|
|
308
|
+
/* @__PURE__ */ jsx(MobileOutlined, {}),
|
|
309
|
+
/* @__PURE__ */ jsx("span", { children: phoneMethod.label || "\u624B\u673A\u53F7" })
|
|
310
|
+
] }),
|
|
311
|
+
children: /* @__PURE__ */ jsx(
|
|
312
|
+
PhoneCodeLoginForm,
|
|
313
|
+
{
|
|
314
|
+
allowRegister,
|
|
315
|
+
form: phoneForm,
|
|
316
|
+
loading: submitting,
|
|
317
|
+
phonePurpose,
|
|
318
|
+
sendingCode,
|
|
319
|
+
onPurposeChange: setPhonePurpose,
|
|
320
|
+
onSendCode: async () => {
|
|
321
|
+
const values = await phoneForm.validateFields(["phone"]);
|
|
322
|
+
setSendingCode(true);
|
|
323
|
+
setError(null);
|
|
324
|
+
try {
|
|
325
|
+
const result = await auth.sendPhoneCode({
|
|
326
|
+
phone: values.phone,
|
|
327
|
+
purpose: phonePurpose
|
|
328
|
+
});
|
|
329
|
+
setPhoneChallengeId(result.challengeId);
|
|
330
|
+
} catch (sendError) {
|
|
331
|
+
setError(normalizeError(sendError).message);
|
|
332
|
+
} finally {
|
|
333
|
+
setSendingCode(false);
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
onFinish: async (values) => {
|
|
337
|
+
setSubmitting(true);
|
|
338
|
+
setError(null);
|
|
339
|
+
try {
|
|
340
|
+
const data = phonePurpose === "register" ? await auth.registerWithPhoneCode({
|
|
341
|
+
phone: values.phone,
|
|
342
|
+
code: values.code,
|
|
343
|
+
challengeId: phoneChallengeId
|
|
344
|
+
}) : await auth.phoneCodeLogin({
|
|
345
|
+
phone: values.phone,
|
|
346
|
+
code: values.code,
|
|
347
|
+
challengeId: phoneChallengeId
|
|
348
|
+
});
|
|
349
|
+
await handleSuccess(data);
|
|
350
|
+
} catch (loginError) {
|
|
351
|
+
setError(normalizeError(loginError).message);
|
|
352
|
+
} finally {
|
|
353
|
+
setSubmitting(false);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
return items;
|
|
361
|
+
}, [
|
|
362
|
+
allowRegister,
|
|
363
|
+
auth,
|
|
364
|
+
handleSuccess,
|
|
365
|
+
passwordForm,
|
|
366
|
+
passwordMethod,
|
|
367
|
+
phoneChallengeId,
|
|
368
|
+
phoneForm,
|
|
369
|
+
phoneMethod,
|
|
370
|
+
phonePurpose,
|
|
371
|
+
sendingCode,
|
|
372
|
+
submitting
|
|
373
|
+
]);
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
const firstKey = tabItems[0]?.key;
|
|
376
|
+
if (firstKey && !tabItems.some((item) => item.key === activeMethod)) {
|
|
377
|
+
setActiveMethod(firstKey);
|
|
378
|
+
}
|
|
379
|
+
}, [activeMethod, tabItems]);
|
|
380
|
+
const handleDingTalkLogin = async () => {
|
|
381
|
+
setSubmitting(true);
|
|
382
|
+
setError(null);
|
|
383
|
+
try {
|
|
384
|
+
const code = await requestDingTalkCode(dingtalkMethod);
|
|
385
|
+
await handleSuccess(
|
|
386
|
+
await auth.dingtalkLogin({
|
|
387
|
+
code,
|
|
388
|
+
corpId: getString(dingtalkMethod, "corpId")
|
|
389
|
+
})
|
|
390
|
+
);
|
|
391
|
+
} catch (loginError) {
|
|
392
|
+
setError(normalizeError(loginError).message);
|
|
393
|
+
} finally {
|
|
394
|
+
setSubmitting(false);
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
const handleSsoLogin = async () => {
|
|
398
|
+
setSubmitting(true);
|
|
399
|
+
setError(null);
|
|
400
|
+
try {
|
|
401
|
+
const redirectUri = getCurrentHref2();
|
|
402
|
+
const result = await auth.getSsoLoginUrl({
|
|
403
|
+
protocol: getString(ssoMethod, "protocol") || "cas",
|
|
404
|
+
redirectUri
|
|
405
|
+
});
|
|
406
|
+
window.location.assign(result.loginUrl);
|
|
407
|
+
} catch (loginError) {
|
|
408
|
+
setError(normalizeError(loginError).message);
|
|
409
|
+
setSubmitting(false);
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
const handleGuestLogin = async () => {
|
|
413
|
+
setSubmitting(true);
|
|
414
|
+
setError(null);
|
|
415
|
+
try {
|
|
416
|
+
await handleSuccess(
|
|
417
|
+
await auth.guestLogin({
|
|
418
|
+
guestIdentifier: getOrCreateGuestIdentifier(auth.client),
|
|
419
|
+
domain: getCurrentHostname()
|
|
420
|
+
})
|
|
421
|
+
);
|
|
422
|
+
} catch (loginError) {
|
|
423
|
+
setError(normalizeError(loginError).message);
|
|
424
|
+
} finally {
|
|
425
|
+
setSubmitting(false);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
async function handleSuccess(data) {
|
|
429
|
+
await onSuccess?.(data);
|
|
430
|
+
await runtime.reload();
|
|
431
|
+
if (redirectOnSuccess && typeof window !== "undefined") {
|
|
432
|
+
window.location.replace(
|
|
433
|
+
redirectUrl || getCallbackUrl() || `/view/${auth.client.appType}`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return /* @__PURE__ */ jsx(
|
|
438
|
+
"div",
|
|
439
|
+
{
|
|
440
|
+
className,
|
|
441
|
+
style: {
|
|
442
|
+
minHeight: "100vh",
|
|
443
|
+
display: "grid",
|
|
444
|
+
placeItems: "center",
|
|
445
|
+
padding: 24,
|
|
446
|
+
background: "#f6f8fb",
|
|
447
|
+
...style
|
|
448
|
+
},
|
|
449
|
+
children: /* @__PURE__ */ jsx(
|
|
450
|
+
Card,
|
|
451
|
+
{
|
|
452
|
+
style: {
|
|
453
|
+
width: "min(100%, 420px)",
|
|
454
|
+
borderRadius: 8,
|
|
455
|
+
boxShadow: "0 16px 48px rgba(15, 23, 42, 0.10)"
|
|
456
|
+
},
|
|
457
|
+
styles: { body: { padding: 28 } },
|
|
458
|
+
children: /* @__PURE__ */ jsxs(Space, { direction: "vertical", size: 20, style: { width: "100%" }, children: [
|
|
459
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
460
|
+
/* @__PURE__ */ jsx(Typography.Title, { level: 3, style: { margin: 0 }, children: title || "\u5E94\u7528\u767B\u5F55" }),
|
|
461
|
+
subtitle ? /* @__PURE__ */ jsx(Typography.Text, { type: "secondary", children: subtitle }) : null
|
|
462
|
+
] }),
|
|
463
|
+
methodsState.error ? /* @__PURE__ */ jsx(
|
|
464
|
+
Alert,
|
|
465
|
+
{
|
|
466
|
+
showIcon: true,
|
|
467
|
+
type: "error",
|
|
468
|
+
message: methodsState.error.message
|
|
469
|
+
}
|
|
470
|
+
) : null,
|
|
471
|
+
error ? /* @__PURE__ */ jsx(Alert, { showIcon: true, type: "error", message: error }) : null,
|
|
472
|
+
methodsState.loading ? /* @__PURE__ */ jsx(Button, { block: true, loading: true, children: "\u52A0\u8F7D\u4E2D" }) : tabItems.length > 0 ? /* @__PURE__ */ jsx(
|
|
473
|
+
Tabs,
|
|
474
|
+
{
|
|
475
|
+
activeKey: activeMethod,
|
|
476
|
+
items: tabItems,
|
|
477
|
+
onChange: setActiveMethod
|
|
478
|
+
}
|
|
479
|
+
) : /* @__PURE__ */ jsx(Empty, { description: "\u672A\u542F\u7528\u767B\u5F55\u65B9\u5F0F" }),
|
|
480
|
+
/* @__PURE__ */ jsxs(Space, { direction: "vertical", size: 10, style: { width: "100%" }, children: [
|
|
481
|
+
dingtalkMethod ? /* @__PURE__ */ jsx(
|
|
482
|
+
Button,
|
|
483
|
+
{
|
|
484
|
+
block: true,
|
|
485
|
+
icon: /* @__PURE__ */ jsx(QrcodeOutlined, {}),
|
|
486
|
+
loading: submitting,
|
|
487
|
+
onClick: handleDingTalkLogin,
|
|
488
|
+
children: dingtalkMethod.label || "\u9489\u9489\u514D\u767B"
|
|
489
|
+
}
|
|
490
|
+
) : null,
|
|
491
|
+
ssoMethod ? /* @__PURE__ */ jsx(
|
|
492
|
+
Button,
|
|
493
|
+
{
|
|
494
|
+
block: true,
|
|
495
|
+
icon: /* @__PURE__ */ jsx(SafetyCertificateOutlined, {}),
|
|
496
|
+
loading: submitting,
|
|
497
|
+
onClick: handleSsoLogin,
|
|
498
|
+
children: ssoMethod.label || "SSO \u767B\u5F55"
|
|
499
|
+
}
|
|
500
|
+
) : null,
|
|
501
|
+
guestMethod ? /* @__PURE__ */ jsx(
|
|
502
|
+
Button,
|
|
503
|
+
{
|
|
504
|
+
block: true,
|
|
505
|
+
icon: /* @__PURE__ */ jsx(LoginOutlined, {}),
|
|
506
|
+
loading: submitting,
|
|
507
|
+
onClick: handleGuestLogin,
|
|
508
|
+
children: guestMethod.label || "\u8BBF\u5BA2\u8BBF\u95EE"
|
|
509
|
+
}
|
|
510
|
+
) : null
|
|
511
|
+
] })
|
|
512
|
+
] })
|
|
513
|
+
}
|
|
514
|
+
)
|
|
515
|
+
}
|
|
516
|
+
);
|
|
517
|
+
};
|
|
518
|
+
var PasswordLoginForm = ({ form, loading, onFinish }) => /* @__PURE__ */ jsxs(Form, { form, layout: "vertical", requiredMark: false, onFinish, children: [
|
|
519
|
+
/* @__PURE__ */ jsx(
|
|
520
|
+
Form.Item,
|
|
521
|
+
{
|
|
522
|
+
label: "\u8D26\u53F7",
|
|
523
|
+
name: "username",
|
|
524
|
+
rules: [{ required: true, message: "\u8BF7\u8F93\u5165\u8D26\u53F7" }],
|
|
525
|
+
children: /* @__PURE__ */ jsx(Input, { autoComplete: "username" })
|
|
526
|
+
}
|
|
527
|
+
),
|
|
528
|
+
/* @__PURE__ */ jsx(
|
|
529
|
+
Form.Item,
|
|
530
|
+
{
|
|
531
|
+
label: "\u5BC6\u7801",
|
|
532
|
+
name: "password",
|
|
533
|
+
rules: [{ required: true, message: "\u8BF7\u8F93\u5165\u5BC6\u7801" }],
|
|
534
|
+
children: /* @__PURE__ */ jsx(Input.Password, { autoComplete: "current-password" })
|
|
535
|
+
}
|
|
536
|
+
),
|
|
537
|
+
/* @__PURE__ */ jsx(Button, { block: true, htmlType: "submit", icon: /* @__PURE__ */ jsx(LoginOutlined, {}), loading, type: "primary", children: "\u767B\u5F55" })
|
|
538
|
+
] });
|
|
539
|
+
var PhoneCodeLoginForm = ({
|
|
540
|
+
allowRegister,
|
|
541
|
+
form,
|
|
542
|
+
loading,
|
|
543
|
+
phonePurpose,
|
|
544
|
+
sendingCode,
|
|
545
|
+
onPurposeChange,
|
|
546
|
+
onSendCode,
|
|
547
|
+
onFinish
|
|
548
|
+
}) => /* @__PURE__ */ jsxs(Form, { form, layout: "vertical", requiredMark: false, onFinish, children: [
|
|
549
|
+
allowRegister ? /* @__PURE__ */ jsx(Form.Item, { style: { marginBottom: 12 }, children: /* @__PURE__ */ jsx(
|
|
550
|
+
Tabs,
|
|
551
|
+
{
|
|
552
|
+
activeKey: phonePurpose,
|
|
553
|
+
items: [
|
|
554
|
+
{ key: "login", label: "\u767B\u5F55" },
|
|
555
|
+
{ key: "register", label: "\u6CE8\u518C" }
|
|
556
|
+
],
|
|
557
|
+
onChange: (key) => onPurposeChange(key === "register" ? "register" : "login"),
|
|
558
|
+
size: "small"
|
|
559
|
+
}
|
|
560
|
+
) }) : null,
|
|
561
|
+
/* @__PURE__ */ jsx(
|
|
562
|
+
Form.Item,
|
|
563
|
+
{
|
|
564
|
+
label: "\u624B\u673A\u53F7",
|
|
565
|
+
name: "phone",
|
|
566
|
+
rules: [{ required: true, message: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7" }],
|
|
567
|
+
children: /* @__PURE__ */ jsx(Input, { autoComplete: "tel" })
|
|
568
|
+
}
|
|
569
|
+
),
|
|
570
|
+
/* @__PURE__ */ jsx(
|
|
571
|
+
Form.Item,
|
|
572
|
+
{
|
|
573
|
+
label: "\u9A8C\u8BC1\u7801",
|
|
574
|
+
name: "code",
|
|
575
|
+
rules: [{ required: true, message: "\u8BF7\u8F93\u5165\u9A8C\u8BC1\u7801" }],
|
|
576
|
+
children: /* @__PURE__ */ jsx(
|
|
577
|
+
Input,
|
|
578
|
+
{
|
|
579
|
+
addonAfter: /* @__PURE__ */ jsx(
|
|
580
|
+
Button,
|
|
581
|
+
{
|
|
582
|
+
loading: sendingCode,
|
|
583
|
+
onClick: onSendCode,
|
|
584
|
+
size: "small",
|
|
585
|
+
type: "link",
|
|
586
|
+
children: "\u53D1\u9001"
|
|
587
|
+
}
|
|
588
|
+
),
|
|
589
|
+
autoComplete: "one-time-code"
|
|
590
|
+
}
|
|
591
|
+
)
|
|
592
|
+
}
|
|
593
|
+
),
|
|
594
|
+
/* @__PURE__ */ jsx(Button, { block: true, htmlType: "submit", icon: /* @__PURE__ */ jsx(MobileOutlined, {}), loading, type: "primary", children: phonePurpose === "register" ? "\u6CE8\u518C" : "\u767B\u5F55" })
|
|
595
|
+
] });
|
|
596
|
+
var findMethod = (methods, type) => methods.find((method) => method.type === type);
|
|
597
|
+
var normalizeError = (error) => error instanceof Error ? error : new Error(String(error || "\u8BF7\u6C42\u5931\u8D25"));
|
|
598
|
+
var getString = (value, key) => {
|
|
599
|
+
if (!value || typeof value !== "object") return void 0;
|
|
600
|
+
const result = value[key];
|
|
601
|
+
return typeof result === "string" ? result : void 0;
|
|
602
|
+
};
|
|
603
|
+
var requestDingTalkCode = (method) => {
|
|
604
|
+
const dd = typeof window === "undefined" ? void 0 : window.dd;
|
|
605
|
+
const corpId = getString(method, "corpId");
|
|
606
|
+
return new Promise((resolve, reject) => {
|
|
607
|
+
const requestAuthCode = dd?.runtime?.permission?.requestAuthCode || dd?.requestAuthCode;
|
|
608
|
+
if (!requestAuthCode) {
|
|
609
|
+
reject(new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u9489\u9489\u514D\u767B"));
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
requestAuthCode({
|
|
613
|
+
corpId,
|
|
614
|
+
onSuccess: (result) => {
|
|
615
|
+
if (result?.code) resolve(result.code);
|
|
616
|
+
else reject(new Error("\u9489\u9489\u672A\u8FD4\u56DE\u514D\u767B\u7801"));
|
|
617
|
+
},
|
|
618
|
+
onFail: (error) => reject(normalizeError(error))
|
|
619
|
+
});
|
|
620
|
+
});
|
|
621
|
+
};
|
|
622
|
+
var getOrCreateGuestIdentifier = (client) => {
|
|
623
|
+
const key = `openxiangda:${client.appType}:guest_id`;
|
|
624
|
+
if (typeof window === "undefined") return createGuestIdentifier();
|
|
625
|
+
const current = window.localStorage.getItem(key);
|
|
626
|
+
if (current) return current;
|
|
627
|
+
const next = createGuestIdentifier();
|
|
628
|
+
window.localStorage.setItem(key, next);
|
|
629
|
+
return next;
|
|
630
|
+
};
|
|
631
|
+
var createGuestIdentifier = () => `guest_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
632
|
+
var getCurrentHref2 = () => typeof window === "undefined" ? "" : window.location.href;
|
|
633
|
+
var getCurrentHostname = () => typeof window === "undefined" ? "" : window.location.hostname;
|
|
634
|
+
var getCallbackUrl = () => {
|
|
635
|
+
if (typeof window === "undefined") return "";
|
|
636
|
+
const query = new URLSearchParams(window.location.search);
|
|
637
|
+
return query.get("callback") || query.get("redirectUri") || "";
|
|
638
|
+
};
|
|
639
|
+
|
|
17
640
|
// packages/sdk/src/runtime/react/openxiangdaProvider.tsx
|
|
18
|
-
import { Fragment, jsx } from "react/jsx-runtime";
|
|
641
|
+
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
19
642
|
var RuntimeHttpError = class extends Error {
|
|
20
643
|
constructor(snapshot) {
|
|
21
644
|
super(snapshot.message);
|
|
@@ -33,17 +656,17 @@ var OpenXiangdaProvider = ({
|
|
|
33
656
|
fetchImpl,
|
|
34
657
|
children
|
|
35
658
|
}) => {
|
|
36
|
-
const resolvedFetch =
|
|
37
|
-
const resolvedAppType =
|
|
659
|
+
const resolvedFetch = useMemo2(() => createBoundFetch(fetchImpl), [fetchImpl]);
|
|
660
|
+
const resolvedAppType = useMemo2(
|
|
38
661
|
() => appType || resolveAppTypeFromLocation(),
|
|
39
662
|
[appType]
|
|
40
663
|
);
|
|
41
|
-
const [state, setState] =
|
|
664
|
+
const [state, setState] = useState2({
|
|
42
665
|
data: null,
|
|
43
666
|
loading: true,
|
|
44
667
|
error: null
|
|
45
668
|
});
|
|
46
|
-
const reload =
|
|
669
|
+
const reload = useCallback2(async () => {
|
|
47
670
|
if (!resolvedAppType) {
|
|
48
671
|
setState({
|
|
49
672
|
data: null,
|
|
@@ -58,7 +681,7 @@ var OpenXiangdaProvider = ({
|
|
|
58
681
|
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
59
682
|
try {
|
|
60
683
|
const response = await resolvedFetch(
|
|
61
|
-
|
|
684
|
+
buildServiceUrl2(
|
|
62
685
|
servicePrefix,
|
|
63
686
|
`/openxiangda-api/v1/apps/${encodeURIComponent(
|
|
64
687
|
resolvedAppType
|
|
@@ -90,10 +713,10 @@ var OpenXiangdaProvider = ({
|
|
|
90
713
|
});
|
|
91
714
|
}
|
|
92
715
|
}, [resolvedAppType, resolvedFetch, servicePrefix]);
|
|
93
|
-
|
|
716
|
+
useEffect2(() => {
|
|
94
717
|
void reload();
|
|
95
718
|
}, [reload]);
|
|
96
|
-
const value =
|
|
719
|
+
const value = useMemo2(
|
|
97
720
|
() => ({
|
|
98
721
|
...state,
|
|
99
722
|
appType: resolvedAppType,
|
|
@@ -103,7 +726,7 @@ var OpenXiangdaProvider = ({
|
|
|
103
726
|
}),
|
|
104
727
|
[reload, resolvedAppType, resolvedFetch, servicePrefix, state]
|
|
105
728
|
);
|
|
106
|
-
return /* @__PURE__ */
|
|
729
|
+
return /* @__PURE__ */ jsx2(OpenXiangdaRuntimeContext.Provider, { value, children });
|
|
107
730
|
};
|
|
108
731
|
var useOpenXiangda = () => {
|
|
109
732
|
const context = useContext(OpenXiangdaRuntimeContext);
|
|
@@ -129,12 +752,12 @@ var usePermission = () => {
|
|
|
129
752
|
};
|
|
130
753
|
var useCanAccessRoute = (input) => {
|
|
131
754
|
const runtime = useOpenXiangda();
|
|
132
|
-
const [state, setState] =
|
|
755
|
+
const [state, setState] = useState2({
|
|
133
756
|
data: null,
|
|
134
757
|
loading: true,
|
|
135
758
|
error: null
|
|
136
759
|
});
|
|
137
|
-
|
|
760
|
+
useEffect2(() => {
|
|
138
761
|
let disposed = false;
|
|
139
762
|
const check = async () => {
|
|
140
763
|
const permissions = runtime.data?.permissions;
|
|
@@ -170,7 +793,7 @@ var useCanAccessRoute = (input) => {
|
|
|
170
793
|
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
171
794
|
try {
|
|
172
795
|
const response = await runtime.fetchImpl(
|
|
173
|
-
|
|
796
|
+
buildServiceUrl2(
|
|
174
797
|
runtime.servicePrefix,
|
|
175
798
|
`/openxiangda-api/v1/apps/${encodeURIComponent(
|
|
176
799
|
runtime.appType
|
|
@@ -256,23 +879,23 @@ var PermissionBoundary = ({
|
|
|
256
879
|
const access = useCanAccessRoute({ routeCode, menuCode, path });
|
|
257
880
|
const fallbackState = createPermissionFallbackState(access, runtime);
|
|
258
881
|
if (access.loading) {
|
|
259
|
-
return /* @__PURE__ */
|
|
882
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: renderBoundaryFallback(loadingFallback, fallbackState) });
|
|
260
883
|
}
|
|
261
884
|
if (!access.canAccess) {
|
|
262
|
-
return /* @__PURE__ */
|
|
885
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: renderBoundaryFallback(fallback, fallbackState) });
|
|
263
886
|
}
|
|
264
|
-
return /* @__PURE__ */
|
|
887
|
+
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
265
888
|
};
|
|
266
889
|
var useRuntimeAuth = () => {
|
|
267
890
|
const runtime = useOpenXiangda();
|
|
268
|
-
const
|
|
891
|
+
const resolveLoginUrl2 = useCallback2(
|
|
269
892
|
async (options = {}) => {
|
|
270
|
-
const redirectUri = options.redirectUri ||
|
|
271
|
-
const domain = options.domain ||
|
|
893
|
+
const redirectUri = options.redirectUri || getCurrentHref3();
|
|
894
|
+
const domain = options.domain || getCurrentHostname2();
|
|
272
895
|
const appTenantId = getRecordString(runtime.data?.app, "tenantId");
|
|
273
896
|
try {
|
|
274
897
|
const statusUrl = withQuery(
|
|
275
|
-
|
|
898
|
+
buildServiceUrl2(runtime.servicePrefix, "/api/sso/status"),
|
|
276
899
|
{ domain }
|
|
277
900
|
);
|
|
278
901
|
const statusResponse = await runtime.fetchImpl(statusUrl, {
|
|
@@ -287,7 +910,7 @@ var useRuntimeAuth = () => {
|
|
|
287
910
|
);
|
|
288
911
|
if (shouldUseSso) {
|
|
289
912
|
const loginUrlResponse = await runtime.fetchImpl(
|
|
290
|
-
withQuery(
|
|
913
|
+
withQuery(buildServiceUrl2(runtime.servicePrefix, "/api/sso/login-url"), {
|
|
291
914
|
domain,
|
|
292
915
|
redirectUri,
|
|
293
916
|
...sso.tenantId || appTenantId ? { tenantId: String(sso.tenantId || appTenantId) } : {},
|
|
@@ -312,9 +935,9 @@ var useRuntimeAuth = () => {
|
|
|
312
935
|
},
|
|
313
936
|
[runtime.data?.app, runtime.fetchImpl, runtime.servicePrefix]
|
|
314
937
|
);
|
|
315
|
-
const redirectToLogin =
|
|
938
|
+
const redirectToLogin = useCallback2(
|
|
316
939
|
async (options = {}) => {
|
|
317
|
-
const loginUrl = await
|
|
940
|
+
const loginUrl = await resolveLoginUrl2(options);
|
|
318
941
|
if (typeof window !== "undefined") {
|
|
319
942
|
if (options.replace === false) {
|
|
320
943
|
window.location.assign(loginUrl);
|
|
@@ -324,11 +947,11 @@ var useRuntimeAuth = () => {
|
|
|
324
947
|
}
|
|
325
948
|
return loginUrl;
|
|
326
949
|
},
|
|
327
|
-
[
|
|
950
|
+
[resolveLoginUrl2]
|
|
328
951
|
);
|
|
329
|
-
const logout =
|
|
952
|
+
const logout = useCallback2(async () => {
|
|
330
953
|
const response = await runtime.fetchImpl(
|
|
331
|
-
|
|
954
|
+
buildServiceUrl2(runtime.servicePrefix, "/api/auth/logout"),
|
|
332
955
|
{
|
|
333
956
|
method: "POST",
|
|
334
957
|
credentials: "include",
|
|
@@ -345,7 +968,7 @@ var useRuntimeAuth = () => {
|
|
|
345
968
|
}
|
|
346
969
|
return payload;
|
|
347
970
|
}, [runtime.fetchImpl, runtime.servicePrefix]);
|
|
348
|
-
const logoutAndRedirect =
|
|
971
|
+
const logoutAndRedirect = useCallback2(
|
|
349
972
|
async (options = {}) => {
|
|
350
973
|
try {
|
|
351
974
|
await logout();
|
|
@@ -360,10 +983,10 @@ var useRuntimeAuth = () => {
|
|
|
360
983
|
logout,
|
|
361
984
|
logoutAndRedirect,
|
|
362
985
|
redirectToLogin,
|
|
363
|
-
resolveLoginUrl
|
|
986
|
+
resolveLoginUrl: resolveLoginUrl2
|
|
364
987
|
};
|
|
365
988
|
};
|
|
366
|
-
var
|
|
989
|
+
var buildServiceUrl2 = (servicePrefix, path) => {
|
|
367
990
|
const prefix = servicePrefix.endsWith("/") ? servicePrefix.slice(0, -1) : servicePrefix;
|
|
368
991
|
const suffix = path.startsWith("/") ? path : `/${path}`;
|
|
369
992
|
return `${prefix}${suffix}`;
|
|
@@ -378,11 +1001,11 @@ var readJsonPayload = async (response) => {
|
|
|
378
1001
|
var createRuntimeHttpError = (response, payload, fallbackMessage) => createRuntimeError({
|
|
379
1002
|
type: classifyRuntimeError(
|
|
380
1003
|
response.status,
|
|
381
|
-
|
|
1004
|
+
getRecordValue2(payload, "code")
|
|
382
1005
|
),
|
|
383
1006
|
status: response.status,
|
|
384
|
-
code:
|
|
385
|
-
message:
|
|
1007
|
+
code: getRecordValue2(payload, "code"),
|
|
1008
|
+
message: getRecordValue2(payload, "message") || fallbackMessage,
|
|
386
1009
|
payload
|
|
387
1010
|
});
|
|
388
1011
|
var createRuntimeError = (snapshot) => new RuntimeHttpError({
|
|
@@ -458,18 +1081,18 @@ var attachCallback = (loginUrl, callback) => {
|
|
|
458
1081
|
return `${loginUrl}${separator}callback=${encodeURIComponent(callback)}`;
|
|
459
1082
|
}
|
|
460
1083
|
};
|
|
461
|
-
var
|
|
462
|
-
var
|
|
1084
|
+
var getCurrentHref3 = () => typeof window === "undefined" ? "" : window.location.href;
|
|
1085
|
+
var getCurrentHostname2 = () => typeof window === "undefined" ? "" : window.location.hostname;
|
|
463
1086
|
var getRuntimeEnv = (key) => {
|
|
464
1087
|
const env = typeof process !== "undefined" ? process.env : void 0;
|
|
465
1088
|
return env?.[key];
|
|
466
1089
|
};
|
|
467
|
-
var
|
|
1090
|
+
var getRecordValue2 = (value, key) => {
|
|
468
1091
|
if (!value || typeof value !== "object") return void 0;
|
|
469
1092
|
return value[key];
|
|
470
1093
|
};
|
|
471
1094
|
var getRecordString = (value, key) => {
|
|
472
|
-
const result =
|
|
1095
|
+
const result = getRecordValue2(value, key);
|
|
473
1096
|
return typeof result === "string" ? result : void 0;
|
|
474
1097
|
};
|
|
475
1098
|
var resolveAppTypeFromLocation = () => {
|
|
@@ -481,11 +1104,16 @@ var resolveAppTypeFromLocation = () => {
|
|
|
481
1104
|
return segments[viewIndex] || "";
|
|
482
1105
|
};
|
|
483
1106
|
export {
|
|
1107
|
+
AuthClientError,
|
|
1108
|
+
LoginPage,
|
|
484
1109
|
OpenXiangdaProvider,
|
|
485
1110
|
PermissionBoundary,
|
|
486
1111
|
RuntimeHttpError,
|
|
1112
|
+
createAuthClient,
|
|
487
1113
|
useAppMenus,
|
|
1114
|
+
useAuth,
|
|
488
1115
|
useCanAccessRoute,
|
|
1116
|
+
useLoginMethods,
|
|
489
1117
|
useOpenXiangda,
|
|
490
1118
|
usePermission,
|
|
491
1119
|
useRuntimeAuth,
|