UNPKG

@clerk/shared

Version:

Internal package utils used by the Clerk SDKs

1,472 lines (1,444 loc) 126 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const require_chunk = require('../_chunks/chunk-C_NdSu1c.js'); const require_authorization_errors = require('../authorization-errors.js'); const require_authorization = require('../authorization.js'); const require_deriveState = require('../deriveState.js'); const require_clerkRuntimeError = require('../_chunks/clerkRuntimeError-CYZdTZ_x.js'); const require_error = require('../_chunks/error-rHNfstgh.js'); const require_logger = require('../logger.js'); const require_organization = require('../organization.js'); const require_telemetry = require('../_chunks/telemetry-DZp9OI1j.js'); const require_createDeferredPromise = require('../_chunks/createDeferredPromise-BXa3LUQE.js'); const require_internal_clerk_js_errors = require('../internal/clerk-js/errors.js'); let react = require("react"); react = require_chunk.__toESM(react); let _tanstack_query_core = require("@tanstack/query-core"); let dequal = require("dequal"); //#region src/react/hooks/createContextAndHook.ts /** * Assert that the context value exists, otherwise throw an error. * * @internal */ function assertContextExists(contextVal, msgOrCtx) { if (!contextVal) throw typeof msgOrCtx === "string" ? new Error(msgOrCtx) : /* @__PURE__ */ new Error(`${msgOrCtx.displayName} not found`); } /** * Create and return a Context and two hooks that return the context value. * The Context type is derived from the type passed in by the user. * * The first hook returned guarantees that the context exists so the returned value is always `CtxValue` * The second hook makes no guarantees, so the returned value can be `CtxValue | undefined` * * @internal */ const createContextAndHook = (displayName, options) => { const { assertCtxFn = assertContextExists } = options || {}; const Ctx = react.default.createContext(void 0); Ctx.displayName = displayName; const useCtx = () => { const ctx = react.default.useContext(Ctx); assertCtxFn(ctx, `${displayName} not found`); return ctx.value; }; const useCtxWithoutGuarantee = () => { const ctx = react.default.useContext(Ctx); return ctx ? ctx.value : {}; }; return [ Ctx, useCtx, useCtxWithoutGuarantee ]; }; //#endregion //#region src/react/contexts.tsx const [ClerkInstanceContext, useClerkInstanceContext] = createContextAndHook("ClerkInstanceContext"); const [InitialStateContext, _useInitialStateContext] = createContextAndHook("InitialStateContext"); /** * Provides initial Clerk state (session, user, organization data) from server-side rendering * to child components via React context. * * Passing in a promise is only supported for React >= 19. * * The initialState is snapshotted on mount and cannot change during the component lifecycle. * * Note that different parts of the React tree can use separate InitialStateProvider instances * with different initialState values if needed. */ function InitialStateProvider({ children, initialState }) { const [initialStateSnapshot] = (0, react.useState)(initialState); const initialStateCtx = react.default.useMemo(() => ({ value: initialStateSnapshot }), [initialStateSnapshot]); return /* @__PURE__ */ react.default.createElement(InitialStateContext.Provider, { value: initialStateCtx }, children); } function useInitialStateContext() { const initialState = _useInitialStateContext(); if (initialState instanceof Promise) if ("use" in react.default && typeof react.default.use === "function") return react.default.use(initialState); else throw new Error("initialState cannot be a promise if React version is less than 19"); return initialState; } const OptionsContext = react.default.createContext({}); const [CheckoutContext, useCheckoutContext] = createContextAndHook("CheckoutContext"); const __experimental_CheckoutProvider = ({ children, ...rest }) => { return /* @__PURE__ */ react.default.createElement(CheckoutContext.Provider, { value: { value: rest } }, children); }; /** * @internal */ function useOptionsContext() { const context = react.default.useContext(OptionsContext); if (context === void 0) throw new Error("useOptions must be used within an OptionsContext"); return context; } /** * @internal */ function useAssertWrappedByClerkProvider(displayNameOrFn) { if (!react.default.useContext(ClerkInstanceContext)) { if (typeof displayNameOrFn === "function") { displayNameOrFn(); return; } throw new Error(`${displayNameOrFn} can only be used within the <ClerkProvider /> component. Possible fixes: 1. Ensure that the <ClerkProvider /> is correctly wrapping your application where this component is used. 2. Check for multiple versions of the \`@clerk/shared\` package in your project. Use a tool like \`npm ls @clerk/shared\` to identify multiple versions, and update your dependencies to only rely on one. Learn more: https://clerk.com/docs/components/clerk-provider`.trim()); } } //#endregion //#region src/react/stable-keys.ts const USER_MEMBERSHIPS_KEY = "userMemberships"; const USER_INVITATIONS_KEY = "userInvitations"; const USER_SUGGESTIONS_KEY = "userSuggestions"; const DOMAINS_KEY = "domains"; const MEMBERSHIP_REQUESTS_KEY = "membershipRequests"; const MEMBERSHIPS_KEY = "memberships"; const INVITATIONS_KEY = "invitations"; const API_KEYS_KEY = "apiKeys"; const ORGANIZATION_CREATION_DEFAULTS_KEY = "organizationCreationDefaults"; const OAUTH_CONSENT_INFO_KEY = "oauthConsentInfo"; const PLANS_KEY = "billing-plans"; const SUBSCRIPTION_KEY = "billing-subscription"; const PAYMENT_METHODS_KEY = "billing-payment-methods"; const PAYMENT_ATTEMPTS_KEY = "billing-payment-attempts"; const STATEMENTS_KEY = "billing-statements"; const CREDIT_BALANCE_KEY = "billing-credit-balance"; const STABLE_KEYS = { USER_MEMBERSHIPS_KEY, USER_INVITATIONS_KEY, USER_SUGGESTIONS_KEY, DOMAINS_KEY, MEMBERSHIP_REQUESTS_KEY, MEMBERSHIPS_KEY, INVITATIONS_KEY, PLANS_KEY, SUBSCRIPTION_KEY, PAYMENT_METHODS_KEY, PAYMENT_ATTEMPTS_KEY, STATEMENTS_KEY, API_KEYS_KEY, ORGANIZATION_CREATION_DEFAULTS_KEY, OAUTH_CONSENT_INFO_KEY, CREDIT_BALANCE_KEY }; /** * Internal stable keys for queries only used by our UI components. * These keys are not used by the hooks themselves. */ const PAYMENT_ATTEMPT_KEY = "billing-payment-attempt"; const BILLING_PLANS_KEY = "billing-plan"; const BILLING_STATEMENTS_KEY = "billing-statement"; const USER_ENTERPRISE_CONNECTIONS_KEY = "userEnterpriseConnections"; const ENTERPRISE_CONNECTION_TEST_RUNS_KEY = "enterpriseConnectionTestRuns"; const ORGANIZATION_ENTERPRISE_CONNECTIONS_KEY = "organizationEnterpriseConnections"; const ORGANIZATION_ENTERPRISE_CONNECTION_TEST_RUNS_KEY = "organizationEnterpriseConnectionTestRuns"; const ORGANIZATION_DOMAINS_KEY = "organizationDomains"; const CREDIT_HISTORY_KEY = "billing-credit-history"; const INTERNAL_STABLE_KEYS = { PAYMENT_ATTEMPT_KEY, BILLING_PLANS_KEY, BILLING_STATEMENTS_KEY, CREDIT_HISTORY_KEY, USER_ENTERPRISE_CONNECTIONS_KEY, ENTERPRISE_CONNECTION_TEST_RUNS_KEY, ORGANIZATION_ENTERPRISE_CONNECTIONS_KEY, ORGANIZATION_ENTERPRISE_CONNECTION_TEST_RUNS_KEY, ORGANIZATION_DOMAINS_KEY }; //#endregion //#region src/react/hooks/createCacheKeys.ts /** * @internal */ function createCacheKeys(params) { return { queryKey: [ params.stablePrefix, params.authenticated, params.tracked, params.untracked ], invalidationKey: [ params.stablePrefix, params.authenticated, params.tracked ], stableKey: params.stablePrefix, authenticated: params.authenticated }; } //#endregion //#region src/react/query/keep-previous-data.ts /** * @internal */ function defineKeepPreviousDataFn(enabled) { if (enabled) return function KeepPreviousDataFn(previousData) { return previousData; }; } //#endregion //#region src/react/query/clerk-query-client.ts /** * The QueryClient backing every clerk-query hook. Owned by `@clerk/shared` so the * `QueryObserver` that observes it and the `Query` objects inside it always * resolve to the same `@tanstack/query-core` (no cross-bundle drift between * the consumer-side `@clerk/shared` and the production CDN `clerk-js` bundle). * * Lazily instantiated on the client only. Server-side renders return * `undefined` so per-request renders never share a cache across requests. */ let clerkQueryClient; let initialized = false; function getClerkQueryClient() { if (typeof window === "undefined") return; if (!initialized) { clerkQueryClient = new _tanstack_query_core.QueryClient(); initialized = true; } return clerkQueryClient; } /** * Test-only: install a custom client (for deterministic defaults like * `staleTime: Infinity`) or pass `undefined` to simulate the "no client" * state without triggering lazy creation on subsequent reads. */ function __setClerkQueryClientForTest(client) { clerkQueryClient = client; initialized = true; } /** * Test-only: build and install a fresh `QueryClient` with deterministic * defaults (no retries, infinite stale time, no refetching). Returns the * client so the spec can read/write its cache directly. * * Avoids forcing every test consumer to depend on `@tanstack/query-core`. */ function __createClerkTestQueryClient() { const client = new _tanstack_query_core.QueryClient({ defaultOptions: { queries: { retry: false, staleTime: Infinity, refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false } } }); __setClerkQueryClientForTest(client); return client; } /** * Test-only: clear both the override and the initialization flag so the * next read lazy-creates a fresh client. */ function __resetClerkQueryClientForTest() { clerkQueryClient = void 0; initialized = false; } //#endregion //#region src/react/query/use-clerk-query-client.ts /** * Creates a recursively self-referential Proxy that safely handles: * - Arbitrary property access (e.g., obj.any.prop.path) * - Function calls at any level (e.g., obj.a().b.c()) * - Construction (e.g., new obj.a.b()) * * Always returns itself to allow infinite chaining without throwing. */ function createRecursiveProxy(label) { const callableTarget = function noop() {}; let self; self = new Proxy(callableTarget, { get(_target, prop) { if (prop === "then") return; if (prop === "toString") return () => `[${label}]`; if (prop === Symbol.toPrimitive) return () => 0; return self; }, apply() { return self; }, construct() { return self; }, has() { return false; }, set() { return false; } }); return self; } const mockQueryClient = createRecursiveProxy("ClerkMockQueryClient"); /** * Returns `[client, isLoaded]`. The real client is owned by `@clerk/shared` * and lazily instantiated on the browser only — SSR returns the proxy mock * + `isLoaded: false` so per-request renders never share a query cache. */ const useClerkQueryClient = () => { const client = getClerkQueryClient(); return [client ?? mockQueryClient, Boolean(client)]; }; //#endregion //#region src/react/query/useBaseQuery.ts /** * Stripped down version of useBaseQuery from @tanstack/query-core. * This implementation allows for an observer to be created every time a query client changes. */ /** * An alternative `useBaseQuery` implementation that allows for an observer to be created every time a query client changes. * * @internal */ function useBaseQuery(options, Observer) { const [client, isQueryClientLoaded] = useClerkQueryClient(); const defaultedOptions = isQueryClientLoaded ? client.defaultQueryOptions(options) : options; defaultedOptions._optimisticResults = "optimistic"; const observer = react.useMemo(() => { return new Observer(client, defaultedOptions); }, [client]); const result = observer.getOptimisticResult(defaultedOptions); const shouldSubscribe = options.subscribed !== false; react.useSyncExternalStore(react.useCallback((onStoreChange) => { const unsubscribe = shouldSubscribe ? observer.subscribe(_tanstack_query_core.notifyManager.batchCalls(onStoreChange)) : _tanstack_query_core.noop; observer.updateResult(); return unsubscribe; }, [observer, shouldSubscribe]), () => observer.getCurrentResult(), () => observer.getCurrentResult()); react.useEffect(() => { observer.setOptions(defaultedOptions); }, [defaultedOptions, observer]); if (!isQueryClientLoaded) return { data: void 0, error: null, isLoading: options.enabled !== false, isFetching: false, status: "pending" }; return !defaultedOptions.notifyOnChangeProps ? observer.trackResult(result) : result; } //#endregion //#region src/react/query/useInfiniteQuery.ts /** * */ function useClerkInfiniteQuery(options) { return useBaseQuery(options, _tanstack_query_core.InfiniteQueryObserver); } //#endregion //#region src/react/query/useQuery.ts /** * */ function useClerkQuery(options) { return useBaseQuery(options, _tanstack_query_core.QueryObserver); } //#endregion //#region src/react/hooks/usePreviousValue.ts /** * A hook that retains the previous value of a primitive type. * It uses a ref to prevent causing unnecessary re-renders. * * @internal * * @example * ``` * Render 1: value = 'A' → returns null * Render 2: value = 'B' → returns 'A' * Render 3: value = 'B' → returns 'A' * Render 4: value = 'B' → returns 'A' * Render 5: value = 'C' → returns 'B' * ``` */ function usePreviousValue(value) { const currentRef = (0, react.useRef)(value); const previousRef = (0, react.useRef)(null); if (currentRef.current !== value) { previousRef.current = currentRef.current; currentRef.current = value; } return previousRef.current; } //#endregion //#region src/react/hooks/useClearQueriesOnSignOut.ts const withInfiniteKey = (key) => [key, `${key}-inf`]; /** * Clears React Query caches associated with the given stable prefixes when * the authenticated state transitions from signed-in to signed-out. * * @internal */ function useClearQueriesOnSignOut(options) { const { isSignedOut, stableKeys, authenticated = true, onCleanup } = options; const stableKeysRef = (0, react.useRef)(stableKeys); const [queryClient] = useClerkQueryClient(); const previousIsSignedIn = usePreviousValue(!isSignedOut); (0, react.useEffect)(() => { if (authenticated !== true) return; if (previousIsSignedIn && isSignedOut === true) { queryClient.removeQueries({ predicate: (query) => { const [cachedStableKey, queryAuthenticated] = query.queryKey; return queryAuthenticated === true && typeof cachedStableKey === "string" && (Array.isArray(stableKeysRef.current) ? stableKeysRef.current.includes(cachedStableKey) : stableKeysRef.current === cachedStableKey); } }); onCleanup?.(); } }, [ authenticated, isSignedOut, previousIsSignedIn, queryClient ]); } //#endregion //#region src/react/hooks/usePagesOrInfinite.shared.ts /** * A hook that safely merges user-provided pagination options with default values. * It caches initial pagination values (page and size) until component unmount to prevent unwanted rerenders. * * @internal * * @example * ```typescript * // Example 1: With user-provided options * const userOptions = { initialPage: 2, pageSize: 20, infinite: true }; * const defaults = { initialPage: 1, pageSize: 10, infinite: false }; * useWithSafeValues(userOptions, defaults); * // Returns { initialPage: 2, pageSize: 20, infinite: true } * * // Example 2: With boolean true (use defaults) * const params = true; * const defaults = { initialPage: 1, pageSize: 10, infinite: false }; * useWithSafeValues(params, defaults); * // Returns { initialPage: 1, pageSize: 10, infinite: false } * * // Example 3: With undefined options (fallback to defaults) * const params = undefined; * const defaults = { initialPage: 1, pageSize: 10, infinite: false }; * useWithSafeValues(params, defaults); * // Returns { initialPage: 1, pageSize: 10, infinite: false } * ``` */ const useWithSafeValues = (params, defaultValues) => { const shouldUseDefaults = typeof params === "boolean" && params; const initialPageRef = (0, react.useRef)(shouldUseDefaults ? defaultValues.initialPage : params?.initialPage ?? defaultValues.initialPage); const pageSizeRef = (0, react.useRef)(shouldUseDefaults ? defaultValues.pageSize : params?.pageSize ?? defaultValues.pageSize); const newObj = {}; for (const key of Object.keys(defaultValues)) newObj[key] = shouldUseDefaults ? defaultValues[key] : params?.[key] ?? defaultValues[key]; return { ...newObj, initialPage: initialPageRef.current, pageSize: pageSizeRef.current }; }; /** * Calculates the offset count for pagination based on initial page and page size. * This represents the number of items to skip before the first page. * * @param initialPage - The starting page number (1-based) * @param pageSize - The number of items per page * @returns The number of items to offset * * @example * ```typescript * calculateOffsetCount(1, 10); // Returns 0 (no offset for first page) * calculateOffsetCount(2, 10); // Returns 10 (skip first 10 items) * calculateOffsetCount(3, 20); // Returns 40 (skip first 40 items) * ``` */ function calculateOffsetCount(initialPage, pageSize) { return (initialPage - 1) * pageSize; } /** * Calculates the total number of pages based on total count, offset, and page size. * * @param totalCount - The total number of items * @param offsetCount - The number of items to offset (from calculateOffsetCount) * @param pageSize - The number of items per page * @returns The total number of pages * * @example * ```typescript * calculatePageCount(100, 0, 10); // Returns 10 * calculatePageCount(95, 0, 10); // Returns 10 (rounds up) * calculatePageCount(100, 20, 10); // Returns 8 (100 - 20 = 80 items, 8 pages) * ``` */ function calculatePageCount(totalCount, offsetCount, pageSize) { return Math.ceil((totalCount - offsetCount) / pageSize); } /** * Determines if there is a next page available in non-infinite pagination mode. * * @param totalCount - The total number of items * @param offsetCount - The number of items to offset * @param currentPage - The current page number (1-based) * @param pageSize - The number of items per page * @returns True if there are more items beyond the current page * * @example * ```typescript * calculateHasNextPage(100, 0, 1, 10); // Returns true (page 1 of 10) * calculateHasNextPage(100, 0, 10, 10); // Returns false (last page) * calculateHasNextPage(25, 0, 2, 10); // Returns true (page 2, 5 more items) * calculateHasNextPage(20, 0, 2, 10); // Returns false (exactly 2 pages) * ``` */ function calculateHasNextPage(totalCount, offsetCount, currentPage, pageSize) { return totalCount - offsetCount > currentPage * pageSize; } /** * Determines if there is a previous page available in non-infinite pagination mode. * * @param currentPage - The current page number (1-based) * @param pageSize - The number of items per page * @param offsetCount - The number of items to offset * @returns True if there are pages before the current page * * @example * ```typescript * calculateHasPreviousPage(1, 10, 0); // Returns false (first page) * calculateHasPreviousPage(2, 10, 0); // Returns true (can go back to page 1) * calculateHasPreviousPage(1, 10, 10); // Returns false (first page with offset) * ``` */ function calculateHasPreviousPage(currentPage, pageSize, offsetCount) { return (currentPage - 1) * pageSize > offsetCount; } //#endregion //#region src/react/hooks/usePagesOrInfinite.tsx const usePagesOrInfinite = (params) => { const { fetcher, config, keys } = params; const [paginatedPage, setPaginatedPage] = (0, react.useState)(config.initialPage ?? 1); const initialPageRef = (0, react.useRef)(config.initialPage ?? 1); const pageSizeRef = (0, react.useRef)(config.pageSize ?? 10); const enabled = config.enabled ?? true; const isSignedIn = config.isSignedIn; const triggerInfinite = config.infinite ?? false; const cacheMode = config.__experimental_mode === "cache"; const keepPreviousData = config.keepPreviousData ?? false; const [queryClient] = useClerkQueryClient(); const queriesEnabled = enabled && Boolean(fetcher) && !cacheMode && isSignedIn !== false; const [forceUpdateCounter, setForceUpdateCounter] = (0, react.useState)(0); const forceUpdate = (0, react.useCallback)((updater) => { setForceUpdateCounter(updater); }, []); const pagesQueryKey = (0, react.useMemo)(() => { const [stablePrefix, authenticated, tracked, untracked] = keys.queryKey; return [ stablePrefix, authenticated, tracked, { ...untracked, args: { ...untracked.args, initialPage: paginatedPage, pageSize: pageSizeRef.current } } ]; }, [keys.queryKey, paginatedPage]); const singlePageQuery = useClerkQuery({ queryKey: pagesQueryKey, queryFn: ({ queryKey }) => { const { args } = queryKey[3]; if (!fetcher) return; return fetcher(args); }, staleTime: 6e4, enabled: queriesEnabled && !triggerInfinite, placeholderData: defineKeepPreviousDataFn(keepPreviousData) }); const infiniteQueryKey = (0, react.useMemo)(() => { const [stablePrefix, authenticated, tracked, untracked] = keys.queryKey; return [ stablePrefix + "-inf", authenticated, tracked, untracked ]; }, [keys.queryKey]); const infiniteQuery = useClerkInfiniteQuery({ queryKey: infiniteQueryKey, initialPageParam: config.initialPage ?? 1, getNextPageParam: (lastPage, allPages, lastPageParam) => { const total = lastPage?.total_count ?? 0; return (allPages.length + (config.initialPage ? config.initialPage - 1 : 0)) * (config.pageSize ?? 10) < total ? lastPageParam + 1 : void 0; }, queryFn: ({ pageParam, queryKey }) => { const { args } = queryKey[3]; if (!fetcher) return; return fetcher({ ...args, initialPage: pageParam, pageSize: pageSizeRef.current }); }, staleTime: 6e4, enabled: queriesEnabled && triggerInfinite }); useClearQueriesOnSignOut({ isSignedOut: isSignedIn === false, authenticated: keys.authenticated, stableKeys: withInfiniteKey(keys.stableKey), onCleanup: () => { setPaginatedPage(initialPageRef.current); Promise.resolve().then(() => forceUpdate((n) => n + 1)); } }); const { data, count, page } = (0, react.useMemo)(() => { if (triggerInfinite) { const cachedData = queryClient.getQueryData(infiniteQueryKey); const pages = queriesEnabled ? infiniteQuery.data?.pages ?? cachedData?.pages ?? [] : cachedData?.pages ?? []; const validPages = Array.isArray(pages) ? pages.filter(Boolean) : []; return { data: validPages.map((a) => a?.data).flat().filter(Boolean) ?? [], count: validPages[validPages.length - 1]?.total_count ?? 0, page: validPages.length > 0 ? validPages.length : initialPageRef.current }; } const pageData = queriesEnabled ? singlePageQuery.data ?? queryClient.getQueryData(pagesQueryKey) : queryClient.getQueryData(pagesQueryKey); return { data: Array.isArray(pageData?.data) ? pageData.data : [], count: typeof pageData?.total_count === "number" ? pageData.total_count : 0, page: paginatedPage }; }, [ queriesEnabled, forceUpdateCounter, triggerInfinite, infiniteQuery.data?.pages, singlePageQuery.data, queryClient, infiniteQueryKey, pagesQueryKey, paginatedPage ]); const fetchPage = (0, react.useCallback)((numberOrgFn) => { if (triggerInfinite) { const next = typeof numberOrgFn === "function" ? numberOrgFn(page) : numberOrgFn; const targetCount = Math.max(0, next); const cachedData = queryClient.getQueryData(infiniteQueryKey); if (targetCount - (infiniteQuery.data?.pages ?? cachedData?.pages ?? []).length > 0) infiniteQuery.fetchNextPage({ cancelRefetch: false }); return; } return setPaginatedPage(numberOrgFn); }, [ infiniteQuery, page, triggerInfinite, queryClient, infiniteQueryKey ]); const isLoading = triggerInfinite ? infiniteQuery.isLoading : singlePageQuery.isLoading; const isFetching = triggerInfinite ? infiniteQuery.isFetching : singlePageQuery.isFetching; const error = (triggerInfinite ? infiniteQuery.error : singlePageQuery.error) ?? null; const isError = !!error; const fetchNext = (0, react.useCallback)(() => { if (triggerInfinite) { infiniteQuery.fetchNextPage({ cancelRefetch: false }); return; } setPaginatedPage((n) => Math.max(0, n + 1)); }, [infiniteQuery, triggerInfinite]); const fetchPrevious = (0, react.useCallback)(() => { if (triggerInfinite) return; setPaginatedPage((n) => Math.max(0, n - 1)); }, [triggerInfinite]); const offsetCount = calculateOffsetCount(initialPageRef.current, pageSizeRef.current); const pageCount = calculatePageCount(count, offsetCount, pageSizeRef.current); const hasNextPage = triggerInfinite ? Boolean(infiniteQuery.hasNextPage) : calculateHasNextPage(count, offsetCount, page, pageSizeRef.current); const hasPreviousPage = triggerInfinite ? Boolean(infiniteQuery.hasPreviousPage) : calculateHasPreviousPage(page, pageSizeRef.current, offsetCount); const setData = (value) => { if (triggerInfinite) { queryClient.setQueryData(infiniteQueryKey, (prevValue = {}) => { const prevPages = Array.isArray(prevValue?.pages) ? prevValue.pages : []; const nextPages = typeof value === "function" ? value(prevPages) : value; return { ...prevValue, pages: nextPages }; }); forceUpdate((n) => n + 1); return Promise.resolve(); } queryClient.setQueryData(pagesQueryKey, (prevValue = { data: [], total_count: 0 }) => { return typeof value === "function" ? value(prevValue) : value; }); forceUpdate((n) => n + 1); return Promise.resolve(); }; const revalidate = async () => { await queryClient.invalidateQueries({ queryKey: keys.invalidationKey }); const [stablePrefix, ...rest] = keys.invalidationKey; return queryClient.invalidateQueries({ queryKey: [stablePrefix + "-inf", ...rest] }); }; return { data, count, error, isLoading, isFetching, isError, page, pageCount, fetchPage, fetchNext, fetchPrevious, hasNextPage, hasPreviousPage, revalidate, setData }; }; //#endregion //#region src/react/hooks/useAPIKeys.tsx /** * The `useAPIKeys()` hook provides access to paginated API keys for the current user or organization. * * @example * ### Basic usage with default pagination * * ```tsx * const { data, isLoading, page, pageCount, fetchNext, fetchPrevious } = useAPIKeys({ * subject: 'user_123', * pageSize: 10, * initialPage: 1, * }); * ``` * * @example * ### With search query * * ```tsx * const [searchValue, setSearchValue] = useState(''); * const debouncedSearch = useDebounce(searchValue, 500); * * const { data, isLoading } = useAPIKeys({ * subject: 'user_123', * query: debouncedSearch.trim(), * pageSize: 10, * }); * ``` * * @example * ### Infinite scroll * * ```tsx * const { data, isLoading, fetchNext, hasNextPage } = useAPIKeys({ * subject: 'user_123', * infinite: true, * }); * ``` */ function useAPIKeys(params) { useAssertWrappedByClerkProvider("useAPIKeys"); const safeValues = useWithSafeValues(params, { initialPage: 1, pageSize: 10, keepPreviousData: false, infinite: false, subject: "", query: "", enabled: true }); const clerk = useClerkInstanceContext(); clerk.telemetry?.record(require_telemetry.eventMethodCalled("useAPIKeys")); const hookParams = { initialPage: safeValues.initialPage, pageSize: safeValues.pageSize, ...safeValues.subject ? { subject: safeValues.subject } : {}, ...safeValues.query ? { query: safeValues.query } : {} }; const isEnabled = (safeValues.enabled ?? true) && clerk.loaded; return usePagesOrInfinite({ fetcher: clerk.apiKeys?.getAll ? (params) => clerk.apiKeys.getAll({ ...params, subject: safeValues.subject }) : void 0, config: { keepPreviousData: safeValues.keepPreviousData, infinite: safeValues.infinite, enabled: isEnabled, isSignedIn: clerk.user !== null, initialPage: safeValues.initialPage, pageSize: safeValues.pageSize }, keys: createCacheKeys({ stablePrefix: STABLE_KEYS.API_KEYS_KEY, authenticated: true, tracked: { subject: safeValues.subject }, untracked: { args: hookParams } }) }); } //#endregion //#region src/react/hooks/base/useUserBase.ts function useUserBase() { const clerk = useClerkInstanceContext(); const initialState = useInitialStateContext(); const getInitialState = (0, react.useCallback)(() => initialState?.user, [initialState?.user]); return (0, react.useSyncExternalStore)((0, react.useCallback)((callback) => { return clerk.addListener(callback, { skipInitialEmit: true }); }, [clerk]), (0, react.useCallback)(() => { if (!clerk.loaded || !clerk.__internal_lastEmittedResources) return getInitialState(); return clerk.__internal_lastEmittedResources.user; }, [clerk, getInitialState]), getInitialState); } //#endregion //#region src/react/hooks/useOAuthConsent.shared.ts function useOAuthConsentCacheKeys(params) { const { userId, oauthClientId, scope, redirectUri } = params; return (0, react.useMemo)(() => { const args = { oauthClientId, ...scope !== void 0 && { scope }, ...redirectUri !== void 0 && { redirectUri } }; return createCacheKeys({ stablePrefix: STABLE_KEYS.OAUTH_CONSENT_INFO_KEY, authenticated: true, tracked: { userId: userId ?? null }, untracked: { args } }); }, [ userId, oauthClientId, scope, redirectUri ]); } //#endregion //#region src/react/hooks/useOAuthConsent.tsx const HOOK_NAME$4 = "useOAuthConsent"; /** * The `useOAuthConsent()` hook loads OAuth application consent metadata for the **signed-in** user * (`GET /me/oauth/consent/{oauthClientId}`). Ensure the user is authenticated before relying on this hook * (e.g., redirect to sign-in on your custom consent route). * * @example * ```tsx * import { useOAuthConsent } from '@clerk/react/internal' * * const { data, isLoading, error } = useOAuthConsent({ * oauthClientId: clientIdFromProps, * scope: scopeFromProps, * }) * ``` */ function useOAuthConsent(params) { useAssertWrappedByClerkProvider(HOOK_NAME$4); const { oauthClientId: oauthClientIdParam, scope, redirectUri, keepPreviousData = true, enabled = true } = params; const clerk = useClerkInstanceContext(); const user = useUserBase(); const oauthClientId = (oauthClientIdParam ?? "").trim(); clerk.telemetry?.record(require_telemetry.eventMethodCalled(HOOK_NAME$4)); const { queryKey } = useOAuthConsentCacheKeys({ userId: user?.id ?? null, oauthClientId, scope, redirectUri }); const hasClientId = oauthClientId.length > 0; const queryEnabled = Boolean(user) && hasClientId && enabled && clerk.loaded; const query = useClerkQuery({ queryKey, queryFn: () => fetchConsentInfo(clerk, { oauthClientId, scope, redirectUri }), enabled: queryEnabled, placeholderData: defineKeepPreviousDataFn(keepPreviousData && queryEnabled) }); return { data: query.data, error: query.error ?? null, isLoading: query.isLoading, isFetching: query.isFetching }; } function fetchConsentInfo(clerk, params) { return clerk.oauthApplication.getConsentInfo(params); } //#endregion //#region src/react/hooks/base/useOrganizationBase.ts function useOrganizationBase() { const clerk = useClerkInstanceContext(); const initialState = useInitialStateContext(); const getInitialState = (0, react.useCallback)(() => initialState?.organization, [initialState?.organization]); return (0, react.useSyncExternalStore)((0, react.useCallback)((callback) => clerk.addListener(callback, { skipInitialEmit: true }), [clerk]), (0, react.useCallback)(() => { if (!clerk.loaded || !clerk.__internal_lastEmittedResources) return getInitialState(); return clerk.__internal_lastEmittedResources.organization; }, [clerk, getInitialState]), getInitialState); } //#endregion //#region src/react/hooks/base/useSessionBase.ts function useSessionBase() { const clerk = useClerkInstanceContext(); const initialState = useInitialStateContext(); const getInitialState = (0, react.useCallback)(() => { return initialState ? require_deriveState.deriveFromSsrInitialState(initialState)?.session : void 0; }, [initialState]); return (0, react.useSyncExternalStore)((0, react.useCallback)((callback) => clerk.addListener(callback, { skipInitialEmit: true }), [clerk]), (0, react.useCallback)(() => { if (!clerk.loaded || !clerk.__internal_lastEmittedResources) return getInitialState(); return clerk.__internal_lastEmittedResources.session; }, [clerk, getInitialState]), getInitialState); } //#endregion //#region src/react/hooks/useClerk.ts /** * > [!WARNING] * > This hook should only be used for advanced use cases, such as building a completely custom OAuth flow or as an escape hatch to access to the `Clerk` object. * * The `useClerk()` hook provides access to the [`Clerk`](https://clerk.com/docs/reference/objects/clerk) object, allowing you to build alternatives to any Clerk Component. * * @function * * @returns The `useClerk()` hook returns the `Clerk` object, which includes all the methods and properties listed in the [`Clerk` reference](https://clerk.com/docs/reference/objects/clerk). * * @example * * The following example uses the `useClerk()` hook to access the `clerk` object. The `clerk` object is used to call the [`openSignIn()`](https://clerk.com/docs/reference/objects/clerk#sign-in) method to open the sign-in modal. * * <Tabs items='React,Next.js'> * <Tab> * * ```tsx {{ filename: 'src/Home.tsx' }} * import { useClerk } from '@clerk/react' * * export default function Home() { * const clerk = useClerk() * * return <button onClick={() => clerk.openSignIn({})}>Sign in</button> * } * ``` * * </Tab> * <Tab> * * ```tsx {{ filename: 'app/page.tsx' }} * 'use client'; * * import { useClerk } from '@clerk/nextjs'; * * export default function HomePage() { * const clerk = useClerk(); * * return <button onClick={() => clerk.openSignIn({})}>Sign in</button>; * } * ``` * * </Tab> * </Tabs> */ const useClerk = () => { useAssertWrappedByClerkProvider("useClerk"); return useClerkInstanceContext(); }; //#endregion //#region src/react/hooks/useAttemptToEnableOrganizations.ts /** * Attempts to enable the organizations environment setting for a given caller * * @internal */ function useAttemptToEnableOrganizations(caller) { const clerk = useClerk(); const hasAttempted = (0, react.useRef)(false); (0, react.useEffect)(() => { if (hasAttempted.current) return; hasAttempted.current = true; clerk.__internal_attemptToEnableEnvironmentSetting?.({ for: "organizations", caller }); }, [clerk, caller]); } //#endregion //#region src/react/hooks/useOrganization.tsx const undefinedPaginatedResource$1 = { data: void 0, count: void 0, error: void 0, isLoading: false, isFetching: false, isError: false, page: void 0, pageCount: void 0, fetchPage: void 0, fetchNext: void 0, fetchPrevious: void 0, hasNextPage: false, hasPreviousPage: false, revalidate: void 0, setData: void 0 }; /** * The `useOrganization()` hook retrieves attributes of the currently Active Organization. * * @example * ### Expand and paginate attributes * * To keep network usage to a minimum, developers are required to opt-in by specifying which resource they need to fetch and paginate through. By default, the `memberships`, `invitations`, `membershipRequests`, and `domains` attributes are not populated. You must pass `true` or an object with the desired properties to fetch and paginate the data. * * ```tsx * // invitations.data will never be populated. * const { invitations } = useOrganization() * * // Use default values to fetch invitations, such as initialPage = 1 and pageSize = 10 * const { invitations } = useOrganization({ * invitations: true, * }) * * // Pass your own values to fetch invitations * const { invitations } = useOrganization({ * invitations: { * pageSize: 20, * initialPage: 2, // skips the first page * }, * }) * * // Aggregate pages in order to render an infinite list * const { invitations } = useOrganization({ * invitations: { * infinite: true, * }, * }) * ``` * * @example * ### Infinite pagination * * The following example demonstrates how to use the `infinite` property to fetch and append new data to the existing list. The `memberships` attribute will be populated with the first page of the Organization's memberships. When the "Load more" button is clicked, the `fetchNext` helper function will be called to append the next page of memberships to the list. * * ```tsx * import { useOrganization } from '@clerk/react' * * export default function MemberList() { * const { memberships } = useOrganization({ * memberships: { * infinite: true, // Append new data to the existing list * keepPreviousData: true, // Persist the cached data until the new data has been fetched * }, * }) * * if (!memberships) { * // Handle loading state * return null * } * * return ( * <div> * <h2>Organization members</h2> * <ul> * {memberships.data?.map((membership) => ( * <li key={membership.id}> * {membership.publicUserData.firstName} {membership.publicUserData.lastName} < * {membership.publicUserData.identifier}> :: {membership.role} * </li> * ))} * </ul> * * <button * disabled={!memberships.hasNextPage} // Disable the button if there are no more available pages to be fetched * onClick={memberships.fetchNext} * > * Load more * </button> * </div> * ) * } * ``` * * @example * ### Simple pagination * * The following example demonstrates how to use the `fetchPrevious` and `fetchNext` helper functions to paginate through the data. The `memberships` attribute will be populated with the first page of the Organization's memberships. When the "Previous page" or "Next page" button is clicked, the `fetchPrevious` or `fetchNext` helper function will be called to fetch the previous or next page of memberships. * * Notice the difference between this example's pagination and the infinite pagination example above. * * ```tsx * import { useOrganization } from '@clerk/react' * * export default function MemberList() { * const { memberships } = useOrganization({ * memberships: { * keepPreviousData: true, // Persist the cached data until the new data has been fetched * }, * }) * * if (!memberships) { * // Handle loading state * return null * } * * return ( * <div> * <h2>Organization members</h2> * <ul> * {memberships.data?.map((membership) => ( * <li key={membership.id}> * {membership.publicUserData.firstName} {membership.publicUserData.lastName} < * {membership.publicUserData.identifier}> :: {membership.role} * </li> * ))} * </ul> * * <button disabled={!memberships.hasPreviousPage} onClick={memberships.fetchPrevious}> * Previous page * </button> * * <button disabled={!memberships.hasNextPage} onClick={memberships.fetchNext}> * Next page * </button> * </div> * ) * } * ``` */ function useOrganization(params) { const { domains: domainListParams, membershipRequests: membershipRequestsListParams, memberships: membersListParams, invitations: invitationsListParams } = params || {}; useAssertWrappedByClerkProvider("useOrganization"); useAttemptToEnableOrganizations("useOrganization"); const organization = useOrganizationBase(); const session = useSessionBase(); const domainSafeValues = useWithSafeValues(domainListParams, { initialPage: 1, pageSize: 10, keepPreviousData: false, infinite: false, enrollmentMode: void 0 }); const membershipRequestSafeValues = useWithSafeValues(membershipRequestsListParams, { initialPage: 1, pageSize: 10, status: "pending", keepPreviousData: false, infinite: false }); const membersSafeValues = useWithSafeValues(membersListParams, { initialPage: 1, pageSize: 10, role: void 0, keepPreviousData: false, infinite: false, query: void 0 }); const invitationsSafeValues = useWithSafeValues(invitationsListParams, { initialPage: 1, pageSize: 10, status: ["pending"], keepPreviousData: false, infinite: false }); const clerk = useClerkInstanceContext(); clerk.telemetry?.record(require_telemetry.eventMethodCalled("useOrganization")); const domainParams = typeof domainListParams === "undefined" ? void 0 : { initialPage: domainSafeValues.initialPage, pageSize: domainSafeValues.pageSize, enrollmentMode: domainSafeValues.enrollmentMode }; const membershipRequestParams = typeof membershipRequestsListParams === "undefined" ? void 0 : { initialPage: membershipRequestSafeValues.initialPage, pageSize: membershipRequestSafeValues.pageSize, status: membershipRequestSafeValues.status }; const membersParams = typeof membersListParams === "undefined" ? void 0 : { initialPage: membersSafeValues.initialPage, pageSize: membersSafeValues.pageSize, role: membersSafeValues.role, query: membersSafeValues.query }; const invitationsParams = typeof invitationsListParams === "undefined" ? void 0 : { initialPage: invitationsSafeValues.initialPage, pageSize: invitationsSafeValues.pageSize, status: invitationsSafeValues.status }; const domains = usePagesOrInfinite({ fetcher: organization?.getDomains, config: { keepPreviousData: domainSafeValues.keepPreviousData, infinite: domainSafeValues.infinite, enabled: !!domainParams, isSignedIn: organization !== null, initialPage: domainSafeValues.initialPage, pageSize: domainSafeValues.pageSize }, keys: createCacheKeys({ stablePrefix: STABLE_KEYS.DOMAINS_KEY, authenticated: true, tracked: { organizationId: organization?.id }, untracked: { args: domainParams } }) }); const membershipRequests = usePagesOrInfinite({ fetcher: organization?.getMembershipRequests, config: { keepPreviousData: membershipRequestSafeValues.keepPreviousData, infinite: membershipRequestSafeValues.infinite, enabled: !!membershipRequestParams, isSignedIn: organization !== null, initialPage: membershipRequestSafeValues.initialPage, pageSize: membershipRequestSafeValues.pageSize }, keys: createCacheKeys({ stablePrefix: STABLE_KEYS.MEMBERSHIP_REQUESTS_KEY, authenticated: true, tracked: { organizationId: organization?.id }, untracked: { args: membershipRequestParams } }) }); const memberships = usePagesOrInfinite({ fetcher: organization?.getMemberships, config: { keepPreviousData: membersSafeValues.keepPreviousData, infinite: membersSafeValues.infinite, enabled: !!membersParams, isSignedIn: organization !== null, initialPage: membersSafeValues.initialPage, pageSize: membersSafeValues.pageSize }, keys: createCacheKeys({ stablePrefix: STABLE_KEYS.MEMBERSHIPS_KEY, authenticated: true, tracked: { organizationId: organization?.id }, untracked: { args: membersParams } }) }); const invitations = usePagesOrInfinite({ fetcher: organization?.getInvitations, config: { keepPreviousData: invitationsSafeValues.keepPreviousData, infinite: invitationsSafeValues.infinite, enabled: !!invitationsParams, isSignedIn: organization !== null, initialPage: invitationsSafeValues.initialPage, pageSize: invitationsSafeValues.pageSize }, keys: createCacheKeys({ stablePrefix: STABLE_KEYS.INVITATIONS_KEY, authenticated: true, tracked: { organizationId: organization?.id }, untracked: { args: invitationsParams } }) }); if (organization === void 0) return { isLoaded: false, organization: void 0, membership: void 0, domains: undefinedPaginatedResource$1, membershipRequests: undefinedPaginatedResource$1, memberships: undefinedPaginatedResource$1, invitations: undefinedPaginatedResource$1 }; if (organization === null) return { isLoaded: true, organization: null, membership: null, domains: null, membershipRequests: null, memberships: null, invitations: null }; /** In SSR context we include only the organization object when loadOrg is set to true. */ if (!clerk.loaded && organization) return { isLoaded: true, organization, membership: void 0, domains: undefinedPaginatedResource$1, membershipRequests: undefinedPaginatedResource$1, memberships: undefinedPaginatedResource$1, invitations: undefinedPaginatedResource$1 }; return { isLoaded: clerk.loaded, organization, membership: require_organization.getCurrentOrganizationMembership(session.user.organizationMemberships, organization.id), domains, membershipRequests, memberships, invitations }; } //#endregion //#region src/react/hooks/useOrganizationCreationDefaults.shared.ts function useOrganizationCreationDefaultsCacheKeys(params) { const { userId } = params; return (0, react.useMemo)(() => { return createCacheKeys({ stablePrefix: STABLE_KEYS.ORGANIZATION_CREATION_DEFAULTS_KEY, authenticated: Boolean(userId), tracked: { userId: userId ?? null }, untracked: { args: {} } }); }, [userId]); } //#endregion //#region src/react/hooks/useOrganizationCreationDefaults.tsx const HOOK_NAME$3 = "useOrganizationCreationDefaults"; /** * The `useOrganizationCreationDefaults()` hook retrieves the organization creation defaults for the current user. * * @example * ### Basic usage * * ```tsx * import { useOrganizationCreationDefaults } from '@clerk/clerk-react' * * export default function CreateOrganizationForm() { * const { data, isLoading } = useOrganizationCreationDefaults() * * if (isLoading) return <div>Loading...</div> * * return ( * <form> * <input defaultValue={data?.form.name} placeholder="Organization name" /> * <input defaultValue={data?.form.slug} placeholder="Slug" /> * <button type="submit">Create</button> * </form> * ) * } * ``` */ function useOrganizationCreationDefaults(params = {}) { useAssertWrappedByClerkProvider(HOOK_NAME$3); const { keepPreviousData = true, enabled = true } = params; const clerk = useClerkInstanceContext(); const user = useUserBase(); const featureEnabled = clerk.__internal_environment?.organizationSettings?.organizationCreationDefaults?.enabled ?? false; clerk.telemetry?.record(require_telemetry.eventMethodCalled(HOOK_NAME$3)); const { queryKey } = useOrganizationCreationDefaultsCacheKeys({ userId: user?.id ?? null }); const queryEnabled = Boolean(user) && enabled && featureEnabled && clerk.loaded; const query = useClerkQuery({ queryKey, queryFn: user?.getOrganizationCreationDefaults, enabled: queryEnabled, placeholderData: defineKeepPreviousDataFn(keepPreviousData) }); return { data: query.data, error: query.error ?? null, isLoading: query.isLoading, isFetching: query.isFetching }; } //#endregion //#region src/react/hooks/useOrganizationList.tsx const undefinedPaginatedResource = { data: void 0, count: void 0, error: void 0, isLoading: false, isFetching: false, isError: false, page: void 0, pageCount: void 0, fetchPage: void 0, fetchNext: void 0, fetchPrevious: void 0, hasNextPage: false, hasPreviousPage: false, revalidate: void 0, setData: void 0 }; /** * The `useOrganizationList()` hook provides access to the current user's organization memberships, invitations, and suggestions. It also includes methods for creating new organizations and managing the active organization. * * @example * ### Expanding and paginating attributes * * To keep network usage to a minimum, developers are required to opt-in by specifying which resource they need to fetch and paginate through. So by default, the `userMemberships`, `userInvitations`, and `userSuggestions` attributes are not populated. You must pass true or an object with the desired properties to fetch and paginate the data. * * ```tsx * // userMemberships.data will never be populated * const { userMemberships } = useOrganizationList() * * // Use default values to fetch userMemberships, such as initialPage = 1 and pageSize = 10 * const { userMemberships } = useOrganizationList({ * userMemberships: true, * }) * * // Pass your own values to fetch userMemberships * const { userMemberships } = useOrganizationList({ * userMemberships: { * pageSize: 20, * initialPage: 2, // skips the first page * }, * }) * * // Aggregate pages in order to render an infinite list * const { userMemberships } = useOrganizationList({ * userMemberships: { * infinite: true, * }, * }) * ``` * * @example * ### Infinite pagination * * The following example demonstrates how to use the `infinite` property to fetch and append new data to the existing list. The `userMemberships` attribute will be populated with the first page of the user's Organization memberships. When the "Load more" button is clicked, the `fetchNext` helper function will be called to append the next page of memberships to the list. * * ```tsx {{ filename: 'src/components/JoinedOrganizations.tsx' }} * import { useOrganizationList } from '@clerk/react' * import React from 'react' * * const JoinedOrganizations = () => { * const { isLoaded, setActive, userMemberships } = useOrganizationList({ * userMemberships: { * infinite: true, * }, * }) * * if (!isLoaded) { * return <>Loading</> * } * * return ( * <> * <ul> * {userMemberships.data?.map((mem) => ( * <li key={mem.id}> * <span>{mem.organization.name}</span> * <button onClick={() => setActive({ organization: mem.organization.id })}>Select</button> * </li> * ))} * </ul> * * <button disabled={!userMemberships.hasNextPage} onClick={() => userMemberships.fetchNext()}> * Load more * </button> * </> * ) * } * * export default JoinedOrganizations * ``` * * @example * ### Simple pagination * * The following example demonstrates how to use the `fetchPrevious` and `fetchNext` helper functions to paginate through the data. The `userInvitations` attribute will be populated with the first page of invitations. When the "Previous page" or "Next page" button is clicked, the `fetchPrevious` or `fetchNext` helper function will be called to fetc