Skip to content

feat: frontend rework with stable /setup fetching and initial js-utils/@kinde-oss/kinde-auth-react impl.#405

Merged
DanielRivers merged 66 commits intomainfrom
feat/client-react
Oct 22, 2025
Merged

feat: frontend rework with stable /setup fetching and initial js-utils/@kinde-oss/kinde-auth-react impl.#405
DanielRivers merged 66 commits intomainfrom
feat/client-react

Conversation

@Yoshify
Copy link
Copy Markdown
Contributor

@Yoshify Yoshify commented Oct 2, 2025

Complete refactor of the frontend structure that includes the following:

  • KindeProvider now has a purpose and provides a stable context value for consumers to use. It wraps the provider from kinde-auth-react
  • Introduces 2 non-public hooks - one for provided state, one for providerless state. useKindeBrowserClient now routes between the 2 depending on whether a provider is available.

useProvidedKindeAuth wraps useKindeAuth from the React SDK with some transformation done to ensure it adheres to the current KindeState type contract and ensures there should be no breaking changes.

useProviderlessKindeAuth is essentially a heavily trimmed down and cleaner version of the current useKindeBrowserClient

The setup handler has been updated to remove queueing and return public env vars - client ID, issuer URL and redirect URL. This is necessary for the React provider.

This should eliminate race conditions in refreshing, and all-in-all be a performance improvement.

Assuming you have a root layout with the provider, and a page (lets say Dashboard) that uses useKindeBrowserClient(), and a nested component (lets say UserPanel) that also uses useKindeBrowserClient(), the flow looks like this:
-> KindeProvider fetches state and mounts
--> Dashboard doesn't fetch, it consumes the provider state and has immediate access to it.
---> UserPanel doesn't fetch, it consumes the provider state and has immediate access to it.

If a user is not using the provider (which they should be, but for backwards compatibility we support that), it looks more like this:
-> Dashboard fetches state, content then loads in
--> UserPanel also fetches state, content then loads in

