Skip to content

poc: observable signin resource #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 40 commits into
base: main
Choose a base branch
from
Open

Conversation

jacekradko
Copy link
Member

@jacekradko jacekradko commented Jun 6, 2025

Description

Demo is here

Observable Store Architecture - Key Benefits

The SignIn Observable Store Demo showcases a new architecture pattern where resources expose a .store property containing a vanilla JS Zustand store. This enables reactive state management across any framework while maintaining backward compatibility.

Framework-Agnostic Reactive Integration

The .store property is a vanilla JS Zustand store that any framework can integrate with for reactive updates:

// React
const status = useStore(signIn.store, (state) => state.resource.status)

// Vue
const status = computed(() => signIn.store.getState().resource.status)

// Svelte
$: status = $signInStore.resource.status

// Angular
signIn.store.subscribe(state => this.status = state.resource.status)

// Vanilla JS
signIn.store.subscribe((state) => { /* handle updates */ })

Benefits:

  • No framework lock-in - works with React, Vue, Svelte, Angular, or vanilla JS
  • Consistent reactive patterns across all frameworks
  • Framework teams can build optimal integrations for their specific needs

Non-Breaking Progressive Enhancement

The new .store property enables gradual adoption without version coupling:

// Framework SDKs can detect and progressively enhance
if (signIn.store) {
  // Use reactive features when available
  return useReactiveSignIn(signIn.store)
} else {
  // Fallback to traditional polling/manual updates
  return useTraditionalSignIn(signIn)
}

Benefits:

  • Zero breaking changes - existing APIs continue to work unchanged
  • Independent release cycles - framework SDKs can ship reactive features without waiting for clerk-js updates
  • Progressive enhancement - better experience when available, graceful degradation when not
  • Version flexibility - framework SDKs work with any clerk-js version (old or new)

Internal Architecture Observability

The observable store powers resource internals regardless of UI usage:

// Internal resource state is always observable
signIn.store.subscribe((state) => {
  console.log('Resource status:', state.resource.status)
  console.log('SignIn flow state:', state.signin.status)
  console.log('Current data:', state.resource.data)
})

Benefits:

  • Debug visibility - inspect resource state changes in real-time
  • Internal consistency - all resource mutations flow through the observable store
  • Development tools - store state can be logged, tracked, and debugged
  • Testing improvements - mock and assert on store state for better test coverage
  • Performance monitoring - track resource lifecycle and identify bottlenecks

Architecture Pattern

BaseResource (Abstract)
├── Provides: .store property (Zustand store)
├── Resource Slice: { status, data, error, dispatch, ... }
└── Generic resource lifecycle management
    ↓ extends
SignInResource (Concrete)
├── Inherits: .store from BaseResource
├── Adds: SignIn Slice: { status, setStatus, ... }
└── Domain-specific SignIn business logic

Impact

This pattern enables:

  • Framework ecosystem growth - any framework can build reactive Clerk integrations
  • Improved developer experience - real-time state inspection and debugging
  • Future-proof architecture - ready for new frameworks and use cases
  • Backward compatibility - no disruption to existing implementations

useSignIn hook without the need for isLoaded

Returning a lazy loaded proxy object from useSignIn until clerk-js is loaded

Screenshot 2025-06-11 at 1 49 23 PM

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.
    • Enhanced sign-in hook and Vue composable to queue method calls until client readiness with deferred proxies, supporting immediate usage without waiting.
  • 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.
    • Simplified sign-in and verification initialization logic and improved async error handling.
    • Adjusted sign-in component to track status directly and improve UI rendering based on sign-in state.
  • Chores

    • Added Zustand as a dependency to both core and React packages.
    • Increased bundle size limits for JavaScript distribution files.

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 12, 2025 2:58pm

Copy link

changeset-bot bot commented Jun 6, 2025

⚠️ No Changeset found

Latest commit: c90c461

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: 20a49e1

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<br>packages/react/package.json                                | Added `[email protected]` as a runtime dependency.                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| packages/clerk-js/sandbox/app.ts<br>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 explicit `isLoaded` flag, implementing queued method calls on proxies for `signIn` and `setActive` until the client context is ready, ensuring async readiness and adding observable state subscription.                                                                                                                                                                                                                                                                       |
| packages/vue/src/composables/useSignIn.ts                                                   | Changed `useSignIn` composable to return proxies that queue method calls when Clerk client is unavailable, removing conditional loading state rendering.                                                                                                                                                                                                                                                                                                                                                         |
| packages/clerk-js/bundlewatch.config.json                                                   | Increased maximum allowed bundle sizes for several `clerk` JS distribution files.                                                                                                                                                                                                                                                                                                                                                                                                                               |

## Suggested reviewers

- tmilewski
✨ 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.

@jacekradko jacekradko marked this pull request as draft June 10, 2025 02:18
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: 1

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

