diff --git a/mobile/app/(app)/add-key/captcha copy.html b/mobile/app/(app)/add-key/captcha copy.html new file mode 100644 index 00000000..5ee01e3d --- /dev/null +++ b/mobile/app/(app)/add-key/captcha copy.html @@ -0,0 +1,45 @@ + + + + + reCAPTCHA demo: Simple page + + + + + + + +
+
+
+
+ +
+
+ + + diff --git a/mobile/app/(app)/add-key/captcha.html b/mobile/app/(app)/add-key/captcha.html new file mode 100644 index 00000000..ab474ac1 --- /dev/null +++ b/mobile/app/(app)/add-key/captcha.html @@ -0,0 +1,42 @@ + + + + + reCAPTCHA demo: Simple page + + + + + + + +
+
+
+
+
+ + + diff --git a/mobile/app/(app)/add-key/index.tsx b/mobile/app/(app)/add-key/index.tsx index f01ddcaf..e328cef4 100644 --- a/mobile/app/(app)/add-key/index.tsx +++ b/mobile/app/(app)/add-key/index.tsx @@ -1,4 +1,4 @@ -import { StyleSheet, Text, View, ScrollView, TextInput as RNTextInput, Alert as RNAlert } from "react-native"; +import { StyleSheet, Text, View, ScrollView, TextInput as RNTextInput, Alert as RNAlert, Modal } from "react-native"; import React, { useEffect, useRef, useState } from "react"; import { router, useNavigation } from "expo-router"; import { useGnoNativeContext } from "@gnolang/gnonative"; @@ -14,16 +14,19 @@ import { selectKeyName, setKeyName, selectPhrase, + selectSelectedChain, } from "@/redux"; import { ProgressViewModal, ChainSelectView } from "@/views"; -import { TextCopy, Layout, Alert, Spacer, Button, TextInput } from "@/components"; +import { TextCopy, Layout, Alert, Spacer, Button, TextInput, ModalHeader, ModalContent } from "@/components"; import { Octicons } from "@expo/vector-icons"; import { colors } from "@/assets"; +import { WebView, WebViewMessageEvent } from 'react-native-webview'; export default function Page() { const [error, setError] = useState(undefined); const [loading, setLoading] = useState(false); + const [modalVisible, setModalVisible] = useState(false); const inputRef = useRef(null); @@ -38,6 +41,8 @@ export default function Page() { const existingAccount = useAppSelector(existingAccountSelector); const keyName = useAppSelector(selectKeyName); const phrase = useAppSelector(selectPhrase); + const currentNetwork = useAppSelector(selectSelectedChain) + useEffect(() => { const unsubscribe = navigation.addListener("focus", async () => { @@ -91,7 +96,7 @@ export default function Page() { })(); }, [signUpState, newAccount]); - const onCreate = async () => { + const onCreate = async (catpchaToken: string | undefined = undefined) => { setError(undefined); if (!keyName) { setError("Please fill out all fields"); @@ -114,16 +119,21 @@ export default function Page() { return; } + if (currentNetwork?.hasCaptcha && !catpchaToken) { + setModalVisible(true); + return + } + if (signUpState === SignUpState.user_exists_only_on_local_storage && existingAccount) { await gnonative.activateAccount(keyName); await gnonative.setPassword(masterPassword, existingAccount.address); - await dispatch(onboarding({ account: existingAccount })).unwrap(); + await dispatch(onboarding({ account: existingAccount, captcha: catpchaToken })).unwrap(); return; } try { setLoading(true); - await dispatch(signUp({ name: keyName, password: masterPassword, phrase })).unwrap(); + await dispatch(signUp({ name: keyName, password: masterPassword, phrase, captcha: catpchaToken })).unwrap(); } catch (error) { RNAlert.alert("Error", "" + error); setError("" + error); @@ -139,6 +149,14 @@ export default function Page() { router.back() } + const handleMessage = (event: WebViewMessageEvent) => { + // Capture the token from the WebView + const { data } = event.nativeEvent; + console.log('Recaptcha token received:', data); + setModalVisible(false); // Update the token in state + onCreate(data); // Call the onCreate function with the token + }; + return ( @@ -169,19 +187,41 @@ export default function Page() { - + onCreate()} variant="primary" loading={loading} /> + + + setModalVisible(false)} /> + + + - + ); } const styles = StyleSheet.create({ + centeredView: { + flex: 1, + justifyContent: "center", + alignItems: "center", + marginTop: 22, + backgroundColor: "#0008", + }, container: { flex: 1, alignItems: "center", diff --git a/mobile/app/(app)/add-key/localHtmlFile.html b/mobile/app/(app)/add-key/localHtmlFile.html new file mode 100644 index 00000000..fbc6e2f0 --- /dev/null +++ b/mobile/app/(app)/add-key/localHtmlFile.html @@ -0,0 +1,13 @@ + + + reCAPTCHA demo: Simple page + + + +
+
+
+ +
+ + diff --git a/mobile/app/(app)/home/home.tsx b/mobile/app/(app)/home/home.tsx index 9f6827c7..1e213fcb 100644 --- a/mobile/app/(app)/home/home.tsx +++ b/mobile/app/(app)/home/home.tsx @@ -33,7 +33,7 @@ export default function Page() { const response = await gnonative.listKeyInfo(); setAccounts(response); - dispatch(checkForKeyOnChains()) + // dispatch(checkForKeyOnChains()) } catch (error: unknown | Error) { console.error(error); } finally { diff --git a/mobile/assets/chains.json b/mobile/assets/chains.json index a5b524d9..dcb692f0 100644 --- a/mobile/assets/chains.json +++ b/mobile/assets/chains.json @@ -3,29 +3,21 @@ "chainId": "portal-loop", "chainName": "Portal loop", "gnoAddress": "https://rpc.gno.land:443", - "faucetAddress": "https://faucet-api.gno.land" + "faucetAddress": "https://faucet-api.gno.land", + "hasCaptcha": true }, { "chainId": "test5", "chainName": "Testnet 5", "gnoAddress": "http://rpc.test5.gno.land", - "faucetAddress": "https://faucet-api.test5.gno.land" + "faucetAddress": "https://faucet-api.test5.gno.land", + "hasCaptcha": true }, { "chainId": "dev", "chainName": "Berty-Dev", "gnoAddress": "https://api.gno.berty.io:443", - "faucetAddress": "https://faucetpass.gno.berty.io" - }, - { - "chainId": "teritori-1", - "chainName": "Teritori-1", - "gnoAddress": "testnet.gno.teritori.com:26657" - }, - { - "chainId": "dev", - "chainName": "localhost", - "gnoAddress": "localhost:26657", - "faucetAddress": "http://localhost:8545" + "faucetAddress": "https://faucetpass.gno.berty.io", + "hasCaptcha": false } ] diff --git a/mobile/redux/features/signupSlice.ts b/mobile/redux/features/signupSlice.ts index 48f85362..26ee6de9 100644 --- a/mobile/redux/features/signupSlice.ts +++ b/mobile/redux/features/signupSlice.ts @@ -46,6 +46,7 @@ interface SignUpParam { name: string; password: string; phrase: string; + captcha?: string; } type SignUpResponse = { newAccount?: KeyInfo, existingAccount?: KeyInfo, state: SignUpState }; @@ -67,7 +68,7 @@ type SignUpResponse = { newAccount?: KeyInfo, existingAccount?: KeyInfo, state: */ export const signUp = createAsyncThunk("user/signUp", async (param, thunkAPI) => { - const { name, password, phrase } = param; + const { name, password, phrase, captcha } = param; const { registerAccount, selectedChain } = (thunkAPI.getState() as RootState).signUp; const gnonative = thunkAPI.extra.gnonative as GnoNativeApi; @@ -161,7 +162,7 @@ export const signUp = createAsyncThunk( thunkAPI.dispatch(addProgress(`no faucetAddress set for chain "${selectedChain.chainName}"`)) } else { thunkAPI.dispatch(addProgress(`onboarding "${name}"`)) - await onboard(gnonative, newAccount, selectedChain.faucetAddress); + await onboard(gnonative, newAccount, selectedChain.faucetAddress, captcha); } thunkAPI.dispatch(addProgress(`SignUpState.account_created`)) @@ -169,7 +170,7 @@ export const signUp = createAsyncThunk( } }) -export const onboarding = createAsyncThunk("user/onboarding", async (param, thunkAPI) => { +export const onboarding = createAsyncThunk("user/onboarding", async (param, thunkAPI) => { thunkAPI.dispatch(addProgress(`onboarding "${param.account.name}"`)) const { selectedChain } = (thunkAPI.getState() as RootState).signUp; @@ -178,9 +179,9 @@ export const onboarding = createAsyncThunk { +const onboard = async (gnonative: GnoNativeApi, account: KeyInfo, faucetRemote?: string, captcha?: string) => { const { name, address } = account const address_bech32 = await gnonative.addressToBech32(address); - console.log("onboarding %s, with address: %s", name, address_bech32); + console.log("onboard %s, with address: %s", name, address_bech32); try { const hasBalance = await hasCoins(gnonative, address); @@ -279,7 +280,7 @@ const onboard = async (gnonative: GnoNativeApi, account: KeyInfo, faucetRemote?: } if (faucetRemote) { - const response = await sendCoins(address_bech32, faucetRemote); + const response = await sendCoins(address_bech32, faucetRemote, captcha); console.log("coins sent, response: %s", response); await registerAccount(gnonative, account); } else { @@ -326,14 +327,19 @@ const hasCoins = async (gnonative: GnoNativeApi, address: Uint8Array) => { } }; -const sendCoins = async (address: string, faucetRemote: string) => { +const sendCoins = async (address: string, faucetRemote: string, captcha?: string) => { const myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); const raw = JSON.stringify({ To: address, + captcha, + amount: 1 * 1000000 + 'ugnot' }); + console.log("sending coins to %s", address); + console.log("raw", raw); + const requestOptions = { method: "POST", headers: myHeaders, @@ -393,7 +399,7 @@ export const signUpSlice = createSlice({ state.existingAccount = action.payload?.existingAccount; state.signUpState = action.payload?.state; }).addCase(initSignUpState.fulfilled, (state, action) => { - console.log("initSignUpState.fulfilled nnnnnn", action.payload); + console.log("initSignUpState.fulfilled", action.payload); state.phrase = action.payload.phrase; state.loading = false; state.newAccount = undefined; @@ -420,7 +426,7 @@ export const signUpSlice = createSlice({ export const selectChainsAvailable = createSelector( (state: RootState) => state.signUp.customChains, - (customChains) => customChains ? chains.concat(customChains) : chains + (customChains) => customChains ? (chains as NetworkMetainfo[]).concat(customChains) : chains ); export const { addProgress, signUpState, clearProgress, addCustomChain, setRegisterAccount, setKeyName, diff --git a/mobile/types.ts b/mobile/types.ts index ba28fd3c..1e85fbf5 100644 --- a/mobile/types.ts +++ b/mobile/types.ts @@ -43,5 +43,6 @@ export type NetworkMetainfo = { chainName: string; gnoAddress: string; faucetAddress?: string; + hasCaptcha?: boolean; };