The recommendation is to use the provider to eliminate redundancy, improve performance, and most of all prevent race conditions with refreshes. The further nested your chain of useKindeBrowserClient becomes, the worse your performance gets (and the higher the chance you'll encounter a race with the multiple /setup calls)

There is a tradeoff with using the Provider (as the React SDK doesn't currently support having values added to its store after its been mounted) - we can't mount the provider until the setup call has completed, so there'll be a flash (and likely affect LCP). A fallback option has been provided to the KindeProvider to supply a user made loading state to be displayed in loading and error situations. We can revisit this after a future look at making the React SDK handle this. No longer true 🥳

This also provides us with a fairly structured path to cutting over to a full React SDK + JS utils impl. in v3.

Yoshify added 26 commits October 3, 2025 00:46
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 2, 2025

Walkthrough

Remove the legacy browser client and its tests; add a client-side KindeProvider, provider-aware and providerless browser hooks, session-sync logic, frontend factories/types/constants/utils to translate React auth state, refactor /setup and token validation (20s threshold), update multiple import sources, and relocate public exports.

Changes

Cohort / File(s) Summary
Config fallbacks
src/config/index.ts
Use NEXT_PUBLIC_* env vars as fallbacks for KINDE_SITE_URL, KINDE_ISSUER_URL, and KINDE_CLIENT_ID.
Legacy browser client removed & public exports relocated
src/frontend/KindeBrowserClient.ts, src/frontend/index.js
Remove the previous useKindeBrowserClient implementation; update public exports to point to new hook/provider files.
Provider component (client)
src/frontend/KindeProvider.tsx
Add KindeProvider client component that uses useSessionSync, wires refresh into storage settings, and renders KindeReactProvider with clientStorage.
Old Auth provider client directive
src/frontend/OldAuthProvider.jsx
Add "use client" directive to mark file as a client component.
Session sync & setup utilities
src/frontend/hooks/internal/use-session-sync.ts, src/frontend/utils.ts, src/handlers/setup.ts
Add useSessionSync and calculateExpirySeconds; add fetchKindeState and getRefreshTokensServerAction; refactor /setup to inline try/catch, include env on failures, use 20s expiry threshold, and return richer success payload.
Browser hooks (provider-aware / providerless)
src/frontend/hooks/public/use-kinde-browser-client.ts, src/frontend/hooks/public/use-provided-kinde-auth.ts, src/frontend/hooks/public/use-providerless-kinde-auth.ts
Add useKindeBrowserClient delegating to useProvidedKindeAuth or useProviderlessKindeAuth; both return unified KindeState and expose refreshData.
Factories: flags, tokens, organization & transformer
src/frontend/factories/index.ts, src/frontend/factories/feature-flag-factory.ts, src/frontend/factories/token-factory.ts, src/frontend/factories/organization-factory.ts
Add factories for typed flag getters, token/claim accessors, organization factory, and functions transformReactAuthStateToNextState and constructKindeClientState.
Types & defaults
src/frontend/types.ts, src/frontend/constants.ts
Add frontend types (feature flags, public config, fetched/KindeNext client state) and export DefaultKindeNextClientState.
Client storage init
src/frontend/store.ts
Initialize MemoryStorage as clientStorage and call setActiveStorage.
Import source updates
src/components/PortalLink.tsx, src/handlers/portal.ts, src/session/*, src/utils/cookies/getSplitSerializedCookies.ts, src/session/sessionManager.js
Replace imports previously from @kinde/js-utils with @kinde-oss/kinde-auth-react/utils (identifiers unchanged).
Middleware & token validation
src/authMiddleware/authMiddleware.ts, src/utils/jwt/validation.ts
Middleware expiry checks use a 20s threshold; isTokenExpired signature changed to accept optional threshold and handle decode errors/missing exp.
Session helpers & entitlements
src/session/getUserOrganizations.ts, src/session/getEntitlements.ts, src/session/index.ts
Minor typing refactor and import-source updates; runtime behavior unchanged.
Utilities & cookies
src/frontend/utils.ts, src/utils/cookies/getSplitSerializedCookies.ts
Add fetchKindeState and getRefreshTokensServerAction; update cookie util imports.
Removed tests
tests/frontend/use-kinde-browser-client.test.tsx
Remove tests for the deleted legacy useKindeBrowserClient hook.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client as Client Component
  participant KP as KindeProvider
  participant USS as useSessionSync
  participant API as /api/auth/setup
  participant Store as clientStorage

  Client->>KP: mount
  KP->>USS: init session sync
  USS->>API: fetchKindeState()
  API-->>USS: setup payload (message, tokens, user, flags, org, env)
  USS->>Store: store tokens (accessTokenEncoded, idTokenRaw)
  USS-->>KP: { loading=false, config, getFetchedState }
  KP-->>Client: render KindeReactProvider(children)
Loading
sequenceDiagram
  autonumber
  actor C as Component
  participant UB as useKindeBrowserClient
  participant P as useProvidedKindeAuth
  participant PL as useProviderlessKindeAuth
  participant USS as useSessionSync
  participant SRV as refreshTokensServerAction

  C->>UB: call useKindeBrowserClient()
  alt KindeContext present
    UB->>P: delegate -> transformReactAuthStateToNextState()
    P-->>C: return KindeState + refreshData()
  else no Provider
    UB->>PL: delegate -> useSessionSync.getFetchedState()
    PL-->>C: return KindeState + refreshData()
    C->>PL: call refreshData()
    PL->>SRV: dynamic import & invoke refreshTokensServerAction()
    SRV-->>PL: success -> PL triggers refetch
  end
Loading
sequenceDiagram
  autonumber
  participant Setup as setup handler
  participant Sess as session manager
  participant KC as kindeClient

  Setup->>Sess: retrieve session tokens
  alt tokens missing
    Setup-->>Client: 200 NOT_LOGGED_IN + env
  else tokens present
    alt tokens expired (threshold 20s)
      Setup->>KC: refreshTokens(session)
      KC-->>Setup: updated tokens or error
      alt refresh failure
        Setup-->>Client: 500 REFRESH_FAILED + env
      end
    end
    Setup->>Setup: decode tokens (access/id)
    alt decode failure
      Setup-->>Client: 500 ACCESS/ID_TOKEN_DECODE_FAILED + env
    else success
      Setup-->>Client: 200 OK + tokens, flags, user, org, permissions + env
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "feat: frontend rework with stable /setup fetching and initial js-utils/@kinde-oss/kinde-auth-react impl." accurately captures the primary changes in the changeset. The title identifies three key aspects of the PR: (1) a frontend rework reflected in the new KindeProvider, hook restructuring, and factory additions; (2) stable /setup fetching with the refactored setup handler that removes queueing and returns environment variables; and (3) the migration toward @kinde-oss/kinde-auth-react and js-utils, evidenced by import changes throughout multiple files. The title is concise, single-sentence, and specific enough that a reviewer scanning commit history would understand the scope of changes.
Description Check ✅ Passed The pull request description is clearly related to the changeset and provides substantive detail about the motivations and implementation approach. It explains the purpose of the refactored KindeProvider, describes the two new non-public hooks (useProvidedKindeAuth and useProviderlessKindeAuth) and how useKindeBrowserClient routes between them, outlines the setup handler changes, documents the intended usage patterns with and without a provider, and articulates the benefits including elimination of refresh race conditions and performance improvements. All described changes are present and substantiated in the raw summary.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/client-react

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e04a1e0 and 7b1b9f1.

📒 Files selected for processing (1)
  • src/frontend/hooks/internal/use-session-sync.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-13T23:54:25.463Z
Learnt from: Yoshify
PR: kinde-oss/kinde-auth-nextjs#405
File: src/handlers/setup.ts:38-65
Timestamp: 2025-10-13T23:54:25.463Z
Learning: In the setup handler (`src/handlers/setup.ts`), it is intentional to check only the access token expiry (`isTokenExpired(accessTokenEncoded, 20)`) before attempting a token refresh, as confirmed by the maintainer. Checking the ID token expiry is not necessary in this context.

Applied to files:

  • src/frontend/hooks/internal/use-session-sync.ts
📚 Learning: 2025-03-28T12:12:55.741Z
Learnt from: DanielRivers
PR: kinde-oss/kinde-auth-nextjs#318
File: src/session/getFlag.js:23-23
Timestamp: 2025-03-28T12:12:55.741Z
Learning: The sessionManager function in the Kinde Auth NextJS package returns a Promise that must be properly awaited before using its result, especially when passing it to functions like kindeClient.getClaimValue().

Applied to files:

  • src/frontend/hooks/internal/use-session-sync.ts
🧬 Code graph analysis (1)
src/frontend/hooks/internal/use-session-sync.ts (4)
src/frontend/types.ts (3)
  • PublicKindeConfig (13-17)
  • KindeNextClientState (33-36)
  • FetchedKindeState (19-31)
src/frontend/hooks/internal/use-sync-state.tsx (1)
  • useSyncState (7-26)
src/frontend/constants.ts (1)
  • DefaultKindeNextClientState (3-16)
src/frontend/utils.ts (1)
  • fetchKindeState (34-83)
🔇 Additional comments (2)
src/frontend/hooks/internal/use-session-sync.ts (2)

60-66: Proper null check for expiry - well done!

The null check on line 63 correctly prevents passing null to setRefreshTimer, addressing the previous review concern.


129-132: Appropriate eslint-disable for mount-only effect.

The eslint-disable comment correctly documents the intentional mount-only execution, addressing the previous review concern.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (6)
src/session/getUserOrganizations.ts (1)

19-22: Inconsistent type assertion formatting.

The type assertion for idTokenOrgs now uses multiline formatting, but the identical assertion for idTokenHasuraOrgs on line 28 remains inline. For consistency, both should use the same format.

Apply this diff to make line 28 consistent with the multiline format:

       const idTokenHasuraOrgs =
         ((await kindeClient.getClaimValue(
           session,
           "x-hasura-organizations",
           "id_token",
-        )) as { id: string; name: string }[]) ?? [];
+        )) as {
+          id: string;
+          name: string;
+        }[]) ?? [];

Alternatively, revert lines 19-22 to inline format to match line 28:

       const idTokenOrgs =
         ((await kindeClient.getClaimValue(
           session,
           "organizations",
           "id_token",
-        )) as {
-          id: string;
-          name: string;
-        }[]) ?? [];
+        )) as { id: string; name: string }[]) ?? [];
src/frontend/hooks/internal/use-fetched-kinde-state.ts (1)

12-49: Consider memoizing onSuccess callback.

Since onSuccess is included in setupState's dependencies (per the recommended fix above), callers should be advised to memoize the callback to prevent unnecessary re-fetches.

Add a note in the JSDoc or inline comment:

 type UseFetchedKindeStateProps = {
+  /** Callback invoked on successful state fetch. Should be memoized with useCallback to prevent re-fetches. */
   onSuccess?: (state: FetchedKindeState) => void | Promise<void>;
 };
src/frontend/KindeProvider.tsx (2)

17-17: Remove debug console.log.

The console.log statement appears to be debug code left in production.

-      console.log("[KindeProvider] Fetched state");

15-30: Memoize onSuccess callback.

The onSuccess callback should be memoized with useCallback to prevent unnecessary re-execution of setupState in useFetchedKindeState, especially after the recommended fix to include onSuccess in dependencies.

+  const handleSuccess = useCallback(async (state: FetchedKindeState) => {
+    await Promise.all([
+      store.clientStorage.setSessionItem(
+        StorageKeys.accessToken,
+        state.accessTokenEncoded,
+      ),
+      store.clientStorage.setSessionItem(
+        StorageKeys.idToken,
+        state.idTokenRaw,
+      ),
+    ]);
+    setConfig(state.env);
+  }, []);
+
   const { loading } = useFetchedKindeState({
-    onSuccess: async (state) => {
-      console.log("[KindeProvider] Fetched state");
-      await Promise.all([
-        store.clientStorage.setSessionItem(
-          StorageKeys.accessToken,
-          state.accessTokenEncoded,
-        ),
-        store.clientStorage.setSessionItem(
-          StorageKeys.idToken,
-          state.idTokenRaw,
-        ),
-      ]);
-      setConfig(state.env);
-    },
+    onSuccess: handleSuccess,
   });
src/frontend/hooks/public/use-providerless-kinde-auth.ts (1)

11-14: Consider extracting API path resolution.

The API path resolution logic appears in multiple places (here and in other hooks). Consider extracting it to a utility function for consistency.

Create a utility function:

// src/frontend/utils.ts
export const resolveApiPath = () => {
  return (
    process.env.NEXT_PUBLIC_KINDE_AUTH_API_PATH ||
    process.env.KINDE_AUTH_API_PATH ||
    "/api/auth"
  );
};

Then use it:

-export const useProviderlessKindeAuth = (
-  apiPath = process.env.NEXT_PUBLIC_KINDE_AUTH_API_PATH ||
-    process.env.KINDE_AUTH_API_PATH ||
-    "/api/auth",
-): KindeState => {
+export const useProviderlessKindeAuth = (
+  apiPath = resolveApiPath(),
+): KindeState => {
src/frontend/hooks/public/use-provided-kinde-auth.ts (1)

40-42: Consider implementing refreshData.

The refreshData function is currently a no-op. Users might expect this to trigger a state refresh when using the provided auth pattern.

Implement refreshData to re-transform state:

   return {
     ...clientState,
-    refreshData: async () => {
-      // noop for now
-    },
+    refreshData: transformState,
   };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8599cc and fff0e8b.

⛔ Files ignored due to path filters (1)
  • package.json is excluded by !**/*.json
📒 Files selected for processing (20)
  • src/config/index.ts (2 hunks)
  • src/frontend/KindeBrowserClient.ts (0 hunks)
  • src/frontend/KindeProvider.tsx (1 hunks)
  • src/frontend/OldAuthProvider.jsx (1 hunks)
  • src/frontend/constants.ts (1 hunks)
  • src/frontend/factories/feature-flag-factory.ts (1 hunks)
  • src/frontend/factories/index.ts (1 hunks)
  • src/frontend/factories/organization-factory.ts (1 hunks)
  • src/frontend/factories/token-factory.ts (1 hunks)
  • src/frontend/hooks/internal/use-fetched-kinde-state.ts (1 hunks)
  • src/frontend/hooks/public/use-kinde-browser-client.ts (1 hunks)
  • src/frontend/hooks/public/use-provided-kinde-auth.ts (1 hunks)
  • src/frontend/hooks/public/use-providerless-kinde-auth.ts (1 hunks)
  • src/frontend/index.js (1 hunks)
  • src/frontend/store.ts (1 hunks)
  • src/frontend/types.ts (1 hunks)
  • src/frontend/utils.ts (1 hunks)
  • src/handlers/setup.ts (1 hunks)
  • src/session/getUserOrganizations.ts (1 hunks)
  • tests/frontend/use-kinde-browser-client.test.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • src/frontend/KindeBrowserClient.ts
🧰 Additional context used
🧬 Code graph analysis (15)
src/frontend/hooks/internal/use-fetched-kinde-state.ts (4)
src/frontend/types.ts (2)
  • FetchedKindeState (19-31)
  • KindeNextClientState (33-36)
src/frontend/hooks/internal/use-sync-state.tsx (1)
  • useSyncState (7-26)
src/frontend/constants.ts (1)
  • DefaultKindeNextClientState (3-16)
src/frontend/utils.ts (1)
  • fetchKindeState (32-62)
src/frontend/constants.ts (1)
src/frontend/types.ts (1)
  • KindeNextClientState (33-36)
src/frontend/types.ts (1)
src/types.ts (7)
  • KindeFlagRaw (187-190)
  • KindeAccessToken (14-32)
  • KindeIdToken (96-142)
  • KindeOrganization (217-221)
  • KindePermissions (171-174)
  • KindeUser (167-169)
  • KindeOrganizations (223-229)
src/frontend/hooks/public/use-providerless-kinde-auth.ts (4)
src/types.ts (1)
  • KindeState (307-361)
src/frontend/hooks/internal/use-fetched-kinde-state.ts (1)
  • useFetchedKindeState (12-49)
src/frontend/utils.ts (1)
  • getRefreshTokensServerAction (4-13)
src/frontend/factories/index.ts (1)
  • constructKindeClientState (47-55)
src/frontend/KindeProvider.tsx (2)
src/frontend/types.ts (1)
  • PublicKindeConfig (13-17)
src/frontend/hooks/internal/use-fetched-kinde-state.ts (1)
  • useFetchedKindeState (12-49)
src/frontend/factories/token-factory.ts (1)
src/types.ts (2)
  • KindeAccessToken (14-32)
  • KindeIdToken (96-142)
src/frontend/utils.ts (3)
src/session/refreshTokensServerAction.ts (1)
  • refreshTokensServerAction (6-9)
src/frontend/types.ts (1)
  • FetchedKindeState (19-31)
src/config/index.ts (2)
  • config (107-144)
  • routes (150-167)
src/frontend/factories/index.ts (4)
src/frontend/factories/feature-flag-factory.ts (4)
  • getFlagFactory (6-36)
  • getBooleanFlagFactory (38-49)
  • getIntegerFlagFactory (64-75)
  • getStringFlagFactory (51-62)
src/frontend/factories/token-factory.ts (5)
  • getClaimFactory (3-14)
  • getNextTypedAccessTokenFactory (16-20)
  • getRawAccessTokenFactory (22-24)
  • getNextTypedIdTokenFactory (26-28)
  • getRawIdTokenFactory (30-32)
src/frontend/factories/organization-factory.ts (1)
  • getOrganizationFactory (9-16)
src/utils/generateUserObject.ts (1)
  • generateUserObject (5-68)
src/frontend/factories/feature-flag-factory.ts (4)
src/frontend/types.ts (1)
  • KindeFeatureFlags (12-12)
src/types.ts (2)
  • KindeFlagTypeCode (192-192)
  • KindeFlag (196-202)
src/frontend/OldAuthProvider.jsx (2)
  • flagDataTypeMap (11-15)
  • flagDataTypeMap (11-15)
src/config/index.ts (1)
  • config (107-144)
src/frontend/factories/organization-factory.ts (2)
src/types.ts (3)
  • KindeIdToken (96-142)
  • KindeAccessToken (14-32)
  • KindeOrganization (217-221)
src/utils/generateOrganizationObject.ts (1)
  • generateOrganizationObject (60-75)
src/handlers/setup.ts (8)
src/utils/getAccessToken.ts (1)
  • getAccessToken (7-41)
src/utils/getIdToken.ts (1)
  • getIdToken (7-43)
src/config/index.ts (1)
  • config (107-144)
src/session/sessionManager.js (2)
  • sessionManager (22-40)
  • sessionManager (22-40)
src/utils/jwt/validation.ts (1)
  • isTokenExpired (7-15)
src/session/kindeServerClient.ts (1)
  • kindeClient (7-10)
src/types.ts (2)
  • KindeAccessToken (14-32)
  • KindeIdToken (96-142)
src/utils/generateUserObject.ts (1)
  • generateUserObject (5-68)
src/frontend/hooks/public/use-provided-kinde-auth.ts (4)
src/types.ts (1)
  • KindeState (307-361)
src/frontend/types.ts (1)
  • KindeNextClientState (33-36)
src/frontend/constants.ts (1)
  • DefaultKindeNextClientState (3-16)
src/frontend/factories/index.ts (2)
  • transformReactAuthStateToNextState (57-111)
  • constructKindeClientState (47-55)
src/frontend/hooks/public/use-kinde-browser-client.ts (3)
src/types.ts (1)
  • KindeState (307-361)
src/frontend/hooks/public/use-provided-kinde-auth.ts (1)
  • useProvidedKindeAuth (17-44)
src/frontend/hooks/public/use-providerless-kinde-auth.ts (1)
  • useProviderlessKindeAuth (11-36)
src/config/index.ts (1)
src/utils/removeTrailingSlash.js (1)
  • removeTrailingSlash (1-11)
tests/frontend/use-kinde-browser-client.test.tsx (2)
src/types.ts (6)
  • KindeAccessToken (14-32)
  • KindeIdToken (96-142)
  • KindeUser (167-169)
  • KindeOrganization (217-221)
  • KindePermissions (171-174)
  • KindeOrganizations (223-229)
src/frontend/hooks/public/use-kinde-browser-client.ts (1)
  • useKindeBrowserClient (8-19)
🔇 Additional comments (21)
tests/frontend/use-kinde-browser-client.test.tsx (18)

14-116: LGTM: Well-structured mock data factories.

The mock data generators provide comprehensive coverage of all Kinde types (access token, id token, user, organization, permissions, user organizations, and composite setup response). The use of partial overrides makes tests flexible and maintainable.


129-185: LGTM: Thorough setup URL configuration tests.

The test suite properly validates default, custom prop, and environment variable-based setup URL resolution, ensuring the hook correctly constructs the setup endpoint path.


187-210: LGTM: Initial state validation.

Correctly verifies the hook initializes with loading state and null values for auth-related fields.


212-260: LGTM: Successful authentication response handling.

Properly tests that the hook processes an OK response and populates all state fields correctly.


262-279: LGTM: NOT_LOGGED_IN response handling.

Validates the hook correctly interprets an unauthenticated response.


281-328: LGTM: Error response handling.

Comprehensive coverage of error scenarios including HTTP errors, missing error messages, and unknown status codes.


330-424: LGTM: Token getter tests.

Thorough validation of all token accessors (getAccessToken, getAccessTokenRaw, getToken, getIdToken, getIdTokenRaw) in both authenticated and unauthenticated states.


426-458: LGTM: User getter tests.

Validates getUser() returns the correct user object when authenticated and null when not.


460-513: LGTM: Organization getter tests.

Tests getOrganization() and getUserOrganizations() for both authenticated and unauthenticated states.


515-586: LGTM: Permission getter tests.

Comprehensive validation of getPermissions(), getPermission() for granted/denied permissions, and proper handling when permissions are null.


588-717: LGTM: Feature flag getFlag tests.

Thorough coverage of getFlag() for string, boolean, and integer flags, including default value handling, missing flags, and type mismatch errors.


719-780: LGTM: getBooleanFlag tests.

Validates boolean flag retrieval, default value handling, and error scenarios.


782-843: LGTM: getStringFlag tests.

Tests string flag retrieval, default value handling, and type mismatch errors.


845-906: LGTM: getIntegerFlag tests.

Validates integer flag retrieval, default value handling, and type mismatch errors.


908-987: LGTM: getClaim tests.

Comprehensive coverage of getClaim() from both access token and id token, with proper null handling.


989-1031: Verify refreshData test expectations.

The refreshData test acknowledges that dynamic import may fail in the test environment and only verifies the function can be called. Consider mocking the import more robustly to verify the full refresh flow.

Generate a more complete test by properly mocking the dynamic import:

it("should call refreshTokensServerAction and refetch state when available", async () => {
  const mockResponse = createMockSetupResponse();
  const mockRefreshedResponse = createMockSetupResponse({
    user: createMockUser({ given_name: "Updated" }),
  });

  let callCount = 0;
  vi.spyOn(global, "fetch").mockImplementation(() => {
    callCount++;
    const response = callCount === 1 ? mockResponse : mockRefreshedResponse;
    return Promise.resolve({
      json: () => Promise.resolve(response),
      ok: true,
    } as any);
  });

  // Mock the refreshTokensServerAction at the module level
  const mockRefreshAction = vi.fn().mockResolvedValue(undefined);
  vi.doMock("../../src/session/refreshTokensServerAction.js", () => ({
    refreshTokensServerAction: vi.fn(() => mockRefreshAction),
  }));

  const { result } = renderHook(() => useKindeBrowserClient());

  await waitFor(() => {
    return expect(result.current.isLoading).toBe(false);
  });

  expect(result.current.user?.given_name).toBe("Test");

  await act(async () => {
    await result.current.refreshData();
  });

  await waitFor(() => {
    return expect(result.current.user?.given_name).toBe("Updated");
  });
  
  expect(mockRefreshAction).toHaveBeenCalled();
  expect(callCount).toBe(2); // Initial fetch + refetch after refresh
});

1033-1065: LGTM: isAuthenticated tests.

Validates isAuthenticated flag for both authenticated and unauthenticated states.


1067-1134: LGTM: Edge case tests.

Comprehensive edge case coverage including empty feature flags, empty permissions, and null flag values.

src/frontend/constants.ts (1)

1-16: LGTM: Well-defined default state.

The DefaultKindeNextClientState provides a consistent baseline with appropriate initial values (isLoading: true, isAuthenticated: false, nulls for auth-related fields). This aligns well with the KindeNextClientState type.

src/frontend/hooks/public/use-providerless-kinde-auth.ts (1)

7-36: LGTM: Clean providerless auth implementation.

The hook properly delegates to useFetchedKindeState for state management and implements a refreshData function that conditionally invokes the server action. The warning message for environments without App Router support is appropriate.

src/frontend/index.js (1)

1-3: LGTM: Public API re-export updates.

The updated exports correctly point to the new module locations for useKindeAuth, useKindeBrowserClient, and KindeProvider.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/handlers/portal.ts (1)

4-10: Fix imports for storage and portal utilities
src/handlers/portal.ts (lines 4–10): @kinde-oss/kinde-auth-react/utils does not export generatePortalUrl, MemoryStorage, PortalPage, setActiveStorage, or StorageKeys; import these from @kinde/js-utils instead.

♻️ Duplicate comments (1)
src/session/sessionManager.js (1)

10-10: LGTM!

The import source change is consistent with the migration strategy outlined in the PR objectives.

🧹 Nitpick comments (2)
src/frontend/KindeProvider.tsx (1)

19-19: Move side effect to useEffect to follow React best practices.

Mutating storageSettings.onRefreshHandler during render violates React's constraint that render functions should be pure. React may invoke render multiple times (especially in StrictMode or with concurrent features), potentially causing unexpected behavior.

Apply this diff to move the side effect into a useEffect:

   const { loading, config, refreshHandler } = useSessionSync();
 
-  storageSettings.onRefreshHandler = refreshHandler;
+  useEffect(() => {
+    storageSettings.onRefreshHandler = refreshHandler;
+  }, [refreshHandler]);
+
   if (loading && waitForInitialLoad) return null;

If synchronous execution before paint is required, use useLayoutEffect instead:

   const { loading, config, refreshHandler } = useSessionSync();
 
-  storageSettings.onRefreshHandler = refreshHandler;
+  useLayoutEffect(() => {
+    storageSettings.onRefreshHandler = refreshHandler;
+  }, [refreshHandler]);
+
   if (loading && waitForInitialLoad) return null;
src/handlers/setup.ts (1)

136-175: Consider removing redundant token fields and extracting env construction.

The success response includes redundant fields:

  • accessTokenRaw (line 139) duplicates accessTokenEncoded (line 138)
  • idTokenRaw (line 141) duplicates idTokenEncoded (line 142)

Additionally, the env object construction is repeated throughout the file (lines 28-32, 56-60, 82-86, 102-106, 170-174). Consider extracting it to a helper function:

const getEnvConfig = () => ({
  clientId: config.clientID,
  issuerUrl: config.issuerURL,
  redirectUrl: config.redirectURL,
});

Then use env: getEnvConfig() in all response objects.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4afa3db and 92b478b.

⛔ Files ignored due to path filters (1)
  • package.json is excluded by !**/*.json
📒 Files selected for processing (10)
  • src/components/PortalLink.tsx (1 hunks)
  • src/frontend/KindeProvider.tsx (1 hunks)
  • src/frontend/hooks/internal/use-session-sync.ts (1 hunks)
  • src/frontend/store.ts (1 hunks)
  • src/handlers/portal.ts (1 hunks)
  • src/handlers/setup.ts (1 hunks)
  • src/session/getEntitlements.ts (1 hunks)
  • src/session/index.ts (1 hunks)
  • src/session/sessionManager.js (1 hunks)
  • src/utils/cookies/getSplitSerializedCookies.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/frontend/store.ts
  • src/frontend/hooks/internal/use-session-sync.ts
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-03-28T12:12:55.741Z
Learnt from: DanielRivers
PR: kinde-oss/kinde-auth-nextjs#318
File: src/session/getFlag.js:23-23
Timestamp: 2025-03-28T12:12:55.741Z
Learning: The sessionManager function in the Kinde Auth NextJS package returns a Promise that must be properly awaited before using its result, especially when passing it to functions like kindeClient.getClaimValue().

Applied to files:

  • src/session/index.ts
📚 Learning: 2025-10-09T16:49:20.471Z
Learnt from: Yoshify
PR: kinde-oss/kinde-auth-nextjs#405
File: src/frontend/KindeProvider.tsx:17-34
Timestamp: 2025-10-09T16:49:20.471Z
Learning: In kinde-auth-nextjs, the KindeProvider from kinde-oss/kinde-auth-react can safely mount with empty/undefined clientId, domain, and redirectUri props and then receive real values on re-render. The provider's callbacks (login, register, logout) are defined with useCallback and depend on these props, so they recreate when the props update. The init() logic runs once and primarily validates tokens already in storage (pre-populated by useSessionSync), so it doesn't require valid config at initial mount. This pattern allows mounting the provider immediately to avoid UI flash while config is being fetched.

Applied to files:

  • src/frontend/KindeProvider.tsx
🧬 Code graph analysis (2)
src/handlers/setup.ts (9)
src/routerClients/RouterClient.js (1)
  • RouterClient (3-68)
src/utils/getAccessToken.ts (1)
  • getAccessToken (7-41)
src/utils/getIdToken.ts (1)
  • getIdToken (7-43)
src/config/index.ts (1)
  • config (107-144)
src/session/sessionManager.js (2)
  • sessionManager (22-40)
  • sessionManager (22-40)
src/utils/jwt/validation.ts (1)
  • isTokenExpired (7-15)
src/session/kindeServerClient.ts (1)
  • kindeClient (7-10)
src/types.ts (2)
  • KindeAccessToken (14-32)
  • KindeIdToken (96-142)
src/utils/generateUserObject.ts (1)
  • generateUserObject (5-68)
src/frontend/KindeProvider.tsx (2)
src/frontend/hooks/internal/use-session-sync.ts (1)
  • useSessionSync (27-123)
src/config/index.ts (1)
  • config (107-144)
🔇 Additional comments (6)
src/session/index.ts (1)

33-33: LGTM! Import source change is consistent.

The import source change for getEntitlementsResponse from @kinde/js-utils to @kinde-oss/kinde-auth-react/utils is consistent with the changes in src/session/getEntitlements.ts and aligns with the PR's module consolidation objective. The verification script in the previous file will confirm that this type is properly exported from the new source.

src/session/getEntitlements.ts (1)

3-10: Verify utilities are exported from the new package source. Automated inspection couldn’t locate @kinde-oss/kinde-auth-react/utils; manually confirm that MemoryStorage, StorageKeys, clearActiveStorage, getEntitlementsResponse, getEntitlementsUtils, and setActiveStorage are correctly exported with unchanged signatures.

src/utils/cookies/getSplitSerializedCookies.ts (1)

3-3: Import migration verified. @kinde-oss/kinde-auth-react@^5.8.0 is declared in package.json; no @kinde/js-utils imports remain; splitString signature and behavior match the previous implementation.

src/handlers/setup.ts (2)

10-10: Confirm isTokenExpired signature
Ensure that the React SDK’s isTokenExpired can be called with only { threshold: 20 } (auto-reading the token)—if it instead requires an explicit token argument, pass the token or switch to the local utility.


17-189: Ensure token refresh coordination
Removing the queue exposes simultaneous refreshTokens calls each using the same refresh token; if your provider rotates or invalidates refresh tokens on use, the second request will fail. Confirm Kinde’s refreshTokens behavior under concurrent calls or reintroduce locking/queue around token refresh.

src/components/PortalLink.tsx (1)

3-3: Verify installed package exports

  1. Confirm @kinde-oss/kinde-auth-react@v5.6.0 is installed and exposes a /utils subpath in its package.json exports.
  2. Ensure GeneratePortalUrlParams is exported from that subpath and its definition matches the original type.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92b478b and 72caad8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !**/*.yaml
📒 Files selected for processing (3)
  • src/authMiddleware/authMiddleware.ts (1 hunks)
  • src/handlers/setup.ts (1 hunks)
  • src/utils/jwt/validation.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Yoshify
PR: kinde-oss/kinde-auth-nextjs#405
File: src/frontend/KindeProvider.tsx:17-34
Timestamp: 2025-10-09T16:49:20.471Z
Learning: In kinde-auth-nextjs, the KindeProvider from kinde-oss/kinde-auth-react can safely mount with empty/undefined clientId, domain, and redirectUri props and then receive real values on re-render. The provider's callbacks (login, register, logout) are defined with useCallback and depend on these props, so they recreate when the props update. The init() logic runs once and primarily validates tokens already in storage (pre-populated by useSessionSync), so it doesn't require valid config at initial mount. This pattern allows mounting the provider immediately to avoid UI flash while config is being fetched.
📚 Learning: 2024-11-21T17:55:06.897Z
Learnt from: DanielRivers
PR: kinde-oss/kinde-auth-nextjs#243
File: src/session/getIdToken.js:21-22
Timestamp: 2024-11-21T17:55:06.897Z
Learning: In the Kinde Auth Next.js library, the `getIdToken(req, res)` function in `src/utils/getIdToken.ts` performs token validation using `validateToken` from `kinde/jwt-validator`, ensuring proper token validation before decoding.

Applied to files:

  • src/authMiddleware/authMiddleware.ts
📚 Learning: 2024-11-21T09:58:35.193Z
Learnt from: DanielRivers
PR: kinde-oss/kinde-auth-nextjs#243
File: src/authMiddleware/authMiddleware.ts:46-49
Timestamp: 2024-11-21T09:58:35.193Z
Learning: In `src/authMiddleware/authMiddleware.ts`, the `validateToken` function from `kinde/jwt-validator` handles exceptions internally, so additional error handling is not necessary when calling it.

Applied to files:

  • src/utils/jwt/validation.ts
🧬 Code graph analysis (2)
src/authMiddleware/authMiddleware.ts (1)
src/utils/jwt/validation.ts (1)
  • isTokenExpired (7-20)
src/handlers/setup.ts (9)
src/routerClients/RouterClient.js (1)
  • RouterClient (3-68)
src/utils/getAccessToken.ts (1)
  • getAccessToken (7-41)
src/utils/getIdToken.ts (1)
  • getIdToken (7-43)
src/config/index.ts (1)
  • config (107-144)
src/session/sessionManager.js (2)
  • sessionManager (22-40)
  • sessionManager (22-40)
src/utils/jwt/validation.ts (1)
  • isTokenExpired (7-20)
src/session/kindeServerClient.ts (1)
  • kindeClient (7-10)
src/types.ts (2)
  • KindeAccessToken (14-32)
  • KindeIdToken (96-142)
src/utils/generateUserObject.ts (1)
  • generateUserObject (5-68)
🔇 Additional comments (1)
src/authMiddleware/authMiddleware.ts (1)

88-91: 20 s skew buffer avoids expiry edge cases.
Adding the 20-second grace period prevents last-second refresh races while matching isTokenExpired’s threshold semantics.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/utils/jwt/validation.ts (2)

16-18: Error logging should respect debug mode.

The error is logged unconditionally, while validateToken below checks config.isDebugMode before logging. This inconsistency may clutter production logs or leak information.

Apply this diff:

   } catch (error) {
+    if (config.isDebugMode) {
       console.error("Error checking authentication:", error);
+    }
     return true;
   }

5-6: Consider removing or updating the outdated comment.

The comment "currently assumes that the token is valid .. should we revalidate here as well?" may confuse readers now that validation logic (try-catch, exp checks) is in place.

src/handlers/setup.ts (1)

136-175: Comprehensive success response with proper env payload.

The response structure correctly includes all required data (tokens, user info, permissions, organizations, feature flags) along with the environment configuration needed by the React provider.

Note: The response includes both accessTokenRaw and accessTokenEncoded (which are identical), as well as idTokenRaw and idTokenEncoded (also identical). This duplication may be intentional for backward compatibility or API contract reasons.

If the duplication is not required for backward compatibility, consider consolidating in a future refactor:

     return routerClient.json({
       accessToken,
       accessTokenEncoded,
-      accessTokenRaw: accessTokenEncoded,
       idToken,
-      idTokenRaw: idTokenEncoded,
       idTokenEncoded,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72caad8 and 6581e2d.

📒 Files selected for processing (2)
  • src/handlers/setup.ts (1 hunks)
  • src/utils/jwt/validation.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/handlers/setup.ts (8)
src/routerClients/RouterClient.js (1)
  • RouterClient (3-68)
src/utils/getAccessToken.ts (1)
  • getAccessToken (7-41)
src/utils/getIdToken.ts (1)
  • getIdToken (7-43)
src/config/index.ts (1)
  • config (107-144)
src/session/sessionManager.js (2)
  • sessionManager (22-40)
  • sessionManager (22-40)
src/utils/jwt/validation.ts (1)
  • isTokenExpired (7-20)
src/session/kindeServerClient.ts (1)
  • kindeClient (7-10)
src/types.ts (2)
  • KindeAccessToken (14-32)
  • KindeIdToken (96-142)
🔇 Additional comments (3)
src/utils/jwt/validation.ts (1)

8-19: Security fix confirmed: decode errors now treated as expired.

The change at line 18 correctly addresses the critical security issue flagged in the previous review. Returning true on decode failures ensures malformed/forged tokens are treated as expired rather than valid.

However, consider respecting debug mode when logging errors (see separate comment).

src/handlers/setup.ts (2)

17-36: LGTM! Proper handling of missing tokens.

The initial token checks correctly identify when users are not logged in and return the required environment configuration for the React provider. The 200 status code is appropriate here since "not logged in" is a valid state, not an error.


176-193: Final error handler properly includes env payload.

The previous review concern about the missing env payload in the SETUP_FAILED path has been correctly addressed. The catch block now returns the environment configuration alongside the error details, ensuring the frontend can always bootstrap the provider even when unexpected failures occur.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c1a70a and 8712e4b.

📒 Files selected for processing (2)
  • src/frontend/hooks/internal/use-session-sync.ts (1 hunks)
  • src/frontend/hooks/public/use-providerless-kinde-auth.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/frontend/hooks/public/use-providerless-kinde-auth.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/frontend/hooks/internal/use-session-sync.ts (4)
src/frontend/types.ts (3)
  • PublicKindeConfig (13-17)
  • KindeNextClientState (33-36)
  • FetchedKindeState (19-31)
src/frontend/hooks/internal/use-sync-state.tsx (1)
  • useSyncState (7-26)
src/frontend/constants.ts (1)
  • DefaultKindeNextClientState (3-16)
src/frontend/utils.ts (1)
  • fetchKindeState (34-83)

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/frontend/hooks/internal/use-session-sync.ts (2)

73-76: Good: log gated behind debug.

This addresses prior feedback to log only in debug.


47-68: Fix stale closure and hook deps; break the cycle with a ref.

updateTokensAndSetRefresh uses shouldAutoRefresh and refreshHandler but omits both from deps, risking stale closures. Add shouldAutoRefresh and read refreshHandler via a ref to avoid a cyclic dependency with refreshHandler.

- import { useCallback, useEffect, useState } from "react";
+ import { useCallback, useEffect, useRef, useState } from "react";
@@
-  const updateTokensAndSetRefresh = useCallback(
+  const refreshHandlerRef = useRef<(() => Promise<RefreshTokenResult>) | null>(null);
+
+  const updateTokensAndSetRefresh = useCallback(
     async (kindeState: Omit<FetchedKindeState, "env">) => {
@@
-      if (shouldAutoRefresh) {
-        const expiry = await calculateExpirySeconds();
-        setRefreshTimer(expiry, refreshHandler);
-      }
+      if (shouldAutoRefresh) {
+        const expiry = await calculateExpirySeconds();
+        if (expiry && expiry > 0 && refreshHandlerRef.current) {
+          setRefreshTimer(expiry, refreshHandlerRef.current);
+        }
+      }
@@
-    [setFetchedState],
+    [setFetchedState, shouldAutoRefresh],
   );
@@
-  const refreshHandler = useCallback(async (): Promise<RefreshTokenResult> => {
+  const refreshHandler = useCallback(async (): Promise<RefreshTokenResult> => {
     const setupResponse = await fetchKindeState();
@@
   }, [handleError, updateTokensAndSetRefresh]);
+
+  useEffect(() => {
+    refreshHandlerRef.current = refreshHandler;
+  }, [refreshHandler]);
🧹 Nitpick comments (3)
src/frontend/hooks/internal/use-session-sync.ts (3)

118-121: Mount-only effect: add brief justification comment.

Rule suppression is fine; add a one-liner rationale to aid future readers.

   useEffect(() => {
     setupState();
-    // eslint-disable-next-line react-hooks/exhaustive-deps
+    // Runs once on mount to avoid duplicate /setup calls; setupState is memoized.
+    // eslint-disable-next-line react-hooks/exhaustive-deps
   }, []);

91-96: Type the refetch result to avoid leaking implicit shapes.

setupState returns a token payload keyed by StorageKeys; give it an explicit return type so consumers don’t rely on implicit/any.

+type RefetchResult =
+  | { success: true; [StorageKeys.accessToken]: string | null; [StorageKeys.idToken]: string | null }
+  | { success: false; error: string };
-  const setupState = useCallback(async () => {
+  const setupState = useCallback(async (): Promise<RefetchResult> => {
@@
-    return {
+    return {
       success: true,
       [StorageKeys.accessToken]: setupResponse.kindeState.accessTokenEncoded,
       [StorageKeys.idToken]: setupResponse.kindeState.idTokenRaw,
     };

98-116: Improve error detail on refresh failures.

Propagate the actual server error when available to aid debugging (still safe for client logs).

-  if (!setupResponse.success) {
-    await handleError("User is unauthenticated or refresh failed");
-    return {
-      success: false,
-      error: "User is unauthenticated or refresh failed",
-    };
-  }
+  if (!setupResponse.success) {
+    const msg = setupResponse.error || "User is unauthenticated or refresh failed";
+    await handleError(msg);
+    return { success: false, error: msg };
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1baf7a4 and add4467.

📒 Files selected for processing (1)
  • src/frontend/hooks/internal/use-session-sync.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: Yoshify
PR: kinde-oss/kinde-auth-nextjs#405
File: src/frontend/KindeProvider.tsx:17-34
Timestamp: 2025-10-09T16:49:20.471Z
Learning: In kinde-auth-nextjs, the KindeProvider from kinde-oss/kinde-auth-react can safely mount with empty/undefined clientId, domain, and redirectUri props and then receive real values on re-render. The provider's callbacks (login, register, logout) are defined with useCallback and depend on these props, so they recreate when the props update. The init() logic runs once and primarily validates tokens already in storage (pre-populated by useSessionSync), so it doesn't require valid config at initial mount. This pattern allows mounting the provider immediately to avoid UI flash while config is being fetched.
🧬 Code graph analysis (1)
src/frontend/hooks/internal/use-session-sync.ts (4)
src/frontend/types.ts (3)
  • PublicKindeConfig (13-17)
  • KindeNextClientState (33-36)
  • FetchedKindeState (19-31)
src/frontend/hooks/internal/use-sync-state.tsx (1)
  • useSyncState (7-26)
src/frontend/constants.ts (1)
  • DefaultKindeNextClientState (3-16)
src/frontend/utils.ts (1)
  • fetchKindeState (34-83)
🔇 Additional comments (1)
src/frontend/hooks/internal/use-session-sync.ts (1)

35-45: Review comment is accurate but unfixable with current library constraints.

The concern is valid: if a refresh timer fires after handleError destroys the session, the callback will attempt to refresh a torn-down state, potentially causing cascading errors. However, @kinde-oss/kinde-auth-react/utils does not export a clearRefreshTimer or documented mechanism to cancel a scheduled refresh, and no local timer tracking exists in the codebase to manage this manually.

The suggested fix requires either an API addition to the external library, local timer tracking/workaround, or accepting the minor inefficiency of a failed refresh attempt on a destroyed session. This is a limitation of the current implementation, not a correctable bug in the code under review.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/frontend/hooks/internal/use-session-sync.ts (1)

21-26: Apply the suggested fixes from the previous review.

This function still has three unresolved issues from the previous review:

  1. Inconsistent constant usage: Line 22 uses the string literal "accessToken" instead of StorageKeys.accessToken, which is inconsistent with lines 56-57 and 124-125 that use the constant.

  2. Missing type validation: token.exp is not validated as a number before the arithmetic operation. If exp is missing or invalid, the calculation will produce unexpected results.

  3. Unhandled negative expiry: The function can return negative values for expired tokens, which should not schedule a timer (the token should be refreshed immediately instead).

Apply the suggested diff from the previous review:

 export const calculateExpirySeconds = async (): Promise<number | null> => {
-  const token = await getDecodedToken<JWTDecoded>("accessToken");
-  if (!token) return null;
-
-  return token.exp - Math.floor(Date.now() / 1000);
+  const token = await getDecodedToken<JWTDecoded>(StorageKeys.accessToken);
+  if (!token || typeof token.exp !== "number") return null;
+  const secondsRemaining = token.exp - Math.floor(Date.now() / 1000);
+  return Number.isFinite(secondsRemaining) ? Math.max(0, secondsRemaining) : null;
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between add4467 and e04a1e0.

📒 Files selected for processing (1)
  • src/frontend/hooks/internal/use-session-sync.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: Yoshify
PR: kinde-oss/kinde-auth-nextjs#405
File: src/frontend/KindeProvider.tsx:17-34
Timestamp: 2025-10-09T16:49:20.471Z
Learning: In kinde-auth-nextjs, the KindeProvider from kinde-oss/kinde-auth-react can safely mount with empty/undefined clientId, domain, and redirectUri props and then receive real values on re-render. The provider's callbacks (login, register, logout) are defined with useCallback and depend on these props, so they recreate when the props update. The init() logic runs once and primarily validates tokens already in storage (pre-populated by useSessionSync), so it doesn't require valid config at initial mount. This pattern allows mounting the provider immediately to avoid UI flash while config is being fetched.
📚 Learning: 2025-10-13T23:54:25.463Z
Learnt from: Yoshify
PR: kinde-oss/kinde-auth-nextjs#405
File: src/handlers/setup.ts:38-65
Timestamp: 2025-10-13T23:54:25.463Z
Learning: In the setup handler (`src/handlers/setup.ts`), it is intentional to check only the access token expiry (`isTokenExpired(accessTokenEncoded, 20)`) before attempting a token refresh, as confirmed by the maintainer. Checking the ID token expiry is not necessary in this context.

Applied to files:

  • src/frontend/hooks/internal/use-session-sync.ts
📚 Learning: 2025-03-28T12:12:55.741Z
Learnt from: DanielRivers
PR: kinde-oss/kinde-auth-nextjs#318
File: src/session/getFlag.js:23-23
Timestamp: 2025-03-28T12:12:55.741Z
Learning: The sessionManager function in the Kinde Auth NextJS package returns a Promise that must be properly awaited before using its result, especially when passing it to functions like kindeClient.getClaimValue().

Applied to files:

  • src/frontend/hooks/internal/use-session-sync.ts
🧬 Code graph analysis (1)
src/frontend/hooks/internal/use-session-sync.ts (4)
src/frontend/types.ts (3)
  • PublicKindeConfig (13-17)
  • KindeNextClientState (33-36)
  • FetchedKindeState (19-31)
src/frontend/hooks/internal/use-sync-state.tsx (1)
  • useSyncState (7-26)
src/frontend/constants.ts (1)
  • DefaultKindeNextClientState (3-16)
src/frontend/utils.ts (1)
  • fetchKindeState (34-83)
🔇 Additional comments (2)
src/frontend/hooks/internal/use-session-sync.ts (2)

97-99: Good use of ref pattern to resolve circular dependency.

The ref pattern correctly resolves the circular dependency issue flagged in previous reviews. By storing refreshHandler in a ref and reading it inside updateTokensAndSetRefresh, you avoid including refreshHandler in the dependency array while still accessing the latest version.


129-132: Appropriate use of eslint-disable for mount-only effect.

The eslint-disable comment correctly addresses the exhaustive-deps rule for this mount-only effect. The setupState callback is stable due to its own dependency array, so running it only on mount is intentional and safe.

@DanielRivers DanielRivers self-requested a review October 22, 2025 19:15
@DanielRivers DanielRivers merged commit 7e771b7 into main Oct 22, 2025
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants