@byline/host-tanstack-start 5.1.2 → 5.1.3

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 (31) hide show
  1. package/dist/admin-shell/chrome/sign-in-page.js +76 -34
  2. package/dist/admin-shell/chrome/sign-in-page.module.js +6 -1
  3. package/dist/admin-shell/chrome/sign-in-page.test.d.ts +8 -0
  4. package/dist/admin-shell/chrome/sign-in-page_module.css +49 -0
  5. package/dist/integrations/admin-session-recovery.d.ts +10 -0
  6. package/dist/integrations/admin-session-recovery.js +33 -0
  7. package/dist/integrations/admin-session-recovery.test.d.ts +8 -0
  8. package/dist/integrations/session-renewal.d.ts +8 -0
  9. package/dist/integrations/session-renewal.js +16 -0
  10. package/dist/integrations/session-renewal.test.d.ts +8 -0
  11. package/dist/routes/admin-layout-guard.d.ts +20 -0
  12. package/dist/routes/admin-layout-guard.js +40 -0
  13. package/dist/routes/create-admin-layout-route.js +25 -35
  14. package/dist/server-fns/auth/current-user.d.ts +18 -2
  15. package/dist/server-fns/auth/current-user.js +32 -15
  16. package/dist/server-fns/auth/index.d.ts +1 -1
  17. package/dist/server-fns/auth/index.js +1 -1
  18. package/package.json +10 -10
  19. package/src/admin-shell/chrome/sign-in-page.module.css +59 -0
  20. package/src/admin-shell/chrome/sign-in-page.test.tsx +99 -0
  21. package/src/admin-shell/chrome/sign-in-page.tsx +81 -25
  22. package/src/integrations/admin-session-recovery.test.tsx +120 -0
  23. package/src/integrations/admin-session-recovery.tsx +72 -0
  24. package/src/integrations/session-renewal.test.tsx +72 -0
  25. package/src/integrations/session-renewal.ts +45 -0
  26. package/src/routes/admin-layout-guard.test.node.ts +136 -0
  27. package/src/routes/admin-layout-guard.ts +91 -0
  28. package/src/routes/create-admin-layout-route.tsx +33 -46
  29. package/src/server-fns/auth/current-user.test.node.ts +85 -0
  30. package/src/server-fns/auth/current-user.ts +49 -19
  31. package/src/server-fns/auth/index.ts +2 -0
@@ -3,12 +3,13 @@ import { useEffect, useState } from "react";
3
3
  import { SignInForm } from "@byline/admin/auth/components/sign-in-form";
4
4
  import { BylineAdminServicesProvider } from "@byline/admin/services";
5
5
  import { getAdminConfig } from "@byline/core";
6
- import { I18nProvider, LanguageMenu } from "@byline/i18n/react";
6
+ import { I18nProvider, LanguageMenu, useTranslation } from "@byline/i18n/react";
7
+ import { Alert, Button, Card, LoaderRing } from "@byline/ui/react";
7
8
  import clsx from "clsx";
8
9
  import { buildLocaleDefinitions } from "../../i18n/locale-definitions.js";
9
10
  import { bylineAdminServices } from "../../integrations/byline-admin-services.js";
10
- import { coordinateAuthAction, expectedSession, flagSessionChanged, observeSession, renewSingleFlight } from "../../integrations/session-coordination.js";
11
- import { renewAdminSession } from "../../server-fns/auth/renew.js";
11
+ import { flagSessionChanged } from "../../integrations/session-coordination.js";
12
+ import { renewExpectedSession } from "../../integrations/session-renewal.js";
12
13
  import { setAdminLocaleFn } from "../../server-fns/i18n/index.js";
13
14
  import { SessionChangeBoundary } from "./session-change-boundary.js";
14
15
  import sign_in_page_module from "./sign-in-page.module.js";
