UNPKG

@dynamic-labs/sdk-react-core

Version:

A React SDK for implementing wallet web3 authentication and authorization to your website.

272 lines (269 loc) 14.2 kB
'use client' import { __awaiter } from '../../../../../_virtual/_tslib.js'; import { useState, useCallback, useEffect } from 'react'; import { CustomError, MissingPublicAddressError } from '@dynamic-labs/utils'; import { getWalletConnectorByKey, logger, isHardwareWalletConnector, getWalletProvider } from '@dynamic-labs/wallet-connector-core'; import { HardwareWalletEnum } from '@dynamic-labs/sdk-api-core'; import { useInternalUserWallets } from '../../../context/UserWalletsContext/UserWalletsContext.js'; import '../../../config/ApiEndpoint.js'; import '../../../store/state/projectSettings/projectSettings.js'; import '../../constants/values.js'; import '@dynamic-labs/multi-wallet'; import '../../../shared/logger.js'; import '../../constants/colors.js'; import 'react-international-phone'; import '@dynamic-labs/iconic'; import 'react/jsx-runtime'; import '../../../context/ViewContext/ViewContext.js'; import '@dynamic-labs/wallet-book'; import '../../../shared/consts/index.js'; import '../../../store/state/nonce/nonce.js'; import { shouldManuallyReconnectOnRefresh } from '../../functions/shouldManuallyReconnectOnRefresh/shouldManuallyReconnectOnRefresh.js'; import { updatePrimaryWalletId } from '../../functions/updatePrimaryWalletId/updatePrimaryWalletId.js'; import { getWalletUniqueId } from '../../functions/getWalletUniqueId/getWalletUniqueId.js'; import { useConnectedWalletsInfo, setConnectedWalletsInfo } from '../../../store/state/connectedWalletsInfo/connectedWalletsInfo.js'; import '../../../store/state/dynamicContextProps/dynamicContextProps.js'; import { resetPrimaryWalletId } from '../../../store/state/primaryWalletId/primaryWalletId.js'; import '../../../store/state/user/user.js'; import { createVisit } from '../../../data/api/session/session.js'; import '../../../locale/locale.js'; import { isConnectOnly } from '../authenticationHooks/helpers/isConnectOnly.js'; import { getWalletConnectorForWallet } from '../../functions/getWalletConnectorForWallet/getWalletConnectorForWallet.js'; import { useDebounce } from '../useDebounce/useDebounce.js'; import { getAuthMode } from '../../../store/state/authMode/authMode.js'; import { updateUserWalletsFromConnectedWallets } from './updateUserWalletsFromConnectedWallets/updateUserWalletsFromConnectedWallets.js'; const useConnectWallet = ({ enableVisitTrackingOnConnectOnly, environmentId, primaryWalletId, walletConnectorOptions, handleConnectedWallet, setShowAuthFlow, isBridgeFlow, }) => { const connectedWalletsInfo = useConnectedWalletsInfo(); const [connectedWallets, _setConnectedWallets] = useState([]); const { setUserWallets, addedWalletsIds, removedWalletsIds } = useInternalUserWallets(); /** This wrapper is to ensure userWallets is always properly updated alongside connectedWallets */ const setConnectedWallets = useCallback((newWallets) => { // Prevent unnecessary re-renders when state is empty _setConnectedWallets((prevWallets) => { if (prevWallets.length === 0 && newWallets.length === 0) { return prevWallets; } return newWallets; }); setUserWallets((userWallets) => updateUserWalletsFromConnectedWallets(userWallets, newWallets)); }, [setUserWallets]); const disconnectWallet = useCallback((walletId) => __awaiter(void 0, void 0, void 0, function* () { const walletToDisconnect = connectedWallets.find((wallet) => wallet.id === walletId); yield (walletToDisconnect === null || walletToDisconnect === void 0 ? void 0 : walletToDisconnect.connector.endSession()); const updatedConnectedWalletsInfo = connectedWalletsInfo.filter((wallet) => wallet.id !== walletId); setConnectedWalletsInfo(updatedConnectedWalletsInfo); removedWalletsIds.current.push(walletId); if (walletId !== primaryWalletId) { return; } const hasConnectedWallet = updatedConnectedWalletsInfo.length > 0; if (isBridgeFlow || !hasConnectedWallet) { resetPrimaryWalletId(); } else { updatePrimaryWalletId(updatedConnectedWalletsInfo[0].id); } }), [ connectedWallets, connectedWalletsInfo, isBridgeFlow, primaryWalletId, removedWalletsIds, ]); // The function to update and define the connectedWallets list. // It should be called on the first render, when connectedWalletsInfo updates and when walletConnectorOptions updates. // To avoid unnecessary updates that are causing issues with connectors added asynchronously, // we're debouncing this function const updateConnectedWalletsList = useDebounce(() => __awaiter(void 0, void 0, void 0, function* () { const walletConnectors = walletConnectorOptions.map((wallet) => wallet.walletConnector); if (!walletConnectors.length) { return; } const updatedConnectedWallets = (yield Promise.all(connectedWalletsInfo.map((storedConnectedWalletInfo) => __awaiter(void 0, void 0, void 0, function* () { const walletConnector = getWalletConnectorByKey(walletConnectors, storedConnectedWalletInfo.walletConnectorKey); if (!walletConnector) { logger.error('Could not find walletConnector: ' + storedConnectedWalletInfo.walletConnectorKey); yield disconnectWallet(storedConnectedWalletInfo.id); return null; } if (isHardwareWalletConnector(walletConnector) && storedConnectedWalletInfo.hardwareWallet === HardwareWalletEnum.Ledger) { walletConnector.isHardwareWalletEnabled = true; } const [walletAddress] = yield walletConnector.getConnectedAccounts(); if (!walletAddress) { yield disconnectWallet(storedConnectedWalletInfo.id); return null; } const additionalAddresses = yield walletConnector.getAdditionalAddresses(walletAddress); const walletChain = walletConnector.connectedChain; const walletObject = walletConnector.createWallet({ additionalAddresses, address: walletAddress, chain: walletChain, connector: walletConnector, id: storedConnectedWalletInfo.id, isAuthenticated: false, key: walletConnector.key, }); return walletObject; })))).filter((wallet) => Boolean(wallet)); setConnectedWallets(updatedConnectedWallets); }), 300); // Generate the connectedWallets list when connectedWalletsInfo (localStorage) or memoized wallet connector updates useEffect(() => { updateConnectedWalletsList(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [connectedWalletsInfo, walletConnectorOptions]); const updateConnectedWalletById = useCallback((walletId, walletObject) => { var _a; const clonedConnectedWalletsList = [...connectedWallets]; const connectedWalletIndex = clonedConnectedWalletsList.findIndex((connectedWallet) => connectedWallet.id === walletId); if (connectedWalletIndex < 0) { return; } clonedConnectedWalletsList[connectedWalletIndex] = clonedConnectedWalletsList[connectedWalletIndex].connector.createWallet(Object.assign(Object.assign({}, clonedConnectedWalletsList[connectedWalletIndex]), { address: (_a = walletObject.address) !== null && _a !== void 0 ? _a : clonedConnectedWalletsList[connectedWalletIndex].address, connector: getWalletConnectorForWallet(clonedConnectedWalletsList[connectedWalletIndex]) })); setConnectedWallets(clonedConnectedWalletsList); }, [connectedWallets, setConnectedWallets]); // Keeps connected wallet data inside localStorage const applyConnectedWalletToStore = useCallback(({ walletId, walletConnectorKey, walletChain, provider, }) => { const updatedConnectedWalletsInfo = [...connectedWalletsInfo]; const walletConnectors = walletConnectorOptions.map((wallet) => wallet.walletConnector); const walletConnector = getWalletConnectorByKey(walletConnectors, walletConnectorKey); const hardwareWallet = walletConnector && isHardwareWalletConnector(walletConnector) && walletConnector.isHardwareWalletEnabled ? HardwareWalletEnum.Ledger : undefined; updatedConnectedWalletsInfo.push({ hardwareWallet, id: walletId, provider, walletChain, walletConnectorKey, }); setConnectedWalletsInfo(updatedConnectedWalletsInfo); }, [connectedWalletsInfo, walletConnectorOptions]); // Updates connected wallet (network and address) and verifies connected wallet connection const refreshConnectedWallet = (walletId, walletConnector) => __awaiter(void 0, void 0, void 0, function* () { if (shouldManuallyReconnectOnRefresh(walletConnector)) { yield walletConnector.connect(); } const currentWalletConnectorAddress = yield walletConnector.getAddress(); if (currentWalletConnectorAddress && handleConnectedWallet) { const shouldProceedWithConnection = yield handleConnectedWallet({ address: currentWalletConnectorAddress, chain: walletConnector.connectedChain, connector: walletConnector, }); if (!shouldProceedWithConnection) { logger.info('Connection was not established because handleConnectedWallet returned false'); disconnectWallet(walletId); setShowAuthFlow(false, { emitCancelAuth: true }); return; } } updateConnectedWalletById(walletId, { address: currentWalletConnectorAddress, }); }); const connectWallet = useCallback((walletConnector, getAddressOpts, options) => __awaiter(void 0, void 0, void 0, function* () { const { applyHandleConnectedWallet = true } = options !== null && options !== void 0 ? options : {}; let walletAddress; try { walletAddress = yield walletConnector.getAddress(getAddressOpts); } catch (error) { if (error instanceof CustomError) { throw error; } } // Throw a generic error for any errors we're not processing above if (!walletAddress) { throw new MissingPublicAddressError(); } if (handleConnectedWallet && applyHandleConnectedWallet) { const shouldProceedWithConnection = yield handleConnectedWallet({ address: walletAddress, chain: walletConnector.connectedChain, connector: walletConnector, }); if (!shouldProceedWithConnection) { logger.info('Connection was not established because handleConnectedWallet returned false'); setShowAuthFlow(false); return; } } const isWalletStored = connectedWalletsInfo.some(({ walletConnectorKey: storedWalletConnectorKey }) => storedWalletConnectorKey === walletConnector.key); const walletChain = walletConnector.connectedChain; const authMode = getAuthMode(); const shouldCreateVisit = !isWalletStored && (authMode !== 'connect-only' || enableVisitTrackingOnConnectOnly); if (shouldCreateVisit) { // send information to backend to kick off background jobs // so verify/sign on the next step could go by more quickly // this is async work, but DO NOT AWAIT createVisit({ authMode, chain: walletChain || '', environmentId, publicWalletAddress: walletAddress, walletName: walletConnector.key, walletProvider: getWalletProvider(walletConnector), }); } // On connect and sign, we don't want to add this wallet to userWallets // until it has been signed. Therefore, we don't add it to // the connectedWallets array because userWallets draws from it. // In this scenario we only need the wallet address, since the id will come from somewhere else. // In fact, there is a refactor planned to never use the connect-wallet-X ids anymore: // https://linear.app/dynamic-labs/issue/QNTM-784/rework-the-way-we-compute-wallet-ids if (!isConnectOnly()) return { address: walletAddress, id: '' }; const walletId = getWalletUniqueId({ address: walletAddress, chain: walletConnector.connectedChain, connectorKey: walletConnector.key, }); if (!primaryWalletId) { updatePrimaryWalletId(walletId); } addedWalletsIds.current.push(walletId); if (isWalletStored) { yield updateConnectedWalletsList(); } else { applyConnectedWalletToStore({ provider: getWalletProvider(walletConnector), walletAddress: walletAddress, walletChain, walletConnectorKey: walletConnector.key, walletId, }); } return { address: walletAddress, id: walletId }; }), [ addedWalletsIds, applyConnectedWalletToStore, connectedWalletsInfo, enableVisitTrackingOnConnectOnly, environmentId, handleConnectedWallet, primaryWalletId, setShowAuthFlow, updateConnectedWalletsList, ]); const getConnectedWalletById = useCallback((walletId) => connectedWallets.find((wallet) => wallet.id === walletId), [connectedWallets]); return { connectWallet, connectedWallets, connectedWalletsInfo, disconnectWallet, getConnectedWalletById, refreshConnectedWallet, }; }; export { useConnectWallet };