openxiangda 1.0.149 → 1.0.151

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.
@@ -42,6 +42,7 @@ __export(react_exports, {
42
42
  ProcessTimeline: () => ProcessTimeline,
43
43
  PublicAccessClientError: () => PublicAccessClientError,
44
44
  PublicAccessGate: () => PublicAccessGate,
45
+ RuntimeAuthGuard: () => RuntimeAuthGuard,
45
46
  createAuthClient: () => createAuthClient,
46
47
  createPageSdk: () => createPageSdk,
47
48
  createPublicAccessClient: () => createPublicAccessClient,
@@ -4797,14 +4798,14 @@ var useAuth = (options = {}) => {
4797
4798
  () => createAuthClient({
4798
4799
  appType: options.appType || runtime.appType,
4799
4800
  servicePrefix: options.servicePrefix || runtime.servicePrefix,
4800
- fetchImpl: options.fetchImpl || runtime.fetchImpl
4801
+ fetchImpl: options.fetchImpl || runtime.baseFetchImpl
4801
4802
  }),
4802
4803
  [
4803
4804
  options.appType,
4804
4805
  options.fetchImpl,
4805
4806
  options.servicePrefix,
4806
4807
  runtime.appType,
4807
- runtime.fetchImpl,
4808
+ runtime.baseFetchImpl,
4808
4809
  runtime.servicePrefix
4809
4810
  ]
4810
4811
  );
@@ -5083,7 +5084,19 @@ var LoginPage = ({
5083
5084
  };
5084
5085
  async function handleSuccess(data) {
5085
5086
  await onSuccess?.(data);
5086
- await runtime.reload();
5087
+ const accessToken = data.accessToken || data.token;
5088
+ const accessTokenOptions = {
5089
+ expiresAt: data.accessTokenExpiresAt
5090
+ };
5091
+ if (accessToken) {
5092
+ runtime.setAccessToken(accessToken, accessTokenOptions);
5093
+ }
5094
+ await runtime.reload(
5095
+ accessToken ? {
5096
+ accessToken,
5097
+ accessTokenOptions
5098
+ } : void 0
5099
+ );
5087
5100
  if (redirectOnSuccess && typeof window !== "undefined") {
5088
5101
  window.location.replace(
5089
5102
  redirectUrl || getCallbackUrl() || `/view/${auth.client.appType}`
@@ -5367,27 +5380,125 @@ var OpenXiangdaProvider = ({
5367
5380
  () => appType || resolveAppTypeFromLocation(),
5368
5381
  [appType]
5369
5382
  );
5370
- const [accessToken, setAccessTokenState] = (0, import_react14.useState)(null);
5383
+ const [, setAccessTokenState] = (0, import_react14.useState)(null);
5384
+ const [authState, setAuthState] = (0, import_react14.useState)({
5385
+ status: "unknown",
5386
+ error: null
5387
+ });
5371
5388
  const accessTokenRef = (0, import_react14.useRef)(null);
5372
- const setAccessToken = (0, import_react14.useCallback)(
5389
+ const refreshRequestRef = (0, import_react14.useRef)(null);
5390
+ const applyAccessToken = (0, import_react14.useCallback)(
5373
5391
  (nextAccessToken, options = {}) => {
5374
5392
  if (!nextAccessToken && options.clearIfToken && accessTokenRef.current?.token !== options.clearIfToken) {
5375
- return;
5393
+ return accessTokenRef.current;
5376
5394
  }
5377
5395
  const normalizedAccessToken = nextAccessToken || null;
5378
5396
  const nextState = normalizedAccessToken ? {
5379
5397
  token: normalizedAccessToken,
5380
5398
  scope: options.scope || "default",
5381
- path: options.path || null
5399
+ path: options.path || null,
5400
+ expiresAt: options.expiresAt
5382
5401
  } : null;
5383
5402
  accessTokenRef.current = nextState;
5384
5403
  setAccessTokenState(nextState);
5404
+ return nextState;
5385
5405
  },
5386
5406
  []
5387
5407
  );
5408
+ const setAccessToken = (0, import_react14.useCallback)(
5409
+ (nextAccessToken, options = {}) => {
5410
+ const previousState = accessTokenRef.current;
5411
+ const nextState = applyAccessToken(nextAccessToken, options);
5412
+ if (nextState) {
5413
+ if (nextState.scope === "public") return;
5414
+ setAuthState({
5415
+ status: "authenticated",
5416
+ error: null
5417
+ });
5418
+ } else if (previousState?.scope !== "public") {
5419
+ setAuthState({ status: "unauthenticated", error: null });
5420
+ }
5421
+ },
5422
+ [applyAccessToken]
5423
+ );
5424
+ const markUnauthenticated = (0, import_react14.useCallback)(
5425
+ (error) => {
5426
+ const runtimeError = normalizeRuntimeError(error);
5427
+ applyAccessToken(null);
5428
+ setAuthState({ status: "unauthenticated", error: runtimeError });
5429
+ },
5430
+ [applyAccessToken]
5431
+ );
5432
+ const refreshAccessToken = (0, import_react14.useCallback)(async () => {
5433
+ if (!resolvedAppType) return null;
5434
+ if (!refreshRequestRef.current) {
5435
+ setAuthState((prev) => ({ ...prev, status: "refreshing", error: null }));
5436
+ refreshRequestRef.current = (async () => {
5437
+ const response = await resolvedFetch(
5438
+ buildServiceUrl2(
5439
+ servicePrefix,
5440
+ `/openxiangda-api/v1/apps/${encodeURIComponent(
5441
+ resolvedAppType
5442
+ )}/auth/refresh`
5443
+ ),
5444
+ {
5445
+ method: "POST",
5446
+ credentials: "include",
5447
+ headers: {
5448
+ accept: "application/json",
5449
+ "content-type": "application/json"
5450
+ },
5451
+ body: "{}"
5452
+ }
5453
+ );
5454
+ const payload = await readJsonPayload(response);
5455
+ if (isRuntimeEnvelopeFailure(response, payload)) {
5456
+ throw createRuntimeHttpError(
5457
+ response,
5458
+ payload,
5459
+ payload?.message || `Token refresh failed: ${response.status}`
5460
+ );
5461
+ }
5462
+ const data = unwrapRuntimePayload(payload);
5463
+ const nextAccessToken = getRecordString(data, "accessToken") || getRecordString(data, "token");
5464
+ if (!nextAccessToken) {
5465
+ throw createRuntimeError({
5466
+ type: "unauthenticated",
5467
+ status: response.status,
5468
+ code: getRecordValue2(payload, "code"),
5469
+ message: "Token refresh response missing accessToken",
5470
+ payload
5471
+ });
5472
+ }
5473
+ const nextState = applyAccessToken(nextAccessToken, {
5474
+ expiresAt: getRecordNumber(data, "accessTokenExpiresAt")
5475
+ });
5476
+ setAuthState({
5477
+ status: "authenticated",
5478
+ error: null,
5479
+ refreshedAt: Date.now()
5480
+ });
5481
+ return nextState;
5482
+ })().catch((error) => {
5483
+ const runtimeError = normalizeRuntimeError(error);
5484
+ applyAccessToken(null);
5485
+ setAuthState({ status: "unauthenticated", error: runtimeError });
5486
+ throw runtimeError;
5487
+ }).finally(() => {
5488
+ refreshRequestRef.current = null;
5489
+ });
5490
+ }
5491
+ return refreshRequestRef.current;
5492
+ }, [applyAccessToken, resolvedAppType, resolvedFetch, servicePrefix]);
5388
5493
  const authorizedFetch = (0, import_react14.useMemo)(
5389
- () => createAuthorizedFetch(resolvedFetch, accessToken),
5390
- [accessToken, resolvedFetch]
5494
+ () => createAuthorizedFetch({
5495
+ appType: resolvedAppType,
5496
+ baseFetch: resolvedFetch,
5497
+ accessTokenRef,
5498
+ markUnauthenticated,
5499
+ refreshAccessToken
5500
+ }),
5501
+ [markUnauthenticated, refreshAccessToken, resolvedAppType, resolvedFetch]
5391
5502
  );
5392
5503
  const getAuthHeaders = (0, import_react14.useCallback)(() => {
5393
5504
  const state2 = accessTokenRef.current;
@@ -5413,13 +5524,13 @@ var OpenXiangdaProvider = ({
5413
5524
  return;
5414
5525
  }
5415
5526
  setState((prev) => ({ ...prev, loading: true, error: null }));
5416
- const requestFetch = options.accessToken !== void 0 ? createAuthorizedFetch(
5527
+ const requestFetch = options.accessToken !== void 0 ? createAccessTokenFetch(
5417
5528
  resolvedFetch,
5418
5529
  createAccessTokenState(
5419
5530
  options.accessToken || null,
5420
5531
  options.accessTokenOptions
5421
5532
  )
5422
- ) : createAuthorizedFetch(resolvedFetch, accessTokenRef.current);
5533
+ ) : authorizedFetch;
5423
5534
  try {
5424
5535
  const response = await requestFetch(
5425
5536
  buildServiceUrl2(
@@ -5441,19 +5552,29 @@ var OpenXiangdaProvider = ({
5441
5552
  payload?.message || `Runtime bootstrap failed: ${response.status}`
5442
5553
  );
5443
5554
  }
5555
+ const nextData = payload?.data || null;
5444
5556
  setState({
5445
- data: payload?.data || null,
5557
+ data: nextData,
5446
5558
  loading: false,
5447
5559
  error: null
5448
5560
  });
5561
+ if (isAuthenticatedBootstrap(nextData)) {
5562
+ setAuthState(
5563
+ (prev) => prev.status === "authenticated" ? prev : { status: "authenticated", error: null }
5564
+ );
5565
+ }
5449
5566
  } catch (error) {
5567
+ const runtimeError = normalizeRuntimeError(error);
5450
5568
  setState({
5451
5569
  data: null,
5452
5570
  loading: false,
5453
- error: normalizeRuntimeError(error)
5571
+ error: runtimeError
5454
5572
  });
5573
+ if (runtimeError.type === "unauthenticated") {
5574
+ setAuthState({ status: "unauthenticated", error: runtimeError });
5575
+ }
5455
5576
  }
5456
- }, [resolvedAppType, resolvedFetch, servicePrefix]);
5577
+ }, [authorizedFetch, resolvedAppType, resolvedFetch, servicePrefix]);
5457
5578
  (0, import_react14.useEffect)(() => {
5458
5579
  void reload();
5459
5580
  }, [reload]);
@@ -5464,6 +5585,7 @@ var OpenXiangdaProvider = ({
5464
5585
  servicePrefix,
5465
5586
  fetchImpl: authorizedFetch,
5466
5587
  baseFetchImpl: resolvedFetch,
5588
+ authState,
5467
5589
  getAuthHeaders,
5468
5590
  reload,
5469
5591
  setAccessToken
@@ -5475,7 +5597,8 @@ var OpenXiangdaProvider = ({
5475
5597
  resolvedAppType,
5476
5598
  resolvedFetch,
5477
5599
  servicePrefix,
5478
- state
5600
+ state,
5601
+ authState
5479
5602
  ]
5480
5603
  );
5481
5604
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(OpenXiangdaRuntimeContext.Provider, { value, children });
@@ -5742,7 +5865,7 @@ var useRuntimeAuth = () => {
5742
5865
  buildServiceUrl2(runtime.servicePrefix, "/api/sso/status"),
5743
5866
  { domain }
5744
5867
  );
5745
- const statusResponse = await runtime.fetchImpl(statusUrl, {
5868
+ const statusResponse = await runtime.baseFetchImpl(statusUrl, {
5746
5869
  credentials: "include",
5747
5870
  headers: { accept: "application/json" }
5748
5871
  });
@@ -5753,7 +5876,7 @@ var useRuntimeAuth = () => {
5753
5876
  enabled && (sso.forceLogin ?? sso.autoRedirect ?? true)
5754
5877
  );
5755
5878
  if (shouldUseSso) {
5756
- const loginUrlResponse = await runtime.fetchImpl(
5879
+ const loginUrlResponse = await runtime.baseFetchImpl(
5757
5880
  withQuery(buildServiceUrl2(runtime.servicePrefix, "/api/sso/login-url"), {
5758
5881
  domain,
5759
5882
  redirectUri,
@@ -5777,7 +5900,7 @@ var useRuntimeAuth = () => {
5777
5900
  }
5778
5901
  return attachCallback("/platform/login", redirectUri);
5779
5902
  },
5780
- [runtime.data?.app, runtime.fetchImpl, runtime.servicePrefix]
5903
+ [runtime.baseFetchImpl, runtime.data?.app, runtime.servicePrefix]
5781
5904
  );
5782
5905
  const redirectToLogin = (0, import_react14.useCallback)(
5783
5906
  async (options = {}) => {
@@ -5794,7 +5917,7 @@ var useRuntimeAuth = () => {
5794
5917
  [resolveLoginUrl2]
5795
5918
  );
5796
5919
  const logout = (0, import_react14.useCallback)(async () => {
5797
- const response = await runtime.fetchImpl(
5920
+ const response = await runtime.baseFetchImpl(
5798
5921
  buildServiceUrl2(runtime.servicePrefix, "/api/auth/logout"),
5799
5922
  {
5800
5923
  method: "POST",
@@ -5810,8 +5933,9 @@ var useRuntimeAuth = () => {
5810
5933
  payload?.message || `Logout failed: ${response.status}`
5811
5934
  );
5812
5935
  }
5936
+ runtime.setAccessToken(null);
5813
5937
  return payload;
5814
- }, [runtime.fetchImpl, runtime.servicePrefix]);
5938
+ }, [runtime.baseFetchImpl, runtime.servicePrefix, runtime.setAccessToken]);
5815
5939
  const logoutAndRedirect = (0, import_react14.useCallback)(
5816
5940
  async (options = {}) => {
5817
5941
  try {
@@ -5823,19 +5947,93 @@ var useRuntimeAuth = () => {
5823
5947
  },
5824
5948
  [logout, redirectToLogin]
5825
5949
  );
5826
- return {
5827
- logout,
5828
- logoutAndRedirect,
5829
- redirectToLogin,
5830
- resolveLoginUrl: resolveLoginUrl2
5831
- };
5950
+ return (0, import_react14.useMemo)(
5951
+ () => ({
5952
+ logout,
5953
+ logoutAndRedirect,
5954
+ redirectToLogin,
5955
+ resolveLoginUrl: resolveLoginUrl2
5956
+ }),
5957
+ [logout, logoutAndRedirect, redirectToLogin, resolveLoginUrl2]
5958
+ );
5959
+ };
5960
+ var RuntimeAuthGuard = ({
5961
+ children,
5962
+ fallback = null,
5963
+ disabled = false,
5964
+ excludedPaths = [],
5965
+ ...redirectOptions
5966
+ }) => {
5967
+ const runtime = useOpenXiangda();
5968
+ const auth = useRuntimeAuth();
5969
+ const redirectedRef = (0, import_react14.useRef)(false);
5970
+ const currentPath = getCurrentPathname();
5971
+ const excluded = isRuntimeAuthGuardExcluded(
5972
+ currentPath,
5973
+ runtime.appType,
5974
+ excludedPaths
5975
+ );
5976
+ const shouldRedirect = !disabled && !excluded && !runtime.loading && (runtime.authState.status === "unauthenticated" || runtime.error?.type === "unauthenticated");
5977
+ (0, import_react14.useEffect)(() => {
5978
+ if (!shouldRedirect || redirectedRef.current) return;
5979
+ redirectedRef.current = true;
5980
+ void auth.redirectToLogin({
5981
+ ...redirectOptions,
5982
+ redirectUri: redirectOptions.redirectUri || getCurrentHref4()
5983
+ }).catch(() => {
5984
+ redirectedRef.current = false;
5985
+ });
5986
+ }, [
5987
+ auth,
5988
+ redirectOptions.domain,
5989
+ redirectOptions.loginUrl,
5990
+ redirectOptions.redirectUri,
5991
+ redirectOptions.replace,
5992
+ shouldRedirect
5993
+ ]);
5994
+ if (excluded) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children });
5995
+ if (shouldRedirect) return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: fallback });
5996
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children });
5832
5997
  };
5833
5998
  var buildServiceUrl2 = (servicePrefix, path) => {
5834
5999
  const prefix = servicePrefix.endsWith("/") ? servicePrefix.slice(0, -1) : servicePrefix;
5835
6000
  const suffix = path.startsWith("/") ? path : `/${path}`;
5836
6001
  return `${prefix}${suffix}`;
5837
6002
  };
5838
- var createAuthorizedFetch = (baseFetch, accessToken) => {
6003
+ var createAuthorizedFetch = ({
6004
+ appType,
6005
+ baseFetch,
6006
+ accessTokenRef,
6007
+ refreshAccessToken,
6008
+ markUnauthenticated
6009
+ }) => (async (input, init = {}) => {
6010
+ const currentTokenState = accessTokenRef.current;
6011
+ const response = await createAccessTokenFetch(
6012
+ baseFetch,
6013
+ currentTokenState
6014
+ )(input, init);
6015
+ if (!shouldAttemptAuthRefresh({
6016
+ appType,
6017
+ input,
6018
+ init,
6019
+ tokenState: currentTokenState
6020
+ })) {
6021
+ return response;
6022
+ }
6023
+ if (!await isUnauthenticatedResponse(response)) return response;
6024
+ try {
6025
+ const refreshedTokenState = await refreshAccessToken();
6026
+ if (!refreshedTokenState) return response;
6027
+ return await createAccessTokenFetch(
6028
+ baseFetch,
6029
+ refreshedTokenState
6030
+ )(input, init);
6031
+ } catch (error) {
6032
+ markUnauthenticated(error);
6033
+ return response;
6034
+ }
6035
+ });
6036
+ var createAccessTokenFetch = (baseFetch, accessToken) => {
5839
6037
  if (!accessToken) return baseFetch;
5840
6038
  return ((input, init = {}) => {
5841
6039
  const token = resolveAccessTokenForCurrentRoute(accessToken);
@@ -5853,8 +6051,53 @@ var createAuthorizedFetch = (baseFetch, accessToken) => {
5853
6051
  var createAccessTokenState = (accessToken, options = {}) => accessToken ? {
5854
6052
  token: accessToken,
5855
6053
  scope: options.scope || "default",
5856
- path: options.path || null
6054
+ path: options.path || null,
6055
+ expiresAt: options.expiresAt
5857
6056
  } : null;
6057
+ var shouldAttemptAuthRefresh = ({
6058
+ appType,
6059
+ input,
6060
+ init,
6061
+ tokenState
6062
+ }) => {
6063
+ if (!appType) return false;
6064
+ if (tokenState?.scope === "public") return false;
6065
+ if (isPublicRoutePath(normalizeRoutePath(getCurrentPathname()))) return false;
6066
+ if (hasAuthorizationHeader(init?.headers)) return false;
6067
+ const pathname = getFetchInputPathname(input);
6068
+ if (!pathname) return false;
6069
+ if (/\/openxiangda-api\/v1\/apps\/[^/]+\/auth(?:\/|$)/.test(pathname)) {
6070
+ return false;
6071
+ }
6072
+ if (/\/openxiangda-api\/v1\/apps\/[^/]+\/public\/session(?:\/|$)/.test(pathname)) {
6073
+ return false;
6074
+ }
6075
+ if (/\/openxiangda-api\/v1\/auth(?:\/|$)/.test(pathname)) return false;
6076
+ if (/\/api\/auth\/logout(?:\/|$)/.test(pathname)) return false;
6077
+ if (/\/api\/sso(?:\/|$)/.test(pathname)) return false;
6078
+ return true;
6079
+ };
6080
+ var isUnauthenticatedResponse = async (response) => {
6081
+ if (response.status === 401) return true;
6082
+ const payload = await readJsonPayload(response.clone());
6083
+ const code = getRecordValue2(payload, "code");
6084
+ return classifyRuntimeError(response.status, code) === "unauthenticated";
6085
+ };
6086
+ var hasAuthorizationHeader = (headers) => {
6087
+ if (!headers) return false;
6088
+ const normalized = new Headers(headers);
6089
+ return normalized.has("authorization");
6090
+ };
6091
+ var getFetchInputPathname = (input) => {
6092
+ const text = typeof input === "string" ? input : input instanceof URL ? input.toString() : typeof Request !== "undefined" && input instanceof Request ? input.url : String(input || "");
6093
+ if (!text) return "";
6094
+ try {
6095
+ const base = typeof window !== "undefined" ? window.location.origin : "http://localhost";
6096
+ return new URL(text, base).pathname;
6097
+ } catch {
6098
+ return text.split(/[?#]/, 1)[0] || "";
6099
+ }
6100
+ };
5858
6101
  var resolveAccessTokenForCurrentRoute = (accessToken) => {
5859
6102
  if (accessToken.scope !== "public") return accessToken.token;
5860
6103
  const scopedPath = normalizeRoutePath(accessToken.path);
@@ -6039,6 +6282,31 @@ var getRecordString = (value, key) => {
6039
6282
  const result = getRecordValue2(value, key);
6040
6283
  return typeof result === "string" ? result : void 0;
6041
6284
  };
6285
+ var getRecordNumber = (value, key) => {
6286
+ const result = getRecordValue2(value, key);
6287
+ const numberValue = Number(result);
6288
+ return Number.isFinite(numberValue) ? numberValue : void 0;
6289
+ };
6290
+ var unwrapRuntimePayload = (payload) => {
6291
+ if (!payload || typeof payload !== "object") return payload;
6292
+ const record = payload;
6293
+ return "data" in record ? record.data : payload;
6294
+ };
6295
+ var isAuthenticatedBootstrap = (value) => {
6296
+ if (!value || typeof value !== "object") return false;
6297
+ const user = toRuntimeRecord(getRecordValue2(value, "user"));
6298
+ if (!getRecordValue2(user, "id") && !getRecordValue2(user, "username")) {
6299
+ return false;
6300
+ }
6301
+ return !getRecordValue2(user, "publicAccess");
6302
+ };
6303
+ var isRuntimeAuthGuardExcluded = (path, appType, excludedPaths) => {
6304
+ const normalizedPath = normalizeRoutePath(path);
6305
+ if (isPublicRoutePath(normalizedPath)) return true;
6306
+ const loginPath = normalizeRoutePath(`/view/${encodeURIComponent(appType)}/login`);
6307
+ if (appType && normalizedPath === loginPath) return true;
6308
+ return excludedPaths.map((item) => normalizeRoutePath(item)).some((item) => item && normalizedPath === item);
6309
+ };
6042
6310
  var resolveAppTypeFromLocation = () => {
6043
6311
  if (typeof window === "undefined") return "";
6044
6312
  const segments = window.location.pathname.split("/").filter(Boolean);