Embed wallet code without using sign request

This commit is contained in:
pranavjadhav007 2025-04-08 18:52:19 +05:30
parent 41acca6a42
commit 23151f95e5
5 changed files with 520 additions and 70 deletions

View File

@ -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<NativeStackNavigationProp<StackParamsList>>();
// const { accounts, currentIndex } = useAccounts();
// const { selectedNetwork } = useNetworks();
// const [pendingMessage, setPendingMessage] = useState<string | null>(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<NativeStackNavigationProp<StackParamsList>>();
// const { accounts, currentIndex } = useAccounts();
// const { selectedNetwork } = useNetworks();
// const [pendingMessage, setPendingMessage] = useState<string | null>(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<NativeStackNavigationProp<StackParamsList>>();
// const [displayContent, setDisplayContent] = useState<string>('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 (
// <div style={containerStyle}>
// <h2 style={headingStyle}>Content from Android:</h2>
// <p style={timeTextStyle}>{displayContent}</p>
// </div>
// );
// };
// 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<NativeStackNavigationProp<StackParamsList>>();
const [displayContent, setDisplayContent] = useState<string>('Waiting for time...');
const { accounts, currentIndex } = useAccounts();
const { selectedNetwork } = useNetworks();
const [pendingMessage, setPendingMessage] = useState<string | null>(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 (
<div style={containerStyle}>
<h2 style={headingStyle}>Content from Android:</h2>
<p style={timeTextStyle}>{displayContent}</p>
</div>
);
};
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'
};

19
src/global.d.ts vendored Normal file
View File

@ -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 {};

View File

@ -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();

View File

@ -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 = () => {
)}
</>
)}
<SignRequestHandler />
<TimeDisplay />
<ImportWalletDialog
visible={importWalletDialog}
hideDialog={() => setImportWalletDialog(false)}

View File

@ -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<StackParamsList, "SignMessage">;
// const SignMessage = ({ route }: SignProps) => {
// const namespace = route.params.selectedNamespace;
// const chainId = route.params.selectedChainId;
// const account = route.params.accountInfo;
// const [message, setMessage] = useState<string>("");
// const signMessageHandler = async () => {
// const signedMessage = await signMessage({
// message,
// namespace,
// chainId,
// accountId: account.index,
// });
// alert(`Signature ${signedMessage}`);
// };
// return (
// <Layout title="Sign Message">
// <Text variant="titleMedium">
// {account && `Account ${account.index + 1}`}
// </Text>
// <AccountDetails account={account} />
// <Stack spacing={4}>
// <Divider flexItem />
// <TextInput
// mode="outlined"
// placeholder="Enter your message"
// onChangeText={(text) => setMessage(text)}
// value={message}
// />
// <Button
// variant="contained"
// onClick={signMessageHandler}
// sx={{ width: "200px", px: 4, py: 1, mt: 2 }}
// >
// Sign
// </Button>
// </Stack>
// </Layout>
// );
// };
// 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<StackParamsList, "SignMessage">;
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<string>("");
const [message, setMessage] = useState<string>(prefillMessage || "");
const [showConfirmDialog, setShowConfirmDialog] = useState<boolean>(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}
/>
<Button
variant="contained"
onClick={signMessageHandler}
sx={{ width: "200px", px: 4, py: 1, mt: 2 }}
>
Sign
</Button>
<Stack direction="row" spacing={2}>
<Button
variant="contained"
onClick={showSignConfirmation}
sx={{ width: "200px", px: 4, py: 1, mt: 2 }}
>
Sign
</Button>
<Button
variant="outlined"
onClick={handleCancelSignature}
sx={{ width: "200px", px: 4, py: 1, mt: 2 }}
>
Cancel
</Button>
</Stack>
</Stack>
{/* Confirmation Dialog */}
<Dialog
open={showConfirmDialog}
onClose={handleCancelSignature}
>
<DialogTitle>Confirm Signature</DialogTitle>
<DialogContent>
<DialogContentText>
Are you sure you want to sign the following message?
</DialogContentText>
<DialogContentText sx={{ mt: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}>
{message}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleCancelSignature} variant="outlined">No, Cancel</Button>
<Button onClick={handleConfirmSignature} variant="contained" autoFocus>Yes, Sign</Button>
</DialogActions>
</Dialog>
</Layout>
);
};
export default SignMessage;
export default SignMessage;