diff --git a/package.json b/package.json index 876dc3d..f44f06f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "web-wallet", - "version": "0.1.3", + "version": "0.1.4", "private": true, "dependencies": { "@cerc-io/registry-sdk": "^0.2.5", diff --git a/src/App.tsx b/src/App.tsx index 201489f..1ed1a09 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -41,7 +41,9 @@ import { AutoSignIn } from "./screens/AutoSignIn"; import { checkSufficientFunds, getPathKey, sendMessage } from "./utils/misc"; import useAccountsData from "./hooks/useAccountsData"; import { useWebViewHandler } from "./hooks/useWebViewHandler"; -import SignMessageEmbed from "./screens/SignMessageEmbed"; +import SignRequestEmbed from "./screens/SignRequestEmbed"; +import useAddAccountEmbed from "./hooks/useAddAccountEmbed"; +import useExportPKEmbed from "./hooks/useExportPrivateKeyEmbed"; const Stack = createStackNavigator(); @@ -282,6 +284,8 @@ const App = (): React.JSX.Element => { const showWalletConnect = useMemo(() => accounts.length > 0, [accounts]); useWebViewHandler(); + useAddAccountEmbed(); + useExportPKEmbed(); return ( @@ -393,7 +397,7 @@ const App = (): React.JSX.Element => { />
, }} diff --git a/src/context/AccountsContext.tsx b/src/context/AccountsContext.tsx index 7c026a5..a413b5f 100644 --- a/src/context/AccountsContext.tsx +++ b/src/context/AccountsContext.tsx @@ -4,7 +4,7 @@ import { Account } from '../types'; const AccountsContext = createContext<{ accounts: Account[]; - setAccounts: (account: Account[]) => void; + setAccounts: React.Dispatch>; currentIndex: number; setCurrentIndex: (index: number) => void; }>({ diff --git a/src/hooks/useAddAccountEmbed.ts b/src/hooks/useAddAccountEmbed.ts new file mode 100644 index 0000000..d226484 --- /dev/null +++ b/src/hooks/useAddAccountEmbed.ts @@ -0,0 +1,58 @@ +import { useEffect, useCallback } from 'react'; + +import { useNetworks } from '../context/NetworksContext'; +import { sendMessage } from '../utils/misc'; +import useAccountsData from '../hooks/useAccountsData'; +import { addAccount } from '../utils/accounts'; +import { useAccounts } from '../context/AccountsContext'; +import { Account, NetworksDataState } from '../types'; + +const REACT_APP_ALLOWED_URLS = process.env.REACT_APP_ALLOWED_URLS; + +const useAddAccountEmbed = () => { + const { networksData } = useNetworks(); + const { setAccounts, setCurrentIndex } = useAccounts(); + const { getAccountsData } = useAccountsData(); + + const addAccountHandler = useCallback(async (network: NetworksDataState) => { + const newAccount = await addAccount(network); + if (newAccount) { + setAccounts(prev => [...prev, newAccount]); + setCurrentIndex(newAccount.index); + } + }, [setAccounts, setCurrentIndex]); + + useEffect(() => { + const handleAddAccount = async (event: MessageEvent) => { + if (event.data.type !== 'ADD_ACCOUNT') return; + + if (!REACT_APP_ALLOWED_URLS) { + console.log('Unauthorized app origin:', event.origin); + return; + } + + const allowedUrls = REACT_APP_ALLOWED_URLS.split(',').map(url => url.trim()); + + if (!allowedUrls.includes(event.origin)) { + console.log('Unauthorized app.'); + return; + } + + const network = networksData.find(network => network.chainId === event.data.chainId); + + await addAccountHandler(network!); + const updatedAccounts = await getAccountsData(event.data.chainId); + const addresses = updatedAccounts.map((account: Account) => account.address); + + sendMessage(event.source as Window, 'ADD_ACCOUNT_RESPONSE', addresses, event.origin); + }; + + window.addEventListener('message', handleAddAccount); + + return () => { + window.removeEventListener('message', handleAddAccount); + }; + }, [networksData, getAccountsData, addAccountHandler]); +}; + +export default useAddAccountEmbed; diff --git a/src/hooks/useExportPrivateKeyEmbed.ts b/src/hooks/useExportPrivateKeyEmbed.ts new file mode 100644 index 0000000..40b5684 --- /dev/null +++ b/src/hooks/useExportPrivateKeyEmbed.ts @@ -0,0 +1,42 @@ +import { useEffect } from 'react'; + +import { useAccounts } from '../context/AccountsContext'; +import { getPathKey, sendMessage } from '../utils/misc'; + +const useExportPKEmbed = () => { + const { accounts } = useAccounts(); + + useEffect(() => { + const handleMessage = async (event: MessageEvent) => { + const { type, chainId, address } = event.data; + + if (type !== 'REQUEST_ACCOUNT_PK') return; + + try { + const selectedAccount = accounts.find(account => account.address === address); + if (!selectedAccount) { + throw new Error("Account not found") + } + + const pathKey = await getPathKey(chainId, selectedAccount.index); + const privateKey = pathKey.privKey; + + sendMessage( + event.source as Window, + 'ACCOUNT_PK_DATA', + { privateKey }, + event.origin, + ); + } catch (error) { + console.error('Error fetching private key:', error); + } + }; + + window.addEventListener('message', handleMessage); + return () => { + window.removeEventListener('message', handleMessage); + }; + }, [accounts]); +}; + +export default useExportPKEmbed; diff --git a/src/hooks/useGetOrCreateAccounts.ts b/src/hooks/useGetOrCreateAccounts.ts index d5fcc47..b00c55d 100644 --- a/src/hooks/useGetOrCreateAccounts.ts +++ b/src/hooks/useGetOrCreateAccounts.ts @@ -34,7 +34,7 @@ const useGetOrCreateAccounts = () => { if (event.data.type !== 'REQUEST_CREATE_OR_GET_ACCOUNTS') return; if (!REACT_APP_ALLOWED_URLS) { - console.log('allowed URLs are not set.'); + console.log('Allowed URLs are not set'); return; } diff --git a/src/screens/AutoSignIn.tsx b/src/screens/AutoSignIn.tsx index f018831..0a16e00 100644 --- a/src/screens/AutoSignIn.tsx +++ b/src/screens/AutoSignIn.tsx @@ -19,7 +19,7 @@ export const AutoSignIn = () => { if (event.data.type !== 'AUTO_SIGN_IN') return; if (!REACT_APP_ALLOWED_URLS) { - console.log('allowed URLs are not set.'); + console.log('Allowed URLs are not set'); return; } diff --git a/src/screens/SignMessageEmbed.tsx b/src/screens/SignRequestEmbed.tsx similarity index 93% rename from src/screens/SignMessageEmbed.tsx rename to src/screens/SignRequestEmbed.tsx index 35f6930..7a3fbfd 100644 --- a/src/screens/SignMessageEmbed.tsx +++ b/src/screens/SignRequestEmbed.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { ScrollView, View } from 'react-native'; import { ActivityIndicator, Button, Text, Appbar } from 'react-native-paper'; @@ -20,7 +20,7 @@ const REACT_APP_ALLOWED_URLS = process.env.REACT_APP_ALLOWED_URLS; type SignRequestProps = NativeStackScreenProps; -const SignMessageEmbed = ({ route }: SignRequestProps) => { +const SignRequestEmbed = ({ route }: SignRequestProps) => { const [displayAccount, setDisplayAccount] = useState(); const [message, setMessage] = useState(''); const [chainId, setChainId] = useState(''); @@ -72,7 +72,7 @@ const SignMessageEmbed = ({ route }: SignRequestProps) => { } }; - const rejectRequestHandler = async () => { + const rejectRequestHandler = useCallback(async () => { if (sourceWindow && origin) { sendMessage( sourceWindow, @@ -81,8 +81,7 @@ const SignMessageEmbed = ({ route }: SignRequestProps) => { origin, ); } - navigation.navigate('Home'); - }; + }, [sourceWindow, origin]); useEffect(() => { const handleCosmosSignMessage = async (event: MessageEvent) => { @@ -90,7 +89,7 @@ const SignMessageEmbed = ({ route }: SignRequestProps) => { if (!REACT_APP_ALLOWED_URLS) { - console.log('allowed URLs are not set.'); + console.log('Allowed URLs are not set'); return; } @@ -150,8 +149,7 @@ const SignMessageEmbed = ({ route }: SignRequestProps) => { ); }, }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [navigation]); + }, [navigation, rejectRequestHandler]); return ( <> @@ -188,4 +186,4 @@ const SignMessageEmbed = ({ route }: SignRequestProps) => { ); }; -export default SignMessageEmbed; +export default SignRequestEmbed;