diff --git a/packages/modal/src/modalManager.ts b/packages/modal/src/modalManager.ts index 1c4d1cadd..ff2ea12a4 100644 --- a/packages/modal/src/modalManager.ts +++ b/packages/modal/src/modalManager.ts @@ -603,9 +603,8 @@ export class Web3Auth extends Web3AuthNoModal implements IWeb3AuthModal { }): Promise => { try { const connector = this.getConnector(params.connector as WALLET_CONNECTOR_TYPE, params.loginParams?.chainNamespace); - // auto-connect WalletConnect and non-injected MetaMask in background to generate QR code URI without interfering with user's selected connection - const shouldStartConnectionInBackground = - connector.name === WALLET_CONNECTORS.WALLET_CONNECT_V2 || (connector.name === WALLET_CONNECTORS.METAMASK && !connector.isInjected); + // auto-connect WalletConnect in background to generate QR code URI without interfering with user's selected connection + const shouldStartConnectionInBackground = connector.name === WALLET_CONNECTORS.WALLET_CONNECT_V2; if (shouldStartConnectionInBackground) { const initialChain = this.getInitialChainIdForConnector(connector); await connector.connect({ chainId: initialChain.chainId }); @@ -646,32 +645,6 @@ export class Web3Auth extends Web3AuthNoModal implements IWeb3AuthModal { wcConnector.status = CONNECTOR_STATUS.READY; } } - - // handle MM session refresh if MM is not injected - const metamaskConnector = this.getConnector(WALLET_CONNECTORS.METAMASK); - if (metamaskConnector && !metamaskConnector.isInjected) { - const status = metamaskConnector?.status; - log.debug("trying refreshing MM session", visibility, status); - if (visibility && (status === CONNECTOR_STATUS.READY || status === CONNECTOR_STATUS.CONNECTING)) { - log.debug("refreshing MM session"); - - // refreshing session for MM whenever modal is opened. - try { - const initialChain = this.getInitialChainIdForConnector(metamaskConnector); - metamaskConnector.connect({ chainId: initialChain.chainId }); - } catch (error) { - log.error(`Error while connecting to MM`, error); - } - } - if ( - !visibility && - this.status === CONNECTOR_STATUS.CONNECTED && - (status === CONNECTOR_STATUS.READY || status === CONNECTOR_STATUS.CONNECTING) - ) { - log.debug("this stops MM connector from trying to reconnect once proposal expires"); - metamaskConnector.status = CONNECTOR_STATUS.READY; - } - } }; private getChainNamespaces = (): ChainNamespaceType[] => { diff --git a/packages/modal/src/ui/components/Button/ButtonWallet/ButtonWallet.tsx b/packages/modal/src/ui/components/Button/ButtonWallet/ButtonWallet.tsx index cd40927d7..622e098f9 100644 --- a/packages/modal/src/ui/components/Button/ButtonWallet/ButtonWallet.tsx +++ b/packages/modal/src/ui/components/Button/ButtonWallet/ButtonWallet.tsx @@ -15,7 +15,7 @@ function ButtonWallet(props: ButtonWalletProps) { const isDark = useContext(ThemedContext); const isLink = useMemo( - () => deviceDetails.platform !== "desktop" && button.href && button.hasWalletConnect && !button.hasInjectedWallet, + () => deviceDetails.platform !== "desktop" && button.href && button.hasWalletConnect && !button.isInstalled, [deviceDetails, button] ); diff --git a/packages/modal/src/ui/components/ConnectWallet/ConnectWallet.tsx b/packages/modal/src/ui/components/ConnectWallet/ConnectWallet.tsx index 4187da83d..d0e4a5652 100644 --- a/packages/modal/src/ui/components/ConnectWallet/ConnectWallet.tsx +++ b/packages/modal/src/ui/components/ConnectWallet/ConnectWallet.tsx @@ -152,6 +152,16 @@ function ConnectWallet(props: ConnectWalletProps) { return walletDiscoverySupported ? defaultButtons.length : installedWalletButtons.length; }, [walletDiscoverySupported, defaultButtons, installedWalletButtons, isShowAllWallets, totalExternalWalletsCount]); + /** + * Wallet click logic + * - For installed wallets + * - For MetaMask non-injected on desktop, show QR code for connection + * - Ask user to select a chain namespace if it has multiple namespaces + * - Otherwise, use their connectors to connect + * - For wallet-discovery wallets (not installed) + * - On desktop, show QR code for connection if wallet connect v2 is supported, otherwise show install links + * - On mobile, open deeplink with wallet connect uri (won't go into this function as it'll open the deeplink) + */ const handleWalletClick = (button: ExternalButton) => { analytics?.track(ANALYTICS_EVENTS.EXTERNAL_WALLET_SELECTED, { connector: button.isInstalled ? button.name : button.hasWalletConnect ? WALLET_CONNECTORS.WALLET_CONNECT_V2 : "", @@ -165,41 +175,50 @@ function ConnectWallet(props: ConnectWalletProps) { total_external_wallets: allUniqueButtons.length, }); - // show chain namespace selector if the button is an injected connector with multiple chain namespaces - const isChainNamespaceSelectorRequired = button.hasInjectedWallet && button.chainNamespaces?.length > 1; - if (isChainNamespaceSelectorRequired) { - setBodyState({ - ...bodyState, - multiChainSelector: { - show: true, - wallet: button, - }, - }); - return; - } + // for installed wallets + if (button.isInstalled) { + // for MetaMask non-injected on desktop, show QR code for connection + if (button.name === WALLET_CONNECTORS.METAMASK && !button.hasInjectedWallet && deviceDetails.platform === "desktop") { + handleExternalWalletClick({ connector: button.name }); + setSelectedButton(button); + setSelectedWallet(true); + setCurrentPage(CONNECT_WALLET_PAGES.SELECTED_WALLET); + handleWalletDetailsHeight(); + return; + } - // connect with connector if injected and single chain namespace or custom connector (except MetaMask) - const isInjectedConnectorAndSingleChainNamespace = button.hasInjectedWallet && button.chainNamespaces?.length === 1; - const isCustomConnector = !button.hasInjectedWallet && button.isInstalled; - if (isInjectedConnectorAndSingleChainNamespace || (isCustomConnector && button.name !== WALLET_CONNECTORS.METAMASK)) { - return handleExternalWalletClick({ connector: button.name }); - } + // show chain namespace selector if the button has multiple chain namespaces + if (button.chainNamespaces?.length > 1) { + setBodyState({ + ...bodyState, + multiChainSelector: { + show: true, + wallet: button, + }, + }); + return; + } - // show QR code for wallet connect v2 and MM (non-injected) - if (button.hasWalletConnect) { - setSelectedButton(button); - setSelectedWallet(true); - setCurrentPage(CONNECT_WALLET_PAGES.SELECTED_WALLET); - handleWalletDetailsHeight(); + // otherwise, use their connectors to connect + handleExternalWalletClick({ connector: button.name }); + return; } else { - // show install links - setBodyState({ - ...bodyState, - installLinks: { - show: true, - wallet: button, - }, - }); + // show QR code if wallet connect v2 is supported + if (button.hasWalletConnect) { + setSelectedButton(button); + setSelectedWallet(true); + setCurrentPage(CONNECT_WALLET_PAGES.SELECTED_WALLET); + handleWalletDetailsHeight(); + } else { + // otherwise, show install links + setBodyState({ + ...bodyState, + installLinks: { + show: true, + wallet: button, + }, + }); + } } }; diff --git a/packages/modal/src/ui/components/ConnectWallet/ConnectWalletChainNamespaceSelect/ConnectWalletChainNamespaceSelect.tsx b/packages/modal/src/ui/components/ConnectWallet/ConnectWalletChainNamespaceSelect/ConnectWalletChainNamespaceSelect.tsx index 101e11cb2..7869d1cfd 100644 --- a/packages/modal/src/ui/components/ConnectWallet/ConnectWalletChainNamespaceSelect/ConnectWalletChainNamespaceSelect.tsx +++ b/packages/modal/src/ui/components/ConnectWallet/ConnectWalletChainNamespaceSelect/ConnectWalletChainNamespaceSelect.tsx @@ -9,7 +9,7 @@ const ConnectWalletChainNamespaceSelect = (props: ConnectWalletChainNamespaceSel const { isDark, wallet, handleExternalWalletClick } = props; const [t] = useTranslation(undefined, { i18n }); - const chainNamespaces = wallet.chainNamespaces!.map((chainNamespace) => { + const chainNamespaces = wallet.chainNamespaces!.sort().map((chainNamespace) => { const imageId = chainNamespace === "eip155" ? "evm" : chainNamespace; const displayName = chainNamespace === "eip155" ? "EVM" : chainNamespace; return { diff --git a/packages/modal/src/ui/components/Login/Login.tsx b/packages/modal/src/ui/components/Login/Login.tsx index 5dec19f1f..802cff4c2 100644 --- a/packages/modal/src/ui/components/Login/Login.tsx +++ b/packages/modal/src/ui/components/Login/Login.tsx @@ -60,6 +60,7 @@ function Login(props: LoginProps) { showInstalledExternalWallets, logoAlignment = "center", buttonRadius = "pill", + deviceDetails, } = props; const [t] = useTranslation(undefined, { i18n }); @@ -342,6 +343,12 @@ function Login(props: LoginProps) { } }; + /** + * Installed wallet click logic: + * - For MetaMask: If not injected and on desktop, display QR code for connection. + * - If wallet supports multiple chain namespaces, prompt user to select a chain. + * - Otherwise, connect directly using the wallet connector. + */ const handleInstalledWalletClick = (wallet: ExternalButton) => { analytics?.track(ANALYTICS_EVENTS.EXTERNAL_WALLET_SELECTED, { connector: wallet.name, @@ -354,8 +361,10 @@ function Login(props: LoginProps) { has_wallet_registry_item: !!wallet.walletRegistryItem, total_external_wallets: totalExternalWallets, }); - // for non-injected Metamask, show QR code to connect - if (wallet.name === WALLET_CONNECTORS.METAMASK && !wallet.hasInjectedWallet) { + // for non-injected Metamask on desktop, show QR code to connect + if (wallet.name === WALLET_CONNECTORS.METAMASK && !wallet.hasInjectedWallet && deviceDetails.platform === "desktop") { + handleExternalWalletClick({ connector: wallet.name }); + // We should show QR code only if the wallet is not installed. setBodyState({ ...bodyState, metamaskQrCode: { diff --git a/packages/modal/src/ui/components/Login/Login.type.ts b/packages/modal/src/ui/components/Login/Login.type.ts index 3f933f3f7..1aeb9b564 100644 --- a/packages/modal/src/ui/components/Login/Login.type.ts +++ b/packages/modal/src/ui/components/Login/Login.type.ts @@ -1,10 +1,13 @@ import { BUILD_ENV_TYPE, WEB3AUTH_NETWORK_TYPE } from "@web3auth/auth"; import type { + browser, ButtonRadiusType, ExternalButton, ExternalWalletEventType, LogoAlignmentType, + os, + platform, SocialLoginEventType, SocialLoginsConfig, } from "../../interfaces"; @@ -31,6 +34,7 @@ export interface LoginProps { totalExternalWallets: number; logoAlignment?: LogoAlignmentType; buttonRadius?: ButtonRadiusType; + deviceDetails: { platform: platform; browser: browser; os: os }; handleExternalWalletBtnClick?: (flag: boolean) => void; handleSocialLoginClick: (params: SocialLoginEventType) => void; handleExternalWalletClick: (params: ExternalWalletEventType) => void; diff --git a/packages/modal/src/ui/components/Root/Root.tsx b/packages/modal/src/ui/components/Root/Root.tsx index 2f59c4763..9d7c8477c 100644 --- a/packages/modal/src/ui/components/Root/Root.tsx +++ b/packages/modal/src/ui/components/Root/Root.tsx @@ -1,12 +1,11 @@ import { WALLET_CONNECTORS, type WalletRegistryItem } from "@web3auth/no-modal"; -import Bowser from "bowser"; import { JSX, useCallback, useContext, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { CONNECT_WALLET_PAGES, DEFAULT_METAMASK_WALLET_REGISTRY_ITEM, PAGES } from "../../constants"; import { BodyState, RootContext } from "../../context/RootContext"; import { ThemedContext } from "../../context/ThemeContext"; -import { browser, ExternalButton, mobileOs, MODAL_STATUS, os, platform, TOAST_TYPE, ToastType } from "../../interfaces"; +import { ExternalButton, mobileOs, MODAL_STATUS, TOAST_TYPE, ToastType } from "../../interfaces"; import i18n from "../../localeImport"; import { cn, getBrowserExtensionUrl, getBrowserName, getIcons, getMobileInstallLink, getOsName } from "../../utils"; import BottomSheet from "../BottomSheet"; @@ -42,6 +41,7 @@ function Root(props: RootProps) { isSmsPasswordLessLoginVisible, preHandleExternalWalletClick, uiConfig, + deviceDetails, } = props; const { @@ -99,16 +99,6 @@ function Root(props: RootProps) { }; // Wallet Details - const deviceDetails = useMemo<{ platform: platform; browser: browser; os: mobileOs }>(() => { - if (typeof window === "undefined") return { platform: "mobile", browser: "chrome", os: "ios" }; - const browserData = Bowser.getParser(window.navigator.userAgent); - return { - platform: browserData.getPlatformType() as platform, - browser: browserData.getBrowserName().toLowerCase() as browser, - os: browserData.getOSName() as mobileOs, - }; - }, []); - const mobileInstallLinks = useMemo(() => { if (deviceDetails.platform === "desktop") return []; const installConfig = bodyState.installLinks?.wallet?.walletRegistryItem?.app || {}; @@ -424,12 +414,8 @@ function Root(props: RootProps) { ); const isShowLoader = useMemo(() => { - // don't show loader if metamask is connecting and there is a connect uri - if (modalState.detailedLoaderConnector === WALLET_CONNECTORS.METAMASK && modalState.metamaskConnectUri) { - return false; - } return modalState.status !== MODAL_STATUS.INITIALIZED; - }, [modalState.detailedLoaderConnector, modalState.metamaskConnectUri, modalState.status]); + }, [modalState.status]); return ( @@ -494,6 +480,7 @@ function Root(props: RootProps) { totalExternalWallets={totalExternalWallets} logoAlignment={logoAlignment} buttonRadius={buttonRadiusType} + deviceDetails={deviceDetails} handleSocialLoginClick={handleSocialLoginClick} handleExternalWalletBtnClick={onExternalWalletBtnClick} handleSocialLoginHeight={handleSocialLoginHeight} @@ -511,11 +498,7 @@ function Root(props: RootProps) { allExternalButtons={allButtons} connectorVisibilityMap={connectorVisibilityMap} customConnectorButtons={customConnectorButtons} - deviceDetails={{ - platform: deviceDetails.platform, - browser: deviceDetails.browser, - os: deviceDetails.os as os, - }} + deviceDetails={deviceDetails} chainNamespace={chainNamespaces} buttonRadius={buttonRadiusType} handleWalletDetailsHeight={handleWalletDetailsHeight} diff --git a/packages/modal/src/ui/components/Root/Root.type.ts b/packages/modal/src/ui/components/Root/Root.type.ts index 6ab13139a..fcd969196 100644 --- a/packages/modal/src/ui/components/Root/Root.type.ts +++ b/packages/modal/src/ui/components/Root/Root.type.ts @@ -1,6 +1,6 @@ import type { ChainNamespaceType, WalletRegistry } from "@web3auth/no-modal"; -import { ModalState, SocialLoginEventType, SocialLoginsConfig, UIConfig } from "../../interfaces"; +import { browser, ModalState, os, platform, SocialLoginEventType, SocialLoginsConfig, UIConfig } from "../../interfaces"; export interface RootProps { appLogo?: string; @@ -18,6 +18,7 @@ export interface RootProps { isEmailPasswordLessLoginVisible: boolean; isSmsPasswordLessLoginVisible: boolean; uiConfig: UIConfig; + deviceDetails: { platform: platform; browser: browser; os: os }; handleSocialLoginClick: (params: SocialLoginEventType) => void; handleExternalWalletBtnClick?: (flag: boolean) => void; preHandleExternalWalletClick: (params: { connector: string; chainNamespace?: ChainNamespaceType }) => void; diff --git a/packages/modal/src/ui/components/Widget/Widget.tsx b/packages/modal/src/ui/components/Widget/Widget.tsx index df618d9af..99875dfb0 100644 --- a/packages/modal/src/ui/components/Widget/Widget.tsx +++ b/packages/modal/src/ui/components/Widget/Widget.tsx @@ -22,6 +22,7 @@ function Widget(props: WidgetProps) { chainNamespaces, walletRegistry, uiConfig, + deviceDetails, } = props; const { widgetType } = uiConfig; @@ -183,13 +184,6 @@ function Widget(props: WidgetProps) { if (wcAvailable && !modalState.walletConnectUri && typeof handleExternalWalletClick === "function") { handleExternalWalletClick({ connector: WALLET_CONNECTORS.WALLET_CONNECT_V2 }); } - - // auto connect to MetaMask if not injected to generate QR code URI for mobile connection - const mmAvailable = - modalState.externalWalletsConfig[WALLET_CONNECTORS.METAMASK] && !modalState.externalWalletsConfig[WALLET_CONNECTORS.METAMASK]?.isInjected; - if (mmAvailable && !modalState.metamaskConnectUri && typeof handleExternalWalletClick === "function") { - handleExternalWalletClick({ connector: WALLET_CONNECTORS.METAMASK }); - } } }, [modalState, handleExternalWalletClick]); @@ -226,6 +220,7 @@ function Widget(props: WidgetProps) { isEmailPasswordLessLoginVisible={isEmailPasswordLessLoginVisible} isSmsPasswordLessLoginVisible={isSmsPasswordLessLoginVisible} uiConfig={uiConfig} + deviceDetails={deviceDetails} /> )} @@ -257,6 +252,7 @@ function Widget(props: WidgetProps) { isEmailPasswordLessLoginVisible={isEmailPasswordLessLoginVisible} isSmsPasswordLessLoginVisible={isSmsPasswordLessLoginVisible} uiConfig={uiConfig} + deviceDetails={deviceDetails} /> )} diff --git a/packages/modal/src/ui/components/Widget/Widget.type.ts b/packages/modal/src/ui/components/Widget/Widget.type.ts index 84ad72032..5a57636fd 100644 --- a/packages/modal/src/ui/components/Widget/Widget.type.ts +++ b/packages/modal/src/ui/components/Widget/Widget.type.ts @@ -1,7 +1,7 @@ import { SafeEventEmitter } from "@web3auth/auth"; import { ChainNamespaceType, WalletRegistry } from "@web3auth/no-modal"; -import { ExternalWalletEventType, SocialLoginEventType, StateEmitterEvents, UIConfig } from "../../interfaces"; +import { browser, ExternalWalletEventType, os, platform, SocialLoginEventType, StateEmitterEvents, UIConfig } from "../../interfaces"; export interface WidgetProps { stateListener: SafeEventEmitter; @@ -9,9 +9,10 @@ export interface WidgetProps { appName?: string; chainNamespaces: ChainNamespaceType[]; walletRegistry?: WalletRegistry; + uiConfig: UIConfig; + deviceDetails: { platform: platform; browser: browser; os: os }; handleSocialLoginClick: (params: SocialLoginEventType) => void; handleExternalWalletClick: (params: ExternalWalletEventType) => void; handleShowExternalWallets: (externalWalletsInitialized: boolean) => void; closeModal: () => void; - uiConfig: UIConfig; } diff --git a/packages/modal/src/ui/loginModal.tsx b/packages/modal/src/ui/loginModal.tsx index 7568ff142..f3664a7f2 100644 --- a/packages/modal/src/ui/loginModal.tsx +++ b/packages/modal/src/ui/loginModal.tsx @@ -26,6 +26,7 @@ import { type Web3AuthNoModalEvents, WIDGET_TYPE, } from "@web3auth/no-modal"; +import Bowser from "bowser"; import { createRoot } from "react-dom/client"; import { getLoginModalAnalyticsProperties } from "../utils"; @@ -34,11 +35,14 @@ import { DEFAULT_LOGO_DARK, DEFAULT_LOGO_LIGHT, DEFAULT_ON_PRIMARY_COLOR, DEFAUL import { AnalyticsContext } from "./context/AnalyticsContext"; import { ThemedContext } from "./context/ThemeContext"; import { + browser, ExternalWalletEventType, LoginModalCallbacks, LoginModalProps, MODAL_STATUS, ModalState, + os, + platform, SocialLoginEventType, StateEmitterEvents, UIConfig, @@ -112,6 +116,16 @@ export class LoginModal { return this.uiConfig.mode === "dark" || (this.uiConfig.mode === "auto" && window.matchMedia("(prefers-color-scheme: dark)").matches); } + get deviceDetails() { + if (typeof window === "undefined") return { platform: "mobile" as platform, browser: "chrome" as browser, os: "ios" as os }; + const browserData = Bowser.getParser(window.navigator.userAgent); + return { + platform: browserData.getPlatformType() as platform, + browser: browserData.getBrowserName().toLowerCase() as browser, + os: browserData.getOSName() as os, + }; + } + initModal = async (): Promise => { const darkState = { isDark: this.isDark }; @@ -249,6 +263,7 @@ export class LoginModal { appName={this.uiConfig.appName} chainNamespaces={this.chainNamespaces} walletRegistry={this.walletRegistry} + deviceDetails={this.deviceDetails} handleShowExternalWallets={this.handleShowExternalWallets} handleExternalWalletClick={this.handleExternalWalletClick} handleSocialLoginClick={this.handleSocialLoginClick} @@ -394,7 +409,7 @@ export class LoginModal { // don't show loader in case of metamask qr code, because currently it listens for incoming connections without any user interaction const isMetamaskInjected = this.externalWalletsConfig?.[WALLET_CONNECTORS.METAMASK]?.isInjected; - if (data?.connector === WALLET_CONNECTORS.METAMASK && !isMetamaskInjected) return; + if (data?.connector === WALLET_CONNECTORS.METAMASK && !isMetamaskInjected && this.deviceDetails.platform === "desktop") return; this.setState({ status: MODAL_STATUS.CONNECTING }); });