diff --git a/src/App.tsx b/src/App.tsx index f297288..ac840a9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,6 +40,7 @@ import { WalletEmbed } from "./screens/WalletEmbed"; import { AutoSignIn } from "./screens/AutoSignIn"; import { checkSufficientFunds, getPathKey, sendMessage } from "./utils/misc"; import useAccountsData from "./hooks/useAccountsData"; +import { useWebViewHandler } from "./hooks/useWebViewHandler"; const Stack = createStackNavigator(); @@ -279,6 +280,8 @@ const App = (): React.JSX.Element => { const showWalletConnect = useMemo(() => accounts.length > 0, [accounts]); + useWebViewHandler(); + return ( void; + + // Called when signature generation fails + onSignatureError?: (error: string) => void; + + // Called when signature process is cancelled + onSignatureCancelled?: () => void; + + // Called when accounts are ready for use + onAccountsReady?: () => void; + }; + + // Handles incoming signature requests from Android + receiveSignRequestFromAndroid?: (message: string) => void; + } +} + +export {}; diff --git a/src/hooks/useGetOrCreateAccounts.ts b/src/hooks/useGetOrCreateAccounts.ts index 32a7815..28b51f7 100644 --- a/src/hooks/useGetOrCreateAccounts.ts +++ b/src/hooks/useGetOrCreateAccounts.ts @@ -1,27 +1,37 @@ -import { useEffect } from "react"; +import { useEffect, useCallback } from "react"; import { createWallet } from "../utils/accounts"; import { sendMessage } from "../utils/misc"; import useAccountsData from "./useAccountsData"; import { useNetworks } from "../context/NetworksContext"; +import { useAccounts } from "../context/AccountsContext"; const useGetOrCreateAccounts = () => { const { networksData } = useNetworks(); const { getAccountsData } = useAccountsData(); + const { setAccounts } = useAccounts(); + + // Wrap the function in useCallback to prevent recreation on each render + const getOrCreateAccountsForChain = useCallback(async (chainId: string) => { + let accountsData = await getAccountsData(chainId); + + if (accountsData.length === 0) { + console.log("Accounts not found, creating wallet..."); + await createWallet(networksData); + accountsData = await getAccountsData(chainId); + } + + // Update the AccountsContext with the new accounts + setAccounts(accountsData); + + return accountsData; + }, [networksData, getAccountsData, setAccounts]); useEffect(() => { const handleCreateAccounts = async (event: MessageEvent) => { if (event.data.type !== 'REQUEST_CREATE_OR_GET_ACCOUNTS') return; - let accountsData = await getAccountsData(event.data.chainId); - - if (accountsData.length === 0) { - console.log("Accounts not found, creating wallet..."); - await createWallet(networksData); - - // Re-fetch newly created accounts - accountsData = await getAccountsData(event.data.chainId); - } + const accountsData = await getOrCreateAccountsForChain(event.data.chainId); sendMessage( event.source as Window, 'WALLET_ACCOUNTS_DATA', @@ -30,12 +40,35 @@ const useGetOrCreateAccounts = () => { ); }; + const autoCreateAccounts = async () => { + const defaultChainId = networksData[0]?.chainId; + + if (!defaultChainId) { + console.log('useGetOrCreateAccounts: No default chainId found'); + return; + } + const accounts = await getOrCreateAccountsForChain(defaultChainId); + + // Only notify Android when we actually have accounts + if (accounts.length > 0 && window.Android?.onAccountsReady) { + window.Android.onAccountsReady(); + } else { + console.log('No accounts created or Android bridge not available'); + } + }; + window.addEventListener('message', handleCreateAccounts); + const isAndroidWebView = !!(window.Android); + + if (isAndroidWebView) { + autoCreateAccounts(); + } + return () => { window.removeEventListener('message', handleCreateAccounts); }; - }, [networksData, getAccountsData]); + }, [networksData, getAccountsData, getOrCreateAccountsForChain]); }; export default useGetOrCreateAccounts; diff --git a/src/hooks/useWebViewHandler.ts b/src/hooks/useWebViewHandler.ts new file mode 100644 index 0000000..9085ecd --- /dev/null +++ b/src/hooks/useWebViewHandler.ts @@ -0,0 +1,81 @@ +import { useEffect, useCallback } from 'react'; +import { useNavigation } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; + +import { useAccounts } from '../context/AccountsContext'; +import { useNetworks } from '../context/NetworksContext'; +import { StackParamsList } from '../types'; +import useGetOrCreateAccounts from './useGetOrCreateAccounts'; + +export const useWebViewHandler = () => { + // Navigation and context hooks + const navigation = useNavigation>(); + const { selectedNetwork } = useNetworks(); + const { accounts, currentIndex } = useAccounts(); + + // Initialize accounts + useGetOrCreateAccounts(); + + // Core navigation handler + const navigateToSignRequest = useCallback((message: string) => { + try { + // Validation checks + if (!selectedNetwork?.namespace || !selectedNetwork?.chainId) { + window.Android?.onSignatureError?.('Invalid network configuration'); + return; + } + + if (!accounts?.length) { + window.Android?.onSignatureError?.('No accounts available'); + return; + } + + const currentAccount = accounts[currentIndex]; + if (!currentAccount) { + window.Android?.onSignatureError?.('Current account not found'); + return; + } + + // Create the path and validate with regex + const path = `/sign/${selectedNetwork.namespace}/${selectedNetwork.chainId}/${currentAccount.address}/${encodeURIComponent(message)}`; + const pathRegex = /^\/sign\/(eip155|cosmos)\/(.+)\/(.+)\/(.+)$/; + const match = path.match(pathRegex); + + if (!match) { + window.Android?.onSignatureError?.('Invalid signing path'); + return; + } + + const [, pathNamespace, pathChainId, pathAddress, pathMessage] = match; + + // Reset navigation stack and navigate to sign request + navigation.reset({ + index: 0, + routes: [ + { + name: 'SignRequest', + path, + params: { + namespace: pathNamespace, + chainId: pathChainId, + address: pathAddress, + message: decodeURIComponent(pathMessage), + accountInfo: currentAccount, + }, + }, + ], + }); + } catch (error) { + window.Android?.onSignatureError?.(`Navigation error: ${error}`); + } + }, [selectedNetwork, accounts, currentIndex, navigation]); + + useEffect(() => { + // Assign the function to the window object + window.receiveSignRequestFromAndroid = navigateToSignRequest; + + return () => { + window.receiveSignRequestFromAndroid = undefined; + }; + }, [navigateToSignRequest]); // Only the function reference as dependency +}; diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index 3e0839d..9259ae7 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -202,7 +202,13 @@ const SignRequest = ({ route }: SignRequestProps) => { chainId, accountId: account.index, }); - alert(`Signature ${signedMessage}`); + + // Send the result back to Android and close dialog + if (window.Android?.onSignatureComplete) { + window.Android.onSignatureComplete(signedMessage || ""); + } else { + alert(`Signature: ${signedMessage}`); + } } }; @@ -230,7 +236,11 @@ const SignRequest = ({ route }: SignRequestProps) => { } setIsRejecting(false); - navigation.navigate('Home'); + if (window.Android?.onSignatureCancelled) { + window.Android.onSignatureCancelled(); + } else { + navigation.navigate('Home'); + } }; useEffect(() => { diff --git a/src/types.ts b/src/types.ts index 95dcb7d..3b7341e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,8 +13,10 @@ export type StackParamsList = { }; SignRequest: { namespace: string; + chainId?: string; address: string; message: string; + accountInfo?: Account; requestEvent?: Web3WalletTypes.SessionRequest; requestSessionData?: SessionTypes.Struct; };