@@ -18,14 +19,7 @@ function SignInPage({ redirectTo, activeLocale, homeUrl, reauthenticate = false
18
19
  useEffect(()=>{
19
20
  if (reauthenticate) return;
20
21
  let active = true;
21
- renewSingleFlight(()=>coordinateAuthAction(async ()=>{
22
- const result = await renewAdminSession({
23
- data: {
24
- expectedSessionId: expectedSession() ?? void 0
25
- }
26
- });
27
- observeSession(result.sessionId);
28
- })).then(()=>{
22
+ renewExpectedSession().then(()=>{
29
23
  if (active) window.location.replace(redirectTo);
30
24
  }).catch((error)=>{
31
25
  if (error?.code === 'ERR_SESSION_CHANGED') flagSessionChanged();
@@ -66,34 +60,19 @@ function SignInPage({ redirectTo, activeLocale, homeUrl, reauthenticate = false
66
60
  children: /*#__PURE__*/ jsxs("main", {
67
61
  className: clsx('byline-sign-in-page', sign_in_page_module.main),
68
62
  children: [
69
- /*#__PURE__*/ jsx("noscript", {
70
- children: "JavaScript is required to sign in or renew your admin session."
71
- }),
72
63
  /*#__PURE__*/ jsx("div", {
73
64
  className: clsx('byline-sign-in-page-bar', sign_in_page_module.bar),
74
65
  children: /*#__PURE__*/ jsx(LanguageMenu, {})
75
66
  }),
76
- /*#__PURE__*/ jsx("div", {
67
+ /*#__PURE__*/ jsxs("div", {
77
68
  className: clsx('byline-sign-in-page-inner', sign_in_page_module.inner),
78
- children: bootstrapError ? /*#__PURE__*/ jsxs("div", {
79
- role: "alert",
80
- children: [
81
- /*#__PURE__*/ jsx("p", {
82
- children: "Unable to check your session. Please try again."
83
- }),
84
- /*#__PURE__*/ jsx("button", {
85
- type: "button",
86
- onClick: ()=>window.location.reload(),
87
- children: "Try again"
88
- })
89
- ]
90
- }) : checking ? /*#__PURE__*/ jsx("p", {
91
- role: "status",
92
- children: "Checking your session…"
93
- }) : /*#__PURE__*/ jsx(SignInForm, {
94
- redirectTo: redirectTo,
95
- homeUrl: homeUrl
96
- })
69
+ children: [
70
+ /*#__PURE__*/ jsx(NoScriptNotice, {}),
71
+ bootstrapError ? /*#__PURE__*/ jsx(SessionCheckError, {}) : checking ? /*#__PURE__*/ jsx(SessionCheckIndicator, {}) : /*#__PURE__*/ jsx(SignInForm, {
72
+ redirectTo: redirectTo,
73
+ homeUrl: homeUrl
74
+ })
75
+ ]
97
76
  })
98
77
  ]
99
78
  })
@@ -103,4 +82,67 @@ function SignInPage({ redirectTo, activeLocale, homeUrl, reauthenticate = false
103
82
  children: content
104
83
  });
105
84
  }
85
+ function SessionCheckIndicator() {
86
+ const { t } = useTranslation('byline-admin');
87
+ return /*#__PURE__*/ jsxs("div", {
88
+ role: "status",
89
+ "aria-live": "polite",
90
+ className: clsx('byline-sign-in-page-checking', sign_in_page_module.checking),
91
+ children: [
92
+ /*#__PURE__*/ jsx(LoaderRing, {
93
+ size: 32,
94
+ "aria-hidden": "true"
95
+ }),
96
+ /*#__PURE__*/ jsx("span", {
97
+ className: sign_in_page_module.srOnly,
98
+ children: t('common.loading')
99
+ })
100
+ ]
101
+ });
102
+ }
103
+ function NoScriptNotice() {
104
+ const { t } = useTranslation('byline-admin');
105
+ return /*#__PURE__*/ jsx("noscript", {
106
+ children: /*#__PURE__*/ jsx(Alert, {
107
+ intent: "warning",
108
+ close: false,
109
+ className: clsx('byline-sign-in-page-alert', sign_in_page_module.alert),
110
+ children: t("auth.signIn.noscript")
111
+ })
112
+ });
113
+ }
114
+ function SessionCheckError() {
115
+ const { t } = useTranslation('byline-admin');
116
+ return /*#__PURE__*/ jsxs(Card, {
117
+ role: "alert",
118
+ className: clsx('byline-sign-in-page-card', sign_in_page_module.card),
119
+ children: [
120
+ /*#__PURE__*/ jsxs(Card.Header, {
121
+ children: [
122
+ /*#__PURE__*/ jsx(Card.Title, {
123
+ children: /*#__PURE__*/ jsx("h2", {
124
+ children: t('auth.signIn.title')
125
+ })
126
+ }),
127
+ /*#__PURE__*/ jsx(Alert, {
128
+ intent: "danger",
129
+ close: false,
130
+ className: clsx('byline-sign-in-page-alert', sign_in_page_module.alert),
131
+ children: t('auth.signIn.errors.bootstrap')
132
+ })
133
+ ]
134
+ }),
135
+ /*#__PURE__*/ jsx(Card.Content, {
136
+ children: /*#__PURE__*/ jsx("div", {
137
+ className: clsx('byline-sign-in-page-actions', sign_in_page_module.actions),
138
+ children: /*#__PURE__*/ jsx(Button, {
139
+ type: "button",
140
+ onClick: ()=>window.location.reload(),
141
+ children: t('common.actions.tryAgain')
142
+ })
143
+ })
144
+ })
145
+ ]
146
+ });
147
+ }
106
148
  export { SignInPage };
@@ -2,6 +2,11 @@ import "./sign-in-page_module.css";
2
2
  const sign_in_page_module = {
3
3
  main: "main-Mz7TWj",
4
4
  bar: "bar-uO4D8B",
5
- inner: "inner-iPyHJh"
5
+ inner: "inner-iPyHJh",
6
+ checking: "checking-GccoM3",
7
+ srOnly: "srOnly-zH8cgE",
8
+ card: "card-TwlYhw",
9
+ alert: "alert-mFCzan",
10
+ actions: "actions-d9wIp6"
6
11
  };
7
12
  export default sign_in_page_module;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export {};
@@ -27,3 +27,52 @@
27
27
  }
28
28
  }
29
29
 
30
+ :is(.checking-GccoM3, .byline-sign-in-page-checking) {
31
+ color: inherit;
32
+ opacity: .6;
33
+ justify-content: center;
34
+ align-items: center;
35
+ padding: 2rem 0;
36
+ display: flex;
37
+ }
38
+
39
+ .srOnly-zH8cgE {
40
+ clip: rect(0, 0, 0, 0);
41
+ white-space: nowrap;
42
+ border: 0;
43
+ width: 1px;
44
+ height: 1px;
45
+ margin: -1px;
46
+ padding: 0;
47
+ position: absolute;
48
+ overflow: hidden;
49
+ }
50
+
51
+ :is(.card-TwlYhw, .byline-sign-in-page-card) {
52
+ width: 100%;
53
+ }
54
+
55
+ @media (min-width: 40rem) {
56
+ :is(.card-TwlYhw, .byline-sign-in-page-card) {
57
+ max-width: 380px;
58
+ }
59
+ }
60
+
61
+ :is(.alert-mFCzan, .byline-sign-in-page-alert) {
62
+ width: 100%;
63
+ margin-top: var(--spacing-12);
64
+ }
65
+
66
+ @media (min-width: 40rem) {
67
+ :is(noscript > .alert-mFCzan, noscript > .byline-sign-in-page-alert) {
68
+ max-width: 380px;
69
+ }
70
+ }
71
+
72
+ :is(.actions-d9wIp6, .byline-sign-in-page-actions) {
73
+ padding-top: var(--spacing-8);
74
+ margin-bottom: var(--spacing-8);
75
+ justify-content: flex-end;
76
+ display: flex;
77
+ }
78
+
@@ -0,0 +1,10 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export declare function AdminSessionRecovery({ renewable }: {
9
+ renewable: boolean;
10
+ }): null;
@@ -0,0 +1,33 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useRouter, useRouterState } from "@tanstack/react-router";
3
+ import { renewExpectedSession } from "./session-renewal.js";
4
+ const TERMINAL_CODES = new Set([
5
+ 'ERR_UNAUTHENTICATED',
6
+ 'ERR_INVALID_TOKEN',
7
+ 'ERR_REVOKED_TOKEN',
8
+ 'ERR_ACCOUNT_DISABLED',
9
+ 'ERR_SESSION_CHANGED'
10
+ ]);
11
+ let suppressed = false;
12
+ function AdminSessionRecovery({ renewable }) {
13
+ const router = useRouter();
14
+ const href = useRouterState({
15
+ select: (state)=>state.location.href
16
+ });
17
+ const inFlight = useRef(false);
18
+ useEffect(()=>{
19
+ if (!renewable || inFlight.current || suppressed) return;
20
+ inFlight.current = true;
21
+ renewExpectedSession().then(()=>router.invalidate()).catch((error)=>{
22
+ if (TERMINAL_CODES.has(error?.code ?? '')) suppressed = true;
23
+ }).finally(()=>{
24
+ inFlight.current = false;
25
+ });
26
+ }, [
27
+ renewable,
28
+ href,
29
+ router
30
+ ]);
31
+ return null;
32
+ }
33
+ export { AdminSessionRecovery };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export {};
@@ -0,0 +1,8 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export declare function renewExpectedSession(): Promise<void>;
@@ -0,0 +1,16 @@
1
+ import { renewAdminSession } from "../server-fns/auth/renew.js";
2
+ import { coordinateAuthAction, expectedSession, flagSessionChanged, observeSession, renewSingleFlight } from "./session-coordination.js";
3
+ function renewExpectedSession() {
4
+ return renewSingleFlight(()=>coordinateAuthAction(async ()=>{
5
+ const result = await renewAdminSession({
6
+ data: {
7
+ expectedSessionId: expectedSession() ?? void 0
8
+ }
9
+ }).catch((error)=>{
10
+ if (error?.code === 'ERR_SESSION_CHANGED') flagSessionChanged();
11
+ throw error;
12
+ });
13
+ observeSession(result.sessionId);
14
+ }));
15
+ }
16
+ export { renewExpectedSession };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export {};
@@ -0,0 +1,20 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ export interface AdminLayoutGuardDeps<T> {
9
+ /** Load the full layout context; expected to verify the access credential first. */
10
+ load: () => Promise<T>;
11
+ /** Coordinated browser renewal. Resolves on success and rejects on any failure. */
12
+ renewSession: () => Promise<void>;
13
+ isBrowser: boolean;
14
+ /** True when the page has already flagged a session change awaiting acknowledgement. */
15
+ sessionChanged: () => boolean;
16
+ signInPath: string;
17
+ /** The requested location, preserved as `callbackUrl` on redirect. */
18
+ href: string;
19
+ }
20
+ export declare function loadAdminLayoutContext<T>(deps: AdminLayoutGuardDeps<T>): Promise<T>;
@@ -0,0 +1,40 @@
1
+ import { redirect } from "@tanstack/react-router";
2
+ const AUTH_REDIRECT_CODES = new Set([
3
+ 'ERR_UNAUTHENTICATED',
4
+ 'ERR_ACCESS_EXPIRED',
5
+ 'ERR_INVALID_TOKEN',
6
+ 'ERR_REVOKED_TOKEN',
7
+ 'ERR_ACCOUNT_DISABLED',
8
+ 'ERR_SESSION_CHANGED'
9
+ ]);
10
+ async function loadAdminLayoutContext(deps) {
11
+ const toSignIn = ()=>redirect({
12
+ to: deps.signInPath,
13
+ search: {
14
+ callbackUrl: deps.href
15
+ }
16
+ });
17
+ try {
18
+ return await deps.load();
19
+ } catch (error) {
20
+ const code = errorCode(error);
21
+ const changed = deps.isBrowser && deps.sessionChanged();
22
+ if (!changed && !AUTH_REDIRECT_CODES.has(code)) throw error;
23
+ if (changed || !deps.isBrowser || 'ERR_ACCESS_EXPIRED' !== code) throw toSignIn();
24
+ }
25
+ try {
26
+ await deps.renewSession();
27
+ } catch {
28
+ throw toSignIn();
29
+ }
30
+ try {
31
+ return await deps.load();
32
+ } catch (error) {
33
+ if (AUTH_REDIRECT_CODES.has(errorCode(error))) throw toSignIn();
34
+ throw error;
35
+ }
36
+ }
37
+ function errorCode(error) {
38
+ return error?.code;
39
+ }
40
+ export { loadAdminLayoutContext };
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useEffect } from "react";
3
- import { Outlet, createFileRoute, redirect } from "@tanstack/react-router";
3
+ import { Outlet, createFileRoute } from "@tanstack/react-router";
4
4
  import { applyStoredTheme } from "@byline/admin/admin-account/components/theme";
5
5
  import { BylineFieldServicesProvider } from "@byline/admin/react";
6
6
  import { BylineAdminServicesProvider } from "@byline/admin/services";
@@ -20,47 +20,37 @@ import { bylineAdminServices } from "../integrations/byline-admin-services.js";
20
20
  import { BylineAiAdminProvider } from "../integrations/byline-ai.js";
21
21
  import { bylineFieldServices } from "../integrations/byline-field-services.js";
22
22
  import { observeSession, sessionIsChanged } from "../integrations/session-coordination.js";
23
+ import { renewExpectedSession } from "../integrations/session-renewal.js";
23
24
  import { getAnalyticsRuntime } from "../server-fns/analytics/index.js";
24
25
  import { getCurrentAdminUser } from "../server-fns/auth/index.js";
25
26
  import { getScheduledPublicationRuntime } from "../server-fns/collections/index.js";
26
27
  import { getActiveLocaleFn, setAdminLocaleFn } from "../server-fns/i18n/index.js";
28
+ import { loadAdminLayoutContext } from "./admin-layout-guard.js";
27
29
  import { getSignInRoutePath } from "./sign-in-path.js";
28
30
  function createAdminLayoutRoute(path) {
29
31
  const Route = createFileRoute(path)({
30
- beforeLoad: async ({ location })=>{
31
- const signInPath = getSignInRoutePath();
32
- try {
33
- const user = await getCurrentAdminUser();
34
- if ("u" > typeof window) observeSession(user.sessionId);
35
- const [activeLocale, scheduledPublicationRuntime, analyticsRuntime] = await Promise.all([
36
- getActiveLocaleFn(),
37
- getScheduledPublicationRuntime(),
38
- getAnalyticsRuntime()
39
- ]);
40
- return {
41
- user,
42
- activeLocale,
43
- scheduledPublicationRuntime,
44
- analyticsRuntime
45
- };
46
- } catch (error) {
47
- const code = error?.code;
48
- if (!("u" > typeof window && sessionIsChanged()) && ![
49
- 'ERR_UNAUTHENTICATED',
50
- 'ERR_ACCESS_EXPIRED',
51
- 'ERR_INVALID_TOKEN',
52
- 'ERR_REVOKED_TOKEN',
53
- 'ERR_ACCOUNT_DISABLED',
54
- 'ERR_SESSION_CHANGED'
55
- ].includes(code ?? '')) throw error;
56
- throw redirect({
57
- to: signInPath,
58
- search: {
59
- callbackUrl: location.href
60
- }
61
- });
62
- }
63
- },
32
+ beforeLoad: ({ location })=>loadAdminLayoutContext({
33
+ load: async ()=>{
34
+ const user = await getCurrentAdminUser();
35
+ if ("u" > typeof window) observeSession(user.sessionId);
36
+ const [activeLocale, scheduledPublicationRuntime, analyticsRuntime] = await Promise.all([
37
+ getActiveLocaleFn(),
38
+ getScheduledPublicationRuntime(),
39
+ getAnalyticsRuntime()
40
+ ]);
41
+ return {
42
+ user,
43
+ activeLocale,
44
+ scheduledPublicationRuntime,
45
+ analyticsRuntime
46
+ };
47
+ },
48
+ renewSession: renewExpectedSession,
49
+ isBrowser: "u" > typeof window,
50
+ sessionChanged: sessionIsChanged,
51
+ signInPath: getSignInRoutePath(),
52
+ href: location.href
53
+ }),
64
54
  component: function() {
65
55
  const { user, activeLocale, scheduledPublicationRuntime, analyticsRuntime } = Route.useRouteContext();
66
56
  const { i18n } = getAdminConfig();
@@ -28,11 +28,27 @@ export interface CurrentAdminUser {
28
28
  }
29
29
  export declare const getCurrentAdminUser: import("@tanstack/react-start").OptionalFetcher<undefined, undefined, Promise<CurrentAdminUser>>;
30
30
  /**
31
- * Soft variant for public-page consumers (e.g. the content admin bar that
32
- * renders only when the visitor is signed in). Returns `null` instead of
31
+ * Soft variants for public-page consumers (e.g. the content admin bar that
32
+ * renders only when the visitor is signed in). Return `null` instead of
33
33
  * throwing when there is no session — anonymous visitors are the common
34
34
  * case here, so a thrown error would be wrong shape for a public route
35
35
  * loader. Other failure modes (DB unavailable, session points to a
36
36
  * deleted admin) are still coerced to `null` so the bar simply hides.
37
+ *
38
+ * `getCurrentAdminSessionSoft` additionally reports `renewable`: true when
39
+ * the access credential has expired but a refresh credential is present.
40
+ * Public reads never rotate credentials, so without this hint a signed-in
41
+ * editor's admin bar and preview mode silently vanish after the access
42
+ * lifetime. The host's `AdminSessionRecovery` component consumes it to run
43
+ * the coordinated browser renewal and re-run the page's loaders.
44
+ *
45
+ * `getCurrentAdminUserSoft` keeps the original `CurrentAdminUser | null`
46
+ * shape for existing integrations.
37
47
  */
48
+ export interface CurrentAdminSession {
49
+ user: CurrentAdminUser | null;
50
+ /** Access expired with a refresh credential present: the browser may renew and reload data. */
51
+ renewable: boolean;
52
+ }
53
+ export declare const getCurrentAdminSessionSoft: import("@tanstack/react-start").OptionalFetcher<undefined, undefined, Promise<CurrentAdminSession>>;
38
54
  export declare const getCurrentAdminUserSoft: import("@tanstack/react-start").OptionalFetcher<undefined, undefined, Promise<CurrentAdminUser | null>>;
@@ -1,6 +1,6 @@
1
1
  import { createServerFn } from "@tanstack/react-start";
2
2
  import { AuthError } from "@byline/auth";
3
- import { getAdminRequestContext } from "@byline/client/server";
3
+ import { getAdminRequestContext, readRefreshTokenCookie } from "@byline/client/server";
4
4
  import { bylineCore } from "../../integrations/byline-core.js";
5
5
  const getCurrentAdminUser = createServerFn({
6
6
  method: 'GET'
@@ -19,32 +19,49 @@ const getCurrentAdminUser = createServerFn({
19
19
  abilities: Array.from(actor.abilities)
20
20
  };
21
21
  });
22
+ const getCurrentAdminSessionSoft = createServerFn({
23
+ method: 'GET'
24
+ }).handler(async ()=>resolveSoftSession());
22
25
  const getCurrentAdminUserSoft = createServerFn({
23
26
  method: 'GET'
24
- }).handler(async ()=>{
27
+ }).handler(async ()=>(await resolveSoftSession()).user);
28
+ const anonymous = {
29
+ user: null,
30
+ renewable: false
31
+ };
32
+ async function resolveSoftSession() {
25
33
  try {
26
34
  let actor;
27
35
  let sessionId;
28
36
  try {
29
37
  ({ actor, sessionId } = await getAdminRequestContext());
30
38
  } catch (err) {
31
- if (err instanceof AuthError) return null;
39
+ if (err instanceof AuthError) {
40
+ const renewable = 'ERR_ACCESS_EXPIRED' === err.code && null != readRefreshTokenCookie();
41
+ return {
42
+ user: null,
43
+ renewable
44
+ };
45
+ }
32
46
  throw err;
33
47
  }
34
- if (!actor) return null;
48
+ if (!actor) return anonymous;
35
49
  const row = await bylineCore().adminStore?.adminUsers.getById(actor.id);
36
- if (!row) return null;
50
+ if (!row) return anonymous;
37
51
  return {
38
- sessionId: sessionId,
39
- id: row.id,
40
- email: row.email,
41
- given_name: row.given_name,
42
- family_name: row.family_name,
43
- is_super_admin: row.is_super_admin,
44
- abilities: Array.from(actor.abilities)
52
+ user: {
53
+ sessionId: sessionId,
54
+ id: row.id,
55
+ email: row.email,
56
+ given_name: row.given_name,
57
+ family_name: row.family_name,
58
+ is_super_admin: row.is_super_admin,
59
+ abilities: Array.from(actor.abilities)
60
+ },
61
+ renewable: false
45
62
  };
46
63
  } catch {
47
- return null;
64
+ return anonymous;
48
65
  }
49
- });
50
- export { getCurrentAdminUser, getCurrentAdminUserSoft };
66
+ }
67
+ export { getCurrentAdminSessionSoft, getCurrentAdminUser, getCurrentAdminUserSoft };
@@ -5,6 +5,6 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- export { type CurrentAdminUser, getCurrentAdminUser, getCurrentAdminUserSoft, } from './current-user.js';
8
+ export { type CurrentAdminSession, type CurrentAdminUser, getCurrentAdminSessionSoft, getCurrentAdminUser, getCurrentAdminUserSoft, } from './current-user.js';
9
9
  export { adminSignIn, type SignInInput, type SignInResult } from './sign-in.js';
10
10
  export { adminSignOut } from './sign-out.js';
@@ -1,3 +1,3 @@
1
- export { getCurrentAdminUser, getCurrentAdminUserSoft } from "./current-user.js";
1
+ export { getCurrentAdminSessionSoft, getCurrentAdminUser, getCurrentAdminUserSoft } from "./current-user.js";
2
2
  export { adminSignIn } from "./sign-in.js";
3
3
  export { adminSignOut } from "./sign-out.js";
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "license": "MPL-2.0",
6
- "version": "5.1.2",
6
+ "version": "5.1.3",
7
7
  "engines": {
8
8
  "node": ">=20.9.0"
9
9
  },
@@ -125,15 +125,15 @@
125
125
  "react-json-view-lite": "^2.5.0",
126
126
  "react-swipeable": "^7.0.2",
127
127
  "zod": "^4.5.4",
128
- "@byline/admin": "5.1.2",
129
- "@byline/analytics": "5.1.2",
130
- "@byline/analytics-agent": "5.1.2",
131
- "@byline/auth": "5.1.2",
132
- "@byline/client": "5.1.2",
133
- "@byline/ai": "5.1.2",
134
- "@byline/i18n": "5.1.2",
135
- "@byline/core": "5.1.2",
136
- "@byline/ui": "5.1.2"
128
+ "@byline/admin": "5.1.3",
129
+ "@byline/analytics": "5.1.3",
130
+ "@byline/auth": "5.1.3",
131
+ "@byline/ui": "5.1.3",
132
+ "@byline/ai": "5.1.3",
133
+ "@byline/client": "5.1.3",
134
+ "@byline/i18n": "5.1.3",
135
+ "@byline/core": "5.1.3",
136
+ "@byline/analytics-agent": "5.1.3"
137
137
  },
138
138
  "peerDependencies": {
139
139
  "@tanstack/react-router": "^1.167.0",