{"record":{"id":"09b0375467cbc885","repo":"koala73/worldmonitor","slug":"sign-in-to-revoke-embed-keys","errorCode":null,"errorMessage":"Sign in to revoke embed keys.","messagePattern":"Sign in to revoke embed keys\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/services/embed-keys.ts","lineNumber":141,"sourceCode":"    () => client.query((api as any).embedKeys.listEmbedKeys, {}),\n  );\n}\n\n/**\n * Revoke an embed key by its Convex document ID.\n *\n * Unlike `revokeApiKey`, this does not bust the edge validation cache: there is\n * no ownership-checked invalidation route for `embedKeys` yet, so a revoked key\n * keeps validating for at most the 60s `CACHE_TTL_SECONDS` in\n * `server/_shared/embed-key.ts`.\n *\n * A map frame is slower still: it already holds a `wmg_` grant good for up to\n * `EMBED_GRANT_TTL_MS` (30 minutes), and revocation only stops the NEXT mint.\n * The UI copy states both windows rather than promising one.\n */\nexport async function revokeEmbedKey(keyId: string): Promise<void> {\n  const userId = getCurrentClerkUser()?.id;\n  if (!userId) throw new Error('Sign in to revoke embed keys.');\n\n  const [client, api] = await Promise.all([getConvexClient(), getConvexApi()]);\n  if (!client || !api) throw new Error('Convex unavailable');\n  if (!await waitForConvexAuthForUser(userId)) {\n    throw new Error('Account changed while revoking the embed key. Try again.');\n  }\n\n  await settleAccountOperation(\n    userId,\n    'revoking the embed key',\n    () => client.mutation((api as any).embedKeys.revokeEmbedKey, { keyId }),\n  );\n  assertAccountStillCurrent(userId, 'revoking the embed key');\n}\n","sourceCodeStart":123,"sourceCodeEnd":156,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/src/services/embed-keys.ts#L123-L156","documentation":"revokeEmbedKey(keyId) requires an authenticated user: it reads the current Clerk user id, and if there is none it throws this error before any Convex call. Unlike the read path (listEmbedKeys returns []), a destructive mutation refuses to silently no-op when signed out.","triggerScenarios":"Calling revokeEmbedKey(keyId) when getCurrentClerkUser() returns undefined — i.e. the visitor is signed out, or the Clerk session has been lost/expired at the moment of the call.","commonSituations":"Clicking 'revoke' on an embed key after the Clerk session expired in a long-lived tab; calling the API from code that runs before Clerk finishes initializing; a signed-out state after session revocation on another device.","solutions":["Check getCurrentClerkUser() for a non-null user before offering/invoking revocation, and route to sign-in instead","Sign in with Clerk, then retry the revocation","If the session just expired, refresh the page so Clerk rehydrates before retrying"],"exampleFix":"// before\nawait revokeEmbedKey(keyId);\n// after\nif (!getCurrentClerkUser()) {\n  openSignIn();\n  return;\n}\nawait revokeEmbedKey(keyId);","handlingStrategy":"validation","validationCode":"if (!getCurrentClerkUser()) { openSignIn(); return; }","typeGuard":"const isSignedIn = (): boolean => getCurrentClerkUser() != null;","tryCatchPattern":null,"preventionTips":["Disable destructive actions until a Clerk user exists","Route to sign-in instead of calling the API when signed out","Treat session expiry proactively in long-lived tabs"],"tags":["authentication","clerk","sign-in"],"backgroundTag":"authentication-required","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}