@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
JavaScript
'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 };