├── .npmrc ├── README.md ├── CHANGELOG.md ├── packages └── clerk-solidjs │ ├── src │ ├── start │ │ ├── index.tsx │ │ ├── utils │ │ │ ├── index.ts │ │ │ └── env.ts │ │ ├── server │ │ │ ├── env.d.ts │ │ │ ├── index.ts │ │ │ ├── clerk-client.ts │ │ │ ├── types.ts │ │ │ ├── auth.ts │ │ │ ├── constants.ts │ │ │ ├── authenticate-request.ts │ │ │ ├── middleware.ts │ │ │ ├── load-options.ts │ │ │ └── utils │ │ │ │ └── index.ts │ │ └── client │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── use-awaitable-navigate.ts │ │ │ ├── clerk-provider.tsx │ │ │ └── utils.ts │ ├── utils │ │ ├── is-constructor.ts │ │ ├── is-dev-or-stage-url.ts │ │ ├── index.ts │ │ ├── version-selector.ts │ │ ├── create-context-provider-and-hook.ts │ │ ├── use-max-allowed-instances-guard.tsx │ │ ├── children-utils.tsx │ │ └── load-clerk-js-script.ts │ ├── errors.ts │ ├── globals.d.ts │ ├── hooks │ │ ├── use-clerk.ts │ │ ├── index.ts │ │ ├── use-user.ts │ │ ├── use-session.ts │ │ ├── use-assert-wrapped-by-clerk-provider.tsx │ │ ├── use-email-link.ts │ │ ├── use-session-list.ts │ │ ├── utils.ts │ │ ├── use-sign-in.ts │ │ ├── use-sign-up.ts │ │ ├── use-auth.ts │ │ ├── use-organization-list.ts │ │ ├── use-pages-or-infinite.ts │ │ └── use-organization.ts │ ├── server │ │ └── index.ts │ ├── index.tsx │ ├── polyfills.ts │ ├── contexts │ │ ├── isomorphic-clerk.tsx │ │ ├── user.tsx │ │ ├── client.tsx │ │ ├── session.tsx │ │ ├── organization.tsx │ │ ├── index.ts │ │ ├── clerk-instance.tsx │ │ ├── auth.tsx │ │ ├── clerk.tsx │ │ ├── clerk-context.tsx │ │ └── __tests__ │ │ │ └── clerk.test.tsx │ ├── errors │ │ ├── error-thrower.ts │ │ └── messages.ts │ ├── components │ │ ├── index.ts │ │ ├── sign-out-button.tsx │ │ ├── __tests__ │ │ │ ├── sign-in.test.tsx │ │ │ ├── sign-up.test.tsx │ │ │ ├── user-profile.test.tsx │ │ │ ├── organization-profile.test.tsx │ │ │ ├── organization-switcher.test.tsx │ │ │ ├── sign-out-button.test.tsx │ │ │ ├── user-button.test.tsx │ │ │ ├── sign-in-with-metamask-button.test.tsx │ │ │ ├── sign-in-button.test.tsx │ │ │ └── sign-up-button.test.tsx │ │ ├── sign-in-with-metamask-button.tsx │ │ ├── sign-up-button.tsx │ │ ├── with-clerk.tsx │ │ ├── sign-in-button.tsx │ │ ├── control-components.tsx │ │ └── ui-components.tsx │ ├── __tests__ │ │ └── isomorphic-clerk.test.ts │ └── types.ts │ ├── .prettierignore │ ├── .prettierrc │ ├── turbo.json │ ├── eslint.config.js │ ├── tsconfig.json │ ├── vitest.config.ts │ ├── tsup.config.ts │ ├── CHANGELOG.md │ ├── package.json │ └── README.md ├── pnpm-workspace.yaml ├── examples └── basic │ ├── .env.example │ ├── src │ ├── app.css │ ├── entry-client.tsx │ ├── global.d.ts │ ├── middleware.ts │ ├── app.tsx │ ├── entry-server.tsx │ └── routes │ │ ├── waitlist.tsx │ │ ├── protected │ │ └── index.tsx │ │ └── index.tsx │ ├── postcss.config.js │ ├── public │ └── favicon.ico │ ├── tailwind.config.js │ ├── app.config.ts │ ├── .gitignore │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── .husky └── pre-commit ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 1.bug_report.yml │ └── 2.feature_request.yml ├── workflows │ ├── ci.yml │ ├── autofix.yml │ ├── release.yml │ └── quality.yml ├── FUNDING.yml └── actions │ └── ci-setup │ └── action.yml ├── docs ├── SECURITY.md └── PUBLISH.md ├── .changeset ├── config.json └── README.md ├── turbo.json ├── package.json ├── LICENSE ├── .gitignore └── assets └── images └── icon.svg /.npmrc: -------------------------------------------------------------------------------- 1 | resolution-mode=highest 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | packages/clerk-solidjs/README.md -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | packages/clerk-solidjs/CHANGELOG.md -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './client'; 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - examples/* 4 | -------------------------------------------------------------------------------- /examples/basic/.env.example: -------------------------------------------------------------------------------- 1 | VITE_CLERK_PUBLISHABLE_KEY= 2 | CLERK_SECRET_KEY= 3 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | pnpm-lock.yaml 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm run format 2 | pnpm run lint:fix 3 | git add . 4 | 5 | pnpm run test 6 | -------------------------------------------------------------------------------- /examples/basic/src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /examples/basic/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /examples/basic/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spirit-led-software/clerk-solidjs/HEAD/examples/basic/public/favicon.ico -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/utils/is-constructor.ts: -------------------------------------------------------------------------------- 1 | export function isConstructor(f: any): f is T { 2 | return typeof f === 'function'; 3 | } 4 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "singleQuote": true, 4 | "plugins": ["prettier-plugin-organize-imports"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const isClient = () => typeof window !== 'undefined'; 2 | 3 | export const isServer = () => !isClient(); 4 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/errors.ts: -------------------------------------------------------------------------------- 1 | export { 2 | EmailLinkErrorCode, 3 | isClerkAPIResponseError, 4 | isEmailLinkError, 5 | isKnownError, 6 | isMetamaskError 7 | } from '@clerk/shared/error'; 8 | -------------------------------------------------------------------------------- /examples/basic/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/utils/is-dev-or-stage-url.ts: -------------------------------------------------------------------------------- 1 | import { createDevOrStagingUrlCache } from '@clerk/shared/keys'; 2 | const { isDevOrStagingUrl } = createDevOrStagingUrlCache(); 3 | export { isDevOrStagingUrl }; 4 | -------------------------------------------------------------------------------- /examples/basic/src/entry-client.tsx: -------------------------------------------------------------------------------- 1 | // @refresh reload 2 | import { mount, StartClient } from "@solidjs/start/client"; 3 | 4 | import "solid-devtools"; 5 | 6 | mount(() => , document.getElementById("app")!); 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | schedule: 6 | interval: daily 7 | groups: 8 | all: 9 | patterns: 10 | - "*" 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/spirit-led-software/clerk-solidjs/discussions 5 | about: Please ask questions in our discussions forum. 6 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "extends": ["//"], 4 | "tasks": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "outputs": ["dist/**"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docs/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security policy 2 | 3 | ## Supported versions 4 | 5 | The latest version of the project is currently supported with security updates. 6 | 7 | ## Reporting a vulnerability 8 | 9 | You can report a vulnerability by contacting maintainers via email. 10 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | const PACKAGE_NAME: string; 3 | const PACKAGE_VERSION: string; 4 | const JS_PACKAGE_VERSION: string; 5 | const __DEV__: boolean; 6 | var __BUILD_DISABLE_RHC__: boolean; // eslint-disable-line no-var 7 | } 8 | 9 | export {}; 10 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/server/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { AuthObject } from '@clerk/backend'; 4 | 5 | declare module '@solidjs/start/server' { 6 | export interface RequestEventLocals { 7 | auth: AuthObject; 8 | } 9 | } 10 | 11 | export {}; 12 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "master", 9 | "updateInternalDependencies": "patch" 10 | } 11 | -------------------------------------------------------------------------------- /examples/basic/src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import { AuthReturn } from "clerk-solidjs/server"; 4 | 5 | declare module "@solidjs/start/server" { 6 | export interface RequestEventLocals { 7 | auth: AuthReturn; 8 | } 9 | } 10 | 11 | export {}; 12 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-clerk.ts: -------------------------------------------------------------------------------- 1 | import { useClerkInstanceContext } from '../contexts/clerk-instance'; 2 | import { useAssertWrappedByClerkProvider } from './use-assert-wrapped-by-clerk-provider'; 3 | 4 | export const useClerk = () => { 5 | useAssertWrappedByClerkProvider('useClerk'); 6 | return useClerkInstanceContext(); 7 | }; 8 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './children-utils'; 2 | export * from './create-context-provider-and-hook'; 3 | export * from './is-constructor'; 4 | export * from './is-dev-or-stage-url'; 5 | export * from './load-clerk-js-script'; 6 | export * from './use-max-allowed-instances-guard'; 7 | export * from './version-selector'; 8 | -------------------------------------------------------------------------------- /examples/basic/app.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "@solidjs/start/config"; 2 | import solidDevtools from "solid-devtools/vite"; 3 | 4 | export default defineConfig({ 5 | middleware: "./src/middleware.ts", 6 | vite: { 7 | plugins: [ 8 | solidDevtools({ 9 | autoname: true, 10 | }), 11 | ], 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/server/index.ts: -------------------------------------------------------------------------------- 1 | import { auth, clerkMiddleware } from '../start/server'; 2 | 3 | export { 4 | /** 5 | * Import from `clerk-solidjs/start/server` instead. 6 | * @deprecated 7 | */ 8 | auth, 9 | 10 | /** 11 | * Import from `clerk-solidjs/start/server` instead. 12 | * @deprecated 13 | */ 14 | clerkMiddleware 15 | }; 16 | -------------------------------------------------------------------------------- /examples/basic/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | dist 3 | .solid 4 | .output 5 | .vercel 6 | .netlify 7 | .vinxi 8 | 9 | # Environment 10 | .env 11 | .env*.local 12 | 13 | # dependencies 14 | /node_modules 15 | 16 | # IDEs and editors 17 | /.idea 18 | .project 19 | .classpath 20 | *.launch 21 | .settings/ 22 | 23 | # Temp 24 | gitignore 25 | 26 | # System Files 27 | .DS_Store 28 | Thumbs.db 29 | -------------------------------------------------------------------------------- /examples/basic/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { createMiddleware } from "@solidjs/start/middleware"; 2 | import { clerkMiddleware } from "clerk-solidjs/start/server"; 3 | 4 | export default createMiddleware({ 5 | onRequest: [ 6 | clerkMiddleware({ 7 | publishableKey: import.meta.env.VITE_CLERK_PUBLISHABLE_KEY, 8 | secretKey: import.meta.env.CLERK_SECRET_KEY, 9 | }), 10 | ], 11 | }); 12 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './polyfills'; 2 | 3 | import { setErrorThrowerOptions } from './errors/error-thrower'; 4 | 5 | export * from './components'; 6 | export * from './contexts'; 7 | 8 | export * from './hooks'; 9 | export type { 10 | BrowserClerk, 11 | ClerkProp, 12 | ClerkProviderProps, 13 | HeadlessBrowserClerk 14 | } from './types'; 15 | 16 | setErrorThrowerOptions({ packageName: PACKAGE_NAME }); 17 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vite does not define `global` by default 3 | * One workaround is to use the `define` config prop 4 | * https://vitejs.dev/config/#define 5 | * We are solving this in the SDK level to reduce setup steps. 6 | */ 7 | if (typeof window !== 'undefined' && !window.global) { 8 | // @ts-ignore 9 | window.global = typeof global === 'undefined' ? window : global; 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | name: Build & Test 12 | timeout-minutes: 20 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: CI setup 18 | uses: ./.github/actions/ci-setup 19 | - name: Test 20 | run: pnpm run test 21 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/contexts/isomorphic-clerk.tsx: -------------------------------------------------------------------------------- 1 | import { Accessor } from 'solid-js'; 2 | import { IsomorphicClerk } from '../isomorphic-clerk'; 3 | import { 4 | ClerkInstanceContextProvider, 5 | useClerkInstanceContext 6 | } from './clerk-instance'; 7 | 8 | export const IsomorphicClerkContextProvider = ClerkInstanceContextProvider; 9 | export const useIsomorphicClerkContext = 10 | useClerkInstanceContext as unknown as () => Accessor; 11 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/contexts/user.tsx: -------------------------------------------------------------------------------- 1 | import { UserResource } from '@clerk/types'; 2 | import { Accessor } from 'solid-js'; 3 | import { createContextProviderAndHook } from '../utils/create-context-provider-and-hook'; 4 | 5 | export const [UserContextProvider, useUserContext] = 6 | createContextProviderAndHook( 7 | 'UserProvider', 8 | (props: { user: Accessor }) => { 9 | return props.user; 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/contexts/client.tsx: -------------------------------------------------------------------------------- 1 | import { ClientResource } from '@clerk/types'; 2 | import { Accessor } from 'solid-js'; 3 | import { createContextProviderAndHook } from '../utils/create-context-provider-and-hook'; 4 | 5 | export const [ClientContextProvider, useClientContext] = 6 | createContextProviderAndHook( 7 | 'ClientProvider', 8 | (props: { client: Accessor }) => { 9 | return props.client; 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/contexts/session.tsx: -------------------------------------------------------------------------------- 1 | import { SessionResource } from '@clerk/types'; 2 | import { Accessor } from 'solid-js'; 3 | import { createContextProviderAndHook } from '../utils/create-context-provider-and-hook'; 4 | 5 | export const [SessionContextProvider, useSessionContext] = 6 | createContextProviderAndHook( 7 | 'SessionProvider', 8 | (props: { session: Accessor }) => { 9 | return props.session; 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /examples/basic/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "allowSyntheticDefaultImports": true, 7 | "esModuleInterop": true, 8 | "jsx": "preserve", 9 | "jsxImportSource": "solid-js", 10 | "allowJs": true, 11 | "strict": true, 12 | "noEmit": true, 13 | "types": ["vinxi/types/client"], 14 | "isolatedModules": true, 15 | "paths": { 16 | "~/*": ["./src/*"] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useAuth } from './use-auth'; 2 | export { useClerk } from './use-clerk'; 3 | export { useEmailLink } from './use-email-link'; 4 | export { useOrganization } from './use-organization'; 5 | export { useOrganizationList } from './use-organization-list'; 6 | export { useSession } from './use-session'; 7 | export { useSessionList } from './use-session-list'; 8 | export { useSignIn } from './use-sign-in'; 9 | export { useSignUp } from './use-sign-up'; 10 | export { useUser } from './use-user'; 11 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/contexts/organization.tsx: -------------------------------------------------------------------------------- 1 | import { OrganizationResource } from '@clerk/types'; 2 | import { Accessor } from 'solid-js'; 3 | import { createContextProviderAndHook } from '../utils/create-context-provider-and-hook'; 4 | 5 | export const [OrganizationContextProvider, useOrganizationContext] = 6 | createContextProviderAndHook( 7 | 'OrganizationProvider', 8 | (props: { 9 | organization: Accessor; 10 | }) => { 11 | return props.organization; 12 | } 13 | ); 14 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/utils/version-selector.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Select the version of clerk-js to use 3 | * @param clerkJSVersion - The optional clerkJSVersion prop on the provider 4 | * @param packageVersion - The version of `@clerk/clerk-solidjs` that will be used if an explicit version is not provided 5 | * @returns The npm tag, version or major version to use 6 | */ 7 | export const versionSelector = (clerkJSVersion: string | undefined) => { 8 | if (clerkJSVersion) { 9 | return clerkJSVersion; 10 | } 11 | 12 | return JS_PACKAGE_VERSION; 13 | }; 14 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/errors/error-thrower.ts: -------------------------------------------------------------------------------- 1 | import type { ErrorThrowerOptions } from '@clerk/shared/error'; 2 | import { buildErrorThrower } from '@clerk/shared/error'; 3 | 4 | const errorThrower = buildErrorThrower({ packageName: 'clerk-solidjs' }); 5 | 6 | export { errorThrower }; 7 | 8 | /** 9 | * Overrides options of the internal errorThrower (eg setting packageName prefix). 10 | * 11 | * @internal 12 | */ 13 | export function setErrorThrowerOptions(options: ErrorThrowerOptions) { 14 | errorThrower.setMessages(options).setPackageName(options); 15 | } 16 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-user.ts: -------------------------------------------------------------------------------- 1 | import { createMemo } from 'solid-js'; 2 | import { useUserContext } from '../contexts/user'; 3 | import { useAssertWrappedByClerkProvider } from './use-assert-wrapped-by-clerk-provider'; 4 | 5 | export function useUser() { 6 | useAssertWrappedByClerkProvider('useUser'); 7 | 8 | const user = useUserContext(); 9 | 10 | const isLoaded = createMemo(() => user() !== undefined); 11 | const isSignedIn = createMemo(() => user() !== null); 12 | 13 | return { 14 | isLoaded, 15 | isSignedIn, 16 | user 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/eslint.config.js: -------------------------------------------------------------------------------- 1 | import pluginJs from '@eslint/js'; 2 | import globals from 'globals'; 3 | import tseslint from 'typescript-eslint'; 4 | 5 | export default tseslint.config( 6 | { 7 | ignores: ['node_modules/**', 'dist/**'] 8 | }, 9 | { languageOptions: { globals: { ...globals.browser, ...globals.node } } }, 10 | pluginJs.configs.recommended, 11 | ...tseslint.configs.recommended, 12 | { 13 | rules: { 14 | '@typescript-eslint/no-explicit-any': 'warn', 15 | '@typescript-eslint/ban-ts-comment': 'warn' 16 | } 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /.github/workflows/autofix.yml: -------------------------------------------------------------------------------- 1 | name: autofix.ci 2 | 3 | on: 4 | pull_request: 5 | branches: ["master"] 6 | push: 7 | branches: ["master"] 8 | workflow_dispatch: 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | autofix: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | - name: CI setup 19 | uses: ./.github/actions/ci-setup 20 | - name: Run format 21 | run: pnpm run format 22 | - name: Autofix 23 | uses: autofix-ci/action@dd55f44df8f7cdb7a6bf74c78677eb8acd40cd0a 24 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-session.ts: -------------------------------------------------------------------------------- 1 | import { createMemo } from 'solid-js'; 2 | import { useSessionContext } from '../contexts/session'; 3 | import { useAssertWrappedByClerkProvider } from './use-assert-wrapped-by-clerk-provider'; 4 | 5 | export const useSession = () => { 6 | useAssertWrappedByClerkProvider('useSession'); 7 | 8 | const session = useSessionContext(); 9 | 10 | const isLoaded = createMemo(() => session() !== undefined); 11 | const isSignedIn = createMemo(() => session() !== null); 12 | 13 | return { 14 | isLoaded, 15 | isSignedIn, 16 | session 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /examples/basic/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { Router } from "@solidjs/router"; 2 | import { FileRoutes } from "@solidjs/start/router"; 3 | import { ClerkProvider } from "clerk-solidjs/start"; 4 | import { Suspense } from "solid-js/web"; 5 | 6 | import "./app.css"; 7 | 8 | export default function App() { 9 | return ( 10 | ( 12 | 15 | {props.children} 16 | 17 | )} 18 | > 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/basic/src/entry-server.tsx: -------------------------------------------------------------------------------- 1 | // @refresh reload 2 | import { createHandler, StartServer } from "@solidjs/start/server"; 3 | 4 | export default createHandler(() => ( 5 | ( 7 | 8 | 9 | 10 | 11 | 12 | {assets} 13 | 14 | 15 |
{children}
16 | {scripts} 17 | 18 | 19 | )} 20 | /> 21 | )); 22 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ian-pascoe 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: 13 | - https://buymeacoffee.com/spiritledsoftware 14 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-assert-wrapped-by-clerk-provider.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'solid-js'; 2 | import { ClerkInstanceContext } from '../contexts/clerk-instance'; 3 | 4 | export function useAssertWrappedByClerkProvider( 5 | displayNameOrFn: string | (() => void) 6 | ): void { 7 | const ctx = useContext(ClerkInstanceContext); 8 | 9 | if (!ctx) { 10 | if (typeof displayNameOrFn === 'function') { 11 | displayNameOrFn(); 12 | return; 13 | } 14 | 15 | throw new Error( 16 | `${displayNameOrFn} can only be used within the component. Learn more: https://clerk.com/docs/components/clerk-provider` 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "tasks": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "env": ["VITE_CLERK_PUBLISHABLE_KEY", "CLERK_SECRET_KEY"], 7 | "outputs": ["dist/**"] 8 | }, 9 | "clean": { 10 | "dependsOn": ["^clean"] 11 | }, 12 | "dev": { 13 | "cache": false, 14 | "persistent": true 15 | }, 16 | "lint": {}, 17 | "lint:fix": {}, 18 | "format": {}, 19 | "format:check": {}, 20 | "type-check": {}, 21 | "test": {}, 22 | "test:watch": { 23 | "cache": false, 24 | "persistent": true 25 | } 26 | }, 27 | "globalEnv": ["CI", "GITHUB_ACTIONS", "PORT"] 28 | } 29 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-email-link.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | EmailAddressResource, 3 | SignInResource, 4 | SignUpResource 5 | } from '@clerk/types'; 6 | import { destructure } from '@solid-primitives/destructure'; 7 | import { Accessor, createEffect, createMemo, onCleanup } from 'solid-js'; 8 | 9 | type EmailLinkable = SignUpResource | EmailAddressResource | SignInResource; 10 | 11 | function useEmailLink(resource: Accessor) { 12 | const linkFlows = createMemo(() => resource().createEmailLinkFlow()); 13 | 14 | createEffect(() => { 15 | onCleanup(linkFlows().cancelEmailLinkFlow); 16 | }); 17 | 18 | return destructure(linkFlows); 19 | } 20 | 21 | export { useEmailLink }; 22 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "allowJs": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": true, 13 | "strict": true, 14 | "skipLibCheck": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "jsx": "preserve", 17 | "jsxImportSource": "solid-js", 18 | "types": ["@types/node"] 19 | }, 20 | "include": ["**/*.ts", "**/*.tsx"], 21 | "exclude": ["node_modules", "dist", "example"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/utils/create-context-provider-and-hook.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ContextProviderProps, 3 | createContextProvider 4 | } from '@solid-primitives/context'; 5 | 6 | export function createContextProviderAndHook( 7 | name: string, 8 | factoryFn: Parameters>[0] 9 | ) { 10 | const [ContextProvider, useContextUnsafe] = createContextProvider(factoryFn); 11 | 12 | const useContext = () => { 13 | const ctx = useContextUnsafe(); 14 | if (ctx === undefined) { 15 | throw new Error(`${name} not found`); 16 | } 17 | return ctx as T; 18 | }; 19 | 20 | return [ContextProvider, useContext, useContextUnsafe] as const; 21 | } 22 | -------------------------------------------------------------------------------- /.github/actions/ci-setup/action.yml: -------------------------------------------------------------------------------- 1 | name: "CI setup" 2 | description: "CI setup for clerk-solidjs" 3 | 4 | runs: 5 | using: "composite" 6 | steps: 7 | - name: Cache turbo build setup 8 | uses: actions/cache@v4 9 | with: 10 | path: .turbo 11 | key: ${{ runner.os }}-turbo-${{ github.sha }} 12 | restore-keys: | 13 | ${{ runner.os }}-turbo- 14 | 15 | - name: Install pnpm 16 | uses: pnpm/action-setup@v4 17 | with: 18 | run_install: false 19 | 20 | - name: Install Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 20 24 | cache: "pnpm" 25 | 26 | - name: Install dependencies 27 | shell: bash 28 | run: pnpm install 29 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/server/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth'; 2 | export * from './middleware'; 3 | 4 | export { clerkClient } from './clerk-client'; 5 | 6 | /** 7 | * Re-export resource types from @clerk/backend 8 | */ 9 | export type { 10 | // Resources 11 | AllowlistIdentifier, 12 | Client, 13 | EmailAddress, 14 | ExternalAccount, 15 | Invitation, 16 | OauthAccessToken, 17 | Organization, 18 | OrganizationInvitation, 19 | OrganizationMembership, 20 | OrganizationMembershipPublicUserData, 21 | OrganizationMembershipRole, 22 | PhoneNumber, 23 | SMSMessage, 24 | Session, 25 | SignInToken, 26 | Token, 27 | User, 28 | // Webhook event types 29 | WebhookEvent, 30 | WebhookEventType 31 | } from '@clerk/backend'; 32 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/contexts/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | /** 3 | * The main Clerk context provider. You must wrap your application in a `ClerkProvider` to enable Clerk features. 4 | * 5 | * If using SolidStart, import from `clerk-solidjs/start` instead. 6 | * 7 | * @example 8 | * ```tsx 9 | * // App.tsx 10 | * import { ClerkProvider } from 'clerk-solidjs'; 11 | * 12 | * const App = () => ( 13 | * ( 14 | * 15 | * {props.children} 16 | * 17 | * )}> 18 | * 19 | * 20 | * 21 | * 22 | * ); 23 | * ``` 24 | */ 25 | ClerkProvider 26 | } from './clerk'; 27 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/client/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | /** 3 | * The main Clerk context provider. You must wrap your application in a `ClerkProvider` to enable Clerk features. 4 | * 5 | * @example 6 | * ```tsx 7 | * // app.tsx 8 | * import { ClerkProvider } from 'clerk-solidjs/start'; 9 | * 10 | * export default function App() { 11 | * return ( 12 | * ( 14 | * 17 | * {props.children} 18 | * 19 | * )} 20 | * > 21 | * 22 | * 23 | * ); 24 | * } 25 | * ``` 26 | */ 27 | ClerkProvider 28 | } from './clerk-provider'; 29 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clerk-solidjs-basic-example", 3 | "description": "An example of how to use clerk-solidjs with SolidStart", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "clean": "rimraf .output .vinxi", 8 | "build": "vinxi build", 9 | "dev": "vinxi dev", 10 | "start": "vinxi start" 11 | }, 12 | "devDependencies": { 13 | "autoprefixer": "^10.4.21", 14 | "postcss": "^8.5.3", 15 | "solid-devtools": "^0.33.0", 16 | "tailwindcss": "^4.0.17", 17 | "vinxi": "^0.5.3" 18 | }, 19 | "dependencies": { 20 | "@solid-primitives/resize-observer": "^2.1.0", 21 | "@solidjs/router": "^0.15.3", 22 | "@solidjs/start": "^1.1.3", 23 | "clerk-solidjs": "workspace:*", 24 | "solid-js": "^1.9.5" 25 | }, 26 | "engines": { 27 | "node": ">=18" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { createRequire } from 'module'; 2 | import solid from 'vite-plugin-solid'; 3 | import tsconfigPaths from 'vite-tsconfig-paths'; 4 | import { defineConfig } from 'vitest/config'; 5 | 6 | const require = createRequire(import.meta.url); 7 | const { name, version } = require('./package.json'); 8 | const { version: clerkJsVersion } = require('@clerk/clerk-js/package.json'); 9 | 10 | export default defineConfig({ 11 | plugins: [tsconfigPaths(), solid()], 12 | define: { 13 | PACKAGE_NAME: `"${name}"`, 14 | PACKAGE_VERSION: `"${version}"`, 15 | JS_PACKAGE_VERSION: `"${clerkJsVersion}"` 16 | }, 17 | test: { 18 | environment: 'jsdom', 19 | setupFiles: ['./node_modules/@testing-library/jest-dom/vitest'] 20 | }, 21 | resolve: { 22 | conditions: ['development', 'browser'] 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/server/clerk-client.ts: -------------------------------------------------------------------------------- 1 | import { createClerkClient } from '@clerk/backend'; 2 | 3 | import { 4 | API_URL, 5 | API_VERSION, 6 | DOMAIN, 7 | IS_SATELLITE, 8 | PROXY_URL, 9 | PUBLISHABLE_KEY, 10 | SDK_METADATA, 11 | SECRET_KEY, 12 | TELEMETRY_DEBUG, 13 | TELEMETRY_DISABLED 14 | } from './constants'; 15 | 16 | const clerkClient: typeof createClerkClient = (options) => 17 | createClerkClient({ 18 | secretKey: SECRET_KEY, 19 | publishableKey: PUBLISHABLE_KEY, 20 | apiUrl: API_URL, 21 | apiVersion: API_VERSION, 22 | userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`, 23 | proxyUrl: PROXY_URL, 24 | domain: DOMAIN, 25 | isSatellite: IS_SATELLITE, 26 | sdkMetadata: SDK_METADATA, 27 | telemetry: { 28 | disabled: TELEMETRY_DISABLED, 29 | debug: TELEMETRY_DEBUG 30 | }, 31 | ...options 32 | }); 33 | 34 | export { clerkClient }; 35 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/server/types.ts: -------------------------------------------------------------------------------- 1 | import { VerifyTokenOptions } from '@clerk/backend'; 2 | import { 3 | LegacyRedirectProps, 4 | MultiDomainAndOrProxy, 5 | SignInFallbackRedirectUrl, 6 | SignInForceRedirectUrl, 7 | SignUpFallbackRedirectUrl, 8 | SignUpForceRedirectUrl 9 | } from '@clerk/types'; 10 | 11 | export type LoaderOptions = { 12 | publishableKey?: string; 13 | jwtKey?: string; 14 | secretKey?: string; 15 | authorizedParties?: []; 16 | signInUrl?: string; 17 | signUpUrl?: string; 18 | } & Pick & 19 | MultiDomainAndOrProxy & 20 | SignInForceRedirectUrl & 21 | SignInFallbackRedirectUrl & 22 | SignUpForceRedirectUrl & 23 | SignUpFallbackRedirectUrl & 24 | LegacyRedirectProps; 25 | 26 | export type AdditionalStateOptions = SignInFallbackRedirectUrl & 27 | SignUpFallbackRedirectUrl & 28 | SignInForceRedirectUrl & 29 | SignUpForceRedirectUrl; 30 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | AuthenticateWithRedirectCallback, 3 | ClerkLoaded, 4 | ClerkLoading, 5 | Protect, 6 | RedirectToCreateOrganization, 7 | RedirectToOrganizationProfile, 8 | RedirectToSignIn, 9 | RedirectToSignUp, 10 | RedirectToUserProfile, 11 | SignedIn, 12 | SignedOut 13 | } from './control-components'; 14 | 15 | export type { ProtectProps } from './control-components'; 16 | 17 | export { SignInButton } from './sign-in-button'; 18 | export { SignInWithMetamaskButton } from './sign-in-with-metamask-button'; 19 | export { SignOutButton } from './sign-out-button'; 20 | export { SignUpButton } from './sign-up-button'; 21 | 22 | export { 23 | CreateOrganization, 24 | GoogleOneTap, 25 | OrganizationList, 26 | OrganizationProfile, 27 | OrganizationSwitcher, 28 | SignIn, 29 | SignUp, 30 | UserButton, 31 | UserProfile, 32 | Waitlist 33 | } from './ui-components'; 34 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-session-list.ts: -------------------------------------------------------------------------------- 1 | import { createMemo } from 'solid-js'; 2 | import { useClerkInstanceContext } from '../contexts/clerk-instance'; 3 | import { useClientContext } from '../contexts/client'; 4 | import { IsomorphicClerk } from '../isomorphic-clerk'; 5 | import { useAssertWrappedByClerkProvider } from './use-assert-wrapped-by-clerk-provider'; 6 | 7 | export const useSessionList = () => { 8 | useAssertWrappedByClerkProvider('useSessionList'); 9 | 10 | const isomorphicClerk = useClerkInstanceContext(); 11 | const client = useClientContext(); 12 | 13 | const isLoaded = createMemo(() => !!client()); 14 | const sessions = createMemo(() => client()?.sessions); 15 | 16 | const setActive = (params: Parameters[0]) => { 17 | return isomorphicClerk()?.setActive(params); 18 | }; 19 | 20 | return { 21 | isLoaded, 22 | sessions, 23 | setActive 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - ".changeset/**" 9 | - ".github/workflows/release.yml" 10 | workflow_dispatch: 11 | 12 | concurrency: ${{ github.workflow }}-${{ github.ref }} 13 | 14 | jobs: 15 | release: 16 | name: Release 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: write 20 | id-token: write 21 | pull-requests: write 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v3 25 | - name: CI setup 26 | uses: ./.github/actions/ci-setup 27 | - name: Create Release Pull Request or Publish to NPM 28 | id: changesets 29 | uses: changesets/action@v1 30 | with: 31 | version: pnpm run version 32 | publish: pnpm run release 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/contexts/clerk-instance.tsx: -------------------------------------------------------------------------------- 1 | import { LoadedClerk } from '@clerk/types'; 2 | import { Accessor, createContext, JSX, useContext } from 'solid-js'; 3 | 4 | const ClerkInstanceContext = createContext>(); 5 | 6 | const ClerkInstanceContextProvider = (props: { 7 | clerk: Accessor; 8 | children: JSX.Element; 9 | }) => { 10 | return ( 11 | 12 | {props.children} 13 | 14 | ); 15 | }; 16 | 17 | function useClerkInstanceContext() { 18 | const ctx = useContext(ClerkInstanceContext); 19 | 20 | if (!ctx) { 21 | throw new Error( 22 | `ClerkInstanceProvider can only be used within the component. Learn more: https://clerk.com/docs/components/clerk-provider` 23 | ); 24 | } 25 | 26 | return ctx; 27 | } 28 | 29 | export { 30 | ClerkInstanceContext, 31 | ClerkInstanceContextProvider, 32 | useClerkInstanceContext 33 | }; 34 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/server/auth.ts: -------------------------------------------------------------------------------- 1 | import { AuthObject } from '@clerk/backend'; 2 | import { getRequestEvent } from 'solid-js/web'; 3 | 4 | export type AuthHelper = () => AuthObject; 5 | 6 | /** 7 | * Function that retrieves the authentication information from the event object. 8 | * You must implement `clerkMiddleware` to use this function. 9 | * 10 | * Must be called from within a server function. 11 | * 12 | * @example 13 | * async function myServerFunction() { 14 | * 'use server'; 15 | * const { userId } = auth(); 16 | * // ... 17 | * } 18 | * 19 | * @return The authentication information stored in the event object. 20 | */ 21 | export const auth: AuthHelper = () => { 22 | const event = getRequestEvent(); 23 | if (!event) { 24 | throw new Error('auth() must be called from within a server function'); 25 | } 26 | 27 | if (!event.locals.auth) { 28 | throw new Error('auth() returned null. Did you implement clerkMiddleware?'); 29 | } 30 | 31 | return event.locals.auth; 32 | }; 33 | -------------------------------------------------------------------------------- /.github/workflows/quality.yml: -------------------------------------------------------------------------------- 1 | name: Quality 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | prettier: 11 | name: Prettier 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | - name: CI setup 17 | uses: ./.github/actions/ci-setup 18 | - name: Run Prettier check 19 | run: pnpm run format:check 20 | 21 | eslint: 22 | name: ESLint 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v3 27 | - name: CI setup 28 | uses: ./.github/actions/ci-setup 29 | - name: Run ESLint check 30 | run: pnpm run lint 31 | 32 | types: 33 | name: TypeScript 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v3 38 | - name: CI setup 39 | uses: ./.github/actions/ci-setup 40 | - name: Run TypeScript type check 41 | run: pnpm run type-check 42 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # SolidStart 2 | 3 | Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com); 4 | 5 | ## Creating a project 6 | 7 | ```bash 8 | # create a new project in the current directory 9 | npm init solid@latest 10 | 11 | # create a new project in my-app 12 | npm init solid@latest my-app 13 | ``` 14 | 15 | ## Developing 16 | 17 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 18 | 19 | ```bash 20 | npm run dev 21 | 22 | # or start the server and open the app in a new browser tab 23 | npm run dev -- --open 24 | ``` 25 | 26 | ## Building 27 | 28 | Solid apps are built with _presets_, which optimise your project for deployment to different environments. 29 | 30 | By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`. 31 | 32 | ## This project was created with the [Solid CLI](https://solid-cli.netlify.app) 33 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/utils.ts: -------------------------------------------------------------------------------- 1 | import { Accessor } from 'solid-js'; 2 | import type { IsomorphicClerk } from '../isomorphic-clerk'; 3 | 4 | /** 5 | * @internal 6 | */ 7 | const clerkLoaded = (isomorphicClerk: Accessor) => { 8 | return new Promise((resolve) => { 9 | const clerk = isomorphicClerk(); 10 | if (clerk.loaded) { 11 | resolve(); 12 | } 13 | clerk.addOnLoaded(resolve); 14 | }); 15 | }; 16 | 17 | /** 18 | * @internal 19 | */ 20 | export const createGetToken = (isomorphicClerk: Accessor) => { 21 | return async (options: any) => { 22 | const clerk = isomorphicClerk(); 23 | await clerkLoaded(isomorphicClerk); 24 | if (!clerk.session) { 25 | return null; 26 | } 27 | return clerk.session!.getToken(options); 28 | }; 29 | }; 30 | 31 | /** 32 | * @internal 33 | */ 34 | export const createSignOut = (isomorphicClerk: Accessor) => { 35 | return async (...args: any) => { 36 | await clerkLoaded(isomorphicClerk); 37 | return isomorphicClerk().signOut(...args); 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/client/types.ts: -------------------------------------------------------------------------------- 1 | import type { InitialState, Without } from '@clerk/types'; 2 | import type { JSX } from 'solid-js'; 3 | import type { ClerkProviderProps } from '../../types'; 4 | 5 | export type ClerkState = { 6 | __type: 'clerkState'; 7 | __internal_clerk_state: { 8 | __clerk_ssr_state: InitialState; 9 | __publishableKey: string | undefined; 10 | __proxyUrl: string | undefined; 11 | __domain: string | undefined; 12 | __isSatellite: boolean; 13 | __signInUrl: string | undefined; 14 | __signUpUrl: string | undefined; 15 | __afterSignInUrl: string | undefined; 16 | __afterSignUpUrl: string | undefined; 17 | __clerk_debug: any; 18 | __clerkJSUrl: string | undefined; 19 | __clerkJSVersion: string | undefined; 20 | __telemetryDisabled: boolean | undefined; 21 | __telemetryDebug: boolean | undefined; 22 | }; 23 | }; 24 | 25 | export type SolidStartClerkProviderProps = Without< 26 | ClerkProviderProps, 27 | 'publishableKey' | 'initialState' 28 | > & { 29 | publishableKey?: string; 30 | children: JSX.Element; 31 | }; 32 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/client/use-awaitable-navigate.ts: -------------------------------------------------------------------------------- 1 | import type { NavigateOptions } from '@solidjs/router'; 2 | import { useLocation, useNavigate } from '@solidjs/router'; 3 | import { createEffect, on, useTransition } from 'solid-js'; 4 | 5 | type Resolve = (value?: unknown) => void; 6 | 7 | export const useAwaitableNavigate = () => { 8 | const navigate = useNavigate(); 9 | const location = useLocation(); 10 | const resolveFunctionsRef: Resolve[] = []; 11 | const resolveAll = () => { 12 | resolveFunctionsRef.forEach((resolve) => resolve()); 13 | resolveFunctionsRef.splice(0, resolveFunctionsRef.length); 14 | }; 15 | const [, startTransition] = useTransition(); 16 | 17 | createEffect( 18 | on( 19 | () => location, 20 | () => { 21 | resolveAll(); 22 | } 23 | ) 24 | ); 25 | 26 | return (to: string, options?: Partial) => { 27 | return new Promise((res) => { 28 | startTransition(() => { 29 | resolveFunctionsRef.push(res); 30 | res(navigate(to, options)); 31 | }); 32 | }); 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clerk-solidjs", 3 | "private": true, 4 | "scripts": { 5 | "build": "turbo build", 6 | "changeset": "changeset", 7 | "clean-install": "rimraf pnpm-lock.yaml --glob **/node_modules && pnpm install", 8 | "clean": "turbo clean", 9 | "dev": "turbo dev", 10 | "format:check": "turbo format:check", 11 | "format": "turbo format", 12 | "lint:fix": "turbo lint:fix", 13 | "lint": "turbo lint", 14 | "prepare": "husky", 15 | "prerelease": "turbo clean && turbo type-check && turbo format:check && turbo lint && turbo test", 16 | "release": "turbo clean && turbo build && changeset publish", 17 | "test:watch": "turbo test:watch", 18 | "test": "turbo test", 19 | "type-check": "turbo type-check", 20 | "version": "changeset version && pnpm install --no-frozen-lockfile" 21 | }, 22 | "author": "Spirit-Led Software", 23 | "license": "MIT", 24 | "devDependencies": { 25 | "@changesets/cli": "^2.28.1", 26 | "husky": "^9.1.7", 27 | "rimraf": "^6.0.1", 28 | "turbo": "^2.4.4" 29 | }, 30 | "packageManager": "pnpm@9.4.0" 31 | } 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1.bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a bug report for the SDK. 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | This template is to report bugs for the SDK. If you need help with your own project, feel free to [start a new thread in our discussions](https://github.com/spirit-led-software/clerk-solidjs/discussions). 8 | - type: textarea 9 | attributes: 10 | label: Description 11 | description: A detailed description of the bug you are encountering with the SDK, and how other people can reproduce it. 12 | placeholder: | 13 | Reproduction steps... 14 | validations: 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: Code example 19 | description: Provide an example code snippet that has the problem 20 | placeholder: | 21 | import { ClerkProvider } from 'clerk-solidjs'; 22 | ... 23 | - type: textarea 24 | attributes: 25 | label: Additional context 26 | description: | 27 | Any extra information that might help us investigate. 28 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-sign-in.ts: -------------------------------------------------------------------------------- 1 | import { eventMethodCalled } from '@clerk/shared/telemetry'; 2 | import { createEffect, createMemo } from 'solid-js'; 3 | import { useClientContext } from '../contexts/client'; 4 | import { useIsomorphicClerkContext } from '../contexts/isomorphic-clerk'; 5 | import { IsomorphicClerk } from '../isomorphic-clerk'; 6 | import { useAssertWrappedByClerkProvider } from './use-assert-wrapped-by-clerk-provider'; 7 | 8 | export const useSignIn = () => { 9 | useAssertWrappedByClerkProvider('useSignIn'); 10 | 11 | const isomorphicClerk = useIsomorphicClerkContext(); 12 | const client = useClientContext(); 13 | 14 | createEffect(() => { 15 | isomorphicClerk().telemetry?.record(eventMethodCalled('useSignIn')); 16 | }); 17 | 18 | const isLoaded = createMemo(() => !!client()); 19 | const signIn = createMemo(() => client()?.signIn); 20 | 21 | const setActive = (params: Parameters[0]) => { 22 | return isomorphicClerk()?.setActive(params); 23 | }; 24 | 25 | return { 26 | isLoaded, 27 | signIn, 28 | setActive 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-sign-up.ts: -------------------------------------------------------------------------------- 1 | import { eventMethodCalled } from '@clerk/shared/telemetry'; 2 | import { createEffect, createMemo } from 'solid-js'; 3 | import { useClientContext } from '../contexts/client'; 4 | import { useIsomorphicClerkContext } from '../contexts/isomorphic-clerk'; 5 | import { IsomorphicClerk } from '../isomorphic-clerk'; 6 | import { useAssertWrappedByClerkProvider } from './use-assert-wrapped-by-clerk-provider'; 7 | 8 | export const useSignUp = () => { 9 | useAssertWrappedByClerkProvider('useSignUp'); 10 | 11 | const isomorphicClerk = useIsomorphicClerkContext(); 12 | const client = useClientContext(); 13 | 14 | createEffect(() => { 15 | isomorphicClerk().telemetry?.record(eventMethodCalled('useSignUp')); 16 | }); 17 | 18 | const isLoaded = createMemo(() => !!client()); 19 | const signUp = createMemo(() => client()?.signUp); 20 | 21 | const setActive = (params: Parameters[0]) => { 22 | return isomorphicClerk()?.setActive(params); 23 | }; 24 | 25 | return { 26 | isLoaded, 27 | signUp, 28 | setActive 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/utils/use-max-allowed-instances-guard.tsx: -------------------------------------------------------------------------------- 1 | import { Accessor, Component, createEffect, onCleanup } from 'solid-js'; 2 | import { errorThrower } from '../errors/error-thrower'; 3 | 4 | const counts = new Map(); 5 | 6 | export function useMaxAllowedInstancesGuard( 7 | props: Accessor<{ name: string; error: string; maxCount?: number }> 8 | ): void { 9 | createEffect(() => { 10 | const count = counts.get(props().name) || 0; 11 | if (count == (props().maxCount ?? 1)) { 12 | return errorThrower.throw(props().error); 13 | } 14 | counts.set(props().name, count + 1); 15 | 16 | onCleanup(() => { 17 | counts.set(props().name, (counts.get(props().name) || 1) - 1); 18 | }); 19 | }); 20 | } 21 | 22 | export function withMaxAllowedInstancesGuard

>( 23 | WrappedComponent: Component

, 24 | name: string, 25 | error: string 26 | ): Component

{ 27 | const HOC = (props: P) => { 28 | useMaxAllowedInstancesGuard(() => ({ name, error })); 29 | return ; 30 | }; 31 | return HOC; 32 | } 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2.feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Propose a new feature for the SDK. 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | This template is to propose new features for the SDK. If you need help with your own project, feel free to [start a new thread in our discussions](https://github.com/spirit-led-software/clerk-solidjs/discussions). 8 | - type: textarea 9 | attributes: 10 | label: Feature Description 11 | description: A detailed description of the feature you are proposing for the SDK. 12 | placeholder: | 13 | Feature description... 14 | validations: 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: Use Case 19 | description: Provide a use case where this feature would be beneficial 20 | placeholder: | 21 | Use case... 22 | - type: textarea 23 | attributes: 24 | label: Additional context 25 | description: | 26 | Any extra information that might help us understand your feature request. 27 | placeholder: | 28 | Additional context... 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ian Pascoe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/basic/src/routes/waitlist.tsx: -------------------------------------------------------------------------------- 1 | import { useWindowSize } from "@solid-primitives/resize-observer"; 2 | import { A } from "@solidjs/router"; 3 | import { 4 | ClerkLoaded, 5 | ClerkLoading, 6 | SignedIn, 7 | SignedOut, 8 | SignOutButton, 9 | UserButton, 10 | Waitlist 11 | } from "clerk-solidjs"; 12 | 13 | export default function Home() { 14 | const size = useWindowSize(); 15 | 16 | return ( 17 |

18 |

19 | clerk-solidjs 20 |

21 | 22 |

Loading...

23 |
24 | 25 | 26 | 27 | 28 | 29 | 768} /> 30 | 31 | 32 | 33 | 34 | Protected Page 35 | 36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /examples/basic/src/routes/protected/index.tsx: -------------------------------------------------------------------------------- 1 | import { A, Navigate } from "@solidjs/router"; 2 | import { 3 | Protect, 4 | SignInButton, 5 | SignOutButton, 6 | useAuth, 7 | useUser, 8 | } from "clerk-solidjs"; 9 | import { auth } from "clerk-solidjs/start/server"; 10 | import { createResource, Show } from "solid-js"; 11 | import { getRequestEvent } from "solid-js/web"; 12 | 13 | async function getSignedIn() { 14 | "use server"; 15 | const { userId } = auth(); 16 | return !!userId; 17 | } 18 | 19 | export default function ProtectedPage() { 20 | const [isSignedIn] = createResource(getSignedIn); 21 | const { user } = useUser(); 22 | 23 | return ( 24 |
25 | 29 | You are not signed in 30 |
31 | } 32 | > 33 | Welcome, {user()?.fullName} 34 | 35 | 36 | Back 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /examples/basic/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { useWindowSize } from "@solid-primitives/resize-observer"; 2 | import { A } from "@solidjs/router"; 3 | import { 4 | ClerkLoaded, 5 | ClerkLoading, 6 | SignedIn, 7 | SignedOut, 8 | SignInButton, 9 | SignOutButton, 10 | UserButton, 11 | } from "clerk-solidjs"; 12 | 13 | export default function Home() { 14 | const size = useWindowSize(); 15 | 16 | return ( 17 |
18 |

19 | clerk-solidjs 20 |

21 | 22 |

Loading...

23 |
24 | 25 | 26 | 27 | 28 | 29 | 768} /> 30 | 31 | 32 | 33 | 34 | Protected Page 35 | 36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/contexts/auth.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ActJWTClaim, 3 | OrganizationCustomPermissionKey, 4 | OrganizationCustomRoleKey 5 | } from '@clerk/types'; 6 | import { Accessor } from 'solid-js'; 7 | import { createContextProviderAndHook } from '../utils/create-context-provider-and-hook'; 8 | 9 | export const [AuthContextProvider, useAuthContext] = 10 | createContextProviderAndHook( 11 | 'AuthContext', 12 | (props: { 13 | userId: Accessor; 14 | sessionId: Accessor; 15 | actor: Accessor; 16 | orgId: Accessor; 17 | orgRole: Accessor; 18 | orgSlug: Accessor; 19 | orgPermissions: Accessor< 20 | OrganizationCustomPermissionKey[] | null | undefined 21 | >; 22 | }) => { 23 | return { 24 | userId: props.userId, 25 | sessionId: props.sessionId, 26 | actor: props.actor, 27 | orgId: props.orgId, 28 | orgRole: props.orgRole, 29 | orgSlug: props.orgSlug, 30 | orgPermissions: props.orgPermissions 31 | }; 32 | } 33 | ); 34 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/utils/children-utils.tsx: -------------------------------------------------------------------------------- 1 | import type { JSX } from 'solid-js'; 2 | import { errorThrower } from '../errors/error-thrower'; 3 | import { multipleChildrenInButtonComponent } from '../errors/messages'; 4 | 5 | export const assertSingleChild = 6 | (children: JSX.Element) => 7 | ( 8 | name: 9 | | 'SignInButton' 10 | | 'SignUpButton' 11 | | 'SignOutButton' 12 | | 'SignInWithMetamaskButton' 13 | ) => { 14 | try { 15 | if (Array.isArray(children)) { 16 | throw new Error(); 17 | } else if (children instanceof Node) { 18 | if (children.previousSibling || children.nextSibling) { 19 | throw new Error(); 20 | } 21 | } 22 | return children; 23 | } catch { 24 | return errorThrower.throw(multipleChildrenInButtonComponent(name)); 25 | } 26 | }; 27 | 28 | export const normalizeWithDefaultValue = ( 29 | children: JSX.Element | undefined, 30 | defaultText: string 31 | ) => { 32 | if (!children) { 33 | children = defaultText; 34 | } 35 | return children; 36 | }; 37 | 38 | export const safeExecute = 39 | (cb: unknown) => 40 | (...args: any) => { 41 | if (cb && typeof cb === 'function') { 42 | return cb(...args); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/sign-out-button.tsx: -------------------------------------------------------------------------------- 1 | import { children as childrenFn, createMemo, splitProps } from 'solid-js'; 2 | import type { SignOutButtonProps, WithClerkProp } from '../types'; 3 | import { 4 | assertSingleChild, 5 | normalizeWithDefaultValue, 6 | safeExecute 7 | } from '../utils'; 8 | import { withClerk } from './with-clerk'; 9 | 10 | export const SignOutButton = withClerk( 11 | (props: WithClerkProp) => { 12 | const [local, clerk, rest] = splitProps( 13 | props, 14 | ['children'], 15 | ['clerk', 'redirectUrl', 'sessionId', 'onClick'] 16 | ); 17 | 18 | const children = childrenFn(() => 19 | normalizeWithDefaultValue(local.children, 'Sign out') 20 | ); 21 | const child = createMemo(() => 22 | assertSingleChild(children())('SignOutButton') 23 | ); 24 | 25 | const clickHandler = async () => { 26 | if (clerk.onClick) { 27 | await safeExecute(clerk.onClick)(); 28 | } 29 | clerk.clerk().signOut({ 30 | redirectUrl: clerk.redirectUrl, 31 | sessionId: clerk.sessionId 32 | }); 33 | }; 34 | 35 | return ( 36 | 39 | ); 40 | }, 41 | 'SignOutButton' 42 | ); 43 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/__tests__/sign-in.test.tsx: -------------------------------------------------------------------------------- 1 | import { expectTypeOf } from 'expect-type'; 2 | 3 | import { ComponentProps } from 'solid-js'; 4 | import { describe, test } from 'vitest'; 5 | import type { SignIn } from '..'; 6 | 7 | export type SignInComponentProps = ComponentProps; 8 | 9 | describe('', () => { 10 | describe('Type tests', () => { 11 | test('has path filled', () => { 12 | expectTypeOf({ path: '/sign-in' }).toMatchTypeOf(); 13 | }); 14 | 15 | test('has path filled and routing has path as a value', () => { 16 | expectTypeOf({ 17 | path: '/sign-in', 18 | routing: 'path' as const 19 | }).toMatchTypeOf(); 20 | }); 21 | 22 | test('when path is filled, routing must only have path as value', () => { 23 | expectTypeOf({ 24 | path: '/sign-in', 25 | routing: 'virtual' as const 26 | }).not.toMatchTypeOf(); 27 | 28 | expectTypeOf({ 29 | path: '/sign-in', 30 | routing: 'hash' as const 31 | }).not.toMatchTypeOf(); 32 | }); 33 | 34 | test('when routing is hash or virtual path must be present', () => { 35 | expectTypeOf({ 36 | routing: 'hash' as const 37 | }).toMatchTypeOf(); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/__tests__/sign-up.test.tsx: -------------------------------------------------------------------------------- 1 | import { expectTypeOf } from 'expect-type'; 2 | 3 | import { ComponentProps } from 'solid-js'; 4 | import { describe, test } from 'vitest'; 5 | import type { SignUp } from '..'; 6 | 7 | export type SignUpComponentProps = ComponentProps; 8 | 9 | describe('', () => { 10 | describe('Type tests', () => { 11 | test('has path filled', () => { 12 | expectTypeOf({ path: '/sign-up' }).toMatchTypeOf(); 13 | }); 14 | 15 | test('has path filled and routing has path as a value', () => { 16 | expectTypeOf({ 17 | path: '/sign-up', 18 | routing: 'path' as const 19 | }).toMatchTypeOf(); 20 | }); 21 | 22 | test('when path is filled, routing must only have path as value', () => { 23 | expectTypeOf({ 24 | path: '/sign-up', 25 | routing: 'virtual' as const 26 | }).not.toMatchTypeOf(); 27 | 28 | expectTypeOf({ 29 | path: '/sign-up', 30 | routing: 'hash' as const 31 | }).not.toMatchTypeOf(); 32 | }); 33 | 34 | test('when routing is hash or virtual path must be present', () => { 35 | expectTypeOf({ 36 | routing: 'hash' as const 37 | }).toMatchTypeOf(); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/__tests__/user-profile.test.tsx: -------------------------------------------------------------------------------- 1 | import { expectTypeOf } from 'expect-type'; 2 | import { ComponentProps } from 'solid-js'; 3 | import { describe, test } from 'vitest'; 4 | import type { UserProfile } from '..'; 5 | 6 | export type UserProfileComponentProps = ComponentProps; 7 | 8 | describe('', () => { 9 | describe('Type tests', () => { 10 | test('has path filled', () => { 11 | expectTypeOf({ 12 | path: '/profile' 13 | }).toMatchTypeOf(); 14 | }); 15 | 16 | test('has path filled and routing has path as a value', () => { 17 | expectTypeOf({ 18 | path: '/profile', 19 | routing: 'path' as const 20 | }).toMatchTypeOf(); 21 | }); 22 | 23 | test('when path is filled, routing must only have path as value', () => { 24 | expectTypeOf({ 25 | path: '/profile', 26 | routing: 'virtual' as const 27 | }).not.toMatchTypeOf(); 28 | 29 | expectTypeOf({ 30 | path: '/profile', 31 | routing: 'hash' as const 32 | }).not.toMatchTypeOf(); 33 | }); 34 | 35 | test('when routing is hash or virtual path must be present', () => { 36 | expectTypeOf({ 37 | routing: 'hash' as const 38 | }).toMatchTypeOf(); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/server/constants.ts: -------------------------------------------------------------------------------- 1 | import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey'; 2 | 3 | import { getEnvVariable, getPublicEnvVariables } from '../utils/env'; 4 | 5 | export const CLERK_JS_VERSION = getPublicEnvVariables().clerkJsVersion || ''; 6 | export const CLERK_JS_URL = getPublicEnvVariables().clerkJsUrl || ''; 7 | export const API_VERSION = getEnvVariable('CLERK_API_VERSION') || 'v1'; 8 | export const SECRET_KEY = getEnvVariable('CLERK_SECRET_KEY') || ''; 9 | export const PUBLISHABLE_KEY = getPublicEnvVariables().publishableKey || ''; 10 | export const ENCRYPTION_KEY = getEnvVariable('CLERK_ENCRYPTION_KEY') || ''; 11 | export const API_URL = 12 | getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(PUBLISHABLE_KEY); 13 | export const DOMAIN = getPublicEnvVariables().domain || ''; 14 | export const PROXY_URL = getPublicEnvVariables().proxyUrl || ''; 15 | export const CLERK_JWT_KEY = getEnvVariable('CLERK_JWT_KEY') || ''; 16 | export const IS_SATELLITE = getPublicEnvVariables().isSatellite || false; 17 | export const SIGN_IN_URL = getPublicEnvVariables().signInUrl || ''; 18 | export const SIGN_UP_URL = getPublicEnvVariables().signUpUrl || ''; 19 | export const SDK_METADATA = { 20 | name: PACKAGE_NAME, 21 | version: PACKAGE_VERSION, 22 | environment: getEnvVariable('NODE_ENV') 23 | }; 24 | 25 | export const TELEMETRY_DISABLED = getPublicEnvVariables().telemetryDisabled; 26 | export const TELEMETRY_DEBUG = getPublicEnvVariables().telemetryDebug; 27 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/sign-in-with-metamask-button.tsx: -------------------------------------------------------------------------------- 1 | import { children as childrenFn, createMemo, splitProps } from 'solid-js'; 2 | import type { SignInWithMetamaskButtonProps, WithClerkProp } from '../types'; 3 | import { 4 | assertSingleChild, 5 | normalizeWithDefaultValue, 6 | safeExecute 7 | } from '../utils'; 8 | import { withClerk } from './with-clerk'; 9 | 10 | export const SignInWithMetamaskButton = withClerk( 11 | (props: WithClerkProp) => { 12 | const [local, clerkProps, rest] = splitProps( 13 | props, 14 | ['children'], 15 | ['clerk', 'redirectUrl', 'onClick'] 16 | ); 17 | 18 | const children = childrenFn(() => 19 | normalizeWithDefaultValue(local.children, 'Sign in with Metamask') 20 | ); 21 | const child = createMemo(() => 22 | assertSingleChild(children())('SignInWithMetamaskButton') 23 | ); 24 | 25 | const clickHandler = async () => { 26 | const { onClick, clerk, ...opts } = clerkProps; 27 | if (onClick) { 28 | await safeExecute(onClick)(); 29 | } 30 | async function authenticate() { 31 | await clerk().authenticateWithMetamask({ 32 | ...opts, 33 | redirectUrl: opts.redirectUrl || undefined 34 | }); 35 | } 36 | void authenticate(); 37 | }; 38 | 39 | return ( 40 | 43 | ); 44 | }, 45 | 'SignInWithMetamask' 46 | ); 47 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/__tests__/organization-profile.test.tsx: -------------------------------------------------------------------------------- 1 | import { expectTypeOf } from 'expect-type'; 2 | import { ComponentProps } from 'solid-js'; 3 | import { describe, test } from 'vitest'; 4 | import type { OrganizationProfile } from '..'; 5 | 6 | export type OrganizationProfileComponentProps = ComponentProps< 7 | typeof OrganizationProfile 8 | >; 9 | 10 | describe('', () => { 11 | describe('Type tests', () => { 12 | test('has path filled', () => { 13 | expectTypeOf({ 14 | path: '/org' 15 | }).toMatchTypeOf(); 16 | }); 17 | 18 | test('has path filled and routing has path as a value', () => { 19 | expectTypeOf({ 20 | path: '/org', 21 | routing: 'path' as const 22 | }).toMatchTypeOf(); 23 | }); 24 | 25 | test('when path is filled, routing must only have path as value', () => { 26 | expectTypeOf({ 27 | path: '/org', 28 | routing: 'virtual' as const 29 | }).not.toMatchTypeOf(); 30 | 31 | expectTypeOf({ 32 | path: '/org', 33 | routing: 'hash' as const 34 | }).not.toMatchTypeOf(); 35 | }); 36 | 37 | test('when routing is hash or virtual path must be present', () => { 38 | expectTypeOf({ 39 | routing: 'hash' as const 40 | }).toMatchTypeOf(); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/sign-up-button.tsx: -------------------------------------------------------------------------------- 1 | import { children as childrenFn, createMemo, splitProps } from 'solid-js'; 2 | import type { SignUpButtonProps, WithClerkProp } from '../types'; 3 | import { 4 | assertSingleChild, 5 | normalizeWithDefaultValue, 6 | safeExecute 7 | } from '../utils'; 8 | import { withClerk } from './with-clerk'; 9 | 10 | export const SignUpButton = withClerk( 11 | (props: WithClerkProp) => { 12 | const [local, clerkProps, rest] = splitProps( 13 | props, 14 | ['children'], 15 | ['clerk', 'mode', 'fallbackRedirectUrl', 'forceRedirectUrl', 'onClick'] 16 | ); 17 | 18 | const children = childrenFn(() => 19 | normalizeWithDefaultValue(local.children, 'Sign up') 20 | ); 21 | const child = createMemo(() => 22 | assertSingleChild(children())('SignUpButton') 23 | ); 24 | 25 | const clickHandler = async () => { 26 | const { onClick, mode, clerk, ...opts } = clerkProps; 27 | 28 | if (onClick) { 29 | await safeExecute(clerkProps.onClick)(); 30 | } 31 | 32 | if (mode === 'modal') { 33 | return clerk().openSignUp(opts); 34 | } 35 | 36 | return clerk().redirectToSignUp({ 37 | ...opts, 38 | signUpFallbackRedirectUrl: clerkProps.fallbackRedirectUrl, 39 | signUpForceRedirectUrl: clerkProps.forceRedirectUrl 40 | }); 41 | }; 42 | 43 | return ( 44 | 47 | ); 48 | }, 49 | 'SignUpButton' 50 | ); 51 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/with-clerk.tsx: -------------------------------------------------------------------------------- 1 | import type { LoadedClerk, Without } from '@clerk/types'; 2 | import { Accessor, Component, createEffect, JSX, Show } from 'solid-js'; 3 | import { useIsomorphicClerkContext } from '../contexts/isomorphic-clerk'; 4 | import { errorThrower } from '../errors/error-thrower'; 5 | import { hocChildrenNotAFunctionError } from '../errors/messages'; 6 | import { useAssertWrappedByClerkProvider } from '../hooks/use-assert-wrapped-by-clerk-provider'; 7 | 8 | export const withClerk =

}>( 9 | Component: Component

, 10 | displayName?: string 11 | ) => { 12 | displayName = displayName || Component.name || 'Component'; 13 | return (props: Without) => { 14 | useAssertWrappedByClerkProvider(displayName || 'withClerk'); 15 | const clerk = useIsomorphicClerkContext(); 16 | const isLoaded = () => clerk().loaded; 17 | 18 | return ( 19 | 20 | 21 | 22 | ); 23 | }; 24 | }; 25 | 26 | export const WithClerk: Component<{ 27 | children: (clerk: Accessor) => JSX.Element; 28 | }> = (props) => { 29 | const clerk = useIsomorphicClerkContext(); 30 | const isLoaded = () => clerk().loaded; 31 | createEffect(() => { 32 | if (typeof props.children !== 'function') { 33 | errorThrower.throw(hocChildrenNotAFunctionError); 34 | } 35 | }); 36 | return ( 37 | 38 | {props.children(clerk as unknown as Accessor)} 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/contexts/clerk.tsx: -------------------------------------------------------------------------------- 1 | import { isPublishableKey } from '@clerk/shared/keys'; 2 | import { createEffect, JSX, splitProps } from 'solid-js'; 3 | import { errorThrower } from '../errors/error-thrower'; 4 | import { multipleClerkProvidersError } from '../errors/messages'; 5 | import type { ClerkProviderProps } from '../types'; 6 | import { withMaxAllowedInstancesGuard } from '../utils'; 7 | import { ClerkContextProvider } from './clerk-context'; 8 | 9 | function ClerkProviderBase(props: ClerkProviderProps): JSX.Element { 10 | const [local, restIsomorphicClerkOptions] = splitProps(props, [ 11 | 'initialState', 12 | 'children' 13 | ]); 14 | 15 | createEffect(() => { 16 | if (!restIsomorphicClerkOptions.Clerk) { 17 | if (!restIsomorphicClerkOptions.publishableKey) { 18 | errorThrower.throwMissingPublishableKeyError(); 19 | } else if ( 20 | restIsomorphicClerkOptions.publishableKey && 21 | !isPublishableKey(restIsomorphicClerkOptions.publishableKey) 22 | ) { 23 | errorThrower.throwInvalidPublishableKeyError({ 24 | key: restIsomorphicClerkOptions.publishableKey 25 | }); 26 | } 27 | } 28 | }); 29 | 30 | return ( 31 | 35 | {local.children} 36 | 37 | ); 38 | } 39 | 40 | const ClerkProvider = withMaxAllowedInstancesGuard( 41 | ClerkProviderBase, 42 | 'ClerkProvider', 43 | multipleClerkProvidersError 44 | ); 45 | 46 | export { ClerkProvider }; 47 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/sign-in-button.tsx: -------------------------------------------------------------------------------- 1 | import { children as childrenFn, createMemo, splitProps } from 'solid-js'; 2 | import type { SignInButtonProps, WithClerkProp } from '../types'; 3 | import { 4 | assertSingleChild, 5 | normalizeWithDefaultValue, 6 | safeExecute 7 | } from '../utils'; 8 | import { withClerk } from './with-clerk'; 9 | 10 | export const SignInButton = withClerk( 11 | (props: WithClerkProp) => { 12 | const [local, clerkProps, rest] = splitProps( 13 | props, 14 | ['children'], 15 | [ 16 | 'clerk', 17 | 'signUpFallbackRedirectUrl', 18 | 'signUpForceRedirectUrl', 19 | 'forceRedirectUrl', 20 | 'fallbackRedirectUrl', 21 | 'mode', 22 | 'onClick' 23 | ] 24 | ); 25 | const children = childrenFn(() => 26 | normalizeWithDefaultValue(local.children, 'Sign in') 27 | ); 28 | const child = createMemo(() => 29 | assertSingleChild(children())('SignInButton') 30 | ); 31 | 32 | const clickHandler = async () => { 33 | const { onClick, mode, clerk, ...opts } = clerkProps; 34 | if (onClick) { 35 | await safeExecute(onClick)(); 36 | } 37 | 38 | if (mode === 'modal') { 39 | return clerk().openSignIn(opts); 40 | } 41 | return clerk().redirectToSignIn({ 42 | ...opts, 43 | signInFallbackRedirectUrl: clerkProps.fallbackRedirectUrl, 44 | signInForceRedirectUrl: clerkProps.forceRedirectUrl 45 | }); 46 | }; 47 | 48 | return ( 49 | 52 | ); 53 | }, 54 | 'SignInButton' 55 | ); 56 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/server/authenticate-request.ts: -------------------------------------------------------------------------------- 1 | import { createClerkClient } from '@clerk/backend'; 2 | import type { 3 | AuthenticateRequestOptions, 4 | SignedInState, 5 | SignedOutState 6 | } from '@clerk/backend/internal'; 7 | import { AuthStatus } from '@clerk/backend/internal'; 8 | 9 | import { errorThrower } from '../../errors/error-thrower'; 10 | import { patchRequest } from './utils'; 11 | 12 | export async function authenticateRequest( 13 | request: Request, 14 | opts: AuthenticateRequestOptions 15 | ): Promise { 16 | const { audience, authorizedParties } = opts; 17 | 18 | const { 19 | apiUrl, 20 | secretKey, 21 | jwtKey, 22 | proxyUrl, 23 | isSatellite, 24 | domain, 25 | publishableKey 26 | } = opts; 27 | const { signInUrl, signUpUrl, afterSignInUrl, afterSignUpUrl } = opts; 28 | 29 | const requestState = await createClerkClient({ 30 | apiUrl, 31 | secretKey, 32 | jwtKey, 33 | proxyUrl, 34 | isSatellite, 35 | domain, 36 | publishableKey, 37 | userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}` 38 | }).authenticateRequest(patchRequest(request), { 39 | audience, 40 | authorizedParties, 41 | signInUrl, 42 | signUpUrl, 43 | afterSignInUrl, 44 | afterSignUpUrl 45 | }); 46 | 47 | const hasLocationHeader = requestState.headers.get('location'); 48 | if (hasLocationHeader) { 49 | // triggering a handshake redirect 50 | throw new Response(null, { status: 307, headers: requestState.headers }); 51 | } 52 | 53 | if (requestState.status === AuthStatus.Handshake) { 54 | throw errorThrower.throw('Clerk: unexpected handshake without redirect'); 55 | } 56 | 57 | return requestState; 58 | } 59 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/client/clerk-provider.tsx: -------------------------------------------------------------------------------- 1 | import { destructure } from '@solid-primitives/destructure'; 2 | import { createMemo, JSX, onMount, splitProps } from 'solid-js'; 3 | import { getRequestEvent, isServer } from 'solid-js/web'; 4 | import { ClerkProvider as SolidClerkProvider } from '../../contexts/clerk'; 5 | import type { SolidStartClerkProviderProps } from './types'; 6 | import { useAwaitableNavigate } from './use-awaitable-navigate'; 7 | import { mergeWithPublicEnvs, pickFromClerkInitState } from './utils'; 8 | 9 | export function ClerkProvider( 10 | props: SolidStartClerkProviderProps 11 | ): JSX.Element { 12 | const [local, providerProps] = splitProps(props, ['children']); 13 | const awaitableNavigate = useAwaitableNavigate(); 14 | 15 | const clerkInitState = () => 16 | isServer 17 | ? getRequestEvent()?.locals.clerkInitialState 18 | : (window as any).__clerk_init_state; 19 | 20 | const states = createMemo(() => 21 | pickFromClerkInitState(clerkInitState()?.__internal_clerk_state) 22 | ); 23 | const { clerkSsrState } = destructure(states); 24 | 25 | const mergedProps = () => ({ 26 | ...mergeWithPublicEnvs(states()), 27 | ...providerProps 28 | }); 29 | 30 | onMount(() => { 31 | (window as any).__clerk_init_state = clerkInitState(); 32 | }); 33 | 34 | return ( 35 | 38 | awaitableNavigate(to, { 39 | replace: false 40 | }) 41 | } 42 | routerReplace={(to: string) => 43 | awaitableNavigate(to, { 44 | replace: true 45 | }) 46 | } 47 | {...mergedProps()} 48 | > 49 | {local.children} 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/__tests__/organization-switcher.test.tsx: -------------------------------------------------------------------------------- 1 | import { expectTypeOf } from 'expect-type'; 2 | import { ComponentProps } from 'solid-js'; 3 | import { describe, test } from 'vitest'; 4 | import type { OrganizationSwitcher } from '..'; 5 | 6 | export type OrganizationSwitcherComponentProps = ComponentProps< 7 | typeof OrganizationSwitcher 8 | >; 9 | 10 | describe('', () => { 11 | describe('Type tests', () => { 12 | test('createOrganizationUrl is a string', () => { 13 | expectTypeOf({ 14 | createOrganizationUrl: '/' 15 | }).toMatchTypeOf(); 16 | }); 17 | 18 | test('createOrganizationUrl is a string and createOrganizationMode is navigation', () => { 19 | expectTypeOf({ 20 | createOrganizationUrl: '/', 21 | createOrganizationMode: 'navigation' as const 22 | }).toMatchTypeOf(); 23 | }); 24 | 25 | test('createOrganizationUrl is a string and createOrganizationMode is not modal', () => { 26 | expectTypeOf({ 27 | createOrganizationUrl: '/', 28 | createOrganizationMode: 'modal' as const 29 | }).not.toMatchTypeOf(); 30 | }); 31 | 32 | test('createOrganizationMode is modal and path must not been present', () => { 33 | expectTypeOf({ 34 | createOrganizationMode: 'modal' as const 35 | }).toMatchTypeOf(); 36 | }); 37 | 38 | test('createOrganizationMode is navigation and path is not present', () => { 39 | expectTypeOf({ 40 | createOrganizationMode: 'navigation' as const 41 | }).not.toMatchTypeOf(); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/__tests__/sign-out-button.test.tsx: -------------------------------------------------------------------------------- 1 | import { cleanup, render, screen, waitFor } from '@solidjs/testing-library'; 2 | import userEvent from '@testing-library/user-event'; 3 | 4 | import { 5 | afterAll, 6 | beforeAll, 7 | beforeEach, 8 | describe, 9 | expect, 10 | it, 11 | vi 12 | } from 'vitest'; 13 | import { SignOutButton } from '../sign-out-button'; 14 | 15 | const mockSignOut = vi.fn(); 16 | const originalError = console.error; 17 | 18 | const mockClerk = { 19 | signOut: mockSignOut 20 | } as any; 21 | 22 | vi.mock('../with-clerk', () => { 23 | return { 24 | withClerk: (Component: any) => (props: any) => { 25 | return mockClerk} />; 26 | } 27 | }; 28 | }); 29 | 30 | describe('', () => { 31 | beforeAll(() => { 32 | console.error = vi.fn(); 33 | }); 34 | 35 | afterAll(() => { 36 | console.error = originalError; 37 | }); 38 | 39 | beforeEach(() => { 40 | cleanup(); 41 | mockSignOut.mockReset(); 42 | }); 43 | 44 | it('calls clerk.signOutOne when clicked', async () => { 45 | render(() => ); 46 | const btn = screen.getByText('Sign out'); 47 | userEvent.click(btn); 48 | await waitFor(() => { 49 | expect(mockSignOut).toHaveBeenCalled(); 50 | }); 51 | }); 52 | 53 | it('uses text passed as children', async () => { 54 | render(() => text); 55 | screen.getByText('text'); 56 | }); 57 | 58 | it('throws if multiple children provided', async () => { 59 | expect(() => { 60 | render(() => ( 61 | 62 | 63 | 64 | 65 | )); 66 | }).toThrow(); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/__tests__/user-button.test.tsx: -------------------------------------------------------------------------------- 1 | import { expectTypeOf } from 'expect-type'; 2 | import { ComponentProps } from 'solid-js'; 3 | import { describe, test } from 'vitest'; 4 | import type { UserButton } from '..'; 5 | 6 | export type UserButtonComponentProps = ComponentProps; 7 | 8 | describe('', () => { 9 | describe('Type tests', () => { 10 | test('userProfileUrl is a string', () => { 11 | expectTypeOf({ 12 | userProfileUrl: '/' 13 | }).toMatchTypeOf(); 14 | expectTypeOf<{ 15 | userProfileUrl: string; 16 | }>().toMatchTypeOf(); 17 | }); 18 | 19 | test('userProfileUrl url is a string and userProfileMode is navigation', () => { 20 | expectTypeOf({ 21 | userProfileUrl: '/', 22 | userProfileMode: 'navigation' as const 23 | }).toMatchTypeOf(); 24 | expectTypeOf<{ 25 | userProfileUrl: string; 26 | }>().toMatchTypeOf(); 27 | }); 28 | 29 | test('that when userProfileMode is navigation that userProfileUrl is filled', () => { 30 | expectTypeOf({ 31 | userProfileMode: 'navigation' as const 32 | }).not.toMatchTypeOf(); 33 | }); 34 | 35 | test('that when userProfileMode is modal that userProfileUrl is not filled', () => { 36 | expectTypeOf({ 37 | userProfileMode: 'modal' as const, 38 | userProfileUrl: '/' 39 | }).not.toMatchTypeOf(); 40 | }); 41 | 42 | test('userProfileMode is modal', () => { 43 | expectTypeOf({ 44 | userProfileMode: 'modal' as const 45 | }).toMatchTypeOf(); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/__tests__/sign-in-with-metamask-button.test.tsx: -------------------------------------------------------------------------------- 1 | import { cleanup, render, screen, waitFor } from '@solidjs/testing-library'; 2 | import userEvent from '@testing-library/user-event'; 3 | import { 4 | afterAll, 5 | beforeAll, 6 | beforeEach, 7 | describe, 8 | expect, 9 | it, 10 | vi 11 | } from 'vitest'; 12 | import { SignInWithMetamaskButton } from '../sign-in-with-metamask-button'; 13 | 14 | const mockAuthenticatewithMetamask = vi.fn(); 15 | const originalError = console.error; 16 | 17 | const mockClerk = { 18 | authenticateWithMetamask: mockAuthenticatewithMetamask 19 | } as any; 20 | 21 | vi.mock('../with-clerk', () => { 22 | return { 23 | withClerk: (Component: any) => (props: any) => { 24 | return mockClerk} />; 25 | } 26 | }; 27 | }); 28 | 29 | describe('', () => { 30 | beforeAll(() => { 31 | console.error = vi.fn(); 32 | }); 33 | 34 | afterAll(() => { 35 | console.error = originalError; 36 | }); 37 | 38 | beforeEach(() => { 39 | cleanup(); 40 | mockAuthenticatewithMetamask.mockReset(); 41 | }); 42 | 43 | it('calls clerk.authenticateWithMetamask when clicked', async () => { 44 | render(() => ); 45 | const btn = screen.getByText('Sign in with Metamask'); 46 | userEvent.click(btn); 47 | await waitFor(() => { 48 | expect(mockAuthenticatewithMetamask).toHaveBeenCalled(); 49 | }); 50 | }); 51 | 52 | it('uses text passed as children', () => { 53 | render(() => text); 54 | screen.getByText('text'); 55 | }); 56 | 57 | it('throws if multiple children provided', () => { 58 | expect(() => { 59 | render(() => ( 60 | 61 | 62 | 63 | 64 | )); 65 | }).toThrow(); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-auth.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | CheckAuthorizationWithCustomPermissions, 3 | GetToken, 4 | SignOut 5 | } from '@clerk/types'; 6 | import { createMemo } from 'solid-js'; 7 | import { useAuthContext } from '../contexts/auth'; 8 | import { useIsomorphicClerkContext } from '../contexts/isomorphic-clerk'; 9 | import { errorThrower } from '../errors/error-thrower'; 10 | import { useAuthHasRequiresRoleOrPermission } from '../errors/messages'; 11 | import { useAssertWrappedByClerkProvider } from './use-assert-wrapped-by-clerk-provider'; 12 | import { createGetToken, createSignOut } from './utils'; 13 | 14 | export const useAuth = () => { 15 | useAssertWrappedByClerkProvider('useAuth'); 16 | 17 | const { sessionId, userId, actor, orgId, orgRole, orgPermissions } = 18 | useAuthContext(); 19 | const isomorphicClerk = useIsomorphicClerkContext(); 20 | 21 | const getToken: GetToken = createGetToken(isomorphicClerk); 22 | const signOut: SignOut = createSignOut(isomorphicClerk); 23 | 24 | const has = ( 25 | params: Parameters[0] 26 | ) => { 27 | if (!params?.permission && !params?.role) { 28 | errorThrower.throw(useAuthHasRequiresRoleOrPermission); 29 | } 30 | 31 | if (!orgId() || !userId() || !orgRole() || !orgPermissions()) { 32 | return false; 33 | } 34 | 35 | if (params.permission) { 36 | return orgPermissions()!.includes(params.permission); 37 | } 38 | 39 | if (params.role) { 40 | return orgRole() === params.role; 41 | } 42 | 43 | return false; 44 | }; 45 | 46 | const isLoaded = createMemo( 47 | () => sessionId() !== undefined && userId() !== undefined 48 | ); 49 | 50 | const isSignedIn = createMemo(() => { 51 | if (!isLoaded()) { 52 | return undefined; 53 | } 54 | return sessionId() !== null && userId() !== null; 55 | }); 56 | 57 | return { 58 | isLoaded, 59 | isSignedIn, 60 | sessionId, 61 | userId, 62 | actor, 63 | orgId, 64 | orgRole, 65 | orgPermissions, 66 | has, 67 | signOut, 68 | getToken 69 | }; 70 | }; 71 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/server/middleware.ts: -------------------------------------------------------------------------------- 1 | import { stripPrivateDataFromObject } from '@clerk/backend/internal'; 2 | import type { RequestMiddleware } from '@solidjs/start/middleware'; 3 | import { authenticateRequest } from './authenticate-request'; 4 | import { loadOptions } from './load-options'; 5 | import { LoaderOptions } from './types'; 6 | import { getResponseClerkState } from './utils'; 7 | 8 | /** 9 | * Returns a middleware function that authenticates a request using Clerk. 10 | * This injects the auth object into Solid Start's RequestEventLocals. 11 | * 12 | * After implementing this middleware you can access the auth object from `RequestEvent.locals`, 13 | * or using the `auth()` helper function. 14 | * 15 | * You must define Solid Start middleware. See {@link https://docs.solidjs.com/solid-start/advanced/middleware} 16 | * 17 | * @param {LoaderOptions} options - The options for creating a Clerk client. 18 | * @return {RequestMiddleware} A middleware function that authenticates a request using Clerk. 19 | * @throws {Error} Throws an error if there is an unexpected handshake without a redirect. 20 | * 21 | * @example 22 | * ```ts 23 | * import { createMiddleware } from "@solidjs/start/middleware"; 24 | * import { clerkMiddleware } from 'clerk-solidjs/start/server'; 25 | * 26 | * export default createMiddleware({ 27 | * onRequest: [ 28 | * clerkMiddleware({ 29 | * publishableKey: import.meta.env.VITE_CLERK_PUBLISHABLE_KEY, 30 | * secretKey: import.meta.env.CLERK_SECRET_KEY, 31 | * }), 32 | * ], 33 | * }); 34 | * ``` 35 | */ 36 | export const clerkMiddleware = ( 37 | options: LoaderOptions = {} 38 | ): RequestMiddleware => { 39 | return async ({ request, locals }) => { 40 | try { 41 | const loadedOptions = loadOptions(request, options); 42 | const requestState = await authenticateRequest(request, loadedOptions); 43 | const state = getResponseClerkState(requestState, loadedOptions); 44 | locals.auth = stripPrivateDataFromObject(requestState.toAuth()); 45 | locals.clerkInitialState = state.clerkInitialState; 46 | } catch (error) { 47 | if (error instanceof Response) { 48 | return error; 49 | } 50 | throw error; 51 | } 52 | }; 53 | }; 54 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import clerkJsPackage from '@clerk/clerk-js/package.json' with { type: 'json' }; 2 | import { defineConfig } from 'tsup'; 3 | import * as preset from 'tsup-preset-solid'; 4 | import thisPackage from './package.json' with { type: 'json' }; 5 | 6 | const CI = 7 | process.env['CI'] === 'true' || 8 | process.env['GITHUB_ACTIONS'] === 'true' || 9 | process.env['CI'] === '"1"' || 10 | process.env['GITHUB_ACTIONS'] === '"1"'; 11 | 12 | export default defineConfig((config) => { 13 | const watching = !!config.watch; 14 | const shouldPublish = !!config.env?.publish; 15 | 16 | const parsed = preset.parsePresetOptions( 17 | { 18 | entries: [ 19 | { 20 | entry: 'src/index.tsx', 21 | server_entry: true 22 | }, 23 | { 24 | name: 'errors', 25 | entry: 'src/errors.ts' 26 | }, 27 | { 28 | name: 'server', 29 | entry: 'src/server/index.ts' 30 | }, 31 | { 32 | name: 'start', 33 | entry: 'src/start/index.tsx', 34 | server_entry: true 35 | }, 36 | { 37 | name: 'start/server', 38 | entry: 'src/start/server/index.ts' 39 | } 40 | ], 41 | cjs: true, 42 | drop_console: !watching, 43 | modify_esbuild_options: (options) => { 44 | options.bundle = true; 45 | options.treeShaking = true; 46 | options.external = [ 47 | ...(options.external ?? []), 48 | ...Object.keys(thisPackage.peerDependencies) 49 | ]; 50 | options.define = { 51 | ...options.define, 52 | PACKAGE_NAME: `"${thisPackage.name}"`, 53 | PACKAGE_VERSION: `"${thisPackage.version}"`, 54 | JS_PACKAGE_VERSION: `"${clerkJsPackage.version}"`, 55 | __DEV__: `${watching}` 56 | }; 57 | 58 | return options; 59 | }, 60 | out_dir: 'dist' 61 | }, 62 | watching 63 | ); 64 | 65 | if (!watching && !CI) { 66 | const packageFields = preset.generatePackageExports(parsed); 67 | preset.writePackageJson(packageFields); 68 | } 69 | 70 | const options = preset.generateTsupOptions(parsed); 71 | if (shouldPublish) { 72 | options[options.length - 1].onSuccess = ' && pnpm run publish:local'; 73 | } 74 | 75 | return options; 76 | }); 77 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/__tests__/isomorphic-clerk.test.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, beforeAll, describe, expect, it, vitest } from 'vitest'; 2 | import { IsomorphicClerk } from '../isomorphic-clerk'; 3 | 4 | describe('isomorphicClerk', () => { 5 | beforeAll(() => { 6 | vitest.useFakeTimers(); 7 | }); 8 | 9 | afterAll(() => { 10 | vitest.useRealTimers(); 11 | }); 12 | 13 | it('instantiates a IsomorphicClerk instance', () => { 14 | expect(() => { 15 | new IsomorphicClerk({ publishableKey: 'pk_test_XXX' }); 16 | }).not.toThrow(); 17 | }); 18 | 19 | it('updates props asynchronously after clerkjs has loaded', async () => { 20 | const propsHistory: any[] = []; 21 | const dummyClerkJS = { 22 | __unstable__updateProps: (props: any) => propsHistory.push(props) 23 | }; 24 | 25 | const isomorphicClerk = new IsomorphicClerk({ 26 | publishableKey: 'pk_test_XXX' 27 | }); 28 | (isomorphicClerk as any).clerkjs = dummyClerkJS as any; 29 | 30 | void isomorphicClerk.__unstable__updateProps({ 31 | appearance: { baseTheme: 'dark' } 32 | }); 33 | void isomorphicClerk.__unstable__updateProps({ 34 | appearance: { baseTheme: 'light' } 35 | }); 36 | void isomorphicClerk.__unstable__updateProps({ 37 | appearance: { baseTheme: 'purple' } 38 | }); 39 | void isomorphicClerk.__unstable__updateProps({ 40 | appearance: { baseTheme: 'yellow' } 41 | }); 42 | void isomorphicClerk.__unstable__updateProps({ 43 | appearance: { baseTheme: 'red' } 44 | }); 45 | void isomorphicClerk.__unstable__updateProps({ 46 | appearance: { baseTheme: 'blue' } 47 | }); 48 | void isomorphicClerk.__unstable__updateProps({ 49 | appearance: { baseTheme: 'green' } 50 | }); 51 | expect(propsHistory).toEqual([]); 52 | 53 | vitest.spyOn(isomorphicClerk, 'loaded', 'get').mockReturnValue(true); 54 | isomorphicClerk.emitLoaded(); 55 | void isomorphicClerk.__unstable__updateProps({ 56 | appearance: { baseTheme: 'white' } 57 | }); 58 | await vitest.runAllTimersAsync(); 59 | 60 | expect(propsHistory).toEqual([ 61 | { appearance: { baseTheme: 'dark' } }, 62 | { appearance: { baseTheme: 'light' } }, 63 | { appearance: { baseTheme: 'purple' } }, 64 | { appearance: { baseTheme: 'yellow' } }, 65 | { appearance: { baseTheme: 'red' } }, 66 | { appearance: { baseTheme: 'blue' } }, 67 | { appearance: { baseTheme: 'green' } }, 68 | { appearance: { baseTheme: 'white' } } 69 | ]); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/client/utils.ts: -------------------------------------------------------------------------------- 1 | import { getPublicEnvVariables } from '../utils/env'; 2 | import { SolidStartClerkProviderProps } from './types'; 3 | 4 | type SolidStartProviderAndInitialProps = Omit< 5 | SolidStartClerkProviderProps, 6 | 'children' 7 | >; 8 | 9 | export const pickFromClerkInitState = ( 10 | clerkInitState: any 11 | ): SolidStartProviderAndInitialProps & { 12 | clerkSsrState: any; 13 | } => { 14 | const { 15 | __clerk_ssr_state, 16 | __publishableKey, 17 | __proxyUrl, 18 | __domain, 19 | __isSatellite, 20 | __signInUrl, 21 | __signUpUrl, 22 | __afterSignInUrl, 23 | __afterSignUpUrl, 24 | __clerkJSUrl, 25 | __clerkJSVersion, 26 | __telemetryDisabled, 27 | __telemetryDebug, 28 | __signInForceRedirectUrl, 29 | __signUpForceRedirectUrl, 30 | __signInFallbackRedirectUrl, 31 | __signUpFallbackRedirectUrl 32 | } = clerkInitState || {}; 33 | 34 | return { 35 | clerkSsrState: __clerk_ssr_state, 36 | publishableKey: __publishableKey, 37 | proxyUrl: __proxyUrl, 38 | domain: __domain, 39 | isSatellite: !!__isSatellite, 40 | signInUrl: __signInUrl, 41 | signUpUrl: __signUpUrl, 42 | afterSignInUrl: __afterSignInUrl, 43 | afterSignUpUrl: __afterSignUpUrl, 44 | clerkJSUrl: __clerkJSUrl, 45 | clerkJSVersion: __clerkJSVersion, 46 | telemetry: { 47 | disabled: __telemetryDisabled, 48 | debug: __telemetryDebug 49 | }, 50 | signInForceRedirectUrl: __signInForceRedirectUrl, 51 | signUpForceRedirectUrl: __signUpForceRedirectUrl, 52 | signInFallbackRedirectUrl: __signInFallbackRedirectUrl, 53 | signUpFallbackRedirectUrl: __signUpFallbackRedirectUrl 54 | }; 55 | }; 56 | 57 | export const mergeWithPublicEnvs = (restInitState: any) => { 58 | const publicEnvVariables = getPublicEnvVariables(); 59 | return { 60 | ...restInitState, 61 | publishableKey: 62 | restInitState.publishableKey || publicEnvVariables.publishableKey, 63 | domain: restInitState.domain || publicEnvVariables.domain, 64 | isSatellite: restInitState.isSatellite || publicEnvVariables.isSatellite, 65 | signInUrl: restInitState.signInUrl || publicEnvVariables.signInUrl, 66 | signUpUrl: restInitState.signUpUrl || publicEnvVariables.signUpUrl, 67 | afterSignInUrl: 68 | restInitState.afterSignInUrl || publicEnvVariables.afterSignInUrl, 69 | afterSignUpUrl: 70 | restInitState.afterSignUpUrl || publicEnvVariables.afterSignUpUrl, 71 | clerkJSUrl: restInitState.clerkJSUrl || publicEnvVariables.clerkJsUrl, 72 | clerkJSVersion: 73 | restInitState.clerkJSVersion || publicEnvVariables.clerkJsVersion, 74 | signInForceRedirectUrl: restInitState.signInForceRedirectUrl, 75 | clerkJSVariant: 76 | restInitState.clerkJSVariant || publicEnvVariables.clerkJsVariant 77 | }; 78 | }; 79 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/utils/env.ts: -------------------------------------------------------------------------------- 1 | import { isTruthy } from '@clerk/shared/underscore'; 2 | 3 | /** 4 | * 5 | * Utility function to get env variables. 6 | * 7 | * @param name env variable name 8 | * @param defaultVaue default value to return if the env variable is not set 9 | * @returns string 10 | * 11 | * @internal 12 | */ 13 | export const getEnvVariable = ( 14 | name: string, 15 | defaultVaue: string = '' 16 | ): string => { 17 | // Node envs 18 | if ( 19 | typeof process !== 'undefined' && 20 | process.env && 21 | typeof process.env[name] === 'string' 22 | ) { 23 | return (process.env[name] as string) || defaultVaue; 24 | } 25 | 26 | if ( 27 | typeof import.meta !== 'undefined' && 28 | // @ts-expect-error - Vite specific 29 | import.meta.env && 30 | // @ts-expect-error - Vite specific 31 | typeof import.meta.env[name] === 'string' 32 | ) { 33 | // @ts-expect-error - Vite specific 34 | return import.meta.env[name]; 35 | } 36 | 37 | return defaultVaue; 38 | }; 39 | 40 | export const getPublicEnvVariables = () => { 41 | return { 42 | publishableKey: 43 | getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || 44 | getEnvVariable('CLERK_PUBLISHABLE_KEY'), 45 | domain: 46 | getEnvVariable('VITE_CLERK_DOMAIN') || getEnvVariable('CLERK_DOMAIN'), 47 | isSatellite: 48 | isTruthy(getEnvVariable('VITE_CLERK_IS_SATELLITE')) || 49 | isTruthy(getEnvVariable('CLERK_IS_SATELLITE')), 50 | proxyUrl: 51 | getEnvVariable('VITE_CLERK_PROXY_URL') || 52 | getEnvVariable('CLERK_PROXY_URL'), 53 | pk: 54 | getEnvVariable('VITE_CLERK_PUBLISHABLE_KEY') || 55 | getEnvVariable('CLERK_PUBLISHABLE_KEY'), 56 | signInUrl: 57 | getEnvVariable('VITE_CLERK_SIGN_IN_URL') || 58 | getEnvVariable('CLERK_SIGN_IN_URL'), 59 | signUpUrl: 60 | getEnvVariable('VITE_CLERK_SIGN_UP_URL') || 61 | getEnvVariable('CLERK_SIGN_UP_URL'), 62 | clerkJsUrl: 63 | getEnvVariable('VITE_CLERK_JS_URL') || getEnvVariable('CLERK_JS'), 64 | clerkJsVariant: (getEnvVariable('VITE_CLERK_JS_VARIANT') || 65 | getEnvVariable('CLERK_JS_VARIANT')) as '' | 'headless' | undefined, 66 | clerkJsVersion: 67 | getEnvVariable('VITE_CLERK_JS_VERSION') || 68 | getEnvVariable('CLERK_JS_VERSION'), 69 | telemetryDisabled: 70 | isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DISABLED')) || 71 | isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')), 72 | telemetryDebug: 73 | isTruthy(getEnvVariable('VITE_CLERK_TELEMETRY_DEBUG')) || 74 | isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')), 75 | afterSignInUrl: 76 | getEnvVariable('VITE_CLERK_AFTER_SIGN_IN_URL') || 77 | getEnvVariable('CLERK_AFTER_SIGN_IN_URL'), 78 | afterSignUpUrl: 79 | getEnvVariable('VITE_CLERK_AFTER_SIGN_UP_URL') || 80 | getEnvVariable('CLERK_AFTER_SIGN_UP_URL') 81 | }; 82 | }; 83 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/server/load-options.ts: -------------------------------------------------------------------------------- 1 | import { createClerkRequest } from '@clerk/backend/internal'; 2 | import { 3 | apiUrlFromPublishableKey, 4 | handleValueOrFn, 5 | isDevelopmentFromSecretKey, 6 | isHttpOrHttps, 7 | isProxyUrlRelative 8 | } from '@clerk/shared'; 9 | 10 | import { errorThrower } from '../../errors/error-thrower'; 11 | import { getEnvVariable, getPublicEnvVariables } from '../utils/env'; 12 | import { 13 | CLERK_JWT_KEY, 14 | DOMAIN, 15 | IS_SATELLITE, 16 | PROXY_URL, 17 | PUBLISHABLE_KEY, 18 | SECRET_KEY, 19 | SIGN_IN_URL, 20 | SIGN_UP_URL 21 | } from './constants'; 22 | import { LoaderOptions } from './types'; 23 | import { patchRequest } from './utils'; 24 | 25 | export const loadOptions = ( 26 | request: Request, 27 | overrides: LoaderOptions = {} 28 | ) => { 29 | const clerkRequest = createClerkRequest(patchRequest(request)); 30 | 31 | const secretKey = overrides.secretKey || SECRET_KEY; 32 | const publishableKey = overrides.publishableKey || PUBLISHABLE_KEY; 33 | const jwtKey = overrides.jwtKey || CLERK_JWT_KEY; 34 | const apiUrl = 35 | getEnvVariable('CLERK_API_URL') || apiUrlFromPublishableKey(publishableKey); 36 | const domain = 37 | handleValueOrFn(overrides.domain, new URL(request.url)) || DOMAIN; 38 | const isSatellite = 39 | handleValueOrFn(overrides.isSatellite, new URL(request.url)) || 40 | IS_SATELLITE; 41 | const relativeOrAbsoluteProxyUrl = handleValueOrFn( 42 | overrides?.proxyUrl, 43 | clerkRequest.clerkUrl, 44 | PROXY_URL 45 | ); 46 | const signInUrl = overrides.signInUrl || SIGN_IN_URL; 47 | const signUpUrl = overrides.signUpUrl || SIGN_UP_URL; 48 | const afterSignInUrl = 49 | overrides.afterSignInUrl || getPublicEnvVariables().afterSignInUrl; 50 | const afterSignUpUrl = 51 | overrides.afterSignUpUrl || getPublicEnvVariables().afterSignUpUrl; 52 | 53 | let proxyUrl; 54 | if ( 55 | !!relativeOrAbsoluteProxyUrl && 56 | isProxyUrlRelative(relativeOrAbsoluteProxyUrl) 57 | ) { 58 | proxyUrl = new URL( 59 | relativeOrAbsoluteProxyUrl, 60 | clerkRequest.clerkUrl 61 | ).toString(); 62 | } else { 63 | proxyUrl = relativeOrAbsoluteProxyUrl; 64 | } 65 | 66 | if (!secretKey) { 67 | throw errorThrower.throw('Clerk: no secret key provided'); 68 | } 69 | 70 | if (isSatellite && !proxyUrl && !domain) { 71 | throw errorThrower.throw( 72 | 'Clerk: satellite mode requires a proxy URL or domain' 73 | ); 74 | } 75 | 76 | if ( 77 | isSatellite && 78 | !isHttpOrHttps(signInUrl) && 79 | isDevelopmentFromSecretKey(secretKey) 80 | ) { 81 | throw errorThrower.throw( 82 | 'Clerk: satellite mode requires a sign-in URL in production' 83 | ); 84 | } 85 | 86 | return { 87 | // used to append options that are not initialized from env 88 | ...overrides, 89 | secretKey, 90 | publishableKey, 91 | jwtKey, 92 | apiUrl, 93 | domain, 94 | isSatellite, 95 | proxyUrl, 96 | signInUrl, 97 | signUpUrl, 98 | afterSignInUrl, 99 | afterSignUpUrl 100 | }; 101 | }; 102 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/__tests__/sign-in-button.test.tsx: -------------------------------------------------------------------------------- 1 | import { cleanup, render, screen } from '@solidjs/testing-library'; 2 | import userEvent from '@testing-library/user-event'; 3 | import { 4 | afterAll, 5 | beforeAll, 6 | beforeEach, 7 | describe, 8 | expect, 9 | it, 10 | vi 11 | } from 'vitest'; 12 | import { SignInButton } from '../sign-in-button'; 13 | 14 | const mockRedirectToSignIn = vi.fn(); 15 | const originalError = console.error; 16 | 17 | const mockClerk = { 18 | redirectToSignIn: mockRedirectToSignIn 19 | } as any; 20 | 21 | vi.mock('../with-clerk', () => { 22 | return { 23 | withClerk: (Component: any) => (props: any) => { 24 | return mockClerk} />; 25 | } 26 | }; 27 | }); 28 | 29 | const url = 'https://www.clerk.com'; 30 | 31 | describe('', () => { 32 | beforeAll(() => { 33 | console.error = vi.fn(); 34 | }); 35 | 36 | afterAll(() => { 37 | console.error = originalError; 38 | }); 39 | 40 | beforeEach(() => { 41 | cleanup(); 42 | mockRedirectToSignIn.mockReset(); 43 | }); 44 | 45 | it('calls clerk.redirectToSignIn when clicked', async () => { 46 | render(() => ); 47 | const btn = screen.getByText('Sign in'); 48 | await userEvent.click(btn); 49 | expect(mockRedirectToSignIn).toHaveBeenCalled(); 50 | }); 51 | 52 | it('handles forceRedirectUrl prop', async () => { 53 | render(() => ); 54 | 55 | const btn = screen.getByText('Sign in'); 56 | await userEvent.click(btn); 57 | 58 | expect(mockRedirectToSignIn).toHaveBeenCalledWith({ 59 | forceRedirectUrl: url, 60 | signInForceRedirectUrl: url 61 | }); 62 | }); 63 | 64 | it('handles fallbackRedirectUrl prop', async () => { 65 | render(() => ); 66 | 67 | const btn = screen.getByText('Sign in'); 68 | await userEvent.click(btn); 69 | 70 | expect(mockRedirectToSignIn).toHaveBeenCalledWith({ 71 | fallbackRedirectUrl: url, 72 | signInFallbackRedirectUrl: url 73 | }); 74 | }); 75 | 76 | it('renders passed button and calls both click handlers', async () => { 77 | const handler = vi.fn(); 78 | 79 | render(() => ( 80 | 81 | 84 | 85 | )); 86 | 87 | const btn = screen.getByText('custom button'); 88 | await userEvent.click(btn); 89 | 90 | expect(handler).toHaveBeenCalled(); 91 | expect(mockRedirectToSignIn).toHaveBeenCalled(); 92 | }); 93 | 94 | it('uses text passed as children', async () => { 95 | render(() => text); 96 | screen.getByText('text'); 97 | }); 98 | 99 | it('throws if multiple children provided', () => { 100 | expect(() => { 101 | render(() => ( 102 | 103 | 104 | 105 | 106 | )); 107 | }).toThrow(); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | 177 | # TurboRepo 178 | .turbo -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/__tests__/sign-up-button.test.tsx: -------------------------------------------------------------------------------- 1 | import { cleanup, render, screen, waitFor } from '@solidjs/testing-library'; 2 | import userEvent from '@testing-library/user-event'; 3 | import { 4 | afterAll, 5 | beforeAll, 6 | beforeEach, 7 | describe, 8 | expect, 9 | it, 10 | vi 11 | } from 'vitest'; 12 | import { SignUpButton } from '../sign-up-button'; 13 | 14 | const mockRedirectToSignUp = vi.fn(); 15 | const originalError = console.error; 16 | 17 | const mockClerk = { 18 | redirectToSignUp: mockRedirectToSignUp 19 | } as any; 20 | 21 | vi.mock('../with-clerk', () => { 22 | return { 23 | withClerk: (Component: any) => (props: any) => { 24 | return mockClerk} />; 25 | } 26 | }; 27 | }); 28 | 29 | const url = 'https://www.clerk.com'; 30 | 31 | describe('', () => { 32 | beforeAll(() => { 33 | console.error = vi.fn(); 34 | }); 35 | 36 | afterAll(() => { 37 | console.error = originalError; 38 | }); 39 | 40 | beforeEach(() => { 41 | cleanup(); 42 | mockRedirectToSignUp.mockReset(); 43 | }); 44 | 45 | it('calls clerk.redirectToSignUp when clicked', async () => { 46 | render(() => ); 47 | const btn = screen.getByText('Sign up'); 48 | userEvent.click(btn); 49 | await waitFor(() => { 50 | expect(mockRedirectToSignUp).toHaveBeenCalled(); 51 | }); 52 | }); 53 | 54 | it('handles forceRedirectUrl prop', async () => { 55 | render(() => ); 56 | const btn = screen.getByText('Sign up'); 57 | userEvent.click(btn); 58 | await waitFor(() => { 59 | expect(mockRedirectToSignUp).toHaveBeenCalledWith({ 60 | forceRedirectUrl: url, 61 | signUpForceRedirectUrl: url 62 | }); 63 | }); 64 | }); 65 | 66 | it('handles fallbackRedirectUrl prop', async () => { 67 | render(() => ); 68 | const btn = screen.getByText('Sign up'); 69 | userEvent.click(btn); 70 | await waitFor(() => { 71 | expect(mockRedirectToSignUp).toHaveBeenCalledWith({ 72 | fallbackRedirectUrl: url, 73 | signUpFallbackRedirectUrl: url 74 | }); 75 | }); 76 | }); 77 | 78 | it('renders passed button and calls both click handlers', async () => { 79 | const handler = vi.fn(); 80 | render(() => ( 81 | 82 | 83 | 84 | )); 85 | const btn = screen.getByText('custom button'); 86 | userEvent.click(btn); 87 | await waitFor(() => { 88 | expect(handler).toHaveBeenCalled(); 89 | expect(mockRedirectToSignUp).toHaveBeenCalled(); 90 | }); 91 | }); 92 | 93 | it('uses text passed as children', async () => { 94 | render(() => text); 95 | screen.getByText('text'); 96 | }); 97 | 98 | it('throws if multiple children provided', async () => { 99 | expect(() => { 100 | render(() => ( 101 | 102 | 103 | 104 | 105 | )); 106 | }).toThrow(); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/start/server/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { debugRequestState, RequestState } from '@clerk/backend/internal'; 2 | import { isTruthy } from '@clerk/shared'; 3 | import { getEnvVariable } from '../../utils/env'; 4 | import { AdditionalStateOptions } from '../types'; 5 | 6 | /** 7 | * Wraps obscured clerk internals with a readable `clerkState` key. 8 | * This is intended to be passed into 9 | * 10 | * @internal 11 | */ 12 | export const wrapWithClerkState = (data: any) => { 13 | return { __internal_clerk_state: { ...data } }; 14 | }; 15 | 16 | /** 17 | * Returns the clerk state object and observability headers to be injected into a context. 18 | * 19 | * @internal 20 | */ 21 | export function getResponseClerkState( 22 | requestState: RequestState, 23 | additionalStateOptions: AdditionalStateOptions = {} 24 | ) { 25 | const clerkInitialState = wrapWithClerkState({ 26 | __clerk_ssr_state: requestState.toAuth(), 27 | __publishableKey: requestState.publishableKey, 28 | __proxyUrl: requestState.proxyUrl, 29 | __domain: requestState.domain, 30 | __isSatellite: requestState.isSatellite, 31 | __signInUrl: requestState.signInUrl, 32 | __signUpUrl: requestState.signUpUrl, 33 | __afterSignInUrl: requestState.afterSignInUrl, 34 | __afterSignUpUrl: requestState.afterSignUpUrl, 35 | __clerk_debug: debugRequestState(requestState), 36 | __clerkJSUrl: getEnvVariable('CLERK_JS'), 37 | __clerkJSVersion: getEnvVariable('CLERK_JS_VERSION'), 38 | __telemetryDisabled: isTruthy(getEnvVariable('CLERK_TELEMETRY_DISABLED')), 39 | __telemetryDebug: isTruthy(getEnvVariable('CLERK_TELEMETRY_DEBUG')), 40 | __signInForceRedirectUrl: 41 | additionalStateOptions.signInForceRedirectUrl || 42 | getEnvVariable('CLERK_SIGN_IN_FORCE_REDIRECT_URL') || 43 | '', 44 | __signUpForceRedirectUrl: 45 | additionalStateOptions.signUpForceRedirectUrl || 46 | getEnvVariable('CLERK_SIGN_UP_FORCE_REDIRECT_URL') || 47 | '', 48 | __signInFallbackRedirectUrl: 49 | additionalStateOptions.signInFallbackRedirectUrl || 50 | getEnvVariable('CLERK_SIGN_IN_FALLBACK_REDIRECT_URL') || 51 | '', 52 | __signUpFallbackRedirectUrl: 53 | additionalStateOptions.signUpFallbackRedirectUrl || 54 | getEnvVariable('CLERK_SIGN_UP_FALLBACK_REDIRECT_URL') || 55 | '' 56 | }); 57 | 58 | return { 59 | clerkInitialState, 60 | headers: requestState.headers 61 | }; 62 | } 63 | 64 | /** 65 | * Patches request to avoid duplex issues with unidici 66 | * For more information, see: 67 | * https://github.com/nodejs/node/issues/46221 68 | * https://github.com/whatwg/fetch/pull/1457 69 | * @internal 70 | */ 71 | export const patchRequest = (request: Request) => { 72 | const clonedRequest = new Request(request.url, { 73 | headers: request.headers, 74 | method: request.method, 75 | redirect: request.redirect, 76 | cache: request.cache, 77 | signal: request.signal 78 | }); 79 | 80 | // If duplex is not set, set it to 'half' to avoid duplex issues with unidici 81 | if ( 82 | clonedRequest.method !== 'GET' && 83 | clonedRequest.body !== null && 84 | !('duplex' in clonedRequest) 85 | ) { 86 | (clonedRequest as unknown as { duplex: 'half' }).duplex = 'half'; 87 | } 88 | 89 | return clonedRequest; 90 | }; 91 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/errors/messages.ts: -------------------------------------------------------------------------------- 1 | export const noClerkProviderError = 2 | 'You must wrap your application in a component.'; 3 | 4 | export const multipleClerkProvidersError = 5 | "You've added multiple components in your SolidJS component tree. Wrap your components in a single ."; 6 | 7 | export const hocChildrenNotAFunctionError = 8 | 'Child of WithClerk must be a function.'; 9 | 10 | export const multipleChildrenInButtonComponent = (name: string) => 11 | `You've passed multiple children components to <${name}/>. You can only pass a single child component or text.`; 12 | 13 | export const invalidStateError = 14 | 'Invalid state. Feel free to submit a bug or reach out to support here: https://clerk.com/support'; 15 | 16 | export const unsupportedNonBrowserDomainOrProxyUrlFunction = 17 | 'Unsupported usage of isSatellite, domain or proxyUrl. The usage of isSatellite, domain or proxyUrl as function is not supported in non-browser environments.'; 18 | 19 | export const userProfilePageRenderedError = 20 | ' component needs to be a direct child of `` or ``.'; 21 | export const userProfileLinkRenderedError = 22 | ' component needs to be a direct child of `` or ``.'; 23 | 24 | export const organizationProfilePageRenderedError = 25 | ' component needs to be a direct child of `` or ``.'; 26 | export const organizationProfileLinkRenderedError = 27 | ' component needs to be a direct child of `` or ``.'; 28 | 29 | export const customPagesIgnoredComponent = (componentName: string) => 30 | `<${componentName} /> can only accept <${componentName}.Page /> and <${componentName}.Link /> as its children. Any other provided component will be ignored.`; 31 | 32 | export const customPageWrongProps = (componentName: string) => 33 | `Missing props. <${componentName}.Page /> component requires the following props: url, label, labelIcon, alongside with children to be rendered inside the page.`; 34 | 35 | export const customLinkWrongProps = (componentName: string) => 36 | `Missing props. <${componentName}.Link /> component requires the following props: url, label and labelIcon.`; 37 | 38 | export const useAuthHasRequiresRoleOrPermission = 39 | 'Missing parameters. `has` from `useAuth` requires a permission or role key to be passed. Example usage: `has({permission: "org:posts:edit"`'; 40 | 41 | export const noPathProvidedError = (componentName: string) => 42 | `The <${componentName}/> component uses path-based routing by default unless a different routing strategy is provided using the \`routing\` prop. When path-based routing is used, you need to provide the path where the component is mounted on by using the \`path\` prop. Example: <${componentName} path={'/my-path'} />`; 43 | 44 | export const incompatibleRoutingWithPathProvidedError = ( 45 | componentName: string 46 | ) => 47 | `The \`path\` prop will only be respected when the Clerk component uses path-based routing. To resolve this error, pass \`routing='path'\` to the <${componentName}/> component, or drop the \`path\` prop to switch to hash-based routing. For more details please refer to our docs: https://clerk.com/docs`; 48 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/utils/load-clerk-js-script.ts: -------------------------------------------------------------------------------- 1 | import { parsePublishableKey } from '@clerk/shared/keys'; 2 | import { loadScript } from '@clerk/shared/loadScript'; 3 | import { isValidProxyUrl, proxyUrlToAbsoluteURL } from '@clerk/shared/proxy'; 4 | import { addClerkPrefix } from '@clerk/shared/url'; 5 | 6 | import { errorThrower } from '../errors/error-thrower'; 7 | import type { IsomorphicClerkOptions } from '../types'; 8 | import { isDevOrStagingUrl } from './is-dev-or-stage-url'; 9 | import { versionSelector } from './version-selector'; 10 | 11 | const FAILED_TO_LOAD_ERROR = 'Clerk: Failed to load Clerk'; 12 | 13 | type LoadClerkJsScriptOptions = Omit< 14 | IsomorphicClerkOptions, 15 | 'proxyUrl' | 'domain' 16 | > & { 17 | proxyUrl?: string; 18 | domain?: string; 19 | }; 20 | 21 | const loadClerkJsScript = (opts: LoadClerkJsScriptOptions) => { 22 | const { publishableKey } = opts; 23 | 24 | if (!publishableKey) { 25 | errorThrower.throwMissingPublishableKeyError(); 26 | } 27 | 28 | const existingScript = document.querySelector( 29 | 'script[data-clerk-js-script]' 30 | ); 31 | 32 | if (existingScript) { 33 | return new Promise((resolve, reject) => { 34 | existingScript.addEventListener('load', () => { 35 | resolve(existingScript); 36 | }); 37 | 38 | existingScript.addEventListener('error', () => { 39 | reject(FAILED_TO_LOAD_ERROR); 40 | }); 41 | }); 42 | } 43 | 44 | return loadScript(clerkJsScriptUrl(opts), { 45 | async: true, 46 | crossOrigin: 'anonymous', 47 | beforeLoad: applyClerkJsScriptAttributes(opts) 48 | }).catch(() => { 49 | throw new Error(FAILED_TO_LOAD_ERROR); 50 | }); 51 | }; 52 | 53 | const clerkJsScriptUrl = (opts: LoadClerkJsScriptOptions) => { 54 | const { 55 | clerkJSUrl, 56 | clerkJSVariant, 57 | clerkJSVersion, 58 | proxyUrl, 59 | domain, 60 | publishableKey 61 | } = opts; 62 | 63 | if (clerkJSUrl) { 64 | return clerkJSUrl; 65 | } 66 | 67 | let scriptHost = ''; 68 | if (!!proxyUrl && isValidProxyUrl(proxyUrl)) { 69 | scriptHost = proxyUrlToAbsoluteURL(proxyUrl).replace(/http(s)?:\/\//, ''); 70 | } else if ( 71 | domain && 72 | !isDevOrStagingUrl(parsePublishableKey(publishableKey)?.frontendApi || '') 73 | ) { 74 | scriptHost = addClerkPrefix(domain); 75 | } else { 76 | scriptHost = parsePublishableKey(publishableKey)?.frontendApi || ''; 77 | } 78 | 79 | const variant = clerkJSVariant 80 | ? `${clerkJSVariant.replace(/\.+$/, '')}.` 81 | : ''; 82 | const version = versionSelector(clerkJSVersion); 83 | return `https://${scriptHost}/npm/@clerk/clerk-js@${version}/dist/clerk.${variant}browser.js`; 84 | }; 85 | 86 | const buildClerkJsScriptAttributes = (options: LoadClerkJsScriptOptions) => { 87 | const obj: Record = {}; 88 | 89 | if (options.publishableKey) { 90 | obj['data-clerk-publishable-key'] = options.publishableKey; 91 | } 92 | 93 | if (options.proxyUrl) { 94 | obj['data-clerk-proxy-url'] = options.proxyUrl; 95 | } 96 | 97 | if (options.domain) { 98 | obj['data-clerk-domain'] = options.domain; 99 | } 100 | 101 | return obj; 102 | }; 103 | 104 | const applyClerkJsScriptAttributes = 105 | (options: LoadClerkJsScriptOptions) => (script: HTMLScriptElement) => { 106 | const attributes = buildClerkJsScriptAttributes(options); 107 | for (const attribute in attributes) { 108 | script.setAttribute(attribute, attributes[attribute]); 109 | } 110 | }; 111 | 112 | export { buildClerkJsScriptAttributes, clerkJsScriptUrl, loadClerkJsScript }; 113 | export type { LoadClerkJsScriptOptions }; 114 | -------------------------------------------------------------------------------- /assets/images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/contexts/clerk-context.tsx: -------------------------------------------------------------------------------- 1 | import { deriveState } from '@clerk/shared'; 2 | import type { 3 | ClientResource, 4 | InitialState, 5 | LoadedClerk, 6 | Resources 7 | } from '@clerk/types'; 8 | import { 9 | Accessor, 10 | createEffect, 11 | createMemo, 12 | createSignal, 13 | JSX, 14 | JSXElement, 15 | on, 16 | onCleanup, 17 | untrack 18 | } from 'solid-js'; 19 | import { IsomorphicClerk } from '../isomorphic-clerk'; 20 | import type { IsomorphicClerkOptions } from '../types'; 21 | import { AuthContextProvider } from './auth'; 22 | import { ClientContextProvider } from './client'; 23 | import { IsomorphicClerkContextProvider } from './isomorphic-clerk'; 24 | import { OrganizationContextProvider } from './organization'; 25 | import { SessionContextProvider } from './session'; 26 | import { UserContextProvider } from './user'; 27 | 28 | type ClerkContextProvider = { 29 | isomorphicClerkOptions: IsomorphicClerkOptions; 30 | initialState: InitialState | undefined; 31 | children: JSXElement; 32 | }; 33 | 34 | export type ClerkContextProviderState = Resources; 35 | 36 | export function ClerkContextProvider(props: ClerkContextProvider): JSX.Element { 37 | const { isomorphicClerk: clerk, loaded: clerkLoaded } = 38 | useLoadedIsomorphicClerk(() => props.isomorphicClerkOptions); 39 | 40 | const [state, setState] = createSignal({ 41 | client: clerk().client as ClientResource, 42 | session: clerk().session, 43 | user: clerk().user, 44 | organization: clerk().organization 45 | }); 46 | createEffect(() => { 47 | const fn = clerk().addListener((e) => setState({ ...e })); 48 | onCleanup(fn); 49 | }); 50 | 51 | const derivedState = createMemo(() => 52 | deriveState(clerkLoaded(), state(), props.initialState) 53 | ); 54 | 55 | const clerkCtx = () => (clerkLoaded() ? clerk() : clerk()); 56 | 57 | return ( 58 | } 60 | > 61 | state().client}> 62 | derivedState().session}> 63 | derivedState().organization} 65 | > 66 | derivedState().userId} 68 | sessionId={() => derivedState().sessionId} 69 | actor={() => derivedState().actor} 70 | orgId={() => derivedState().orgId} 71 | orgRole={() => derivedState().orgRole} 72 | orgSlug={() => derivedState().orgSlug} 73 | orgPermissions={() => derivedState().orgPermissions} 74 | > 75 | derivedState().user}> 76 | {props.children} 77 | 78 | 79 | 80 | 81 | 82 | 83 | ); 84 | } 85 | 86 | const useLoadedIsomorphicClerk = ( 87 | options: Accessor 88 | ) => { 89 | const [loaded, setLoaded] = createSignal(false); 90 | const isomorphicClerk = createMemo(() => 91 | IsomorphicClerk.getOrCreateInstance(untrack(options)) 92 | ); 93 | 94 | createEffect( 95 | on( 96 | () => options().appearance, 97 | (appearance) => { 98 | void isomorphicClerk().__unstable__updateProps({ 99 | appearance 100 | }); 101 | } 102 | ) 103 | ); 104 | 105 | createEffect( 106 | on( 107 | () => options().localization, 108 | (localization) => { 109 | void isomorphicClerk().__unstable__updateProps({ 110 | options: { 111 | localization 112 | } 113 | }); 114 | } 115 | ) 116 | ); 117 | 118 | createEffect(() => { 119 | isomorphicClerk().addOnLoaded(() => { 120 | setLoaded(true); 121 | }); 122 | }); 123 | 124 | onCleanup(() => { 125 | IsomorphicClerk.clearInstance(); 126 | }); 127 | 128 | return { isomorphicClerk, loaded }; 129 | }; 130 | -------------------------------------------------------------------------------- /docs/PUBLISH.md: -------------------------------------------------------------------------------- 1 | # Publishing packages 2 | 3 | _Note: Only maintainers can trigger the actions described below._ 4 | 5 | ## Publishing stable package versions (`@latest`) 6 | 7 | We are using [changesets](https://github.com/changesets/changesets), so our CICD is using [`changesets/action`](https://github.com/changesets/action) to automate the release process when releasing stable package versions targeting the `@latest` tag. 8 | 9 | Every time a PR is merged into `main`, the changesets action parses all changesets found in `.changeset` and updates the "ci(repo): Version packages" PR with the new package versions and the changelog for the next stable release. 10 | 11 | To release a new stable version of all Clerk packages, find the "ci(repo): Version packages" PR, verify the changes, and merge it. 12 | 13 | ## Publishing canary package versions (`@canary`) 14 | 15 | An automated canary release will be take place every time a PR gets merged into `main`. 16 | 17 | - Staging versions use the following format: `clerk-solidjs@x.y.z-canary.commit`, where `package` is the package name, `x`,`y`,`z` are the major, minor and patch versions respectively, `canary` is a stable prerelease mame and `commit` is the id of the last commit in the branch. 18 | - Currently, canary version changes are _not_ committed to the repo and no git tags will be generated. Using this strategy, we avoid merge conflicts, allowing us to constantly deploy canary versions without switching the repo to a "prerelease" mode. 19 | - During a canary release, `clerk-solidjs` will also be released. If needed, use the `clerkJSVersion` prop to use a specific version, eg: `` 20 | - A package will not be published if it's not affected by a changeset. 21 | 22 | **Note:** If the `main` branch is in [prerelease mode](https://github.com/changesets/changesets/blob/main/docs/prereleases.md) merges into the latest `release/` branch will be released under the `@canary` tag. 23 | 24 | ## Publishing snapshot package versions (`@snapshot`) 25 | 26 | Snapshot releases are a way to release your changes for testing without updating the versions or waiting for your PR to be merged into `main`. This is especially useful when you want to test your changes in a cloud environment that does not offer a way to upload prebuilt artifacts of your app or if you want to test the end-to-end flow as a user, without relying on local linking to test. Snapshot releases can also be used as a tool for customers to verify a fix on their machines. 27 | 28 | **Important:** Before requesting a snapshot release, ensure that your Clerk organization membership status is set to "Public". Otherwise, the snapshot release will fail. To set your status to "Public", follow [these steps](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-your-membership-in-organizations/publicizing-or-hiding-organization-membership). 29 | 30 | **Important:** When using a snapshot release, it's highly recommended you install a specific version directly (eg `npm i @clerk/nextjs@4.27.0-snapshot.v609cd23`). Do not use the `@snapshot` tag to get the most recent snapshot release, as this will result in unexpected behavior when another member publishes their own snapshot. 31 | 32 | To perform a snapshot release, simply comment `!snapshot` in your PR. Once the packages are built and published (~2mins), [clerk-cookie](https://github.com/clerk-cookie) will post a reply with the published versions ([example](https://github.com/clerk/javascript/pull/1329#issuecomment-1586970784)). Simply install the snap version using `npm install` as usual. 33 | 34 | Notes: 35 | 36 | - Snapshot versions use the following format: `clerk-solidjs@x.y.z-snapshot.commit`, where `package` is the package name, `x`,`y`,`z` are the major, minor and patch versions respectively, `snapshot` is a stable prerelease mame and `commit` is the id of the last commit in the branch. 37 | - If you want to name your snapshot release, you can pass an argument to the snapshot comment, eg `!snapshot myname` will use `myname` instead of `snapshot`, eg: `clerk-solidjs@4.1.1-myname.90012`. Please note: When using a custom name, the underlying id stays the same and only the tag changes. This has the consequence that npm resolves versions alphabetically. You should pin your versions and not rely on resolving through `^` or `~`. 38 | - Snapshot version changes are _not_ committed to the repo and no git tags will be generated - they are meant to be used as "snapshots" of the repo at a particular state for testing purposes. 39 | - During a snapshot release, `clerk-solidjs` will also be released. If needed, use the `clerkJSVersion` prop to use a specific version, eg: `` 40 | - To make iterations faster, tests will not run for snapshot versions. 41 | - A package will not be published if it's not affected by a changeset. 42 | 43 | ## Previewing PRs 44 | 45 | Deploy previews enable you to give yourself and others reviewing the PR a chance to see your changes deployed in a live environment. Your changes are applied to a test site and deployed to a URL one can visit. This is especially helpful for e.g. visual changes made inside the UI components. 46 | 47 | **Important:** Before requesting a deploy preview, ensure that your Clerk organization membership status is set to "Public". Otherwise, the preview will fail. To set your status to "Public", follow [these steps](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-your-membership-in-organizations/publicizing-or-hiding-organization-membership). 48 | 49 | You can request a deploy preview by commenting `!preview` in your PR. A GitHub workflow will take your current PR, install the current state of the packages into a test site, and deploy that test site to Vercel. Once the deployment is done, a GitHub comment will point you to the deployed URL. 50 | 51 | **Important:** In order for you (or others) to visit the preview URL, you’ll need to be part of the Clerk Vercel team and logged into Vercel. 52 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # clerk-solidjs 2 | 3 | ## 2.2.0 4 | 5 | ### Minor Changes 6 | 7 | - 766ec60: Added Waitlist component 8 | 9 | ## 2.1.0 10 | 11 | ### Minor Changes 12 | 13 | - a8e40c2: Dependency updates 14 | 15 | ## 2.0.10 16 | 17 | ### Patch Changes 18 | 19 | - 6d5f58a: Update dependencies 20 | 21 | ## 2.0.9 22 | 23 | ### Patch Changes 24 | 25 | - f391fe1: Dependency updates 26 | 27 | ## 2.0.8 28 | 29 | ### Patch Changes 30 | 31 | - 51f6e19: Update Dependencies 32 | 33 | ## 2.0.7 34 | 35 | ### Patch Changes 36 | 37 | - 5277948: dependency bumps 38 | 39 | ## 2.0.6 40 | 41 | ### Patch Changes 42 | 43 | - 35a819a: update dependencies 44 | 45 | ## 2.0.5 46 | 47 | ### Patch Changes 48 | 49 | - 835699b: Update dependencies 50 | 51 | ## 2.0.4 52 | 53 | ### Patch Changes 54 | 55 | - b4d4d47: Add some in-code documentation 56 | 57 | ## 2.0.3 58 | 59 | ### Patch Changes 60 | 61 | - 97efc9e: Dependency updates 62 | - dbe3a4a: Fix CI issues and isomorphic clerk updates 63 | 64 | ## 2.0.2 65 | 66 | ### Patch Changes 67 | 68 | - cec1e6d: Update dependencies 69 | 70 | ## 2.0.1 71 | 72 | ### Patch Changes 73 | 74 | - a6f51d8: Update deps 75 | 76 | ## 2.0.0 77 | 78 | ### Major Changes 79 | 80 | - 69a85bd: Refactor solid start code 81 | 82 | ## 1.2.1 83 | 84 | ### Patch Changes 85 | 86 | - c73195c: Update all deps 87 | 88 | ## 1.2.0 89 | 90 | ### Minor Changes 91 | 92 | - b0f0f1c: Fixing reactive props 93 | 94 | ## 1.1.18 95 | 96 | ### Patch Changes 97 | 98 | - fc47cbf: Fix reactivity, bump deps 99 | 100 | ## 1.1.17 101 | 102 | ### Patch Changes 103 | 104 | - 41d8f77: Add capabilities to isomorphic clerk to match react 105 | 106 | ## 1.1.16 107 | 108 | ### Patch Changes 109 | 110 | - 43bd09c: Use shared clerk functions 111 | 112 | ## 1.1.15 113 | 114 | ### Patch Changes 115 | 116 | - efdef59: Update README to adjust support contact options 117 | 118 | ## 1.1.14 119 | 120 | ### Patch Changes 121 | 122 | - f28555e: Bump dependencies 123 | 124 | ## 1.1.13 125 | 126 | ### Patch Changes 127 | 128 | - 1b89ee5: Bump deps 129 | 130 | ## 1.1.12 131 | 132 | ### Patch Changes 133 | 134 | - 1564e86: Bump deps 135 | 136 | ## 1.1.11 137 | 138 | ### Patch Changes 139 | 140 | - 8011ddc: Bump dependencies 141 | 142 | ## 1.1.10 143 | 144 | ### Patch Changes 145 | 146 | - 3413ae1: Bump dependencies 147 | 148 | ## 1.1.9 149 | 150 | ### Patch Changes 151 | 152 | - 3ba313f: Bump dependencies 153 | 154 | ## 1.1.8 155 | 156 | ### Patch Changes 157 | 158 | - 3c13e20: Bump deps 159 | 160 | ## 1.1.7 161 | 162 | ### Patch Changes 163 | 164 | - fb84480: Bump dependencies 165 | 166 | ## 1.1.6 167 | 168 | ### Patch Changes 169 | 170 | - 9e5638d: Updates @clerk/backend from 1.3.2 to 1.4.1. Updates @clerk/shared from 2.3.3 to 2.4.0 171 | 172 | ## 1.1.5 173 | 174 | ### Patch Changes 175 | 176 | - 2df1e5d: Dependency updates 177 | 178 | ## 1.1.4 179 | 180 | ### Patch Changes 181 | 182 | - fb0fce1: Dependency updates 183 | 184 | ## 1.1.3 185 | 186 | ### Patch Changes 187 | 188 | - 4d256c4: Dependency updates, readme update 189 | 190 | ## 1.1.2 191 | 192 | ### Patch Changes 193 | 194 | - a1dc7fa: Documentation updates 195 | 196 | ## 1.1.1 197 | 198 | ### Patch Changes 199 | 200 | - 1ceeb17: Dependency updates 201 | 202 | ## 1.1.0 203 | 204 | ### Minor Changes 205 | 206 | - 8a7e778: Change ownership to LLC 207 | 208 | ## 1.0.6 209 | 210 | ### Patch Changes 211 | 212 | - 335f96e: Changing changelog implementation, fixing readme 213 | 214 | ## 1.0.5 215 | 216 | ### Patch Changes 217 | 218 | - [#26](https://github.com/ian-pascoe/clerk-solidjs/pull/26) [`85df583`](https://github.com/ian-pascoe/clerk-solidjs/commit/85df583095dea1af29c377dff4a33a860f1c3a9f) Thanks [@dependabot](https://github.com/apps/dependabot)! - Dependency update & add provenance to npm 219 | 220 | - [#28](https://github.com/ian-pascoe/clerk-solidjs/pull/28) [`eab768b`](https://github.com/ian-pascoe/clerk-solidjs/commit/eab768ba05047c6efa116fa07e22f1f927a23d41) Thanks [@ian-pascoe](https://github.com/ian-pascoe)! - Cleanup dependency graph 221 | 222 | ## 1.0.4 223 | 224 | ### Patch Changes 225 | 226 | - [`b39bb70`](https://github.com/ian-pascoe/clerk-solidjs/commit/b39bb70cb2e1bef9f3eed71280540718271cd45a) Thanks [@ian-pascoe](https://github.com/ian-pascoe)! - Dependency updates 227 | 228 | ## 1.0.3 229 | 230 | ### Patch Changes 231 | 232 | - [`b1f41ce`](https://github.com/ian-pascoe/clerk-solidjs/commit/b1f41ce733bcd386b4bf6875290f8af4afce9af8) Thanks [@ian-pascoe](https://github.com/ian-pascoe)! - Add customizable fields to components 233 | 234 | - [`952d335`](https://github.com/ian-pascoe/clerk-solidjs/commit/952d335bfe853160f0ebc161644a2f0db920fe17) Thanks [@ian-pascoe](https://github.com/ian-pascoe)! - Finalize changes 235 | 236 | ## 1.0.2 237 | 238 | ### Patch Changes 239 | 240 | - [`43d3559`](https://github.com/ian-pascoe/clerk-solidjs/commit/43d3559ead538c8f7b03ec79733692a455f16e7d) Thanks [@ian-pascoe](https://github.com/ian-pascoe)! - Fixing types and readme 241 | 242 | ## 1.0.1 243 | 244 | ### Patch Changes 245 | 246 | - [`b6ed275`](https://github.com/ian-pascoe/clerk-solidjs/commit/b6ed2756ba211bc96a3378f973e688b3146e0ac6) Thanks [@ian-pascoe](https://github.com/ian-pascoe)! - Fix readme 247 | 248 | ## 1.0.0 249 | 250 | ### Major Changes 251 | 252 | - [`da57f6f`](https://github.com/ian-pascoe/clerk-solidjs/commit/da57f6f0f355f65605d4922d47db81aea241fed8) Thanks [@ian-pascoe](https://github.com/ian-pascoe)! - First working version 253 | 254 | ### Minor Changes 255 | 256 | - [`ac671d8`](https://github.com/ian-pascoe/clerk-solidjs/commit/ac671d8d7abefad6b6d02b0c9383461bab3cf97f) Thanks [@ian-pascoe](https://github.com/ian-pascoe)! - Got it working!! 257 | 258 | ### Patch Changes 259 | 260 | - [`8567c8d`](https://github.com/ian-pascoe/clerk-solidjs/commit/8567c8dba59cb9ce7a6f6c095d688e38b7996014) Thanks [@ian-pascoe](https://github.com/ian-pascoe)! - Convert to monorepo 261 | 262 | - [`17b1d72`](https://github.com/ian-pascoe/clerk-solidjs/commit/17b1d72459a0a3716011a9662b6d2785a6597e68) Thanks [@ian-pascoe](https://github.com/ian-pascoe)! - Tweak tsup 263 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-organization-list.ts: -------------------------------------------------------------------------------- 1 | import { eventMethodCalled } from '@clerk/shared/telemetry'; 2 | import type { 3 | ClerkPaginatedResponse, 4 | GetUserOrganizationInvitationsParams, 5 | GetUserOrganizationMembershipParams, 6 | GetUserOrganizationSuggestionsParams, 7 | OrganizationMembershipResource, 8 | OrganizationSuggestionResource, 9 | UserOrganizationInvitationResource 10 | } from '@clerk/types'; 11 | import { Accessor, createEffect, createMemo } from 'solid-js'; 12 | import { useClerkInstanceContext } from '../contexts/clerk-instance'; 13 | import { useUserContext } from '../contexts/user'; 14 | import { IsomorphicClerk } from '../isomorphic-clerk'; 15 | import type { PaginatedHookConfig } from '../types'; 16 | import { useAssertWrappedByClerkProvider } from './use-assert-wrapped-by-clerk-provider'; 17 | import { 18 | convertToSafeValues, 19 | usePagesOrInfinite 20 | } from './use-pages-or-infinite'; 21 | 22 | type UseOrganizationListParams = { 23 | userMemberships?: 24 | | true 25 | | PaginatedHookConfig; 26 | userInvitations?: 27 | | true 28 | | PaginatedHookConfig; 29 | userSuggestions?: 30 | | true 31 | | PaginatedHookConfig; 32 | }; 33 | 34 | export const useOrganizationList = ( 35 | params?: Accessor 36 | ) => { 37 | useAssertWrappedByClerkProvider('useOrganizationList'); 38 | 39 | const userMembershipsSafeValues = createMemo(() => 40 | convertToSafeValues(params?.().userMemberships, { 41 | initialPage: 1, 42 | pageSize: 10, 43 | keepPreviousData: false, 44 | infinite: false 45 | }) 46 | ); 47 | 48 | const userInvitationsSafeValues = createMemo(() => 49 | convertToSafeValues(params?.().userInvitations, { 50 | initialPage: 1, 51 | pageSize: 10, 52 | status: 'pending', 53 | keepPreviousData: false, 54 | infinite: false 55 | }) 56 | ); 57 | 58 | const userSuggestionsSafeValues = createMemo(() => 59 | convertToSafeValues(params?.().userSuggestions, { 60 | initialPage: 1, 61 | pageSize: 10, 62 | status: 'pending', 63 | keepPreviousData: false, 64 | infinite: false 65 | }) 66 | ); 67 | 68 | const clerk = useClerkInstanceContext(); 69 | const user = useUserContext(); 70 | 71 | createEffect(() => { 72 | clerk().telemetry?.record(eventMethodCalled('useOrganizationList')); 73 | }); 74 | 75 | const userMembershipsParams = createMemo(() => 76 | typeof params?.().userMemberships === 'undefined' 77 | ? undefined 78 | : { 79 | initialPage: userMembershipsSafeValues().initialPage, 80 | pageSize: userMembershipsSafeValues().pageSize 81 | } 82 | ); 83 | 84 | const userInvitationsParams = createMemo(() => 85 | typeof params?.().userInvitations === 'undefined' 86 | ? undefined 87 | : { 88 | initialPage: userInvitationsSafeValues().initialPage, 89 | pageSize: userInvitationsSafeValues().pageSize, 90 | status: userInvitationsSafeValues().status 91 | } 92 | ); 93 | 94 | const userSuggestionsParams = createMemo(() => 95 | typeof params?.().userSuggestions === 'undefined' 96 | ? undefined 97 | : { 98 | initialPage: userSuggestionsSafeValues().initialPage, 99 | pageSize: userSuggestionsSafeValues().pageSize, 100 | status: userSuggestionsSafeValues().status 101 | } 102 | ); 103 | 104 | const isClerkLoaded = createMemo(() => !!(clerk().loaded && user())); 105 | 106 | const memberships = usePagesOrInfinite< 107 | GetUserOrganizationMembershipParams, 108 | ClerkPaginatedResponse 109 | >(() => ({ 110 | params: userMembershipsParams() || {}, 111 | fetcher: user()?.getOrganizationMemberships, 112 | config: { 113 | keepPreviousData: userMembershipsSafeValues().keepPreviousData, 114 | infinite: userMembershipsSafeValues().infinite, 115 | enabled: !!userMembershipsParams() 116 | }, 117 | cacheKeys: { 118 | type: 'userMemberships', 119 | userId: user()?.id 120 | } 121 | })); 122 | 123 | const invitations = usePagesOrInfinite< 124 | GetUserOrganizationInvitationsParams, 125 | ClerkPaginatedResponse 126 | >(() => ({ 127 | params: { 128 | ...userInvitationsParams() 129 | }, 130 | fetcher: user()?.getOrganizationInvitations, 131 | config: { 132 | keepPreviousData: userInvitationsSafeValues().keepPreviousData, 133 | infinite: userInvitationsSafeValues().infinite, 134 | enabled: !!userInvitationsParams() 135 | }, 136 | cacheKeys: { 137 | type: 'userInvitations', 138 | userId: user()?.id 139 | } 140 | })); 141 | 142 | const suggestions = usePagesOrInfinite< 143 | GetUserOrganizationSuggestionsParams, 144 | ClerkPaginatedResponse 145 | >(() => ({ 146 | params: { 147 | ...userSuggestionsParams() 148 | }, 149 | fetcher: user()?.getOrganizationSuggestions, 150 | config: { 151 | keepPreviousData: userSuggestionsSafeValues().keepPreviousData, 152 | infinite: userSuggestionsSafeValues().infinite, 153 | enabled: !!userSuggestionsParams() 154 | }, 155 | cacheKeys: { 156 | type: 'userSuggestions', 157 | userId: user()?.id 158 | } 159 | })); 160 | 161 | const createOrganization = ( 162 | params: Parameters[0] 163 | ) => { 164 | return clerk().createOrganization(params); 165 | }; 166 | const setActive = (params: Parameters[0]) => { 167 | return clerk().setActive(params); 168 | }; 169 | 170 | return { 171 | isLoaded: isClerkLoaded, 172 | createOrganization, 173 | setActive, 174 | userMemberships: createMemo(() => 175 | isClerkLoaded() ? memberships : undefined 176 | ), 177 | userInvitations: createMemo(() => 178 | isClerkLoaded() ? invitations : undefined 179 | ), 180 | userSuggestions: createMemo(() => 181 | isClerkLoaded() ? suggestions : undefined 182 | ) 183 | }; 184 | }; 185 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/control-components.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | CheckAuthorizationWithCustomPermissions, 3 | HandleOAuthCallbackParams, 4 | OrganizationCustomPermissionKey, 5 | OrganizationCustomRoleKey 6 | } from '@clerk/types'; 7 | import { 8 | createEffect, 9 | createMemo, 10 | JSX, 11 | Match, 12 | ParentProps, 13 | Show, 14 | splitProps, 15 | Switch 16 | } from 'solid-js'; 17 | import { useAuthContext } from '../contexts/auth'; 18 | import { useIsomorphicClerkContext } from '../contexts/isomorphic-clerk'; 19 | import { useAssertWrappedByClerkProvider } from '../hooks/use-assert-wrapped-by-clerk-provider'; 20 | import { useAuth } from '../hooks/use-auth'; 21 | import type { 22 | RedirectToSignInProps, 23 | RedirectToSignUpProps, 24 | WithClerkProp 25 | } from '../types'; 26 | import { withClerk } from './with-clerk'; 27 | 28 | export const SignedIn = (props: ParentProps): JSX.Element => { 29 | useAssertWrappedByClerkProvider('SignedIn'); 30 | const { userId } = useAuthContext(); 31 | return {props.children}; 32 | }; 33 | 34 | export const SignedOut = (props: ParentProps): JSX.Element => { 35 | useAssertWrappedByClerkProvider('SignedOut'); 36 | const { userId } = useAuthContext(); 37 | return {props.children}; 38 | }; 39 | 40 | export const ClerkLoaded = (props: ParentProps): JSX.Element => { 41 | useAssertWrappedByClerkProvider('ClerkLoaded'); 42 | const isomorphicClerk = useIsomorphicClerkContext(); 43 | const isLoaded = () => isomorphicClerk().loaded; 44 | return {props.children}; 45 | }; 46 | 47 | export const ClerkLoading = (props: ParentProps): JSX.Element => { 48 | useAssertWrappedByClerkProvider('ClerkLoading'); 49 | const isomorphicClerk = useIsomorphicClerkContext(); 50 | const isLoaded = () => isomorphicClerk().loaded; 51 | return {props.children}; 52 | }; 53 | 54 | export type ProtectProps = ParentProps< 55 | ( 56 | | { 57 | condition?: never; 58 | role: OrganizationCustomRoleKey; 59 | permission?: never; 60 | } 61 | | { 62 | condition?: never; 63 | role?: never; 64 | permission: OrganizationCustomPermissionKey; 65 | } 66 | | { 67 | condition: (has: CheckAuthorizationWithCustomPermissions) => boolean; 68 | role?: never; 69 | permission?: never; 70 | } 71 | | { 72 | condition?: never; 73 | role?: never; 74 | permission?: never; 75 | } 76 | ) & { 77 | fallback?: JSX.Element; 78 | } 79 | >; 80 | 81 | /** 82 | * Use `` in order to prevent unauthenticated or unauthorized users from accessing the children passed to the component. 83 | * 84 | * Examples: 85 | * ``` 86 | * 87 | * 88 | * has({permission:"a_permission_key"})} /> 89 | * has({role:"a_role_key"})} /> 90 | * Unauthorized

} /> 91 | * ``` 92 | */ 93 | export const Protect = (props: ProtectProps) => { 94 | useAssertWrappedByClerkProvider('Protect'); 95 | 96 | const [local, restAuthorizedParams] = splitProps(props, [ 97 | 'children', 98 | 'fallback' 99 | ]); 100 | 101 | const { isLoaded, has, userId } = useAuth(); 102 | 103 | /** 104 | * Fallback to UI provided by user or `null` if authorization checks failed 105 | */ 106 | const unauthorized = <>{local.fallback ?? null}; 107 | const authorized = <>{local.children}; 108 | 109 | return ( 110 | 111 | {unauthorized} 112 | {unauthorized} 113 | 119 | {authorized} 120 | 121 | 127 | {authorized} 128 | 129 | 130 | ); 131 | }; 132 | 133 | export const RedirectToSignIn = withClerk( 134 | (props: WithClerkProp) => { 135 | const [local, redirectToSignInProps] = splitProps(props, ['clerk']); 136 | 137 | const hasActiveSessions = createMemo( 138 | () => 139 | local.clerk().client.activeSessions && 140 | local.clerk().client.activeSessions.length > 0 141 | ); 142 | 143 | createEffect(() => { 144 | if (local.clerk().session === null && hasActiveSessions()) { 145 | void local.clerk().redirectToAfterSignOut(); 146 | } else { 147 | void local.clerk().redirectToSignIn(redirectToSignInProps); 148 | } 149 | }); 150 | 151 | return null; 152 | }, 153 | 'RedirectToSignIn' 154 | ); 155 | 156 | export const RedirectToSignUp = withClerk( 157 | (props: WithClerkProp) => { 158 | const [local, redirectToSignUpProps] = splitProps(props, ['clerk']); 159 | createEffect(() => { 160 | void local.clerk().redirectToSignUp(redirectToSignUpProps); 161 | }); 162 | 163 | return null; 164 | }, 165 | 'RedirectToSignUp' 166 | ); 167 | 168 | export const RedirectToUserProfile = withClerk((props) => { 169 | createEffect(() => { 170 | void props.clerk().redirectToUserProfile(); 171 | }); 172 | 173 | return null; 174 | }, 'RedirectToUserProfile'); 175 | 176 | export const RedirectToOrganizationProfile = withClerk((props) => { 177 | createEffect(() => { 178 | void props.clerk().redirectToOrganizationProfile(); 179 | }); 180 | 181 | return null; 182 | }, 'RedirectToOrganizationProfile'); 183 | 184 | export const RedirectToCreateOrganization = withClerk((props) => { 185 | createEffect(() => { 186 | void props.clerk().redirectToCreateOrganization(); 187 | }); 188 | 189 | return null; 190 | }, 'RedirectToCreateOrganization'); 191 | 192 | export const AuthenticateWithRedirectCallback = withClerk( 193 | (props: WithClerkProp) => { 194 | const [local, handleRedirectCallbackParams] = splitProps(props, ['clerk']); 195 | createEffect(() => { 196 | void local.clerk().handleRedirectCallback(handleRedirectCallbackParams); 197 | }); 198 | 199 | return null; 200 | }, 201 | 'AuthenticateWithRedirectCallback' 202 | ); 203 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Clerk, 3 | ClerkOptions, 4 | ClerkPaginatedResponse, 5 | ClientResource, 6 | DomainOrProxyUrl, 7 | InitialState, 8 | LoadedClerk, 9 | MultiDomainAndOrProxy, 10 | RedirectUrlProp, 11 | SignInProps, 12 | SignInRedirectOptions, 13 | SignOutOptions, 14 | SignUpProps, 15 | SignUpRedirectOptions, 16 | Without 17 | } from '@clerk/types'; 18 | import { Accessor, JSX, JSXElement } from 'solid-js'; 19 | 20 | declare global { 21 | interface Window { 22 | __clerk_publishable_key?: string; 23 | __clerk_proxy_url?: Clerk['proxyUrl']; 24 | __clerk_domain?: Clerk['domain']; 25 | } 26 | } 27 | 28 | export type Prettify = { 29 | [K in keyof T]: T[K]; 30 | } & {}; 31 | 32 | export type IsomorphicClerkOptions = Without & { 33 | Clerk?: ClerkProp; 34 | /** 35 | * Define the URL that `@clerk/clerk-js` should be hot-loaded from 36 | */ 37 | clerkJSUrl?: string; 38 | /** 39 | * If your web application only uses Control components, you can set this value to `'headless'` and load a minimal ClerkJS bundle for optimal page performance. 40 | */ 41 | clerkJSVariant?: 'headless' | ''; 42 | /** 43 | * Define the npm version for `@clerk/clerk-js` 44 | */ 45 | clerkJSVersion?: string; 46 | /** 47 | * The Clerk publishable key for your instance 48 | * @note This can be found in your Clerk Dashboard on the [API Keys](https://dashboard.clerk.com/last-active?path=api-keys) page 49 | */ 50 | publishableKey: string; 51 | /** 52 | * This nonce value will be passed through to the `@clerk/clerk-js` script tag. 53 | * @note You can use this to implement [strict-dynamic CSP](https://clerk.com/docs/security/clerk-csp#implementing-a-strict-dynamic-csp) 54 | */ 55 | nonce?: string; 56 | } & MultiDomainAndOrProxy; 57 | 58 | export type ClerkProviderProps = IsomorphicClerkOptions & { 59 | children: JSX.Element; 60 | initialState?: InitialState; 61 | }; 62 | 63 | export interface BrowserClerkConstructor { 64 | new (publishableKey: string, options?: DomainOrProxyUrl): BrowserClerk; 65 | } 66 | 67 | export interface HeadlessBrowserClerkConstructor { 68 | new ( 69 | publishableKey: string, 70 | options?: DomainOrProxyUrl 71 | ): HeadlessBrowserClerk; 72 | } 73 | 74 | export type WithClerkProp = T & { clerk: Accessor }; 75 | 76 | // Clerk object 77 | export interface MountProps { 78 | mount: (node: HTMLDivElement, props: any) => void; 79 | unmount: (node: HTMLDivElement) => void; 80 | updateProps: (props: any) => void; 81 | props?: any; 82 | } 83 | 84 | export interface OpenProps { 85 | open: (props: any) => void; 86 | close: () => void; 87 | props?: any; 88 | } 89 | 90 | export interface HeadlessBrowserClerk extends Clerk { 91 | load: (opts?: Without) => Promise; 92 | updateClient: (client: ClientResource) => void; 93 | } 94 | 95 | export interface BrowserClerk extends HeadlessBrowserClerk { 96 | onComponentsReady: Promise; 97 | components: any; 98 | } 99 | 100 | export type ClerkProp = 101 | | BrowserClerkConstructor 102 | | BrowserClerk 103 | | HeadlessBrowserClerk 104 | | HeadlessBrowserClerkConstructor 105 | | undefined 106 | | null; 107 | 108 | type ButtonProps = JSX.ButtonHTMLAttributes & { 109 | mode?: 'redirect' | 'modal'; 110 | }; 111 | 112 | export type SignInButtonProps = ButtonProps & 113 | Pick< 114 | SignInProps, 115 | | 'fallbackRedirectUrl' 116 | | 'forceRedirectUrl' 117 | | 'signUpForceRedirectUrl' 118 | | 'signUpFallbackRedirectUrl' 119 | >; 120 | 121 | export type SignOutButtonProps = ButtonProps & 122 | Pick; 123 | 124 | export type SignUpButtonProps = { 125 | unsafeMetadata?: SignUpUnsafeMetadata; 126 | } & ButtonProps & 127 | Pick< 128 | SignUpProps, 129 | | 'fallbackRedirectUrl' 130 | | 'forceRedirectUrl' 131 | | 'signInForceRedirectUrl' 132 | | 'signInFallbackRedirectUrl' 133 | >; 134 | 135 | export type SignInWithMetamaskButtonProps = ButtonProps & RedirectUrlProp; 136 | 137 | export type RedirectToSignInProps = SignInRedirectOptions; 138 | export type RedirectToSignUpProps = SignUpRedirectOptions; 139 | 140 | type PageProps = 141 | | { 142 | label: string; 143 | url: string; 144 | labelIcon: JSXElement; 145 | } 146 | | { 147 | label: T; 148 | url?: never; 149 | labelIcon?: never; 150 | }; 151 | 152 | export type UserProfilePageProps = PageProps<'account' | 'security'>; 153 | 154 | export type UserProfileLinkProps = { 155 | url: string; 156 | label: string; 157 | labelIcon: JSXElement; 158 | }; 159 | 160 | export type OrganizationProfilePageProps = PageProps<'general' | 'members'>; 161 | export type OrganizationProfileLinkProps = UserProfileLinkProps; 162 | 163 | import type { ClerkAPIResponseError } from '@clerk/shared/error'; 164 | 165 | export type ValueOrSetter = (size: T | ((_size: T) => T)) => void; 166 | 167 | export type CacheSetter = ( 168 | data?: 169 | | CData 170 | | ((currentData?: CData) => Promise | undefined | CData) 171 | ) => Promise; 172 | 173 | export type PaginatedResources = { 174 | data: () => T[]; 175 | count: () => number; 176 | error: () => ClerkAPIResponseError | null; 177 | isLoading: () => boolean; 178 | isFetching: () => boolean; 179 | isError: () => boolean; 180 | page: () => number; 181 | pageCount: () => number; 182 | fetchPrevious: () => void; 183 | fetchNext: () => void; 184 | hasNextPage: () => boolean; 185 | hasPreviousPage: () => boolean; 186 | setData: Infinite extends true 187 | ? // Array of pages of data 188 | CacheSetter<(ClerkPaginatedResponse | undefined)[]> 189 | : // Array of data 190 | CacheSetter | undefined>; 191 | revalidate: () => Promise; 192 | }; 193 | 194 | // Utility type to convert PaginatedDataAPI to properties as undefined, except booleans set to false 195 | export type PaginatedResourcesWithDefault = { 196 | [K in keyof PaginatedResources]: PaginatedResources[K] extends boolean 197 | ? false 198 | : undefined; 199 | }; 200 | 201 | export type PaginatedHookConfig = T & { 202 | /** 203 | * Persists the previous pages with new ones in the same array 204 | */ 205 | infinite?: boolean; 206 | /** 207 | * Return the previous key's data until the new data has been loaded 208 | */ 209 | keepPreviousData?: boolean; 210 | }; 211 | 212 | export type PagesOrInfiniteConfig = PaginatedHookConfig<{ 213 | /** 214 | * Should a request be triggered 215 | */ 216 | enabled?: boolean; 217 | }>; 218 | 219 | export type PagesOrInfiniteOptions = { 220 | /** 221 | * This the starting point for your fetched results. The initial value persists between re-renders 222 | */ 223 | initialPage?: number; 224 | /** 225 | * Maximum number of items returned per request. The initial value persists between re-renders 226 | */ 227 | pageSize?: number; 228 | }; 229 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clerk-solidjs", 3 | "description": "Clerk for SolidJS", 4 | "private": false, 5 | "version": "2.2.0", 6 | "keywords": [ 7 | "solidjs", 8 | "clerk", 9 | "solid", 10 | "clerk-auth" 11 | ], 12 | "bugs": { 13 | "url": "https://github.com/spirit-led-software/clerk-solidjs/issues" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/spirit-led-software/clerk-solidjs" 18 | }, 19 | "license": "MIT", 20 | "author": "Spirit-Led Software", 21 | "type": "module", 22 | "main": "./dist/index/server.cjs", 23 | "module": "./dist/index/server.js", 24 | "types": "./dist/index/index.d.ts", 25 | "typesVersions": { 26 | "*": { 27 | "errors": [ 28 | "./dist/errors/index.d.ts" 29 | ], 30 | "server": [ 31 | "./dist/server/index.d.ts" 32 | ], 33 | "start": [ 34 | "./dist/start/index.d.ts" 35 | ], 36 | "start/server": [ 37 | "./dist/start/server/index.d.ts" 38 | ] 39 | } 40 | }, 41 | "exports": { 42 | ".": { 43 | "worker": { 44 | "solid": "./dist/index/server.jsx", 45 | "import": { 46 | "types": "./dist/index/index.d.ts", 47 | "default": "./dist/index/server.js" 48 | }, 49 | "require": { 50 | "types": "./dist/index/index.d.cts", 51 | "default": "./dist/index/server.cjs" 52 | } 53 | }, 54 | "browser": { 55 | "solid": "./dist/index/index.jsx", 56 | "import": { 57 | "types": "./dist/index/index.d.ts", 58 | "default": "./dist/index/index.js" 59 | }, 60 | "require": { 61 | "types": "./dist/index/index.d.cts", 62 | "default": "./dist/index/index.cjs" 63 | } 64 | }, 65 | "deno": { 66 | "solid": "./dist/index/server.jsx", 67 | "import": { 68 | "types": "./dist/index/index.d.ts", 69 | "default": "./dist/index/server.js" 70 | }, 71 | "require": { 72 | "types": "./dist/index/index.d.cts", 73 | "default": "./dist/index/server.cjs" 74 | } 75 | }, 76 | "node": { 77 | "solid": "./dist/index/server.jsx", 78 | "import": { 79 | "types": "./dist/index/index.d.ts", 80 | "default": "./dist/index/server.js" 81 | }, 82 | "require": { 83 | "types": "./dist/index/index.d.cts", 84 | "default": "./dist/index/server.cjs" 85 | } 86 | }, 87 | "solid": "./dist/index/index.jsx", 88 | "import": { 89 | "types": "./dist/index/index.d.ts", 90 | "default": "./dist/index/index.js" 91 | }, 92 | "require": { 93 | "types": "./dist/index/index.d.cts", 94 | "default": "./dist/index/index.cjs" 95 | } 96 | }, 97 | "./errors": { 98 | "import": { 99 | "types": "./dist/errors/index.d.ts", 100 | "default": "./dist/errors/index.js" 101 | }, 102 | "require": { 103 | "types": "./dist/errors/index.d.cts", 104 | "default": "./dist/errors/index.cjs" 105 | } 106 | }, 107 | "./server": { 108 | "import": { 109 | "types": "./dist/server/index.d.ts", 110 | "default": "./dist/server/index.js" 111 | }, 112 | "require": { 113 | "types": "./dist/server/index.d.cts", 114 | "default": "./dist/server/index.cjs" 115 | } 116 | }, 117 | "./start": { 118 | "worker": { 119 | "solid": "./dist/start/server.jsx", 120 | "import": { 121 | "types": "./dist/start/index.d.ts", 122 | "default": "./dist/start/server.js" 123 | }, 124 | "require": { 125 | "types": "./dist/start/index.d.cts", 126 | "default": "./dist/start/server.cjs" 127 | } 128 | }, 129 | "browser": { 130 | "solid": "./dist/start/index.jsx", 131 | "import": { 132 | "types": "./dist/start/index.d.ts", 133 | "default": "./dist/start/index.js" 134 | }, 135 | "require": { 136 | "types": "./dist/start/index.d.cts", 137 | "default": "./dist/start/index.cjs" 138 | } 139 | }, 140 | "deno": { 141 | "solid": "./dist/start/server.jsx", 142 | "import": { 143 | "types": "./dist/start/index.d.ts", 144 | "default": "./dist/start/server.js" 145 | }, 146 | "require": { 147 | "types": "./dist/start/index.d.cts", 148 | "default": "./dist/start/server.cjs" 149 | } 150 | }, 151 | "node": { 152 | "solid": "./dist/start/server.jsx", 153 | "import": { 154 | "types": "./dist/start/index.d.ts", 155 | "default": "./dist/start/server.js" 156 | }, 157 | "require": { 158 | "types": "./dist/start/index.d.cts", 159 | "default": "./dist/start/server.cjs" 160 | } 161 | }, 162 | "solid": "./dist/start/index.jsx", 163 | "import": { 164 | "types": "./dist/start/index.d.ts", 165 | "default": "./dist/start/index.js" 166 | }, 167 | "require": { 168 | "types": "./dist/start/index.d.cts", 169 | "default": "./dist/start/index.cjs" 170 | } 171 | }, 172 | "./start/server": { 173 | "import": { 174 | "types": "./dist/start/server/index.d.ts", 175 | "default": "./dist/start/server/index.js" 176 | }, 177 | "require": { 178 | "types": "./dist/start/server/index.d.cts", 179 | "default": "./dist/start/server/index.cjs" 180 | } 181 | } 182 | }, 183 | "browser": { 184 | "./dist/index/server.js": "./dist/index/index.js", 185 | "./dist/index/server.cjs": "./dist/index/index.cjs", 186 | "./dist/start/server.js": "./dist/start/index.js", 187 | "./dist/start/server.cjs": "./dist/start/index.cjs" 188 | }, 189 | "files": [ 190 | "./dist" 191 | ], 192 | "scripts": { 193 | "build": "tsup", 194 | "dev": "tsup --watch .", 195 | "dev:publish": "pnpm run dev -- --env.publish", 196 | "type-check": "tsc --noEmit", 197 | "format": "prettier --write .", 198 | "format:check": "prettier --check .", 199 | "lint": "eslint .", 200 | "lint:fix": "eslint . --fix", 201 | "test": "vitest run", 202 | "test:watch": "vitest watch", 203 | "clean": "rimraf dist", 204 | "publish:local": "pnpm dlx yalc push --replace --sig" 205 | }, 206 | "dependencies": { 207 | "@clerk/backend": "^1.25.8", 208 | "@clerk/shared": "^3.2.3", 209 | "@solid-primitives/context": "^0.3.0", 210 | "@solid-primitives/destructure": "^0.2.0", 211 | "@solid-primitives/memo": "^1.4.1", 212 | "@tanstack/solid-query": "^5.69.0" 213 | }, 214 | "devDependencies": { 215 | "@clerk/clerk-js": "^5.58.1", 216 | "@clerk/localizations": "^3.13.4", 217 | "@clerk/themes": "^2.2.26", 218 | "@clerk/types": "^4.50.1", 219 | "@eslint/js": "^9.23.0", 220 | "@solidjs/testing-library": "^0.8.10", 221 | "@testing-library/jest-dom": "^6.6.3", 222 | "@testing-library/user-event": "^14.6.1", 223 | "@types/node": "^22.13.14", 224 | "eslint": "^9.23.0", 225 | "expect-type": "^1.2.0", 226 | "globals": "^16.0.0", 227 | "jsdom": "^26.0.0", 228 | "prettier": "3.5.3", 229 | "prettier-plugin-organize-imports": "^4.1.0", 230 | "rimraf": "^6.0.1", 231 | "tsup": "^8.4.0", 232 | "tsup-preset-solid": "^2.2.0", 233 | "typescript": "^5.8.2", 234 | "typescript-eslint": "^8.28.0", 235 | "vite-plugin-solid": "^2.11.6", 236 | "vite-tsconfig-paths": "^5.1.4", 237 | "vitest": "^3.0.9" 238 | }, 239 | "peerDependencies": { 240 | "@solidjs/router": ">=0.14", 241 | "@solidjs/start": ">=1", 242 | "solid-js": ">=1" 243 | }, 244 | "engines": { 245 | "node": ">=18" 246 | }, 247 | "publishConfig": { 248 | "access": "public", 249 | "provenance": true 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-pages-or-infinite.ts: -------------------------------------------------------------------------------- 1 | import { ClerkAPIResponseError } from '@clerk/clerk-js'; 2 | import { createWritableMemo } from '@solid-primitives/memo'; 3 | import { 4 | createInfiniteQuery, 5 | createQuery, 6 | QueryClient 7 | } from '@tanstack/solid-query'; 8 | import { Accessor, createMemo, createSignal } from 'solid-js'; 9 | import type { 10 | CacheSetter, 11 | PagesOrInfiniteConfig, 12 | PagesOrInfiniteOptions, 13 | PaginatedResources 14 | } from '../types'; 15 | 16 | function getDifferentKeys( 17 | obj1: Record, 18 | obj2: Record 19 | ): Record { 20 | const keysSet = new Set(Object.keys(obj2)); 21 | const differentKeysObject: Record = {}; 22 | 23 | for (const key1 of Object.keys(obj1)) { 24 | if (!keysSet.has(key1)) { 25 | differentKeysObject[key1] = obj1[key1]; 26 | } 27 | } 28 | 29 | return differentKeysObject; 30 | } 31 | 32 | export const convertToSafeValues = ( 33 | params: T | true | undefined, 34 | defaultValues: T 35 | ) => { 36 | const shouldUseDefaults = typeof params === 'boolean' && params; 37 | 38 | // Cache initialPage and initialPageSize until unmount 39 | const initialPageRef = shouldUseDefaults 40 | ? defaultValues.initialPage 41 | : (params?.initialPage ?? defaultValues.initialPage); 42 | 43 | const pageSizeRef = shouldUseDefaults 44 | ? defaultValues.pageSize 45 | : (params?.pageSize ?? defaultValues.pageSize); 46 | 47 | const newObj: Record = {}; 48 | for (const key of Object.keys(defaultValues)) { 49 | newObj[key] = shouldUseDefaults 50 | ? // @ts-ignore 51 | defaultValues[key] 52 | : // @ts-ignore 53 | (params?.[key] ?? defaultValues[key]); 54 | } 55 | 56 | return { 57 | ...newObj, 58 | initialPage: initialPageRef, 59 | pageSize: pageSizeRef 60 | } as T; 61 | }; 62 | 63 | type ArrayType = 64 | DataArray extends Array ? ElementType : never; 65 | type ExtractData = Type extends { data: infer Data } 66 | ? ArrayType 67 | : Type; 68 | 69 | type UsePagesOrInfinite = < 70 | Params extends PagesOrInfiniteOptions, 71 | FetcherReturnData extends Record, 72 | CacheKeys = Record, 73 | TConfig extends PagesOrInfiniteConfig = PagesOrInfiniteConfig 74 | >( 75 | options: Accessor<{ 76 | /** 77 | * The parameters will be passed to the fetcher 78 | */ 79 | params: Params; 80 | /** 81 | * A Promise returning function to fetch your data 82 | */ 83 | fetcher: 84 | | ((p: Params) => FetcherReturnData | Promise) 85 | | undefined; 86 | /** 87 | * Internal configuration of the hook 88 | */ 89 | config: TConfig; 90 | cacheKeys: CacheKeys; 91 | }> 92 | ) => PaginatedResources>; 93 | 94 | export const usePagesOrInfinite: UsePagesOrInfinite = (options) => { 95 | const [paginatedPage, setPaginatedPage] = createWritableMemo( 96 | () => options().params.initialPage ?? 1 97 | ); 98 | 99 | // Cache initialPage and initialPageSize until unmount 100 | const initialPage = createMemo(() => options().params.initialPage ?? 1); 101 | const pageSize = createMemo(() => options().params.pageSize ?? 10); 102 | 103 | const enabled = createMemo(() => options().config.enabled ?? true); 104 | const triggerInfinite = createMemo(() => options().config.infinite ?? false); 105 | 106 | const [queryClient] = createSignal(new QueryClient()); 107 | 108 | const pagesCacheKey = createMemo(() => ({ 109 | ...options().cacheKeys, 110 | ...options().params, 111 | initialPage: paginatedPage(), 112 | pageSize: pageSize() 113 | })); 114 | const query = createQuery( 115 | () => ({ 116 | queryKey: [pagesCacheKey()], 117 | queryFn: ({ queryKey: [cacheKeyParams] }) => { 118 | // @ts-ignore 119 | const requestParams = getDifferentKeys(cacheKeyParams, cacheKeys); 120 | // @ts-ignore 121 | return fetcher?.(requestParams); 122 | }, 123 | enabled: !triggerInfinite() && !!options().fetcher && enabled 124 | }), 125 | queryClient 126 | ); 127 | 128 | const infiniteQueryKey = createMemo(() => ({ 129 | ...options().cacheKeys, 130 | ...options().params, 131 | pageSize: pageSize() 132 | })); 133 | const infiniteQuery = createInfiniteQuery( 134 | () => ({ 135 | queryKey: [infiniteQueryKey()], 136 | queryFn: ({ pageParam, queryKey: [cacheKeyParams] }) => { 137 | const requestParams = getDifferentKeys( 138 | { 139 | initialPage: initialPage() + pageParam, 140 | ...cacheKeyParams 141 | }, 142 | // @ts-ignore 143 | cacheKeys 144 | ); 145 | // @ts-ignore 146 | return fetcher?.(requestParams); 147 | }, 148 | initialPageParam: initialPage(), 149 | getNextPageParam: (lastPage) => { 150 | if (lastPage && !lastPage.length) { 151 | return null; 152 | } 153 | }, 154 | enabled: triggerInfinite() && enabled 155 | }), 156 | queryClient 157 | ); 158 | 159 | const page = createMemo(() => { 160 | if (triggerInfinite()) { 161 | return infiniteQuery.data?.pages.length ?? 0; 162 | } 163 | return paginatedPage(); 164 | }); 165 | 166 | const data = createMemo(() => { 167 | if (triggerInfinite()) { 168 | return infiniteQuery.data?.pages.flat() ?? []; 169 | } 170 | return query.data?.data ?? []; 171 | }); 172 | 173 | const count = createMemo(() => { 174 | if (triggerInfinite()) { 175 | return ( 176 | infiniteQuery.data?.pages?.[infiniteQuery.data?.pages.length - 1] 177 | ?.total_count || 0 178 | ); 179 | } 180 | return query.data?.total_count ?? 0; 181 | }); 182 | 183 | const isLoading = createMemo(() => 184 | triggerInfinite() ? infiniteQuery.isLoading : query.isLoading 185 | ); 186 | const isFetching = createMemo(() => 187 | triggerInfinite() ? infiniteQuery.isFetching : query.isFetching 188 | ); 189 | const error = createMemo( 190 | () => 191 | (triggerInfinite() 192 | ? infiniteQuery.error 193 | : query.error) as ClerkAPIResponseError | null 194 | ); 195 | const isError = createMemo(() => !!error()); 196 | /** 197 | * Helpers 198 | */ 199 | const fetchNext = () => { 200 | if (triggerInfinite()) { 201 | void infiniteQuery.fetchNextPage(); 202 | return; 203 | } 204 | return setPaginatedPage((n) => n + 1); 205 | }; 206 | 207 | const fetchPrevious = () => { 208 | if (triggerInfinite()) { 209 | void infiniteQuery.fetchPreviousPage(); 210 | return; 211 | } 212 | return setPaginatedPage((n) => n - 1); 213 | }; 214 | 215 | const offsetCount = createMemo(() => (initialPage() - 1) * pageSize()); 216 | 217 | const pageCount = createMemo(() => 218 | Math.ceil((count() - offsetCount()) / pageSize()) 219 | ); 220 | const hasNextPage = createMemo( 221 | () => count() - offsetCount() * pageSize() > page() * pageSize() 222 | ); 223 | const hasPreviousPage = createMemo( 224 | () => (page() - 1) * pageSize() > offsetCount() * pageSize() 225 | ); 226 | 227 | const setData: CacheSetter = triggerInfinite() 228 | ? (value) => queryClient().setQueryData([infiniteQueryKey], value)! 229 | : (value) => queryClient().setQueryData([pagesCacheKey()], value)!; 230 | 231 | const revalidate = triggerInfinite() 232 | ? () => infiniteQuery.refetch() 233 | : () => query.refetch(); 234 | 235 | return { 236 | data, 237 | count, 238 | error, 239 | isLoading, 240 | isFetching, 241 | isError, 242 | page, 243 | pageCount, 244 | fetchNext, 245 | fetchPrevious, 246 | hasNextPage, 247 | hasPreviousPage, 248 | // Let the hook return type define this type 249 | setData: setData as any, 250 | // Let the hook return type define this type 251 | revalidate: revalidate as any 252 | }; 253 | }; 254 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/hooks/use-organization.ts: -------------------------------------------------------------------------------- 1 | import { eventMethodCalled } from '@clerk/shared/telemetry'; 2 | import type { 3 | ClerkPaginatedResponse, 4 | GetDomainsParams, 5 | GetInvitationsParams, 6 | GetMembershipRequestParams, 7 | GetMembersParams, 8 | OrganizationDomainResource, 9 | OrganizationInvitationResource, 10 | OrganizationMembershipRequestResource, 11 | OrganizationMembershipResource 12 | } from '@clerk/types'; 13 | import { Accessor, createEffect, createMemo } from 'solid-js'; 14 | import { useClerkInstanceContext } from '../contexts/clerk-instance'; 15 | import { useOrganizationContext } from '../contexts/organization'; 16 | import { useSessionContext } from '../contexts/session'; 17 | import type { PaginatedHookConfig } from '../types'; 18 | import { useAssertWrappedByClerkProvider } from './use-assert-wrapped-by-clerk-provider'; 19 | import { 20 | convertToSafeValues, 21 | usePagesOrInfinite 22 | } from './use-pages-or-infinite'; 23 | 24 | type UseOrganizationParams = { 25 | domains?: true | PaginatedHookConfig; 26 | membershipRequests?: true | PaginatedHookConfig; 27 | memberships?: true | PaginatedHookConfig; 28 | invitations?: true | PaginatedHookConfig; 29 | }; 30 | 31 | export const useOrganization = (params?: Accessor) => { 32 | useAssertWrappedByClerkProvider('useOrganization'); 33 | 34 | const organization = useOrganizationContext(); 35 | const session = useSessionContext(); 36 | 37 | const domainSafeValues = createMemo(() => 38 | convertToSafeValues(params?.().domains, { 39 | initialPage: 1, 40 | pageSize: 10, 41 | keepPreviousData: false, 42 | infinite: false, 43 | enrollmentMode: undefined 44 | }) 45 | ); 46 | 47 | const membershipRequestSafeValues = createMemo(() => 48 | convertToSafeValues(params?.().membershipRequests, { 49 | initialPage: 1, 50 | pageSize: 10, 51 | status: 'pending', 52 | keepPreviousData: false, 53 | infinite: false 54 | }) 55 | ); 56 | 57 | const membersSafeValues = createMemo(() => 58 | convertToSafeValues(params?.().memberships, { 59 | initialPage: 1, 60 | pageSize: 10, 61 | role: undefined, 62 | keepPreviousData: false, 63 | infinite: false 64 | }) 65 | ); 66 | 67 | const invitationsSafeValues = createMemo(() => 68 | convertToSafeValues(params?.().invitations, { 69 | initialPage: 1, 70 | pageSize: 10, 71 | status: ['pending'], 72 | keepPreviousData: false, 73 | infinite: false 74 | }) 75 | ); 76 | 77 | const clerk = useClerkInstanceContext(); 78 | 79 | createEffect(() => { 80 | clerk().telemetry?.record(eventMethodCalled('useOrganization')); 81 | }); 82 | 83 | const domainParams = createMemo(() => 84 | typeof params?.().domains === 'undefined' 85 | ? undefined 86 | : { 87 | initialPage: domainSafeValues().initialPage, 88 | pageSize: domainSafeValues().pageSize, 89 | enrollmentMode: domainSafeValues().enrollmentMode 90 | } 91 | ); 92 | 93 | const membershipRequestParams = createMemo(() => 94 | typeof params?.().membershipRequests === 'undefined' 95 | ? undefined 96 | : { 97 | initialPage: membershipRequestSafeValues().initialPage, 98 | pageSize: membershipRequestSafeValues().pageSize, 99 | status: membershipRequestSafeValues().status 100 | } 101 | ); 102 | 103 | const membersParams = createMemo(() => 104 | typeof params?.().memberships === 'undefined' 105 | ? undefined 106 | : { 107 | initialPage: membersSafeValues().initialPage, 108 | pageSize: membersSafeValues().pageSize, 109 | role: membersSafeValues().role 110 | } 111 | ); 112 | 113 | const invitationsParams = createMemo(() => 114 | typeof params?.().invitations === 'undefined' 115 | ? undefined 116 | : { 117 | initialPage: invitationsSafeValues().initialPage, 118 | pageSize: invitationsSafeValues().pageSize, 119 | status: invitationsSafeValues().status 120 | } 121 | ); 122 | 123 | const domains = usePagesOrInfinite< 124 | GetDomainsParams, 125 | ClerkPaginatedResponse 126 | >(() => ({ 127 | params: { 128 | ...domainParams() 129 | }, 130 | fetcher: organization()?.getDomains, 131 | config: { 132 | keepPreviousData: domainSafeValues().keepPreviousData, 133 | infinite: domainSafeValues().infinite, 134 | enabled: !!domainParams() 135 | }, 136 | cacheKeys: { 137 | type: 'domains', 138 | organizationId: organization()?.id 139 | } 140 | })); 141 | 142 | const membershipRequests = usePagesOrInfinite< 143 | GetMembershipRequestParams, 144 | ClerkPaginatedResponse 145 | >(() => ({ 146 | params: { 147 | ...membershipRequestParams() 148 | }, 149 | fetcher: organization()?.getMembershipRequests, 150 | config: { 151 | keepPreviousData: membershipRequestSafeValues().keepPreviousData, 152 | infinite: membershipRequestSafeValues().infinite, 153 | enabled: !!membershipRequestParams() 154 | }, 155 | cacheKeys: { 156 | type: 'membershipRequests', 157 | organizationId: organization()?.id 158 | } 159 | })); 160 | 161 | const memberships = usePagesOrInfinite< 162 | GetMembersParams, 163 | ClerkPaginatedResponse 164 | >(() => ({ 165 | params: membersParams() || {}, 166 | fetcher: organization()?.getMemberships, 167 | config: { 168 | keepPreviousData: membersSafeValues().keepPreviousData, 169 | infinite: membersSafeValues().infinite, 170 | enabled: !!membersParams() 171 | }, 172 | cacheKeys: { 173 | type: 'members', 174 | organizationId: organization()?.id 175 | } 176 | })); 177 | 178 | const invitations = usePagesOrInfinite< 179 | GetInvitationsParams, 180 | ClerkPaginatedResponse 181 | >(() => ({ 182 | params: { 183 | ...invitationsParams() 184 | }, 185 | fetcher: organization()?.getInvitations, 186 | config: { 187 | keepPreviousData: invitationsSafeValues().keepPreviousData, 188 | infinite: invitationsSafeValues().infinite, 189 | enabled: !!invitationsParams() 190 | }, 191 | cacheKeys: { 192 | type: 'invitations', 193 | organizationId: organization()?.id 194 | } 195 | })); 196 | 197 | const isLoaded = createMemo(() => organization() === undefined); 198 | 199 | return { 200 | isLoaded, 201 | organization, 202 | membership: createMemo(() => { 203 | if (isLoaded()) { 204 | if (organization()) { 205 | return getCurrentOrganizationMembership( 206 | session()!.user!.organizationMemberships, 207 | organization()!.id 208 | ); 209 | } else { 210 | return null; 211 | } 212 | } 213 | return undefined; 214 | }), 215 | domains: createMemo(() => { 216 | if (isLoaded()) { 217 | if (organization()) { 218 | return domains; 219 | } else { 220 | return null; 221 | } 222 | } 223 | return undefined; 224 | }), 225 | membershipRequests: createMemo(() => { 226 | if (isLoaded()) { 227 | if (organization()) { 228 | return membershipRequests; 229 | } else { 230 | return null; 231 | } 232 | } 233 | return undefined; 234 | }), 235 | memberships: createMemo(() => { 236 | if (isLoaded()) { 237 | if (organization()) { 238 | return memberships; 239 | } else { 240 | return null; 241 | } 242 | } 243 | return undefined; 244 | }), 245 | invitations: createMemo(() => { 246 | if (isLoaded()) { 247 | if (organization()) { 248 | return invitations; 249 | } else { 250 | return null; 251 | } 252 | } 253 | return undefined; 254 | }) 255 | }; 256 | }; 257 | 258 | function getCurrentOrganizationMembership( 259 | organizationMemberships: OrganizationMembershipResource[], 260 | activeOrganizationId: string 261 | ) { 262 | return organizationMemberships.find( 263 | (organizationMembership) => 264 | organizationMembership.organization.id === activeOrganizationId 265 | ); 266 | } 267 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | clerk-solidjs 4 | 5 | --- 6 | 7 | _This is an unofficial community-led port of the [Clerk React SDK](https://www.npmjs.com/package/@clerk/clerk-react) for [SolidJS](https://solidjs.com) and [SolidStart](https://start.solidjs.com)._ 8 | 9 | [![Clerk documentation](https://img.shields.io/badge/documentation-clerk-lavender.svg?style=for-the-badge&logo=clerk)](https://clerk.com/docs?utm_source=github&utm_medium=clerk_solidjs) 10 | 11 | ### Released on NPM 12 | 13 | [![NPM version](https://img.shields.io/npm/v/clerk-solidjs.svg?style=for-the-badge&logo=npm)](https://www.npmjs.com/package/clerk-solidjs) 14 | [![NPM Downloads](https://img.shields.io/npm/dm/clerk-solidjs?style=for-the-badge) 15 | ](https://www.npmjs.com/package/clerk-solidjs) 16 | 17 | ### Maintained on GitHub 18 | 19 | [![GitHub stars](https://img.shields.io/github/stars/spirit-led-software/clerk-solidjs.svg?style=for-the-badge)](https://github.com/spirit-led-software/clerk-solidjs/stargazers) 20 | [![GitHub watchers](https://img.shields.io/github/watchers/spirit-led-software/clerk-solidjs.svg?style=for-the-badge)](https://github.com/spirit-led-software/clerk-solidjs/watchers) 21 | [![GitHub license](https://img.shields.io/github/license/spirit-led-software/clerk-solidjs.svg?style=for-the-badge)](https://github.com/spirit-led-software/clerk-solidjs/blob/master/LICENSE) 22 | [![GitHub forks](https://img.shields.io/github/forks/spirit-led-software/clerk-solidjs.svg?style=for-the-badge)](https://github.com/spirit-led-software/clerk-solidjs/forks) 23 | [![GitHub issues](https://img.shields.io/github/issues/spirit-led-software/clerk-solidjs.svg?style=for-the-badge)](https://github.com/spirit-led-software/clerk-solidjs/issues) 24 | [![GitHub pull requests](https://img.shields.io/github/issues-pr/spirit-led-software/clerk-solidjs.svg?style=for-the-badge)](https://github.com/spirit-led-software/clerk-solidjs/pulls) 25 | [![GitHub contributors](https://img.shields.io/github/contributors/spirit-led-software/clerk-solidjs.svg?style=for-the-badge)](https://github.com/spirit-led-software/clerk-solidjs/graphs/contributors) 26 | ![GitHub last commit](https://img.shields.io/github/last-commit/spirit-led-software/clerk-solidjs.svg?style=for-the-badge) 27 | 28 | [![pnpm](https://img.shields.io/badge/maintained%20with-pnpm-yellow.svg?style=for-the-badge&logo=pnpm)](https://pnpm.io/) 29 | [![turborepo](https://img.shields.io/badge/built%20with-turborepo-cc00ff.svg?style=for-the-badge&logo=turborepo)](https://turborepo.org/) 30 | 31 |
32 | 33 | --- 34 | 35 | ## Overview 36 | 37 | Clerk is the easiest way to add authentication and user management to your application. Add sign up, sign in, and profile management to your application in minutes. 38 | 39 | ## Features 40 | 41 | This project has near-complete feature parity with @clerk/clerk-react: 42 | ✔ SSR support 43 | ✔ [Components](https://clerk.com/docs/components/overview) 44 | ✔ [Hooks](https://clerk.com/docs/references/react/use-user)\* 45 | 46 | Missing features for SolidJS: 47 | ✖ [Custom pages for UI components](https://clerk.com/docs/components/customization/user-profile) 48 | 49 | Plus additional features for SolidStart: 50 | ✔ [Middleware](#middleware) 51 | ✔ [Server-side `auth()` helper](#the-auth-helper) 52 | 53 | \* = Hooks with parameters have been altered to use the `Accessor` type for reactivity. For example: 54 | 55 | ```ts 56 | useOrganizationList(() => ({ userMemberships: { infinite: true } })); 57 | ``` 58 | 59 | ## Getting Started 60 | 61 | ### Prerequisites 62 | 63 | - SolidJS `>=1` 64 | - SolidStart `>=1` 65 | - Node.js `>=18` or later 66 | 67 | ### Installation 68 | 69 | ```sh 70 | npm install clerk-solidjs 71 | # or 72 | yarn add clerk-solidjs 73 | # or 74 | pnpm add clerk-solidjs 75 | # or 76 | bun add clerk-solidjs 77 | ``` 78 | 79 | ### Build 80 | 81 | ```sh 82 | pnpm run build 83 | ``` 84 | 85 | To build the package in watch mode, run the following: 86 | 87 | ```sh 88 | pnpm run dev 89 | ``` 90 | 91 | ## Usage 92 | 93 | Clerk requires your application to be wrapped in the `` context. 94 | 95 | If using Vite, set `VITE_CLERK_PUBLISHABLE_KEY` to your Publishable key in your `.env.local` file to make the environment variable accessible on `process.env` and pass it as the `publishableKey` prop. 96 | 97 | ```tsx 98 | // App.tsx 99 | 100 | import { Router } from '@solidjs/router'; 101 | import { FileRoutes } from '@solidjs/start/router'; 102 | import { Suspense } from 'solid-js/web'; 103 | import { ClerkProvider } from 'clerk-solidjs'; 104 | 105 | import './app.css'; 106 | 107 | export default function App() { 108 | return ( 109 | ( 111 | 114 | {props.children} 115 | 116 | )} 117 | > 118 | 119 | 120 | ); 121 | } 122 | ``` 123 | 124 | Once you have wrapped your app in `` you can access hooks and components. 125 | 126 | ```tsx 127 | import { 128 | SignedIn, 129 | SignedOut, 130 | SignInButton, 131 | UserButton, 132 | useAuth, 133 | ClerkLoading, 134 | ClerkLoaded 135 | } from 'clerk-solidjs'; 136 | 137 | export default function MyComponent() { 138 | const { userId } = useAuth(); 139 | 140 | return ( 141 |
142 | 143 |

Loading...

144 |
145 | 146 | 147 | 148 |

Welcome, {userId()}

149 |
150 | 151 | 152 | 153 |
154 |
155 | ); 156 | } 157 | ``` 158 | 159 | ### Middleware 160 | 161 | Clerk provides the `clerkMiddleware` helper function which can be used in `solid-start` middleware. 162 | 163 | See [SolidStart middleware](https://docs.solidjs.com/solid-start/advanced/middleware) for how to enable middleware. 164 | 165 | ```ts 166 | // middleware.ts 167 | 168 | import { createMiddleware } from '@solidjs/start/middleware'; 169 | import { clerkMiddleware } from 'clerk-solidjs/start/server'; 170 | 171 | export default createMiddleware({ 172 | onRequest: [ 173 | clerkMiddleware({ 174 | publishableKey: process.env.VITE_CLERK_PUBLISHABLE_KEY, 175 | secretKey: process.env.CLERK_SECRET_KEY 176 | }) 177 | // ... other middleware 178 | ] 179 | }); 180 | ``` 181 | 182 | ### The `auth()` Helper 183 | 184 | Once your have the `clerkMiddleware` middleware enabled, you can use the `auth()` helper to access the `AuthReturn` object. 185 | 186 | ```ts 187 | import { auth } from 'clerk-solidjs/start/server'; 188 | 189 | async function myProtectedServerFunction() { 190 | 'use server'; 191 | const { userId } = auth(); 192 | if (!userId) { 193 | throw new Error('You must be signed in'); 194 | } 195 | 196 | // ... 197 | } 198 | ``` 199 | 200 | If you would like the access the auth object from `event.locals` directly, you must add this to your globals.d.ts file: 201 | 202 | ```ts 203 | /// 204 | import { AuthObject } from '@clerk/backend'; 205 | 206 | declare module '@solidjs/start/server' { 207 | export interface RequestEventLocals { 208 | auth: AuthObject; 209 | } 210 | } 211 | 212 | export {}; 213 | ``` 214 | 215 | ## Examples 216 | 217 | ### Basic 218 | 219 | [Basic example](/examples/basic) 220 | 221 | ## Support 222 | 223 | You can get in touch in any of the following ways: 224 | 225 | - Create a [GitHub Issue](https://github.com/spirit-led-software/clerk-solidjs/issues) 226 | - Create a [GitHub Discussion](https://github.com/spirit-led-software/clerk-solidjs/discussions) 227 | 228 | ## Contributing 229 | 230 | We're open to all community contributions! If you'd like to contribute in any way, please read [our contribution guidelines](https://github.com/spirit-led-software/clerk-solidjs/blob/master/docs/CONTRIBUTING.md). 231 | 232 | ## Security 233 | 234 | `clerk-solidjs` follows good practices of security, but 100% security cannot be assured. 235 | 236 | `clerk-solidjs` is provided **"as is"** without any **warranty**. Use at your own risk. 237 | 238 | _For more information and to report security issues, please refer to our [security documentation](https://github.com/spirit-led-software/clerk-solidjs/blob/master/docs/SECURITY.md)._ 239 | 240 | ## License 241 | 242 | This project is licensed under the **MIT license**. 243 | 244 | See [LICENSE](https://github.com/spirit-led-software/clerk-solidjs/blob/master/LICENSE) for more information. 245 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/contexts/__tests__/clerk.test.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | csCZ, 3 | deDE, 4 | enUS, 5 | esES, 6 | frFR, 7 | itIT, 8 | jaJP, 9 | koKR, 10 | ptBR, 11 | ruRU, 12 | skSK, 13 | svSE, 14 | trTR, 15 | ukUA 16 | } from '@clerk/localizations'; 17 | import { dark } from '@clerk/themes'; 18 | import { expectTypeOf } from 'expect-type'; 19 | 20 | import { describe, it } from 'vitest'; 21 | import type { ClerkProvider } from '../clerk'; 22 | 23 | type ClerkProviderProps = Parameters[0]; 24 | 25 | describe('ClerkProvider', () => { 26 | describe('Type tests', () => { 27 | describe('publishableKey', () => { 28 | it('expects a publishableKey and children as the minimum accepted case', () => { 29 | expectTypeOf({ 30 | publishableKey: 'test', 31 | children: '' 32 | }).toMatchTypeOf(); 33 | }); 34 | 35 | it('errors if no publishableKey', () => { 36 | expectTypeOf({ children: '' }).not.toMatchTypeOf(); 37 | }); 38 | }); 39 | }); 40 | 41 | describe('Multi domain', () => { 42 | const defaultProps = { publishableKey: 'test', children: '' }; 43 | 44 | it('proxyUrl (primary app)', () => { 45 | expectTypeOf({ 46 | ...defaultProps, 47 | proxyUrl: 'test' 48 | }).toMatchTypeOf(); 49 | }); 50 | 51 | it('proxyUrl + isSatellite (satellite app)', () => { 52 | expectTypeOf({ 53 | ...defaultProps, 54 | proxyUrl: 'test', 55 | isSatellite: true 56 | }).toMatchTypeOf(); 57 | }); 58 | 59 | it('domain + isSatellite (satellite app)', () => { 60 | expectTypeOf({ 61 | ...defaultProps, 62 | domain: 'test', 63 | isSatellite: true 64 | }).toMatchTypeOf(); 65 | }); 66 | 67 | it('only domain is not allowed', () => { 68 | expectTypeOf({ 69 | ...defaultProps, 70 | domain: 'test' 71 | }).not.toMatchTypeOf(); 72 | }); 73 | 74 | it('only isSatellite is not allowed', () => { 75 | expectTypeOf({ 76 | ...defaultProps, 77 | isSatellite: true 78 | }).not.toMatchTypeOf(); 79 | }); 80 | 81 | it('proxyUrl + domain is not allowed', () => { 82 | expectTypeOf({ 83 | ...defaultProps, 84 | proxyUrl: 'test', 85 | domain: 'test' 86 | }).not.toMatchTypeOf(); 87 | }); 88 | 89 | it('proxyUrl + domain + isSatellite is not allowed', () => { 90 | expectTypeOf({ 91 | ...defaultProps, 92 | proxyUrl: 'test', 93 | domain: 'test', 94 | isSatellite: true 95 | }).not.toMatchTypeOf(); 96 | }); 97 | }); 98 | 99 | describe('clerkJSVariant', () => { 100 | const defaultProps = { publishableKey: 'test', children: '' }; 101 | 102 | it('is either headless or empty', () => { 103 | expectTypeOf({ 104 | ...defaultProps, 105 | clerkJSVariant: 'headless' as const 106 | }).toMatchTypeOf(); 107 | expectTypeOf({ 108 | ...defaultProps, 109 | clerkJSVariant: '' as const 110 | }).toMatchTypeOf(); 111 | expectTypeOf({ 112 | ...defaultProps, 113 | clerkJSVariant: undefined 114 | }).toMatchTypeOf(); 115 | expectTypeOf({ 116 | ...defaultProps, 117 | clerkJSVariant: 'test' 118 | }).not.toMatchTypeOf(); 119 | }); 120 | }); 121 | 122 | describe('appearance', () => { 123 | const defaultProps = { publishableKey: 'test', children: '' }; 124 | 125 | it('exists as a prop', () => { 126 | expectTypeOf({ 127 | ...defaultProps, 128 | appearance: {} 129 | }).toMatchTypeOf(); 130 | }); 131 | 132 | it('includes variables, elements, layout baseTheme', () => { 133 | expectTypeOf({ 134 | ...defaultProps, 135 | appearance: { elements: {}, variables: {}, layout: {}, baseTheme: dark } 136 | }).toMatchTypeOf(); 137 | }); 138 | 139 | it('errors if a non existent key is provided', () => { 140 | expectTypeOf({ 141 | ...defaultProps, 142 | appearance: { variables: { nonExistentKey: '' } } 143 | }).not.toMatchTypeOf(); 144 | 145 | expectTypeOf({ 146 | ...defaultProps, 147 | appearance: { layout: { nonExistentKey: '' } } 148 | }).not.toMatchTypeOf(); 149 | 150 | // expectTypeOf({ 151 | // ...defaultProps, 152 | // appearance: { elements: { nonExistentKey: '' } }, 153 | // }).not.toMatchTypeOf(); 154 | }); 155 | }); 156 | 157 | describe('localization', () => { 158 | const defaultProps = { publishableKey: 'test', children: '' }; 159 | 160 | it('exists as a prop', () => { 161 | expectTypeOf({ 162 | ...defaultProps, 163 | localization: {} 164 | }).toMatchTypeOf(); 165 | }); 166 | 167 | it('errors if a non existent key is provided', () => { 168 | expectTypeOf({ 169 | ...defaultProps, 170 | localization: { a: 'test' } 171 | }).not.toMatchTypeOf(); 172 | 173 | expectTypeOf({ 174 | ...defaultProps, 175 | localization: { signUp: { start: 'test' } } 176 | }).not.toMatchTypeOf(); 177 | }); 178 | 179 | it('works with all our prebuilt localizations', () => { 180 | expectTypeOf({ 181 | ...defaultProps, 182 | localization: deDE 183 | }).toMatchTypeOf(); 184 | 185 | expectTypeOf({ 186 | ...defaultProps, 187 | localization: frFR 188 | }).toMatchTypeOf(); 189 | 190 | expectTypeOf({ 191 | ...defaultProps, 192 | localization: enUS 193 | }).toMatchTypeOf(); 194 | 195 | expectTypeOf({ 196 | ...defaultProps, 197 | localization: esES 198 | }).toMatchTypeOf(); 199 | 200 | expectTypeOf({ 201 | ...defaultProps, 202 | localization: itIT 203 | }).toMatchTypeOf(); 204 | 205 | expectTypeOf({ 206 | ...defaultProps, 207 | localization: ptBR 208 | }).toMatchTypeOf(); 209 | 210 | expectTypeOf({ 211 | ...defaultProps, 212 | localization: ruRU 213 | }).toMatchTypeOf(); 214 | 215 | expectTypeOf({ 216 | ...defaultProps, 217 | localization: svSE 218 | }).toMatchTypeOf(); 219 | 220 | expectTypeOf({ 221 | ...defaultProps, 222 | localization: trTR 223 | }).toMatchTypeOf(); 224 | 225 | expectTypeOf({ 226 | ...defaultProps, 227 | localization: jaJP 228 | }).toMatchTypeOf(); 229 | 230 | expectTypeOf({ 231 | ...defaultProps, 232 | localization: jaJP 233 | }).toMatchTypeOf(); 234 | 235 | expectTypeOf({ 236 | ...defaultProps, 237 | localization: csCZ 238 | }).toMatchTypeOf(); 239 | 240 | expectTypeOf({ 241 | ...defaultProps, 242 | localization: koKR 243 | }).toMatchTypeOf(); 244 | 245 | expectTypeOf({ 246 | ...defaultProps, 247 | localization: skSK 248 | }).toMatchTypeOf(); 249 | 250 | expectTypeOf({ 251 | ...defaultProps, 252 | localization: ukUA 253 | }).toMatchTypeOf(); 254 | }); 255 | 256 | it('is able to receive multiple localizations', () => { 257 | expectTypeOf({ 258 | ...defaultProps, 259 | localization: { ...frFR, ...deDE } 260 | }).toMatchTypeOf(); 261 | }); 262 | }); 263 | 264 | describe('children', () => { 265 | it('errors if no children', () => { 266 | expectTypeOf({ 267 | publishableKey: 'test' 268 | }).not.toMatchTypeOf(); 269 | }); 270 | }); 271 | 272 | describe('navigation options', () => { 273 | it('expects both routerPush & routerReplace to pass', () => { 274 | expectTypeOf({ 275 | publishableKey: 'test', 276 | children: '', 277 | routerPush: () => {}, 278 | routerReplace: () => {} 279 | }).toMatchTypeOf(); 280 | }); 281 | 282 | it('errors if one of routerPush / routerReplace is passed', () => { 283 | expectTypeOf({ 284 | publishableKey: 'test', 285 | children: '', 286 | routerPush: () => {} 287 | }).not.toMatchTypeOf(); 288 | }); 289 | }); 290 | }); 291 | -------------------------------------------------------------------------------- /packages/clerk-solidjs/src/components/ui-components.tsx: -------------------------------------------------------------------------------- 1 | import { logErrorInDevMode } from '@clerk/shared'; 2 | import type { 3 | CreateOrganizationProps, 4 | GoogleOneTapProps, 5 | OrganizationListProps, 6 | OrganizationProfileProps, 7 | OrganizationSwitcherProps, 8 | SignInProps, 9 | SignUpProps, 10 | UserButtonProps, 11 | UserProfileProps, 12 | Without 13 | } from '@clerk/types'; 14 | import { 15 | children, 16 | Component, 17 | createEffect, 18 | createSignal, 19 | onCleanup, 20 | onMount, 21 | ParentProps, 22 | splitProps 23 | } from 'solid-js'; 24 | import { 25 | organizationProfileLinkRenderedError, 26 | organizationProfilePageRenderedError, 27 | userProfileLinkRenderedError, 28 | userProfilePageRenderedError 29 | } from '../errors/messages'; 30 | import { IsomorphicClerk } from '../isomorphic-clerk'; 31 | import type { 32 | MountProps, 33 | OpenProps, 34 | OrganizationProfileLinkProps, 35 | OrganizationProfilePageProps, 36 | UserProfileLinkProps, 37 | UserProfilePageProps, 38 | WithClerkProp 39 | } from '../types'; 40 | import { withClerk } from './with-clerk'; 41 | 42 | type UserProfileExportType = typeof _UserProfile & { 43 | Page: typeof UserProfilePage; 44 | Link: typeof UserProfileLink; 45 | }; 46 | 47 | type UserButtonExportType = typeof _UserButton & { 48 | UserProfilePage: typeof UserProfilePage; 49 | UserProfileLink: typeof UserProfileLink; 50 | }; 51 | 52 | type UserButtonPropsWithoutCustomPages = Without< 53 | UserButtonProps, 54 | 'userProfileProps' 55 | > & { 56 | userProfileProps?: Pick< 57 | UserProfileProps, 58 | 'additionalOAuthScopes' | 'appearance' 59 | >; 60 | }; 61 | 62 | type OrganizationProfileExportType = typeof _OrganizationProfile & { 63 | Page: typeof OrganizationProfilePage; 64 | Link: typeof OrganizationProfileLink; 65 | }; 66 | 67 | type OrganizationSwitcherExportType = typeof _OrganizationSwitcher & { 68 | OrganizationProfilePage: typeof OrganizationProfilePage; 69 | OrganizationProfileLink: typeof OrganizationProfileLink; 70 | }; 71 | 72 | type OrganizationSwitcherPropsWithoutCustomPages = Without< 73 | OrganizationSwitcherProps, 74 | 'organizationProfileProps' 75 | > & { 76 | organizationProfileProps?: Pick; 77 | }; 78 | 79 | const isMountProps = (props: any): props is MountProps => { 80 | return 'mount' in props; 81 | }; 82 | 83 | const isOpenProps = (props: any): props is OpenProps => { 84 | return 'open' in props; 85 | }; 86 | 87 | const Portal: Component = (props) => { 88 | const [portalRef, setPortalRef] = createSignal(); 89 | 90 | const componentProps = () => props.props; 91 | 92 | createEffect(() => { 93 | const ref = portalRef(); 94 | if (ref && isMountProps(props)) { 95 | props.updateProps({ 96 | node: ref, 97 | props: componentProps() 98 | }); 99 | } 100 | }); 101 | 102 | onMount(() => { 103 | const ref = portalRef(); 104 | if (ref) { 105 | if (isMountProps(props)) { 106 | props.mount(ref, componentProps()); 107 | } else if (isOpenProps(props)) { 108 | props.open(componentProps()); 109 | } 110 | } 111 | }); 112 | 113 | onCleanup(() => { 114 | const ref = portalRef(); 115 | if (ref) { 116 | if (isMountProps(props)) { 117 | props.unmount(ref); 118 | } else if (isOpenProps(props)) { 119 | props.close(); 120 | } 121 | } 122 | }); 123 | 124 | return
; 125 | }; 126 | 127 | export const SignIn = withClerk((props: WithClerkProp) => { 128 | const [local, rest] = splitProps(props, ['clerk']); 129 | return ( 130 | 138 | ); 139 | }, 'SignIn'); 140 | 141 | export const Waitlist = withClerk((props: WithClerkProp) => { 142 | const [local, rest] = splitProps(props, ['clerk']); 143 | return ( 144 | 152 | ); 153 | }, 'Waitlist'); 154 | 155 | export const SignUp = withClerk((props: WithClerkProp) => { 156 | const [local, rest] = splitProps(props, ['clerk']); 157 | return ( 158 | 166 | ); 167 | }, 'SignUp'); 168 | 169 | export function UserProfilePage(props: ParentProps) { 170 | logErrorInDevMode(userProfilePageRenderedError); 171 | return children(() => props.children); 172 | } 173 | 174 | export function UserProfileLink(props: ParentProps) { 175 | logErrorInDevMode(userProfileLinkRenderedError); 176 | return children(() => props.children); 177 | } 178 | 179 | const _UserProfile = withClerk( 180 | ( 181 | props: WithClerkProp>> 182 | ) => { 183 | const [local, rest] = splitProps(props, ['clerk']); 184 | return ( 185 | 193 | ); 194 | }, 195 | 'UserProfile' 196 | ); 197 | 198 | export const UserProfile: UserProfileExportType = Object.assign(_UserProfile, { 199 | Page: UserProfilePage, 200 | Link: UserProfileLink 201 | }); 202 | 203 | const _UserButton = withClerk( 204 | (props: WithClerkProp>) => { 205 | const [local, rest] = splitProps(props, ['clerk']); 206 | return ( 207 | 215 | ); 216 | }, 217 | 'UserButton' 218 | ); 219 | 220 | export const UserButton: UserButtonExportType = Object.assign(_UserButton, { 221 | UserProfilePage, 222 | UserProfileLink 223 | }); 224 | 225 | export function OrganizationProfilePage( 226 | props: ParentProps 227 | ) { 228 | logErrorInDevMode(organizationProfilePageRenderedError); 229 | return children(() => props.children); 230 | } 231 | 232 | export function OrganizationProfileLink( 233 | props: ParentProps 234 | ) { 235 | logErrorInDevMode(organizationProfileLinkRenderedError); 236 | return children(() => props.children); 237 | } 238 | 239 | const _OrganizationProfile = withClerk( 240 | ( 241 | props: WithClerkProp< 242 | ParentProps> 243 | > 244 | ) => { 245 | const [local, rest] = splitProps(props, ['clerk']); 246 | return ( 247 | 255 | ); 256 | }, 257 | 'OrganizationProfile' 258 | ); 259 | 260 | export const OrganizationProfile: OrganizationProfileExportType = Object.assign( 261 | _OrganizationProfile, 262 | { 263 | Page: OrganizationProfilePage, 264 | Link: OrganizationProfileLink 265 | } 266 | ); 267 | 268 | export const CreateOrganization = withClerk( 269 | (props: WithClerkProp) => { 270 | const [local, rest] = splitProps(props, ['clerk']); 271 | return ( 272 | 280 | ); 281 | }, 282 | 'CreateOrganization' 283 | ); 284 | 285 | const _OrganizationSwitcher = withClerk( 286 | ( 287 | props: WithClerkProp< 288 | ParentProps 289 | > 290 | ) => { 291 | const [local, rest] = splitProps(props, ['clerk']); 292 | 293 | return ( 294 | 302 | ); 303 | }, 304 | 'OrganizationSwitcher' 305 | ); 306 | 307 | export const OrganizationSwitcher: OrganizationSwitcherExportType = 308 | Object.assign(_OrganizationSwitcher, { 309 | OrganizationProfilePage, 310 | OrganizationProfileLink 311 | }); 312 | 313 | export const OrganizationList = withClerk( 314 | (props: WithClerkProp) => { 315 | const [local, rest] = splitProps(props, ['clerk']); 316 | return ( 317 | 325 | ); 326 | }, 327 | 'OrganizationList' 328 | ); 329 | 330 | export const GoogleOneTap = withClerk( 331 | (props: WithClerkProp) => { 332 | const [local, rest] = splitProps(props, ['clerk']); 333 | return ( 334 | 339 | ); 340 | }, 341 | 'GoogleOneTap' 342 | ); 343 | --------------------------------------------------------------------------------