diff --git a/.changeset/fine-weeks-give.md b/.changeset/fine-weeks-give.md new file mode 100644 index 00000000000..a4121243b27 --- /dev/null +++ b/.changeset/fine-weeks-give.md @@ -0,0 +1,5 @@ +--- +'@clerk/nextjs': patch +--- + +Fix keyless drift logic to support client components and ensure only apps on development are included. Change createClerkClient to accept an argument for samplingRate in telemetry options and update the ClerkOptions type. diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 5aa8a0f8da9..de6f3ef4b98 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -17,7 +17,7 @@ export type ClerkOptions = Omit - > & { sdkMetadata?: SDKMetadata; telemetry?: Pick }; + > & { sdkMetadata?: SDKMetadata; telemetry?: Pick }; // The current exported type resolves the following issue in packages importing createClerkClient // TS4023: Exported variable 'clerkClient' has or is using name 'AuthErrorReason' from external module "/packages/backend/dist/index" but cannot be named. @@ -31,11 +31,11 @@ export function createClerkClient(options: ClerkOptions): ClerkClient { const apiClient = createBackendApiClient(opts); const requestState = createAuthenticateRequest({ options: opts, apiClient }); const telemetry = new TelemetryCollector({ - ...options.telemetry, publishableKey: opts.publishableKey, secretKey: opts.secretKey, samplingRate: 0.1, ...(opts.sdkMetadata ? { sdk: opts.sdkMetadata.name, sdkVersion: opts.sdkMetadata.version } : {}), + ...(opts.telemetry || {}), }); return { diff --git a/packages/nextjs/src/app-router/client/ClerkProvider.tsx b/packages/nextjs/src/app-router/client/ClerkProvider.tsx index 0ec599df6c6..cb3f398fe0a 100644 --- a/packages/nextjs/src/app-router/client/ClerkProvider.tsx +++ b/packages/nextjs/src/app-router/client/ClerkProvider.tsx @@ -15,6 +15,7 @@ import { canUseKeyless } from '../../utils/feature-flags'; import { mergeNextClerkPropsWithEnv } from '../../utils/mergeNextClerkPropsWithEnv'; import { RouterTelemetry } from '../../utils/router-telemetry'; import { isNextWithUnstableServerActions } from '../../utils/sdk-versions'; +import { detectKeylessEnvDriftAction } from '../keyless-actions'; import { invalidateCacheAction } from '../server-actions'; import { useAwaitablePush } from './useAwaitablePush'; import { useAwaitableReplace } from './useAwaitableReplace'; @@ -43,6 +44,13 @@ const NextClientClerkProvider = (props: NextClerkProviderProps) => { const replace = useAwaitableReplace(); const [isPending, startTransition] = useTransition(); + // Call drift detection on mount (client-side) + useSafeLayoutEffect(() => { + if (canUseKeyless) { + void detectKeylessEnvDriftAction(); + } + }, []); + // Avoid rendering nested ClerkProviders by checking for the existence of the ClerkNextOptions context provider const isNested = Boolean(useClerkNextOptions()); if (isNested) { diff --git a/packages/nextjs/src/app-router/keyless-actions.ts b/packages/nextjs/src/app-router/keyless-actions.ts index a9f2094544c..3b9b1558388 100644 --- a/packages/nextjs/src/app-router/keyless-actions.ts +++ b/packages/nextjs/src/app-router/keyless-actions.ts @@ -93,3 +93,16 @@ export async function deleteKeylessAction() { await import('../server/keyless-node.js').then(m => m.removeKeyless()).catch(() => {}); return; } + +export async function detectKeylessEnvDriftAction() { + if (!canUseKeyless) { + return; + } + + try { + const { detectKeylessEnvDrift } = await import('../server/keyless-telemetry.js'); + await detectKeylessEnvDrift(); + } catch { + // ignore + } +} diff --git a/packages/nextjs/src/server/keyless-telemetry.ts b/packages/nextjs/src/server/keyless-telemetry.ts index f91b1ae9501..223b0b8c931 100644 --- a/packages/nextjs/src/server/keyless-telemetry.ts +++ b/packages/nextjs/src/server/keyless-telemetry.ts @@ -2,6 +2,7 @@ import type { TelemetryEventRaw } from '@clerk/types'; import { promises as fs } from 'fs'; import { dirname, join } from 'path'; +import { canUseKeyless } from '../utils/feature-flags'; import { createClerkClientWithOptions } from './createClerkClient'; const EVENT_KEYLESS_ENV_DRIFT_DETECTED = 'KEYLESS_ENV_DRIFT_DETECTED'; @@ -86,6 +87,9 @@ async function tryMarkTelemetryEventAsFired(): Promise { * @returns Promise - Function completes silently, errors are logged but don't throw */ export async function detectKeylessEnvDrift(): Promise { + if (!canUseKeyless) { + return; + } // Only run on server side if (typeof window !== 'undefined') { return; @@ -163,6 +167,9 @@ export async function detectKeylessEnvDrift(): Promise { const clerkClient = createClerkClientWithOptions({ publishableKey: keylessFile.publishableKey, secretKey: keylessFile.secretKey, + telemetry: { + samplingRate: 1, + }, }); const shouldFireEvent = await tryMarkTelemetryEventAsFired();