Skip to content

Commit 69f6547

Browse files
tweak
1 parent 0cf7474 commit 69f6547

File tree

3 files changed

+61
-47
lines changed

3 files changed

+61
-47
lines changed

packages/nextjs/src/app-router/server/ClerkProvider.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ export async function ClerkProvider(
6969

7070
let output: ReactNode;
7171

72+
const detectKeylessEnvDrift = await import('../../server/keyless-telemetry.js').then(
73+
mod => mod.detectKeylessEnvDrift,
74+
);
75+
76+
await detectKeylessEnvDrift();
77+
7278
if (shouldRunAsKeyless) {
7379
output = (
7480
<KeylessProvider

packages/nextjs/src/app-router/server/keyless-provider.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ export const KeylessProvider = async (props: KeylessProviderProps) => {
4747
.then(mod => mod.createOrReadKeyless())
4848
.catch(() => null);
4949

50+
const detectKeylessEnvDrift = await import('../../server/keyless-telemetry.js').then(
51+
mod => mod.detectKeylessEnvDrift,
52+
);
53+
54+
if (newOrReadKeys) {
55+
await detectKeylessEnvDrift();
56+
}
57+
5058
const { clerkDevelopmentCache, createConfirmationMessage, createKeylessModeMessage } = await import(
5159
'../../server/keyless-log-cache.js'
5260
);

packages/nextjs/src/server/keyless-telemetry.ts

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { createClerkClientWithOptions } from './createClerkClient';
66

77
const EVENT_KEYLESS_ENV_DRIFT_DETECTED = 'KEYLESS_ENV_DRIFT_DETECTED';
88
const EVENT_SAMPLING_RATE = 1; // 100% sampling rate
9-
const TELEMETRY_FLAG_FILE = '.clerk/.tmp/keyless-telemetry-fired.json';
9+
const TELEMETRY_FLAG_FILE = '.clerk/.tmp/telemetry.json';
1010

1111
type EventKeylessEnvDriftPayload = {
1212
publicKeyMatch: boolean;
@@ -19,56 +19,60 @@ type EventKeylessEnvDriftPayload = {
1919

2020
/**
2121
* Gets the absolute path to the telemetry flag file.
22+
*
23+
* This file is used to track whether telemetry events have already been fired
24+
* to prevent duplicate event reporting during the application lifecycle.
25+
*
26+
* @returns The absolute path to the telemetry flag file in the project's .clerk/.tmp directory
2227
*/
2328
function getTelemetryFlagFilePath(): string {
2429
return join(process.cwd(), TELEMETRY_FLAG_FILE);
2530
}
2631

2732
/**
28-
* Checks if the telemetry event has already been fired by looking for a flag file.
29-
*/
30-
async function hasTelemetryEventBeenFired(): Promise<boolean> {
31-
try {
32-
await fs.access(getTelemetryFlagFilePath());
33-
return true;
34-
} catch {
35-
return false;
36-
}
37-
}
38-
39-
/**
40-
* Creates a flag file to mark that the telemetry event has been fired.
33+
* Attempts to create a telemetry flag file to mark that a telemetry event has been fired.
34+
*
35+
* This function uses the 'wx' flag to create the file atomically - it will only succeed
36+
* if the file doesn't already exist. This ensures that telemetry events are only fired
37+
* once per application lifecycle, preventing duplicate event reporting.
38+
*
39+
* @returns Promise<boolean> - Returns true if the flag file was successfully created (meaning
40+
* the event should be fired), false if the file already exists (meaning the event was
41+
* already fired) or if there was an error creating the file
4142
*/
42-
async function markTelemetryEventAsFired(): Promise<void> {
43+
async function tryMarkTelemetryEventAsFired(): Promise<boolean> {
4344
try {
44-
// Ensure the directory exists
45-
const dir = join(process.cwd(), '.clerk/.tmp');
46-
await fs.mkdir(dir, { recursive: true });
47-
48-
// Create the flag file with timestamp
4945
const flagData = {
5046
firedAt: new Date().toISOString(),
5147
event: EVENT_KEYLESS_ENV_DRIFT_DETECTED,
5248
};
53-
54-
await fs.writeFile(getTelemetryFlagFilePath(), JSON.stringify(flagData, null, 2));
55-
} catch (error) {
56-
// Silently handle errors to avoid breaking the application
49+
await fs.writeFile(getTelemetryFlagFilePath(), JSON.stringify(flagData, null, 2), { flag: 'wx' });
50+
return true;
51+
} catch (error: unknown) {
52+
if ((error as { code?: string })?.code === 'EEXIST') {
53+
return false;
54+
}
5755
console.warn('Failed to create telemetry flag file:', error);
56+
return false;
5857
}
5958
}
6059

6160
/**
62-
* Detects environment variable drift for keyless Next.js applications and fires telemetry events.
61+
* Detects and reports environment drift between keyless configuration and environment variables.
6362
*
64-
* This function compares the publishableKey and secretKey values from `.clerk/.tmp/keyless.json`
65-
* with the `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` environment variables.
63+
* This function compares the Clerk keys stored in the keyless configuration file (.clerk/clerk.json)
64+
* with the keys set in environment variables (NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY).
65+
* If there's a mismatch (drift), it reports this as a telemetry event to help diagnose configuration issues.
6666
*
67-
* If there's a mismatch, it fires a `KEYLESS_ENV_DRIFT_DETECTED` event.
68-
* For local testing purposes, it also fires a `KEYLESS_ENV_DRIFT_NOT_DETECTED` event when
69-
* keys exist and match the environment variables.
67+
* The function handles several scenarios:
68+
* - Normal keyless mode: env vars missing but keyless file has keys (no drift)
69+
* - Drift detected: env vars and keyless file have different keys
70+
* - Mixed configuration: some keys match, others don't
7071
*
71-
* The telemetry event will only fire once per application instance to avoid noise.
72+
* Telemetry events are only fired once per application lifecycle using a flag file mechanism
73+
* to prevent duplicate reporting.
74+
*
75+
* @returns Promise<void> - Function completes silently, errors are logged but don't throw
7276
*/
7377
export async function detectKeylessEnvDrift(): Promise<void> {
7478
// Only run on server side
@@ -77,11 +81,6 @@ export async function detectKeylessEnvDrift(): Promise<void> {
7781
}
7882

7983
try {
80-
// Check if telemetry event has already been fired
81-
if (await hasTelemetryEventBeenFired()) {
82-
return;
83-
}
84-
8584
// Dynamically import server-side dependencies to avoid client-side issues
8685
const { safeParseClerkFile } = await import('./keyless-node.js');
8786

@@ -130,17 +129,18 @@ export async function detectKeylessEnvDrift(): Promise<void> {
130129
});
131130

132131
if (hasDrift) {
133-
// Fire drift detected event
134-
const driftDetectedEvent: TelemetryEventRaw<EventKeylessEnvDriftPayload> = {
135-
event: EVENT_KEYLESS_ENV_DRIFT_DETECTED,
136-
eventSamplingRate: EVENT_SAMPLING_RATE,
137-
payload,
138-
};
139-
140-
clerkClient.telemetry?.record(driftDetectedEvent);
141-
142-
// Mark the telemetry event as fired to prevent future executions
143-
await markTelemetryEventAsFired();
132+
const shouldFireEvent = await tryMarkTelemetryEventAsFired();
133+
134+
if (shouldFireEvent) {
135+
// Fire drift detected event only if we successfully created the flag
136+
const driftDetectedEvent: TelemetryEventRaw<EventKeylessEnvDriftPayload> = {
137+
event: EVENT_KEYLESS_ENV_DRIFT_DETECTED,
138+
eventSamplingRate: EVENT_SAMPLING_RATE,
139+
payload,
140+
};
141+
142+
clerkClient.telemetry?.record(driftDetectedEvent);
143+
}
144144
}
145145
} catch (error) {
146146
// Silently handle errors to avoid breaking the application

0 commit comments

Comments
 (0)