From c1766716d4fb21fc711ba1df5b4b93186514b8e7 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 17 Jan 2025 17:21:47 +0200 Subject: [PATCH 01/14] feat(nextjs): Run on keyless until after you dismiss --- packages/clerk-js/src/core/clerk.ts | 1 + packages/clerk-js/src/ui/Components.tsx | 1 + packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx | 4 ++++ packages/nextjs/src/app-router/server/ClerkProvider.tsx | 5 ++++- packages/nextjs/src/server/keyless-node.ts | 2 +- packages/types/src/clerk.ts | 5 +++++ 6 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 755e6e83f8d..34cf8c71d1f 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -2104,6 +2104,7 @@ export class Clerk implements ClerkInterface { options: { __internal_claimKeylessApplicationUrl: this.#options.__internal_claimKeylessApplicationUrl, __internal_copyInstanceKeysUrl: this.#options.__internal_copyInstanceKeysUrl, + __internal_keylessWithClaimedKeys: this.#options.__internal_keylessWithClaimedKeys, }, }); }); diff --git a/packages/clerk-js/src/ui/Components.tsx b/packages/clerk-js/src/ui/Components.tsx index 0e35b0a7236..95a4f023504 100644 --- a/packages/clerk-js/src/ui/Components.tsx +++ b/packages/clerk-js/src/ui/Components.tsx @@ -522,6 +522,7 @@ const Components = (props: ComponentsProps) => { )} diff --git a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx index bf2c3a3f2b7..f109de8419c 100644 --- a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx +++ b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx @@ -14,6 +14,7 @@ import { useRevalidateEnvironment } from './use-revalidate-environment'; type KeylessPromptProps = { claimUrl: string; copyKeysUrl: string; + success: boolean; }; const buttonIdentifierPrefix = `--clerk-keyless-prompt`; @@ -29,6 +30,9 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => { const isForcedExpanded = claimed || success || isExpanded; + // Use this + // const showSuccessState = _props.success && claimed; + const baseElementStyles = css` box-sizing: border-box; padding: 0; diff --git a/packages/nextjs/src/app-router/server/ClerkProvider.tsx b/packages/nextjs/src/app-router/server/ClerkProvider.tsx index 6f7b8f8c97d..5b0f3ad2ebc 100644 --- a/packages/nextjs/src/app-router/server/ClerkProvider.tsx +++ b/packages/nextjs/src/app-router/server/ClerkProvider.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { PromisifiedAuthProvider } from '../../client-boundary/PromisifiedAuthProvider'; import { getDynamicAuthData } from '../../server/buildClerkProps'; +import { safeParseClerkFile } from '../../server/keyless-node'; import type { NextClerkProviderProps } from '../../types'; import { canUseKeyless } from '../../utils/feature-flags'; import { mergeNextClerkPropsWithEnv } from '../../utils/mergeNextClerkPropsWithEnv'; @@ -69,7 +70,8 @@ export async function ClerkProvider( ); - const shouldRunAsKeyless = !propsWithEnvs.publishableKey && canUseKeyless; + const runningWithClaimedKeys = propsWithEnvs.publishableKey === safeParseClerkFile()?.publishableKey; + const shouldRunAsKeyless = (!propsWithEnvs.publishableKey || runningWithClaimedKeys) && canUseKeyless; if (shouldRunAsKeyless) { // NOTE: Create or read keys on every render. Usually this means only on hard refresh or hard navigations. @@ -85,6 +87,7 @@ export async function ClerkProvider( publishableKey: newOrReadKeys.publishableKey, __internal_claimKeylessApplicationUrl: newOrReadKeys.claimUrl, __internal_copyInstanceKeysUrl: newOrReadKeys.apiKeysUrl, + __internal_keylessWithClaimedKeys: runningWithClaimedKeys, })} nonce={await generateNonce()} initialState={await generateStatePromise()} diff --git a/packages/nextjs/src/server/keyless-node.ts b/packages/nextjs/src/server/keyless-node.ts index 28226dba7a6..30a19415809 100644 --- a/packages/nextjs/src/server/keyless-node.ts +++ b/packages/nextjs/src/server/keyless-node.ts @@ -64,7 +64,7 @@ const getKeylessReadMePath = () => generatePath(_TEMP_DIR_NAME, 'README.md'); let isCreatingFile = false; -function safeParseClerkFile(): AccountlessApplication | undefined { +export function safeParseClerkFile(): AccountlessApplication | undefined { if (!nodeRuntime.fs) { throwMissingFsModule(); } diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 41bf0282eed..63901514768 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -777,6 +777,11 @@ export type ClerkOptions = ClerkOptionsNavigation & */ __internal_copyInstanceKeysUrl?: string; + /** + * When `true` a success state should be shown on Keyless prompt. It means that the developer has successfully using the claimed keys. + */ + __internal_keylessWithClaimedKeys?: boolean; + /** * [EXPERIMENTAL] Provide the underlying host router, required for the new experimental UI components. */ From 0a51adb4ec1c71ea22a924f8ccc7e8645a0a0806 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 17 Jan 2025 17:57:06 +0200 Subject: [PATCH 02/14] feat(nextjs): Run on keyless until after you dismiss 2 --- .../src/app-router/server/ClerkProvider.tsx | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/nextjs/src/app-router/server/ClerkProvider.tsx b/packages/nextjs/src/app-router/server/ClerkProvider.tsx index 5b0f3ad2ebc..fd0a1585950 100644 --- a/packages/nextjs/src/app-router/server/ClerkProvider.tsx +++ b/packages/nextjs/src/app-router/server/ClerkProvider.tsx @@ -79,23 +79,27 @@ export async function ClerkProvider( if (newOrReadKeys) { const KeylessCookieSync = await import('../client/keyless-cookie-sync.js').then(mod => mod.KeylessCookieSync); - output = ( - - - {children} - - + const clientProvider = ( + + {children} + ); + + if (runningWithClaimedKeys) { + output = clientProvider; + } else { + output = {clientProvider}; + } } } From ae68384fe20c15b5505ded7d2738d36c8c6a8958 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 17 Jan 2025 18:00:41 +0200 Subject: [PATCH 03/14] Delete `.clerk/` on dismiss --- packages/clerk-js/src/ui/Components.tsx | 1 + .../src/ui/components/KeylessPrompt/index.tsx | 15 ++++++- .../nextjs/src/app-router/keyless-actions.ts | 9 +++++ .../src/app-router/server/ClerkProvider.tsx | 2 + packages/nextjs/src/server/keyless-node.ts | 39 ++++++++++++++++++- packages/types/src/clerk.ts | 5 +++ 6 files changed, 69 insertions(+), 2 deletions(-) diff --git a/packages/clerk-js/src/ui/Components.tsx b/packages/clerk-js/src/ui/Components.tsx index 95a4f023504..bab6baec864 100644 --- a/packages/clerk-js/src/ui/Components.tsx +++ b/packages/clerk-js/src/ui/Components.tsx @@ -523,6 +523,7 @@ const Components = (props: ComponentsProps) => { claimUrl={state.options.__internal_claimKeylessApplicationUrl} copyKeysUrl={state.options.__internal_copyInstanceKeysUrl} success={state.options.__internal_keylessWithClaimedKeys || false} + onDismiss={state.options.__internal_keylessFinalize} /> )} diff --git a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx index f109de8419c..747a07c3ac9 100644 --- a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx +++ b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx @@ -15,6 +15,7 @@ type KeylessPromptProps = { claimUrl: string; copyKeysUrl: string; success: boolean; + onDismiss?: () => Promise; }; const buttonIdentifierPrefix = `--clerk-keyless-prompt`; @@ -25,7 +26,7 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => { const [isExpanded, setIsExpanded] = useState(false); const claimed = Boolean(useRevalidateEnvironment().authConfig.claimedAt); - const success = false; + const success = _props.success && claimed; const appName = useRevalidateEnvironment().displayConfig.applicationName; const isForcedExpanded = claimed || success || isExpanded; @@ -450,12 +451,24 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {

+ {_props.onDismiss && ( + + )} + {isForcedExpanded && (success ? ( ) : ( { + const url = new URL(_props.claimUrl); + url.searchParams.append('return_url', window.location.href); + return url.href; + })() + } target='_blank' rel='noopener noreferrer' data-expanded={isForcedExpanded} From 9d603e82f641b7b20480f43d060ef5a240d2dcf4 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 20 Jan 2025 12:31:05 +0200 Subject: [PATCH 06/14] improve code quality in keyless-node --- packages/nextjs/src/server/keyless-node.ts | 106 +++++++++++---------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/packages/nextjs/src/server/keyless-node.ts b/packages/nextjs/src/server/keyless-node.ts index 5ff989cad5e..ffc73d64234 100644 --- a/packages/nextjs/src/server/keyless-node.ts +++ b/packages/nextjs/src/server/keyless-node.ts @@ -25,20 +25,29 @@ const throwMissingFsModule = () => { throw "Clerk: fsModule.fs is missing. This is an internal error. Please contact Clerk's support."; }; -/** - * The `.clerk/` is NOT safe to be commited as it may include sensitive information about a Clerk instance. - * It may include an instance's secret key and the secret token for claiming that instance. - */ -function updateGitignore() { +const safeNodeRuntimeFs = () => { if (!nodeRuntime.fs) { throwMissingFsModule(); } - const { existsSync, writeFileSync, readFileSync, appendFileSync } = nodeRuntime.fs; + return nodeRuntime.fs; +}; +const safeNodeRuntimePath = () => { if (!nodeRuntime.path) { throwMissingFsModule(); } - const gitignorePath = nodeRuntime.path.join(process.cwd(), '.gitignore'); + return nodeRuntime.path; +}; + +/** + * The `.clerk/` is NOT safe to be commited as it may include sensitive information about a Clerk instance. + * It may include an instance's secret key and the secret token for claiming that instance. + */ +function updateGitignore() { + const { existsSync, writeFileSync, readFileSync, appendFileSync } = safeNodeRuntimeFs(); + + const path = safeNodeRuntimePath(); + const gitignorePath = path.join(process.cwd(), '.gitignore'); if (!existsSync(gitignorePath)) { writeFileSync(gitignorePath, ''); } @@ -52,10 +61,8 @@ function updateGitignore() { } const generatePath = (...slugs: string[]) => { - if (!nodeRuntime.path) { - throwMissingFsModule(); - } - return nodeRuntime.path.join(process.cwd(), CLERK_HIDDEN, ...slugs); + const path = safeNodeRuntimePath(); + return path.join(process.cwd(), CLERK_HIDDEN, ...slugs); }; const _TEMP_DIR_NAME = '.tmp'; @@ -65,10 +72,7 @@ const getKeylessReadMePath = () => generatePath(_TEMP_DIR_NAME, 'README.md'); let isCreatingFile = false; export function safeParseClerkFile(): AccountlessApplication | undefined { - if (!nodeRuntime.fs) { - throwMissingFsModule(); - } - const { readFileSync } = nodeRuntime.fs; + const { readFileSync } = safeNodeRuntimeFs(); try { const CONFIG_PATH = getKeylessConfigurationPath(); let fileAsString; @@ -87,20 +91,11 @@ const createMessage = (keys: AccountlessApplication) => { return `\n\x1b[35m\n[Clerk]:\x1b[0m You are running in keyless mode.\nYou can \x1b[35mclaim your keys\x1b[0m by visiting ${keys.claimUrl}\n`; }; -async function createOrReadKeyless(): Promise { - if (!nodeRuntime.fs) { - // This should never happen. - throwMissingFsModule(); - } - const { existsSync, writeFileSync, mkdirSync, rmSync } = nodeRuntime.fs; - - /** - * If another request is already in the process of acquiring keys return early. - * Using both an in-memory and file system lock seems to be the most effective solution. - */ - if (isCreatingFile || existsSync(CLERK_LOCK)) { - return undefined; - } +/** + * Using both an in-memory and file system lock seems to be the most effective solution. + */ +const lockFileWriting = () => { + const { writeFileSync } = safeNodeRuntimeFs(); isCreatingFile = true; @@ -114,6 +109,32 @@ async function createOrReadKeyless(): Promise { + const { rmSync } = safeNodeRuntimeFs(); + + rmSync(CLERK_LOCK, { force: true, recursive: true }); + isCreatingFile = false; +}; + +const isFileWritingLocked = () => { + const { existsSync } = safeNodeRuntimeFs(); + return isCreatingFile || existsSync(CLERK_LOCK); +}; + +async function createOrReadKeyless(): Promise { + const { writeFileSync, mkdirSync, rmSync } = safeNodeRuntimeFs(); + + /** + * If another request is already in the process of acquiring keys return early. + * Using both an in-memory and file system lock seems to be the most effective solution. + */ + if (isFileWritingLocked()) { + return undefined; + } + + lockFileWriting(); const CONFIG_PATH = getKeylessConfigurationPath(); const README_PATH = getKeylessReadMePath(); @@ -169,47 +190,30 @@ This directory is auto-generated from \`@clerk/nextjs\` because you are running /** * Clean up locks. */ - rmSync(CLERK_LOCK, { force: true, recursive: true }); - isCreatingFile = false; + unlockFileWriting(); return accountlessApplication; } function removeKeyless() { - if (!nodeRuntime.fs) { - // This should never happen. - throwMissingFsModule(); - } - const { existsSync, writeFileSync, rmSync } = nodeRuntime.fs; + const { rmSync } = safeNodeRuntimeFs(); /** * If another request is already in the process of acquiring keys return early. * Using both an in-memory and file system lock seems to be the most effective solution. */ - if (isCreatingFile || existsSync(CLERK_LOCK)) { + if (isFileWritingLocked()) { return undefined; } - isCreatingFile = true; - - writeFileSync( - CLERK_LOCK, - // In the rare case, the file persists give the developer enough context. - 'This file can be deleted. Please delete this file and refresh your application', - { - encoding: 'utf8', - mode: '0777', - flag: 'w', - }, - ); + lockFileWriting(); rmSync(generatePath(), { force: true, recursive: true }); /** * Clean up locks. */ - rmSync(CLERK_LOCK, { force: true, recursive: true }); - isCreatingFile = false; + unlockFileWriting(); } export { createOrReadKeyless, removeKeyless }; From 85fc605dd7244d36ece49e06031956e387b442b5 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 20 Jan 2025 12:36:48 +0200 Subject: [PATCH 07/14] fix issue from merging --- packages/nextjs/src/app-router/keyless-actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/src/app-router/keyless-actions.ts b/packages/nextjs/src/app-router/keyless-actions.ts index e064ad3b5ba..08cfa3859b3 100644 --- a/packages/nextjs/src/app-router/keyless-actions.ts +++ b/packages/nextjs/src/app-router/keyless-actions.ts @@ -56,7 +56,7 @@ export async function createOrReadKeylessAction(): Promise Date: Mon, 20 Jan 2025 13:26:16 +0200 Subject: [PATCH 08/14] cleanup --- packages/clerk-js/src/core/clerk.ts | 3 +- packages/clerk-js/src/ui/Components.tsx | 3 +- .../src/ui/components/KeylessPrompt/index.tsx | 32 ++++++++++--------- .../nextjs/src/app-router/keyless-actions.ts | 2 +- .../src/app-router/server/ClerkProvider.tsx | 3 +- packages/types/src/clerk.ts | 10 ++---- 6 files changed, 25 insertions(+), 28 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 34cf8c71d1f..4063983e5d0 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -2100,11 +2100,12 @@ export class Clerk implements ClerkInterface { #handleKeylessPrompt = () => { if (this.#options.__internal_claimKeylessApplicationUrl) { void this.#componentControls?.ensureMounted().then(controls => { + // TODO(@pantelis): Investigate if this resets existing props controls.updateProps({ options: { __internal_claimKeylessApplicationUrl: this.#options.__internal_claimKeylessApplicationUrl, __internal_copyInstanceKeysUrl: this.#options.__internal_copyInstanceKeysUrl, - __internal_keylessWithClaimedKeys: this.#options.__internal_keylessWithClaimedKeys, + __internal_keyless_dismissPrompt: this.#options.__internal_keyless_dismissPrompt, }, }); }); diff --git a/packages/clerk-js/src/ui/Components.tsx b/packages/clerk-js/src/ui/Components.tsx index bab6baec864..6e4ff92c2e3 100644 --- a/packages/clerk-js/src/ui/Components.tsx +++ b/packages/clerk-js/src/ui/Components.tsx @@ -522,8 +522,7 @@ const Components = (props: ComponentsProps) => { )} diff --git a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx index 67c1f043f00..5d6de86f72b 100644 --- a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx +++ b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx @@ -14,8 +14,7 @@ import { useRevalidateEnvironment } from './use-revalidate-environment'; type KeylessPromptProps = { claimUrl: string; copyKeysUrl: string; - success: boolean; - onDismiss?: () => Promise; + onDismiss: (() => Promise) | undefined; }; const buttonIdentifierPrefix = `--clerk-keyless-prompt`; @@ -24,10 +23,11 @@ const contentIdentifier = `${buttonIdentifierPrefix}-content`; const _KeylessPrompt = (_props: KeylessPromptProps) => { const [isExpanded, setIsExpanded] = useState(false); - const claimed = Boolean(useRevalidateEnvironment().authConfig.claimedAt); + const environment = useRevalidateEnvironment(); + const claimed = Boolean(environment.authConfig.claimedAt); - const success = _props.success && claimed; - const appName = useRevalidateEnvironment().displayConfig.applicationName; + const success = typeof _props.onDismiss === 'function' && claimed; + const appName = environment.displayConfig.applicationName; const isForcedExpanded = claimed || success || isExpanded; @@ -72,6 +72,7 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => { text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.32); white-space: nowrap; user-select: none; + cursor: pointer; background: linear-gradient(180deg, rgba(0, 0, 0, 0) 30.5%, rgba(0, 0, 0, 0.05) 100%), #454545; box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.04) inset, @@ -357,6 +358,7 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => { color: #8c8c8c; transition: color 130ms ease-out; display: ${isExpanded && !claimed && !success ? 'block' : 'none'}; + cursor: pointer; :hover { color: #eeeeee; @@ -448,16 +450,16 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {

- {_props.onDismiss && ( - - )} + {/*{_props.onDismiss && (*/} + {/* {*/} + {/* await _props.onDismiss?.();*/} + {/* window.location.reload();*/} + {/* }}*/} + {/* >*/} + {/* Delete*/} + {/* */} + {/*)}*/} {isForcedExpanded && (success ? ( diff --git a/packages/nextjs/src/app-router/keyless-actions.ts b/packages/nextjs/src/app-router/keyless-actions.ts index 08cfa3859b3..1d4dbdf91aa 100644 --- a/packages/nextjs/src/app-router/keyless-actions.ts +++ b/packages/nextjs/src/app-router/keyless-actions.ts @@ -57,7 +57,7 @@ export async function createOrReadKeylessAction(): Promise m.removeKeyless()); diff --git a/packages/nextjs/src/app-router/server/ClerkProvider.tsx b/packages/nextjs/src/app-router/server/ClerkProvider.tsx index 0bffa1d299b..9cc319801df 100644 --- a/packages/nextjs/src/app-router/server/ClerkProvider.tsx +++ b/packages/nextjs/src/app-router/server/ClerkProvider.tsx @@ -87,8 +87,7 @@ export async function ClerkProvider( publishableKey: newOrReadKeys.publishableKey, __internal_claimKeylessApplicationUrl: newOrReadKeys.claimUrl, __internal_copyInstanceKeysUrl: newOrReadKeys.apiKeysUrl, - __internal_keylessWithClaimedKeys: runningWithClaimedKeys, - __internal_keylessFinalize: deleteKeylessAction, + __internal_keyless_dismissPrompt: runningWithClaimedKeys ? deleteKeylessAction : undefined, })} nonce={await generateNonce()} initialState={await generateStatePromise()} diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 6a70aa5c4b2..a33f7413705 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -778,14 +778,10 @@ export type ClerkOptions = ClerkOptionsNavigation & __internal_copyInstanceKeysUrl?: string; /** - * When `true` a success state should be shown on Keyless prompt. It means that the developer has successfully using the claimed keys. + * Pass a function that will trigger the unmounting of the Keyless Prompt. + * It should cause the values of `__internal_claimKeylessApplicationUrl` and `__internal_copyInstanceKeysUrl` to become undefined. */ - __internal_keylessWithClaimedKeys?: boolean; - - /** - * When `true` a success state should be shown on Keyless prompt. It means that the developer has successfully using the claimed keys. - */ - __internal_keylessFinalize?: () => Promise; + __internal_keyless_dismissPrompt?: () => Promise; /** * [EXPERIMENTAL] Provide the underlying host router, required for the new experimental UI components. From 0499bb9dc932d101605571e37bfbc06acbb108b1 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 20 Jan 2025 13:53:11 +0200 Subject: [PATCH 09/14] improve code quality --- .../src/ui/components/KeylessPrompt/index.tsx | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx index 5d6de86f72b..789e54a36d6 100644 --- a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx +++ b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx @@ -1,7 +1,7 @@ // eslint-disable-next-line no-restricted-imports import { css } from '@emotion/react'; import type { PropsWithChildren } from 'react'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { createPortal } from 'react-dom'; import { Flex } from '../../customizables'; @@ -31,6 +31,17 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => { const isForcedExpanded = claimed || success || isExpanded; + const urlToDashboard = useMemo(() => { + if (claimed) { + return _props.copyKeysUrl; + } + + const url = new URL(_props.claimUrl); + // Clerk Dashboard accepts a `return_url` query param when visiting `/apps/claim`. + url.searchParams.append('return_url', window.location.href); + return url.href; + }, [claimed, _props.copyKeysUrl, _props.claimUrl]); + const baseElementStyles = css` box-sizing: border-box; padding: 0; @@ -450,17 +461,6 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => {

- {/*{_props.onDismiss && (*/} - {/* {*/} - {/* await _props.onDismiss?.();*/} - {/* window.location.reload();*/} - {/* }}*/} - {/* >*/} - {/* Delete*/} - {/* */} - {/*)}*/} - {isForcedExpanded && (success ? ( ) : (
{ - const url = new URL(_props.claimUrl); - url.searchParams.append('return_url', window.location.href); - return url.href; - })() - } + href={urlToDashboard} target='_blank' rel='noopener noreferrer' data-expanded={isForcedExpanded} From 8c32b37bacae491e05ca8c28a46a4cd5398bf527 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 20 Jan 2025 13:59:27 +0200 Subject: [PATCH 10/14] changeset --- .changeset/thin-wolves-camp.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/thin-wolves-camp.md diff --git a/.changeset/thin-wolves-camp.md b/.changeset/thin-wolves-camp.md new file mode 100644 index 00000000000..20e9f51b749 --- /dev/null +++ b/.changeset/thin-wolves-camp.md @@ -0,0 +1,7 @@ +--- +'@clerk/clerk-js': minor +'@clerk/nextjs': minor +'@clerk/types': minor +--- + +Display keyless prompt until the developer manually dismisses it. From cea4d7b9b974b1c1a7798cb688292088347d67fc Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 20 Jan 2025 14:12:21 +0200 Subject: [PATCH 11/14] bump bundlewatch --- packages/clerk-js/bundlewatch.config.json | 2 +- packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index 5bab893c45a..7335d9c08dd 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -18,6 +18,6 @@ { "path": "./dist/userverification*.js", "maxSize": "5KB" }, { "path": "./dist/onetap*.js", "maxSize": "1KB" }, { "path": "./dist/waitlist*.js", "maxSize": "1.3KB" }, - { "path": "./dist/keylessPrompt*.js", "maxSize": "4.9KB" } + { "path": "./dist/keylessPrompt*.js", "maxSize": "5.5KB" } ] } diff --git a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx index 789e54a36d6..83fb16f90e2 100644 --- a/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx +++ b/packages/clerk-js/src/ui/components/KeylessPrompt/index.tsx @@ -471,7 +471,6 @@ const _KeylessPrompt = (_props: KeylessPromptProps) => { }} css={css` ${mainCTAStyles}; - &:hover { background: #4b4b4b; transition: all 120ms ease-in-out; From 27f72fe9adce7261e57839b453d119c2ea386ae2 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 20 Jan 2025 18:07:06 +0200 Subject: [PATCH 12/14] error handling on `rmSync` --- packages/nextjs/src/server/keyless-node.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/nextjs/src/server/keyless-node.ts b/packages/nextjs/src/server/keyless-node.ts index ffc73d64234..1593f5e3fc9 100644 --- a/packages/nextjs/src/server/keyless-node.ts +++ b/packages/nextjs/src/server/keyless-node.ts @@ -114,7 +114,12 @@ const lockFileWriting = () => { const unlockFileWriting = () => { const { rmSync } = safeNodeRuntimeFs(); - rmSync(CLERK_LOCK, { force: true, recursive: true }); + try { + rmSync(CLERK_LOCK, { force: true, recursive: true }); + } catch (e) { + // Simply ignore if the removal of the directory/file fails + } + isCreatingFile = false; }; @@ -124,7 +129,7 @@ const isFileWritingLocked = () => { }; async function createOrReadKeyless(): Promise { - const { writeFileSync, mkdirSync, rmSync } = safeNodeRuntimeFs(); + const { writeFileSync, mkdirSync } = safeNodeRuntimeFs(); /** * If another request is already in the process of acquiring keys return early. @@ -147,8 +152,7 @@ async function createOrReadKeyless(): Promise Date: Mon, 20 Jan 2025 20:36:35 +0200 Subject: [PATCH 13/14] Update packages/nextjs/src/server/keyless-node.ts Co-authored-by: Bryce Kalow --- packages/nextjs/src/server/keyless-node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/src/server/keyless-node.ts b/packages/nextjs/src/server/keyless-node.ts index 1593f5e3fc9..21c9dee3987 100644 --- a/packages/nextjs/src/server/keyless-node.ts +++ b/packages/nextjs/src/server/keyless-node.ts @@ -40,7 +40,7 @@ const safeNodeRuntimePath = () => { }; /** - * The `.clerk/` is NOT safe to be commited as it may include sensitive information about a Clerk instance. + * The `.clerk/` directory is NOT safe to be committed as it may include sensitive information about a Clerk instance. * It may include an instance's secret key and the secret token for claiming that instance. */ function updateGitignore() { From 14580fdfb2caea636b7239fdf5a7ca86d12b2a21 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Tue, 21 Jan 2025 14:35:55 +0200 Subject: [PATCH 14/14] prefix types with `__internal_keyless_` --- packages/clerk-js/src/core/clerk.ts | 6 +++--- packages/clerk-js/src/ui/Components.tsx | 19 ++++++++++--------- .../src/app-router/server/ClerkProvider.tsx | 4 ++-- packages/types/src/clerk.ts | 4 ++-- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 4063983e5d0..f1632f19e2f 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -2098,13 +2098,13 @@ export class Clerk implements ClerkInterface { }; #handleKeylessPrompt = () => { - if (this.#options.__internal_claimKeylessApplicationUrl) { + if (this.#options.__internal_keyless_claimKeylessApplicationUrl) { void this.#componentControls?.ensureMounted().then(controls => { // TODO(@pantelis): Investigate if this resets existing props controls.updateProps({ options: { - __internal_claimKeylessApplicationUrl: this.#options.__internal_claimKeylessApplicationUrl, - __internal_copyInstanceKeysUrl: this.#options.__internal_copyInstanceKeysUrl, + __internal_keyless_claimKeylessApplicationUrl: this.#options.__internal_keyless_claimKeylessApplicationUrl, + __internal_keyless_copyInstanceKeysUrl: this.#options.__internal_keyless_copyInstanceKeysUrl, __internal_keyless_dismissPrompt: this.#options.__internal_keyless_dismissPrompt, }, }); diff --git a/packages/clerk-js/src/ui/Components.tsx b/packages/clerk-js/src/ui/Components.tsx index 6e4ff92c2e3..eac3243a9b5 100644 --- a/packages/clerk-js/src/ui/Components.tsx +++ b/packages/clerk-js/src/ui/Components.tsx @@ -517,15 +517,16 @@ const Components = (props: ComponentsProps) => { )} - {state.options?.__internal_claimKeylessApplicationUrl && state.options?.__internal_copyInstanceKeysUrl && ( - - - - )} + {state.options?.__internal_keyless_claimKeylessApplicationUrl && + state.options?.__internal_keyless_copyInstanceKeysUrl && ( + + + + )} {state.organizationSwitcherPrefetch && } diff --git a/packages/nextjs/src/app-router/server/ClerkProvider.tsx b/packages/nextjs/src/app-router/server/ClerkProvider.tsx index 9cc319801df..541d8c405f8 100644 --- a/packages/nextjs/src/app-router/server/ClerkProvider.tsx +++ b/packages/nextjs/src/app-router/server/ClerkProvider.tsx @@ -85,8 +85,8 @@ export async function ClerkProvider( {...mergeNextClerkPropsWithEnv({ ...rest, publishableKey: newOrReadKeys.publishableKey, - __internal_claimKeylessApplicationUrl: newOrReadKeys.claimUrl, - __internal_copyInstanceKeysUrl: newOrReadKeys.apiKeysUrl, + __internal_keyless_claimKeylessApplicationUrl: newOrReadKeys.claimUrl, + __internal_keyless_copyInstanceKeysUrl: newOrReadKeys.apiKeysUrl, __internal_keyless_dismissPrompt: runningWithClaimedKeys ? deleteKeylessAction : undefined, })} nonce={await generateNonce()} diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index a33f7413705..d3e0b37b0b5 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -770,12 +770,12 @@ export type ClerkOptions = ClerkOptionsNavigation & /** * The URL a developer should be redirected to in order to claim an instance created in Keyless mode. */ - __internal_claimKeylessApplicationUrl?: string; + __internal_keyless_claimKeylessApplicationUrl?: string; /** * After a developer has claimed their instance created by Keyless mode, they can use this URL to find their instance's keys */ - __internal_copyInstanceKeysUrl?: string; + __internal_keyless_copyInstanceKeysUrl?: string; /** * Pass a function that will trigger the unmounting of the Keyless Prompt.