Skip to content

poc: observable signin #6078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open

poc: observable signin #6078

wants to merge 18 commits into from

Conversation

jacekradko
Copy link
Member

@jacekradko jacekradko commented Jun 6, 2025

Description

Introduce the observable store to BaseResource:

  public get store() {
    return this._store;
  }

Detailed look at the observable resource stores pattern: https://clerk-js-sandbox-5zzwfsgco.clerkstage.dev/sign-in-observable

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Introduced reactive state management for resources and sign-in using Zustand, enabling real-time updates and improved error handling.
    • Added a comprehensive "Sign In Observable" demo page with interactive controls and state visualization.
    • Exposed new React hooks for resource store integration, allowing granular access to resource state in React components.
  • Improvements

    • Enhanced sign-in error reporting with detailed global and field-specific errors.
    • Refined sign-in and verification flows for better status tracking and debugging, including extensive logging.
    • Updated navigation to include the new "Sign In Observable" demo.
    • Improved sign-in hook to queue method calls until client readiness, removing explicit loading states.
  • Chores

    • Added Zustand as a dependency to both core and React packages.

Copy link

vercel bot commented Jun 6, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
clerk-js-sandbox ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 11, 2025 3:12am

Copy link

changeset-bot bot commented Jun 6, 2025

⚠️ No Changeset found

Latest commit: c52b7fb

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

pkg-pr-new bot commented Jun 8, 2025

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@6078

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@6078

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@6078

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@6078

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@6078

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@6078

@clerk/elements

npm i https://pkg.pr.new/@clerk/elements@6078

@clerk/clerk-expo

npm i https://pkg.pr.new/@clerk/clerk-expo@6078

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@6078

@clerk/express

npm i https://pkg.pr.new/@clerk/express@6078

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@6078

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@6078

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@6078

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@6078

@clerk/clerk-react

npm i https://pkg.pr.new/@clerk/clerk-react@6078

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@6078

@clerk/remix

npm i https://pkg.pr.new/@clerk/remix@6078

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@6078

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@6078

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@6078

@clerk/themes

npm i https://pkg.pr.new/@clerk/themes@6078

@clerk/types

npm i https://pkg.pr.new/@clerk/types@6078

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@6078

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@6078

commit: c52b7fb

Copy link

coderabbitai bot commented Jun 10, 2025

📝 Walkthrough

Walkthrough

This update introduces a reactive state management system for Clerk resources using Zustand, refactoring core resource classes and adding new store APIs and hooks. It implements a detailed observable sign-in component for debugging, updates package dependencies, and enhances error handling and async state tracking throughout the sign-in flow. New React hooks and TypeScript interfaces are provided for seamless integration.

Changes

File(s) Change Summary
packages/clerk-js/package.json
packages/react/package.json
Added [email protected] as a runtime dependency.
packages/clerk-js/sandbox/app.ts
packages/clerk-js/sandbox/template.html
Added a "Sign In Observable" component and navigation link for interactive sign-in state visualization and debugging in the sandbox app. Introduced new route and UI controls.
packages/clerk-js/src/core/resources/Base.ts Refactored BaseResource to integrate a Zustand store for resource state, adding a store property, dispatching state actions on fetch/mutate, and a reset method. Enhanced fetch options.
packages/clerk-js/src/core/resources/SignIn.ts Refactored SignIn to use a combined Zustand store for state management, added signInError, improved async error handling, and centralized status/error updates.
packages/clerk-js/src/core/resources/Verification.ts Refactored Verification to integrate Zustand state management for error handling, updated constructors and methods for reactive error state, and simplified initialization logic.
packages/clerk-js/src/core/resources/state.ts Introduced a generic Zustand resource state management module with state unions, actions, selectors, and store creation utilities.
packages/clerk-js/src/ui/components/SignIn/SignInStart.ts Removed useLoadingStatus hook, replaced with direct status tracking and logging, updated effect dependencies, and refined UI rendering based on new status logic.
packages/types/src/resource.ts Added ResourceStoreApi interface for minimal reactive store API and extended ClerkResource with a store property.
packages/types/src/signIn.ts Added signInError property to SignInResource interface.
packages/react/src/hooks/index.ts Exported createResourceStoreHooks from ./useResourceStore.
packages/react/src/hooks/useResourceStore.ts Added createResourceStoreHooks for React integration with Zustand resource stores, providing granular hooks for state, data, error, and status.
packages/react/src/hooks/useSignIn.ts Refactored useSignIn to remove isLoaded flag, implementing queued method calls on proxies for signIn and setActive until the client context is ready, ensuring async readiness.
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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: 6

🔭 Outside diff range comments (2)
packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx (2)

207-274: ⚠️ Potential issue

Remove all debugging console.log statements from the useEffect.

This effect contains extensive debugging logs that should be removed.

Apply this diff to clean up the debugging statements:

   useEffect(() => {
-    console.log('useEffect triggered');
-    console.log('organizationTicket:', organizationTicket);
-    console.log('signInFetchStatus:', signInFetchStatus);
-    console.log('signInStatus:', signInStatus);
-
     if (!organizationTicket || signInFetchStatus === 'fetching' || signInStatus === 'complete') {
-      console.log('Early return from useEffect');
       return;
     }

     if (clerkStatus === 'sign_up') {
       const paramsToForward = new URLSearchParams();
       if (organizationTicket) {
         paramsToForward.set('__clerk_ticket', organizationTicket);
       }
-      console.log('Navigating to signUpUrl with params:', paramsToForward.toString());
       void navigate(isCombinedFlow ? `create` : signUpUrl, { searchParams: paramsToForward });
       return;
     }

-    console.log('Setting card to loading state');
     card.setLoading();
     signIn
       .create({
         strategy: 'ticket',
         ticket: organizationTicket,
       })
       .then(res => {
-        console.log('API response:', res);
         switch (res.status) {
           case 'needs_first_factor':
-            console.log('Status: needs_first_factor');
             if (hasOnlyEnterpriseSSOFirstFactors(res)) {
-              console.log('Authenticating with Enterprise SSO');
               return authenticateWithEnterpriseSSO();
             }

             return navigate('factor-one');
           case 'needs_second_factor':
-            console.log('Status: needs_second_factor');
             return navigate('factor-two');
           case 'complete':
-            console.log('Status: complete');
             removeClerkQueryParam('__clerk_ticket');
             return clerk.setActive({
               session: res.createdSessionId,
               redirectUrl: afterSignInUrl,
             });
           default: {
-            console.error('Invalid API response status:', res.status);
             console.error(clerkInvalidFAPIResponse(res.status, supportEmail));
             return;
           }
         }
       })
       .catch(err => {
-        console.error('Error during signIn.create:', err);
         return attemptToRecoverFromSignInError(err);
       })
       .finally(() => {
         const isRedirectingToSSOProvider = hasOnlyEnterpriseSSOFirstFactors(signIn);
         if (isRedirectingToSSOProvider) return;

-        console.log('Setting card to idle state');
         card.setIdle();
       });
   }, [organizationTicket, signInFetchStatus, signInStatus]);

276-320: ⚠️ Potential issue

Remove console.log statements from OAuth error handling.

The OAuth error handling effect also contains debugging logs that should be cleaned up.

Apply this diff:

-    console.log('OAuth error handling useEffect triggered');
     async function handleOauthError() {
       const defaultErrorHandler = () => {
-        console.error('Default error handler triggered');
         card.setError('Unable to complete action at this time. If the problem persists please contact support.');
       };

       const error = signIn?.firstFactorVerification?.error;
       if (error) {
-        console.log('OAuth error detected:', error);
         switch (error.code) {
           // ... cases ...
           default:
             defaultErrorHandler();
         }

         // TODO: This is a workaround in order to reset the sign in attempt
         // so that the oauth error does not persist on full page reloads.
-        console.log('Resetting sign-in attempt');
         void (await signIn.create({}));
       }
     }
🧹 Nitpick comments (8)
packages/clerk-js/src/core/resources/__tests__/Environment.test.ts (1)

283-329: Consider extracting common test patterns into test utilities.

The explicit assertions are good, but there's repetition in the assertion patterns across this and other resource test files. Consider creating shared test utilities for common assertion patterns to reduce duplication and improve consistency.

Would you like me to help create shared test utilities for these common resource assertion patterns?

packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx (1)

527-530: Consider a more polished UI for production use.

The "Sign-in complete!" message appears to be a placeholder. Consider implementing a proper completion UI or redirecting to the appropriate page after successful sign-in.

Would you like me to suggest a more polished completion UI component?

packages/clerk-js/src/core/resources/state.ts (1)

8-12: Consider adding a 'fetching' state for clearer loading semantics.

The current state types use 'loading' which might be confused with initial loading. Consider using 'fetching' to make it clearer that this represents an active fetch operation.

This is just a naming suggestion for better clarity. The current implementation works well.

packages/clerk-js/sandbox/app.ts (1)

563-578: Consider simplifying the async route handler.

The route handler has duplicate Clerk loading checks. Since Clerk.load() is called before the route handler, the additional check might be redundant.

Consider verifying if the additional loading check is necessary, or if it can be handled by the main Clerk.load() call.

packages/clerk-js/src/core/resources/Base.ts (2)

83-97: Duplicated status mapping invites drift

fetchStatus manually maps internal state → public strings. Whenever you extend ResourceState you must update this switch or risk silent fall-through to 'idle'. A safer, future-proof pattern:

const map = {
  idle:    'idle',
  loading: 'fetching',
  success: 'fetched',
  error:   'error',
} as const;

return map[stateType];

or expose the raw state.type and let consumers translate.


308-312: reset() only clears the Zustand store, leaving instance fields stale

After calling reset() the resource’s own properties (id, status, etc.) still hold the last successful values, so subsequent UI reads can show contradictory information. Either:

  1. Re-instantiate the object (return new (this.constructor as any)()), or
  2. Explicitly null/reset critical fields inside reset().
packages/clerk-js/src/core/resources/SignIn.ts (2)

72-88: Unused type field & unconditional devtools

type: 'idle' | 'loading' | 'error' | 'success' is never updated, so the store always reports 'idle'. Remove it or wire it properly.

Additionally, devtools middleware is enabled for every instance (including production bundles) which adds ~1 KB and leaks store snapshots to the Redux DevTools extension. Wrap it:

const withDevtools = process.env.NODE_ENV === 'development' ? devtools : (f => f);
create<SignInUIState>()(withDevtools(set => ({})));

204-231: Partial error propagation

attemptFirstFactor pushes global errors to signInError but does not propagate field-specific errors returned by the API (they exist under errors[0].meta?.param_name). Exposing them would enable granular UI feedback.

Optional enhancement: extend updateError to parse and map ClerkAPIErrorJSON[] into fields.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06df74d and c8b0689.

