From 358f30d2f0c8921dacf8bd92a2457c91b9bfc78e Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Fri, 30 Aug 2024 15:18:40 -0500 Subject: [PATCH 1/9] wip: elements rendering in clerk-js --- .../clerk-js/src/ui/lazyModules/providers.tsx | 5 ++- packages/elements/package.json | 1 - .../elements/src/internals/constants/index.ts | 10 +++-- .../machines/shared/shared.actors.ts | 2 +- .../utils/inspector/browser/index.ts | 5 ++- .../utils/inspector/console/index.ts | 8 ++-- .../elements/src/react/common/form/input.tsx | 2 +- .../elements/src/react/common/loading.tsx | 2 +- .../src/react/hooks/use-password.hook.ts | 2 +- .../hooks/use-third-party-provider.hook.ts | 2 +- packages/elements/src/react/sign-in/root.tsx | 20 +++++---- packages/elements/src/react/sign-in/step.tsx | 2 +- packages/elements/src/react/sign-up/root.tsx | 20 +++++---- packages/elements/src/react/sign-up/step.tsx | 2 +- .../src/app-router/client/ClerkProvider.tsx | 42 ++++++++++++++++++- packages/shared/src/react/index.ts | 4 +- packages/shared/src/router.ts | 9 +++- packages/shared/src/router/react.tsx | 18 +++++++- 18 files changed, 118 insertions(+), 38 deletions(-) diff --git a/packages/clerk-js/src/ui/lazyModules/providers.tsx b/packages/clerk-js/src/ui/lazyModules/providers.tsx index 70c3dcf56e8..bf50c25a38d 100644 --- a/packages/clerk-js/src/ui/lazyModules/providers.tsx +++ b/packages/clerk-js/src/ui/lazyModules/providers.tsx @@ -5,6 +5,7 @@ import type { FlowMetadata } from '../elements'; import type { ThemableCssProp } from '../styledSystem'; import type { ClerkComponentName } from './components'; import { ClerkComponents } from './components'; +import { ClerkHostRouterContext } from '@clerk/shared/router'; const CoreClerkContextWrapper = lazy(() => import('../contexts').then(m => ({ default: m.CoreClerkContextWrapper }))); const EnvironmentProvider = lazy(() => import('../contexts').then(m => ({ default: m.EnvironmentProvider }))); @@ -23,7 +24,9 @@ export const LazyProviders = (props: LazyProvidersProps) => { return ( - {props.children} + + {props.children} + ); diff --git a/packages/elements/package.json b/packages/elements/package.json index 4dde6998a73..1591ed1f020 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -94,7 +94,6 @@ "typescript": "*" }, "peerDependencies": { - "@clerk/clerk-react": "^5.0.0", "@clerk/shared": "^2.0.0", "next": "^13.5.4 || ^14.0.3 || ^15.0.0-rc", "react": "^18.0.0 || ^19.0.0-beta", diff --git a/packages/elements/src/internals/constants/index.ts b/packages/elements/src/internals/constants/index.ts index f4777a9ee2c..01541049fc8 100644 --- a/packages/elements/src/internals/constants/index.ts +++ b/packages/elements/src/internals/constants/index.ts @@ -2,10 +2,12 @@ export const SSO_CALLBACK_PATH_ROUTE = '/sso-callback'; export const CHOOSE_SESSION_PATH_ROUTE = '/choose'; export const MAGIC_LINK_VERIFY_PATH_ROUTE = '/verify'; -export const SIGN_IN_DEFAULT_BASE_PATH = - process.env.CLERK_SIGN_IN_URL ?? process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL ?? '/sign-in'; -export const SIGN_UP_DEFAULT_BASE_PATH = - process.env.CLERK_SIGN_UP_URL ?? process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL ?? '/sign-up'; +export const SIGN_IN_DEFAULT_BASE_PATH = '/sign-in'; +export const SIGN_UP_DEFAULT_BASE_PATH = '/sign-up'; +// export const SIGN_IN_DEFAULT_BASE_PATH = +// process.env.CLERK_SIGN_IN_URL ?? process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL ?? '/sign-in'; +// export const SIGN_UP_DEFAULT_BASE_PATH = +// process.env.CLERK_SIGN_UP_URL ?? process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL ?? '/sign-up'; // The version that Next added support for the window.history.pushState and replaceState APIs. // ref: https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate diff --git a/packages/elements/src/internals/machines/shared/shared.actors.ts b/packages/elements/src/internals/machines/shared/shared.actors.ts index 61f7692e40a..447ef67e32e 100644 --- a/packages/elements/src/internals/machines/shared/shared.actors.ts +++ b/packages/elements/src/internals/machines/shared/shared.actors.ts @@ -10,7 +10,7 @@ export const clerkLoader = fromCallback(({ sen if (clerk.loaded) { reportLoaded(); } else if ('addOnLoaded' in clerk) { - // @ts-expect-error - Expects `addOnLoaded` from @clerk/clerk-react's IsomorphicClerk. + // @ts-expect-error - Expects `addOnLoaded` from @clerk/shared/react's IsomorphicClerk. clerk.addOnLoaded(reportLoaded); } else { sendBack({ type: 'ERROR', message: 'Clerk client could not be loaded' }); diff --git a/packages/elements/src/internals/utils/inspector/browser/index.ts b/packages/elements/src/internals/utils/inspector/browser/index.ts index ba3d0ff2d09..222d13d5d29 100644 --- a/packages/elements/src/internals/utils/inspector/browser/index.ts +++ b/packages/elements/src/internals/utils/inspector/browser/index.ts @@ -1,4 +1,4 @@ -import { isTruthy } from '@clerk/shared/underscore'; +// import { isTruthy } from '@clerk/shared/underscore'; import { createBrowserInspector } from '@statelyai/inspect'; export const getInspector = () => { @@ -6,7 +6,8 @@ export const getInspector = () => { __DEV__ && typeof window !== 'undefined' && process.env.NODE_ENV === 'development' && - isTruthy(process.env.NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG_UI ?? process.env.CLERK_ELEMENTS_DEBUG_UI) + false + // isTruthy(process.env.NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG_UI ?? process.env.CLERK_ELEMENTS_DEBUG_UI) ) { const { inspect } = createBrowserInspector({ autoStart: true, diff --git a/packages/elements/src/internals/utils/inspector/console/index.ts b/packages/elements/src/internals/utils/inspector/console/index.ts index 62385651a6d..fef96cbffbc 100644 --- a/packages/elements/src/internals/utils/inspector/console/index.ts +++ b/packages/elements/src/internals/utils/inspector/console/index.ts @@ -1,4 +1,4 @@ -import { isTruthy } from '@clerk/shared/underscore'; +// import { isTruthy } from '@clerk/shared/underscore'; import { createConsoleInspector } from './console'; @@ -6,11 +6,13 @@ export function getInspector() { if ( __DEV__ && process.env.NODE_ENV === 'development' && - isTruthy(process.env.NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG ?? process.env.CLERK_ELEMENTS_DEBUG) + false + // isTruthy(process.env.NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG ?? process.env.CLERK_ELEMENTS_DEBUG) ) { return createConsoleInspector({ enabled: true, - debugServer: isTruthy(process.env.CLERK_ELEMENTS_DEBUG_SERVER), + // debugServer: isTruthy(process.env.CLERK_ELEMENTS_DEBUG_SERVER), + debugServer: false, }); } return undefined; diff --git a/packages/elements/src/react/common/form/input.tsx b/packages/elements/src/react/common/form/input.tsx index cfda5181915..38e7e82e29c 100644 --- a/packages/elements/src/react/common/form/input.tsx +++ b/packages/elements/src/react/common/form/input.tsx @@ -1,4 +1,4 @@ -import { useClerk } from '@clerk/clerk-react'; +import { useClerk } from '@clerk/shared/react'; import { logger } from '@clerk/shared/logger'; import { eventComponentMounted } from '@clerk/shared/telemetry'; import type { diff --git a/packages/elements/src/react/common/loading.tsx b/packages/elements/src/react/common/loading.tsx index 370a241cb1a..d9937cf8930 100644 --- a/packages/elements/src/react/common/loading.tsx +++ b/packages/elements/src/react/common/loading.tsx @@ -1,4 +1,4 @@ -import { useClerk } from '@clerk/clerk-react'; +import { useClerk } from '@clerk/shared/react'; import { eventComponentMounted } from '@clerk/shared/telemetry'; import type { OAuthProvider, SamlStrategy, Web3Provider } from '@clerk/types'; import { useSelector } from '@xstate/react'; diff --git a/packages/elements/src/react/hooks/use-password.hook.ts b/packages/elements/src/react/hooks/use-password.hook.ts index d5b5860bc43..24e61d2b3be 100644 --- a/packages/elements/src/react/hooks/use-password.hook.ts +++ b/packages/elements/src/react/hooks/use-password.hook.ts @@ -1,4 +1,4 @@ -import { useClerk } from '@clerk/clerk-react'; +import { useClerk } from '@clerk/shared/react'; import { noop } from '@clerk/shared'; import type { PasswordSettingsData, PasswordValidation } from '@clerk/types'; import * as React from 'react'; diff --git a/packages/elements/src/react/hooks/use-third-party-provider.hook.ts b/packages/elements/src/react/hooks/use-third-party-provider.hook.ts index 070c244eb4b..6bf4a03b59c 100644 --- a/packages/elements/src/react/hooks/use-third-party-provider.hook.ts +++ b/packages/elements/src/react/hooks/use-third-party-provider.hook.ts @@ -1,4 +1,4 @@ -import { useClerk } from '@clerk/clerk-react'; +import { useClerk } from '@clerk/shared/react'; import type { OAuthProvider, SamlStrategy, Web3Provider } from '@clerk/types'; import type React from 'react'; import { useCallback } from 'react'; diff --git a/packages/elements/src/react/sign-in/root.tsx b/packages/elements/src/react/sign-in/root.tsx index 94292b8c54b..fdcd3d6ff86 100644 --- a/packages/elements/src/react/sign-in/root.tsx +++ b/packages/elements/src/react/sign-in/root.tsx @@ -1,4 +1,4 @@ -import { useClerk } from '@clerk/clerk-react'; +import { useClerk } from '@clerk/shared/react'; import { eventComponentMounted } from '@clerk/shared/telemetry'; import { useSelector } from '@xstate/react'; import React, { useEffect } from 'react'; @@ -9,11 +9,12 @@ import { FormStoreProvider, useFormStore } from '~/internals/machines/form/form. import type { SignInRouterInitEvent } from '~/internals/machines/sign-in'; import { SignInRouterMachine } from '~/internals/machines/sign-in'; import { inspect } from '~/internals/utils/inspector'; -import { Router, useClerkRouter, useNextRouter, useVirtualRouter } from '~/react/router'; +import { Router, useClerkRouter, useVirtualRouter } from '~/react/router'; import { SignInRouterCtx } from '~/react/sign-in/context'; import { Form } from '../common/form'; import { usePathnameWithoutCatchAll } from '../utils/path-inference/next'; +import { useClerkHostRouter } from '@clerk/shared/router'; type SignInFlowProviderProps = { children: React.ReactNode; @@ -39,8 +40,7 @@ function SignInFlowProvider({ children, exampleMode, fallback, isRootPath }: Sig return; } - // @ts-expect-error -- This is actually an IsomorphicClerk instance - clerk.addOnLoaded(() => { + const cb = () => { const evt: SignInRouterInitEvent = { type: 'INIT', clerk, @@ -53,7 +53,14 @@ function SignInFlowProvider({ children, exampleMode, fallback, isRootPath }: Sig if (actor.getSnapshot().can(evt)) { actor.send(evt); } - }); + }; + + if ('addOnLoaded' in clerk) { + // @ts-expect-error - who cares + clerk.addOnLoaded(cb); + } else { + cb(); + } // Ensure that the latest instantiated formRef is attached to the router if (formRef && actor.getSnapshot().can({ type: 'RESET.STEP' })) { @@ -123,8 +130,7 @@ export function SignInRoot({ }), ); - // TODO: eventually we'll rely on the framework SDK to specify its host router, but for now we'll default to Next.js - const router = (routing === ROUTING.virtual ? useVirtualRouter : useNextRouter)(); + const router = (routing === ROUTING.virtual ? useVirtualRouter : useClerkHostRouter)(); const isRootPath = path === router.pathname(); return ( diff --git a/packages/elements/src/react/sign-in/step.tsx b/packages/elements/src/react/sign-in/step.tsx index c01a08b02bf..89a5be4f9c1 100644 --- a/packages/elements/src/react/sign-in/step.tsx +++ b/packages/elements/src/react/sign-in/step.tsx @@ -1,4 +1,4 @@ -import { useClerk } from '@clerk/clerk-react'; +import { useClerk } from '@clerk/shared/react'; import { eventComponentMounted } from '@clerk/shared/telemetry'; import { ClerkElementsRuntimeError } from '~/internals/errors'; diff --git a/packages/elements/src/react/sign-up/root.tsx b/packages/elements/src/react/sign-up/root.tsx index 9cea15248d4..e1484c690db 100644 --- a/packages/elements/src/react/sign-up/root.tsx +++ b/packages/elements/src/react/sign-up/root.tsx @@ -1,4 +1,4 @@ -import { useClerk } from '@clerk/clerk-react'; +import { useClerk } from '@clerk/shared/react'; import { eventComponentMounted } from '@clerk/shared/telemetry'; import { useSelector } from '@xstate/react'; import { useEffect } from 'react'; @@ -9,11 +9,12 @@ import { FormStoreProvider, useFormStore } from '~/internals/machines/form/form. import type { SignUpRouterInitEvent } from '~/internals/machines/sign-up'; import { SignUpRouterMachine } from '~/internals/machines/sign-up'; import { inspect } from '~/internals/utils/inspector'; -import { Router, useClerkRouter, useNextRouter, useVirtualRouter } from '~/react/router'; +import { Router, useClerkRouter, useVirtualRouter } from '~/react/router'; import { SignUpRouterCtx } from '~/react/sign-up/context'; import { Form } from '../common/form'; import { usePathnameWithoutCatchAll } from '../utils/path-inference/next'; +import { useClerkHostRouter } from '@clerk/shared/router'; type SignUpFlowProviderProps = { children: React.ReactNode; @@ -39,8 +40,7 @@ function SignUpFlowProvider({ children, exampleMode, fallback, isRootPath }: Sig return; } - // @ts-expect-error -- This is actually an IsomorphicClerk instance - clerk.addOnLoaded(() => { + const cb = () => { const evt: SignUpRouterInitEvent = { type: 'INIT', clerk, @@ -61,7 +61,14 @@ function SignUpFlowProvider({ children, exampleMode, fallback, isRootPath }: Sig formRef, }); } - }); + }; + + if ('addOnLoaded' in clerk) { + // @ts-expect-error - who cares + clerk.addOnLoaded(cb); + } else { + cb(); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [clerk, exampleMode, formRef?.id, !!router, clerk.loaded]); @@ -122,8 +129,7 @@ export function SignUpRoot({ }), ); - // TODO: eventually we'll rely on the framework SDK to specify its host router, but for now we'll default to Next.js - const router = (routing === ROUTING.virtual ? useVirtualRouter : useNextRouter)(); + const router = (routing === ROUTING.virtual ? useVirtualRouter : useClerkHostRouter)(); const isRootPath = path === router.pathname(); return ( diff --git a/packages/elements/src/react/sign-up/step.tsx b/packages/elements/src/react/sign-up/step.tsx index cd7c7788e1b..d17e72c5672 100644 --- a/packages/elements/src/react/sign-up/step.tsx +++ b/packages/elements/src/react/sign-up/step.tsx @@ -1,4 +1,4 @@ -import { useClerk } from '@clerk/clerk-react'; +import { useClerk } from '@clerk/shared/react'; import { eventComponentMounted } from '@clerk/shared/telemetry'; import { ClerkElementsRuntimeError } from '~/internals/errors'; diff --git a/packages/nextjs/src/app-router/client/ClerkProvider.tsx b/packages/nextjs/src/app-router/client/ClerkProvider.tsx index debb2b2a7ac..dcbbe7935cc 100644 --- a/packages/nextjs/src/app-router/client/ClerkProvider.tsx +++ b/packages/nextjs/src/app-router/client/ClerkProvider.tsx @@ -1,6 +1,6 @@ 'use client'; import { ClerkProvider as ReactClerkProvider } from '@clerk/clerk-react'; -import { useRouter } from 'next/navigation'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import React, { useEffect, useTransition } from 'react'; import { useSafeLayoutEffect } from '../../client-boundary/hooks/useSafeLayoutEffect'; @@ -11,18 +11,55 @@ import { mergeNextClerkPropsWithEnv } from '../../utils/mergeNextClerkPropsWithE import { invalidateCacheAction } from '../server-actions'; import { useAwaitablePush } from './useAwaitablePush'; import { useAwaitableReplace } from './useAwaitableReplace'; +import { ClerkHostRouter } from '@clerk/shared/router'; declare global { export interface Window { __clerk_nav_await: Array<(value: void) => void>; __clerk_nav: (to: string) => Promise; __clerk_internal_invalidateCachePromise: () => void | undefined; + next?: { + version: string; + }; } } +// The version that Next added support for the window.history.pushState and replaceState APIs. +// ref: https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate +export const NEXT_WINDOW_HISTORY_SUPPORT_VERSION = '14.1.0'; + +/** + * Clerk router integration with Next.js's router. + */ +export const useNextRouter = (): ClerkHostRouter => { + const router = useRouter(); + const pathname = usePathname(); + // eslint-disable-next-line react-hooks/rules-of-hooks -- The order doesn't differ between renders as we're checking the execution environment. + const searchParams = typeof window === 'undefined' ? new URLSearchParams() : useSearchParams(); + + // The window.history APIs seem to prevent Next.js from triggering a full page re-render, allowing us to + // preserve internal state between steps. + const canUseWindowHistoryAPIs = + typeof window !== 'undefined' && window.next && window.next.version >= NEXT_WINDOW_HISTORY_SUPPORT_VERSION; + + return { + mode: 'path', + name: 'NextRouter', + push: (path: string) => router.push(path), + replace: (path: string) => + canUseWindowHistoryAPIs ? window.history.replaceState(null, '', path) : router.replace(path), + shallowPush(path: string) { + canUseWindowHistoryAPIs ? window.history.pushState(null, '', path) : router.push(path, {}); + }, + pathname: () => pathname, + searchParams: () => searchParams, + }; +}; + export const ClientClerkProvider = (props: NextClerkProviderProps) => { const { __unstable_invokeMiddlewareOnAuthStateChange = true, children } = props; const router = useRouter(); + const clerkRouter = useNextRouter(); const push = useAwaitablePush(); const replace = useAwaitableReplace(); const [isPending, startTransition] = useTransition(); @@ -57,7 +94,6 @@ export const ClientClerkProvider = (props: NextClerkProviderProps) => { return new Promise(res => { window.__clerk_internal_invalidateCachePromise = res; startTransition(() => { - //@ts-expect-error next exists on window if (window.next?.version && typeof window.next.version === 'string' && window.next.version.startsWith('13')) { router.refresh(); } else { @@ -78,6 +114,8 @@ export const ClientClerkProvider = (props: NextClerkProviderProps) => { ...props, routerPush: push, routerReplace: replace, + // @ts-expect-error -- TODO: type + router: clerkRouter, }); return ( diff --git a/packages/shared/src/react/index.ts b/packages/shared/src/react/index.ts index 40735d10f2d..4b716f41052 100644 --- a/packages/shared/src/react/index.ts +++ b/packages/shared/src/react/index.ts @@ -3,13 +3,15 @@ export * from './hooks'; export { ClerkInstanceContext, ClientContext, + OptionsContext, OrganizationProvider, SessionContext, + useAssertWrappedByClerkProvider, useClerkInstanceContext, useClientContext, + useOptionsContext, useOrganizationContext, UserContext, useSessionContext, useUserContext, - useAssertWrappedByClerkProvider, } from './contexts'; diff --git a/packages/shared/src/router.ts b/packages/shared/src/router.ts index a6bc0ca6a83..32dc0abf2ed 100644 --- a/packages/shared/src/router.ts +++ b/packages/shared/src/router.ts @@ -1,3 +1,10 @@ export { type ClerkRouter, type ClerkHostRouter, createClerkRouter } from './router/router'; export { type RoutingMode } from './router/types'; -export { Router, useClerkRouter, Route, ClerkRouterContext } from './router/react'; +export { + Router, + useClerkRouter, + useClerkHostRouter, + Route, + ClerkRouterContext, + ClerkHostRouterContext, +} from './router/react'; diff --git a/packages/shared/src/router/react.tsx b/packages/shared/src/router/react.tsx index c0fcce0649b..3fad08e889e 100644 --- a/packages/shared/src/router/react.tsx +++ b/packages/shared/src/router/react.tsx @@ -6,8 +6,21 @@ import React, { createContext, useContext } from 'react'; import type { ClerkHostRouter, ClerkRouter } from './router'; import { createClerkRouter } from './router'; +export const ClerkHostRouterContext = createContext(null); export const ClerkRouterContext = createContext(null); +export function useClerkHostRouter() { + const ctx = useContext(ClerkHostRouterContext); + + if (!ctx) { + throw new Error( + 'clerk: Unable to locate ClerkHostRouter, make sure this is rendered within ``.', + ); + } + + return ctx; +} + export function useClerkRouter() { const ctx = useContext(ClerkRouterContext); @@ -28,9 +41,10 @@ export function Router({ }: { children: React.ReactNode; basePath?: string; - router: ClerkHostRouter; + router?: ClerkHostRouter; }) { - const clerkRouter = createClerkRouter(router, basePath); + const hostRouter = useClerkHostRouter(); + const clerkRouter = createClerkRouter(router ?? hostRouter, basePath); return {children}; } From 8df5c467c68abd8f35f2f295c331f9baaa58ab5d Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Fri, 30 Aug 2024 17:15:10 -0500 Subject: [PATCH 2/9] safely access env vars --- .../elements/src/internals/constants/index.ts | 17 ++++++---- .../utils/inspector/browser/index.ts | 9 ++++-- .../utils/inspector/console/index.ts | 12 ++++--- packages/elements/src/react/router/next.ts | 32 ------------------- packages/elements/src/react/sign-in/root.tsx | 4 +-- packages/elements/src/react/sign-up/root.tsx | 4 +-- packages/elements/src/utils/safe-access.ts | 7 ++++ 7 files changed, 35 insertions(+), 50 deletions(-) delete mode 100644 packages/elements/src/react/router/next.ts create mode 100644 packages/elements/src/utils/safe-access.ts diff --git a/packages/elements/src/internals/constants/index.ts b/packages/elements/src/internals/constants/index.ts index 01541049fc8..1ca489d3c2c 100644 --- a/packages/elements/src/internals/constants/index.ts +++ b/packages/elements/src/internals/constants/index.ts @@ -1,13 +1,18 @@ +import { safeAccess } from '~/utils/safe-access'; + export const SSO_CALLBACK_PATH_ROUTE = '/sso-callback'; export const CHOOSE_SESSION_PATH_ROUTE = '/choose'; export const MAGIC_LINK_VERIFY_PATH_ROUTE = '/verify'; -export const SIGN_IN_DEFAULT_BASE_PATH = '/sign-in'; -export const SIGN_UP_DEFAULT_BASE_PATH = '/sign-up'; -// export const SIGN_IN_DEFAULT_BASE_PATH = -// process.env.CLERK_SIGN_IN_URL ?? process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL ?? '/sign-in'; -// export const SIGN_UP_DEFAULT_BASE_PATH = -// process.env.CLERK_SIGN_UP_URL ?? process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL ?? '/sign-up'; +// TODO: remove reliance on next-specific variables here +export const SIGN_IN_DEFAULT_BASE_PATH = safeAccess( + () => process.env.CLERK_SIGN_IN_URL ?? process.env.NEXT_PUBLIC_CLERK_SIGN_IN_URL, + '/sign-in', +); +export const SIGN_UP_DEFAULT_BASE_PATH = safeAccess( + () => process.env.CLERK_SIGN_UP_URL ?? process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL, + '/sign-up', +); // The version that Next added support for the window.history.pushState and replaceState APIs. // ref: https://nextjs.org/blog/next-14-1#windowhistorypushstate-and-windowhistoryreplacestate diff --git a/packages/elements/src/internals/utils/inspector/browser/index.ts b/packages/elements/src/internals/utils/inspector/browser/index.ts index 222d13d5d29..30f12a3e821 100644 --- a/packages/elements/src/internals/utils/inspector/browser/index.ts +++ b/packages/elements/src/internals/utils/inspector/browser/index.ts @@ -1,13 +1,16 @@ -// import { isTruthy } from '@clerk/shared/underscore'; +import { isTruthy } from '@clerk/shared/underscore'; import { createBrowserInspector } from '@statelyai/inspect'; +import { safeAccess } from '~/utils/safe-access'; + export const getInspector = () => { if ( __DEV__ && typeof window !== 'undefined' && process.env.NODE_ENV === 'development' && - false - // isTruthy(process.env.NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG_UI ?? process.env.CLERK_ELEMENTS_DEBUG_UI) + isTruthy( + safeAccess(() => process.env.NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG_UI ?? process.env.CLERK_ELEMENTS_DEBUG_UI, false), + ) ) { const { inspect } = createBrowserInspector({ autoStart: true, diff --git a/packages/elements/src/internals/utils/inspector/console/index.ts b/packages/elements/src/internals/utils/inspector/console/index.ts index fef96cbffbc..1af63ce18df 100644 --- a/packages/elements/src/internals/utils/inspector/console/index.ts +++ b/packages/elements/src/internals/utils/inspector/console/index.ts @@ -1,4 +1,6 @@ -// import { isTruthy } from '@clerk/shared/underscore'; +import { isTruthy } from '@clerk/shared/underscore'; + +import { safeAccess } from '~/utils/safe-access'; import { createConsoleInspector } from './console'; @@ -6,13 +8,13 @@ export function getInspector() { if ( __DEV__ && process.env.NODE_ENV === 'development' && - false - // isTruthy(process.env.NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG ?? process.env.CLERK_ELEMENTS_DEBUG) + isTruthy( + safeAccess(() => process.env.NEXT_PUBLIC_CLERK_ELEMENTS_DEBUG_UI ?? process.env.CLERK_ELEMENTS_DEBUG_UI, false), + ) ) { return createConsoleInspector({ enabled: true, - // debugServer: isTruthy(process.env.CLERK_ELEMENTS_DEBUG_SERVER), - debugServer: false, + debugServer: isTruthy(safeAccess(() => process.env.CLERK_ELEMENTS_DEBUG_SERVER, false)), }); } return undefined; diff --git a/packages/elements/src/react/router/next.ts b/packages/elements/src/react/router/next.ts deleted file mode 100644 index d8a8ac96a17..00000000000 --- a/packages/elements/src/react/router/next.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ClerkHostRouter } from '@clerk/shared/router'; -import { usePathname, useRouter, useSearchParams } from 'next/navigation'; - -import { NEXT_WINDOW_HISTORY_SUPPORT_VERSION } from '~/internals/constants'; - -/** - * Clerk router integration with Next.js's router. - */ -export const useNextRouter = (): ClerkHostRouter => { - const router = useRouter(); - const pathname = usePathname(); - // eslint-disable-next-line react-hooks/rules-of-hooks -- The order doesn't differ between renders as we're checking the execution environment. - const searchParams = typeof window === 'undefined' ? new URLSearchParams() : useSearchParams(); - - // The window.history APIs seem to prevent Next.js from triggering a full page re-render, allowing us to - // preserve internal state between steps. - const canUseWindowHistoryAPIs = - typeof window !== 'undefined' && window.next && window.next.version >= NEXT_WINDOW_HISTORY_SUPPORT_VERSION; - - return { - mode: 'path', - name: 'NextRouter', - push: (path: string) => router.push(path), - replace: (path: string) => - canUseWindowHistoryAPIs ? window.history.replaceState(null, '', path) : router.replace(path), - shallowPush(path: string) { - canUseWindowHistoryAPIs ? window.history.pushState(null, '', path) : router.push(path, {}); - }, - pathname: () => pathname, - searchParams: () => searchParams, - }; -}; diff --git a/packages/elements/src/react/sign-in/root.tsx b/packages/elements/src/react/sign-in/root.tsx index fdcd3d6ff86..e844cd3eba5 100644 --- a/packages/elements/src/react/sign-in/root.tsx +++ b/packages/elements/src/react/sign-in/root.tsx @@ -1,4 +1,5 @@ import { useClerk } from '@clerk/shared/react'; +import { useClerkHostRouter } from '@clerk/shared/router'; import { eventComponentMounted } from '@clerk/shared/telemetry'; import { useSelector } from '@xstate/react'; import React, { useEffect } from 'react'; @@ -14,7 +15,6 @@ import { SignInRouterCtx } from '~/react/sign-in/context'; import { Form } from '../common/form'; import { usePathnameWithoutCatchAll } from '../utils/path-inference/next'; -import { useClerkHostRouter } from '@clerk/shared/router'; type SignInFlowProviderProps = { children: React.ReactNode; @@ -56,7 +56,7 @@ function SignInFlowProvider({ children, exampleMode, fallback, isRootPath }: Sig }; if ('addOnLoaded' in clerk) { - // @ts-expect-error - who cares + // @ts-expect-error - addOnLoaded doesn't exist on the clerk type, but it does on IsomorphicClerk, which can be hit when Elements is used standalone clerk.addOnLoaded(cb); } else { cb(); diff --git a/packages/elements/src/react/sign-up/root.tsx b/packages/elements/src/react/sign-up/root.tsx index e1484c690db..e080d731fc7 100644 --- a/packages/elements/src/react/sign-up/root.tsx +++ b/packages/elements/src/react/sign-up/root.tsx @@ -1,4 +1,5 @@ import { useClerk } from '@clerk/shared/react'; +import { useClerkHostRouter } from '@clerk/shared/router'; import { eventComponentMounted } from '@clerk/shared/telemetry'; import { useSelector } from '@xstate/react'; import { useEffect } from 'react'; @@ -14,7 +15,6 @@ import { SignUpRouterCtx } from '~/react/sign-up/context'; import { Form } from '../common/form'; import { usePathnameWithoutCatchAll } from '../utils/path-inference/next'; -import { useClerkHostRouter } from '@clerk/shared/router'; type SignUpFlowProviderProps = { children: React.ReactNode; @@ -64,7 +64,7 @@ function SignUpFlowProvider({ children, exampleMode, fallback, isRootPath }: Sig }; if ('addOnLoaded' in clerk) { - // @ts-expect-error - who cares + // @ts-expect-error - addOnLoaded doesn't exist on the clerk type, but it does on IsomorphicClerk, which can be hit when Elements is used standalone clerk.addOnLoaded(cb); } else { cb(); diff --git a/packages/elements/src/utils/safe-access.ts b/packages/elements/src/utils/safe-access.ts new file mode 100644 index 00000000000..8813fbc6a65 --- /dev/null +++ b/packages/elements/src/utils/safe-access.ts @@ -0,0 +1,7 @@ +export function safeAccess(fn: any, fallback: any) { + try { + return fn(); + } catch (e) { + return fallback; + } +} From 63239001b3f4bbfac6c298801815143eedc298b7 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Tue, 3 Sep 2024 08:53:23 -0500 Subject: [PATCH 3/9] Add ClerkHostRouter provider for elements usage in next.js --- packages/nextjs/src/app-router/client/ClerkProvider.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/nextjs/src/app-router/client/ClerkProvider.tsx b/packages/nextjs/src/app-router/client/ClerkProvider.tsx index dcbbe7935cc..7dff69badc2 100644 --- a/packages/nextjs/src/app-router/client/ClerkProvider.tsx +++ b/packages/nextjs/src/app-router/client/ClerkProvider.tsx @@ -1,5 +1,7 @@ 'use client'; import { ClerkProvider as ReactClerkProvider } from '@clerk/clerk-react'; +import type { ClerkHostRouter } from '@clerk/shared/router'; +import { ClerkHostRouterContext } from '@clerk/shared/router'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import React, { useEffect, useTransition } from 'react'; @@ -11,7 +13,6 @@ import { mergeNextClerkPropsWithEnv } from '../../utils/mergeNextClerkPropsWithE import { invalidateCacheAction } from '../server-actions'; import { useAwaitablePush } from './useAwaitablePush'; import { useAwaitableReplace } from './useAwaitableReplace'; -import { ClerkHostRouter } from '@clerk/shared/router'; declare global { export interface Window { @@ -122,7 +123,7 @@ export const ClientClerkProvider = (props: NextClerkProviderProps) => { - {children} + {children} ); From 234f0f567ad4dca85bab6cc9a6f1e3508291ce07 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 4 Sep 2024 10:19:44 -0500 Subject: [PATCH 4/9] undo clerk-js change for now --- packages/clerk-js/src/ui/lazyModules/providers.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/clerk-js/src/ui/lazyModules/providers.tsx b/packages/clerk-js/src/ui/lazyModules/providers.tsx index bf50c25a38d..70c3dcf56e8 100644 --- a/packages/clerk-js/src/ui/lazyModules/providers.tsx +++ b/packages/clerk-js/src/ui/lazyModules/providers.tsx @@ -5,7 +5,6 @@ import type { FlowMetadata } from '../elements'; import type { ThemableCssProp } from '../styledSystem'; import type { ClerkComponentName } from './components'; import { ClerkComponents } from './components'; -import { ClerkHostRouterContext } from '@clerk/shared/router'; const CoreClerkContextWrapper = lazy(() => import('../contexts').then(m => ({ default: m.CoreClerkContextWrapper }))); const EnvironmentProvider = lazy(() => import('../contexts').then(m => ({ default: m.EnvironmentProvider }))); @@ -24,9 +23,7 @@ export const LazyProviders = (props: LazyProvidersProps) => { return ( - - {props.children} - + {props.children} ); From cc4e2d158e4ee86b7fb5eaf19b814a28b8c50e1a Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 4 Sep 2024 10:27:19 -0500 Subject: [PATCH 5/9] adds changeset --- .changeset/good-jeans-call.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/good-jeans-call.md diff --git a/.changeset/good-jeans-call.md b/.changeset/good-jeans-call.md new file mode 100644 index 00000000000..f0b0048f7e4 --- /dev/null +++ b/.changeset/good-jeans-call.md @@ -0,0 +1,7 @@ +--- +"@clerk/elements": minor +"@clerk/nextjs": minor +"@clerk/shared": minor +--- + +Remove `@clerk/elements` reliance on `next` and `@clerk/clerk-react` directly. The host router is now provided by `@clerk/nextjs`. From 796465fff674763a7cde9d43708dcb92572dc946 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 4 Sep 2024 10:37:57 -0500 Subject: [PATCH 6/9] Add missing contest, remove unused export --- packages/elements/src/react/router/index.ts | 1 - packages/shared/src/react/contexts.tsx | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/elements/src/react/router/index.ts b/packages/elements/src/react/router/index.ts index 5b0979e8a25..3b037aa70a6 100644 --- a/packages/elements/src/react/router/index.ts +++ b/packages/elements/src/react/router/index.ts @@ -1,3 +1,2 @@ -export { useNextRouter } from './next'; export { Route, Router, useClerkRouter } from '@clerk/shared/router'; export { useVirtualRouter } from './virtual'; diff --git a/packages/shared/src/react/contexts.tsx b/packages/shared/src/react/contexts.tsx index 51b9bab76db..d8965507ab2 100644 --- a/packages/shared/src/react/contexts.tsx +++ b/packages/shared/src/react/contexts.tsx @@ -2,6 +2,7 @@ import type { ActiveSessionResource, + ClerkOptions, ClientResource, LoadedClerk, OrganizationResource, @@ -20,6 +21,16 @@ const [SessionContext, useSessionContext] = createContextAndHook({}); + +function useOptionsContext(): ClerkOptions { + const context = React.useContext(OptionsContext); + if (context === undefined) { + throw new Error('useOptions must be used within an OptionsContext'); + } + return context; +} + type OrganizationContextProps = { organization: OrganizationResource | null | undefined; }; @@ -71,6 +82,8 @@ export { OrganizationProvider, useOrganizationContext, UserContext, + OptionsContext, + useOptionsContext, useUserContext, SessionContext, useSessionContext, From 502bd8bf003f68e8fdaf3f8de125c80add108206 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 4 Sep 2024 10:39:02 -0500 Subject: [PATCH 7/9] remove next as a peer dep --- package-lock.json | 8 +++----- packages/elements/package.json | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb2bc96ec08..1ee856da1ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36051,9 +36051,9 @@ } }, "node_modules/postcss": { - "version": "8.4.44", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.44.tgz", - "integrity": "sha512-Aweb9unOEpQ3ezu4Q00DPvvM2ZTUitJdNKeP/+uQgr1IBIqu574IaZoURId7BKtWMREwzKa9OgzPzezWGPWFQw==", + "version": "8.4.45", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", + "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", "funding": [ { "type": "opencollective", @@ -46316,9 +46316,7 @@ "node": ">=18.17.0" }, "peerDependencies": { - "@clerk/clerk-react": "^5.0.0", "@clerk/shared": "^2.0.0", - "next": "^13.5.4 || ^14.0.3 || ^15.0.0-rc", "react": "^18.0.0 || ^19.0.0-beta", "react-dom": "^18.0.0 || ^19.0.0-beta" }, diff --git a/packages/elements/package.json b/packages/elements/package.json index 1591ed1f020..00787008363 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -95,7 +95,6 @@ }, "peerDependencies": { "@clerk/shared": "^2.0.0", - "next": "^13.5.4 || ^14.0.3 || ^15.0.0-rc", "react": "^18.0.0 || ^19.0.0-beta", "react-dom": "^18.0.0 || ^19.0.0-beta" }, From c5c5c9b82e103d1eff2b9a20db080e0fb586c401 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 4 Sep 2024 10:39:59 -0500 Subject: [PATCH 8/9] Update packages/nextjs/src/app-router/client/ClerkProvider.tsx --- packages/nextjs/src/app-router/client/ClerkProvider.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/nextjs/src/app-router/client/ClerkProvider.tsx b/packages/nextjs/src/app-router/client/ClerkProvider.tsx index 7dff69badc2..d9c1f9cf330 100644 --- a/packages/nextjs/src/app-router/client/ClerkProvider.tsx +++ b/packages/nextjs/src/app-router/client/ClerkProvider.tsx @@ -115,8 +115,6 @@ export const ClientClerkProvider = (props: NextClerkProviderProps) => { ...props, routerPush: push, routerReplace: replace, - // @ts-expect-error -- TODO: type - router: clerkRouter, }); return ( From 0967c8a85d36ef9a11928daa09f8b28ec2e81537 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Wed, 4 Sep 2024 11:55:11 -0500 Subject: [PATCH 9/9] lint fixes --- packages/elements/src/react/common/form/input.tsx | 2 +- packages/elements/src/react/hooks/use-password.hook.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/elements/src/react/common/form/input.tsx b/packages/elements/src/react/common/form/input.tsx index 38e7e82e29c..07a09af1c50 100644 --- a/packages/elements/src/react/common/form/input.tsx +++ b/packages/elements/src/react/common/form/input.tsx @@ -1,5 +1,5 @@ -import { useClerk } from '@clerk/shared/react'; import { logger } from '@clerk/shared/logger'; +import { useClerk } from '@clerk/shared/react'; import { eventComponentMounted } from '@clerk/shared/telemetry'; import type { Control as RadixControl, diff --git a/packages/elements/src/react/hooks/use-password.hook.ts b/packages/elements/src/react/hooks/use-password.hook.ts index 24e61d2b3be..f7138b3eb2d 100644 --- a/packages/elements/src/react/hooks/use-password.hook.ts +++ b/packages/elements/src/react/hooks/use-password.hook.ts @@ -1,5 +1,5 @@ -import { useClerk } from '@clerk/shared/react'; import { noop } from '@clerk/shared'; +import { useClerk } from '@clerk/shared/react'; import type { PasswordSettingsData, PasswordValidation } from '@clerk/types'; import * as React from 'react';