147-152: Missing method-existence guard in processQueue

(targetObj as any)[method] is invoked without checking the member actually exists or is a function. A malformed call string will crash the queue handler.

The previous review suggested adding a validation guard; it’s still absent.


52-70: 🛠️ Refactor suggestion

createStoreObservable is still a hook that hides from eslint-plugin-react-hooks

The returned arrow function calls useStore, but its name doesn’t start with use*.
That keeps the linter blind, and if consumers call signIn.observable() conditionally it will violate the rules of hooks at runtime just as before.

Please rename the returned function to start with use (e.g. useSignInStore) and expose it as a dedicated hook rather than a method to avoid silent misuse.
This was raised in a previous review and remains unresolved.


140-157: ⚠️ Potential issue

callQueue recreated on every render – enqueued promises can be lost

const callQueue: QueuedCall[] = [] lives inside useSignIn, so each re-render allocates a fresh array.
Any proxy created in an earlier render closes over the old array; its queued calls will never be flushed once client becomes available.

Same concern was flagged earlier but the implementation is unchanged.

Recommended fix (sketch):

-  const callQueue: QueuedCall[] = [];
+  const callQueueRef = useRef<QueuedCall[]>([]);
...
-    while (callQueue.length > 0) {
-      const queuedCall = callQueue.shift();
+    while (callQueueRef.current.length > 0) {
+      const queuedCall = callQueueRef.current.shift();

and push to callQueueRef.current.


200-206: ⚠️ Potential issue

Hook invocation inside setTimeout callback still violates React rules

useClientContext() and useIsomorphicClerkContext() are called inside the asynchronous setTimeout handler. Hooks must only be invoked unconditionally at the top level of a component or custom hook – never inside callbacks.

The prior review comment highlighting this breach was not addressed.

Replace those hook calls with the captured client / isomorphicClerk references or trigger a re-render via state instead.


245-249: 🛠️ Refactor suggestion

isLoaded is always true even when the real client is absent

Returning isLoaded: true alongside proxy placeholders is misleading – consumers rely on this flag to know the resource is truly ready.
Earlier feedback pointed this out; behaviour is unchanged.

Either defer isLoaded until client is available or rename the flag (e.g. isDeferred) and update downstream usage.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0d8d45a and c113bd8.

📒 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: Build Packages
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines 168 to 176
const createProxy = <T>(target: 'signIn' | 'setActive'): T => {
const proxyTarget = target === 'signIn' ? { observable: () => ({}) } : {};

return new Proxy(
proxyTarget,
{
get(_, prop) {
// Handle the observable property for the proxy as well
if (prop === 'observable') {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

setActive proxy is not callable – will throw TypeError: xyz is not a function

When client is not ready the fallback proxy for setActive is created over a plain object, yet downstream code expects a function.
Invoking setActive() will therefore explode before the call ever reaches the queue.

Proposed minimal patch:

-    const proxyTarget = target === 'signIn' ? { observable: () => ({}) } : {};
+    const proxyTarget =
+      target === 'signIn'
+        ? { observable: () => ({}) }
+        : (..._args: unknown[]) => undefined; // make proxyTarget callable
...
-        get(_, prop) {
+        apply(_unused, _thisArg, args) {
+          return new Promise((resolve, reject) => {
+            callQueue.push({
+              target,
+              method: 'apply',
+              args,
+              resolve,
+              reject,
+            });
+          });
+        },
+        get(_, prop) {

This turns the proxy into a callable function and routes direct invocations through the queue.

Also applies to: 185-199, 230-240

🤖 Prompt for AI Agents
In packages/react/src/hooks/useSignIn.ts around lines 168 to 176, the proxy
created for 'setActive' is a plain object but downstream code expects it to be
callable as a function, causing a TypeError when invoked. To fix this, change
the proxy target for 'setActive' from an object to a callable function (e.g., an
empty function) so that invoking setActive() does not throw an error and the
call can be routed through the queue. Apply the same fix to similar proxy
creations around lines 185-199 and 230-240.

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 (5)
packages/react/src/hooks/useSignIn.ts (5)

151-170: ⚠️ Potential issue

callQueue recreated on each render → promises silently lost

callQueue is defined inside the hook body, so every re-render allocates a fresh array.
Promises enqueued by proxies created during previous renders will never be flushed, leaving callers hanging.

-  const callQueue: QueuedCall[] = [];
+  // Persist across renders
+  const callQueueRef = useRef<QueuedCall[]>([]);

Subsequently replace all callQueue references with callQueueRef.current.

This issue was raised in an earlier review but is still present.


221-229: ⚠️ Potential issue

React Hook still called inside setTimeout callback

useClientContext() (and useIsomorphicClerkContext()) are invoked inside the setTimeout callback, continuing to violate the Rules-of-Hooks.

Capture the references in the outer scope instead:

-              setTimeout(() => {
-                // Re-check context when the queued call executes
-                const currentClient = useClientContext();
-                const currentIsomorphicClerk = useIsomorphicClerkContext();
+              const currentClient = client;
+              const currentIsomorphicClerk = isomorphicClerk;
+              setTimeout(() => {
                 if (currentClient) {
                   processQueue(
                     currentClient.signIn,
                     currentIsomorphicClerk.setActive,
                   );
                 }
               }, 0);

Unresolved from the previous review.


179-195: ⚠️ Potential issue

setActive proxy is not callable – will throw TypeError

proxyTarget is an object, yet downstream code treats setActive as a function.
Invoking it will explode before the call is queued.

-    const proxyTarget: any = {};
+    const proxyTarget: any =
+      target === 'setActive'
+        ? (..._args: unknown[]) => undefined // make proxy callable
+        : {};

Add an apply trap to route direct calls through the queue if needed.

Previously reported, still outstanding.


158-166: 🛠️ Refactor suggestion

Missing method-existence check in processQueue

Invoking a non-existent method will throw synchronously. Validate before calling:

-        const targetObj = target === 'setActive' ? setActive : signIn;
-        const result = (targetObj as any)[method](...args);
+        const targetObj = target === 'setActive' ? setActive : signIn;
+        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);

Already suggested earlier; still missing.


256-260: ⚠️ Potential issue

isLoaded is always true even when only proxies are returned

Down-stream consumers rely on this flag to know when the real client is ready.
Return false until client is available, or rename the flag to avoid breaking the contract.

Issue persists from previous review.

🧹 Nitpick comments (1)
packages/react/src/hooks/useSignIn.ts (1)

57-85: Remove verbose console.log statements before shipping

The numerous debug logs leak internal state (e.g. store contents) and clutter production output.

Either guard with process.env.NODE_ENV !== 'production' or drop them entirely.

-    // Debug logging
-    console.log('createStoreObservable debug:', { ... });
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8820c2c and e6c2610.

📒 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: Build Packages
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
packages/react/src/hooks/useSignIn.ts (1)

52-55: Function using hooks does not start with use* – ESLint rule-of-hooks blind spot

The anonymous function returned by createStoreObservable calls useStore, but its name doesn’t start with “use”.
ESLint will not validate correct usage, increasing risk of accidental conditional invocation.

Rename the returned function to function useSignInStore() { … } and expose it accordingly.

[ suggest_essential_refactor ]

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: 1

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

173-190: ⚠️ Potential issue

callQueue recreated on every render → queued promises are lost

callQueue is a plain array defined inside the hook body. Each re-render allocates a new array, so any promises stored before the first client hydration are silently dropped.

-  const callQueue: QueuedCall[] = [];
+  // Persist across renders
+  const callQueueRef = useRef<QueuedCall[]>([]);

…and update all reads/writes to callQueueRef.current.

Also move processQueue invocation into a useEffect so it runs whenever client becomes available.


175-186: 🛠️ Refactor suggestion

Validate method before invocation & surface context on error

Invoking (targetObj as any)[method] without existence checks can throw or silently fail on typos. Add a guard:

-        const result = (targetObj as any)[method](...args);
+        const fn = (targetObj as any)[method];
+        if (typeof fn !== 'function') {
+          reject(new Error(`Method ${method} does not exist on ${target}`));
+          continue;
+        }
+        const result = fn(...args);

191-199: ⚠️ Potential issue

isLoaded is true when resources are still proxies → breaks contract

When client is falsy we still return isLoaded: true together with proxy placeholders that may never resolve (e.g. SSR failure).
Either:

  1. expose isLoaded: false until the real client is present, or
  2. rename the flag (e.g. isDeferred) and update docs/consumers.

Failing to adjust will cause subtle runtime errors in downstream code relying on the original semantics.


201-216: ⚠️ Potential issue

setActive proxy is not callable – TypeError: proxy is not a function

createProxy('setActive') returns an object proxy, but callers expect
setActive(...) to be invokable. Wrap a dummy function instead and forward
apply to the queue:

-    const proxyTarget: any = {};
+    // Make the proxy callable
+    const proxyTarget: any =
+      target === 'setActive' ? function noop() {} : {};
...
-        get(_, prop) {
+        // Intercept both property access *and* direct calls
+        apply(_unused, _thisArg, args) {
+          return new Promise((resolve, reject) => {
+            callQueueRef.current.push({
+              target,
+              method: 'apply',
+              args,
+              resolve,
+              reject,
+            });
+          });
+        },
+        get(_, prop) {

Without this fix any early setActive() call explodes before reaching the queue.

Also applies to: 229-241

🧹 Nitpick comments (1)
packages/react/src/hooks/useSignIn.ts (1)

127-142: Hook hiding from ESLint – expose a proper use* API

observable returns a function that contains hooks (useStore) but is invoked
via signIn.observable(). ESLint’s rule-of-hooks cannot detect misuse (e.g.
conditional calls). Consider publishing a real hook instead:

// Outside the resource
export function useSignInStore(signIn: SignInResource) {
  return createStoreObservable(signIn)();
}

This preserves type safety and linter protection.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e6c2610 and 39a7097.

📒 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: Build Packages
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep/ci
  • 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: 0

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

72-85: Un-guarded verbose console.log still present

An earlier review already requested gating or removing these logs to avoid leaking internals and bloating production bundles.
They are still unconditional.

-    console.log('createStoreObservable debug:', {
+    if (process.env.NODE_ENV !== 'production') {
+      /* eslint-disable no-console */
+      console.debug('[Clerk] useSignIn observable:', {
         /* … */
-    });
+      });
+      /* eslint-enable no-console */
+    }

186-204: callQueue recreated on every render → promises are lost

const callQueue: QueuedCall[] = [] is scoped inside the hook body, so each
render allocates a new array. Calls queued by proxies created in previous
renders will never be flushed, leaving callers hanging.

Past feedback already covered this; consider hoisting it into useRef (or a
module-level singleton) and processing it inside a useEffect.


214-244: ⚠️ Potential issue

setActive proxy is not callable – invocations will throw TypeError

proxyTarget is a plain object and no apply trap is defined, so
setActiveProxy() explodes before it even hits the queue.
This was flagged previously and is still unresolved.

-    const proxyTarget: any = {};
+    // Make the proxy target callable so `setActive()` does not crash
+    const proxyTarget: any =
+      target === 'setActive'
+        ? (..._args: unknown[]) => undefined
+        : {};

Also add an apply trap that funnels direct calls through the queue:

+      apply(_unused, _thisArg, args) {
+        return new Promise((resolve, reject) => {
+          callQueue.push({
+            target,
+            method: 'apply',
+            args,
+            resolve,
+            reject,
+          });
+        });
+      },

286-289: ⚠️ Potential issue

isLoaded reports true while only proxies are returned

Down-stream code relies on isLoaded to know when the real signIn /
setActive are usable. Returning true with placeholders breaks that contract
and can surface subtle runtime errors.

This has been highlighted in previous reviews but remains. Either:

  1. Keep isLoaded false until client is ready, or
  2. Rename the flag (e.g. isDeferred) and update consumers.
🧹 Nitpick comments (1)
packages/react/src/hooks/useSignIn.ts (1)

1-1: Style nit: missing whitespace after comma in import list

The first import statement lacks a space after the comma, which is likely to break the repo-wide lint rule (comma-spacing) and create unnecessary noise in CI.

-import { ClerkInstanceContext,useClientContext } from '@clerk/shared/react';
+import { ClerkInstanceContext, useClientContext } from '@clerk/shared/react';
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 39a7097 and abcafc2.

📒 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)

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 (2)
packages/react/src/hooks/useSignIn.ts (2)

159-176: ⚠️ Potential issue

Queued promises are lost on every render – hoist the queue into a ref

callQueue lives in render scope, so any re-render (e.g. client update) swaps it for a new array.
Promises enqueued by proxy calls in earlier renders will never be flushed, causing silent hangs.

Move the queue to useRef and flush it from useEffect.

-  const callQueue: QueuedCall[] = [];
+  const callQueueRef = useRef<QueuedCall[]>([]);
@@
-  const processQueue = (signIn: SignInResource, setActive: SetActive) => {
-    while (callQueue.length > 0) {
-      const queuedCall = callQueue.shift();
+  const processQueue = (signIn: SignInResource, setActive: SetActive) => {
+    while (callQueueRef.current.length > 0) {
+      const queuedCall = callQueueRef.current.shift();
@@
-              callQueue.push({
+              callQueueRef.current.push({
@@
-  if (client) {
-    processQueue(client.signIn, isomorphicClerk.setActive);
+  useEffect(() => {
+    if (client) {
+      processQueue(client.signIn, isomorphicClerk.setActive);
+    }
+  }, [client]);

This guarantees queued calls survive until the real client is ready.


187-260: ⚠️ Potential issue

setActive proxy isn’t callable – runtime will throw TypeError: … is not a function

The proxy’s target is a plain object, so invoking setActive() explodes before the queue handler runs.
Swap to a callable target (empty function) and implement an apply trap.

-    const proxyTarget: any = {};
+    // function target so the proxy itself is callable
+    const proxyTarget: any =
+      target === 'setActive'
+        ? (..._args: unknown[]) => undefined
+        : {};

@@
-        get(_, prop) {
+        apply(_unused, _thisArg, args) {
+          return new Promise((resolve, reject) => {
+            callQueueRef.current.push({
+              target,
+              method: 'apply',
+              args,
+              resolve,
+              reject,
+            });
+          });
+        },
+        get(_, prop) {

This mirrors the Vue implementation and prevents hard crashes.

🧹 Nitpick comments (3)
packages/react/src/hooks/useSignIn.ts (3)

1-1: Add spacing after comma for readability and eslint consistency

import { ClerkInstanceContext,useClientContext } misses a space after the comma – most style-guides (incl. the repo’s eslint config) require Foo, Bar.
Purely cosmetic, but keeps diffs noise-free.

-import { ClerkInstanceContext,useClientContext } from '@clerk/shared/react';
+import { ClerkInstanceContext, useClientContext } from '@clerk/shared/react';

51-56: subscribe signature is incompatible with Zustand – add the listener param

useStore will call store.subscribe(listener, …), but the fallback store’s subscribe accepts zero args.
While JS allows extra args, TypeScript’s structural typing flags this as an error and it breaks intellisense.
Add the param (and ignore it) to remain API-compatible.

-  subscribe: () => () => {}, // Return unsubscribe function
+  subscribe: (_listener: () => void) => () => {}, // noop unsubscribe

85-92: Debug console.log calls leak noise to production bundles

Un-guarded logs inside a hook fire on every render and ship to prod builds, hurting perf and exposing internals.
Wrap them in a dev-only guard or remove entirely.

-      console.log('No real store found, returning empty object');
+      if (process.env.NODE_ENV !== 'production') {
+        /* eslint-disable no-console */
+        console.debug('[Clerk] useSignIn: no store – returning empty object');
+        /* eslint-enable no-console */
+      }
@@
-    console.log('Returning store state:', storeState);
+    if (process.env.NODE_ENV !== 'production') {
+      /* eslint-disable no-console */
+      console.debug('[Clerk] useSignIn store state', storeState);
+      /* eslint-enable no-console */
+    }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between abcafc2 and cd7afb7.

📒 Files selected for processing (1)
  • packages/react/src/hooks/useSignIn.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/react/src/hooks/useSignIn.ts (6)
packages/types/src/signIn.ts (1)
  • SignInResource (76-127)
packages/clerk-js/src/core/resources/Base.ts (1)
  • store (63-65)
packages/vue/src/composables/useSignIn.ts (1)
  • useSignIn (47-128)
packages/shared/src/react/contexts.tsx (3)
  • ClerkInstanceContext (96-96)
  • useClientContext (87-87)
  • useAssertWrappedByClerkProvider (98-98)
packages/react/src/isomorphicClerk.ts (1)
  • client (633-640)
packages/types/src/clerk.ts (1)
  • SetActive (1073-1073)
⏰ 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 (1)
packages/react/src/hooks/useSignIn.ts (1)

262-266: isLoaded: true while returning proxies misleads consumers

isLoaded traditionally signals that real Clerk resources are ready.
Here it is always true, even when signIn/setActive are placeholders that may never resolve (SSR, network error).

Either:

  1. Keep isLoaded = client !== undefined, or
  2. Rename the flag (e.g. isDeferred) and update docs + downstream usage.

Failing to align the semantic contract risks subtle runtime bugs.

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 (2)
packages/react/src/hooks/useSignIn.ts (2)

107-157: ⚠️ Potential issue

Proxy for setActive is not callable & generic constraint is redundant

  1. createProxy builds the fallback for setActive over {}; invoking it (setActive()) will throw TypeError: ... is not a function. Provide a callable target and an apply trap that enqueues direct invocations.

  2. T extends any is the exact lint error reported – the constraint is useless; just use T.

-  const createProxy = useCallback(<T extends any>(target: 'signIn' | 'setActive'): T => {
-    const proxyTarget: any = {};
+  const createProxy = useCallback<<T>(target: 'signIn' | 'setActive'): T => {
+    // make setActive callable, keep signIn an object
+    const proxyTarget: any =
+      target === 'setActive'
+        ? (..._args: unknown[]) => undefined
+        : {};
 ...
-      {
+      {
+        // Allow direct invocation of the proxy (for setActive)
+        apply(_unused, _thisArg, args) {
+          return new Promise((resolve, reject) => {
+            callQueueRef.current.push({
+              target,
+              method: 'apply',
+              args,
+              resolve,
+              reject,
+            });
+          });
+        },
         get(_, prop) {

This prevents runtime crashes and satisfies the static-analysis complaint in one go.

🧰 Tools
🪛 Biome (1.9.4)

[error] 107-107: Constraining a type parameter to any or unknown is useless.

All types are subtypes of any and unknown.
Safe fix: Remove the constraint.

(lint/complexity/noUselessTypeConstraint)

🪛 ESLint

[error] 107-107: Constraining the generic type T to any does nothing and is unnecessary.

(@typescript-eslint/no-unnecessary-type-constraint)


89-106: ⚠️ Potential issue

Queued calls are lost on every re-render – hoist the queue into a stable ref

callQueue is re-created on each hook invocation, so promises enqueued by proxies returned during the first render will never be flushed once client becomes truthy in a later render.
This reproduces the exact bug already pointed out in earlier review comments.

-  const callQueue: QueuedCall[] = [];
+  // Persist across renders
+  const callQueueRef = useRef<QueuedCall[]>([]);

and everywhere else:

-      const queuedCall = callQueue.shift();
+      const queuedCall = callQueueRef.current.shift();
...
-              callQueue.push({
+              callQueueRef.current.push({

Finally, trigger processQueue from a useEffect to guarantee it runs when client appears:

-    if (client) {
-      processQueue(client.signIn, isomorphicClerk.setActive);
-    }
+    useEffect(() => {
+      if (client) {
+        processQueue(client.signIn, isomorphicClerk.setActive);
+      }
+    }, [client, processQueue]);

Without this, any deferred calls made before Clerk hydrates will hang forever.

🧹 Nitpick comments (1)
packages/react/src/hooks/useSignIn.ts (1)

4-4: Minor nit: missing space in imports

-import { useCallback,useContext, useMemo } from 'react';
+import { useCallback, useContext, useMemo } from 'react';

Readability only; no functional impact.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between cd7afb7 and fd74064.

📒 Files selected for processing (1)
  • packages/react/src/hooks/useSignIn.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/react/src/hooks/useSignIn.ts

[error] 107-107: Constraining a type parameter to any or unknown is useless.

All types are subtypes of any and unknown.
Safe fix: Remove the constraint.

(lint/complexity/noUselessTypeConstraint)

🪛 ESLint
packages/react/src/hooks/useSignIn.ts

[error] 107-107: Constraining the generic type T to any does nothing and is unnecessary.

(@typescript-eslint/no-unnecessary-type-constraint)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
packages/react/src/hooks/useSignIn.ts (2)

181-186: isLoaded: true even when only proxies are returned – breaks contract

Down-stream code historically relied on isLoaded before calling signIn/setActive. Returning true while handing out dummy proxies is misleading and breaks semantic guarantees.

Please either:

  1. Keep isLoaded === false until a real client exists, or
  2. Rename the flag (e.g. isDeferred) and update all consumer types/docs accordingly.

Failing to do so risks subtle production errors when Clerk never hydrates (SSR, network fault, etc.).


60-63: Skipping the provider assertion hides configuration errors

useAssertWrappedByClerkProvider is only invoked when clerkInstanceContext is truthy, effectively disabling the runtime guard when the provider is missing (the very case it should detect).
Was this relaxation intentional? If not, revert to unconditional assertion or add an explicit comment explaining the new expectation.

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: 1

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

175-185: isLoaded is always true even when real client is absent

The hook advertises readiness while still returning proxies that may never
resolve (SSR, network error, etc.), breaking the historical contract that
isLoaded guarantees a usable resource.

Either:

  1. Keep isLoaded = false until client is truthy, or
  2. Rename the flag (isDeferred?) and update all consumers/docs.

This concern has been raised in previous rounds but remains unaddressed.


90-107: 🛠️ Refactor suggestion

Queue is re-created per render – risk of losing calls

callQueue is declared inside the hook body.
A re-renderallocates a fresh array, but createProxy & processQueue
instances from the first render keep a reference to the old queue; any
promises enqueued by proxies created after the first render are pushed onto
a queue that processQueue will never drain.

Stabilise the queue with useRef (or module scope):

-  const callQueue: QueuedCall[] = [];
+  const callQueueRef = useRef<QueuedCall[]>([]);
...
-    while (callQueue.length > 0) {
-      const queuedCall = callQueue.shift();
+    while (callQueueRef.current.length > 0) {
+      const queuedCall = callQueueRef.current.shift();
...
-              callQueue.push({
+              callQueueRef.current.push({

And run processQueue from a useEffect when client becomes available so the
queue is always flushed.

This was flagged in earlier reviews but is still unresolved.

🧹 Nitpick comments (1)
packages/react/src/hooks/useSignIn.ts (1)

4-4: Minor formatting nit – missing space after comma

import { useCallback,useContext, useMemo } from 'react';

Add a space after useCallback for consistency with the rest of the imports.

-import { useCallback,useContext, useMemo } from 'react';
+import { useCallback, useContext, useMemo } from 'react';
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between fd74064 and c211b68.

📒 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: Build Packages
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: semgrep/ci
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines +108 to +158
const createProxy = useCallback(<T>(target: 'signIn' | 'setActive'): T => {
const proxyTarget: any = {};

return new Proxy(
proxyTarget,
{
get(_, prop) {
// Prevent React from treating this proxy as a Promise by returning undefined for 'then'
if (prop === 'then') {
return undefined;
}

// Handle Symbol properties and other non-method properties
if (typeof prop === 'symbol' || typeof prop !== 'string') {
return undefined;
}

// For observableState property, return undefined in proxy mode
if (prop === 'observableState' && target === 'signIn') {
return undefined;
}

return (...args: any[]) => {
return new Promise((resolve, reject) => {
callQueue.push({
target,
method: String(prop),
args,
resolve,
reject,
});
});
};
},
has(_, prop) {
// Return false for 'then' to prevent Promise-like behavior
if (prop === 'then') {
return false;
}
// Return true for all other properties to indicate they exist on the proxy
return true;
},
ownKeys(_) {
return Object.getOwnPropertyNames(proxyTarget);
},
getOwnPropertyDescriptor(_, prop) {
return Object.getOwnPropertyDescriptor(proxyTarget, prop);
}
},
) as T;
}, []);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

setActive proxy is not callable – will throw at runtime

createProxy always uses a plain object ({}) as proxyTarget.
When the target is 'setActive', downstream code expects a function and will do:

const { setActive } = useSignIn();
await setActive('someSessionId');

A proxy over a non-callable object cannot be invoked and will raise
TypeError: setActive is not a function.

Fix: make the proxy target callable and implement an apply trap so direct
invocations are routed through the queue, in line with how property access is
handled for signIn methods.

   const createProxy = useCallback(<T>(target: 'signIn' | 'setActive'): T => {
-    const proxyTarget: any = {};
+    // Function targets stay callable; object targets keep method semantics
+    const proxyTarget: any =
+      target === 'setActive' ? (..._args: unknown[]) => undefined : {};

     return new Proxy(
       proxyTarget,
       {
+        /* Proxy invoked like a function → enqueue */
+        apply(_unused, _thisArg, args) {
+          return new Promise((resolve, reject) => {
+            callQueue.push({
+              target,
+              // Special marker for direct invocation
+              method: 'apply',
+              args,
+              resolve,
+              reject,
+            });
+          });
+        },

This keeps setActive usable before Clerk is loaded and prevents hard crashes.

🤖 Prompt for AI Agents
In packages/react/src/hooks/useSignIn.ts between lines 108 and 158, the
createProxy function uses a plain object as the proxy target for both 'signIn'
and 'setActive', but 'setActive' is expected to be callable. To fix this, change
the proxy target to a callable function when target is 'setActive' and add an
apply trap to the proxy handler that routes direct function calls through the
callQueue, similar to how property accesses are handled. This will prevent
runtime TypeErrors when invoking setActive before Clerk is loaded.

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: 1

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

175-185: isLoaded: true while resources are still proxies misleads consumers
(Re-raising previous feedback)

The hook returns isLoaded: true even when client is absent and both signIn & setActive are dummy proxies that might never resolve (e.g. SSR, network error).
Down-stream code that correctly waits for isLoaded will falsely assume the resource is ready.

Either:

  1. Keep isLoaded = false until client is truthy, or
  2. Rename the flag (e.g. isDeferred) and update docs/tests.

Failing to fix will cause subtle runtime errors in existing apps.


91-108: ⚠️ Potential issue

callQueue recreated on every render → lost promises
(Re-raising previous feedback)

const callQueue: QueuedCall[] = [] lives inside the hook’s render scope, so each re-render allocates a fresh array.
Proxies created during earlier renders push into an obsolete queue that is never flushed, leaving callers hanging indefinitely.

See prior comment “Queue is recreated on every render → pending calls may be lost”.
Please hoist the queue into a useRef and flush it in an effect:

-import { useCallback, useContext, useMemo } from 'react';
+import { useCallback, useContext, useMemo, useRef, useEffect } from 'react';

-  const callQueue: QueuedCall[] = [];
+  const callQueueRef = useRef<QueuedCall[]>([]);

   const processQueue = useCallback((signIn: SignInResource, setActive: SetActive) => {
-    while (callQueue.length > 0) {
-      const queuedCall = callQueue.shift();
+    while (callQueueRef.current.length > 0) {
+      const queuedCall = callQueueRef.current.shift();
       ...
   }, []);

   // flush once the client is available
-  if (client) {
-    processQueue(client.signIn, isomorphicClerk.setActive);
-  }
+  useEffect(() => {
+    if (client) {
+      processQueue(client.signIn, isomorphicClerk.setActive);
+    }
+  }, [client, processQueue, isomorphicClerk?.setActive]);

109-159: ⚠️ Potential issue

setActive proxy is not callable – will throw TypeError: xxx is not a function
(Re-raising previous feedback)

When client is undefined the hook returns a proxy for setActive, but the proxy target is a plain object, not a function.
Any consumer invoking setActive() before hydration will crash.

Patch:

-    const proxyTarget: any = {};
+    // keep function semantics for 'setActive', object semantics for 'signIn'
+    const proxyTarget: any = target === 'setActive' ? () => undefined : {};

+        // Allow direct invocation of the proxy (apply trap)
+        apply(_unused, _thisArg, args) {
+          return new Promise((resolve, reject) => {
+            callQueueRef.current.push({
+              target,
+              method: 'apply',
+              args,
+              resolve,
+              reject,
+            });
+          });
+        },

This keeps the proxy callable and routes the call through the queue.

🧹 Nitpick comments (1)
packages/react/src/hooks/useSignIn.ts (1)

4-4: Missing space in import list – triggers eslint/prettier noise

useCallback,useContext lacks a space after the comma.
While harmless at runtime, it breaks the project’s formatting conventions and will be auto-fixed by any formatter run.

-import { useCallback,useContext, useMemo } from 'react';
+import { useCallback, useContext, useMemo } from 'react';
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between c211b68 and 5768162.

📒 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 +48 to +51
getState: () => FALLBACK_STATE,
getInitialState: () => FALLBACK_STATE,
subscribe: () => () => {}, // Return unsubscribe function
setState: () => {},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

subscribe signature incompatible with Zustand → runtime/type errors

useStore expects the store’s subscribe method to accept a listener parameter ((listener, selector?, equalityFn?)), but the fallback stub is declared with no parameters.
Type-checking will fail and, at runtime, Zustand will still call the function with arguments that will be silently ignored, making the fallback store impossible to observe.

-  subscribe: () => () => {}, // Return unsubscribe function
+  subscribe: (_listener: unknown) => () => {}, // accept listener to satisfy Zustand
📝 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
getState: () => FALLBACK_STATE,
getInitialState: () => FALLBACK_STATE,
subscribe: () => () => {}, // Return unsubscribe function
setState: () => {},
getState: () => FALLBACK_STATE,
getInitialState: () => FALLBACK_STATE,
subscribe: (_listener: unknown) => () => {}, // accept listener to satisfy Zustand
setState: () => {},
🤖 Prompt for AI Agents
In packages/react/src/hooks/useSignIn.ts around lines 48 to 51, the fallback
store's subscribe method is declared without parameters, which is incompatible
with Zustand's expected signature that requires a listener and optional selector
and equality function parameters. Update the subscribe method to accept these
parameters and return an unsubscribe function, ensuring it matches Zustand's
signature to avoid type errors and enable proper observation of the fallback
store.

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: 1

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

268-279: Repeated override of console.error – keep previous feedback
The global hijack of console.error is still present. This has already been flagged as risky because it pollutes global state and can break other logging / error-reporting logic. Please replace with a scoped handler (e.g. a local wrapper or try/catch) as suggested earlier.

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

868-896: Busy-wait polling for Clerk readiness – switch to event/promise API
The waitForClerk helper recursively setTimeouts every 100 ms. This is CPU-friendly but still unnecessary; Clerk exposes Clerk.addListener('loaded', …) and returns a promise from Clerk.load() you can await.

Refactor suggestion:

- await waitForClerk();
+ await Clerk.load();      // resolves when both library and client are ready

This removes ~30 LOC, avoids custom polling, and makes failure-handling explicit.


1058-1066: cleanup never invoked → potential subscription leak
You create (element as any)._cleanup = cleanup, but no code calls it on route change or page unload. Navigating between routes repeatedly mounts new subscriptions and leaked timers.

Consider:

window.addEventListener('beforeunload', cleanup);
document.addEventListener('clerk:sandbox:route-change', cleanup); // custom event you dispatch before mounting new route

or store the unsubscribe in a higher-level router so each mount’s teardown is guaranteed.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 20a49e1 and c90c461.

📒 Files selected for processing (1)
  • packages/clerk-js/sandbox/app.ts (6 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines +941 to +946
// Update status to show we're creating SignIn
statusContainer.innerHTML = `
<div class="text-blue-500">
<strong>Status:</strong> Creating SignIn with identifier: ${emailInput.value}
</div>
`;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unsanitised interpolation → XSS vector
emailInput.value and error.message are injected directly into innerHTML. An attacker (or an accidentally-malformed email) could smuggle HTML/JS and execute arbitrary code.

- 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}`;

Likewise for the error block:

- <div class="text-sm">${errorMessage}</div>
+ <div class="text-sm" id="errorText"></div>

document.getElementById('errorText')!.textContent = errorMessage;

If preserving rich markup is required, run the value through a trusted sanitiser (e.g. DOMPurify) before assignment.

Also applies to: 1038-1047

🤖 Prompt for AI Agents
In packages/clerk-js/sandbox/app.ts around lines 941 to 946, the code directly
injects user input (emailInput.value) into innerHTML, creating an XSS
vulnerability. To fix this, sanitize the emailInput.value using a trusted
sanitizer like DOMPurify before assigning it to innerHTML. Apply the same
sanitization approach to any other user inputs or error messages inserted as
HTML, including the block at lines 1038-1047.

@jacekradko jacekradko changed the title poc: observable signin poc: observable signin resource Jun 12, 2025
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