⛔ Files ignored due to path filters (13)
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/Client.test.ts.snap is excluded by !**/*.snap
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/Environment.test.ts.snap is excluded by !**/*.snap
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/Organization.test.ts.snap is excluded by !**/*.snap
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/OrganizationDomain.test.ts.snap is excluded by !**/*.snap
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/OrganizationInvitation.test.ts.snap is excluded by !**/*.snap
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/OrganizationMembership.test.ts.snap is excluded by !**/*.snap
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/OrganizationMembershipRequest.test.ts.snap is excluded by !**/*.snap
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/OrganizationSuggestion.test.ts.snap is excluded by !**/*.snap
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/PublicUserData.test.ts.snap is excluded by !**/*.snap
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/Session.test.ts.snap is excluded by !**/*.snap
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/UserSettings.test.ts.snap is excluded by !**/*.snap
  • packages/clerk-js/src/core/resources/__tests__/__snapshots__/Waitlist.test.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • packages/clerk-js/package.json (1 hunks)
  • packages/clerk-js/sandbox/app.ts (6 hunks)
  • packages/clerk-js/sandbox/template.html (1 hunks)
  • packages/clerk-js/src/core/resources/Base.ts (4 hunks)
  • packages/clerk-js/src/core/resources/SignIn.ts (4 hunks)
  • packages/clerk-js/src/core/resources/Verification.ts (5 hunks)
  • packages/clerk-js/src/core/resources/__tests__/Client.test.ts (2 hunks)
  • packages/clerk-js/src/core/resources/__tests__/Environment.test.ts (3 hunks)
  • packages/clerk-js/src/core/resources/__tests__/Organization.test.ts (1 hunks)
  • packages/clerk-js/src/core/resources/__tests__/OrganizationDomain.test.ts (2 hunks)
  • packages/clerk-js/src/core/resources/__tests__/OrganizationInvitation.test.ts (1 hunks)
  • packages/clerk-js/src/core/resources/__tests__/OrganizationMembership.test.ts (2 hunks)
  • packages/clerk-js/src/core/resources/__tests__/OrganizationMembershipRequest.test.ts (1 hunks)
  • packages/clerk-js/src/core/resources/__tests__/OrganizationSuggestion.test.ts (1 hunks)
  • packages/clerk-js/src/core/resources/__tests__/PublicUserData.test.ts (1 hunks)
  • packages/clerk-js/src/core/resources/__tests__/Session.test.ts (2 hunks)
  • packages/clerk-js/src/core/resources/__tests__/UserSettings.test.ts (1 hunks)
  • packages/clerk-js/src/core/resources/__tests__/Waitlist.test.ts (1 hunks)
  • packages/clerk-js/src/core/resources/state.ts (1 hunks)
  • packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx (5 hunks)
  • packages/types/src/resource.ts (1 hunks)
  • packages/types/src/signIn.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
packages/clerk-js/src/core/resources/state.ts (1)
packages/types/src/json.ts (1)
  • ClerkAPIErrorJSON (337-353)
packages/clerk-js/src/core/resources/Base.ts (3)
packages/types/src/resource.ts (1)
  • ClerkResourceReloadParams (1-3)
packages/clerk-js/src/core/resources/state.ts (2)
  • ResourceStore (26-41)
  • createResourceStore (59-91)
packages/types/src/json.ts (1)
  • ClerkAPIErrorJSON (337-353)
packages/clerk-js/src/core/resources/Verification.ts (6)
packages/types/src/phoneCodeChannel.ts (1)
  • PhoneCodeChannel (9-9)
packages/types/src/verification.ts (1)
  • VerificationStatus (26-26)
packages/clerk-js/src/core/resources/Base.ts (2)
  • error (75-77)
  • data (79-81)
packages/types/src/api.ts (1)
  • ClerkAPIError (4-33)
packages/shared/src/error.ts (2)
  • errorToJSON (102-115)
  • parseError (87-100)
packages/types/src/json.ts (1)
  • VerificationJSON (311-322)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (28)
packages/clerk-js/package.json (1)

81-81:

✅ Verification successful

Verify the zustand version for security and currency.

Please ensure that version 5.0.5 is the latest stable version and check for any security advisories.


🌐 Web query:

What is the latest version of zustand and are there any known security vulnerabilities in version 5.0.5?

💡 Result:

Latest Zustand Version

The latest official release of Zustand is v5.0.5, published on May 21, 2025. This release brings minor improvements and a few new methods, including updates to the shallow compare function, new devtools cleanup features, and some internal refactoring and type improvements[1]. There are no major new features in this release; rather, it focuses on dropping deprecated code and ensuring compatibility with modern React and TypeScript versions[1][7][10].

Security Vulnerabilities in v5.0.5

There are currently no publicly reported security vulnerabilities specific to Zustand v5.0.5 in official documentation or discussions[1][7]. The changelogs and migration guides highlight breaking changes and improvements, but do not list any security issues or CVEs associated with this version[1][10].

As of the latest available information:

  • Zustand v5.0.5 is considered safe to use for production in terms of reported vulnerabilities[1].
  • No security advisories or warnings have been published in the main repository or in widely referenced React/JavaScript ecosystem discussions[1][7].
  • The focus of v5.x releases has been modernization, with deprecated features removed and stricter dependency/version requirements, further reducing exposure to known older issues[7][10].

Summary Table

Category Status/Details
Latest Version v5.0.5 (May 21, 2025)
Security in v5.0.5 No known vulnerabilities

If you are migrating from an earlier version, review the official migration guide for best practices and breaking changes[10]. For ongoing security monitoring, regularly check the [Zustand GitHub releases page][1] and security advisories on npm and GitHub.

Citations:


Zustand v5.0.5 is up-to-date and has no known vulnerabilities

The addition of
packages/clerk-js/package.json (line 81):

"zustand": "5.0.5"

is correct—v5.0.5 is the latest stable release (May 21, 2025) and no security advisories have been reported. No further changes are needed.

packages/types/src/resource.ts (1)

21-21:

✅ Verification successful

Verify that implementing classes provide the fetchStatus property.

The fetchStatus property addition looks good and follows standard async operation state patterns. However, this is a breaking change to the interface.


🏁 Script executed:

#!/bin/bash
# Description: Verify that BaseResource and other implementing classes provide fetchStatus property

# Search for classes that implement ClerkResource and check if they have fetchStatus
ast-grep --pattern $'class $_ implements $$$ClerkResource$$$ {
  $$$
}'

# Also search for fetchStatus implementation in resource files
rg -A 3 "fetchStatus" --type ts packages/clerk-js/src/core/resources/

Length of output: 495


fetchStatus implementation verified in BaseResource

  • Verified that packages/clerk-js/src/core/resources/Base.ts provides a public get fetchStatus(): 'idle' | 'fetching' | 'fetched' | 'error' getter, satisfying the new ClerkResource interface requirement.

This addition follows standard async-state patterns and all implementing classes inherit it via BaseResource. Approved.

packages/types/src/signIn.ts (1)

92-92:

✅ Verification successful

Verify that the SignIn class implements the signInError property.

The signInError property design is excellent - it properly separates global errors from field-specific validation errors. However, this is a breaking change to the interface.


🏁 Script executed:

#!/bin/bash
# Description: Verify that SignIn class implements the signInError property

# Search for SignIn class implementation and check for signInError property
ast-grep --pattern $'class SignIn $$$implements$$$ SignInResource {
  $$$
}'

# Search for signInError in SignIn implementation
rg -A 5 "signInError" packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 668


SignIn class now implements the new signInError property

Verified that in packages/clerk-js/src/core/resources/SignIn.ts the SignIn class defines and initializes:

  • signInError: { global: string | null; fields: Record<string, string> } = { global: null, fields: {} };
  • Updates it via this.signInError = { global: globalError, fields: fieldErrors };

The interface change is fully implemented—approving.

packages/clerk-js/src/core/resources/__tests__/Waitlist.test.ts (1)

12-16: Excellent improvement to test clarity.

Replacing the snapshot assertion with explicit property checks makes the test more maintainable and clearly shows what's being validated - the proper conversion of timestamps to Date objects.

packages/clerk-js/src/core/resources/__tests__/PublicUserData.test.ts (1)

16-25: Good improvement to test explicitness.

This change makes the test self-documenting by explicitly showing the expected camelCase property transformation (first_name → firstName, etc.). Much better than a snapshot for understanding the actual behavior being tested.

packages/clerk-js/sandbox/template.html (1)

191-197: Navigation item addition looks good.

The new "Sign In Observable" navigation item follows the established pattern and is appropriately positioned between related sign-in functionality.

packages/clerk-js/src/core/resources/__tests__/OrganizationSuggestion.test.ts (1)

20-32: Excellent improvement from snapshot to explicit assertions.

This change enhances test maintainability by explicitly verifying the expected object structure and property values instead of relying on fragile snapshots.

packages/clerk-js/src/core/resources/__tests__/OrganizationMembership.test.ts (2)

5-5: Good catch on the variable naming fix.

The variable name correction improves code clarity.


43-72: Excellent test assertion improvements.

The migration from snapshot to explicit object matching significantly improves test maintainability and clearly documents the expected object structure with comprehensive property validation.

packages/clerk-js/src/core/resources/__tests__/Organization.test.ts (1)

22-38: Solid improvement to test assertions.

The explicit object matching clearly defines expected behavior and improves test maintainability compared to snapshot testing.

packages/clerk-js/src/core/resources/__tests__/Session.test.ts (2)

50-62: Excellent explicit event verification.

The detailed verification of the 'token:update' event payload with structured JWT claims significantly improves test clarity over snapshot matching.


111-124: Comprehensive event dispatch validation.

The explicit verification of both 'token:update' and 'session:tokenResolved' events ensures the reactive state management works correctly with proper event payloads.

packages/clerk-js/src/core/resources/__tests__/OrganizationInvitation.test.ts (1)

19-31: Excellent improvement from snapshot to explicit assertions.

The replacement of toMatchSnapshot() with toMatchObject() makes the test more focused and less brittle. The explicit property checks clearly document the expected object structure and properly handle date type validation.

packages/clerk-js/src/core/resources/__tests__/OrganizationMembershipRequest.test.ts (1)

23-36: Well-structured explicit assertions improve test clarity.

The test properly validates both top-level properties and nested publicUserData structure using expect.objectContaining(). This approach is more maintainable than snapshot testing and clearly documents the expected object shape.

packages/clerk-js/src/core/resources/__tests__/UserSettings.test.ts (1)

7-24: Comprehensive validation of UserSettings default structure.

The explicit assertions provide clear documentation of the expected default UserSettings structure, including nested actions and attributes configurations. This is a significant improvement over snapshot testing for understanding the object's expected shape.

packages/clerk-js/src/core/resources/__tests__/OrganizationDomain.test.ts (2)

24-38: Proper validation of OrganizationDomain structure with verification object.

The explicit assertions correctly validate the expected properties including the nested verification object structure with proper date type checking for expiresAt.


56-65: Correct handling of nullable verification property.

The test properly validates the scenario where verification and affiliationEmailAddress are null, ensuring the object structure is validated in both presence and absence cases.

packages/clerk-js/src/core/resources/__tests__/Client.test.ts (2)

271-291: Comprehensive validation of Client object structure.

The explicit assertions properly validate the complex Client structure including nested signUp and signIn objects with their verification states. The use of expect.objectContaining() provides appropriate flexibility for nested object validation.


427-448: Correct snapshot structure validation with proper type handling.

The test appropriately validates the snapshot structure, correctly expecting Number types for timestamps in the snapshot format versus Date objects in the main instance. This distinction is important for snapshot serialization.

packages/clerk-js/src/core/resources/__tests__/Environment.test.ts (1)

9-43: LGTM! Improved test clarity with explicit assertions.

The switch from snapshot testing to explicit property assertions using toMatchObject improves test maintainability and clarity. The assertions now clearly document the expected shape and default values of the Environment resource.

packages/clerk-js/src/core/resources/state.ts (2)

1-4: LGTM! Well-structured reactive state management implementation.

The implementation uses Zustand effectively to create a reusable pattern for managing resource states with TypeScript generics. The devtools integration is helpful for debugging.


59-91: Excellent implementation with proper separation of concerns.

The use of selectors and the devtools integration with a named store makes this very debuggable. The state machine pattern with the reducer-like dispatch is clean and maintainable.

packages/clerk-js/src/core/resources/Verification.ts (4)

28-35: Good property organization and consistent initialization.

The reordering of properties improves readability by grouping related fields together.


46-50: Clean integration with the reactive store.

The updateError method properly transforms the error to JSON format before dispatching to the store, maintaining consistency with the store's expected format.


51-72: Proper null handling and early return pattern.

The early return for null data and the use of nullish coalescing operators improve the robustness of the deserialization logic.


122-126: Good use of nullish coalescing for cleaner constructors.

The simplified constructors using ?? operator are more concise and readable.

Also applies to: 144-145

packages/clerk-js/sandbox/app.ts (1)

1-2: Well-implemented POC demonstrating reactive sign-in functionality.

The new signInObservable component effectively demonstrates the reactive state management capabilities. The UI provides clear visibility into the sign-in state, fetch status, and errors, making it an excellent proof of concept.

A few observations:

  • Good error handling and loading states
  • Clear visual feedback with color-coded status indicators
  • Proper initialization flow with Clerk loading checks

Also applies to: 39-39, 99-99, 263-485

packages/clerk-js/src/core/resources/Base.ts (1)

57-61: Generic argument this might not behave as intended in all subclasses

Passing this as a type-parameter (createResourceStore<this>()) relies on TS’s contextual this type resolving to the instance type of every future subclass. This works today, but breaks if a subclass widens/narrows this, uses intersection/conditional types, or is transpiled with noImplicitThis quirks. Consider switching to a protected generic hook inside each subclass or:

protected _store = createResourceStore<this & BaseResource>();

to anchor the reference explicitly.

Comment on lines +391 to +392
console.log('needs_identifier');
console.log('res.supportedFirstFactors:', res.supportedFirstFactors);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove debugging logs from signInWithFields function.

         case 'needs_identifier':
-          console.log('needs_identifier');
-          console.log('res.supportedFirstFactors:', res.supportedFirstFactors);
           // Check if we need to initiate an enterprise sso flow
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log('needs_identifier');
console.log('res.supportedFirstFactors:', res.supportedFirstFactors);
case 'needs_identifier':
// Check if we need to initiate an enterprise sso flow
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx at lines 391 to
392, remove the console.log statements used for debugging inside the
signInWithFields function to clean up the code and avoid unnecessary logging in
production.

Comment on lines 197 to 206
const signInStatus = signIn.status;
const signInFetchStatus = signIn.fetchStatus;

useEffect(() => {
console.log('Component mounted');
console.log('Initial organizationTicket:', organizationTicket);
console.log('Initial signInFetchStatus:', signInFetchStatus);
console.log('Initial signInStatus:', signInStatus);
}, []);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove console.log statements before merging.

Multiple console.log statements have been added for debugging purposes. These should be removed before merging to production as they can clutter the console and potentially expose sensitive information.

Apply this diff to remove the debugging logs:

-  const signInStatus = signIn.status;
-  const signInFetchStatus = signIn.fetchStatus;
-
-  useEffect(() => {
-    console.log('Component mounted');
-    console.log('Initial organizationTicket:', organizationTicket);
-    console.log('Initial signInFetchStatus:', signInFetchStatus);
-    console.log('Initial signInStatus:', signInStatus);
-  }, []);
+  const signInStatus = signIn.status;
+  const signInFetchStatus = signIn.fetchStatus;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const signInStatus = signIn.status;
const signInFetchStatus = signIn.fetchStatus;
useEffect(() => {
console.log('Component mounted');
console.log('Initial organizationTicket:', organizationTicket);
console.log('Initial signInFetchStatus:', signInFetchStatus);
console.log('Initial signInStatus:', signInStatus);
}, []);
const signInStatus = signIn.status;
const signInFetchStatus = signIn.fetchStatus;
🤖 Prompt for AI Agents
In packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx around lines 197
to 206, remove all console.log statements inside the useEffect hook that logs
component mount and initial state values. These debugging logs should be deleted
to avoid cluttering the console and exposing sensitive information before
merging to production.

Comment on lines 449 to 469
signIn = await Clerk.client.signIn.create({
identifier: emailInput.value,
strategy: 'email_code',
});

if (!signIn) {
throw new Error('Failed to create SignIn instance');
}

// Initial update using getters
updateStatus();

await signIn.prepareFirstFactor({
strategy: 'email_code',
emailAddressId: emailInput.value,
});

await signIn.attemptFirstFactor({
strategy: 'email_code',
code: passwordInput.value,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the sign-in flow implementation.

The current implementation uses email_code strategy but then attempts to use the password input value as the code. This appears to be incorrect and will likely fail.

Based on the POC objectives mentioning "observable signin", you should either:

  1. Use password strategy if implementing password-based sign-in:
-      signIn = await Clerk.client.signIn.create({
-        identifier: emailInput.value,
-        strategy: 'email_code',
-      });
+      signIn = await Clerk.client.signIn.create({
+        identifier: emailInput.value,
+        password: passwordInput.value,
+      });

       // ... remove prepareFirstFactor and attemptFirstFactor calls
  1. Or implement proper email code flow with a code input field instead of password field.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
signIn = await Clerk.client.signIn.create({
identifier: emailInput.value,
strategy: 'email_code',
});
if (!signIn) {
throw new Error('Failed to create SignIn instance');
}
// Initial update using getters
updateStatus();
await signIn.prepareFirstFactor({
strategy: 'email_code',
emailAddressId: emailInput.value,
});
await signIn.attemptFirstFactor({
strategy: 'email_code',
code: passwordInput.value,
});
signIn = await Clerk.client.signIn.create({
identifier: emailInput.value,
password: passwordInput.value,
});
if (!signIn) {
throw new Error('Failed to create SignIn instance');
}
// Initial update using getters
updateStatus();
🤖 Prompt for AI Agents
In packages/clerk-js/sandbox/app.ts around lines 449 to 469, the sign-in flow
incorrectly uses the 'email_code' strategy but passes the password input value
as the code, which will fail. To fix this, either switch the strategy to
'password' and use the password input accordingly, or implement the proper email
code flow by replacing the password input with a code input field and use that
value for the 'code' parameter in attemptFirstFactor. Adjust the UI and input
handling to match the chosen strategy.

Comment on lines 236 to 256
protected async _baseGet<J extends ClerkResourceJSON | null>(opts: BaseFetchOptions = {}): Promise<this> {
const json = await BaseResource._fetch<J>(
{
method: 'GET',
path: this.path(),
rotatingTokenNonce: opts.rotatingTokenNonce,
},
opts,
);
this._store.getState().dispatch({ type: 'FETCH_START' });

return this.fromJSON((json?.response || json) as J);
try {
const { forceUpdateClient, fetchMaxTries, ...fetchOpts } = opts;
const json = await BaseResource._fetch<J>({
method: 'GET',
path: this.path(opts.action),
...fetchOpts,
});

const data = this.fromJSON((json?.response || json) as J);
this._store.getState().dispatch({ type: 'FETCH_SUCCESS', data });
return data;
} catch (error) {
this._store.getState().dispatch({
type: 'FETCH_ERROR',
error: error as ClerkAPIErrorJSON,
});
throw error;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Casting unknown error objects to ClerkAPIErrorJSON may pollute the store

The catch block treats every thrown value as ClerkAPIErrorJSON. Network errors wrapped in TypeError, runtime exceptions, or the custom ClerkRuntimeError will break consumers expecting { code, message }. Guard before dispatching:

const asApiError = isClerkApiError(error) ? error : { code: 'unknown', message: String(error) };
_store.getState().dispatch({ type: 'FETCH_ERROR', error: asApiError });

where isClerkApiError is a type-predicate.

🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/Base.ts around lines 236 to 256, the
catch block casts all caught errors to ClerkAPIErrorJSON, which can cause issues
if the error is not of that type. To fix this, add a type guard function
isClerkApiError to check if the error matches ClerkAPIErrorJSON, then dispatch
the error as is if true; otherwise, dispatch a generic error object with code
'unknown' and the error message string. This prevents invalid error objects from
polluting the store.

@jacekradko jacekradko marked this pull request as draft June 10, 2025 02:18
Comment on lines +856 to +860
statusContainer.innerHTML = `
<div class="text-blue-500">
<strong>Status:</strong> Creating SignIn with identifier: ${emailInput.value}
</div>
`;

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML Medium

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI about 4 hours ago

To fix the issue, the untrusted input (emailInput.value) must be sanitized or escaped before being interpolated into the HTML string. The best approach is to use a library like DOMPurify to sanitize the input or to escape the input using a utility function that converts special characters (e.g., <, >, &) into their HTML entity equivalents.

Steps to fix:

  1. Import a library like DOMPurify or implement a simple escaping function.
  2. Sanitize or escape emailInput.value before using it in the innerHTML assignment.
  3. Replace the vulnerable innerHTML assignment with a safe alternative.

Suggested changeset 2
packages/clerk-js/sandbox/app.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts
--- a/packages/clerk-js/sandbox/app.ts
+++ b/packages/clerk-js/sandbox/app.ts
@@ -1,2 +1,3 @@
 import type { SignInResource } from '@clerk/types';
+import DOMPurify from 'dompurify';
 
@@ -855,5 +856,6 @@
       // Update status to show we're creating SignIn
+      const sanitizedEmail = DOMPurify.sanitize(emailInput.value);
       statusContainer.innerHTML = `
         <div class="text-blue-500">
-          <strong>Status:</strong> Creating SignIn with identifier: ${emailInput.value}
+          <strong>Status:</strong> Creating SignIn with identifier: ${sanitizedEmail}
         </div>
EOF
@@ -1,2 +1,3 @@
import type { SignInResource } from '@clerk/types';
import DOMPurify from 'dompurify';

@@ -855,5 +856,6 @@
// Update status to show we're creating SignIn
const sanitizedEmail = DOMPurify.sanitize(emailInput.value);
statusContainer.innerHTML = `
<div class="text-blue-500">
<strong>Status:</strong> Creating SignIn with identifier: ${emailInput.value}
<strong>Status:</strong> Creating SignIn with identifier: ${sanitizedEmail}
</div>
packages/clerk-js/package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json
--- a/packages/clerk-js/package.json
+++ b/packages/clerk-js/package.json
@@ -80,3 +80,4 @@
     "swr": "2.3.3",
-    "zustand": "5.0.5"
+    "zustand": "5.0.5",
+    "dompurify": "^3.2.6"
   },
EOF
@@ -80,3 +80,4 @@
"swr": "2.3.3",
"zustand": "5.0.5"
"zustand": "5.0.5",
"dompurify": "^3.2.6"
},
This fix introduces these dependencies
Package Version Security advisories
dompurify (npm) 3.2.6 None
Copilot is powered by AI and may make mistakes. Always verify output.
Comment on lines +952 to +964
statusContainer.innerHTML = `
<div class="text-red-500 space-y-2">
<div><strong>SignIn Creation Failed:</strong></div>
<div class="text-sm">${errorMessage}</div>
<div class="text-xs text-gray-600">
${
error instanceof Error && error.message.includes("Couldn't find your account")
? 'Try using an email that exists in your Clerk application, or enable sign-up to create new accounts.'
: 'Check browser console for detailed error information'
}
</div>
</div>
`;

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML Medium

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI about 4 hours ago

To fix the issue, we need to ensure that any user-controlled input is properly escaped before being inserted into the DOM as HTML. Instead of directly interpolating errorMessage and other dynamic content into the innerHTML, we can use text nodes or a library like DOMPurify to sanitize the content. Alternatively, we can set the content as plain text using textContent for dynamic parts.

The best approach here is to replace the use of innerHTML with a combination of textContent for dynamic text and static HTML structure created programmatically. This ensures that no untrusted input is interpreted as HTML.


Suggested changeset 1
packages/clerk-js/sandbox/app.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts
--- a/packages/clerk-js/sandbox/app.ts
+++ b/packages/clerk-js/sandbox/app.ts
@@ -951,15 +951,24 @@
 
-      statusContainer.innerHTML = `
-        <div class="text-red-500 space-y-2">
-          <div><strong>SignIn Creation Failed:</strong></div>
-          <div class="text-sm">${errorMessage}</div>
-          <div class="text-xs text-gray-600">
-            ${
-              error instanceof Error && error.message.includes("Couldn't find your account")
-                ? 'Try using an email that exists in your Clerk application, or enable sign-up to create new accounts.'
-                : 'Check browser console for detailed error information'
-            }
-          </div>
-        </div>
-      `;
+      const errorContainer = document.createElement('div');
+      errorContainer.className = 'text-red-500 space-y-2';
+
+      const title = document.createElement('div');
+      title.innerHTML = '<strong>SignIn Creation Failed:</strong>';
+      errorContainer.appendChild(title);
+
+      const message = document.createElement('div');
+      message.className = 'text-sm';
+      message.textContent = errorMessage;
+      errorContainer.appendChild(message);
+
+      const suggestion = document.createElement('div');
+      suggestion.className = 'text-xs text-gray-600';
+      suggestion.textContent =
+        error instanceof Error && error.message.includes("Couldn't find your account")
+          ? 'Try using an email that exists in your Clerk application, or enable sign-up to create new accounts.'
+          : 'Check browser console for detailed error information';
+      errorContainer.appendChild(suggestion);
+
+      statusContainer.innerHTML = ''; // Clear existing content
+      statusContainer.appendChild(errorContainer);
 
EOF
@@ -951,15 +951,24 @@

statusContainer.innerHTML = `
<div class="text-red-500 space-y-2">
<div><strong>SignIn Creation Failed:</strong></div>
<div class="text-sm">${errorMessage}</div>
<div class="text-xs text-gray-600">
${
error instanceof Error && error.message.includes("Couldn't find your account")
? 'Try using an email that exists in your Clerk application, or enable sign-up to create new accounts.'
: 'Check browser console for detailed error information'
}
</div>
</div>
`;
const errorContainer = document.createElement('div');
errorContainer.className = 'text-red-500 space-y-2';

const title = document.createElement('div');
title.innerHTML = '<strong>SignIn Creation Failed:</strong>';
errorContainer.appendChild(title);

const message = document.createElement('div');
message.className = 'text-sm';
message.textContent = errorMessage;
errorContainer.appendChild(message);

const suggestion = document.createElement('div');
suggestion.className = 'text-xs text-gray-600';
suggestion.textContent =
error instanceof Error && error.message.includes("Couldn't find your account")
? 'Try using an email that exists in your Clerk application, or enable sign-up to create new accounts.'
: 'Check browser console for detailed error information';
errorContainer.appendChild(suggestion);

statusContainer.innerHTML = ''; // Clear existing content
statusContainer.appendChild(errorContainer);

Copilot is powered by AI and may make mistakes. Always verify output.
@jacekradko jacekradko marked this pull request as ready for review June 11, 2025 01:30
Copy link

@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: 5

♻️ Duplicate comments (3)
packages/clerk-js/src/core/resources/Base.ts (1)

218-223: ⚠️ Potential issue

Validate error types before casting to ClerkAPIErrorJSON

The catch blocks assume all errors are ClerkAPIErrorJSON, which could cause issues with network errors, runtime exceptions, or other error types.

As mentioned in the previous review, add type validation:

 } catch (error) {
+  const apiError = isClerkAPIError(error) ? error : {
+    code: 'unknown_error',
+    message: error instanceof Error ? error.message : String(error),
+    meta: {}
+  };
   this.store.getState().resource.dispatch({
     type: 'FETCH_ERROR',
-    error: error as ClerkAPIErrorJSON,
+    error: apiError,
   });
   throw error;
 }

You'll need to implement the isClerkAPIError type guard to check if the error has the expected structure.

Also applies to: 237-242

packages/clerk-js/sandbox/app.ts (2)

854-860: ⚠️ Potential issue

Fix XSS vulnerability: Escape HTML in status messages

Setting innerHTML with user-controlled content (email input value) creates an XSS vulnerability.

Escape the HTML or use textContent:

-      statusContainer.innerHTML = `
-        <div class="text-blue-500">
-          <strong>Status:</strong> Creating SignIn with identifier: ${emailInput.value}
-        </div>
-      `;
+      const statusDiv = document.createElement('div');
+      statusDiv.className = 'text-blue-500';
+      const strongEl = document.createElement('strong');
+      strongEl.textContent = 'Status:';
+      statusDiv.appendChild(strongEl);
+      statusDiv.appendChild(document.createTextNode(` Creating SignIn with identifier: ${emailInput.value}`));
+      statusContainer.innerHTML = '';
+      statusContainer.appendChild(statusDiv);

952-964: ⚠️ Potential issue

Fix XSS vulnerability: Escape error messages

Setting innerHTML with error messages that may contain user input creates an XSS vulnerability.

Use DOM methods to safely display the error:

-      statusContainer.innerHTML = `
-        <div class="text-red-500 space-y-2">
-          <div><strong>SignIn Creation Failed:</strong></div>
-          <div class="text-sm">${errorMessage}</div>
-          <div class="text-xs text-gray-600">
-            ${
-              error instanceof Error && error.message.includes("Couldn't find your account")
-                ? 'Try using an email that exists in your Clerk application, or enable sign-up to create new accounts.'
-                : 'Check browser console for detailed error information'
-            }
-          </div>
-        </div>
-      `;
+      const errorContainer = document.createElement('div');
+      errorContainer.className = 'text-red-500 space-y-2';
+      
+      const titleDiv = document.createElement('div');
+      const titleStrong = document.createElement('strong');
+      titleStrong.textContent = 'SignIn Creation Failed:';
+      titleDiv.appendChild(titleStrong);
+      
+      const messageDiv = document.createElement('div');
+      messageDiv.className = 'text-sm';
+      messageDiv.textContent = errorMessage;
+      
+      const helpDiv = document.createElement('div');
+      helpDiv.className = 'text-xs text-gray-600';
+      helpDiv.textContent = error instanceof Error && error.message.includes("Couldn't find your account")
+        ? 'Try using an email that exists in your Clerk application, or enable sign-up to create new accounts.'
+        : 'Check browser console for detailed error information';
+      
+      errorContainer.appendChild(titleDiv);
+      errorContainer.appendChild(messageDiv);
+      errorContainer.appendChild(helpDiv);
+      
+      statusContainer.innerHTML = '';
+      statusContainer.appendChild(errorContainer);
🧹 Nitpick comments (1)
packages/clerk-js/sandbox/app.ts (1)

264-1014: Well-structured debugging component for observable stores

This sandbox component provides excellent visibility into the Zustand store architecture with clear separation of concerns:

  • Combined store view
  • Resource slice view
  • SignIn slice view
  • Comprehensive debugging controls

The inline documentation explaining the store architecture is particularly helpful for understanding the flattened, consistent structure with namespaced slices.

Consider extracting the store structure documentation (lines 390-414) into a separate markdown file or component for reusability across other debugging tools.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8b0689 and 084c46b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • packages/clerk-js/package.json (1 hunks)
  • packages/clerk-js/sandbox/app.ts (6 hunks)
  • packages/clerk-js/sandbox/template.html (1 hunks)
  • packages/clerk-js/src/core/resources/Base.ts (4 hunks)
  • packages/clerk-js/src/core/resources/SignIn.ts (4 hunks)
  • packages/clerk-js/src/core/resources/Verification.ts (5 hunks)
  • packages/clerk-js/src/core/resources/state.ts (1 hunks)
  • packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx (5 hunks)
  • packages/react/package.json (1 hunks)
  • packages/react/src/hooks/index.ts (1 hunks)
  • packages/react/src/hooks/useResourceStore.ts (1 hunks)
  • packages/react/src/hooks/useSignIn.ts (1 hunks)
  • packages/types/src/resource.ts (2 hunks)
  • packages/types/src/signIn.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • packages/react/package.json
  • packages/react/src/hooks/index.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • packages/clerk-js/package.json
  • packages/types/src/signIn.ts
  • packages/clerk-js/sandbox/template.html
  • packages/clerk-js/src/core/resources/state.ts
  • packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
  • packages/clerk-js/src/core/resources/Verification.ts
  • packages/clerk-js/src/core/resources/SignIn.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
packages/react/src/hooks/useSignIn.ts (5)
packages/vue/src/composables/useSignIn.ts (1)
  • useSignIn (32-55)
packages/shared/src/react/contexts.tsx (2)
  • useAssertWrappedByClerkProvider (98-98)
  • useClientContext (87-87)
packages/react/src/isomorphicClerk.ts (1)
  • client (633-640)
packages/types/src/signIn.ts (1)
  • SignInResource (76-127)
packages/types/src/clerk.ts (1)
  • SetActive (1073-1073)
packages/react/src/hooks/useResourceStore.ts (3)
packages/clerk-js/src/core/resources/state.ts (1)
  • ResourceStore (26-37)
packages/react/src/hooks/index.ts (1)
  • createResourceStoreHooks (5-5)
packages/clerk-js/src/core/resources/Base.ts (1)
  • store (63-65)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep/ci
🔇 Additional comments (3)
packages/types/src/resource.ts (1)

5-16: Well-designed minimal store interface

The ResourceStoreApi interface provides a clean abstraction that matches Zustand's API without creating a dependency. The overloaded setState signatures correctly handle both partial and full state updates, and the subscribe method follows the standard observer pattern.

Also applies to: 33-36

packages/clerk-js/src/core/resources/Base.ts (2)

63-65: Clean store accessor implementation

The getter provides a clean public API for accessing the resource store while keeping the underlying implementation protected.


277-279: Useful reset method for clearing resource state

The reset method provides a clean way to dispatch the RESET action to clear the resource state.

Comment on lines +66 to +72
setTimeout(() => {
const currentClient = useClientContext();
if (currentClient) {
processQueue(currentClient.signIn, isomorphicClerk.setActive);
}
}, 0);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: React Hook called inside setTimeout callback

Calling useClientContext() inside the setTimeout callback violates React's rules of hooks. Hooks must be called at the top level of your function component or custom hook, not inside callbacks, conditions, or loops.

The client reference is already captured in the closure. Use it directly:

-              setTimeout(() => {
-                const currentClient = useClientContext();
-                if (currentClient) {
-                  processQueue(currentClient.signIn, isomorphicClerk.setActive);
-                }
-              }, 0);
+              setTimeout(() => {
+                // Re-check the client from the outer scope
+                const currentClient = client;
+                if (currentClient) {
+                  processQueue(currentClient.signIn, isomorphicClerk.setActive);
+                }
+              }, 0);

Alternatively, if you need to check for an updated client value, you could trigger a re-render through a state update mechanism instead of trying to call the hook again.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setTimeout(() => {
const currentClient = useClientContext();
if (currentClient) {
processQueue(currentClient.signIn, isomorphicClerk.setActive);
}
}, 0);
});
setTimeout(() => {
// Re-check the client from the outer scope
const currentClient = client;
if (currentClient) {
processQueue(currentClient.signIn, isomorphicClerk.setActive);
}
}, 0);
});
🤖 Prompt for AI Agents
In packages/react/src/hooks/useSignIn.ts around lines 66 to 72, the
useClientContext hook is incorrectly called inside a setTimeout callback,
violating React's rules of hooks. To fix this, remove the useClientContext call
from inside the setTimeout and instead use the client reference already captured
in the closure directly within the callback. If you need to handle updated
client values, implement a state update to trigger a re-render rather than
calling the hook inside the callback.

Comment on lines +26 to +40
const processQueue = (signIn: SignInResource, setActive: SetActive) => {
while (callQueue.length > 0) {
const queuedCall = callQueue.shift();
if (!queuedCall) continue;

const { target, method, args, resolve, reject } = queuedCall;
try {
const targetObj = target === 'setActive' ? setActive : signIn;
const result = (targetObj as any)[method](...args);
resolve(result);
} catch (error) {
reject(error);
}
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve robustness of the processQueue function

The current implementation has potential issues with method existence checking and error handling.

Consider these improvements:

 const processQueue = (signIn: SignInResource, setActive: SetActive) => {
   while (callQueue.length > 0) {
     const queuedCall = callQueue.shift();
     if (!queuedCall) continue;

     const { target, method, args, resolve, reject } = queuedCall;
     try {
       const targetObj = target === 'setActive' ? setActive : signIn;
-      const result = (targetObj as any)[method](...args);
+      
+      // Validate method exists
+      if (typeof (targetObj as any)[method] !== 'function') {
+        reject(new Error(`Method ${method} does not exist on ${target}`));
+        continue;
+      }
+      
+      const result = (targetObj as any)[method](...args);
       resolve(result);
     } catch (error) {
+      console.error(`Error calling ${target}.${method}:`, error);
       reject(error);
     }
   }
 };

This adds method validation and better error context for debugging.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const processQueue = (signIn: SignInResource, setActive: SetActive) => {
while (callQueue.length > 0) {
const queuedCall = callQueue.shift();
if (!queuedCall) continue;
const { target, method, args, resolve, reject } = queuedCall;
try {
const targetObj = target === 'setActive' ? setActive : signIn;
const result = (targetObj as any)[method](...args);
resolve(result);
} catch (error) {
reject(error);
}
}
};
const processQueue = (signIn: SignInResource, setActive: SetActive) => {
while (callQueue.length > 0) {
const queuedCall = callQueue.shift();
if (!queuedCall) continue;
const { target, method, args, resolve, reject } = queuedCall;
try {
const targetObj = target === 'setActive' ? setActive : signIn;
// Validate method exists
if (typeof (targetObj as any)[method] !== 'function') {
reject(new Error(`Method ${method} does not exist on ${target}`));
continue;
}
const result = (targetObj as any)[method](...args);
resolve(result);
} catch (error) {
console.error(`Error calling ${target}.${method}:`, error);
reject(error);
}
}
};
🤖 Prompt for AI Agents
In packages/react/src/hooks/useSignIn.ts around lines 26 to 40, the processQueue
function lacks validation to check if the method exists on the target object
before invoking it, which can cause runtime errors. To fix this, add a check to
verify that the method is a function on the target object before calling it, and
if not, reject with a clear error message. Also, enhance the catch block to
include more context about the error for better debugging.

Comment on lines +4 to +11
interface ResourceStore<T> {
state: any;
dispatch: (action: any) => void;
getData: () => T | null;
getError: () => any;
status: () => 'idle' | 'loading' | 'error' | 'success';
hasError: () => boolean;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Interface doesn't match the actual store structure

Based on the relevant code snippets, the actual ResourceStore<T> type has a nested structure with properties under a resource namespace. The current interface appears to be flattened.

Update the interface to match the actual structure:

 interface ResourceStore<T> {
-  state: any;
-  dispatch: (action: any) => void;
-  getData: () => T | null;
-  getError: () => any;
-  status: () => 'idle' | 'loading' | 'error' | 'success';
-  hasError: () => boolean;
+  resource: {
+    status: 'idle' | 'loading' | 'error' | 'success';
+    data: T | null;
+    error: any;
+    dispatch: (action: any) => void;
+    getData: () => T | null;
+    getError: () => any;
+    hasError: () => boolean;
+    getStatus: () => 'idle' | 'loading' | 'error' | 'success';
+  };
 }

Additionally, update the hooks to use the correct paths:

-  const useResourceState = () => useStore(store, state => state.state);
+  const useResourceState = () => useStore(store, state => state.resource);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface ResourceStore<T> {
state: any;
dispatch: (action: any) => void;
getData: () => T | null;
getError: () => any;
status: () => 'idle' | 'loading' | 'error' | 'success';
hasError: () => boolean;
}
interface ResourceStore<T> {
resource: {
status: 'idle' | 'loading' | 'error' | 'success';
data: T | null;
error: any;
dispatch: (action: any) => void;
getData: () => T | null;
getError: () => any;
hasError: () => boolean;
getStatus: () => 'idle' | 'loading' | 'error' | 'success';
};
}
🤖 Prompt for AI Agents
In packages/react/src/hooks/useResourceStore.ts between lines 4 and 11, the
ResourceStore<T> interface is incorrectly flattened and does not reflect the
actual nested structure under a resource property. Update the interface to nest
state, dispatch, getData, getError, status, and hasError inside a resource
object to match the real store shape. Then, revise any hook usages to access
these properties through the resource namespace accordingly.

Comment on lines +268 to +279
const originalError = console.error;
console.error = function (...args) {
if (args.some(arg => typeof arg === 'string' && arg.includes('dispatch is not a function'))) {
console.log('=== Store Dispatch Error Detected ===');
console.log('Arguments:', args);
console.log('Current signIn:', signIn);
if (signIn?.store) {
console.log('SignIn store state:', signIn.store.getState());
}
}
originalError.apply(console, args);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid overriding global console.error

Overriding console.error globally can interfere with other error handling and debugging. This approach is risky and should be limited to development/debugging contexts.

Consider using a more targeted approach:

-  const originalError = console.error;
-  console.error = function (...args) {
-    if (args.some(arg => typeof arg === 'string' && arg.includes('dispatch is not a function'))) {
-      console.log('=== Store Dispatch Error Detected ===');
-      console.log('Arguments:', args);
-      console.log('Current signIn:', signIn);
-      if (signIn?.store) {
-        console.log('SignIn store state:', signIn.store.getState());
-      }
-    }
-    originalError.apply(console, args);
-  };
+  // Add error boundary or try-catch blocks around specific operations
+  const debugStoreError = (error: unknown) => {
+    if (error instanceof Error && error.message.includes('dispatch is not a function')) {
+      console.log('=== Store Dispatch Error Detected ===');
+      console.log('Error:', error);
+      console.log('Current signIn:', signIn);
+      if (signIn?.store) {
+        console.log('SignIn store state:', signIn.store.getState());
+      }
+    }
+  };

Then wrap specific operations with try-catch blocks that call debugStoreError.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const originalError = console.error;
console.error = function (...args) {
if (args.some(arg => typeof arg === 'string' && arg.includes('dispatch is not a function'))) {
console.log('=== Store Dispatch Error Detected ===');
console.log('Arguments:', args);
console.log('Current signIn:', signIn);
if (signIn?.store) {
console.log('SignIn store state:', signIn.store.getState());
}
}
originalError.apply(console, args);
};
// Add error boundary or try‐catch blocks around specific operations
const debugStoreError = (error: unknown) => {
if (error instanceof Error && error.message.includes('dispatch is not a function')) {
console.log('=== Store Dispatch Error Detected ===');
console.log('Error:', error);
console.log('Current signIn:', signIn);
if (signIn?.store) {
console.log('SignIn store state:', signIn.store.getState());
}
}
};
🤖 Prompt for AI Agents
In packages/clerk-js/sandbox/app.ts around lines 268 to 279, avoid globally
overriding console.error as it can disrupt other error handling. Instead, remove
the global override and implement a dedicated function debugStoreError to log
the specific error details. Then, wrap the particular code sections where the
"dispatch is not a function" error might occur in try-catch blocks, calling
debugStoreError within the catch to handle and log the error in a targeted
manner.

Copy link

@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: 0

♻️ Duplicate comments (3)
packages/clerk-js/sandbox/app.ts (3)

268-279: 🛠️ Refactor suggestion

Avoid re-assigning console.error globally (again)

We flagged this pattern in the previous review; it is still here unchanged.
Overriding console.error can break other logging/error handling, leaks across every route in the sandbox, and is never reverted in cleanup. Wrap the logic in a scoped helper or restore the original in the returned cleanup:

+const debugStoreError = (...args: unknown[]) => {
+  if (args.some(arg => typeof arg === 'string' && arg.includes('dispatch is not a function'))) {
+    console.log('=== Store Dispatch Error Detected ===', { args, signIn: currentSignIn });
+    console.log('SignIn store state:', currentSignIn?.store?.getState());
+  }
+};
+
-const originalError = console.error;
-console.error = function (...args) {
-  ...
-  originalError.apply(console, args);
-};
+const originalError = console.error;
+console.error = (...args: unknown[]) => {
+  debugStoreError(...args);
+  originalError(...args);
+};
...
// inside cleanup
originalError && (console.error = originalError);

952-964: ⚠️ Potential issue

Error messages rendered via innerHTML are not escaped

errorMessage may contain HTML returned from the backend or thrown locally.
Render it via textContent, or escape prior to insertion, to avoid XSS vectors.

-statusContainer.innerHTML = `
-  <div class="text-red-500 space-y-2">
-    <div><strong>SignIn Creation Failed:</strong></div>
-    <div class="text-sm">${errorMessage}</div>
-    ...
-  </div>
-`;
+const msg = document.createElement('div');
+msg.className = 'text-red-500';
+msg.textContent = `SignIn Creation Failed: ${errorMessage}`;
+statusContainer.innerHTML = '';
+statusContainer.appendChild(msg);

856-860: ⚠️ Potential issue

Unsanitised user input injected into HTML – XSS risk

emailInput.value is interpolated directly into innerHTML.
A crafted value like <img src=x onerror=alert(1)> will execute in the sandbox.

-statusContainer.innerHTML = `
-  <div class="text-blue-500">
-    <strong>Status:</strong> Creating SignIn with identifier: ${emailInput.value}
-  </div>
-`;
+statusContainer.textContent =
+  `Status: Creating SignIn with identifier: ${emailInput.value}`;

Prefer textContent or escape the value before insertion.
Audit other occurrences of innerHTML that display arbitrary strings (error.message, signInError.global, etc.).

🧹 Nitpick comments (1)
packages/clerk-js/sandbox/app.ts (1)

264-1014: mountSignInObservable exceeds 700 lines – extract into smaller units

The function mixes UI construction, Clerk polling, demo logic, store inspection and cleanup.
Splitting it into focused helpers (buildUI, wireDemoButtons, waitForClerk, subscribeStore, etc.) will:

• Improve readability & navigation
• Allow re-use in other demos
• Simplify unit testing

No behavioural change required—just move the blocks into private helpers and keep a slim orchestrator.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 084c46b and e49cd1b.

📒 Files selected for processing (2)
  • packages/clerk-js/sandbox/app.ts (6 hunks)
  • packages/clerk-js/src/core/resources/SignIn.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/clerk-js/src/core/resources/SignIn.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/clerk-js/sandbox/app.ts (3)
packages/clerk-js/src/core/clerk.ts (2)
  • Clerk (182-2676)
  • status (267-269)
packages/types/src/signIn.ts (1)
  • SignInResource (76-127)
packages/clerk-js/src/core/resources/SignIn.ts (2)
  • status (135-137)
  • status (139-141)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: semgrep/ci
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Analyze (javascript-typescript)

Copy link

@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

♻️ Duplicate comments (2)
packages/react/src/hooks/useSignIn.ts (2)

26-40: Still missing method-existence guard in processQueue

Previous review already highlighted that invoking (targetObj as any)[method] without checking it’s a function can explode at runtime.
Adding a simple typeof fn === 'function' validation will turn a crash into a controlled rejection.

This is the same comment as before.


66-71: Hook inside callback – React rule of hooks violation persists

useClientContext() is still called inside a setTimeout callback. React hooks must run unconditionally at the top level of a component/hook. Keeping this will break in strict-mode and future React versions.

Same issue flagged in the previous review.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8da5fd1 and 8c80879.

📒 Files selected for processing (1)
  • packages/react/src/hooks/useSignIn.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines +24 to +25
const callQueue: QueuedCall[] = [];

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Queue is recreated on every render → pending calls may be lost

callQueue lives in the render scope, so a re-render (e.g. when client becomes available) allocates a fresh array.
Promises enqueued by proxies created in earlier renders will never be flushed, leaving callers hanging and causing silent logic breaks.

Fix: hoist the queue into a stable ref (or module-level) and process it from a useEffect so it survives renders:

-import { useClientContext } from '@clerk/shared/react';
+import { useClientContext } from '@clerk/shared/react';
+import { useRef, useEffect } from 'react';
...
-  const callQueue: QueuedCall[] = [];
+  const callQueueRef = useRef<QueuedCall[]>([]);
...
-              callQueue.push({
+              callQueueRef.current.push({
                 target,
...
-    while (callQueue.length > 0) {
-      const queuedCall = callQueue.shift();
+    while (callQueueRef.current.length > 0) {
+      const queuedCall = callQueueRef.current.shift();
...
-  if (client) {
-    processQueue(client.signIn, isomorphicClerk.setActive);
+  useEffect(() => {
+    if (client) {
+      processQueue(client.signIn, isomorphicClerk.setActive);
+    }
+  }, [client]);

Without this change, any call issued before client resolves is effectively dropped.

Also applies to: 51-65

🤖 Prompt for AI Agents
In packages/react/src/hooks/useSignIn.ts around lines 24 to 25 and 51 to 65, the
callQueue array is declared inside the render scope, causing it to be recreated
on every render and losing any pending calls. To fix this, move callQueue into a
stable React ref using useRef so it persists across renders, and handle
processing the queue inside a useEffect hook to ensure all queued calls are
properly flushed once the client becomes available.

Comment on lines 80 to 83
isLoaded: true,
signIn: client.signIn,
setActive: isomorphicClerk.setActive,
signIn: createProxy('signIn') as SignInResource,
setActive: createProxy('setActive') as SetActive,
};
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

isLoaded: true is misleading when real resources are still proxies

Consumers previously relied on isLoaded to know when signIn was usable.
Now the hook always returns isLoaded: true, but the returned objects may be mere proxies that queue work and can still fail if the client never materialises (e.g. SSR or auth errors).

Either:

  1. Restore the explicit isLoaded signal, or
  2. Rename/remove the flag to avoid breaking existing semantic contracts and update docs/tests accordingly.

Please verify downstream usage before shipping.


🏁 Script executed:

#!/usr/bin/env bash
set -e

# Locate the useSignIn hook file
file=$(fd useSignIn.ts)

echo "Located file: $file"
echo
echo "----- Context around lines 80–100 -----"
sed -n '1,120p' "$file" | tail -n +60

echo
echo "----- All createProxy occurrences in the repo -----"
rg -n "createProxy\(" .

Length of output: 985


Action Required: Align isLoaded with actual readiness
The useSignIn hook unconditionally returns isLoaded: true while signIn and setActive remain proxy objects that may queue calls (and can still error if the client never initializes). This breaks existing contracts where consumers awaited isLoaded before invoking signIn.

Please address one of the following:

  • Restore an explicit isLoaded flag that only becomes true once the underlying client is fully initialized.
  • Remove or rename the flag, and update documentation and tests to reflect the new behavior.

• Location: packages/react/src/hooks/useSignIn.ts (around lines 80–83)
• Verify downstream consumers relying on isLoaded before merging.

🤖 Prompt for AI Agents
In packages/react/src/hooks/useSignIn.ts around lines 80 to 83, the isLoaded
flag is always set to true even though signIn and setActive are proxies that may
not be fully initialized, breaking the expected contract for consumers. Fix this
by restoring an explicit isLoaded flag that only becomes true once the
underlying client is fully ready, or alternatively remove or rename the flag and
update all related documentation and tests to reflect this change. Also verify
downstream usage to ensure no breaking changes occur before merging.

Copy link

@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: 4

🧹 Nitpick comments (1)
packages/clerk-js/src/core/resources/SignIn.ts (1)

146-152: Getter/Setter repeatedly reads the store – cache the selector

Every access to this.status triggers this._store.getState(). In hot render paths that can add measurable overhead.
Store creators already expose a selector API; keeping a tiny closure avoids the extra object traversal:

-  get status(): SignInStatus | null {
-    return (this._store.getState() as unknown as CombinedSignInStore).signin.status;
-  }
-
-  set status(newStatus: SignInStatus | null) {
-    (this._store.getState() as unknown as CombinedSignInStore).signin.setStatus(newStatus);
-  }
+  private readonly _sel = () => this._store.getState() as CombinedSignInStore;
+
+  get status(): SignInStatus | null {
+    return this._sel().signin.status;
+  }
+
+  set status(newStatus: SignInStatus | null) {
+    this._sel().signin.setStatus(newStatus);
+  }

A similar micro-optimisation can be applied to signInError.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c80879 and c52b7fb.

📒 Files selected for processing (1)
  • packages/clerk-js/src/core/resources/SignIn.ts (5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/clerk-js/src/core/resources/SignIn.ts (4)
packages/types/src/signIn.ts (9)
  • SignInStatus (129-134)
  • SignInResource (76-127)
  • SignInFirstFactor (142-153)
  • SignInIdentifier (136-140)
  • SignInSecondFactor (155-155)
  • UserData (157-162)
  • SignInJSON (263-278)
  • SignInCreateParams (191-232)
  • AttemptFirstFactorParams (178-185)
packages/clerk-js/src/core/resources/Verification.ts (2)
  • error (106-108)
  • Verification (74-160)
packages/clerk-js/src/core/resources/state.ts (1)
  • ResourceStore (26-37)
packages/shared/src/underscore.ts (1)
  • deepSnakeToCamel (111-111)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines +170 to +180
create = async (params: SignInCreateParams): Promise<SignInResource> => {
try {
const result = await this._basePost({
path: this.pathRoot,
body: params,
});
return result;
} catch (error) {
this.updateError(error.message);
throw error;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

error is typed as unknown; accessing message without narrowing may crash

catch clauses default to unknown (TS 5+). If the thrown value is a plain string or a non-Error object, error.message is undefined, and updateError receives undefined (violates the non-nullable contract).

-    } catch (error) {
-      this.updateError(error.message);
-      throw error;
+    } catch (error: unknown) {
+      const msg =
+        typeof error === 'object' && error && 'message' in error ? (error as Error).message : String(error);
+      this.updateError(msg);
+      throw error;
     }

Replicate the same guard in attemptFirstFactor.

Also applies to: 249-274

🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 170 to 180, the
catch block accesses error.message directly, but error is typed as unknown in TS
5+, which can cause runtime issues if error is not an Error object. Fix this by
adding a type guard to check if error is an instance of Error before accessing
error.message; if not, provide a fallback string to updateError. Apply the same
fix to the catch blocks in lines 249 to 274 and replicate the guard used in
attemptFirstFactor.

Comment on lines +131 to +138
devtools(
(set, get) => ({
...createResourceSlice<SignIn>(set, get),
...createSignInSlice(set, get),
}),
{ name: 'SignInStore' },
),
) as any;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid shipping devtools middleware in production bundles

zustand/middleware devtools adds a sizable footprint and opens an extra connection to the Redux DevTools extension. Unless you tree-shake it out at build time (e.g., via process.env.NODE_ENV !== 'production' && devtools(...)), every consumer of @clerk/clerk-js will pay the cost.

-    this._store = createStore<CombinedSignInStore>()(
-      devtools(
-        (set, get) => ({
-          ...createResourceSlice<SignIn>(set, get),
-          ...createSignInSlice(set, get),
-        }),
-        { name: 'SignInStore' },
-      ),
-    ) as any;
+    const initializer = (set: any, get: any) => ({
+      ...createResourceSlice<SignIn>(set, get),
+      ...createSignInSlice(set, get),
+    });
+
+    this._store = createStore<CombinedSignInStore>()(
+      process.env.NODE_ENV === 'production' ? initializer : devtools(initializer, { name: 'SignInStore' }),
+    ) as any;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
devtools(
(set, get) => ({
...createResourceSlice<SignIn>(set, get),
...createSignInSlice(set, get),
}),
{ name: 'SignInStore' },
),
) as any;
// Define a shared initializer for the store slices
const initializer = (set: any, get: any) => ({
...createResourceSlice<SignIn>(set, get),
...createSignInSlice(set, get),
});
// Only apply devtools in non-production environments
this._store = createStore<CombinedSignInStore>()(
process.env.NODE_ENV === 'production'
? initializer
: devtools(initializer, { name: 'SignInStore' }),
) as any;
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 131 to 138, the
devtools middleware is included unconditionally, which increases bundle size and
opens a Redux DevTools connection in production. Modify the code to only apply
the devtools middleware when process.env.NODE_ENV is not 'production', ensuring
it is excluded from production builds to reduce footprint and avoid unnecessary
connections.

Comment on lines 536 to +549
protected fromJSON(data: SignInJSON | SignInJSONSnapshot | null): this {
if (data) {
this.id = data.id;
this.status = data.status;
this.supportedIdentifiers = data.supported_identifiers;
this.identifier = data.identifier;
this.supportedFirstFactors = deepSnakeToCamel(data.supported_first_factors) as SignInFirstFactor[] | null;
this.supportedSecondFactors = deepSnakeToCamel(data.supported_second_factors) as SignInSecondFactor[] | null;
this.firstFactorVerification = new Verification(data.first_factor_verification);
this.secondFactorVerification = new Verification(data.second_factor_verification);
this.createdSessionId = data.created_session_id;
this.userData = new UserData(data.user_data);
}
if (!data) return this;

this.createdSessionId = data.created_session_id;
this.firstFactorVerification = new Verification(data.first_factor_verification);
this.id = data.id;
this.identifier = data.identifier;
this.secondFactorVerification = new Verification(data.second_factor_verification);
this.status = data.status;
this.supportedFirstFactors = deepSnakeToCamel(data.supported_first_factors) as SignInFirstFactor[] | null;
this.supportedIdentifiers = data.supported_identifiers;
this.supportedSecondFactors = deepSnakeToCamel(data.supported_second_factors) as SignInSecondFactor[] | null;
this.userData = new UserData(data.user_data);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

deepSnakeToCamel on potentially null arrays risks a runtime error

data.supported_first_factors and supported_second_factors can be null per the type definition. deepSnakeToCamel(null) will throw because it expects an object/array. Guard first:

-    this.supportedFirstFactors = deepSnakeToCamel(data.supported_first_factors) as SignInFirstFactor[] | null;
+    this.supportedFirstFactors = data.supported_first_factors
+      ? (deepSnakeToCamel(data.supported_first_factors) as SignInFirstFactor[])
+      : null;

Apply the same pattern to supported_second_factors.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
protected fromJSON(data: SignInJSON | SignInJSONSnapshot | null): this {
if (data) {
this.id = data.id;
this.status = data.status;
this.supportedIdentifiers = data.supported_identifiers;
this.identifier = data.identifier;
this.supportedFirstFactors = deepSnakeToCamel(data.supported_first_factors) as SignInFirstFactor[] | null;
this.supportedSecondFactors = deepSnakeToCamel(data.supported_second_factors) as SignInSecondFactor[] | null;
this.firstFactorVerification = new Verification(data.first_factor_verification);
this.secondFactorVerification = new Verification(data.second_factor_verification);
this.createdSessionId = data.created_session_id;
this.userData = new UserData(data.user_data);
}
if (!data) return this;
this.createdSessionId = data.created_session_id;
this.firstFactorVerification = new Verification(data.first_factor_verification);
this.id = data.id;
this.identifier = data.identifier;
this.secondFactorVerification = new Verification(data.second_factor_verification);
this.status = data.status;
this.supportedFirstFactors = deepSnakeToCamel(data.supported_first_factors) as SignInFirstFactor[] | null;
this.supportedIdentifiers = data.supported_identifiers;
this.supportedSecondFactors = deepSnakeToCamel(data.supported_second_factors) as SignInSecondFactor[] | null;
this.userData = new UserData(data.user_data);
protected fromJSON(data: SignInJSON | SignInJSONSnapshot | null): this {
if (!data) return this;
this.createdSessionId = data.created_session_id;
this.firstFactorVerification = new Verification(data.first_factor_verification);
this.id = data.id;
this.identifier = data.identifier;
this.secondFactorVerification = new Verification(data.second_factor_verification);
this.status = data.status;
- this.supportedFirstFactors = deepSnakeToCamel(data.supported_first_factors) as SignInFirstFactor[] | null;
+ this.supportedFirstFactors = data.supported_first_factors
+ ? (deepSnakeToCamel(data.supported_first_factors) as SignInFirstFactor[])
+ : null;
this.supportedIdentifiers = data.supported_identifiers;
this.supportedSecondFactors = deepSnakeToCamel(data.supported_second_factors) as SignInSecondFactor[] | null;
this.userData = new UserData(data.user_data);
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 536 to 549, the
calls to deepSnakeToCamel on data.supported_first_factors and
data.supported_second_factors do not check for null, which can cause runtime
errors. Fix this by adding a null check before calling deepSnakeToCamel, only
transforming if the value is not null; otherwise, assign null directly. Apply
this null guard pattern to both supported_first_factors and
supported_second_factors properties.

Comment on lines +87 to +110
const createSignInSlice = (set: any, _get: any): SignInSliceState => ({
signin: {
status: null,
setStatus: (status: SignInStatus | null) => {
set((state: any) => ({
...state,
signin: {
...state.signin,
status: status,
},
}));
},
error: { global: null, fields: {} },
setError: (error: { global: string | null; fields: Record<string, string> }) => {
set((state: any) => ({
...state,
signin: {
...state.signin,
error: error,
},
}));
},
},
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Strong-type the slice instead of falling back to any

Both set and get can be fully typed via StoreApi<CombinedSignInStore>, giving us compile-time safety and IntelliSense for the callback bodies.

-const createSignInSlice = (set: any, _get: any): SignInSliceState => ({
+import type { StoreApi } from 'zustand/vanilla';
+
+const createSignInSlice = (
+  set: StoreApi<CombinedSignInStore>['setState'],
+  _get: StoreApi<CombinedSignInStore>['getState'],
+): SignInSliceState => ({

This eliminates the need for the repetitive state: any casts further down.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const createSignInSlice = (set: any, _get: any): SignInSliceState => ({
signin: {
status: null,
setStatus: (status: SignInStatus | null) => {
set((state: any) => ({
...state,
signin: {
...state.signin,
status: status,
},
}));
},
error: { global: null, fields: {} },
setError: (error: { global: string | null; fields: Record<string, string> }) => {
set((state: any) => ({
...state,
signin: {
...state.signin,
error: error,
},
}));
},
},
});
import type { StoreApi } from 'zustand/vanilla';
const createSignInSlice = (
set: StoreApi<CombinedSignInStore>['setState'],
_get: StoreApi<CombinedSignInStore>['getState'],
): SignInSliceState => ({
signin: {
status: null,
setStatus: (status: SignInStatus | null) => {
set((state: any) => ({
...state,
signin: {
...state.signin,
status: status,
},
}));
},
error: { global: null, fields: {} },
setError: (error: { global: string | null; fields: Record<string, string> }) => {
set((state: any) => ({
...state,
signin: {
...state.signin,
error: error,
},
}));
},
},
});
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 87 to 110, the
parameters 'set' and '_get' are typed as 'any', which removes type safety and
IntelliSense support. To fix this, replace the 'any' types with the appropriate
typed version using 'StoreApi<CombinedSignInStore>' for both 'set' and '_get'.
This will provide compile-time safety and remove the need for casting 'state' as
'any' inside the callback functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants