From 23151f95e5f99d66888f445080fdb5237c14e903 Mon Sep 17 00:00:00 2001 From: pranavjadhav007 Date: Tue, 8 Apr 2025 18:52:19 +0530 Subject: [PATCH] Embed wallet code without using sign request --- src/components/SignRequestHandler.tsx | 356 +++++++++++++++++++++++--- src/global.d.ts | 19 ++ src/hooks/useGetOrCreateAccounts.ts | 18 +- src/screens/HomeScreen.tsx | 4 +- src/screens/SignMessage.tsx | 193 ++++++++++++-- 5 files changed, 520 insertions(+), 70 deletions(-) create mode 100644 src/global.d.ts diff --git a/src/components/SignRequestHandler.tsx b/src/components/SignRequestHandler.tsx index 89ba14f..bc24182 100644 --- a/src/components/SignRequestHandler.tsx +++ b/src/components/SignRequestHandler.tsx @@ -1,75 +1,355 @@ +// import React, { useState, useEffect } 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 '../hooks/useGetOrCreateAccounts'; + +// declare global { +// interface Window { +// receiveSignRequestFromAndroid?: (message: string) => void; +// } +// } + +// export const SignRequestHandler: React.FC = () => { +// const navigation = useNavigation>(); +// const { accounts, currentIndex } = useAccounts(); +// const { selectedNetwork } = useNetworks(); +// const [pendingMessage, setPendingMessage] = useState(null); + +// useGetOrCreateAccounts(); + +// useEffect(() => { +// if (pendingMessage && selectedNetwork && accounts && accounts.length > 0) { +// navigation.reset({ +// index: 0, +// routes: [ +// { +// name: 'SignRequest', +// params: { +// address: accounts[currentIndex].address, +// message: pendingMessage, +// requestEvent: null, +// path: `/sign/${selectedNetwork.namespace}/${selectedNetwork.chainId}/${accounts[currentIndex].address}/${encodeURIComponent(pendingMessage)}` +// } +// } +// ] +// }); +// setPendingMessage(null); +// } +// }, [pendingMessage, selectedNetwork, accounts, currentIndex, navigation]); + +// useEffect(() => { +// window.receiveSignRequestFromAndroid = (message: string) => { +// if (!selectedNetwork || !accounts || accounts.length === 0) { +// // Store the message and wait for wallet creation +// setPendingMessage(message); +// return; +// } + +// navigation.reset({ +// index: 0, +// routes: [ +// { +// name: 'SignRequest', +// params: { +// address: accounts[currentIndex].address, +// message: message, +// requestEvent: null, +// path: `/sign/${selectedNetwork.namespace}/${selectedNetwork.chainId}/${accounts[currentIndex].address}/${encodeURIComponent(message)}` +// } +// } +// ] +// }); +// }; + +// return () => { +// if ('receiveSignRequestFromAndroid' in window) { +// window.receiveSignRequestFromAndroid = undefined; +// } +// }; +// }, [navigation, accounts, currentIndex, selectedNetwork]); + +// return null; +// }; + + +// import React, { useState, useEffect } 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 '../hooks/useGetOrCreateAccounts'; + +// declare global { +// interface Window { +// receiveSignRequestFromAndroid?: (message: string) => void; +// reactAppReady: boolean; +// reactAppReceiveMessage: (message: string) => void; +// pendingAndroidMessage?: string; +// Android?: any; +// } +// } + +// export const SignRequestHandler: React.FC = () => { +// const navigation = useNavigation>(); +// const { accounts, currentIndex } = useAccounts(); +// const { selectedNetwork } = useNetworks(); +// const [pendingMessage, setPendingMessage] = useState(null); + +// useGetOrCreateAccounts(); + +// const handleMessage = (message: string) => { +// console.log("Handling message in React app:", message); + +// if (!selectedNetwork || !accounts || accounts.length === 0) { +// // Store the message and wait for wallet creation +// console.log("Storing message for later processing"); +// setPendingMessage(message); +// return; +// } + +// console.log("Navigating to SignRequest screen"); +// navigation.reset({ +// index: 0, +// routes: [ +// { +// name: 'SignRequest', +// params: { +// address: accounts[currentIndex].address, +// message: message, +// requestEvent: null, +// path: `/sign/${selectedNetwork.namespace}/${selectedNetwork.chainId}/${accounts[currentIndex].address}/${encodeURIComponent(message)}` +// } +// } +// ] +// }); +// }; + +// useEffect(() => { +// if (pendingMessage && selectedNetwork && accounts && accounts.length > 0) { +// handleMessage(pendingMessage); +// setPendingMessage(null); +// } +// }, [pendingMessage, selectedNetwork, accounts, currentIndex, navigation]); + +// useEffect(() => { +// window.reactAppReceiveMessage = handleMessage; +// window.reactAppReady = true; + +// if (window.pendingAndroidMessage) { +// console.log("Processing pending message:", window.pendingAndroidMessage); +// handleMessage(window.pendingAndroidMessage); +// window.pendingAndroidMessage = undefined; +// } + +// if (window.Android && typeof window.Android.bridgeReady === 'function') { +// window.Android.bridgeReady(); +// } + +// console.log("React app ready to receive messages"); + +// return () => { +// window.reactAppReady = false; +// }; +// }, [navigation, accounts, currentIndex, selectedNetwork]); + +// return null; +// }; + +// import React, { useState, useEffect } 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'; + +// // declare global { +// // interface Window { +// // receiveTimeFromAndroid?: (timestamp: number) => void; +// // receiveDataFromAndroid?: (data: string) => void; +// // receiveSignRequestFromAndroid?: (message: string) => void; +// // } +// // } + +// export const TimeDisplay: React.FC = () => { +// const navigation = useNavigation>(); +// const [displayContent, setDisplayContent] = useState('Waiting for time...'); +// const { accounts, currentIndex } = useAccounts(); +// const { selectedNetwork } = useNetworks(); + +// useEffect(() => { +// console.log('TimeDisplay mounted useeffect'); + +// window.receiveTimeFromAndroid = (timestamp: number) => { +// const date = new Date(timestamp); +// setDisplayContent(date.toLocaleString()); +// }; + +// window.receiveDataFromAndroid = (data: string) => { +// setDisplayContent(data); +// }; + +// window.receiveSignRequestFromAndroid = (message: string) => { +// if (!selectedNetwork || !accounts || accounts.length === 0) { +// console.error('No network or accounts available'); +// return; +// } + +// // Reset the entire navigation state and go directly to SignMessage +// navigation.reset({ +// index: 0, +// routes: [ +// { +// name: 'SignMessage', +// params: { +// selectedNamespace: selectedNetwork.namespace, +// selectedChainId: selectedNetwork.chainId, +// accountInfo: accounts[currentIndex], +// prefillMessage: message +// } +// } +// ] +// }); +// }; + +// return () => { +// if ('receiveTimeFromAndroid' in window) { +// window.receiveTimeFromAndroid = undefined; +// } +// if ('receiveDataFromAndroid' in window) { +// window.receiveDataFromAndroid = undefined; +// } +// if ('receiveSignRequestFromAndroid' in window) { +// window.receiveSignRequestFromAndroid = undefined; +// } +// }; +// }, [navigation, accounts, currentIndex, selectedNetwork]); + +// return ( +//
+//

Content from Android:

+//

{displayContent}

+//
+// ); +// }; + +// const containerStyle: React.CSSProperties = { +// padding: 20, +// backgroundColor: '#f5f5f5', +// borderRadius: 8, +// textAlign: 'center' +// }; + +// const headingStyle: React.CSSProperties = { +// fontSize: 18, +// fontWeight: 'bold', +// marginBottom: 10 +// }; + +// const timeTextStyle: React.CSSProperties = { +// fontSize: 16, +// color: '#333' +// }; + + + import React, { useState, useEffect } 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 '../hooks/useGetOrCreateAccounts'; -declare global { - interface Window { - receiveSignRequestFromAndroid?: (message: string) => void; - } -} - -export const SignRequestHandler: React.FC = () => { +export const TimeDisplay: React.FC = () => { const navigation = useNavigation>(); + const [displayContent, setDisplayContent] = useState('Waiting for time...'); const { accounts, currentIndex } = useAccounts(); const { selectedNetwork } = useNetworks(); - const [pendingMessage, setPendingMessage] = useState(null); - - useGetOrCreateAccounts(); useEffect(() => { - if (pendingMessage && selectedNetwork && accounts && accounts.length > 0) { - navigation.reset({ - index: 0, - routes: [ - { - name: 'SignRequest', - params: { - address: accounts[currentIndex].address, - message: pendingMessage, - requestEvent: null, - path: `/sign/${selectedNetwork.namespace}/${selectedNetwork.chainId}/${accounts[currentIndex].address}/${encodeURIComponent(pendingMessage)}` - } - } - ] - }); - setPendingMessage(null); - } - }, [pendingMessage, selectedNetwork, accounts, currentIndex, navigation]); + console.log('TimeDisplay mounted useeffect'); + + // Immediately set up the function on window object + window.receiveTimeFromAndroid = (timestamp: number) => { + const date = new Date(timestamp); + setDisplayContent(date.toLocaleString()); + }; + + window.receiveDataFromAndroid = (data: string) => { + setDisplayContent(data); + }; - useEffect(() => { window.receiveSignRequestFromAndroid = (message: string) => { + console.log('Sign request received with message:', message); if (!selectedNetwork || !accounts || accounts.length === 0) { - // Store the message and wait for wallet creation - setPendingMessage(message); + console.error('No network or accounts available'); return; } + // Reset the entire navigation state and go directly to SignMessage navigation.reset({ index: 0, routes: [ { - name: 'SignRequest', + name: 'SignMessage', params: { - address: accounts[currentIndex].address, - message: message, - requestEvent: null, - path: `/sign/${selectedNetwork.namespace}/${selectedNetwork.chainId}/${accounts[currentIndex].address}/${encodeURIComponent(message)}` + selectedNamespace: selectedNetwork.namespace, + selectedChainId: selectedNetwork.chainId, + accountInfo: accounts[currentIndex], + prefillMessage: message } } ] }); }; + // Signal to Android that the JS bridge is ready + if (window.Android) { + console.log('Notifying Android that JS bridge is ready'); + setTimeout(() => { + // This timeout ensures the function is properly attached before Android tries to use it + window.Android.onJsBridgeReady?.(); + }, 100); + } + return () => { + if ('receiveTimeFromAndroid' in window) { + window.receiveTimeFromAndroid = undefined; + } + if ('receiveDataFromAndroid' in window) { + window.receiveDataFromAndroid = undefined; + } if ('receiveSignRequestFromAndroid' in window) { window.receiveSignRequestFromAndroid = undefined; } }; }, [navigation, accounts, currentIndex, selectedNetwork]); - return null; + return ( +
+

Content from Android:

+

{displayContent}

+
+ ); }; + +const containerStyle: React.CSSProperties = { + padding: 20, + backgroundColor: '#f5f5f5', + borderRadius: 8, + textAlign: 'center' +}; + +const headingStyle: React.CSSProperties = { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 10 +}; + +const timeTextStyle: React.CSSProperties = { + fontSize: 16, + color: '#333' +}; \ No newline at end of file diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..5b950f6 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,19 @@ +declare global { + interface Window { + Android: { + onSignatureComplete?: (signature: string) => void; + onSignatureError?: (error: string) => void; + onSignatureCancelled?: () => void; + onJsBridgeReady?: () => void; + }; + // Android WebView bridge methods (from TimeDisplay) + receiveTimeFromAndroid?: (timestamp: number) => void; + receiveDataFromAndroid?: (data: string) => void; + receiveSignRequestFromAndroid?: (message: string) => void; + + // Android native bridge object (from useGetOrCreateAccount) + + } + } + +export {}; \ No newline at end of file diff --git a/src/hooks/useGetOrCreateAccounts.ts b/src/hooks/useGetOrCreateAccounts.ts index 38a2166..be58643 100644 --- a/src/hooks/useGetOrCreateAccounts.ts +++ b/src/hooks/useGetOrCreateAccounts.ts @@ -4,15 +4,15 @@ import { sendMessage } from "../utils/misc"; import useAccountsData from "./useAccountsData"; import { useNetworks } from "../context/NetworksContext"; -declare global { - interface Window { - Android?: { - onSignatureComplete?: (signature: string) => void; - onSignatureError?: (error: string) => void; - onSignatureCancelled?: () => void; - }; - } -} +// declare global { +// interface Window { +// Android?: { +// onSignatureComplete?: (signature: string) => void; +// onSignatureError?: (error: string) => void; +// onSignatureCancelled?: () => void; +// }; +// } +// } const useGetOrCreateAccounts = (onWalletCreated?: () => void) => { const { networksData } = useNetworks(); diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 30f2af0..17ead86 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -18,7 +18,7 @@ import { useNetworks } from "../context/NetworksContext"; import ImportWalletDialog from "../components/ImportWalletDialog"; import { MnemonicDialog } from "../components/MnemonicDialog"; import { Container } from "../components/Container"; -import { SignRequestHandler } from '../components/SignRequestHandler'; +import { TimeDisplay } from '../components/SignRequestHandler'; import useGetOrCreateAccounts from '../hooks/useGetOrCreateAccounts'; import { IS_IMPORT_WALLET_ENABLED } from "../utils/constants"; @@ -185,7 +185,7 @@ const HomeScreen = () => { )} )} - + setImportWalletDialog(false)} diff --git a/src/screens/SignMessage.tsx b/src/screens/SignMessage.tsx index 1377128..1271326 100644 --- a/src/screens/SignMessage.tsx +++ b/src/screens/SignMessage.tsx @@ -1,7 +1,67 @@ -import React, { useState } from "react"; -import { Text, TextInput } from "react-native-paper"; -import { Button, Divider, Stack } from "@mui/material"; +// import React, { useState } from "react"; +// import { Text, TextInput } from "react-native-paper"; +// import { Button, Divider, Stack } from "@mui/material"; +// import { NativeStackScreenProps } from "@react-navigation/native-stack"; + +// import { StackParamsList } from "../types"; +// import { signMessage } from "../utils/sign-message"; +// import AccountDetails from "../components/AccountDetails"; +// import { Layout } from "../components/Layout"; + +// type SignProps = NativeStackScreenProps; + +// const SignMessage = ({ route }: SignProps) => { +// const namespace = route.params.selectedNamespace; +// const chainId = route.params.selectedChainId; +// const account = route.params.accountInfo; + +// const [message, setMessage] = useState(""); + +// const signMessageHandler = async () => { +// const signedMessage = await signMessage({ +// message, +// namespace, +// chainId, +// accountId: account.index, +// }); +// alert(`Signature ${signedMessage}`); +// }; + +// return ( +// +// +// {account && `Account ${account.index + 1}`} +// +// + +// +// +// setMessage(text)} +// value={message} +// /> + +// +// +// +// ); +// }; + +// export default SignMessage; + + +import React, { useState, useEffect } from "react"; +import { Text, TextInput } from "react-native-paper"; +import { Button, Divider, Stack, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "@mui/material"; import { NativeStackScreenProps } from "@react-navigation/native-stack"; import { StackParamsList } from "../types"; @@ -11,21 +71,82 @@ import { Layout } from "../components/Layout"; type SignProps = NativeStackScreenProps; -const SignMessage = ({ route }: SignProps) => { +const SignMessage = ({ route, navigation }: SignProps) => { const namespace = route.params.selectedNamespace; const chainId = route.params.selectedChainId; const account = route.params.accountInfo; + const prefillMessage = route.params.prefillMessage; - const [message, setMessage] = useState(""); + const [message, setMessage] = useState(prefillMessage || ""); + const [showConfirmDialog, setShowConfirmDialog] = useState(false); + + // If message is prefilled from Android, show the confirmation dialog immediately + useEffect(() => { + if (prefillMessage) { + setMessage(prefillMessage); + // Show confirmation dialog automatically when prefilled from Android + setShowConfirmDialog(true); + } + }, [prefillMessage]); - const signMessageHandler = async () => { - const signedMessage = await signMessage({ - message, - namespace, - chainId, - accountId: account.index, - }); - alert(`Signature ${signedMessage}`); + const handleConfirmSignature = async () => { + try { + const signedMessage = await signMessage({ + message, + namespace, + chainId, + accountId: account.index, + }); + + // Send the result back to Android and close dialog + if (window.Android?.onSignatureComplete) { + window.Android.onSignatureComplete(signedMessage || ""); + } else { + alert(`Signature: ${signedMessage}`); + } + + // Close dialog + setShowConfirmDialog(false); + + // If this was opened from Android, we should return + if (prefillMessage && window.Android) { + // Return to previous screen if applicable + if (navigation.canGoBack()) { + navigation.goBack(); + } + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + + // Send error back to Android + if (window.Android?.onSignatureError) { + window.Android.onSignatureError(errorMessage); + } else { + alert(`Error: ${errorMessage}`); + } + setShowConfirmDialog(false); + } + }; + + const handleCancelSignature = () => { + setShowConfirmDialog(false); + + if (window.Android?.onSignatureCancelled) { + window.Android.onSignatureCancelled(); + } + + // If this was opened from Android, we should return + if (prefillMessage && window.Android) { + // Return to previous screen if applicable + if (navigation.canGoBack()) { + navigation.goBack(); + } + } + }; + + // This is triggered by the main "Sign" button + const showSignConfirmation = () => { + setShowConfirmDialog(true); }; return ( @@ -44,16 +165,46 @@ const SignMessage = ({ route }: SignProps) => { value={message} /> - + + + + + + + {/* Confirmation Dialog */} + + Confirm Signature + + + Are you sure you want to sign the following message? + + + {message} + + + + + + + ); }; -export default SignMessage; +export default SignMessage; \ No newline at end of file