Embed laconic wallet in android without using sign request

This commit is contained in:
pranavjadhav007 2025-04-09 09:44:18 +05:30
parent 23151f95e5
commit 2e84a41aaf
5 changed files with 74 additions and 399 deletions

View File

@ -1,295 +1,32 @@
// 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 React, { useState, useEffect, useRef } 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';
export const TimeDisplay: React.FC = () => {
export const SignRequestAndroid: React.FC = () => {
const navigation = useNavigation<NativeStackNavigationProp<StackParamsList>>();
const [displayContent, setDisplayContent] = useState<string>('Waiting for time...');
const { accounts, currentIndex } = useAccounts();
const { selectedNetwork } = useNetworks();
const pendingMessageRef = useRef<string | null>(null);
// Run on mount
useGetOrCreateAccounts();
// Retry navigation if message is pending and accounts are ready
useEffect(() => {
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());
};
if (
pendingMessageRef.current &&
selectedNetwork &&
accounts &&
accounts.length > 0
) {
const message = pendingMessageRef.current;
pendingMessageRef.current = null;
window.receiveDataFromAndroid = (data: string) => {
setDisplayContent(data);
};
window.receiveSignRequestFromAndroid = (message: string) => {
console.log('Sign request received with message:', message);
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: [
@ -299,57 +36,73 @@ export const TimeDisplay: React.FC = () => {
selectedNamespace: selectedNetwork.namespace,
selectedChainId: selectedNetwork.chainId,
accountInfo: accounts[currentIndex],
prefillMessage: message
}
}
]
prefillMessage: message,
},
},
],
});
}
}, [accounts, selectedNetwork]);
useEffect(() => {
window.receiveSignRequestFromAndroid = (message: string) => {
console.log('Sign request received with message:', message);
if (!selectedNetwork || !accounts || accounts.length === 0) {
console.warn('Delaying sign request until data is ready...');
pendingMessageRef.current = message;
return;
}
navigation.reset({
index: 0,
routes: [
{
name: 'SignMessage',
params: {
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?.();
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;
}
window.receiveSignRequestFromAndroid = undefined;
};
}, [navigation, accounts, currentIndex, selectedNetwork]);
}, []);
return (
<div style={containerStyle}>
<h2 style={headingStyle}>Content from Android:</h2>
<p style={timeTextStyle}>{displayContent}</p>
</div>
);
return null;
};
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'
};

4
src/global.d.ts vendored
View File

@ -6,13 +6,9 @@ declare global {
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)
}
}

View File

@ -4,16 +4,6 @@ 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;
// };
// }
// }
const useGetOrCreateAccounts = (onWalletCreated?: () => void) => {
const { networksData } = useNetworks();
const { getAccountsData } = useAccountsData();

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 { TimeDisplay } from '../components/SignRequestHandler';
import { SignRequestAndroid } from '../components/SignRequestHandler';
import useGetOrCreateAccounts from '../hooks/useGetOrCreateAccounts';
import { IS_IMPORT_WALLET_ENABLED } from "../utils/constants";
@ -185,7 +185,7 @@ const HomeScreen = () => {
)}
</>
)}
<TimeDisplay />
<SignRequestAndroid />
<ImportWalletDialog
visible={importWalletDialog}
hideDialog={() => setImportWalletDialog(false)}

View File

@ -1,64 +1,3 @@
// 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";
@ -105,7 +44,6 @@ const SignMessage = ({ route, navigation }: SignProps) => {
alert(`Signature: ${signedMessage}`);
}
// Close dialog
setShowConfirmDialog(false);
// If this was opened from Android, we should return
@ -144,7 +82,6 @@ const SignMessage = ({ route, navigation }: SignProps) => {
}
};
// This is triggered by the main "Sign" button
const showSignConfirmation = () => {
setShowConfirmDialog(true);
};
@ -184,7 +121,6 @@ const SignMessage = ({ route, navigation }: SignProps) => {
</Stack>
</Stack>
{/* Confirmation Dialog */}
<Dialog
open={showConfirmDialog}
onClose={handleCancelSignature}
@ -194,7 +130,7 @@ const SignMessage = ({ route, navigation }: SignProps) => {
<DialogContentText>
Are you sure you want to sign the following message?
</DialogContentText>
<DialogContentText sx={{ mt: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}>
<DialogContentText sx={{ mt: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1, color: 'black' }}>
{message}
</DialogContentText>
</DialogContent>