openxiangda 1.0.37 → 1.0.38

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.
@@ -11,6 +11,8 @@ Guidelines:
11
11
  - Use `sdk.connector.invoke`, `sdk.connector.call("connector.api")`, or `sdk.connector.download` for external services. The SDK calls the platform runtime connector endpoint; it must not call third-party domains directly.
12
12
  - Use `sdk.notification.sendByType` and `batchSendByType` for reusable business messages. Custom notification types must be declared in `src/resources/notifications/` and published with `openxiangda resource publish`.
13
13
  - For the current user's department hierarchy, use `sdk.department.getCurrentUserParentDepartments()`; do not hardcode `GET /department/:id/parentDepartments` in page code.
14
+ - Use `sdk.auth.logoutAndRedirect({ loginUrl })` for user logout when the page should return after login. It calls the platform logout endpoint, appends the current page URL as `callback`, and redirects to the login URL. Use `sdk.auth.logout()` only when the page wants to handle redirect itself.
15
+ - Use `sdk.role.getMyRoles()`, `sdk.role.getCurrentRole()`, and `sdk.role.switchAppRole()` for current-user app role switching. Pass `roleId: ""` to switch back to all app roles.
14
16
  - Keep API calls behind small local functions so generated UI stays testable.
15
17
  - Treat user context and tenant context as runtime-provided values.
16
18
 
