diff --git a/.changeset/calm-wasps-accept.md b/.changeset/calm-wasps-accept.md new file mode 100644 index 00000000000..2a3ea3b0bcc --- /dev/null +++ b/.changeset/calm-wasps-accept.md @@ -0,0 +1,36 @@ +--- +'@clerk/clerk-expo': minor +--- + +Introduce `getClerkInstance()` to avoid importing the Clerk class from clerk-js manually. + +This enables developers to create and access a Clerk instance in their application outside of React. +```tsx +import { ClerkProvider, getClerkInstance } from "@clerk/expo" + +const clerkInstance = getClerkInstance({ publishableKey: 'xxxx' }) + +// Always pass the `publishableKey` to `ClerkProvider` + + ... + + +// Somewhere in your code, outside of React you can do +const token = await clerkInstance.session?.getToken(); +fetch('http://example.com/', {headers: {Authorization: token }) +``` +```tsx +import { ClerkProvider, getClerkInstance } from "@clerk/expo" + +// Always pass the `publishableKey` to `ClerkProvider` + + ... + + +// If you sure that this code will run after the ClerkProvider has rendered then you can use `getClerkIntance` without options +const token = await getClerkInstance().session?.getToken(); +fetch('http://example.com/', {headers: {Authorization: token }) + +``` +Attention: If `getClerkInstance` is called without a publishable key, and ClerkProvider has not rendered yet, an error will be thrown + diff --git a/.changeset/dirty-panthers-perform.md b/.changeset/dirty-panthers-perform.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/dirty-panthers-perform.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/empty-deers-notice.md b/.changeset/empty-deers-notice.md new file mode 100644 index 00000000000..0c65810650f --- /dev/null +++ b/.changeset/empty-deers-notice.md @@ -0,0 +1,5 @@ +--- +"@clerk/shared": patch +--- + +Update `js-cookie` from `3.0.1` to `3.0.5`. Update `swr` from `2.2.0` to `2.2.5`. diff --git a/.changeset/gentle-brooms-worry.md b/.changeset/gentle-brooms-worry.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/gentle-brooms-worry.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/giant-cougars-grab.md b/.changeset/giant-cougars-grab.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/giant-cougars-grab.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/metal-foxes-raise.md b/.changeset/metal-foxes-raise.md new file mode 100644 index 00000000000..b960dae3869 --- /dev/null +++ b/.changeset/metal-foxes-raise.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-react': patch +--- + +Update `SignUpButton` and `SignInButton` to respect `forceRedirect` and `fallbackRedirect` props. Previously, these were getting ignored and successful completions of the flows would fallback to the default redirect URL. diff --git a/.changeset/pretty-hounds-drive.md b/.changeset/pretty-hounds-drive.md new file mode 100644 index 00000000000..ec380ec43f2 --- /dev/null +++ b/.changeset/pretty-hounds-drive.md @@ -0,0 +1,3 @@ +--- +--- + diff --git a/.changeset/rotten-eyes-tickle.md b/.changeset/rotten-eyes-tickle.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/rotten-eyes-tickle.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/sixty-ears-rest.md b/.changeset/sixty-ears-rest.md new file mode 100644 index 00000000000..c59bdb3a1e9 --- /dev/null +++ b/.changeset/sixty-ears-rest.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Fixed a bug where Clerk components rendered in modals were wrapped with `aria-hidden`. diff --git a/.changeset/yellow-deers-dress.md b/.changeset/yellow-deers-dress.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/yellow-deers-dress.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/integration/templates/express-vite/package.json b/integration/templates/express-vite/package.json index 9aa07214537..f2fbca4ae5f 100644 --- a/integration/templates/express-vite/package.json +++ b/integration/templates/express-vite/package.json @@ -14,11 +14,11 @@ "ejs": "^3.1.6", "express": "^4.18.2", "ts-node": "^10.9.1", - "typescript": "^4.9.3", + "typescript": "^5.4.5", "vite-express": "^0.11.0" }, "devDependencies": { - "@types/express": "^4.17.15", + "@types/express": "^4.17.21", "@types/node": "^18.19.33", "nodemon": "^2.0.20", "vite": "^4.0.4" diff --git a/integration/templates/next-app-router-quickstart/package.json b/integration/templates/next-app-router-quickstart/package.json index a8a2ca029b4..6d39af16fcd 100644 --- a/integration/templates/next-app-router-quickstart/package.json +++ b/integration/templates/next-app-router-quickstart/package.json @@ -15,7 +15,7 @@ "next": "14.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "typescript": "^5" + "typescript": "^5.4.5" }, "engines": { "node": ">=18.17.0" diff --git a/integration/templates/next-app-router/package.json b/integration/templates/next-app-router/package.json index 76a46c7549b..056a0a8e25f 100644 --- a/integration/templates/next-app-router/package.json +++ b/integration/templates/next-app-router/package.json @@ -15,7 +15,7 @@ "next": "^13.5", "react": "18.3.1", "react-dom": "18.3.1", - "typescript": "5.1.6" + "typescript": "^5.4.5" }, "engines": { "node": ">=18.17.0" diff --git a/integration/templates/next-app-router/src/app/buttons/page.tsx b/integration/templates/next-app-router/src/app/buttons/page.tsx new file mode 100644 index 00000000000..4e4a500e721 --- /dev/null +++ b/integration/templates/next-app-router/src/app/buttons/page.tsx @@ -0,0 +1,35 @@ +import { SignInButton, SignUpButton } from '@clerk/nextjs'; + +export default function Home() { + return ( +
+ + Sign in button (force) + + + + Sign in button (fallback) + + + + Sign up button (force) + + + + Sign up button (fallback) + +
+ ); +} diff --git a/integration/templates/react-vite/package.json b/integration/templates/react-vite/package.json index 73cf36d9dd7..2d5fc3e4e31 100644 --- a/integration/templates/react-vite/package.json +++ b/integration/templates/react-vite/package.json @@ -24,7 +24,7 @@ "eslint": "^8.38.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.3.4", - "typescript": "^5.0.2", + "typescript": "^5.4.5", "vite": "^4.3.9" }, "engines": { diff --git a/integration/templates/remix-node/package.json b/integration/templates/remix-node/package.json index 346561d2152..46dadb7297a 100644 --- a/integration/templates/remix-node/package.json +++ b/integration/templates/remix-node/package.json @@ -25,7 +25,7 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "eslint": "^8.38.0", - "typescript": "^5.0.4" + "typescript": "^5.4.5" }, "engines": { "node": ">=18.17.0" diff --git a/integration/tests/redirects.test.ts b/integration/tests/redirects.test.ts new file mode 100644 index 00000000000..ce8d1434e0f --- /dev/null +++ b/integration/tests/redirects.test.ts @@ -0,0 +1,130 @@ +import { test } from '@playwright/test'; + +import { appConfigs } from '../presets'; +import type { FakeUser } from '../testUtils'; +import { createTestUtils, testAgainstRunningApps } from '../testUtils'; + +testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('redirect props @nextjs', ({ 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.describe('SignInButton', () => { + test('sign in button respects forceRedirectUrl', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + await u.page.goToRelative('/buttons'); + await u.page.waitForClerkJsLoaded(); + await u.po.expect.toBeSignedOut(); + + await u.page.getByText('Sign in button (force)').click(); + + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + + await u.page.waitForAppUrl('/protected'); + + await u.po.expect.toBeSignedIn(); + }); + + test('sign in button respects fallbackRedirectUrl', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + + await u.page.goToRelative('/buttons'); + await u.page.waitForClerkJsLoaded(); + await u.po.expect.toBeSignedOut(); + + await u.page.getByText('Sign in button (fallback)').click(); + + await u.po.signIn.waitForMounted(); + await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password }); + + await u.page.waitForAppUrl('/protected'); + + await u.po.expect.toBeSignedIn(); + }); + }); + + test.describe('SignUpButton', () => { + test('sign up button respects forceRedirectUrl', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + const fakeUser = u.services.users.createFakeUser({ + fictionalEmail: true, + withPhoneNumber: true, + withUsername: true, + }); + + await u.page.goToRelative('/buttons'); + await u.page.waitForClerkJsLoaded(); + + await u.page.getByText('Sign up button (force)').click(); + + // Fill in sign up form + await u.po.signUp.signUpWithEmailAndPassword({ + email: fakeUser.email, + password: fakeUser.password, + }); + + // Verify email + await u.po.signUp.enterTestOtpCode(); + + await u.page.waitForAppUrl('/protected'); + + // Check if user is signed in + await u.po.expect.toBeSignedIn(); + + await fakeUser.deleteIfExists(); + }); + + test('sign up button respects fallbackRedirectUrl', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + const fakeUser = u.services.users.createFakeUser({ + fictionalEmail: true, + withPhoneNumber: true, + withUsername: true, + }); + + await u.page.goToRelative('/buttons'); + await u.page.waitForClerkJsLoaded(); + + await u.page.getByText('Sign up button (fallback)').click(); + + // Fill in sign up form + await u.po.signUp.signUpWithEmailAndPassword({ + email: fakeUser.email, + password: fakeUser.password, + }); + + // Verify email + await u.po.signUp.enterTestOtpCode(); + + await u.page.waitForAppUrl('/protected'); + + // Check if user is signed in + await u.po.expect.toBeSignedIn(); + + await fakeUser.deleteIfExists(); + }); + }); +}); diff --git a/package-lock.json b/package-lock.json index a119d489205..1e914696a37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@actions/core": "^1.10.1", - "@arethetypeswrong/cli": "^0.12.1", + "@arethetypeswrong/cli": "^0.15.3", "@changesets/cli": "^2.26.2", "@changesets/get-github-info": "^0.5.2", "@commitlint/cli": "^19.3.0", @@ -58,7 +58,7 @@ "ts-jest": "^29.0.3", "tsup": "^8.0.1", "turbo": "^1.10.16", - "typescript": "^5.2.2", + "typescript": "^5.4.5", "verdaccio": "^5.26.3", "zx": "^7.2.3" }, @@ -127,7 +127,9 @@ } }, "node_modules/@andrewbranch/untar.js": { - "version": "1.0.2", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@andrewbranch/untar.js/-/untar.js-1.0.3.tgz", + "integrity": "sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==", "dev": true }, "node_modules/@ardatan/relay-compiler": { @@ -327,21 +329,24 @@ } }, "node_modules/@arethetypeswrong/cli": { - "version": "0.12.1", + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/cli/-/cli-0.15.3.tgz", + "integrity": "sha512-sIMA9ZJBWDEg1+xt5RkAEflZuf8+PO8SdKj17x6PtETuUho+qlZJg4DgmKc3q+QwQ9zOB5VLK6jVRbFdNLdUIA==", "dev": true, - "license": "MIT", "dependencies": { - "@arethetypeswrong/core": "0.12.1", + "@arethetypeswrong/core": "0.15.1", "chalk": "^4.1.2", "cli-table3": "^0.6.3", "commander": "^10.0.1", - "marked": "^5.1.0", - "marked-terminal": "^5.2.0", - "node-fetch": "^2.6.4", + "marked": "^9.1.2", + "marked-terminal": "^6.0.0", "semver": "^7.5.4" }, "bin": { "attw": "dist/index.js" + }, + "engines": { + "node": ">=18" } }, "node_modules/@arethetypeswrong/cli/node_modules/ansi-styles": { @@ -417,16 +422,33 @@ } }, "node_modules/@arethetypeswrong/core": { - "version": "0.12.1", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/core/-/core-0.15.1.tgz", + "integrity": "sha512-FYp6GBAgsNz81BkfItRz8RLZO03w5+BaeiPma1uCfmxTnxbtuMrI/dbzGiOk8VghO108uFI0oJo0OkewdSHw7g==", "dev": true, - "license": "MIT", "dependencies": { - "@andrewbranch/untar.js": "^1.0.0", - "fetch-ponyfill": "^7.1.0", - "fflate": "^0.7.4", + "@andrewbranch/untar.js": "^1.0.3", + "fflate": "^0.8.2", "semver": "^7.5.4", - "typescript": "^5.2.2", + "ts-expose-internals-conditionally": "1.0.0-empty.0", + "typescript": "5.3.3", "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, "node_modules/@babel/code-frame": { @@ -11066,12 +11088,13 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "4.17.14", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", + "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } @@ -11411,8 +11434,9 @@ } }, "node_modules/@types/semver": { - "version": "7.5.4", - "license": "MIT" + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==" }, "node_modules/@types/send": { "version": "0.17.1", @@ -12830,8 +12854,9 @@ }, "node_modules/ansicolors": { "version": "0.3.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true }, "node_modules/any-eslint-parser": { "version": "1.0.1", @@ -14745,8 +14770,9 @@ }, "node_modules/cardinal": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", "dev": true, - "license": "MIT", "dependencies": { "ansicolors": "~0.3.2", "redeyed": "~2.1.0" @@ -18175,6 +18201,12 @@ "version": "9.2.2", "license": "MIT" }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true + }, "node_modules/emojis-list": { "version": "3.0.0", "dev": true, @@ -20530,52 +20562,6 @@ "node": "^12.20 || >= 14.13" } }, - "node_modules/fetch-ponyfill": { - "version": "7.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "node-fetch": "~2.6.1" - } - }, - "node_modules/fetch-ponyfill/node_modules/node-fetch": { - "version": "2.6.13", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/fetch-ponyfill/node_modules/tr46": { - "version": "0.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/fetch-ponyfill/node_modules/webidl-conversions": { - "version": "3.0.1", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/fetch-ponyfill/node_modules/whatwg-url": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/fetch-retry": { "version": "4.1.1", "dev": true, @@ -20583,9 +20569,10 @@ "peer": true }, "node_modules/fflate": { - "version": "0.7.4", - "dev": true, - "license": "MIT" + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true }, "node_modules/figures": { "version": "3.2.0", @@ -26858,10 +26845,11 @@ } }, "node_modules/js-cookie": { - "version": "3.0.1", - "license": "MIT", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/js-tokens": { @@ -28310,9 +28298,10 @@ "dev": true }, "node_modules/marked": { - "version": "5.1.2", + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", + "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", "dev": true, - "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -28321,31 +28310,30 @@ } }, "node_modules/marked-terminal": { - "version": "5.2.0", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-6.2.0.tgz", + "integrity": "sha512-ubWhwcBFHnXsjYNsu+Wndpg0zhY4CahSpPlA70PlO0rR9r2sZpkyU+rkCsOWH+KMEkx847UpALON+HWgxowFtw==", "dev": true, - "license": "MIT", "dependencies": { "ansi-escapes": "^6.2.0", "cardinal": "^2.1.1", - "chalk": "^5.2.0", + "chalk": "^5.3.0", "cli-table3": "^0.6.3", - "node-emoji": "^1.11.0", - "supports-hyperlinks": "^2.3.0" + "node-emoji": "^2.1.3", + "supports-hyperlinks": "^3.0.0" }, "engines": { - "node": ">=14.13.1 || >=16.0.0" + "node": ">=16.0.0" }, "peerDependencies": { - "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + "marked": ">=1 <12" } }, "node_modules/marked-terminal/node_modules/ansi-escapes": { - "version": "6.2.0", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^3.0.0" - }, "engines": { "node": ">=14.16" }, @@ -28355,8 +28343,9 @@ }, "node_modules/marked-terminal/node_modules/chalk": { "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -28364,15 +28353,38 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/marked-terminal/node_modules/type-fest": { - "version": "3.13.1", + "node_modules/marked-terminal/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=14.16" + "node": ">=8" + } + }, + "node_modules/marked-terminal/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" + } + }, + "node_modules/marked-terminal/node_modules/supports-hyperlinks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz", + "integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" } }, "node_modules/marky": { @@ -29181,11 +29193,18 @@ } }, "node_modules/node-emoji": { - "version": "1.11.0", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", + "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", "dev": true, - "license": "MIT", "dependencies": { - "lodash": "^4.17.21" + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/node-fetch": { @@ -32682,8 +32701,9 @@ }, "node_modules/redeyed": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", "dev": true, - "license": "MIT", "dependencies": { "esprima": "~4.0.0" } @@ -33958,6 +33978,18 @@ "devOptional": true, "license": "MIT" }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/slash": { "version": "3.0.0", "license": "MIT", @@ -35193,6 +35225,7 @@ "version": "2.3.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -35205,6 +35238,7 @@ "version": "4.0.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -35213,6 +35247,7 @@ "version": "7.2.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -35272,9 +35307,11 @@ } }, "node_modules/swr": { - "version": "2.2.0", - "license": "MIT", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", + "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", "dependencies": { + "client-only": "^0.0.1", "use-sync-external-store": "^1.2.0" }, "peerDependencies": { @@ -36052,6 +36089,12 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-expose-internals-conditionally": { + "version": "1.0.0-empty.0", + "resolved": "https://registry.npmjs.org/ts-expose-internals-conditionally/-/ts-expose-internals-conditionally-1.0.0-empty.0.tgz", + "integrity": "sha512-F8m9NOF6ZhdOClDVdlM8gj3fDCav4ZIFSs/EI3ksQbAAXVSCN/Jh5OCJDDZWBuBy9psFc6jULGDlPwjMYMhJDw==", + "dev": true + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "license": "Apache-2.0" @@ -36718,8 +36761,9 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "license": "Apache-2.0", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -36803,6 +36847,15 @@ "node": ">=4" } }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/unicode-match-property-ecmascript": { "version": "2.0.0", "dev": true, @@ -37141,12 +37194,10 @@ } }, "node_modules/validate-npm-package-name": { - "version": "5.0.0", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, - "license": "ISC", - "dependencies": { - "builtins": "^5.0.0" - }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -39618,7 +39669,7 @@ "tslib": "2.4.1", "tsup": "*", "type-fest": "^4.9.0", - "typescript": "^5.3.3" + "typescript": "*" }, "engines": { "node": ">=18.17.0" @@ -40137,7 +40188,7 @@ }, "devDependencies": { "@clerk/eslint-config-custom": "*", - "@types/express": "^4.17", + "@types/express": "^4.17.21", "@types/node": "^18.19.33", "@types/supertest": "^6.0.2", "express": "^4.19.2", @@ -40564,7 +40615,7 @@ }, "devDependencies": { "@clerk/eslint-config-custom": "*", - "@types/express": "4.17.14", + "@types/express": "^4.17.21", "@types/node": "^18.19.33", "nock": "^13.0.7", "npm-run-all": "^4.1.5", @@ -40588,7 +40639,7 @@ "dependencies": { "@clerk/types": "4.5.1", "glob-to-regexp": "0.4.1", - "js-cookie": "3.0.1", + "js-cookie": "3.0.5", "std-env": "^3.7.0", "swr": "^2.2.0" }, diff --git a/package.json b/package.json index cf9aa8ac759..204680c5ac9 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ }, "devDependencies": { "@actions/core": "^1.10.1", - "@arethetypeswrong/cli": "^0.12.1", + "@arethetypeswrong/cli": "^0.15.3", "@changesets/cli": "^2.26.2", "@changesets/get-github-info": "^0.5.2", "@commitlint/cli": "^19.3.0", @@ -97,7 +97,7 @@ "ts-jest": "^29.0.3", "tsup": "^8.0.1", "turbo": "^1.10.16", - "typescript": "^5.2.2", + "typescript": "^5.4.5", "verdaccio": "^5.26.3", "zx": "^7.2.3" }, diff --git a/packages/clerk-js/src/ui/elements/Modal.tsx b/packages/clerk-js/src/ui/elements/Modal.tsx index 5815040fcc6..a5cd6a734f3 100644 --- a/packages/clerk-js/src/ui/elements/Modal.tsx +++ b/packages/clerk-js/src/ui/elements/Modal.tsx @@ -50,7 +50,6 @@ export const Modal = withFloatingTree((props: ModalProps) => { > { animation: `${animations.fadeIn} 150ms ${t.transitionTiming.$common}`, zIndex: t.zIndices.$modal, backgroundColor: t.colors.$modalBackdrop, - // ...common.centeredFlex(), alignItems: 'flex-start', justifyContent: 'center', overflow: 'auto', diff --git a/packages/elements/package.json b/packages/elements/package.json index 02b95868c56..267b5b049c5 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -90,7 +90,7 @@ "tslib": "2.4.1", "tsup": "*", "type-fest": "^4.9.0", - "typescript": "^5.3.3" + "typescript": "*" }, "peerDependencies": { "@clerk/clerk-react": "^5.0.0", diff --git a/packages/expo/src/ClerkProvider.tsx b/packages/expo/src/ClerkProvider.tsx index fc4f804c306..c25b7280663 100644 --- a/packages/expo/src/ClerkProvider.tsx +++ b/packages/expo/src/ClerkProvider.tsx @@ -5,27 +5,25 @@ import { ClerkProvider as ClerkReactProvider } from '@clerk/clerk-react'; import React from 'react'; import type { TokenCache } from './cache'; -import { MemoryTokenCache } from './cache'; import { isReactNative } from './runtime'; -import { buildClerk } from './singleton'; +import { getClerkInstance } from './singleton'; export type ClerkProviderProps = ClerkReactProviderProps & { tokenCache?: TokenCache; }; export function ClerkProvider(props: ClerkProviderProps): JSX.Element { - const { children, tokenCache = MemoryTokenCache, publishableKey, ...rest } = props; - const key = - publishableKey || process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY || process.env.CLERK_PUBLISHABLE_KEY || ''; + const { children, tokenCache, publishableKey, ...rest } = props; + const pk = publishableKey || process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY || process.env.CLERK_PUBLISHABLE_KEY || ''; return ( {children} diff --git a/packages/expo/src/errorThrower.ts b/packages/expo/src/errorThrower.ts new file mode 100644 index 00000000000..339b52dc6e3 --- /dev/null +++ b/packages/expo/src/errorThrower.ts @@ -0,0 +1,5 @@ +import { buildErrorThrower } from '@clerk/shared/error'; + +const errorThrower = buildErrorThrower({ packageName: PACKAGE_NAME }); + +export { errorThrower }; diff --git a/packages/expo/src/index.ts b/packages/expo/src/index.ts index 3e3eeb04815..4fa4c6b6a0e 100644 --- a/packages/expo/src/index.ts +++ b/packages/expo/src/index.ts @@ -17,7 +17,11 @@ export { export { isClerkAPIResponseError, isEmailLinkError, isKnownError, isMetamaskError } from '@clerk/clerk-react/errors'; +/** + * @deprecated Use `getClerkInstance()` instead. + */ export { clerk as Clerk } from './singleton'; +export { getClerkInstance } from './singleton'; export * from './ClerkProvider'; export * from './useOAuth'; diff --git a/packages/expo/src/singleton.ts b/packages/expo/src/singleton.ts index 996e2fee415..1929b00ecae 100644 --- a/packages/expo/src/singleton.ts +++ b/packages/expo/src/singleton.ts @@ -3,6 +3,8 @@ import { Clerk } from '@clerk/clerk-js/headless'; import type { HeadlessBrowserClerk } from '@clerk/clerk-react'; import type { TokenCache } from './cache'; +import { MemoryTokenCache } from './cache'; +import { errorThrower } from './errorThrower'; Clerk.sdkMetadata = { name: PACKAGE_NAME, @@ -11,29 +13,56 @@ Clerk.sdkMetadata = { const KEY = '__clerk_client_jwt'; +/** + * @deprecated Use `getClerkInstance` instead. `Clerk` will be removed in the next major version. + */ export let clerk: HeadlessBrowserClerk; +let __internal_clerk: HeadlessBrowserClerk | undefined; type BuildClerkOptions = { - key: string; - tokenCache: TokenCache; + publishableKey?: string; + tokenCache?: TokenCache; }; -export function buildClerk({ key, tokenCache }: BuildClerkOptions): HeadlessBrowserClerk { +/** + * Access or create a Clerk instance outside of React. + * @example + * import { ClerkProvider, getClerkInstance } from "@clerk/expo" + * + * const clerkInstance = getClerkInstance({ publishableKey: 'xxxx' }) + * + * // Always pass the `publishableKey` to `ClerkProvider` + * + * ... + * + * + * // Somewhere in your code, outside of React you can do + * const token = await clerkInstance.session?.getToken(); + * fetch('http://example.com/', {headers: {Authorization: token }) + * @throws MissingPublishableKeyError publishableKey is missing and Clerk has not been initialized yet + * @returns HeadlessBrowserClerk + */ +export function getClerkInstance(options?: BuildClerkOptions): HeadlessBrowserClerk { + const { publishableKey = process.env.CLERK_PUBLISHABLE_KEY || '', tokenCache = MemoryTokenCache } = options || {}; + + if (!__internal_clerk && !publishableKey) { + errorThrower.throwMissingPublishableKeyError(); + } + // Support "hot-swapping" the Clerk instance at runtime. See JS-598 for additional details. - const hasKeyChanged = clerk && key !== clerk.publishableKey; + const hasKeyChanged = __internal_clerk && !!publishableKey && publishableKey !== __internal_clerk.publishableKey; - if (!clerk || hasKeyChanged) { + if (!__internal_clerk || hasKeyChanged) { if (hasKeyChanged) { tokenCache.clearToken?.(KEY); } const getToken = tokenCache.getToken; const saveToken = tokenCache.saveToken; - // TODO: DO NOT ACCEPT THIS - clerk = new Clerk(key); + __internal_clerk = clerk = new Clerk(publishableKey); // @ts-expect-error - clerk.__unstable__onBeforeRequest(async (requestInit: FapiRequestInit) => { + __internal_clerk.__unstable__onBeforeRequest(async (requestInit: FapiRequestInit) => { // https://reactnative.dev/docs/0.61/network#known-issues-with-fetch-and-cookie-based-authentication requestInit.credentials = 'omit'; @@ -44,13 +73,12 @@ export function buildClerk({ key, tokenCache }: BuildClerkOptions): HeadlessBrow }); // @ts-expect-error - clerk.__unstable__onAfterResponse(async (_: FapiRequestInit, response: FapiResponse) => { + __internal_clerk.__unstable__onAfterResponse(async (_: FapiRequestInit, response: FapiResponse) => { const authHeader = response.headers.get('authorization'); if (authHeader) { await saveToken(KEY, authHeader); } }); } - - return clerk; + return __internal_clerk; } diff --git a/packages/express/package.json b/packages/express/package.json index e9339be4651..a91c5383146 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -60,7 +60,7 @@ }, "devDependencies": { "@clerk/eslint-config-custom": "*", - "@types/express": "^4.17", + "@types/express": "^4.17.21", "@types/node": "^18.19.33", "@types/supertest": "^6.0.2", "express": "^4.19.2", diff --git a/packages/nextjs/examples/next/.eslintrc.json b/packages/nextjs/examples/next/.eslintrc.json deleted file mode 100644 index da874f7928c..00000000000 --- a/packages/nextjs/examples/next/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "next/core-web-vitals", - "parserOptions": { - "project": ["./tsconfig.json"] - } -} diff --git a/packages/nextjs/examples/next/.gitignore b/packages/nextjs/examples/next/.gitignore deleted file mode 100644 index 737d8721092..00000000000 --- a/packages/nextjs/examples/next/.gitignore +++ /dev/null @@ -1,35 +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* -.pnpm-debug.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo diff --git a/packages/nextjs/examples/next/README.md b/packages/nextjs/examples/next/README.md deleted file mode 100644 index c87e0421d22..00000000000 --- a/packages/nextjs/examples/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 -npm run dev -# or -yarn dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. - -[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. - -The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. - -## 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/nextjs/examples/next/next-env.d.ts b/packages/nextjs/examples/next/next-env.d.ts deleted file mode 100644 index 4f11a03dc6c..00000000000 --- a/packages/nextjs/examples/next/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/packages/nextjs/examples/next/next.config.js b/packages/nextjs/examples/next/next.config.js deleted file mode 100644 index 3d3bc9990d8..00000000000 --- a/packages/nextjs/examples/next/next.config.js +++ /dev/null @@ -1,7 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: true, - swcMinify: true, -}; - -module.exports = nextConfig; diff --git a/packages/nextjs/examples/next/package.json b/packages/nextjs/examples/next/package.json deleted file mode 100644 index 0504fb78e8c..00000000000 --- a/packages/nextjs/examples/next/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "my-app", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@clerk/nextjs": "latest", - "next": "12.3.4", - "react": "18.3.1", - "react-dom": "18.3.1" - }, - "devDependencies": { - "@types/node": "^18.19.33", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", - "eslint": "8.21.0", - "eslint-config-next": "12.2.3", - "typescript": "4.7.4" - } -} \ No newline at end of file diff --git a/packages/nextjs/examples/next/pages/_app.tsx b/packages/nextjs/examples/next/pages/_app.tsx deleted file mode 100644 index caacba57ca8..00000000000 --- a/packages/nextjs/examples/next/pages/_app.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import '../styles/globals.css'; - -import { ClerkProvider, RedirectToSignIn, SignedIn, SignedOut } from '@clerk/nextjs'; -import type { AppProps } from 'next/app'; -import { useRouter } from 'next/router'; -import React from 'react'; - -const publicPages: Array = ['/']; - -function MyApp({ Component, pageProps }: AppProps) { - // Get the pathname - const { pathname } = useRouter(); - - // Check if the current route matches a public page - const isPublicPage = publicPages.includes(pathname); - - // If the current route is listed as public, render it directly - // Otherwise, use Clerk to require authentication - return ( - - {isPublicPage ? ( - - ) : ( - <> - - - - - - - - )} - - ); -} - -export default MyApp; diff --git a/packages/nextjs/examples/next/pages/api/hello.ts b/packages/nextjs/examples/next/pages/api/hello.ts deleted file mode 100644 index eb4cc6657b3..00000000000 --- a/packages/nextjs/examples/next/pages/api/hello.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from 'next'; - -type Data = { - name: string; -}; - -export default function handler(req: NextApiRequest, res: NextApiResponse) { - res.status(200).json({ name: 'John Doe' }); -} diff --git a/packages/nextjs/examples/next/pages/api/require-auth.ts b/packages/nextjs/examples/next/pages/api/require-auth.ts deleted file mode 100644 index 087b30e48c4..00000000000 --- a/packages/nextjs/examples/next/pages/api/require-auth.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { requireAuth, RequireAuthProp } from '@clerk/nextjs/api'; -import type { NextApiRequest, NextApiResponse } from 'next'; - -function handler(req: RequireAuthProp, res: NextApiResponse) { - console.log('Session required'); - res.statusCode = 200; - res.json(req.auth); -} - -export default requireAuth(handler); diff --git a/packages/nextjs/examples/next/pages/api/with-auth.ts b/packages/nextjs/examples/next/pages/api/with-auth.ts deleted file mode 100644 index 461bf6e09c0..00000000000 --- a/packages/nextjs/examples/next/pages/api/with-auth.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { withAuth, WithAuthProp } from '@clerk/nextjs/api'; -import type { NextApiRequest, NextApiResponse } from 'next'; - -function handler(req: WithAuthProp, res: NextApiResponse) { - console.log('Session optional'); - res.statusCode = 200; - res.json(req.auth); -} - -export default withAuth(handler); diff --git a/packages/nextjs/examples/next/pages/index.tsx b/packages/nextjs/examples/next/pages/index.tsx deleted file mode 100644 index 3ffeb025699..00000000000 --- a/packages/nextjs/examples/next/pages/index.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import type { NextPage } from 'next'; -import Head from 'next/head'; -import Image from 'next/image'; -import React from 'react'; - -import styles from '../styles/Home.module.css'; - -const Home: NextPage = () => { - const [responses, setResponse] = React.useState>({}); - - const makeRequest = async (path: string) => { - setResponse({ - ...responses, - [path]: '// Loading...', - }); - - try { - const res = await fetch(path); - const body = await res.json(); - setResponse({ - ...responses, - [path]: JSON.stringify(body, null, ' '), - }); - } catch (e) { - setResponse({ ...responses, [path]: '// There was an error with the request. Please contact support@clerk.com' }); - } - }; - - const getResponse = (path: string): string => responses[path] || '// Click above to run the request'; - - return ( -
- - Create Next App - - - - -
-

- Welcome to Next.js! -

- -

- Get started by editing pages/index.tsx -

- -
-
-

withAuth →

-

- -

-
-              {getResponse('/api/with-auth')}
-            
-
- -
-

requireAuth →

- -
-              {getResponse('/api/require-auth')}
-            
-
-
-
- - -
- ); -}; - -export default Home; diff --git a/packages/nextjs/examples/next/public/favicon.ico b/packages/nextjs/examples/next/public/favicon.ico deleted file mode 100644 index 718d6fea483..00000000000 Binary files a/packages/nextjs/examples/next/public/favicon.ico and /dev/null differ diff --git a/packages/nextjs/examples/next/public/vercel.svg b/packages/nextjs/examples/next/public/vercel.svg deleted file mode 100644 index fbf0e25a651..00000000000 --- a/packages/nextjs/examples/next/public/vercel.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/packages/nextjs/examples/next/styles/Home.module.css b/packages/nextjs/examples/next/styles/Home.module.css deleted file mode 100644 index f7a8472d22d..00000000000 --- a/packages/nextjs/examples/next/styles/Home.module.css +++ /dev/null @@ -1,130 +0,0 @@ -.container { - padding: 0 2rem; -} - -.main { - min-height: 100vh; - padding: 4rem 0; - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -} - -.footer { - display: flex; - flex: 1; - padding: 2rem 0; - border-top: 1px solid #eaeaea; - justify-content: center; - align-items: center; -} - -.footer a { - display: flex; - justify-content: center; - align-items: center; - flex-grow: 1; -} - -.title a { - color: #0070f3; - text-decoration: none; -} - -.title a:hover, -.title a:focus, -.title a:active { - text-decoration: underline; -} - -.title { - margin: 0; - line-height: 1.15; - font-size: 4rem; -} - -.title, -.description { - text-align: center; -} - -.description { - margin: 4rem 0; - line-height: 1.5; - font-size: 1.5rem; -} - -.code { - background: #fafafa; - border-radius: 5px; - padding: 0.75rem; - font-size: 1.1rem; - font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, - monospace; -} - -.grid { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - max-width: 800px; -} - -.card { - margin: 1rem; - padding: 1.5rem; - text-align: left; - color: inherit; - text-decoration: none; - border: 1px solid #eaeaea; - border-radius: 10px; - transition: color 0.15s ease, border-color 0.15s ease; - max-width: 300px; - min-height: 500px; -} - -.card:hover, -.card:focus, -.card:active { - color: #0070f3; - border-color: #0070f3; -} - -.card h2 { - margin: 0 0 1rem 0; - font-size: 1.5rem; -} - -.card p { - margin: 0; - font-size: 1.25rem; - line-height: 1.5; -} - -.logo { - height: 1em; - margin-left: 0.5rem; -} - -@media (max-width: 600px) { - .grid { - width: 100%; - flex-direction: column; - } -} - -@media (prefers-color-scheme: dark) { - .card, - .footer { - border-color: #222; - } - .code { - background: #111; - } - .logo img { - filter: invert(1); - } -} diff --git a/packages/nextjs/examples/next/styles/globals.css b/packages/nextjs/examples/next/styles/globals.css deleted file mode 100644 index aa3b7f2a5b3..00000000000 --- a/packages/nextjs/examples/next/styles/globals.css +++ /dev/null @@ -1,26 +0,0 @@ -html, -body { - padding: 0; - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, - Helvetica Neue, sans-serif; -} - -a { - color: inherit; - text-decoration: none; -} - -* { - box-sizing: border-box; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } - body { - color: white; - background: black; - } -} diff --git a/packages/nextjs/examples/next/tsconfig.json b/packages/nextjs/examples/next/tsconfig.json deleted file mode 100644 index 99710e85787..00000000000 --- a/packages/nextjs/examples/next/tsconfig.json +++ /dev/null @@ -1,20 +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": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] -} diff --git a/packages/react/src/components/SignInButton.tsx b/packages/react/src/components/SignInButton.tsx index 50cec831de3..6dd8453a028 100644 --- a/packages/react/src/components/SignInButton.tsx +++ b/packages/react/src/components/SignInButton.tsx @@ -1,3 +1,4 @@ +import type { SignInProps } from '@clerk/types'; import React from 'react'; import type { SignInButtonProps, WithClerkProp } from '../types'; @@ -11,21 +12,27 @@ export const SignInButton = withClerk(({ clerk, children, ...props }: WithClerkP const child = assertSingleChild(children)('SignInButton'); const clickHandler = () => { - const opts = { + const opts: SignInProps = { + forceRedirectUrl, + fallbackRedirectUrl, signUpFallbackRedirectUrl, signUpForceRedirectUrl, - signInForceRedirectUrl: forceRedirectUrl, - signInFallbackRedirectUrl: fallbackRedirectUrl, }; if (mode === 'modal') { return clerk.openSignIn(opts); } - return clerk.redirectToSignIn(opts); + return clerk.redirectToSignIn({ + ...opts, + signInFallbackRedirectUrl: fallbackRedirectUrl, + signInForceRedirectUrl: forceRedirectUrl, + }); }; const wrappedChildClickHandler: React.MouseEventHandler = async e => { - await safeExecute((child as any).props.onClick)(e); + if (child && typeof child === 'object' && 'props' in child) { + await safeExecute(child.props.onClick)(e); + } return clickHandler(); }; diff --git a/packages/react/src/components/SignUpButton.tsx b/packages/react/src/components/SignUpButton.tsx index 1db1a5f30ea..729494550a8 100644 --- a/packages/react/src/components/SignUpButton.tsx +++ b/packages/react/src/components/SignUpButton.tsx @@ -1,3 +1,4 @@ +import type { SignUpProps } from '@clerk/types'; import React from 'react'; import type { SignUpButtonProps, WithClerkProp } from '../types'; @@ -19,9 +20,9 @@ export const SignUpButton = withClerk(({ clerk, children, ...props }: WithClerkP const child = assertSingleChild(children)('SignUpButton'); const clickHandler = () => { - const opts = { - signUpFallbackRedirectUrl: fallbackRedirectUrl, - signUpForceRedirectUrl: forceRedirectUrl, + const opts: SignUpProps = { + fallbackRedirectUrl, + forceRedirectUrl, signInFallbackRedirectUrl, signInForceRedirectUrl, unsafeMetadata, @@ -31,11 +32,17 @@ export const SignUpButton = withClerk(({ clerk, children, ...props }: WithClerkP return clerk.openSignUp(opts); } - return clerk.redirectToSignUp(opts); + return clerk.redirectToSignUp({ + ...opts, + signUpFallbackRedirectUrl: fallbackRedirectUrl, + signUpForceRedirectUrl: forceRedirectUrl, + }); }; const wrappedChildClickHandler: React.MouseEventHandler = async e => { - await safeExecute((child as any).props.onClick)(e); + if (child && typeof child === 'object' && 'props' in child) { + await safeExecute(child.props.onClick)(e); + } return clickHandler(); }; diff --git a/packages/react/src/components/__tests__/SignInButton.test.tsx b/packages/react/src/components/__tests__/SignInButton.test.tsx index 22050952ef6..a1b14241b9e 100644 --- a/packages/react/src/components/__tests__/SignInButton.test.tsx +++ b/packages/react/src/components/__tests__/SignInButton.test.tsx @@ -52,7 +52,7 @@ describe('', () => { const btn = screen.getByText('Sign in'); await userEvent.click(btn); - expect(mockRedirectToSignIn).toHaveBeenCalledWith({ signInForceRedirectUrl: url }); + expect(mockRedirectToSignIn).toHaveBeenCalledWith({ forceRedirectUrl: url, signInForceRedirectUrl: url }); }); it('handles fallbackRedirectUrl prop', async () => { @@ -62,6 +62,7 @@ describe('', () => { await userEvent.click(btn); expect(mockRedirectToSignIn).toHaveBeenCalledWith({ + fallbackRedirectUrl: url, signInFallbackRedirectUrl: url, }); }); diff --git a/packages/react/src/components/__tests__/SignUpButton.test.tsx b/packages/react/src/components/__tests__/SignUpButton.test.tsx index 918ecf8baa6..b1f9f4e755c 100644 --- a/packages/react/src/components/__tests__/SignUpButton.test.tsx +++ b/packages/react/src/components/__tests__/SignUpButton.test.tsx @@ -53,7 +53,7 @@ describe('', () => { const btn = screen.getByText('Sign up'); userEvent.click(btn); await waitFor(() => { - expect(mockRedirectToSignUp).toHaveBeenCalledWith({ signUpForceRedirectUrl: url }); + expect(mockRedirectToSignUp).toHaveBeenCalledWith({ forceRedirectUrl: url, signUpForceRedirectUrl: url }); }); }); @@ -63,6 +63,7 @@ describe('', () => { userEvent.click(btn); await waitFor(() => { expect(mockRedirectToSignUp).toHaveBeenCalledWith({ + fallbackRedirectUrl: url, signUpFallbackRedirectUrl: url, }); }); diff --git a/packages/sdk-node/package.json b/packages/sdk-node/package.json index 142155eb18f..d0431b5f4b6 100644 --- a/packages/sdk-node/package.json +++ b/packages/sdk-node/package.json @@ -60,7 +60,7 @@ }, "devDependencies": { "@clerk/eslint-config-custom": "*", - "@types/express": "4.17.14", + "@types/express": "^4.17.21", "@types/node": "^18.19.33", "nock": "^13.0.7", "npm-run-all": "^4.1.5", diff --git a/packages/sdk-node/src/__tests__/authenticateRequest.test.ts b/packages/sdk-node/src/__tests__/authenticateRequest.test.ts index 27753a593cb..60444dc0edb 100644 --- a/packages/sdk-node/src/__tests__/authenticateRequest.test.ts +++ b/packages/sdk-node/src/__tests__/authenticateRequest.test.ts @@ -1,5 +1,6 @@ import { constants } from '@clerk/backend/internal'; -import { Request } from 'express'; +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +import type { Request } from 'express'; import { authenticateRequest } from '../authenticateRequest'; diff --git a/packages/shared/package.json b/packages/shared/package.json index 57c3f614e90..4746ef3d212 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -90,7 +90,7 @@ "dependencies": { "@clerk/types": "4.5.1", "glob-to-regexp": "0.4.1", - "js-cookie": "3.0.1", + "js-cookie": "3.0.5", "std-env": "^3.7.0", "swr": "^2.2.0" }, diff --git a/packages/ui/src/global.css b/packages/ui/src/global.css index 0e4d7c008d2..b3e4dd54fe4 100644 --- a/packages/ui/src/global.css +++ b/packages/ui/src/global.css @@ -12,6 +12,7 @@ --cl-color-destructive: 0 84% 60%; --cl-radius: 0.375rem; --cl-spacing-unit: 1rem; + --cl-font-size: 0.8125rem; } *, diff --git a/packages/ui/src/primitives/button.tsx b/packages/ui/src/primitives/button.tsx index 90e90098567..0eff921a05c 100644 --- a/packages/ui/src/primitives/button.tsx +++ b/packages/ui/src/primitives/button.tsx @@ -20,7 +20,7 @@ export const Button = React.forwardRef(function Button( ref={forwardedRef} {...props} className={cn( - 'text-accent-contrast bg-accent-9 border-accent-9 focus:ring-gray-a3 relative isolate inline-flex w-full select-none appearance-none items-center justify-center rounded-md border px-2 py-1.5 text-[0.8125rem]/[1.125rem] font-medium shadow-[0_1px_1px_0_theme(colors.white/.07)_inset] outline-none ring-[0.1875rem] ring-transparent before:absolute before:inset-0 before:rounded-[calc(theme(borderRadius.md)-1px)] before:shadow-[0_1px_1px_0_theme(colors.white/.07)_inset] after:pointer-events-none after:absolute after:inset-0 after:-z-10 after:rounded-[calc(theme(borderRadius.md)-1px)] after:bg-gradient-to-b after:from-white/10 after:to-transparent disabled:pointer-events-none disabled:cursor-not-allowed', + 'text-accent-contrast bg-accent-9 border-accent-9 focus:ring-gray-a3 relative isolate inline-flex w-full select-none appearance-none items-center justify-center rounded-md border px-2 py-1.5 text-base font-medium shadow-[0_1px_1px_0_theme(colors.white/.07)_inset] outline-none ring-[0.1875rem] ring-transparent before:absolute before:inset-0 before:rounded-[calc(theme(borderRadius.md)-1px)] before:shadow-[0_1px_1px_0_theme(colors.white/.07)_inset] after:pointer-events-none after:absolute after:inset-0 after:-z-10 after:rounded-[calc(theme(borderRadius.md)-1px)] after:bg-gradient-to-b after:from-white/10 after:to-transparent disabled:pointer-events-none disabled:cursor-not-allowed', // note: only reduce opacity of `disabled` so `busy` is more prominent disabled && 'disabled:opacity-50', !busy && !disabled && 'hover:bg-accent-10 hover:after:opacity-0', @@ -33,7 +33,7 @@ export const Button = React.forwardRef(function Button( ) : ( <> {children} - {icon && {icon}} + {icon && {icon}} )} diff --git a/packages/ui/src/primitives/card.tsx b/packages/ui/src/primitives/card.tsx index b70dfa4476c..0e05c8d9411 100644 --- a/packages/ui/src/primitives/card.tsx +++ b/packages/ui/src/primitives/card.tsx @@ -47,7 +47,7 @@ export const Header = React.forwardRef {children} @@ -62,7 +62,7 @@ export const Title = React.forwardRef {children} @@ -75,7 +75,7 @@ export const Description = React.forwardRef {children}

@@ -110,7 +110,7 @@ export const Footer = React.forwardRef {children}
-

+

Secured by{' '} {children}

@@ -160,7 +160,7 @@ export const FooterActionLink = React.forwardRef {children} diff --git a/packages/ui/src/primitives/connection.tsx b/packages/ui/src/primitives/connection.tsx index 04067b8e7c8..1442a87bb98 100644 --- a/packages/ui/src/primitives/connection.tsx +++ b/packages/ui/src/primitives/connection.tsx @@ -35,7 +35,7 @@ export const Button = React.forwardRef(function Button( ref={forwardedRef} {...props} className={cn( - 'flex items-center justify-center gap-2 w-full bg-transparent text-gray-12 font-medium rounded-md bg-clip-padding border border-gray-a6 shadow-sm shadow-gray-a3 py-1.5 px-2.5 outline-none focus-visible:ring-[0.1875rem] focus-visible:ring-gray-a3 focus-visible:border-gray-a8 disabled:cursor-not-allowed text-[0.8125rem]/[1.125rem]', + 'flex items-center justify-center gap-2 w-full bg-transparent text-gray-12 font-medium rounded-md bg-clip-padding border border-gray-a6 shadow-sm shadow-gray-a3 py-1.5 px-2.5 outline-none focus-visible:ring-[0.1875rem] focus-visible:ring-gray-a3 focus-visible:border-gray-a8 disabled:cursor-not-allowed text-base', // note: only reduce opacity of `disabled` so `busy` is more prominent disabled && 'disabled:opacity-40', !busy && !disabled && 'hover:bg-gray-a2', diff --git a/packages/ui/src/primitives/field.tsx b/packages/ui/src/primitives/field.tsx index da17fd956d5..f5f01a41e96 100644 --- a/packages/ui/src/primitives/field.tsx +++ b/packages/ui/src/primitives/field.tsx @@ -25,7 +25,7 @@ export const Label = React.forwardRef @@ -44,7 +44,7 @@ export const Input = React.forwardRef @@ -68,7 +68,7 @@ export const Message = React.forwardRef< ref={forwardedRef} {...props} className={cn( - 'text-[0.8125rem]/[1.125rem] flex gap-x-1', + 'text-base flex gap-x-1', { // TODO: Use the color tokens here 'text-[#ef4444]': intent === 'error', diff --git a/packages/ui/src/primitives/icon.tsx b/packages/ui/src/primitives/icon.tsx index 621a1df4acc..b5b21c9eb4d 100644 --- a/packages/ui/src/primitives/icon.tsx +++ b/packages/ui/src/primitives/icon.tsx @@ -4,6 +4,9 @@ import * as React from 'react'; type IconRef = SVGSVGElement; type IconProps = Omit, 'viewBox'>; +// Icons that need to be rotated 180deg in RTL mode +const rtlIcons = ['IconCaretRight']; + function createIcon({ displayName, viewBox, path }: { displayName: string; viewBox: string; path: React.ReactNode }) { const Icon = React.forwardRef(function ({ className, ...props }: IconProps, ref: React.ForwardedRef) { return ( @@ -12,7 +15,7 @@ function createIcon({ displayName, viewBox, path }: { displayName: string; viewB viewBox={viewBox} fill='none' xmlns='http://www.w3.org/2000/svg' - className={cn('size-[1em]', className)} + className={cn('size-[1em]', rtlIcons.includes(displayName) && 'rtl:rotate-180', className)} {...props} > {path} diff --git a/packages/ui/src/primitives/seperator.tsx b/packages/ui/src/primitives/seperator.tsx index 70314a671a9..5b10574e61e 100644 --- a/packages/ui/src/primitives/seperator.tsx +++ b/packages/ui/src/primitives/seperator.tsx @@ -8,7 +8,7 @@ export const Seperator = React.forwardRef diff --git a/packages/ui/src/tailwind.config.ts b/packages/ui/src/tailwind.config.ts index 2119b38ef00..7551fb5ef75 100644 --- a/packages/ui/src/tailwind.config.ts +++ b/packages/ui/src/tailwind.config.ts @@ -43,6 +43,15 @@ const config = { fontFamily: { sans: ['var(--cl-font-family)'], }, + fontSize: { + DEFAULT: ['var(--cl-font-size)', '1.38462'], + base: ['var(--cl-font-size)', '1.38462'], + xs: ['calc(var(--cl-font-size) * 0.8)', '1.38462'], + sm: ['calc(var(--cl-font-size) * 0.9)', '1.38462'], + md: ['var(--cl-font-size)', '1.38462'], + lg: ['calc(var(--cl-font-size) * 1.3)', '1.38462'], + xl: ['calc(var(--cl-font-size) * 1.85)', '1.38462'], + }, borderRadius: { DEFAULT: 'var(--cl-radius)', sm: 'calc(var(--cl-radius) * 0.66)', @@ -50,6 +59,13 @@ const config = { lg: 'calc(var(--cl-radius) * 1.33)', xl: 'calc(var(--cl-radius) * 2)', }, + lineHeight: { + normal: 'normal', + extraSmall: '1.33333', + small: '1.38462', + medium: '1.41176', + large: '1.45455', + }, spacing: { ...generateSpaceScale(), }, diff --git a/packages/ui/theme-builder/.gitignore b/packages/ui/theme-builder/.gitignore index fd3dbb571a1..e32376ee409 100644 --- a/packages/ui/theme-builder/.gitignore +++ b/packages/ui/theme-builder/.gitignore @@ -34,3 +34,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +package-lock.json diff --git a/packages/ui/theme-builder/app/color-picker.tsx b/packages/ui/theme-builder/app/color-picker.tsx index c11eee9103c..57e2599eb0b 100644 --- a/packages/ui/theme-builder/app/color-picker.tsx +++ b/packages/ui/theme-builder/app/color-picker.tsx @@ -58,7 +58,7 @@ export function ColorPicker({
- + { +export const getPreviewStyles = ({ + lightColors, + darkColors, + radius, + spacingUnit, + fontSize, +}: GetNewPreviewStylesParams) => { const lightAccentColorsCss = getColorScaleCss({ isDarkMode: false, name: 'accent', @@ -711,6 +718,7 @@ ${darkGrayColorsCss} :where(:root) { --cl-radius: ${radius}; --cl-spacing-unit: ${spacingUnit}; + --cl-font-size: ${fontSize}; } `.trim(); }; diff --git a/packages/ui/theme-builder/app/theme-builder.tsx b/packages/ui/theme-builder/app/theme-builder.tsx index 378b0ababff..95fa0e869a1 100644 --- a/packages/ui/theme-builder/app/theme-builder.tsx +++ b/packages/ui/theme-builder/app/theme-builder.tsx @@ -2,12 +2,12 @@ import { SignIn } from '@clerk/ui/sign-in'; import { SignUp } from '@clerk/ui/sign-up'; import cn from 'clsx'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; -import { AppearanceToggle } from './appearance-toggle'; import { ColorPicker } from './color-picker'; import { generateColors, getPreviewStyles } from './generate-colors'; import { ThemeDialog } from './theme-dialog'; +import { ToggleGroup } from './toggle-group'; const lightAccentDefault = '#2F3037'; const lightGrayDefault = '#2f3037'; @@ -19,6 +19,7 @@ const darkBackgroundDefault = '#111'; const radiusDefault = '0.375rem'; const spacingUnitDefault = '1rem'; +const fontSizeDefault = '0.8125rem'; const componnents = { SignIn: , @@ -27,6 +28,7 @@ const componnents = { type Component = keyof typeof componnents; export function ThemeBuilder() { + const [dir, setDir] = useState('ltr'); const [appearance, setAppearance] = useState('light'); const [lightAccent, setLightAccent] = useState(lightAccentDefault); const [lightGray, setLightGray] = useState(lightGrayDefault); @@ -36,6 +38,7 @@ export function ThemeBuilder() { const [darkBackground, setDarkBackground] = useState(darkBackgroundDefault); const [radius, setRadius] = useState(radiusDefault); const [spacingUnit, setSpacingUnit] = useState(spacingUnitDefault); + const [fontSize, setFontSize] = useState(fontSizeDefault); const [selectedComponent, setSelectedComponent] = useState('SignIn'); const handleReset = () => { setLightAccent(lightAccentDefault); @@ -46,6 +49,7 @@ export function ThemeBuilder() { setDarkBackground(darkBackgroundDefault); setRadius(radiusDefault); setSpacingUnit(spacingUnitDefault); + setFontSize(fontSizeDefault); }; const lightResult = generateColors({ appearance: 'light', @@ -64,7 +68,11 @@ export function ThemeBuilder() { darkColors: darkResult, radius, spacingUnit, + fontSize, }); + useEffect(() => { + document.documentElement.dir = dir; + }, [dir]); return ( <>