From db3ef38e5b73f34ddd4d3009dac4291f8f8b39c4 Mon Sep 17 00:00:00 2001 From: nikospapcom Date: Mon, 16 Dec 2024 17:13:16 +0200 Subject: [PATCH] fix(clerk-js,clerk-react,types,elements): Optimize Coinbase SDK loading Instead of lazy loading the Coinbase SDK in getEthereumProvider(), move loading when the SignIn/SignUp components mount. This fix avoid the Safari to block the Coinbase popup --- .changeset/healthy-pears-play.md | 8 +++++ packages/clerk-js/src/core/clerk.ts | 36 ++++++++++++++++++- packages/clerk-js/src/utils/web3.ts | 2 +- .../elements/src/react/common/connections.tsx | 8 ++++- packages/react/src/isomorphicClerk.ts | 11 ++++++ packages/types/src/clerk.ts | 5 +++ 6 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 .changeset/healthy-pears-play.md diff --git a/.changeset/healthy-pears-play.md b/.changeset/healthy-pears-play.md new file mode 100644 index 0000000000..86720e38e3 --- /dev/null +++ b/.changeset/healthy-pears-play.md @@ -0,0 +1,8 @@ +--- +'@clerk/clerk-js': patch +'@clerk/elements': patch +'@clerk/clerk-react': patch +'@clerk/types': patch +--- + +Move Coinbase SDK loading in the SignIn/SignUp components. This fix avoid the Safari to block the Coinbase popup diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index b1cfd5d586..c5da28a845 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -7,7 +7,7 @@ import { logger } from '@clerk/shared/logger'; import { isHttpOrHttps, isValidProxyUrl, proxyUrlToAbsoluteURL } from '@clerk/shared/proxy'; import { eventPrebuiltComponentMounted, TelemetryCollector } from '@clerk/shared/telemetry'; import { addClerkPrefix, stripScheme } from '@clerk/shared/url'; -import { handleValueOrFn, noop } from '@clerk/shared/utils'; +import { createDeferredPromise, handleValueOrFn, noop } from '@clerk/shared/utils'; import type { __internal_UserVerificationModalProps, ActiveSessionResource, @@ -65,7 +65,9 @@ import type { WaitlistProps, WaitlistResource, Web3Provider, + Web3Strategy, } from '@clerk/types'; +import type CoinbaseWalletSDK from '@coinbase/wallet-sdk'; import type { MountComponentRenderer } from '../ui/Components'; import { UI } from '../ui/new'; @@ -138,6 +140,7 @@ declare global { __clerk_publishable_key?: string; __clerk_proxy_url?: ClerkInterface['proxyUrl']; __clerk_domain?: ClerkInterface['domain']; + __clerk_coinbase_sdk: Promise; } } @@ -180,6 +183,7 @@ export class Clerk implements ClerkInterface { protected internal_last_error: ClerkAPIError | null = null; // converted to protected environment to support `updateEnvironment` type assertion protected environment?: EnvironmentResource | null; + private __promise = createDeferredPromise(); #publishableKey = ''; #domain: DomainOrProxyUrl['domain']; @@ -247,6 +251,20 @@ export class Clerk implements ClerkInterface { return false; } + get isCoinbaseWalletEnabled(): boolean { + const attributes = this.environment?.userSettings.attributes; + if (!attributes) { + return false; + } + + const findWeb3Attributes = Object.entries(attributes) + .filter(([name, attr]) => attr.used_for_first_factor && name.startsWith('web3')) + .map(([, desc]) => desc.first_factors) + .flat() as any as Web3Strategy[]; + + return findWeb3Attributes.includes('web3_coinbase_wallet_signature'); + } + get domain(): string { if (inBrowser()) { const strippedDomainString = stripScheme(handleValueOrFn(this.#domain, new URL(window.location.href))); @@ -582,6 +600,7 @@ export class Clerk implements ClerkInterface { this.__experimental_ui.mount('SignIn', node, props); } else { this.assertComponentsReady(this.#componentControls); + void this.__internal_warmupDependencies(); void this.#componentControls.ensureMounted({ preloadHint: 'SignIn' }).then(controls => controls.mountComponent({ name: 'SignIn', @@ -608,6 +627,7 @@ export class Clerk implements ClerkInterface { this.__experimental_ui.mount('SignUp', node, props); } else { this.assertComponentsReady(this.#componentControls); + void this.__internal_warmupDependencies(); void this.#componentControls.ensureMounted({ preloadHint: 'SignUp' }).then(controls => controls.mountComponent({ name: 'SignUp', @@ -1654,6 +1674,16 @@ export class Clerk implements ClerkInterface { this.environment = environment; } + public __internal_warmupDependencies = async (): Promise => { + if (this.isCoinbaseWalletEnabled) { + window.__clerk_coinbase_sdk = this.__promise.promise as Promise; + + await import('@coinbase/wallet-sdk').then(mod => { + this.__promise.resolve(mod.CoinbaseWalletSDK); + }); + } + }; + __internal_setCountry = (country: string | null) => { if (!this.__internal_country) { this.__internal_country = country; @@ -1909,6 +1939,10 @@ export class Clerk implements ClerkInterface { initComponents(); + if (!Clerk.mountComponentRenderer) { + void this.__internal_warmupDependencies(); + } + break; } catch (err) { if (isError(err, 'dev_browser_unauthenticated')) { diff --git a/packages/clerk-js/src/utils/web3.ts b/packages/clerk-js/src/utils/web3.ts index 2bec8b08f8..0a39b1ce7c 100644 --- a/packages/clerk-js/src/utils/web3.ts +++ b/packages/clerk-js/src/utils/web3.ts @@ -71,7 +71,7 @@ export async function generateSignatureWithOKXWallet(params: GenerateSignaturePa async function getEthereumProvider(provider: Web3Provider) { if (provider === 'coinbase_wallet') { - const CoinbaseWalletSDK = await import('@coinbase/wallet-sdk').then(mod => mod.CoinbaseWalletSDK); + const CoinbaseWalletSDK = await window.__clerk_coinbase_sdk; const sdk = new CoinbaseWalletSDK({}); return sdk.makeWeb3Provider({ options: 'all' }); } diff --git a/packages/elements/src/react/common/connections.tsx b/packages/elements/src/react/common/connections.tsx index 0f94933774..e5e41f71c2 100644 --- a/packages/elements/src/react/common/connections.tsx +++ b/packages/elements/src/react/common/connections.tsx @@ -1,6 +1,7 @@ +import { useClerk } from '@clerk/shared/react'; import type { OAuthProvider, SamlStrategy, Web3Provider } from '@clerk/types'; import { Slot } from '@radix-ui/react-slot'; -import { createContext, useContext } from 'react'; +import { createContext, useContext, useEffect } from 'react'; import type { ThirdPartyProvider } from '~/utils/third-party-strategies'; @@ -50,10 +51,15 @@ export interface ConnectionProps extends React.ButtonHTMLAttributes */ export function Connection({ asChild, name, ...rest }: ConnectionProps) { + const clerk = useClerk(); const signInRef = SignInRouterCtx.useActorRef(true); const signUpRef = SignUpRouterCtx.useActorRef(true); const provider = useThirdPartyProvider((signInRef || signUpRef)!, name); + useEffect(() => { + void clerk.__internal_warmupDependencies(); + }, [clerk]); + if (!provider) { return null; } diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index f27c338546..c26f723f71 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -123,6 +123,7 @@ type IsomorphicLoadedClerk = Without< | 'client' | '__internal_getCachedResources' | '__internal_reloadInitialResources' + | '__internal_warmupDependencies' > & { // TODO: Align return type and parms handleRedirectCallback: (params: HandleOAuthCallbackParams) => void; @@ -173,6 +174,7 @@ type IsomorphicLoadedClerk = Without< mountSignIn: (node: HTMLDivElement, props: SignInProps) => void; mountUserProfile: (node: HTMLDivElement, props: UserProfileProps) => void; mountWaitlist: (node: HTMLDivElement, props: WaitlistProps) => void; + __internal_warmupDependencies: () => void; client: ClientResource | undefined; }; @@ -1214,4 +1216,13 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { this.premountMethodCalls.set('signOut', callback); } }; + + __internal_warmupDependencies = async (): Promise => { + const callback = () => this.clerkjs?.__internal_warmupDependencies(); + if (this.clerkjs && this.#loaded) { + return callback() as Promise; + } else { + this.premountMethodCalls.set('__internal_warmupDependencies', callback); + } + }; } diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 464886a76f..f480843ea2 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -143,6 +143,9 @@ export interface Clerk { telemetry: TelemetryCollector | undefined; + /** Check if coinbase wallet is enabled. */ + isCoinbaseWalletEnabled: boolean; + __internal_country?: string | null; /** @@ -606,6 +609,8 @@ export interface Clerk { * This funtion is used to reload the initial resources (Environment/Client) from the Frontend API. **/ __internal_reloadInitialResources: () => Promise; + + __internal_warmupDependencies: () => Promise; } export type HandleOAuthCallbackParams = TransferableOption &