@checkstack/auth-frontend 0.5.15 → 0.5.16

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @checkstack/auth-frontend
2
2
 
3
+ ## 0.5.16
4
+
5
+ ### Patch Changes
6
+
7
+ - e01945b: Reduce excessive /api/auth/get-session requests
8
+
9
+ - Enable better-auth's `cookieCache` on the server (5-minute TTL) so repeated session
10
+ checks verify a signed cookie instead of querying the database. Compatible with
11
+ horizontal scaling since validation uses the shared `BETTER_AUTH_SECRET`.
12
+
13
+ - Introduce a `SessionProvider` React context that fetches the session exactly once
14
+ at the top of the component tree. All 7+ components that previously called
15
+ `useSession()` independently now read from this shared context — eliminating
16
+ duplicate HTTP requests on every page load.
17
+
18
+ - Remove the `useAuthClient()` hook which created per-component better-auth client
19
+ instances via `useMemo`, causing separate nanostore atoms and independent fetches.
20
+ All imperative usages (signIn, signUp, resetPassword, etc.) now use the singleton
21
+ `getAuthClientLazy()` instead.
22
+
3
23
  ## 0.5.15
4
24
 
5
25
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@checkstack/auth-frontend",
3
- "version": "0.5.15",
3
+ "version": "0.5.16",
4
4
  "type": "module",
5
5
  "main": "src/index.tsx",
6
6
  "checkstack": {
@@ -19,7 +19,7 @@ import {
19
19
  AlertDescription,
20
20
  Checkbox,
21
21
  } from "@checkstack/ui";
22
- import { useAuthClient } from "../lib/auth-client";
22
+ import { getAuthClientLazy } from "../lib/auth-client";
23
23
 
24
24
  export const ChangePasswordPage = () => {
25
25
  const navigate = useNavigate();
@@ -32,7 +32,7 @@ export const ChangePasswordPage = () => {
32
32
  const [error, setError] = useState<string>();
33
33
  const [success, setSuccess] = useState(false);
34
34
  const [validationErrors, setValidationErrors] = useState<string[]>([]);
35
- const authClient = useAuthClient();
35
+ const authClient = getAuthClientLazy();
36
36
 
37
37
  // Validate new password on change
38
38
  useEffect(() => {
@@ -14,13 +14,13 @@ import {
14
14
  CardContent,
15
15
  CardFooter,
16
16
  } from "@checkstack/ui";
17
- import { useAuthClient } from "../lib/auth-client";
17
+ import { getAuthClientLazy } from "../lib/auth-client";
18
18
 
19
19
  export const ForgotPasswordPage = () => {
20
20
  const [email, setEmail] = useState("");
21
21
  const [loading, setLoading] = useState(false);
22
22
  const [submitted, setSubmitted] = useState(false);
23
- const authClient = useAuthClient();
23
+ const authClient = getAuthClientLazy();
24
24
 
25
25
  const handleSubmit = async (e: React.FormEvent) => {
26
26
  e.preventDefault();
@@ -39,7 +39,7 @@ import {
39
39
  import { authApiRef, EnabledAuthStrategy } from "../api";
40
40
  import { useEnabledStrategies } from "../hooks/useEnabledStrategies";
41
41
  import { useAccessRules } from "../hooks/useAccessRules";
42
- import { useAuthClient } from "../lib/auth-client";
42
+ import { getAuthClientLazy } from "../lib/auth-client";
43
43
  import { SocialProviderButton } from "./SocialProviderButton";
44
44
  import { useEffect } from "react";
45
45
 
@@ -417,7 +417,6 @@ export const LoginNavbarAction = () => {
417
417
  const authApi = useApi(authApiRef);
418
418
  const { data: session, isPending } = authApi.useSession();
419
419
  const { accessRules, loading: accessRulesLoading } = useAccessRules();
420
- const authClient = useAuthClient();
421
420
  const [hasCredentialAccount, setHasCredentialAccount] =
422
421
  useState<boolean>(false);
423
422
  const [credentialLoading, setCredentialLoading] = useState(true);
@@ -427,7 +426,7 @@ export const LoginNavbarAction = () => {
427
426
  setCredentialLoading(false);
428
427
  return;
429
428
  }
430
- authClient.listAccounts().then((result) => {
429
+ getAuthClientLazy().listAccounts().then((result) => {
431
430
  if (result.data) {
432
431
  const hasCredential = result.data.some(
433
432
  (account) => account.providerId === "credential",
@@ -436,7 +435,7 @@ export const LoginNavbarAction = () => {
436
435
  }
437
436
  setCredentialLoading(false);
438
437
  });
439
- }, [session?.user, authClient]);
438
+ }, [session?.user]);
440
439
 
441
440
  if (isPending || accessRulesLoading || credentialLoading) {
442
441
  return <div className="w-20 h-9 bg-muted animate-pulse rounded-full" />;
@@ -20,7 +20,7 @@ import {
20
20
  AlertTitle,
21
21
  AlertDescription,
22
22
  } from "@checkstack/ui";
23
- import { useAuthClient } from "../lib/auth-client";
23
+ import { getAuthClientLazy } from "../lib/auth-client";
24
24
 
25
25
  export const OnboardingPage = () => {
26
26
  const navigate = useNavigate();
@@ -36,7 +36,7 @@ export const OnboardingPage = () => {
36
36
  const authClient = usePluginClient(AuthApi);
37
37
  const completeOnboardingMutation =
38
38
  authClient.completeOnboarding.useMutation();
39
- const betterAuthClient = useAuthClient();
39
+ const betterAuthClient = getAuthClientLazy();
40
40
 
41
41
  // Check if onboarding is needed
42
42
  const { data: onboardingStatus, isLoading: checkingStatus } =
@@ -23,7 +23,7 @@ import {
23
23
  } from "@checkstack/ui";
24
24
  import { useEnabledStrategies } from "../hooks/useEnabledStrategies";
25
25
  import { SocialProviderButton } from "./SocialProviderButton";
26
- import { useAuthClient } from "../lib/auth-client";
26
+ import { getAuthClientLazy } from "../lib/auth-client";
27
27
 
28
28
  export const RegisterPage = () => {
29
29
  const [name, setName] = useState("");
@@ -35,7 +35,7 @@ export const RegisterPage = () => {
35
35
  const authApi = useApi(authApiRef);
36
36
  const authClient = usePluginClient(AuthApi);
37
37
  const { strategies, loading: strategiesLoading } = useEnabledStrategies();
38
- const authBetterClient = useAuthClient();
38
+ const authBetterClient = getAuthClientLazy();
39
39
 
40
40
  // Validate password on change
41
41
  useEffect(() => {
@@ -19,7 +19,7 @@ import {
19
19
  AlertTitle,
20
20
  AlertDescription,
21
21
  } from "@checkstack/ui";
22
- import { useAuthClient } from "../lib/auth-client";
22
+ import { getAuthClientLazy } from "../lib/auth-client";
23
23
 
24
24
  export const ResetPasswordPage = () => {
25
25
  const [searchParams] = useSearchParams();
@@ -32,7 +32,7 @@ export const ResetPasswordPage = () => {
32
32
  const [error, setError] = useState<string>();
33
33
  const [success, setSuccess] = useState(false);
34
34
  const [validationErrors, setValidationErrors] = useState<string[]>([]);
35
- const authClient = useAuthClient();
35
+ const authClient = getAuthClientLazy();
36
36
 
37
37
  // Validate password on change
38
38
  useEffect(() => {
@@ -1,12 +1,11 @@
1
- import { usePluginClient } from "@checkstack/frontend-api";
1
+ import { useApi, usePluginClient } from "@checkstack/frontend-api";
2
2
  import { AuthApi } from "@checkstack/auth-common";
3
- import { useAuthClient } from "../lib/auth-client";
3
+ import { authApiRef } from "../api";
4
4
 
5
5
  export const useAccessRules = () => {
6
- const authBetterClient = useAuthClient();
6
+ const authApi = useApi(authApiRef);
7
7
  const authClient = usePluginClient(AuthApi);
8
- const { data: session, isPending: sessionPending } =
9
- authBetterClient.useSession();
8
+ const { data: session, isPending: sessionPending } = authApi.useSession();
10
9
 
11
10
  // Query: Fetch access rules (only when user is authenticated)
12
11
  const { data, isLoading } = authClient.accessRules.useQuery(
package/src/index.tsx CHANGED
@@ -24,6 +24,7 @@ import { ProfilePage } from "./components/ProfilePage";
24
24
  import { authApiRef, AuthApi, AuthSession } from "./api";
25
25
  import { getAuthClientLazy } from "./lib/auth-client";
26
26
  import { AuthAccessApi } from "./lib/AuthAccessApi";
27
+ import { useSessionContext } from "./lib/SessionProvider";
27
28
 
28
29
  import { useNavigate } from "react-router-dom";
29
30
  import { Settings2, User } from "lucide-react";
@@ -107,12 +108,7 @@ class BetterAuthApi implements AuthApi {
107
108
  }
108
109
 
109
110
  useSession() {
110
- const { data, isPending, error } = getAuthClientLazy().useSession();
111
- return {
112
- data: data as AuthSession | undefined,
113
- isPending,
114
- error: error as Error | undefined,
115
- };
111
+ return useSessionContext();
116
112
  }
117
113
  }
118
114
 
@@ -120,6 +116,9 @@ class BetterAuthApi implements AuthApi {
120
116
  export { TeamAccessEditor } from "./components/TeamAccessEditor";
121
117
  export type { TeamAccessEditorProps } from "./components/TeamAccessEditor";
122
118
 
119
+ // Re-export SessionProvider for App.tsx to wrap the component tree
120
+ export { SessionProvider } from "./lib/SessionProvider";
121
+
123
122
  export const authPlugin = createFrontendPlugin({
124
123
  metadata: pluginMetadata,
125
124
  apis: [
@@ -0,0 +1,64 @@
1
+ import React, { createContext, useContext } from "react";
2
+ import { getAuthClientLazy } from "./auth-client";
3
+ import type { AuthSession } from "../api";
4
+
5
+ // =============================================================================
6
+ // SESSION CONTEXT
7
+ // =============================================================================
8
+
9
+ interface SessionContextValue {
10
+ data: AuthSession | undefined;
11
+ isPending: boolean;
12
+ error: Error | undefined;
13
+ }
14
+
15
+ const SessionContext = createContext<SessionContextValue>({
16
+ data: undefined,
17
+ isPending: true,
18
+ error: undefined,
19
+ });
20
+
21
+ // =============================================================================
22
+ // PROVIDER
23
+ // =============================================================================
24
+
25
+ /**
26
+ * SessionProvider fetches the session exactly once at the top of the tree
27
+ * and distributes the result via React context.
28
+ *
29
+ * All consumers calling `authApi.useSession()` read from this context
30
+ * instead of each independently fetching `/api/auth/get-session`.
31
+ *
32
+ * Must be placed inside RuntimeConfigProvider (needs baseUrl for the client).
33
+ */
34
+ export const SessionProvider: React.FC<{ children: React.ReactNode }> = ({
35
+ children,
36
+ }) => {
37
+ const { data, isPending, error } = getAuthClientLazy().useSession();
38
+
39
+ const value: SessionContextValue = {
40
+ data: data as AuthSession | undefined,
41
+ isPending,
42
+ error: error as Error | undefined,
43
+ };
44
+
45
+ return (
46
+ <SessionContext.Provider value={value}>{children}</SessionContext.Provider>
47
+ );
48
+ };
49
+
50
+ // =============================================================================
51
+ // HOOK
52
+ // =============================================================================
53
+
54
+ /**
55
+ * Read session data from the SessionProvider context.
56
+ *
57
+ * This does NOT trigger a fetch — it reads the value provided by the
58
+ * single SessionProvider at the top of the tree.
59
+ *
60
+ * @throws If called outside a SessionProvider
61
+ */
62
+ export function useSessionContext(): SessionContextValue {
63
+ return useContext(SessionContext);
64
+ }
@@ -1,35 +1,20 @@
1
- import { useMemo } from "react";
2
1
  import { createAuthClient } from "better-auth/react";
3
- import {
4
- useRuntimeConfig,
5
- getCachedRuntimeConfig,
6
- } from "@checkstack/frontend-api";
2
+ import { getCachedRuntimeConfig } from "@checkstack/frontend-api";
7
3
 
8
4
  // Cache for lazy-initialized client
9
5
  let cachedClient: ReturnType<typeof createAuthClient> | undefined;
10
6
  let cachedBaseUrl: string | undefined;
11
7
 
12
8
  /**
13
- * React hook to get the auth client with proper runtime config.
14
- * Uses RuntimeConfigProvider to get the base URL.
15
- */
16
- export function useAuthClient() {
17
- const { baseUrl } = useRuntimeConfig();
18
-
19
- return useMemo(
20
- () =>
21
- createAuthClient({
22
- baseURL: baseUrl,
23
- basePath: "/api/auth",
24
- }),
25
- [baseUrl]
26
- );
27
- }
28
-
29
- /**
30
- * Lazy-initialized auth client for class-based APIs.
9
+ * Lazy-initialized auth client for imperative (non-hook) APIs like
10
+ * `getAuthClientLazy().listAccounts()` or `getAuthClientLazy().signOut()`.
11
+ *
31
12
  * Uses the cached runtime config from RuntimeConfigProvider.
32
13
  *
14
+ * IMPORTANT: Do NOT call `.useSession()` on this client from components.
15
+ * Use `authApi.useSession()` (via `useApi(authApiRef)`) instead — it reads
16
+ * from the shared SessionProvider context and does not trigger extra fetches.
17
+ *
33
18
  * Note: This should only be called AFTER RuntimeConfigProvider has loaded.
34
19
  * Components rendered inside the provider tree are guaranteed to have config available.
35
20
  */
@@ -48,3 +33,4 @@ export function getAuthClientLazy(): ReturnType<typeof createAuthClient> {
48
33
 
49
34
  return cachedClient;
50
35
  }
36
+