@@ -6,7 +6,7 @@ import { createClerkClientWithOptions } from './createClerkClient';
6
6
7
7
const EVENT_KEYLESS_ENV_DRIFT_DETECTED = 'KEYLESS_ENV_DRIFT_DETECTED' ;
8
8
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' ;
10
10
11
11
type EventKeylessEnvDriftPayload = {
12
12
publicKeyMatch : boolean ;
@@ -19,56 +19,60 @@ type EventKeylessEnvDriftPayload = {
19
19
20
20
/**
21
21
* 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
22
27
*/
23
28
function getTelemetryFlagFilePath ( ) : string {
24
29
return join ( process . cwd ( ) , TELEMETRY_FLAG_FILE ) ;
25
30
}
26
31
27
32
/**
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
41
42
*/
42
- async function markTelemetryEventAsFired ( ) : Promise < void > {
43
+ async function tryMarkTelemetryEventAsFired ( ) : Promise < boolean > {
43
44
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
49
45
const flagData = {
50
46
firedAt : new Date ( ) . toISOString ( ) ,
51
47
event : EVENT_KEYLESS_ENV_DRIFT_DETECTED ,
52
48
} ;
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
+ }
57
55
console . warn ( 'Failed to create telemetry flag file:' , error ) ;
56
+ return false ;
58
57
}
59
58
}
60
59
61
60
/**
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 .
63
62
*
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.
66
66
*
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
70
71
*
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
72
76
*/
73
77
export async function detectKeylessEnvDrift ( ) : Promise < void > {
74
78
// Only run on server side
@@ -77,11 +81,6 @@ export async function detectKeylessEnvDrift(): Promise<void> {
77
81
}
78
82
79
83
try {
80
- // Check if telemetry event has already been fired
81
- if ( await hasTelemetryEventBeenFired ( ) ) {
82
- return ;
83
- }
84
-
85
84
// Dynamically import server-side dependencies to avoid client-side issues
86
85
const { safeParseClerkFile } = await import ( './keyless-node.js' ) ;
87
86
@@ -130,17 +129,18 @@ export async function detectKeylessEnvDrift(): Promise<void> {
130
129
} ) ;
131
130
132
131
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
+ }
144
144
}
145
145
} catch ( error ) {
146
146
// Silently handle errors to avoid breaking the application
0 commit comments