diff --git a/.changeset/cuddly-shrimps-refuse.md b/.changeset/cuddly-shrimps-refuse.md new file mode 100644 index 00000000000..8b706ab25f5 --- /dev/null +++ b/.changeset/cuddly-shrimps-refuse.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': major +--- + +Update engines config to require node@20 or higher diff --git a/.changeset/cyan-dancers-chew.md b/.changeset/cyan-dancers-chew.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/cyan-dancers-chew.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/deprecate-clerk-types.md b/.changeset/deprecate-clerk-types.md new file mode 100644 index 00000000000..7900eeacd81 --- /dev/null +++ b/.changeset/deprecate-clerk-types.md @@ -0,0 +1,42 @@ +--- +'@clerk/shared': minor +'@clerk/astro': patch +'@clerk/backend': patch +'@clerk/chrome-extension': patch +'@clerk/clerk-js': patch +'@clerk/themes': patch +'@clerk/vue': patch +--- + +Deprecate `@clerk/types` in favor of `@clerk/shared/types` + +The `@clerk/types` package is now deprecated. All type definitions have been consolidated and moved to `@clerk/shared/types` to improve consistency across the Clerk ecosystem. + +**Backward Compatibility:** + +The `@clerk/types` package will remain available and will continue to re-export all types from `@clerk/shared/types` to ensure backward compatibility. Existing applications will continue to work without any immediate breaking changes. However, we strongly recommend migrating to `@clerk/shared/types` as new type definitions and updates will only be added to `@clerk/shared/types` starting with the next major release. + +**Migration Steps:** + +Please update your imports from `@clerk/types` to `@clerk/shared/types`: + +```typescript +// Before +import type { ClerkResource, UserResource } from '@clerk/types'; + +// After +import type { ClerkResource, UserResource } from '@clerk/shared/types'; +``` + +**What Changed:** + +All type definitions including: +- Resource types (User, Organization, Session, etc.) +- API response types +- Configuration types +- Authentication types +- Error types +- And all other shared types + +Have been moved from `packages/types/src` to `packages/shared/src/types` and are now exported via `@clerk/shared/types`. + diff --git a/.changeset/eight-groups-poke.md b/.changeset/eight-groups-poke.md new file mode 100644 index 00000000000..cd15f927f9b --- /dev/null +++ b/.changeset/eight-groups-poke.md @@ -0,0 +1,12 @@ +--- +'@clerk/upgrade': minor +--- + +Add support for the latest versions of the following packages: +- `@clerk/react` (replacement for `@clerk/clerk-react`) +- `@clerk/expo` (replacement for `@clerk/clerk-expo`) +- `@clerk/nextjs` +- `@clerk/react-router` +- `@clerk/tanstack-start-react` + +During the upgrade, imports of the `useSignIn()` and `useSignUp()` hooks will be updated to import from the `/legacy` subpath. diff --git a/.changeset/fine-symbols-occur.md b/.changeset/fine-symbols-occur.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/fine-symbols-occur.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/fix-ui-cicd.md b/.changeset/fix-ui-cicd.md new file mode 100644 index 00000000000..4bfec3e10d8 --- /dev/null +++ b/.changeset/fix-ui-cicd.md @@ -0,0 +1,6 @@ +--- +"@clerk/ui": patch +--- + +Fix UI package serving in CI/CD integration tests + diff --git a/.changeset/fruity-apes-deny.md b/.changeset/fruity-apes-deny.md new file mode 100644 index 00000000000..3f4e1aaf0a6 --- /dev/null +++ b/.changeset/fruity-apes-deny.md @@ -0,0 +1,39 @@ +--- +'@clerk/clerk-js': major +'@clerk/shared': major +'@clerk/react': major +--- + +Updated returned values of `Clerk.checkout()` and `useCheckout`. + +### Vanilla JS +```ts +// Before +const { getState, subscribe, confirm, start, clear, finalize } = Clerk.checkout({ planId: "xxx", planPeriod: "annual" }) +getState().isStarting +getState().isConfirming +getState().error +getState().checkout +getState().fetchStatus +getState().status + +// After +const { checkout, errors, fetchStatus } = Clerk.checkout({ planId: "xxx", planPeriod: "annual" }) +checkout.plan // null or defined based on `checkout.status` +checkout.status +checkout.start +checkout.confirm +``` + +### React +```ts +// Before +const { id, plan, status, start, confirm, paymentSource } = useCheckout({ planId: "xxx", planPeriod: "annual" }) + +// After +const { checkout, errors, fetchStatus } = usecCheckout({ planId: "xxx", planPeriod: "annual" }) +checkout.plan // null or defined based on `checkout.status` +checkout.status +checkout.start +checkout.confirm +``` \ No newline at end of file diff --git a/.changeset/fuzzy-chefs-stand.md b/.changeset/fuzzy-chefs-stand.md new file mode 100644 index 00000000000..875ce1de5a5 --- /dev/null +++ b/.changeset/fuzzy-chefs-stand.md @@ -0,0 +1,7 @@ +--- +'@clerk/nextjs': major +'@clerk/shared': major +'@clerk/react': major +--- + +Updating minimum version of Node to v20.9.0 diff --git a/.changeset/happy-apes-care.md b/.changeset/happy-apes-care.md new file mode 100644 index 00000000000..05764126cb3 --- /dev/null +++ b/.changeset/happy-apes-care.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': major +'@clerk/shared': major +--- + +Remove deprecated `saml` property from `UserSettings` in favor of `enterpriseSSO` diff --git a/.changeset/happy-breads-begin.md b/.changeset/happy-breads-begin.md new file mode 100644 index 00000000000..622cff39209 --- /dev/null +++ b/.changeset/happy-breads-begin.md @@ -0,0 +1,8 @@ +--- +'@clerk/clerk-js': major +'@clerk/backend': major +'@clerk/shared': major +'@clerk/ui': major +--- + +Remove deprecated `samlAccount` in favor of `enterpriseAccount` diff --git a/.changeset/hungry-beers-slide.md b/.changeset/hungry-beers-slide.md new file mode 100644 index 00000000000..1a965dad658 --- /dev/null +++ b/.changeset/hungry-beers-slide.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': major +--- + +Hide "Create organization" action when user reaches organization membership limit diff --git a/.changeset/mean-owls-brake.md b/.changeset/mean-owls-brake.md new file mode 100644 index 00000000000..3c4fc1b3986 --- /dev/null +++ b/.changeset/mean-owls-brake.md @@ -0,0 +1,18 @@ +--- +"@clerk/astro": major +"@clerk/chrome-extension": major +"@clerk/clerk-js": major +"@clerk/dev-cli": major +"@clerk/expo": major +"@clerk/express": major +"@clerk/localizations": major +"@clerk/nuxt": major +"@clerk/tanstack-react-start": major +"@clerk/testing": major +"@clerk/themes": major +"@clerk/upgrade": major +"@clerk/vue": major +--- + +Require Node.js 20.9.0 in all packages + diff --git a/.changeset/ninety-days-dream.md b/.changeset/ninety-days-dream.md new file mode 100644 index 00000000000..f2ae53ff06f --- /dev/null +++ b/.changeset/ninety-days-dream.md @@ -0,0 +1,8 @@ +--- +'@clerk/shared': major +'@clerk/ui': major +--- + +Remove deprecated `hideSlug` in favor of `organizationSettings.slug.disabled` setting + +Slugs can now be enabled directly from the Organization Settings page in the Clerk Dashboard diff --git a/.changeset/orange-hotels-join.md b/.changeset/orange-hotels-join.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/orange-hotels-join.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/remove-deprecated-props.md b/.changeset/remove-deprecated-props.md new file mode 100644 index 00000000000..913464ff3cc --- /dev/null +++ b/.changeset/remove-deprecated-props.md @@ -0,0 +1,11 @@ +--- +"@clerk/nextjs": major +"@clerk/react": major +"@clerk/clerk-js": major +"@clerk/shared": major +"@clerk/ui": major +"@clerk/react-router": major +"@clerk/tanstack-react-start": minor +--- + +Remove all previously deprecated UI props across the Next.js, React and clerk-js SDKs. The legacy `afterSign(In|Up)Url`/`redirectUrl` props, `UserButton` sign-out overrides, organization `hideSlug` flags, `OrganizationSwitcher`'s `afterSwitchOrganizationUrl`, `Client.activeSessions`, `setActive({ beforeEmit })`, and the `ClerkMiddlewareAuthObject` type alias are no longer exported. Components now rely solely on the new redirect options and server-side configuration. diff --git a/.changeset/salty-maps-fry.md b/.changeset/salty-maps-fry.md new file mode 100644 index 00000000000..17fcbef3683 --- /dev/null +++ b/.changeset/salty-maps-fry.md @@ -0,0 +1,7 @@ +--- +'@clerk/clerk-js': major +'@clerk/shared': major +'@clerk/ui': major +--- + +Remove deprecated `saml` strategy in favor of `enterprise_sso` diff --git a/.changeset/set-minimum-expo-53.md b/.changeset/set-minimum-expo-53.md new file mode 100644 index 00000000000..61ab909ddca --- /dev/null +++ b/.changeset/set-minimum-expo-53.md @@ -0,0 +1,37 @@ +--- +'@clerk/expo': major +'@clerk/expo-passkeys': major +'@clerk/shared': major +'@clerk/react': major +'@clerk/localizations': major +--- + +Drop support for Expo 50, 51 and 52. This release includes two breaking changes: + +## 1. Updated Expo peer dependency requirements + +**@clerk/clerk-expo** +- **Added** new peer dependency: `expo: >=53 <55` + - The core `expo` package is now explicitly required as a peer dependency + - This ensures compatibility with the Expo SDK version range that supports the features used by Clerk + +**@clerk/expo-passkeys** +- **Updated** peer dependency: `expo: >=53 <55` (previously `>=50 <55`) + - Minimum Expo version increased from 50 to 53 + - This aligns with the main `@clerk/clerk-expo` package requirements + +## 2. Removed legacy subpath exports + +The following packages have removed their legacy subpath export mappings: +- `@clerk/clerk-expo` +- `@clerk/shared` +- `@clerk/clerk-react` +- `@clerk/localizations` + +**What changed:** +Previously, these packages used a workaround to support subpath imports (e.g., `@clerk/shared/react`, `@clerk/clerk-expo/web`). These legacy exports have been removed in favor of modern package.json `exports` field configuration. + +All public APIs remain available through the main package entry points. + + + diff --git a/.changeset/shaky-books-occur.md b/.changeset/shaky-books-occur.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/shaky-books-occur.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/slimy-sheep-kick.md b/.changeset/slimy-sheep-kick.md new file mode 100644 index 00000000000..48f651eef5a --- /dev/null +++ b/.changeset/slimy-sheep-kick.md @@ -0,0 +1,15 @@ +--- +"@clerk/nuxt": major +--- + +Removed deprecated `getAuth()` helper. Use `event.context.auth()` in your server routes instead. + +```ts +export default defineEventHandler((event) => { + const { userId } = event.context.auth() + + return { + userId, + } +}) +``` diff --git a/.changeset/strict-hornets-kneel.md b/.changeset/strict-hornets-kneel.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/strict-hornets-kneel.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/strong-bars-learn.md b/.changeset/strong-bars-learn.md new file mode 100644 index 00000000000..1f6d62ffcc3 --- /dev/null +++ b/.changeset/strong-bars-learn.md @@ -0,0 +1,9 @@ +--- +'@clerk/chrome-extension': patch +'@clerk/expo': patch +'@clerk/nextjs': patch +'@clerk/react-router': patch +'@clerk/tanstack-react-start': patch +--- + +Use new `@clerk/react` package. diff --git a/.changeset/tame-suits-try.md b/.changeset/tame-suits-try.md new file mode 100644 index 00000000000..4f8e78ae499 --- /dev/null +++ b/.changeset/tame-suits-try.md @@ -0,0 +1,5 @@ +--- +'@clerk/react': major +--- + +Change package name to `@clerk/react`. diff --git a/.changeset/ten-wolves-attack.md b/.changeset/ten-wolves-attack.md new file mode 100644 index 00000000000..fe551c47575 --- /dev/null +++ b/.changeset/ten-wolves-attack.md @@ -0,0 +1,5 @@ +--- +'@clerk/nextjs': major +--- + +Drop support for `next@13` and `next@14` since they have reached [EOL](https://nextjs.org/support-policy#unsupported-versions). Now `>= next@15.2.3` is required. diff --git a/.changeset/thirty-cherries-pull.md b/.changeset/thirty-cherries-pull.md new file mode 100644 index 00000000000..bb6317e0c6f --- /dev/null +++ b/.changeset/thirty-cherries-pull.md @@ -0,0 +1,12 @@ +--- +"@clerk/nuxt": major +--- + +Routing strategy for the ff. components now default to `path`: + +- `` +- `` +- `` +- `` +- `` +- `` diff --git a/.changeset/tricky-humans-stand.md b/.changeset/tricky-humans-stand.md new file mode 100644 index 00000000000..b91ce44192e --- /dev/null +++ b/.changeset/tricky-humans-stand.md @@ -0,0 +1,5 @@ +--- +'@clerk/expo': major +--- + +Rename package to `@clerk/expo`. diff --git a/.changeset/twenty-rockets-stop.md b/.changeset/twenty-rockets-stop.md new file mode 100644 index 00000000000..59744b67074 --- /dev/null +++ b/.changeset/twenty-rockets-stop.md @@ -0,0 +1,5 @@ +--- +'@clerk/shared': major +--- + +Removing deprecated top-level exports from @clerk/shared diff --git a/.changeset/wild-bees-explode.md b/.changeset/wild-bees-explode.md new file mode 100644 index 00000000000..151b584c52b --- /dev/null +++ b/.changeset/wild-bees-explode.md @@ -0,0 +1,5 @@ +--- +"@clerk/vue": minor +--- + +Introduced internal composable for handling routing configuration for UI components diff --git a/.cursor/rules/monorepo.mdc b/.cursor/rules/monorepo.mdc index e7b45bc0abd..05c2748443e 100644 --- a/.cursor/rules/monorepo.mdc +++ b/.cursor/rules/monorepo.mdc @@ -22,7 +22,7 @@ Core Package Categories - **Backend**: `@clerk/backend` - Server-side utilities and JWT verification - **Shared Utilities**: `@clerk/shared`, `@clerk/types` - Common utilities and TypeScript types - **Developer Tools**: `@clerk/testing`, `@clerk/dev-cli`, `@clerk/upgrade` -- **UI Libraries**: `@clerk/elements` - Unstyled UI primitives, `@clerk/themes` - Pre-built themes +- **UI Libraries**: `@clerk/themes` - Pre-built themes - **Specialized**: `@clerk/agent-toolkit` - AI agent integration tools Directory Structure @@ -46,11 +46,10 @@ Development Workflow Framework-Specific Packages - `@clerk/nextjs` - Next.js App Router and Pages Router support -- `@clerk/clerk-react` - React hooks and components +- `@clerk/react` - React hooks and components - `@clerk/vue` - Vue.js composables and components - `@clerk/astro` - Astro integration with SSR support - `@clerk/nuxt` - Nuxt.js module -- `@clerk/remix` - Remix loader and action utilities - `@clerk/express` - Express.js middleware - `@clerk/fastify` - Fastify plugin - `@clerk/expo` - React Native/Expo SDK diff --git a/.github/labeler.yml b/.github/labeler.yml index c84d8ae5e4d..635dad7011f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -12,9 +12,6 @@ chrome-extension: clerk-js: - packages/clerk-js/** -elements: - - packages/elements/** - expo: - packages/expo/** @@ -39,9 +36,6 @@ react: react-router: - packages/react-router/** -remix: - - packages/remix/** - tanstack: - packages/tanstack-react-start/** @@ -51,9 +45,6 @@ testing: themes: - packages/themes/** -types: - - packages/types/** - vue: - packages/vue/** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 608ed268986..d65f4832ed1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -161,14 +161,15 @@ jobs: turbo-token: ${{ secrets.TURBO_TOKEN }} - name: Check size using bundlewatch - run: pnpm turbo bundlewatch $TURBO_ARGS + continue-on-error: true env: BUNDLEWATCH_GITHUB_TOKEN: ${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }} - CI_REPO_OWNER: ${{ vars.REPO_OWNER }} - CI_REPO_NAME: ${{ vars.REPO_NAME }} - CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} CI_BRANCH: ${{ github.ref }} CI_BRANCH_BASE: refs/heads/main + CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} + CI_REPO_NAME: ${{ vars.REPO_NAME }} + CI_REPO_OWNER: ${{ vars.REPO_OWNER }} + run: pnpm turbo bundlewatch $TURBO_ARGS - name: Lint packages using publint run: pnpm turbo lint:publint $TURBO_ARGS @@ -191,7 +192,7 @@ jobs: unit-tests: needs: [check-permissions, build-packages] if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} - name: Unit Tests (${{ matrix.node-version }}, ${{ matrix.filter-label }}${{ matrix.clerk-use-rq == 'true' && ', RQ' || '' }}) + name: Unit Tests (${{ matrix.filter-label }}${{ matrix.clerk-use-rq == 'true' && ', RQ' || '' }}) permissions: contents: read actions: write # needed for actions/upload-artifact @@ -228,7 +229,7 @@ jobs: id: config uses: ./.github/actions/init-blacksmith with: - # Ensures that all builds are cached appropriately with a consistent run name `Unit Tests (18)`. + # Ensures that all builds are cached appropriately with a consistent run name `Unit Tests (20)`. node-version: ${{ matrix.node-version }} turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} turbo-summarize: ${{ env.TURBO_SUMMARIZE }} @@ -264,7 +265,7 @@ jobs: run: | # Only run Typedoc tests for one matrix version and main test run if [ "${{ matrix.node-version }}" == "22" ] && [ "${{ matrix.test-filter }}" = "**" ]; then - pnpm test:typedoc + pnpm turbo run //#test:typedoc fi env: NODE_VERSION: ${{ matrix.node-version }} @@ -294,58 +295,54 @@ jobs: strategy: fail-fast: false matrix: - test-name: [ - 'generic', - 'express', - 'ap-flows', - 'elements', - 'localhost', - 'sessions', - 'sessions:staging', - 'handshake', - 'handshake:staging', - 'astro', - 'expo-web', - 'tanstack-react-start', - 'vue', - 'nuxt', - 'react-router', - 'custom', + test-name: + [ + "generic", + "express", + "ap-flows", + "localhost", + "sessions", + "sessions:staging", + "handshake", + "handshake:staging", + "astro", + "tanstack-react-start", + "vue", + "nuxt", + "react-router", + "custom", ] test-project: ["chrome"] include: - - test-name: 'billing' - test-project: 'chrome' - clerk-use-rq: 'false' - - test-name: 'billing' - test-project: 'chrome' - clerk-use-rq: 'true' - - test-name: 'machine' - test-project: 'chrome' - clerk-use-rq: 'false' - - test-name: 'machine' - test-project: 'chrome' - clerk-use-rq: 'true' - - test-name: 'nextjs' - test-project: 'chrome' - next-version: '14' - - test-name: 'nextjs' - test-project: 'chrome' - next-version: '15' - clerk-use-rq: 'false' - - test-name: 'nextjs' - test-project: 'chrome' - next-version: '15' - clerk-use-rq: 'true' - - test-name: 'nextjs' - test-project: 'chrome' - next-version: '16' - - test-name: 'quickstart' - test-project: 'chrome' - next-version: '15' - - test-name: 'quickstart' - test-project: 'chrome' - next-version: '16' + - test-name: "billing" + test-project: "chrome" + clerk-use-rq: "false" + - test-name: "billing" + test-project: "chrome" + clerk-use-rq: "true" + - test-name: "machine" + test-project: "chrome" + clerk-use-rq: "false" + - test-name: "machine" + test-project: "chrome" + clerk-use-rq: "true" + - test-name: "nextjs" + test-project: "chrome" + next-version: "15" + clerk-use-rq: "false" + - test-name: "nextjs" + test-project: "chrome" + next-version: "15" + clerk-use-rq: "true" + - test-name: "nextjs" + test-project: "chrome" + next-version: "16" + - test-name: "quickstart" + test-project: "chrome" + next-version: "15" + - test-name: "quickstart" + test-project: "chrome" + next-version: "16" steps: - name: Checkout Repo @@ -376,7 +373,9 @@ jobs: id: task-status env: E2E_APP_CLERK_JS_DIR: ${{runner.temp}} - E2E_CLERK_VERSION: "latest" + E2E_APP_CLERK_UI_DIR: ${{runner.temp}} + E2E_CLERK_JS_VERSION: "latest" + E2E_CLERK_UI_VERSION: "latest" E2E_NEXTJS_VERSION: ${{ matrix.next-version }} E2E_PROJECT: ${{ matrix.test-project }} INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }} @@ -411,7 +410,7 @@ jobs: uses: ./.github/actions/verdaccio with: publish-cmd: | - if [ "$(pnpm config get registry)" = "https://registry.npmjs.org/" ]; then echo 'Error: Using default registry' && exit 1; else CLERK_USE_RQ=${{ matrix.clerk-use-rq }} pnpm turbo build $TURBO_ARGS --only && pnpm changeset publish --no-git-tag; fi + if [ "$(pnpm config get registry)" = "https://registry.npmjs.org/" ]; then echo 'Error: Using default registry' && exit 1; else CLERK_USE_RQ=${{ matrix.clerk-use-rq }} pnpm turbo build $TURBO_ARGS --only && pnpm changeset publish --no-git-tag --tag integration; fi - name: Edit .npmrc [link-workspace-packages=false] run: sed -i -E 's/link-workspace-packages=(deep|true)/link-workspace-packages=false/' .npmrc @@ -433,6 +432,15 @@ jobs: pnpm config set minimum-release-age-exclude @clerk/* pnpm add @clerk/clerk-js + - name: Install @clerk/ui in os temp + if: ${{ steps.task-status.outputs.affected == '1' }} + working-directory: ${{runner.temp}} + run: | + mkdir clerk-ui && cd clerk-ui + pnpm init + pnpm config set minimum-release-age-exclude @clerk/* + pnpm add @clerk/ui + - name: Copy components @clerk/astro if: ${{ matrix.test-name == 'astro' }} run: cd packages/astro && pnpm copy:components @@ -467,7 +475,9 @@ jobs: run: pnpm turbo test:integration:${{ matrix.test-name }} $TURBO_ARGS env: E2E_APP_CLERK_JS_DIR: ${{runner.temp}} - E2E_CLERK_VERSION: "latest" + E2E_APP_CLERK_UI_DIR: ${{runner.temp}} + E2E_CLERK_JS_VERSION: "latest" + E2E_CLERK_UI_VERSION: "latest" E2E_NEXTJS_VERSION: ${{ matrix.next-version }} E2E_PROJECT: ${{ matrix.test-project }} E2E_CLERK_ENCRYPTION_KEY: ${{ matrix.clerk-encryption-key }} diff --git a/.github/workflows/nightly-checks.yml b/.github/workflows/nightly-checks.yml index b8edac5a23a..f74f87a2e0e 100644 --- a/.github/workflows/nightly-checks.yml +++ b/.github/workflows/nightly-checks.yml @@ -2,7 +2,7 @@ name: Nightly upstream tests on: workflow_dispatch: schedule: - - cron: '0 7 * * *' + - cron: "0 7 * * *" jobs: integration-tests: @@ -12,7 +12,7 @@ jobs: strategy: matrix: - test-name: ['nextjs'] + test-name: ["nextjs"] steps: - name: Checkout Repo @@ -44,6 +44,10 @@ jobs: working-directory: ${{runner.temp}} run: mkdir clerk-js && cd clerk-js && pnpm init && pnpm add @clerk/clerk-js + - name: Install @clerk/ui in os temp + working-directory: ${{runner.temp}} + run: mkdir clerk-ui && cd clerk-ui && pnpm init && pnpm add @clerk/ui + - name: Run Integration Tests id: integration_tests continue-on-error: true @@ -59,11 +63,13 @@ jobs: echo "exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT env: E2E_APP_CLERK_JS_DIR: ${{runner.temp}} - E2E_CLERK_VERSION: 'latest' - E2E_NEXTJS_VERSION: 'canary' - E2E_NPM_FORCE: 'true' - E2E_REACT_DOM_VERSION: '19.1.0' - E2E_REACT_VERSION: '19.1.0' + E2E_APP_CLERK_UI_DIR: ${{runner.temp}} + E2E_CLERK_JS_VERSION: "latest" + E2E_CLERK_UI_VERSION: "latest" + E2E_NEXTJS_VERSION: "canary" + E2E_NPM_FORCE: "true" + E2E_REACT_DOM_VERSION: "19.1.0" + E2E_REACT_VERSION: "19.1.0" INTEGRATION_INSTANCE_KEYS: ${{ secrets.INTEGRATION_INSTANCE_KEYS }} MAILSAC_API_KEY: ${{ secrets.MAILSAC_API_KEY }} @@ -78,6 +84,7 @@ jobs: integration/test-results/ integration/.next/ ${{runner.temp}}/clerk-js/node_modules/ + ${{runner.temp}}/clerk-ui/node_modules/ retention-days: 7 - name: Report Status @@ -85,8 +92,8 @@ jobs: uses: ravsamhq/notify-slack-action@v1 with: status: ${{ steps.integration_tests.outputs.exit_code == '0' && 'success' || 'failure' }} - notify_when: 'failure' - notification_title: 'Integration Test Failure - ${{ matrix.test-name }}' + notify_when: "failure" + notification_title: "Integration Test Failure - ${{ matrix.test-name }}" message_format: | *Job:* ${{ github.workflow }} (${{ matrix.test-name }}) *Status:* ${{ steps.integration_tests.outputs.exit_code == '0' && 'Success' || 'Failed' }} diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index e1bfb68d1c9..b2b1ea1182a 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -77,7 +77,7 @@ jobs: turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} turbo-team: ${{ vars.TURBO_TEAM }} turbo-token: ${{ secrets.TURBO_TOKEN }} - registry-url: 'https://registry.npmjs.org' + registry-url: "https://registry.npmjs.org" - name: Build packages run: pnpm turbo build $TURBO_ARGS @@ -101,12 +101,17 @@ jobs: run: | cp -r $GITHUB_WORKSPACE/packages/clerk-js/dist $FULL_TMP_FOLDER/public/clerk-js + - name: Copy ui/dist/browser into public/clerk-ui of test site + run: | + cp -r $GITHUB_WORKSPACE/packages/ui/dist/browser $FULL_TMP_FOLDER/public/clerk-ui + - name: Build with Vercel run: | cd $FULL_TMP_FOLDER vercel build --yes env: NEXT_PUBLIC_CLERK_JS_URL: /clerk-js/clerk.browser.js + NEXT_PUBLIC_CLERK_UI_URL: /clerk-ui/ui.browser.js - name: Deploy to Vercel (prebuilt) id: vercel-deploy diff --git a/.github/workflows/release-canary-core-3.yml b/.github/workflows/release-canary-core-3.yml new file mode 100644 index 00000000000..054f75ab1b6 --- /dev/null +++ b/.github/workflows/release-canary-core-3.yml @@ -0,0 +1,73 @@ +name: canary-core3 release +run-name: canary-core3 release from ${{ github.ref_name }} + +on: + push: + branches: + - vincent-and-the-doctor + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + canary-core3-release: + if: ${{ github.repository == 'clerk/javascript' }} + runs-on: ${{ vars.RUNNER_NORMAL || 'ubuntu-latest' }} + timeout-minutes: ${{ vars.TIMEOUT_MINUTES_NORMAL && fromJSON(vars.TIMEOUT_MINUTES_NORMAL) || 10 }} + env: + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ vars.TURBO_TEAM }} + TURBO_CACHE: remote:rw + permissions: + contents: read + id-token: write + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Setup + id: config + uses: ./.github/actions/init + with: + turbo-signature: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} + turbo-team: ${{ vars.TURBO_TEAM }} + turbo-token: ${{ secrets.TURBO_TOKEN }} + playwright-enabled: true # Must be present to enable caching on branched workflows + registry-url: "https://registry.npmjs.org" + + - name: Version packages for canary-core3 + id: version-packages + run: pnpm version-packages:canary-core3 | tail -1 >> "$GITHUB_OUTPUT" + + - name: Build release + if: steps.version-packages.outputs.success == '1' + run: pnpm turbo build $TURBO_ARGS + + - name: canary-core3 release + if: steps.version-packages.outputs.success == '1' + run: pnpm release:canary-core3 + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: true + + - name: Trigger workflows on related repos + if: steps.version-packages.outputs.success == '1' + uses: actions/github-script@v7 + with: + result-encoding: string + retries: 3 + retry-exempt-status-codes: 400,401 + github-token: ${{ secrets.CLERK_COOKIE_PAT }} + script: | + const clerkjsVersion = require('./packages/clerk-js/package.json').version; + const clerkUiVersion = require('./packages/ui/package.json').version; + const nextjsVersion = require('./packages/nextjs/package.json').version; + + github.rest.actions.createWorkflowDispatch({ + owner: 'clerk', + repo: 'sdk-infra-workers', + workflow_id: 'update-pkg-versions.yml', + ref: 'main', + inputs: { clerkjsVersion, clerkUiVersion } + }) diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index fe72e07ba67..fcb13d15294 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -34,7 +34,7 @@ jobs: turbo-team: ${{ vars.TURBO_TEAM }} turbo-token: ${{ secrets.TURBO_TOKEN }} playwright-enabled: true # Must be present to enable caching on branched workflows - registry-url: 'https://registry.npmjs.org' + registry-url: "https://registry.npmjs.org" - name: Version packages for canary id: version-packages @@ -65,7 +65,7 @@ jobs: github.rest.actions.createWorkflowDispatch({ owner: 'clerk', repo: 'sdk-infra-workers', - workflow_id: 'update-clerkjs.yml', + workflow_id: 'update-pkg-versions', ref: 'main', inputs: { version: clerkjsVersion } }) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5ebbf2d695..d80c5f5e415 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,6 +39,15 @@ jobs: fetch-depth: 0 show-progress: false + - name: Safety check - Prevent production releases on canary-core3 branch + run: | + if [ -f ".canary-core3-branch" ]; then + echo "❌ ERROR: This branch is marked for canary-core3 releases only!" + echo "Production releases (latest tag) are not allowed on this branch." + echo "If you need to make a production release, use the main production branch." + exit 1 + fi + - name: Setup id: config uses: ./.github/actions/init @@ -55,8 +64,8 @@ jobs: id: changesets uses: changesets/action@v1 with: - commit: 'ci(repo): Version packages' - title: 'ci(repo): Version packages' + commit: "ci(repo): Version packages" + title: "ci(repo): Version packages" publish: pnpm release # Workaround for https://github.com/changesets/changesets/issues/421 version: pnpm version-packages @@ -83,7 +92,7 @@ jobs: github.rest.actions.createWorkflowDispatch({ owner: 'clerk', repo: 'sdk-infra-workers', - workflow_id: 'update-clerkjs.yml', + workflow_id: 'update-pkg-versions', ref: 'main', inputs: { version: clerkjsVersion } }) @@ -145,5 +154,5 @@ jobs: uses: ./.github/actions/init with: node-version: ${{ matrix.version }} - turbo-team: '' - turbo-token: '' + turbo-team: "" + turbo-token: "" diff --git a/.jit/config.yml b/.jit/config.yml index 0381e46946f..1055ef4dbfc 100644 --- a/.jit/config.yml +++ b/.jit/config.yml @@ -17,9 +17,6 @@ folders: - path: /packages/react exclude: - ./**/*.test.ts - - path: /packages/remix - exclude: - - ./**/*.test.ts - path: /packages/shared exclude: - ./**/*.test.ts diff --git a/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap b/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap deleted file mode 100644 index d0d845d9b2a..00000000000 --- a/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap +++ /dev/null @@ -1,291 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Typedoc output > should have a deliberate file structure 1`] = ` -[ - "types/act-claim.mdx", - "types/act-jwt-claim.mdx", - "types/active-session-resource.mdx", - "types/add-payment-method-params.mdx", - "types/billing-checkout-json.mdx", - "types/billing-checkout-resource.mdx", - "types/billing-checkout-totals-json.mdx", - "types/billing-checkout-totals.mdx", - "types/billing-initialized-payment-method-json.mdx", - "types/billing-initialized-payment-method-resource.mdx", - "types/billing-money-amount-json.mdx", - "types/billing-money-amount.mdx", - "types/billing-namespace.mdx", - "types/billing-payer-json.mdx", - "types/billing-payer-methods.mdx", - "types/billing-payer-resource-type.mdx", - "types/billing-payer-resource.mdx", - "types/billing-payment-charge-type.mdx", - "types/billing-payment-json.mdx", - "types/billing-payment-method-json.mdx", - "types/billing-payment-method-resource.mdx", - "types/billing-payment-method-status.mdx", - "types/billing-payment-resource.mdx", - "types/billing-payment-status.mdx", - "types/billing-plan-json.mdx", - "types/billing-plan-resource.mdx", - "types/billing-statement-group-json.mdx", - "types/billing-statement-group.mdx", - "types/billing-statement-json.mdx", - "types/billing-statement-resource.mdx", - "types/billing-statement-status.mdx", - "types/billing-statement-totals-json.mdx", - "types/billing-statement-totals.mdx", - "types/billing-subscription-item-json.mdx", - "types/billing-subscription-item-resource.mdx", - "types/billing-subscription-json.mdx", - "types/billing-subscription-plan-period.mdx", - "types/billing-subscription-resource.mdx", - "types/billing-subscription-status.mdx", - "types/cancel-subscription-params.mdx", - "types/check-authorization-fn.mdx", - "types/check-authorization-from-session-claims.mdx", - "types/check-authorization-params-from-session-claims.mdx", - "types/check-authorization-with-custom-permissions.mdx", - "types/clerk-api-error.mdx", - "types/clerk-api-response-error.mdx", - "types/clerk-host-router.mdx", - "types/clerk-jwt-claims.mdx", - "types/clerk-paginated-response.mdx", - "types/clerk-pagination-params.mdx", - "types/clerk-pagination-request.mdx", - "types/clerk-resource.mdx", - "types/clerk-status.mdx", - "types/clerk.mdx", - "types/confirm-checkout-params.mdx", - "types/create-checkout-params.mdx", - "types/create-organization-params.mdx", - "types/deleted-object-resource.mdx", - "types/element-object-key.mdx", - "types/elements-config.mdx", - "types/errors.mdx", - "types/experimental_checkout-button-props.mdx", - "types/experimental_plan-details-button-props.mdx", - "types/experimental_subscription-details-button-props.mdx", - "types/feature-json.mdx", - "types/feature-resource.mdx", - "types/field-error.mdx", - "types/field-errors.mdx", - "types/for-payer-type.mdx", - "types/get-payment-attempts-params.mdx", - "types/get-payment-methods-params.mdx", - "types/get-plans-params.mdx", - "types/get-statements-params.mdx", - "types/get-subscription-params.mdx", - "types/get-token.mdx", - "types/id-selectors.mdx", - "types/initialize-payment-method-params.mdx", - "types/internal_checkout-props.mdx", - "types/internal_plan-details-props.mdx", - "types/internal_subscription-details-props.mdx", - "types/jwt-claims.mdx", - "types/jwt-header.mdx", - "types/legacy-redirect-props.mdx", - "types/localization-resource.mdx", - "types/make-default-payment-method-params.mdx", - "types/multi-domain-and-or-proxy.mdx", - "types/organization-custom-role-key.mdx", - "types/organization-domain-resource.mdx", - "types/organization-domain-verification-status.mdx", - "types/organization-enrollment-mode.mdx", - "types/organization-invitation-resource.mdx", - "types/organization-invitation-status.mdx", - "types/organization-membership-request-resource.mdx", - "types/organization-membership-resource.mdx", - "types/organization-permission-key.mdx", - "types/organization-resource.mdx", - "types/organization-suggestion-resource.mdx", - "types/organization-suggestion-status.mdx", - "types/organizations-jwt-claim.mdx", - "types/override.mdx", - "types/path-value.mdx", - "types/payment-gateway.mdx", - "types/pending-session-options.mdx", - "types/pending-session-resource.mdx", - "types/protect-props.mdx", - "types/record-to-path.mdx", - "types/redirect-options.mdx", - "types/remove-payment-method-params.mdx", - "types/reverification-config.mdx", - "types/saml-strategy.mdx", - "types/sdk-metadata.mdx", - "types/server-get-token-options.mdx", - "types/server-get-token.mdx", - "types/session-resource.mdx", - "types/session-status-claim.mdx", - "types/session-task.mdx", - "types/session-verification-level.mdx", - "types/session-verification-types.mdx", - "types/set-active-params.mdx", - "types/set-active.mdx", - "types/sign-in-future-resource.mdx", - "types/sign-in-resource.mdx", - "types/sign-in-signal-value.mdx", - "types/sign-out.mdx", - "types/sign-up-authenticate-with-metamask-params.mdx", - "types/sign-up-enterprise-connection-json.mdx", - "types/sign-up-enterprise-connection-resource.mdx", - "types/sign-up-future-resource.mdx", - "types/sign-up-resource.mdx", - "types/signed-in-session-resource.mdx", - "types/state-selectors.mdx", - "types/telemetry-log-entry.mdx", - "types/use-auth-return.mdx", - "types/use-session-list-return.mdx", - "types/use-session-return.mdx", - "types/use-sign-in-return.mdx", - "types/use-sign-up-return.mdx", - "types/use-user-return.mdx", - "types/user-organization-invitation-resource.mdx", - "types/user-resource.mdx", - "types/without.mdx", - "shared/api-url-from-publishable-key.mdx", - "shared/build-clerk-js-script-attributes.mdx", - "shared/build-publishable-key.mdx", - "shared/camel-to-snake.mdx", - "shared/clerk-api-error.mdx", - "shared/clerk-js-script-url.mdx", - "shared/clerk-runtime-error.mdx", - "shared/create-dev-or-staging-url-cache.mdx", - "shared/create-path-matcher.mdx", - "shared/deep-camel-to-snake.mdx", - "shared/deep-snake-to-camel.mdx", - "shared/deprecated-object-property.mdx", - "shared/derive-state.mdx", - "shared/extract-dev-browser-jwt-from-url.mdx", - "shared/fast-deep-merge-and-replace.mdx", - "shared/get-clerk-js-major-version-or-tag.mdx", - "shared/get-cookie-suffix.mdx", - "shared/get-env-variable.mdx", - "shared/get-non-undefined-values.mdx", - "shared/get-script-url.mdx", - "shared/get-suffixed-cookie-name.mdx", - "shared/icon-image-url.mdx", - "shared/in-browser.mdx", - "shared/is-browser-online.mdx", - "shared/is-clerk-runtime-error.mdx", - "shared/is-development-from-publishable-key.mdx", - "shared/is-development-from-secret-key.mdx", - "shared/is-ipv4-address.mdx", - "shared/is-production-from-publishable-key.mdx", - "shared/is-production-from-secret-key.mdx", - "shared/is-publishable-key.mdx", - "shared/is-staging.mdx", - "shared/is-truthy.mdx", - "shared/is-valid-browser-online.mdx", - "shared/is-valid-browser.mdx", - "shared/isomorphic-atob.mdx", - "shared/load-clerk-js-script.mdx", - "shared/local-storage-broadcast-channel.mdx", - "shared/pages-or-infinite-options.mdx", - "shared/paginated-hook-config.mdx", - "shared/paginated-resources.mdx", - "shared/parse-publishable-key.mdx", - "shared/read-json-file.mdx", - "shared/set-clerk-js-loading-error-package-name.mdx", - "shared/snake-to-camel.mdx", - "shared/titleize.mdx", - "shared/to-sentence.mdx", - "shared/use-clerk.mdx", - "shared/use-organization-list-params.mdx", - "shared/use-organization-list-return.mdx", - "shared/use-organization-list.mdx", - "shared/use-organization-params.mdx", - "shared/use-organization-return.mdx", - "shared/use-organization.mdx", - "shared/use-reverification.mdx", - "shared/use-session-list.mdx", - "shared/use-session.mdx", - "shared/use-user.mdx", - "shared/user-agent-is-robot.mdx", - "shared/version-selector.mdx", - "nextjs/auth.mdx", - "nextjs/build-clerk-props.mdx", - "nextjs/clerk-middleware-auth-object.mdx", - "nextjs/clerk-middleware-options.mdx", - "nextjs/clerk-middleware.mdx", - "nextjs/create-async-get-auth.mdx", - "nextjs/create-sync-get-auth.mdx", - "nextjs/current-user.mdx", - "nextjs/get-auth.mdx", - "nextjs/session-auth-with-redirect.mdx", - "clerk-react/api-keys.mdx", - "clerk-react/checkout-button-props.mdx", - "clerk-react/checkout-button.mdx", - "clerk-react/clerk-provider-props.mdx", - "clerk-react/plan-details-button-props.mdx", - "clerk-react/plan-details-button.mdx", - "clerk-react/protect.mdx", - "clerk-react/redirect-to-create-organization.mdx", - "clerk-react/redirect-to-organization-profile.mdx", - "clerk-react/redirect-to-user-profile.mdx", - "clerk-react/subscription-details-button-props.mdx", - "clerk-react/subscription-details-button.mdx", - "clerk-react/use-auth.mdx", - "clerk-react/use-clerk.mdx", - "clerk-react/use-organization-list.mdx", - "clerk-react/use-organization.mdx", - "clerk-react/use-reverification.mdx", - "clerk-react/use-session-list.mdx", - "clerk-react/use-session.mdx", - "clerk-react/use-sign-in-signal.mdx", - "clerk-react/use-sign-in.mdx", - "clerk-react/use-sign-up-signal.mdx", - "clerk-react/use-sign-up.mdx", - "clerk-react/use-user.mdx", - "backend/allowlist-identifier.mdx", - "backend/auth-object.mdx", - "backend/authenticate-request-options.mdx", - "backend/billing-payment-attempt-webhook-event-json.mdx", - "backend/billing-plan-json.mdx", - "backend/billing-plan.mdx", - "backend/billing-subscription-item-json.mdx", - "backend/billing-subscription-item-webhook-event-json.mdx", - "backend/billing-subscription-item.mdx", - "backend/billing-subscription-webhook-event-json.mdx", - "backend/billing-subscription.mdx", - "backend/client.mdx", - "backend/email-address.mdx", - "backend/external-account.mdx", - "backend/feature.mdx", - "backend/get-auth-fn-no-request.mdx", - "backend/get-auth-fn.mdx", - "backend/identification-link.mdx", - "backend/infer-auth-object-from-token-array.mdx", - "backend/infer-auth-object-from-token.mdx", - "backend/invitation-status.mdx", - "backend/invitation.mdx", - "backend/m2-m-token.mdx", - "backend/machine-scope.mdx", - "backend/machine-secret-key.mdx", - "backend/machine.mdx", - "backend/o-auth-application.mdx", - "backend/organization-invitation-status.mdx", - "backend/organization-invitation.mdx", - "backend/organization-membership-public-user-data.mdx", - "backend/organization-membership.mdx", - "backend/organization-sync-options.mdx", - "backend/organization-sync-target.mdx", - "backend/organization.mdx", - "backend/paginated-resource-response.mdx", - "backend/phone-number.mdx", - "backend/public-organization-data-json.mdx", - "backend/redirect-url.mdx", - "backend/saml-account.mdx", - "backend/saml-connection.mdx", - "backend/session-activity.mdx", - "backend/session.mdx", - "backend/user.mdx", - "backend/verification.mdx", - "backend/verify-machine-auth-token.mdx", - "backend/verify-token-options.mdx", - "backend/verify-token.mdx", - "backend/verify-webhook-options.mdx", - "backend/verify-webhook.mdx", - "backend/web3-wallet.mdx", -] -`; diff --git a/.typedoc/__tests__/file-structure.test.ts b/.typedoc/__tests__/file-structure.test.ts index a85c13fc087..010c72e267c 100644 --- a/.typedoc/__tests__/file-structure.test.ts +++ b/.typedoc/__tests__/file-structure.test.ts @@ -33,8 +33,8 @@ describe('Typedoc output', () => { expect(folders).toMatchInlineSnapshot(` [ "backend", - "clerk-react", "nextjs", + "react", "shared", ] `); diff --git a/canary-core3-branch b/canary-core3-branch new file mode 100644 index 00000000000..9dfa0b10923 --- /dev/null +++ b/canary-core3-branch @@ -0,0 +1,5 @@ +# Alpha v6 Development Branch +# This file marks this branch as an canary-core3 development branch. +# Production releases (latest tag) are disabled on this branch. +# Only canary-core3 releases are allowed. + diff --git a/commitlint.config.ts b/commitlint.config.ts index 17a8e933ac7..426d2f16fab 100644 --- a/commitlint.config.ts +++ b/commitlint.config.ts @@ -10,11 +10,12 @@ const getPackageNames = () => { const fullPath = join(packagesDir, entry); return statSync(fullPath).isDirectory(); }) - .map(dir => { + .flatMap(dir => { const packageJsonPath = join(packagesDir, dir, 'package.json'); try { const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); - return packageJson.name.split('/').pop(); + const name = packageJson.name.split('/').pop() as string; + return [name, name.replace('clerk-', '')]; } catch { // Ignore directories without a package.json return null; @@ -27,7 +28,7 @@ const getPackageNames = () => { const Configuration = { extends: ['@commitlint/config-conventional'], rules: { - 'subject-case': [2, 'always', ['sentence-case']], + 'subject-case': [2, 'always', ['lower-case', 'sentence-case']], 'body-max-line-length': [1, 'always', '150'], 'scope-empty': [2, 'never'], 'scope-enum': [2, 'always', [...getPackageNames(), 'repo', 'release', 'e2e', '*']], diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 013f49829b7..cd792b06f1a 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -44,8 +44,7 @@ All packages of the monorepo are inside [packages](../packages). For package spe - [`@clerk/backend`](../packages/backend): Functionalities regarded as "core" for Clerk to operate with. _Authentication resolution, API Resources etc._ - [`@clerk/clerk-js`](../packages/clerk-js): Core JavaScript implementation used by Clerk in the browser. -- [`@clerk/clerk-react`](../packages/react) Clerk package for React applications. -- [`@clerk/types`](../packages/types): Main TypeScript typings for Clerk libraries. +- [`@clerk/react`](../packages/react) Clerk package for React applications. - Browse [packages](../packages) to see more Additionally there are packages which act as shared utilities or building blocks. @@ -186,10 +185,10 @@ To review your changes locally, you can run `pnpm run typedoc:generate` to gener Create a PR that includes your changes to any Typedoc comments. Once the PR has been merged and a release is published, a PR will [automatically](https://github.com/clerk/clerk-docs/blob/main/.github/workflows/typedoc.yml) be opened in `clerk-docs` to merge in the Typedoc changes. -Typedoc output is embedded in `clerk-docs` files with the `` component. For example, if you updated Typedoc comments for the `useAuth()` hook in `clerk/javascript`, you'll need to make sure that in `clerk-docs`, in the `/hooks/use-auth.mdx` file, there's a `` component linked to the `./clerk-typedoc/clerk-react/use-auth.mdx` file, like: +Typedoc output is embedded in `clerk-docs` files with the `` component. For example, if you updated Typedoc comments for the `useAuth()` hook in `clerk/javascript`, you'll need to make sure that in `clerk-docs`, in the `/hooks/use-auth.mdx` file, there's a `` component linked to the `./clerk-typedoc/react/use-auth.mdx` file, like: ```mdx - + ``` Read more about this in the [`clerk-docs` CONTRIBUTING.md](https://github.com/clerk/clerk-docs/blob/main/CONTRIBUTING.md#typedoc-). diff --git a/eslint.config.mjs b/eslint.config.mjs index 8881ed71ad6..fc55d8da6c3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -374,6 +374,29 @@ export default tseslint.config([ '@typescript-eslint/unbound-method': 'off', }, }, + { + name: 'packages/shared', + files: ['packages/shared/src/**/*'], + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@clerk/shared', '@clerk/shared/*'], + message: + 'Do not import from @clerk/shared package exports within the package itself. Use the @/ alias or relative imports from source files instead (e.g., import from "@/types" or "../../types").', + }, + { + group: ['../../../*'], + message: + 'Relative imports should not traverse more than 2 levels up (../../). Use the @/ path alias instead (e.g., import from "@/types").', + }, + ], + }, + ], + }, + }, { name: 'packages/expo-passkeys', files: ['packages/expo-passkeys/src/**/*'], @@ -442,7 +465,7 @@ export default tseslint.config([ { definedTags: ['inline', 'unionReturnHeadings', 'displayFunctionSignature', 'paramExtension'], typed: false }, ], 'jsdoc/require-hyphen-before-param-description': 'warn', - 'jsdoc/require-description': 'warn', + 'jsdoc/require-description': 'off', 'jsdoc/require-description-complete-sentence': 'warn', 'jsdoc/require-param': ['warn', { ignoreWhenAllParamsMissing: true }], 'jsdoc/require-param-description': 'warn', @@ -455,6 +478,16 @@ export default tseslint.config([ ], }, }, + { + name: 'repo/jsdoc-internal', + files: ['packages/shared/src/**/internal/**/*.{ts,tsx}', 'packages/shared/src/**/*.{ts,tsx}'], + plugins: { + jsdoc: pluginJsDoc, + }, + rules: { + 'jsdoc/require-jsdoc': 'off', + }, + }, ...pluginYml.configs['flat/recommended'], { name: 'eslint-prettier', diff --git a/integration/README.md b/integration/README.md index e6165c54dd8..64e26f9ac06 100644 --- a/integration/README.md +++ b/integration/README.md @@ -364,7 +364,7 @@ Assuming you have a `react-parcel` template defined in `integration/templates`, .setName('react-parcel') .useTemplate(templates['react-parcel']) .setEnvFormatter('public', key => `${key}`) - .addDependency('@clerk/clerk-react', constants.E2E_CLERK_VERSION || clerkReactLocal); + .addDependency('@clerk/react', constants.E2E_CLERK_JS_VERSION || clerkReactLocal); ``` Here's what each thing is doing: diff --git a/integration/constants.ts b/integration/constants.ts index 2369077fe84..7195880dba2 100644 --- a/integration/constants.ts +++ b/integration/constants.ts @@ -28,6 +28,15 @@ export const constants = { * Controls the path where clerk.browser.js is located on the disk. */ E2E_APP_CLERK_JS_DIR: process.env.E2E_APP_CLERK_JS_DIR, + /** + * Controls the URL the apps will load ui.browser.js from. + * If this is set, clerk-ui will not be served automatically from the test runner. + */ + E2E_APP_CLERK_UI: process.env.E2E_APP_CLERK_UI, + /** + * Controls the path where ui.browser.js is located on the disk. + */ + E2E_APP_CLERK_UI_DIR: process.env.E2E_APP_CLERK_UI_DIR, /** * If CLEANUP=0 is used, the .tmp_integration directory will not be deleted. * This is useful for debugging locally. @@ -63,7 +72,11 @@ export const constants = { /** * The version of the dependency to use, controlled programmatically. */ - E2E_CLERK_VERSION: process.env.E2E_CLERK_VERSION, + E2E_CLERK_JS_VERSION: process.env.E2E_CLERK_JS_VERSION, + /** + * The version of the dependency to use, controlled programmatically. + */ + E2E_CLERK_UI_VERSION: process.env.E2E_CLERK_UI_VERSION, /** * Key used to encrypt request data for Next.js dynamic keys. * @ref https://clerk.com/docs/references/nextjs/clerk-middleware#dynamic-keys diff --git a/integration/models/stateFile.ts b/integration/models/stateFile.ts index e95eb0ec02f..e5713b422f4 100644 --- a/integration/models/stateFile.ts +++ b/integration/models/stateFile.ts @@ -32,6 +32,12 @@ type StateFile = Partial<{ * The PID is used to teardown the http-server after the tests are done. */ clerkJsHttpServerPid: number; + /** + * This prop describes the pid of the http server that serves the clerk-ui hotloaded lib. + * The http-server replaces the production clerk-ui delivery mechanism. + * The PID is used to teardown the http-server after the tests are done. + */ + clerkUiHttpServerPid: number; }>; const createStateFile = () => { @@ -83,6 +89,16 @@ const createStateFile = () => { return read().clerkJsHttpServerPid; }; + const setClerkUiHttpServerPid = (pid: number) => { + const json = read(); + json.clerkUiHttpServerPid = pid; + write(json); + }; + + const getClerkUiHttpServerPid = () => { + return read().clerkUiHttpServerPid; + }; + const debug = () => { const json = read(); console.log('state file', JSON.stringify(json, null, 2)); @@ -94,6 +110,8 @@ const createStateFile = () => { getStandAloneApp, setClerkJsHttpServerPid, getClerkJsHttpServerPid, + setClerkUiHttpServerPid, + getClerkUiHttpServerPid, addLongRunningApp, getLongRunningApps, debug, diff --git a/integration/presets/astro.ts b/integration/presets/astro.ts index 7995725b97b..65cbebff911 100644 --- a/integration/presets/astro.ts +++ b/integration/presets/astro.ts @@ -10,9 +10,9 @@ const astroNode = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm preview') - .addDependency('@clerk/astro', linkPackage('astro')) - .addDependency('@clerk/types', linkPackage('types')) - .addDependency('@clerk/localizations', linkPackage('localizations')); + .addDependency('@clerk/astro', linkPackage('astro', 'integration')) + .addDependency('@clerk/shared', linkPackage('types', 'integration')) + .addDependency('@clerk/localizations', linkPackage('localizations', 'integration')); const astroStatic = astroNode.clone().setName('astro-hybrid').useTemplate(templates['astro-hybrid']); diff --git a/integration/presets/custom-flows.ts b/integration/presets/custom-flows.ts index bda524479f6..f816de17057 100644 --- a/integration/presets/custom-flows.ts +++ b/integration/presets/custom-flows.ts @@ -1,4 +1,3 @@ -import { constants } from '../constants'; import { applicationConfig } from '../models/applicationConfig'; import { templates } from '../templates'; import { linkPackage } from './utils'; @@ -11,8 +10,9 @@ const reactVite = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm preview') - .addDependency('@clerk/clerk-react', constants.E2E_CLERK_VERSION || linkPackage('react')) - .addDependency('@clerk/themes', constants.E2E_CLERK_VERSION || linkPackage('themes')); + .addDependency('@clerk/react', linkPackage('react', 'integration')) + .addDependency('@clerk/shared', linkPackage('shared', 'integration')) + .addDependency('@clerk/themes', linkPackage('themes', 'integration')); export const customFlows = { reactVite, diff --git a/integration/presets/elements.ts b/integration/presets/elements.ts deleted file mode 100644 index fb95a2ce5dc..00000000000 --- a/integration/presets/elements.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { constants } from '../constants'; -import { applicationConfig } from '../models/applicationConfig'; -import { templates } from '../templates'; -import { linkPackage } from './utils'; - -const nextAppRouter = applicationConfig() - .setName('elements-next') - .useTemplate(templates['elements-next']) - .setEnvFormatter('public', key => `NEXT_PUBLIC_${key}`) - .addScript('setup', 'pnpm install') - .addScript('dev', 'pnpm dev') - .addScript('build', 'pnpm build') - .addScript('serve', 'pnpm start') - .addDependency('next', constants.E2E_NEXTJS_VERSION) - .addDependency('react', constants.E2E_REACT_VERSION) - .addDependency('react-dom', constants.E2E_REACT_DOM_VERSION) - .addDependency('@clerk/nextjs', constants.E2E_CLERK_VERSION || linkPackage('nextjs')) - .addDependency('@clerk/elements', constants.E2E_CLERK_VERSION || linkPackage('elements')); - -export const elements = { - nextAppRouter, -} as const; diff --git a/integration/presets/envs.ts b/integration/presets/envs.ts index d52dfeb8aab..318bbc4133b 100644 --- a/integration/presets/envs.ts +++ b/integration/presets/envs.ts @@ -27,7 +27,8 @@ const base = environmentConfig() .setEnvVariable('public', 'CLERK_KEYLESS_DISABLED', true) .setEnvVariable('public', 'CLERK_SIGN_IN_URL', '/sign-in') .setEnvVariable('public', 'CLERK_SIGN_UP_URL', '/sign-up') - .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js'); + .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js') + .setEnvVariable('public', 'CLERK_UI_URL', constants.E2E_APP_CLERK_UI || 'http://localhost:18212/ui.browser.js'); const withKeyless = base .clone() @@ -56,7 +57,8 @@ const sessionsProd1 = base .setId('sessionsProd1') .setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('sessions-prod-1').sk) .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('sessions-prod-1').pk) - .setEnvVariable('public', 'CLERK_JS_URL', ''); + .setEnvVariable('public', 'CLERK_JS_URL', '') + .setEnvVariable('public', 'CLERK_UI_URL', ''); const withEmailCodes_destroy_client = withEmailCodes .clone() @@ -91,7 +93,8 @@ const withAPCore1ClerkLatest = environmentConfig() .setEnvVariable('public', 'CLERK_TELEMETRY_DISABLED', true) .setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('with-email-codes').sk) .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-email-codes').pk) - .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js'); + .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js') + .setEnvVariable('public', 'CLERK_UI_URL', constants.E2E_APP_CLERK_UI || 'http://localhost:18212/ui.browser.js'); const withAPCore1ClerkV4 = environmentConfig() .setId('withAPCore1ClerkV4') @@ -104,7 +107,8 @@ const withAPCore2ClerkLatest = environmentConfig() .setEnvVariable('public', 'CLERK_TELEMETRY_DISABLED', true) .setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('core-2-all-enabled').sk) .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('core-2-all-enabled').pk) - .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js'); + .setEnvVariable('public', 'CLERK_JS_URL', constants.E2E_APP_CLERK_JS || 'http://localhost:18211/clerk.browser.js') + .setEnvVariable('public', 'CLERK_UI_URL', constants.E2E_APP_CLERK_UI || 'http://localhost:18212/ui.browser.js'); const withAPCore2ClerkV4 = environmentConfig() .setId('withAPCore2ClerkV4') diff --git a/integration/presets/expo.ts b/integration/presets/expo.ts index bf9806fce22..ad692fd2268 100644 --- a/integration/presets/expo.ts +++ b/integration/presets/expo.ts @@ -10,7 +10,7 @@ const expoWeb = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm start') - .addDependency('@clerk/clerk-expo', linkPackage('expo')); + .addDependency('@clerk/expo', linkPackage('expo')); export const expo = { expoWeb, diff --git a/integration/presets/express.ts b/integration/presets/express.ts index 8ca84ae40ae..5c253f5daf4 100644 --- a/integration/presets/express.ts +++ b/integration/presets/express.ts @@ -11,8 +11,9 @@ const vite = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm start') - .addDependency('@clerk/express', constants.E2E_CLERK_VERSION || linkPackage('express')) - .addDependency('@clerk/clerk-js', constants.E2E_CLERK_VERSION || linkPackage('clerk-js')); + .addDependency('@clerk/express', constants.E2E_CLERK_JS_VERSION || linkPackage('express')) + .addDependency('@clerk/clerk-js', constants.E2E_CLERK_JS_VERSION || linkPackage('clerk-js')) + .addDependency('@clerk/ui', constants.E2E_CLERK_UI_VERSION || linkPackage('ui')); export const express = { vite, diff --git a/integration/presets/index.ts b/integration/presets/index.ts index 5048abef518..0d53bfaeaa5 100644 --- a/integration/presets/index.ts +++ b/integration/presets/index.ts @@ -1,6 +1,5 @@ import { astro } from './astro'; import { customFlows } from './custom-flows'; -import { elements } from './elements'; import { envs, instanceKeys } from './envs'; import { expo } from './expo'; import { express } from './express'; @@ -19,7 +18,6 @@ export const appConfigs = { longRunningApps: createLongRunningApps(), next, react, - elements, expo, astro, tanstack, diff --git a/integration/presets/longRunningApps.ts b/integration/presets/longRunningApps.ts index ebca49ac50a..055a246d924 100644 --- a/integration/presets/longRunningApps.ts +++ b/integration/presets/longRunningApps.ts @@ -1,7 +1,6 @@ import type { LongRunningApplication } from '../models/longRunningApplication'; import { longRunningApplication } from '../models/longRunningApplication'; import { astro } from './astro'; -import { elements } from './elements'; import { envs } from './envs'; import { expo } from './expo'; import { express } from './express'; @@ -75,7 +74,6 @@ export const createLongRunningApps = () => { { id: 'nuxt.node', config: nuxt.node, env: envs.withCustomRoles }, { id: 'react-router.node', config: reactRouter.reactRouterNode, env: envs.withEmailCodes }, { id: 'express.vite.withEmailCodes', config: express.vite, env: envs.withEmailCodes }, - { id: 'elements.next.appRouter', config: elements.nextAppRouter, env: envs.withEmailCodes }, ] as const; const apps = configs.map(longRunningApplication); diff --git a/integration/presets/next.ts b/integration/presets/next.ts index e2397d2a236..bfa1966d957 100644 --- a/integration/presets/next.ts +++ b/integration/presets/next.ts @@ -14,9 +14,8 @@ const appRouter = applicationConfig() .addDependency('next', constants.E2E_NEXTJS_VERSION) .addDependency('react', constants.E2E_REACT_VERSION) .addDependency('react-dom', constants.E2E_REACT_DOM_VERSION) - .addDependency('@clerk/nextjs', constants.E2E_CLERK_VERSION || linkPackage('nextjs')) - .addDependency('@clerk/shared', linkPackage('shared')) - .addDependency('@clerk/types', linkPackage('types')); + .addDependency('@clerk/nextjs', constants.E2E_CLERK_JS_VERSION || linkPackage('nextjs')) + .addDependency('@clerk/shared', linkPackage('shared')); const appRouterTurbo = appRouter.clone().setName('next-app-router-turbopack').addScript('dev', 'pnpm dev'); diff --git a/integration/presets/nuxt.ts b/integration/presets/nuxt.ts index 72baab0b22d..a57d6cc4aaa 100644 --- a/integration/presets/nuxt.ts +++ b/integration/presets/nuxt.ts @@ -12,7 +12,7 @@ const nuxtNode = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm preview') - .addDependency('@clerk/nuxt', constants.E2E_CLERK_VERSION || linkPackage('nuxt')) + .addDependency('@clerk/nuxt', constants.E2E_CLERK_JS_VERSION || linkPackage('nuxt')) .addDependency('@clerk/shared', linkPackage('shared')) .addDependency('@clerk/types', linkPackage('types')) .addDependency('@clerk/vue', linkPackage('vue')); diff --git a/integration/presets/react-router.ts b/integration/presets/react-router.ts index 6dc759ddae7..da2bf124c05 100644 --- a/integration/presets/react-router.ts +++ b/integration/presets/react-router.ts @@ -11,7 +11,7 @@ const reactRouterNode = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm start') - .addDependency('@clerk/react-router', constants.E2E_CLERK_VERSION || linkPackage('react-router')); + .addDependency('@clerk/react-router', constants.E2E_CLERK_JS_VERSION || linkPackage('react-router')); const reactRouterLibrary = applicationConfig() .setName('react-router-library') @@ -21,7 +21,7 @@ const reactRouterLibrary = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm preview') - .addDependency('@clerk/react-router', constants.E2E_CLERK_VERSION || linkPackage('react-router')); + .addDependency('@clerk/react-router', constants.E2E_CLERK_JS_VERSION || linkPackage('react-router')); export const reactRouter = { reactRouterNode, diff --git a/integration/presets/react.ts b/integration/presets/react.ts index 06e14342827..d2ea6b0861c 100644 --- a/integration/presets/react.ts +++ b/integration/presets/react.ts @@ -1,4 +1,3 @@ -import { constants } from '../constants'; import { applicationConfig } from '../models/applicationConfig'; import { templates } from '../templates'; import { linkPackage } from './utils'; @@ -11,8 +10,9 @@ const cra = applicationConfig() .addScript('dev', 'pnpm start') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm start') - .addDependency('@clerk/clerk-react', constants.E2E_CLERK_VERSION || linkPackage('react')) - .addDependency('@clerk/themes', constants.E2E_CLERK_VERSION || linkPackage('themes')); + .addDependency('@clerk/react', linkPackage('react', 'integration')) + .addDependency('@clerk/shared', linkPackage('shared', 'integration')) + .addDependency('@clerk/themes', linkPackage('themes', 'integration')); const vite = cra .clone() diff --git a/integration/presets/utils.ts b/integration/presets/utils.ts index f7831c39663..d22e39250dd 100644 --- a/integration/presets/utils.ts +++ b/integration/presets/utils.ts @@ -1,9 +1,9 @@ import path from 'node:path'; -export function linkPackage(pkg: string) { +export function linkPackage(pkg: string, tag?: string) { // eslint-disable-next-line turbo/no-undeclared-env-vars if (process.env.CI === 'true') { - return '*'; + return tag || '*'; } return `link:${path.resolve(process.cwd(), `packages/${pkg}`)}`; diff --git a/integration/presets/vue.ts b/integration/presets/vue.ts index b8d73168403..85272d2c836 100644 --- a/integration/presets/vue.ts +++ b/integration/presets/vue.ts @@ -11,7 +11,7 @@ const vite = applicationConfig() .addScript('dev', 'pnpm dev') .addScript('build', 'pnpm build') .addScript('serve', 'pnpm preview') - .addDependency('@clerk/vue', constants.E2E_CLERK_VERSION || linkPackage('vue')) + .addDependency('@clerk/vue', constants.E2E_CLERK_JS_VERSION || linkPackage('vue')) .addDependency('@clerk/localizations', linkPackage('localizations')); export const vue = { diff --git a/integration/scripts/clerkJsServer.ts b/integration/scripts/clerkJsServer.ts index 5e837f9a9cd..315b75cd6ef 100644 --- a/integration/scripts/clerkJsServer.ts +++ b/integration/scripts/clerkJsServer.ts @@ -1,67 +1,34 @@ /* eslint-disable turbo/no-undeclared-env-vars */ -import os from 'node:os'; import path from 'node:path'; -import { constants } from '../constants'; import { stateFile } from '../models/stateFile'; -import { awaitableTreekill, fs, waitForServer } from '.'; -import { run } from './run'; +import { getTempDir, killHttpServer, startHttpServer } from './httpServer'; -export const startClerkJsHttpServer = async () => { +export const startClerkJsHttpServer = async (): Promise => { if (process.env.E2E_APP_CLERK_JS) { return; } - if (!process.env.CI) { - await copyClerkJsToTempDir(); - } - return serveFromTempDir(); -}; - -export const killClerkJsHttpServer = async () => { - const clerkJsHttpServerPid = stateFile.getClerkJsHttpServerPid(); - if (clerkJsHttpServerPid) { - console.log('Killing clerkJsHttpServer', clerkJsHttpServerPid); - await awaitableTreekill(clerkJsHttpServerPid, 'SIGKILL'); - } -}; -// If we are running the tests locally, then clerk.browser.js should be built already -// so we simply copy it from packages/clerk to the same location as CICD would install it -const copyClerkJsToTempDir = async () => { - const clerkJsTempDir = getClerkJsTempDir(); - await fs.remove(clerkJsTempDir); - await fs.ensureDir(clerkJsTempDir); - const packagesClerkJsDistPath = path.join(process.cwd(), 'packages/clerk-js/dist'); - fs.copySync(packagesClerkJsDistPath, clerkJsTempDir); -}; + const clerkJsTempDir = getTempDir('clerk-js/node_modules/@clerk/clerk-js/dist', 'E2E_APP_CLERK_JS_DIR'); + const sourceDir = path.join(process.cwd(), 'packages/clerk-js/dist'); -const serveFromTempDir = async () => { - console.log('Serving clerkJs from temp dir'); - const port = 18211; - const serverUrl = `http://localhost:${port}`; - const now = Date.now(); - const stdoutFilePath = path.resolve(constants.TMP_DIR, `clerkJsHttpServer.${now}.log`); - const stderrFilePath = path.resolve(constants.TMP_DIR, `clerkJsHttpServer.${now}.err.log`); - const clerkJsTempDir = getClerkJsTempDir(); - const proc = run(`node_modules/.bin/http-server ${clerkJsTempDir} -d --gzip --cors -a localhost`, { - cwd: process.cwd(), - env: { PORT: port.toString() }, - detached: true, - stdout: fs.openSync(stdoutFilePath, 'a'), - stderr: fs.openSync(stderrFilePath, 'a'), + const { pid } = await startHttpServer({ + name: 'clerkJs', + port: 18211, + sourceDir, + targetTempDir: clerkJsTempDir, + envVarOverride: 'E2E_APP_CLERK_JS', + envVarDirOverride: 'E2E_APP_CLERK_JS_DIR', + shouldCopyInLocal: true, }); - stateFile.setClerkJsHttpServerPid(proc.pid); - await waitForServer(serverUrl, { log: console.log, maxAttempts: Infinity }); - console.log('clerk.browser.js is being served from', serverUrl); + + stateFile.setClerkJsHttpServerPid(pid); }; -// The location where the clerk.browser.js is served from -// For simplicity, on CICD we install `@clerk/clerk-js` on osTemp -// so the actual clerk.browser.file is at osTemp/clerk-js/node_modules/@clerk/clerk-js/dist -// Locally, it's the osTemp/clerk-js/node_modules/@clerk/clerk-js/dist -// You can override it by setting the `E2E_APP_CLERK_JS_DIR` env variable -const getClerkJsTempDir = () => { - const osTempDir = process.env.E2E_APP_CLERK_JS_DIR || os.tmpdir(); - return path.join(osTempDir, ...'clerk-js/node_modules/@clerk/clerk-js/dist'.split('/')); +export const killClerkJsHttpServer = async (): Promise => { + const clerkJsHttpServerPid = stateFile.getClerkJsHttpServerPid(); + if (clerkJsHttpServerPid) { + await killHttpServer(clerkJsHttpServerPid, 'clerkJs'); + } }; diff --git a/integration/scripts/clerkUiServer.ts b/integration/scripts/clerkUiServer.ts new file mode 100644 index 00000000000..c5c940d25f5 --- /dev/null +++ b/integration/scripts/clerkUiServer.ts @@ -0,0 +1,34 @@ +/* eslint-disable turbo/no-undeclared-env-vars */ + +import path from 'node:path'; + +import { stateFile } from '../models/stateFile'; +import { getTempDir, killHttpServer, startHttpServer } from './httpServer'; + +export const startClerkUiHttpServer = async (): Promise => { + if (process.env.E2E_APP_CLERK_UI) { + return; + } + + const clerkUiTempDir = getTempDir('clerk-ui/node_modules/@clerk/ui/dist/browser', 'E2E_APP_CLERK_UI_DIR'); + const sourceDir = path.join(process.cwd(), 'packages/ui/dist/browser'); + + const { pid } = await startHttpServer({ + name: 'clerkUi', + port: 18212, + sourceDir, + targetTempDir: clerkUiTempDir, + envVarOverride: 'E2E_APP_CLERK_UI', + envVarDirOverride: 'E2E_APP_CLERK_UI_DIR', + shouldCopyInLocal: true, + }); + + stateFile.setClerkUiHttpServerPid(pid); +}; + +export const killClerkUiHttpServer = async (): Promise => { + const clerkUiHttpServerPid = stateFile.getClerkUiHttpServerPid(); + if (clerkUiHttpServerPid) { + await killHttpServer(clerkUiHttpServerPid, 'clerkUi'); + } +}; diff --git a/integration/scripts/httpServer.ts b/integration/scripts/httpServer.ts new file mode 100644 index 00000000000..7bcd8fb3abf --- /dev/null +++ b/integration/scripts/httpServer.ts @@ -0,0 +1,71 @@ +/* eslint-disable turbo/no-undeclared-env-vars */ + +import os from 'node:os'; +import path from 'node:path'; + +import { constants } from '../constants'; +import { awaitableTreekill, fs, waitForServer } from '.'; +import { run } from './run'; + +type HttpServerConfig = { + name: string; + port: number; + sourceDir: string; + targetTempDir: string; + envVarOverride?: string; + envVarDirOverride?: string; + shouldCopyInLocal: boolean; +}; + +const copyToTempDir = async (sourceDir: string, targetTempDir: string): Promise => { + await fs.remove(targetTempDir); + await fs.ensureDir(targetTempDir); + fs.copySync(sourceDir, targetTempDir); +}; + +const serveFromTempDir = async (config: HttpServerConfig): Promise<{ pid: number; serverUrl: string }> => { + console.log(`Serving ${config.name} from temp dir`); + const serverUrl = `http://localhost:${config.port}`; + const now = Date.now(); + const stdoutFilePath = path.resolve(constants.TMP_DIR, `${config.name}HttpServer.${now}.log`); + const stderrFilePath = path.resolve(constants.TMP_DIR, `${config.name}HttpServer.${now}.err.log`); + + const proc = run(`node_modules/.bin/http-server ${config.targetTempDir} -d --gzip --cors -a localhost`, { + cwd: process.cwd(), + env: { PORT: config.port.toString() }, + detached: true, + stdout: fs.openSync(stdoutFilePath, 'a'), + stderr: fs.openSync(stderrFilePath, 'a'), + }); + + await waitForServer(serverUrl, { log: console.log, maxAttempts: Infinity }); + console.log(`${config.name} is being served from`, serverUrl); + + return { pid: proc.pid, serverUrl }; +}; + +export const startHttpServer = async (config: HttpServerConfig): Promise<{ pid: number; serverUrl: string }> => { + // Skip if override env var is provided + if (config.envVarOverride && process.env[config.envVarOverride]) { + return { pid: 0, serverUrl: process.env[config.envVarOverride] }; + } + + // In local development, copy files to temp directory + if (!process.env.CI && config.shouldCopyInLocal) { + await copyToTempDir(config.sourceDir, config.targetTempDir); + } + + return serveFromTempDir(config); +}; + +export const killHttpServer = async (pid: number, serverName: string): Promise => { + if (pid) { + console.log(`Killing ${serverName}HttpServer`, pid); + await awaitableTreekill(pid, 'SIGKILL'); + } +}; + +export const getTempDir = (basePath: string, envVarOverride?: string): string => { + const osTempDir = envVarOverride && process.env[envVarOverride] ? process.env[envVarOverride] : os.tmpdir(); + return path.join(osTempDir, ...basePath.split('/')); +}; diff --git a/integration/scripts/index.ts b/integration/scripts/index.ts index e87e998628b..ff301be7798 100644 --- a/integration/scripts/index.ts +++ b/integration/scripts/index.ts @@ -15,3 +15,5 @@ export * from './setup'; export * from './waitForServer'; export { awaitableTreekill } from './awaitableTreekill'; export { startClerkJsHttpServer, killClerkJsHttpServer } from './clerkJsServer'; +export { startClerkUiHttpServer, killClerkUiHttpServer } from './clerkUiServer'; +export { startHttpServer, killHttpServer, getTempDir } from './httpServer'; diff --git a/integration/templates/astro-node/src/components/CustomUserButton.astro b/integration/templates/astro-node/src/components/CustomUserButton.astro index 7586c0db5ba..6eed2bea4a9 100644 --- a/integration/templates/astro-node/src/components/CustomUserButton.astro +++ b/integration/templates/astro-node/src/components/CustomUserButton.astro @@ -2,7 +2,7 @@ import { UserButton } from '@clerk/astro/components'; --- - + diff --git a/integration/templates/astro-node/src/layouts/react/Layout.astro b/integration/templates/astro-node/src/layouts/react/Layout.astro index 2bc68f059e2..41b878880e3 100644 --- a/integration/templates/astro-node/src/layouts/react/Layout.astro +++ b/integration/templates/astro-node/src/layouts/react/Layout.astro @@ -80,10 +80,7 @@ import { LanguagePicker } from '../../components/LanguagePicker'; - + diff --git a/integration/templates/custom-flows-react-vite/src/main.tsx b/integration/templates/custom-flows-react-vite/src/main.tsx index 7b170e17b18..966d034a194 100644 --- a/integration/templates/custom-flows-react-vite/src/main.tsx +++ b/integration/templates/custom-flows-react-vite/src/main.tsx @@ -2,7 +2,7 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { BrowserRouter, Route, Routes } from 'react-router'; import './index.css'; -import { ClerkProvider } from '@clerk/clerk-react'; +import { ClerkProvider } from '@clerk/react'; import { Home } from './routes/Home'; import { SignIn } from './routes/SignIn'; import { SignUp } from './routes/SignUp'; @@ -22,6 +22,7 @@ createRoot(document.getElementById('root')!).render( diff --git a/integration/templates/custom-flows-react-vite/src/routes/Protected.tsx b/integration/templates/custom-flows-react-vite/src/routes/Protected.tsx index 1f937b66941..6c326c87021 100644 --- a/integration/templates/custom-flows-react-vite/src/routes/Protected.tsx +++ b/integration/templates/custom-flows-react-vite/src/routes/Protected.tsx @@ -1,4 +1,4 @@ -import { useUser } from '@clerk/clerk-react'; +import { useUser } from '@clerk/react'; export function Protected() { const { user, isLoaded } = useUser(); diff --git a/integration/templates/custom-flows-react-vite/src/routes/SignIn.tsx b/integration/templates/custom-flows-react-vite/src/routes/SignIn.tsx index eb4ab0041f9..27eead90579 100644 --- a/integration/templates/custom-flows-react-vite/src/routes/SignIn.tsx +++ b/integration/templates/custom-flows-react-vite/src/routes/SignIn.tsx @@ -5,15 +5,14 @@ import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { useUser } from '@clerk/clerk-react'; -import { useSignInSignal } from '@clerk/clerk-react/experimental'; +import { useSignIn, useUser } from '@clerk/react'; import { useState } from 'react'; import { NavLink, useNavigate } from 'react-router'; type AvailableStrategy = 'email_code' | 'phone_code' | 'password' | 'reset_password_email_code'; export function SignIn({ className, ...props }: React.ComponentProps<'div'>) { - const { signIn, errors, fetchStatus } = useSignInSignal(); + const { signIn, errors, fetchStatus } = useSignIn(); const [selectedStrategy, setSelectedStrategy] = useState(null); const { isSignedIn } = useUser(); const navigate = useNavigate(); diff --git a/integration/templates/custom-flows-react-vite/src/routes/SignUp.tsx b/integration/templates/custom-flows-react-vite/src/routes/SignUp.tsx index ef74268e35a..b506c46ecb0 100644 --- a/integration/templates/custom-flows-react-vite/src/routes/SignUp.tsx +++ b/integration/templates/custom-flows-react-vite/src/routes/SignUp.tsx @@ -5,11 +5,11 @@ import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { useSignUpSignal } from '@clerk/clerk-react/experimental'; +import { useSignUp } from '@clerk/react'; import { NavLink, useNavigate } from 'react-router'; export function SignUp({ className, ...props }: React.ComponentProps<'div'>) { - const { signUp, errors, fetchStatus } = useSignUpSignal(); + const { signUp, errors, fetchStatus } = useSignUp(); const navigate = useNavigate(); const handleSubmit = async (formData: FormData) => { diff --git a/integration/templates/elements-next/.gitignore b/integration/templates/elements-next/.gitignore deleted file mode 100644 index cdbd42c5c32..00000000000 --- a/integration/templates/elements-next/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -package-lock.json diff --git a/integration/templates/elements-next/README.md b/integration/templates/elements-next/README.md deleted file mode 100644 index a7da5398280..00000000000 --- a/integration/templates/elements-next/README.md +++ /dev/null @@ -1,34 +0,0 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -pnpm dev -# or -yarn dev -# or -pnpm dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/integration/templates/elements-next/next.config.js b/integration/templates/elements-next/next.config.js deleted file mode 100644 index 954fac0d40b..00000000000 --- a/integration/templates/elements-next/next.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - eslint: { - ignoreDuringBuilds: true, - }, -}; - -module.exports = nextConfig; diff --git a/integration/templates/elements-next/package.json b/integration/templates/elements-next/package.json deleted file mode 100644 index 526697ee31f..00000000000 --- a/integration/templates/elements-next/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "elements-next", - "version": "0.1.0", - "private": true, - "scripts": { - "build": "next build", - "dev": "next dev", - "lint": "next lint", - "start": "next start" - }, - "dependencies": { - "@clerk/elements": "file:../../../packages/elements", - "@clerk/nextjs": "file:../../../packages/nextjs", - "@types/node": "^18.19.33", - "@types/react": "18.3.12", - "@types/react-dom": "18.3.1", - "next": "^14.2.13", - "react": "18.3.1", - "react-dom": "18.3.1", - "typescript": "^5.7.3" - }, - "devDependencies": { - "autoprefixer": "^10.4.20", - "postcss": "^8.4.49", - "tailwindcss": "^3.4.17" - }, - "engines": { - "node": ">=18.17.0" - } -} diff --git a/integration/templates/elements-next/postcss.config.js b/integration/templates/elements-next/postcss.config.js deleted file mode 100644 index 12a703d900d..00000000000 --- a/integration/templates/elements-next/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/integration/templates/elements-next/src/app/favicon.ico b/integration/templates/elements-next/src/app/favicon.ico deleted file mode 100644 index 718d6fea483..00000000000 Binary files a/integration/templates/elements-next/src/app/favicon.ico and /dev/null differ diff --git a/integration/templates/elements-next/src/app/globals.css b/integration/templates/elements-next/src/app/globals.css deleted file mode 100644 index ea46f6b7409..00000000000 --- a/integration/templates/elements-next/src/app/globals.css +++ /dev/null @@ -1,49 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb)); - font-family: - -apple-system, - BlinkMacSystemFont, - avenir next, - avenir, - segoe ui, - helvetica neue, - helvetica, - Cantarell, - Ubuntu, - roboto, - noto, - arial, - sans-serif; -} - -main { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - padding: 6rem; - min-height: 100vh; -} diff --git a/integration/templates/elements-next/src/app/layout.tsx b/integration/templates/elements-next/src/app/layout.tsx deleted file mode 100644 index 9e5b6a73819..00000000000 --- a/integration/templates/elements-next/src/app/layout.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import './globals.css'; - -import { ClerkProvider } from '@clerk/nextjs'; -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: 'Clerk Elements - Next.js E2E', - description: 'Clerk Elements - Next.js E2E', -}; - -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - {children} - - - ); -} diff --git a/integration/templates/elements-next/src/app/otp/page.tsx b/integration/templates/elements-next/src/app/otp/page.tsx deleted file mode 100644 index 60447b7dc6f..00000000000 --- a/integration/templates/elements-next/src/app/otp/page.tsx +++ /dev/null @@ -1,117 +0,0 @@ -'use client'; - -import * as Clerk from '@clerk/elements/common'; -import * as SignIn from '@clerk/elements/sign-in'; - -function clsx(...args: (string | undefined | Record)[]): string { - const classes: string[] = []; - - for (const arg of args) { - switch (typeof arg) { - case 'string': - classes.push(arg); - break; - case 'object': - for (const key in arg) { - if (arg[key]) { - classes.push(key); - } - } - break; - } - } - - return classes.join(' '); -} - -export default function OTP() { - return ( -
- - -
-

OTP Playground

-
- - Simple OTP Input - - - - Segmented OTP Input - { - return ( -
- {value} - {status === 'cursor' && ( -
-
-
- )} -
- ); - }} - /> - - - Segmented OTP Input (with props) - { - return ( -
- {value} - {status === 'cursor' && ( -
-
-
- )} -
- ); - }} - /> - - - -
- ); -} diff --git a/integration/templates/elements-next/src/app/page.tsx b/integration/templates/elements-next/src/app/page.tsx deleted file mode 100644 index dafd45e8d6e..00000000000 --- a/integration/templates/elements-next/src/app/page.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import { SignedIn, SignedOut, SignOutButton } from '@clerk/nextjs'; -import Link from 'next/link'; - -function Card({ children, title }: { children: React.ReactNode; title: string }) { - return ( -
-

{title}

- {children} -
- ); -} - -export default function Home() { - return ( -
-

Clerk Elements: Next.js E2E

-

- Kitchen sink template to test out Clerk Elements in Next.js App Router. -

-
- - -

signed-out-state

-
- -

signed-in-state

-
-
- -
    -
  • - - Sign-In - -
  • -
  • - - Sign-Up - -
  • -
  • - - OTP Playground - -
  • -
  • - - Password Validation - -
  • -
-
- - -

Not logged in.

-
- - - - - -
-
-
- ); -} diff --git a/integration/templates/elements-next/src/app/sign-in/[[...sign-in]]/page.tsx b/integration/templates/elements-next/src/app/sign-in/[[...sign-in]]/page.tsx deleted file mode 100644 index 71d8af7573d..00000000000 --- a/integration/templates/elements-next/src/app/sign-in/[[...sign-in]]/page.tsx +++ /dev/null @@ -1,347 +0,0 @@ -'use client'; - -import * as React from 'react'; -import * as Clerk from '@clerk/elements/common'; -import * as SignIn from '@clerk/elements/sign-in'; - -// password, phone_code, email_code, email_link, reset_password_email_code, but the rendered strategies are: -// password, email_code, reset_password_email_code, phone_code - -function Button({ children, ...props }: { children: React.ReactNode }) { - return ( - - ); -} - -export default function SignInPage() { - const [usePhone, setUsePhone] = React.useState(false); - - return ( -
-
- - -
-

Sign in to Clover

-
- - -
- - {usePhone ? 'Phone number' : 'Email or username'} - - -
- - -
- - - - - - -
-

Alternatively, sign in with these platforms

-
- - - Login with Google - -
-
-
- -
-

Use another method

-
- - - - - - - - - - - - -
-

Alternatively, sign in with these platforms

-
- - - Login with Google - -
-
-

- - Go back - -

-
- -
-

Forgot password?

-
- - - - -
-

Alternatively, sign in with these platforms

-
- - - Login with Google - -
-
-
- - -
-

Enter your password

-

- Welcome back -

-
- - -
- Password - - Forgot password? - -
- - -
- - - -
- -
-

Verify email code

-
- - - Email code - - - - - - -
- -
-

Verify email link

-
- - - Email link - - - - - - -
- -
-

Verify email code

-
- - - Email code - - - - - - -
- -
-

Verify phone code

-
- - - Phone code - - - - - - -
-
- - Use another method - -
-
- -
-

Reset your password

-
- - - New password - - - - - Confirm password - - - - - - -
-
-
-
- ); -} diff --git a/integration/templates/elements-next/src/app/sign-up/[[...sign-up]]/page.tsx b/integration/templates/elements-next/src/app/sign-up/[[...sign-up]]/page.tsx deleted file mode 100644 index 6ff8dd5e569..00000000000 --- a/integration/templates/elements-next/src/app/sign-up/[[...sign-up]]/page.tsx +++ /dev/null @@ -1,152 +0,0 @@ -'use client'; - -import * as Clerk from '@clerk/elements/common'; -import * as SignUp from '@clerk/elements/sign-up'; - -export default function SignUpPage() { - return ( -
- - -
-

Create an account

-
- -
- - Email - - - - - Password - - - - - Phone number (optional) - - - - - Username (optional) - - - -
- - Continue - -
- - - -
-

Verify email code

-
- - Email code - - - - - Continue - -
- -
-

Verify phone code

-
- - Phone code - - - - - Continue - -
-
- -
-

Continue registration

-
- - - Username - - - - - Continue - -
-
-
- ); -} diff --git a/integration/templates/elements-next/src/app/validate-password/page.tsx b/integration/templates/elements-next/src/app/validate-password/page.tsx deleted file mode 100644 index 869ea04794b..00000000000 --- a/integration/templates/elements-next/src/app/validate-password/page.tsx +++ /dev/null @@ -1,94 +0,0 @@ -'use client'; - -import * as React from 'react'; -import * as Clerk from '@clerk/elements/common'; -import * as SignIn from '@clerk/elements/sign-in'; - -export default function ValitePassword() { - const [hidden, setHidden] = React.useState(true); - - return ( -
- - -
-

Password Validation Playground

-

- Just to test out the{' '} - - password validation - {' '} - 🙃 -

-
- -
- Password - -
- - - {({ state, codes, message }) => ( -
-

Field State Props

- - - - - - - - - - - - - - - - - - - - - -
PropValue
State - {state} -
Codes - {JSON.stringify(codes)} -
Message - {message} -
-
- )} -
-
-
-
-
- ); -} diff --git a/integration/templates/elements-next/src/middleware.ts b/integration/templates/elements-next/src/middleware.ts deleted file mode 100644 index 545508cedc1..00000000000 --- a/integration/templates/elements-next/src/middleware.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clerkMiddleware } from '@clerk/nextjs/server'; -export default clerkMiddleware; - -export const config = { - matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'], -}; diff --git a/integration/templates/elements-next/tailwind.config.js b/integration/templates/elements-next/tailwind.config.js deleted file mode 100644 index 5eaa3171157..00000000000 --- a/integration/templates/elements-next/tailwind.config.js +++ /dev/null @@ -1,18 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'], - theme: { - extend: { - keyframes: { - 'caret-blink': { - '0%,70%,100%': { opacity: '1' }, - '20%,50%': { opacity: '0' }, - }, - }, - animation: { - 'caret-blink': 'caret-blink 1.25s ease-out infinite', - }, - }, - }, - plugins: [], -}; diff --git a/integration/templates/elements-next/tsconfig.json b/integration/templates/elements-next/tsconfig.json deleted file mode 100644 index eb0b41d94d5..00000000000 --- a/integration/templates/elements-next/tsconfig.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/integration/templates/expo-web/app/_layout.tsx b/integration/templates/expo-web/app/_layout.tsx index 95a03ee596d..24295ee033d 100644 --- a/integration/templates/expo-web/app/_layout.tsx +++ b/integration/templates/expo-web/app/_layout.tsx @@ -1,5 +1,5 @@ import { Stack, useRouter } from 'expo-router'; -import { ClerkLoaded, ClerkProvider } from '@clerk/clerk-expo'; +import { ClerkLoaded, ClerkProvider } from '@clerk/expo'; export default function RootLayout() { const router = useRouter(); @@ -8,6 +8,8 @@ export default function RootLayout() { router.push(to)} routerReplace={to => router.replace(to)} + clerkJSUrl={process.env.EXPO_PUBLIC_CLERK_JS_URL} + clerkUiUrl={process.env.EXPO_PUBLIC_CLERK_UI_URL} > diff --git a/integration/templates/expo-web/app/custom-sign-in.tsx b/integration/templates/expo-web/app/custom-sign-in.tsx index 3ca42a81d3c..e5dc5a10911 100644 --- a/integration/templates/expo-web/app/custom-sign-in.tsx +++ b/integration/templates/expo-web/app/custom-sign-in.tsx @@ -1,38 +1,25 @@ -import { useSignIn } from '@clerk/clerk-expo'; +import { useSignIn } from '@clerk/expo'; import { Link, useRouter } from 'expo-router'; import { Text, TextInput, Button, View } from 'react-native'; import React from 'react'; export default function Page() { - const { signIn, setActive, isLoaded } = useSignIn(); + const { signIn } = useSignIn(); const router = useRouter(); const [emailAddress, setEmailAddress] = React.useState(''); const [password, setPassword] = React.useState(''); const onSignInPress = React.useCallback(async () => { - if (!isLoaded) { - return; - } - - try { - const signInAttempt = await signIn.create({ - identifier: emailAddress, - password, + await signIn.password({ emailAddress, password }); + if (signIn.status === 'complete') { + await signIn.finalize({ + navigate: async () => { + router.replace('/'); + }, }); - - if (signInAttempt.status === 'complete') { - await setActive({ session: signInAttempt.createdSessionId }); - router.replace('/'); - } else { - // See https://clerk.com/docs/custom-flows/error-handling - // for more info on error handling - console.error(JSON.stringify(signInAttempt, null, 2)); - } - } catch (err: any) { - console.error(JSON.stringify(err, null, 2)); } - }, [isLoaded, emailAddress, password]); + }, [emailAddress, password]); return ( diff --git a/integration/templates/expo-web/app/custom-sign-up.tsx b/integration/templates/expo-web/app/custom-sign-up.tsx index a51d547d908..6368bc0d1cd 100644 --- a/integration/templates/expo-web/app/custom-sign-up.tsx +++ b/integration/templates/expo-web/app/custom-sign-up.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; import { TextInput, Button, View } from 'react-native'; -import { useSignUp } from '@clerk/clerk-expo'; +import { useSignUp } from '@clerk/expo'; import { useRouter } from 'expo-router'; export default function SignUpScreen() { - const { isLoaded, signUp, setActive } = useSignUp(); + const { signUp } = useSignUp(); const router = useRouter(); const [emailAddress, setEmailAddress] = React.useState(''); @@ -13,46 +13,19 @@ export default function SignUpScreen() { const [code, setCode] = React.useState(''); const onSignUpPress = async () => { - if (!isLoaded) { - return; - } - - try { - await signUp.create({ - emailAddress, - password, - }); - - await signUp.prepareEmailAddressVerification({ strategy: 'email_code' }); - - setPendingVerification(true); - } catch (err: any) { - // See https://clerk.com/docs/custom-flows/error-handling - // for more info on error handling - console.error(JSON.stringify(err, null, 2)); - } + await signUp.password({ emailAddress, password }); + await signUp.verifications.sendEmailCode({ emailAddress }); + setPendingVerification(true); }; const onPressVerify = async () => { - if (!isLoaded) { - return; - } - - try { - const completeSignUp = await signUp.attemptEmailAddressVerification({ - code, + await signUp.verifications.verifyEmailCode({ code }); + if (signUp.status === 'complete') { + await signUp.finalize({ + navigate: async () => { + router.replace('/'); + }, }); - - if (completeSignUp.status === 'complete') { - await setActive({ session: completeSignUp.createdSessionId }); - router.replace('/'); - } else { - console.error(JSON.stringify(completeSignUp, null, 2)); - } - } catch (err: any) { - // See https://clerk.com/docs/custom-flows/error-handling - // for more info on error handling - console.error(JSON.stringify(err, null, 2)); } }; diff --git a/integration/templates/expo-web/app/index.tsx b/integration/templates/expo-web/app/index.tsx index f43e714fac6..431bf8c209f 100644 --- a/integration/templates/expo-web/app/index.tsx +++ b/integration/templates/expo-web/app/index.tsx @@ -1,6 +1,6 @@ import { Text, View } from 'react-native'; -import { SignedIn, SignedOut } from '@clerk/clerk-expo'; -import { UserButton } from '@clerk/clerk-expo/web'; +import { SignedIn, SignedOut } from '@clerk/expo'; +import { UserButton } from '@clerk/expo/web'; export default function Index() { return ( diff --git a/integration/templates/expo-web/app/sign-in.tsx b/integration/templates/expo-web/app/sign-in.tsx index 240376991d9..d627d7781a7 100644 --- a/integration/templates/expo-web/app/sign-in.tsx +++ b/integration/templates/expo-web/app/sign-in.tsx @@ -1,5 +1,5 @@ import { Text, View } from 'react-native'; -import { SignIn } from '@clerk/clerk-expo/web'; +import { SignIn } from '@clerk/expo/web'; export default function Index() { return ( diff --git a/integration/templates/expo-web/metro.config.js b/integration/templates/expo-web/metro.config.js index 1874df5a11a..045418d1e79 100644 --- a/integration/templates/expo-web/metro.config.js +++ b/integration/templates/expo-web/metro.config.js @@ -8,10 +8,10 @@ const path = require('node:path'); /** @type {() => string | undefined} */ const getClerkExpoPath = () => { - const clerkExpoPath = packageJson.dependencies['@clerk/clerk-expo']; + const clerkExpoPath = packageJson.dependencies['@clerk/expo']; if (clerkExpoPath?.startsWith('*')) { - const pathToModule = require.resolve('@clerk/clerk-expo'); + const pathToModule = require.resolve('@clerk/expo'); return pathToModule.replace('dist/index.js', ''); } diff --git a/integration/templates/expo-web/package.json b/integration/templates/expo-web/package.json index d26d26f63d8..2aba208949d 100644 --- a/integration/templates/expo-web/package.json +++ b/integration/templates/expo-web/package.json @@ -15,7 +15,7 @@ "dependencies": { "@expo/vector-icons": "^14.0.2", "@react-navigation/native": "^6.0.2", - "expo": "~51.0.17", + "expo": "~53", "expo-constants": "~16.0.2", "expo-font": "~12.0.7", "expo-linking": "~6.3.1", @@ -24,9 +24,9 @@ "expo-status-bar": "~1.12.1", "expo-system-ui": "~3.0.6", "expo-web-browser": "~13.0.3", - "react": "18.3.1", - "react-dom": "18.3.1", - "react-native": "0.74.3", + "react": "19.2.0", + "react-dom": "19.2.0", + "react-native": "0.82.1", "react-native-gesture-handler": "~2.16.1", "react-native-reanimated": "~3.10.1", "react-native-safe-area-context": "4.10.9", diff --git a/integration/templates/express-vite/src/client/main.ts b/integration/templates/express-vite/src/client/main.ts index 2656c4e02df..bf19f46d7b7 100644 --- a/integration/templates/express-vite/src/client/main.ts +++ b/integration/templates/express-vite/src/client/main.ts @@ -1,10 +1,14 @@ import { Clerk } from '@clerk/clerk-js'; +import { ClerkUi } from '@clerk/ui/entry'; const publishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY; document.addEventListener('DOMContentLoaded', async function () { const clerk = new Clerk(publishableKey); - await clerk.load(); + + await clerk.load({ + clerkUiCtor: ClerkUi, + }); if (clerk.isSignedIn) { document.getElementById('app')!.innerHTML = ` diff --git a/integration/templates/next-app-router-quickstart/package.json b/integration/templates/next-app-router-quickstart/package.json index cbda141d7fa..f03c8bd84da 100644 --- a/integration/templates/next-app-router-quickstart/package.json +++ b/integration/templates/next-app-router-quickstart/package.json @@ -18,6 +18,6 @@ "typescript": "^5.7.3" }, "engines": { - "node": ">=18.17.0" + "node": ">=20.9.0" } } diff --git a/integration/templates/next-app-router/package.json b/integration/templates/next-app-router/package.json index f419946d5e8..c2243548937 100644 --- a/integration/templates/next-app-router/package.json +++ b/integration/templates/next-app-router/package.json @@ -19,6 +19,6 @@ "typescript": "^5.7.3" }, "engines": { - "node": ">=18.17.0" + "node": ">=20.9.0" } } diff --git a/integration/templates/next-app-router/src/app/(reverification)/actions.ts b/integration/templates/next-app-router/src/app/(reverification)/actions.ts index 54334730534..76750881672 100644 --- a/integration/templates/next-app-router/src/app/(reverification)/actions.ts +++ b/integration/templates/next-app-router/src/app/(reverification)/actions.ts @@ -1,7 +1,7 @@ 'use server'; import { auth, reverificationError } from '@clerk/nextjs/server'; -import { ReverificationConfig } from '@clerk/types'; +import type { ReverificationConfig } from '@clerk/shared/types'; const logUserIdActionReverification = async () => { const { userId, has } = await auth.protect(); diff --git a/integration/templates/nuxt-node/app/middleware/auth.global.js b/integration/templates/nuxt-node/app/middleware/auth.global.js index 8ecaf1bb3f5..0e6f082773b 100644 --- a/integration/templates/nuxt-node/app/middleware/auth.global.js +++ b/integration/templates/nuxt-node/app/middleware/auth.global.js @@ -1,12 +1,12 @@ export default defineNuxtRouteMiddleware(to => { const { userId } = useAuth(); - const isPublicPage = createRouteMatcher(['/sign-in']); - const isProtectedPage = createRouteMatcher(['/user']); + const isPublicPage = createRouteMatcher(['/sign-in(.*)', '/sign-up(.*)']); + const isProtectedPage = createRouteMatcher(['/user-profile(.*)']); // Is authenticated and trying to access a public page if (userId.value && isPublicPage(to)) { - return navigateTo('/user'); + return navigateTo('/user-profile'); } // Is not authenticated and trying to access a protected page diff --git a/integration/templates/nuxt-node/app/pages/hash/sign-in/[...slug].vue b/integration/templates/nuxt-node/app/pages/hash/sign-in/[...slug].vue new file mode 100644 index 00000000000..19c4b6f25bf --- /dev/null +++ b/integration/templates/nuxt-node/app/pages/hash/sign-in/[...slug].vue @@ -0,0 +1,3 @@ + diff --git a/integration/templates/nuxt-node/app/pages/sign-in.vue b/integration/templates/nuxt-node/app/pages/sign-in.vue deleted file mode 100644 index b9258533122..00000000000 --- a/integration/templates/nuxt-node/app/pages/sign-in.vue +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/integration/templates/nuxt-node/app/pages/sign-in/[...slug].vue b/integration/templates/nuxt-node/app/pages/sign-in/[...slug].vue new file mode 100644 index 00000000000..8a075773a66 --- /dev/null +++ b/integration/templates/nuxt-node/app/pages/sign-in/[...slug].vue @@ -0,0 +1,3 @@ + diff --git a/integration/templates/nuxt-node/app/pages/sign-up/[...slug].vue b/integration/templates/nuxt-node/app/pages/sign-up/[...slug].vue new file mode 100644 index 00000000000..81430e4734e --- /dev/null +++ b/integration/templates/nuxt-node/app/pages/sign-up/[...slug].vue @@ -0,0 +1,3 @@ + diff --git a/integration/templates/nuxt-node/app/pages/user.vue b/integration/templates/nuxt-node/app/pages/user-profile/[...slug].vue similarity index 100% rename from integration/templates/nuxt-node/app/pages/user.vue rename to integration/templates/nuxt-node/app/pages/user-profile/[...slug].vue diff --git a/integration/templates/react-cra/package.json b/integration/templates/react-cra/package.json index ddf6492bb4f..ebcfd8289fe 100644 --- a/integration/templates/react-cra/package.json +++ b/integration/templates/react-cra/package.json @@ -39,6 +39,6 @@ "@types/react-dom": "18.3.1" }, "engines": { - "node": ">=18.17.0" + "node": ">=20.9.0" } } diff --git a/integration/templates/react-cra/src/App.tsx b/integration/templates/react-cra/src/App.tsx index 7689a4c38ed..38197953f08 100644 --- a/integration/templates/react-cra/src/App.tsx +++ b/integration/templates/react-cra/src/App.tsx @@ -1,7 +1,7 @@ // @ts-ignore import React from 'react'; import './App.css'; -import { SignedIn, SignedOut, SignIn, UserButton } from '@clerk/clerk-react'; +import { SignedIn, SignedOut, SignIn, UserButton } from '@clerk/react'; function App() { return ( @@ -10,7 +10,7 @@ function App() {
Signed In - + ); } diff --git a/integration/templates/react-cra/src/index.tsx b/integration/templates/react-cra/src/index.tsx index 3f52fdb0a98..5a20abd8759 100644 --- a/integration/templates/react-cra/src/index.tsx +++ b/integration/templates/react-cra/src/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; -import { ClerkProvider } from '@clerk/clerk-react'; +import { ClerkProvider } from '@clerk/react'; const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( @@ -10,6 +10,7 @@ root.render( diff --git a/integration/templates/react-vite/package.json b/integration/templates/react-vite/package.json index 49105904108..97ace6085d8 100644 --- a/integration/templates/react-vite/package.json +++ b/integration/templates/react-vite/package.json @@ -28,6 +28,6 @@ "vite": "^4.3.9" }, "engines": { - "node": ">=18.17.0" + "node": ">=20.9.0" } } diff --git a/integration/templates/react-vite/src/App.tsx b/integration/templates/react-vite/src/App.tsx index 57996dd8890..3c7aabd5906 100644 --- a/integration/templates/react-vite/src/App.tsx +++ b/integration/templates/react-vite/src/App.tsx @@ -1,4 +1,4 @@ -import { OrganizationSwitcher, SignedIn, SignedOut, UserButton } from '@clerk/clerk-react'; +import { OrganizationSwitcher, SignedIn, SignedOut, UserButton } from '@clerk/react'; import { Link } from 'react-router-dom'; import React from 'react'; import { ClientId } from './client-id'; @@ -6,7 +6,7 @@ import { ClientId } from './client-id'; function App() { return (
- + Loading organization switcher} /> SignedOut diff --git a/integration/templates/react-vite/src/buttons/index.tsx b/integration/templates/react-vite/src/buttons/index.tsx index 5aa32d433cf..4b5510477b3 100644 --- a/integration/templates/react-vite/src/buttons/index.tsx +++ b/integration/templates/react-vite/src/buttons/index.tsx @@ -1,4 +1,4 @@ -import { SignInButton, SignUpButton } from '@clerk/clerk-react'; +import { SignInButton, SignUpButton } from '@clerk/react'; export default function Home() { return ( diff --git a/integration/templates/react-vite/src/clerk-status/index.tsx b/integration/templates/react-vite/src/clerk-status/index.tsx index b8cbfd2c0b0..a683d480215 100644 --- a/integration/templates/react-vite/src/clerk-status/index.tsx +++ b/integration/templates/react-vite/src/clerk-status/index.tsx @@ -1,4 +1,4 @@ -import { ClerkLoaded, ClerkLoading, ClerkFailed, ClerkDegraded, useClerk } from '@clerk/clerk-react'; +import { ClerkLoaded, ClerkLoading, ClerkFailed, ClerkDegraded, useClerk } from '@clerk/react'; export default function ClerkStatusPage() { const { loaded, status } = useClerk(); diff --git a/integration/templates/react-vite/src/client-id.tsx b/integration/templates/react-vite/src/client-id.tsx index 88ccc8cf7cc..84f907c1d26 100644 --- a/integration/templates/react-vite/src/client-id.tsx +++ b/integration/templates/react-vite/src/client-id.tsx @@ -1,4 +1,4 @@ -import { useClerk, useSession } from '@clerk/clerk-react'; +import { useClerk, useSession } from '@clerk/react'; export function ClientId() { const clerk = useClerk(); diff --git a/integration/templates/react-vite/src/create-organization/index.tsx b/integration/templates/react-vite/src/create-organization/index.tsx index 7f268110e72..466529f98eb 100644 --- a/integration/templates/react-vite/src/create-organization/index.tsx +++ b/integration/templates/react-vite/src/create-organization/index.tsx @@ -1,4 +1,4 @@ -import { CreateOrganization } from '@clerk/clerk-react'; +import { CreateOrganization } from '@clerk/react'; export default function Page() { return ( diff --git a/integration/templates/react-vite/src/custom-user-button-trigger/index.tsx b/integration/templates/react-vite/src/custom-user-button-trigger/index.tsx index bbcd41b52e9..b387a7f36fc 100644 --- a/integration/templates/react-vite/src/custom-user-button-trigger/index.tsx +++ b/integration/templates/react-vite/src/custom-user-button-trigger/index.tsx @@ -1,4 +1,4 @@ -import { UserButton } from '@clerk/clerk-react'; +import { UserButton } from '@clerk/react'; import { PropsWithChildren, useContext, useState } from 'react'; import { PageContext, PageContextProvider } from '../PageContext.tsx'; diff --git a/integration/templates/react-vite/src/custom-user-button/index.tsx b/integration/templates/react-vite/src/custom-user-button/index.tsx index 728bb51f439..77a7199781a 100644 --- a/integration/templates/react-vite/src/custom-user-button/index.tsx +++ b/integration/templates/react-vite/src/custom-user-button/index.tsx @@ -1,4 +1,4 @@ -import { UserButton } from '@clerk/clerk-react'; +import { UserButton } from '@clerk/react'; import { useContext } from 'react'; import { PageContext, PageContextProvider } from '../PageContext.tsx'; diff --git a/integration/templates/react-vite/src/custom-user-button/with-dynamic-items.tsx b/integration/templates/react-vite/src/custom-user-button/with-dynamic-items.tsx index 5295b353e84..167dba77fb7 100644 --- a/integration/templates/react-vite/src/custom-user-button/with-dynamic-items.tsx +++ b/integration/templates/react-vite/src/custom-user-button/with-dynamic-items.tsx @@ -1,4 +1,4 @@ -import { UserButton } from '@clerk/clerk-react'; +import { UserButton } from '@clerk/react'; import { PageContextProvider } from '../PageContext.tsx'; import { useState } from 'react'; diff --git a/integration/templates/react-vite/src/custom-user-button/with-dynamic-label-and-custom-pages.tsx b/integration/templates/react-vite/src/custom-user-button/with-dynamic-label-and-custom-pages.tsx index 45527039c27..3cae5135ace 100644 --- a/integration/templates/react-vite/src/custom-user-button/with-dynamic-label-and-custom-pages.tsx +++ b/integration/templates/react-vite/src/custom-user-button/with-dynamic-label-and-custom-pages.tsx @@ -1,4 +1,4 @@ -import { UserButton } from '@clerk/clerk-react'; +import { UserButton } from '@clerk/react'; import { PageContextProvider } from '../PageContext.tsx'; import React from 'react'; diff --git a/integration/templates/react-vite/src/custom-user-button/with-dynamic-labels.tsx b/integration/templates/react-vite/src/custom-user-button/with-dynamic-labels.tsx index 8ad9aab0dae..6d80cc2dee5 100644 --- a/integration/templates/react-vite/src/custom-user-button/with-dynamic-labels.tsx +++ b/integration/templates/react-vite/src/custom-user-button/with-dynamic-labels.tsx @@ -1,4 +1,4 @@ -import { UserButton } from '@clerk/clerk-react'; +import { UserButton } from '@clerk/react'; import { PageContextProvider } from '../PageContext.tsx'; import React from 'react'; diff --git a/integration/templates/react-vite/src/custom-user-profile/index.tsx b/integration/templates/react-vite/src/custom-user-profile/index.tsx index c6f2fa42e8d..f44e25f0e90 100644 --- a/integration/templates/react-vite/src/custom-user-profile/index.tsx +++ b/integration/templates/react-vite/src/custom-user-profile/index.tsx @@ -1,4 +1,4 @@ -import { UserProfile } from '@clerk/clerk-react'; +import { UserProfile } from '@clerk/react'; import { useContext } from 'react'; import { PageContext, PageContextProvider } from '../PageContext.tsx'; diff --git a/integration/templates/react-vite/src/main.tsx b/integration/templates/react-vite/src/main.tsx index b337553375d..f35d944287b 100644 --- a/integration/templates/react-vite/src/main.tsx +++ b/integration/templates/react-vite/src/main.tsx @@ -1,4 +1,4 @@ -import { ClerkProvider } from '@clerk/clerk-react'; +import { ClerkProvider } from '@clerk/react'; import React from 'react'; import ReactDOM from 'react-dom/client'; import { createBrowserRouter, Outlet, RouterProvider, useNavigate } from 'react-router-dom'; @@ -31,6 +31,7 @@ const Root = () => { // @ts-ignore publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string} clerkJSUrl={import.meta.env.VITE_CLERK_JS_URL as string} + clerkUiUrl={import.meta.env.VITE_CLERK_UI_URL as string} routerPush={(to: string) => navigate(to)} routerReplace={(to: string) => navigate(to, { replace: true })} experimental={{ diff --git a/integration/templates/react-vite/src/organization-list/index.tsx b/integration/templates/react-vite/src/organization-list/index.tsx index 393856f058a..d35a442a03d 100644 --- a/integration/templates/react-vite/src/organization-list/index.tsx +++ b/integration/templates/react-vite/src/organization-list/index.tsx @@ -1,4 +1,4 @@ -import { OrganizationList } from '@clerk/clerk-react'; +import { OrganizationList } from '@clerk/react'; export default function Page() { return ( diff --git a/integration/templates/react-vite/src/organization-profile/index.tsx b/integration/templates/react-vite/src/organization-profile/index.tsx index 144b8b1a537..183d8a6bd7e 100644 --- a/integration/templates/react-vite/src/organization-profile/index.tsx +++ b/integration/templates/react-vite/src/organization-profile/index.tsx @@ -1,4 +1,4 @@ -import { OrganizationProfile } from '@clerk/clerk-react'; +import { OrganizationProfile } from '@clerk/react'; export default function Page() { return ( diff --git a/integration/templates/react-vite/src/organization-switcher/index.tsx b/integration/templates/react-vite/src/organization-switcher/index.tsx index cce7878d001..c929ea46de9 100644 --- a/integration/templates/react-vite/src/organization-switcher/index.tsx +++ b/integration/templates/react-vite/src/organization-switcher/index.tsx @@ -1,4 +1,4 @@ -import { OrganizationSwitcher } from '@clerk/clerk-react'; +import { OrganizationSwitcher } from '@clerk/react'; export default function Page() { return ( diff --git a/integration/templates/react-vite/src/protected/index.tsx b/integration/templates/react-vite/src/protected/index.tsx index 3130475df2a..2eb58aa8d76 100644 --- a/integration/templates/react-vite/src/protected/index.tsx +++ b/integration/templates/react-vite/src/protected/index.tsx @@ -1,4 +1,4 @@ -import { SignedIn } from '@clerk/clerk-react'; +import { SignedIn } from '@clerk/react'; export default function Page() { return ( diff --git a/integration/templates/react-vite/src/sign-in/index.tsx b/integration/templates/react-vite/src/sign-in/index.tsx index 7ec25930367..b1e0d12a2e3 100644 --- a/integration/templates/react-vite/src/sign-in/index.tsx +++ b/integration/templates/react-vite/src/sign-in/index.tsx @@ -1,4 +1,4 @@ -import { SignIn } from '@clerk/clerk-react'; +import { SignIn } from '@clerk/react'; export default function Page() { return ( diff --git a/integration/templates/react-vite/src/sign-up/index.tsx b/integration/templates/react-vite/src/sign-up/index.tsx index fa00b90a68a..1aa91997e56 100644 --- a/integration/templates/react-vite/src/sign-up/index.tsx +++ b/integration/templates/react-vite/src/sign-up/index.tsx @@ -1,4 +1,4 @@ -import { SignUp } from '@clerk/clerk-react'; +import { SignUp } from '@clerk/react'; export default function Page() { return ( diff --git a/integration/templates/react-vite/src/user-avatar/index.tsx b/integration/templates/react-vite/src/user-avatar/index.tsx index d608db004a8..dbbbad32fa0 100644 --- a/integration/templates/react-vite/src/user-avatar/index.tsx +++ b/integration/templates/react-vite/src/user-avatar/index.tsx @@ -1,4 +1,4 @@ -import { UserAvatar } from '@clerk/clerk-react'; +import { UserAvatar } from '@clerk/react'; import React from 'react'; export default function UserAvatarPage() { diff --git a/integration/templates/react-vite/src/user-button/index.tsx b/integration/templates/react-vite/src/user-button/index.tsx index a8c6df3a105..1d17595c78e 100644 --- a/integration/templates/react-vite/src/user-button/index.tsx +++ b/integration/templates/react-vite/src/user-button/index.tsx @@ -1,4 +1,4 @@ -import { UserButton } from '@clerk/clerk-react'; +import { UserButton } from '@clerk/react'; export default function Page() { return ( diff --git a/integration/templates/react-vite/src/user/index.tsx b/integration/templates/react-vite/src/user/index.tsx index ca6b2c770f4..d39e4a07d2c 100644 --- a/integration/templates/react-vite/src/user/index.tsx +++ b/integration/templates/react-vite/src/user/index.tsx @@ -1,4 +1,4 @@ -import { UserProfile } from '@clerk/clerk-react'; +import { UserProfile } from '@clerk/react'; export default function Page() { return ( diff --git a/integration/templates/react-vite/src/waitlist/index.tsx b/integration/templates/react-vite/src/waitlist/index.tsx index effbf8a5a49..53b82d06d4b 100644 --- a/integration/templates/react-vite/src/waitlist/index.tsx +++ b/integration/templates/react-vite/src/waitlist/index.tsx @@ -1,4 +1,4 @@ -import { Waitlist } from '@clerk/clerk-react'; +import { Waitlist } from '@clerk/react'; export default function Page() { return ( diff --git a/integration/templates/tanstack-react-start/README.md b/integration/templates/tanstack-react-start/README.md index e76cb6abd8a..ede31f2551c 100644 --- a/integration/templates/tanstack-react-start/README.md +++ b/integration/templates/tanstack-react-start/README.md @@ -11,8 +11,8 @@

Clerk and TanStack Start Quickstart

- - Downloads + + Downloads Discord diff --git a/integration/templates/vue-vite/src/main.ts b/integration/templates/vue-vite/src/main.ts index 375584714b4..d8980dd44a8 100644 --- a/integration/templates/vue-vite/src/main.ts +++ b/integration/templates/vue-vite/src/main.ts @@ -8,6 +8,7 @@ const app = createApp(App); app.use(clerkPlugin, { publishableKey: import.meta.env.VITE_CLERK_PUBLISHABLE_KEY, clerkJSUrl: import.meta.env.VITE_CLERK_JS_URL, + clerkUiUrl: import.meta.env.VITE_CLERK_UI_URL, clerkJSVersion: import.meta.env.VITE_CLERK_JS_VERSION, }); app.use(router); diff --git a/integration/tests/appearance.test.ts b/integration/tests/appearance.test.ts index 589d6eff679..73192089912 100644 --- a/integration/tests/appearance.test.ts +++ b/integration/tests/appearance.test.ts @@ -14,7 +14,7 @@ test.describe('appearance prop', () => { .addFile( 'src/App.tsx', ({ tsx }) => tsx` - import { SignIn, SignUp } from '@clerk/clerk-react'; + import { SignIn, SignUp } from '@clerk/react'; import { dark, neobrutalism, shadesOfPurple } from '@clerk/themes'; const themes = { shadesOfPurple, neobrutalism, dark }; diff --git a/integration/tests/elements/next-sign-in.test.ts b/integration/tests/elements/next-sign-in.test.ts deleted file mode 100644 index 6534f28d3e5..00000000000 --- a/integration/tests/elements/next-sign-in.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { expect, test } from '@playwright/test'; - -import { appConfigs } from '../../presets'; -import type { FakeUser } from '../../testUtils'; -import { createTestUtils, testAgainstRunningApps } from '../../testUtils'; - -testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js Sign-In Flow @elements', ({ app }) => { - test.describe.configure({ mode: 'serial' }); - - let fakeUser: FakeUser; - - test.beforeAll(async () => { - const u = createTestUtils({ app }); - fakeUser = u.services.users.createFakeUser({ - fictionalEmail: true, - withPhoneNumber: true, - withUsername: true, - }); - await u.services.users.createBapiUser(fakeUser); - }); - - test.afterAll(async () => { - await fakeUser.deleteIfExists(); - await app.teardown(); - }); - - test.afterEach(async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.page.signOut(); - await u.page.context().clearCookies(); - }); - - test('sign in with email and password', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.goTo({ headlessSelector: '[data-test-id="sign-in-step-start"]' }); - - await u.po.signIn.setIdentifier(fakeUser.email); - await u.po.signIn.continue(); - await u.page.waitForAppUrl('/sign-in/continue'); - await u.po.signIn.setPassword(fakeUser.password); - await u.po.signIn.continue(); - - await u.po.expect.toBeSignedIn(); - }); - - test('sign in with email and instant password', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.goTo({ headlessSelector: '[data-test-id="sign-in-step-start"]' }); - - await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); - - await u.po.expect.toBeSignedIn(); - }); - - test('does not allow arbitrary redirect URLs on sign in', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.goTo({ - searchParams: new URLSearchParams({ redirect_url: 'https://evil.com' }), - headlessSelector: '[data-test-id="sign-in-step-start"]', - }); - - await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); - - expect(u.page.url()).not.toContain('https://evil.com'); - - await u.po.expect.toBeSignedIn(); - }); - - test('sign in with email code', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.goTo({ headlessSelector: '[data-test-id="sign-in-step-start"]' }); - - await u.po.signIn.setIdentifier(fakeUser.email); - await u.po.signIn.continue(); - - await u.page.getByRole('button', { name: /use another method/i }).click(); - await u.po.signIn.getAltMethodsEmailCodeButton().click(); - await page.getByRole('textbox', { name: 'Enter email verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signIn.continue(); - - await u.page.waitForAppUrl('/'); - await u.po.expect.toBeSignedIn(); - }); - - test('sign in with phone number and password', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.goTo({ headlessSelector: '[data-test-id="sign-in-step-start"]' }); - - await u.page.getByRole('button', { name: /^use phone/i }).click(); - await u.po.signIn.getIdentifierInput().fill(fakeUser.phoneNumber); - await u.po.signIn.continue(); - await u.page.waitForAppUrl('/sign-in/continue'); - await u.po.signIn.setPassword(fakeUser.password); - await u.po.signIn.continue(); - - await u.po.expect.toBeSignedIn(); - }); - - test('sign in only with phone number', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - const fakeUserWithoutPassword = u.services.users.createFakeUser({ - fictionalEmail: true, - withPassword: false, - withPhoneNumber: true, - }); - await u.services.users.createBapiUser(fakeUserWithoutPassword); - await u.po.signIn.goTo({ headlessSelector: '[data-test-id="sign-in-step-start"]' }); - await u.page.getByRole('button', { name: /^use phone/i }).click(); - await u.po.signIn.getIdentifierInput().fill(fakeUserWithoutPassword.phoneNumber); - await u.po.signIn.continue(); - await page.getByRole('textbox', { name: 'Enter phone verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signIn.continue(); - - await u.po.expect.toBeSignedIn(); - - await fakeUserWithoutPassword.deleteIfExists(); - }); - - test('sign in with username and password', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.goTo({ headlessSelector: '[data-test-id="sign-in-step-start"]' }); - - await u.po.signIn.getIdentifierInput().fill(fakeUser.username); - await u.po.signIn.continue(); - await u.page.waitForAppUrl('/sign-in/continue'); - await u.po.signIn.setPassword(fakeUser.password); - await u.po.signIn.continue(); - - await u.po.expect.toBeSignedIn(); - }); - - test('can reset password', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - const fakeUserWithPasword = u.services.users.createFakeUser({ - fictionalEmail: true, - withPassword: true, - }); - await u.services.users.createBapiUser(fakeUserWithPasword); - - await u.po.signIn.goTo({ headlessSelector: '[data-test-id="sign-in-step-start"]' }); - - await u.po.signIn.getIdentifierInput().fill(fakeUserWithPasword.email); - await u.po.signIn.continue(); - await u.page.getByRole('button', { name: /^forgot password/i }).click(); - await u.po.signIn.getResetPassword().click(); - await page.getByRole('textbox', { name: 'Enter email verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signIn.continue(); - - await u.po.signIn.setPassword(`${fakeUserWithPasword.password}_reset`); - await u.po.signIn.setPasswordConfirmation(`${fakeUserWithPasword.password}_reset`); - await u.po.signIn.getResetPassword().click(); - await u.po.expect.toBeSignedIn(); - - await fakeUserWithPasword.deleteIfExists(); - }); - - test('cannot sign in with wrong password', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - - await u.po.signIn.goTo({ headlessSelector: '[data-test-id="sign-in-step-start"]' }); - await u.po.signIn.getIdentifierInput().fill(fakeUser.email); - await u.po.signIn.continue(); - await u.page.waitForAppUrl('/sign-in/continue'); - await u.po.signIn.setPassword('wrong-password'); - await u.po.signIn.continue(); - await expect(u.page.getByText(/^password is incorrect/i)).toBeVisible(); - - await u.po.expect.toBeSignedOut(); - }); - - test('cannot sign in with wrong password but can sign in with email', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - - await u.po.signIn.goTo({ headlessSelector: '[data-test-id="sign-in-step-start"]' }); - await u.po.signIn.getIdentifierInput().fill(fakeUser.email); - await u.po.signIn.continue(); - await u.page.waitForAppUrl('/sign-in/continue'); - await u.po.signIn.setPassword('wrong-password'); - await u.po.signIn.continue(); - - await expect(u.page.getByText(/^password is incorrect/i)).toBeVisible(); - - await u.page.getByRole('button', { name: /use another method/i }).click(); - await u.po.signIn.getAltMethodsEmailCodeButton().click(); - await page.getByRole('textbox', { name: 'Enter email verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signIn.continue(); - - await u.po.expect.toBeSignedIn(); - }); -}); diff --git a/integration/tests/elements/next-sign-up.test.ts b/integration/tests/elements/next-sign-up.test.ts deleted file mode 100644 index 70f7d42dd25..00000000000 --- a/integration/tests/elements/next-sign-up.test.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { expect, test } from '@playwright/test'; - -import { appConfigs } from '../../presets'; -import { createTestUtils, testAgainstRunningApps } from '../../testUtils'; - -testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Next.js Sign-Up Flow @elements', ({ app }) => { - test.describe.configure({ mode: 'serial' }); - - test.afterAll(async () => { - await app.teardown(); - }); - - test('sign up with email and password', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - const fakeUser = u.services.users.createFakeUser({ - fictionalEmail: true, - withPhoneNumber: true, - withUsername: true, - }); - - await u.po.signUp.goTo({ headlessSelector: '[data-test-id="sign-up-step-start"]' }); - - await u.po.signUp.signUpWithEmailAndPassword({ - email: fakeUser.email, - password: fakeUser.password, - }); - - await page.getByRole('textbox', { name: 'Enter email verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signUp.continue(); - - await u.page.waitForAppUrl('/'); - await u.po.expect.toBeSignedIn(); - - await fakeUser.deleteIfExists(); - }); - - test('does not allow arbitrary redirect URLs on sign up', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - const fakeUser = u.services.users.createFakeUser({ - fictionalEmail: true, - withPhoneNumber: true, - withUsername: true, - }); - - await u.po.signUp.goTo({ - searchParams: new URLSearchParams({ redirect_url: 'https://evil.com' }), - headlessSelector: '[data-test-id="sign-up-step-start"]', - }); - - await u.po.signUp.signUpWithEmailAndPassword({ - email: fakeUser.email, - password: fakeUser.password, - }); - - await page.getByRole('textbox', { name: 'Enter email verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signUp.continue(); - - await u.page.waitForAppUrl('/'); - await u.po.expect.toBeSignedIn(); - - await fakeUser.deleteIfExists(); - }); - - test("can't sign up with weak password", async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - const fakeUser = u.services.users.createFakeUser({ - fictionalEmail: true, - withPhoneNumber: true, - withUsername: true, - }); - - await u.po.signUp.goTo({ headlessSelector: '[data-test-id="sign-up-step-start"]' }); - - await u.po.signUp.signUpWithEmailAndPassword({ - email: fakeUser.email, - password: '12345', - }); - - // Check if password error is visible - await expect(u.page.getByText(/Passwords must be \d+ characters or more/i)).toBeVisible(); - - await u.po.expect.toBeSignedOut(); - - await fakeUser.deleteIfExists(); - }); - - test('can sign up with phone number', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - const fakeUser = u.services.users.createFakeUser({ - fictionalEmail: true, - withPhoneNumber: true, - withUsername: true, - }); - - await u.po.signUp.goTo({ headlessSelector: '[data-test-id="sign-up-step-start"]' }); - - await u.po.signUp.signUp({ - email: fakeUser.email, - phoneNumber: fakeUser.phoneNumber, - password: fakeUser.password, - }); - - await page.getByRole('textbox', { name: 'Enter phone verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signUp.continue(); - - await page.getByRole('textbox', { name: 'Enter email verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signUp.continue(); - - await u.po.expect.toBeSignedIn(); - await fakeUser.deleteIfExists(); - }); - - test('sign up with first name, last name, email, phone and password', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - const fakeUser = u.services.users.createFakeUser({ - fictionalEmail: true, - withPhoneNumber: true, - withUsername: true, - }); - - await u.po.signUp.goTo({ headlessSelector: '[data-test-id="sign-up-step-start"]' }); - - await u.po.signUp.signUp({ - username: fakeUser.username, - email: fakeUser.email, - phoneNumber: fakeUser.phoneNumber, - password: fakeUser.password, - }); - - await page.getByRole('textbox', { name: 'Enter phone verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signUp.continue(); - - await page.getByRole('textbox', { name: 'Enter email verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signUp.continue(); - - await u.po.expect.toBeSignedIn(); - - await fakeUser.deleteIfExists(); - }); - - test('sign up, sign out and sign in again', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - const fakeUser = u.services.users.createFakeUser({ - fictionalEmail: true, - withPhoneNumber: true, - withUsername: true, - }); - - await u.po.signUp.goTo({ headlessSelector: '[data-test-id="sign-up-step-start"]' }); - - await u.po.signUp.signUp({ - username: fakeUser.username, - email: fakeUser.email, - phoneNumber: fakeUser.phoneNumber, - password: fakeUser.password, - }); - - await page.getByRole('textbox', { name: 'Enter phone verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signUp.continue(); - - await page.getByRole('textbox', { name: 'Enter email verification code' }).click(); - await page.keyboard.type('424242', { delay: 100 }); - await u.po.signUp.continue(); - - await u.po.expect.toBeSignedIn(); - - await u.page.evaluate(async () => { - await window.Clerk.signOut(); - }); - - await u.po.expect.toBeSignedOut(); - - await u.po.signIn.goTo({ headlessSelector: '[data-test-id="sign-in-step-start"]' }); - - await u.po.signIn.setIdentifier(fakeUser.email); - await u.po.signIn.continue(); - await u.page.waitForAppUrl('/sign-in/continue'); - await u.po.signIn.setPassword(fakeUser.password); - await u.po.signIn.continue(); - - await u.po.expect.toBeSignedIn(); - - await fakeUser.deleteIfExists(); - }); -}); diff --git a/integration/tests/elements/otp.test.ts b/integration/tests/elements/otp.test.ts deleted file mode 100644 index 59f63f3414f..00000000000 --- a/integration/tests/elements/otp.test.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { expect, test } from '@playwright/test'; - -import { appConfigs } from '../../presets'; -import { createTestUtils, testAgainstRunningApps } from '../../testUtils'; - -testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('OTP @elements', ({ app }) => { - test.describe.configure({ mode: 'parallel' }); - - test.afterAll(async () => { - await app.teardown(); - }); - - test.beforeEach(async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.page.goToRelative('/otp'); - }); - - const otpTypes = { - simpleOtp: 'simple-otp', - segmentedOtp: 'segmented-otp', - segmentedOtpWithProps: 'segmented-otp-with-props', - } as const; - - for (const otpType of [otpTypes.simpleOtp, otpTypes.segmentedOtp]) { - test.describe(`Type: ${otpType}`, () => { - test(`should receive correct standard props`, async ({ page }) => { - const otp = page.getByTestId(otpType); - - await expect(otp).toHaveAttribute('autocomplete', 'one-time-code'); - await expect(otp).toHaveAttribute('spellcheck', 'false'); - await expect(otp).toHaveAttribute('inputmode', 'numeric'); - await expect(otp).toHaveAttribute('maxlength', '6'); - await expect(otp).toHaveAttribute('minlength', '6'); - await expect(otp).toHaveAttribute('pattern', '[0-9]{6}'); - await expect(otp).toHaveAttribute('type', 'text'); - }); - - test(`should change the input value`, async ({ page }) => { - const otp = page.getByTestId(otpType); - - // Check that the input starts with an empty value - await expect(otp).toHaveValue(''); - - await otp.pressSequentially('1'); - await expect(otp).toHaveValue('1'); - - await otp.pressSequentially('23456'); - await expect(otp).toHaveValue('123456'); - }); - }); - } - - test.describe(`Type: ${otpTypes.simpleOtp}`, () => { - test(`should prevent typing greater than max length`, async ({ page }) => { - const otp = page.getByTestId(otpTypes.simpleOtp); - - await otp.pressSequentially('1234567'); - await expect(otp).toHaveValue('123456'); - }); - }); - - test.describe(`Type: ${otpTypes.segmentedOtp}`, () => { - test('renders hidden segments', async ({ page }) => { - const otpSegmentsWrapper = page.locator('.segmented-otp-wrapper'); - - await expect(otpSegmentsWrapper).toHaveAttribute('aria-hidden', 'true'); - // Check that 6 segments are rendered - await expect(otpSegmentsWrapper.locator('> div')).toHaveCount(6); - }); - - test(`should prevent typing greater than max length`, async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtp); - - await otp.pressSequentially('1234567'); - // With the segmented OTP we expect the last char to be replaced by any new input - await expect(otp).toHaveValue('123457'); - }); - - test(`should put values into segments`, async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtp); - - // Check initial state before any interaction - for (let i = 0; i < 6; i++) { - await expect(page.getByTestId(`segmented-otp-${i}`)).toHaveText(''); - await expect(page.getByTestId(`segmented-otp-${i}`)).toHaveAttribute('data-status', 'none'); - } - - await otp.pressSequentially('123456'); - - for (let i = 0; i < 6; i++) { - await expect(page.getByTestId(`segmented-otp-${i}`)).toHaveText(`${i + 1}`); - } - }); - - test('should set hover status on segments', async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtp); - - await otp.hover(); - for (let i = 0; i < 6; i++) { - await expect(page.getByTestId(`segmented-otp-${i}`)).toHaveAttribute('data-status', 'hovered'); - } - }); - - test('should not set hover status on segments if they are focused', async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtp); - - await otp.pressSequentially('123'); - await otp.hover(); - for (let i = 0; i < 6; i++) { - await expect(page.getByTestId(`segmented-otp-${i}`)).not.toHaveAttribute('data-status', 'hovered'); - } - }); - - test('should set cursor and selected status on segments', async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtp); - - await otp.pressSequentially('12'); - - await expect(page.getByTestId('segmented-otp-0')).toHaveAttribute('data-status', 'none'); - await expect(page.getByTestId('segmented-otp-1')).toHaveAttribute('data-status', 'none'); - await expect(page.getByTestId('segmented-otp-2')).toHaveAttribute('data-status', 'cursor'); - - await otp.press('ArrowLeft'); - - await expect(page.getByTestId('segmented-otp-0')).toHaveAttribute('data-status', 'none'); - await expect(page.getByTestId('segmented-otp-1')).toHaveAttribute('data-status', 'selected'); - await expect(page.getByTestId('segmented-otp-2')).toHaveAttribute('data-status', 'none'); - - await otp.press('ArrowLeft'); - - await expect(page.getByTestId('segmented-otp-0')).toHaveAttribute('data-status', 'selected'); - await expect(page.getByTestId('segmented-otp-1')).toHaveAttribute('data-status', 'none'); - }); - - test('should replace selected segment with new input', async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtp); - - await otp.pressSequentially('12'); - - await otp.press('ArrowLeft'); - await otp.pressSequentially('1'); - await expect(otp).toHaveValue('11'); - }); - - test('should replace multi-selected segments with new input', async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtp); - - await otp.pressSequentially('12345'); - // Mark two segments to the left of the cursor - await otp.press('Shift+ArrowLeft'); - await otp.press('Shift+ArrowLeft'); - await expect(page.getByTestId('segmented-otp-3')).toHaveAttribute('data-status', 'selected'); - await expect(page.getByTestId('segmented-otp-4')).toHaveAttribute('data-status', 'selected'); - await otp.pressSequentially('1'); - - await expect(otp).toHaveValue('1231'); - - // Mark all segments - await otp.press('ControlOrMeta+a'); - await otp.pressSequentially('1'); - - await expect(otp).toHaveValue('1'); - }); - - test('should backspace char', async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtp); - - await otp.pressSequentially('123'); - await otp.press('Backspace'); - - await expect(otp).toHaveValue('12'); - await expect(page.getByTestId('segmented-otp-2')).toHaveAttribute('data-status', 'cursor'); - }); - - test('should backspace all chars with modifier', async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtp); - - await otp.pressSequentially('123'); - await otp.press('ControlOrMeta+Backspace'); - - await expect(otp).toHaveValue(''); - await expect(page.getByTestId('segmented-otp-0')).toHaveAttribute('data-status', 'cursor'); - }); - - test('should backspace selected char', async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtp); - - await otp.pressSequentially('123'); - await otp.press('ArrowLeft'); - await otp.press('ArrowLeft'); - await otp.press('Backspace'); - - await expect(otp).toHaveValue('13'); - }); - - test('should forward-delete char when pressing delete', async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtp); - - await otp.pressSequentially('1234'); - - await otp.press('ArrowLeft'); - await otp.press('ArrowLeft'); - await otp.press('Delete'); - - await expect(otp).toHaveValue('124'); - await otp.press('ArrowRight'); - await otp.press('Delete'); - await expect(otp).toHaveValue('12'); - }); - }); - - test.describe('Custom props', () => { - test('length', async ({ page }) => { - const otp = page.getByTestId(otpTypes.segmentedOtpWithProps); - const otpSegmentsWrapper = page.locator('.segmented-otp-with-props-wrapper'); - - await expect(otp).toHaveAttribute('maxlength', '4'); - await expect(otp).toHaveAttribute('minlength', '4'); - await expect(otp).toHaveAttribute('pattern', '[0-9]{4}'); - - // Check that only 4 segments are rendered - await expect(otpSegmentsWrapper.locator('> div')).toHaveCount(4); - }); - }); -}); diff --git a/integration/tests/elements/validate-password.test.ts b/integration/tests/elements/validate-password.test.ts deleted file mode 100644 index a79999d4d64..00000000000 --- a/integration/tests/elements/validate-password.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { expect, test } from '@playwright/test'; - -import { appConfigs } from '../../presets'; -import { createTestUtils, testAgainstRunningApps } from '../../testUtils'; - -testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('Password Validation @elements', ({ app }) => { - test.describe.configure({ mode: 'parallel' }); - - test.afterAll(async () => { - await app.teardown(); - }); - - test.beforeEach(async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.page.goToRelative('/validate-password'); - }); - - test('should have initial "idle" state', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - - await expect(u.po.signIn.getPasswordInput()).toHaveAttribute('data-state', 'idle'); - await expect(page.getByTestId('state')).toHaveText('idle'); - }); - - test('should change state to "info" on focus', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.getPasswordInput().focus(); - - await expect(page.getByTestId('state')).toHaveText('info'); - }); - - test('should return codes and message with non-idle state', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.getPasswordInput().focus(); - - await expect(page.getByTestId('codes')).toHaveText(/min_length/); - await expect(page.getByTestId('message')).toHaveText('Your password must contain 8 or more characters.'); - }); - - test('should return error when requirements are not met', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.setPassword('12345678'); - - await expect(page.getByTestId('state')).toHaveText('error'); - await expect(page.getByTestId('codes')).toHaveText(/require_special_char/); - await expect(page.getByTestId('message')).toHaveText('Your password must contain a special character.'); - }); - - test('should return success when requirements are met', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - await u.po.signIn.setPassword('12345678@'); - - await expect(page.getByTestId('state')).toHaveText('success'); - await expect(page.getByTestId('codes')).toHaveText(''); - await expect(page.getByTestId('message')).toHaveText('Your password meets all the necessary requirements.'); - }); - - test('should have working flow', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - - await expect(page.getByTestId('state')).toHaveText('idle'); - await u.po.signIn.setPassword('123'); - await expect(page.getByTestId('state')).toHaveText('info'); - await u.po.signIn.setPassword('12345678'); - await expect(page.getByTestId('state')).toHaveText('error'); - await u.po.signIn.setPassword('12345678@'); - await expect(page.getByTestId('state')).toHaveText('success'); - }); -}); diff --git a/integration/tests/global.setup.ts b/integration/tests/global.setup.ts index 52a373ebe71..377477b4979 100644 --- a/integration/tests/global.setup.ts +++ b/integration/tests/global.setup.ts @@ -2,7 +2,7 @@ import { test as setup } from '@playwright/test'; import { constants } from '../constants'; import { appConfigs } from '../presets'; -import { fs, parseEnvOptions, startClerkJsHttpServer } from '../scripts'; +import { fs, parseEnvOptions, startClerkJsHttpServer, startClerkUiHttpServer } from '../scripts'; setup('start long running apps', async () => { setup.setTimeout(90_000); @@ -10,6 +10,7 @@ setup('start long running apps', async () => { await fs.ensureDir(constants.TMP_DIR); await startClerkJsHttpServer(); + await startClerkUiHttpServer(); const { appIds } = parseEnvOptions(); if (appIds.length) { diff --git a/integration/tests/global.teardown.ts b/integration/tests/global.teardown.ts index e9aee7c0f26..00e9296dca5 100644 --- a/integration/tests/global.teardown.ts +++ b/integration/tests/global.teardown.ts @@ -3,13 +3,14 @@ import { test as setup } from '@playwright/test'; import { constants } from '../constants'; import { stateFile } from '../models/stateFile'; import { appConfigs } from '../presets'; -import { killClerkJsHttpServer, parseEnvOptions } from '../scripts'; +import { killClerkJsHttpServer, killClerkUiHttpServer, parseEnvOptions } from '../scripts'; setup('teardown long running apps', async () => { setup.setTimeout(90_000); const { appUrl } = parseEnvOptions(); await killClerkJsHttpServer(); + await killClerkUiHttpServer(); if (appUrl || !constants.CLEANUP) { // if appUrl is provided, it means that the user is running an app manually diff --git a/integration/tests/last-authentication-strategy.test.ts b/integration/tests/last-authentication-strategy.test.ts index 09449c0fde8..194ca1c6a82 100644 --- a/integration/tests/last-authentication-strategy.test.ts +++ b/integration/tests/last-authentication-strategy.test.ts @@ -1,7 +1,7 @@ +import type { LastAuthenticationStrategy } from '@clerk/shared/types'; import type { Page } from '@playwright/test'; import { expect, test } from '@playwright/test'; -import type { LastAuthenticationStrategy } from '../../packages/types'; import { appConfigs } from '../presets'; import { createTestUtils, testAgainstRunningApps } from '../testUtils'; diff --git a/integration/tests/nuxt/basic.test.ts b/integration/tests/nuxt/basic.test.ts index 12bf61f114d..5f48239e37e 100644 --- a/integration/tests/nuxt/basic.test.ts +++ b/integration/tests/nuxt/basic.test.ts @@ -30,21 +30,6 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te await u.page.context().clearCookies(); }); - test('sign in with hash routing', async ({ page, context }) => { - const u = createTestUtils({ app, page, context }); - - await u.page.goToRelative('/sign-in'); - await u.po.signIn.waitForMounted(); - - await u.po.signIn.setIdentifier(fakeUser.email); - await u.po.signIn.continue(); - await u.page.waitForURL(`${app.serverUrl}/sign-in#/factor-one`); - - await u.po.signIn.setPassword(fakeUser.password); - await u.po.signIn.continue(); - await u.po.expect.toBeSignedIn(); - }); - test('render user profile with SSR data', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); @@ -54,7 +39,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te await u.po.expect.toBeSignedIn(); await u.po.userButton.waitForMounted(); - await u.page.goToRelative('/user'); + await u.page.goToRelative('/user-profile'); await u.po.userProfile.waitForMounted(); // Fetched from an API endpoint (/api/me), which is server-rendered. @@ -66,7 +51,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te test('redirects to sign-in when unauthenticated', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); - await u.page.goToRelative('/user'); + await u.page.goToRelative('/user-profile'); await u.page.waitForURL(`${app.serverUrl}/sign-in`); await u.po.signIn.waitForMounted(); }); diff --git a/integration/tests/nuxt/navigation.test.ts b/integration/tests/nuxt/navigation.test.ts new file mode 100644 index 00000000000..de872e9e8e0 --- /dev/null +++ b/integration/tests/nuxt/navigation.test.ts @@ -0,0 +1,76 @@ +import { expect, test } from '@playwright/test'; + +import { appConfigs } from '../../presets'; +import type { FakeUser } from '../../testUtils'; +import { createTestUtils, testAgainstRunningApps } from '../../testUtils'; + +testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('navigation modes @nuxt', ({ app }) => { + test.describe.configure({ mode: 'serial' }); + let fakeUser: FakeUser; + + test.beforeAll(async () => { + const m = createTestUtils({ app }); + fakeUser = m.services.users.createFakeUser(); + await m.services.users.createBapiUser(fakeUser); + }); + + test.afterAll(async () => { + await fakeUser.deleteIfExists(); + await app.teardown(); + }); + + test('sign in with path routing', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.po.signIn.goTo(); + await u.po.signIn.waitForMounted(); + + await u.po.signIn.setIdentifier(fakeUser.email); + await u.po.signIn.continue(); + await u.page.waitForURL(`${app.serverUrl}/sign-in/factor-one`); + + await u.po.signIn.setPassword(fakeUser.password); + await u.po.signIn.continue(); + + await u.po.expect.toBeSignedIn(); + }); + + test('sign in with hash routing', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + await u.page.goToRelative('/hash/sign-in'); + await u.po.signIn.waitForMounted(); + + await u.po.signIn.setIdentifier(fakeUser.email); + await u.po.signIn.continue(); + await u.page.waitForURL(`${app.serverUrl}/hash/sign-in#/factor-one`); + + await u.po.signIn.setPassword(fakeUser.password); + await u.po.signIn.continue(); + await u.po.expect.toBeSignedIn(); + }); + + test('sign in with path routing navigates to previous page', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.po.signIn.goTo(); + await u.po.signIn.waitForMounted(); + + await u.po.signIn.getGoToSignUp().click(); + await u.po.signUp.waitForMounted(); + await u.page.waitForURL(`${app.serverUrl}/sign-up`); + + await page.goBack(); + await u.po.signIn.waitForMounted(); + await u.page.waitForURL(`${app.serverUrl}/sign-in`); + }); + + test('user profile uses path routing', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.po.signIn.goTo(); + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + await u.po.expect.toBeSignedIn(); + + await u.page.goToRelative('/user-profile/security'); + await expect(u.page.locator('.cl-headerTitle').filter({ hasText: 'Security' })).toBeVisible(); + }); +}); diff --git a/integration/types.d.ts b/integration/types.d.ts index 8df81fba45b..241f1637063 100644 --- a/integration/types.d.ts +++ b/integration/types.d.ts @@ -1,4 +1,4 @@ -import type { Clerk } from '@clerk/types'; +import type { Clerk } from '@clerk/shared/types'; declare global { interface Window { diff --git a/package.json b/package.json index 9d09bb226ed..bd269d632a0 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,12 @@ "license": "MIT", "scripts": { "build": "FORCE_COLOR=1 turbo build --concurrency=${TURBO_CONCURRENCY:-80%}", - "build:declarations": "FORCE_COLOR=1 turbo build:declarations --concurrency=${TURBO_CONCURRENCY:-80%} --filter=@clerk/nextjs --filter=@clerk/clerk-react --filter=@clerk/shared --filter=@clerk/types", + "build:declarations": "FORCE_COLOR=1 turbo build:declarations --concurrency=${TURBO_CONCURRENCY:-80%} --filter=@clerk/nextjs --filter=@clerk/react --filter=@clerk/shared", "bundlewatch": "turbo bundlewatch", "changeset": "changeset", "changeset:empty": "pnpm changeset --empty", "clean": "turbo clean", - "dev": "TURBO_UI=0 FORCE_COLOR=1 turbo dev --filter=@clerk/* --filter=!@clerk/clerk-expo --filter=!@clerk/tanstack-react-start --filter=!@clerk/elements --filter=!@clerk/remix --filter=!@clerk/chrome-extension", + "dev": "TURBO_UI=0 FORCE_COLOR=1 turbo dev --filter=@clerk/* --filter=!@clerk/expo --filter=!@clerk/tanstack-react-start --filter=!@clerk/chrome-extension", "dev:js": "TURBO_UI=0 FORCE_COLOR=1 turbo dev:current --filter=@clerk/clerk-js", "format": "turbo format && node scripts/format-non-workspace.mjs", "format:check": "turbo format:check && node scripts/format-non-workspace.mjs --check", @@ -26,6 +26,7 @@ "prepare": "husky install", "release": "changeset publish && git push --follow-tags", "release:canary": "changeset publish --tag canary --no-git-tag", + "release:canary-core3": "changeset publish --tag canary-core3 --no-git-tag", "release:snapshot": "changeset publish --tag snapshot --no-git-tag", "release:verdaccio": "if [ \"$(npm config get registry)\" = \"https://registry.npmjs.org/\" ]; then echo 'Error: Using default registry' && exit 1; else TURBO_CONCURRENCY=1 pnpm build && changeset publish --no-git-tag; fi", "test": "FORCE_COLOR=1 turbo test --concurrency=${TURBO_CONCURRENCY:-80%}", @@ -37,8 +38,7 @@ "test:integration:cleanup": "pnpm playwright test --config integration/playwright.cleanup.config.ts", "test:integration:custom": "pnpm test:integration:base --grep @custom", "test:integration:deployment:nextjs": "pnpm playwright test --config integration/playwright.deployments.config.ts", - "test:integration:elements": "E2E_APP_ID=elements.* pnpm test:integration:base --grep @elements", - "test:integration:expo-web": "E2E_APP_ID=expo.expo-web pnpm test:integration:base --grep @expo-web", + "test:integration:expo-web:disabled": "E2E_APP_ID=expo.expo-web pnpm test:integration:base --grep @expo-web", "test:integration:express": "E2E_APP_ID=express.* pnpm test:integration:base --grep @express", "test:integration:generic": "E2E_APP_ID=react.vite.*,next.appRouter.withEmailCodes* pnpm test:integration:base --grep @generic", "test:integration:handshake": "DISABLE_WEB_SECURITY=true E2E_APP_1_ENV_KEY=sessions-prod-1 E2E_SESSIONS_APP_1_HOST=multiple-apps-e2e.clerk.app pnpm test:integration:base --grep @handshake", @@ -55,10 +55,11 @@ "test:integration:vue": "E2E_APP_ID=vue.vite pnpm test:integration:base --grep @vue", "test:typedoc": "pnpm typedoc:generate && cd ./.typedoc && vitest run", "turbo:clean": "turbo daemon clean", - "typedoc:generate": "pnpm build:declarations && pnpm typedoc:generate:skip-build", + "typedoc:generate": "pnpm build && pnpm typedoc:generate:skip-build", "typedoc:generate:skip-build": "typedoc --tsconfig tsconfig.typedoc.json && node .typedoc/extract-returns-and-params.mjs && rimraf .typedoc/docs && cpy '.typedoc/temp-docs/**' '.typedoc/docs' && rimraf .typedoc/temp-docs", "version-packages": "changeset version && pnpm install --lockfile-only --engine-strict=false", "version-packages:canary": "./scripts/canary.mjs", + "version-packages:canary-core3": "./scripts/canary-core3.mjs", "version-packages:snapshot": "./scripts/snapshot.mjs", "yalc:all": "for d in packages/*/; do echo $d; cd $d; pnpm yalc push --replace --sig; cd '../../'; done" }, @@ -154,7 +155,7 @@ }, "packageManager": "pnpm@10.17.1", "engines": { - "node": ">=18.17.0", + "node": ">=20.9.0", "pnpm": ">=10.17.1" }, "pnpm": { diff --git a/packages/agent-toolkit/package.json b/packages/agent-toolkit/package.json index 92b136c88dd..569405f3ca9 100644 --- a/packages/agent-toolkit/package.json +++ b/packages/agent-toolkit/package.json @@ -49,7 +49,6 @@ "dependencies": { "@clerk/backend": "workspace:^", "@clerk/shared": "workspace:^", - "@clerk/types": "workspace:^", "@modelcontextprotocol/sdk": "1.7.0", "yargs": "17.7.2", "zod": "3.24.2" diff --git a/packages/astro/package.json b/packages/astro/package.json index 07e216f1e15..2cb0404cab9 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -90,7 +90,6 @@ "dependencies": { "@clerk/backend": "workspace:^", "@clerk/shared": "workspace:^", - "@clerk/types": "workspace:^", "nanoid": "5.1.6", "nanostores": "1.0.1" }, @@ -101,7 +100,7 @@ "astro": "^4.15.0 || ^5.0.0" }, "engines": { - "node": ">=18.17.0" + "node": ">=20.9.0" }, "publishConfig": { "access": "public" diff --git a/packages/astro/src/astro-components/control/AuthenticateWithRedirectCallback.astro b/packages/astro/src/astro-components/control/AuthenticateWithRedirectCallback.astro index aef20144140..43032416254 100644 --- a/packages/astro/src/astro-components/control/AuthenticateWithRedirectCallback.astro +++ b/packages/astro/src/astro-components/control/AuthenticateWithRedirectCallback.astro @@ -1,5 +1,5 @@ --- -import type { HandleOAuthCallbackParams } from '@clerk/types'; +import type { HandleOAuthCallbackParams } from '@clerk/shared/types'; type Props = HandleOAuthCallbackParams; diff --git a/packages/astro/src/astro-components/control/ProtectCSR.astro b/packages/astro/src/astro-components/control/ProtectCSR.astro index cee284935c5..e3aa5ca8f3c 100644 --- a/packages/astro/src/astro-components/control/ProtectCSR.astro +++ b/packages/astro/src/astro-components/control/ProtectCSR.astro @@ -44,7 +44,7 @@ const { + \n`; } diff --git a/packages/astro/src/server/clerk-middleware.ts b/packages/astro/src/server/clerk-middleware.ts index 6841e1c40df..b40c656de99 100644 --- a/packages/astro/src/server/clerk-middleware.ts +++ b/packages/astro/src/server/clerk-middleware.ts @@ -18,8 +18,8 @@ import { import { isDevelopmentFromSecretKey } from '@clerk/shared/keys'; import { handleNetlifyCacheInDevInstance } from '@clerk/shared/netlifyCacheHandler'; import { isHttpOrHttps } from '@clerk/shared/proxy'; +import type { PendingSessionOptions } from '@clerk/shared/types'; import { handleValueOrFn } from '@clerk/shared/utils'; -import type { PendingSessionOptions } from '@clerk/types'; import type { APIContext } from 'astro'; import { authAsyncStorage } from '#async-local-storage'; diff --git a/packages/astro/src/server/get-safe-env.ts b/packages/astro/src/server/get-safe-env.ts index c1b21574f8d..7ec1824029b 100644 --- a/packages/astro/src/server/get-safe-env.ts +++ b/packages/astro/src/server/get-safe-env.ts @@ -31,6 +31,7 @@ function getSafeEnv(context: ContextOrLocals) { signInUrl: getContextEnvVar('PUBLIC_CLERK_SIGN_IN_URL', context), signUpUrl: getContextEnvVar('PUBLIC_CLERK_SIGN_UP_URL', context), clerkJsUrl: getContextEnvVar('PUBLIC_CLERK_JS_URL', context), + clerkUiUrl: getContextEnvVar('PUBLIC_CLERK_UI_URL', context), clerkJsVariant: getContextEnvVar('PUBLIC_CLERK_JS_VARIANT', context) as 'headless' | '' | undefined, clerkJsVersion: getContextEnvVar('PUBLIC_CLERK_JS_VERSION', context), apiVersion: getContextEnvVar('CLERK_API_VERSION', context), diff --git a/packages/astro/src/stores/external.ts b/packages/astro/src/stores/external.ts index c5516fd5da1..3903ea9f0fb 100644 --- a/packages/astro/src/stores/external.ts +++ b/packages/astro/src/stores/external.ts @@ -1,6 +1,6 @@ import { deriveState } from '@clerk/shared/deriveState'; import { eventMethodCalled } from '@clerk/shared/telemetry'; -import type { SignedInSessionResource } from '@clerk/types'; +import type { SignedInSessionResource } from '@clerk/shared/types'; import { batched, computed, onMount, type Store } from 'nanostores'; import { $clerk, $csrState, $initialState } from './internal'; @@ -70,7 +70,7 @@ export const $organizationStore = computed([$authStore], auth => auth.organizati * It is a nanostore, for instructions on how to use nanostores please review the [documentation](https://github.com/nanostores/nanostores) * * @example - * $clientStore.subscribe((client) => console.log(client.activeSessions)) + * $clientStore.subscribe((client) => console.log(client?.signedInSessions?.length)) */ export const $clientStore = computed([$csrState], csr => csr.client); diff --git a/packages/astro/src/stores/internal.ts b/packages/astro/src/stores/internal.ts index 4f9a9d50dc4..383cf38e2cc 100644 --- a/packages/astro/src/stores/internal.ts +++ b/packages/astro/src/stores/internal.ts @@ -5,7 +5,7 @@ import type { OrganizationResource, SignedInSessionResource, UserResource, -} from '@clerk/types'; +} from '@clerk/shared/types'; import { atom, map } from 'nanostores'; export const $csrState = map<{ diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 4636b5aa317..707006e66dc 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -5,7 +5,8 @@ import type { MultiDomainAndOrProxyPrimitives, ProtectProps, Without, -} from '@clerk/types'; +} from '@clerk/shared/types'; +import type { ClerkUiConstructor } from '@clerk/shared/ui'; type AstroClerkUpdateOptions = Pick; @@ -21,17 +22,25 @@ type AstroClerkIntegrationParams = Without< | 'polling' | 'touchSession' > & - MultiDomainAndOrProxyPrimitives; + MultiDomainAndOrProxyPrimitives & { + clerkJSUrl?: string; + clerkJSVariant?: 'headless' | ''; + clerkJSVersion?: string; + /** + * The URL that `@clerk/ui` should be hot-loaded from. + */ + clerkUiUrl?: string; + }; type AstroClerkCreateInstanceParams = AstroClerkIntegrationParams & { publishableKey: string }; -// Copied from `@clerk/clerk-react` +// Copied from `@clerk/react` export interface HeadlessBrowserClerk extends Clerk { load: (opts?: Without) => Promise; updateClient: (client: ClientResource) => void; } -// Copied from `@clerk/clerk-react` +// Copied from `@clerk/react` export interface BrowserClerk extends HeadlessBrowserClerk { onComponentsReady: Promise; components: any; @@ -42,6 +51,7 @@ declare global { __astro_clerk_component_props: Map>>; __astro_clerk_function_props: Map>>; Clerk: BrowserClerk; + __unstable_ClerkUiCtor?: ClerkUiConstructor; } } diff --git a/packages/backend/package.json b/packages/backend/package.json index 354a0e6121d..8d2e7cac028 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -110,7 +110,6 @@ }, "dependencies": { "@clerk/shared": "workspace:^", - "@clerk/types": "workspace:^", "cookie": "1.0.2", "standardwebhooks": "^1.0.0", "tslib": "catalog:repo" @@ -123,7 +122,7 @@ "vitest-environment-miniflare": "2.14.4" }, "engines": { - "node": ">=18.17.0" + "node": ">=20.9.0" }, "publishConfig": { "access": "public" diff --git a/packages/backend/src/__tests__/createRedirect.test.ts b/packages/backend/src/__tests__/createRedirect.test.ts index ab23730aca6..ee4fb558d16 100644 --- a/packages/backend/src/__tests__/createRedirect.test.ts +++ b/packages/backend/src/__tests__/createRedirect.test.ts @@ -1,4 +1,4 @@ -import type { SessionStatusClaim } from '@clerk/types'; +import type { SessionStatusClaim } from '@clerk/shared/types'; import { describe, expect, it, vi } from 'vitest'; import { createRedirect } from '../createRedirect'; diff --git a/packages/backend/src/api/endpoints/AllowlistIdentifierApi.ts b/packages/backend/src/api/endpoints/AllowlistIdentifierApi.ts index 9d51be554bc..62611e92e8e 100644 --- a/packages/backend/src/api/endpoints/AllowlistIdentifierApi.ts +++ b/packages/backend/src/api/endpoints/AllowlistIdentifierApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest } from '@clerk/types'; +import type { ClerkPaginationRequest } from '@clerk/shared/types'; import { joinPaths } from '../../util/path'; import type { AllowlistIdentifier } from '../resources/AllowlistIdentifier'; diff --git a/packages/backend/src/api/endpoints/BillingApi.ts b/packages/backend/src/api/endpoints/BillingApi.ts index c14d3a5edf5..3561a7cc9f3 100644 --- a/packages/backend/src/api/endpoints/BillingApi.ts +++ b/packages/backend/src/api/endpoints/BillingApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest } from '@clerk/types'; +import type { ClerkPaginationRequest } from '@clerk/shared/types'; import { joinPaths } from '../../util/path'; import type { BillingPlan } from '../resources/CommercePlan'; diff --git a/packages/backend/src/api/endpoints/BlocklistIdentifierApi.ts b/packages/backend/src/api/endpoints/BlocklistIdentifierApi.ts index ee6199bf912..86756c73942 100644 --- a/packages/backend/src/api/endpoints/BlocklistIdentifierApi.ts +++ b/packages/backend/src/api/endpoints/BlocklistIdentifierApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest } from '@clerk/types'; +import type { ClerkPaginationRequest } from '@clerk/shared/types'; import { joinPaths } from '../../util/path'; import type { BlocklistIdentifier } from '../resources/BlocklistIdentifier'; diff --git a/packages/backend/src/api/endpoints/ClientApi.ts b/packages/backend/src/api/endpoints/ClientApi.ts index f04202dff38..13d313cf449 100644 --- a/packages/backend/src/api/endpoints/ClientApi.ts +++ b/packages/backend/src/api/endpoints/ClientApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest } from '@clerk/types'; +import type { ClerkPaginationRequest } from '@clerk/shared/types'; import { joinPaths } from '../../util/path'; import type { Client } from '../resources/Client'; diff --git a/packages/backend/src/api/endpoints/InvitationApi.ts b/packages/backend/src/api/endpoints/InvitationApi.ts index f869234ec99..51962256ce7 100644 --- a/packages/backend/src/api/endpoints/InvitationApi.ts +++ b/packages/backend/src/api/endpoints/InvitationApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest } from '@clerk/types'; +import type { ClerkPaginationRequest } from '@clerk/shared/types'; import { joinPaths } from '../../util/path'; import type { PaginatedResourceResponse } from '../resources/Deserializer'; diff --git a/packages/backend/src/api/endpoints/JwtTemplatesApi.ts b/packages/backend/src/api/endpoints/JwtTemplatesApi.ts index 66e3dc72a0f..65beca638b3 100644 --- a/packages/backend/src/api/endpoints/JwtTemplatesApi.ts +++ b/packages/backend/src/api/endpoints/JwtTemplatesApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest } from '@clerk/types'; +import type { ClerkPaginationRequest } from '@clerk/shared/types'; import { joinPaths } from 'src/util/path'; import type { DeletedObject, JwtTemplate } from '../resources'; diff --git a/packages/backend/src/api/endpoints/MachineApi.ts b/packages/backend/src/api/endpoints/MachineApi.ts index 800295a5dc8..21bb4ff133a 100644 --- a/packages/backend/src/api/endpoints/MachineApi.ts +++ b/packages/backend/src/api/endpoints/MachineApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest } from '@clerk/types'; +import type { ClerkPaginationRequest } from '@clerk/shared/types'; import { joinPaths } from '../../util/path'; import type { PaginatedResourceResponse } from '../resources/Deserializer'; diff --git a/packages/backend/src/api/endpoints/OAuthApplicationsApi.ts b/packages/backend/src/api/endpoints/OAuthApplicationsApi.ts index 641ff786ce1..37acf982456 100644 --- a/packages/backend/src/api/endpoints/OAuthApplicationsApi.ts +++ b/packages/backend/src/api/endpoints/OAuthApplicationsApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest } from '@clerk/types'; +import type { ClerkPaginationRequest } from '@clerk/shared/types'; import { joinPaths } from '../../util/path'; import type { DeletedObject } from '../resources'; diff --git a/packages/backend/src/api/endpoints/OrganizationApi.ts b/packages/backend/src/api/endpoints/OrganizationApi.ts index 3d89bd27905..e32782aa58d 100644 --- a/packages/backend/src/api/endpoints/OrganizationApi.ts +++ b/packages/backend/src/api/endpoints/OrganizationApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest, OrganizationEnrollmentMode } from '@clerk/types'; +import type { ClerkPaginationRequest, OrganizationEnrollmentMode } from '@clerk/shared/types'; import { runtime } from '../../runtime'; import { joinPaths } from '../../util/path'; diff --git a/packages/backend/src/api/endpoints/SamlConnectionApi.ts b/packages/backend/src/api/endpoints/SamlConnectionApi.ts index 9224995add4..98101026b8f 100644 --- a/packages/backend/src/api/endpoints/SamlConnectionApi.ts +++ b/packages/backend/src/api/endpoints/SamlConnectionApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest, SamlIdpSlug } from '@clerk/types'; +import type { ClerkPaginationRequest, SamlIdpSlug } from '@clerk/shared/types'; import { joinPaths } from '../../util/path'; import type { SamlConnection } from '../resources'; diff --git a/packages/backend/src/api/endpoints/SessionApi.ts b/packages/backend/src/api/endpoints/SessionApi.ts index b2180f7d126..a4fc70ddf55 100644 --- a/packages/backend/src/api/endpoints/SessionApi.ts +++ b/packages/backend/src/api/endpoints/SessionApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest, SessionStatus } from '@clerk/types'; +import type { ClerkPaginationRequest, SessionStatus } from '@clerk/shared/types'; import { joinPaths } from '../../util/path'; import type { Cookies } from '../resources/Cookies'; diff --git a/packages/backend/src/api/endpoints/UserApi.ts b/packages/backend/src/api/endpoints/UserApi.ts index 06c8ae8e974..794d4e4b1ef 100644 --- a/packages/backend/src/api/endpoints/UserApi.ts +++ b/packages/backend/src/api/endpoints/UserApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest, OAuthProvider, OrganizationInvitationStatus } from '@clerk/types'; +import type { ClerkPaginationRequest, OAuthProvider, OrganizationInvitationStatus } from '@clerk/shared/types'; import { runtime } from '../../runtime'; import { joinPaths } from '../../util/path'; diff --git a/packages/backend/src/api/endpoints/WaitlistEntryApi.ts b/packages/backend/src/api/endpoints/WaitlistEntryApi.ts index 3441739ce95..f740d9f58de 100644 --- a/packages/backend/src/api/endpoints/WaitlistEntryApi.ts +++ b/packages/backend/src/api/endpoints/WaitlistEntryApi.ts @@ -1,4 +1,4 @@ -import type { ClerkPaginationRequest } from '@clerk/types'; +import type { ClerkPaginationRequest } from '@clerk/shared/types'; import { joinPaths } from 'src/util/path'; import type { DeletedObject } from '../resources/DeletedObject'; diff --git a/packages/backend/src/api/request.ts b/packages/backend/src/api/request.ts index c9d321f505e..b9a99f283cc 100644 --- a/packages/backend/src/api/request.ts +++ b/packages/backend/src/api/request.ts @@ -1,5 +1,5 @@ import { ClerkAPIResponseError, parseError } from '@clerk/shared/error'; -import type { ClerkAPIError, ClerkAPIErrorJSON } from '@clerk/types'; +import type { ClerkAPIError, ClerkAPIErrorJSON } from '@clerk/shared/types'; import snakecaseKeys from 'snakecase-keys'; import { API_URL, API_VERSION, constants, SUPPORTED_BAPI_VERSION, USER_AGENT } from '../constants'; diff --git a/packages/backend/src/api/resources/Client.ts b/packages/backend/src/api/resources/Client.ts index c58e2aba146..1ad7a4786fe 100644 --- a/packages/backend/src/api/resources/Client.ts +++ b/packages/backend/src/api/resources/Client.ts @@ -1,4 +1,4 @@ -import type { LastAuthenticationStrategy } from '@clerk/types'; +import type { LastAuthenticationStrategy } from '@clerk/shared/types'; import type { ClientJSON } from './JSON'; import { Session } from './Session'; diff --git a/packages/backend/src/api/resources/CommerceSubscription.ts b/packages/backend/src/api/resources/CommerceSubscription.ts index 64d04089170..7c2dd2b61bb 100644 --- a/packages/backend/src/api/resources/CommerceSubscription.ts +++ b/packages/backend/src/api/resources/CommerceSubscription.ts @@ -1,4 +1,4 @@ -import type { BillingMoneyAmount } from '@clerk/types'; +import type { BillingMoneyAmount } from '@clerk/shared/types'; import { BillingSubscriptionItem } from './CommerceSubscriptionItem'; import type { BillingSubscriptionJSON } from './JSON'; diff --git a/packages/backend/src/api/resources/CommerceSubscriptionItem.ts b/packages/backend/src/api/resources/CommerceSubscriptionItem.ts index e1e477e0605..03b4681d3b7 100644 --- a/packages/backend/src/api/resources/CommerceSubscriptionItem.ts +++ b/packages/backend/src/api/resources/CommerceSubscriptionItem.ts @@ -1,4 +1,4 @@ -import type { BillingMoneyAmount, BillingMoneyAmountJSON } from '@clerk/types'; +import type { BillingMoneyAmount, BillingMoneyAmountJSON } from '@clerk/shared/types'; import { BillingPlan } from './CommercePlan'; import type { BillingSubscriptionItemJSON } from './JSON'; diff --git a/packages/backend/src/api/resources/Enums.ts b/packages/backend/src/api/resources/Enums.ts index c42e4a31a14..c75c70f8026 100644 --- a/packages/backend/src/api/resources/Enums.ts +++ b/packages/backend/src/api/resources/Enums.ts @@ -1,4 +1,4 @@ -import type { OrganizationCustomRoleKey } from '@clerk/types'; +import type { OrganizationCustomRoleKey } from '@clerk/shared/types'; export type OAuthProvider = | 'facebook' diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index 2c9ce9d30bb..9662cc5f736 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -1,4 +1,4 @@ -import type { LastAuthenticationStrategy, SignUpStatus, VerificationStatus } from '@clerk/types'; +import type { LastAuthenticationStrategy, SignUpStatus, VerificationStatus } from '@clerk/shared/types'; import type { ActorTokenStatus, @@ -50,7 +50,6 @@ export const ObjectType = { PhoneNumber: 'phone_number', ProxyCheck: 'proxy_check', RedirectUrl: 'redirect_url', - SamlAccount: 'saml_account', SamlConnection: 'saml_connection', Session: 'session', SignInAttempt: 'sign_in_attempt', @@ -235,21 +234,6 @@ export interface JwtTemplateJSON extends ClerkResourceJSON { created_at: number; updated_at: number; } - -export interface SamlAccountJSON extends ClerkResourceJSON { - object: typeof ObjectType.SamlAccount; - provider: string; - provider_user_id: string | null; - active: boolean; - email_address: string; - first_name: string; - last_name: string; - verification: VerificationJSON | null; - saml_connection: SamlAccountConnectionJSON | null; - last_authenticated_at: number | null; - enterprise_connection_id: string | null; -} - export interface IdentificationLinkJSON extends ClerkResourceJSON { type: string; } @@ -601,7 +585,6 @@ export interface UserJSON extends ClerkResourceJSON { web3_wallets: Web3WalletJSON[]; organization_memberships: OrganizationMembershipJSON[] | null; external_accounts: ExternalAccountJSON[]; - saml_accounts: SamlAccountJSON[]; password_last_updated_at: number | null; public_metadata: UserPublicMetadata; private_metadata: UserPrivateMetadata; @@ -734,20 +717,6 @@ export interface PermissionJSON extends ClerkResourceJSON { updated_at: number; } -export interface SamlAccountConnectionJSON extends ClerkResourceJSON { - id: string; - name: string; - domain: string; - active: boolean; - provider: string; - sync_user_attributes: boolean; - allow_subdomains: boolean; - allow_idp_initiated: boolean; - disable_additional_identifications: boolean; - created_at: number; - updated_at: number; -} - export interface MachineJSON extends ClerkResourceJSON { object: typeof ObjectType.Machine; id: string; diff --git a/packages/backend/src/api/resources/SamlAccount.ts b/packages/backend/src/api/resources/SamlAccount.ts deleted file mode 100644 index 60714aa13aa..00000000000 --- a/packages/backend/src/api/resources/SamlAccount.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { SamlAccountJSON } from './JSON'; -import { SamlAccountConnection } from './SamlConnection'; -import { Verification } from './Verification'; - -/** - * The Backend `SamlAccount` object describes a SAML account. - */ -export class SamlAccount { - constructor( - /** - * The unique identifier for the SAML account. - */ - readonly id: string, - /** - * The provider of the SAML account. - */ - readonly provider: string, - /** - * The user's ID as used in the provider. - */ - readonly providerUserId: string | null, - /** - * A boolean that indicates whether the SAML account is active. - */ - readonly active: boolean, - /** - * The email address of the SAML account. - */ - readonly emailAddress: string, - /** - * The first name of the SAML account. - */ - readonly firstName: string, - /** - * The last name of the SAML account. - */ - readonly lastName: string, - /** - * The verification of the SAML account. - */ - readonly verification: Verification | null, - /** - * The SAML connection of the SAML account. - */ - readonly samlConnection: SamlAccountConnection | null, - /** - * The date when the SAML account was last authenticated. - */ - readonly lastAuthenticatedAt: number | null, - /** - * The ID of the enterprise connection associated with this SAML account. - */ - readonly enterpriseConnectionId: string | null, - ) {} - - static fromJSON(data: SamlAccountJSON): SamlAccount { - return new SamlAccount( - data.id, - data.provider, - data.provider_user_id, - data.active, - data.email_address, - data.first_name, - data.last_name, - data.verification && Verification.fromJSON(data.verification), - data.saml_connection && SamlAccountConnection.fromJSON(data.saml_connection), - data.last_authenticated_at ?? null, - data.enterprise_connection_id, - ); - } -} diff --git a/packages/backend/src/api/resources/SamlConnection.ts b/packages/backend/src/api/resources/SamlConnection.ts index 60e87bbb3d8..ef0c3e3d7b0 100644 --- a/packages/backend/src/api/resources/SamlConnection.ts +++ b/packages/backend/src/api/resources/SamlConnection.ts @@ -1,4 +1,4 @@ -import type { AttributeMappingJSON, SamlAccountConnectionJSON, SamlConnectionJSON } from './JSON'; +import type { AttributeMappingJSON, SamlConnectionJSON } from './JSON'; /** * The Backend `SamlConnection` object holds information about a SAML connection for an organization. @@ -117,35 +117,6 @@ export class SamlConnection { } } -export class SamlAccountConnection { - constructor( - readonly id: string, - readonly name: string, - readonly domain: string, - readonly active: boolean, - readonly provider: string, - readonly syncUserAttributes: boolean, - readonly allowSubdomains: boolean, - readonly allowIdpInitiated: boolean, - readonly createdAt: number, - readonly updatedAt: number, - ) {} - static fromJSON(data: SamlAccountConnectionJSON): SamlAccountConnection { - return new SamlAccountConnection( - data.id, - data.name, - data.domain, - data.active, - data.provider, - data.sync_user_attributes, - data.allow_subdomains, - data.allow_idp_initiated, - data.created_at, - data.updated_at, - ); - } -} - class AttributeMapping { constructor( /** diff --git a/packages/backend/src/api/resources/SignUpAttempt.ts b/packages/backend/src/api/resources/SignUpAttempt.ts index 62707b24bb0..7426f25de1c 100644 --- a/packages/backend/src/api/resources/SignUpAttempt.ts +++ b/packages/backend/src/api/resources/SignUpAttempt.ts @@ -1,4 +1,4 @@ -import type { SignUpStatus } from '@clerk/types'; +import type { SignUpStatus } from '@clerk/shared/types'; import type { SignUpVerificationNextAction } from './Enums'; import type { SignUpJSON, SignUpVerificationJSON, SignUpVerificationsJSON } from './JSON'; diff --git a/packages/backend/src/api/resources/User.ts b/packages/backend/src/api/resources/User.ts index df05ef53466..b1fe8e93377 100644 --- a/packages/backend/src/api/resources/User.ts +++ b/packages/backend/src/api/resources/User.ts @@ -1,8 +1,7 @@ import { EmailAddress } from './EmailAddress'; import { ExternalAccount } from './ExternalAccount'; -import type { ExternalAccountJSON, SamlAccountJSON, UserJSON } from './JSON'; +import type { ExternalAccountJSON, UserJSON } from './JSON'; import { PhoneNumber } from './PhoneNumber'; -import { SamlAccount } from './SamlAccount'; import { Web3Wallet } from './Web3Wallet'; /** @@ -120,10 +119,6 @@ export class User { * An array of all the `ExternalAccount` objects associated with the user via OAuth. **Note**: This includes both verified & unverified external accounts. */ readonly externalAccounts: ExternalAccount[] = [], - /** - * An array of all the `SamlAccount` objects associated with the user via SAML. - */ - readonly samlAccounts: SamlAccount[] = [], /** * Date when the user was last active. */ @@ -179,7 +174,6 @@ export class User { (data.phone_numbers || []).map(x => PhoneNumber.fromJSON(x)), (data.web3_wallets || []).map(x => Web3Wallet.fromJSON(x)), (data.external_accounts || []).map((x: ExternalAccountJSON) => ExternalAccount.fromJSON(x)), - (data.saml_accounts || []).map((x: SamlAccountJSON) => SamlAccount.fromJSON(x)), data.last_active_at, data.create_organization_enabled, data.create_organizations_limit, diff --git a/packages/backend/src/api/resources/Verification.ts b/packages/backend/src/api/resources/Verification.ts index 7f65bc9a492..4da2489071e 100644 --- a/packages/backend/src/api/resources/Verification.ts +++ b/packages/backend/src/api/resources/Verification.ts @@ -1,4 +1,4 @@ -import type { VerificationStatus } from '@clerk/types'; +import type { VerificationStatus } from '@clerk/shared/types'; import type { OrganizationDomainVerificationJSON, VerificationJSON } from './JSON'; diff --git a/packages/backend/src/api/resources/index.ts b/packages/backend/src/api/resources/index.ts index 855fb43df45..00e119dbb21 100644 --- a/packages/backend/src/api/resources/index.ts +++ b/packages/backend/src/api/resources/index.ts @@ -20,9 +20,13 @@ export type { SignInStatus, } from './Enums'; -export type { SignUpStatus } from '@clerk/types'; +export type { SignUpStatus } from '@clerk/shared/types'; +export * from './CommercePlan'; +export * from './CommerceSubscription'; +export * from './CommerceSubscriptionItem'; export * from './ExternalAccount'; +export * from './Feature'; export * from './IdentificationLink'; export * from './IdPOAuthAccessToken'; export * from './Instance'; @@ -30,11 +34,11 @@ export * from './InstanceRestrictions'; export * from './InstanceSettings'; export * from './Invitation'; export * from './JSON'; +export * from './JwtTemplate'; +export * from './M2MToken'; export * from './Machine'; export * from './MachineScope'; export * from './MachineSecretKey'; -export * from './M2MToken'; -export * from './JwtTemplate'; export * from './OauthAccessToken'; export * from './OAuthApplication'; export * from './Organization'; @@ -45,7 +49,6 @@ export * from './OrganizationSettings'; export * from './PhoneNumber'; export * from './ProxyCheck'; export * from './RedirectUrl'; -export * from './SamlAccount'; export * from './SamlConnection'; export * from './Session'; export * from './SignInTokens'; @@ -57,17 +60,13 @@ export * from './User'; export * from './Verification'; export * from './WaitlistEntry'; export * from './Web3Wallet'; -export * from './CommercePlan'; -export * from './CommerceSubscription'; -export * from './CommerceSubscriptionItem'; -export * from './Feature'; export type { EmailWebhookEvent, - OrganizationWebhookEvent, OrganizationDomainWebhookEvent, OrganizationInvitationWebhookEvent, OrganizationMembershipWebhookEvent, + OrganizationWebhookEvent, PermissionWebhookEvent, RoleWebhookEvent, SessionWebhookEvent, diff --git a/packages/backend/src/createRedirect.ts b/packages/backend/src/createRedirect.ts index 07f2f6b8123..41eb4fd7626 100644 --- a/packages/backend/src/createRedirect.ts +++ b/packages/backend/src/createRedirect.ts @@ -1,5 +1,5 @@ import { buildAccountsBaseUrl } from '@clerk/shared/buildAccountsBaseUrl'; -import type { SessionStatusClaim } from '@clerk/types'; +import type { SessionStatusClaim } from '@clerk/shared/types'; import { constants } from './constants'; import { errorThrower, parsePublishableKey } from './util/shared'; diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index e8215708399..5d8dc167986 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -1,6 +1,6 @@ import type { TelemetryCollectorOptions } from '@clerk/shared/telemetry'; import { TelemetryCollector } from '@clerk/shared/telemetry'; -import type { SDKMetadata } from '@clerk/types'; +import type { SDKMetadata } from '@clerk/shared/types'; import type { ApiClient, CreateBackendApiOptions } from './api'; import { createBackendApiClient } from './api'; diff --git a/packages/backend/src/jwt/__tests__/signJwt.test.ts b/packages/backend/src/jwt/__tests__/signJwt.test.ts index 75e00499523..f163600b071 100644 --- a/packages/backend/src/jwt/__tests__/signJwt.test.ts +++ b/packages/backend/src/jwt/__tests__/signJwt.test.ts @@ -1,4 +1,4 @@ -import type { JwtPayload } from '@clerk/types'; +import type { JwtPayload } from '@clerk/shared/types'; import { beforeEach, describe, expect, it } from 'vitest'; import { diff --git a/packages/backend/src/jwt/verifyJwt.ts b/packages/backend/src/jwt/verifyJwt.ts index d1b4a9cbcf5..1d1b205109f 100644 --- a/packages/backend/src/jwt/verifyJwt.ts +++ b/packages/backend/src/jwt/verifyJwt.ts @@ -1,4 +1,4 @@ -import type { Jwt, JwtPayload } from '@clerk/types'; +import type { Jwt, JwtPayload } from '@clerk/shared/types'; import { TokenVerificationError, TokenVerificationErrorAction, TokenVerificationErrorReason } from '../errors'; import { runtime } from '../runtime'; diff --git a/packages/backend/src/tokens/__tests__/authObjects.test.ts b/packages/backend/src/tokens/__tests__/authObjects.test.ts index 2df02870852..0e16bbb75c8 100644 --- a/packages/backend/src/tokens/__tests__/authObjects.test.ts +++ b/packages/backend/src/tokens/__tests__/authObjects.test.ts @@ -1,4 +1,4 @@ -import type { JwtPayload } from '@clerk/types'; +import type { JwtPayload } from '@clerk/shared/types'; import { describe, expect, it, vi } from 'vitest'; import { createBackendApiClient } from '../../api/factory'; diff --git a/packages/backend/src/tokens/__tests__/authStatus.test.ts b/packages/backend/src/tokens/__tests__/authStatus.test.ts index ba301bdfb05..4ecd46ffd1a 100644 --- a/packages/backend/src/tokens/__tests__/authStatus.test.ts +++ b/packages/backend/src/tokens/__tests__/authStatus.test.ts @@ -1,4 +1,4 @@ -import type { JwtPayload } from '@clerk/types'; +import type { JwtPayload } from '@clerk/shared/types'; import { describe, expect, it } from 'vitest'; import { mockTokens, mockVerificationResults } from '../../fixtures/machine'; diff --git a/packages/backend/src/tokens/authObjects.ts b/packages/backend/src/tokens/authObjects.ts index 5f359d73ca3..bd6f4079bce 100644 --- a/packages/backend/src/tokens/authObjects.ts +++ b/packages/backend/src/tokens/authObjects.ts @@ -9,7 +9,7 @@ import type { ServerGetTokenOptions, SessionStatusClaim, SharedSignedInAuthObjectProperties, -} from '@clerk/types'; +} from '@clerk/shared/types'; import type { APIKey, CreateBackendApiOptions, IdPOAuthAccessToken, M2MToken } from '../api'; import { createBackendApiClient } from '../api'; diff --git a/packages/backend/src/tokens/authStatus.ts b/packages/backend/src/tokens/authStatus.ts index 881e5f40ca6..27205ed40b4 100644 --- a/packages/backend/src/tokens/authStatus.ts +++ b/packages/backend/src/tokens/authStatus.ts @@ -1,4 +1,4 @@ -import type { JwtPayload, PendingSessionOptions } from '@clerk/types'; +import type { JwtPayload, PendingSessionOptions } from '@clerk/shared/types'; import { constants } from '../constants'; import type { TokenVerificationErrorReason } from '../errors'; diff --git a/packages/backend/src/tokens/authenticateContext.ts b/packages/backend/src/tokens/authenticateContext.ts index 44b05ac6b14..6d9fdcb9c5a 100644 --- a/packages/backend/src/tokens/authenticateContext.ts +++ b/packages/backend/src/tokens/authenticateContext.ts @@ -1,6 +1,6 @@ import { buildAccountsBaseUrl } from '@clerk/shared/buildAccountsBaseUrl'; +import type { Jwt } from '@clerk/shared/types'; import { isCurrentDevAccountPortalOrigin, isLegacyDevAccountPortalOrigin } from '@clerk/shared/url'; -import type { Jwt } from '@clerk/types'; import { constants } from '../constants'; import { decodeJwt } from '../jwt/verifyJwt'; diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts index 1d2aaaa6d1e..992233d84c7 100644 --- a/packages/backend/src/tokens/request.ts +++ b/packages/backend/src/tokens/request.ts @@ -1,4 +1,4 @@ -import type { JwtPayload } from '@clerk/types'; +import type { JwtPayload } from '@clerk/shared/types'; import { constants } from '../constants'; import type { TokenCarrier } from '../errors'; diff --git a/packages/backend/src/tokens/types.ts b/packages/backend/src/tokens/types.ts index dab308d6d94..f421ce58933 100644 --- a/packages/backend/src/tokens/types.ts +++ b/packages/backend/src/tokens/types.ts @@ -1,5 +1,5 @@ import type { MatchFunction } from '@clerk/shared/pathToRegexp'; -import type { PendingSessionOptions } from '@clerk/types'; +import type { PendingSessionOptions } from '@clerk/shared/types'; import type { ApiClient, APIKey, IdPOAuthAccessToken, M2MToken } from '../api'; import type { diff --git a/packages/backend/src/tokens/verify.ts b/packages/backend/src/tokens/verify.ts index dfc22cc4d66..f73f4374e14 100644 --- a/packages/backend/src/tokens/verify.ts +++ b/packages/backend/src/tokens/verify.ts @@ -1,6 +1,5 @@ import { isClerkAPIResponseError } from '@clerk/shared/error'; -import type { Simplify } from '@clerk/shared/types'; -import type { JwtPayload } from '@clerk/types'; +import type { JwtPayload, Simplify } from '@clerk/shared/types'; import type { APIKey, IdPOAuthAccessToken, M2MToken } from '../api'; import { createBackendApiClient } from '../api/factory'; diff --git a/packages/backend/typedoc.json b/packages/backend/typedoc.json index 7aeba3de0e3..904b837abec 100644 --- a/packages/backend/typedoc.json +++ b/packages/backend/typedoc.json @@ -6,7 +6,7 @@ "./src/tokens/verify.ts", "./src/tokens/request.ts", "./src/tokens/types.ts", - "./src/tokens/authOjbects.ts", + "./src/tokens/authObjects.ts", "./src/api/resources/index.ts", "./src/api/resources/Deserializer.ts" ] diff --git a/packages/chrome-extension/package.json b/packages/chrome-extension/package.json index 1336e86c16c..232ba0667f9 100644 --- a/packages/chrome-extension/package.json +++ b/packages/chrome-extension/package.json @@ -50,8 +50,9 @@ }, "dependencies": { "@clerk/clerk-js": "workspace:^", - "@clerk/clerk-react": "workspace:^", + "@clerk/react": "workspace:^", "@clerk/shared": "workspace:^", + "@clerk/ui": "workspace:^", "webextension-polyfill": "~0.12.0" }, "devDependencies": { @@ -64,7 +65,7 @@ "react-dom": "catalog:peer-react" }, "engines": { - "node": ">=18.17.0" + "node": ">=20.9.0" }, "publishConfig": { "access": "public" diff --git a/packages/chrome-extension/src/background/clerk.ts b/packages/chrome-extension/src/background/clerk.ts index c77b4722651..57c68d5097d 100644 --- a/packages/chrome-extension/src/background/clerk.ts +++ b/packages/chrome-extension/src/background/clerk.ts @@ -1,4 +1,4 @@ -import { Clerk } from '@clerk/clerk-js/no-rhc'; +import type { Clerk } from '@clerk/clerk-js/no-rhc'; import { createClerkClient as _createClerkClient, @@ -6,8 +6,6 @@ import { } from '../internal'; import { SCOPE } from '../types'; -Clerk.mountComponentRenderer = undefined; - export type CreateClerkClientOptions = Omit<_CreateClerkClientOptions, 'scope'>; export async function createClerkClient(opts: CreateClerkClientOptions): Promise { diff --git a/packages/chrome-extension/src/index.ts b/packages/chrome-extension/src/index.ts index ec4cc5aa03e..96fa19a9706 100644 --- a/packages/chrome-extension/src/index.ts +++ b/packages/chrome-extension/src/index.ts @@ -2,9 +2,9 @@ export * from './react/re-exports'; export type { StorageCache } from './internal/utils/storage'; -// The order matters since we want override @clerk/clerk-react components +// The order matters since we want override @clerk/react components export { ClerkProvider, GoogleOneTap } from './react'; // Override Clerk React error thrower to show that errors come from @clerk/chrome-extension -import { setErrorThrowerOptions } from '@clerk/clerk-react/internal'; +import { setErrorThrowerOptions } from '@clerk/react/internal'; setErrorThrowerOptions({ packageName: PACKAGE_NAME }); diff --git a/packages/chrome-extension/src/internal/clerk.ts b/packages/chrome-extension/src/internal/clerk.ts index f5231d6c040..1569bcafc15 100644 --- a/packages/chrome-extension/src/internal/clerk.ts +++ b/packages/chrome-extension/src/internal/clerk.ts @@ -28,14 +28,16 @@ export type CreateClerkClientOptions = { syncHost?: string; }; -export async function createClerkClient({ +export function createClerkClient({ __experimental_syncHostListener = false, publishableKey, scope, storageCache = BrowserStorageCache, syncHost, -}: CreateClerkClientOptions): Promise { +}: CreateClerkClientOptions) { if (scope === SCOPE.BACKGROUND) { + // TODO @nikos + // @ts-expect-error will be replaced by clerk ui Clerk.mountComponentRenderer = undefined; } @@ -64,7 +66,7 @@ export async function createClerkClient({ const url = syncHost ? syncHost : DEFAULT_LOCAL_HOST_PERMISSION; // Create Clerk instance - clerk = new Clerk(publishableKey); + clerk = new Clerk(publishableKey, {}); // @ts-expect-error - TODO: sync is evaluating to true vs boolean const jwtOptions: JWTHandlerParams = { diff --git a/packages/chrome-extension/src/react/ClerkProvider.tsx b/packages/chrome-extension/src/react/ClerkProvider.tsx index ba9ad71f50a..003604882ee 100644 --- a/packages/chrome-extension/src/react/ClerkProvider.tsx +++ b/packages/chrome-extension/src/react/ClerkProvider.tsx @@ -1,6 +1,7 @@ import type { Clerk } from '@clerk/clerk-js/no-rhc'; -import type { ClerkProviderProps as ClerkReactProviderProps } from '@clerk/clerk-react'; -import { ClerkProvider as ClerkReactProvider } from '@clerk/clerk-react'; +import type { ClerkProviderProps as ClerkReactProviderProps } from '@clerk/react'; +import { ClerkProvider as ClerkReactProvider } from '@clerk/react'; +import { ClerkUi } from '@clerk/ui/entry'; import React from 'react'; import { createClerkClient } from '../internal/clerk'; @@ -23,11 +24,7 @@ export function ClerkProvider(props: ChromeExtensionClerkProviderProps): JSX.Ele const [clerkInstance, setClerkInstance] = React.useState(null); React.useEffect(() => { - void (async () => { - setClerkInstance( - await createClerkClient({ publishableKey, storageCache, syncHost, __experimental_syncHostListener }), - ); - })(); + setClerkInstance(createClerkClient({ publishableKey, storageCache, syncHost, __experimental_syncHostListener })); }, [publishableKey, storageCache, syncHost, __experimental_syncHostListener]); if (!clerkInstance) { @@ -38,6 +35,7 @@ export function ClerkProvider(props: ChromeExtensionClerkProviderProps): JSX.Ele {children} diff --git a/packages/chrome-extension/src/react/re-exports.ts b/packages/chrome-extension/src/react/re-exports.ts index 4c4ca13d711..2838dc6264b 100644 --- a/packages/chrome-extension/src/react/re-exports.ts +++ b/packages/chrome-extension/src/react/re-exports.ts @@ -39,4 +39,4 @@ export { useSignIn, useSignUp, useUser, -} from '@clerk/clerk-react'; +} from '@clerk/react'; diff --git a/packages/chrome-extension/tsup.config.ts b/packages/chrome-extension/tsup.config.ts index 6a75787567b..03130188e9f 100644 --- a/packages/chrome-extension/tsup.config.ts +++ b/packages/chrome-extension/tsup.config.ts @@ -16,7 +16,7 @@ export default defineConfig(overrideOptions => { sourcemap: true, legacyOutput: true, treeshake: true, - noExternal: ['@clerk/clerk-react', '@clerk/shared'], + noExternal: ['@clerk/react', '@clerk/shared'], external: ['use-sync-external-store', '@stripe/stripe-js', '@stripe/react-stripe-js'], define: { PACKAGE_NAME: `"${name}"`, diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 509aa6dfaba..6ed05d9e876 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -1,36 +1,18 @@ { "files": [ - { "path": "./dist/clerk.js", "maxSize": "840KB" }, - { "path": "./dist/clerk.browser.js", "maxSize": "81KB" }, - { "path": "./dist/clerk.channel.browser.js", "maxSize": "81KB" }, - { "path": "./dist/clerk.legacy.browser.js", "maxSize": "123KB" }, + { "path": "./dist/clerk.js", "maxSize": "538KB" }, + { "path": "./dist/clerk.browser.js", "maxSize": "63KB" }, + { "path": "./dist/clerk.channel.browser.js", "maxSize": "64KB" }, + { "path": "./dist/clerk.chips.browser.js", "maxSize": "63KB" }, + { "path": "./dist/clerk.legacy.browser.js", "maxSize": "105KB" }, + { "path": "./dist/clerk.no-rhc.js", "maxSize": "305KB" }, { "path": "./dist/clerk.headless*.js", "maxSize": "65KB" }, - { "path": "./dist/ui-common*.js", "maxSize": "117.1KB" }, - { "path": "./dist/ui-common*.legacy.*.js", "maxSize": "122KB" }, - { "path": "./dist/vendors*.js", "maxSize": "47KB" }, - { "path": "./dist/coinbase*.js", "maxSize": "38KB" }, + { "path": "./dist/vendors*.js", "maxSize": "7KB" }, + { "path": "./dist/coinbase*.js", "maxSize": "36KB" }, + { "path": "./dist/base-account-sdk*.js", "maxSize": "203KB" }, { "path": "./dist/stripe-vendors*.js", "maxSize": "1KB" }, - { "path": "./dist/createorganization*.js", "maxSize": "5KB" }, - { "path": "./dist/impersonationfab*.js", "maxSize": "5KB" }, - { "path": "./dist/organizationprofile*.js", "maxSize": "10KB" }, - { "path": "./dist/organizationswitcher*.js", "maxSize": "5KB" }, - { "path": "./dist/organizationlist*.js", "maxSize": "5.5KB" }, - { "path": "./dist/signin*.js", "maxSize": "18KB" }, - { "path": "./dist/signup*.js", "maxSize": "9.5KB" }, - { "path": "./dist/userbutton*.js", "maxSize": "5KB" }, - { "path": "./dist/userprofile*.js", "maxSize": "16KB" }, - { "path": "./dist/userverification*.js", "maxSize": "5KB" }, - { "path": "./dist/onetap*.js", "maxSize": "1KB" }, - { "path": "./dist/waitlist*.js", "maxSize": "1.5KB" }, - { "path": "./dist/keylessPrompt*.js", "maxSize": "6.5KB" }, - { "path": "./dist/pricingTable*.js", "maxSize": "4.02KB" }, - { "path": "./dist/checkout*.js", "maxSize": "8.82KB" }, - { "path": "./dist/up-billing-page*.js", "maxSize": "3.0KB" }, - { "path": "./dist/op-billing-page*.js", "maxSize": "3.0KB" }, - { "path": "./dist/up-plans-page*.js", "maxSize": "1.0KB" }, - { "path": "./dist/op-plans-page*.js", "maxSize": "1.0KB" }, - { "path": "./dist/statement-page*.js", "maxSize": "1.0KB" }, - { "path": "./dist/payment-attempt-page*.js", "maxSize": "3.0KB" }, - { "path": "./dist/sessionTasks*.js", "maxSize": "1.5KB" } + { "path": "./dist/query-core-vendors*.js", "maxSize": "11KB" }, + { "path": "./dist/zxcvbn-ts-core*.js", "maxSize": "12KB" }, + { "path": "./dist/zxcvbn-common*.js", "maxSize": "226KB" } ] } diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index cd8ad69e3cb..ff83e0d2f22 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -32,7 +32,6 @@ ], "scripts": { "build": "pnpm build:bundle && pnpm build:declarations", - "postbuild": "node ../../scripts/search-for-rhc.mjs file dist/clerk.no-rhc.mjs", "build:analyze": "rspack build --config rspack.config.js --env production --env variant=\"clerk.browser\" --env analysis --analyze", "build:bundle": "pnpm clean && rspack build --config rspack.config.js --env production", "build:declarations": "tsc -p tsconfig.declarations.json", @@ -52,7 +51,8 @@ "lint": "eslint src", "lint:attw": "attw --pack . --profile node16 --ignore-rules named-exports", "lint:publint": "publint || true", - "test": "vitest --watch=false", + "postbuild:disabled": "node ../../scripts/search-for-rhc.mjs file dist/clerk.no-rhc.mjs", + "test:disabled": "vitest --watch=false", "test:sandbox:integration": "playwright test", "test:sandbox:integration:ui": "playwright test --ui", "test:sandbox:integration:update-snapshots": "playwright test --update-snapshots", @@ -60,37 +60,26 @@ }, "browserslist": "last 2 years", "dependencies": { - "@base-org/account": "2.0.1", - "@clerk/localizations": "workspace:^", + "@base-org/account": "catalog:module-manager", "@clerk/shared": "workspace:^", - "@coinbase/wallet-sdk": "4.3.0", - "@emotion/cache": "11.11.0", - "@emotion/react": "11.11.1", - "@floating-ui/react": "0.27.12", - "@floating-ui/react-dom": "^2.1.3", - "@formkit/auto-animate": "^0.8.2", + "@coinbase/wallet-sdk": "catalog:module-manager", "@stripe/stripe-js": "5.6.0", "@swc/helpers": "^0.5.17", "@tanstack/query-core": "5.87.4", - "@zxcvbn-ts/core": "3.0.4", - "@zxcvbn-ts/language-common": "3.0.4", + "@zxcvbn-ts/core": "catalog:module-manager", + "@zxcvbn-ts/language-common": "catalog:module-manager", "alien-signals": "2.0.6", "browser-tabs-lock": "1.3.0", - "copy-to-clipboard": "3.3.3", "core-js": "3.41.0", "crypto-js": "^4.2.0", "dequal": "2.0.3", - "input-otp": "1.4.2", - "qrcode.react": "4.2.0", "regenerator-runtime": "0.14.1" }, "devDependencies": { "@clerk/testing": "workspace:^", "@rsdoctor/rspack-plugin": "^0.4.13", - "@rspack/cli": "^1.4.11", - "@rspack/core": "^1.4.11", - "@rspack/plugin-react-refresh": "^1.5.0", - "@svgr/webpack": "^6.5.1", + "@rspack/cli": "^1.6.0", + "@rspack/core": "^1.6.0", "@types/cloudflare-turnstile": "^0.2.2", "@types/node": "^22.18.12", "@types/webpack-env": "^1.18.8", @@ -99,12 +88,8 @@ "minimatch": "^10.0.3", "webpack-merge": "^5.10.0" }, - "peerDependencies": { - "react": "catalog:peer-react", - "react-dom": "catalog:peer-react" - }, "engines": { - "node": ">=18.17.0" + "node": ">=20.9.0" }, "publishConfig": { "access": "public" diff --git a/packages/clerk-js/rspack.config.js b/packages/clerk-js/rspack.config.js index 18e842bb3d5..237263febb2 100644 --- a/packages/clerk-js/rspack.config.js +++ b/packages/clerk-js/rspack.config.js @@ -5,6 +5,7 @@ const path = require('path'); const { merge } = require('webpack-merge'); const ReactRefreshPlugin = require('@rspack/plugin-react-refresh'); const { RsdoctorRspackPlugin } = require('@rsdoctor/rspack-plugin'); +const { svgLoader, typescriptLoaderProd, typescriptLoaderDev } = require('../../scripts/rspack-common'); const isProduction = mode => mode === 'production'; const isDevelopment = mode => !isProduction(mode); @@ -123,20 +124,6 @@ const common = ({ mode, variant, disableRHC = false }) => { chunks: 'all', enforce: true, }, - /** - * Sign up is shared between the SignUp component and the SignIn component. - */ - signUp: { - minChunks: 1, - name: 'signup', - test: module => !!(module.resource && module.resource.includes('/ui/components/SignUp')), - }, - common: { - minChunks: 1, - name: 'ui-common', - priority: -20, - test: module => !!(module.resource && !module.resource.includes('/ui/components')), - }, defaultVendors: { minChunks: 1, test: /[\\/]node_modules[\\/]/, @@ -158,116 +145,6 @@ const common = ({ mode, variant, disableRHC = false }) => { }; }; -/** @type { () => (import('@rspack/core').RuleSetRule) } */ -const svgLoader = () => { - return { - test: /\.svg$/, - resolve: { - fullySpecified: false, - }, - use: { - loader: '@svgr/webpack', - options: { - svgo: true, - svgoConfig: { - floatPrecision: 3, - transformPrecision: 1, - plugins: ['preset-default', 'removeDimensions', 'removeStyleElement'], - }, - }, - }, - }; -}; - -/** @type { (opts?: { targets?: string, useCoreJs?: boolean }) => (import('@rspack/core').RuleSetRule[]) } */ -const typescriptLoaderProd = ( - { targets = packageJSON.browserslist, useCoreJs = false } = { targets: packageJSON.browserslist, useCoreJs: false }, -) => { - return [ - { - test: /\.(jsx?|tsx?)$/, - exclude: /node_modules/, - use: { - loader: 'builtin:swc-loader', - options: { - env: { - targets, - ...(useCoreJs - ? { - mode: 'usage', - coreJs: require('core-js/package.json').version, - } - : {}), - }, - jsc: { - parser: { - syntax: 'typescript', - tsx: true, - }, - externalHelpers: true, - transform: { - react: { - runtime: 'automatic', - importSource: '@emotion/react', - development: false, - refresh: false, - }, - }, - }, - }, - }, - }, - { - test: /\.m?js$/, - exclude: /node_modules[\\/]core-js/, - use: { - loader: 'builtin:swc-loader', - options: { - env: { - targets, - ...(useCoreJs - ? { - mode: 'usage', - coreJs: '3.41.0', - } - : {}), - }, - isModule: 'unknown', - }, - }, - }, - ]; -}; - -/** @type { () => (import('@rspack/core').RuleSetRule[]) } */ -const typescriptLoaderDev = () => { - return [ - { - test: /\.(jsx?|tsx?)$/, - exclude: /node_modules/, - loader: 'builtin:swc-loader', - options: { - jsc: { - target: 'esnext', - parser: { - syntax: 'typescript', - tsx: true, - }, - externalHelpers: true, - transform: { - react: { - runtime: 'automatic', - importSource: '@emotion/react', - development: true, - refresh: true, - }, - }, - }, - }, - }, - ]; -}; - /** * Used for production builds that have dynamicly loaded chunks. * @type { (opts?: { targets?: string, useCoreJs?: boolean }) => (import('@rspack/core').Configuration) } @@ -619,7 +496,7 @@ const devConfig = ({ mode, env }) => { cache: true, experiments: { cache: { - type: 'persistent', + type: 'memory', }, }, }; diff --git a/packages/clerk-js/src/core/__tests__/clerk.test.ts b/packages/clerk-js/src/core/__tests__/clerk.test.ts index 1cccffbbcd8..63405363870 100644 --- a/packages/clerk-js/src/core/__tests__/clerk.test.ts +++ b/packages/clerk-js/src/core/__tests__/clerk.test.ts @@ -246,100 +246,6 @@ describe('Clerk singleton', () => { await sut.setActive({ session: mockSession as any as ActiveSessionResource }); }); - it('calls __unstable__onAfterSetActive after beforeEmit and session.touch', async () => { - const beforeEmitMock = vi.fn(); - mockSession.touch.mockReturnValueOnce(Promise.resolve()); - mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); - - (window as any).__unstable__onAfterSetActive = () => { - expect(mockSession.touch).toHaveBeenCalled(); - expect(beforeEmitMock).toHaveBeenCalled(); - }; - - const sut = new Clerk(productionPublishableKey); - await sut.load(); - await sut.setActive({ session: mockSession as any as ActiveSessionResource, beforeEmit: beforeEmitMock }); - }); - - // TODO: @dimkl include set transitive state - it('calls session.touch -> set cookie -> before emit with touched session on session switch', async () => { - const mockSession2 = { - id: '2', - remove: vi.fn(), - status: 'active', - user: {}, - touch: vi.fn(), - getToken: vi.fn(), - }; - mockClientFetch.mockReturnValue( - Promise.resolve({ - signedInSessions: [mockSession, mockSession2], - }), - ); - - const sut = new Clerk(productionPublishableKey); - await sut.load(); - - const executionOrder: string[] = []; - mockSession2.touch.mockImplementationOnce(() => { - sut.session = mockSession2 as any; - executionOrder.push('session.touch'); - return Promise.resolve(); - }); - mockSession2.getToken.mockImplementation(() => { - executionOrder.push('set cookie'); - return 'mocked-token-2'; - }); - const beforeEmitMock = vi.fn().mockImplementationOnce(() => { - executionOrder.push('before emit'); - return Promise.resolve(); - }); - - await sut.setActive({ session: mockSession2 as any as ActiveSessionResource, beforeEmit: beforeEmitMock }); - - await waitFor(() => { - expect(executionOrder).toEqual(['session.touch', 'set cookie', 'before emit']); - expect(mockSession2.touch).toHaveBeenCalled(); - expect(mockSession2.getToken).toHaveBeenCalled(); - expect(beforeEmitMock).toHaveBeenCalledWith(mockSession2); - expect(sut.session).toMatchObject(mockSession2); - }); - }); - - // TODO: @dimkl include set transitive state - it('calls with lastActiveOrganizationId session.touch -> set cookie -> before emit -> set accessors with touched session on organization switch', async () => { - mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockSession] })); - const sut = new Clerk(productionPublishableKey); - await sut.load(); - - const executionOrder: string[] = []; - mockSession.touch.mockImplementationOnce(() => { - sut.session = mockSession as any; - executionOrder.push('session.touch'); - return Promise.resolve(); - }); - mockSession.getToken.mockImplementation(() => { - executionOrder.push('set cookie'); - return 'mocked-token'; - }); - - const beforeEmitMock = vi.fn().mockImplementationOnce(() => { - executionOrder.push('before emit'); - return Promise.resolve(); - }); - - await sut.setActive({ organization: { id: 'org_id' } as Organization, beforeEmit: beforeEmitMock }); - - await waitFor(() => { - expect(executionOrder).toEqual(['session.touch', 'set cookie', 'before emit']); - expect(mockSession.touch).toHaveBeenCalled(); - expect(mockSession.getToken).toHaveBeenCalled(); - expect((mockSession as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual('org_id'); - expect(beforeEmitMock).toHaveBeenCalledWith(mockSession); - expect(sut.session).toMatchObject(mockSession); - }); - }); - it('sets active organization by slug', async () => { const mockSession2 = { id: '1', @@ -465,24 +371,16 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ standardBrowser: false }); - const executionOrder: string[] = []; mockSession.touch.mockImplementationOnce(() => { sut.session = mockSession as any; - executionOrder.push('session.touch'); - return Promise.resolve(); - }); - const beforeEmitMock = vi.fn().mockImplementationOnce(() => { - executionOrder.push('before emit'); return Promise.resolve(); }); - await sut.setActive({ organization: { id: 'org_id' } as Organization, beforeEmit: beforeEmitMock }); + await sut.setActive({ organization: { id: 'org_id' } as Organization }); - expect(executionOrder).toEqual(['session.touch', 'before emit']); expect(mockSession.touch).toHaveBeenCalled(); expect((mockSession as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual('org_id'); expect(mockSession.getToken).toHaveBeenCalled(); - expect(beforeEmitMock).toHaveBeenCalledWith(mockSession); expect(sut.session).toMatchObject(mockSession); }); }); diff --git a/packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.test.ts b/packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.test.ts index 889cca10513..1f48f4e4ee0 100644 --- a/packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.test.ts +++ b/packages/clerk-js/src/core/auth/cookies/__tests__/clientUat.test.ts @@ -1,15 +1,15 @@ import { createCookieHandler } from '@clerk/shared/cookie'; import { addYears } from '@clerk/shared/date'; +import { inCrossOriginIframe } from '@clerk/shared/internal/clerk-js/runtime'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { inCrossOriginIframe } from '../../../../utils'; import { getCookieDomain } from '../../getCookieDomain'; import { getSecureAttribute } from '../../getSecureAttribute'; import { createClientUatCookie } from '../clientUat'; vi.mock('@clerk/shared/cookie'); vi.mock('@clerk/shared/date'); -vi.mock('../../../../utils'); +vi.mock('@clerk/shared/internal/clerk-js/runtime'); vi.mock('../../getCookieDomain'); vi.mock('../../getSecureAttribute'); diff --git a/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts b/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts index 6248d78b9eb..d51bf732de9 100644 --- a/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts +++ b/packages/clerk-js/src/core/auth/cookies/__tests__/session.test.ts @@ -1,14 +1,14 @@ import { createCookieHandler } from '@clerk/shared/cookie'; import { addYears } from '@clerk/shared/date'; +import { inCrossOriginIframe } from '@clerk/shared/internal/clerk-js/runtime'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { inCrossOriginIframe } from '../../../../utils'; import { getSecureAttribute } from '../../getSecureAttribute'; import { createSessionCookie } from '../session'; vi.mock('@clerk/shared/cookie'); vi.mock('@clerk/shared/date'); -vi.mock('../../../../utils'); +vi.mock('@clerk/shared/internal/clerk-js/runtime'); vi.mock('../../getSecureAttribute'); describe('createSessionCookie', () => { diff --git a/packages/clerk-js/src/core/auth/cookies/clientUat.ts b/packages/clerk-js/src/core/auth/cookies/clientUat.ts index a6d11bdd5a7..20a620938b8 100644 --- a/packages/clerk-js/src/core/auth/cookies/clientUat.ts +++ b/packages/clerk-js/src/core/auth/cookies/clientUat.ts @@ -1,9 +1,9 @@ import { createCookieHandler } from '@clerk/shared/cookie'; import { addYears } from '@clerk/shared/date'; +import { inCrossOriginIframe } from '@clerk/shared/internal/clerk-js/runtime'; import { getSuffixedCookieName } from '@clerk/shared/keys'; import type { ClientResource } from '@clerk/shared/types'; -import { inCrossOriginIframe } from '../../../utils'; import { getCookieDomain } from '../getCookieDomain'; import { getSecureAttribute } from '../getSecureAttribute'; diff --git a/packages/clerk-js/src/core/auth/cookies/devBrowser.ts b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts index 47edda5fadd..ab3f66eb6d4 100644 --- a/packages/clerk-js/src/core/auth/cookies/devBrowser.ts +++ b/packages/clerk-js/src/core/auth/cookies/devBrowser.ts @@ -1,9 +1,9 @@ import { createCookieHandler } from '@clerk/shared/cookie'; import { addYears } from '@clerk/shared/date'; import { DEV_BROWSER_JWT_KEY } from '@clerk/shared/devBrowser'; +import { inCrossOriginIframe } from '@clerk/shared/internal/clerk-js/runtime'; import { getSuffixedCookieName } from '@clerk/shared/keys'; -import { inCrossOriginIframe } from '../../../utils'; import { getSecureAttribute } from '../getSecureAttribute'; export type DevBrowserCookieHandler = { @@ -12,10 +12,10 @@ export type DevBrowserCookieHandler = { remove: () => void; }; -const getCookieAttributes = (): { sameSite: string; secure: boolean } => { +const getCookieAttributes = () => { const sameSite = inCrossOriginIframe() ? 'None' : 'Lax'; const secure = getSecureAttribute(sameSite); - return { sameSite, secure }; + return { sameSite, secure } as const; }; /** diff --git a/packages/clerk-js/src/core/auth/cookies/session.ts b/packages/clerk-js/src/core/auth/cookies/session.ts index 209755d96f4..e4362a2522d 100644 --- a/packages/clerk-js/src/core/auth/cookies/session.ts +++ b/packages/clerk-js/src/core/auth/cookies/session.ts @@ -1,8 +1,8 @@ import { createCookieHandler } from '@clerk/shared/cookie'; import { addYears } from '@clerk/shared/date'; +import { inCrossOriginIframe } from '@clerk/shared/internal/clerk-js/runtime'; import { getSuffixedCookieName } from '@clerk/shared/keys'; -import { inCrossOriginIframe } from '../../../utils'; import { getSecureAttribute } from '../getSecureAttribute'; const SESSION_COOKIE_NAME = '__session'; @@ -13,11 +13,11 @@ export type SessionCookieHandler = { get: () => string | undefined; }; -const getCookieAttributes = (): { sameSite: string; secure: boolean; partitioned: boolean } => { +const getCookieAttributes = () => { const sameSite = __BUILD_VARIANT_CHIPS__ ? 'None' : inCrossOriginIframe() ? 'None' : 'Lax'; const secure = getSecureAttribute(sameSite); const partitioned = __BUILD_VARIANT_CHIPS__ && secure; - return { sameSite, secure, partitioned }; + return { sameSite, secure, partitioned } as const; }; /** diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 6353d4c09d3..8501c15b759 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -1,6 +1,5 @@ import { inBrowser as inClientSide, isValidBrowserOnline } from '@clerk/shared/browser'; import { clerkEvents, createClerkEventBus } from '@clerk/shared/clerkEventBus'; -import { deprecated } from '@clerk/shared/deprecated'; import { ClerkRuntimeError, EmailLinkError, @@ -9,6 +8,30 @@ import { isClerkAPIResponseError, isClerkRuntimeError, } from '@clerk/shared/error'; +import { + disabledAllAPIKeysFeatures, + disabledAllBillingFeatures, + disabledOrganizationAPIKeysFeature, + disabledOrganizationsFeature, + disabledUserAPIKeysFeature, + isSignedInAndSingleSessionModeEnabled, + noOrganizationExists, + noUserExists, +} from '@clerk/shared/internal/clerk-js/componentGuards'; +import { + CLERK_SATELLITE_URL, + CLERK_SUFFIXED_COOKIES, + CLERK_SYNCED, + ERROR_CODES, +} from '@clerk/shared/internal/clerk-js/constants'; +import { RedirectUrls } from '@clerk/shared/internal/clerk-js/redirectUrls'; +import { + getTaskEndpoint, + navigateIfTaskExists, + warnMissingPendingTaskHandlers, +} from '@clerk/shared/internal/clerk-js/sessionTasks'; +import { warnings } from '@clerk/shared/internal/clerk-js/warnings'; +import { windowNavigate } from '@clerk/shared/internal/clerk-js/windowNavigate'; import { parsePublishableKey } from '@clerk/shared/keys'; import { logger } from '@clerk/shared/logger'; import { CLERK_NETLIFY_CACHE_BUST_PARAM } from '@clerk/shared/netlifyCacheHandler'; @@ -20,7 +43,6 @@ import { TelemetryCollector, } from '@clerk/shared/telemetry'; import type { - __experimental_CheckoutInstance, __experimental_CheckoutOptions, __internal_CheckoutProps, __internal_OAuthConsentProps, @@ -35,6 +57,7 @@ import type { AuthenticateWithMetamaskParams, AuthenticateWithOKXWalletParams, BillingNamespace, + CheckoutSignalValue, Clerk as ClerkInterface, ClerkAPIError, ClerkAuthenticateWithWeb3Params, @@ -92,13 +115,14 @@ import type { WaitlistResource, Web3Provider, } from '@clerk/shared/types'; +import type { ClerkUi } from '@clerk/shared/ui'; import { addClerkPrefix, isAbsoluteUrl, stripScheme } from '@clerk/shared/url'; import { allSettled, handleValueOrFn, noop } from '@clerk/shared/utils'; import type { QueryClient } from '@tanstack/query-core'; import { debugLogger, initDebugLogger } from '@/utils/debug'; +import { ModuleManager } from '@/utils/moduleManager'; -import type { MountComponentRenderer } from '../ui/Components'; import { ALLOWED_PROTOCOLS, buildURL, @@ -106,18 +130,8 @@ import { createAllowedRedirectOrigins, createBeforeUnloadTracker, createPageLifecycle, - disabledAllAPIKeysFeatures, - disabledAllBillingFeatures, - disabledOrganizationAPIKeysFeature, - disabledOrganizationsFeature, - disabledUserAPIKeysFeature, errorThrower, - generateSignatureWithBase, - generateSignatureWithCoinbaseWallet, - generateSignatureWithMetamask, - generateSignatureWithOKXWallet, getClerkQueryParam, - getWeb3Identifier, hasExternalAccountSignUpError, inActiveBrowserTab, inBrowser, @@ -125,22 +139,15 @@ import { isError, isOrganizationId, isRedirectForFAPIInitiatedFlow, - isSignedInAndSingleSessionModeEnabled, - noOrganizationExists, - noUserExists, - processCssLayerNameExtraction, removeClerkQueryParam, requiresUserInput, stripOrigin, - windowNavigate, + web3, } from '../utils'; -import { assertNoLegacyProp } from '../utils/assertNoLegacyProp'; import { CLERK_ENVIRONMENT_STORAGE_ENTRY, SafeLocalStorage } from '../utils/localStorage'; import { memoizeListenerCallback } from '../utils/memoizeStateListenerCallback'; -import { RedirectUrls } from '../utils/redirectUrls'; import { AuthCookieService } from './auth/AuthCookieService'; import { CaptchaHeartbeat } from './auth/CaptchaHeartbeat'; -import { CLERK_SATELLITE_URL, CLERK_SUFFIXED_COOKIES, CLERK_SYNCED, ERROR_CODES } from './constants'; import { clerkErrorInitFailed, clerkInvalidSignInUrlFormat, @@ -160,9 +167,7 @@ import { Billing } from './modules/billing'; import { createCheckoutInstance } from './modules/checkout/instance'; import { Protect } from './protect'; import { BaseResource, Client, Environment, Organization, Waitlist } from './resources/internal'; -import { getTaskEndpoint, navigateIfTaskExists, warnMissingPendingTaskHandlers } from './sessionTasks'; import { State } from './state'; -import { warnings } from './warnings'; type SetActiveHook = (intent?: 'sign-out') => void | Promise; @@ -199,8 +204,6 @@ const defaultOptions: ClerkOptions = { }; export class Clerk implements ClerkInterface { - public static mountComponentRenderer?: MountComponentRenderer; - public static version: string = __PKG_VERSION__; public static sdkMetadata: SDKMetadata = { name: __PKG_NAME__, @@ -231,7 +234,7 @@ export class Clerk implements ClerkInterface { #protect?: Protect; #captchaHeartbeat?: CaptchaHeartbeat; #broadcastChannel: BroadcastChannel | null = null; - #componentControls?: ReturnType | null; + #clerkUi?: Promise; //@ts-expect-error with being undefined even though it's not possible - related to issue with ts and error thrower #fapiClient: FapiClient; #instanceType?: InstanceType; @@ -376,9 +379,9 @@ export class Clerk implements ClerkInterface { return Clerk._apiKeys; } - __experimental_checkout(options: __experimental_CheckoutOptions): __experimental_CheckoutInstance { + __experimental_checkout(options: __experimental_CheckoutOptions): CheckoutSignalValue { if (!this._checkout) { - this._checkout = params => createCheckoutInstance(this, params); + this._checkout = (params: any) => createCheckoutInstance(this, params); } return this._checkout(options); } @@ -451,6 +454,19 @@ export class Clerk implements ClerkInterface { this.#options = this.#initOptions(options); + // Initialize ClerkUi if it was provided + if (this.#options.clerkUiCtor) { + this.#clerkUi = Promise.resolve(this.#options.clerkUiCtor).then( + ClerkUI => + new ClerkUI( + () => this, + () => this.environment, + this.#options, + new ModuleManager(), + ), + ); + } + // In development mode, if custom router options are provided, warn if both routerPush and routerReplace are not provided if ( this.#instanceType === 'development' && @@ -475,8 +491,6 @@ export class Clerk implements ClerkInterface { this.#emit(); }); - assertNoLegacyProp(this.#options); - if (this.#options.sdkMetadata) { Clerk.sdkMetadata = this.#options.sdkMetadata; } @@ -621,22 +635,18 @@ export class Clerk implements ClerkInterface { }; public openGoogleOneTap = (props?: GoogleOneTapProps): void => { + this.assertComponentsReady(this.#clerkUi); const component = 'GoogleOneTap'; - this.assertComponentsReady(this.#componentControls); - void this.#componentControls - .ensureMounted({ preloadHint: component }) - .then(controls => controls.openModal('googleOneTap', props || {})); + void this.#clerkUi.then(ui => ui.ensureMounted()).then(controls => controls.openModal('googleOneTap', props || {})); this.telemetry?.record(eventPrebuiltComponentOpened(component, props)); }; public closeGoogleOneTap = (): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.closeModal('googleOneTap')); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.closeModal('googleOneTap')); }; public openSignIn = (props?: SignInProps): void => { - this.assertComponentsReady(this.#componentControls); if (isSignedInAndSingleSessionModeEnabled(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotOpenSignInOrSignUp, { @@ -645,22 +655,19 @@ export class Clerk implements ClerkInterface { } return; } + this.assertComponentsReady(this.#clerkUi); const component = 'SignIn'; - void this.#componentControls - .ensureMounted({ preloadHint: component }) - .then(controls => controls.openModal('signIn', props || {})); + void this.#clerkUi.then(ui => ui.ensureMounted()).then(controls => controls.openModal('signIn', props || {})); const additionalData = { withSignUp: props?.withSignUp ?? this.#isCombinedSignInOrUpFlow() }; this.telemetry?.record(eventPrebuiltComponentOpened(component, props, additionalData)); }; public closeSignIn = (): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.closeModal('signIn')); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.closeModal('signIn')); }; public __internal_openCheckout = (props?: __internal_CheckoutProps): void => { - this.assertComponentsReady(this.#componentControls); if (disabledAllBillingFeatures(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyBillingComponent('Checkout'), { @@ -678,18 +685,15 @@ export class Clerk implements ClerkInterface { return; } - void this.#componentControls - .ensureMounted({ preloadHint: 'Checkout' }) - .then(controls => controls.openDrawer('checkout', props || {})); + this.assertComponentsReady(this.#clerkUi); + void this.#clerkUi.then(ui => ui.ensureMounted()).then(controls => controls.openDrawer('checkout', props || {})); }; public __internal_closeCheckout = (): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('checkout')); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.closeDrawer('checkout')); }; public __internal_openPlanDetails = (props: __internal_PlanDetailsProps): void => { - this.assertComponentsReady(this.#componentControls); if (disabledAllBillingFeatures(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyBillingComponent('PlanDetails'), { @@ -698,33 +702,29 @@ export class Clerk implements ClerkInterface { } return; } + this.assertComponentsReady(this.#clerkUi); const component = 'PlanDetails'; - void this.#componentControls - .ensureMounted({ preloadHint: component }) - .then(controls => controls.openDrawer('planDetails', props || {})); + void this.#clerkUi.then(ui => ui.ensureMounted()).then(controls => controls.openDrawer('planDetails', props || {})); this.telemetry?.record(eventPrebuiltComponentOpened(component, props)); }; public __internal_closePlanDetails = (): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('planDetails')); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.closeDrawer('planDetails')); }; public __internal_openSubscriptionDetails = (props?: __internal_SubscriptionDetailsProps): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls - .ensureMounted({ preloadHint: 'SubscriptionDetails' }) + this.assertComponentsReady(this.#clerkUi); + void this.#clerkUi + .then(ui => ui.ensureMounted()) .then(controls => controls.openDrawer('subscriptionDetails', props || {})); }; public __internal_closeSubscriptionDetails = (): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.closeDrawer('subscriptionDetails')); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.closeDrawer('subscriptionDetails')); }; public __internal_openReverification = (props?: __internal_UserVerificationModalProps): void => { - this.assertComponentsReady(this.#componentControls); if (noUserExists(this)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotOpenUserProfile, { @@ -733,30 +733,26 @@ export class Clerk implements ClerkInterface { } return; } - void this.#componentControls - .ensureMounted({ preloadHint: 'UserVerification' }) + this.assertComponentsReady(this.#clerkUi); + void this.#clerkUi + .then(ui => ui.ensureMounted()) .then(controls => controls.openModal('userVerification', props || {})); this.telemetry?.record(eventPrebuiltComponentOpened(`UserVerification`, props)); }; public __internal_closeReverification = (): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.closeModal('userVerification')); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.closeModal('userVerification')); }; public __internal_openBlankCaptchaModal = (): Promise => { - this.assertComponentsReady(this.#componentControls); - return this.#componentControls - .ensureMounted({ preloadHint: 'BlankCaptchaModal' }) - .then(controls => controls.openModal('blankCaptcha', {})); + this.assertComponentsReady(this.#clerkUi); + return this.#clerkUi.then(ui => ui.ensureMounted()).then(controls => controls.openModal('blankCaptcha', {})); }; public __internal_closeBlankCaptchaModal = (): Promise => { - this.assertComponentsReady(this.#componentControls); - return this.#componentControls - .ensureMounted({ preloadHint: 'BlankCaptchaModal' }) - .then(controls => controls.closeModal('blankCaptcha')); + this.assertComponentsReady(this.#clerkUi); + return this.#clerkUi.then(ui => ui.ensureMounted()).then(controls => controls.closeModal('blankCaptcha')); }; public __internal_loadStripeJs = async () => { @@ -770,7 +766,6 @@ export class Clerk implements ClerkInterface { }; public openSignUp = (props?: SignUpProps): void => { - this.assertComponentsReady(this.#componentControls); if (isSignedInAndSingleSessionModeEnabled(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotOpenSignInOrSignUp, { @@ -779,20 +774,17 @@ export class Clerk implements ClerkInterface { } return; } - void this.#componentControls - .ensureMounted({ preloadHint: 'SignUp' }) - .then(controls => controls.openModal('signUp', props || {})); + this.assertComponentsReady(this.#clerkUi); + void this.#clerkUi.then(ui => ui.ensureMounted()).then(controls => controls.openModal('signUp', props || {})); this.telemetry?.record(eventPrebuiltComponentOpened('SignUp', props)); }; public closeSignUp = (): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.closeModal('signUp')); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.closeModal('signUp')); }; public openUserProfile = (props?: UserProfileProps): void => { - this.assertComponentsReady(this.#componentControls); if (noUserExists(this)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotOpenUserProfile, { @@ -801,21 +793,18 @@ export class Clerk implements ClerkInterface { } return; } - void this.#componentControls - .ensureMounted({ preloadHint: 'UserProfile' }) - .then(controls => controls.openModal('userProfile', props || {})); + this.assertComponentsReady(this.#clerkUi); + void this.#clerkUi.then(ui => ui.ensureMounted()).then(controls => controls.openModal('userProfile', props || {})); const additionalData = (props?.customPages?.length || 0) > 0 ? { customPages: true } : undefined; this.telemetry?.record(eventPrebuiltComponentOpened('UserProfile', props, additionalData)); }; public closeUserProfile = (): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.closeModal('userProfile')); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.closeModal('userProfile')); }; public openOrganizationProfile = (props?: OrganizationProfileProps): void => { - this.assertComponentsReady(this.#componentControls); if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationProfile'), { @@ -832,20 +821,19 @@ export class Clerk implements ClerkInterface { } return; } - void this.#componentControls - .ensureMounted({ preloadHint: 'OrganizationProfile' }) + this.assertComponentsReady(this.#clerkUi); + void this.#clerkUi + .then(ui => ui.ensureMounted()) .then(controls => controls.openModal('organizationProfile', props || {})); this.telemetry?.record(eventPrebuiltComponentOpened('OrganizationProfile', props)); }; public closeOrganizationProfile = (): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.closeModal('organizationProfile')); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.closeModal('organizationProfile')); }; public openCreateOrganization = (props?: CreateOrganizationProps): void => { - this.assertComponentsReady(this.#componentControls); if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('CreateOrganization'), { @@ -854,107 +842,94 @@ export class Clerk implements ClerkInterface { } return; } - void this.#componentControls - .ensureMounted({ preloadHint: 'CreateOrganization' }) + this.assertComponentsReady(this.#clerkUi); + void this.#clerkUi + .then(ui => ui.ensureMounted()) .then(controls => controls.openModal('createOrganization', props || {})); this.telemetry?.record(eventPrebuiltComponentOpened('CreateOrganization', props)); }; public closeCreateOrganization = (): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.closeModal('createOrganization')); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.closeModal('createOrganization')); }; public openWaitlist = (props?: WaitlistProps): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls - .ensureMounted({ preloadHint: 'Waitlist' }) - .then(controls => controls.openModal('waitlist', props || {})); + this.assertComponentsReady(this.#clerkUi); + void this.#clerkUi.then(ui => ui.ensureMounted()).then(controls => controls.openModal('waitlist', props || {})); this.telemetry?.record(eventPrebuiltComponentOpened('Waitlist', props)); }; public closeWaitlist = (): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.closeModal('waitlist')); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.closeModal('waitlist')); }; public mountSignIn = (node: HTMLDivElement, props?: SignInProps): void => { - this.assertComponentsReady(this.#componentControls); + this.assertComponentsReady(this.#clerkUi); const component = 'SignIn'; - void this.#componentControls.ensureMounted({ preloadHint: component }).then(controls => - controls.mountComponent({ - name: component, - appearanceKey: 'signIn', - node, - props, - }), - ); + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'signIn', + node, + props, + }), + ); const additionalData = { withSignUp: props?.withSignUp ?? this.#isCombinedSignInOrUpFlow() }; this.telemetry?.record(eventPrebuiltComponentMounted(component, props, additionalData)); }; public unmountSignIn = (node: HTMLDivElement): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => - controls.unmountComponent({ - node, - }), - ); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public mountUserAvatar = (node: HTMLDivElement, props?: UserAvatarProps): void => { - this.assertComponentsReady(this.#componentControls); + this.assertComponentsReady(this.#clerkUi); const component = 'UserAvatar'; - void this.#componentControls.ensureMounted({ preloadHint: component }).then(controls => - controls.mountComponent({ - name: component, - appearanceKey: 'userAvatar', - node, - props, - }), - ); + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'userAvatar', + node, + props, + }), + ); this.telemetry?.record(eventPrebuiltComponentMounted(component, props)); }; public unmountUserAvatar = (node: HTMLDivElement): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => - controls.unmountComponent({ - node, - }), - ); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public mountSignUp = (node: HTMLDivElement, props?: SignUpProps): void => { - this.assertComponentsReady(this.#componentControls); + this.assertComponentsReady(this.#clerkUi); const component = 'SignUp'; - void this.#componentControls.ensureMounted({ preloadHint: component }).then(controls => - controls.mountComponent({ - name: component, - appearanceKey: 'signUp', - node, - props, - }), - ); + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'signUp', + node, + props, + }), + ); this.telemetry?.record(eventPrebuiltComponentMounted(component, props)); }; public unmountSignUp = (node: HTMLDivElement): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => - controls.unmountComponent({ - node, - }), - ); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public mountUserProfile = (node: HTMLDivElement, props?: UserProfileProps): void => { - this.assertComponentsReady(this.#componentControls); if (noUserExists(this)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderComponentWhenUserDoesNotExist, { @@ -963,31 +938,28 @@ export class Clerk implements ClerkInterface { } return; } + this.assertComponentsReady(this.#clerkUi); const component = 'UserProfile'; - void this.#componentControls.ensureMounted({ preloadHint: component }).then(controls => - controls.mountComponent({ - name: component, - appearanceKey: 'userProfile', - node, - props, - }), - ); + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'userProfile', + node, + props, + }), + ); const additionalData = (props?.customPages?.length || 0) > 0 ? { customPages: true } : undefined; this.telemetry?.record(eventPrebuiltComponentMounted(component, props, additionalData)); }; public unmountUserProfile = (node: HTMLDivElement): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => - controls.unmountComponent({ - node, - }), - ); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public mountOrganizationProfile = (node: HTMLDivElement, props?: OrganizationProfileProps) => { - this.assertComponentsReady(this.#componentControls); if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationProfile'), { @@ -1005,29 +977,27 @@ export class Clerk implements ClerkInterface { } return; } - void this.#componentControls.ensureMounted({ preloadHint: 'OrganizationProfile' }).then(controls => - controls.mountComponent({ - name: 'OrganizationProfile', - appearanceKey: 'userProfile', - node, - props, - }), - ); + this.assertComponentsReady(this.#clerkUi); + const component = 'OrganizationProfile'; + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'userProfile', + node, + props, + }), + ); - this.telemetry?.record(eventPrebuiltComponentMounted('OrganizationProfile', props)); + this.telemetry?.record(eventPrebuiltComponentMounted(component, props)); }; public unmountOrganizationProfile = (node: HTMLDivElement) => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => - controls.unmountComponent({ - node, - }), - ); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public mountCreateOrganization = (node: HTMLDivElement, props?: CreateOrganizationProps) => { - this.assertComponentsReady(this.#componentControls); if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('CreateOrganization'), { @@ -1036,29 +1006,27 @@ export class Clerk implements ClerkInterface { } return; } - void this.#componentControls?.ensureMounted({ preloadHint: 'CreateOrganization' }).then(controls => - controls.mountComponent({ - name: 'CreateOrganization', - appearanceKey: 'createOrganization', - node, - props, - }), - ); + this.assertComponentsReady(this.#clerkUi); + const component = 'CreateOrganization'; + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'createOrganization', + node, + props, + }), + ); - this.telemetry?.record(eventPrebuiltComponentMounted('CreateOrganization', props)); + this.telemetry?.record(eventPrebuiltComponentMounted(component, props)); }; public unmountCreateOrganization = (node: HTMLDivElement) => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls?.ensureMounted().then(controls => - controls.unmountComponent({ - node, - }), - ); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public mountOrganizationSwitcher = (node: HTMLDivElement, props?: OrganizationSwitcherProps) => { - this.assertComponentsReady(this.#componentControls); if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationSwitcher'), { @@ -1067,17 +1035,21 @@ export class Clerk implements ClerkInterface { } return; } - void this.#componentControls?.ensureMounted({ preloadHint: 'OrganizationSwitcher' }).then(controls => - controls.mountComponent({ - name: 'OrganizationSwitcher', - appearanceKey: 'organizationSwitcher', - node, - props, - }), - ); + this.assertComponentsReady(this.#clerkUi); + const component = 'OrganizationSwitcher'; + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'organizationSwitcher', + node, + props, + }), + ); this.telemetry?.record( - eventPrebuiltComponentMounted('OrganizationSwitcher', { + eventPrebuiltComponentMounted(component, { ...props, forceOrganizationSelection: this.environment?.organizationSettings.forceOrganizationSelection, }), @@ -1085,19 +1057,15 @@ export class Clerk implements ClerkInterface { }; public unmountOrganizationSwitcher = (node: HTMLDivElement): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls?.ensureMounted().then(controls => controls.unmountComponent({ node })); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public __experimental_prefetchOrganizationSwitcher = () => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls - ?.ensureMounted({ preloadHint: 'OrganizationSwitcher' }) - .then(controls => controls.prefetch('organizationSwitcher')); + this.assertComponentsReady(this.#clerkUi); + void this.#clerkUi.then(ui => ui.ensureMounted()).then(controls => controls.prefetch('organizationSwitcher')); }; public mountOrganizationList = (node: HTMLDivElement, props?: OrganizationListProps) => { - this.assertComponentsReady(this.#componentControls); if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('OrganizationList'), { @@ -1106,17 +1074,21 @@ export class Clerk implements ClerkInterface { } return; } - void this.#componentControls?.ensureMounted({ preloadHint: 'OrganizationList' }).then(controls => - controls.mountComponent({ - name: 'OrganizationList', - appearanceKey: 'organizationList', - node, - props, - }), - ); + this.assertComponentsReady(this.#clerkUi); + const component = 'OrganizationList'; + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'organizationList', + node, + props, + }), + ); this.telemetry?.record( - eventPrebuiltComponentMounted('OrganizationList', { + eventPrebuiltComponentMounted(component, { ...props, forceOrganizationSelection: this.environment?.organizationSettings.forceOrganizationSelection, }), @@ -1124,55 +1096,57 @@ export class Clerk implements ClerkInterface { }; public unmountOrganizationList = (node: HTMLDivElement): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls?.ensureMounted().then(controls => controls.unmountComponent({ node })); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public mountUserButton = (node: HTMLDivElement, props?: UserButtonProps) => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls?.ensureMounted({ preloadHint: 'UserButton' }).then(controls => - controls.mountComponent({ - name: 'UserButton', - appearanceKey: 'userButton', - node, - props, - }), - ); + this.assertComponentsReady(this.#clerkUi); + const component = 'UserButton'; + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'userButton', + node, + props, + }), + ); const additionalData = { ...(props?.customMenuItems?.length || 0 > 0 ? { customItems: true } : undefined), ...(props?.__experimental_asStandalone ? { standalone: true } : undefined), }; - this.telemetry?.record(eventPrebuiltComponentMounted('UserButton', props, additionalData)); + this.telemetry?.record(eventPrebuiltComponentMounted(component, props, additionalData)); }; public unmountUserButton = (node: HTMLDivElement): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls?.ensureMounted().then(controls => controls.unmountComponent({ node })); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public mountWaitlist = (node: HTMLDivElement, props?: WaitlistProps) => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls?.ensureMounted({ preloadHint: 'Waitlist' }).then(controls => - controls.mountComponent({ - name: 'Waitlist', - appearanceKey: 'waitlist', - node, - props, - }), - ); + this.assertComponentsReady(this.#clerkUi); + const component = 'Waitlist'; + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'waitlist', + node, + props, + }), + ); - this.telemetry?.record(eventPrebuiltComponentMounted('Waitlist', props)); + this.telemetry?.record(eventPrebuiltComponentMounted(component, props)); }; public unmountWaitlist = (node: HTMLDivElement): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls?.ensureMounted().then(controls => controls.unmountComponent({ node })); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public mountPricingTable = (node: HTMLDivElement, props?: PricingTableProps): void => { - this.assertComponentsReady(this.#componentControls); if (disabledAllBillingFeatures(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyBillingComponent('PricingTable'), { @@ -1181,50 +1155,43 @@ export class Clerk implements ClerkInterface { } return; } - // Temporary backward compatibility for legacy prop: `forOrganizations`. Will be removed in the coming minor release. - const nextProps = { ...(props as any) } as PricingTableProps & { forOrganizations?: boolean }; - if (typeof (props as any)?.forOrganizations !== 'undefined') { - logger.warnOnce( - 'Clerk: [IMPORTANT] prop `forOrganizations` is deprecated and will be removed in the coming minors. Use `for="organization"` instead.', + this.assertComponentsReady(this.#clerkUi); + const component = 'PricingTable'; + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'pricingTable', + node, + props, + }), ); - } - void this.#componentControls.ensureMounted({ preloadHint: 'PricingTable' }).then(controls => - controls.mountComponent({ - name: 'PricingTable', - appearanceKey: 'pricingTable', - node, - props: nextProps, - }), - ); - - this.telemetry?.record(eventPrebuiltComponentMounted('PricingTable', nextProps)); + this.telemetry?.record(eventPrebuiltComponentMounted(component, props)); }; public unmountPricingTable = (node: HTMLDivElement): void => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => - controls.unmountComponent({ - node, - }), - ); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public __internal_mountOAuthConsent = (node: HTMLDivElement, props?: __internal_OAuthConsentProps) => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted({ preloadHint: 'OAuthConsent' }).then(controls => - controls.mountComponent({ - name: 'OAuthConsent', - appearanceKey: '__internal_oauthConsent', - node, - props, - }), - ); + this.assertComponentsReady(this.#clerkUi); + const component = 'OAuthConsent'; + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: '__internal_oauthConsent', + node, + props, + }), + ); }; public __internal_unmountOAuthConsent = (node: HTMLDivElement) => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.unmountComponent({ node })); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; /** @@ -1235,8 +1202,6 @@ export class Clerk implements ClerkInterface { * @param props Configuration parameters. */ public mountAPIKeys = (node: HTMLDivElement, props?: APIKeysProps) => { - this.assertComponentsReady(this.#componentControls); - logger.warnOnce('Clerk: component is in early access and not yet recommended for production use.'); if (disabledAllAPIKeysFeatures(this, this.environment)) { @@ -1266,16 +1231,20 @@ export class Clerk implements ClerkInterface { return; } - void this.#componentControls.ensureMounted({ preloadHint: 'APIKeys' }).then(controls => - controls.mountComponent({ - name: 'APIKeys', - appearanceKey: 'apiKeys', - node, - props, - }), - ); + this.assertComponentsReady(this.#clerkUi); + const component = 'APIKeys'; + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'apiKeys', + node, + props, + }), + ); - this.telemetry?.record(eventPrebuiltComponentMounted('APIKeys', props)); + this.telemetry?.record(eventPrebuiltComponentMounted(component, props)); }; /** @@ -1287,13 +1256,10 @@ export class Clerk implements ClerkInterface { * @param targetNode Target node to unmount the APIKeys component from. */ public unmountAPIKeys = (node: HTMLDivElement) => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.unmountComponent({ node })); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; public mountTaskChooseOrganization = (node: HTMLDivElement, props?: TaskChooseOrganizationProps) => { - this.assertComponentsReady(this.#componentControls); - if (disabledOrganizationsFeature(this, this.environment)) { if (this.#instanceType === 'development') { throw new ClerkRuntimeError(warnings.cannotRenderAnyOrganizationComponent('TaskChooseOrganization'), { @@ -1303,28 +1269,31 @@ export class Clerk implements ClerkInterface { return; } - void this.#componentControls.ensureMounted({ preloadHint: 'TaskChooseOrganization' }).then(controls => - controls.mountComponent({ - name: 'TaskChooseOrganization', - appearanceKey: 'taskChooseOrganization', - node, - props, - }), - ); + this.assertComponentsReady(this.#clerkUi); + const component = 'TaskChooseOrganization'; + void this.#clerkUi + .then(ui => ui.ensureMounted()) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'taskChooseOrganization', + node, + props, + }), + ); - this.telemetry?.record(eventPrebuiltComponentMounted('TaskChooseOrganization', props)); + this.telemetry?.record(eventPrebuiltComponentMounted(component, props)); }; public unmountTaskChooseOrganization = (node: HTMLDivElement) => { - this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted().then(controls => controls.unmountComponent({ node })); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; /** * `setActive` can be used to set the active session and/or organization. */ public setActive = async (params: SetActiveParams): Promise => { - const { organization, beforeEmit, redirectUrl, navigate: setActiveNavigate } = params; + const { organization, redirectUrl, navigate: setActiveNavigate } = params; let { session } = params; this.__internal_setActiveInProgress = true; debugLogger.debug( @@ -1429,29 +1398,18 @@ export class Clerk implements ClerkInterface { eventBus.emit(events.TokenUpdate, { token: null }); } - //2. If there's a beforeEmit, typically we're navigating. Emit the session as - // undefined, then wait for beforeEmit to complete before emitting the new session. + //2. When navigation is required we emit the session as undefined, + // then wait for navigation to finish before emitting the new session. // When undefined, neither SignedIn nor SignedOut renders, which avoids flickers or // automatic reloading when reloading shouldn't be happening. const tracker = createBeforeUnloadTracker(this.#options.standardBrowser); - if (beforeEmit) { - deprecated( - 'Clerk.setActive({beforeEmit})', - 'Use the `redirectUrl` property instead. Example `Clerk.setActive({redirectUrl:"/"})`', - ); - await tracker.track(async () => { - this.#setTransitiveState(); - await beforeEmit(newSession); - }); - } - const taskUrl = newSession?.status === 'pending' && newSession?.currentTask && this.#options.taskUrls?.[newSession?.currentTask.key]; - if (!beforeEmit && (redirectUrl || taskUrl || setActiveNavigate)) { + if (redirectUrl || taskUrl || setActiveNavigate) { await tracker.track(async () => { if (!this.client) { // Typescript is not happy because since thinks this.client might have changed to undefined because the function is asynchronous. @@ -2271,20 +2229,20 @@ export class Clerk implements ClerkInterface { const { displayConfig } = this.environment; const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider; - const identifier = await getWeb3Identifier({ provider }); + const identifier = await web3().getWeb3Identifier({ provider }); let generateSignature: (params: GenerateSignatureParams) => Promise; switch (provider) { case 'metamask': - generateSignature = generateSignatureWithMetamask; + generateSignature = web3().generateSignatureWithMetamask; break; case 'base': - generateSignature = generateSignatureWithBase; + generateSignature = web3().generateSignatureWithBase; break; case 'coinbase_wallet': - generateSignature = generateSignatureWithCoinbaseWallet; + generateSignature = web3().generateSignatureWithCoinbaseWallet; break; default: - generateSignature = generateSignatureWithOKXWallet; + generateSignature = web3().generateSignatureWithOKXWallet; break; } @@ -2448,9 +2406,10 @@ export class Clerk implements ClerkInterface { __unstable__setEnvironment = async (env: EnvironmentJSON) => { this.environment = new Environment(env); - if (Clerk.mountComponentRenderer) { - this.#componentControls = Clerk.mountComponentRenderer(this, this.environment, this.#options); - } + // TODO @nikos update + // if (Clerk.mountComponentRenderer) { + // this.#componentRenderer = Clerk.mountComponentRenderer(this, this.environment, this.#options); + // } }; __unstable__onBeforeRequest = (callback: FapiRequestCallback): void => { @@ -2474,7 +2433,7 @@ export class Clerk implements ClerkInterface { options: this.#initOptions({ ...this.#options, ..._props.options }), }; - return this.#componentControls?.ensureMounted().then(controls => controls.updateProps(props)); + return this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.updateProps(props)); }; __internal_navigateWithError(to: string, err: ClerkAPIError) { @@ -2683,23 +2642,11 @@ export class Clerk implements ClerkInterface { }); }; - const initComponents = () => { - if (Clerk.mountComponentRenderer && !this.#componentControls) { - this.#componentControls = Clerk.mountComponentRenderer( - this, - this.environment as Environment, - this.#options, - ); - } - }; - const [, clientResult] = await allSettled([initEnvironmentPromise, initClient()]); - if (clientResult.status === 'rejected') { const e = clientResult.reason; if (isError(e, 'requires_captcha')) { - initComponents(); await initClient(); } else { throw e; @@ -2711,9 +2658,6 @@ export class Clerk implements ClerkInterface { if (await this.#redirectFAPIInitiatedFlow()) { return; } - - initComponents(); - break; } catch (err) { if (isError(err, 'dev_browser_unauthenticated')) { @@ -2767,17 +2711,11 @@ export class Clerk implements ClerkInterface { this.updateClient(client); this.updateEnvironment(environment); - // TODO: Add an auth service also for non standard browsers that will poll for the __session JWT but won't use cookies - - if (Clerk.mountComponentRenderer) { - this.#componentControls = Clerk.mountComponentRenderer(this, this.environment, this.#options); - } - this.#publicEventBus.emit(clerkEvents.Status, initializationDegradedCounter > 0 ? 'degraded' : 'ready'); }; - // This is used by @clerk/clerk-expo + // This is used by @clerk/expo __internal_reloadInitialResources = async (): Promise => { const [environment, client] = await Promise.all([ Environment.getInstance().fetch({ touch: false, fetchMaxTries: 1 }), @@ -2914,23 +2852,26 @@ export class Clerk implements ClerkInterface { this.addListener(({ session }) => { const isImpersonating = !!session?.actor; if (isImpersonating) { - void this.#componentControls?.ensureMounted().then(controls => controls.mountImpersonationFab()); + void this.#clerkUi?.then(ui => ui.ensureMounted()).then(controls => controls.mountImpersonationFab()); } }); }; #handleKeylessPrompt = () => { if (this.#options.__internal_keyless_claimKeylessApplicationUrl) { - void this.#componentControls?.ensureMounted().then(controls => { - // TODO(@pantelis): Investigate if this resets existing props - controls.updateProps({ - options: { - __internal_keyless_claimKeylessApplicationUrl: this.#options.__internal_keyless_claimKeylessApplicationUrl, - __internal_keyless_copyInstanceKeysUrl: this.#options.__internal_keyless_copyInstanceKeysUrl, - __internal_keyless_dismissPrompt: this.#options.__internal_keyless_dismissPrompt, - }, + void this.#clerkUi + ?.then(ui => ui.ensureMounted()) + .then(controls => { + // TODO(@pantelis): Investigate if this resets existing props + controls.updateProps({ + options: { + __internal_keyless_claimKeylessApplicationUrl: + this.#options.__internal_keyless_claimKeylessApplicationUrl, + __internal_keyless_copyInstanceKeysUrl: this.#options.__internal_keyless_copyInstanceKeysUrl, + __internal_keyless_dismissPrompt: this.#options.__internal_keyless_dismissPrompt, + }, + }); }); - }); } }; @@ -2961,12 +2902,9 @@ export class Clerk implements ClerkInterface { return this.buildUrlWithAuth(url); }; - assertComponentsReady(controls: unknown): asserts controls is ReturnType { - if (!Clerk.mountComponentRenderer) { - throw new Error('ClerkJS was loaded without UI components.'); - } - if (!controls) { - throw new Error('ClerkJS components are not ready yet.'); + assertComponentsReady(val: unknown): asserts val is ClerkUi { + if (!val) { + throw new Error('Clerk was not loaded with Ui components'); } } @@ -2995,16 +2933,9 @@ export class Clerk implements ClerkInterface { }; #initOptions = (options?: ClerkOptions): ClerkOptions => { - const processedOptions = options ? { ...options } : {}; - - // Extract cssLayerName from baseTheme if present and move it to appearance level - if (processedOptions.appearance) { - processedOptions.appearance = processCssLayerNameExtraction(processedOptions.appearance); - } - return { ...defaultOptions, - ...processedOptions, + ...options, allowedRedirectOrigins: createAllowedRedirectOrigins( options?.allowedRedirectOrigins, this.frontendApi, diff --git a/packages/clerk-js/src/core/constants.ts b/packages/clerk-js/src/core/constants.ts index bddf6b20541..43ee8e244f7 100644 --- a/packages/clerk-js/src/core/constants.ts +++ b/packages/clerk-js/src/core/constants.ts @@ -1,57 +1,6 @@ -import type { SignUpModes } from '@clerk/shared/types'; - -// TODO: Do we still have a use for this or can we simply preserve all params? -export const PRESERVED_QUERYSTRING_PARAMS = [ - 'redirect_url', - 'after_sign_in_url', - 'after_sign_up_url', - 'sign_in_force_redirect_url', - 'sign_in_fallback_redirect_url', - 'sign_up_force_redirect_url', - 'sign_up_fallback_redirect_url', -]; - -export const CLERK_MODAL_STATE = '__clerk_modal_state'; -export const CLERK_SATELLITE_URL = '__clerk_satellite_url'; -export const CLERK_SUFFIXED_COOKIES = 'suffixed_cookies'; -export const CLERK_SYNCED = '__clerk_synced'; -export const ERROR_CODES = { - FORM_IDENTIFIER_NOT_FOUND: 'form_identifier_not_found', - FORM_PASSWORD_INCORRECT: 'form_password_incorrect', - FORM_PASSWORD_PWNED: 'form_password_pwned', - INVALID_STRATEGY_FOR_USER: 'strategy_for_user_invalid', - NOT_ALLOWED_TO_SIGN_UP: 'not_allowed_to_sign_up', - OAUTH_ACCESS_DENIED: 'oauth_access_denied', - OAUTH_EMAIL_DOMAIN_RESERVED_BY_SAML: 'oauth_email_domain_reserved_by_saml', - NOT_ALLOWED_ACCESS: 'not_allowed_access', - SAML_USER_ATTRIBUTE_MISSING: 'saml_user_attribute_missing', - USER_LOCKED: 'user_locked', - EXTERNAL_ACCOUNT_NOT_FOUND: 'external_account_not_found', - SIGN_UP_MODE_RESTRICTED: 'sign_up_mode_restricted', - SIGN_UP_MODE_RESTRICTED_WAITLIST: 'sign_up_restricted_waitlist', - ENTERPRISE_SSO_USER_ATTRIBUTE_MISSING: 'enterprise_sso_user_attribute_missing', - ENTERPRISE_SSO_EMAIL_ADDRESS_DOMAIN_MISMATCH: 'enterprise_sso_email_address_domain_mismatch', - ENTERPRISE_SSO_HOSTED_DOMAIN_MISMATCH: 'enterprise_sso_hosted_domain_mismatch', - SAML_EMAIL_ADDRESS_DOMAIN_MISMATCH: 'saml_email_address_domain_mismatch', - INVITATION_ACCOUNT_NOT_EXISTS: 'invitation_account_not_exists', - ORGANIZATION_MEMBERSHIP_QUOTA_EXCEEDED_FOR_SSO: 'organization_membership_quota_exceeded_for_sso', - CAPTCHA_INVALID: 'captcha_invalid', - FRAUD_DEVICE_BLOCKED: 'device_blocked', - FRAUD_ACTION_BLOCKED: 'action_blocked', - SIGNUP_RATE_LIMIT_EXCEEDED: 'signup_rate_limit_exceeded', - USER_BANNED: 'user_banned', -} as const; - -export const SIGN_IN_INITIAL_VALUE_KEYS = ['email_address', 'phone_number', 'username']; -export const SIGN_UP_INITIAL_VALUE_KEYS = ['email_address', 'phone_number', 'username', 'first_name', 'last_name']; - -export const DEBOUNCE_MS = 350; - -export const SIGN_UP_MODES = { - PUBLIC: 'public', - RESTRICTED: 'restricted', - WAITLIST: 'waitlist', -} satisfies Record; - -// This is the currently supported version of the Frontend API -export const SUPPORTED_FAPI_VERSION = '2025-11-10'; +/** + * Re-exporting constants from @clerk/shared to avoid refactoring all imports. + * The constants have been moved to @clerk/shared/internal/clerk-js/constants + * to make them available across all Clerk packages. + */ +export * from '@clerk/shared/internal/clerk-js/constants'; diff --git a/packages/clerk-js/src/core/errors.ts b/packages/clerk-js/src/core/errors.ts index 3a589781565..45005bff61e 100644 --- a/packages/clerk-js/src/core/errors.ts +++ b/packages/clerk-js/src/core/errors.ts @@ -1,129 +1 @@ -const errorPrefix = 'ClerkJS:'; - -/** - * Used to log a warning when a Clerk feature is used in an unsupported environment. - * (Development Only) - * This is a warning and not an error because the application will still work, but the feature will not be available. - * - * @param strategy The strategy that is not supported in the current environment. - * @returns void - */ -export function clerkUnsupportedEnvironmentWarning(strategy: string) { - console.warn(`${errorPrefix} ${strategy} is not supported in this environment.`); -} - -export function clerkNetworkError(url: string, e: Error): never { - throw new Error(`${errorPrefix} Network error at "${url}" - ${e}. Please try again.`); -} - -export function clerkErrorInitFailed(): never { - throw new Error(`${errorPrefix} Something went wrong initializing Clerk.`); -} - -export function clerkErrorDevInitFailed(msg = ''): never { - throw new Error(`${errorPrefix} Something went wrong initializing Clerk in development mode.${msg && ` ${msg}`}`); -} - -export function clerkErrorPathRouterMissingPath(componentName: string): never { - throw new Error( - `${errorPrefix} Missing path option. The ${componentName} component was mounted with path routing so you need to specify the path where the component is mounted on e.g. path="/sign-in".`, - ); -} - -export function clerkCoreErrorContextProviderNotFound(providerName: string): never { - throw new Error(`${errorPrefix} You must wrap your application in a <${providerName}> component.`); -} - -export function clerkCoreErrorNoClerkSingleton(): never { - throw new Error(`${errorPrefix} Clerk is undefined`); -} - -export function clerkUIErrorDOMElementNotFound(): never { - throw new Error(`${errorPrefix} The target element is empty. Provide a valid DOM element.`); -} - -export function clerkMissingFapiClientInResources(): never { - throw new Error(`${errorPrefix} Missing FAPI client in resources.`); -} - -export function clerkOAuthCallbackDidNotCompleteSignInSignUp(type: 'sign in' | 'sign up'): never { - throw new Error( - `${errorPrefix} Something went wrong initializing Clerk during the ${type} flow. Please contact support.`, - ); -} - -export function clerkVerifyEmailAddressCalledBeforeCreate(type: 'SignIn' | 'SignUp'): never { - throw new Error(`${errorPrefix} You need to start a ${type} flow by calling ${type}.create() first.`); -} - -export function clerkInvalidStrategy(functionaName: string, strategy: string): never { - throw new Error(`${errorPrefix} Strategy "${strategy}" is not a valid strategy for ${functionaName}.`); -} - -export function clerkVerifyWeb3WalletCalledBeforeCreate(type: 'SignIn' | 'SignUp'): never { - throw new Error( - `${errorPrefix} You need to start a ${type} flow by calling ${type}.create({ identifier: 'your web3 wallet address' }) first`, - ); -} - -export function clerkVerifyPasskeyCalledBeforeCreate(): never { - throw new Error( - `${errorPrefix} You need to start a SignIn flow by calling SignIn.create({ strategy: 'passkey' }) first`, - ); -} - -export function clerkMissingOptionError(name = ''): never { - throw new Error(`${errorPrefix} Missing '${name}' option`); -} - -export function clerkInvalidFAPIResponse(status: string | null, supportEmail: string): never { - throw new Error( - `${errorPrefix} Response: ${status || 0} not supported yet.\nFor more information contact us at ${supportEmail}`, - ); -} - -export function clerkMissingDevBrowserJwt(): never { - throw new Error(`${errorPrefix} Missing dev browser jwt. Please contact support.`); -} - -export function clerkMissingProxyUrlAndDomain(): never { - throw new Error( - `${errorPrefix} Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl.`, - ); -} - -export function clerkInvalidSignInUrlOrigin(): never { - throw new Error(`${errorPrefix} The signInUrl needs to be on a different origin than your satellite application.`); -} - -export function clerkInvalidSignInUrlFormat(): never { - throw new Error(`${errorPrefix} The signInUrl needs to have a absolute url format.`); -} - -export function clerkMissingSignInUrlAsSatellite(): never { - throw new Error( - `${errorPrefix} Missing signInUrl. A satellite application needs to specify the signInUrl for development instances.`, - ); -} - -export function clerkRedirectUrlIsMissingScheme(): never { - throw new Error(`${errorPrefix} Invalid redirect_url. A valid http or https url should be used for the redirection.`); -} - -export function clerkFailedToLoadThirdPartyScript(name?: string): never { - throw new Error(`${errorPrefix} Unable to retrieve a third party script${name ? ` ${name}` : ''}.`); -} - -export function clerkInvalidRoutingStrategy(strategy?: string): never { - throw new Error(`${errorPrefix} Invalid routing strategy, path cannot be used in tandem with ${strategy}.`); -} - -export function clerkUnsupportedReloadMethod(className: string): never { - throw new Error(`${errorPrefix} Calling ${className}.reload is not currently supported. Please contact support.`); -} - -export function clerkMissingWebAuthnPublicKeyOptions(name: 'create' | 'get'): never { - throw new Error( - `${errorPrefix} Missing publicKey. When calling 'navigator.credentials.${name}()' it is required to pass a publicKey object.`, - ); -} +export * from '@clerk/shared/internal/clerk-js/errors'; diff --git a/packages/clerk-js/src/core/fapiClient.ts b/packages/clerk-js/src/core/fapiClient.ts index 7d03e620c27..94060f9ebf6 100644 --- a/packages/clerk-js/src/core/fapiClient.ts +++ b/packages/clerk-js/src/core/fapiClient.ts @@ -1,16 +1,13 @@ import { isBrowserOnline } from '@clerk/shared/browser'; +import { buildEmailAddress as buildEmailAddressUtil } from '@clerk/shared/internal/clerk-js/email'; +import { stringifyQueryParams } from '@clerk/shared/internal/clerk-js/querystring'; import { retry } from '@clerk/shared/retry'; import type { ClerkAPIErrorJSON, ClientJSON, InstanceType } from '@clerk/shared/types'; import { camelToSnake } from '@clerk/shared/underscore'; import { debugLogger } from '@/utils/debug'; -import { - buildEmailAddress as buildEmailAddressUtil, - buildURL as buildUrlUtil, - filterUndefinedValues, - stringifyQueryParams, -} from '../utils'; +import { buildURL as buildUrlUtil, filterUndefinedValues } from '../utils'; import { SUPPORTED_FAPI_VERSION } from './constants'; import { clerkNetworkError } from './errors'; diff --git a/packages/clerk-js/src/core/modules/checkout/__tests__/manager.test.ts b/packages/clerk-js/src/core/modules/checkout/__tests__/manager.test.ts deleted file mode 100644 index 7342174ad23..00000000000 --- a/packages/clerk-js/src/core/modules/checkout/__tests__/manager.test.ts +++ /dev/null @@ -1,714 +0,0 @@ -import type { BillingCheckoutResource, ClerkAPIResponseError } from '@clerk/shared/types'; -import type { MockedFunction } from 'vitest'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; - -import { type CheckoutCacheState, type CheckoutKey, createCheckoutManager, FETCH_STATUS } from '../manager'; - -// Type-safe mock for BillingCheckoutResource -const createMockCheckoutResource = (overrides: Partial = {}): BillingCheckoutResource => ({ - id: 'checkout_123', - status: 'needs_confirmation', - externalClientSecret: 'cs_test_123', - externalGatewayId: 'gateway_123', - totals: { - totalDueNow: { amount: 1000, currency: 'USD', currencySymbol: '$', amountFormatted: '10.00' }, - credit: { amount: 0, currency: 'USD', currencySymbol: '$', amountFormatted: '0.00' }, - pastDue: { amount: 0, currency: 'USD', currencySymbol: '$', amountFormatted: '0.00' }, - subtotal: { amount: 1000, currency: 'USD', currencySymbol: '$', amountFormatted: '10.00' }, - grandTotal: { amount: 1000, currency: 'USD', currencySymbol: '$', amountFormatted: '10.00' }, - taxTotal: { amount: 0, currency: 'USD', currencySymbol: '$', amountFormatted: '0.00' }, - }, - isImmediatePlanChange: false, - planPeriod: 'month', - freeTrialEndsAt: null, - needsPaymentMethod: true, - payer: { - id: 'payer_123', - createdAt: new Date('2025-01-01'), - updatedAt: new Date('2025-01-01'), - firstName: 'Test Payer', - lastName: 'Test Payer', - email: 'test@clerk.com', - imageUrl: 'https://example.com/avatar.png', - pathRoot: '', - reload: vi.fn(), - }, - plan: { - id: 'plan_123', - name: 'Pro Plan', - description: 'Professional plan', - features: [], - fee: { amount: 1000, amountFormatted: '10.00', currency: 'USD', currencySymbol: '$' }, - annualFee: { amount: 12000, amountFormatted: '120.00', currency: 'USD', currencySymbol: '$' }, - annualMonthlyFee: { amount: 1000, amountFormatted: '10.00', currency: 'USD', currencySymbol: '$' }, - slug: 'pro-plan', - isDefault: false, - isRecurring: true, - hasBaseFee: false, - forPayerType: 'user', - publiclyVisible: true, - freeTrialDays: 0, - freeTrialEnabled: false, - avatarUrl: '', - pathRoot: '', - reload: vi.fn(), - }, - paymentMethod: undefined, - confirm: vi.fn(), - reload: vi.fn(), - pathRoot: '/checkout', - ...overrides, -}); - -// Type-safe mock for ClerkAPIResponseError -const createMockError = (message = 'Test error'): ClerkAPIResponseError => { - const error = new Error(message) as ClerkAPIResponseError; - error.status = 400; - error.clerkTraceId = 'trace_123'; - error.clerkError = true; - return error; -}; - -// Helper to create a typed cache key -const createCacheKey = (key: string): CheckoutKey => key as CheckoutKey; - -describe('createCheckoutManager', () => { - const testCacheKey = createCacheKey('user-123-plan-456-monthly'); - let manager: ReturnType; - - beforeEach(() => { - vi.clearAllMocks(); - manager = createCheckoutManager(testCacheKey); - }); - - describe('getCacheState', () => { - it('should return default state when cache is empty', () => { - const state = manager.getCacheState(); - - expect(state).toEqual({ - isStarting: false, - isConfirming: false, - error: null, - checkout: null, - fetchStatus: 'idle', - status: 'needs_initialization', - }); - }); - - it('should return immutable state object', () => { - const state = manager.getCacheState(); - - // State should be frozen - expect(Object.isFrozen(state)).toBe(true); - }); - }); - - describe('subscribe', () => { - it('should add listener and return unsubscribe function', () => { - const listener: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); - - const unsubscribe = manager.subscribe(listener); - - expect(typeof unsubscribe).toBe('function'); - expect(listener).not.toHaveBeenCalled(); - }); - - it('should remove listener when unsubscribe is called', async () => { - const listener: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); - - const unsubscribe = manager.subscribe(listener); - - // Trigger a state change - const mockCheckout = createMockCheckoutResource(); - const mockOperation = vi.fn().mockResolvedValue(mockCheckout); - await manager.executeOperation('start', mockOperation); - - expect(listener).toHaveBeenCalled(); - - // Clear the mock and unsubscribe - listener.mockClear(); - unsubscribe(); - - // Trigger another state change - const anotherMockOperation = vi.fn().mockResolvedValue(mockCheckout); - await manager.executeOperation('confirm', anotherMockOperation); - - // Listener should not be called after unsubscribing - expect(listener).not.toHaveBeenCalled(); - }); - - it('should notify all listeners when state changes', async () => { - const listener1: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); - const listener2: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); - const mockCheckout = createMockCheckoutResource(); - - manager.subscribe(listener1); - manager.subscribe(listener2); - - const mockOperation = vi.fn().mockResolvedValue(mockCheckout); - await manager.executeOperation('start', mockOperation); - - expect(listener1).toHaveBeenCalled(); - expect(listener2).toHaveBeenCalled(); - - // Verify they were called with the updated state - const expectedState = expect.objectContaining({ - checkout: mockCheckout, - isStarting: false, - error: null, - fetchStatus: 'idle', - status: 'needs_confirmation', - }); - - expect(listener1).toHaveBeenCalledWith(expectedState); - expect(listener2).toHaveBeenCalledWith(expectedState); - }); - - it('should handle multiple subscribe/unsubscribe cycles', () => { - const listener: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); - - // Subscribe and unsubscribe multiple times - const unsubscribe1 = manager.subscribe(listener); - unsubscribe1(); - - const unsubscribe2 = manager.subscribe(listener); - const unsubscribe3 = manager.subscribe(listener); - - unsubscribe2(); - unsubscribe3(); - - // Should not throw errors - expect(() => unsubscribe1()).not.toThrow(); - expect(() => unsubscribe2()).not.toThrow(); - }); - }); - - describe('executeOperation - start operations', () => { - it('should execute start operation successfully', async () => { - const mockCheckout = createMockCheckoutResource(); - const mockOperation: MockedFunction<() => Promise> = vi - .fn() - .mockResolvedValue(mockCheckout); - - const result = await manager.executeOperation('start', mockOperation); - - expect(mockOperation).toHaveBeenCalledOnce(); - expect(result).toEqual({ - data: mockCheckout, - error: null, - }); - - const finalState = manager.getCacheState(); - expect(finalState).toEqual( - expect.objectContaining({ - isStarting: false, - checkout: mockCheckout, - error: null, - fetchStatus: 'idle', - status: 'needs_confirmation', - }), - ); - }); - - it('should set isStarting to true during operation', async () => { - let capturedState: CheckoutCacheState | null = null; - const mockOperation: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(async () => { - // Capture state while operation is running - capturedState = manager.getCacheState(); - return createMockCheckoutResource(); - }); - - await manager.executeOperation('start', mockOperation); - - expect(capturedState).toEqual( - expect.objectContaining({ - isStarting: true, - fetchStatus: 'fetching', - }), - ); - }); - - it('should handle operation errors correctly', async () => { - const mockError = createMockError('Operation failed'); - const mockOperation: MockedFunction<() => Promise> = vi - .fn() - .mockRejectedValue(mockError); - - const result = await manager.executeOperation('start', mockOperation); - - expect(result).toEqual({ - data: null, - error: mockError, - }); - - const finalState = manager.getCacheState(); - expect(finalState).toEqual( - expect.objectContaining({ - isStarting: false, - error: mockError, - fetchStatus: 'error', - status: 'needs_initialization', - }), - ); - }); - - it('should clear previous errors when starting new operation', async () => { - // First, create an error state - const mockError = createMockError('Previous error'); - const failingOperation: MockedFunction<() => Promise> = vi - .fn() - .mockRejectedValue(mockError); - - const result = await manager.executeOperation('start', failingOperation); - expect(result).toEqual({ - data: null, - error: mockError, - }); - - const errorState = manager.getCacheState(); - expect(errorState.error).toBe(mockError); - - // Now start a successful operation - const mockCheckout = createMockCheckoutResource(); - const successfulOperation: MockedFunction<() => Promise> = vi - .fn() - .mockResolvedValue(mockCheckout); - - const successResult = await manager.executeOperation('start', successfulOperation); - expect(successResult).toEqual({ - data: mockCheckout, - error: null, - }); - - const finalState = manager.getCacheState(); - expect(finalState.error).toBeNull(); - expect(finalState.checkout).toBe(mockCheckout); - }); - }); - - describe('executeOperation - confirm operations', () => { - it('should execute confirm operation successfully', async () => { - const mockCheckout = createMockCheckoutResource({ status: 'completed' }); - const mockOperation: MockedFunction<() => Promise> = vi - .fn() - .mockResolvedValue(mockCheckout); - - const result = await manager.executeOperation('confirm', mockOperation); - - expect(result).toEqual({ - data: mockCheckout, - error: null, - }); - - const finalState = manager.getCacheState(); - expect(finalState).toEqual( - expect.objectContaining({ - isConfirming: false, - checkout: mockCheckout, - error: null, - fetchStatus: 'idle', - status: 'completed', - }), - ); - }); - - it('should set isConfirming to true during operation', async () => { - let capturedState: CheckoutCacheState | null = null; - const mockOperation: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(async () => { - capturedState = manager.getCacheState(); - return createMockCheckoutResource(); - }); - - await manager.executeOperation('confirm', mockOperation); - - expect(capturedState).toEqual( - expect.objectContaining({ - isConfirming: true, - fetchStatus: 'fetching', - }), - ); - }); - - it('should handle confirm operation errors', async () => { - const mockError = createMockError('Confirm failed'); - const mockOperation: MockedFunction<() => Promise> = vi - .fn() - .mockRejectedValue(mockError); - - const result = await manager.executeOperation('confirm', mockOperation); - expect(result).toEqual({ - data: null, - error: mockError, - }); - - const finalState = manager.getCacheState(); - expect(finalState).toEqual( - expect.objectContaining({ - isConfirming: false, - error: mockError, - fetchStatus: 'error', - }), - ); - }); - }); - - describe('operation deduplication', () => { - it('should deduplicate concurrent start operations', async () => { - const mockCheckout = createMockCheckoutResource(); - const mockOperation: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(mockCheckout), 50))); - - // Start multiple operations concurrently - const [result1, result2, result3] = await Promise.all([ - manager.executeOperation('start', mockOperation), - manager.executeOperation('start', mockOperation), - manager.executeOperation('start', mockOperation), - ]); - - // Operation should only be called once - expect(mockOperation).toHaveBeenCalledOnce(); - - // All results should be the same - expect(result1).toEqual({ - data: mockCheckout, - error: null, - }); - expect(result2).toEqual({ - data: mockCheckout, - error: null, - }); - expect(result3).toEqual({ - data: mockCheckout, - error: null, - }); - }); - - it('should deduplicate concurrent confirm operations', async () => { - const mockCheckout = createMockCheckoutResource(); - const mockOperation: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(mockCheckout), 50))); - - const [result1, result2] = await Promise.all([ - manager.executeOperation('confirm', mockOperation), - manager.executeOperation('confirm', mockOperation), - ]); - - expect(mockOperation).toHaveBeenCalledOnce(); - expect(result1).toBe(result2); - }); - - it('should allow different operation types to run concurrently', async () => { - const startCheckout = createMockCheckoutResource({ id: 'start_checkout' }); - const confirmCheckout = createMockCheckoutResource({ id: 'confirm_checkout' }); - - const startOperation: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(startCheckout), 50))); - const confirmOperation: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(confirmCheckout), 50))); - - const [startResult, confirmResult] = await Promise.all([ - manager.executeOperation('start', startOperation), - manager.executeOperation('confirm', confirmOperation), - ]); - - expect(startOperation).toHaveBeenCalledOnce(); - expect(confirmOperation).toHaveBeenCalledOnce(); - expect(startResult).toEqual({ - data: startCheckout, - error: null, - }); - expect(confirmResult).toEqual({ - data: confirmCheckout, - error: null, - }); - }); - - it('should propagate errors to all concurrent callers', async () => { - const mockError = createMockError('Concurrent operation failed'); - const mockOperation: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(() => new Promise((_, reject) => setTimeout(() => reject(mockError), 50))); - - const promises = [ - manager.executeOperation('start', mockOperation), - manager.executeOperation('start', mockOperation), - manager.executeOperation('start', mockOperation), - ]; - - const results = await Promise.all(promises); - - // All promises should resolve with the same error - results.forEach(result => { - expect(result).toEqual({ - data: null, - error: mockError, - }); - }); - expect(mockOperation).toHaveBeenCalledOnce(); - }); - - it('should allow sequential operations of the same type', async () => { - const checkout1 = createMockCheckoutResource({ id: 'checkout1' }); - const checkout2 = createMockCheckoutResource({ id: 'checkout2' }); - - const operation1: MockedFunction<() => Promise> = vi.fn().mockResolvedValue(checkout1); - const operation2: MockedFunction<() => Promise> = vi.fn().mockResolvedValue(checkout2); - - const result1 = await manager.executeOperation('start', operation1); - const result2 = await manager.executeOperation('start', operation2); - - expect(operation1).toHaveBeenCalledOnce(); - expect(operation2).toHaveBeenCalledOnce(); - expect(result1).toEqual({ - data: checkout1, - error: null, - }); - expect(result2).toEqual({ - data: checkout2, - error: null, - }); - }); - }); - - describe('clearCheckout', () => { - it('should clear checkout state when no operations are pending', () => { - const listener: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); - manager.subscribe(listener); - - manager.clearCheckout(); - - const state = manager.getCacheState(); - expect(state).toEqual({ - isStarting: false, - isConfirming: false, - error: null, - checkout: null, - fetchStatus: 'idle', - status: 'needs_initialization', - }); - - // Should notify listeners - expect(listener).toHaveBeenCalledWith(state); - }); - - it('should not clear checkout state when operations are pending', async () => { - const mockCheckout = createMockCheckoutResource(); - let resolveOperation: ((value: BillingCheckoutResource) => void) | undefined; - - const mockOperation: MockedFunction<() => Promise> = vi.fn().mockImplementation( - () => - new Promise(resolve => { - resolveOperation = resolve; - }), - ); - - // Start an operation but don't resolve it yet - const operationPromise = manager.executeOperation('start', mockOperation); - - // Verify operation is in progress - let state = manager.getCacheState(); - expect(state.isStarting).toBe(true); - expect(state.fetchStatus).toBe('fetching'); - - // Try to clear while operation is pending - manager.clearCheckout(); - - // State should not be cleared - state = manager.getCacheState(); - expect(state.isStarting).toBe(true); - expect(state.fetchStatus).toBe('fetching'); - - // Resolve the operation - resolveOperation?.(mockCheckout); - await operationPromise; - - // Now clearing should work - manager.clearCheckout(); - state = manager.getCacheState(); - expect(state.checkout).toBeNull(); - expect(state.status).toBe('needs_initialization'); - }); - }); - - describe('state derivation', () => { - it('should derive fetchStatus correctly based on operation state', async () => { - // Initially idle - expect(manager.getCacheState().fetchStatus).toBe(FETCH_STATUS.IDLE); - - // During operation - fetching - let capturedState: CheckoutCacheState | null = null; - const mockOperation: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(async () => { - capturedState = manager.getCacheState(); - return createMockCheckoutResource(); - }); - - await manager.executeOperation('start', mockOperation); - expect(capturedState?.fetchStatus).toBe(FETCH_STATUS.FETCHING); - - // After successful operation - idle - expect(manager.getCacheState().fetchStatus).toBe(FETCH_STATUS.IDLE); - - // After error - error - const mockError = createMockError(); - const failingOperation: MockedFunction<() => Promise> = vi - .fn() - .mockRejectedValue(mockError); - - const result = await manager.executeOperation('start', failingOperation); - expect(result).toEqual({ - data: null, - error: mockError, - }); - expect(manager.getCacheState().fetchStatus).toBe(FETCH_STATUS.ERROR); - }); - - it('should derive status based on checkout state', async () => { - // Initially needs initialization - expect(manager.getCacheState().status).toBe('needs_initialization'); - - // After starting checkout - needs confirmation - const pendingCheckout = createMockCheckoutResource({ status: 'needs_confirmation' }); - const startOperation: MockedFunction<() => Promise> = vi - .fn() - .mockResolvedValue(pendingCheckout); - - await manager.executeOperation('start', startOperation); - expect(manager.getCacheState().status).toBe('needs_confirmation'); - - // After completing checkout - completed - const completedCheckout = createMockCheckoutResource({ status: 'completed' }); - const confirmOperation: MockedFunction<() => Promise> = vi - .fn() - .mockResolvedValue(completedCheckout); - - await manager.executeOperation('confirm', confirmOperation); - expect(manager.getCacheState().status).toBe('completed'); - }); - - it('should handle both operations running simultaneously', async () => { - let startCapturedState: CheckoutCacheState | null = null; - let confirmCapturedState: CheckoutCacheState | null = null; - - const startOperation: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(async () => { - await new Promise(resolve => setTimeout(resolve, 30)); - startCapturedState = manager.getCacheState(); - return createMockCheckoutResource({ id: 'start' }); - }); - - const confirmOperation: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(async () => { - await new Promise(resolve => setTimeout(resolve, 20)); - confirmCapturedState = manager.getCacheState(); - return createMockCheckoutResource({ id: 'confirm' }); - }); - - await Promise.all([ - manager.executeOperation('start', startOperation), - manager.executeOperation('confirm', confirmOperation), - ]); - - // Both should have seen fetching status - expect(startCapturedState?.fetchStatus).toBe(FETCH_STATUS.FETCHING); - expect(confirmCapturedState?.fetchStatus).toBe(FETCH_STATUS.FETCHING); - - // At least one should have seen both operations running - expect( - (startCapturedState?.isStarting && startCapturedState?.isConfirming) || - (confirmCapturedState?.isStarting && confirmCapturedState?.isConfirming), - ).toBe(true); - }); - }); - - describe('cache isolation', () => { - it('should isolate state between different cache keys', async () => { - const manager1 = createCheckoutManager(createCacheKey('key1')); - const manager2 = createCheckoutManager(createCacheKey('key2')); - - const checkout1 = createMockCheckoutResource({ id: 'checkout1' }); - const checkout2 = createMockCheckoutResource({ id: 'checkout2' }); - - const operation1: MockedFunction<() => Promise> = vi.fn().mockResolvedValue(checkout1); - const operation2: MockedFunction<() => Promise> = vi.fn().mockResolvedValue(checkout2); - - await manager1.executeOperation('start', operation1); - await manager2.executeOperation('confirm', operation2); - - const state1 = manager1.getCacheState(); - const state2 = manager2.getCacheState(); - - expect(state1.checkout?.id).toBe('checkout1'); - expect(state1.status).toBe('needs_confirmation'); - - expect(state2.checkout?.id).toBe('checkout2'); - expect(state2.isStarting).toBe(false); - expect(state2.isConfirming).toBe(false); - }); - - it('should isolate listeners between different cache keys', async () => { - const manager1 = createCheckoutManager(createCacheKey('key1')); - const manager2 = createCheckoutManager(createCacheKey('key2')); - - const listener1: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); - const listener2: MockedFunction<(state: CheckoutCacheState) => void> = vi.fn(); - - manager1.subscribe(listener1); - manager2.subscribe(listener2); - - // Trigger operation on manager1 - const mockOperation: MockedFunction<() => Promise> = vi - .fn() - .mockResolvedValue(createMockCheckoutResource()); - await manager1.executeOperation('start', mockOperation); - - // Only listener1 should be called - expect(listener1).toHaveBeenCalled(); - expect(listener2).not.toHaveBeenCalled(); - }); - - it('should isolate pending operations between different cache keys', async () => { - const manager1 = createCheckoutManager(createCacheKey('key1')); - const manager2 = createCheckoutManager(createCacheKey('key2')); - - const checkout1 = createMockCheckoutResource({ id: 'checkout1' }); - const checkout2 = createMockCheckoutResource({ id: 'checkout2' }); - - const operation1: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(checkout1), 50))); - const operation2: MockedFunction<() => Promise> = vi - .fn() - .mockImplementation(() => new Promise(resolve => setTimeout(() => resolve(checkout2), 50))); - - // Start concurrent operations on both managers - const [result1, result2] = await Promise.all([ - manager1.executeOperation('start', operation1), - manager2.executeOperation('start', operation2), - ]); - - // Both operations should execute (not deduplicated across managers) - expect(operation1).toHaveBeenCalledOnce(); - expect(operation2).toHaveBeenCalledOnce(); - expect(result1).toEqual({ - data: checkout1, - error: null, - }); - expect(result2).toEqual({ - data: checkout2, - error: null, - }); - }); - }); -}); diff --git a/packages/clerk-js/src/core/modules/checkout/instance.ts b/packages/clerk-js/src/core/modules/checkout/instance.ts index 8b28b694f10..85224308462 100644 --- a/packages/clerk-js/src/core/modules/checkout/instance.ts +++ b/packages/clerk-js/src/core/modules/checkout/instance.ts @@ -1,12 +1,10 @@ -import type { - __experimental_CheckoutCacheState, - __experimental_CheckoutInstance, - __experimental_CheckoutOptions, - SetActiveNavigate, -} from '@clerk/shared/types'; +import type { __experimental_CheckoutOptions, CheckoutSignalValue } from '@clerk/shared/types'; + +import { CheckoutFlow, createSignals } from '@/core/resources/BillingCheckout'; import type { Clerk } from '../../clerk'; -import { type CheckoutKey, createCheckoutManager } from './manager'; + +type CheckoutKey = string & { readonly __tag: 'CheckoutKey' }; /** * Generate cache key for checkout instance @@ -16,72 +14,52 @@ function cacheKey(options: { userId: string; orgId?: string; planId: string; pla return `${userId}-${orgId || 'user'}-${planId}-${planPeriod}` as CheckoutKey; } +/** + * Stores the state of checkout instances based on their configuration as a cache key. + */ +const CheckoutSignalCache = new Map< + CheckoutKey, + { resource: CheckoutFlow; signals: ReturnType } +>(); + /** * Create a checkout instance with the given options */ -function createCheckoutInstance( - clerk: Clerk, - options: __experimental_CheckoutOptions, -): __experimental_CheckoutInstance { +function createCheckoutInstance(clerk: Clerk, options: __experimental_CheckoutOptions): CheckoutSignalValue { const { for: forOrganization, planId, planPeriod } = options; - if (!clerk.isSignedIn || !clerk.user) { + if (clerk.user === null) { throw new Error('Clerk: User is not authenticated'); } - if (forOrganization === 'organization' && !clerk.organization) { - throw new Error('Clerk: Use `setActive` to set the organization'); + if (forOrganization === 'organization' && clerk.organization === null) { + throw new Error( + 'Clerk: The current session does not have an active organization. Use `setActive` to set the organization', + ); } const checkoutKey = cacheKey({ - userId: clerk.user.id, + userId: clerk.user?.id || '', orgId: forOrganization === 'organization' ? clerk.organization?.id : undefined, planId, planPeriod, }); - const manager = createCheckoutManager(checkoutKey); - - const start: __experimental_CheckoutInstance['start'] = async () => { - return manager.executeOperation('start', async () => { - const result = await clerk.billing?.startCheckout({ - ...(forOrganization === 'organization' ? { orgId: clerk.organization?.id } : {}), - planId, - planPeriod, - }); - return result; - }); - }; - - const confirm: __experimental_CheckoutInstance['confirm'] = async params => { - return manager.executeOperation('confirm', async () => { - const checkout = manager.getCacheState().checkout; - if (!checkout) { - throw new Error('Clerk: Call `start` before `confirm`'); - } - return checkout.confirm(params); - }); - }; - - const finalize = (params?: { navigate?: SetActiveNavigate }) => { - const { navigate } = params || {}; - return clerk.setActive({ session: clerk.session?.id, navigate }); - }; + const checkoutInstance = CheckoutSignalCache.get(checkoutKey); + if (checkoutInstance) { + return checkoutInstance.signals.computedSignal() as CheckoutSignalValue; + } - const clear = () => manager.clearCheckout(); + const signals = createSignals(); - const subscribe = (listener: (state: __experimental_CheckoutCacheState) => void) => { - return manager.subscribe(listener); - }; + const checkout = new CheckoutFlow(signals, { + ...(forOrganization === 'organization' ? { orgId: clerk.organization?.id } : {}), + planId, + planPeriod, + }); - return { - start, - confirm, - finalize, - clear, - subscribe, - getState: manager.getCacheState, - }; + CheckoutSignalCache.set(checkoutKey, { resource: checkout, signals }); + return signals.computedSignal() as CheckoutSignalValue; } export { createCheckoutInstance }; diff --git a/packages/clerk-js/src/core/modules/checkout/manager.ts b/packages/clerk-js/src/core/modules/checkout/manager.ts deleted file mode 100644 index e0b5960038b..00000000000 --- a/packages/clerk-js/src/core/modules/checkout/manager.ts +++ /dev/null @@ -1,195 +0,0 @@ -import type { - __experimental_CheckoutCacheState, - __experimental_CheckoutInstance, - BillingCheckoutResource, - ClerkAPIResponseError, -} from '@clerk/shared/types'; - -type CheckoutKey = string & { readonly __tag: 'CheckoutKey' }; - -type CheckoutResult = Awaited>; - -const createManagerCache = () => { - const cache = new Map(); - const listeners = new Map void>>(); - const pendingOperations = new Map>>(); - - return { - cache, - listeners, - pendingOperations, - safeGet>(key: K, map: Map): NonNullable { - if (!map.has(key)) { - map.set(key, new Set() as V); - } - return map.get(key) as NonNullable; - }, - safeGetOperations(key: K): Map> { - if (!this.pendingOperations.has(key)) { - this.pendingOperations.set(key, new Map>()); - } - return this.pendingOperations.get(key) as Map>; - }, - }; -}; - -const managerCache = createManagerCache(); - -const CHECKOUT_STATUS = { - NEEDS_INITIALIZATION: 'needs_initialization', - NEEDS_CONFIRMATION: 'needs_confirmation', - COMPLETED: 'completed', -} as const; - -export const FETCH_STATUS = { - IDLE: 'idle', - FETCHING: 'fetching', - ERROR: 'error', -} as const; - -/** - * Derives the checkout state from the base state. - */ -function deriveCheckoutState( - baseState: Omit<__experimental_CheckoutCacheState, 'fetchStatus' | 'status'>, -): __experimental_CheckoutCacheState { - const fetchStatus = (() => { - if (baseState.isStarting || baseState.isConfirming) { - return FETCH_STATUS.FETCHING; - } - if (baseState.error) { - return FETCH_STATUS.ERROR; - } - return FETCH_STATUS.IDLE; - })(); - - const status = (() => { - if (baseState.checkout?.status === CHECKOUT_STATUS.COMPLETED) { - return CHECKOUT_STATUS.COMPLETED; - } - if (baseState.checkout) { - return CHECKOUT_STATUS.NEEDS_CONFIRMATION; - } - return CHECKOUT_STATUS.NEEDS_INITIALIZATION; - })(); - - return { - ...baseState, - fetchStatus, - status, - }; -} - -const defaultCacheState: __experimental_CheckoutCacheState = Object.freeze( - deriveCheckoutState({ - isStarting: false, - isConfirming: false, - error: null, - checkout: null, - }), -); - -/** - * Creates a checkout manager for handling checkout operations and state management. - * - * @param cacheKey - Unique identifier for the checkout instance - * @returns Manager with methods for checkout operations and state subscription - * - * @example - * ```typescript - * const manager = createCheckoutManager('user-123-plan-456-monthly'); - * const unsubscribe = manager.subscribe(state => console.log(state)); - * ``` - */ -function createCheckoutManager(cacheKey: CheckoutKey) { - const listeners = managerCache.safeGet(cacheKey, managerCache.listeners); - const pendingOperations = managerCache.safeGetOperations(cacheKey); - - const notifyListeners = () => { - listeners.forEach(listener => listener(getCacheState())); - }; - - const getCacheState = (): __experimental_CheckoutCacheState => { - return managerCache.cache.get(cacheKey) || defaultCacheState; - }; - - const updateCacheState = ( - updates: Partial>, - ): void => { - const currentState = getCacheState(); - const baseState = { ...currentState, ...updates }; - const newState = deriveCheckoutState(baseState); - managerCache.cache.set(cacheKey, Object.freeze(newState)); - notifyListeners(); - }; - - return { - subscribe(listener: (newState: __experimental_CheckoutCacheState) => void): () => void { - listeners.add(listener); - return () => { - listeners.delete(listener); - }; - }, - - getCacheState, - - // Shared operation handler to eliminate duplication - async executeOperation( - operationType: 'start' | 'confirm', - operationFn: () => Promise, - ): Promise { - const operationId = `${cacheKey}-${operationType}`; - const isRunningField = operationType === 'start' ? 'isStarting' : 'isConfirming'; - - // Check if there's already a pending operation - const existingOperation = pendingOperations.get(operationId); - if (existingOperation) { - // Wait for the existing operation to complete and return its result - // If it fails, all callers should receive the same error - return await existingOperation; - } - - // Create and store the operation promise - const operationPromise = (async () => { - let data: BillingCheckoutResource | null = null; - let error: ClerkAPIResponseError | null = null; - try { - // Mark operation as in progress and clear any previous errors - updateCacheState({ - [isRunningField]: true, - error: null, - ...(operationType === 'start' ? { checkout: null } : {}), - }); - - // Execute the checkout operation - const result = await operationFn(); - - // Update state with successful result - updateCacheState({ [isRunningField]: false, error: null, checkout: result }); - data = result; - } catch (e) { - // Cast error to expected type and update state - const clerkError = e as ClerkAPIResponseError; - error = clerkError; - updateCacheState({ [isRunningField]: false, error: clerkError }); - } finally { - // Always clean up pending operation tracker - pendingOperations.delete(operationId); - } - return { data, error } as CheckoutResult; - })(); - - pendingOperations.set(operationId, operationPromise); - return operationPromise; - }, - - clearCheckout(): void { - // Only reset the state if there are no pending operations - if (pendingOperations.size === 0) { - updateCacheState(defaultCacheState); - } - }, - }; -} - -export { createCheckoutManager, type __experimental_CheckoutCacheState as CheckoutCacheState, type CheckoutKey }; diff --git a/packages/clerk-js/src/core/resources/BillingCheckout.ts b/packages/clerk-js/src/core/resources/BillingCheckout.ts index e3229d3f0cd..0bd0736ebbd 100644 --- a/packages/clerk-js/src/core/resources/BillingCheckout.ts +++ b/packages/clerk-js/src/core/resources/BillingCheckout.ts @@ -1,3 +1,4 @@ +import type { ClerkError } from '@clerk/shared/error'; import { isClerkAPIResponseError } from '@clerk/shared/error'; import { retry } from '@clerk/shared/retry'; import type { @@ -7,13 +8,20 @@ import type { BillingPayerResource, BillingPaymentMethodResource, BillingSubscriptionPlanPeriod, + CheckoutFlowFinalizeParams, + CheckoutFlowResource, + CheckoutFlowResourceNonStrict, + CheckoutSignalValue, ConfirmCheckoutParams, + CreateCheckoutParams, } from '@clerk/shared/types'; +import { computed, endBatch, signal, startBatch } from 'alien-signals'; import { unixEpochToDate } from '@/utils/date'; import { billingTotalsFromJSON } from '../../utils'; import { Billing } from '../modules/billing/namespace'; +import { errorsToParsedErrors } from '../signals'; import { BillingPayer } from './BillingPayer'; import { BaseResource, BillingPaymentMethod, BillingPlan } from './internal'; @@ -32,7 +40,7 @@ export class BillingCheckout extends BaseResource implements BillingCheckoutReso payer!: BillingPayerResource; needsPaymentMethod!: boolean; - constructor(data: BillingCheckoutJSON) { + constructor(data: BillingCheckoutJSON | null = null) { super(); this.fromJSON(data); } @@ -91,3 +99,164 @@ export class BillingCheckout extends BaseResource implements BillingCheckoutReso ); }; } + +export const createSignals = () => { + const resourceSignal = signal<{ resource: CheckoutFlow | null }>({ resource: null }); + const errorSignal = signal<{ error: ClerkError | null }>({ error: null }); + const fetchSignal = signal<{ status: 'idle' | 'fetching' }>({ status: 'idle' }); + const computedSignal = computed & { checkout: CheckoutFlowResource | null }>( + () => { + const resource = resourceSignal().resource; + const error = errorSignal().error; + const fetchStatus = fetchSignal().status; + + const errors = errorsToParsedErrors(error, {}); + return { errors: errors, fetchStatus, checkout: resource }; + }, + ); + + return { resourceSignal, errorSignal, fetchSignal, computedSignal }; +}; + +type CheckoutTask = 'start' | 'confirm' | 'finalize'; + +export class CheckoutFlow implements CheckoutFlowResourceNonStrict { + private resource = new BillingCheckout(null); + private readonly config: CreateCheckoutParams; + private readonly signals: ReturnType; + private readonly pendingOperations = new Map | null>(); + + constructor(signals: ReturnType, config: CreateCheckoutParams) { + this.config = config; + this.signals = signals; + this.signals.resourceSignal({ resource: this }); + } + + get status() { + return this.resource.status ?? 'needs_initialization'; + } + + get externalClientSecret() { + return this.resource.externalClientSecret; + } + + get externalGatewayId() { + return this.resource.externalGatewayId; + } + + get plan() { + return this.resource.plan; + } + get planPeriod() { + return this.resource.planPeriod; + } + get totals() { + return this.resource.totals; + } + get isImmediatePlanChange() { + return this.resource.isImmediatePlanChange; + } + get freeTrialEndsAt() { + return this.resource.freeTrialEndsAt; + } + get payer() { + return this.resource.payer; + } + + get paymentMethod() { + return this.resource.paymentMethod ?? null; + } + + get planPeriodStart() { + return this.resource.planPeriodStart; + } + + get needsPaymentMethod() { + return this.resource.needsPaymentMethod; + } + + async start(): Promise<{ error: ClerkError | null }> { + return this.runAsyncCheckoutTask( + 'start', + async () => { + const checkout = (await BillingCheckout.clerk.billing?.startCheckout(this.config)) as BillingCheckout; + this.resource = checkout; + }, + () => { + this.resource = new BillingCheckout(null); + this.signals.resourceSignal({ resource: this }); + }, + ); + } + + async confirm(params: ConfirmCheckoutParams): Promise<{ error: ClerkError | null }> { + if (!this.resource.id) { + throw new Error('Clerk: `start()` must be called before `confirm()`'); + } + return this.runAsyncCheckoutTask('confirm', async () => { + await this.resource.confirm(params); + }); + } + + async finalize(params?: CheckoutFlowFinalizeParams): Promise<{ error: ClerkError | null }> { + const { navigate } = params || {}; + return this.runAsyncCheckoutTask('finalize', async () => { + if (this.resource.status !== 'completed') { + throw new Error('Clerk: `confirm()` must be called before `finalize()`'); + } + + await BillingCheckout.clerk.setActive({ session: BillingCheckout.clerk.session?.id, navigate }); + }); + } + + private runAsyncCheckoutTask(operationType: CheckoutTask, task: () => Promise, beforeTask?: () => void) { + // Noops during transitive state + if (typeof BillingCheckout.clerk.user === 'undefined') { + console.warn('Clerk: Checkout operations cannot be performed during transitive state'); + return { error: null }; + } + return createRunAsyncCheckoutTask(this, this.signals, this.pendingOperations)(operationType, task, beforeTask); + } +} + +function createRunAsyncCheckoutTask( + resource: CheckoutFlow, + signals: ReturnType, + pendingOperations: Map | null>, +): ( + operationType: CheckoutTask, + task: () => Promise, + beforeTask?: () => void, +) => Promise<{ error: ClerkError | null }> { + return async (operationType, task, beforeTask?: () => void) => { + if (pendingOperations.get(operationType)) { + // Wait for the existing operation to complete and return its result + // If it fails, all callers should receive the same error + return pendingOperations.get(operationType) as Promise<{ error: unknown }>; + } + + const operationPromise = (async () => { + startBatch(); + signals.errorSignal({ error: null }); + signals.fetchSignal({ status: 'fetching' }); + beforeTask?.(); + endBatch(); + startBatch(); + try { + await task(); + signals.resourceSignal({ resource: resource }); + return { error: null }; + } catch (err) { + signals.errorSignal({ error: err }); + return { error: err }; + } finally { + pendingOperations.delete(operationType); + signals.fetchSignal({ status: 'idle' }); + endBatch(); + } + })(); + + pendingOperations.set(operationType, operationPromise); + return operationPromise; + }; +} diff --git a/packages/clerk-js/src/core/resources/Client.ts b/packages/clerk-js/src/core/resources/Client.ts index 3371b8435c0..f246d7efbad 100644 --- a/packages/clerk-js/src/core/resources/Client.ts +++ b/packages/clerk-js/src/core/resources/Client.ts @@ -1,5 +1,4 @@ import type { - ActiveSessionResource, ClientJSON, ClientJSONSnapshot, ClientResource, @@ -57,13 +56,6 @@ export class Client extends BaseResource implements ClientResource { return this.signIn; } - /** - * @deprecated Use `signedInSessions()` instead. - */ - get activeSessions(): ActiveSessionResource[] { - return this.sessions.filter(s => s.status === 'active') as ActiveSessionResource[]; - } - get signedInSessions(): SignedInSessionResource[] { return this.sessions.filter(s => s.status === 'active' || s.status === 'pending') as SignedInSessionResource[]; } diff --git a/packages/clerk-js/src/core/resources/Passkey.ts b/packages/clerk-js/src/core/resources/Passkey.ts index ea415f635c5..f3325604f67 100644 --- a/packages/clerk-js/src/core/resources/Passkey.ts +++ b/packages/clerk-js/src/core/resources/Passkey.ts @@ -1,4 +1,8 @@ import { ClerkWebAuthnError } from '@clerk/shared/error'; +import { + serializePublicKeyCredential, + webAuthnCreateCredential as webAuthnCreateCredentialOnWindow, +} from '@clerk/shared/internal/clerk-js/passkeys'; import type { DeletedObjectJSON, DeletedObjectResource, @@ -15,10 +19,6 @@ import { } from '@clerk/shared/webauthn'; import { unixEpochToDate } from '../../utils/date'; -import { - serializePublicKeyCredential, - webAuthnCreateCredential as webAuthnCreateCredentialOnWindow, -} from '../../utils/passkeys'; import { clerkMissingWebAuthnPublicKeyOptions } from '../errors'; import { BaseResource, DeletedObject, PasskeyVerification } from './internal'; diff --git a/packages/clerk-js/src/core/resources/SamlAccount.ts b/packages/clerk-js/src/core/resources/SamlAccount.ts deleted file mode 100644 index 173ea50e848..00000000000 --- a/packages/clerk-js/src/core/resources/SamlAccount.ts +++ /dev/null @@ -1,132 +0,0 @@ -import type { - SamlAccountConnectionJSON, - SamlAccountConnectionJSONSnapshot, - SamlAccountConnectionResource, - SamlAccountJSON, - SamlAccountJSONSnapshot, - SamlAccountResource, - SamlIdpSlug, - VerificationResource, -} from '@clerk/shared/types'; - -import { unixEpochToDate } from '../../utils/date'; -import { BaseResource } from './Base'; -import { Verification } from './Verification'; - -export class SamlAccount extends BaseResource implements SamlAccountResource { - id!: string; - provider: SamlIdpSlug = 'saml_custom'; - providerUserId: string | null = null; - active = false; - emailAddress = ''; - firstName = ''; - lastName = ''; - verification: VerificationResource | null = null; - samlConnection: SamlAccountConnectionResource | null = null; - lastAuthenticatedAt: Date | null = null; - enterpriseConnectionId: string | null = null; - - public constructor(data: Partial, pathRoot: string); - public constructor(data: SamlAccountJSON | SamlAccountJSONSnapshot, pathRoot: string) { - super(); - this.pathRoot = pathRoot; - this.fromJSON(data); - } - - protected fromJSON(data: SamlAccountJSON | SamlAccountJSONSnapshot | null): this { - if (!data) { - return this; - } - - this.id = data.id; - this.provider = data.provider; - this.providerUserId = data.provider_user_id; - this.active = data.active; - this.emailAddress = data.email_address; - this.firstName = data.first_name; - this.lastName = data.last_name; - this.enterpriseConnectionId = data.enterprise_connection_id; - - if (data.verification) { - this.verification = new Verification(data.verification); - } - - if (data.saml_connection) { - this.samlConnection = new SamlAccountConnection(data.saml_connection); - } - - this.lastAuthenticatedAt = data.last_authenticated_at ? unixEpochToDate(data.last_authenticated_at) : null; - - return this; - } - - public __internal_toSnapshot(): SamlAccountJSONSnapshot { - return { - object: 'saml_account', - id: this.id, - provider: this.provider, - provider_user_id: this.providerUserId, - active: this.active, - email_address: this.emailAddress, - first_name: this.firstName, - last_name: this.lastName, - verification: this.verification?.__internal_toSnapshot() || null, - saml_connection: this.samlConnection?.__internal_toSnapshot(), - enterprise_connection_id: this.enterpriseConnectionId, - last_authenticated_at: this.lastAuthenticatedAt ? this.lastAuthenticatedAt.getTime() : null, - }; - } -} - -export class SamlAccountConnection extends BaseResource implements SamlAccountConnectionResource { - id!: string; - name!: string; - domain!: string; - active!: boolean; - provider!: string; - syncUserAttributes!: boolean; - allowSubdomains!: boolean; - allowIdpInitiated!: boolean; - disableAdditionalIdentifications!: boolean; - createdAt!: Date; - updatedAt!: Date; - - constructor(data: SamlAccountConnectionJSON | SamlAccountConnectionJSONSnapshot | null) { - super(); - this.fromJSON(data); - } - protected fromJSON(data: SamlAccountConnectionJSON | SamlAccountConnectionJSONSnapshot | null): this { - if (data) { - this.id = data.id; - this.name = data.name; - this.domain = data.domain; - this.active = data.active; - this.provider = data.provider; - this.syncUserAttributes = data.sync_user_attributes; - this.allowSubdomains = data.allow_subdomains; - this.allowIdpInitiated = data.allow_idp_initiated; - this.disableAdditionalIdentifications = data.disable_additional_identifications; - this.createdAt = unixEpochToDate(data.created_at); - this.updatedAt = unixEpochToDate(data.updated_at); - } - - return this; - } - - public __internal_toSnapshot(): SamlAccountConnectionJSONSnapshot { - return { - object: 'saml_account_connection', - id: this.id, - name: this.name, - domain: this.domain, - active: this.active, - provider: this.provider, - sync_user_attributes: this.syncUserAttributes, - allow_subdomains: this.allowSubdomains, - allow_idp_initiated: this.allowIdpInitiated, - disable_additional_identifications: this.disableAdditionalIdentifications, - created_at: this.createdAt.getTime(), - updated_at: this.updatedAt.getTime(), - }; - } -} diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index 7ac829ca520..a31123b8d71 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -1,5 +1,10 @@ import { createCheckAuthorization } from '@clerk/shared/authorization'; import { ClerkWebAuthnError, is4xxError } from '@clerk/shared/error'; +import { + convertJSONToPublicKeyRequestOptions, + serializePublicKeyCredentialAssertion, + webAuthnGetCredential as webAuthnGetCredentialOnWindow, +} from '@clerk/shared/internal/clerk-js/passkeys'; import { retry } from '@clerk/shared/retry'; import type { ActClaim, @@ -28,11 +33,6 @@ import { isWebAuthnSupported as isWebAuthnSupportedOnWindow } from '@clerk/share import { unixEpochToDate } from '@/utils/date'; import { debugLogger } from '@/utils/debug'; -import { - convertJSONToPublicKeyRequestOptions, - serializePublicKeyCredentialAssertion, - webAuthnGetCredential as webAuthnGetCredentialOnWindow, -} from '@/utils/passkeys'; import { TokenId } from '@/utils/tokenId'; import { clerkInvalidStrategy, clerkMissingWebAuthnPublicKeyOptions } from '../errors'; diff --git a/packages/clerk-js/src/core/resources/SignIn.ts b/packages/clerk-js/src/core/resources/SignIn.ts index 8888af69a0a..a0704986521 100644 --- a/packages/clerk-js/src/core/resources/SignIn.ts +++ b/packages/clerk-js/src/core/resources/SignIn.ts @@ -1,5 +1,13 @@ import { inBrowser } from '@clerk/shared/browser'; import { type ClerkError, ClerkRuntimeError, ClerkWebAuthnError } from '@clerk/shared/error'; +import { + convertJSONToPublicKeyRequestOptions, + serializePublicKeyCredentialAssertion, + webAuthnGetCredential as webAuthnGetCredentialOnWindow, +} from '@clerk/shared/internal/clerk-js/passkeys'; +import { createValidatePassword } from '@clerk/shared/internal/clerk-js/passwords/password'; +import { getClerkQueryParam } from '@clerk/shared/internal/clerk-js/queryParams'; +import { windowNavigate } from '@clerk/shared/internal/clerk-js/windowNavigate'; import { Poller } from '@clerk/shared/poller'; import type { AttemptFirstFactorParams, @@ -24,7 +32,6 @@ import type { ResetPasswordEmailCodeFactorConfig, ResetPasswordParams, ResetPasswordPhoneCodeFactorConfig, - SamlConfig, SignInCreateParams, SignInFirstFactor, SignInFutureBackupCodeVerifyParams, @@ -64,31 +71,14 @@ import { import { debugLogger } from '@/utils/debug'; -import { - generateSignatureWithBase, - generateSignatureWithCoinbaseWallet, - generateSignatureWithMetamask, - generateSignatureWithOKXWallet, - getBaseIdentifier, - getBrowserLocale, - getClerkQueryParam, - getCoinbaseWalletIdentifier, - getMetamaskIdentifier, - getOKXWalletIdentifier, - windowNavigate, -} from '../../utils'; +import { getBrowserLocale, web3 } from '../../utils'; import { _authenticateWithPopup, _futureAuthenticateWithPopup, wrapWithPopupRoutes, } from '../../utils/authenticateWithPopup'; -import { - convertJSONToPublicKeyRequestOptions, - serializePublicKeyCredentialAssertion, - webAuthnGetCredential as webAuthnGetCredentialOnWindow, -} from '../../utils/passkeys'; -import { createValidatePassword } from '../../utils/passwords/password'; import { runAsyncResourceTask } from '../../utils/runAsyncResourceTask'; +import { loadZxcvbn } from '../../utils/zxcvbn'; import { clerkInvalidFAPIResponse, clerkInvalidStrategy, @@ -220,12 +210,6 @@ export class SignIn extends BaseResource implements SignInResource { case 'reset_password_email_code': config = { emailAddressId: params.emailAddressId } as ResetPasswordEmailCodeFactorConfig; break; - case 'saml': - config = { - redirectUrl: params.redirectUrl, - actionCompleteRedirectUrl: params.actionCompleteRedirectUrl, - } as SamlConfig; - break; case 'enterprise_sso': config = { redirectUrl: params.redirectUrl, @@ -345,7 +329,7 @@ export class SignIn extends BaseResource implements SignInResource { }); } - if (strategy === 'saml' || strategy === 'enterprise_sso') { + if (strategy === 'enterprise_sso') { await this.prepareFirstFactor({ strategy, redirectUrl, @@ -425,37 +409,37 @@ export class SignIn extends BaseResource implements SignInResource { }; public authenticateWithMetamask = async (): Promise => { - const identifier = await getMetamaskIdentifier(); + const identifier = await web3().getMetamaskIdentifier(); return this.authenticateWithWeb3({ identifier, - generateSignature: generateSignatureWithMetamask, + generateSignature: web3().generateSignatureWithMetamask, strategy: 'web3_metamask_signature', }); }; public authenticateWithCoinbaseWallet = async (): Promise => { - const identifier = await getCoinbaseWalletIdentifier(); + const identifier = await web3().getCoinbaseWalletIdentifier(); return this.authenticateWithWeb3({ identifier, - generateSignature: generateSignatureWithCoinbaseWallet, + generateSignature: web3().generateSignatureWithCoinbaseWallet, strategy: 'web3_coinbase_wallet_signature', }); }; public authenticateWithBase = async (): Promise => { - const identifier = await getBaseIdentifier(); + const identifier = await web3().getBaseIdentifier(); return this.authenticateWithWeb3({ identifier, - generateSignature: generateSignatureWithBase, + generateSignature: web3().generateSignatureWithBase, strategy: 'web3_base_signature', }); }; public authenticateWithOKXWallet = async (): Promise => { - const identifier = await getOKXWalletIdentifier(); + const identifier = await web3().getOKXWalletIdentifier(); return this.authenticateWithWeb3({ identifier, - generateSignature: generateSignatureWithOKXWallet, + generateSignature: web3().generateSignatureWithOKXWallet, strategy: 'web3_okx_wallet_signature', }); }; @@ -531,7 +515,7 @@ export class SignIn extends BaseResource implements SignInResource { validatePassword: ReturnType = (password, cb) => { if (SignIn.clerk.__unstable__environment?.userSettings.passwordSettings) { - return createValidatePassword({ + return createValidatePassword(loadZxcvbn(), { ...SignIn.clerk.__unstable__environment?.userSettings.passwordSettings, validatePassword: true, })(password, cb); @@ -631,6 +615,8 @@ class SignInFuture implements SignInFutureResource { verifyBackupCode: this.verifyBackupCode.bind(this), }; + #hasBeenFinalized = false; + constructor(readonly resource: SignIn) {} get id() { @@ -686,6 +672,10 @@ class SignInFuture implements SignInFutureResource { return this.resource.secondFactorVerification; } + get hasBeenFinalized() { + return this.#hasBeenFinalized; + } + async sendResetPasswordEmailCode(): Promise<{ error: ClerkError | null }> { if (!this.resource.id) { throw new Error('Cannot reset password without a sign in.'); @@ -971,20 +961,20 @@ class SignInFuture implements SignInFutureResource { let generateSignature; switch (provider) { case 'metamask': - identifier = await getMetamaskIdentifier(); - generateSignature = generateSignatureWithMetamask; + identifier = await web3().getMetamaskIdentifier(); + generateSignature = web3().generateSignatureWithMetamask; break; case 'coinbase_wallet': - identifier = await getCoinbaseWalletIdentifier(); - generateSignature = generateSignatureWithCoinbaseWallet; + identifier = await web3().getCoinbaseWalletIdentifier(); + generateSignature = web3().generateSignatureWithCoinbaseWallet; break; case 'base': - identifier = await getBaseIdentifier(); - generateSignature = generateSignatureWithBase; + identifier = await web3().getBaseIdentifier(); + generateSignature = web3().generateSignatureWithBase; break; case 'okx_wallet': - identifier = await getOKXWalletIdentifier(); - generateSignature = generateSignatureWithOKXWallet; + identifier = await web3().getOKXWalletIdentifier(); + generateSignature = web3().generateSignatureWithOKXWallet; break; default: throw new Error(`Unsupported Web3 provider: ${provider}`); @@ -1163,9 +1153,13 @@ class SignInFuture implements SignInFutureResource { } return runAsyncResourceTask(this.resource, async () => { - // Reload the client to prevent an issue where the created session is not picked up. - await SignIn.clerk.client?.reload(); + // Reload the client if the created session is not in the client's sessions. This can happen during modal SSO + // flows where the in-memory client does not have the created session. + if (SignIn.clerk.client && !SignIn.clerk.client.sessions.some(s => s.id === this.resource.createdSessionId)) { + await SignIn.clerk.client.reload(); + } + this.#hasBeenFinalized = true; await SignIn.clerk.setActive({ session: this.resource.createdSessionId, navigate }); }); } diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index bb520ccdcea..928f51924bb 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -1,4 +1,6 @@ import { type ClerkError, ClerkRuntimeError, isCaptchaError, isClerkAPIResponseError } from '@clerk/shared/error'; +import { createValidatePassword } from '@clerk/shared/internal/clerk-js/passwords/password'; +import { windowNavigate } from '@clerk/shared/internal/clerk-js/windowNavigate'; import { Poller } from '@clerk/shared/poller'; import type { AttemptEmailAddressVerificationParams, @@ -42,28 +44,16 @@ import type { import { debugLogger } from '@/utils/debug'; -import { - generateSignatureWithBase, - generateSignatureWithCoinbaseWallet, - generateSignatureWithMetamask, - generateSignatureWithOKXWallet, - getBaseIdentifier, - getBrowserLocale, - getClerkQueryParam, - getCoinbaseWalletIdentifier, - getMetamaskIdentifier, - getOKXWalletIdentifier, - windowNavigate, -} from '../../utils'; +import { getBrowserLocale, getClerkQueryParam, web3 } from '../../utils'; import { _authenticateWithPopup, _futureAuthenticateWithPopup, wrapWithPopupRoutes, } from '../../utils/authenticateWithPopup'; import { CaptchaChallenge } from '../../utils/captcha/CaptchaChallenge'; -import { createValidatePassword } from '../../utils/passwords/password'; import { normalizeUnsafeMetadata } from '../../utils/resourceParams'; import { runAsyncResourceTask } from '../../utils/runAsyncResourceTask'; +import { loadZxcvbn } from '../../utils/zxcvbn'; import { clerkInvalidFAPIResponse, clerkMissingOptionError, @@ -321,10 +311,10 @@ export class SignUp extends BaseResource implements SignUpResource { legalAccepted?: boolean; }, ): Promise => { - const identifier = await getMetamaskIdentifier(); + const identifier = await web3().getMetamaskIdentifier(); return this.authenticateWithWeb3({ identifier, - generateSignature: generateSignatureWithMetamask, + generateSignature: web3().generateSignatureWithMetamask, unsafeMetadata: params?.unsafeMetadata, strategy: 'web3_metamask_signature', legalAccepted: params?.legalAccepted, @@ -336,10 +326,10 @@ export class SignUp extends BaseResource implements SignUpResource { legalAccepted?: boolean; }, ): Promise => { - const identifier = await getCoinbaseWalletIdentifier(); + const identifier = await web3().getCoinbaseWalletIdentifier(); return this.authenticateWithWeb3({ identifier, - generateSignature: generateSignatureWithCoinbaseWallet, + generateSignature: web3().generateSignatureWithCoinbaseWallet, unsafeMetadata: params?.unsafeMetadata, strategy: 'web3_coinbase_wallet_signature', legalAccepted: params?.legalAccepted, @@ -351,10 +341,10 @@ export class SignUp extends BaseResource implements SignUpResource { legalAccepted?: boolean; }, ): Promise => { - const identifier = await getBaseIdentifier(); + const identifier = await web3().getBaseIdentifier(); return this.authenticateWithWeb3({ identifier, - generateSignature: generateSignatureWithBase, + generateSignature: web3().generateSignatureWithBase, unsafeMetadata: params?.unsafeMetadata, strategy: 'web3_base_signature', legalAccepted: params?.legalAccepted, @@ -366,10 +356,10 @@ export class SignUp extends BaseResource implements SignUpResource { legalAccepted?: boolean; }, ): Promise => { - const identifier = await getOKXWalletIdentifier(); + const identifier = await web3().getOKXWalletIdentifier(); return this.authenticateWithWeb3({ identifier, - generateSignature: generateSignatureWithOKXWallet, + generateSignature: web3().generateSignatureWithOKXWallet, unsafeMetadata: params?.unsafeMetadata, strategy: 'web3_okx_wallet_signature', legalAccepted: params?.legalAccepted, @@ -467,7 +457,7 @@ export class SignUp extends BaseResource implements SignUpResource { validatePassword: ReturnType = (password, cb) => { if (SignUp.clerk.__unstable__environment?.userSettings.passwordSettings) { - return createValidatePassword({ + return createValidatePassword(loadZxcvbn(), { ...SignUp.clerk.__unstable__environment?.userSettings.passwordSettings, validatePassword: true, })(password, cb); @@ -580,6 +570,8 @@ class SignUpFuture implements SignUpFutureResource { verifyPhoneCode: this.verifyPhoneCode.bind(this), }; + #hasBeenFinalized = false; + constructor(readonly resource: SignUp) {} get id() { @@ -680,6 +672,10 @@ class SignUpFuture implements SignUpFutureResource { return undefined; } + get hasBeenFinalized() { + return this.#hasBeenFinalized; + } + private async getCaptchaToken(): Promise<{ captchaToken?: string; captchaWidgetType?: CaptchaWidgetType; @@ -877,20 +873,20 @@ class SignUpFuture implements SignUpFutureResource { let generateSignature; switch (provider) { case 'metamask': - identifier = await getMetamaskIdentifier(); - generateSignature = generateSignatureWithMetamask; + identifier = await web3().getMetamaskIdentifier(); + generateSignature = web3().generateSignatureWithMetamask; break; case 'coinbase_wallet': - identifier = await getCoinbaseWalletIdentifier(); - generateSignature = generateSignatureWithCoinbaseWallet; + identifier = await web3().getCoinbaseWalletIdentifier(); + generateSignature = web3().generateSignatureWithCoinbaseWallet; break; case 'base': - identifier = await getBaseIdentifier(); - generateSignature = generateSignatureWithBase; + identifier = await web3().getBaseIdentifier(); + generateSignature = web3().generateSignatureWithBase; break; case 'okx_wallet': - identifier = await getOKXWalletIdentifier(); - generateSignature = generateSignatureWithOKXWallet; + identifier = await web3().getOKXWalletIdentifier(); + generateSignature = web3().generateSignatureWithOKXWallet; break; default: throw new Error(`Unsupported Web3 provider: ${provider}`); @@ -946,6 +942,7 @@ class SignUpFuture implements SignUpFutureResource { throw new Error('Cannot finalize sign-up without a created session.'); } + this.#hasBeenFinalized = true; await SignUp.clerk.setActive({ session: this.resource.createdSessionId, navigate }); }); } diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index 8f61851b1c5..ae7ef203c63 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -1,3 +1,4 @@ +import { getFullName } from '@clerk/shared/internal/clerk-js/user'; import type { BackupCodeJSON, BackupCodeResource, @@ -19,7 +20,6 @@ import type { PasskeyResource, PhoneNumberResource, RemoveUserPasswordParams, - SamlAccountResource, SetProfileImageParams, TOTPJSON, TOTPResource, @@ -34,7 +34,6 @@ import type { import { unixEpochToDate } from '../../utils/date'; import { normalizeUnsafeMetadata } from '../../utils/resourceParams'; -import { getFullName } from '../../utils/user'; import { eventBus, events } from '../events'; import { addPaymentMethod, getPaymentMethods, initializePaymentMethod } from '../modules/billing'; import { BackupCode } from './BackupCode'; @@ -49,7 +48,6 @@ import { OrganizationSuggestion, Passkey, PhoneNumber, - SamlAccount, SessionWithActivities, TOTP, UserOrganizationInvitation, @@ -68,9 +66,6 @@ export class User extends BaseResource implements UserResource { externalAccounts: ExternalAccountResource[] = []; enterpriseAccounts: EnterpriseAccountResource[] = []; passkeys: PasskeyResource[] = []; - - samlAccounts: SamlAccountResource[] = []; - organizationMemberships: OrganizationMembershipResource[] = []; passwordEnabled = false; firstName: string | null = null; @@ -365,8 +360,6 @@ export class User extends BaseResource implements UserResource { this.organizationMemberships = (data.organization_memberships || []).map(om => new OrganizationMembership(om)); - this.samlAccounts = (data.saml_accounts || []).map(sa => new SamlAccount(sa, this.path() + '/saml_accounts')); - this.enterpriseAccounts = (data.enterprise_accounts || []).map( ea => new EnterpriseAccount(ea, this.path() + '/enterprise_accounts'), ); @@ -413,7 +406,6 @@ export class User extends BaseResource implements UserResource { external_accounts: this.externalAccounts.map(ea => ea.__internal_toSnapshot()), passkeys: this.passkeys.map(passkey => passkey.__internal_toSnapshot()), organization_memberships: this.organizationMemberships.map(om => om.__internal_toSnapshot()), - saml_accounts: this.samlAccounts.map(sa => sa.__internal_toSnapshot()), enterprise_accounts: this.enterpriseAccounts.map(ea => ea.__internal_toSnapshot()), totp_enabled: this.totpEnabled, backup_code_enabled: this.backupCodeEnabled, diff --git a/packages/clerk-js/src/core/resources/UserSettings.ts b/packages/clerk-js/src/core/resources/UserSettings.ts index 8af85ab38c8..5e1ae2b84ac 100644 --- a/packages/clerk-js/src/core/resources/UserSettings.ts +++ b/packages/clerk-js/src/core/resources/UserSettings.ts @@ -6,7 +6,6 @@ import type { PasskeySettingsData, PasswordSettingsData, PhoneCodeChannel, - SamlSettings, SignInData, SignUpData, UsernameSettingsData, @@ -112,9 +111,6 @@ export class UserSettings extends BaseResource implements UserSettingsResource { show_sign_in_button: false, }; passwordSettings: PasswordSettingsData = {} as PasswordSettingsData; - saml: SamlSettings = { - enabled: false, - }; signIn: SignInData = { second_factor: { required: false, @@ -229,7 +225,6 @@ export class UserSettings extends BaseResource implements UserSettingsResource { : Math.min(data.password_settings?.max_length ?? defaultMaxPasswordLength, defaultMaxPasswordLength), } : this.passwordSettings; - this.saml = this.withDefault(data.saml, this.saml); this.signIn = this.withDefault(data.sign_in, this.signIn); this.signUp = this.withDefault(data.sign_up, this.signUp); this.social = this.withDefault(data.social, this.social); @@ -256,7 +251,6 @@ export class UserSettings extends BaseResource implements UserSettingsResource { attributes: this.attributes, passkey_settings: this.passkeySettings, password_settings: this.passwordSettings, - saml: this.saml, sign_in: this.signIn, sign_up: this.signUp, social: this.social, diff --git a/packages/clerk-js/src/core/resources/Verification.ts b/packages/clerk-js/src/core/resources/Verification.ts index d9f739d2565..af1f61a4f88 100644 --- a/packages/clerk-js/src/core/resources/Verification.ts +++ b/packages/clerk-js/src/core/resources/Verification.ts @@ -1,4 +1,5 @@ import { ClerkAPIError, errorToJSON } from '@clerk/shared/error'; +import { convertJSONToPublicKeyCreateOptions } from '@clerk/shared/internal/clerk-js/passkeys'; import type { PasskeyVerificationResource, PhoneCodeChannel, @@ -17,7 +18,6 @@ import type { } from '@clerk/shared/types'; import { unixEpochToDate } from '../../utils/date'; -import { convertJSONToPublicKeyCreateOptions } from '../../utils/passkeys'; import { BaseResource } from './internal'; export class Verification extends BaseResource implements VerificationResource { diff --git a/packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts b/packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts index d168a79d57d..55b2dac50be 100644 --- a/packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/SignIn.test.ts @@ -1290,11 +1290,11 @@ describe('SignIn', () => { }); BaseResource._fetch = mockFetch; - const getMetamaskIdentifierModule = await import('../../../utils'); - vi.spyOn(getMetamaskIdentifierModule, 'getMetamaskIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - vi.spyOn(getMetamaskIdentifierModule, 'generateSignatureWithMetamask').mockResolvedValue('signature_123'); + const utilsModule = await import('../../../utils'); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getMetamaskIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithMetamask: vi.fn().mockResolvedValue('signature_123'), + } as any); const signIn = new SignIn(); await signIn.__internal_future.web3({ strategy: 'web3_metamask_signature' }); @@ -1348,13 +1348,11 @@ describe('SignIn', () => { }); BaseResource._fetch = mockFetch; - const getCoinbaseWalletIdentifierModule = await import('../../../utils'); - vi.spyOn(getCoinbaseWalletIdentifierModule, 'getCoinbaseWalletIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - vi.spyOn(getCoinbaseWalletIdentifierModule, 'generateSignatureWithCoinbaseWallet').mockResolvedValue( - 'signature_123', - ); + const utilsModule = await import('../../../utils'); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getCoinbaseWalletIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithCoinbaseWallet: vi.fn().mockResolvedValue('signature_123'), + } as any); const signIn = new SignIn(); await signIn.__internal_future.web3({ strategy: 'web3_coinbase_wallet_signature' }); @@ -1393,19 +1391,16 @@ describe('SignIn', () => { }); BaseResource._fetch = mockFetch; - const getCoinbaseWalletIdentifierModule = await import('../../../utils'); - vi.spyOn(getCoinbaseWalletIdentifierModule, 'getCoinbaseWalletIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - + const utilsModule = await import('../../../utils'); const mockGenerateSignature = vi .fn() .mockRejectedValueOnce({ code: 4001, message: 'User rejected' }) .mockResolvedValueOnce('signature_123'); - vi.spyOn(getCoinbaseWalletIdentifierModule, 'generateSignatureWithCoinbaseWallet').mockImplementation( - mockGenerateSignature, - ); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getCoinbaseWalletIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithCoinbaseWallet: mockGenerateSignature, + } as any); const signIn = new SignIn(); await signIn.__internal_future.web3({ strategy: 'web3_coinbase_wallet_signature' }); @@ -1441,13 +1436,12 @@ describe('SignIn', () => { }); BaseResource._fetch = mockFetch; - const getCoinbaseWalletIdentifierModule = await import('../../../utils'); - vi.spyOn(getCoinbaseWalletIdentifierModule, 'getCoinbaseWalletIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - + const utilsModule = await import('../../../utils'); const mockError = { code: 5000, message: 'Other error' }; - vi.spyOn(getCoinbaseWalletIdentifierModule, 'generateSignatureWithCoinbaseWallet').mockRejectedValue(mockError); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getCoinbaseWalletIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithCoinbaseWallet: vi.fn().mockRejectedValue(mockError), + } as any); const signIn = new SignIn(); const result = await signIn.__internal_future.web3({ strategy: 'web3_coinbase_wallet_signature' }); @@ -1479,11 +1473,11 @@ describe('SignIn', () => { }); BaseResource._fetch = mockFetch; - const getBaseIdentifierModule = await import('../../../utils'); - vi.spyOn(getBaseIdentifierModule, 'getBaseIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - vi.spyOn(getBaseIdentifierModule, 'generateSignatureWithBase').mockResolvedValue('signature_123'); + const utilsModule = await import('../../../utils'); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getBaseIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithBase: vi.fn().mockResolvedValue('signature_123'), + } as any); const signIn = new SignIn(); await signIn.__internal_future.web3({ strategy: 'web3_base_signature' }); @@ -1522,11 +1516,11 @@ describe('SignIn', () => { }); BaseResource._fetch = mockFetch; - const getOKXWalletIdentifierModule = await import('../../../utils'); - vi.spyOn(getOKXWalletIdentifierModule, 'getOKXWalletIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - vi.spyOn(getOKXWalletIdentifierModule, 'generateSignatureWithOKXWallet').mockResolvedValue('signature_123'); + const utilsModule = await import('../../../utils'); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getOKXWalletIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithOKXWallet: vi.fn().mockResolvedValue('signature_123'), + } as any); const signIn = new SignIn(); await signIn.__internal_future.web3({ strategy: 'web3_okx_wallet_signature' }); @@ -1552,10 +1546,10 @@ describe('SignIn', () => { }); BaseResource._fetch = mockFetch; - const getMetamaskIdentifierModule = await import('../../../utils'); - vi.spyOn(getMetamaskIdentifierModule, 'getMetamaskIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); + const utilsModule = await import('../../../utils'); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getMetamaskIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + } as any); const signIn = new SignIn(); const result = await signIn.__internal_future.web3({ strategy: 'web3_metamask_signature' }); @@ -1584,10 +1578,10 @@ describe('SignIn', () => { }); BaseResource._fetch = mockFetch; - const getMetamaskIdentifierModule = await import('../../../utils'); - vi.spyOn(getMetamaskIdentifierModule, 'getMetamaskIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); + const utilsModule = await import('../../../utils'); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getMetamaskIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + } as any); const signIn = new SignIn(); const result = await signIn.__internal_future.web3({ strategy: 'web3_metamask_signature' }); diff --git a/packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts b/packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts index a0f6dbf653b..43ad74a005e 100644 --- a/packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/SignUp.test.ts @@ -362,17 +362,18 @@ describe('SignUp', () => { }); BaseResource._fetch = mockFetch; - const getMetamaskIdentifierModule = await import('../../../utils'); - vi.spyOn(getMetamaskIdentifierModule, 'getMetamaskIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - vi.spyOn(getMetamaskIdentifierModule, 'generateSignatureWithMetamask').mockResolvedValue('signature_123'); + const utilsModule = await import('../../../utils'); + const mockGenerateSignature = vi.fn().mockResolvedValue('signature_123'); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getMetamaskIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithMetamask: mockGenerateSignature, + } as any); const signUp = new SignUp(); await signUp.__internal_future.web3({ strategy: 'web3_metamask_signature' }); // Verify signature generation was called - expect(getMetamaskIdentifierModule.generateSignatureWithMetamask).toHaveBeenCalled(); + expect(mockGenerateSignature).toHaveBeenCalled(); }); it('authenticates with coinbase_wallet strategy', async () => { @@ -402,19 +403,18 @@ describe('SignUp', () => { }); BaseResource._fetch = mockFetch; - const getCoinbaseWalletIdentifierModule = await import('../../../utils'); - vi.spyOn(getCoinbaseWalletIdentifierModule, 'getCoinbaseWalletIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - vi.spyOn(getCoinbaseWalletIdentifierModule, 'generateSignatureWithCoinbaseWallet').mockResolvedValue( - 'signature_123', - ); + const utilsModule = await import('../../../utils'); + const mockGenerateSignature = vi.fn().mockResolvedValue('signature_123'); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getCoinbaseWalletIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithCoinbaseWallet: mockGenerateSignature, + } as any); const signUp = new SignUp(); await signUp.__internal_future.web3({ strategy: 'web3_coinbase_wallet_signature' }); // Verify signature generation was called - expect(getCoinbaseWalletIdentifierModule.generateSignatureWithCoinbaseWallet).toHaveBeenCalled(); + expect(mockGenerateSignature).toHaveBeenCalled(); }); it('authenticates with base strategy', async () => { @@ -444,17 +444,18 @@ describe('SignUp', () => { }); BaseResource._fetch = mockFetch; - const getBaseIdentifierModule = await import('../../../utils'); - vi.spyOn(getBaseIdentifierModule, 'getBaseIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - vi.spyOn(getBaseIdentifierModule, 'generateSignatureWithBase').mockResolvedValue('signature_123'); + const utilsModule = await import('../../../utils'); + const mockGenerateSignature = vi.fn().mockResolvedValue('signature_123'); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getBaseIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithBase: mockGenerateSignature, + } as any); const signUp = new SignUp(); await signUp.__internal_future.web3({ strategy: 'web3_base_signature' }); // Verify signature generation was called - expect(getBaseIdentifierModule.generateSignatureWithBase).toHaveBeenCalled(); + expect(mockGenerateSignature).toHaveBeenCalled(); }); it('authenticates with okx_wallet strategy', async () => { @@ -484,17 +485,18 @@ describe('SignUp', () => { }); BaseResource._fetch = mockFetch; - const getOKXWalletIdentifierModule = await import('../../../utils'); - vi.spyOn(getOKXWalletIdentifierModule, 'getOKXWalletIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - vi.spyOn(getOKXWalletIdentifierModule, 'generateSignatureWithOKXWallet').mockResolvedValue('signature_123'); + const utilsModule = await import('../../../utils'); + const mockGenerateSignature = vi.fn().mockResolvedValue('signature_123'); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getOKXWalletIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithOKXWallet: mockGenerateSignature, + } as any); const signUp = new SignUp(); await signUp.__internal_future.web3({ strategy: 'web3_okx_wallet_signature' }); // Verify signature generation was called - expect(getOKXWalletIdentifierModule.generateSignatureWithOKXWallet).toHaveBeenCalled(); + expect(mockGenerateSignature).toHaveBeenCalled(); }); it('retries coinbase_wallet signature on error code 4001', async () => { @@ -524,19 +526,16 @@ describe('SignUp', () => { }); BaseResource._fetch = mockFetch; - const getCoinbaseWalletIdentifierModule = await import('../../../utils'); - vi.spyOn(getCoinbaseWalletIdentifierModule, 'getCoinbaseWalletIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - + const utilsModule = await import('../../../utils'); const mockGenerateSignature = vi .fn() .mockRejectedValueOnce({ code: 4001, message: 'User rejected' }) .mockResolvedValueOnce('signature_123'); - vi.spyOn(getCoinbaseWalletIdentifierModule, 'generateSignatureWithCoinbaseWallet').mockImplementation( - mockGenerateSignature, - ); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getCoinbaseWalletIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithCoinbaseWallet: mockGenerateSignature, + } as any); const signUp = new SignUp(); await signUp.__internal_future.web3({ strategy: 'web3_coinbase_wallet_signature' }); @@ -568,13 +567,12 @@ describe('SignUp', () => { }); BaseResource._fetch = mockFetch; - const getCoinbaseWalletIdentifierModule = await import('../../../utils'); - vi.spyOn(getCoinbaseWalletIdentifierModule, 'getCoinbaseWalletIdentifier').mockResolvedValue( - '0x1234567890123456789012345678901234567890', - ); - + const utilsModule = await import('../../../utils'); const mockError = { code: 5000, message: 'Other error' }; - vi.spyOn(getCoinbaseWalletIdentifierModule, 'generateSignatureWithCoinbaseWallet').mockRejectedValue(mockError); + vi.spyOn(utilsModule, 'web3').mockReturnValue({ + getCoinbaseWalletIdentifier: vi.fn().mockResolvedValue('0x1234567890123456789012345678901234567890'), + generateSignatureWithCoinbaseWallet: vi.fn().mockRejectedValue(mockError), + } as any); const signUp = new SignUp(); const result = await signUp.__internal_future.web3({ strategy: 'web3_coinbase_wallet_signature' }); diff --git a/packages/clerk-js/src/core/resources/index.ts b/packages/clerk-js/src/core/resources/index.ts index f49a5f0882e..b07196edac8 100644 --- a/packages/clerk-js/src/core/resources/index.ts +++ b/packages/clerk-js/src/core/resources/index.ts @@ -8,7 +8,6 @@ export * from './ExternalAccount'; export * from './IdentificationLink'; export * from './Image'; export * from './PhoneNumber'; -export * from './SamlAccount'; export * from './Session'; export * from './SessionWithActivities'; export * from './SignIn'; @@ -18,4 +17,3 @@ export * from './User'; export * from './Verification'; export * from './Waitlist'; export * from './Web3Wallet'; - diff --git a/packages/clerk-js/src/core/resources/internal.ts b/packages/clerk-js/src/core/resources/internal.ts index e9e1e1f5fa8..1c905019344 100644 --- a/packages/clerk-js/src/core/resources/internal.ts +++ b/packages/clerk-js/src/core/resources/internal.ts @@ -1,45 +1,46 @@ export type { Clerk } from '../clerk'; +// Ordering matters. If you move this you will be fired !! jk export * from './Base'; -export * from './UserSettings'; -export * from './CommerceSettings'; + +export * from './APIKey'; export * from './AuthConfig'; -export * from './Client'; export * from './BillingCheckout'; -export * from './Feature'; -export * from './BillingStatement'; export * from './BillingPayment'; export * from './BillingPaymentMethod'; export * from './BillingPlan'; +export * from './BillingStatement'; export * from './BillingSubscription'; +export * from './Client'; +export * from './CommerceSettings'; export * from './DeletedObject'; export * from './DisplayConfig'; export * from './EmailAddress'; +export * from './EnterpriseAccount'; export * from './Environment'; export * from './ExternalAccount'; -export * from './EnterpriseAccount'; +export * from './Feature'; export * from './IdentificationLink'; export * from './Image'; -export * from './PhoneNumber'; export * from './Organization'; export * from './OrganizationDomain'; export * from './OrganizationInvitation'; export * from './OrganizationMembership'; export * from './OrganizationMembershipRequest'; export * from './OrganizationSuggestion'; -export * from './SamlAccount'; -export * from './Session'; export * from './Passkey'; +export * from './PhoneNumber'; export * from './ProtectConfig'; export * from './PublicUserData'; +export * from './Session'; export * from './SessionWithActivities'; export * from './SignIn'; -export * from './UserData'; export * from './SignUp'; export * from './Token'; export * from './TOTP'; export * from './User'; +export * from './UserData'; export * from './UserOrganizationInvitation'; +export * from './UserSettings'; export * from './Verification'; -export * from './Web3Wallet'; export * from './Waitlist'; -export * from './APIKey'; +export * from './Web3Wallet'; diff --git a/packages/clerk-js/src/core/signals.ts b/packages/clerk-js/src/core/signals.ts index 7d0310161db..2046eca114c 100644 --- a/packages/clerk-js/src/core/signals.ts +++ b/packages/clerk-js/src/core/signals.ts @@ -1,4 +1,5 @@ -import { type ClerkError, createClerkGlobalHookError, isClerkAPIResponseError } from '@clerk/shared/error'; +import type { ClerkAPIError, ClerkError } from '@clerk/shared/error'; +import { createClerkGlobalHookError, isClerkAPIResponseError } from '@clerk/shared/error'; import type { Errors, SignInErrors, SignInSignal, SignUpErrors, SignUpSignal } from '@clerk/shared/types'; import { snakeToCamel } from '@clerk/shared/underscore'; import { computed, signal } from 'alien-signals'; @@ -58,7 +59,11 @@ export function errorsToParsedErrors>( return parsedErrors; } - const hasFieldErrors = error.errors.some(error => 'meta' in error && error.meta && 'paramName' in error.meta); + function isFieldError(error: ClerkAPIError): boolean { + return 'meta' in error && error.meta && 'paramName' in error.meta && error.meta.paramName !== undefined; + } + + const hasFieldErrors = error.errors.some(isFieldError); if (hasFieldErrors) { error.errors.forEach(error => { if (parsedErrors.raw) { @@ -66,7 +71,7 @@ export function errorsToParsedErrors>( } else { parsedErrors.raw = [error]; } - if ('meta' in error && error.meta && 'paramName' in error.meta) { + if (isFieldError(error)) { const name = snakeToCamel(error.meta.paramName); if (name in parsedErrors.fields) { (parsedErrors.fields as any)[name] = error; diff --git a/packages/clerk-js/src/core/state.ts b/packages/clerk-js/src/core/state.ts index 2fea2b45820..411325c068b 100644 --- a/packages/clerk-js/src/core/state.ts +++ b/packages/clerk-js/src/core/state.ts @@ -49,10 +49,18 @@ export class State implements StateInterface { private onResourceUpdated = (payload: { resource: BaseResource }) => { if (payload.resource instanceof SignIn) { + const previousResource = this.signInResourceSignal().resource; + if (shouldIgnoreNullUpdate(previousResource, payload.resource)) { + return; + } this.signInResourceSignal({ resource: payload.resource }); } if (payload.resource instanceof SignUp) { + const previousResource = this.signUpResourceSignal().resource; + if (shouldIgnoreNullUpdate(previousResource, payload.resource)) { + return; + } this.signUpResourceSignal({ resource: payload.resource }); } }; @@ -67,3 +75,13 @@ export class State implements StateInterface { } }; } + +/** + * Returns true if the new resource is null and the previous resource has not been finalized. This is used to prevent + * nullifying the resource after it's been completed. + */ +function shouldIgnoreNullUpdate(previousResource: SignIn | null, newResource: SignIn | null): boolean; +function shouldIgnoreNullUpdate(previousResource: SignUp | null, newResource: SignUp | null): boolean; +function shouldIgnoreNullUpdate(previousResource: SignIn | SignUp | null, newResource: SignIn | SignUp | null) { + return !newResource?.id && previousResource && previousResource.__internal_future?.hasBeenFinalized === false; +} diff --git a/packages/clerk-js/src/core/warnings.ts b/packages/clerk-js/src/core/warnings.ts index ac0621a4160..dc18ffaced5 100644 --- a/packages/clerk-js/src/core/warnings.ts +++ b/packages/clerk-js/src/core/warnings.ts @@ -1,65 +1,2 @@ -import type { Serializable } from '@clerk/shared/types'; - -const formatWarning = (msg: string) => { - return `🔒 Clerk:\n${msg.trim()}\n(This notice only appears in development)`; -}; - -const createMessageForDisabledOrganizations = ( - componentName: - | 'OrganizationProfile' - | 'OrganizationSwitcher' - | 'OrganizationList' - | 'CreateOrganization' - | 'TaskChooseOrganization', -) => { - return formatWarning( - `The <${componentName}/> cannot be rendered when the feature is turned off. Visit 'dashboard.clerk.com' to enable the feature. Since the feature is turned off, this is no-op.`, - ); -}; -const createMessageForDisabledBilling = (componentName: 'PricingTable' | 'Checkout' | 'PlanDetails') => { - return formatWarning( - `The <${componentName}/> component cannot be rendered when billing is disabled. Visit 'https://dashboard.clerk.com/last-active?path=billing/settings' to follow the necessary steps to enable billing. Since billing is disabled, this is no-op.`, - ); -}; -const warnings = { - cannotRenderComponentWhenSessionExists: - 'The and components cannot render when a user is already signed in, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the Home URL instead.', - cannotRenderSignUpComponentWhenSessionExists: - 'The component cannot render when a user is already signed in, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the value set in `afterSignUp` URL instead.', - cannotRenderSignUpComponentWhenTaskExists: - 'The component cannot render when a user has a pending task, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the task instead.', - cannotRenderComponentWhenTaskDoesNotExist: - ' cannot render unless a session task is pending. Clerk is redirecting to the value set in `redirectUrlComplete` instead.', - cannotRenderSignInComponentWhenSessionExists: - 'The component cannot render when a user is already signed in, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the `afterSignIn` URL instead.', - cannotRenderSignInComponentWhenTaskExists: - 'The component cannot render when a user has a pending task, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the task instead.', - cannotRenderComponentWhenUserDoesNotExist: - ' cannot render unless a user is signed in. Since no user is signed in, this is no-op.', - cannotRenderComponentWhenOrgDoesNotExist: ` cannot render unless an organization is active. Since no organization is currently active, this is no-op.`, - cannotRenderAnyOrganizationComponent: createMessageForDisabledOrganizations, - cannotRenderAnyBillingComponent: createMessageForDisabledBilling, - cannotOpenUserProfile: - 'The UserProfile modal cannot render unless a user is signed in. Since no user is signed in, this is no-op.', - cannotOpenCheckout: - 'The Checkout drawer cannot render unless a user is signed in. Since no user is signed in, this is no-op.', - cannotOpenSignInOrSignUp: - 'The SignIn or SignUp modals do not render when a user is already signed in, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, this is no-op.', - cannotRenderAPIKeysComponent: - 'The component cannot be rendered when API keys are disabled. Since API keys are disabled, this is no-op.', - cannotRenderAPIKeysComponentForUserWhenDisabled: - 'The component cannot be rendered when user API keys are disabled. Since user API keys are disabled, this is no-op.', - cannotRenderAPIKeysComponentForOrgWhenDisabled: - 'The component cannot be rendered when organization API keys are disabled. Since organization API keys are disabled, this is no-op.', -}; - -type SerializableWarnings = Serializable; - -for (const key of Object.keys(warnings)) { - const item = warnings[key as keyof typeof warnings]; - if (typeof item !== 'function') { - warnings[key as keyof SerializableWarnings] = formatWarning(item); - } -} - -export { warnings }; +// Re-export warnings from shared package +export { warnings } from '@clerk/shared/internal/clerk-js/warnings'; diff --git a/packages/clerk-js/src/global.d.ts b/packages/clerk-js/src/global.d.ts index 50a17aae822..4700a374243 100644 --- a/packages/clerk-js/src/global.d.ts +++ b/packages/clerk-js/src/global.d.ts @@ -3,16 +3,16 @@ declare module '*.svg' { export default value; } -declare const __PKG_NAME__: string; -declare const __PKG_VERSION__: string; -declare const __DEV__: boolean; +const __PKG_NAME__: string; +const __PKG_VERSION__: string; +const __DEV__: boolean; /** * Build time feature flags. */ -declare const __BUILD_DISABLE_RHC__: string; -declare const __BUILD_VARIANT_CHANNEL__: boolean; -declare const __BUILD_VARIANT_CHIPS__: boolean; +const __BUILD_DISABLE_RHC__: string; +const __BUILD_VARIANT_CHANNEL__: boolean; +const __BUILD_VARIANT_CHIPS__: boolean; interface Window { __unstable__onBeforeSetActive: (intent?: 'sign-out') => Promise | void; diff --git a/packages/clerk-js/src/index.browser.ts b/packages/clerk-js/src/index.browser.ts index 82bd326c1a6..66647f279f6 100644 --- a/packages/clerk-js/src/index.browser.ts +++ b/packages/clerk-js/src/index.browser.ts @@ -1,14 +1,10 @@ // It's crucial this is the first import, // otherwise chunk loading will not work -// eslint-disable-next-line + import './utils/setWebpackChunkPublicPath'; import { Clerk } from './core/clerk'; -import { mountComponentRenderer } from './ui/Components'; - -Clerk.mountComponentRenderer = mountComponentRenderer; - const publishableKey = document.querySelector('script[data-clerk-publishable-key]')?.getAttribute('data-clerk-publishable-key') || window.__clerk_publishable_key || @@ -26,7 +22,6 @@ const domain = if (!window.Clerk) { window.Clerk = new Clerk(publishableKey, { proxyUrl, - // @ts-expect-error domain, }); } diff --git a/packages/clerk-js/src/index.headless.browser.ts b/packages/clerk-js/src/index.headless.browser.ts index 4151921ff3c..fbdd2407a53 100644 --- a/packages/clerk-js/src/index.headless.browser.ts +++ b/packages/clerk-js/src/index.headless.browser.ts @@ -23,7 +23,6 @@ const domain = if (!window.Clerk) { window.Clerk = new Clerk(publishableKey, { proxyUrl, - // @ts-expect-error domain, }); } diff --git a/packages/clerk-js/src/index.legacy.browser.ts b/packages/clerk-js/src/index.legacy.browser.ts index 7d0d031c6cc..73dab30df70 100644 --- a/packages/clerk-js/src/index.legacy.browser.ts +++ b/packages/clerk-js/src/index.legacy.browser.ts @@ -7,10 +7,6 @@ import 'regenerator-runtime/runtime'; import { Clerk } from './core/clerk'; -import { mountComponentRenderer } from './ui/Components'; - -Clerk.mountComponentRenderer = mountComponentRenderer; - const publishableKey = document.querySelector('script[data-clerk-publishable-key]')?.getAttribute('data-clerk-publishable-key') || window.__clerk_publishable_key || @@ -28,7 +24,6 @@ const domain = if (!window.Clerk) { window.Clerk = new Clerk(publishableKey, { proxyUrl, - // @ts-expect-error domain, }); } diff --git a/packages/clerk-js/src/index.ts b/packages/clerk-js/src/index.ts index 2f9acf96b6e..f7ad89e436d 100644 --- a/packages/clerk-js/src/index.ts +++ b/packages/clerk-js/src/index.ts @@ -1,7 +1,6 @@ import 'regenerator-runtime/runtime'; import { Clerk } from './core/clerk'; -import { mountComponentRenderer } from './ui/Components'; export { ClerkAPIResponseError, @@ -19,8 +18,6 @@ export { } from '@clerk/shared/error'; export { Clerk }; -Clerk.mountComponentRenderer = mountComponentRenderer; - if (module.hot) { module.hot.accept(); } diff --git a/packages/clerk-js/src/test/create-fixtures.tsx b/packages/clerk-js/src/test/create-fixtures.tsx index e9af007e3aa..2ffa7c64034 100644 --- a/packages/clerk-js/src/test/create-fixtures.tsx +++ b/packages/clerk-js/src/test/create-fixtures.tsx @@ -1,3 +1,6 @@ +/* eslint-disable */ +// @ts-nocheck + import type { ClerkOptions, ClientJSON, EnvironmentJSON, LoadedClerk } from '@clerk/shared/types'; import { useState } from 'react'; import { vi } from 'vitest'; diff --git a/packages/clerk-js/src/test/fixture-helpers.ts b/packages/clerk-js/src/test/fixture-helpers.ts index ad32d5c9d2d..5b188b932d8 100644 --- a/packages/clerk-js/src/test/fixture-helpers.ts +++ b/packages/clerk-js/src/test/fixture-helpers.ts @@ -8,7 +8,6 @@ import type { OrganizationEnrollmentMode, PhoneNumberJSON, PublicUserDataJSON, - SamlAccountJSON, SessionJSON, SignInJSON, SignUpJSON, @@ -44,12 +43,11 @@ export const createClientFixtureHelpers = (baseClient: ClientJSON) => { const createUserFixtureHelpers = (baseClient: ClientJSON) => { type WithUserParams = Omit< Partial, - 'email_addresses' | 'phone_numbers' | 'external_accounts' | 'saml_accounts' | 'organization_memberships' + 'email_addresses' | 'phone_numbers' | 'external_accounts' | 'organization_memberships' > & { email_addresses?: Array>; phone_numbers?: Array>; external_accounts?: Array>; - saml_accounts?: Array>; organization_memberships?: Array; tasks?: SessionJSON['tasks']; }; diff --git a/packages/clerk-js/src/test/fixtures.ts b/packages/clerk-js/src/test/fixtures.ts index 9b3403f0b57..f8596b5329c 100644 --- a/packages/clerk-js/src/test/fixtures.ts +++ b/packages/clerk-js/src/test/fixtures.ts @@ -1,3 +1,6 @@ +/* eslint-disable */ +// @ts-nocheck + import type { AuthConfigJSON, ClientJSON, diff --git a/packages/clerk-js/src/ui/components/SignUp/util.ts b/packages/clerk-js/src/ui/components/SignUp/util.ts deleted file mode 100644 index 343a9bcaccc..00000000000 --- a/packages/clerk-js/src/ui/components/SignUp/util.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../../../utils/completeSignUpFlow'; diff --git a/packages/clerk-js/src/ui/localization/defaultEnglishResource.ts b/packages/clerk-js/src/ui/localization/defaultEnglishResource.ts deleted file mode 100644 index 81219b7f1ef..00000000000 --- a/packages/clerk-js/src/ui/localization/defaultEnglishResource.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { enUS } from '@clerk/localizations'; -import type { DeepRequired } from '@clerk/shared/types'; - -export const defaultResource = enUS as DeepRequired; diff --git a/packages/clerk-js/src/utils/assertNoLegacyProp.ts b/packages/clerk-js/src/utils/assertNoLegacyProp.ts deleted file mode 100644 index a574b8d9e2f..00000000000 --- a/packages/clerk-js/src/utils/assertNoLegacyProp.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { logger } from '@clerk/shared/logger'; - -export function assertNoLegacyProp(props: Record) { - const legacyProps = ['redirectUrl', 'afterSignInUrl', 'afterSignUpUrl', 'after_sign_in_url', 'after_sign_up_url']; - const legacyProp = Object.keys(props).find(key => legacyProps.includes(key)); - - if (legacyProp && props[legacyProp]) { - logger.warnOnce( - `Clerk: The prop "${legacyProp}" is deprecated and should be replaced with the new "fallbackRedirectUrl" or "forceRedirectUrl" props instead. Learn more: https://clerk.com/docs/guides/custom-redirects#redirect-url-props`, - ); - } -} - -export function warnForNewPropShadowingLegacyProp( - newKey: string | undefined, - newValue: string | undefined | null, - legacyKey: string | undefined, - legacyValue: string | undefined | null, -) { - if (newValue && legacyValue) { - logger.warnOnce( - `Clerk: The "${newKey}" prop ("${newValue}") has priority over the legacy "${legacyKey}" (or "redirectUrl") ("${legacyValue}"), which will be completely ignored in this case. "${legacyKey}" (or "redirectUrl" prop) should be replaced with the new "fallbackRedirectUrl" or "forceRedirectUrl" props instead. Learn more: https://clerk.com/docs/guides/custom-redirects#redirect-url-props`, - ); - } -} diff --git a/packages/clerk-js/src/utils/beforeUnloadTracker.ts b/packages/clerk-js/src/utils/beforeUnloadTracker.ts index 59094c63bed..a9ce63b70f1 100644 --- a/packages/clerk-js/src/utils/beforeUnloadTracker.ts +++ b/packages/clerk-js/src/utils/beforeUnloadTracker.ts @@ -1,4 +1,4 @@ -import { CLERK_BEFORE_UNLOAD_EVENT } from './windowNavigate'; +import { CLERK_BEFORE_UNLOAD_EVENT } from '@clerk/shared/internal/clerk-js/windowNavigate'; /** * Tracks beforeUnload events. diff --git a/packages/clerk-js/src/utils/captcha/constants.ts b/packages/clerk-js/src/utils/captcha/constants.ts deleted file mode 100644 index 7ad0a0e05c0..00000000000 --- a/packages/clerk-js/src/utils/captcha/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const CAPTCHA_ELEMENT_ID = 'clerk-captcha'; -export const CAPTCHA_INVISIBLE_CLASSNAME = 'clerk-invisible-captcha'; diff --git a/packages/clerk-js/src/utils/captcha/turnstile.ts b/packages/clerk-js/src/utils/captcha/turnstile.ts index 1a21f57d1dc..71150d992f4 100644 --- a/packages/clerk-js/src/utils/captcha/turnstile.ts +++ b/packages/clerk-js/src/utils/captcha/turnstile.ts @@ -1,8 +1,8 @@ import { waitForElement } from '@clerk/shared/dom'; +import { CAPTCHA_ELEMENT_ID, CAPTCHA_INVISIBLE_CLASSNAME } from '@clerk/shared/internal/clerk-js/constants'; import { loadScript } from '@clerk/shared/loadScript'; import type { CaptchaAppearanceOptions, CaptchaWidgetType } from '@clerk/shared/types'; -import { CAPTCHA_ELEMENT_ID, CAPTCHA_INVISIBLE_CLASSNAME } from './constants'; import type { CaptchaOptions } from './types'; // We use the explicit render mode to be able to control when the widget is rendered. diff --git a/packages/clerk-js/src/utils/index.ts b/packages/clerk-js/src/utils/index.ts index 37bd8d49ff6..2a66443941e 100644 --- a/packages/clerk-js/src/utils/index.ts +++ b/packages/clerk-js/src/utils/index.ts @@ -1,30 +1,23 @@ -export * from './appearance'; export * from './beforeUnloadTracker'; export * from './billing'; -export * from './completeSignUpFlow'; -export * from './componentGuards'; -export * from './dynamicParamParser'; -export * from './email'; -export * from './encoders'; +export * from '@clerk/shared/internal/clerk-js/completeSignUpFlow'; +export * from '@clerk/shared/internal/clerk-js/email'; +export * from '@clerk/shared/internal/clerk-js/encoders'; export * from './errors'; export * from './errorThrower'; export * from './filterUndefinedValues'; -export * from './getClerkQueryParam'; -export * from './hex'; +export * from '@clerk/shared/internal/clerk-js/queryParams'; export * from './ignoreEventValue'; -export * from './image'; export * from './instance'; export * from './jwt'; export * from './locale'; -export * from './normalizeRoutingOptions'; -export * from './organization'; +export * from '@clerk/shared/internal/clerk-js/organization'; export * from './pageLifecycle'; -export * from './path'; -export * from './props'; -export * from './queryStateParams'; -export * from './querystring'; -export * from './runtime'; +export * from '@clerk/shared/internal/clerk-js/path'; +export * from '@clerk/shared/internal/clerk-js/queryStateParams'; +export * from '@clerk/shared/internal/clerk-js/querystring'; +export * from '@clerk/shared/internal/clerk-js/runtime'; export * from './tokenId'; -export * from './url'; +export * from '@clerk/shared/internal/clerk-js/url'; export * from './web3'; -export * from './windowNavigate'; +export * from '@clerk/shared/internal/clerk-js/windowNavigate'; diff --git a/packages/clerk-js/src/utils/instance.ts b/packages/clerk-js/src/utils/instance.ts index 404a81608ec..97b869e7675 100644 --- a/packages/clerk-js/src/utils/instance.ts +++ b/packages/clerk-js/src/utils/instance.ts @@ -1,4 +1,4 @@ -import { isDevOrStagingUrl } from './url'; +import { isDevOrStagingUrl } from '@clerk/shared/internal/clerk-js/url'; const FRONTEND_API_DEV_OR_STG_REGEX = /^clerk\.([\w|-]+\.){2,4}(dev|com)$/i; diff --git a/packages/clerk-js/src/utils/jwt.ts b/packages/clerk-js/src/utils/jwt.ts index 9bac22a6274..e921ddb8912 100644 --- a/packages/clerk-js/src/utils/jwt.ts +++ b/packages/clerk-js/src/utils/jwt.ts @@ -1,7 +1,6 @@ +import { urlDecodeB64 } from '@clerk/shared/internal/clerk-js/encoders'; import type { JWT, JwtPayload } from '@clerk/shared/types'; -import { urlDecodeB64 } from './encoders'; - export function decode(token: string): JWT { const parts = (token || '').split('.'); const [header, payload, signature] = parts; diff --git a/packages/clerk-js/src/utils/moduleManager.ts b/packages/clerk-js/src/utils/moduleManager.ts new file mode 100644 index 00000000000..ee9859aa2ac --- /dev/null +++ b/packages/clerk-js/src/utils/moduleManager.ts @@ -0,0 +1,18 @@ +import type { ImportableModule, ModuleManager as ModuleManagerI } from '@clerk/shared/moduleManager'; +import { safeImport } from '@clerk/shared/safeImport'; + +export class ModuleManager implements ModuleManagerI { + #importMap = { + '@zxcvbn-ts/core': () => safeImport(() => import('@zxcvbn-ts/core')), + '@zxcvbn-ts/language-common': () => safeImport(() => import('@zxcvbn-ts/language-common')), + '@base-org/account': () => safeImport(() => import('@base-org/account')), + '@coinbase/wallet-sdk': () => safeImport(() => import('@coinbase/wallet-sdk')), + '@stripe/stripe-js': () => safeImport(() => import('@stripe/stripe-js')), + } satisfies Record Promise>; + + import(module: ImportableModule) { + // Not typing this as any because we want to allow any type to be returned from the interface this class implements insetad of + // defining the types twice + return (this.#importMap[module] ? this.#importMap[module]() : Promise.resolve(undefined)) as any; + } +} diff --git a/packages/clerk-js/src/utils/web3.ts b/packages/clerk-js/src/utils/web3.ts index fc494876f78..7e0a2fbd766 100644 --- a/packages/clerk-js/src/utils/web3.ts +++ b/packages/clerk-js/src/utils/web3.ts @@ -1,124 +1,5 @@ -import type { Web3Provider } from '@clerk/shared/types'; +import { createWeb3 } from '@clerk/shared/internal/clerk-js/web3'; -import { clerkUnsupportedEnvironmentWarning } from '@/core/errors'; +import { ModuleManager } from './moduleManager'; -import { toHex } from './hex'; -import { getInjectedWeb3Providers } from './injectedWeb3Providers'; - -type GetWeb3IdentifierParams = { - provider: Web3Provider; -}; - -export async function getWeb3Identifier(params: GetWeb3IdentifierParams): Promise { - const { provider } = params; - const ethereum = await getEthereumProvider(provider); - - // TODO - core-3: Improve error handling for the case when the provider is not found - if (!ethereum) { - // If a plugin for the requested provider is not found, - // the flow will fail as it has been the expected behavior so far. - return ''; - } - - const identifiers = await ethereum.request({ method: 'eth_requestAccounts' }); - // @ts-ignore -- Provider SDKs may return unknown shape; use first address if present - return (identifiers && identifiers[0]) || ''; -} - -type GenerateWeb3SignatureParams = GenerateSignatureParams & { - provider: Web3Provider; -}; - -export async function generateWeb3Signature(params: GenerateWeb3SignatureParams): Promise { - const { identifier, nonce, provider } = params; - const ethereum = await getEthereumProvider(provider); - - // TODO - core-3: Improve error handling for the case when the provider is not found - if (!ethereum) { - // If a plugin for the requested provider is not found, - // the flow will fail as it has been the expected behavior so far. - return ''; - } - - return await ethereum.request({ - method: 'personal_sign', - params: [`0x${toHex(nonce)}`, identifier], - }); -} - -export async function getMetamaskIdentifier(): Promise { - return await getWeb3Identifier({ provider: 'metamask' }); -} - -export async function getCoinbaseWalletIdentifier(): Promise { - return await getWeb3Identifier({ provider: 'coinbase_wallet' }); -} - -export async function getOKXWalletIdentifier(): Promise { - return await getWeb3Identifier({ provider: 'okx_wallet' }); -} - -export async function getBaseIdentifier(): Promise { - return await getWeb3Identifier({ provider: 'base' }); -} - -type GenerateSignatureParams = { - identifier: string; - nonce: string; -}; - -export async function generateSignatureWithMetamask(params: GenerateSignatureParams): Promise { - return await generateWeb3Signature({ ...params, provider: 'metamask' }); -} - -export async function generateSignatureWithCoinbaseWallet(params: GenerateSignatureParams): Promise { - return await generateWeb3Signature({ ...params, provider: 'coinbase_wallet' }); -} - -export async function generateSignatureWithOKXWallet(params: GenerateSignatureParams): Promise { - return await generateWeb3Signature({ ...params, provider: 'okx_wallet' }); -} - -export async function generateSignatureWithBase(params: GenerateSignatureParams): Promise { - return await generateWeb3Signature({ ...params, provider: 'base' }); -} - -async function getEthereumProvider(provider: Web3Provider) { - if (provider === 'coinbase_wallet') { - if (__BUILD_DISABLE_RHC__) { - clerkUnsupportedEnvironmentWarning('Coinbase Wallet'); - return null; - } - - const createCoinbaseWalletSDK = await import('@coinbase/wallet-sdk').then(mod => mod.createCoinbaseWalletSDK); - const sdk = createCoinbaseWalletSDK({ - preference: { - options: 'all', - }, - }); - return sdk.getProvider(); - } - if (provider === 'base') { - if (__BUILD_DISABLE_RHC__) { - clerkUnsupportedEnvironmentWarning('Base'); - return null; - } - - try { - const createBaseAccountSDK = await import('@base-org/account').then(mod => mod.createBaseAccountSDK); - - const sdk = createBaseAccountSDK({ - appName: - (typeof window !== 'undefined' && - (window.Clerk as any)?.__unstable__environment?.displayConfig?.applicationName) || - (typeof document !== 'undefined' && document.title) || - 'Web3 Application', - }); - return sdk.getProvider(); - } catch { - return null; - } - } - - return getInjectedWeb3Providers().get(provider); -} +export const web3 = () => createWeb3(new ModuleManager()); diff --git a/packages/clerk-js/src/utils/zxcvbn.ts b/packages/clerk-js/src/utils/zxcvbn.ts index 546fb23cf02..70eb563d916 100644 --- a/packages/clerk-js/src/utils/zxcvbn.ts +++ b/packages/clerk-js/src/utils/zxcvbn.ts @@ -1,19 +1,7 @@ -import type { ZxcvbnResult } from '@clerk/shared/types'; +import { createLoadZxcvbn } from '@clerk/shared/internal/clerk-js/passwords/loadZxcvbn'; -export type zxcvbnFN = (password: string, userInputs?: (string | number)[]) => ZxcvbnResult; +import { ModuleManager } from './moduleManager'; export const loadZxcvbn = () => { - return Promise.all([import('@zxcvbn-ts/core'), import('@zxcvbn-ts/language-common')]).then( - ([coreModule, languageCommonModule]) => { - const { zxcvbnOptions, zxcvbn } = coreModule; - const { dictionary, adjacencyGraphs } = languageCommonModule; - zxcvbnOptions.setOptions({ - dictionary: { - ...dictionary, - }, - graphs: adjacencyGraphs, - }); - return zxcvbn; - }, - ); + return createLoadZxcvbn(new ModuleManager()).loadZxcvbn; }; diff --git a/packages/clerk-js/tsconfig.json b/packages/clerk-js/tsconfig.json index 96bcc440b5b..896a6e8665a 100644 --- a/packages/clerk-js/tsconfig.json +++ b/packages/clerk-js/tsconfig.json @@ -24,5 +24,5 @@ "@/*": ["./src/*"] } }, - "include": ["src", "vitest.config.mts", "vitest.setup.mts"] + "include": ["src", "vitest.config.mts", "vitest.setup.mts", "../shared/internal/clerk-js/componentGuards.ts"] } diff --git a/packages/clerk-js/turbo.json b/packages/clerk-js/turbo.json index a6e53939738..10c4d163114 100644 --- a/packages/clerk-js/turbo.json +++ b/packages/clerk-js/turbo.json @@ -22,7 +22,7 @@ ] }, "build:sandbox": { - "dependsOn": ["^build"], + "dependsOn": ["^build", "@clerk/localizations#build"], "inputs": ["sandbox/**"], "outputs": ["dist/**"] }, diff --git a/packages/dev-cli/package.json b/packages/dev-cli/package.json index 6a77669275b..5da57139a34 100644 --- a/packages/dev-cli/package.json +++ b/packages/dev-cli/package.json @@ -32,7 +32,7 @@ }, "devDependencies": {}, "engines": { - "node": ">=18.17.0" + "node": ">=20.9.0" }, "publishConfig": { "access": "public" diff --git a/packages/elements/.gitignore b/packages/elements/.gitignore deleted file mode 100644 index 912a0263905..00000000000 --- a/packages/elements/.gitignore +++ /dev/null @@ -1,65 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Typescript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env.local - -# Distribution directories -dist/ -.next - -# Mac OSX files -.DS_Store diff --git a/packages/elements/.npmignore b/packages/elements/.npmignore deleted file mode 100644 index 1e8fc102c3d..00000000000 --- a/packages/elements/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -examples -dist/metafile-*.json diff --git a/packages/elements/CHANGELOG.md b/packages/elements/CHANGELOG.md deleted file mode 100644 index d8b3ef0dd59..00000000000 --- a/packages/elements/CHANGELOG.md +++ /dev/null @@ -1,2248 +0,0 @@ -# @clerk/elements - -## 0.23.85 - -### Patch Changes - -- Update peerDependencies to allow Next.js v16 ([#7274](https://github.com/clerk/javascript/pull/7274)) by [@nikosdouvlis](https://github.com/nikosdouvlis) - -- Updated dependencies [[`d8f59a6`](https://github.com/clerk/javascript/commit/d8f59a66d56d8fb0dfea353ecd86af97d0ec56b7)]: - - @clerk/shared@3.35.2 - - @clerk/clerk-react@5.56.2 - - @clerk/types@4.101.2 - -## 0.23.84 - -### Patch Changes - -- Updated dependencies [[`a9c13ca`](https://github.com/clerk/javascript/commit/a9c13cae5a6f46ca753d530878f7e4492ca7938b)]: - - @clerk/shared@3.35.1 - - @clerk/clerk-react@5.56.1 - - @clerk/types@4.101.1 - -## 0.23.83 - -### Patch Changes - -- Updated dependencies [[`7be8f45`](https://github.com/clerk/javascript/commit/7be8f458367b2c050b0dc8c0481d7bbe090ea400), [`bdbb0d9`](https://github.com/clerk/javascript/commit/bdbb0d91712a84fc214c534fc47b62b1a2028ac9), [`aa184a4`](https://github.com/clerk/javascript/commit/aa184a46a91f9dec3fd275ec5867a8366d310469), [`1d4e7a7`](https://github.com/clerk/javascript/commit/1d4e7a7769e9efaaa945e4ba6468ad47bd24c807), [`42f0d95`](https://github.com/clerk/javascript/commit/42f0d95e943d82960de3f7e5da17d199eff9fddd), [`c63cc8e`](https://github.com/clerk/javascript/commit/c63cc8e9c38ed0521a22ebab43e10111f04f9daf), [`d32d724`](https://github.com/clerk/javascript/commit/d32d724c34a921a176eca159273f270c2af4e787), [`00291bc`](https://github.com/clerk/javascript/commit/00291bc8ae03c06f7154bd937628e8193f6e3ce9)]: - - @clerk/shared@3.35.0 - - @clerk/clerk-react@5.56.0 - - @clerk/types@4.101.0 - -## 0.23.82 - -### Patch Changes - -- Updated dependencies [[`b5a7e2f`](https://github.com/clerk/javascript/commit/b5a7e2f8af5514e19e06918632d982be65f4a854), [`a1d10fc`](https://github.com/clerk/javascript/commit/a1d10fc6e231f27ec7eabd0db45b8f7e8c98250e), [`b944ff3`](https://github.com/clerk/javascript/commit/b944ff30494a8275450ca0d5129cdf58f02bea81), [`4011c5e`](https://github.com/clerk/javascript/commit/4011c5e0014ede5e480074b73d064a1bc2a577dd), [`791e942`](https://github.com/clerk/javascript/commit/791e9426181f89012d4c5380a99141f3bb7ae88a)]: - - @clerk/types@4.100.0 - - @clerk/shared@3.34.0 - - @clerk/clerk-react@5.55.0 - -## 0.23.81 - -### Patch Changes - -- Updated dependencies [[`613cb97`](https://github.com/clerk/javascript/commit/613cb97cb7b3b33c3865cfe008ef9b1ea624cc8d)]: - - @clerk/shared@3.33.0 - - @clerk/clerk-react@5.54.0 - - @clerk/types@4.99.0 - -## 0.23.80 - -### Patch Changes - -- Updated dependencies [[`cc11472`](https://github.com/clerk/javascript/commit/cc11472e7318b806ee43d609cd03fb0446f56146), [`539fad7`](https://github.com/clerk/javascript/commit/539fad7b80ed284a7add6cf8c4c45cf4c6a0a8b2), [`296fb0b`](https://github.com/clerk/javascript/commit/296fb0b8f34aca4f527508a5e6a6bbaad89cfdaa), [`c413433`](https://github.com/clerk/javascript/commit/c413433fee49701f252df574ce6a009d256c0cb9), [`a940c39`](https://github.com/clerk/javascript/commit/a940c39354bd0ee48d2fc9b0f3217ec20b2f32b4)]: - - @clerk/shared@3.32.0 - - @clerk/types@4.98.0 - - @clerk/clerk-react@5.53.9 - -## 0.23.79 - -### Patch Changes - -- Updated dependencies [[`92fba5d`](https://github.com/clerk/javascript/commit/92fba5d2874bf8a740f21ab0a4e21e63beb099f9)]: - - @clerk/clerk-react@5.53.8 - -## 0.23.78 - -### Patch Changes - -- Updated dependencies [[`a474c59`](https://github.com/clerk/javascript/commit/a474c59e3017358186de15c5b1e5b83002e72527), [`5536429`](https://github.com/clerk/javascript/commit/55364291e245ff05ca1e50e614e502d2081b87fb)]: - - @clerk/shared@3.31.1 - - @clerk/clerk-react@5.53.7 - - @clerk/types@4.97.2 - -## 0.23.77 - -### Patch Changes - -- Updated dependencies [[`85b5acc`](https://github.com/clerk/javascript/commit/85b5acc5ba192a8247f072fa93d5bc7d42986293), [`ea65d39`](https://github.com/clerk/javascript/commit/ea65d390cd6d3b0fdd35202492e858f8c8370f73), [`b09b29e`](https://github.com/clerk/javascript/commit/b09b29e82323c8fc508c49ffe10c77a737ef0bec)]: - - @clerk/types@4.97.1 - - @clerk/shared@3.31.0 - - @clerk/clerk-react@5.53.6 - -## 0.23.76 - -### Patch Changes - -- Deprecate `@clerk/types` in favor of `@clerk/shared/types` ([#7022](https://github.com/clerk/javascript/pull/7022)) by [@nikosdouvlis](https://github.com/nikosdouvlis) - - The `@clerk/types` package is now deprecated. All type definitions have been consolidated and moved to `@clerk/shared/types` to improve consistency across the Clerk ecosystem. - - **Backward Compatibility:** - - The `@clerk/types` package will remain available and will continue to re-export all types from `@clerk/shared/types` to ensure backward compatibility. Existing applications will continue to work without any immediate breaking changes. However, we strongly recommend migrating to `@clerk/shared/types` as new type definitions and updates will only be added to `@clerk/shared/types` starting with the next major release. - - **Migration Steps:** - - Please update your imports from `@clerk/types` to `@clerk/shared/types`: - - ```typescript - // Before - import type { ClerkResource, UserResource } from '@clerk/types'; - - // After - import type { ClerkResource, UserResource } from '@clerk/shared/types'; - ``` - - **What Changed:** - - All type definitions including: - - Resource types (User, Organization, Session, etc.) - - API response types - - Configuration types - - Authentication types - - Error types - - And all other shared types - - Have been moved from `packages/types/src` to `packages/shared/src/types` and are now exported via `@clerk/shared/types`. - -- Updated dependencies [[`3e0ef92`](https://github.com/clerk/javascript/commit/3e0ef9281194714f56dcf656d0caf4f75dcf097c), [`2587aa6`](https://github.com/clerk/javascript/commit/2587aa671dac1ca66711889bf1cd1c2e2ac8d7c8)]: - - @clerk/shared@3.30.0 - - @clerk/types@4.97.0 - - @clerk/clerk-react@5.53.5 - -## 0.23.75 - -### Patch Changes - -- Updated dependencies [[`791ff19`](https://github.com/clerk/javascript/commit/791ff19a55ecb39eac20e1533a7d578a30386388), [`439427e`](https://github.com/clerk/javascript/commit/439427e44adef4f43e5f0719adf5654ea58c33e7), [`7dfbf3a`](https://github.com/clerk/javascript/commit/7dfbf3aa1b5269aee2d3af628b02027be9767088), [`d33b7b5`](https://github.com/clerk/javascript/commit/d33b7b5538e9bcbbca1ac23c46793d0cddcef533)]: - - @clerk/shared@3.29.0 - - @clerk/types@4.96.0 - - @clerk/clerk-react@5.53.4 - -## 0.23.74 - -### Patch Changes - -- Updated dependencies [[`4d46e4e`](https://github.com/clerk/javascript/commit/4d46e4e601a5f2a213f1718af3f9271db4db0911)]: - - @clerk/types@4.95.1 - - @clerk/clerk-react@5.53.3 - - @clerk/shared@3.28.3 - -## 0.23.73 - -### Patch Changes - -- Updated dependencies [[`a172d51`](https://github.com/clerk/javascript/commit/a172d51df2d7f2e450c983a15ae897624304a764), [`947d0f5`](https://github.com/clerk/javascript/commit/947d0f5480b0151a392966cad2e1a45423f66035)]: - - @clerk/types@4.95.0 - - @clerk/shared@3.28.2 - - @clerk/clerk-react@5.53.2 - -## 0.23.72 - -### Patch Changes - -- Updated dependencies [[`d8147fb`](https://github.com/clerk/javascript/commit/d8147fb58bfd6caf9a4f0a36fdc48c630d00387f)]: - - @clerk/shared@3.28.1 - - @clerk/clerk-react@5.53.1 - -## 0.23.71 - -### Patch Changes - -- Updated dependencies [[`305f4ee`](https://github.com/clerk/javascript/commit/305f4eeb825086d55d1b0df198a0c43da8d94993), [`53214f9`](https://github.com/clerk/javascript/commit/53214f9a600074affc84d616bbbe7a6b625e7d33), [`1441e68`](https://github.com/clerk/javascript/commit/1441e6851102e9eed5697ad78c695f75b4a20db2), [`1236c74`](https://github.com/clerk/javascript/commit/1236c745fd58020e0972938ca0a9ae697a24af02)]: - - @clerk/shared@3.28.0 - - @clerk/types@4.94.0 - - @clerk/clerk-react@5.53.0 - -## 0.23.70 - -### Patch Changes - -- Updated dependencies [[`65b7cc7`](https://github.com/clerk/javascript/commit/65b7cc787a5f02a302b665b6eaf4d4b9a1cae4b0), [`6e09786`](https://github.com/clerk/javascript/commit/6e09786adeb0f481ca8b6d060ae8754b556a3f9a), [`aa7210c`](https://github.com/clerk/javascript/commit/aa7210c7fff34f6c6e2d4ca3cb736bbd35439cb6), [`2cd53cd`](https://github.com/clerk/javascript/commit/2cd53cd8c713dfa7f2e802fe08986411587095fa), [`1a2eee6`](https://github.com/clerk/javascript/commit/1a2eee6b8b6ead2d0481e93104fcaed6452bd1b9), [`2cd53cd`](https://github.com/clerk/javascript/commit/2cd53cd8c713dfa7f2e802fe08986411587095fa), [`1a2430a`](https://github.com/clerk/javascript/commit/1a2430a166fb1df5fbca76437c63423b18a49ced), [`31a04fc`](https://github.com/clerk/javascript/commit/31a04fc2b783f01cd4848c1e681af3b30e57bb2f), [`9766c4a`](https://github.com/clerk/javascript/commit/9766c4afd26f2841d6f79dbdec2584ef8becd22f), [`22b8e49`](https://github.com/clerk/javascript/commit/22b8e49f9fb65d55ab737d11f1f57a25bf947511), [`a66357e`](https://github.com/clerk/javascript/commit/a66357e8a5928199aebde408ec7cfaac152c2c42), [`dacc1af`](https://github.com/clerk/javascript/commit/dacc1af22e1d1af0940b2d626b8a47d376c19342)]: - - @clerk/types@4.93.0 - - @clerk/clerk-react@5.52.0 - - @clerk/shared@3.27.4 - -## 0.23.69 - -### Patch Changes - -- Updated dependencies [[`fba4781`](https://github.com/clerk/javascript/commit/fba4781ff2a2d16f8934029fa6fb77d70953f2be), [`a1f6714`](https://github.com/clerk/javascript/commit/a1f671480cda6f978db059ba0640d4ed8b08f112)]: - - @clerk/types@4.92.0 - - @clerk/clerk-react@5.51.0 - - @clerk/shared@3.27.3 - -## 0.23.68 - -### Patch Changes - -- Updated dependencies [[`f737d26`](https://github.com/clerk/javascript/commit/f737d268aa167889a4f3f7aba2658c2ba1fd909a), [`8777f35`](https://github.com/clerk/javascript/commit/8777f350f5fb51413609a53d9de05b2e5d1d7cfe), [`2c0128b`](https://github.com/clerk/javascript/commit/2c0128b05ecf48748f27f10f0b0215a279ba6cc1)]: - - @clerk/clerk-react@5.50.0 - - @clerk/types@4.91.0 - - @clerk/shared@3.27.2 - -## 0.23.67 - -### Patch Changes - -- Updated dependencies [[`37028ca`](https://github.com/clerk/javascript/commit/37028caad59cb0081ac74e70a44e4a419082a999)]: - - @clerk/types@4.90.0 - - @clerk/clerk-react@5.49.1 - - @clerk/shared@3.27.1 - -## 0.23.66 - -### Patch Changes - -- Updated dependencies [[`e3e77eb`](https://github.com/clerk/javascript/commit/e3e77eb277c6b36847265db7b863c418e3708ab6), [`9cf89cd`](https://github.com/clerk/javascript/commit/9cf89cd3402c278e8d5bfcd8277cee292bc45333), [`090ca74`](https://github.com/clerk/javascript/commit/090ca742c590bc4f369cf3e1ca2ec9917410ffe4), [`5546352`](https://github.com/clerk/javascript/commit/55463527df9a710ef3215c353bab1ef423d1de62)]: - - @clerk/shared@3.27.0 - - @clerk/clerk-react@5.49.0 - - @clerk/types@4.89.0 - -## 0.23.65 - -### Patch Changes - -- Updated dependencies [[`41e0a41`](https://github.com/clerk/javascript/commit/41e0a4190b33dd2c4bdc0d536bbe83fcf99af9b0), [`1aa9e9f`](https://github.com/clerk/javascript/commit/1aa9e9f10c051319e9ff4b1a0ecd71507bd6a6aa), [`a88ee58`](https://github.com/clerk/javascript/commit/a88ee5827adee0cc8a62246d03a3034d8566fe21), [`d6c7bbb`](https://github.com/clerk/javascript/commit/d6c7bbba23f38c0b3ca7edebb53028a05c7b38e6)]: - - @clerk/shared@3.26.1 - - @clerk/clerk-react@5.48.1 - - @clerk/types@4.88.0 - -## 0.23.64 - -### Patch Changes - -- Updated dependencies [[`bcf24f2`](https://github.com/clerk/javascript/commit/bcf24f2f91913fa0dd3fbf02b3bbef345c4e1ea9), [`1ceedad`](https://github.com/clerk/javascript/commit/1ceedad4bc5bc3d5f01c95185f82ff0f43983cf5), [`de90ede`](https://github.com/clerk/javascript/commit/de90ede82664b58bef9e294498384cf2c99a331e), [`9d4a95c`](https://github.com/clerk/javascript/commit/9d4a95c766396a0bc327fbf0560228bedb4828eb), [`428cd57`](https://github.com/clerk/javascript/commit/428cd57a8581a58a6a42325ec50eb98000068e97)]: - - @clerk/clerk-react@5.48.0 - - @clerk/types@4.87.0 - - @clerk/shared@3.26.0 - -## 0.23.63 - -### Patch Changes - -- Updated dependencies [[`23948dc`](https://github.com/clerk/javascript/commit/23948dc777ec6a17bafbae59c253a93143b0e105), [`82b84fe`](https://github.com/clerk/javascript/commit/82b84fed5f207673071ba7354a17f4a76e101201), [`54b4b5a`](https://github.com/clerk/javascript/commit/54b4b5a5f811f612fadf5c47ffda94a750c57a5e), [`50a8622`](https://github.com/clerk/javascript/commit/50a8622c3579306f15e5d40e5ea72b4fe4384ef7), [`23948dc`](https://github.com/clerk/javascript/commit/23948dc777ec6a17bafbae59c253a93143b0e105)]: - - @clerk/types@4.86.0 - - @clerk/shared@3.25.0 - - @clerk/clerk-react@5.47.0 - -## 0.23.62 - -### Patch Changes - -- Correctly specify dependency on `type-fest` package. ([#6711](https://github.com/clerk/javascript/pull/6711)) by [@dstaley](https://github.com/dstaley) - -- Updated dependencies [[`55490c3`](https://github.com/clerk/javascript/commit/55490c31fadc82bdca6cd5f2b22e5e158aaba0cb), [`e8d21de`](https://github.com/clerk/javascript/commit/e8d21de39b591973dad48fc1d1851c4d28b162fe), [`4a5bd7a`](https://github.com/clerk/javascript/commit/4a5bd7a4d9e96c89af07db69fc140ca2adb87f08), [`637f2e8`](https://github.com/clerk/javascript/commit/637f2e8768b76aaf756062b6b5b44bf651f66789)]: - - @clerk/types@4.85.0 - - @clerk/clerk-react@5.46.2 - - @clerk/shared@3.24.2 - -## 0.23.61 - -### Patch Changes - -- Updated dependencies [[`fced4fc`](https://github.com/clerk/javascript/commit/fced4fc869bb21c77826dfaf281b6640e0f0c006), [`9796fbf`](https://github.com/clerk/javascript/commit/9796fbf57494e108bdb531accc26bdb8e54e59f9), [`f28179b`](https://github.com/clerk/javascript/commit/f28179b30102550cc5601fcd22da7ca9a90959ee), [`e6e19d2`](https://github.com/clerk/javascript/commit/e6e19d2d2f3b2c4617b25f53830216a1d550e616), [`1b1e8b1`](https://github.com/clerk/javascript/commit/1b1e8b1fd33b787f956b17b193e5fd0a4cdc6cec)]: - - @clerk/types@4.84.1 - - @clerk/clerk-react@5.46.1 - - @clerk/shared@3.24.1 - -## 0.23.60 - -### Patch Changes - -- Updated dependencies [[`2a82737`](https://github.com/clerk/javascript/commit/2a8273705b9764e1a4613d5a0dbb738d0b156c05), [`cda5d7b`](https://github.com/clerk/javascript/commit/cda5d7b79b28dc03ec794ea54e0feb64b148cdd2), [`ba25a5b`](https://github.com/clerk/javascript/commit/ba25a5b5a3fa686a65f52e221d9d1712a389fea9), [`a50cfc8`](https://github.com/clerk/javascript/commit/a50cfc8f1dd168b436499e32fc8b0fc41d28bbff), [`377f67b`](https://github.com/clerk/javascript/commit/377f67b8e552d1a19efbe4530e9306675b7f8eab), [`65b12ee`](https://github.com/clerk/javascript/commit/65b12eeeb57ee80cdd8c36c5949d51f1227a413e), [`263722e`](https://github.com/clerk/javascript/commit/263722e61fd27403b4c8d9794880686771e123f9)]: - - @clerk/clerk-react@5.46.0 - - @clerk/types@4.84.0 - - @clerk/shared@3.24.0 - -## 0.23.59 - -### Patch Changes - -- Updated dependencies [[`600c648`](https://github.com/clerk/javascript/commit/600c648d4087a823341041c90018797fbc0033f0)]: - - @clerk/shared@3.23.0 - - @clerk/clerk-react@5.45.0 - - @clerk/types@4.83.0 - -## 0.23.58 - -### Patch Changes - -- Updated dependencies [[`d52714e`](https://github.com/clerk/javascript/commit/d52714e4cb7f369c74826cd4341c58eb1900abe4), [`ce49740`](https://github.com/clerk/javascript/commit/ce49740d474d6dd9da5096982ea4e9f14cf68f09), [`2ed539c`](https://github.com/clerk/javascript/commit/2ed539cc7f08ed4d70c33621563ad386ea8becc5), [`deaafe4`](https://github.com/clerk/javascript/commit/deaafe449773632d690aa2f8cafaf959392622b9), [`a26ecae`](https://github.com/clerk/javascript/commit/a26ecae09fd06cd34f094262f038a8eefbb23f7d), [`c16a7a5`](https://github.com/clerk/javascript/commit/c16a7a5837fc15e0e044baf9c809b8da6fbac795), [`05b6d65`](https://github.com/clerk/javascript/commit/05b6d65c0bc5736443325a5defee4c263ef196af)]: - - @clerk/clerk-react@5.44.0 - - @clerk/types@4.82.0 - - @clerk/shared@3.22.1 - -## 0.23.57 - -### Patch Changes - -- Updated dependencies [[`e52bf8e`](https://github.com/clerk/javascript/commit/e52bf8ebef74a9e123c69b69acde1340c01d32d7), [`c043c19`](https://github.com/clerk/javascript/commit/c043c1919854aaa5b9cf7f6df5bb517f5617f7a1), [`c28d29c`](https://github.com/clerk/javascript/commit/c28d29c79bb4f144d782313ca72df7db91a77340), [`172e054`](https://github.com/clerk/javascript/commit/172e054a3511be12d16ba19037db320c2d9838bf)]: - - @clerk/types@4.81.0 - - @clerk/clerk-react@5.43.1 - - @clerk/shared@3.22.0 - -## 0.23.56 - -### Patch Changes - -- Updated dependencies [[`8dc6bad`](https://github.com/clerk/javascript/commit/8dc6bad5c7051b59bd8c73e65d497f6a974bb1c3), [`aa6a3c3`](https://github.com/clerk/javascript/commit/aa6a3c3d3ba2de67a468c996cbf0bff43a09ddb8), [`db50c47`](https://github.com/clerk/javascript/commit/db50c4734920ada6002de8c62c994047eb6cb5a0)]: - - @clerk/types@4.80.0 - - @clerk/clerk-react@5.43.0 - - @clerk/shared@3.21.2 - -## 0.23.55 - -### Patch Changes - -- Updated dependencies [[`413468c`](https://github.com/clerk/javascript/commit/413468c9b9c8fb7576f8e4cbdccff98784e33fef), [`7b7eb1f`](https://github.com/clerk/javascript/commit/7b7eb1fc0235249c5c179239078294118f2947cd)]: - - @clerk/shared@3.21.1 - - @clerk/types@4.79.0 - - @clerk/clerk-react@5.42.2 - -## 0.23.54 - -### Patch Changes - -- Updated dependencies [[`5b24129`](https://github.com/clerk/javascript/commit/5b24129ddcfc2f7dc6eb79d8c818b4ff97c68e9a)]: - - @clerk/shared@3.21.0 - - @clerk/types@4.78.0 - - @clerk/clerk-react@5.42.1 - -## 0.23.53 - -### Patch Changes - -- Updated dependencies [[`4db1e58`](https://github.com/clerk/javascript/commit/4db1e58d70b60e1e236709b507666715d571e925), [`69498df`](https://github.com/clerk/javascript/commit/69498dfca3e6bb388eb8c94313eac06347dd5a27), [`59f1559`](https://github.com/clerk/javascript/commit/59f15593bab708b9e13eebfff6780c2d52b31b0a), [`69498df`](https://github.com/clerk/javascript/commit/69498dfca3e6bb388eb8c94313eac06347dd5a27)]: - - @clerk/types@4.77.0 - - @clerk/shared@3.20.1 - - @clerk/clerk-react@5.42.0 - -## 0.23.52 - -### Patch Changes - -- Updated dependencies [[`15fe106`](https://github.com/clerk/javascript/commit/15fe1060f730a6a4391f3d2451d23edd3218e1ae), [`173837c`](https://github.com/clerk/javascript/commit/173837c2526aa826b7981ee8d6d4f52c00675da5), [`8b52d7a`](https://github.com/clerk/javascript/commit/8b52d7ae19407e8ab5a5451bd7d34b6bc38417de), [`854dde8`](https://github.com/clerk/javascript/commit/854dde88e642c47b5a29ac8f576c8c1976e5d067), [`ae2e2d6`](https://github.com/clerk/javascript/commit/ae2e2d6b336be6b596cc855e549843beb5bfd2a1), [`037f25a`](https://github.com/clerk/javascript/commit/037f25a8171888168913b186b7edf871e0aaf197), [`f8b38b7`](https://github.com/clerk/javascript/commit/f8b38b7059e498fef3ac1271346be0710aa31c76)]: - - @clerk/types@4.76.0 - - @clerk/shared@3.20.0 - - @clerk/clerk-react@5.41.1 - -## 0.23.51 - -### Patch Changes - -- Updated dependencies [[`b72a3dd`](https://github.com/clerk/javascript/commit/b72a3dda2467720e5dc8cab3e7e6a110f3beb79b), [`d93b0ed`](https://github.com/clerk/javascript/commit/d93b0edf4adc57d48a26cb08444192887ccec659), [`6459f7d`](https://github.com/clerk/javascript/commit/6459f7dabe5f163f48ed73106bb901d8187da3e2), [`0ff648a`](https://github.com/clerk/javascript/commit/0ff648aeac0e2f5481596a98c8046d9d58a7bf75), [`9084759`](https://github.com/clerk/javascript/commit/90847593300be605e1ee1c06dac147ce68b25dc7)]: - - @clerk/types@4.75.0 - - @clerk/clerk-react@5.41.0 - - @clerk/shared@3.19.0 - -## 0.23.50 - -### Patch Changes - -- Updated dependencies [[`1ad16da`](https://github.com/clerk/javascript/commit/1ad16daa49795a861ae277001831230580b6b9f4), [`4edef81`](https://github.com/clerk/javascript/commit/4edef81dd423a0471e3f579dd6b36094aa8546aa), [`696f8e1`](https://github.com/clerk/javascript/commit/696f8e11a3e5391e6b5a97d98e929b8973575b9a), [`f318d22`](https://github.com/clerk/javascript/commit/f318d22cf83caaef272bcf532561a03ca72575e7), [`1cc66ab`](https://github.com/clerk/javascript/commit/1cc66aba1c0adac24323876e4cc3d96be888b07b)]: - - @clerk/clerk-react@5.40.0 - - @clerk/types@4.74.0 - - @clerk/shared@3.18.1 - -## 0.23.49 - -### Patch Changes - -- Updated dependencies [[`9368daf`](https://github.com/clerk/javascript/commit/9368dafb119b5a8ec6a9d6d82270e72bab6d8f1e), [`f93965f`](https://github.com/clerk/javascript/commit/f93965f64c81030f9fcf9d1cc4e4984d30cd12ec), [`7b6dcee`](https://github.com/clerk/javascript/commit/7b6dceea5bfd7f1cc1bf24126aa715307e24ae7f), [`ef87617`](https://github.com/clerk/javascript/commit/ef87617ae1fd125c806a33bfcfdf09c885319fa8)]: - - @clerk/shared@3.18.0 - - @clerk/clerk-react@5.39.0 - - @clerk/types@4.73.0 - -## 0.23.48 - -### Patch Changes - -- Updated dependencies [[`7a46679`](https://github.com/clerk/javascript/commit/7a46679a004739a7f712097c5779e9f5c068722e), [`05cc5ec`](https://github.com/clerk/javascript/commit/05cc5ecd82ecdbcc9922d3286224737a81813be0), [`22c35ef`](https://github.com/clerk/javascript/commit/22c35efb59226df2efaa2891fa4775c13312f4c6), [`e8d816a`](https://github.com/clerk/javascript/commit/e8d816a3350e862c3e9e1d4f8c96c047a0a016a2), [`aa9f185`](https://github.com/clerk/javascript/commit/aa9f185e21b58f8a6e03ea44ce29ee09ad2477d9), [`af0e123`](https://github.com/clerk/javascript/commit/af0e12393c9412281626e20dafb1b3a15558f6d9), [`241bbbd`](https://github.com/clerk/javascript/commit/241bbbd5ad3915419fe222861a2eeb0132a294e0), [`3d1d871`](https://github.com/clerk/javascript/commit/3d1d8711405646cf3c2aabe99e08337a1028703a)]: - - @clerk/shared@3.17.0 - - @clerk/clerk-react@5.38.1 - - @clerk/types@4.72.0 - -## 0.23.47 - -### Patch Changes - -- Updated dependencies [[`e404456`](https://github.com/clerk/javascript/commit/e4044566bca81f63c8e9c630fdec0f498ad6fc08), [`2803133`](https://github.com/clerk/javascript/commit/28031330a9810946feb44b93be10c067fb3b63ba), [`f1d9d34`](https://github.com/clerk/javascript/commit/f1d9d3482a796dd5f7796ede14159850e022cba2), [`d58b959`](https://github.com/clerk/javascript/commit/d58b9594cf65158e87dbaa90d632c45f543373e1), [`f6375f0`](https://github.com/clerk/javascript/commit/f6375f01e8d8a06e12d4a71285912e9dda7b6f20), [`822ba1f`](https://github.com/clerk/javascript/commit/822ba1fd5e7daf665120cf183e4600a227098d53), [`d4d2612`](https://github.com/clerk/javascript/commit/d4d2612483baf356c389ef0ba5084059025481f2)]: - - @clerk/types@4.71.0 - - @clerk/shared@3.16.0 - - @clerk/clerk-react@5.38.0 - -## 0.23.46 - -### Patch Changes - -- Updated dependencies [[`cfa7882`](https://github.com/clerk/javascript/commit/cfa78827cea6e81ce671ae204f529d2f93e3304d), [`b0fdc9e`](https://github.com/clerk/javascript/commit/b0fdc9eaf764ca0c17cbe0810b7d240f6d9db0b6)]: - - @clerk/clerk-react@5.37.0 - - @clerk/types@4.70.1 - - @clerk/shared@3.15.1 - -## 0.23.45 - -### Patch Changes - -- Updated dependencies [[`8feb59b`](https://github.com/clerk/javascript/commit/8feb59b808254a59c9bf4cf9c00f177e29e5e41b), [`cd59c0e`](https://github.com/clerk/javascript/commit/cd59c0e5512a341dd8fb420aca583333c8243aa5), [`cd59c0e`](https://github.com/clerk/javascript/commit/cd59c0e5512a341dd8fb420aca583333c8243aa5)]: - - @clerk/clerk-react@5.36.0 - - @clerk/types@4.70.0 - - @clerk/shared@3.15.0 - -## 0.23.44 - -### Patch Changes - -- Updated dependencies [[`fecc99d`](https://github.com/clerk/javascript/commit/fecc99d43cb7db5b99863829acb234cbce0da264), [`959d63d`](https://github.com/clerk/javascript/commit/959d63de27e5bfe27b46699b441dfd4e48616bf8), [`10e1060`](https://github.com/clerk/javascript/commit/10e10605b18a58f33a93caed058159c190678e74), [`92c44dd`](https://github.com/clerk/javascript/commit/92c44dd9d51e771a928a8da7004bdb8f8bdbaf58), [`a04a8f5`](https://github.com/clerk/javascript/commit/a04a8f5f81241ee41d93cd64793beca9d6296abb), [`c61855c`](https://github.com/clerk/javascript/commit/c61855c51d9c129d48c4543da3719939ad82f623), [`43ea069`](https://github.com/clerk/javascript/commit/43ea069c570dc64503fc82356ad28a2e43689d45)]: - - @clerk/clerk-react@5.35.4 - - @clerk/types@4.69.0 - - @clerk/shared@3.14.0 - -## 0.23.43 - -### Patch Changes - -- Updated dependencies [[`d2f6f9e`](https://github.com/clerk/javascript/commit/d2f6f9e02036a4288916fcce14f24be5d56561c4), [`a329836`](https://github.com/clerk/javascript/commit/a329836a6c64f0a551a277ccae07043456a70523), [`6041c39`](https://github.com/clerk/javascript/commit/6041c39a31e787a6065dbc3f21e1c569982a06de), [`3f1270d`](https://github.com/clerk/javascript/commit/3f1270db86a21ead0ed6f0bd4f9986485203e973)]: - - @clerk/clerk-react@5.35.3 - - @clerk/types@4.68.0 - - @clerk/shared@3.13.0 - -## 0.23.42 - -### Patch Changes - -- Updated dependencies [[`2a90b68`](https://github.com/clerk/javascript/commit/2a90b689550ae960496c9292ca23e0225e3425cd), [`af50905`](https://github.com/clerk/javascript/commit/af50905ea497ed3286c8c4c374498e06ca6ee82b)]: - - @clerk/clerk-react@5.35.2 - - @clerk/types@4.67.0 - - @clerk/shared@3.12.3 - -## 0.23.41 - -### Patch Changes - -- Updated dependencies [[`8ee859c`](https://github.com/clerk/javascript/commit/8ee859ce00d1d5747c14a80fe7166303e64a4f1f)]: - - @clerk/shared@3.12.2 - - @clerk/types@4.66.1 - - @clerk/clerk-react@5.35.1 - -## 0.23.40 - -### Patch Changes - -- Updated dependencies [[`025e304`](https://github.com/clerk/javascript/commit/025e304c4d6402dfd750ee51ac9c8fc2dea1f353), [`dedf487`](https://github.com/clerk/javascript/commit/dedf48703986d547d5b28155b0182a51030cffeb), [`b96114e`](https://github.com/clerk/javascript/commit/b96114e438638896ba536bb7a17b09cdadcd9407)]: - - @clerk/types@4.66.0 - - @clerk/clerk-react@5.35.0 - - @clerk/shared@3.12.1 - -## 0.23.39 - -### Patch Changes - -- Updated dependencies [[`2be6a53`](https://github.com/clerk/javascript/commit/2be6a53959cb8a3127c2eb5d1aeb4248872d2c24), [`f6a1c35`](https://github.com/clerk/javascript/commit/f6a1c35bd5fb4bd2a3cd45bdaf9defe6be59d4a9), [`6826d0b`](https://github.com/clerk/javascript/commit/6826d0bbd03e844d49224565878a4326684f06b4), [`f6a1c35`](https://github.com/clerk/javascript/commit/f6a1c35bd5fb4bd2a3cd45bdaf9defe6be59d4a9), [`97a07f7`](https://github.com/clerk/javascript/commit/97a07f78b4b0c3dc701a2610097ec7d6232f79e7)]: - - @clerk/types@4.65.0 - - @clerk/shared@3.12.0 - - @clerk/clerk-react@5.34.0 - -## 0.23.38 - -### Patch Changes - -- Updated dependencies [[`f42c4fe`](https://github.com/clerk/javascript/commit/f42c4fedfdab873129b876eba38b3677f190b460), [`ec207dc`](https://github.com/clerk/javascript/commit/ec207dcd2a13340cfa4e3b80d3d52d1b4e7d5f23), [`ec207dc`](https://github.com/clerk/javascript/commit/ec207dcd2a13340cfa4e3b80d3d52d1b4e7d5f23), [`0e0cc1f`](https://github.com/clerk/javascript/commit/0e0cc1fa85347d727a4fd3718fe45b0f0244ddd9)]: - - @clerk/types@4.64.0 - - @clerk/clerk-react@5.33.0 - - @clerk/shared@3.11.0 - -## 0.23.37 - -### Patch Changes - -- Updated dependencies [[`abd8446`](https://github.com/clerk/javascript/commit/abd844609dad263d974da7fbf5e3575afce73abe), [`8387a39`](https://github.com/clerk/javascript/commit/8387a392a04906f0f10d84c61cfee36f23942f85), [`f2a6641`](https://github.com/clerk/javascript/commit/f2a66419b1813abc86ea98fde7475861995a1486)]: - - @clerk/shared@3.10.2 - - @clerk/types@4.63.0 - - @clerk/clerk-react@5.32.4 - -## 0.23.36 - -### Patch Changes - -- Updated dependencies [[`02a1f42`](https://github.com/clerk/javascript/commit/02a1f42dfdb28ea956d6cbd3fbabe10093d2fad8), [`edc0bfd`](https://github.com/clerk/javascript/commit/edc0bfdae929dad78a99dfd6275aad947d9ddd73)]: - - @clerk/shared@3.10.1 - - @clerk/clerk-react@5.32.3 - - @clerk/types@4.62.1 - -## 0.23.35 - -### Patch Changes - -- Updated dependencies [[`f1be1fe`](https://github.com/clerk/javascript/commit/f1be1fe3d575c11acd04fc7aadcdec8f89829894), [`bffb42a`](https://github.com/clerk/javascript/commit/bffb42aaf266a188b9ae7d16ace3024d468a3bd4)]: - - @clerk/types@4.62.0 - - @clerk/shared@3.10.0 - - @clerk/clerk-react@5.32.2 - -## 0.23.34 - -### Patch Changes - -- Updated dependencies [[`b495279`](https://github.com/clerk/javascript/commit/b4952796e3c7dee4ab4726de63a17b7f4265ce37), [`c3fa15d`](https://github.com/clerk/javascript/commit/c3fa15d60642b4fcbcf26e21caaca0fc60975795), [`52d5e57`](https://github.com/clerk/javascript/commit/52d5e5768d54725b4d20d028135746493e05d44c), [`15a945c`](https://github.com/clerk/javascript/commit/15a945c02a9f6bc8d2f7d1e3534217100bf45936), [`72629b0`](https://github.com/clerk/javascript/commit/72629b06fb1fe720fa2a61462306a786a913e9a8)]: - - @clerk/types@4.61.0 - - @clerk/shared@3.9.8 - - @clerk/clerk-react@5.32.1 - -## 0.23.33 - -### Patch Changes - -- Updated dependencies [[`19e9e11`](https://github.com/clerk/javascript/commit/19e9e11af04f13fd12975fbf7016fe0583202056), [`18bcb64`](https://github.com/clerk/javascript/commit/18bcb64a3e8b6d352d7933ed094d68214e6e80fb), [`138f733`](https://github.com/clerk/javascript/commit/138f733f13121487268a4f96e6eb2cffedc6e238), [`18bcb64`](https://github.com/clerk/javascript/commit/18bcb64a3e8b6d352d7933ed094d68214e6e80fb), [`48be55b`](https://github.com/clerk/javascript/commit/48be55b61a86e014dd407414764d24bb43fd26f3), [`2c6f805`](https://github.com/clerk/javascript/commit/2c6f805a9e6e4685990f9a8abc740b2d0859a453), [`97749d5`](https://github.com/clerk/javascript/commit/97749d570bc687c7e05cd800a50e0ae4180a371d)]: - - @clerk/types@4.60.1 - - @clerk/clerk-react@5.32.0 - - @clerk/shared@3.9.7 - -## 0.23.32 - -### Patch Changes - -- Updated dependencies [[`d8fa5d9`](https://github.com/clerk/javascript/commit/d8fa5d9d3d8dc575260d8d2b7c7eeeb0052d0b0d), [`be2e89c`](https://github.com/clerk/javascript/commit/be2e89ca11aa43d48f74c57a5a34e20d85b4003c), [`5644d94`](https://github.com/clerk/javascript/commit/5644d94f711a0733e4970c3f15c24d56cafc8743), [`a3232c7`](https://github.com/clerk/javascript/commit/a3232c7ee8c1173d2ce70f8252fc083c7bf19374), [`b578225`](https://github.com/clerk/javascript/commit/b5782258242474c9b0987a3f8349836cd763f24b), [`8838120`](https://github.com/clerk/javascript/commit/8838120596830b88fec1c6c853371dabfec74a0d)]: - - @clerk/types@4.60.0 - - @clerk/clerk-react@5.31.9 - - @clerk/shared@3.9.6 - -## 0.23.31 - -### Patch Changes - -- Updated dependencies [[`f897773`](https://github.com/clerk/javascript/commit/f89777379da63cf45039c1570b51ba10a400817c), [`2c6a0cc`](https://github.com/clerk/javascript/commit/2c6a0cca6e824bafc6b0d0501784517a5b1f75ea), [`71e6a1f`](https://github.com/clerk/javascript/commit/71e6a1f1024d65b7a09cdc8fa81ce0164e0a34cb)]: - - @clerk/shared@3.9.5 - - @clerk/types@4.59.3 - - @clerk/clerk-react@5.31.8 - -## 0.23.30 - -### Patch Changes - -- Updated dependencies [[`6ed3dfc`](https://github.com/clerk/javascript/commit/6ed3dfc1bc742ac9d9a2307fe8e4733411cbc0d7)]: - - @clerk/types@4.59.2 - - @clerk/clerk-react@5.31.7 - - @clerk/shared@3.9.4 - -## 0.23.29 - -### Patch Changes - -- Updated dependencies [[`f237d76`](https://github.com/clerk/javascript/commit/f237d7617e5398ca0ba981e4336cac2191505b00)]: - - @clerk/shared@3.9.3 - - @clerk/clerk-react@5.31.6 - -## 0.23.28 - -### Patch Changes - -- Updated dependencies [[`c305b31`](https://github.com/clerk/javascript/commit/c305b310e351e9ce2012f805b35e464c3e43e310), [`6bb480e`](https://github.com/clerk/javascript/commit/6bb480ef663a6dfa219bc9546aca087d5d9624d0)]: - - @clerk/types@4.59.1 - - @clerk/shared@3.9.2 - - @clerk/clerk-react@5.31.5 - -## 0.23.27 - -### Patch Changes - -- Updated dependencies [[`b1337df`](https://github.com/clerk/javascript/commit/b1337dfeae8ccf8622efcf095e3201f9bbf1cefa), [`65f0878`](https://github.com/clerk/javascript/commit/65f08788ee5e56242eee2194c73ba90965c75c97), [`df6fefd`](https://github.com/clerk/javascript/commit/df6fefd05fd2df93f5286d97e546b48911adea7c), [`4282bfa`](https://github.com/clerk/javascript/commit/4282bfa09491225bde7d619fe9a3561062703f69), [`5491491`](https://github.com/clerk/javascript/commit/5491491711e0a8ee37828451c1f603a409de32cf)]: - - @clerk/types@4.59.0 - - @clerk/clerk-react@5.31.4 - - @clerk/shared@3.9.1 - -## 0.23.26 - -### Patch Changes - -- Updated dependencies [[`1ff6d6e`](https://github.com/clerk/javascript/commit/1ff6d6efbe838b3f7f6977b2b5215c2cafd715f6), [`fbf3cf4`](https://github.com/clerk/javascript/commit/fbf3cf4916469c4e118870bf12efca2d0f77d9d8)]: - - @clerk/shared@3.9.0 - - @clerk/types@4.58.1 - - @clerk/clerk-react@5.31.3 - -## 0.23.25 - -### Patch Changes - -- Updated dependencies [[`0f5145e`](https://github.com/clerk/javascript/commit/0f5145e164f3d3d5faf57e58162b05e7110d2403), [`afdfd18`](https://github.com/clerk/javascript/commit/afdfd18d645608dec37e52a291a91ba5f42dcbe7), [`b7c51ba`](https://github.com/clerk/javascript/commit/b7c51baac6df1129b468274c9a7f63ca303f16ce), [`437b53b`](https://github.com/clerk/javascript/commit/437b53b67e281d076b5b3f927e11c1d64666d154), [`5217155`](https://github.com/clerk/javascript/commit/52171554250c5c58f4f497b6d3c7416e79ac77da)]: - - @clerk/types@4.58.0 - - @clerk/clerk-react@5.31.2 - - @clerk/shared@3.8.2 - -## 0.23.24 - -### Patch Changes - -- Updated dependencies [[`4db96e0`](https://github.com/clerk/javascript/commit/4db96e0ff2ab44c7bdd8540e09ec70b84b19d3eb), [`36fb43f`](https://github.com/clerk/javascript/commit/36fb43f8b35866bdc20680fac58020f036d30d1f), [`e5ac444`](https://github.com/clerk/javascript/commit/e5ac4447f52bb6887ad686feab308fe9daf76e33), [`4db96e0`](https://github.com/clerk/javascript/commit/4db96e0ff2ab44c7bdd8540e09ec70b84b19d3eb), [`d227805`](https://github.com/clerk/javascript/commit/d22780599a5e29545a3d8309cc411c2e8659beac)]: - - @clerk/types@4.57.1 - - @clerk/clerk-react@5.31.1 - - @clerk/shared@3.8.1 - -## 0.23.23 - -### Patch Changes - -- Updated dependencies [[`db0138f`](https://github.com/clerk/javascript/commit/db0138f3f72aea8cb68a5684a90123f733848f63), [`45e8298`](https://github.com/clerk/javascript/commit/45e829890ec9ac66f07e0d7076cd283f14c893ed), [`aa97231`](https://github.com/clerk/javascript/commit/aa97231962e3f472a46135e376159c6ddcf1157b), [`c792f37`](https://github.com/clerk/javascript/commit/c792f37129fd6475d5af95146e9ef0f1c8eff730), [`3bf08a9`](https://github.com/clerk/javascript/commit/3bf08a9e0a9e65496edac5fc3bb22ad7b561df26), [`74cf3b2`](https://github.com/clerk/javascript/commit/74cf3b28cdf622a942aaf99caabfba74b7e856fd), [`037b113`](https://github.com/clerk/javascript/commit/037b113aaedd53d4647d88f1659eb9c14cf6f275), [`c15a412`](https://github.com/clerk/javascript/commit/c15a412169058e2304a51c9e92ffaa7f6bb2a898), [`7726a03`](https://github.com/clerk/javascript/commit/7726a03a7fec4d292b6de2587b84ed4371984c23), [`ed10566`](https://github.com/clerk/javascript/commit/ed1056637624eec5bfd50333407c1e63e34c193b), [`b846a9a`](https://github.com/clerk/javascript/commit/b846a9ab96db6b1d8344a4b693051618865508a8), [`e66c800`](https://github.com/clerk/javascript/commit/e66c8002b82b2902f77e852e16482f5cfb062d2c), [`45e8298`](https://github.com/clerk/javascript/commit/45e829890ec9ac66f07e0d7076cd283f14c893ed), [`9c41091`](https://github.com/clerk/javascript/commit/9c41091eb795bce8ffeeeca0264ae841fe07b426), [`29462b4`](https://github.com/clerk/javascript/commit/29462b433eb411ce614e4768e5844cacd00c1975), [`322c43f`](https://github.com/clerk/javascript/commit/322c43f6807a932c3cfaaef1b587b472c80180d2), [`17397f9`](https://github.com/clerk/javascript/commit/17397f95b715bd4fefd7f63c1d351abcf1c8ee16), [`45e8298`](https://github.com/clerk/javascript/commit/45e829890ec9ac66f07e0d7076cd283f14c893ed)]: - - @clerk/types@4.57.0 - - @clerk/clerk-react@5.31.0 - - @clerk/shared@3.8.0 - -## 0.23.22 - -### Patch Changes - -- Updated dependencies [[`9ec0a73`](https://github.com/clerk/javascript/commit/9ec0a7353e9f6ea661c3d7b9542423b6eb1d29e9), [`d9222fc`](https://github.com/clerk/javascript/commit/d9222fc3c21da2bcae30b06f0b1897f526935582)]: - - @clerk/types@4.56.3 - - @clerk/clerk-react@5.30.4 - - @clerk/shared@3.7.8 - -## 0.23.21 - -### Patch Changes - -- Updated dependencies [[`225b9ca`](https://github.com/clerk/javascript/commit/225b9ca21aba44930872a85d6b112ee2a1b606b9)]: - - @clerk/types@4.56.2 - - @clerk/clerk-react@5.30.3 - - @clerk/shared@3.7.7 - -## 0.23.20 - -### Patch Changes - -- Updated dependencies [[`387bf62`](https://github.com/clerk/javascript/commit/387bf623406306e0c5c08da937f4930a7ec5e4a5), [`2716622`](https://github.com/clerk/javascript/commit/27166224e12af582298460d438bd7f83ea8e04bf), [`294da82`](https://github.com/clerk/javascript/commit/294da82336e7a345900d7ef9b28f56a7c8864c52)]: - - @clerk/types@4.56.1 - - @clerk/shared@3.7.6 - - @clerk/clerk-react@5.30.2 - -## 0.23.19 - -### Patch Changes - -- Updated dependencies [[`b02e766`](https://github.com/clerk/javascript/commit/b02e76627e47aec314573586451fa345a089115a), [`5d78b28`](https://github.com/clerk/javascript/commit/5d78b286b63e35fbcf44aac1f7657cbeaba4d659), [`d7f4438`](https://github.com/clerk/javascript/commit/d7f4438fa4bfd04474d5cdb9212ba908568ad6d2), [`5866855`](https://github.com/clerk/javascript/commit/58668550ec91d5511cf775972c54dc485185cc58), [`0007106`](https://github.com/clerk/javascript/commit/00071065998a3676c51e396b4c0afcbf930a9898), [`462b5b2`](https://github.com/clerk/javascript/commit/462b5b271d4e120d58a85818a358b60a6b3c8100), [`447d7a9`](https://github.com/clerk/javascript/commit/447d7a9e133c2a0e7db014bd5837e6ffff08f572), [`2beea29`](https://github.com/clerk/javascript/commit/2beea2957c67bc62446fe24d36332b0a4e850d7d), [`115601d`](https://github.com/clerk/javascript/commit/115601d12fd65dbf3011c0cda368525a2b95bfeb)]: - - @clerk/types@4.56.0 - - @clerk/clerk-react@5.30.1 - - @clerk/shared@3.7.5 - -## 0.23.18 - -### Patch Changes - -- Updated dependencies [[`8b25035`](https://github.com/clerk/javascript/commit/8b25035aa49382fe1cd1c6f30ec80e86bcf9d66e), [`f0f1ed7`](https://github.com/clerk/javascript/commit/f0f1ed7ef3666bfc1cbf945326e94a51e83c4646), [`25c3502`](https://github.com/clerk/javascript/commit/25c35023ee995acbf8f5c8989619ebc176a366d6)]: - - @clerk/types@4.55.1 - - @clerk/clerk-react@5.30.0 - - @clerk/shared@3.7.4 - -## 0.23.17 - -### Patch Changes - -- Updated dependencies [[`4334598`](https://github.com/clerk/javascript/commit/4334598108ff2cfa3c25b5a46117c1c9c65b7974), [`33201bf`](https://github.com/clerk/javascript/commit/33201bf972d6a980617d47ebd776bef76f871833), [`4334598`](https://github.com/clerk/javascript/commit/4334598108ff2cfa3c25b5a46117c1c9c65b7974), [`0ae0403`](https://github.com/clerk/javascript/commit/0ae040303d239b75a3221436354a2c2ecdb85aae)]: - - @clerk/clerk-react@5.29.0 - - @clerk/types@4.55.0 - - @clerk/shared@3.7.3 - -## 0.23.16 - -### Patch Changes - -- Updated dependencies [[`45486ac`](https://github.com/clerk/javascript/commit/45486acebf4d133efb09a3622a738cdbf4e51d66), [`837692a`](https://github.com/clerk/javascript/commit/837692aa40197b1574783ad36d0d017a771c08e1), [`0c00e59`](https://github.com/clerk/javascript/commit/0c00e59ff4714491650ac9480ae3b327c626d30d), [`6a5f644`](https://github.com/clerk/javascript/commit/6a5f6447a36a635d6201f8bb7619fb844ab21b79)]: - - @clerk/types@4.54.2 - - @clerk/shared@3.7.2 - - @clerk/clerk-react@5.28.2 - -## 0.23.15 - -### Patch Changes - -- Updated dependencies [[`ab939fd`](https://github.com/clerk/javascript/commit/ab939fdb29150c376280b42f861a188a33f57dcc), [`03284da`](https://github.com/clerk/javascript/commit/03284da6a93a790ce3e3ebbd871c06e19f5a8803), [`7389ba3`](https://github.com/clerk/javascript/commit/7389ba3164ca0d848fb0a9de5d7e9716925fadcc), [`f6ef841`](https://github.com/clerk/javascript/commit/f6ef841125ff21ca8cae731d1f47f3a101d887e1), [`e634830`](https://github.com/clerk/javascript/commit/e6348301ab56a7868f24c1b9a4dd9e1d60f6027b), [`f8887b2`](https://github.com/clerk/javascript/commit/f8887b2cbd145e8e49bec890e8b6e02e34178d6a)]: - - @clerk/types@4.54.1 - - @clerk/shared@3.7.1 - - @clerk/clerk-react@5.28.1 - -## 0.23.14 - -### Patch Changes - -- Updated dependencies [[`e4d04ae`](https://github.com/clerk/javascript/commit/e4d04aea490ab67e3431729398d3f4c46fc3e7e7), [`431a821`](https://github.com/clerk/javascript/commit/431a821b590835bcf6193a4cbdd234c5e763e08c), [`93068ea`](https://github.com/clerk/javascript/commit/93068ea9eb19d8c8b9c7ade35d0cd860e08049fc), [`431a821`](https://github.com/clerk/javascript/commit/431a821b590835bcf6193a4cbdd234c5e763e08c), [`431a821`](https://github.com/clerk/javascript/commit/431a821b590835bcf6193a4cbdd234c5e763e08c), [`103bc03`](https://github.com/clerk/javascript/commit/103bc03571c8845df205f4c6fd0c871c3368d1d0), [`431a821`](https://github.com/clerk/javascript/commit/431a821b590835bcf6193a4cbdd234c5e763e08c), [`48438b4`](https://github.com/clerk/javascript/commit/48438b409036088701bda7e1e732d6a51bee8cdc), [`196dcb4`](https://github.com/clerk/javascript/commit/196dcb47928bd22a3382197f8594a590f688faee)]: - - @clerk/types@4.54.0 - - @clerk/shared@3.7.0 - - @clerk/clerk-react@5.28.0 - -## 0.23.13 - -### Patch Changes - -- Improve JSDoc comments ([#5575](https://github.com/clerk/javascript/pull/5575)) by [@LekoArts](https://github.com/LekoArts) - -- Upgrading xstate and @xstate/react to add react@19 as a transitive peerDependency ([#5572](https://github.com/clerk/javascript/pull/5572)) by [@jacekradko](https://github.com/jacekradko) - -- Updated dependencies [[`70c9db9`](https://github.com/clerk/javascript/commit/70c9db9f3b51ba034f76e0cc4cf338e7b406d9b1), [`554242e`](https://github.com/clerk/javascript/commit/554242e16e50c92a6afb6ed74c681b04b9f113b5), [`cc1f9a0`](https://github.com/clerk/javascript/commit/cc1f9a0adb7771b615b0f2994a5ac571b59889dd), [`8186cb5`](https://github.com/clerk/javascript/commit/8186cb564575ac3ce97079ec203865bf5deb05ee)]: - - @clerk/shared@3.6.0 - - @clerk/clerk-react@5.27.0 - - @clerk/types@4.53.0 - -## 0.23.12 - -### Patch Changes - -- Updated dependencies [[`3ad3bc8`](https://github.com/clerk/javascript/commit/3ad3bc8380b354b0cd952eb58eb6c07650efa0f2), [`cfa94b8`](https://github.com/clerk/javascript/commit/cfa94b88476608edf8c2486e8ec0d3f3f82e0bfb), [`2033919`](https://github.com/clerk/javascript/commit/203391964857b98dae11944799d1e6328439e838), [`5f3cc46`](https://github.com/clerk/javascript/commit/5f3cc460b6b775b5a74746758b8cff11649a877a)]: - - @clerk/shared@3.5.0 - - @clerk/types@4.52.0 - - @clerk/clerk-react@5.26.2 - -## 0.23.11 - -### Patch Changes - -- Updated dependencies [[`f6f275d`](https://github.com/clerk/javascript/commit/f6f275dac5ae83ac0c2016a85a6a0cee9513f224)]: - - @clerk/types@4.51.1 - - @clerk/clerk-react@5.26.1 - - @clerk/shared@3.4.1 - -## 0.23.10 - -### Patch Changes - -- Updated dependencies [[`e1ec52b`](https://github.com/clerk/javascript/commit/e1ec52b93038c9cb24e030dc06e53825a384a480), [`bebb6d8`](https://github.com/clerk/javascript/commit/bebb6d8af66b2bb7a4b3bdf96f9d480e65b31ba2), [`a8180ce`](https://github.com/clerk/javascript/commit/a8180ceef447a13d84eac6a64ed7a04d791d8d64), [`d0d5203`](https://github.com/clerk/javascript/commit/d0d5203e4ee9e2e1bed5c00ef0f87f0130f1d298), [`6112420`](https://github.com/clerk/javascript/commit/6112420889f1577fb16d7bfa706aaffe1090093d), [`026ad57`](https://github.com/clerk/javascript/commit/026ad5717cf661182c219fb0ffab4522083065c3), [`9b25e31`](https://github.com/clerk/javascript/commit/9b25e311cf5e15f896c7948faa42ace45df364c5)]: - - @clerk/clerk-react@5.26.0 - - @clerk/types@4.51.0 - - @clerk/shared@3.4.0 - -## 0.23.9 - -### Patch Changes - -- Updated dependencies [[`60a9a51`](https://github.com/clerk/javascript/commit/60a9a51dff7d59e7397536586cf1cfe029bc021b), [`e984494`](https://github.com/clerk/javascript/commit/e984494416dda9a6f04acaaba61f8c2683090961), [`ec4521b`](https://github.com/clerk/javascript/commit/ec4521b4fe56602f524a0c6d1b09d21aef5d8bd0), [`38828ae`](https://github.com/clerk/javascript/commit/38828ae58d6d4e8e3c60945284930179b2b6bb40), [`f30fa75`](https://github.com/clerk/javascript/commit/f30fa750754f19030f932a666d2bdbdf0d86743d), [`9c68678`](https://github.com/clerk/javascript/commit/9c68678e87047e6312b708b775ebfb23a3e22f8a), [`619cde8`](https://github.com/clerk/javascript/commit/619cde8c532d635d910ebbc08ad6abcc025694b4)]: - - @clerk/shared@3.3.0 - - @clerk/clerk-react@5.25.6 - - @clerk/types@4.50.2 - -## 0.23.8 - -### Patch Changes - -- Updated dependencies [[`e20fb6b`](https://github.com/clerk/javascript/commit/e20fb6b397fb69c9d5af4e321267b82f12a5f127), [`77e6462`](https://github.com/clerk/javascript/commit/77e64628560cab688af214edb5922e67cd68a951)]: - - @clerk/shared@3.2.3 - - @clerk/types@4.50.1 - - @clerk/clerk-react@5.25.5 - -## 0.23.7 - -### Patch Changes - -- Updated dependencies [[`1da28a2`](https://github.com/clerk/javascript/commit/1da28a28bf602069b433c15b92df21f682779294), [`a9b618d`](https://github.com/clerk/javascript/commit/a9b618dfa97a0dacc462186c8b2588ad5ddb6902), [`f20dc15`](https://github.com/clerk/javascript/commit/f20dc159f542449e7f5b437b70d3eb3ba04d6975), [`4d9f1ee`](https://github.com/clerk/javascript/commit/4d9f1ee8c22fe1e4a166ff054d0af4d37b829f0a)]: - - @clerk/types@4.50.0 - - @clerk/shared@3.2.2 - - @clerk/clerk-react@5.25.4 - -## 0.23.6 - -### Patch Changes - -- Updated dependencies [[`466ed13`](https://github.com/clerk/javascript/commit/466ed136af73b59b267d92ad3296039d1c3a4fcc)]: - - @clerk/types@4.49.2 - - @clerk/clerk-react@5.25.3 - - @clerk/shared@3.2.1 - -## 0.23.5 - -### Patch Changes - -- Updated dependencies [[`892bc0e`](https://github.com/clerk/javascript/commit/892bc0eee9e0bb04d327eb84b44201fa34806483)]: - - @clerk/shared@3.2.0 - - @clerk/clerk-react@5.25.2 - -## 0.23.4 - -### Patch Changes - -- Updated dependencies [[`e513333`](https://github.com/clerk/javascript/commit/e5133330a196c5c3742634cc9c3d3233ff488b0d), [`3910ebe`](https://github.com/clerk/javascript/commit/3910ebea85817273f18fd2f3f142dd1c728e2220), [`e513333`](https://github.com/clerk/javascript/commit/e5133330a196c5c3742634cc9c3d3233ff488b0d)]: - - @clerk/clerk-react@5.25.1 - - @clerk/types@4.49.1 - - @clerk/shared@3.1.0 - -## 0.23.3 - -### Patch Changes - -- Updated dependencies [[`725918d`](https://github.com/clerk/javascript/commit/725918df2e74cea15e9b748aaf103a52df8e8500), [`91d0f0b`](https://github.com/clerk/javascript/commit/91d0f0b0dccab7168ad4dc06c8629808938c235f), [`9572bf5`](https://github.com/clerk/javascript/commit/9572bf5bdfb7dc309ec8714989b98ab12174965b), [`39bbc51`](https://github.com/clerk/javascript/commit/39bbc5189a33dc6cebdc269ac2184dc4ffff2534), [`3dddcda`](https://github.com/clerk/javascript/commit/3dddcda191d8f8d6a9b02464f1f6374d3c6aacb9), [`7524943`](https://github.com/clerk/javascript/commit/7524943300d7e693d61cc1820b520abfadec1c64), [`150b5c8`](https://github.com/clerk/javascript/commit/150b5c89477abb0feab15e0a886179473f653cac), [`23c931e`](https://github.com/clerk/javascript/commit/23c931e9e95e6de992549ad499b477aca9a9c344), [`730262f`](https://github.com/clerk/javascript/commit/730262f0f973923c8749b09078c80c2fc966a8ec), [`0b18bb1`](https://github.com/clerk/javascript/commit/0b18bb1fe6fa3ded97547bb6b4d2c73030aad329), [`021bc5f`](https://github.com/clerk/javascript/commit/021bc5f40044d34e49956ce3c9b61d833d815b42), [`1a61390`](https://github.com/clerk/javascript/commit/1a61390d3482bd4af58508b972ad89dea56fa224)]: - - @clerk/types@4.49.0 - - @clerk/clerk-react@5.25.0 - - @clerk/shared@3.0.2 - -## 0.23.2 - -### Patch Changes - -- Updated dependencies [[`75879672c5805bfba1caca906ac0729497744164`](https://github.com/clerk/javascript/commit/75879672c5805bfba1caca906ac0729497744164), [`7ec95a7e59033600958aca4b86f3bcd5da947dec`](https://github.com/clerk/javascript/commit/7ec95a7e59033600958aca4b86f3bcd5da947dec), [`3c225d90227141dc62d955e76c7f8e0202524bc7`](https://github.com/clerk/javascript/commit/3c225d90227141dc62d955e76c7f8e0202524bc7), [`2a66c16af08573000bb619607346ac420cd4ce56`](https://github.com/clerk/javascript/commit/2a66c16af08573000bb619607346ac420cd4ce56)]: - - @clerk/shared@3.0.1 - - @clerk/clerk-react@5.24.2 - - @clerk/types@4.48.0 - -## 0.23.1 - -### Patch Changes - -- Updated dependencies [[`3d436484eb01b42e0008b6675f4be68f15d21079`](https://github.com/clerk/javascript/commit/3d436484eb01b42e0008b6675f4be68f15d21079)]: - - @clerk/clerk-react@5.24.1 - -## 0.23.0 - -### Minor Changes - -- Surface new `pending` session as a signed-in state ([#5136](https://github.com/clerk/javascript/pull/5136)) by [@LauraBeatris](https://github.com/LauraBeatris) - -### Patch Changes - -- Updated dependencies [[`28179323d9891bd13625e32c5682a3276e73cdae`](https://github.com/clerk/javascript/commit/28179323d9891bd13625e32c5682a3276e73cdae), [`7ae77b74326e378bf161e29886ee82e1556d9840`](https://github.com/clerk/javascript/commit/7ae77b74326e378bf161e29886ee82e1556d9840), [`c5c246ce91c01db9f1eaccbd354f646bcd24ec0a`](https://github.com/clerk/javascript/commit/c5c246ce91c01db9f1eaccbd354f646bcd24ec0a), [`b707e942bfd434ff8a3b9a9fadf9d1b694d702c8`](https://github.com/clerk/javascript/commit/b707e942bfd434ff8a3b9a9fadf9d1b694d702c8), [`bcbe5f6382ebcc70ef4fddb950d052bf6b7d693a`](https://github.com/clerk/javascript/commit/bcbe5f6382ebcc70ef4fddb950d052bf6b7d693a), [`382c30240f563e58bc4d4832557c6825da40ce7f`](https://github.com/clerk/javascript/commit/382c30240f563e58bc4d4832557c6825da40ce7f)]: - - @clerk/types@4.47.0 - - @clerk/shared@3.0.0 - - @clerk/clerk-react@5.24.0 - -## 0.22.23 - -### Patch Changes - -- Updated dependencies [[`d76c4699990b8477745c2584b1b98d5c92f9ace6`](https://github.com/clerk/javascript/commit/d76c4699990b8477745c2584b1b98d5c92f9ace6), [`a9b0087fca3f427f65907b358d9b5bc0c95921d8`](https://github.com/clerk/javascript/commit/a9b0087fca3f427f65907b358d9b5bc0c95921d8), [`92d17d7c087470b262fa5407cb6720fe6b17d333`](https://github.com/clerk/javascript/commit/92d17d7c087470b262fa5407cb6720fe6b17d333), [`30f6f3808e9b3778d5a9eb275780f94f9e9c7651`](https://github.com/clerk/javascript/commit/30f6f3808e9b3778d5a9eb275780f94f9e9c7651)]: - - @clerk/shared@2.22.0 - - @clerk/clerk-react@5.23.0 - - @clerk/types@4.46.1 - -## 0.22.22 - -### Patch Changes - -- Updated dependencies [[`dd2cbfe9f30358b6b298901bb52fa378b0acdca3`](https://github.com/clerk/javascript/commit/dd2cbfe9f30358b6b298901bb52fa378b0acdca3), [`dd2cbfe9f30358b6b298901bb52fa378b0acdca3`](https://github.com/clerk/javascript/commit/dd2cbfe9f30358b6b298901bb52fa378b0acdca3), [`570d8386f6aa596bf7bb1659bdddb8dd4d992b1d`](https://github.com/clerk/javascript/commit/570d8386f6aa596bf7bb1659bdddb8dd4d992b1d), [`570d8386f6aa596bf7bb1659bdddb8dd4d992b1d`](https://github.com/clerk/javascript/commit/570d8386f6aa596bf7bb1659bdddb8dd4d992b1d)]: - - @clerk/clerk-react@5.22.13 - - @clerk/types@4.46.0 - - @clerk/shared@2.21.1 - -## 0.22.21 - -### Patch Changes - -- Updated dependencies [[`f41081c563ddd2afc05b837358e0de087ae0c895`](https://github.com/clerk/javascript/commit/f41081c563ddd2afc05b837358e0de087ae0c895), [`767ac85fe6ce0ee0594c923e9af701bb05f40a0b`](https://github.com/clerk/javascript/commit/767ac85fe6ce0ee0594c923e9af701bb05f40a0b), [`225b38c7187d31fc755155ea99834ca03894d36b`](https://github.com/clerk/javascript/commit/225b38c7187d31fc755155ea99834ca03894d36b), [`429f1bfe5f7a554ab1fdf265475ba6c8b3f78472`](https://github.com/clerk/javascript/commit/429f1bfe5f7a554ab1fdf265475ba6c8b3f78472)]: - - @clerk/shared@2.21.0 - - @clerk/types@4.45.1 - - @clerk/clerk-react@5.22.12 - -## 0.22.20 - -### Patch Changes - -- Updated dependencies [[`0fa449cd09c9973297464a14f785895e3ddcab4d`](https://github.com/clerk/javascript/commit/0fa449cd09c9973297464a14f785895e3ddcab4d)]: - - @clerk/clerk-react@5.22.11 - -## 0.22.19 - -### Patch Changes - -- - `@clerk/clerk-js`, `@clerk/types`: Add `redirectUrl` option to `buildAfterSignInUrl()` and `buildAfterSignUpUrl()` methods. ([#5052](https://github.com/clerk/javascript/pull/5052)) by [@brkalow](https://github.com/brkalow) - - - `@clerk/elements`: Ensure redirect_url params passed to Elements components are always passed to Clerk's underlying `build*Url()` methods. - -- Updated dependencies [[`d3152be7f01fbb5ca26aeddc2437021f4b7ecc83`](https://github.com/clerk/javascript/commit/d3152be7f01fbb5ca26aeddc2437021f4b7ecc83), [`f976349243da2b75023e59e802460e6f3592ebbd`](https://github.com/clerk/javascript/commit/f976349243da2b75023e59e802460e6f3592ebbd)]: - - @clerk/types@4.45.0 - - @clerk/clerk-react@5.22.10 - - @clerk/shared@2.20.18 - -## 0.22.18 - -### Patch Changes - -- Updated dependencies [[`26225f2c31a22560f7ece2e02f1d0080b5b89520`](https://github.com/clerk/javascript/commit/26225f2c31a22560f7ece2e02f1d0080b5b89520), [`833693a6792b621e72162d70673e7bdfa84a69b6`](https://github.com/clerk/javascript/commit/833693a6792b621e72162d70673e7bdfa84a69b6)]: - - @clerk/shared@2.20.17 - - @clerk/clerk-react@5.22.9 - - @clerk/types@4.44.3 - -## 0.22.17 - -### Patch Changes - -- Updated dependencies [[`a309be354275b91a7b17d5a67e8ef6aa230a9935`](https://github.com/clerk/javascript/commit/a309be354275b91a7b17d5a67e8ef6aa230a9935), [`1345cb487970a7347351897e80dfb829d85c41ea`](https://github.com/clerk/javascript/commit/1345cb487970a7347351897e80dfb829d85c41ea)]: - - @clerk/shared@2.20.16 - - @clerk/types@4.44.2 - - @clerk/clerk-react@5.22.8 - -## 0.22.16 - -### Patch Changes - -- Updated dependencies [[`57c983fdc2b8d883623a2294daae0ac6c02c48f6`](https://github.com/clerk/javascript/commit/57c983fdc2b8d883623a2294daae0ac6c02c48f6), [`a26cf0ff10c76244975c454fdf6c615475d4bcd5`](https://github.com/clerk/javascript/commit/a26cf0ff10c76244975c454fdf6c615475d4bcd5)]: - - @clerk/types@4.44.1 - - @clerk/shared@2.20.15 - - @clerk/clerk-react@5.22.7 - -## 0.22.15 - -### Patch Changes - -- Updated dependencies [[`2179690c10a61b117e82fdd566b34939f4d28bc1`](https://github.com/clerk/javascript/commit/2179690c10a61b117e82fdd566b34939f4d28bc1), [`bdb537a9902c0f0ae58ca1d4b7590d929f28fedb`](https://github.com/clerk/javascript/commit/bdb537a9902c0f0ae58ca1d4b7590d929f28fedb)]: - - @clerk/types@4.44.0 - - @clerk/clerk-react@5.22.6 - - @clerk/shared@2.20.14 - -## 0.22.14 - -### Patch Changes - -- Updated dependencies [[`f87ede848265d75ea1e880a3ab80c53a250f42cf`](https://github.com/clerk/javascript/commit/f87ede848265d75ea1e880a3ab80c53a250f42cf), [`6126cc98281bca96797fd8a55b6ec6aeda397e46`](https://github.com/clerk/javascript/commit/6126cc98281bca96797fd8a55b6ec6aeda397e46), [`6e096564a459db4eaf953e99e570905b10be6c84`](https://github.com/clerk/javascript/commit/6e096564a459db4eaf953e99e570905b10be6c84)]: - - @clerk/shared@2.20.13 - - @clerk/types@4.43.0 - - @clerk/clerk-react@5.22.5 - -## 0.22.13 - -### Patch Changes - -- Updated dependencies [[`fe3e49f61acefe8d7f1992405f7cb415fea2e5c8`](https://github.com/clerk/javascript/commit/fe3e49f61acefe8d7f1992405f7cb415fea2e5c8), [`4427c4702f64d4f28f7564ce5889d41e260aa519`](https://github.com/clerk/javascript/commit/4427c4702f64d4f28f7564ce5889d41e260aa519)]: - - @clerk/types@4.42.0 - - @clerk/clerk-react@5.22.4 - - @clerk/shared@2.20.12 - -## 0.22.12 - -### Patch Changes - -- Updated dependencies [[`418ec5c62c4eb600566faab07684c068a29007e3`](https://github.com/clerk/javascript/commit/418ec5c62c4eb600566faab07684c068a29007e3)]: - - @clerk/types@4.41.2 - - @clerk/clerk-react@5.22.3 - - @clerk/shared@2.20.11 - -## 0.22.11 - -### Patch Changes - -- Standardizing ambient declaration files for all SDKs ([#4919](https://github.com/clerk/javascript/pull/4919)) by [@jacekradko](https://github.com/jacekradko) - -- Updated dependencies [[`9eef7713212378351e8e01628611eaa18de250e8`](https://github.com/clerk/javascript/commit/9eef7713212378351e8e01628611eaa18de250e8)]: - - @clerk/shared@2.20.10 - - @clerk/clerk-react@5.22.2 - -## 0.22.10 - -### Patch Changes - -- Support `enterprise_sso` strategy (SAML, OIDC, EASIE) on custom flows with `@clerk/elements` ([#4916](https://github.com/clerk/javascript/pull/4916)) by [@LauraBeatris](https://github.com/LauraBeatris) - -- Updated dependencies [[`7ffc99b48977b9f6c74c0c71c500b60cb8aba65e`](https://github.com/clerk/javascript/commit/7ffc99b48977b9f6c74c0c71c500b60cb8aba65e)]: - - @clerk/types@4.41.1 - - @clerk/clerk-react@5.22.1 - - @clerk/shared@2.20.9 - -## 0.22.9 - -### Patch Changes - -- Updated dependencies [[`4af35380f18d1d06c15ad1f5745c2d5a1ab1c37d`](https://github.com/clerk/javascript/commit/4af35380f18d1d06c15ad1f5745c2d5a1ab1c37d), [`aa48b1f9e890b2402e9d05989a4820141076f7bf`](https://github.com/clerk/javascript/commit/aa48b1f9e890b2402e9d05989a4820141076f7bf), [`53bd34fff38b17498edf66cc4bc2d42d707f28dc`](https://github.com/clerk/javascript/commit/53bd34fff38b17498edf66cc4bc2d42d707f28dc)]: - - @clerk/types@4.41.0 - - @clerk/clerk-react@5.22.0 - - @clerk/shared@2.20.8 - -## 0.22.8 - -### Patch Changes - -- Updated dependencies [[`fd7a5be73db3acaa7daeb9b15af73c2ce99d03a6`](https://github.com/clerk/javascript/commit/fd7a5be73db3acaa7daeb9b15af73c2ce99d03a6)]: - - @clerk/types@4.40.3 - - @clerk/clerk-react@5.21.3 - - @clerk/shared@2.20.7 - -## 0.22.7 - -### Patch Changes - -- Updated dependencies [[`44cab6038af0a4d23869b3b292ece742fbbc4d85`](https://github.com/clerk/javascript/commit/44cab6038af0a4d23869b3b292ece742fbbc4d85)]: - - @clerk/types@4.40.2 - - @clerk/clerk-react@5.21.2 - - @clerk/shared@2.20.6 - -## 0.22.6 - -### Patch Changes - -- Updated dependencies [[`80e1117631d35834705119a79cdcf9e0ed423fdd`](https://github.com/clerk/javascript/commit/80e1117631d35834705119a79cdcf9e0ed423fdd)]: - - @clerk/types@4.40.1 - - @clerk/clerk-react@5.21.1 - - @clerk/shared@2.20.5 - -## 0.22.5 - -### Patch Changes - -- Updated dependencies [[`b5eb15bf81d94456309d6ca44ad423a4175d50b6`](https://github.com/clerk/javascript/commit/b5eb15bf81d94456309d6ca44ad423a4175d50b6), [`b933a2ba8112aefbabd7fe3313b89e083452d2dd`](https://github.com/clerk/javascript/commit/b933a2ba8112aefbabd7fe3313b89e083452d2dd)]: - - @clerk/clerk-react@5.21.0 - -## 0.22.4 - -### Patch Changes - -- Updated dependencies [[`c9da04636ffe1ba804a1ce5e5b79027d3a2344d2`](https://github.com/clerk/javascript/commit/c9da04636ffe1ba804a1ce5e5b79027d3a2344d2)]: - - @clerk/types@4.40.0 - - @clerk/clerk-react@5.20.4 - - @clerk/shared@2.20.4 - -## 0.22.3 - -### Patch Changes - -- Updated dependencies [[`84ccb0049041534f111be65f7c7d4d6120069446`](https://github.com/clerk/javascript/commit/84ccb0049041534f111be65f7c7d4d6120069446)]: - - @clerk/shared@2.20.3 - - @clerk/clerk-react@5.20.3 - -## 0.22.2 - -### Patch Changes - -- Updated dependencies [[`aeafa7c5efd50c893d088ac99199d7eaecc04025`](https://github.com/clerk/javascript/commit/aeafa7c5efd50c893d088ac99199d7eaecc04025), [`acd9326ef2d6942b981b3ee59c4b20ddd303323d`](https://github.com/clerk/javascript/commit/acd9326ef2d6942b981b3ee59c4b20ddd303323d)]: - - @clerk/types@4.39.4 - - @clerk/clerk-react@5.20.2 - - @clerk/shared@2.20.2 - -## 0.22.1 - -### Patch Changes - -- Using the same peerDependencies semver for react and react-dom ([#4758](https://github.com/clerk/javascript/pull/4758)) by [@jacekradko](https://github.com/jacekradko) - -- Handle organization membership quote exceeded error during enterprise SSO ([#4763](https://github.com/clerk/javascript/pull/4763)) by [@mzhong9723](https://github.com/mzhong9723) - -- Updated dependencies [[`66ad299e4b6496ea4a93799de0f1ecfad920ddad`](https://github.com/clerk/javascript/commit/66ad299e4b6496ea4a93799de0f1ecfad920ddad), [`dd3fdc7b2a96ddb90b33c6f1cefb055a60f99a9d`](https://github.com/clerk/javascript/commit/dd3fdc7b2a96ddb90b33c6f1cefb055a60f99a9d), [`e1748582d0c89462f48a482a7805871b7065fa19`](https://github.com/clerk/javascript/commit/e1748582d0c89462f48a482a7805871b7065fa19), [`7f7edcaa8228c26d19e9081979100ada7e982095`](https://github.com/clerk/javascript/commit/7f7edcaa8228c26d19e9081979100ada7e982095), [`dd3fdc7b2a96ddb90b33c6f1cefb055a60f99a9d`](https://github.com/clerk/javascript/commit/dd3fdc7b2a96ddb90b33c6f1cefb055a60f99a9d)]: - - @clerk/shared@2.20.1 - - @clerk/clerk-react@5.20.1 - - @clerk/types@4.39.3 - -## 0.22.0 - -### Minor Changes - -- Handle ticket-based sign in flows such as impersonation ([#4746](https://github.com/clerk/javascript/pull/4746)) by [@LauraBeatris](https://github.com/LauraBeatris) - -- Switching to use ^ for semver ranges of internal @clerk/ production dependencies. ([#4664](https://github.com/clerk/javascript/pull/4664)) by [@jacekradko](https://github.com/jacekradko) - -### Patch Changes - -- Updated dependencies [[`9d656c16bc78ac31b59b5edbd25118dfc33c4469`](https://github.com/clerk/javascript/commit/9d656c16bc78ac31b59b5edbd25118dfc33c4469), [`ffa631d2480cfe77bf08c61b1302ace308e5b630`](https://github.com/clerk/javascript/commit/ffa631d2480cfe77bf08c61b1302ace308e5b630), [`0266f6a73fc34748a86603bc89b6125d6bbb679b`](https://github.com/clerk/javascript/commit/0266f6a73fc34748a86603bc89b6125d6bbb679b)]: - - @clerk/clerk-react@5.20.0 - - @clerk/shared@2.20.0 - -## 0.21.4 - -### Patch Changes - -- Updated dependencies [[`cd72a27a75863dfd94b0a00ed5b2d03231556bc0`](https://github.com/clerk/javascript/commit/cd72a27a75863dfd94b0a00ed5b2d03231556bc0)]: - - @clerk/types@4.39.2 - - @clerk/clerk-react@5.19.3 - - @clerk/shared@2.19.4 - -## 0.21.3 - -### Patch Changes - -- Updated dependencies [[`1b86a1da34ce4bc309f69980ac13a691a0a633c2`](https://github.com/clerk/javascript/commit/1b86a1da34ce4bc309f69980ac13a691a0a633c2)]: - - @clerk/types@4.39.1 - - @clerk/clerk-react@5.19.2 - - @clerk/shared@2.19.3 - -## 0.21.2 - -### Patch Changes - -- Updated dependencies [[`4cb22548da81dd8b186a6ef1cf120aea99c85c62`](https://github.com/clerk/javascript/commit/4cb22548da81dd8b186a6ef1cf120aea99c85c62)]: - - @clerk/shared@2.19.2 - - @clerk/clerk-react@5.19.1 - -## 0.21.1 - -### Patch Changes - -- Updated dependencies [[`3f640805d2a4e1616aafa56f6848d6657911bb99`](https://github.com/clerk/javascript/commit/3f640805d2a4e1616aafa56f6848d6657911bb99), [`550c7e9851329688e37be29b83ea0c3b12482af7`](https://github.com/clerk/javascript/commit/550c7e9851329688e37be29b83ea0c3b12482af7), [`3f640805d2a4e1616aafa56f6848d6657911bb99`](https://github.com/clerk/javascript/commit/3f640805d2a4e1616aafa56f6848d6657911bb99)]: - - @clerk/clerk-react@5.19.0 - - @clerk/types@4.39.0 - - @clerk/shared@2.19.1 - -## 0.21.0 - -### Minor Changes - -- Replace usage of `OAUTH_PROVIDERS` and `WEB3_PROVIDERS` from `@clerk/types` to `@clerk/shared`. ([#4716](https://github.com/clerk/javascript/pull/4716)) by [@panteliselef](https://github.com/panteliselef) - -### Patch Changes - -- Updated dependencies [[`0bc3ccc5bd4a93121bb7e7d6a32271af9c31f8c3`](https://github.com/clerk/javascript/commit/0bc3ccc5bd4a93121bb7e7d6a32271af9c31f8c3), [`0bc3ccc5bd4a93121bb7e7d6a32271af9c31f8c3`](https://github.com/clerk/javascript/commit/0bc3ccc5bd4a93121bb7e7d6a32271af9c31f8c3)]: - - @clerk/shared@2.19.0 - - @clerk/types@4.38.0 - - @clerk/clerk-react@5.18.2 - -## 0.20.1 - -### Patch Changes - -- Updated dependencies [[`4e5e7f463c12893a21cb3b5f9317fc3f2945879b`](https://github.com/clerk/javascript/commit/4e5e7f463c12893a21cb3b5f9317fc3f2945879b)]: - - @clerk/types@4.37.0 - - @clerk/clerk-react@5.18.1 - - @clerk/shared@2.18.1 - -## 0.20.0 - -### Minor Changes - -- Support OKW Wallet Web3 provider and authentication strategy ([#4696](https://github.com/clerk/javascript/pull/4696)) by [@chanioxaris](https://github.com/chanioxaris) - -### Patch Changes - -- Updated dependencies [[`8ee5d84995fa17532491ff96efac5738c9bcd9ef`](https://github.com/clerk/javascript/commit/8ee5d84995fa17532491ff96efac5738c9bcd9ef), [`09fedd1df155d30cc055ce701b133aa6470e9b47`](https://github.com/clerk/javascript/commit/09fedd1df155d30cc055ce701b133aa6470e9b47), [`235eaae4c3c9400492fca47d20a47c7081041565`](https://github.com/clerk/javascript/commit/235eaae4c3c9400492fca47d20a47c7081041565)]: - - @clerk/types@4.36.0 - - @clerk/clerk-react@5.18.0 - - @clerk/shared@2.18.0 - -## 0.19.9 - -### Patch Changes - -- Updated dependencies [[`8a28d1f403309f692d9332704f07effbf39d056d`](https://github.com/clerk/javascript/commit/8a28d1f403309f692d9332704f07effbf39d056d)]: - - @clerk/types@4.35.1 - - @clerk/clerk-react@5.17.2 - - @clerk/shared@2.17.1 - -## 0.19.8 - -### Patch Changes - -- Updated dependencies [[`115fd0c32443c6fc4e692c0ebdd60c092e57057e`](https://github.com/clerk/javascript/commit/115fd0c32443c6fc4e692c0ebdd60c092e57057e), [`0a1807552dcf0501a97f60b4df0280525bca9743`](https://github.com/clerk/javascript/commit/0a1807552dcf0501a97f60b4df0280525bca9743)]: - - @clerk/clerk-react@5.17.1 - -## 0.19.7 - -### Patch Changes - -- Updated dependencies [[`4da28fa857d1e5538eb5bbe28ecc4bf9dba1ce7d`](https://github.com/clerk/javascript/commit/4da28fa857d1e5538eb5bbe28ecc4bf9dba1ce7d), [`fe9e147e366153d664af7fc325655ecb299a1f9d`](https://github.com/clerk/javascript/commit/fe9e147e366153d664af7fc325655ecb299a1f9d), [`fe9e147e366153d664af7fc325655ecb299a1f9d`](https://github.com/clerk/javascript/commit/fe9e147e366153d664af7fc325655ecb299a1f9d), [`fe9e147e366153d664af7fc325655ecb299a1f9d`](https://github.com/clerk/javascript/commit/fe9e147e366153d664af7fc325655ecb299a1f9d), [`d84d7e31235c5c7da3415981dc76db4473a71a39`](https://github.com/clerk/javascript/commit/d84d7e31235c5c7da3415981dc76db4473a71a39), [`dce4f7ffca7248c0500f0ec9a978672b1f2fad69`](https://github.com/clerk/javascript/commit/dce4f7ffca7248c0500f0ec9a978672b1f2fad69)]: - - @clerk/clerk-react@5.17.0 - - @clerk/shared@2.17.0 - - @clerk/types@4.35.0 - -## 0.19.6 - -### Patch Changes - -- Updated dependencies [[`c70994b5b6f92a6550dfe37547f01bbfa810c223`](https://github.com/clerk/javascript/commit/c70994b5b6f92a6550dfe37547f01bbfa810c223), [`7623a99594e7329200b6b374e483152d7679ce66`](https://github.com/clerk/javascript/commit/7623a99594e7329200b6b374e483152d7679ce66)]: - - @clerk/types@4.34.2 - - @clerk/clerk-react@5.16.2 - - @clerk/shared@2.16.1 - -## 0.19.5 - -### Patch Changes - -- Updated dependencies [[`e47eb5882a7fd4a8dee25933c6644790d6ea3407`](https://github.com/clerk/javascript/commit/e47eb5882a7fd4a8dee25933c6644790d6ea3407), [`273d16cb0665d4d960838cb294dc356f41814745`](https://github.com/clerk/javascript/commit/273d16cb0665d4d960838cb294dc356f41814745), [`6b0961765e1f3d09679be4b163fa13ac7dd97191`](https://github.com/clerk/javascript/commit/6b0961765e1f3d09679be4b163fa13ac7dd97191)]: - - @clerk/clerk-react@5.16.1 - - @clerk/shared@2.16.0 - - @clerk/types@4.34.1 - -## 0.19.4 - -### Patch Changes - -- Updated dependencies [[`536fa996ff84a545678a3036b28409824d1c00dd`](https://github.com/clerk/javascript/commit/536fa996ff84a545678a3036b28409824d1c00dd), [`b2671affd230eed176ac03af516307898d371757`](https://github.com/clerk/javascript/commit/b2671affd230eed176ac03af516307898d371757), [`b28c5e8bc44885bf6b1533df48e872ba90c387da`](https://github.com/clerk/javascript/commit/b28c5e8bc44885bf6b1533df48e872ba90c387da), [`6c424e179850f520ae738e816bf0423a55ddf3ef`](https://github.com/clerk/javascript/commit/6c424e179850f520ae738e816bf0423a55ddf3ef)]: - - @clerk/shared@2.15.0 - - @clerk/clerk-react@5.16.0 - -## 0.19.3 - -### Patch Changes - -- Updated dependencies [[`46faeb6f59b19c963fb137c858347525b1cd9e19`](https://github.com/clerk/javascript/commit/46faeb6f59b19c963fb137c858347525b1cd9e19)]: - - @clerk/types@4.34.0 - -## 0.19.2 - -### Patch Changes - -- Display additional errors from enterprise SSO ([#4553](https://github.com/clerk/javascript/pull/4553)) by [@LauraBeatris](https://github.com/LauraBeatris) - -- Export `` from root sign-in exports. ([#4548](https://github.com/clerk/javascript/pull/4548)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Updated dependencies [[`1c0b5001f7f975a2f3f54ad692526ecf7257847e`](https://github.com/clerk/javascript/commit/1c0b5001f7f975a2f3f54ad692526ecf7257847e), [`6217a3f7c94311d49f873214bd406961e0b8d6f7`](https://github.com/clerk/javascript/commit/6217a3f7c94311d49f873214bd406961e0b8d6f7), [`1783025cdb94c447028704c2121fa0b8af785904`](https://github.com/clerk/javascript/commit/1783025cdb94c447028704c2121fa0b8af785904)]: - - @clerk/types@4.33.0 - -## 0.19.1 - -### Patch Changes - -- Updated dependencies [[`7dbad4c5abd226d7b10941a626ead5d85b1a3f24`](https://github.com/clerk/javascript/commit/7dbad4c5abd226d7b10941a626ead5d85b1a3f24)]: - - @clerk/types@4.32.0 - -## 0.19.0 - -### Minor Changes - -- Introduce support for `` and ``. This allows rendering of a CAPTCHA widget when a sign in attempt is transferred to a sign up attempt. ([#4523](https://github.com/clerk/javascript/pull/4523)) by [@BRKalow](https://github.com/BRKalow) - -- The Legal consent feature is now stable. ([#4487](https://github.com/clerk/javascript/pull/4487)) by [@octoper](https://github.com/octoper) - - Removed the `__experimental_` prefix. - -### Patch Changes - -- Fixes issues in `ClerkRouter` that were causing inaccurate pathnames within Elements flows. Also fixes a dependency issue where `@clerk/elements` was pulling in the wrong version of `@clerk/shared`. ([#4513](https://github.com/clerk/javascript/pull/4513)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Updated dependencies [[`f7472e22877f62fc7f3c8d3efe409ff2276fb4a3`](https://github.com/clerk/javascript/commit/f7472e22877f62fc7f3c8d3efe409ff2276fb4a3), [`e199037b8f484abdeeb9fc24455a1b4b8c31c8dd`](https://github.com/clerk/javascript/commit/e199037b8f484abdeeb9fc24455a1b4b8c31c8dd), [`0e443ad7c76643420b50e5b169193e03f6ef79f9`](https://github.com/clerk/javascript/commit/0e443ad7c76643420b50e5b169193e03f6ef79f9), [`cc24c8145f1eea7fb91550f2c3e0bac3993e4320`](https://github.com/clerk/javascript/commit/cc24c8145f1eea7fb91550f2c3e0bac3993e4320)]: - - @clerk/types@4.31.0 - -## 0.18.5 - -### Patch Changes - -- Updated dependencies [[`8a04ae47b8305f994b348301fd8134d5baf02943`](https://github.com/clerk/javascript/commit/8a04ae47b8305f994b348301fd8134d5baf02943)]: - - @clerk/shared@2.11.5 - -## 0.18.4 - -### Patch Changes - -- Add Elements `` component. ([#4456](https://github.com/clerk/javascript/pull/4456)) by [@alexcarpenter](https://github.com/alexcarpenter) - - ```tsx - import * as Clerk from '@clerk/elements/common'; - import NextLink from 'next/link'; - - function SignInPage() { - return ( - <> - Sign up - - {url => Sign up} - - ); - } - ``` - -- Updated dependencies [[`d74a6a7c0f291104c6bba722a8c432814d7b336e`](https://github.com/clerk/javascript/commit/d74a6a7c0f291104c6bba722a8c432814d7b336e), [`1a0c8fe665869e732d3c800bde0f5219fce54301`](https://github.com/clerk/javascript/commit/1a0c8fe665869e732d3c800bde0f5219fce54301), [`0800fc3f1f4e1b6a1d13f5c02557001a283af6e8`](https://github.com/clerk/javascript/commit/0800fc3f1f4e1b6a1d13f5c02557001a283af6e8)]: - - @clerk/types@4.30.0 - - @clerk/shared@2.11.4 - -## 0.18.3 - -### Patch Changes - -- Use host router instead of directly calling Next's `useRouter` hook by [@nikosdouvlis](https://github.com/nikosdouvlis) - -- Updated dependencies [[`a7726cc12a824b278f6d2a37cb1901c38c5f70dc`](https://github.com/clerk/javascript/commit/a7726cc12a824b278f6d2a37cb1901c38c5f70dc)]: - - @clerk/shared@2.11.3 - -## 0.18.0 - -### Minor Changes - -- Added support for `__experimental_legalAccepted` field ([#4427](https://github.com/clerk/javascript/pull/4427)) by [@octoper](https://github.com/octoper) - -### Patch Changes - -- - Introduce `redirectUrl` property on `setActive` as a replacement for `beforeEmit`. ([#4312](https://github.com/clerk/javascript/pull/4312)) by [@issuedat](https://github.com/issuedat) - - - Deprecates `beforeEmit` property on `setActive`. - -- Updated dependencies [[`f875463da`](https://github.com/clerk/javascript/commit/f875463da9692f2d173b6d5388743cf720750ae3), [`5be7ca9fd`](https://github.com/clerk/javascript/commit/5be7ca9fd239c937cc88e20ce8f5bfc9f3b84f22), [`08c5a2add`](https://github.com/clerk/javascript/commit/08c5a2add6872c76e62fc0df06db723e3728452e), [`434b432f8`](https://github.com/clerk/javascript/commit/434b432f8c114825120eef0f2c278b8142ed1563)]: - - @clerk/types@4.29.0 - - @clerk/shared@2.11.0 - -## 0.17.1 - -### Patch Changes - -- Updated dependencies [[`3fdcdbf88`](https://github.com/clerk/javascript/commit/3fdcdbf88c38facf8b82563f634ec1b6604fd8e5)]: - - @clerk/types@4.28.0 - - @clerk/shared@2.10.1 - -## 0.17.0 - -### Minor Changes - -- Add experimental support for new UI components ([#4114](https://github.com/clerk/javascript/pull/4114)) by [@BRKalow](https://github.com/BRKalow) - -### Patch Changes - -- Remove @clerk/clerk-react as a dev depedency. Move @clerk/shared to dependencies (previously devDepedencies). ([#4114](https://github.com/clerk/javascript/pull/4114)) by [@BRKalow](https://github.com/BRKalow) - -- Updated dependencies [[`3b50b67bd`](https://github.com/clerk/javascript/commit/3b50b67bd40da33c9e36773aa05462717e9f44cc), [`3b50b67bd`](https://github.com/clerk/javascript/commit/3b50b67bd40da33c9e36773aa05462717e9f44cc)]: - - @clerk/shared@2.10.0 - - @clerk/types@4.27.0 - -## 0.16.2 - -### Patch Changes - -- Updated dependencies [[`e81d45b72`](https://github.com/clerk/javascript/commit/e81d45b72c81403c7c206dac5454de1fef6bec57), [`99cdf9b67`](https://github.com/clerk/javascript/commit/99cdf9b67d1e99e66cc73d8a5bfce1f1f8df1b83), [`ce40ff6f0`](https://github.com/clerk/javascript/commit/ce40ff6f0d3bc79e33375be6dd5e03f140a07000), [`2102052c0`](https://github.com/clerk/javascript/commit/2102052c017065ab511339870fcebaa6719f2702)]: - - @clerk/types@4.26.0 - -## 0.16.1 - -### Patch Changes - -- Fixes a bug during a ticket-based sign-up where the form could not be submitted if additional fields were needed. ([#4318](https://github.com/clerk/javascript/pull/4318)) by [@BRKalow](https://github.com/BRKalow) - -- Updated dependencies [[`2ba2fd148`](https://github.com/clerk/javascript/commit/2ba2fd1483b7561d7df9a1952ead0ee15e422131)]: - - @clerk/types@4.25.1 - -## 0.16.0 - -### Minor Changes - -- Adds `restricted` Step for restricted sign-up mode ([#4221](https://github.com/clerk/javascript/pull/4221)) by [@tmilewski](https://github.com/tmilewski) - -## 0.15.10 - -### Patch Changes - -- Remove "example mode" guard form "AUTHENICTATE.PASSKEY" event in verification flow ([#4295](https://github.com/clerk/javascript/pull/4295)) by [@tmilewski](https://github.com/tmilewski) - -## 0.15.9 - -### Patch Changes - -- Updated dependencies [[`fb932e5cf`](https://github.com/clerk/javascript/commit/fb932e5cf21315adf60bee0855b6bd5ee2ff9867)]: - - @clerk/types@4.25.0 - -## 0.15.8 - -### Patch Changes - -- Updated dependencies [[`f6fb8b53d`](https://github.com/clerk/javascript/commit/f6fb8b53d236863ad7eca576ee7a16cd33f3506b), [`4a8570590`](https://github.com/clerk/javascript/commit/4a857059059a02bb4f20893e08601e1e67babbed)]: - - @clerk/types@4.24.0 - -## 0.15.7 - -### Patch Changes - -- Updated dependencies [[`4749ed4c5`](https://github.com/clerk/javascript/commit/4749ed4c55a5ba5810451b8d436aad0d49829050), [`f1f17eaab`](https://github.com/clerk/javascript/commit/f1f17eaabed0dc4b7de405fb77d85503cf75ad33), [`2e35ac538`](https://github.com/clerk/javascript/commit/2e35ac53885f8008779940d41d1e804fa77ebfa9)]: - - @clerk/types@4.23.0 - -## 0.15.6 - -### Patch Changes - -- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: - - @clerk/types@4.22.0 - -## 0.15.5 - -### Patch Changes - -- Internal change to move `iconImageUrl` util to `shared` package. ([#4188](https://github.com/clerk/javascript/pull/4188)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Updated dependencies [[`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7)]: - - @clerk/types@4.21.1 - -## 0.15.4 - -## 0.15.3 - -### Patch Changes - -- Updated dependencies [[`248142a6d`](https://github.com/clerk/javascript/commit/248142a6ded6ca937d0df7d628197f25228aadec), [`1189f71f8`](https://github.com/clerk/javascript/commit/1189f71f872f2683c12de5add5f154aeb953ca8d)]: - - @clerk/types@4.21.0 - -## 0.15.2 - -## 0.15.1 - -### Patch Changes - -- Updated dependencies [[`8c6909d46`](https://github.com/clerk/javascript/commit/8c6909d46328c943f1d464a28f1a324a27d0f3f1)]: - - @clerk/types@4.20.1 - -## 0.15.0 - -### Minor Changes - -- Remove `@clerk/elements` reliance on `next` and `@clerk/clerk-react` directly. The host router is now provided by `@clerk/nextjs`. ([#4064](https://github.com/clerk/javascript/pull/4064)) by [@BRKalow](https://github.com/BRKalow) - -### Patch Changes - -- Add support for Coinbase Wallet Web3 provider ([#4103](https://github.com/clerk/javascript/pull/4103)) by [@chanioxaris](https://github.com/chanioxaris) - -- Adds support for `asChild` prop within `choose-strategy` and `choose-session` sign-in steps. ([#4094](https://github.com/clerk/javascript/pull/4094)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Drop support for deprecated Coinbase Web3 provider ([#4092](https://github.com/clerk/javascript/pull/4092)) by [@chanioxaris](https://github.com/chanioxaris) - -- Fixes issue where errors were incorrectly being returned as an `any` type. ([#4119](https://github.com/clerk/javascript/pull/4119)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Allow for passkey triggers in the verification steps ([#4093](https://github.com/clerk/javascript/pull/4093)) by [@tmilewski](https://github.com/tmilewski) - -- Updated dependencies [[`c63a5adf0`](https://github.com/clerk/javascript/commit/c63a5adf0ba4b99252146f168318f51b709bb5dd), [`8823c21a2`](https://github.com/clerk/javascript/commit/8823c21a26bc81cbc3ed007908b1a9ea474bd343), [`a0cb062fa`](https://github.com/clerk/javascript/commit/a0cb062faa4d23bef7a577e5cc486f4c5efe6bfa)]: - - @clerk/types@4.20.0 - -## 0.14.6 - -### Patch Changes - -- Updated dependencies [[`8a3b9f079`](https://github.com/clerk/javascript/commit/8a3b9f0793484b32dd609a5c80a194e62151d6ea), [`e95c28196`](https://github.com/clerk/javascript/commit/e95c2819675cea7963f2404e5f71f37ebed8d5e0)]: - - @clerk/types@4.19.0 - -## 0.14.5 - -### Patch Changes - -- Add support for sign in and sign up with Coinbase ([#4051](https://github.com/clerk/javascript/pull/4051)) by [@EmmanouelaPothitou](https://github.com/EmmanouelaPothitou) - -- Updated dependencies [[`82593173a`](https://github.com/clerk/javascript/commit/82593173aafbf6646e12c5779627cdcb138a1f27), [`afad9af89`](https://github.com/clerk/javascript/commit/afad9af893984a19d7284f0ad3b36e7891d0d733)]: - - @clerk/types@4.18.0 - -## 0.14.4 - -### Patch Changes - -- Tidy up and improve README ([#4053](https://github.com/clerk/javascript/pull/4053)) by [@LekoArts](https://github.com/LekoArts) - -- Moves the common `ClerkRouter` interface into `@clerk/shared/router`. Elements has been refactored internally to import the router from the shared package. ([#4045](https://github.com/clerk/javascript/pull/4045)) by [@BRKalow](https://github.com/BRKalow) - -- Updated dependencies [[`58e6754ad`](https://github.com/clerk/javascript/commit/58e6754ad9f9a1244b023ce1f5e5f2c1c4eb20e7), [`13693018f`](https://github.com/clerk/javascript/commit/13693018f4f7ac5d224698aa730e20960896f68c), [`3304dcc0b`](https://github.com/clerk/javascript/commit/3304dcc0bc93a92a7f729f585c60ff91d2ae04f6)]: - - @clerk/types@4.17.0 - -## 0.14.3 - -### Patch Changes - -- Updated dependencies [[`c1389492d`](https://github.com/clerk/javascript/commit/c1389492d8b6a9292ab04889bf776c0f45e66845)]: - - @clerk/types@4.16.0 - -## 0.14.2 - -### Patch Changes - -- Updated dependencies [[`0158c774a`](https://github.com/clerk/javascript/commit/0158c774af2243a2cd13b55c4d6fae877178c961), [`8be1a7abc`](https://github.com/clerk/javascript/commit/8be1a7abc8849d7d59552011bd6b25bc917d51f5)]: - - @clerk/types@4.15.1 - -## 0.14.1 - -### Patch Changes - -- Fix `SignIn.SafeIdentifier` potentially outputting an incorrect identifier when using similar multi-session sign in strategies. ([#3974](https://github.com/clerk/javascript/pull/3974)) by [@tmilewski](https://github.com/tmilewski) - -- Updated dependencies [[`247b3fd75`](https://github.com/clerk/javascript/commit/247b3fd75042365dc9f950db056b76f9fadfdcf6)]: - - @clerk/types@4.15.0 - -## 0.14.0 - -### Minor Changes - -- Introduce multi-session choose account step and associated actions/components. ([#3957](https://github.com/clerk/javascript/pull/3957)) by [@tmilewski](https://github.com/tmilewski) - - Example: - - ```tsx - - - - {({ session }) => ( - <> - {session.identifier} | Switch... - - )} - - - - ``` - -### Patch Changes - -- Refactor form hooks and utils into separate files ([#3933](https://github.com/clerk/javascript/pull/3933)) by [@tmilewski](https://github.com/tmilewski) - -- feat(elements): Add support for sign in with email_link via redirect_url ([#3926](https://github.com/clerk/javascript/pull/3926)) by [@dstaley](https://github.com/dstaley) - -- Preserve absolute URLs passed via `redirect_url` ([#3947](https://github.com/clerk/javascript/pull/3947)) by [@dstaley](https://github.com/dstaley) - -- Extract common Form components from single file ([#3933](https://github.com/clerk/javascript/pull/3933)) by [@tmilewski](https://github.com/tmilewski) - -- Add support for submit and passkey loading scopes ([#3927](https://github.com/clerk/javascript/pull/3927)) by [@dstaley](https://github.com/dstaley) - -- Updated dependencies [[`dc0e1c33d`](https://github.com/clerk/javascript/commit/dc0e1c33d6844b028cb1ee11c3359b886d609f3c)]: - - @clerk/types@4.14.0 - -## 0.13.0 - -### Minor Changes - -- Handle ticket-based invitation sign-up workflows ([#3910](https://github.com/clerk/javascript/pull/3910)) by [@tmilewski](https://github.com/tmilewski) - -### Patch Changes - -- Refactor form hooks and utils into separate files ([#3931](https://github.com/clerk/javascript/pull/3931)) by [@tmilewski](https://github.com/tmilewski) - -- In certain situations the Frontend API response contains [`supported_first_factors`](https://clerk.com/docs/reference/frontend-api/tag/Sign-Ins#operation/createSignIn!c=200&path=response/supported_first_factors&t=response) with a `null` value while the current code always assumed to receive an array. `SignInResource['supportedFirstFactors']` has been updated to account for that and any code accessing this value has been made more resilient against `null` values. ([#3938](https://github.com/clerk/javascript/pull/3938)) by [@dstaley](https://github.com/dstaley) - -- Updated dependencies [[`b6f0613dc`](https://github.com/clerk/javascript/commit/b6f0613dc9d8b0bab41cfabbaa8621b126e3bdf5)]: - - @clerk/types@4.13.1 - -## 0.12.4 - -### Patch Changes - -- Add support for redirect_url URL parameter ([#3906](https://github.com/clerk/javascript/pull/3906)) by [@dstaley](https://github.com/dstaley) - -- Updated dependencies [[`4e6c94e3f`](https://github.com/clerk/javascript/commit/4e6c94e3f4cc92cbba8bddcd2b90fcc9cfb83763)]: - - @clerk/types@4.13.0 - -## 0.12.3 - -### Patch Changes - -- Updated dependencies [[`9b2aeacb3`](https://github.com/clerk/javascript/commit/9b2aeacb32fff7c300bda458636a1cc81a42ee7b)]: - - @clerk/types@4.12.1 - -## 0.12.2 - -### Patch Changes - -- Return password validation errors with additional supporting information from instance configuration ([#3812](https://github.com/clerk/javascript/pull/3812)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Fixes a bug that briefly showed the underlying primitive input for OTPs when auto-filled in MacOS ([#3899](https://github.com/clerk/javascript/pull/3899)) by [@joe-bell](https://github.com/joe-bell) - -- Updated dependencies [[`7e94fcf0f`](https://github.com/clerk/javascript/commit/7e94fcf0fcbee8842a54f7931c45190370aa870d)]: - - @clerk/types@4.12.0 - -## 0.12.1 - -### Patch Changes - -- Updated dependencies [[`568186cad`](https://github.com/clerk/javascript/commit/568186cad29acaf0b084a9f86ccb9d29bd23fcf4), [`407195270`](https://github.com/clerk/javascript/commit/407195270ed8aab6eef18c64a4918e3870fef471)]: - - @clerk/types@4.11.0 - -## 0.12.0 - -### Minor Changes - -- Add Metamask (Web3) support for sign in and sign up ([#3879](https://github.com/clerk/javascript/pull/3879)) by [@dstaley](https://github.com/dstaley) - -## 0.11.0 - -### Minor Changes - -- Add full SAML support ([#3842](https://github.com/clerk/javascript/pull/3842)) by [@tmilewski](https://github.com/tmilewski) - -- Update signin `isLoggedInAndSingleSession` guard to navigate using `buildAfterSignInUrl` when true. ([#3841](https://github.com/clerk/javascript/pull/3841)) by [@alexcarpenter](https://github.com/alexcarpenter) - -### Patch Changes - -- Fixes issue where the incorrect sign in first factor strategy was being returned during sign in. ([#3828](https://github.com/clerk/javascript/pull/3828)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Ensure correct supported strategies are rendered based on first or second factor needs. ([#3843](https://github.com/clerk/javascript/pull/3843)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Updated dependencies [[`aa06f3ba7`](https://github.com/clerk/javascript/commit/aa06f3ba7e725071c90d4a1d6840060236da3c23), [`80e647731`](https://github.com/clerk/javascript/commit/80e64773135865434cf0e6c220e287397aa07937)]: - - @clerk/types@4.10.0 - -## 0.10.7 - -### Patch Changes - -- Updated dependencies [[`b48689705`](https://github.com/clerk/javascript/commit/b48689705f9fc2251d2f24addec7a0d0b1da0fe1)]: - - @clerk/types@4.9.1 - -## 0.10.6 - -### Patch Changes - -- Add support for checkbox input usage and `signOutOfOtherSessions` functionality ([#3791](https://github.com/clerk/javascript/pull/3791)) by [@alexcarpenter](https://github.com/alexcarpenter) - -## 0.10.5 - -### Patch Changes - -- Reverts [addition of relatedTarget check](https://github.com/clerk/javascript/pull/3762) in onFocus event handler which prevented fieldstate info from render on focus. ([#3770](https://github.com/clerk/javascript/pull/3770)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Handle call to `hasTags` on undefined `state` ([#3738](https://github.com/clerk/javascript/pull/3738)) by [@tmilewski](https://github.com/tmilewski) - -- Fixes issue where an invalid password field was immediately being refocused after submission causing the validation to run and show the success state. ([#3762](https://github.com/clerk/javascript/pull/3762)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Pass resource directly to machine over getSnapshot to avoid empty context ([#3738](https://github.com/clerk/javascript/pull/3738)) by [@tmilewski](https://github.com/tmilewski) - -- Fix issue where password field validation was incorrectly showing successful field state due to input being refocused on invalid form submission ([#3778](https://github.com/clerk/javascript/pull/3778)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Update XState from 5.13.x to 5.15.x ([#3738](https://github.com/clerk/javascript/pull/3738)) by [@tmilewski](https://github.com/tmilewski) - -- Update types to account for null second factors ([#3780](https://github.com/clerk/javascript/pull/3780)) by [@dstaley](https://github.com/dstaley) - -- Add support for `transform` prop on `SignIn.SafeIdentifier` and determine identifier based on strategy ([#3749](https://github.com/clerk/javascript/pull/3749)) by [@dstaley](https://github.com/dstaley) - -- Updated dependencies [[`b2788f67b`](https://github.com/clerk/javascript/commit/b2788f67b75cce17af1a2f91a984bb826a5a42e1), [`86c75e50c`](https://github.com/clerk/javascript/commit/86c75e50cba9c4efb480672f1b8c6a6fff4ef477)]: - - @clerk/types@4.9.0 - -## 0.10.4 - -### Patch Changes - -- Fix issue where default field values were being set and clearing field errors. ([#3736](https://github.com/clerk/javascript/pull/3736)) by [@alexcarpenter](https://github.com/alexcarpenter) - - Fix issue where resendable UI in the email_link verification step was not updating on click. - -## 0.10.3 - -### Patch Changes - -- Ensure updated provided values to controlled inputs are sent to the machine by [@nikosdouvlis](https://github.com/nikosdouvlis) - -- Fix isWeb3Strategy check to account for prefix and suffix by [@nikosdouvlis](https://github.com/nikosdouvlis) - -- Updated dependencies [[`df7d856d5`](https://github.com/clerk/javascript/commit/df7d856d56bc3b1dcbdbf9155b4ef1b1ea5971f7)]: - - @clerk/types@4.8.0 - -## 0.10.2 - -### Patch Changes - -- Prefill populated fields when navigating back to the start step from the verify step ([#3685](https://github.com/clerk/javascript/pull/3685)) by [@alexcarpenter](https://github.com/alexcarpenter) - -## 0.10.1 - -## 0.10.0 - -### Minor Changes - -- Add `backup_code` verification strategy ([#3627](https://github.com/clerk/javascript/pull/3627)) by [@tmilewski](https://github.com/tmilewski) - - ```tsx - - Use a backup code - - ``` - - ```tsx - - - - Code: - - - - - Continue - - - ``` - -### Patch Changes - -- Addresses the issue where sign-in factors were not properly falling back to empty arrays. ([#3647](https://github.com/clerk/javascript/pull/3647)) by [@LauraBeatris](https://github.com/LauraBeatris) - -- Refactors sign-up loading logic to be in-line with sign-in ([#3648](https://github.com/clerk/javascript/pull/3648)) by [@tmilewski](https://github.com/tmilewski) - -- Ensure Sign Up resending resets upon being triggered ([#3652](https://github.com/clerk/javascript/pull/3652)) by [@tmilewski](https://github.com/tmilewski) - -- Fixes persistent loading states within the `forgot-password` step ([#3648](https://github.com/clerk/javascript/pull/3648)) by [@tmilewski](https://github.com/tmilewski) - -- Fix Sign In forgot-password step not rendering ([#3653](https://github.com/clerk/javascript/pull/3653)) by [@tmilewski](https://github.com/tmilewski) - -## 0.9.2 - -### Patch Changes - -- Updated dependencies [[`d6b5006c4`](https://github.com/clerk/javascript/commit/d6b5006c4cc1b6f07bb3a6832b4ec6e65ea15814)]: - - @clerk/types@4.7.0 - -## 0.9.1 - -### Patch Changes - -- Add a development-only warning for cases when a user renders a `` component that isn't activated for their Clerk instance. As this can be intended behavior (e.g. build out a full example and let user enable/disable stuff solely in the dashboard) the warning can safely be ignored if necessary. ([#3609](https://github.com/clerk/javascript/pull/3609)) by [@LekoArts](https://github.com/LekoArts) - -## 0.9.0 - -### Minor Changes - -- Improve `` and re-organize some data attributes related to validity states. These changes might be breaking changes for you. ([#3594](https://github.com/clerk/javascript/pull/3594)) by [@LekoArts](https://github.com/LekoArts) - - Overview of changes: - - `
` no longer has `data-valid` and `data-invalid` attributes. If there are global errors (same heuristics as ``) then a `data-global-error` attribute will be present. - - Fixed a bug where `` could contain `data-valid` and `data-invalid` at the same time. - - The field state (accessible through e.g. ``) now also incorporates the field's [ValidityState](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState) into its output. If the `ValidityState` is invalid, the field state will be an `error`. You can access this information in three places: - 1. `` - 2. `data-state` attribute on `` - 3. `{(state) =>

Field's state is {state}

}
` - -### Patch Changes - -- Fix Sign In & Sign Up root fallbacks not rendering as expected ([#3601](https://github.com/clerk/javascript/pull/3601)) by [@tmilewski](https://github.com/tmilewski) - -- Update all Radix dependencies to their June 19, 2024 release ([#3606](https://github.com/clerk/javascript/pull/3606)) by [@LekoArts](https://github.com/LekoArts) - -## 0.8.0 - -### Minor Changes - -- The `path` prop on the `` and `` component is now automatically inferred. Previously, the default values were `/sign-in` and `/sign-up`, on other routes you had to explicitly define your route. ([#3557](https://github.com/clerk/javascript/pull/3557)) by [@LekoArts](https://github.com/LekoArts) - - The new heuristic for determining the path where `` and `` are mounted is: - 1. `path` prop - 2. Automatically inferred - 3. If it can't be inferred, fallback to `CLERK_SIGN_IN_URL` and `CLERK_SIGN_UP_URL` env var - 4. Fallback to `/sign-in` and `/sign-up` - -### Patch Changes - -- Render the resendable button at the 0 tick ([#3575](https://github.com/clerk/javascript/pull/3575)) by [@alexcarpenter](https://github.com/alexcarpenter) - -- Updated dependencies [[`1273b04ec`](https://github.com/clerk/javascript/commit/1273b04ecf1866b59ef59a74abe31dbcc726da2c)]: - - @clerk/types@4.6.1 - -## 0.7.0 - -### Minor Changes - -- Support passkeys in `` flows. ([#3472](https://github.com/clerk/javascript/pull/3472)) by [@panteliselef](https://github.com/panteliselef) - - APIs introduced: - - `` - - `` - - `` - - Detects the usage of `webauthn` to trigger passkey autofill `` - - Usage examples: - - `` - - ```tsx - - - {isLoading => (isLoading ? : 'Use passkey instead')}. - - - ``` - - - `` - - ```tsx - - - - ``` - - - `` - - ```tsx - -

- Welcome back ! -

- - Continue with Passkey -
- ``` - - - Passkey Autofill - ```tsx - - - Email - - - - - ``` - -## 0.6.0 - -### Minor Changes - -- - Adds virtual router to support modal scenarios ([#3461](https://github.com/clerk/javascript/pull/3461)) by [@tmilewski](https://github.com/tmilewski) - - - Adds `routing` prop to `SignIn.Root` and `SignUp.Root` for handling `virtual` routing - - Better support for Account Portal redirect callback flows - -### Patch Changes - -- Fix forms unable to submit upon re-mounting ([#3473](https://github.com/clerk/javascript/pull/3473)) by [@tmilewski](https://github.com/tmilewski) - -- Set `@clerk/types` as a dependency for packages that had it as a dev dependency. ([#3450](https://github.com/clerk/javascript/pull/3450)) by [@desiprisg](https://github.com/desiprisg) - -- Ensure missing passwordSettings don't throw an error ([#3474](https://github.com/clerk/javascript/pull/3474)) by [@tmilewski](https://github.com/tmilewski) - -- Display hard to catch errors inside the sign-in verification step during development (when `NODE_ENV` is set to `development`). ([#3517](https://github.com/clerk/javascript/pull/3517)) by [@LekoArts](https://github.com/LekoArts) - -- Updated dependencies [[`73e5d61e2`](https://github.com/clerk/javascript/commit/73e5d61e21ab3f77f3c8343bc63da0626466c7ac), [`b8e46328d`](https://github.com/clerk/javascript/commit/b8e46328da874859c4928f19f924219cd6520b11)]: - - @clerk/types@4.6.0 - -## 0.5.2 - -### Patch Changes - -- Widen optional peerDependency of `next` to include `>=15.0.0-rc`. This way you can use Next.js 15 with Clerk Elements without your package manager complaining. Also allow React 19. ([#3445](https://github.com/clerk/javascript/pull/3445)) by [@LekoArts](https://github.com/LekoArts) - -## 0.5.1 - -### Patch Changes - -- Update the TypeScript type of `` to allow the `validatePassword` prop also on `type="text"` (in addition to `type="password"`) ([#3394](https://github.com/clerk/javascript/pull/3394)) by [@LekoArts](https://github.com/LekoArts) - -## 0.5.0 - -### Minor Changes - -- - Adds Stately's Browser Inspector in development builds ([#3424](https://github.com/clerk/javascript/pull/3424)) by [@tmilewski](https://github.com/tmilewski) - - - Removes `@statelyai/inspect` from dependencies - - Ensures all inspector-related code is omitted from the build - -### Patch Changes - -- Fix: Verification form submission wasn't working after returning from "choosing an alternate strategy" without making a selection. ([#3425](https://github.com/clerk/javascript/pull/3425)) by [@tmilewski](https://github.com/tmilewski) - - Perf: Adds a `NeverRetriable` state for applicable strategies so the countdown doesn't run needlessly. - -## 0.4.7 - -### Patch Changes - -- Update FieldError/GlobalError types to allow render function children while using the asChild prop ([#3426](https://github.com/clerk/javascript/pull/3426)) by [@tmilewski](https://github.com/tmilewski) - -## 0.4.6 - -## 0.4.5 - -### Patch Changes - -- Update `` to enable `asChild` prop for custom markup in render function usage. ([#3396](https://github.com/clerk/javascript/pull/3396)) by [@alexcarpenter](https://github.com/alexcarpenter) - -## 0.4.4 - -### Patch Changes - -- Fix `setActive` not firing upon a successful sign up. ([#3391](https://github.com/clerk/javascript/pull/3391)) by [@tmilewski](https://github.com/tmilewski) - -## 0.4.3 - -### Patch Changes - -- Fix typing for GlobalError and FieldError render functions ([#3387](https://github.com/clerk/javascript/pull/3387)) by [@tmilewski](https://github.com/tmilewski) - -## 0.4.2 - -## 0.4.1 - -### Patch Changes - -- This release includes various smaller fixes and one dependency update: ([#3343](https://github.com/clerk/javascript/pull/3343)) by [@tmilewski](https://github.com/tmilewski) - - `xstate` was updated from `5.12.0` to `5.13.0` - - Previously, the contents of the `fallback` prop were sometimes shown even if the user wasn't on the `start` step. This bug is fixed now. - - Upon completion of an sign-in/sign-up attempt, don't immediately return to the `start` step. This fixes the issue of a "flash of content" that could e.g. be seen during sign-in with OAuth providers. - - Some underlying fixes in Clerk Elements' XState logic were applied to make sure that during a sign-in/sign-up attempt the state is properly maintained. For example, if you visit an already completed attempt (some step of that flow) it now properly keeps track of that state. - -## 0.4.0 - -### Minor Changes - -- With this change `` and `` now render a `
`. This aligns them with all other `` components (which render an element, mostly ``). ([#3359](https://github.com/clerk/javascript/pull/3359)) by [@LekoArts](https://github.com/LekoArts) - - **Required action:** Update your markup to account for the new `
`, e.g. by removing an element you previously added yourself and moving props like `className` to the `` now. This change can be considered a breaking change so check if you're affected. - -## 0.3.3 - -### Patch Changes - -- The following are all internal changes and not relevant to any end-user: ([#3329](https://github.com/clerk/javascript/pull/3329)) by [@LauraBeatris](https://github.com/LauraBeatris) - - Create type interface for `TelemetryCollector` on `@clerk/types`. This allows to assign `telemetry` on the main Clerk SDK object, while inheriting from the actual `TelemetryCollector` implementation. - -- Refactors internal logic to avoid reliance on `useEffect`. This resolves potential for race conditions as a result of functionality coupled to component renders. ([#3320](https://github.com/clerk/javascript/pull/3320)) by [@tmilewski](https://github.com/tmilewski) - -- Typo fixes in README ([#3335](https://github.com/clerk/javascript/pull/3335)) by [@LekoArts](https://github.com/LekoArts) - -## 0.3.2 - -### Patch Changes - -- Fix issue where sign-up action resend would render type error for applying submit attribute ([#3327](https://github.com/clerk/javascript/pull/3327)) by [@alexcarpenter](https://github.com/alexcarpenter) - -## 0.3.1 - -### Patch Changes - -- Fix otp input overflow using clip path to prevent users clicking in the overflow space for password managers causing unexpected focus on input element ([#3317](https://github.com/clerk/javascript/pull/3317)) by [@alexcarpenter](https://github.com/alexcarpenter) - -## 0.3.0 - -### Minor Changes - -- Fix sign in route registration on development environments ([#3308](https://github.com/clerk/javascript/pull/3308)) by [@tmilewski](https://github.com/tmilewski) - -## 0.2.1 - -### Patch Changes - -- Add appropriate messaging for an invalid instance configuration vs implemented Elements strategies ([#3303](https://github.com/clerk/javascript/pull/3303)) by [@tmilewski](https://github.com/tmilewski) - -- Fixes a bug where multiple verification codes were sent at once. ([#3303](https://github.com/clerk/javascript/pull/3303)) by [@tmilewski](https://github.com/tmilewski) - -## 0.2.0 - -### Minor Changes - -- Bump version to 0.2.0 ([#3301](https://github.com/clerk/javascript/pull/3301)) by [@tmilewski](https://github.com/tmilewski) - - Fix return type of `` to be `JSX.Element | null` - -### Patch Changes - -- Update README to add install and usage instructions ([#3253](https://github.com/clerk/javascript/pull/3253)) by [@LekoArts](https://github.com/LekoArts) - -## 0.1.46 - -### Patch Changes - -- Consistently use sign-in/sign-up as a noun, sign in/sign up as a verb -- Change the `peerDependencies` range of `@clerk/clerk-react` and `@clerk/shared` to the stable Core 2 range -- Change the `peerDependency` range of `next` to mimic what `@clerk/nextjs` is doing -- Remove the `peerDependenciesMeta` entry for `@clerk/clerk-react` and `@clerk/shared` since that hacky workaround shouldn't be necessary anymore as Core 2 is stable now -- Change some imports of `@clerk/shared` to their subpath imports (e.g. `@clerk/shared/url`) to improve tree-shaking -- Add JSDoc comments where they were missing -- Update JSDoc comments to use namespace notation -- Misc JSDoc updates here and there - -## 0.1.45 - -### Patch Changes - -- Add the ability to use `` to support visible challenges - -## 0.1.44 - -### Patch Changes - -- Add `` / `` to Sign-Up - -## 0.1.43 - -### Patch Changes - -- **[BREAKING]** - - Rename `Provider` to `Connection` - - Rename `ProviderIcon` to `Icon` - - Update to handle both sign-up and sign-in - - export under `/common` - -## 0.1.42 - -### Patch Changes - -- Add Sign In Forgot Password functionality -- Clerk’s all-in-one components have a neat feature for the password input during sign-up: There's an instant validation (according to the dashboard password strength settings) and feedback on how good/bad the password is. - - You can add a `validatePassword` prop to `` to enable the aforementioned validation - - The `` component now returns beside `state` also `message` and `codes` - -## 0.1.41 - -### Patch Changes - -- Introduce `exampleMode`, which unconditionally renders the start step. In the future, we can leverage this to make "mock" functional flows in docs. We'll use this currently in the docs to showcase pre-built examples. - -## 0.1.40 - -### Patch Changes - -- Add resendable verifications to sign-in - -## 0.1.39 - -### Patch Changes - -- Add verifications warning/error messages when building a verifications flow - -## 0.1.38 - -### Patch Changes - -- Adjust types to prevent `asChild` usage when using the render prop for OTP input - -## 0.1.37 - -### Patch Changes - -- Change data attribute on OTP input to `data-otp-input-standard` - -## 0.1.36 - -### Patch Changes - -- Allow `className` on `` and `` - -## 0.1.35 - -### Patch Changes - -- Allow for controlled inputs - -## 0.1.34 - -### Patch Changes - -- Fix issues with TypeScript completions / IntelliSense - -## 0.1.33 - -### Patch Changes - -- Correctly passthrough `onChange` prop to `` - -## 0.1.32 - -### Patch Changes - -- Bugfixes for OTP component (``). You can now also pass a `passwordManagerOffset` prop to the component. It adds your specified number of pixels to the `width` of the input. - -## 0.1.31 - -### Patch Changes - -- Enable support for Next.js 14.1.0 or later (and its [window.history](https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate) changes) - -## 0.1.30 - -### Patch Changes - -- Ensure that `` can accept a `ref` - -## 0.1.29 - -### Patch Changes - -- Fix for a type issue with `` - -## 0.1.28 - -### Patch Changes - -- **[BREAKING]** - - The import for the `` component was moved to `/common` - ```diff - - import { Loading } from "@clerk/elements/sign-in" - - import { Loading } from "@clerk/elements/sign-up" - + import { Loading } from "@clerk/elements/common" - ``` - -## 0.1.27 - -### Patch Changes - -- **[BREAKING]** - - `` now returns its state directly in the function, not nested inside an object - - Before: `{({ state }) => ()}` - - After: `{state => ()}` -- `` now optionally allows for its children to accept a function providing the state. You don’t have to use the function, it’s optional. - - Example: `{state => ()}` - -## 0.1.26 - -### Patch Changes - -- Introduction of the `` component - -## 0.1.25 - -### Patch Changes - -- Removed internal usage of `"use client"` directive and replaced it with an implementation that still marks our components as client-only but throws a better error message when Clerk Elements is used in Server Components - -## 0.1.24 - -### Patch Changes - -- **[BREAKING]** - - `` was renamed to `` - - The `` component was removed. Use `` instead. - - Before: `Go back` - - After: `Go back` -- You can use `` now instead of the `` component - - Example: `Log in` - -## 0.1.23 - -### Patch Changes - -- Adds `` to SignIn, enabling the ability to display the current identifier being validated against. E.g.: `We’ve sent a temporary code to **.**` -- **[BREAKING]** Renames the following components: - - `` to `` - - `` to `` - -## 0.1.22 - -### Patch Changes - -- Disable debug logging by default. Go you can opt-in to it by using an environment variable: `NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG=1` - -## 0.1.21 - -### Patch Changes - -- Resolved installation issues with pnpm - -## 0.1.20 - -### Patch Changes - -- Temporarily removed Core 2 Beta peerDependencies to resolve npm issues. Once Core 2 is stable they’ll get added back - -## 0.1.19 - -### Patch Changes - -- **[BREAKING]** - - Require Core 2 Beta version to be used with Clerk Elements - - You can install the Next.js SDK with `npm install @clerk/nextjs@beta` - -## 0.1.18 - -### Patch Changes - -- Attempt to fix Strict Mode errors during development - -## 0.1.17 - -### Patch Changes - -- Fix incorrect peer dependencies - -## 0.1.16 - -### Patch Changes - -- Fix internal function that maps the `name` of the `` component to input types. Use the autocomplete/IntelliSense on `` to use supported names - -## 0.1.15 - -### Patch Changes - -- You can now provide alternative login strategies during sign in. It’s the “Use another method” functionality that Clerk’s current prebuilt components offer. - - Introduction of a `` component for the sign in flow. -- Introduction of a `` component. - -## 0.1.14 - -### Patch Changes - -- Performance improvements to underlying business logic -- **[BREAKING]** - - ``, `` and `` as export were removed. Use `` instead. - -## 0.1.13 - -### Patch Changes - -- Added JSDoc comments to all public APIs - -## 0.1.12 - -### Patch Changes - -- You can now add a `autoSubmit` prop to `` to automatically submit the form if the OTP input is complete - -## 0.1.11 - -### Patch Changes - -- Fix bugs in SAML/OAuth flows -- Add `` component which can be used instead of ``, `` and `` like this: - ```tsx - // You can also use name="continue" or name="verifications" - Contents - ``` - -## 0.1.10 - -### Patch Changes - -- Improved focus handling and accessibility of the `` component -- You can now pass a `length` prop to the ` - - - - - - -
-

@clerk/elements

-

- -
- -[![Chat on Discord](https://img.shields.io/discord/856971667393609759.svg?logo=discord)](https://clerk.com/discord) -[![Clerk documentation](https://img.shields.io/badge/documentation-clerk-green.svg)](https://clerk.com/docs?utm_source=github&utm_medium=clerk_elements) -[![Follow on Twitter](https://img.shields.io/twitter/follow/ClerkDev?style=social)](https://twitter.com/intent/follow?screen_name=ClerkDev) - -[Changelog](https://github.com/clerk/javascript/blob/main/packages/elements/CHANGELOG.md) -· -[Report a Bug](https://github.com/clerk/javascript/issues/new?assignees=&labels=needs-triage&projects=&template=BUG_REPORT.yml) -· -[Request a Feature](https://feedback.clerk.com/roadmap) -· -[Get help](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_elements) - -
- -## Getting started - -Clerk Elements is a library of unstyled, composable components that can be used to build completely custom UIs on top of Clerk's APIs, without having to manage the underlying logic. - -> [!WARNING] -> Clerk Elements is currently in beta. It's not recommended to use it in production just yet, but it would be much appreciated if you give it a try. -> If you have any feedback, please reach out to [beta-elements@clerk.dev](mailto:beta-elements@clerk.dev) or head over to the [GitHub Discussion](https://github.com/orgs/clerk/discussions/3315). - -### Prerequisites - -- Next.js `^13.5.4 || ^14.0.3` or later -- React 18 or later -- Node.js `>=18.17.0` or later -- Use the [Core 2 version](https://clerk.com/changelog/2024-04-19) (or later) of Clerk's SDKs -- An existing Clerk application. [Create your account for free](https://dashboard.clerk.com/sign-up?utm_source=github&utm_medium=clerk_elements). - -### Installation - -The fastest way to get started with Clerk Elements is by following the [Clerk Elements "Getting started" guide](https://clerk.com/docs/customization/elements/overview#getting-started?utm_source=github&utm_medium=clerk_elements). - -## Usage - -For further information, guides, and examples visit the [Clerk Elements reference documentation](https://clerk.com/docs/customization/elements/overview?utm_source=github&utm_medium=clerk_elements). - -The following guides will show you how to build your own custom flows: - -- [Build a sign-in flow](https://clerk.com/docs/customization/elements/guides/sign-in?utm_source=github&utm_medium=clerk_elements) -- [Build a sign-up flow](https://clerk.com/docs/customization/elements/guides/sign-up?utm_source=github&utm_medium=clerk_elements) - -If you want to see what's possible with Clerk Elements, check out these pre-built examples from the Clerk team: - -- [Sign-in examples](https://clerk.com/docs/customization/elements/examples/sign-in?utm_source=github&utm_medium=clerk_elements) -- [Sign-up examples](https://clerk.com/docs/customization/elements/examples/sign-up?utm_source=github&utm_medium=clerk_elements) - -Finally, to learn about the available components and how to use them, check out the component reference pages: - -- [Common components](https://clerk.com/docs/customization/elements/reference/common?utm_source=github&utm_medium=clerk_elements) -- [Sign-in components](https://clerk.com/docs/customization/elements/reference/sign-in?utm_source=github&utm_medium=clerk_elements) -- [Sign-up components](https://clerk.com/docs/customization/elements/reference/sign-up?utm_source=github&utm_medium=clerk_elements) - -_With the beta release, only sign-up and sign-in flows are supported. Support for building the rest of Clerk's prebuilt components with Elements is actively being worked on._ - -## Support - -You can get in touch with us in any of the following ways: - -- Join our official community [Discord server](https://clerk.com/discord) -- On [our support page](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_elements) - -## Contributing - -We're open to all community contributions! If you'd like to contribute in any way, please read [our contribution guidelines](https://github.com/clerk/javascript/blob/main/docs/CONTRIBUTING.md) and [code of conduct](https://github.com/clerk/javascript/blob/main/docs/CODE_OF_CONDUCT.md). - -## Security - -`@clerk/elements` follows good practices of security, but 100% security cannot be assured. - -`@clerk/elements` is provided **"as is"** without any **warranty**. Use at your own risk. - -_For more information and to report security issues, please refer to our [security documentation](https://github.com/clerk/javascript/blob/main/docs/SECURITY.md)._ - -## License - -This project is licensed under the **MIT license**. - -See [LICENSE](https://github.com/clerk/javascript/blob/main/packages/elements/LICENSE) for more information. diff --git a/packages/elements/examples/nextjs/.env.local.example b/packages/elements/examples/nextjs/.env.local.example deleted file mode 100644 index e85051c07b3..00000000000 --- a/packages/elements/examples/nextjs/.env.local.example +++ /dev/null @@ -1,7 +0,0 @@ -NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= -CLERK_SECRET_KEY= - -NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in -NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up -NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/ -NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/ diff --git a/packages/elements/examples/nextjs/.gitignore b/packages/elements/examples/nextjs/.gitignore deleted file mode 100644 index 27bf9049787..00000000000 --- a/packages/elements/examples/nextjs/.gitignore +++ /dev/null @@ -1,40 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts -/test-results/ -/playwright-report/ -/blob-report/ -/playwright/.cache/ diff --git a/packages/elements/examples/nextjs/README.md b/packages/elements/examples/nextjs/README.md deleted file mode 100644 index c4daa7315ea..00000000000 --- a/packages/elements/examples/nextjs/README.md +++ /dev/null @@ -1,28 +0,0 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -### Development Process - -Please see the [package development documentation](../../README.md#package-development) for more information. - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/packages/elements/examples/nextjs/app/example/page.tsx b/packages/elements/examples/nextjs/app/example/page.tsx deleted file mode 100644 index a878a1a067f..00000000000 --- a/packages/elements/examples/nextjs/app/example/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -'use client'; - -import * as Clerk from '@clerk/elements/common'; -import * as SignIn from '@clerk/elements/sign-in'; - -export default function ExamplePage() { - return ( - - -

Sign in

- - Email - - - - Sign in -
-
- ); -} diff --git a/packages/elements/examples/nextjs/app/favicon.ico b/packages/elements/examples/nextjs/app/favicon.ico deleted file mode 100644 index 718d6fea483..00000000000 Binary files a/packages/elements/examples/nextjs/app/favicon.ico and /dev/null differ diff --git a/packages/elements/examples/nextjs/app/globals.css b/packages/elements/examples/nextjs/app/globals.css deleted file mode 100644 index ec731e37102..00000000000 --- a/packages/elements/examples/nextjs/app/globals.css +++ /dev/null @@ -1,20 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - --foreground-rgb: 0, 0, 0; - --background-rgb: 214, 219, 220; -} - -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-rgb: 25, 26, 35; - } -} - -body { - color: rgb(var(--foreground-rgb)); - background: rgb(var(--background-rgb)); -} diff --git a/packages/elements/examples/nextjs/app/layout.tsx b/packages/elements/examples/nextjs/app/layout.tsx deleted file mode 100644 index 406ae495fbc..00000000000 --- a/packages/elements/examples/nextjs/app/layout.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import './globals.css'; - -import { ClerkProvider } from '@clerk/nextjs'; -import { GeistMono } from 'geist/font/mono'; -import { GeistSans } from 'geist/font/sans'; -import type { Metadata } from 'next'; - -export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -}; - -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - {children} - - - ); -} diff --git a/packages/elements/examples/nextjs/app/modal/page.tsx b/packages/elements/examples/nextjs/app/modal/page.tsx deleted file mode 100644 index 92e5edcfda2..00000000000 --- a/packages/elements/examples/nextjs/app/modal/page.tsx +++ /dev/null @@ -1,422 +0,0 @@ -'use client'; - -import * as Clerk from '@clerk/elements/common'; -import * as SignIn from '@clerk/elements/sign-in'; -import { SignedIn, SignedOut, SignOutButton } from '@clerk/nextjs'; -import * as Popover from '@radix-ui/react-popover'; -import Link from 'next/link'; -import { type ComponentProps, useState } from 'react'; - -import { H1, H3, P } from '@/components/design'; -import { CustomField } from '@/components/form'; -import { Spinner } from '@/components/spinner'; - -function CustomProvider({ - children, - provider, -}: { - children: string; - provider: ComponentProps['name']; -}) { - return ( - - {isLoading => ( - - - - {isLoading ? ( - <> - Loading... - - ) : ( - children - )} - - - )} - - ); -} - -function TextButton({ children, ...props }: ComponentProps<'button'>) { - return ( - - ); -} - -function Button({ children, ...props }: ComponentProps<'button'>) { - return ( - - ); -} - -function CustomSubmit({ children }: { children: string }) { - return ( - - {isLoading => (isLoading ? : children)} - - ); -} - -function ResendableFallback({ resendableAfter }: { resendableAfter: number }) { - return

Didn't recieve a code? Retry in {resendableAfter} seconds.

; -} - -function CustomResendable() { - return ( - - Didn't recieve a code? Retry Now - - ); -} - -export default function SignInPage() { - const [continueWithEmail, setContinueWithEmail] = useState(false); - - return ( -
- -
- - -

- Sign Out -

-
-
- - - - Open Sign In - - -
- - - - -
-

Sign In

-

- Don't have an account?{' '} - - Sign Up - -

-
-
- - -
- Continue with GitHub - Continue with Google -
- - {continueWithEmail ? ( - <> - - {fieldState => ( - <> - Email - - - - )} - - - Sign in with Email - - ) : ( - setContinueWithEmail(true)}>Continue with Email - )} -
-
- } - > -
-
-

Sign In

-

- Don't have an account?{' '} - - Sign Up - -

-
- -
- - {isLoading => Loading: {JSON.stringify(isLoading, null, 2)}} - -
- - -
- - -
- Continue with GitHub - Continue with Google -
- - {continueWithEmail ? ( - <> - - {fieldState => ( - <> - Email - - - - )} - - - Sign in with Email - - ) : ( - setContinueWithEmail(true)}>Continue with Email - )} -
-
- - -

CHOOSE STRATEGY:

- - Continue with GitHub - Continue with Google - - - - - - - - - - - - - - - Go back - -
- - -

FORGOT PASSWORD:

- - - - - - - - - -

Or

- - Continue with GitHub - Continue with Google - - - Go back - -
- - -
- - - -

- Welcome back ! -

- - - - Verify - - - Forgot Password - -
- - -

- Welcome back! We've sent a temporary code to -

- - - - - - Verify -
- - -

- Welcome back! We've sent a temporary code to -

- - - - - - Verify -
- - -

Verify your email

- -

- We've sent a verification code to -

- - - - Continue -
- - -

Verify your phone number

- -

- We've sent a verification code to -

- - - - Continue -
-
- - - Use another method - -
- - -
-

Reset your password

- -

Please reset your password to continue:

- - - Update Password -
-
-
- - - - - -
- ); -} diff --git a/packages/elements/examples/nextjs/app/otp-playground/page.tsx b/packages/elements/examples/nextjs/app/otp-playground/page.tsx deleted file mode 100644 index 7b1afd12bb7..00000000000 --- a/packages/elements/examples/nextjs/app/otp-playground/page.tsx +++ /dev/null @@ -1,62 +0,0 @@ -'use client'; - -import { Field, Input, Label } from '@clerk/elements/common'; -import { SignIn, Step } from '@clerk/elements/sign-in'; -import clsx from 'clsx'; -import { AnimatePresence, motion } from 'framer-motion'; - -export default function Page() { - return ( - - -
- - - - - - ( -
- - {value && ( - - {value} - - )} - {value} - - - {/* {(status === 'cursor' || status === 'selected') && ( - - )} */} -
- )} - /> -
-
-
-
- ); -} diff --git a/packages/elements/examples/nextjs/app/page.tsx b/packages/elements/examples/nextjs/app/page.tsx deleted file mode 100644 index 2b5387cfe5d..00000000000 --- a/packages/elements/examples/nextjs/app/page.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { SignedIn, SignOutButton } from '@clerk/nextjs'; -import Image from 'next/image'; -import Link from 'next/link'; - -export default function Home() { - return ( -
-
-

- Get started by editing  - app/sign-in/[[...sign-in]]/page.tsx -

- - -

- Sign Out -

-
-
-
- -
- Clerk Logo -
- -
- -

- Sign-In Flow{' '} - - -> - -

-

Sign in using Elements

- - - -

- Sign Up Flow{' '} - - -> - -

-

Sign up using Elements

- - - -

- OTP{' '} - - -> - -

-

OTP Playground

- - - -

- Modal{' '} - - -> - -

-

Modal Playground

- - - - -

- Sessions{' '} - - -> - -

-

Choose from Active Sessions via Multi-session support

- -
- - -

- Clerk Docs{' '} - - -> - -

-

Clerk Custom Flow Sign-In Reference Docs

-
- - -

- XState 5 Docs{' '} - - -> - -

-

- Core XState 5 Documentation (used by Clerk Custom Flows) -

-
-
-
- ); -} diff --git a/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx b/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx deleted file mode 100644 index d428a07f6de..00000000000 --- a/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx +++ /dev/null @@ -1,506 +0,0 @@ -'use client'; - -import * as Clerk from '@clerk/elements/common'; -import * as SignIn from '@clerk/elements/sign-in'; -import Link from 'next/link'; -import { type ComponentProps, useState } from 'react'; - -import { H1, H3, P } from '@/components/design'; -import { CustomField } from '@/components/form'; -import { Spinner } from '@/components/spinner'; - -function CustomSamlConnection({ children }: { children: string }) { - return ( - - {isLoading => ( - - - {isLoading ? ( - <> - Loading... - - ) : ( - children - )} - - - )} - - ); -} - -function CustomProvider({ - children, - provider, -}: { - children: string; - provider: ComponentProps['name']; -}) { - return ( - - {isLoading => ( - - - - {isLoading ? ( - <> - Loading... - - ) : ( - children - )} - - - )} - - ); -} - -function TextButton({ children, ...props }: ComponentProps<'button'>) { - return ( - - ); -} - -function Button({ children, ...props }: ComponentProps<'button'>) { - return ( - - ); -} - -function CustomSubmit({ children }: { children: string }) { - return ( - - {isLoading => (isLoading ? : children)} - - ); -} - -function ResendableFallback({ resendableAfter }: { resendableAfter: number }) { - return

Didn't recieve a code? Retry in {resendableAfter} seconds.

; -} - -function CustomResendable() { - return ( - - Didn't recieve a code? Retry Now - - ); -} - -export default function SignInPage() { - const [continueWithEmail, setContinueWithEmail] = useState(false); - - return ( - -
-

Sign In

-

- Don't have an account?{' '} - - Sign Up - -

-
-
- - -
- Continue with GitHub - Continue with Google -
- - {continueWithEmail ? ( - <> - - {fieldState => ( - <> - Email - - - - )} - - -
- Sign in with Email - Continue with SAML -
- - ) : ( - setContinueWithEmail(true)}>Continue with Email - )} -
-
- } - > -
-
-

Sign In

-

- Don't have an account?{' '} - - Sign Up - -

-
- -
- {isLoading => Loading: {JSON.stringify(isLoading, null, 2)}} -
- - -
- - -
- Continue with GitHub - Continue with Google -
- - - {isLoading => (isLoading ? : 'Use passkey instead')} - - - {continueWithEmail ? ( - <> - - {fieldState => ( - <> - Email - - - - )} - - -
- Sign in with Email - Continue with SAML -
- - ) : ( - setContinueWithEmail(true)}>Continue with Email - )} -
-
- - -

CHOOSE SESSION:

- - -
- - {({ session }) => ( -

- {session.identifier} | Switch...{' '} -

- )} -
-
-
-
- -

CHOOSE STRATEGY:

- - Continue with GitHub - Continue with Google - - - - - - - - - - - - - - - - - - - - - - - - - - - Go back - -
- - -

FORGOT PASSWORD:

- - - - - - - - - -

Or

- - Continue with GitHub - Continue with Google - - - Go back - -
- - -
- - - -

- Welcome back ! -

- - Continue with Passkey -
- - -

- Welcome back ! -

- - - - Verify - - - Forgot Password - -
- - -

- Welcome back! We've sent a temporary code to -

- - - - - - Verify -
- - -

- Welcome back! We've sent a temporary code to -

- - - - - - Verify -
- - -

Please enter your authenticator code...

- - - - - - Verify -
- - -

Please enter your backup code...

- - - - - - Verify -
- - -

Verify your email

- -

- We've sent a verification code to -

- - - - Continue -
- - -

Verify your phone number

- -

- We've sent a verification code to -

- - - - Continue -
-
- - - Use another method - -
- - -
-

Reset your password

- -

Please reset your password to continue:

- - - Update Password -
-
- -
- - ); -} diff --git a/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx b/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx deleted file mode 100644 index 79c77e6b5ca..00000000000 --- a/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx +++ /dev/null @@ -1,238 +0,0 @@ -'use client'; - -import * as Clerk from '@clerk/elements/common'; -import * as SignUp from '@clerk/elements/sign-up'; -import Link from 'next/link'; -import type { ComponentProps } from 'react'; - -import { H1, HR as Hr, P } from '@/components/design'; -import { CustomField } from '@/components/form'; -import { Spinner } from '@/components/spinner'; - -function CustomSamlConnection({ children }: { children: string }) { - return ( - - {isLoading => ( - - - {isLoading ? ( - <> - Loading... - - ) : ( - children - )} - - - )} - - ); -} - -function CustomSubmit({ children }: ComponentProps<'button'>) { - return ( - - {isLoading => (isLoading ? : children)} - - ); -} - -function ResendableFallback({ resendableAfter }: { resendableAfter: number }) { - return

Didn't recieve a code? Retry in {resendableAfter} seconds.

; -} - -function CustomResendable() { - return ( - - Didn't recieve a code? Retry Now - - ); -} - -export default function SignUpPage() { - return ( - -
-

Sign Up

- -

- Have an account?{' '} - - Sign In - -

- -
- - - Sign In with GitHub - - - - - Sign In with Google - -
- -
- - - -
- - - - -
- Sign Up - Continue with SAML -
-
-
- - } - > -
- -
-

Sign Up

- -

- Have an account?{' '} - - Sign In - -

- -
- - - Sign In with GitHub - - - - - Sign In with Google - -
- -
- - - -
- - - - - - -
- Sign Up - Continue with SAML -
-
-
-
- - -

Please enter additional information:

- - - - - - Sign Up -
- - -

Verify your information:

- - - - - - - - - Verify - - - - - - - - Verify - - - - Please check your email for a link to verify your account. - - -
- - -

Restricted Access

-

Access to this app is limited, and an invitation is required to sign up.

-
-
-
- ); -} diff --git a/packages/elements/examples/nextjs/components/design.tsx b/packages/elements/examples/nextjs/components/design.tsx deleted file mode 100644 index 53470617662..00000000000 --- a/packages/elements/examples/nextjs/components/design.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { ComponentPropsWithoutRef } from 'react'; - -export const H1 = (props: ComponentPropsWithoutRef<'h1'>) => ( -

-); - -export const H2 = (props: ComponentPropsWithoutRef<'h2'>) => ( -

-); - -export const H3 = (props: ComponentPropsWithoutRef<'h3'>) => ( -

-); - -export const P = (props: ComponentPropsWithoutRef<'p'>) => ( -

-); - -export const HR = (props: ComponentPropsWithoutRef<'hr'>) => ( -


-); - -export function Button(props: React.ComponentProps<'button'>) { - return ( - - ) : null} - - - - - {({ state, codes, message }) => ( -
-
Field state: {state}
-
Field msg: {message}
- {name === 'password' ?
Pwd Keys: {codes?.join(', ')}
: null} -
- )} -
- - ); -}); - -const Field = CustomField; - -export { Field }; diff --git a/packages/elements/examples/nextjs/components/social-providers.tsx b/packages/elements/examples/nextjs/components/social-providers.tsx deleted file mode 100644 index e959fb0fb48..00000000000 --- a/packages/elements/examples/nextjs/components/social-providers.tsx +++ /dev/null @@ -1,22 +0,0 @@ -'use client'; - -import { ProviderIcon as ClerkElementsProviderIcon } from '@clerk/elements/common'; -import Image from 'next/image'; -import type { ComponentProps } from 'react'; - -/** - * Helper component for easily circumventing Next's typing - * which requires `src`. It's being passed by the parent component. - */ -export const SocialProviderIcon = (props: ComponentProps) => ( - - {/* @ts-expect-error - required props are passed to child */} - - -); diff --git a/packages/elements/examples/nextjs/components/spinner.tsx b/packages/elements/examples/nextjs/components/spinner.tsx deleted file mode 100644 index d8b19d3f332..00000000000 --- a/packages/elements/examples/nextjs/components/spinner.tsx +++ /dev/null @@ -1,25 +0,0 @@ -'use client'; - -export const Spinner = () => ( - - - - -); diff --git a/packages/elements/examples/nextjs/middleware.ts b/packages/elements/examples/nextjs/middleware.ts deleted file mode 100644 index 545508cedc1..00000000000 --- a/packages/elements/examples/nextjs/middleware.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clerkMiddleware } from '@clerk/nextjs/server'; -export default clerkMiddleware; - -export const config = { - matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'], -}; diff --git a/packages/elements/examples/nextjs/next.config.js b/packages/elements/examples/nextjs/next.config.js deleted file mode 100644 index 840f438e060..00000000000 --- a/packages/elements/examples/nextjs/next.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - // Disable React strict mode when using the state machine inspector - reactStrictMode: false, - typescript: { - ignoreBuildErrors: true, - }, - eslint: { - ignoreDuringBuilds: true, - }, -}; - -module.exports = nextConfig; diff --git a/packages/elements/examples/nextjs/package.json b/packages/elements/examples/nextjs/package.json deleted file mode 100644 index f01c151b138..00000000000 --- a/packages/elements/examples/nextjs/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "elements-nextjs", - "version": "0.1.0", - "private": true, - "scripts": { - "build": "next build", - "dev": "next dev", - "dev:debug": "NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG=true next dev", - "lint": "next lint", - "start": "next start" - }, - "dependencies": { - "@clerk/elements": "file:../../elements", - "@clerk/nextjs": "file:../../nextjs", - "@radix-ui/react-form": "^0.1.8", - "@radix-ui/react-popover": "^1.1.15", - "clsx": "^2.0.0", - "framer-motion": "^11.0.28", - "geist": "^1.3.1", - "next": "14.2.33", - "react": "18.3.1", - "react-dom": "18.3.1" - }, - "devDependencies": { - "@types/node": "^18.19.130", - "@types/react": "catalog:react", - "@types/react-dom": "catalog:react", - "autoprefixer": "^10.4.21", - "postcss": "^8.5.6", - "tailwindcss": "^3.4.18", - "typescript": "^5.8.3" - } -} diff --git a/packages/elements/examples/nextjs/postcss.config.js b/packages/elements/examples/nextjs/postcss.config.js deleted file mode 100644 index 12a703d900d..00000000000 --- a/packages/elements/examples/nextjs/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/packages/elements/examples/nextjs/public/clerk.svg b/packages/elements/examples/nextjs/public/clerk.svg deleted file mode 100644 index b4ba8eb3084..00000000000 --- a/packages/elements/examples/nextjs/public/clerk.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/elements/examples/nextjs/public/next.svg b/packages/elements/examples/nextjs/public/next.svg deleted file mode 100644 index 5174b28c565..00000000000 --- a/packages/elements/examples/nextjs/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/elements/examples/nextjs/public/vercel.svg b/packages/elements/examples/nextjs/public/vercel.svg deleted file mode 100644 index d2f84222734..00000000000 --- a/packages/elements/examples/nextjs/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/elements/examples/nextjs/tailwind.config.ts b/packages/elements/examples/nextjs/tailwind.config.ts deleted file mode 100644 index dd2f8e0e028..00000000000 --- a/packages/elements/examples/nextjs/tailwind.config.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Config } from 'tailwindcss'; - -const config: Config = { - content: [ - './pages/**/*.{js,ts,jsx,tsx,mdx}', - './components/**/*.{js,ts,jsx,tsx,mdx}', - './app/**/*.{js,ts,jsx,tsx,mdx}', - ], - theme: { - container: { - center: true, - }, - extend: { - colors: { - foreground: 'rgb(var(--foreground-rgb))', - background: 'rgb(var(--background-rgb))', - - secondary: 'rgba(0, 0, 0, 0.1)', - tertiary: 'rgba(255, 255, 255, 0.1)', - }, - fontFamily: { - sans: ['var(--font-geist-sans)'], - mono: ['var(--font-geist-mono)'], - }, - backgroundImage: { - 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', - 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', - }, - }, - }, - plugins: [], -}; -export default config; diff --git a/packages/elements/examples/nextjs/tsconfig.json b/packages/elements/examples/nextjs/tsconfig.json deleted file mode 100644 index c7146963787..00000000000 --- a/packages/elements/examples/nextjs/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./*"] - } - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/packages/elements/jest.config.js b/packages/elements/jest.config.js deleted file mode 100644 index 42f0580aa8f..00000000000 --- a/packages/elements/jest.config.js +++ /dev/null @@ -1,22 +0,0 @@ -const { name } = require('./package.json'); -const { pathsToModuleNameMapper } = require('ts-jest'); -const { compilerOptions } = require('./tsconfig'); - -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - globals: { - PACKAGE_NAME: '@clerk/elements', - PACKAGE_VERSION: '0.0.0-test', - __DEV__: false, - }, - displayName: name.replace('@clerk', ''), - injectGlobals: true, - roots: [''], - testMatch: ['**/?(*.)+(spec|test).+(ts|tsx|js)'], - testEnvironment: 'jsdom', - transform: { '^.+\\.m?tsx?$': ['ts-jest', { tsconfig: 'tsconfig.test.json' }] }, - setupFilesAfterEnv: ['/jest.setup.js'], - testPathIgnorePatterns: ['/node_modules/', '/jest/', '/.turbo', '/dist/', '/examples'], - modulePaths: [compilerOptions.baseUrl], - moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths), -}; diff --git a/packages/elements/jest.setup.js b/packages/elements/jest.setup.js deleted file mode 100644 index 1a5dcdc343c..00000000000 --- a/packages/elements/jest.setup.js +++ /dev/null @@ -1 +0,0 @@ -process.env.CLERK_SECRET_KEY = 'TEST_SECRET_KEY'; diff --git a/packages/elements/package.json b/packages/elements/package.json deleted file mode 100644 index 2a035584bec..00000000000 --- a/packages/elements/package.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "name": "@clerk/elements", - "version": "0.23.85", - "description": "Clerk Elements", - "keywords": [ - "clerk", - "typescript", - "auth", - "authentication", - "passwordless", - "session", - "jwt", - "elements", - "radix" - ], - "homepage": "https://clerk.com/", - "bugs": { - "url": "https://github.com/clerk/javascript/issues" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/clerk/javascript.git", - "directory": "packages/elements" - }, - "license": "MIT", - "author": "Clerk", - "sideEffects": false, - "exports": { - "./*": { - "import": { - "types": "./dist/react/*/index.d.mts", - "default": "./dist/react/*/index.mjs" - }, - "require": { - "types": "./dist/react/*/index.d.ts", - "default": "./dist/react/*/index.js" - } - }, - ".": { - "import": { - "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" - }, - "require": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - } - } - }, - "files": [ - "dist" - ], - "scripts": { - "app:build": "(cd examples/nextjs && pnpm build)", - "app:dev": "(cd examples/nextjs && pnpm dev)", - "app:dev:debug": "(cd examples/nextjs && NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG=true pnpm dev)", - "app:dev:debug:server": "(cd examples/nextjs && NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG=true CLERK_ELEMENTS_DEBUG_SERVER=true pnpm dev)", - "app:dev:debug:ui": "(cd examples/nextjs && NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG_UI=true pnpm dev)", - "app:e2e": "(cd examples/nextjs && pnpm e2e)", - "app:lint": "(cd examples/nextjs && pnpm lint)", - "build": "tsup --env.NODE_ENV production", - "build:analyze": "tsup --env.NODE_ENV production --metafile; open https://esbuild.github.io/analyze/", - "build:declarations": "tsc -p tsconfig.declarations.json", - "dev": "tsup --env.NODE_ENV development --watch", - "dev:example": "concurrently \"pnpm dev\" \"pnpm app:dev\"", - "format": "node ../../scripts/format-package.mjs", - "format:check": "node ../../scripts/format-package.mjs --check", - "lint": "eslint src", - "lint:attw": "attw --pack . --profile node16", - "lint:publint": "publint", - "test": "jest", - "test:cache:clear": "jest --clearCache --useStderr" - }, - "dependencies": { - "@clerk/clerk-react": "workspace:^", - "@clerk/shared": "workspace:^", - "@clerk/types": "workspace:^", - "@radix-ui/primitive": "^1.1.3", - "@radix-ui/react-form": "^0.1.8", - "@radix-ui/react-slot": "^1.2.3", - "@xstate/react": "^6.0.0", - "client-only": "^0.0.1", - "tslib": "catalog:repo", - "type-fest": "^4.41.0", - "xstate": "^5.20.2" - }, - "devDependencies": { - "@statelyai/inspect": "^0.4.0", - "concurrently": "^9.2.1", - "next": "14.2.33" - }, - "peerDependencies": { - "next": "^13.5.7 || ^14.2.25 || ^15.2.3 || ^16", - "react": "catalog:peer-react", - "react-dom": "catalog:peer-react" - }, - "peerDependenciesMeta": { - "next": { - "optional": true - } - }, - "engines": { - "node": ">=18.17.0" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/packages/elements/src/index.ts b/packages/elements/src/index.ts deleted file mode 100644 index 1eb9b4dc1a7..00000000000 --- a/packages/elements/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -throw new Error(`No exports are available from the top-level "@clerk/elements" package. -Use specific subpath imports instead, e.g. "@clerk/elements/sign-in". - -Find all available exports in the documentation: -https://clerk.com/docs/elements/overview`); diff --git a/packages/elements/src/internals/constants/index.ts b/packages/elements/src/internals/constants/index.ts deleted file mode 100644 index 135bdfd35aa..00000000000 --- a/packages/elements/src/internals/constants/index.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { SignUpModes } from '@clerk/types'; - -import { safeAccess } from '~/utils/safe-access'; - -export const SSO_CALLBACK_PATH_ROUTE = '/sso-callback'; -export const CHOOSE_SESSION_PATH_ROUTE = '/choose'; -export const MAGIC_LINK_VERIFY_PATH_ROUTE = '/verify'; - -export const SIGN_UP_MODES: Record = { - PUBLIC: 'public', - RESTRICTED: 'restricted', -}; - -// TODO: remove reliance on next-specific variables here -export const SIGN_IN_DEFAULT_BASE_PATH = safeAccess( - () => process.env.CLERK_SIGN_IN_URL ?? process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL, - '/sign-in', -); -export const SIGN_UP_DEFAULT_BASE_PATH = safeAccess( - () => process.env.CLERK_SIGN_UP_URL ?? process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL, - '/sign-up', -); - -// The version that Next added support for the window.history.pushState and replaceState APIs. -// ref: https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate -export const NEXT_WINDOW_HISTORY_SUPPORT_VERSION = '14.1.0'; - -export const SEARCH_PARAMS = { - createdSession: '__clerk_created_session', - handshake: '__clerk_handshake', - help: '__clerk_help', - invitationToken: '__clerk_invitation_token', - modalState: '__clerk_modal_state', - satelliteUrl: '__clerk_satellite_url', - status: '__clerk_status', - synced: '__clerk_synced', - ticket: '__clerk_ticket', - transfer: '__clerk_transfer', -} as const; - -export const RESENDABLE_COUNTDOWN_DEFAULT = 60; - -export const CAPTCHA_ELEMENT_ID = 'clerk-captcha'; - -// Pulled from: https://github.com/clerk/javascript/blob/c7d626292a9fd12ca0f1b31a1035e711b6e99531/packages/clerk-js/src/core/constants.ts#L15 -export const ERROR_CODES = { - FORM_IDENTIFIER_NOT_FOUND: 'form_identifier_not_found', - FORM_PASSWORD_INCORRECT: 'form_password_incorrect', - INVALID_STRATEGY_FOR_USER: 'strategy_for_user_invalid', - NOT_ALLOWED_TO_SIGN_UP: 'not_allowed_to_sign_up', - OAUTH_ACCESS_DENIED: 'oauth_access_denied', - OAUTH_EMAIL_DOMAIN_RESERVED_BY_SAML: 'oauth_email_domain_reserved_by_saml', - NOT_ALLOWED_ACCESS: 'not_allowed_access', - SAML_USER_ATTRIBUTE_MISSING: 'saml_user_attribute_missing', - USER_LOCKED: 'user_locked', - ENTERPRISE_SSO_USER_ATTRIBUTE_MISSING: 'enterprise_sso_user_attribute_missing', - ENTERPRISE_SSO_EMAIL_ADDRESS_DOMAIN_MISMATCH: 'enterprise_sso_email_address_domain_mismatch', - ENTERPRISE_SSO_HOSTED_DOMAIN_MISMATCH: 'enterprise_sso_hosted_domain_mismatch', - SAML_EMAIL_ADDRESS_DOMAIN_MISMATCH: 'saml_email_address_domain_mismatch', - ORGANIZATION_MEMBERSHIP_QUOTA_EXCEEDED_FOR_SSO: 'organization_membership_quota_exceeded_for_sso', -}; - -export const ROUTING = { - path: 'path', - virtual: 'virtual', - hash: 'hash', -} as const; - -export type ROUTING = (typeof ROUTING)[keyof typeof ROUTING]; diff --git a/packages/elements/src/internals/errors/index.ts b/packages/elements/src/internals/errors/index.ts deleted file mode 100644 index 74957c32861..00000000000 --- a/packages/elements/src/internals/errors/index.ts +++ /dev/null @@ -1,66 +0,0 @@ -// eslint-disable-next-line no-restricted-imports -import type { MetamaskError } from '@clerk/shared'; -import type { ClerkAPIError } from '@clerk/types'; - -export abstract class ClerkElementsErrorBase extends Error { - clerkError = true; - clerkElementsError = true; - rawMessage: string; - - constructor( - readonly code: string, - message: string, - ) { - super(message); - - this.name = 'ClerkElementsError'; - this.rawMessage = message; - } - - toString() { - return `[${this.name}]\nCode: ${this.code}\nMessage: ${this.message}`; - } -} - -export class ClerkElementsError extends ClerkElementsErrorBase { - static fromAPIError(error: ClerkAPIError | MetamaskError) { - return new ClerkElementsError( - error.code.toString(), - // @ts-expect-error - Expected that longMessage isn't a property of MetamaskError - error.longMessage || error.message, - ); - } - - constructor(code: string, message: string) { - super(code, message); - this.name = 'ClerkElementsError'; - } -} - -export class ClerkElementsRuntimeError extends ClerkElementsErrorBase { - constructor(message: string) { - super('elements_runtime_error', message); - this.name = 'ClerkElementsRuntimeError'; - } -} - -export class ClerkElementsFieldError extends ClerkElementsErrorBase { - static fromAPIError(error: ClerkAPIError) { - return new ClerkElementsFieldError(error.code, error.longMessage || error.message); - } - - constructor(code: string, message: string) { - super(code, message); - this.name = 'ClerkElementsFieldError'; - } - - get validityState() { - return this.code; - } - - get forceMatch() { - return true; - } - - matchFn = () => true; -} diff --git a/packages/elements/src/internals/machines/form/form.context.ts b/packages/elements/src/internals/machines/form/form.context.ts deleted file mode 100644 index 43948935e66..00000000000 --- a/packages/elements/src/internals/machines/form/form.context.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { createActorContext } from '@xstate/react'; -import type { SnapshotFrom } from 'xstate'; - -import { FormMachine } from '~/internals/machines/form'; -import { inspect } from '~/internals/utils/inspector'; - -export type SnapshotState = SnapshotFrom; - -const FormMachineContext = createActorContext(FormMachine, { inspect }); - -export const FormStoreProvider = FormMachineContext.Provider; -export const useFormStore = FormMachineContext.useActorRef; -export const useFormSelector = FormMachineContext.useSelector; - -/** - * Selects a global error, if it exists - */ -export const globalErrorsSelector = (state: SnapshotState) => state.context.errors; - -/** - * Selects if a specific field has a value - */ -export const fieldValueSelector = (name: string | undefined) => (state: SnapshotState) => - name ? state.context.fields.get(name)?.value : ''; - -/** - * Selects if a specific field has a value - */ -export const fieldHasValueSelector = (name: string | undefined) => (state: SnapshotState) => - Boolean(fieldValueSelector(name)(state)); - -type MapValue = A extends Map ? V : never; - -/** - * Selects field-specific feedback, if they exist - * - * We declare an explicit return type here because TypeScript's inference results in the subtype reduction of the - * union used for feedback. Explicitly declaring the return type allows for all members of the union to be - * included in the return type. - */ -export const fieldFeedbackSelector = - (name: string | undefined) => - (state: SnapshotState): MapValue['feedback'] | undefined => - name ? state.context.fields.get(name)?.feedback : undefined; diff --git a/packages/elements/src/internals/machines/form/form.machine.ts b/packages/elements/src/internals/machines/form/form.machine.ts deleted file mode 100644 index 71c1cfb5bbc..00000000000 --- a/packages/elements/src/internals/machines/form/form.machine.ts +++ /dev/null @@ -1,297 +0,0 @@ -import { isClerkAPIResponseError, isKnownError, isMetamaskError } from '@clerk/shared/error'; -import { snakeToCamel } from '@clerk/shared/underscore'; -import type { ClerkAPIError } from '@clerk/types'; -import type { MachineContext } from 'xstate'; -import { assign, enqueueActions, setup } from 'xstate'; - -import { ClerkElementsError, ClerkElementsFieldError } from '~/internals/errors'; - -import type { FieldDetails, FormDefaultValues, FormFields } from './form.types'; - -export interface FormMachineContext extends MachineContext { - defaultValues: FormDefaultValues; - errors: ClerkElementsError[]; - fields: FormFields; - hidden?: Set; - missing?: Set; - optional?: Set; - progressive: boolean; - required?: Set; -} - -export type FormMachineEvents = - | { type: 'FIELD.ADD'; field: Pick } - | { type: 'FIELD.REMOVE'; field: Pick } - | { type: 'FIELD.ENABLE'; field: Pick } - | { type: 'FIELD.DISABLE'; field: Pick } - | { - type: 'MARK_AS_PROGRESSIVE'; - defaultValues: FormDefaultValues; - missing: string[]; - optional: string[]; - required: string[]; - } - | { - type: 'PREFILL_DEFAULT_VALUES'; - defaultValues: FormDefaultValues; - } - | { type: 'UNMARK_AS_PROGRESSIVE' } - | { - type: 'FIELD.UPDATE'; - field: Pick; - } - | { type: 'ERRORS.SET'; error: any } - | { type: 'ERRORS.CLEAR' } - | { - type: 'FIELD.FEEDBACK.SET'; - field: Pick; - } - | { - type: 'FIELD.FEEDBACK.CLEAR'; - field: Pick; - } - | { type: 'FIELD.FEEDBACK.CLEAR.ALL' }; - -type FormMachineTypes = { - events: FormMachineEvents; - context: FormMachineContext; -}; - -export type TFormMachine = typeof FormMachine; - -/** - * A machine for managing form state. - * This machine is used alongside our other, flow-specific machines and a reference to a spawned FormMachine actor is used in the flows to interact with the form state. - */ -export const FormMachine = setup({ - actions: { - setGlobalErrors: assign({ - errors: (_, params: { errors: ClerkElementsError[] }) => [...params.errors], - }), - setFieldFeedback: assign({ - fields: ({ context }, params: Pick) => { - if (!params.name) { - throw new Error('Field name is required'); - } - - const fieldName = params.name; - if (context.fields.has(fieldName)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - context.fields.get(fieldName)!.feedback = params.feedback; - } - - return context.fields; - }, - }), - }, - types: {} as FormMachineTypes, -}).createMachine({ - id: 'Form', - context: () => ({ - defaultValues: new Map(), - errors: [], - fields: new Map(), - progressive: false, - }), - on: { - 'ERRORS.SET': { - actions: enqueueActions(({ enqueue, event }) => { - const isClerkAPIError = (err: any): err is ClerkAPIError => 'meta' in err; - - if (isKnownError(event.error)) { - const fields: Record = {}; - const globalErrors: ClerkElementsError[] = []; - const errors = isClerkAPIResponseError(event.error) ? event.error?.errors : [event.error]; - - for (const error of errors) { - const name = isClerkAPIError(error) ? snakeToCamel(error.meta?.paramName) : null; - - if (!name || isMetamaskError(error)) { - globalErrors.push(ClerkElementsError.fromAPIError(error)); - continue; - } - - if (!fields[name]) { - fields[name] = []; - } - - fields[name]?.push(ClerkElementsFieldError.fromAPIError(error)); - } - - enqueue({ - type: 'setGlobalErrors', - params: { - errors: globalErrors, - }, - }); - - for (const field in fields) { - enqueue({ - type: 'setFieldFeedback', - params: { - name: field, - feedback: { - type: 'error', - message: fields[field][0], - }, - }, - }); - } - } - }), - }, - 'ERRORS.CLEAR': { - actions: assign({ - errors: () => [], - }), - }, - 'FIELD.ADD': { - actions: assign({ - fields: ({ context, event }) => { - if (!event.field.name) { - throw new Error('Field name is required'); - } - - event.field.value = event.field.value || context.defaultValues.get(event.field.name) || undefined; - - context.fields.set(event.field.name, event.field); - return context.fields; - }, - }), - }, - 'FIELD.UPDATE': { - actions: assign({ - fields: ({ context, event }) => { - if (!event.field.name) { - throw new Error('Field name is required'); - } - - const field = context.fields.get(event.field.name); - - if (field) { - field.checked = event.field.checked; - field.disabled = event.field.disabled ?? field.disabled; - field.value = event.field.value; - - context.fields.set(event.field.name, field); - } - - return context.fields; - }, - }), - }, - 'FIELD.DISABLE': { - actions: assign({ - fields: ({ context, event }) => { - if (!event.field.name) { - throw new Error('Field name is required'); - } - - const field = context.fields.get(event.field.name); - - if (field) { - field.disabled = true; - context.fields.set(event.field.name, field); - } - - return context.fields; - }, - }), - }, - 'FIELD.ENABLE': { - actions: assign({ - fields: ({ context, event }) => { - if (!event.field.name) { - throw new Error('Field name is required'); - } - - const field = context.fields.get(event.field.name); - - if (field) { - field.disabled = false; - context.fields.set(event.field.name, field); - } - - return context.fields; - }, - }), - }, - 'FIELD.REMOVE': { - actions: assign({ - fields: ({ context, event }) => { - if (!event.field.name) { - throw new Error('Field name is required'); - } - - context.fields.delete(event.field.name); - return context.fields; - }, - }), - }, - 'FIELD.FEEDBACK.SET': { - actions: [ - { - type: 'setFieldFeedback', - params: ({ event }) => event.field, - }, - ], - }, - 'FIELD.FEEDBACK.CLEAR': { - actions: assign({ - fields: ({ context, event }) => { - if (!event.field.name) { - throw new Error('Field name is required'); - } - if (context.fields.has(event.field.name)) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - context.fields.get(event.field.name)!.feedback = undefined; - } - - return context.fields; - }, - }), - }, - 'FIELD.FEEDBACK.CLEAR.ALL': { - actions: assign({ - fields: ({ context }) => { - context.fields.forEach(field => { - field.feedback = undefined; - }); - - return context.fields; - }, - }), - }, - MARK_AS_PROGRESSIVE: { - actions: assign(({ event }) => { - const missing = new Set(event.missing); - - return { - defaultValues: event.defaultValues, - hidden: new Set([...event.required.filter(f => !missing.has(f)), ...event.optional]), - missing, - optional: new Set(event.optional), - progressive: true, - required: new Set(event.required), - }; - }), - }, - UNMARK_AS_PROGRESSIVE: { - actions: assign({ - defaultValues: new Map(), - hidden: undefined, - missing: undefined, - optional: undefined, - progressive: false, - required: undefined, - }), - }, - PREFILL_DEFAULT_VALUES: { - actions: assign(({ event }) => { - return { - defaultValues: event.defaultValues, - }; - }), - }, - }, -}); diff --git a/packages/elements/src/internals/machines/form/form.types.ts b/packages/elements/src/internals/machines/form/form.types.ts deleted file mode 100644 index a5d4cfc5a7e..00000000000 --- a/packages/elements/src/internals/machines/form/form.types.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { ClerkElementsFieldError } from '~/internals/errors'; -import type { FieldStates } from '~/react/common/form/types'; -import type { PasswordConfig } from '~/react/hooks/use-password.hook'; -import type { ErrorCodeOrTuple } from '~/react/utils/generate-password-error-text'; - -export type FormDefaultValues = Map; - -interface FeedbackBase { - codes?: Array; -} - -export interface FeedbackErrorType extends FeedbackBase { - message: ClerkElementsFieldError; - type: Extract; -} - -export interface FeedbackOtherType extends FeedbackBase { - message: string; - type: Exclude; -} - -export interface FeedbackPasswordErrorType extends FeedbackErrorType { - config?: PasswordConfig; -} - -export interface FeedbackPasswordInfoType extends FeedbackOtherType { - config?: PasswordConfig; -} - -export type FieldDetails = { - checked?: boolean; - disabled?: boolean; - feedback?: FeedbackErrorType | FeedbackOtherType | FeedbackPasswordErrorType | FeedbackPasswordInfoType; - name?: string; - type: React.HTMLInputTypeAttribute; - value?: string | readonly string[] | number; -}; - -export type FormFields = Map; diff --git a/packages/elements/src/internals/machines/form/index.ts b/packages/elements/src/internals/machines/form/index.ts deleted file mode 100644 index 4d397cd4a26..00000000000 --- a/packages/elements/src/internals/machines/form/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './form.context'; -export * from './form.machine'; -export * from './form.types'; diff --git a/packages/elements/src/internals/machines/shared/__tests__/shared.actions.test.ts b/packages/elements/src/internals/machines/shared/__tests__/shared.actions.test.ts deleted file mode 100644 index ef65ac39e14..00000000000 --- a/packages/elements/src/internals/machines/shared/__tests__/shared.actions.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { sendToLoading } from '../shared.actions'; - -describe('sendToLoading', () => { - let context: any; - let event: any; - let parentSendMock: jest.Mock; - - beforeEach(() => { - context = { - parent: { - send: jest.fn(), - }, - loadingStep: new Error('Not implemented'), - }; - event = { - type: new Error('Not implemented'), - }; - parentSendMock = context.parent.send; - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('should set loading state to false when event type starts with "xstate.done."', () => { - event.type = 'xstate.done.SOME_EVENT'; - context.loadingStep = 'start'; - - sendToLoading({ context, event }); - - expect(parentSendMock).toHaveBeenCalledWith({ - type: 'LOADING', - isLoading: false, - step: undefined, - strategy: undefined, - }); - }); - - test('should set loading state to false when event type starts with "xstate.error."', () => { - event.type = 'xstate.error.SOME_EVENT'; - context.loadingStep = 'start'; - - sendToLoading({ context, event }); - - expect(parentSendMock).toHaveBeenCalledWith({ - type: 'LOADING', - isLoading: false, - step: undefined, - strategy: undefined, - }); - }); - - test('should set loading state to true with undefined step and defined strategy when context.loadingStep is "strategy" and event.type is "REDIRECT"', () => { - context.loadingStep = 'strategy'; - event.type = 'REDIRECT'; - event.params = { - strategy: 'some-strategy', - }; - - sendToLoading({ context, event }); - - expect(parentSendMock).toHaveBeenCalledWith({ - type: 'LOADING', - isLoading: true, - step: undefined, - strategy: 'some-strategy', - }); - }); - - test('should set loading state to true with "continue" step and undefined strategy when loadingStep is "continue"', () => { - context.loadingStep = 'continue'; - event.type = 'SUBMIT'; - - sendToLoading({ context, event }); - - expect(parentSendMock).toHaveBeenCalledWith({ - type: 'LOADING', - isLoading: true, - step: 'continue', - strategy: undefined, - }); - }); - - test('should set loading state to true with the correct step and undefined strategy when loadingStep is not "strategy" or "continue"', () => { - context.loadingStep = 'some-step'; - event.type = 'SUBMIT'; - - sendToLoading({ context, event }); - - expect(parentSendMock).toHaveBeenCalledWith({ - type: 'LOADING', - isLoading: true, - step: 'some-step', - strategy: undefined, - }); - }); -}); diff --git a/packages/elements/src/internals/machines/shared/index.ts b/packages/elements/src/internals/machines/shared/index.ts deleted file mode 100644 index 77518a8a86b..00000000000 --- a/packages/elements/src/internals/machines/shared/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './shared.actions'; -export * from './shared.actors'; -export * from './shared.types'; diff --git a/packages/elements/src/internals/machines/shared/shared.actions.ts b/packages/elements/src/internals/machines/shared/shared.actions.ts deleted file mode 100644 index 33fd00af02d..00000000000 --- a/packages/elements/src/internals/machines/shared/shared.actions.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { SignInStrategy } from '@clerk/types'; - -import type { - SignInResetPasswordContext, - SignInResetPasswordEvents, - SignInStartContext, - SignInStartEvents, - SignInVerificationContext, - SignInVerificationEvents, -} from '~/internals/machines/sign-in'; -import type { - SignUpContinueContext, - SignUpContinueEvents, - SignUpStartContext, - SignUpStartEvents, - SignUpVerificationContext, - SignUpVerificationEvents, -} from '~/internals/machines/sign-up'; -import type { ThirdPartyMachineContext, ThirdPartyMachineEvent } from '~/internals/machines/third-party'; -import type { BaseRouterLoadingStep } from '~/internals/machines/types'; - -type SendToLoadingProps = { - context: - | SignInStartContext - | SignInVerificationContext - | SignInResetPasswordContext - | ThirdPartyMachineContext - | SignUpStartContext - | SignUpContinueContext - | SignUpVerificationContext; - event: - | SignInStartEvents - | SignInVerificationEvents - | SignInResetPasswordEvents - | ThirdPartyMachineEvent - | SignUpStartEvents - | SignUpContinueEvents - | SignUpVerificationEvents; -}; - -export function sendToLoading({ context, event }: SendToLoadingProps): void { - // Unrelated to the `context` of each machine, the step passed to the loading event must use BaseRouterLoadingStep - let step: BaseRouterLoadingStep | undefined; - let strategy: SignInStrategy | undefined; - let action: string | undefined; - - // By default the loading state is set to `true` when this function is called - // Only if these events are received, the loading state is set to `false` - // Early return here to avoid unnecessary checks - if (event.type.startsWith('xstate.done.') || event.type.startsWith('xstate.error.')) { - return context.parent.send({ - type: 'LOADING', - isLoading: false, - step: undefined, - strategy: undefined, - }); - } - - // `context.loadingStep: "strategy"` is not a valid BaseRouterLoadingStep (on purpose) so needs to be handled here. This context should be used when `step` should be undefined and `strategy` be defined instead - if (context.loadingStep === 'strategy') { - step = undefined; - - // Third-party machine handling - if (event.type === 'REDIRECT') { - strategy = event.params.strategy; - } - - return context.parent.send({ - type: 'LOADING', - isLoading: true, - step, - strategy, - }); - } else if (context.loadingStep === 'continue') { - step = 'continue'; - strategy = undefined; - action = 'action' in event ? event.action : undefined; - - return context.parent.send({ - type: 'LOADING', - isLoading: true, - step, - strategy, - action, - }); - } else if (context.loadingStep === 'reset-password') { - step = 'reset-password'; - strategy = undefined; - action = 'action' in event ? event.action : undefined; - - return context.parent.send({ - type: 'LOADING', - isLoading: true, - step, - strategy, - action, - }); - } else if (context.loadingStep === 'start') { - step = 'start'; - strategy = undefined; - action = 'action' in event ? event.action : undefined; - - return context.parent.send({ - type: 'LOADING', - isLoading: true, - step, - strategy, - action, - }); - } else { - step = context.loadingStep; - strategy = undefined; - action = 'action' in event ? event.action : undefined; - - return context.parent.send({ - type: 'LOADING', - isLoading: true, - step, - strategy, - action, - }); - } -} diff --git a/packages/elements/src/internals/machines/shared/shared.actors.ts b/packages/elements/src/internals/machines/shared/shared.actors.ts deleted file mode 100644 index 447ef67e32e..00000000000 --- a/packages/elements/src/internals/machines/shared/shared.actors.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Clerk, LoadedClerk } from '@clerk/types'; -import type { EventObject } from 'xstate'; -import { fromCallback } from 'xstate'; - -export type ClerkLoaderEvents = { type: 'CLERK.READY' } | { type: 'CLERK.ERROR'; message: string }; - -export const clerkLoader = fromCallback(({ sendBack, input: clerk }) => { - const reportLoaded = () => sendBack({ type: 'CLERK.READY' }); - - if (clerk.loaded) { - reportLoaded(); - } else if ('addOnLoaded' in clerk) { - // @ts-expect-error - Expects `addOnLoaded` from @clerk/shared/react's IsomorphicClerk. - clerk.addOnLoaded(reportLoaded); - } else { - sendBack({ type: 'ERROR', message: 'Clerk client could not be loaded' }); - } - - return () => {}; -}); diff --git a/packages/elements/src/internals/machines/shared/shared.types.ts b/packages/elements/src/internals/machines/shared/shared.types.ts deleted file mode 100644 index 6add45dfe40..00000000000 --- a/packages/elements/src/internals/machines/shared/shared.types.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { - AuthenticateWithRedirectParams, - LoadedClerk, - OAuthStrategy, - SamlStrategy, - SignInStrategy, -} from '@clerk/types'; -import type { SetRequired, Simplify } from 'type-fest'; -import type { ActorRefFrom } from 'xstate'; - -import type { FormMachine } from '../form'; - -export type WithClerk> = { clerk: LoadedClerk } & T; -export type WithClient> = { client: LoadedClerk['client'] } & T; -export type WithParams = { params: T }; - -// ================= Unsafe Metadata ================= // - -export type WithUnsafeMetadata = T & { - unsafeMetadata?: SignUpUnsafeMetadata | undefined; -}; - -// ================= Authenticate With Redirect ================= // - -type SamlOnlyKeys = 'identifier' | 'emailAddress'; - -export type AuthenticateWithRedirectOAuthParams = Simplify< - Omit & { strategy: OAuthStrategy } ->; -export type AuthenticateWithRedirectSamlParams = Simplify< - SetRequired & { - strategy: SamlStrategy; - } ->; - -// ================= Strategies ================= // - -export type SignInStrategyName = SignInStrategy | 'oauth' | 'web3'; - -export type SetFormEvent = { type: 'SET_FORM'; formRef: ActorRefFrom }; diff --git a/packages/elements/src/internals/machines/sign-in/__tests__/router.selectors.test.ts b/packages/elements/src/internals/machines/sign-in/__tests__/router.selectors.test.ts deleted file mode 100644 index 36fe7661dfd..00000000000 --- a/packages/elements/src/internals/machines/sign-in/__tests__/router.selectors.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type { SignInFirstFactor } from '@clerk/types'; - -import { SignInSafeIdentifierSelectorForStrategy } from '../router.selectors'; -import type { SignInRouterSnapshot } from '../router.types'; - -const IDENTIFIER = 'support@clerk.dev'; - -function createSnapshot( - supportedFirstFactors: Partial[] = [], - identifier?: string, -): SignInRouterSnapshot { - return { - context: { - clerk: { - client: { - signIn: { - status: 'needs_first_factor', - supportedFirstFactors: supportedFirstFactors.map(f => ({ - strategy: 'email_code', - emailAddressId: 'idn_foo', - ...f, - })), - supportedSecondFactors: null, - identifier: identifier, - }, - }, - }, - }, - } as unknown as SignInRouterSnapshot; -} - -describe('SignInSafeIdentifierSelectorForStrategy', () => { - describe('Match: Identifier', () => { - it('should output support@clerk.dev (matchingFactorForIdentifier.safeIdentifier)', () => { - const snapshot = createSnapshot( - [ - { - safeIdentifier: 's******@c****.com', - }, - { - safeIdentifier: IDENTIFIER, - primary: true, - }, - ], - IDENTIFIER, - ); - - const result = SignInSafeIdentifierSelectorForStrategy('email_code')(snapshot); - expect(result).toEqual(IDENTIFIER); - }); - }); - - describe('Match: Strategy', () => { - it('should output support@clerk.dev (matchingFactorForStrategy.safeIdentifier)', () => { - const snapshot = createSnapshot( - [ - { - safeIdentifier: 's******@c****.com', - }, - { - safeIdentifier: IDENTIFIER, - primary: true, - }, - ], - IDENTIFIER, - ); - - const result = SignInSafeIdentifierSelectorForStrategy('email_code')(snapshot); - expect(result).toEqual(IDENTIFIER); - }); - - it('should output s*****1@c****.com (matchingFactorForStrategy.safeIdentifier)', () => { - const snapshot = createSnapshot( - [ - { - safeIdentifier: 's*****1@c****.com', - }, - { - safeIdentifier: 's*****2@c****.com', - primary: true, - }, - ], - IDENTIFIER, - ); - - const result = SignInSafeIdentifierSelectorForStrategy('email_code')(snapshot); - expect(result).toEqual('s*****1@c****.com'); - }); - }); - - describe('Match: Default', () => { - it('should output support@clerk.dev (signIn.identifier)', () => { - const snapshot = createSnapshot([], IDENTIFIER); - const result = SignInSafeIdentifierSelectorForStrategy('email_code')(snapshot); - expect(result).toEqual(IDENTIFIER); - }); - - it('should output an empty string', () => { - const snapshot = createSnapshot([], undefined); - const result = SignInSafeIdentifierSelectorForStrategy('email_code')(snapshot); - expect(result).toEqual(''); - }); - }); -}); diff --git a/packages/elements/src/internals/machines/sign-in/index.ts b/packages/elements/src/internals/machines/sign-in/index.ts deleted file mode 100644 index 4aa5a84e817..00000000000 --- a/packages/elements/src/internals/machines/sign-in/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export { SignInFirstFactorMachine, SignInSecondFactorMachine } from './verification.machine'; -export { SignInRouterMachine, SignInRouterMachineId } from './router.machine'; -export { SignInStartMachine, SignInStartMachineId } from './start.machine'; -export { SignInResetPasswordMachine, SignInResetPasswordMachineId } from './reset-password.machine'; - -export { SignInSafeIdentifierSelectorForStrategy, SignInSalutationSelector } from './router.selectors'; - -export type { TSignInRouterMachine } from './router.machine'; -export type { TSignInStartMachine } from './start.machine'; -export type { TSignInFirstFactorMachine, TSignInSecondFactorMachine } from './verification.machine'; -export type { TSignInResetPasswordMachine } from './reset-password.machine'; - -export * from './verification.types'; -export * from './router.types'; -export * from './start.types'; -export * from './reset-password.types'; diff --git a/packages/elements/src/internals/machines/sign-in/reset-password.machine.ts b/packages/elements/src/internals/machines/sign-in/reset-password.machine.ts deleted file mode 100644 index 7da26caae5a..00000000000 --- a/packages/elements/src/internals/machines/sign-in/reset-password.machine.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { SignInResource } from '@clerk/types'; -import type { DoneActorEvent } from 'xstate'; -import { fromPromise, sendTo, setup } from 'xstate'; - -import type { FormFields } from '~/internals/machines/form'; -import { sendToLoading } from '~/internals/machines/shared'; -import { assertActorEventError } from '~/internals/machines/utils/assert'; - -import type { SignInResetPasswordSchema } from './reset-password.types'; -import type { SignInRouterMachineActorRef } from './router.types'; - -export type TSignInResetPasswordMachine = typeof SignInResetPasswordMachine; - -export const SignInResetPasswordMachineId = 'SignInResetPasswordMachine'; - -export const SignInResetPasswordMachine = setup({ - actors: { - attempt: fromPromise( - ({ input: { fields, parent } }) => { - const password = (fields.get('password')?.value as string) || ''; - const signOutOfOtherSessions = fields.get('signOutOfOtherSessions')?.checked || false; - return parent.getSnapshot().context.clerk.client.signIn.resetPassword({ password, signOutOfOtherSessions }); - }, - ), - }, - actions: { - sendToLoading, - sendToNext: ({ context, event }) => - context.parent.send({ type: 'NEXT', resource: (event as unknown as DoneActorEvent).output }), - setFormErrors: sendTo( - ({ context }) => context.formRef, - ({ event }) => { - assertActorEventError(event); - return { - type: 'ERRORS.SET', - error: event.error, - }; - }, - ), - }, - types: {} as SignInResetPasswordSchema, -}).createMachine({ - id: SignInResetPasswordMachineId, - context: ({ input }) => ({ - loadingStep: 'reset-password', - parent: input.parent, - formRef: input.formRef, - }), - initial: 'Pending', - states: { - Pending: { - tags: ['state:pending'], - description: 'Waiting for user input', - on: { - SUBMIT: { - target: 'Attempting', - reenter: true, - }, - }, - }, - Attempting: { - tags: ['state:attempting', 'state:loading'], - entry: 'sendToLoading', - invoke: { - id: 'attempt', - src: 'attempt', - input: ({ context }) => ({ - parent: context.parent, - fields: context.formRef.getSnapshot().context.fields, - }), - onDone: { - actions: ['sendToNext', 'sendToLoading'], - }, - onError: { - actions: ['setFormErrors', 'sendToLoading'], - target: 'Pending', - }, - }, - }, - }, -}); diff --git a/packages/elements/src/internals/machines/sign-in/reset-password.types.ts b/packages/elements/src/internals/machines/sign-in/reset-password.types.ts deleted file mode 100644 index a6b9b2d16ea..00000000000 --- a/packages/elements/src/internals/machines/sign-in/reset-password.types.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { ClerkAPIResponseError } from '@clerk/shared/error'; -import type { ActorRefFrom, ErrorActorEvent } from 'xstate'; - -import type { FormMachine } from '~/internals/machines/form'; - -import type { SignInRouterMachineActorRef } from './router.types'; - -// ---------------------------------- Tags ---------------------------------- // - -export type SignInResetPasswordTags = 'state:pending' | 'state:attempting' | 'state:loading'; - -// ---------------------------------- Events ---------------------------------- // - -export type SignInResetPasswordSubmitEvent = { type: 'SUBMIT'; action: 'submit' }; - -export type SignInResetPasswordEvents = ErrorActorEvent | SignInResetPasswordSubmitEvent; - -// ---------------------------------- Input ---------------------------------- // - -export type SignInResetPasswordInput = { - formRef: ActorRefFrom; - parent: SignInRouterMachineActorRef; -}; - -// ---------------------------------- Context ---------------------------------- // - -export interface SignInResetPasswordContext { - error?: Error | ClerkAPIResponseError; - formRef: ActorRefFrom; - loadingStep: 'reset-password'; - parent: SignInRouterMachineActorRef; -} - -// ---------------------------------- Schema ---------------------------------- // - -export interface SignInResetPasswordSchema { - context: SignInResetPasswordContext; - input: SignInResetPasswordInput; - events: SignInResetPasswordEvents; - tags: SignInResetPasswordTags; -} diff --git a/packages/elements/src/internals/machines/sign-in/router.machine.ts b/packages/elements/src/internals/machines/sign-in/router.machine.ts deleted file mode 100644 index d012894beb1..00000000000 --- a/packages/elements/src/internals/machines/sign-in/router.machine.ts +++ /dev/null @@ -1,658 +0,0 @@ -import { joinURL } from '@clerk/shared/url'; -import { isWebAuthnAutofillSupported } from '@clerk/shared/webauthn'; -import type { SignInStatus } from '@clerk/types'; -import type { NonReducibleUnknown } from 'xstate'; -import { and, assign, enqueueActions, fromPromise, log, not, or, raise, sendTo, setup } from 'xstate'; - -import { - CHOOSE_SESSION_PATH_ROUTE, - ERROR_CODES, - ROUTING, - SEARCH_PARAMS, - SIGN_IN_DEFAULT_BASE_PATH, - SIGN_UP_DEFAULT_BASE_PATH, - SSO_CALLBACK_PATH_ROUTE, -} from '~/internals/constants'; -import { ClerkElementsError, ClerkElementsRuntimeError } from '~/internals/errors'; -import { ThirdPartyMachine, ThirdPartyMachineId } from '~/internals/machines/third-party'; -import { shouldUseVirtualRouting } from '~/internals/machines/utils/next'; - -import { FormMachine } from '../form'; -import { SignInResetPasswordMachine } from './reset-password.machine'; -import type { - SignInRouterContext, - SignInRouterEvents, - SignInRouterNextEvent, - SignInRouterSchema, - SignInRouterSessionSetActiveEvent, -} from './router.types'; -import { SignInStartMachine } from './start.machine'; -import { SignInFirstFactorMachine, SignInSecondFactorMachine } from './verification.machine'; - -export type TSignInRouterMachine = typeof SignInRouterMachine; - -const isCurrentPath = - (path: `/${string}`) => - ({ context }: { context: SignInRouterContext }, _params?: NonReducibleUnknown) => { - return context.router?.match(path) ?? false; - }; - -const needsStatus = - (status: SignInStatus) => - ({ context, event }: { context: SignInRouterContext; event?: SignInRouterEvents }, _?: NonReducibleUnknown) => - (event as SignInRouterNextEvent)?.resource?.status === status || context.clerk?.client.signIn.status === status; - -export const SignInRouterMachineId = 'SignInRouter'; - -export const SignInRouterMachine = setup({ - actors: { - firstFactorMachine: SignInFirstFactorMachine, - formMachine: FormMachine, - resetPasswordMachine: SignInResetPasswordMachine, - startMachine: SignInStartMachine, - secondFactorMachine: SignInSecondFactorMachine, - thirdPartyMachine: ThirdPartyMachine, - webAuthnAutofillSupport: fromPromise(() => isWebAuthnAutofillSupported()), - }, - actions: { - clearFormErrors: sendTo(({ context }) => context.formRef, { type: 'ERRORS.CLEAR' }), - navigateInternal: ({ context }, { path, force = false }: { path: string; force?: boolean }) => { - if (!context.router) { - return; - } - if (!force && shouldUseVirtualRouting()) { - return; - } - if (context.exampleMode) { - return; - } - - const resolvedPath = joinURL(context.router.basePath, path); - if (resolvedPath === context.router.pathname()) { - return; - } - - context.router.shallowPush(resolvedPath); - }, - navigateExternal: ({ context }, { path }: { path: string }) => context.router?.push(path), - raiseNext: raise({ type: 'NEXT' }), - setActive: enqueueActions(({ enqueue, check, context, event }) => { - if (check('isExampleMode')) { - return; - } - - const id = (event as SignInRouterSessionSetActiveEvent)?.id; - const lastActiveSessionId = context.clerk.client.lastActiveSessionId; - const createdSessionId = ((event as SignInRouterNextEvent)?.resource || context.clerk.client.signIn) - .createdSessionId; - - const session = id || createdSessionId || lastActiveSessionId || null; - - void context.clerk.setActive({ - session, - redirectUrl: context.clerk.buildAfterSignInUrl({ - params: context.router?.searchParams(), - }), - }); - - enqueue.raise({ type: 'RESET' }, { delay: 2000 }); // Reset machine after 2s delay. - }), - setError: assign({ - error: (_, { error }: { error?: ClerkElementsError }) => { - if (error) { - return error; - } - return new ClerkElementsRuntimeError('Unknown error'); - }, - }), - setFormErrors: ({ context }, params: { error: Error }) => - sendTo(context.formRef, { - type: 'ERRORS.SET', - error: params.error, - }), - setFormOAuthErrors: ({ context }) => { - const errorOrig = context.clerk.client.signIn.firstFactorVerification.error; - - if (!errorOrig) { - return; - } - - let error: ClerkElementsError; - - switch (errorOrig.code) { - case ERROR_CODES.NOT_ALLOWED_TO_SIGN_UP: - case ERROR_CODES.OAUTH_ACCESS_DENIED: - case ERROR_CODES.NOT_ALLOWED_ACCESS: - case ERROR_CODES.SAML_USER_ATTRIBUTE_MISSING: - case ERROR_CODES.OAUTH_EMAIL_DOMAIN_RESERVED_BY_SAML: - case ERROR_CODES.USER_LOCKED: - case ERROR_CODES.ENTERPRISE_SSO_USER_ATTRIBUTE_MISSING: - case ERROR_CODES.ENTERPRISE_SSO_EMAIL_ADDRESS_DOMAIN_MISMATCH: - case ERROR_CODES.ENTERPRISE_SSO_HOSTED_DOMAIN_MISMATCH: - case ERROR_CODES.SAML_EMAIL_ADDRESS_DOMAIN_MISMATCH: - case ERROR_CODES.ORGANIZATION_MEMBERSHIP_QUOTA_EXCEEDED_FOR_SSO: - error = new ClerkElementsError(errorOrig.code, errorOrig.longMessage || ''); - break; - default: - error = new ClerkElementsError( - 'unable_to_complete', - 'Unable to complete action at this time. If the problem persists please contact support.', - ); - } - - context.formRef.send({ - type: 'ERRORS.SET', - error, - }); - }, - transfer: ({ context }) => { - const searchParams = new URLSearchParams({ __clerk_transfer: '1' }); - context.router?.push(`${context.signUpPath}?${searchParams}`); - }, - }, - guards: { - hasAuthenticatedViaClerkJS: ({ context }) => - Boolean(context.clerk.client.signIn.status === null && context.clerk.client.lastActiveSessionId), - hasOAuthError: ({ context }) => Boolean(context.clerk?.client?.signIn?.firstFactorVerification?.error), - hasResource: ({ context }) => Boolean(context.clerk?.client?.signIn?.status), - hasTicket: ({ context }) => Boolean(context.ticket), - - isLoggedInAndSingleSession: and(['isLoggedIn', 'isSingleSessionMode', not('isExampleMode')]), - isActivePathRoot: isCurrentPath('/'), - isComplete: ({ context, event }) => { - const resource = (event as SignInRouterNextEvent)?.resource; - const signIn = context.clerk.client.signIn; - - return ( - (resource?.status === 'complete' && Boolean(resource?.createdSessionId)) || - (signIn.status === 'complete' && Boolean(signIn.createdSessionId)) - ); - }, - isLoggedIn: ({ context }) => Boolean(context.clerk?.user), - isSingleSessionMode: ({ context }) => Boolean(context.clerk?.__unstable__environment?.authConfig.singleSessionMode), - isExampleMode: ({ context }) => Boolean(context.exampleMode), - - needsStart: or([not('hasResource'), 'statusNeedsIdentifier', isCurrentPath('/')]), - needsFirstFactor: and(['statusNeedsFirstFactor', isCurrentPath('/continue')]), - needsSecondFactor: and(['statusNeedsSecondFactor', isCurrentPath('/continue')]), - needsCallback: isCurrentPath(SSO_CALLBACK_PATH_ROUTE), - needsChooseSession: isCurrentPath(CHOOSE_SESSION_PATH_ROUTE), - needsNewPassword: and(['statusNeedsNewPassword', isCurrentPath('/new-password')]), - - statusNeedsIdentifier: needsStatus('needs_identifier'), - statusNeedsFirstFactor: needsStatus('needs_first_factor'), - statusNeedsSecondFactor: needsStatus('needs_second_factor'), - statusNeedsNewPassword: needsStatus('needs_new_password'), - }, - types: {} as SignInRouterSchema, -}).createMachine({ - id: SignInRouterMachineId, - // @ts-expect-error - Set in INIT event - context: {}, - initial: 'Idle', - on: { - 'AUTHENTICATE.OAUTH': { - actions: sendTo(ThirdPartyMachineId, ({ context, event }) => ({ - type: 'REDIRECT', - params: { - strategy: event.strategy, - redirectUrl: `${ - context.router?.mode === ROUTING.virtual - ? context.clerk.__unstable__environment?.displayConfig.signInUrl - : context.router?.basePath - }${SSO_CALLBACK_PATH_ROUTE}`, - redirectUrlComplete: context.clerk.buildAfterSignInUrl({ - params: context.router?.searchParams(), - }), - }, - })), - }, - 'AUTHENTICATE.SAML': { - actions: sendTo(ThirdPartyMachineId, ({ context }) => ({ - type: 'REDIRECT', - params: { - strategy: 'saml', - identifier: context.formRef.getSnapshot().context.fields.get('identifier')?.value, - redirectUrl: `${ - context.router?.mode === ROUTING.virtual - ? context.clerk.__unstable__environment?.displayConfig.signInUrl - : context.router?.basePath - }${SSO_CALLBACK_PATH_ROUTE}`, - redirectUrlComplete: context.clerk.buildAfterSignInUrl({ - params: context.router?.searchParams(), - }), - }, - })), - }, - 'AUTHENTICATE.ENTERPRISE_SSO': { - actions: sendTo(ThirdPartyMachineId, ({ context }) => ({ - type: 'REDIRECT', - params: { - strategy: 'enterprise_sso', - identifier: context.formRef.getSnapshot().context.fields.get('identifier')?.value, - redirectUrl: `${ - context.router?.mode === ROUTING.virtual - ? context.clerk.__unstable__environment?.displayConfig.signInUrl - : context.router?.basePath - }${SSO_CALLBACK_PATH_ROUTE}`, - redirectUrlComplete: context.clerk.buildAfterSignInUrl({ - params: context.router?.searchParams(), - }), - }, - })), - }, - 'FORM.ATTACH': { - description: 'Attach/re-attach the form to the router.', - actions: enqueueActions(({ enqueue, event }) => { - enqueue.assign({ - formRef: event.formRef, - }); - - // Reset the current step, to reset the form reference. - enqueue.raise({ type: 'RESET.STEP' }); - }), - }, - 'NAVIGATE.PREVIOUS': '.Hist', - 'NAVIGATE.START': '.Start', - LOADING: { - actions: assign(({ event }) => ({ - loading: { - isLoading: event.isLoading, - step: event.step, - strategy: event.strategy, - action: event.action, - }, - })), - }, - RESET: '.Idle', - }, - states: { - Idle: { - invoke: { - id: 'webAuthnAutofill', - src: 'webAuthnAutofillSupport', - onDone: { - actions: assign({ webAuthnAutofillSupport: ({ event }) => event.output }), - }, - }, - on: { - INIT: { - actions: assign(({ event }) => { - const searchParams = event.router?.searchParams(); - - return { - clerk: event.clerk, - exampleMode: event.exampleMode || false, - formRef: event.formRef, - loading: { - isLoading: false, - }, - router: event.router, - signUpPath: event.signUpPath || SIGN_UP_DEFAULT_BASE_PATH, - ticket: searchParams?.get(SEARCH_PARAMS.ticket) || undefined, - }; - }), - target: 'Init', - }, - }, - }, - Init: { - entry: enqueueActions(({ context, enqueue, self }) => { - if (!self.getSnapshot().children[ThirdPartyMachineId]) { - enqueue.spawnChild('thirdPartyMachine', { - id: ThirdPartyMachineId, - systemId: ThirdPartyMachineId, - input: { - basePath: context.router?.basePath ?? SIGN_IN_DEFAULT_BASE_PATH, - flow: 'signIn', - formRef: context.formRef, - parent: self, - }, - }); - } - }), - always: [ - { - guard: 'needsCallback', - target: 'Callback', - }, - { - guard: 'needsChooseSession', - target: 'ChooseSession', - }, - { - guard: 'isComplete', - actions: 'setActive', - }, - { - guard: 'isLoggedInAndSingleSession', - actions: [ - log('Already logged in'), - { - type: 'navigateExternal', - params: ({ context }) => ({ - path: context.clerk.buildAfterSignInUrl({ - params: context.router?.searchParams(), - }), - }), - }, - ], - }, - { - guard: 'needsStart', - actions: { type: 'navigateInternal', params: { force: true, path: '/' } }, - target: 'Start', - }, - { - guard: 'needsFirstFactor', - actions: { type: 'navigateInternal', params: { force: true, path: '/continue' } }, - target: 'FirstFactor', - }, - { - guard: 'needsSecondFactor', - actions: { type: 'navigateInternal', params: { force: true, path: '/continue' } }, - target: 'SecondFactor', - }, - { - guard: 'needsNewPassword', - actions: { type: 'navigateInternal', params: { force: true, path: '/reset-password' } }, - target: 'ResetPassword', - }, - { - actions: { type: 'navigateInternal', params: { force: true, path: '/' } }, - target: 'Start', - }, - { - guard: 'hasTicket', - actions: { type: 'navigateInternal', params: { force: true, path: '/' } }, - target: 'Start', - }, - ], - }, - Start: { - tags: ['step:start'], - exit: 'clearFormErrors', - invoke: { - id: 'start', - src: 'startMachine', - input: ({ context, self }) => ({ - basePath: context.router?.basePath, - formRef: context.formRef, - parent: self, - ticket: context.ticket, - }), - onDone: { - actions: 'raiseNext', - }, - }, - on: { - 'RESET.STEP': { - target: 'Start', - reenter: true, - }, - 'AUTHENTICATE.PASSKEY': { - actions: sendTo('start', ({ event }) => event), - }, - 'AUTHENTICATE.PASSKEY.AUTOFILL': { - actions: sendTo('start', ({ event }) => event), - }, - 'AUTHENTICATE.WEB3': { - actions: sendTo('start', ({ event }) => event), - }, - NEXT: [ - { - guard: 'isComplete', - actions: 'setActive', - }, - { - guard: 'statusNeedsFirstFactor', - actions: { type: 'navigateInternal', params: { path: '/continue' } }, - target: 'FirstFactor', - }, - { - guard: 'statusNeedsSecondFactor', - actions: { type: 'navigateInternal', params: { path: '/continue' } }, - target: 'SecondFactor', - }, - { - guard: 'statusNeedsNewPassword', - actions: { type: 'navigateInternal', params: { path: '/reset-password' } }, - target: 'ResetPassword', - }, - ], - }, - }, - FirstFactor: { - tags: ['step:first-factor', 'step:verifications'], - invoke: { - id: 'firstFactor', - src: 'firstFactorMachine', - input: ({ context, self }) => ({ - formRef: context.formRef, - parent: self, - basePath: context.router?.basePath, - }), - onDone: { - actions: 'raiseNext', - }, - }, - on: { - 'AUTHENTICATE.PASSKEY': { - actions: sendTo('firstFactor', ({ event }) => event), - }, - 'RESET.STEP': { - target: 'FirstFactor', - reenter: true, - }, - NEXT: [ - { - guard: 'isComplete', - actions: 'setActive', - }, - { - guard: 'statusNeedsSecondFactor', - actions: { type: 'navigateInternal', params: { path: '/continue' } }, - target: 'SecondFactor', - }, - { - guard: 'statusNeedsNewPassword', - actions: { type: 'navigateInternal', params: { path: '/reset-password' } }, - target: 'ResetPassword', - }, - ], - 'STRATEGY.UPDATE': { - description: 'Send event to verification machine to update the current strategy.', - actions: sendTo('firstFactor', ({ event }) => event), - target: '.Idle', - }, - }, - initial: 'Idle', - states: { - Idle: { - on: { - 'NAVIGATE.FORGOT_PASSWORD': { - description: 'Navigate to forgot password screen.', - actions: sendTo('firstFactor', ({ event }) => event), - target: 'ForgotPassword', - }, - 'NAVIGATE.CHOOSE_STRATEGY': { - description: 'Navigate to choose strategy screen.', - actions: sendTo('firstFactor', ({ event }) => event), - target: 'ChoosingStrategy', - }, - }, - }, - ChoosingStrategy: { - tags: ['step:choose-strategy'], - on: { - 'NAVIGATE.PREVIOUS': { - description: 'Go to Idle, and also tell firstFactor to go to Pending', - target: 'Idle', - actions: sendTo('firstFactor', { type: 'NAVIGATE.PREVIOUS' }), - }, - }, - }, - ForgotPassword: { - tags: ['step:forgot-password'], - on: { - 'NAVIGATE.PREVIOUS': 'Idle', - }, - }, - }, - }, - SecondFactor: { - tags: ['step:second-factor', 'step:verifications'], - invoke: { - id: 'secondFactor', - src: 'secondFactorMachine', - input: ({ context, self }) => ({ - formRef: context.formRef, - parent: self, - }), - onDone: { - actions: 'raiseNext', - }, - }, - on: { - 'RESET.STEP': { - target: 'SecondFactor', - reenter: true, - }, - NEXT: [ - { - guard: 'isComplete', - actions: 'setActive', - }, - { - guard: 'statusNeedsNewPassword', - actions: { type: 'navigateInternal', params: { path: '/reset-password' } }, - target: 'ResetPassword', - }, - ], - 'STRATEGY.UPDATE': { - description: 'Send event to verification machine to update the current strategy.', - actions: sendTo('secondFactor', ({ event }) => event), - target: '.Idle', - }, - }, - initial: 'Idle', - states: { - Idle: { - on: { - 'NAVIGATE.CHOOSE_STRATEGY': { - description: 'Navigate to choose strategy screen.', - actions: sendTo('secondFactor', ({ event }) => event), - target: 'ChoosingStrategy', - }, - }, - }, - ChoosingStrategy: { - tags: ['step:choose-strategy'], - on: { - 'NAVIGATE.PREVIOUS': { - description: 'Go to Idle, and also tell firstFactor to go to Pending', - target: 'Idle', - actions: sendTo('secondFactor', { type: 'NAVIGATE.PREVIOUS' }), - }, - }, - }, - }, - }, - ResetPassword: { - tags: ['step:reset-password'], - invoke: { - id: 'resetPassword', - src: 'resetPasswordMachine', - input: ({ context, self }) => ({ - formRef: context.formRef, - parent: self, - }), - onDone: { - actions: 'raiseNext', - }, - }, - on: { - 'RESET.STEP': { - target: 'ResetPassword', - reenter: true, - }, - NEXT: [ - { - guard: 'isComplete', - actions: 'setActive', - }, - { - guard: 'statusNeedsFirstFactor', - actions: { type: 'navigateInternal', params: { path: '/continue' } }, - target: 'FirstFactor', - }, - { - guard: 'statusNeedsSecondFactor', - actions: { type: 'navigateInternal', params: { path: '/continue' } }, - target: 'SecondFactor', - }, - ], - }, - }, - Callback: { - tags: ['step:callback'], - entry: sendTo(ThirdPartyMachineId, { type: 'CALLBACK' }), - on: { - NEXT: [ - { - guard: 'hasOAuthError', - actions: ['setFormOAuthErrors', { type: 'navigateInternal', params: { force: true, path: '/' } }], - target: 'Start', - }, - { - guard: or(['isLoggedIn', 'isComplete', 'hasAuthenticatedViaClerkJS']), - actions: 'setActive', - }, - { - guard: 'statusNeedsIdentifier', - actions: 'transfer', - }, - { - guard: 'statusNeedsFirstFactor', - actions: { type: 'navigateInternal', params: { path: '/continue' } }, - target: 'FirstFactor', - }, - { - guard: 'statusNeedsSecondFactor', - actions: { type: 'navigateInternal', params: { path: '/continue' } }, - target: 'SecondFactor', - }, - { - guard: 'statusNeedsNewPassword', - actions: { type: 'navigateInternal', params: { path: '/reset-password' } }, - target: 'ResetPassword', - }, - ], - }, - }, - ChooseSession: { - tags: ['step:choose-session'], - on: { - 'SESSION.SET_ACTIVE': { - actions: { - type: 'setActive', - params: ({ event }) => ({ id: event.id }), - }, - }, - }, - }, - Error: { - tags: ['step:error'], - on: { - NEXT: { - target: 'Start', - actions: 'clearFormErrors', - }, - }, - }, - Hist: { - type: 'history', - exit: 'clearFormErrors', - }, - }, -}); diff --git a/packages/elements/src/internals/machines/sign-in/router.selectors.ts b/packages/elements/src/internals/machines/sign-in/router.selectors.ts deleted file mode 100644 index 801c78d835f..00000000000 --- a/packages/elements/src/internals/machines/sign-in/router.selectors.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { SignInStrategyName } from '~/internals/machines/shared'; -import { formatSalutation } from '~/internals/machines/utils/formatters'; - -import type { SignInRouterSnapshot } from './router.types'; - -export function SignInSafeIdentifierSelectorForStrategy( - strategy: SignInStrategyName | undefined, -): (s: SignInRouterSnapshot) => string { - return (s: SignInRouterSnapshot) => { - const signIn = s.context.clerk?.client.signIn; - - if (strategy) { - const matchingFactors = [ - ...(signIn.supportedFirstFactors ?? []), - ...(signIn.supportedSecondFactors ?? []), - ].filter(f => f.strategy === strategy); - - const matchingFactorForIdentifier = - signIn.identifier && matchingFactors.length > 0 - ? matchingFactors.find(f => 'safeIdentifier' in f && f.safeIdentifier === signIn.identifier) - : null; - - const matchingFactorForStrategy = matchingFactors[0]; - - if (matchingFactorForIdentifier && 'safeIdentifier' in matchingFactorForIdentifier) { - return matchingFactorForIdentifier.safeIdentifier; - } - - if (matchingFactorForStrategy && 'safeIdentifier' in matchingFactorForStrategy) { - return matchingFactorForStrategy.safeIdentifier; - } - } - - return signIn.identifier || ''; - }; -} - -export function SignInSalutationSelector(s: SignInRouterSnapshot): string { - const signIn = s.context.clerk?.client.signIn; - - return formatSalutation({ - firstName: signIn?.userData?.firstName, - identifier: signIn?.identifier, - lastName: signIn?.userData?.lastName, - }); -} diff --git a/packages/elements/src/internals/machines/sign-in/router.types.ts b/packages/elements/src/internals/machines/sign-in/router.types.ts deleted file mode 100644 index d084c173f18..00000000000 --- a/packages/elements/src/internals/machines/sign-in/router.types.ts +++ /dev/null @@ -1,168 +0,0 @@ -import type { SignInResource } from '@clerk/types'; -import type { ActorRefFrom, MachineSnapshot, StateMachine } from 'xstate'; - -import type { TFormMachine } from '~/internals/machines/form'; -import type { - BaseRouterContext, - BaseRouterErrorEvent, - BaseRouterFormAttachEvent, - BaseRouterInput, - BaseRouterLoadingEvent, - BaseRouterNextEvent, - BaseRouterPrevEvent, - BaseRouterRedirectEvent, - BaseRouterResetEvent, - BaseRouterResetStepEvent, - BaseRouterSetClerkEvent, - BaseRouterStartEvent, - BaseRouterTransferEvent, -} from '~/internals/machines/types'; - -import type { SignInVerificationFactorUpdateEvent } from './verification.types'; - -// ---------------------------------- Tags ---------------------------------- // - -export const SignInRouterSteps = { - start: 'step:start', - verifications: 'step:verifications', - firstFactor: 'step:first-factor', - secondFactor: 'step:second-factor', - callback: 'step:callback', - error: 'step:error', - forgotPassword: 'step:forgot-password', - resetPassword: 'step:reset-password', - chooseSession: 'step:choose-session', - chooseStrategy: 'step:choose-strategy', -} as const; - -export type SignInRouterSteps = keyof typeof SignInRouterSteps; -export type SignInRouterTags = (typeof SignInRouterSteps)[keyof typeof SignInRouterSteps]; - -// ---------------------------------- Children ---------------------------------- // - -export const SignInRouterSystemId = { - start: 'start', - firstFactor: 'firstFactor', - secondFactor: 'secondFactor', - resetPassword: 'resetPassword', -} as const; - -export type SignInRouterSystemId = keyof typeof SignInRouterSystemId; - -// ---------------------------------- Events ---------------------------------- // - -export type SignInRouterFormAttachEvent = BaseRouterFormAttachEvent; -export type SignInRouterNextEvent = BaseRouterNextEvent; -export type SignInRouterStartEvent = BaseRouterStartEvent; -export type SignInRouterPrevEvent = BaseRouterPrevEvent; -export type SignInRouterChooseStrategyEvent = { type: 'NAVIGATE.CHOOSE_STRATEGY' }; -export type SignInRouterForgotPasswordEvent = { type: 'NAVIGATE.FORGOT_PASSWORD' }; -export type SignInRouterErrorEvent = BaseRouterErrorEvent; -export type SignInRouterTransferEvent = BaseRouterTransferEvent; -export type SignInRouterRedirectEvent = BaseRouterRedirectEvent; -export type SignInRouterResetEvent = BaseRouterResetEvent; -export type SignInRouterResetStepEvent = BaseRouterResetStepEvent; -export type SignInRouterLoadingEvent = BaseRouterLoadingEvent< - 'start' | 'verifications' | 'reset-password' | 'forgot-password' | 'choose-strategy' ->; -export type SignInRouterSetClerkEvent = BaseRouterSetClerkEvent; -export type SignInRouterSubmitEvent = { type: 'SUBMIT' }; -export type SignInRouterPasskeyEvent = { type: 'AUTHENTICATE.PASSKEY' }; -export type SignInRouterPasskeyAutofillEvent = { - type: 'AUTHENTICATE.PASSKEY.AUTOFILL'; -}; -export type SignInRouterSessionSetActiveEvent = { type: 'SESSION.SET_ACTIVE'; id: string }; - -export interface SignInRouterInitEvent extends BaseRouterInput { - type: 'INIT'; - formRef: ActorRefFrom; - signUpPath?: string; -} - -export type SignInRouterNavigationEvents = - | SignInRouterStartEvent - | SignInRouterChooseStrategyEvent - | SignInRouterForgotPasswordEvent - | SignInRouterPrevEvent; - -export type SignInRouterEvents = - | SignInRouterFormAttachEvent - | SignInRouterInitEvent - | SignInRouterNextEvent - | SignInRouterNavigationEvents - | SignInRouterErrorEvent - | SignInRouterTransferEvent - | SignInRouterRedirectEvent - | SignInRouterResetEvent - | SignInRouterResetStepEvent - | SignInVerificationFactorUpdateEvent - | SignInRouterLoadingEvent - | SignInRouterSessionSetActiveEvent - | SignInRouterSetClerkEvent - | SignInRouterSubmitEvent - | SignInRouterPasskeyEvent - | SignInRouterPasskeyAutofillEvent; - -// ---------------------------------- Context ---------------------------------- // - -export type SignInRouterLoadingContext = Omit; - -export interface SignInRouterContext extends BaseRouterContext { - formRef: ActorRefFrom; - loading: SignInRouterLoadingContext; - signUpPath: string; - webAuthnAutofillSupport: boolean; - ticket: string | undefined; -} - -// ---------------------------------- Input ---------------------------------- // - -export type SignInRouterInput = object; - -// ---------------------------------- Schema ---------------------------------- // - -export interface SignInRouterSchema { - context: SignInRouterContext; - events: SignInRouterEvents; - tags: SignInRouterTags; -} - -// ---------------------------------- Schema ---------------------------------- // - -export type SignInRouterChildren = any; // TODO: Update -export type SignInRouterOuptut = any; // TODO: Update -export type SignInRouterStateValue = any; // TODO: Update - -export type SignInRouterSnapshot = MachineSnapshot< - SignInRouterContext, - SignInRouterEvents, - SignInRouterChildren, - SignInRouterStateValue, - SignInRouterTags, - SignInRouterOuptut, - any, // TMeta - Introduced in XState 5.12.x - any // TConfig - Required in newer XState versions ->; - -// ---------------------------------- Machine Type ---------------------------------- // - -export type TSignInRouterParentMachine = StateMachine< - SignInRouterContext, // context - SignInRouterEvents, // event - SignInRouterChildren, // children - any, // actor - any, // action - any, // guard - any, // delay - any, // state value - string, // tag - any, // input - SignInRouterOuptut, // output - any, // emitted - any, // meta - any // config ->; - -// ---------------------------------- Machine Actor Ref ---------------------------------- // - -export type SignInRouterMachineActorRef = ActorRefFrom; diff --git a/packages/elements/src/internals/machines/sign-in/start.machine.ts b/packages/elements/src/internals/machines/sign-in/start.machine.ts deleted file mode 100644 index 8a9f3a9e657..00000000000 --- a/packages/elements/src/internals/machines/sign-in/start.machine.ts +++ /dev/null @@ -1,261 +0,0 @@ -import type { SignInResource, Web3Strategy } from '@clerk/types'; -import { assertEvent, enqueueActions, fromPromise, not, sendTo, setup } from 'xstate'; - -import { SIGN_IN_DEFAULT_BASE_PATH } from '~/internals/constants'; -import { ClerkElementsRuntimeError } from '~/internals/errors'; -import type { FormFields } from '~/internals/machines/form'; -import { sendToLoading } from '~/internals/machines/shared'; -import { assertActorEventError } from '~/internals/machines/utils/assert'; - -import type { SignInRouterMachineActorRef } from './router.types'; -import type { SignInStartSchema } from './start.types'; - -const DISABLEABLE_FIELDS = ['emailAddress', 'phoneNumber'] as const; - -export type TSignInStartMachine = typeof SignInStartMachine; - -export const SignInStartMachineId = 'SignInStart'; - -type AttemptParams = { strategy: 'ticket'; ticket: string } | { strategy?: never; ticket?: never }; - -export const SignInStartMachine = setup({ - actors: { - attemptPasskey: fromPromise< - SignInResource, - { parent: SignInRouterMachineActorRef; flow: 'autofill' | 'discoverable' | undefined } - >(({ input: { parent, flow } }) => { - return parent.getSnapshot().context.clerk.client.signIn.authenticateWithPasskey({ - flow, - }); - }), - attemptWeb3: fromPromise( - ({ input: { parent, strategy } }) => { - if (strategy === 'web3_metamask_signature') { - return parent.getSnapshot().context.clerk.client.signIn.authenticateWithMetamask(); - } - if (strategy === 'web3_coinbase_wallet_signature') { - return parent.getSnapshot().context.clerk.client.signIn.authenticateWithCoinbaseWallet(); - } - if (strategy === 'web3_base_signature') { - return parent.getSnapshot().context.clerk.client.signIn.authenticateWithBase(); - } - if (strategy === 'web3_okx_wallet_signature') { - return parent.getSnapshot().context.clerk.client.signIn.authenticateWithOKXWallet(); - } - throw new ClerkElementsRuntimeError(`Unsupported Web3 strategy: ${strategy}`); - }, - ), - attempt: fromPromise< - SignInResource, - { parent: SignInRouterMachineActorRef; fields: FormFields; params?: AttemptParams } - >(({ input: { fields, parent, params } }) => { - const clerk = parent.getSnapshot().context.clerk; - - const password = fields.get('password'); - const identifier = fields.get('identifier'); - - const passwordParams = password?.value - ? { - password: password.value, - strategy: 'password', - } - : {}; - - return clerk.client.signIn.create({ - ...passwordParams, - ...(params?.ticket - ? params - : { - identifier: (identifier?.value as string) ?? '', - }), - }); - }), - }, - actions: { - sendToNext: ({ context, event }) => { - // @ts-expect-error -- We're calling this in onDone, and event.output exists on the actor done event - return context.parent.send({ type: 'NEXT', resource: event?.output }); - }, - sendToLoading, - setFormDisabledTicketFields: enqueueActions(({ context, enqueue }) => { - if (!context.ticket) { - return; - } - - const currentFields = context.formRef.getSnapshot().context.fields; - - for (const name of DISABLEABLE_FIELDS) { - if (currentFields.has(name)) { - enqueue.sendTo(context.formRef, { type: 'FIELD.DISABLE', field: { name } }); - } - } - }), - setFormErrors: sendTo( - ({ context }) => context.formRef, - ({ event }) => { - assertActorEventError(event); - return { - type: 'ERRORS.SET', - error: event.error, - }; - }, - ), - }, - guards: { - hasTicket: ({ context }) => Boolean(context.ticket), - isExampleMode: ({ context }) => Boolean(context.parent.getSnapshot().context.exampleMode), - }, - types: {} as SignInStartSchema, -}).createMachine({ - id: SignInStartMachineId, - context: ({ input }) => ({ - basePath: input.basePath || SIGN_IN_DEFAULT_BASE_PATH, - parent: input.parent, - formRef: input.formRef, - loadingStep: 'start', - ticket: input.ticket, - }), - initial: 'Init', - states: { - Init: { - description: 'Handle ticket, if present; Else, default to Pending state.', - always: [ - { - guard: 'hasTicket', - target: 'Attempting', - }, - { - target: 'Pending', - }, - ], - }, - Pending: { - tags: ['state:pending'], - description: 'Waiting for user input', - on: { - SUBMIT: { - guard: not('isExampleMode'), - target: 'Attempting', - reenter: true, - }, - 'AUTHENTICATE.PASSKEY': { - guard: not('isExampleMode'), - target: 'AttemptingPasskey', - reenter: true, - }, - 'AUTHENTICATE.PASSKEY.AUTOFILL': { - guard: not('isExampleMode'), - target: 'AttemptingPasskeyAutoFill', - reenter: false, - }, - 'AUTHENTICATE.WEB3': { - guard: not('isExampleMode'), - target: 'AttemptingWeb3', - reenter: true, - }, - }, - }, - Attempting: { - tags: ['state:attempting', 'state:loading'], - entry: 'sendToLoading', - invoke: { - id: 'attempt', - src: 'attempt', - input: ({ context }) => { - // Standard fields - const defaultParams = { - fields: context.formRef.getSnapshot().context.fields, - parent: context.parent, - }; - - // Handle ticket-specific flows - const params: AttemptParams = context.ticket - ? { - strategy: 'ticket', - ticket: context.ticket, - } - : {}; - - return { ...defaultParams, params }; - }, - onDone: { - actions: ['setFormDisabledTicketFields', 'sendToNext', 'sendToLoading'], - }, - onError: { - actions: ['setFormDisabledTicketFields', 'setFormErrors', 'sendToLoading'], - target: 'Pending', - }, - }, - }, - AttemptingPasskey: { - tags: ['state:attempting', 'state:loading'], - entry: 'sendToLoading', - invoke: { - id: 'attemptPasskey', - src: 'attemptPasskey', - input: ({ context }) => ({ - parent: context.parent, - flow: 'discoverable', - }), - onDone: { - actions: ['sendToNext', 'sendToLoading'], - }, - onError: { - actions: ['setFormErrors', 'sendToLoading'], - target: 'Pending', - }, - }, - }, - AttemptingPasskeyAutoFill: { - on: { - 'AUTHENTICATE.PASSKEY': { - guard: not('isExampleMode'), - target: 'AttemptingPasskey', - reenter: true, - }, - SUBMIT: { - guard: not('isExampleMode'), - target: 'Attempting', - reenter: true, - }, - }, - invoke: { - id: 'attemptPasskeyAutofill', - src: 'attemptPasskey', - input: ({ context }) => ({ - parent: context.parent, - flow: 'autofill', - }), - onDone: { - actions: ['sendToNext'], - }, - onError: { - actions: ['setFormErrors'], - target: 'Pending', - }, - }, - }, - AttemptingWeb3: { - tags: ['state:attempting', 'state:loading'], - entry: 'sendToLoading', - invoke: { - id: 'attemptWeb3', - src: 'attemptWeb3', - input: ({ context, event }) => { - assertEvent(event, 'AUTHENTICATE.WEB3'); - return { - parent: context.parent, - strategy: event.strategy, - }; - }, - onDone: { - actions: ['sendToNext', 'sendToLoading'], - }, - onError: { - actions: ['setFormErrors', 'sendToLoading'], - target: 'Pending', - }, - }, - }, - }, -}); diff --git a/packages/elements/src/internals/machines/sign-in/start.types.ts b/packages/elements/src/internals/machines/sign-in/start.types.ts deleted file mode 100644 index e15b9145357..00000000000 --- a/packages/elements/src/internals/machines/sign-in/start.types.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { ClerkAPIResponseError } from '@clerk/shared/error'; -import type { Web3Strategy } from '@clerk/types'; -import type { ActorRefFrom, DoneActorEvent, ErrorActorEvent } from 'xstate'; - -import type { FormMachine } from '~/internals/machines/form'; - -import type { SignInRouterMachineActorRef } from './router.types'; - -// ---------------------------------- Tags ---------------------------------- // - -export type SignInStartTags = 'state:pending' | 'state:attempting' | 'state:loading'; - -// ---------------------------------- Events ---------------------------------- // - -export type SignInStartSubmitEvent = { type: 'SUBMIT'; action: 'submit' }; -export type SignInStartPasskeyEvent = { type: 'AUTHENTICATE.PASSKEY'; action: 'passkey' }; -export type SignInStartPasskeyAutofillEvent = { type: 'AUTHENTICATE.PASSKEY.AUTOFILL' }; -export type SignInStartWeb3Event = { type: 'AUTHENTICATE.WEB3'; strategy: Web3Strategy }; - -export type SignInStartEvents = - | ErrorActorEvent - | SignInStartSubmitEvent - | SignInStartPasskeyEvent - | SignInStartPasskeyAutofillEvent - | SignInStartWeb3Event - | DoneActorEvent; - -// ---------------------------------- Input ---------------------------------- // - -export type SignInStartInput = { - basePath?: string; - formRef: ActorRefFrom; - parent: SignInRouterMachineActorRef; - ticket?: string | undefined; -}; - -// ---------------------------------- Context ---------------------------------- // - -export interface SignInStartContext { - basePath: string; - error?: Error | ClerkAPIResponseError; - formRef: ActorRefFrom; - parent: SignInRouterMachineActorRef; - loadingStep: 'start'; - ticket?: string | undefined; -} - -// ---------------------------------- Schema ---------------------------------- // - -export interface SignInStartSchema { - context: SignInStartContext; - input: SignInStartInput; - events: SignInStartEvents; - tags: SignInStartTags; -} diff --git a/packages/elements/src/internals/machines/sign-in/utils/index.ts b/packages/elements/src/internals/machines/sign-in/utils/index.ts deleted file mode 100644 index 2065da71a47..00000000000 --- a/packages/elements/src/internals/machines/sign-in/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { determineStartingSignInFactor, determineStartingSignInSecondFactor } from './starting-factors'; diff --git a/packages/elements/src/internals/machines/sign-in/utils/starting-factors.ts b/packages/elements/src/internals/machines/sign-in/utils/starting-factors.ts deleted file mode 100644 index 1035720cef7..00000000000 --- a/packages/elements/src/internals/machines/sign-in/utils/starting-factors.ts +++ /dev/null @@ -1,130 +0,0 @@ -// These utilities are ported from: packages/clerk-js/src/ui/components/SignIn/utils.ts -// They should be functionally identical. -import { isWebAuthnSupported } from '@clerk/shared/webauthn'; -import type { - PreferredSignInStrategy, - SignInFactor, - SignInFirstFactor, - SignInSecondFactor, - SignInStrategy, -} from '@clerk/types'; - -// Factor sorting - https://github.com/clerk/javascript/blob/5764e2911790051589bb5c4f3b1a2c79f7f30c7e/packages/clerk-js/src/ui/utils/factorSorting.ts -const makeSortingOrderMap = (arr: T[]): Record => - arr.reduce( - (acc, k, i) => { - acc[k] = i; - return acc; - }, - {} as Record, - ); - -const STRATEGY_SORT_ORDER_PASSWORD_PREF = makeSortingOrderMap([ - 'passkey', - 'password', - 'email_link', - 'email_code', - 'phone_code', -] as SignInStrategy[]); - -const STRATEGY_SORT_ORDER_OTP_PREF = makeSortingOrderMap([ - 'email_code', - 'email_link', - 'phone_code', - 'passkey', - 'password', -] as SignInStrategy[]); - -const makeSortingFunction = - (sortingMap: Record) => - (a: SignInFactor, b: SignInFactor): number => { - const orderA = sortingMap[a.strategy]; - const orderB = sortingMap[b.strategy]; - if (orderA === undefined || orderB === undefined) { - return 0; - } - return orderA - orderB; - }; - -const passwordPrefFactorComparator = makeSortingFunction(STRATEGY_SORT_ORDER_PASSWORD_PREF); -const otpPrefFactorComparator = makeSortingFunction(STRATEGY_SORT_ORDER_OTP_PREF); - -const findFactorForIdentifier = (i: string | null) => (f: SignInFactor) => { - return 'safeIdentifier' in f && f.safeIdentifier === i; -}; - -// The algorithm can be found at -// https://www.notion.so/clerkdev/Implement-sign-in-alt-methods-e6e60ffb644645b3a0553b50556468ce -export function determineStartingSignInFactor( - firstFactors: SignInFirstFactor[] | null, - identifier: string | null, - preferredSignInStrategy?: PreferredSignInStrategy, -) { - if (!firstFactors || firstFactors.length === 0) { - return null; - } - - return preferredSignInStrategy === 'password' - ? determineStrategyWhenPasswordIsPreferred(firstFactors, identifier) - : determineStrategyWhenOTPIsPreferred(firstFactors, identifier); -} - -function findPasskeyStrategy(factors: SignInFirstFactor[]) { - if (isWebAuthnSupported()) { - const passkeyFactor = factors.find(({ strategy }) => strategy === 'passkey'); - - if (passkeyFactor) { - return passkeyFactor; - } - } - return null; -} - -function determineStrategyWhenPasswordIsPreferred(factors: SignInFirstFactor[], identifier: string | null) { - const passkeyFactor = findPasskeyStrategy(factors); - if (passkeyFactor) { - return passkeyFactor; - } - const selected = factors.sort(passwordPrefFactorComparator)[0]; - if (selected.strategy === 'password') { - return selected; - } - return factors.find(findFactorForIdentifier(identifier)) || selected || null; -} - -function determineStrategyWhenOTPIsPreferred(factors: SignInFirstFactor[], identifier: string | null) { - const passkeyFactor = findPasskeyStrategy(factors); - if (passkeyFactor) { - return passkeyFactor; - } - - const sortedBasedOnPrefFactor = factors.sort(otpPrefFactorComparator); - const forIdentifier = sortedBasedOnPrefFactor.find(findFactorForIdentifier(identifier)); - if (forIdentifier) { - return forIdentifier; - } - const firstBasedOnPref = sortedBasedOnPrefFactor[0]; - if (firstBasedOnPref.strategy === 'email_link') { - return firstBasedOnPref; - } - return factors.find(findFactorForIdentifier(identifier)) || firstBasedOnPref || null; -} - -// The priority of second factors is: TOTP -> Phone code -> any other factor -export function determineStartingSignInSecondFactor(secondFactors: SignInSecondFactor[] | null) { - if (!secondFactors || secondFactors.length === 0) { - return null; - } - - const totpFactor = secondFactors.find(f => f.strategy === 'totp'); - if (totpFactor) { - return totpFactor; - } - - const phoneCodeFactor = secondFactors.find(f => f.strategy === 'phone_code'); - if (phoneCodeFactor) { - return phoneCodeFactor; - } - - return secondFactors[0]; -} diff --git a/packages/elements/src/internals/machines/sign-in/verification.machine.ts b/packages/elements/src/internals/machines/sign-in/verification.machine.ts deleted file mode 100644 index 208adc3b190..00000000000 --- a/packages/elements/src/internals/machines/sign-in/verification.machine.ts +++ /dev/null @@ -1,568 +0,0 @@ -import { isClerkAPIResponseError } from '@clerk/shared/error'; -import type { - AttemptFirstFactorParams, - EmailCodeAttempt, - PasswordAttempt, - PhoneCodeAttempt, - PrepareFirstFactorParams, - PrepareSecondFactorParams, - ResetPasswordEmailCodeAttempt, - ResetPasswordPhoneCodeAttempt, - SignInFirstFactor, - SignInResource, - SignInSecondFactor, - Web3Attempt, -} from '@clerk/types'; -import type { DoneActorEvent } from 'xstate'; -import { assign, fromPromise, log, sendTo, setup } from 'xstate'; - -import { - MAGIC_LINK_VERIFY_PATH_ROUTE, - RESENDABLE_COUNTDOWN_DEFAULT, - SIGN_IN_DEFAULT_BASE_PATH, -} from '~/internals/constants'; -import { ClerkElementsRuntimeError } from '~/internals/errors'; -import type { FormFields } from '~/internals/machines/form'; -import type { SignInStrategyName, WithParams } from '~/internals/machines/shared'; -import { sendToLoading } from '~/internals/machines/shared'; -import { determineStartingSignInFactor, determineStartingSignInSecondFactor } from '~/internals/machines/sign-in/utils'; -import { assertActorEventError, assertIsDefined } from '~/internals/machines/utils/assert'; - -import type { SignInRouterMachineActorRef } from './router.types'; -import type { SignInVerificationSchema } from './verification.types'; -import { SignInVerificationDelays } from './verification.types'; - -export type TSignInFirstFactorMachine = typeof SignInFirstFactorMachine; -export type TSignInSecondFactorMachine = typeof SignInSecondFactorMachine; - -export type DetermineStartingFactorInput = { - parent: SignInRouterMachineActorRef; -}; - -export type PrepareFirstFactorInput = WithParams & { - parent: SignInRouterMachineActorRef; - resendable: boolean; -}; -export type PrepareSecondFactorInput = WithParams & { - parent: SignInRouterMachineActorRef; - resendable: boolean; -}; - -export type AttemptFirstFactorInput = { - parent: SignInRouterMachineActorRef; - fields: FormFields; - currentFactor: SignInFirstFactor | null; -}; -export type AttemptSecondFactorInput = { - parent: SignInRouterMachineActorRef; - fields: FormFields; - currentFactor: SignInSecondFactor | null; -}; - -const isNonPreparableStrategy = (strategy?: SignInFirstFactor['strategy'] | SignInSecondFactor['strategy']) => { - if (!strategy) { - return false; - } - - return ['passkey', 'password'].includes(strategy); -}; - -export const SignInVerificationMachineId = 'SignInVerification'; - -const SignInVerificationMachine = setup({ - actors: { - determineStartingFactor: fromPromise( - () => Promise.reject(new ClerkElementsRuntimeError('Actor `determineStartingFactor` must be overridden')), - ), - prepare: fromPromise(() => - Promise.reject(new ClerkElementsRuntimeError('Actor `prepare` must be overridden')), - ), - attempt: fromPromise(() => - Promise.reject(new ClerkElementsRuntimeError('Actor `attempt` must be overridden')), - ), - attemptPasskey: fromPromise< - SignInResource, - { parent: SignInRouterMachineActorRef; flow: 'autofill' | 'discoverable' | undefined } - >(({ input: { parent, flow } }) => { - return parent.getSnapshot().context.clerk.client.signIn.authenticateWithPasskey({ - flow, - }); - }), - }, - actions: { - resendableTick: assign(({ context }) => ({ - resendable: context.resendableAfter === 0, - resendableAfter: context.resendableAfter > 0 ? context.resendableAfter - 1 : context.resendableAfter, - })), - resendableReset: assign({ - resendable: false, - resendableAfter: RESENDABLE_COUNTDOWN_DEFAULT, - }), - validateRegisteredStrategies: ({ context }) => { - const clerk = context.parent.getSnapshot().context.clerk; - - if (clerk.__unstable__environment?.isProduction()) { - return; - } - - // Only show these warnings in development! - if (process.env.NODE_ENV === 'development') { - if ( - clerk.client.signIn.supportedFirstFactors && - !clerk.client.signIn.supportedFirstFactors.every((factor: SignInFirstFactor) => - context.registeredStrategies.has(factor.strategy), - ) - ) { - console.warn( - `Clerk: Your instance is configured to support these strategies: ${clerk.client.signIn.supportedFirstFactors - .map((factor: SignInFirstFactor) => factor.strategy) - .join(', ')}, but the rendered strategies are: ${Array.from(context.registeredStrategies).join( - ', ', - )}. Make sure to render a component for each supported strategy. More information: https://clerk.com/docs/elements/reference/sign-in#strategy`, - ); - } - - if ( - clerk.client.signIn.supportedSecondFactors && - !clerk.client.signIn.supportedSecondFactors.every((factor: SignInSecondFactor) => - context.registeredStrategies.has(factor.strategy), - ) - ) { - console.warn( - `Clerk: Your instance is configured to support these 2FA strategies: ${clerk.client.signIn.supportedSecondFactors - .map((f: SignInSecondFactor) => f.strategy) - .join(', ')}, but the rendered strategies are: ${Array.from(context.registeredStrategies).join( - ', ', - )}. Make sure to render a component for each supported strategy. More information: https://clerk.com/docs/elements/reference/sign-in#strategy`, - ); - } - - const strategiesUsedButNotActivated = Array.from(context.registeredStrategies).filter( - strategy => - !clerk.client.signIn.supportedFirstFactors?.some( - (supported: SignInFirstFactor) => supported.strategy === strategy, - ), - ); - - if (strategiesUsedButNotActivated.length > 0) { - console.warn( - `Clerk: These rendered strategies are not configured for your instance: ${strategiesUsedButNotActivated.join(', ')}. If this is unexpected, make sure to enable them in your Clerk dashboard: https://dashboard.clerk.com/last-active?path=/user-authentication/email-phone-username`, - ); - } - - if (context.currentFactor?.strategy && !context.registeredStrategies.has(context.currentFactor?.strategy)) { - throw new ClerkElementsRuntimeError( - `Your sign-in attempt is missing a ${context.currentFactor?.strategy} strategy. Make sure is rendered in your flow. More information: https://clerk.com/docs/elements/reference/sign-in#strategy`, - ); - } else if (!context.currentFactor?.strategy) { - throw new ClerkElementsRuntimeError( - 'Unable to determine an authentication strategy to verify. This means your instance is misconfigured. Visit the Clerk Dashboard and verify that your instance has authentication strategies enabled: https://dashboard.clerk.com/last-active?path=/user-authentication/email-phone-username', - ); - } - } - }, - sendToNext: ({ context, event }) => - context.parent.send({ type: 'NEXT', resource: (event as unknown as DoneActorEvent).output }), - sendToLoading, - setFormErrors: sendTo( - ({ context }) => context.formRef, - ({ event }) => { - assertActorEventError(event); - return { - type: 'ERRORS.SET', - error: event.error, - }; - }, - ), - setConsoleError: ({ event }) => { - if (process.env.NODE_ENV !== 'development') { - return; - } - - assertActorEventError(event); - - const error = isClerkAPIResponseError(event.error) ? event.error.errors[0].longMessage : event.error.message; - - console.error(`Unable to fulfill the prepare or attempt request for the sign-in verification. - Error: ${error} - Please open an issue if you continue to run into this issue.`); - }, - }, - guards: { - isResendable: ({ context }) => context.resendable || context.resendableAfter === 0, - isNeverResendable: ({ context }) => isNonPreparableStrategy(context.currentFactor?.strategy), - }, - delays: SignInVerificationDelays, - types: {} as SignInVerificationSchema, -}).createMachine({ - id: SignInVerificationMachineId, - context: ({ input }) => ({ - currentFactor: null, - basePath: input.basePath || SIGN_IN_DEFAULT_BASE_PATH, - formRef: input.formRef, - loadingStep: 'verifications', - parent: input.parent, - registeredStrategies: new Set(), - resendable: false, - resendableAfter: RESENDABLE_COUNTDOWN_DEFAULT, - }), - initial: 'Init', - on: { - 'NAVIGATE.PREVIOUS': '.Hist', - 'STRATEGY.REGISTER': { - actions: assign({ - registeredStrategies: ({ context, event }) => context.registeredStrategies.add(event.factor), - }), - }, - 'STRATEGY.UNREGISTER': { - actions: assign({ - registeredStrategies: ({ context, event }) => { - context.registeredStrategies.delete(event.factor); - return context.registeredStrategies; - }, - }), - }, - }, - states: { - Init: { - tags: ['state:preparing', 'state:loading'], - invoke: { - id: 'determineStartingFactor', - src: 'determineStartingFactor', - input: ({ context }) => ({ - parent: context.parent, - }), - onDone: { - target: 'Preparing', - actions: assign({ - currentFactor: ({ event }) => event.output, - }), - }, - onError: { - target: 'Preparing', - actions: [ - log('Clerk [Sign In Verification]: Error determining starting factor'), - assign({ - currentFactor: { strategy: 'password' }, - }), - ], - }, - }, - }, - Preparing: { - tags: ['state:preparing', 'state:loading'], - invoke: { - id: 'prepare', - src: 'prepare', - input: ({ context }) => ({ - parent: context.parent, - resendable: context.resendable, - params: { - ...context.currentFactor, - redirectUrl: `${window.location.origin}${context.basePath}${MAGIC_LINK_VERIFY_PATH_ROUTE}`, - } as PrepareFirstFactorParams, - }), - onDone: { - actions: 'resendableReset', - target: 'Pending', - }, - onError: { - actions: ['setFormErrors', 'setConsoleError'], - target: 'Pending', - }, - }, - }, - Pending: { - tags: ['state:pending'], - description: 'Waiting for user input', - on: { - 'AUTHENTICATE.PASSKEY': { - target: 'AttemptingPasskey', - reenter: true, - }, - 'NAVIGATE.CHOOSE_STRATEGY': 'ChooseStrategy', - 'NAVIGATE.FORGOT_PASSWORD': 'ChooseStrategy', - RETRY: 'Preparing', - SUBMIT: { - target: 'Attempting', - reenter: true, - }, - }, - initial: 'Init', - states: { - Init: { - description: 'Marks appropriate factors as never resendable.', - always: [ - { - guard: 'isNeverResendable', - target: 'NeverResendable', - }, - { - target: 'NotResendable', - }, - ], - }, - Resendable: { - description: 'Waiting for user to retry', - }, - NeverResendable: { - description: 'Handles never resendable', - on: { - RETRY: { - actions: log('Never retriable'), - }, - }, - }, - NotResendable: { - description: 'Handle countdowns', - on: { - RETRY: { - actions: log(({ context }) => `Not retriable; Try again in ${context.resendableAfter}s`), - }, - }, - after: { - resendableTimeout: [ - { - description: 'Set as retriable if countdown is 0', - guard: 'isResendable', - actions: 'resendableTick', - target: 'Resendable', - }, - { - description: 'Continue countdown if not retriable', - actions: 'resendableTick', - target: 'NotResendable', - reenter: true, - }, - ], - }, - }, - }, - after: { - 3000: { - actions: 'validateRegisteredStrategies', - }, - }, - }, - ChooseStrategy: { - description: 'Handles both choose strategy and forgot password as the latter is similar in functionality', - tags: ['state:choose-strategy', 'state:forgot-password'], - on: { - 'STRATEGY.UPDATE': { - actions: assign({ currentFactor: ({ event }) => event.factor || null }), - target: 'Preparing', - }, - }, - }, - Attempting: { - tags: ['state:attempting', 'state:loading'], - entry: 'sendToLoading', - invoke: { - id: 'attempt', - src: 'attempt', - input: ({ context }) => ({ - parent: context.parent, - currentFactor: context.currentFactor as SignInFirstFactor | null, - fields: context.formRef.getSnapshot().context.fields, - }), - onDone: { - actions: ['sendToNext', 'sendToLoading'], - }, - onError: { - actions: ['setFormErrors', 'setConsoleError', 'sendToLoading'], - target: 'Pending', - }, - }, - }, - AttemptingPasskey: { - tags: ['state:attempting', 'state:loading'], - entry: 'sendToLoading', - invoke: { - id: 'attemptPasskey', - src: 'attemptPasskey', - input: ({ context }) => ({ - parent: context.parent, - flow: 'discoverable', - }), - onDone: { - actions: ['sendToNext', 'sendToLoading'], - }, - onError: { - actions: ['setFormErrors', 'sendToLoading'], - target: 'Pending', - }, - }, - }, - Hist: { - type: 'history', - }, - }, -}); - -export const SignInFirstFactorMachine = SignInVerificationMachine.provide({ - actors: { - determineStartingFactor: fromPromise(async ({ input }) => { - const clerk = input.parent.getSnapshot().context.clerk; - - return Promise.resolve( - determineStartingSignInFactor( - clerk.client.signIn.supportedFirstFactors, - clerk.client.signIn.identifier, - clerk.__unstable__environment?.displayConfig.preferredSignInStrategy, - ), - ); - }), - prepare: fromPromise(async ({ input }) => { - // `input` is a union of PrepareFirstFactor and PrepareSecondFactor. Since we're passing params to - // prepareFirstFactor, we need to assert that the input is a PrepareFirstFactor. For some reason, ESLint thinks - // the assertion is unnecessary, and will remove it during the pre-commit hook. To prevent that, we disable the - // rule for the line. - - const { params, parent, resendable } = input as PrepareFirstFactorInput; - const clerk = parent.getSnapshot().context.clerk; - - // If a prepare call has already been fired recently, don't re-send - const currentVerificationExpiration = clerk.client.signIn.firstFactorVerification.expireAt; - const needsPrepare = resendable || !currentVerificationExpiration || currentVerificationExpiration < new Date(); - - if (isNonPreparableStrategy(params?.strategy) || !needsPrepare) { - return Promise.resolve(clerk.client.signIn); - } - - assertIsDefined(params, 'First factor params'); - return await clerk.client.signIn.prepareFirstFactor(params); - }), - attempt: fromPromise(async ({ input }) => { - const { currentFactor, fields, parent } = input as AttemptFirstFactorInput; - - assertIsDefined(currentFactor, 'Current factor'); - - let attemptParams: AttemptFirstFactorParams; - - const strategy = currentFactor.strategy; - const code = fields.get('code')?.value as string | undefined; - const password = fields.get('password')?.value as string | undefined; - - switch (strategy) { - case 'passkey': { - return await parent.getSnapshot().context.clerk.client.signIn.authenticateWithPasskey(); - } - case 'password': { - assertIsDefined(password, 'Password'); - - attemptParams = { - strategy, - password, - } satisfies PasswordAttempt; - - break; - } - case 'reset_password_phone_code': - case 'reset_password_email_code': { - assertIsDefined(code, 'Code for resetting phone/email'); - - attemptParams = { - strategy, - code, - password, - } satisfies ResetPasswordPhoneCodeAttempt | ResetPasswordEmailCodeAttempt; - - break; - } - case 'phone_code': - case 'email_code': { - assertIsDefined(code, 'Code for phone/email'); - - attemptParams = { - strategy, - code, - } satisfies PhoneCodeAttempt | EmailCodeAttempt; - - break; - } - case 'web3_metamask_signature': { - const signature = fields.get('signature')?.value as string | undefined; - assertIsDefined(signature, 'Web3 Metamask signature'); - - attemptParams = { - strategy, - signature, - } satisfies Web3Attempt; - - break; - } - case 'web3_coinbase_wallet_signature': { - const signature = fields.get('signature')?.value as string | undefined; - assertIsDefined(signature, 'Web3 Coinbase Wallet signature'); - - attemptParams = { - strategy, - signature, - } satisfies Web3Attempt; - - break; - } - case 'web3_okx_wallet_signature': { - const signature = fields.get('signature')?.value as string | undefined; - assertIsDefined(signature, 'Web3 OKX Wallet signature'); - - attemptParams = { - strategy, - signature, - } satisfies Web3Attempt; - - break; - } - default: - throw new ClerkElementsRuntimeError(`Invalid strategy: ${strategy}`); - } - - return await parent.getSnapshot().context.clerk.client.signIn.attemptFirstFactor(attemptParams); - }), - }, -}); - -export const SignInSecondFactorMachine = SignInVerificationMachine.provide({ - actors: { - determineStartingFactor: fromPromise(async ({ input }) => - Promise.resolve( - determineStartingSignInSecondFactor( - input.parent.getSnapshot().context.clerk.client.signIn.supportedSecondFactors, - ), - ), - ), - prepare: fromPromise(async ({ input }) => { - const { params, parent, resendable } = input as PrepareSecondFactorInput; - const clerk = parent.getSnapshot().context.clerk; - - // If a prepare call has already been fired recently, don't re-send - const currentVerificationExpiration = clerk.client.signIn.secondFactorVerification.expireAt; - const needsPrepare = resendable || !currentVerificationExpiration || currentVerificationExpiration < new Date(); - - assertIsDefined(params, 'Second factor params'); - - if (params.strategy !== 'phone_code' || !needsPrepare) { - return Promise.resolve(clerk.client.signIn); - } - - return await clerk.client.signIn.prepareSecondFactor({ - strategy: params.strategy, - phoneNumberId: params.phoneNumberId, - }); - }), - attempt: fromPromise(async ({ input }) => { - const { fields, parent, currentFactor } = input as AttemptSecondFactorInput; - - const code = fields.get('code')?.value as string; - - assertIsDefined(currentFactor, 'Current factor'); - assertIsDefined(code, 'Code'); - - return await parent.getSnapshot().context.clerk.client.signIn.attemptSecondFactor({ - // @ts-expect-error - email_link is not supported in the attemptSecondFactor params - strategy: currentFactor.strategy, - code, - }); - }), - }, -}); diff --git a/packages/elements/src/internals/machines/sign-in/verification.types.ts b/packages/elements/src/internals/machines/sign-in/verification.types.ts deleted file mode 100644 index 4c0c32dd1cc..00000000000 --- a/packages/elements/src/internals/machines/sign-in/verification.types.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { ClerkAPIResponseError } from '@clerk/shared/error'; -import type { SignInFactor } from '@clerk/types'; -import type { ActorRefFrom, ErrorActorEvent } from 'xstate'; - -import type { FormMachine } from '~/internals/machines/form'; -import type { SignInStrategyName } from '~/internals/machines/shared'; - -import type { SignInRouterMachineActorRef } from './router.types'; - -// ---------------------------------- Tags ---------------------------------- // - -export type SignInVerificationTags = - | 'state:attempting' - | 'state:choose-strategy' - | 'state:forgot-password' - | 'state:loading' - | 'state:pending' - | 'state:preparing'; - -// ---------------------------------- Events ---------------------------------- // - -export type SignInVerificationSubmitEvent = { type: 'SUBMIT'; action: 'submit' }; -export type SignInVerificationFactorUpdateEvent = { type: 'STRATEGY.UPDATE'; factor: SignInFactor | undefined }; -export type SignInVerificationRetryEvent = { type: 'RETRY' }; -export type SignInVerificationStrategyRegisterEvent = { type: 'STRATEGY.REGISTER'; factor: SignInStrategyName }; -export type SignInVerificationStrategyUnregisterEvent = { type: 'STRATEGY.UNREGISTER'; factor: SignInStrategyName }; - -export type SignInVerificationEvents = - | ErrorActorEvent - | SignInVerificationSubmitEvent - | SignInVerificationFactorUpdateEvent - | SignInVerificationRetryEvent - | SignInVerificationStrategyRegisterEvent - | SignInVerificationStrategyUnregisterEvent; - -// ---------------------------------- Input ---------------------------------- // - -export interface SignInVerificationInput { - formRef: ActorRefFrom; - parent: SignInRouterMachineActorRef; - basePath?: string; -} - -// ---------------------------------- Context ---------------------------------- // - -export interface SignInVerificationContext { - currentFactor: SignInFactor | null; - error?: Error | ClerkAPIResponseError; - formRef: ActorRefFrom; - parent: SignInRouterMachineActorRef; - loadingStep: 'verifications'; - registeredStrategies: Set; - resendable: boolean; - resendableAfter: number; - basePath?: string; -} - -// ---------------------------------- Delays ---------------------------------- // - -export const SignInVerificationDelays = { - resendableTimeout: 1_000, // 1 second -} as const; - -export type SignInVerificationDelays = keyof typeof SignInVerificationDelays; - -// ---------------------------------- Schema ---------------------------------- // - -export interface SignInVerificationSchema { - context: SignInVerificationContext; - input: SignInVerificationInput; - delays: SignInVerificationDelays; - events: SignInVerificationEvents; - tags: SignInVerificationTags; -} diff --git a/packages/elements/src/internals/machines/sign-up/continue.machine.ts b/packages/elements/src/internals/machines/sign-up/continue.machine.ts deleted file mode 100644 index dee44be5f61..00000000000 --- a/packages/elements/src/internals/machines/sign-up/continue.machine.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { snakeToCamel } from '@clerk/shared/underscore'; -import type { SignUpResource } from '@clerk/types'; -import type { DoneActorEvent } from 'xstate'; -import { fromPromise, not, or, setup } from 'xstate'; - -import { SIGN_UP_DEFAULT_BASE_PATH } from '~/internals/constants'; -import type { FormDefaultValues, FormFields } from '~/internals/machines/form'; -import { sendToLoading } from '~/internals/machines/shared'; -import { fieldsToSignUpParams } from '~/internals/machines/sign-up/utils'; -import { assertActorEventError } from '~/internals/machines/utils/assert'; - -import type { SignUpContinueSchema } from './continue.types'; -import type { SignInRouterMachineActorRef } from './router.types'; - -export type TSignUpContinueMachine = typeof SignUpContinueMachine; - -export const SignUpContinueMachineId = 'SignUpContinue'; - -export const SignUpContinueMachine = setup({ - actors: { - attempt: fromPromise( - ({ input: { fields, parent } }) => { - const params = fieldsToSignUpParams(fields); - return parent.getSnapshot().context.clerk.client.signUp.update(params); - }, - ), - }, - actions: { - setFormErrors: ({ context, event }) => { - assertActorEventError(event); - context.formRef.send({ - type: 'ERRORS.SET', - error: event.error, - }); - }, - markFormAsProgressive: ({ context }) => { - const signUp = context.parent.getSnapshot().context.clerk.client.signUp; - - const missing = signUp.missingFields.map(snakeToCamel); - const optional = signUp.optionalFields.map(snakeToCamel); - const required = signUp.requiredFields.map(snakeToCamel); - - const progressiveFieldValues: FormDefaultValues = new Map(); - - for (const key of required.concat(optional) as (keyof SignUpResource)[]) { - if (key in signUp) { - progressiveFieldValues.set(key, signUp[key] as string | number | readonly string[] | undefined); - } - } - - context.formRef.send({ - type: 'MARK_AS_PROGRESSIVE', - missing, - optional, - required, - defaultValues: progressiveFieldValues, - }); - }, - unmarkFormAsProgressive: ({ context }) => context.formRef.send({ type: 'UNMARK_AS_PROGRESSIVE' }), - sendToNext: ({ context, event }) => - context.parent.send({ type: 'NEXT', resource: (event as unknown as DoneActorEvent).output }), - sendToLoading, - }, - guards: { - isStatusMissingRequirements: ({ context }) => - context.parent.getSnapshot().context.clerk?.client?.signUp?.status === 'missing_requirements', - hasMetPreviousMissingRequirements: ({ context }) => { - const signUp = context.parent.getSnapshot().context.clerk.client.signUp; - - const fields = context.formRef.getSnapshot().context.fields; - const signUpMissingFields = signUp.missingFields.map(snakeToCamel); - const missingFields = Array.from(context.formRef.getSnapshot().context.fields.keys()).filter(key => { - return !signUpMissingFields.includes(key) && !fields.get(key)?.value && !fields.get(key)?.checked; - }); - - return missingFields.length === 0; - }, - }, - types: {} as SignUpContinueSchema, -}).createMachine({ - id: SignUpContinueMachineId, - context: ({ input }) => ({ - basePath: input.basePath || SIGN_UP_DEFAULT_BASE_PATH, - formRef: input.formRef, - parent: input.parent, - loadingStep: 'continue', - }), - entry: 'markFormAsProgressive', - onDone: { - actions: 'unmarkFormAsProgressive', - }, - initial: 'Pending', - states: { - Pending: { - tags: ['state:pending'], - description: 'Waiting for user input', - on: { - SUBMIT: { - guard: or(['hasMetPreviousMissingRequirements', not('isStatusMissingRequirements')]), - target: 'Attempting', - reenter: true, - }, - }, - }, - Attempting: { - tags: ['state:attempting', 'state:loading'], - entry: 'sendToLoading', - invoke: { - id: 'attempt', - src: 'attempt', - input: ({ context }) => ({ - parent: context.parent, - fields: context.formRef.getSnapshot().context.fields, - }), - onDone: { - actions: ['sendToNext', 'sendToLoading'], - }, - onError: { - actions: ['setFormErrors', 'sendToLoading'], - target: 'Pending', - }, - }, - }, - }, -}); diff --git a/packages/elements/src/internals/machines/sign-up/continue.types.ts b/packages/elements/src/internals/machines/sign-up/continue.types.ts deleted file mode 100644 index 52b1276e692..00000000000 --- a/packages/elements/src/internals/machines/sign-up/continue.types.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { ClerkAPIResponseError } from '@clerk/shared/error'; -import type { ActorRefFrom, ErrorActorEvent } from 'xstate'; - -import type { FormMachine } from '~/internals/machines/form'; - -import type { SignInRouterMachineActorRef } from './router.types'; - -// ---------------------------------- Tags ---------------------------------- // - -export type SignUpContinueTags = 'state:pending' | 'state:attempting' | 'state:loading'; - -// ---------------------------------- Events ---------------------------------- // - -export type SignUpContinueSubmitEvent = { type: 'SUBMIT'; action: 'submit' }; - -export type SignUpContinueEvents = ErrorActorEvent | SignUpContinueSubmitEvent; - -// ---------------------------------- Input ---------------------------------- // - -export type SignUpContinueInput = { - basePath?: string; - formRef: ActorRefFrom; - parent: SignInRouterMachineActorRef; -}; - -// ---------------------------------- Context ---------------------------------- // - -export interface SignUpContinueContext { - basePath: string; - error?: Error | ClerkAPIResponseError; - formRef: ActorRefFrom; - parent: SignInRouterMachineActorRef; - loadingStep: 'continue'; -} - -// ---------------------------------- Schema ---------------------------------- // - -export interface SignUpContinueSchema { - context: SignUpContinueContext; - input: SignUpContinueInput; - events: SignUpContinueEvents; - tags: SignUpContinueTags; -} diff --git a/packages/elements/src/internals/machines/sign-up/index.ts b/packages/elements/src/internals/machines/sign-up/index.ts deleted file mode 100644 index be94c5b3404..00000000000 --- a/packages/elements/src/internals/machines/sign-up/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export { SignUpContinueMachine, SignUpContinueMachineId } from './continue.machine'; -export { SignUpRouterMachine, SignUpRouterMachineId } from './router.machine'; -export { SignUpStartMachine, SignUpStartMachineId } from './start.machine'; -export { SignUpVerificationMachine, SignUpVerificationMachineId } from './verification.machine'; - -export type { TSignUpContinueMachine } from './continue.machine'; -export type { TSignUpRouterMachine } from './router.machine'; -export type { TSignUpStartMachine } from './start.machine'; -export type { TSignUpVerificationMachine } from './verification.machine'; - -export type * from './continue.types'; -export type * from './router.types'; -export type * from './start.types'; -export type * from './verification.types'; diff --git a/packages/elements/src/internals/machines/sign-up/router.machine.ts b/packages/elements/src/internals/machines/sign-up/router.machine.ts deleted file mode 100644 index f3dcf09b8cc..00000000000 --- a/packages/elements/src/internals/machines/sign-up/router.machine.ts +++ /dev/null @@ -1,542 +0,0 @@ -import { joinURL } from '@clerk/shared/url'; -import type { SignUpStatus, VerificationStatus } from '@clerk/types'; -import type { NonReducibleUnknown } from 'xstate'; -import { and, assign, enqueueActions, log, not, or, raise, sendTo, setup } from 'xstate'; - -import { - ERROR_CODES, - ROUTING, - SEARCH_PARAMS, - SIGN_IN_DEFAULT_BASE_PATH, - SIGN_UP_DEFAULT_BASE_PATH, - SIGN_UP_MODES, - SSO_CALLBACK_PATH_ROUTE, -} from '~/internals/constants'; -import { ClerkElementsError, ClerkElementsRuntimeError } from '~/internals/errors'; -import { ThirdPartyMachine, ThirdPartyMachineId } from '~/internals/machines/third-party'; -import { shouldUseVirtualRouting } from '~/internals/machines/utils/next'; - -import { SignUpContinueMachine } from './continue.machine'; -import type { - SignUpRouterContext, - SignUpRouterEvents, - SignUpRouterNextEvent, - SignUpRouterSchema, -} from './router.types'; -import { SignUpStartMachine } from './start.machine'; -import { SignUpVerificationMachine } from './verification.machine'; - -export const SignUpRouterMachineId = 'SignUpRouter'; -export type TSignUpRouterMachine = typeof SignUpRouterMachine; - -const isCurrentPath = - (path: `/${string}`) => - ({ context }: { context: SignUpRouterContext }, _params?: NonReducibleUnknown) => - context.router?.match(path) ?? false; - -const needsStatus = - (status: SignUpStatus) => - ({ context, event }: { context: SignUpRouterContext; event?: SignUpRouterEvents }, _?: NonReducibleUnknown) => - (event as SignUpRouterNextEvent)?.resource?.status === status || context.clerk?.client?.signUp?.status === status; - -export const SignUpRouterMachine = setup({ - actors: { - continueMachine: SignUpContinueMachine, - startMachine: SignUpStartMachine, - thirdPartyMachine: ThirdPartyMachine, - verificationMachine: SignUpVerificationMachine, - }, - actions: { - clearFormErrors: sendTo(({ context }) => context.formRef, { type: 'ERRORS.CLEAR' }), - logUnknownError: snapshot => console.error('Unknown error:', snapshot), - navigateInternal: ({ context }, { path, force = false }: { path: string; force?: boolean }) => { - if (!context.router) { - return; - } - if (!force && shouldUseVirtualRouting()) { - return; - } - if (context.exampleMode) { - return; - } - - const resolvedPath = joinURL(context.router.basePath, path); - if (resolvedPath === context.router.pathname()) { - return; - } - - context.router.shallowPush(resolvedPath); - }, - navigateExternal: ({ context }, { path }: { path: string }) => context.router?.push(path), - raiseNext: raise({ type: 'NEXT' }), - setActive: ({ context, event }, params?: { sessionId?: string; useLastActiveSession?: boolean }) => { - if (context.exampleMode) { - return; - } - - const session = - params?.sessionId || - (params?.useLastActiveSession && context.clerk.client.lastActiveSessionId) || - ((event as SignUpRouterNextEvent)?.resource || context.clerk.client.signUp).createdSessionId; - - void context.clerk.setActive({ - session, - redirectUrl: context.clerk.buildAfterSignUpUrl({ - params: context.router?.searchParams(), - }), - }); - }, - delayedReset: raise({ type: 'RESET' }, { delay: 3000 }), // Reset machine after 3s delay. - setError: assign({ - error: (_, { error }: { error?: ClerkElementsError }) => { - if (error) { - return error; - } - return new ClerkElementsRuntimeError('Unknown error'); - }, - }), - setFormOAuthErrors: ({ context }) => { - const errorOrig = context.clerk.client.signIn.firstFactorVerification.error; - - if (!errorOrig) { - return; - } - - let error: ClerkElementsError; - - switch (errorOrig.code) { - case ERROR_CODES.NOT_ALLOWED_TO_SIGN_UP: - case ERROR_CODES.OAUTH_ACCESS_DENIED: - case ERROR_CODES.NOT_ALLOWED_ACCESS: - case ERROR_CODES.SAML_USER_ATTRIBUTE_MISSING: - case ERROR_CODES.OAUTH_EMAIL_DOMAIN_RESERVED_BY_SAML: - case ERROR_CODES.USER_LOCKED: - case ERROR_CODES.ENTERPRISE_SSO_USER_ATTRIBUTE_MISSING: - case ERROR_CODES.ENTERPRISE_SSO_EMAIL_ADDRESS_DOMAIN_MISMATCH: - case ERROR_CODES.ENTERPRISE_SSO_HOSTED_DOMAIN_MISMATCH: - case ERROR_CODES.SAML_EMAIL_ADDRESS_DOMAIN_MISMATCH: - case ERROR_CODES.ORGANIZATION_MEMBERSHIP_QUOTA_EXCEEDED_FOR_SSO: - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - error = new ClerkElementsError(errorOrig.code, errorOrig.longMessage!); - break; - default: - error = new ClerkElementsError( - 'unable_to_complete', - 'Unable to complete action at this time. If the problem persists please contact support.', - ); - } - - context.formRef.send({ - type: 'ERRORS.SET', - error, - }); - }, - transfer: ({ context }) => context.router?.push(context.clerk.buildSignInUrl()), - }, - guards: { - areFieldsMissing: ({ context }) => context.clerk?.client?.signUp?.missingFields?.length > 0, - areFieldsUnverified: ({ context }) => context.clerk?.client?.signUp?.unverifiedFields?.length > 0, - - hasAuthenticatedViaClerkJS: ({ context }) => - Boolean(context.clerk.client.signUp.status === null && context.clerk.client.lastActiveSessionId), - hasCreatedSession: ({ context }) => Boolean(context.router?.searchParams().get(SEARCH_PARAMS.createdSession)), - hasClerkStatus: ({ context }, params?: { status: VerificationStatus }) => { - const value = context.router?.searchParams().get(SEARCH_PARAMS.status); - if (!params) { - return Boolean(value); - } - return value === params.status; - }, - hasClerkTransfer: ({ context }) => Boolean(context.router?.searchParams().get(SEARCH_PARAMS.transfer)), - hasResource: ({ context }) => Boolean(context.clerk.client.signUp), - hasTicket: ({ context }) => Boolean(context.ticket), - - isLoggedInAndSingleSession: and(['isLoggedIn', 'isSingleSessionMode', not('isExampleMode')]), - isStatusAbandoned: needsStatus('abandoned'), - isStatusComplete: ({ context, event }) => { - const resource = (event as SignUpRouterNextEvent)?.resource; - const signUp = context.clerk?.client?.signUp; - - return ( - (resource?.status === 'complete' && Boolean(resource?.createdSessionId)) || - (signUp?.status === 'complete' && Boolean(signUp?.createdSessionId)) - ); - }, - isStatusMissingRequirements: needsStatus('missing_requirements'), - - isLoggedIn: or(['isStatusComplete', ({ context }) => Boolean(context.clerk.user)]), - isSingleSessionMode: ({ context }) => Boolean(context.clerk?.__unstable__environment?.authConfig.singleSessionMode), - isRestricted: ({ context }) => - context.clerk?.__unstable__environment?.userSettings.signUp.mode === SIGN_UP_MODES.RESTRICTED, - isRestrictedWithoutTicket: and(['isRestricted', not('hasTicket')]), - isExampleMode: ({ context }) => Boolean(context.exampleMode), - isMissingRequiredFields: and(['isStatusMissingRequirements', 'areFieldsMissing']), - isMissingRequiredUnverifiedFields: and(['isStatusMissingRequirements', 'areFieldsUnverified']), - - needsIdentifier: or(['statusNeedsIdentifier', isCurrentPath('/')]), - needsContinue: and(['statusNeedsContinue', isCurrentPath('/continue')]), - needsVerification: and(['statusNeedsVerification', isCurrentPath('/verify')]), - needsCallback: isCurrentPath(SSO_CALLBACK_PATH_ROUTE), - - statusNeedsIdentifier: or([not('hasResource'), 'isStatusAbandoned']), - statusNeedsContinue: or(['isMissingRequiredFields']), - statusNeedsVerification: or(['isMissingRequiredUnverifiedFields', and(['areFieldsMissing', 'hasClerkStatus'])]), - }, - delays: { - 'TIMEOUT.POLLING': 300_000, // 5 minutes - }, - types: {} as SignUpRouterSchema, -}).createMachine({ - id: SignUpRouterMachineId, - // @ts-expect-error - Set in INIT event - context: {}, - initial: 'Idle', - on: { - 'AUTHENTICATE.OAUTH': { - actions: sendTo(ThirdPartyMachineId, ({ context, event }) => ({ - type: 'REDIRECT', - params: { - strategy: event.strategy, - redirectUrl: `${ - context.router?.mode === ROUTING.virtual - ? context.clerk.__unstable__environment?.displayConfig.signUpUrl - : context.router?.basePath - }${SSO_CALLBACK_PATH_ROUTE}`, - redirectUrlComplete: context.clerk.buildAfterSignUpUrl({ - params: context.router?.searchParams(), - }), - }, - })), - }, - 'AUTHENTICATE.SAML': { - actions: sendTo(ThirdPartyMachineId, ({ context }) => ({ - type: 'REDIRECT', - params: { - strategy: 'saml', - emailAddress: context.formRef.getSnapshot().context.fields.get('emailAddress')?.value, - redirectUrl: `${ - context.router?.mode === ROUTING.virtual - ? context.clerk.__unstable__environment?.displayConfig.signUpUrl - : context.router?.basePath - }${SSO_CALLBACK_PATH_ROUTE}`, - redirectUrlComplete: context.clerk.buildAfterSignUpUrl({ - params: context.router?.searchParams(), - }), - }, - })), - }, - 'AUTHENTICATE.ENTERPRISE_SSO': { - actions: sendTo(ThirdPartyMachineId, ({ context }) => ({ - type: 'REDIRECT', - params: { - strategy: 'enterprise_sso', - emailAddress: context.formRef.getSnapshot().context.fields.get('emailAddress')?.value, - redirectUrl: `${ - context.router?.mode === ROUTING.virtual - ? context.clerk.__unstable__environment?.displayConfig.signUpUrl - : context.router?.basePath - }${SSO_CALLBACK_PATH_ROUTE}`, - redirectUrlComplete: context.clerk.buildAfterSignUpUrl({ - params: context.router?.searchParams(), - }), - }, - })), - }, - 'AUTHENTICATE.WEB3': { - actions: sendTo('start', ({ event }) => event), - }, - 'FORM.ATTACH': { - description: 'Attach/re-attach the form to the router.', - actions: enqueueActions(({ enqueue, event }) => { - enqueue.assign({ - formRef: event.formRef, - }); - - // Reset the current step, to reset the form reference. - enqueue.raise({ type: 'RESET.STEP' }); - }), - }, - 'NAVIGATE.PREVIOUS': '.Hist', - 'NAVIGATE.START': '.Start', - LOADING: { - actions: assign(({ event }) => ({ - loading: { - isLoading: event.isLoading, - step: event.step, - strategy: event.strategy, - action: event.action, - }, - })), - }, - RESET: '.Idle', - }, - states: { - Idle: { - on: { - INIT: { - actions: assign(({ event }) => { - const searchParams = event.router?.searchParams(); - - return { - clerk: event.clerk, - router: event.router, - signInPath: event.signInPath || SIGN_IN_DEFAULT_BASE_PATH, - loading: { - isLoading: false, - }, - exampleMode: event.exampleMode || false, - formRef: event.formRef, - ticket: - searchParams?.get(SEARCH_PARAMS.ticket) || - searchParams?.get(SEARCH_PARAMS.invitationToken) || - undefined, - }; - }), - target: 'Init', - }, - }, - }, - Init: { - entry: enqueueActions(({ context, enqueue, self }) => { - if (!self.getSnapshot().children[ThirdPartyMachineId]) { - enqueue.spawnChild('thirdPartyMachine', { - id: ThirdPartyMachineId, - systemId: ThirdPartyMachineId, - input: { - basePath: context.router?.basePath ?? SIGN_UP_DEFAULT_BASE_PATH, - flow: 'signUp', - formRef: context.formRef, - parent: self, - }, - }); - } - }), - always: [ - { - guard: 'isLoggedInAndSingleSession', - actions: [ - log('Already logged in'), - { - type: 'navigateExternal', - params: ({ context }) => ({ - path: context.clerk.buildAfterSignUpUrl({ - params: context.router?.searchParams(), - }), - }), - }, - ], - }, - { - guard: 'needsCallback', - target: 'Callback', - }, - { - guard: 'hasTicket', - actions: { type: 'navigateInternal', params: { force: true, path: '/' } }, - target: 'Start', - }, - { - guard: 'needsVerification', - actions: { type: 'navigateInternal', params: { force: true, path: '/verify' } }, - target: 'Verification', - }, - { - guard: or(['needsContinue', 'hasClerkTransfer']), - actions: { type: 'navigateInternal', params: { force: true, path: '/continue' } }, - target: 'Continue', - }, - { - guard: 'isRestrictedWithoutTicket', - target: 'Restricted', - }, - { - actions: { type: 'navigateInternal', params: { force: true, path: '/' } }, - target: 'Start', - }, - ], - }, - Start: { - tags: ['step:start'], - exit: 'clearFormErrors', - invoke: { - id: 'start', - src: 'startMachine', - input: ({ context, self }) => ({ - basePath: context.router?.basePath, - formRef: context.formRef, - parent: self, - ticket: context.ticket, - }), - onDone: { - actions: 'raiseNext', - }, - }, - on: { - 'RESET.STEP': { - actions: enqueueActions(({ enqueue, context }) => { - enqueue('clearFormErrors'); - enqueue.sendTo('start', { type: 'SET_FORM', formRef: context.formRef }); - }), - }, - NEXT: [ - { - guard: 'isStatusComplete', - actions: ['setActive', 'delayedReset'], - }, - { - guard: and(['hasTicket', 'statusNeedsContinue']), - actions: { type: 'navigateInternal', params: { path: '/' } }, - target: 'Start', - reenter: true, - }, - { - guard: 'statusNeedsVerification', - target: 'Verification', - actions: { type: 'navigateInternal', params: { path: '/verify' } }, - }, - { - guard: 'statusNeedsContinue', - actions: { type: 'navigateInternal', params: { path: '/continue' } }, - target: 'Continue', - }, - ], - }, - }, - Continue: { - tags: ['step:continue'], - invoke: { - id: 'continue', - src: 'continueMachine', - input: ({ context, self }) => ({ - basePath: context.router?.basePath, - formRef: context.formRef, - parent: self, - }), - onDone: { - actions: 'raiseNext', - }, - }, - on: { - 'RESET.STEP': { - target: 'Continue', - reenter: true, - }, - NEXT: [ - { - guard: 'isStatusComplete', - actions: ['setActive', 'delayedReset'], - }, - { - guard: 'statusNeedsVerification', - target: 'Verification', - actions: { type: 'navigateInternal', params: { path: '/verify' } }, - }, - ], - }, - }, - Verification: { - tags: ['step:verification'], - invoke: { - id: 'verification', - src: 'verificationMachine', - input: ({ context, self }) => ({ - attributes: context.clerk.__unstable__environment?.userSettings.attributes, - basePath: context.router?.basePath, - formRef: context.formRef, - parent: self, - resource: context.clerk.client.signUp, - }), - onDone: { - actions: 'raiseNext', - }, - }, - always: [ - { - guard: 'hasCreatedSession', - actions: [ - ({ context }) => ({ - type: 'setActive', - params: { sessionId: context.router?.searchParams().get(SEARCH_PARAMS.createdSession) }, - }), - 'delayedReset', - ], - }, - { - guard: { type: 'hasClerkStatus', params: { status: 'verified' } }, - actions: { type: 'navigateInternal', params: { force: true, path: '/continue' } }, - }, - { - guard: { type: 'hasClerkStatus', params: { status: 'expired' } }, - actions: { type: 'navigateInternal', params: { force: true, path: '/' } }, - }, - ], - on: { - 'RESET.STEP': { - target: 'Verification', - reenter: true, - }, - NEXT: [ - { - guard: 'isStatusComplete', - actions: ['setActive', 'delayedReset'], - }, - { - guard: 'statusNeedsContinue', - actions: { type: 'navigateInternal', params: { path: '/continue' } }, - target: 'Continue', - }, - ], - }, - }, - Callback: { - tags: ['step:callback'], - entry: sendTo(ThirdPartyMachineId, { type: 'CALLBACK' }), - on: { - NEXT: [ - { - guard: 'isStatusComplete', - actions: ['setActive', 'delayedReset'], - }, - { - description: 'Handle a case where the user has already been authenticated via ClerkJS', - guard: 'hasAuthenticatedViaClerkJS', - actions: [{ type: 'setActive', params: { useLastActiveSession: true } }, 'delayedReset'], - }, - { - guard: 'statusNeedsVerification', - actions: { type: 'navigateInternal', params: { path: '/verify' } }, - target: 'Verification', - }, - { - guard: 'statusNeedsContinue', - actions: { type: 'navigateInternal', params: { path: '/continue' } }, - target: 'Continue', - }, - { - actions: { type: 'navigateInternal', params: { path: '/' } }, - target: 'Start', - }, - ], - }, - }, - Restricted: { - tags: ['step:restricted'], - on: { - NEXT: 'Start', - }, - }, - Error: { - tags: ['step:error'], - on: { - NEXT: { - target: 'Start', - actions: 'clearFormErrors', - }, - }, - }, - Hist: { - type: 'history', - exit: 'clearFormErrors', - }, - }, -}); diff --git a/packages/elements/src/internals/machines/sign-up/router.types.ts b/packages/elements/src/internals/machines/sign-up/router.types.ts deleted file mode 100644 index 217ef7265d9..00000000000 --- a/packages/elements/src/internals/machines/sign-up/router.types.ts +++ /dev/null @@ -1,137 +0,0 @@ -import type { SignUpResource } from '@clerk/types'; -import type { ActorRefFrom, SnapshotFrom, StateMachine } from 'xstate'; - -import type { TFormMachine } from '~/internals/machines/form'; -import type { - BaseRouterContext, - BaseRouterErrorEvent, - BaseRouterFormAttachEvent, - BaseRouterInput, - BaseRouterLoadingEvent, - BaseRouterNextEvent, - BaseRouterPrevEvent, - BaseRouterRedirectEvent, - BaseRouterResetEvent, - BaseRouterResetStepEvent, - BaseRouterSetClerkEvent, - BaseRouterStartEvent, - BaseRouterTransferEvent, -} from '~/internals/machines/types'; - -// ---------------------------------- Tags ---------------------------------- // - -export const SignUpRouterSteps = { - start: 'step:start', - continue: 'step:continue', - verification: 'step:verification', - callback: 'step:callback', - error: 'step:error', - restricted: 'step:restricted', -} as const; - -export type SignUpRouterSteps = keyof typeof SignUpRouterSteps; -export type SignUpRouterTags = (typeof SignUpRouterSteps)[keyof typeof SignUpRouterSteps]; - -// ---------------------------------- Children ---------------------------------- // - -export const SignUpRouterSystemId = { - start: 'start', - continue: 'continue', - verification: 'verification', -} as const; - -export type SignUpRouterSystemId = keyof typeof SignUpRouterSystemId; - -// ---------------------------------- Events ---------------------------------- // - -export type SignUpRouterFormAttachEvent = BaseRouterFormAttachEvent; -export type SignUpRouterNextEvent = BaseRouterNextEvent; -export type SignUpRouterStartEvent = BaseRouterStartEvent; -export type SignUpRouterPrevEvent = BaseRouterPrevEvent; -export type SignUpRouterErrorEvent = BaseRouterErrorEvent; -export type SignUpRouterTransferEvent = BaseRouterTransferEvent; -export type SignUpRouterRedirectEvent = BaseRouterRedirectEvent; -export type SignUpRouterResetEvent = BaseRouterResetEvent; -export type SignUpRouterResetStepEvent = BaseRouterResetStepEvent; -export type SignUpRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications' | 'continue'>; -export type SignUpRouterSetClerkEvent = BaseRouterSetClerkEvent; - -export interface SignUpRouterInitEvent extends BaseRouterInput { - type: 'INIT'; - formRef: ActorRefFrom; - signInPath?: string; -} - -export type SignUpRouterNavigationEvents = SignUpRouterStartEvent | SignUpRouterPrevEvent; - -export type SignUpRouterEvents = - | SignUpRouterFormAttachEvent - | SignUpRouterInitEvent - | SignUpRouterNextEvent - | SignUpRouterNavigationEvents - | SignUpRouterErrorEvent - | SignUpRouterTransferEvent - | SignUpRouterRedirectEvent - | SignUpRouterResetEvent - | SignUpRouterResetStepEvent - | SignUpRouterLoadingEvent - | SignUpRouterSetClerkEvent; - -// ---------------------------------- Delays ---------------------------------- // - -export const SignUpRouterDelays = { - polling: 300_000, // 5 minutes -} as const; - -export type SignUpRouterDelays = keyof typeof SignUpRouterDelays; - -// ---------------------------------- Context ---------------------------------- // - -export type SignUpRouterLoadingContext = Omit; - -export interface SignUpRouterContext extends BaseRouterContext { - formRef: ActorRefFrom; - loading: SignUpRouterLoadingContext; - signInPath: string; - ticket: string | undefined; -} - -// ---------------------------------- Schema ---------------------------------- // - -export interface SignUpRouterSchema { - context: SignUpRouterContext; - events: SignUpRouterEvents; - tags: SignUpRouterTags; - delays: SignUpRouterDelays; -} - -// ---------------------------------- Machine Type ---------------------------------- // - -export type SignUpRouterChildren = any; // TODO: Update -export type SignUpRouterOuptut = any; // TODO: Update -export type SignUpRouterStateValue = any; // TODO: Update - -export type TSignUpRouterParentMachine = StateMachine< - SignUpRouterContext, // context - SignUpRouterEvents, // event - SignUpRouterChildren, // children - any, // actor - any, // action - any, // guard - any, // delay - SignUpRouterStateValue, // state value - string, // tag - any, // input - SignUpRouterOuptut, // output - any, // emitted - any, // meta - Introduced in XState 5.12.x - any // config - Required in newer XState versions ->; - -// ---------------------------------- Machine Actor Ref ---------------------------------- // - -export type SignInRouterMachineActorRef = ActorRefFrom; - -// ---------------------------------- Snapshot ---------------------------------- // - -export type SignUpRouterSnapshot = SnapshotFrom; diff --git a/packages/elements/src/internals/machines/sign-up/start.machine.ts b/packages/elements/src/internals/machines/sign-up/start.machine.ts deleted file mode 100644 index 315207acc1d..00000000000 --- a/packages/elements/src/internals/machines/sign-up/start.machine.ts +++ /dev/null @@ -1,215 +0,0 @@ -import type { SignUpResource, Web3Strategy } from '@clerk/types'; -import type { DoneActorEvent } from 'xstate'; -import { and, assertEvent, assign, enqueueActions, fromPromise, not, sendTo, setup } from 'xstate'; - -import { SIGN_UP_DEFAULT_BASE_PATH } from '~/internals/constants'; -import { ClerkElementsRuntimeError } from '~/internals/errors'; -import type { FormFields } from '~/internals/machines/form'; -import type { SetFormEvent } from '~/internals/machines/shared'; -import { sendToLoading } from '~/internals/machines/shared'; -import { fieldsToSignUpParams } from '~/internals/machines/sign-up/utils'; -import { ThirdPartyMachine } from '~/internals/machines/third-party'; -import { assertActorEventError } from '~/internals/machines/utils/assert'; - -import type { SignInRouterMachineActorRef } from './router.types'; -import type { SignUpStartSchema } from './start.types'; - -const DISABLEABLE_FIELDS = ['emailAddress', 'phoneNumber'] as const; - -export type TSignUpStartMachine = typeof SignUpStartMachine; - -export const SignUpStartMachineId = 'SignUpStart'; - -type AttemptParams = { strategy: 'ticket'; ticket: string } | { strategy?: never; ticket?: never }; -type PrefillFieldsKeys = keyof Pick< - SignUpResource, - 'username' | 'firstName' | 'lastName' | 'emailAddress' | 'phoneNumber' ->; -const PREFILL_FIELDS: PrefillFieldsKeys[] = ['firstName', 'lastName', 'emailAddress', 'username', 'phoneNumber']; - -export const SignUpStartMachine = setup({ - actors: { - attempt: fromPromise< - SignUpResource, - { parent: SignInRouterMachineActorRef; fields: FormFields; params?: AttemptParams } - >(({ input: { fields, parent, params } }) => { - const fieldParams = fieldsToSignUpParams(fields); - return parent.getSnapshot().context.clerk.client.signUp.create({ ...fieldParams, ...params }); - }), - attemptWeb3: fromPromise( - ({ input: { parent, strategy } }) => { - if (strategy === 'web3_metamask_signature') { - return parent.getSnapshot().context.clerk.client.signUp.authenticateWithMetamask(); - } - if (strategy === 'web3_coinbase_wallet_signature') { - return parent.getSnapshot().context.clerk.client.signUp.authenticateWithCoinbaseWallet(); - } - if (strategy === 'web3_okx_wallet_signature') { - return parent.getSnapshot().context.clerk.client.signUp.authenticateWithOKXWallet(); - } - throw new ClerkElementsRuntimeError(`Unsupported Web3 strategy: ${strategy}`); - }, - ), - thirdParty: ThirdPartyMachine, - }, - actions: { - sendToNext: ({ context, event }) => - context.parent.send({ type: 'NEXT', resource: (event as unknown as DoneActorEvent).output }), - sendToLoading, - setFormRef: assign(({ event }) => { - return { - formRef: (event as unknown as SetFormEvent).formRef, - }; - }), - setFormDisabledTicketFields: enqueueActions(({ context, enqueue }) => { - if (!context.ticket) { - return; - } - - const currentFields = context.formRef.getSnapshot().context.fields; - - for (const name of DISABLEABLE_FIELDS) { - if (currentFields.has(name)) { - enqueue.sendTo(context.formRef, { type: 'FIELD.DISABLE', field: { name } }); - } - } - }), - setFormErrors: sendTo( - ({ context }) => context.formRef, - ({ event }) => { - assertActorEventError(event); - return { - type: 'ERRORS.SET', - error: event.error, - }; - }, - ), - setDefaultFormValues: ({ context }) => { - const signUp = context.parent.getSnapshot().context.clerk.client.signUp; - const prefilledDefaultValues = new Map(); - - for (const key of PREFILL_FIELDS) { - if (key in signUp) { - prefilledDefaultValues.set(key, signUp[key]); - } - } - - context.formRef.send({ - type: 'PREFILL_DEFAULT_VALUES', - defaultValues: prefilledDefaultValues, - }); - }, - }, - guards: { - isMissingRequirements: ({ context }) => - context.parent.getSnapshot().context.clerk?.client?.signUp?.status === 'missing_requirements', - hasTicket: ({ context }) => Boolean(context.ticket), - isExampleMode: ({ context }) => Boolean(context.parent.getSnapshot().context.exampleMode), - }, - types: {} as SignUpStartSchema, -}).createMachine({ - id: SignUpStartMachineId, - context: ({ input }) => ({ - basePath: input.basePath || SIGN_UP_DEFAULT_BASE_PATH, - formRef: input.formRef, - parent: input.parent, - loadingStep: 'start', - ticket: input.ticket, - }), - entry: 'setDefaultFormValues', - initial: 'Init', - on: { - SET_FORM: { - actions: 'setFormRef', - }, - }, - states: { - Init: { - description: - 'Handle ticket, if present; Else, default to Pending state. Per tickets, `Attempting` makes a `signUp.create` request allowing for an incomplete sign up to contain progressively filled fields on the Start step.', - always: [ - { - guard: and(['hasTicket', 'isMissingRequirements']), - target: 'Pending', - }, - { - guard: 'hasTicket', - target: 'Attempting', - }, - { - target: 'Pending', - }, - ], - }, - Pending: { - tags: ['state:pending'], - description: 'Waiting for user input', - on: { - SUBMIT: { - guard: not('isExampleMode'), - target: 'Attempting', - reenter: true, - }, - 'AUTHENTICATE.WEB3': { - guard: not('isExampleMode'), - target: 'AttemptingWeb3', - reenter: true, - }, - }, - }, - Attempting: { - tags: ['state:attempting', 'state:loading'], - entry: 'sendToLoading', - invoke: { - id: 'attemptCreate', - src: 'attempt', - input: ({ context }) => { - // Standard fields - const defaultParams = { - fields: context.formRef.getSnapshot().context.fields, - parent: context.parent, - }; - - // Handle ticket-specific flows - const params: AttemptParams = context.ticket - ? { - strategy: 'ticket', - ticket: context.ticket, - } - : {}; - - return { ...defaultParams, params }; - }, - onDone: { - actions: ['setFormDisabledTicketFields', 'sendToNext', 'sendToLoading'], - }, - onError: { - actions: ['setFormDisabledTicketFields', 'setFormErrors', 'sendToLoading'], - target: 'Pending', - }, - }, - }, - AttemptingWeb3: { - tags: ['state:attempting', 'state:loading'], - entry: 'sendToLoading', - invoke: { - id: 'attemptCreateWeb3', - src: 'attemptWeb3', - input: ({ context, event }) => { - assertEvent(event, 'AUTHENTICATE.WEB3'); - return { - parent: context.parent, - strategy: event.strategy, - }; - }, - onDone: { - actions: ['sendToNext', 'sendToLoading'], - }, - onError: { - actions: ['setFormErrors', 'sendToLoading'], - target: 'Pending', - }, - }, - }, - }, -}); diff --git a/packages/elements/src/internals/machines/sign-up/start.types.ts b/packages/elements/src/internals/machines/sign-up/start.types.ts deleted file mode 100644 index edbaec7b389..00000000000 --- a/packages/elements/src/internals/machines/sign-up/start.types.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { ClerkAPIResponseError } from '@clerk/shared/error'; -import type { EnterpriseSSOStrategy, OAuthStrategy, SamlStrategy, Web3Strategy } from '@clerk/types'; -import type { ActorRefFrom, ErrorActorEvent } from 'xstate'; - -import type { FormMachine } from '~/internals/machines/form'; - -import type { SetFormEvent } from '../shared'; -import type { SignInRouterMachineActorRef } from './router.types'; - -// ---------------------------------- Tags ---------------------------------- // - -export type SignUpStartTags = 'state:pending' | 'state:attempting' | 'state:loading'; - -// ---------------------------------- Events ---------------------------------- // - -export type SignUpStartSubmitEvent = { type: 'SUBMIT'; action: 'submit' }; - -// TODO: Consolidate with SignInStartMachine -export type SignUpStartRedirectOauthEvent = { type: 'AUTHENTICATE.OAUTH'; strategy: OAuthStrategy }; -export type SignUpStartRedirectSamlEvent = { type: 'AUTHENTICATE.SAML'; strategy?: SamlStrategy }; -export type SignUpStartRedirectEnterpriseSSOEvent = { - type: 'AUTHENTICATE.ENTERPRISE_SSO'; - strategy?: EnterpriseSSOStrategy; -}; -export type SignUpStartRedirectWeb3Event = { type: 'AUTHENTICATE.WEB3'; strategy: Web3Strategy }; - -export type SignUpStartRedirectEvent = - | SignUpStartRedirectOauthEvent - | SignUpStartRedirectSamlEvent - | SignUpStartRedirectWeb3Event - | SignUpStartRedirectEnterpriseSSOEvent; - -export type SignUpStartEvents = ErrorActorEvent | SignUpStartSubmitEvent | SignUpStartRedirectEvent | SetFormEvent; - -// ---------------------------------- Input ---------------------------------- // - -export type SignUpStartInput = { - basePath?: string; - formRef: ActorRefFrom; - parent: SignInRouterMachineActorRef; - ticket?: string | undefined; -}; - -// ---------------------------------- Context ---------------------------------- // - -export interface SignUpStartContext { - basePath: string; - error?: Error | ClerkAPIResponseError; - loadingStep: 'start'; - formRef: ActorRefFrom; - parent: SignInRouterMachineActorRef; - ticket?: string | undefined; -} - -// ---------------------------------- Schema ---------------------------------- // - -export interface SignUpStartSchema { - context: SignUpStartContext; - input: SignUpStartInput; - events: SignUpStartEvents; - tags: SignUpStartTags; -} diff --git a/packages/elements/src/internals/machines/sign-up/utils/__tests__/fields-to-params.test.ts b/packages/elements/src/internals/machines/sign-up/utils/__tests__/fields-to-params.test.ts deleted file mode 100644 index ffe29242530..00000000000 --- a/packages/elements/src/internals/machines/sign-up/utils/__tests__/fields-to-params.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { fieldsToSignUpParams } from '../fields-to-params'; - -describe('fieldsToSignUpParams', () => { - it('converts form fields to sign up params', () => { - const fields = new Map([ - ['firstName', { type: 'text', value: 'John' }], - ['emailAddress', { type: 'text', value: 'john@example.com' }], - ['password', { type: 'text', value: 'password123' }], - ]); - - const params = fieldsToSignUpParams(fields); - - expect(params).toEqual({ - firstName: 'John', - emailAddress: 'john@example.com', - password: 'password123', - }); - }); - - it('ignores undefined values', () => { - const fields = new Map([ - ['firstName', { type: 'text', value: 'John' }], - ['emailAddress', { type: 'text', value: undefined }], - ['password', { type: 'text', value: 'password123' }], - ]); - - const params = fieldsToSignUpParams(fields); - - expect(params).toEqual({ - firstName: 'John', - password: 'password123', - }); - }); - - it('ignores non-sign-up keys', () => { - const fields = new Map([ - ['firstName', { type: 'text', value: 'John' }], - ['foo', { type: 'text', value: 'bar' }], - ['bar', { type: 'text', value: 'foo' }], - ]); - - const params = fieldsToSignUpParams(fields); - - expect(params).toEqual({ - firstName: 'John', - }); - }); -}); diff --git a/packages/elements/src/internals/machines/sign-up/utils/fields-to-params.ts b/packages/elements/src/internals/machines/sign-up/utils/fields-to-params.ts deleted file mode 100644 index 3ebe006eee4..00000000000 --- a/packages/elements/src/internals/machines/sign-up/utils/fields-to-params.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { SignUpCreateParams, SignUpUpdateParams } from '@clerk/types'; - -import type { FormFields } from '~/internals/machines/form'; - -const SignUpAdditionalKeys = [ - 'firstName', - 'lastName', - 'emailAddress', - 'username', - 'password', - 'phoneNumber', - 'legalAccepted', -] as const; - -type SignUpAdditionalKeys = (typeof SignUpAdditionalKeys)[number]; - -const signUpKeys = new Set(SignUpAdditionalKeys); - -function isSignUpParam(key: string): key is T { - return signUpKeys.has(key as T); -} - -export function fieldsToSignUpParams( - fields: FormFields, -): Pick { - const params: SignUpUpdateParams = {}; - - fields.forEach(({ value, checked, type }, key) => { - if (isSignUpParam(key) && value !== undefined && type !== 'checkbox') { - // @ts-expect-error - Type is not narrowed to string - params[key] = value as string; - } - - if (isSignUpParam(key) && checked !== undefined && type === 'checkbox') { - // @ts-expect-error - Type is not narrowed to boolean - params[key] = checked; - } - }); - - return params; -} diff --git a/packages/elements/src/internals/machines/sign-up/utils/index.ts b/packages/elements/src/internals/machines/sign-up/utils/index.ts deleted file mode 100644 index e9b1197a1be..00000000000 --- a/packages/elements/src/internals/machines/sign-up/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { fieldsToSignUpParams } from './fields-to-params'; diff --git a/packages/elements/src/internals/machines/sign-up/verification.machine.ts b/packages/elements/src/internals/machines/sign-up/verification.machine.ts deleted file mode 100644 index 63e27144200..00000000000 --- a/packages/elements/src/internals/machines/sign-up/verification.machine.ts +++ /dev/null @@ -1,559 +0,0 @@ -import { Poller } from '@clerk/shared/poller'; -import type { - AttemptVerificationParams, - Attribute, - PrepareVerificationParams, - SignUpResource, - SignUpVerifiableField, - SignUpVerificationsResource, - VerificationStrategy, -} from '@clerk/types'; -import type { Writable } from 'type-fest'; -import { and, assign, enqueueActions, fromCallback, fromPromise, log, raise, sendParent, sendTo, setup } from 'xstate'; - -import { - MAGIC_LINK_VERIFY_PATH_ROUTE, - RESENDABLE_COUNTDOWN_DEFAULT, - SIGN_UP_DEFAULT_BASE_PATH, -} from '~/internals/constants'; -import { ClerkElementsError, ClerkElementsRuntimeError } from '~/internals/errors'; -import type { WithParams } from '~/internals/machines/shared'; -import { sendToLoading } from '~/internals/machines/shared'; -import { assertActorEventError } from '~/internals/machines/utils/assert'; - -import type { SignInRouterMachineActorRef } from './router.types'; -import { - type SignUpVerificationContext, - SignUpVerificationDelays, - type SignUpVerificationEmailLinkFailedEvent, - type SignUpVerificationEvents, - type SignUpVerificationSchema, -} from './verification.types'; - -export type SignUpVerificationsResourceKey = keyof SignUpVerificationsResource; -export type TSignUpVerificationMachine = typeof SignUpVerificationMachine; - -export type StartSignUpEmailLinkFlowEvents = { type: 'STOP' }; -export type StartSignUpEmailLinkFlowInput = { - parent: SignInRouterMachineActorRef; -}; - -export const SignUpVerificationMachineId = 'SignUpVerification'; - -const shouldVerify = (field: SignUpVerifiableField, strategy?: VerificationStrategy) => { - const guards: Writable>[0]> = [ - { - type: 'isFieldUnverified', - params: { - field, - }, - }, - ]; - - if (strategy) { - guards.push({ - type: 'isStrategyEnabled', - params: { - attribute: field, - strategy, - }, - }); - } - - return and(guards); -}; - -export type PrepareVerificationInput = { - parent: SignInRouterMachineActorRef; -} & WithParams; -export type AttemptVerificationInput = { - parent: SignInRouterMachineActorRef; -} & WithParams; - -export const SignUpVerificationMachine = setup({ - actors: { - prepare: fromPromise(({ input: { params, parent } }) => { - const clerk = parent.getSnapshot().context.clerk; - - if (params.strategy === 'email_link' && params.redirectUrl) { - params.redirectUrl = clerk.buildUrlWithAuth(params.redirectUrl); - } - - return clerk.client.signUp.prepareVerification(params); - }), - attempt: fromPromise(async ({ input: { params, parent } }) => - parent.getSnapshot().context.clerk.client.signUp.attemptVerification(params), - ), - attemptEmailLinkVerification: fromCallback( - ({ receive, sendBack, input: { parent } }) => { - const { run, stop } = Poller(); - - const clerk = parent.getSnapshot().context.clerk; - - void run(async () => - clerk.client.signUp - .reload() - .then((resource: SignUpResource) => { - const signInStatus = resource.status; - const verificationStatus = resource.verifications.emailAddress.status; - - // Short-circuit if the sign-up resource is already complete - if (signInStatus === 'complete') { - return sendBack({ type: 'EMAIL_LINK.VERIFIED', resource }); - } - - switch (verificationStatus) { - case 'verified': - case 'transferable': - case 'expired': { - sendBack({ type: `EMAIL_LINK.${verificationStatus.toUpperCase()}`, resource }); - break; - } - case 'failed': { - sendBack({ - type: 'EMAIL_LINK.FAILED', - error: new ClerkElementsError('email-link-verification-failed', 'Email verification failed'), - resource, - }); - break; - } - // case 'unverified': - default: - return; - } - - stop(); - }) - .catch((error: Error) => { - stop(); - new ClerkElementsRuntimeError(error.message); - }), - ); - - receive(event => { - if (event.type === 'STOP') { - stop(); - } - }); - - return () => stop(); - }, - ), - }, - actions: { - resendableTick: assign(({ context }) => ({ - resendable: context.resendableAfter === 1, - resendableAfter: context.resendableAfter > 1 ? context.resendableAfter - 1 : context.resendableAfter, - })), - resendableReset: assign({ - resendable: false, - resendableAfter: RESENDABLE_COUNTDOWN_DEFAULT, - }), - sendToLoading, - setFormErrors: sendTo( - ({ context }) => context.formRef, - ({ event }) => { - assertActorEventError(event); - return { - type: 'ERRORS.SET', - error: event.error, - }; - }, - ), - }, - guards: { - isComplete: ({ context }) => context.resource.status === 'complete', - isFieldUnverified: ({ context, event }, { field }: { field: SignUpVerifiableField }) => { - let resource = context.resource; - - if (event?.type === 'NEXT' && event.resource) { - resource = event.resource; - } - - return resource.unverifiedFields.includes(field); - }, - isResendable: ({ context }) => context.resendable || context.resendableAfter === 0, - isStrategyEnabled: ( - { context }, - { attribute, strategy }: { attribute: Attribute; strategy: VerificationStrategy }, - ) => Boolean(context.attributes?.[attribute].verifications.includes(strategy)), - shouldVerifyPhoneCode: shouldVerify('phone_number'), - shouldVerifyEmailLink: shouldVerify('email_address', 'email_link'), - shouldVerifyEmailCode: shouldVerify('email_address', 'email_code'), - }, - delays: SignUpVerificationDelays, - types: {} as SignUpVerificationSchema, -}).createMachine({ - id: SignUpVerificationMachineId, - initial: 'Init', - context: ({ input }) => ({ - attributes: input.attributes, - basePath: input.basePath || SIGN_UP_DEFAULT_BASE_PATH, - loadingStep: 'verifications', - formRef: input.formRef, - parent: input.parent, - resendable: false, - resendableAfter: RESENDABLE_COUNTDOWN_DEFAULT, - resource: input.resource, - }), - on: { - NEXT: [ - { - guard: 'isComplete', - actions: sendParent(({ event }) => ({ type: 'NEXT', resource: event.resource })), - }, - { - description: 'Validate via phone number', - guard: 'shouldVerifyPhoneCode', - target: '.PhoneCode', - }, - { - description: 'Validate via email link', - guard: 'shouldVerifyEmailLink', - target: '.EmailLink', - }, - { - description: 'Verify via email code', - guard: 'shouldVerifyEmailCode', - target: '.EmailCode', - }, - { - actions: sendParent(({ event }) => ({ type: 'NEXT', resource: event.resource })), - }, - ], - }, - states: { - Init: { - always: [ - { - description: 'Validate via phone number', - guard: 'shouldVerifyPhoneCode', - target: 'PhoneCode', - }, - { - description: 'Validate via email link', - guard: 'shouldVerifyEmailLink', - target: 'EmailLink', - }, - { - description: 'Verify via email code', - guard: 'shouldVerifyEmailCode', - target: 'EmailCode', - }, - { - actions: sendParent(({ context }) => ({ type: 'NEXT', resource: context.resource })), - }, - ], - }, - EmailLink: { - tags: ['verification:method:email', 'verification:category:link', 'verification:email_link'], - initial: 'Preparing', - on: { - RETRY: '.Preparing', - 'EMAIL_LINK.RESTART': { - target: '.Attempting', - reenter: true, - }, - 'EMAIL_LINK.FAILED': { - actions: [ - { - type: 'setFormErrors', - params: ({ event }: { event: SignUpVerificationEmailLinkFailedEvent }) => ({ error: event.error }), - }, - assign({ resource: ({ event }) => event.resource }), - ], - target: '.Pending', - }, - 'EMAIL_LINK.*': { - actions: enqueueActions(({ enqueue, event }) => { - if (event.type === 'EMAIL_LINK.RESTART') { - return; - } - - enqueue.assign({ resource: event.resource }); - enqueue.raise({ type: 'NEXT', resource: event.resource }); - }), - }, - }, - states: { - Preparing: { - tags: ['state:preparing', 'state:loading'], - exit: 'resendableReset', - invoke: { - id: 'prepareEmailLinkVerification', - src: 'prepare', - input: ({ context }) => ({ - parent: context.parent, - params: { - strategy: 'email_link', - redirectUrl: `${context.basePath}${MAGIC_LINK_VERIFY_PATH_ROUTE}`, - }, - }), - onDone: { - target: 'Attempting', - actions: assign({ resource: ({ event }) => event.output }), - }, - onError: { - actions: 'setFormErrors', - target: 'Pending', - }, - }, - }, - Pending: { - description: 'Placeholder for allowing resending of email link', - tags: ['state:pending'], - on: { - NEXT: 'Preparing', - }, - }, - Attempting: { - tags: ['state:attempting'], - invoke: { - id: 'attemptEmailLinkVerification', - src: 'attemptEmailLinkVerification', - input: ({ context }) => ({ - parent: context.parent, - }), - }, - after: { - emailLinkTimeout: { - description: 'Timeout after 5 minutes', - target: 'Pending', - actions: sendTo(({ context }) => context.formRef, { - type: 'ERRORS.SET', - error: new ClerkElementsError('verify-email-link-timeout', 'Email link verification timed out'), - }), - }, - }, - initial: 'NotResendable', - states: { - Resendable: { - description: 'Waiting for user to retry', - }, - NotResendable: { - description: 'Handle countdowns', - on: { - RETRY: { - actions: log(({ context }) => `Not retriable; Try again in ${context.resendableAfter}s`), - }, - }, - after: { - resendableTimeout: [ - { - description: 'Set as retriable if countdown is 0', - guard: 'isResendable', - actions: 'resendableTick', - target: 'Resendable', - }, - { - description: 'Continue countdown if not retriable', - actions: 'resendableTick', - target: 'NotResendable', - reenter: true, - }, - ], - }, - }, - }, - }, - }, - }, - EmailCode: { - tags: ['verification:method:email', 'verification:category:code', 'verification:email_code'], - initial: 'Preparing', - states: { - Preparing: { - tags: ['state:preparing', 'state:loading'], - exit: 'resendableReset', - invoke: { - id: 'prepareEmailAddressCodeVerification', - src: 'prepare', - input: ({ context }) => ({ - parent: context.parent, - params: { - strategy: 'email_code', - }, - }), - onDone: [ - { - guard: 'shouldVerifyEmailCode', - target: 'Pending', - }, - { - actions: [ - assign({ resource: ({ event }) => event.output }), - raise(({ event }) => ({ type: 'NEXT', resource: event.output })), - ], - }, - ], - onError: { - actions: 'setFormErrors', - target: 'Pending', - }, - }, - }, - Pending: { - tags: ['state:pending'], - on: { - RETRY: 'Preparing', - SUBMIT: { - target: 'Attempting', - reenter: true, - }, - }, - initial: 'NotResendable', - states: { - Resendable: { - description: 'Waiting for user to retry', - }, - NotResendable: { - description: 'Handle countdowns', - on: { - RETRY: { - actions: log(({ context }) => `Not retriable; Try again in ${context.resendableAfter}s`), - }, - }, - after: { - resendableTimeout: [ - { - description: 'Set as retriable if countdown is 0', - guard: 'isResendable', - actions: 'resendableTick', - target: 'Resendable', - }, - { - description: 'Continue countdown if not retriable', - actions: 'resendableTick', - target: 'NotResendable', - reenter: true, - }, - ], - }, - }, - }, - }, - Attempting: { - tags: ['state:attempting', 'state:loading'], - entry: 'sendToLoading', - invoke: { - id: 'attemptEmailAddressCodeVerification', - src: 'attempt', - input: ({ context }) => ({ - parent: context.parent, - params: { - strategy: 'email_code', - code: (context.formRef.getSnapshot().context.fields.get('code')?.value as string) || '', - }, - }), - onDone: { - actions: [raise(({ event }) => ({ type: 'NEXT', resource: event.output })), 'sendToLoading'], - }, - onError: { - actions: ['setFormErrors', 'sendToLoading'], - target: 'Pending', - }, - }, - }, - }, - }, - PhoneCode: { - tags: ['verification:method:phone', 'verification:category:code', 'verification:phone_code'], - initial: 'Preparing', - states: { - Preparing: { - tags: ['state:preparing', 'state:loading'], - exit: 'resendableReset', - invoke: { - id: 'preparePhoneCodeVerification', - src: 'prepare', - input: ({ context }) => ({ - parent: context.parent, - params: { - strategy: 'phone_code', - }, - }), - onDone: [ - { - guard: 'shouldVerifyPhoneCode', - target: 'Pending', - actions: assign({ resource: ({ event }) => event.output }), - }, - { - actions: [ - assign({ resource: ({ event }) => event.output }), - raise(({ event }) => ({ type: 'NEXT', resource: event.output })), - ], - }, - ], - onError: { - actions: 'setFormErrors', - target: 'Pending', - }, - }, - }, - Pending: { - tags: ['state:pending'], - on: { - RETRY: 'Preparing', - SUBMIT: { - target: 'Attempting', - reenter: true, - }, - }, - initial: 'NotResendable', - states: { - Resendable: { - description: 'Waiting for user to retry', - }, - NotResendable: { - description: 'Handle countdowns', - on: { - RETRY: { - actions: log(({ context }) => `Not retriable; Try again in ${context.resendableAfter}s`), - }, - }, - after: { - resendableTimeout: [ - { - description: 'Set as retriable if countdown is 0', - guard: 'isResendable', - actions: 'resendableTick', - target: 'Resendable', - }, - { - description: 'Continue countdown if not retriable', - actions: 'resendableTick', - target: 'NotResendable', - reenter: true, - }, - ], - }, - }, - }, - }, - Attempting: { - tags: ['state:attempting', 'state:loading'], - entry: 'sendToLoading', - invoke: { - id: 'attemptPhoneNumberVerification', - src: 'attempt', - input: ({ context }) => ({ - parent: context.parent, - params: { - strategy: 'phone_code', - code: (context.formRef.getSnapshot().context.fields.get('code')?.value as string) || '', - }, - }), - onDone: { - actions: [raise(({ event }) => ({ type: 'NEXT', resource: event.output })), 'sendToLoading'], - }, - onError: { - actions: ['setFormErrors', 'sendToLoading'], - target: 'Pending', - }, - }, - }, - }, - }, - }, -}); diff --git a/packages/elements/src/internals/machines/sign-up/verification.types.ts b/packages/elements/src/internals/machines/sign-up/verification.types.ts deleted file mode 100644 index edf46bf35f9..00000000000 --- a/packages/elements/src/internals/machines/sign-up/verification.types.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type { ClerkAPIResponseError } from '@clerk/shared/error'; -import type { Attributes, SignUpResource } from '@clerk/types'; -import type { ActorRefFrom, DoneActorEvent, ErrorActorEvent } from 'xstate'; - -import type { FormMachine } from '~/internals/machines/form'; - -import type { SignInRouterMachineActorRef } from './router.types'; - -// ---------------------------------- Tags ---------------------------------- // - -export type SignUpVerificationStateTags = 'state:preparing' | 'state:pending' | 'state:attempting' | 'state:loading'; - -export type SignUpVerificationVerificationCategoryTags = 'verification:category:code' | 'verification:category:link'; -export type SignUpVerificationVerificationMethodTags = 'verification:method:email' | 'verification:method:phone'; -export type SignUpVerificationVerificationTypeTags = - | 'verification:email_link' - | 'verification:email_code' - | 'verification:phone_code'; - -export type SignUpVerificationVerificationTags = - | SignUpVerificationVerificationCategoryTags - | SignUpVerificationVerificationMethodTags - | SignUpVerificationVerificationTypeTags; - -export type SignUpVerificationTags = SignUpVerificationStateTags | SignUpVerificationVerificationTags; -export type SignUpVerificationFriendlyTags = 'code' | 'email_link' | 'email_code' | 'phone_code'; - -// ---------------------------------- Events ---------------------------------- // - -export type SignUpVerificationSubmitEvent = { type: 'SUBMIT'; action: 'submit' }; -export type SignUpVerificationNextEvent = { type: 'NEXT'; resource?: SignUpResource }; -export type SignUpVerificationRetryEvent = { type: 'RETRY' }; - -export type SignUpVerificationEmailLinkVerifiedEvent = { type: 'EMAIL_LINK.VERIFIED'; resource: SignUpResource }; -export type SignUpVerificationEmailLinkUnverifiedEvent = { type: 'EMAIL_LINK.UNVERIFIED'; resource: SignUpResource }; -export type SignUpVerificationEmailLinkExpiredEvent = { type: 'EMAIL_LINK.EXPIRED'; resource: SignUpResource }; -export type SignUpVerificationEmailLinkTransferrableEvent = { - type: 'EMAIL_LINK.TRANSFERRABLE'; - resource: SignUpResource; -}; -export type SignUpVerificationEmailLinkRestartEvent = { type: 'EMAIL_LINK.RESTART' }; -export type SignUpVerificationEmailLinkFailedEvent = { - type: 'EMAIL_LINK.FAILED'; - resource: SignUpResource; - error: Error; -}; - -export type SignUpVerificationEmailLinkEvent = - | SignUpVerificationEmailLinkVerifiedEvent - | SignUpVerificationEmailLinkUnverifiedEvent - | SignUpVerificationEmailLinkExpiredEvent - | SignUpVerificationEmailLinkRestartEvent - | SignUpVerificationEmailLinkFailedEvent; - -export type SignUpVerificationEvents = - | DoneActorEvent - | ErrorActorEvent - | SignUpVerificationRetryEvent - | SignUpVerificationSubmitEvent - | SignUpVerificationNextEvent - | SignUpVerificationEmailLinkEvent; - -// ---------------------------------- Input ---------------------------------- // - -export type SignUpVerificationInput = { - attributes: Attributes | undefined; - basePath?: string; - formRef: ActorRefFrom; - parent: SignInRouterMachineActorRef; - resource: SignUpResource; -}; - -// ---------------------------------- Delays ---------------------------------- // - -export const SignUpVerificationDelays = { - emailLinkTimeout: 300_000, // 5 minutes - resendableTimeout: 1_000, // 1 second -} as const; - -export type SignUpVerificationDelays = keyof typeof SignUpVerificationDelays; - -// ---------------------------------- Context ---------------------------------- // - -export interface SignUpVerificationContext { - attributes: Attributes | undefined; - basePath: string; - resource: SignUpResource; - error?: Error | ClerkAPIResponseError; - formRef: ActorRefFrom; - parent: SignInRouterMachineActorRef; - loadingStep: 'verifications'; - resendable: boolean; - resendableAfter: number; -} - -// ---------------------------------- Schema ---------------------------------- // - -export interface SignUpVerificationSchema { - context: SignUpVerificationContext; - delays: SignUpVerificationDelays; - input: SignUpVerificationInput; - events: SignUpVerificationEvents; - tags: SignUpVerificationTags; -} diff --git a/packages/elements/src/internals/machines/third-party/index.ts b/packages/elements/src/internals/machines/third-party/index.ts deleted file mode 100644 index 711a35e90f4..00000000000 --- a/packages/elements/src/internals/machines/third-party/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './third-party.actors'; -export * from './third-party.machine'; -export * from './third-party.types'; diff --git a/packages/elements/src/internals/machines/third-party/third-party.actors.ts b/packages/elements/src/internals/machines/third-party/third-party.actors.ts deleted file mode 100644 index e80a48b2e9c..00000000000 --- a/packages/elements/src/internals/machines/third-party/third-party.actors.ts +++ /dev/null @@ -1,95 +0,0 @@ -import type { - AuthenticateWithRedirectParams, - HandleOAuthCallbackParams, - HandleSamlCallbackParams, - LoadedClerk, -} from '@clerk/types'; -import type { SetOptional } from 'type-fest'; -import type { AnyActorRef, AnyEventObject } from 'xstate'; -import { fromCallback, fromPromise } from 'xstate'; - -import { ClerkElementsRuntimeError } from '~/internals/errors'; -import type { WithParams, WithUnsafeMetadata } from '~/internals/machines/shared'; -import { ClerkJSNavigationEvent, isClerkJSNavigationEvent } from '~/internals/machines/utils/clerkjs'; - -type OptionalRedirectParams = 'redirectUrl' | 'redirectUrlComplete'; - -export type AuthenticateWithRedirectSignInParams = SetOptional; -export type AuthenticateWithRedirectSignUpParams = SetOptional< - WithUnsafeMetadata, - OptionalRedirectParams ->; - -export type AuthenticateWithRedirectInput = ( - | (WithParams & { flow: 'signIn' }) - | (WithParams & { flow: 'signUp' }) -) & { basePath: string; parent: AnyActorRef }; // TODO: Fix circular dependency - -export const redirect = fromPromise( - async ({ input: { flow, params, parent } }) => { - const clerk: LoadedClerk = parent.getSnapshot().context.clerk; - - return clerk.client[flow].authenticateWithRedirect({ - redirectUrl: clerk.buildUrlWithAuth(params.redirectUrl || '/'), - redirectUrlComplete: clerk.buildUrlWithAuth(params.redirectUrlComplete || '/'), - ...params, - }); - }, -); - -export type HandleRedirectCallbackParams> = { - [K in keyof T]: NonNullable; -}; - -export type HandleRedirectCallbackInput = AnyActorRef; - -/** - * This function hijacks handleRedirectCallback from ClerkJS to handle navigation events - * from the state machine. - */ -export const handleRedirectCallback = fromCallback( - ({ sendBack, input: parent }) => { - const clerk: LoadedClerk = parent.getSnapshot().context.clerk; - const displayConfig = clerk.__unstable__environment?.displayConfig; - - const customNavigate = (toEvt: string) => { - const to = toEvt.split('/').slice(-1)[0]; - - if (isClerkJSNavigationEvent(to)) { - // Handle known redefined navigation events - sendBack({ type: to }); - } else if (to === displayConfig?.signInUrl) { - // Handle known non-redefined sign-in navigation events - sendBack({ type: ClerkJSNavigationEvent.signIn }); - } else if (to === displayConfig?.signUpUrl) { - // Handle known non-redefined sign-up navigation events - sendBack({ type: ClerkJSNavigationEvent.signUp }); - } else { - // Handle unknown navigation events - sendBack({ type: 'FAILURE', error: new ClerkElementsRuntimeError(`Unknown navigation event: ${to}`) }); - } - - return Promise.resolve(); - }; - - // @ts-expect-error - Clerk types are incomplete - // TODO: Update local Clerk types - const loadedClerk = (clerk.clerkjs ?? clerk) as LoadedClerk; - - void loadedClerk.handleRedirectCallback( - { - continueSignUpUrl: ClerkJSNavigationEvent.continue, - firstFactorUrl: ClerkJSNavigationEvent.signIn, - resetPasswordUrl: ClerkJSNavigationEvent.resetPassword, - secondFactorUrl: ClerkJSNavigationEvent.signIn, - verifyEmailAddressUrl: ClerkJSNavigationEvent.verification, - verifyPhoneNumberUrl: ClerkJSNavigationEvent.verification, - signUpUrl: ClerkJSNavigationEvent.signUp, - signInUrl: ClerkJSNavigationEvent.signIn, - } satisfies HandleOAuthCallbackParams, - customNavigate, - ); - - return () => void 0; - }, -); diff --git a/packages/elements/src/internals/machines/third-party/third-party.machine.ts b/packages/elements/src/internals/machines/third-party/third-party.machine.ts deleted file mode 100644 index fbefe674277..00000000000 --- a/packages/elements/src/internals/machines/third-party/third-party.machine.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { assertEvent, assign, log, not, sendTo, setup } from 'xstate'; - -import { sendToLoading } from '~/internals/machines/shared'; -import { assertActorEventError } from '~/internals/machines/utils/assert'; - -import { handleRedirectCallback, redirect } from './third-party.actors'; -import type { ThirdPartyMachineSchema } from './third-party.types'; - -export const ThirdPartyMachineId = 'ThirdParty'; - -export type TThirdPartyMachine = typeof ThirdPartyMachine; - -export const ThirdPartyMachine = setup({ - actors: { - handleRedirectCallback, - redirect, - }, - actions: { - logError: log(({ event }) => `Error: ${event.type}`), - assignActiveStrategy: assign({ - activeStrategy: ({ event }) => { - assertEvent(event, 'REDIRECT'); - return event.params.strategy; - }, - }), - unassignActiveStrategy: assign({ - activeStrategy: null, - }), - sendToNext: ({ context }) => context.parent.send({ type: 'NEXT' }), - sendToLoading, - setFormErrors: sendTo( - ({ context }) => context.formRef, - ({ event }) => { - assertActorEventError(event); - return { - type: 'ERRORS.SET', - error: event.error, - }; - }, - ), - }, - guards: { - isExampleMode: ({ context }) => Boolean(context.parent.getSnapshot().context.exampleMode), - }, - types: {} as ThirdPartyMachineSchema, -}).createMachine({ - id: ThirdPartyMachineId, - context: ({ input }) => ({ - activeStrategy: null, - basePath: input.basePath, - formRef: input.formRef, - flow: input.flow, - parent: input.parent, - loadingStep: 'strategy', - }), - initial: 'Idle', - states: { - Idle: { - description: 'Sets third-party providers if not already set, and waits for a redirect or callback event', - on: { - CALLBACK: 'HandlingCallback', - REDIRECT: { - guard: not('isExampleMode'), - target: 'Redirecting', - reenter: true, - }, - }, - }, - Redirecting: { - description: 'Redirects to the third-party provider for authentication', - tags: ['state:redirect', 'state:loading'], - entry: ['assignActiveStrategy', 'sendToLoading'], - exit: ['unassignActiveStrategy', 'sendToLoading'], - invoke: { - id: 'redirect', - src: 'redirect', - input: ({ context, event }) => { - assertEvent(event, 'REDIRECT'); - - const legalAcceptedField = context.formRef.getSnapshot().context.fields.get('legalAccepted')?.checked; - - return { - basePath: context.basePath, - flow: context.flow, - params: { - ...event.params, - legalAccepted: legalAcceptedField || undefined, - }, - parent: context.parent, - }; - }, - onError: { - actions: 'setFormErrors', - target: 'Idle', - }, - }, - }, - HandlingCallback: { - description: 'Handles the callback from the third-party provider', - tags: ['state:callback', 'state:loading'], - invoke: { - id: 'handleRedirectCallback', - src: 'handleRedirectCallback', - input: ({ context }) => context.parent, - onError: { - actions: ['logError', 'setFormErrors'], - target: 'Idle', - }, - }, - on: { - 'CLERKJS.NAVIGATE.*': { - actions: 'sendToNext', - target: 'Idle', - }, - }, - }, - }, -}); diff --git a/packages/elements/src/internals/machines/third-party/third-party.types.ts b/packages/elements/src/internals/machines/third-party/third-party.types.ts deleted file mode 100644 index 328e2efaaa6..00000000000 --- a/packages/elements/src/internals/machines/third-party/third-party.types.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { AuthenticateWithRedirectParams } from '@clerk/types'; -import type { SetOptional } from 'type-fest'; -import type { ActorRefFrom, AnyActorRef } from 'xstate'; - -import type { ClerkJSNavigationEvent } from '~/internals/machines/utils/clerkjs'; - -import type { TFormMachine } from '../form'; - -type Flow = 'signIn' | 'signUp'; - -// ================= Schema ================= // - -export interface ThirdPartyMachineSchema { - context: ThirdPartyMachineContext; - input: ThirdPartyMachineInput; - events: ThirdPartyMachineEvent; -} - -// ================= Context ================= // - -export interface ThirdPartyMachineContext { - /** - * Currently active strategy - * (Can be used for loading states) - */ - activeStrategy: string | null; // TODO: Update type - basePath: string; - flow: Flow; - formRef: ActorRefFrom; - parent: AnyActorRef; // TODO: Fix circular dependency - loadingStep: 'strategy'; -} - -// ================= Input ================= // - -export interface ThirdPartyMachineInput { - basePath: string; - flow: Flow; - formRef: ActorRefFrom; - parent: AnyActorRef; // TODO: Fix circular dependency -} - -// ================= Events ================= // - -export type RedirectEvent = { - type: 'REDIRECT'; - params: SetOptional; -}; -export type RedirectCallbackEvent = { type: 'CALLBACK' }; -export type CallbackNavigationEvent = { type: ClerkJSNavigationEvent }; - -export type ThirdPartyMachineEvent = RedirectEvent | RedirectCallbackEvent | CallbackNavigationEvent; diff --git a/packages/elements/src/internals/machines/types/index.ts b/packages/elements/src/internals/machines/types/index.ts deleted file mode 100644 index 9331557b3ad..00000000000 --- a/packages/elements/src/internals/machines/types/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './router.types'; diff --git a/packages/elements/src/internals/machines/types/router.types.ts b/packages/elements/src/internals/machines/types/router.types.ts deleted file mode 100644 index 89bd93ee25d..00000000000 --- a/packages/elements/src/internals/machines/types/router.types.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { ClerkRouter } from '@clerk/shared/router'; -import type { - ClerkResource, - EnterpriseSSOStrategy, - LoadedClerk, - OAuthStrategy, - SamlStrategy, - SignInStrategy, - Web3Strategy, -} from '@clerk/types'; -import type { ActorRefFrom } from 'xstate'; - -import type { ClerkElementsError } from '~/internals/errors'; -import type { TFormMachine } from '~/internals/machines/form'; - -// ---------------------------------- Events ---------------------------------- // - -export type BaseRouterLoadingStep = - | 'start' - | 'verifications' - | 'continue' - | 'reset-password' - | 'forgot-password' - | 'choose-strategy' - | 'error'; - -export type BaseRouterNextEvent = { type: 'NEXT'; resource?: T }; -export type BaseRouterFormAttachEvent = { type: 'FORM.ATTACH'; formRef: ActorRefFrom }; -export type BaseRouterPrevEvent = { type: 'NAVIGATE.PREVIOUS' }; -export type BaseRouterStartEvent = { type: 'NAVIGATE.START' }; -export type BaseRouterResetEvent = { type: 'RESET' }; -export type BaseRouterResetStepEvent = { type: 'RESET.STEP' }; -export type BaseRouterErrorEvent = { type: 'ERROR'; error: Error }; -export type BaseRouterTransferEvent = { type: 'TRANSFER' }; -export type BaseRouterLoadingEvent = ( - | { - step: TSteps | undefined; - strategy?: never; - action?: string; - } - | { - step?: never; - strategy: SignInStrategy | undefined; - action?: never; - } -) & { type: 'LOADING'; isLoading: boolean }; - -export type BaseRouterRedirectOauthEvent = { type: 'AUTHENTICATE.OAUTH'; strategy: OAuthStrategy }; -export type BaseRouterRedirectSamlEvent = { type: 'AUTHENTICATE.SAML'; strategy?: SamlStrategy }; -export type BaseRouterRedirectEnterpriseSSOEvent = { - type: 'AUTHENTICATE.ENTERPRISE_SSO'; - strategy?: EnterpriseSSOStrategy; -}; -export type BaseRouterRedirectWeb3Event = { type: 'AUTHENTICATE.WEB3'; strategy: Web3Strategy }; -export type BaseRouterSetClerkEvent = { type: 'CLERK.SET'; clerk: LoadedClerk }; - -export type BaseRouterRedirectEvent = - | BaseRouterRedirectOauthEvent - | BaseRouterRedirectSamlEvent - | BaseRouterRedirectWeb3Event - | BaseRouterRedirectEnterpriseSSOEvent; - -// ---------------------------------- Input ---------------------------------- // - -export interface BaseRouterInput { - clerk: LoadedClerk; - router?: ClerkRouter; - exampleMode?: boolean; -} - -// ---------------------------------- Context ---------------------------------- // - -export interface BaseRouterContext { - clerk: LoadedClerk; - error?: ClerkElementsError; - router?: ClerkRouter; - exampleMode?: boolean; -} diff --git a/packages/elements/src/internals/machines/utils/__tests__/assert.test.ts b/packages/elements/src/internals/machines/utils/__tests__/assert.test.ts deleted file mode 100644 index a0088401120..00000000000 --- a/packages/elements/src/internals/machines/utils/__tests__/assert.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { assertActorEventDone, assertActorEventError, assertIsDefined } from '../assert'; - -describe('assertIsDefined', () => { - it('should throw an error if the value is undefined', () => { - const value = undefined; - expect(() => assertIsDefined(value)).toThrowError('undefined is not defined'); - }); - - it('should throw an error if the value is null', () => { - const value = null; - expect(() => assertIsDefined(value)).toThrowError('null is not defined'); - }); - - it('should not throw an error if the value is defined', () => { - const value = 'Hello'; - expect(() => assertIsDefined(value)).not.toThrowError(); - }); -}); - -describe('assertActorEventError', () => { - it('should throw an error if the event is not an error event', () => { - const event = { type: 'success' }; - expect(() => assertActorEventError(event)).toThrowError('Expected an error event, got "success"'); - }); - - it('should not throw an error if the event is an error event', () => { - const event = { type: 'error', error: new Error('Something went wrong') }; - expect(() => assertActorEventError(event)).not.toThrowError(); - }); -}); - -describe('assertActorEventDone', () => { - it('should throw an error if the event is not a done event', () => { - const event = { type: 'success' }; - expect(() => assertActorEventDone(event)).toThrowError('Expected a done event, got "success"'); - }); - - it('should not throw an error if the event is a done event', () => { - const event = { type: 'done', output: 'Result' }; - expect(() => assertActorEventDone(event)).not.toThrowError(); - }); -}); diff --git a/packages/elements/src/internals/machines/utils/__tests__/formatters.test.ts b/packages/elements/src/internals/machines/utils/__tests__/formatters.test.ts deleted file mode 100644 index b1ee63bc791..00000000000 --- a/packages/elements/src/internals/machines/utils/__tests__/formatters.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { formatName, formatSalutation } from '../formatters'; - -describe('formatName', () => { - test('returns undefined when no arguments are provided', () => { - expect(formatName()).toBeUndefined(); - }); - - test('returns the titleized version of the single argument', () => { - expect(formatName('john')).toBe('John'); - }); - - test('returns the titleized version of multiple arguments joined by space', () => { - expect(formatName('john', 'doe')).toBe('John Doe'); - }); - - test('ignores undefined arguments and returns the titleized version of the rest', () => { - expect(formatName(undefined, 'john', undefined, 'doe')).toBe('John Doe'); - }); -}); - -describe('formatSalutation', () => { - test('returns the formatted salutation based on firstName', () => { - expect(formatSalutation({ firstName: 'John', lastName: undefined, identifier: undefined })).toBe('John'); - }); - - test('returns the formatted salutation based on lastName', () => { - expect(formatSalutation({ firstName: undefined, lastName: 'Doe', identifier: undefined })).toBe('Doe'); - }); - - test('returns the formatted salutation based on identifier', () => { - expect(formatSalutation({ firstName: undefined, lastName: undefined, identifier: 'test@clerk.dev' })).toBe( - 'test@clerk.dev', - ); - }); - - test('returns an empty string when no arguments are provided', () => { - expect(formatSalutation({ firstName: undefined, lastName: undefined, identifier: undefined })).toBe(''); - }); - - test('returns the formatted salutation based on firstName and lastName', () => { - expect(formatSalutation({ firstName: 'John', lastName: 'Doe', identifier: undefined })).toBe('John'); - }); - - test('returns the formatted salutation based on firstName, lastName, and identifier', () => { - expect(formatSalutation({ firstName: 'John', lastName: 'Doe', identifier: 'test@clerk.dev' })).toBe('John'); - }); -}); diff --git a/packages/elements/src/internals/machines/utils/__tests__/next.test.ts b/packages/elements/src/internals/machines/utils/__tests__/next.test.ts deleted file mode 100644 index 611e87a68e2..00000000000 --- a/packages/elements/src/internals/machines/utils/__tests__/next.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { NEXT_WINDOW_HISTORY_SUPPORT_VERSION } from '~/internals/constants'; - -import { shouldUseVirtualRouting } from '../next'; - -let windowSpy: jest.SpyInstance; - -beforeEach(() => { - windowSpy = jest.spyOn(globalThis, 'window', 'get'); -}); - -afterEach(() => { - windowSpy.mockRestore(); -}); - -describe('shouldUseVirtualRouting', () => { - it('should return false if window is undefined', () => { - windowSpy.mockReturnValue(undefined); - - expect(shouldUseVirtualRouting()).toBe(false); - }); - it('should return false if window.next is undefined', () => { - windowSpy.mockReturnValue({}); - - expect(shouldUseVirtualRouting()).toBe(false); - }); - it('should return true if version is lower than NEXT_WINDOW_HISTORY_SUPPORT_VERSION', () => { - windowSpy.mockReturnValue({ next: { version: '14.0.0' } }); - - expect(shouldUseVirtualRouting()).toBe(true); - }); - it('should return false if version is NEXT_ROUTING_CHANGE_VERSION', () => { - windowSpy.mockReturnValue({ next: { version: NEXT_WINDOW_HISTORY_SUPPORT_VERSION } }); - - expect(shouldUseVirtualRouting()).toBe(false); - }); - it('should return false if version is higher than NEXT_WINDOW_HISTORY_SUPPORT_VERSION', () => { - windowSpy.mockReturnValue({ next: { version: '14.6.0' } }); - - expect(shouldUseVirtualRouting()).toBe(false); - }); -}); diff --git a/packages/elements/src/internals/machines/utils/__tests__/strategies.test.ts b/packages/elements/src/internals/machines/utils/__tests__/strategies.test.ts deleted file mode 100644 index 189a2049c8c..00000000000 --- a/packages/elements/src/internals/machines/utils/__tests__/strategies.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { matchStrategy } from '../strategies'; - -describe('matchStrategy', () => { - it('should return false if either current or desired is undefined', () => { - expect(matchStrategy(undefined, 'oauth')).toBe(false); - expect(matchStrategy('password', undefined)).toBe(false); - expect(matchStrategy(undefined, undefined)).toBe(false); - }); - - it('should return true if current is equal to desired', () => { - expect(matchStrategy('password', 'password')).toBe(true); - }); - - it('should return true if current partially matches desired', () => { - expect(matchStrategy('oauth_google', 'oauth')).toBe(true); - expect(matchStrategy('web3_metamask_signature', 'web3')).toBe(true); - expect(matchStrategy('web3_metamask_signature', 'web3_metamask')).toBe(true); - }); - - it('should return false on invalid partial matches', () => { - expect(matchStrategy('oauth_google', 'web3')).toBe(false); - expect(matchStrategy('oauth_google', 'oauth_goog')).toBe(false); - }); -}); diff --git a/packages/elements/src/internals/machines/utils/assert.ts b/packages/elements/src/internals/machines/utils/assert.ts deleted file mode 100644 index 0ae136a2698..00000000000 --- a/packages/elements/src/internals/machines/utils/assert.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { DoneActorEvent, ErrorActorEvent, EventObject } from 'xstate'; - -export function assertIsDefined(value: T, label?: string): asserts value is NonNullable { - if (value === undefined || value === null) { - throw new Error(`${label || value} is not defined`); - } -} - -export function assertActorEventDone(event: EventObject): asserts event is DoneActorEvent { - if ('output' in event === false) { - throw new Error(`Expected a done event, got "${event.type}"`); - } -} - -export function assertActorEventError(event: EventObject): asserts event is ErrorActorEvent { - if ('error' in event === false) { - throw new Error(`Expected an error event, got "${event.type}"`); - } -} diff --git a/packages/elements/src/internals/machines/utils/clerkjs.ts b/packages/elements/src/internals/machines/utils/clerkjs.ts deleted file mode 100644 index 8afb93772a3..00000000000 --- a/packages/elements/src/internals/machines/utils/clerkjs.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Simplify } from 'type-fest'; - -// ================= Types ================= // - -export type ClerkJSEventCategory = 'NAVIGATE'; -export type ClerkJSEvent = `CLERKJS.${T}.${string}`; -export type ClerkJSEventObject = Simplify< - Record> ->; -export type ClerkJSEventExtractCategory = S extends `CLERKJS.${infer T}.${string}` ? T : never; - -// ================= Type Narrowing ================= // - -export function isClerkJSEvent>, E extends ClerkJSEvent>( - eventObj: T, - event: E, -): event is typeof event { - return Object.values(eventObj).includes(event as any); -} - -// ================= ClerkJSNavigationEvent ================= // - -export type ClerkJSNavigationEvent = (typeof ClerkJSNavigationEvent)[keyof typeof ClerkJSNavigationEvent]; -export const ClerkJSNavigationEvent: ClerkJSEventObject<'NAVIGATE'> = { - complete: 'CLERKJS.NAVIGATE.COMPLETE', - signUp: 'CLERKJS.NAVIGATE.SIGN_UP', - continue: 'CLERKJS.NAVIGATE.CONTINUE', - generic: 'CLERKJS.NAVIGATE.GENERIC', - resetPassword: 'CLERKJS.NAVIGATE.RESET_PASSWORD', - signIn: 'CLERKJS.NAVIGATE.SIGN_IN', - verification: 'CLERKJS.NAVIGATE.VERIFICATION', -} as const; - -export function isClerkJSNavigationEvent(event: unknown): event is ClerkJSNavigationEvent { - return isClerkJSEvent(ClerkJSNavigationEvent, event as ClerkJSEvent<'NAVIGATE'>); -} diff --git a/packages/elements/src/internals/machines/utils/formatters.ts b/packages/elements/src/internals/machines/utils/formatters.ts deleted file mode 100644 index 8198ec4e052..00000000000 --- a/packages/elements/src/internals/machines/utils/formatters.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { titleize } from '@clerk/shared/underscore'; - -// TODO: ideally the derivation of these values lives in FAPI and comes back directly from the API - -export function formatName(...args: (string | undefined)[]): string | undefined { - switch (args.length) { - case 0: - return undefined; - case 1: - return titleize(args[0]); - default: - return args.filter(Boolean).map(titleize).join(' '); - } -} - -export function formatSalutation({ - firstName, - lastName, - identifier, -}: { - firstName: string | undefined; - lastName: string | undefined; - identifier: string | undefined | null; -}): string { - return (firstName && formatName(firstName)) || (lastName && formatName(lastName)) || identifier || ''; -} diff --git a/packages/elements/src/internals/machines/utils/next.ts b/packages/elements/src/internals/machines/utils/next.ts deleted file mode 100644 index 69a08cc6bdd..00000000000 --- a/packages/elements/src/internals/machines/utils/next.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { NEXT_WINDOW_HISTORY_SUPPORT_VERSION } from '~/internals/constants'; - -export function shouldUseVirtualRouting() { - if (typeof window === 'undefined') { - return false; - } - - if (!window.next) { - return false; - } - - return window.next.version < NEXT_WINDOW_HISTORY_SUPPORT_VERSION; -} diff --git a/packages/elements/src/internals/machines/utils/strategies.ts b/packages/elements/src/internals/machines/utils/strategies.ts deleted file mode 100644 index 5ea580e25ca..00000000000 --- a/packages/elements/src/internals/machines/utils/strategies.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const matchStrategy = (current: string | undefined, desired: string | undefined): boolean => { - if (!current || !desired) { - return false; - } - - if (current === desired) { - return true; - } - - return current.startsWith(`${desired}_`); -}; diff --git a/packages/elements/src/internals/utils/inspector/browser/index.ts b/packages/elements/src/internals/utils/inspector/browser/index.ts deleted file mode 100644 index 30f12a3e821..00000000000 --- a/packages/elements/src/internals/utils/inspector/browser/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { isTruthy } from '@clerk/shared/underscore'; -import { createBrowserInspector } from '@statelyai/inspect'; - -import { safeAccess } from '~/utils/safe-access'; - -export const getInspector = () => { - if ( - __DEV__ && - typeof window !== 'undefined' && - process.env.NODE_ENV === 'development' && - isTruthy( - safeAccess(() => process.env.NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG_UI ?? process.env.CLERK_ELEMENTS_DEBUG_UI, false), - ) - ) { - const { inspect } = createBrowserInspector({ - autoStart: true, - }); - - return inspect; - } - - return undefined; -}; diff --git a/packages/elements/src/internals/utils/inspector/console/console.ts b/packages/elements/src/internals/utils/inspector/console/console.ts deleted file mode 100644 index 0f2f6fa2f57..00000000000 --- a/packages/elements/src/internals/utils/inspector/console/console.ts +++ /dev/null @@ -1,169 +0,0 @@ -import type { ActorRefLike, AnyEventObject, InspectionEvent, Observer } from 'xstate'; - -let consoleInspector: Observer | undefined; - -export interface ConsoleInspectorOptions { - /** - * Enable server-side debugging - */ - debugServer?: boolean; - /** - * Enable console inspector - */ - enabled: boolean; -} - -export function createConsoleInspector({ - enabled, - debugServer, -}: ConsoleInspectorOptions): Observer | undefined { - if (consoleInspector) { - return consoleInspector; - } - if (!enabled || (!debugServer && typeof window === 'undefined')) { - return undefined; - } - - const parseRefId = (ref: ActorRefLike | undefined, includeSystemId?: false): string | undefined => { - if (!ref) { - return undefined; - } - - // @ts-expect-error - Exists on the ref.src - const id = ref.src?.id; - - // @ts-expect-error - id exists on ActorRefLike - let output = id || ref.id; - - if (includeSystemId) { - // @ts-expect-error - id exists on ActorRefLike - output += `(${ref.id})`; - } - - return output; - }; - - function logEvent(labelOrMsg: any, ...optionalParams: any[]): void { - if (optionalParams && optionalParams.length > 0) { - console.log(`%c${labelOrMsg}%c`, 'font-weight: bold;', 'color: inherit;', ...optionalParams); - } else { - console.log(labelOrMsg); - } - } - - const defaults = 'font-weight: bold; line-height: 1.5; border-radius: 8px; padding: 4px 10px;'; - const reset = 'color: inherit;'; - - const Styles = { - info: { - label: `background: #113264; color: #8EC8F6;`, // blue 12, blue 5 - sublabel: `background: #113264; color: #C2E5FF;`, // blue 12, blue 7 - }, - success: { - label: `background: #203C25; color: #94CE9A;`, // grass 12, grass 5 - sublabel: `background: #203C25; color: #C9E8CA;`, // grass 12, grass 7 - }, - warning: { - label: `background: #473B1F; color: #E4C767;`, // yellow 12, yellow 5 - sublabel: `background: #473B1F; color: #FFE770;`, // yellow 12, yellow 7 - }, - error: { - label: `background: #5C271F; color: #F5A898;`, // tomato 12, tomato 5 - sublabel: `background: #5C271F; color: #FFCDC2;`, // tomato 12, tomato 7 - }, - } as const; - - type Style = keyof typeof Styles; - - const logGroup = ( - { label, sublabel, details, style = 'info' }: { label: string; sublabel?: string; details?: string; style?: Style }, - cb: () => void, - ) => { - const styles = Styles[style]; - - const msg = [`%c${label}%c\t`]; - const params: string[] = [`${defaults} ${styles.label}`, reset]; - - if (sublabel) { - msg.push(`%c${sublabel}%c`); - params.push(`${defaults} ${styles.sublabel}`, reset); - } - - if (details) { - msg.push(`%c${details}`); - params.push(defaults); - } - - console.groupCollapsed(msg.join(''), ...params); - cb(); - console.groupEnd(); - }; - - function determineStyleFromEvent(event: InspectionEvent | AnyEventObject): Style { - switch (event.type) { - case 'ROUTE.REGISTER': - return 'success'; - case 'ROUTE.UNREGISTER': - return 'warning'; - case 'SUBMIT': - return 'info'; - } - - if (event.type.startsWith('xstate.done.')) { - return 'success'; - } else if (event.type.startsWith('xstate.error.')) { - return 'error'; - } - - return 'info'; - } - - consoleInspector = { - next: inspectionEvent => { - if (inspectionEvent.type === '@xstate.actor') { - logGroup({ label: 'ACTOR', sublabel: parseRefId(inspectionEvent.actorRef) }, () => { - logEvent('Actor Ref', inspectionEvent.actorRef); - }); - } - - if (inspectionEvent.type === '@xstate.event') { - logGroup( - { - label: 'EVENT', - sublabel: inspectionEvent.event.type, - details: [parseRefId(inspectionEvent.sourceRef), parseRefId(inspectionEvent.actorRef)] - .filter(Boolean) - .join(' ⮕ '), - style: determineStyleFromEvent(inspectionEvent.event), - }, - () => { - logEvent('Type', inspectionEvent.event.type); - logEvent('Source', inspectionEvent.sourceRef); - logEvent('Actor', inspectionEvent.actorRef); - logEvent('Event', inspectionEvent.event); - }, - ); - } - - if (inspectionEvent.type === '@xstate.snapshot') { - logGroup( - { - label: 'SNAPSHOT', - sublabel: parseRefId(inspectionEvent.actorRef), - style: determineStyleFromEvent(inspectionEvent.event), - }, - () => { - logEvent('Type', inspectionEvent.event.type); - // @ts-expect-error - _parent exists on ActorRefLike - logEvent('Parent', parseRefId(inspectionEvent.actorRef._parent)); - logEvent('Actor', inspectionEvent.actorRef); - logEvent('Event', inspectionEvent.event); - logEvent('Snapshot', inspectionEvent.snapshot); - }, - ); - } - }, - }; - - return consoleInspector; -} diff --git a/packages/elements/src/internals/utils/inspector/console/index.ts b/packages/elements/src/internals/utils/inspector/console/index.ts deleted file mode 100644 index 1af63ce18df..00000000000 --- a/packages/elements/src/internals/utils/inspector/console/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { isTruthy } from '@clerk/shared/underscore'; - -import { safeAccess } from '~/utils/safe-access'; - -import { createConsoleInspector } from './console'; - -export function getInspector() { - if ( - __DEV__ && - process.env.NODE_ENV === 'development' && - isTruthy( - safeAccess(() => process.env.NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG_UI ?? process.env.CLERK_ELEMENTS_DEBUG_UI, false), - ) - ) { - return createConsoleInspector({ - enabled: true, - debugServer: isTruthy(safeAccess(() => process.env.CLERK_ELEMENTS_DEBUG_SERVER, false)), - }); - } - return undefined; -} diff --git a/packages/elements/src/internals/utils/inspector/index.ts b/packages/elements/src/internals/utils/inspector/index.ts deleted file mode 100644 index 8ff89fbdd38..00000000000 --- a/packages/elements/src/internals/utils/inspector/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { InspectionEvent, Observer } from 'xstate'; - -import { getInspector as getBrowserInspector } from './browser'; -import { getInspector as getConsoleInspector } from './console'; - -export let inspect: Observer | undefined; - -if (__DEV__) { - inspect = getBrowserInspector() ?? getConsoleInspector(); -} - -const inspector = { - inspect, -}; - -export default inspector; diff --git a/packages/elements/src/react/common/connections.tsx b/packages/elements/src/react/common/connections.tsx deleted file mode 100644 index 53986bb83ba..00000000000 --- a/packages/elements/src/react/common/connections.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import type { EnterpriseSSOStrategy, OAuthProvider, SamlStrategy, Web3Provider } from '@clerk/types'; -import { Slot } from '@radix-ui/react-slot'; -import { createContext, useContext } from 'react'; - -import type { ThirdPartyProvider } from '~/utils/third-party-strategies'; - -import { useThirdPartyProvider } from '../hooks'; -import { SignInRouterCtx } from '../sign-in/context'; -import { SignUpRouterCtx } from '../sign-up/context'; - -export type UseThirdPartyProviderReturn = - | (ThirdPartyProvider & { - events: { - authenticate: (event: React.MouseEvent) => void; - }; - }) - | null; - -export const ConnectionContext = createContext(null); -export const useConnectionContext = () => { - const ctx = useContext(ConnectionContext); - - if (!ctx) { - throw new Error('useConnectionContext must be used within '); - } - - return ctx; -}; - -export interface ConnectionProps extends React.ButtonHTMLAttributes { - asChild?: boolean; - name: OAuthProvider | Web3Provider | SamlStrategy | EnterpriseSSOStrategy; -} - -/** - * Renders a social connection button based on the provided name. If your instance does not have the social connection enabled, this component will throw an error in development. - * - * **Tip:** You can use the `` component to render the social connection icon. - * - * @param {boolean} [asChild] - If true, `` will render as its child element, passing along any necessary props. - * @param {OAuthProvider | Web3Provider} name - The name of the social connection to render. - * - * @example - * - * - * - * Sign in with Google - * - * - * - */ -export function Connection({ asChild, name, ...rest }: ConnectionProps) { - const signInRef = SignInRouterCtx.useActorRef(true); - const signUpRef = SignUpRouterCtx.useActorRef(true); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const provider = useThirdPartyProvider((signInRef || signUpRef)!, name); - - if (!provider) { - return null; - } - - const Comp = asChild ? Slot : 'button'; - const defaultProps = asChild ? {} : { type: 'button' as const }; - - return ( - - - - ); -} - -export interface IconProps extends Omit, 'src'> { - asChild?: boolean; -} - -/** - * `` **must** be used inside ``. By default, `` will render as an `` element with the `src` pointing to the logo of the currently used ``. - * - * @param {boolean} [asChild] - If true, `` will render as its child element, passing along any necessary props. - * - * @example - * - * - * - * - * Sign in with Google - * - * - * - */ -export function Icon({ asChild, ...rest }: IconProps) { - const { iconUrl, name } = useConnectionContext(); - - const Comp = asChild ? Slot : 'img'; - return ( - - ); -} diff --git a/packages/elements/src/react/common/form/field-error.tsx b/packages/elements/src/react/common/form/field-error.tsx deleted file mode 100644 index 605312e0c11..00000000000 --- a/packages/elements/src/react/common/form/field-error.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import type { FormMessageProps as RadixFormMessageProps } from '@radix-ui/react-form'; -import { FormMessage as RadixFormMessage } from '@radix-ui/react-form'; -import { Slot } from '@radix-ui/react-slot'; -import * as React from 'react'; - -import { ClerkElementsRuntimeError } from '~/internals/errors'; -import { isReactFragment } from '~/react/utils/is-react-fragment'; - -import { useFieldContext, useFieldFeedback } from './hooks'; -import type { FormErrorProps } from './types'; - -const DISPLAY_NAME = 'ClerkElementsFieldError'; - -export type FormFieldErrorProps = FormErrorProps; -type FormFieldErrorElement = React.ElementRef; - -/** - * FieldError renders error messages associated with a specific field. By default, the error's message will be rendered in an unstyled ``. Optionally, the `children` prop accepts a function to completely customize rendering. - * - * @param {string} [name] - Used to target a specific field by name when rendering outside of a `` component. - * @param {Function} [children] - A function that receives `message` and `code` as arguments. - * - * @example - * - * - * - * - * @example - * - * - * {({ message, code }) => ( - * {message} - * )} - * - * - */ -export const FieldError = React.forwardRef( - ({ asChild = false, children, code, name, ...rest }, forwardedRef) => { - const fieldContext = useFieldContext(); - const rawFieldName = fieldContext?.name || name; - const fieldName = rawFieldName === 'backup_code' ? 'code' : rawFieldName; - const { feedback } = useFieldFeedback({ name: fieldName }); - - if (!(feedback?.type === 'error')) { - return null; - } - - const error = feedback.message; - - if (!error) { - return null; - } - - const Comp = asChild ? Slot : 'span'; - const child = typeof children === 'function' ? children(error) : children; - - // const forceMatch = code ? error.code === code : undefined; // TODO: Re-add when Radix Form is updated - - if (isReactFragment(child)) { - throw new ClerkElementsRuntimeError(' cannot render a Fragment as a child.'); - } - - return ( - - {child || error.message} - - ); - }, -); - -FieldError.displayName = DISPLAY_NAME; diff --git a/packages/elements/src/react/common/form/field-state.tsx b/packages/elements/src/react/common/form/field-state.tsx deleted file mode 100644 index 0f7038e37e0..00000000000 --- a/packages/elements/src/react/common/form/field-state.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { ClerkElementsFieldError } from '~/internals/errors'; -import type { ErrorCodeOrTuple } from '~/react/utils/generate-password-error-text'; - -import { useFieldContext, useFieldFeedback, useFieldState, useValidityStateContext } from './hooks'; -import type { FieldStates } from './types'; -import { enrichFieldState } from './utils'; - -type FieldStateRenderFn = { - children: (state: { - state: FieldStates; - message: string | undefined; - codes: ErrorCodeOrTuple[] | undefined; - }) => React.ReactNode; -}; - -const DISPLAY_NAME = 'ClerkElementsFieldState'; - -/** - * Programmatically access the state of the wrapping ``. Useful for implementing animations when direct access to the state value is necessary. - * - * @param {Function} children - A function that receives `state`, `message`, and `codes` as an argument. `state` will is a union of `"success" | "error" | "idle" | "warning" | "info"`. `message` will be the corresponding message, e.g. error message. `codes` will be an array of keys that were used to generate the password validation messages. This prop is only available when the field is of type `password` and has `validatePassword` set to `true`. - * - * @example - * - * Email - * - * {({ state }) => ( - * - * )} - * - * - * - * @example - * - * Password - * - * - * {({ state, message, codes }) => ( - *
Field state: {state}
- *
Field msg: {message}
- *
Pwd keys: {codes.join(', ')}
- * )} - *
- *
- */ -export function FieldState({ children }: FieldStateRenderFn) { - const field = useFieldContext(); - const { feedback } = useFieldFeedback({ name: field?.name }); - const { state } = useFieldState({ name: field?.name }); - const validity = useValidityStateContext(); - - const message = feedback?.message instanceof ClerkElementsFieldError ? feedback.message.message : feedback?.message; - const codes = feedback?.codes; - - const fieldState = { state: enrichFieldState(validity, state), message, codes }; - - return children(fieldState); -} - -FieldState.displayName = DISPLAY_NAME; diff --git a/packages/elements/src/react/common/form/field.tsx b/packages/elements/src/react/common/form/field.tsx deleted file mode 100644 index ed85455edba..00000000000 --- a/packages/elements/src/react/common/form/field.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import type { Autocomplete } from '@clerk/types'; -import type { FormFieldProps as RadixFormFieldProps } from '@radix-ui/react-form'; -import { Field as RadixField, ValidityState as RadixValidityState } from '@radix-ui/react-form'; -import * as React from 'react'; - -import { useFormStore } from '~/internals/machines/form/form.context'; - -import { FieldContext, useField, useFieldState, ValidityStateContext } from './hooks'; -import type { ClerkFieldId, FieldStates } from './types'; -import { enrichFieldState } from './utils'; - -const DISPLAY_NAME = 'ClerkElementsField'; -const DISPLAY_NAME_INNER = 'ClerkElementsFieldInner'; - -type FormFieldElement = React.ElementRef; -export type FormFieldProps = Omit & { - name: Autocomplete; - alwaysShow?: boolean; - children: React.ReactNode | ((state: FieldStates) => React.ReactNode); -}; - -/** - * Field is used to associate its child elements with a specific input. It automatically handles unique ID generation and associating the contained label and input elements. - * - * @param name - Give your `` a unique name inside the current form. If you choose one of the following names Clerk Elements will automatically set the correct type on the `` element: `emailAddress`, `password`, `phoneNumber`, and `code`. - * @param alwaysShow - Optional. When `true`, the field will always be renydered, regardless of its state. By default, a field is hidden if it's optional or if it's a filled-out required field. - * @param {Function} children - A function that receives `state` as an argument. `state` is a union of `"success" | "error" | "idle" | "warning" | "info"`. - * - * @example - * - * Email - * - * - * - * @example - * - * {(fieldState) => ( - * Email - * - * )} - * - */ -export const Field = React.forwardRef(({ alwaysShow, ...rest }, forwardedRef) => { - const formRef = useFormStore(); - const formCtx = formRef.getSnapshot().context; - // A field is marked as hidden if it's optional OR if it's a filled-out required field - const isHiddenField = formCtx.progressive && Boolean(formCtx.hidden?.has(rest.name)); - - // Only alwaysShow={true} should force behavior to render the field, on `undefined` or alwaysShow={false} the isHiddenField logic should take over - const shouldHide = alwaysShow ? false : isHiddenField; - - return shouldHide ? null : ( - - - - ); -}); - -Field.displayName = DISPLAY_NAME; - -const FieldInner = React.forwardRef((props, forwardedRef) => { - const { children, ...rest } = props; - const field = useField({ name: rest.name }); - const { state: fieldState } = useFieldState({ name: rest.name }); - - return ( - - - {validity => { - const enrichedFieldState = enrichFieldState(validity, fieldState); - - return ( - - {typeof children === 'function' ? children(enrichedFieldState) : children} - - ); - }} - - - ); -}); - -FieldInner.displayName = DISPLAY_NAME_INNER; diff --git a/packages/elements/src/react/common/form/form.tsx b/packages/elements/src/react/common/form/form.tsx deleted file mode 100644 index 9f5b67bf740..00000000000 --- a/packages/elements/src/react/common/form/form.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { composeEventHandlers } from '@radix-ui/primitive'; -import type { FormProps as RadixFormProps } from '@radix-ui/react-form'; -import { Form as RadixForm } from '@radix-ui/react-form'; -import * as React from 'react'; -import type { BaseActorRef } from 'xstate'; - -import { useForm } from './hooks'; - -const DISPLAY_NAME = 'ClerkElementsForm'; - -type FormElement = React.ElementRef; -export type FormProps = Omit & { - children: React.ReactNode; - flowActor?: BaseActorRef<{ type: 'SUBMIT'; action: 'submit' }>; -}; - -export const Form = React.forwardRef(({ flowActor, onSubmit, ...rest }, forwardedRef) => { - const form = useForm({ flowActor: flowActor }); - - const { onSubmit: internalOnSubmit, ...internalFormProps } = form.props; - - return ( - - ); -}); - -Form.displayName = DISPLAY_NAME; diff --git a/packages/elements/src/react/common/form/global-error.tsx b/packages/elements/src/react/common/form/global-error.tsx deleted file mode 100644 index 14d92642854..00000000000 --- a/packages/elements/src/react/common/form/global-error.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Slot } from '@radix-ui/react-slot'; -import * as React from 'react'; - -import { ClerkElementsRuntimeError } from '~/internals/errors'; -import { isReactFragment } from '~/react/utils/is-react-fragment'; - -import { useGlobalErrors } from './hooks'; -import type { FormErrorProps } from './types'; - -const DISPLAY_NAME = 'ClerkElementsGlobalError'; - -type FormGlobalErrorElement = React.ElementRef<'div'>; -export type FormGlobalErrorProps = FormErrorProps>; - -/** - * Used to render errors that are returned from Clerk's API, but that are not associated with a specific form field. By default, will render the error's message wrapped in a `
`. Optionally, the `children` prop accepts a function to completely customize rendering. Must be placed **inside** components like ``/`` to have access to the underlying form state. - * - * @param {string} [code] - Forces the message with the matching code to be shown. This is useful when using server-side validation. - * @param {Function} [children] - A function that receives `message` and `code` as arguments. - * @param {boolean} [asChild] - If `true`, `` will render as its child element, passing along any necessary props. - * - * @example - * - * - * - * - * @example - * - * Your custom error message. - * - * - * @example - * - * - * {({ message, code }) => ( - * {message} - * )} - * - * - */ -export const GlobalError = React.forwardRef( - ({ asChild = false, children, code, ...rest }, forwardedRef) => { - const { errors } = useGlobalErrors(); - - const error = errors?.[0]; - - if (!error || (code && error.code !== code)) { - return null; - } - - const Comp = asChild ? Slot : 'div'; - const child = typeof children === 'function' ? children(error) : children; - - if (isReactFragment(child)) { - throw new ClerkElementsRuntimeError(' cannot render a Fragment as a child.'); - } - - return ( - - {child || error.message} - - ); - }, -); - -GlobalError.displayName = DISPLAY_NAME; diff --git a/packages/elements/src/react/common/form/hooks/__tests__/use-field-feedback.test.ts b/packages/elements/src/react/common/form/hooks/__tests__/use-field-feedback.test.ts deleted file mode 100644 index 312a2e65257..00000000000 --- a/packages/elements/src/react/common/form/hooks/__tests__/use-field-feedback.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { renderHook } from '@testing-library/react'; - -import * as internalFormHooks from '~/internals/machines/form/form.context'; - -import { useFieldFeedback } from '../use-field-feedback'; - -type Props = Parameters[0]; - -describe('useFieldFeedback', () => { - it('should correctly output feedback', () => { - const initialProps = { name: 'foo' }; - const returnValue = { codes: 'bar', message: 'baz', type: 'error' }; - - jest.spyOn(internalFormHooks, 'useFormSelector').mockReturnValue(returnValue); - - const { result } = renderHook((props: Props) => useFieldFeedback(props), { initialProps }); - - expect(result.current).toEqual({ feedback: returnValue }); - }); -}); diff --git a/packages/elements/src/react/common/form/hooks/__tests__/use-form.test.tsx b/packages/elements/src/react/common/form/hooks/__tests__/use-form.test.tsx deleted file mode 100644 index 889c330fce1..00000000000 --- a/packages/elements/src/react/common/form/hooks/__tests__/use-form.test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { fireEvent, render, renderHook } from '@testing-library/react'; -import { createActor, createMachine } from 'xstate'; - -import { ClerkElementsError } from '~/internals/errors'; - -import { useForm } from '../use-form'; -import * as errorHooks from '../use-global-errors'; - -describe('useForm', () => { - const machine = createMachine({ - on: { - RESET: '.idle', - }, - initial: 'idle', - states: { - idle: { - on: { - SUBMIT: 'success', - }, - }, - success: {}, - }, - }); - - const actor = createActor(machine).start(); - - beforeEach(() => { - actor.send({ type: 'RESET' }); - }); - - it('should correctly output props (no errors)', () => { - jest.spyOn(errorHooks, 'useGlobalErrors').mockReturnValue({ errors: [] }); - - const { result } = renderHook(() => useForm({ flowActor: actor })); - - expect(result.current).toEqual({ - props: { - onSubmit: expect.any(Function), - }, - }); - }); - - it('should correctly output props (has errors)', () => { - jest.spyOn(errorHooks, 'useGlobalErrors').mockReturnValue({ - errors: [new ClerkElementsError('email-link-verification-failed', 'Email verification failed')], - }); - - const { result } = renderHook(() => useForm({ flowActor: actor })); - - expect(result.current).toEqual({ - props: { - 'data-global-error': true, - onSubmit: expect.any(Function), - }, - }); - }); - - it('should create an onSubmit handler', () => { - jest.spyOn(errorHooks, 'useGlobalErrors').mockReturnValue({ errors: [] }); - - const { result } = renderHook(() => useForm({ flowActor: actor })); - const { getByTestId } = render( - , - ); - - fireEvent.submit(getByTestId('form')); - - expect(actor.getSnapshot().value).toEqual('success'); - }); -}); diff --git a/packages/elements/src/react/common/form/hooks/__tests__/use-global-errors.test.ts b/packages/elements/src/react/common/form/hooks/__tests__/use-global-errors.test.ts deleted file mode 100644 index c322f1e2d8a..00000000000 --- a/packages/elements/src/react/common/form/hooks/__tests__/use-global-errors.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { renderHook } from '@testing-library/react'; - -import { ClerkElementsError } from '~/internals/errors'; -import * as internalFormHooks from '~/internals/machines/form/form.context'; - -import { useGlobalErrors } from '../use-global-errors'; - -describe('useGlobalErrors', () => { - it('should correctly output errors (no errors)', () => { - const returnValue: ClerkElementsError[] = []; - - jest.spyOn(internalFormHooks, 'useFormSelector').mockReturnValue([]); - - const { result } = renderHook(() => useGlobalErrors()); - - expect(result.current).toEqual({ errors: returnValue }); - }); - - it('should correctly output errors (has errors)', () => { - const returnValue = [new ClerkElementsError('email-link-verification-failed', 'Email verification failed')]; - - jest.spyOn(internalFormHooks, 'useFormSelector').mockReturnValue(returnValue); - - const { result } = renderHook(() => useGlobalErrors()); - - expect(result.current).toEqual({ errors: returnValue }); - }); -}); diff --git a/packages/elements/src/react/common/form/hooks/__tests__/use-previous.test.ts b/packages/elements/src/react/common/form/hooks/__tests__/use-previous.test.ts deleted file mode 100644 index 50cec13a168..00000000000 --- a/packages/elements/src/react/common/form/hooks/__tests__/use-previous.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { renderHook } from '@testing-library/react'; - -import { usePrevious } from '../use-previous'; - -describe('usePrevious', () => { - it('should retain the previous value', () => { - const { result, rerender } = renderHook((props: string) => usePrevious(props), { initialProps: 'foo' }); - expect(result.current).toBeUndefined(); - - rerender('bar'); - expect(result.current).toBe('foo'); - - rerender('baz'); - expect(result.current).toBe('bar'); - }); -}); diff --git a/packages/elements/src/react/common/form/hooks/index.ts b/packages/elements/src/react/common/form/hooks/index.ts deleted file mode 100644 index 3086f41f44b..00000000000 --- a/packages/elements/src/react/common/form/hooks/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { useField } from './use-field'; -export { useFieldContext, FieldContext } from './use-field-context'; -export { useFieldFeedback } from './use-field-feedback'; -export { useFieldState } from './use-field-state'; -export { useForm } from './use-form'; -export { useGlobalErrors } from './use-global-errors'; -export { useInput } from './use-input'; -export { usePrevious } from './use-previous'; -export { useValidityStateContext, ValidityStateContext } from './use-validity-state-context'; diff --git a/packages/elements/src/react/common/form/hooks/use-field-context.ts b/packages/elements/src/react/common/form/hooks/use-field-context.ts deleted file mode 100644 index 7ffb519dc69..00000000000 --- a/packages/elements/src/react/common/form/hooks/use-field-context.ts +++ /dev/null @@ -1,6 +0,0 @@ -import * as React from 'react'; - -import type { FieldDetails } from '~/internals/machines/form'; - -export const FieldContext = React.createContext | null>(null); -export const useFieldContext = () => React.useContext(FieldContext); diff --git a/packages/elements/src/react/common/form/hooks/use-field-feedback.ts b/packages/elements/src/react/common/form/hooks/use-field-feedback.ts deleted file mode 100644 index 86b9e49f830..00000000000 --- a/packages/elements/src/react/common/form/hooks/use-field-feedback.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type FieldDetails, fieldFeedbackSelector, useFormSelector } from '~/internals/machines/form'; - -export function useFieldFeedback({ name }: Partial>) { - const feedback = useFormSelector(fieldFeedbackSelector(name)); - - return { - feedback, - }; -} diff --git a/packages/elements/src/react/common/form/hooks/use-field-state.ts b/packages/elements/src/react/common/form/hooks/use-field-state.ts deleted file mode 100644 index a30b8cf44b4..00000000000 --- a/packages/elements/src/react/common/form/hooks/use-field-state.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { type FieldDetails, fieldHasValueSelector, useFormSelector } from '~/internals/machines/form'; - -import { FIELD_STATES, type FieldStates } from '../types'; -import { useFieldFeedback } from './use-field-feedback'; - -/** - * Given a field name, determine the current state of the field - */ -export function useFieldState({ name }: Partial>) { - const { feedback } = useFieldFeedback({ name }); - const hasValue = useFormSelector(fieldHasValueSelector(name)); - - /** - * If hasValue is false, the state should be idle - * The rest depends on the feedback type - */ - let state: FieldStates = FIELD_STATES.idle; - - if (!hasValue) { - state = FIELD_STATES.idle; - } - - switch (feedback?.type) { - case 'error': - state = FIELD_STATES.error; - break; - case 'warning': - state = FIELD_STATES.warning; - break; - case 'info': - state = FIELD_STATES.info; - break; - case 'success': - state = FIELD_STATES.success; - break; - default: - break; - } - - return { - state, - }; -} diff --git a/packages/elements/src/react/common/form/hooks/use-field.ts b/packages/elements/src/react/common/form/hooks/use-field.ts deleted file mode 100644 index e12f93123e1..00000000000 --- a/packages/elements/src/react/common/form/hooks/use-field.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { type FieldDetails, fieldHasValueSelector, useFormSelector } from '~/internals/machines/form'; - -import { useFieldFeedback } from './use-field-feedback'; - -export function useField({ name }: Partial>) { - const hasValue = useFormSelector(fieldHasValueSelector(name)); - const { feedback } = useFieldFeedback({ name }); - - const shouldBeHidden = false; // TODO: Implement clerk-js utils - const hasError = feedback ? feedback.type === 'error' : false; - - return { - hasValue, - props: { - 'data-hidden': shouldBeHidden ? true : undefined, - serverInvalid: hasError, - }, - }; -} diff --git a/packages/elements/src/react/common/form/hooks/use-form.ts b/packages/elements/src/react/common/form/hooks/use-form.ts deleted file mode 100644 index 7213241551a..00000000000 --- a/packages/elements/src/react/common/form/hooks/use-form.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useCallback } from 'react'; -import type { BaseActorRef } from 'xstate'; - -import { useGlobalErrors } from './use-global-errors'; - -/** - * Provides the form submission handler along with the form's validity via a data attribute - */ -export function useForm({ flowActor }: { flowActor?: BaseActorRef<{ type: 'SUBMIT'; action: 'submit' }> }) { - const { errors } = useGlobalErrors(); - - // Register the onSubmit handler for form submission - // TODO: merge user-provided submit handler - const onSubmit = useCallback( - (event: React.FormEvent) => { - event.preventDefault(); - if (flowActor) { - flowActor.send({ type: 'SUBMIT', action: 'submit' }); - } - }, - [flowActor], - ); - - return { - props: { - ...(errors.length > 0 ? { 'data-global-error': true } : {}), - onSubmit, - }, - }; -} diff --git a/packages/elements/src/react/common/form/hooks/use-global-errors.ts b/packages/elements/src/react/common/form/hooks/use-global-errors.ts deleted file mode 100644 index 3b28a13784a..00000000000 --- a/packages/elements/src/react/common/form/hooks/use-global-errors.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { globalErrorsSelector, useFormSelector } from '~/internals/machines/form'; - -export function useGlobalErrors() { - const errors = useFormSelector(globalErrorsSelector); - - return { - errors, - }; -} diff --git a/packages/elements/src/react/common/form/hooks/use-input.tsx b/packages/elements/src/react/common/form/hooks/use-input.tsx deleted file mode 100644 index 2500b6eb24b..00000000000 --- a/packages/elements/src/react/common/form/hooks/use-input.tsx +++ /dev/null @@ -1,232 +0,0 @@ -import { Control as RadixControl, type FormControlProps } from '@radix-ui/react-form'; -import * as React from 'react'; - -import { ClerkElementsFieldError } from '~/internals/errors'; -import { fieldValueSelector, useFormSelector, useFormStore } from '~/internals/machines/form'; -import { usePassword } from '~/react/hooks/use-password.hook'; - -import type { FormInputProps } from '../index'; -import { OTP_LENGTH_DEFAULT, OTPInput, type OTPInputProps } from '../otp'; -import { determineInputTypeFromName, enrichFieldState } from '../utils'; -import { useFieldContext } from './use-field-context'; -import { useFieldState } from './use-field-state'; -import { usePrevious } from './use-previous'; -import { useValidityStateContext } from './use-validity-state-context'; - -// TODO: DRY -type PasswordInputProps = Exclude & { - validatePassword?: boolean; -}; - -export function useInput({ - name: inputName, - value: providedValue, - checked: providedChecked, - onChange: onChangeProp, - onBlur: onBlurProp, - onFocus: onFocusProp, - type: inputType, - ...passthroughProps -}: FormInputProps) { - // Inputs can be used outside a wrapper if desired, so safely destructure here - const fieldContext = useFieldContext(); - const rawName = inputName || fieldContext?.name; - const name = rawName === 'backup_code' ? 'code' : rawName; // `backup_code` is a special case of `code` - const { state: fieldState } = useFieldState({ name }); - const validity = useValidityStateContext(); - - if (!rawName || !name) { - throw new Error('Clerk: must be wrapped in a component or have a name prop.'); - } - - const ref = useFormStore(); - const [hasPassedValiation, setHasPassedValidation] = React.useState(false); - - const { validatePassword } = usePassword({ - onValidationComplexity: hasPassed => setHasPassedValidation(hasPassed), - onValidationSuccess: () => { - ref.send({ - type: 'FIELD.FEEDBACK.SET', - field: { name, feedback: { type: 'success', message: 'Your password meets all the necessary requirements.' } }, - }); - }, - onValidationError: (error, codes) => { - if (error) { - ref.send({ - type: 'FIELD.FEEDBACK.SET', - field: { - name, - feedback: { - type: 'error', - message: new ClerkElementsFieldError('password-validation-error', error), - codes, - }, - }, - }); - } - }, - onValidationWarning: (warning, codes) => - ref.send({ - type: 'FIELD.FEEDBACK.SET', - field: { name, feedback: { type: 'warning', message: warning, codes } }, - }), - onValidationInfo: (info, codes) => { - // TODO: If input is not focused, make this info an error - ref.send({ - type: 'FIELD.FEEDBACK.SET', - field: { - name, - feedback: { - type: 'info', - message: info, - codes, - }, - }, - }); - }, - }); - - const value = useFormSelector(fieldValueSelector(name)); - const prevValue = usePrevious(value); - const hasValue = Boolean(value); - const type = inputType ?? determineInputTypeFromName(rawName); - let nativeFieldType = type; - let shouldValidatePassword = false; - - if (type === 'password' || type === 'text') { - shouldValidatePassword = Boolean((passthroughProps as PasswordInputProps).validatePassword); - } - - if (nativeFieldType === 'otp' || nativeFieldType === 'backup_code') { - nativeFieldType = 'text'; - } - - // Register the field in the machine context - React.useEffect(() => { - if (!name) { - return; - } - - ref.send({ - type: 'FIELD.ADD', - field: { name, type: nativeFieldType, value: providedValue, checked: providedChecked }, - }); - - return () => ref.send({ type: 'FIELD.REMOVE', field: { name } }); - }, [ref]); // eslint-disable-line react-hooks/exhaustive-deps - - React.useEffect(() => { - if (!name) { - return; - } - - if ( - (type === 'checkbox' && providedChecked !== undefined) || - (type !== 'checkbox' && providedValue !== undefined) - ) { - ref.send({ - type: 'FIELD.UPDATE', - field: { name, value: providedValue, checked: providedChecked }, - }); - } - }, [name, type, ref, providedValue, providedChecked]); - - // Register the onChange handler for field updates to persist to the machine context - const onChange = React.useCallback( - (event: React.ChangeEvent) => { - onChangeProp?.(event); - if (!name) { - return; - } - ref.send({ type: 'FIELD.UPDATE', field: { name, value: event.target.value, checked: event.target.checked } }); - if (shouldValidatePassword) { - validatePassword(event.target.value); - } - }, - [ref, name, onChangeProp, shouldValidatePassword, validatePassword], - ); - - const onBlur = React.useCallback( - (event: React.FocusEvent) => { - onBlurProp?.(event); - if (shouldValidatePassword && event.target.value !== prevValue) { - validatePassword(event.target.value); - } - }, - [onBlurProp, shouldValidatePassword, validatePassword, prevValue], - ); - - const onFocus = React.useCallback( - (event: React.FocusEvent) => { - onFocusProp?.(event); - if (shouldValidatePassword && event.target.value !== prevValue) { - validatePassword(event.target.value); - } - }, - [onFocusProp, shouldValidatePassword, validatePassword, prevValue], - ); - - // TODO: Implement clerk-js utils - const shouldBeHidden = false; - - const Element = type === 'otp' ? OTPInput : RadixControl; - - let props = {}; - if (type === 'otp') { - const p = passthroughProps as Omit; - const length = p.length || OTP_LENGTH_DEFAULT; - - props = { - 'data-otp-input': true, - autoComplete: 'one-time-code', - inputMode: 'numeric', - pattern: `[0-9]{${length}}`, - minLength: length, - maxLength: length, - // Enhanced naming for better password manager detection - name: 'otp', - id: 'otp-input', - // Additional attributes for password manager compatibility - 'data-testid': 'otp-input', - role: 'textbox', - 'aria-label': 'Enter verification code', - onChange: (event: React.ChangeEvent) => { - // Only accept numbers - event.currentTarget.value = event.currentTarget.value.replace(/\D+/g, ''); - onChange(event); - }, - type: 'text', - spellCheck: false, - }; - } else if (type === 'backup_code') { - props = { - autoComplete: 'off', - type: 'text', - spellCheck: false, - }; - } else if (type === 'password' && shouldValidatePassword) { - props = { - 'data-has-passed-validation': hasPassedValiation ? true : undefined, - }; - } - - // Filter out invalid props that should not be passed through - // @ts-expect-error - Doesn't know about type narrowing by type here - const { validatePassword: _1, ...rest } = passthroughProps; - - return { - Element, - props: { - type, - value: value ?? '', - onChange, - onBlur, - onFocus, - 'data-hidden': shouldBeHidden ? true : undefined, - 'data-has-value': hasValue ? true : undefined, - 'data-state': enrichFieldState(validity, fieldState), - ...props, - ...rest, - }, - }; -} diff --git a/packages/elements/src/react/common/form/hooks/use-previous.ts b/packages/elements/src/react/common/form/hooks/use-previous.ts deleted file mode 100644 index e1bb334fbbc..00000000000 --- a/packages/elements/src/react/common/form/hooks/use-previous.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as React from 'react'; - -export function usePrevious(value: T): T | undefined { - const ref = React.useRef(); - - React.useEffect(() => { - ref.current = value; - }, [value]); - - return ref.current; -} diff --git a/packages/elements/src/react/common/form/hooks/use-validity-state-context.ts b/packages/elements/src/react/common/form/hooks/use-validity-state-context.ts deleted file mode 100644 index f95cb49f2ac..00000000000 --- a/packages/elements/src/react/common/form/hooks/use-validity-state-context.ts +++ /dev/null @@ -1,4 +0,0 @@ -import * as React from 'react'; - -export const ValidityStateContext = React.createContext(undefined); -export const useValidityStateContext = () => React.useContext(ValidityStateContext); diff --git a/packages/elements/src/react/common/form/index.tsx b/packages/elements/src/react/common/form/index.tsx deleted file mode 100644 index 29e8641e41a..00000000000 --- a/packages/elements/src/react/common/form/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -export type { FormControlProps } from '@radix-ui/react-form'; - -export { Field } from './field'; -export { FieldError } from './field-error'; -export { FieldState } from './field-state'; -export { Form } from './form'; -export { GlobalError } from './global-error'; -export { Input } from './input'; -export { Label } from './label'; -export { Submit } from './submit'; - -export type { FormProps } from './form'; -export type { FormInputProps } from './input'; -export type { FormFieldProps } from './field'; -export type { FormFieldErrorProps } from './field-error'; -export type { FormGlobalErrorProps } from './global-error'; -export type { FormSubmitProps } from './submit'; -export type { FormErrorProps, FormErrorRenderProps } from './types'; diff --git a/packages/elements/src/react/common/form/input.tsx b/packages/elements/src/react/common/form/input.tsx deleted file mode 100644 index 07a09af1c50..00000000000 --- a/packages/elements/src/react/common/form/input.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { logger } from '@clerk/shared/logger'; -import { useClerk } from '@clerk/shared/react'; -import { eventComponentMounted } from '@clerk/shared/telemetry'; -import type { - Control as RadixControl, - FormControlProps, - FormControlProps as RadixFormControlProps, -} from '@radix-ui/react-form'; -import * as React from 'react'; - -import { SignInRouterCtx } from '~/react/sign-in/context'; -import { useSignInPasskeyAutofill } from '~/react/sign-in/context/router.context'; - -import { useInput } from './hooks'; -import type { OTPInputProps } from './otp'; - -const DISPLAY_NAME = 'ClerkElementsInput'; -const DISPLAY_NAME_PASSKEYS_AUTOFILL = 'ClerkElementsInputWithPasskeyAutofill'; - -type PasswordInputProps = Exclude & { - validatePassword?: boolean; -}; - -export type FormInputProps = - | RadixFormControlProps - | ({ type: 'otp'; render: OTPInputProps['render'] } & Omit) - | ({ type: 'otp'; render?: undefined } & OTPInputProps) - // Usecase: Toggle the visibility of the password input, therefore 'password' and 'text' are allowed - | ({ type: 'password' | 'text' } & PasswordInputProps); - -/** - * Handles rendering of `` elements within Clerk's flows. Supports special `type` prop values to render input types that are unique to authentication and user management flows. Additional props will be passed through to the `` element. - * - * @param {boolean} [asChild] - If true, `` will render as its child element, passing along any necessary props. - * @param {string} [name] - Used to target a specific field by name when rendering outside of a `` component. - * - * @example - * - * Email - * - * - * - * @param {Number} [length] - The length of the OTP input. Defaults to 6. - * @param {Number} [passwordManagerOffset] - Password managers place their icon inside an ``. This default behaviour is not desirable when you use the render prop to display N distinct element. With this prop you can increase the width of the `` so that the icon is rendered outside the OTP inputs. - * @param {string} [type] - Type of control to render. Supports a special `'otp'` type for one-time password inputs. If the wrapping `` component has `name='code'`, the type will default to `'otp'`. With the `'otp'` type, the input will have a pattern and length set to 6 by default and render a single `` element. - * - * @example - * - * Email code - * - * - * - * @param {Function} [render] - Optionally, you can use a render prop that controls how each individual character is rendered. If no `render` prop is provided, a single text `` will be rendered. - * - * @example - * - * Email code - * {value}} - * /> - * - */ -export const Input = React.forwardRef, FormInputProps>( - (props: FormInputProps, forwardedRef) => { - const clerk = useClerk(); - const field = useInput(props); - - const hasPasskeyAutofillProp = Boolean(field.props.autoComplete?.includes('webauthn')); - const allowedTypeForPasskey = (['text', 'email', 'tel'] as FormInputProps['type'][]).includes(field.props.type); - const signInRouterRef = SignInRouterCtx.useActorRef(true); - - clerk.telemetry?.record( - eventComponentMounted('Elements_Input', { - type: props.type ?? false, - // @ts-expect-error - Depending on type the props can be different - render: Boolean(props?.render), - // @ts-expect-error - Depending on type the props can be different - asChild: Boolean(props?.asChild), - // @ts-expect-error - Depending on type the props can be different - validatePassword: Boolean(props?.validatePassword), - }), - ); - - if (signInRouterRef && hasPasskeyAutofillProp && allowedTypeForPasskey) { - return ( - - ); - } - - if (hasPasskeyAutofillProp && !allowedTypeForPasskey) { - logger.warnOnce( - ` can only be used with or `, - ); - } else if (hasPasskeyAutofillProp) { - logger.warnOnce( - ` can only be used inside in order to trigger a sign-in attempt, otherwise it will be ignored.`, - ); - } - - return ( - - ); - }, -); - -Input.displayName = DISPLAY_NAME; - -const InputWithPasskeyAutofill = React.forwardRef, FormInputProps>( - (props: FormInputProps, forwardedRef) => { - const signInRouterRef = SignInRouterCtx.useActorRef(true); - const passkeyAutofillSupported = useSignInPasskeyAutofill(); - - React.useEffect(() => { - if (passkeyAutofillSupported) { - signInRouterRef?.send({ type: 'AUTHENTICATE.PASSKEY.AUTOFILL' }); - } - }, [passkeyAutofillSupported, signInRouterRef]); - - const field = useInput(props); - return ( - - ); - }, -); - -InputWithPasskeyAutofill.displayName = DISPLAY_NAME_PASSKEYS_AUTOFILL; diff --git a/packages/elements/src/react/common/form/label.tsx b/packages/elements/src/react/common/form/label.tsx deleted file mode 100644 index 565ead07a4e..00000000000 --- a/packages/elements/src/react/common/form/label.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Label as RadixLabel } from '@radix-ui/react-form'; - -const DISPLAY_NAME = 'ClerkElementsLabel'; - -/** - * Renders a `