@@ -56,3 +58,33 @@ const response = await sdk.dataSource.run("tickets", {
56
58
  ```
57
59
 
58
60
  Data view filters and fields use output aliases such as `customerName`, not source references such as `customer.name`. If the page only needs one form, prefer `sdk.form.advancedSearch`. If the page only needs a simple one-form dropdown, prefer linkedForm options.
61
+
62
+ Logout and current-user role switching:
63
+
64
+ ```ts
65
+ await sdk.auth.logoutAndRedirect({
66
+ loginUrl: "/login",
67
+ })
68
+
69
+ await sdk.auth.logoutAndRedirect({
70
+ loginUrl: "/login",
71
+ callbackParamName: "redirect",
72
+ })
73
+
74
+ await sdk.auth.logout()
75
+
76
+ const roles = await sdk.role.getMyRoles({ scope: "app" })
77
+ const currentRole = await sdk.role.getCurrentRole({ scope: "app" })
78
+
79
+ await sdk.role.switchAppRole({
80
+ roleId: roles.result?.[0]?.id || "",
81
+ })
82
+
83
+ await sdk.role.switchAppRole({
84
+ roleId: "",
85
+ })
86
+
87
+ await sdk.role.switchPlatformRole({
88
+ roleId: "platform-admin",
89
+ })
90
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.37",
3
+ "version": "1.0.38",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -361,6 +361,99 @@ var withDefaultAppType = (context, params) => {
361
361
  }
362
362
  return params;
363
363
  };
364
+ var AUTH_LOGIN_URL_ENV_KEYS = [
365
+ "loginUrl",
366
+ "authLoginUrl",
367
+ "bathAuthUrl",
368
+ "REACT_APP_VITE_BATH_AUTH_URL",
369
+ "VITE_BATH_AUTH_URL",
370
+ "BATH_AUTH_URL"
371
+ ];
372
+ var normalizeOptionalString = (value) => {
373
+ const text = typeof value === "string" ? value.trim() : "";
374
+ return text || void 0;
375
+ };
376
+ var getCurrentHref = () => {
377
+ if (typeof window === "undefined") {
378
+ return "";
379
+ }
380
+ return window.location?.href || "";
381
+ };
382
+ var resolveAuthLoginUrl = (context, options) => {
383
+ const explicitLoginUrl = normalizeOptionalString(options.loginUrl);
384
+ if (explicitLoginUrl) {
385
+ return explicitLoginUrl;
386
+ }
387
+ for (const key of AUTH_LOGIN_URL_ENV_KEYS) {
388
+ const loginUrl = normalizeOptionalString(context.env?.[key]);
389
+ if (loginUrl) {
390
+ return loginUrl;
391
+ }
392
+ }
393
+ return void 0;
394
+ };
395
+ var buildAuthRedirectUrl = (loginUrl, callbackUrl, callbackParamName) => {
396
+ const paramName = normalizeOptionalString(callbackParamName) || "callback";
397
+ const normalizedCallbackUrl = normalizeOptionalString(callbackUrl);
398
+ if (!normalizedCallbackUrl) {
399
+ return loginUrl;
400
+ }
401
+ const baseHref = getCurrentHref() || "http://localhost/";
402
+ const isAbsoluteUrl = /^[a-z][a-z\d+.-]*:/i.test(loginUrl);
403
+ const isProtocolRelativeUrl = loginUrl.startsWith("//");
404
+ const isRootRelativeUrl = loginUrl.startsWith("/");
405
+ try {
406
+ const url = new URL(loginUrl, baseHref);
407
+ url.searchParams.set(paramName, normalizedCallbackUrl);
408
+ if (isAbsoluteUrl || isProtocolRelativeUrl) {
409
+ return url.toString();
410
+ }
411
+ const path = `${url.pathname}${url.search}${url.hash}`;
412
+ return isRootRelativeUrl ? path : path.replace(/^\//, "");
413
+ } catch {
414
+ const hashIndex = loginUrl.indexOf("#");
415
+ const pathAndQuery = hashIndex >= 0 ? loginUrl.slice(0, hashIndex) : loginUrl;
416
+ const hash = hashIndex >= 0 ? loginUrl.slice(hashIndex) : "";
417
+ const separator = pathAndQuery.includes("?") ? "&" : "?";
418
+ return `${pathAndQuery}${separator}${encodeURIComponent(
419
+ paramName
420
+ )}=${encodeURIComponent(normalizedCallbackUrl)}${hash}`;
421
+ }
422
+ };
423
+ var performRedirect = (url, options) => {
424
+ if (options.redirect) {
425
+ options.redirect(url);
426
+ return;
427
+ }
428
+ if (typeof window === "undefined" || !window.location) {
429
+ return;
430
+ }
431
+ if (options.replace && typeof window.location.replace === "function") {
432
+ window.location.replace(url);
433
+ return;
434
+ }
435
+ window.location.href = url;
436
+ };
437
+ var reloadCurrentPage = () => {
438
+ if (typeof window === "undefined" || !window.location || typeof window.location.reload !== "function") {
439
+ return;
440
+ }
441
+ window.location.reload();
442
+ };
443
+ var redirectAfterLogout = (context, options) => {
444
+ const loginUrl = resolveAuthLoginUrl(context, options);
445
+ const callbackUrl = normalizeOptionalString(options.callbackUrl) || getCurrentHref();
446
+ if (loginUrl) {
447
+ performRedirect(
448
+ buildAuthRedirectUrl(loginUrl, callbackUrl, options.callbackParamName),
449
+ options
450
+ );
451
+ return;
452
+ }
453
+ if (options.fallback !== "none") {
454
+ reloadCurrentPage();
455
+ }
456
+ };
364
457
  var createPageSdk = (context) => {
365
458
  const request = async (options) => {
366
459
  const payload = {
@@ -401,6 +494,26 @@ var createPageSdk = (context) => {
401
494
  throw toSdkError(error, payload);
402
495
  }
403
496
  };
497
+ const logout = () => request({
498
+ path: "/api/auth/logout",
499
+ method: "post"
500
+ });
501
+ const auth = {
502
+ logout,
503
+ logoutAndRedirect: async (options = {}) => {
504
+ try {
505
+ const response = await logout();
506
+ redirectAfterLogout(context, options);
507
+ return response;
508
+ } catch (error) {
509
+ if (options.continueOnLogoutError === false) {
510
+ throw error;
511
+ }
512
+ redirectAfterLogout(context, options);
513
+ return null;
514
+ }
515
+ }
516
+ };
404
517
  const connector = {
405
518
  invoke: (params) => request({
406
519
  path: buildAppPath(
@@ -1158,6 +1271,7 @@ var createPageSdk = (context) => {
1158
1271
  request,
1159
1272
  download
1160
1273
  },
1274
+ auth,
1161
1275
  connector,
1162
1276
  form,
1163
1277
  user,