diff --git a/blue_modules/BlueElectrum.ts b/blue_modules/BlueElectrum.ts index 38cedf97d..72f9dd454 100644 --- a/blue_modules/BlueElectrum.ts +++ b/blue_modules/BlueElectrum.ts @@ -9,6 +9,7 @@ import Realm from 'realm'; import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet, TaprootWallet } from '../class'; import presentAlert from '../components/Alert'; import loc from '../loc'; +import { GROUP_IO_BLUEWALLET } from './currency'; const ElectrumClient = require('electrum-client'); const net = require('net'); @@ -200,7 +201,7 @@ export async function connectMain(): Promise { usingPeer = savedPeer; } - await DefaultPreference.setName('group.io.bluewallet.bluewallet'); + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); try { if (usingPeer.host.endsWith('onion')) { const randomPeer = getCurrentPeer(); diff --git a/class/blue-app.ts b/class/blue-app.ts index e77b7451a..414ecf257 100644 --- a/class/blue-app.ts +++ b/class/blue-app.ts @@ -24,6 +24,7 @@ import { SegwitP2SHWallet } from './wallets/segwit-p2sh-wallet'; import { SLIP39LegacyP2PKHWallet, SLIP39SegwitBech32Wallet, SLIP39SegwitP2SHWallet } from './wallets/slip39-wallets'; import { ExtendedTransaction, Transaction, TWallet } from './wallets/types'; import { WatchOnlyWallet } from './wallets/watch-only-wallet'; +import { getLNDHub } from '../helpers/lndHub'; let usedBucketNum: boolean | number = false; let savingInProgress = 0; // its both a flag and a counter of attempts to write to disk @@ -437,7 +438,7 @@ export class BlueApp { unserializedWallet = LightningCustodianWallet.fromJson(key) as unknown as LightningCustodianWallet; let lndhub: false | any = false; try { - lndhub = await AsyncStorage.getItem(BlueApp.LNDHUB); + lndhub = await getLNDHub(); } catch (error) { console.warn(error); } diff --git a/class/wallets/lightning-custodian-wallet.ts b/class/wallets/lightning-custodian-wallet.ts index a8e7c99b6..3bc9ead1d 100644 --- a/class/wallets/lightning-custodian-wallet.ts +++ b/class/wallets/lightning-custodian-wallet.ts @@ -579,14 +579,17 @@ export class LightningCustodianWallet extends LegacyWallet { } } - static async isValidNodeAddress(address: string) { - const response = await fetch((address?.endsWith('/') ? address.slice(0, -1) : address) + '/getinfo', { + static async isValidNodeAddress(address: string): Promise { + const normalizedAddress = new URL('/getinfo', address.replace(/([^:]\/)\/+/g, '$1')); + + const response = await fetch(normalizedAddress.toString(), { method: 'GET', headers: { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json', }, }); + const json = await response.json(); if (!json) { throw new Error('API failure: ' + response.statusText); @@ -595,6 +598,7 @@ export class LightningCustodianWallet extends LegacyWallet { if (json.code && json.code !== 1) { throw new Error('API error: ' + json.message + ' (code ' + json.code + ')'); } + return true; } diff --git a/helpers/lndHub.ts b/helpers/lndHub.ts new file mode 100644 index 000000000..f58f113d6 --- /dev/null +++ b/helpers/lndHub.ts @@ -0,0 +1,49 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import DefaultPreference from 'react-native-default-preference'; +import { BlueApp } from '../class'; +import { GROUP_IO_BLUEWALLET } from '..//blue_modules/currency'; + +// Function to get the value from DefaultPreference first, then fallback to AsyncStorage +// as DefaultPreference uses truly native storage. +// If found in AsyncStorage, migrate it to DefaultPreference and remove it from AsyncStorage. +export const getLNDHub = async (): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + let value = await DefaultPreference.get(BlueApp.LNDHUB); + + // If not found, check AsyncStorage and migrate it to DefaultPreference + if (!value) { + value = await AsyncStorage.getItem(BlueApp.LNDHUB); + + if (value) { + await DefaultPreference.set(BlueApp.LNDHUB, value); + await AsyncStorage.removeItem(BlueApp.LNDHUB); + console.log('Migrated LNDHub value from AsyncStorage to DefaultPreference'); + } + } + + return value ?? undefined; + } catch (error) { + console.log('Error getting LNDHub preference:', error); + return undefined; + } +}; + +export const setLNDHub = async (value: string): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.set(BlueApp.LNDHUB, value); + } catch (error) { + console.log('Error setting LNDHub preference:', error); + } +}; + +export const clearLNDHub = async (): Promise => { + try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); + await DefaultPreference.clear(BlueApp.LNDHUB); + await AsyncStorage.removeItem(BlueApp.LNDHUB); + } catch (error) { + console.log('Error clearing LNDHub preference:', error); + } +}; \ No newline at end of file diff --git a/navigation/LazyLoadSettingsStack.tsx b/navigation/LazyLoadSettingsStack.tsx index b22c9adad..654a397b5 100644 --- a/navigation/LazyLoadSettingsStack.tsx +++ b/navigation/LazyLoadSettingsStack.tsx @@ -12,7 +12,7 @@ const About = lazy(() => import('../screen/settings/About')); const DefaultView = lazy(() => import('../screen/settings/DefaultView')); const ElectrumSettings = lazy(() => import('../screen/settings/electrumSettings')); const EncryptStorage = lazy(() => import('../screen/settings/EncryptStorage')); -const LightningSettings = lazy(() => import('../screen/settings/lightningSettings')); +const LightningSettings = lazy(() => import('../screen/settings/LightningSettings')); const NotificationSettings = lazy(() => import('../screen/settings/notificationSettings')); const SelfTest = lazy(() => import('../screen/settings/SelfTest')); const ReleaseNotes = lazy(() => import('../screen/settings/ReleaseNotes')); diff --git a/screen/settings/lightningSettings.tsx b/screen/settings/LightningSettings.tsx similarity index 69% rename from screen/settings/lightningSettings.tsx rename to screen/settings/LightningSettings.tsx index 66ebf7c2f..44416e6d8 100644 --- a/screen/settings/lightningSettings.tsx +++ b/screen/settings/LightningSettings.tsx @@ -1,18 +1,19 @@ import React, { useCallback, useEffect, useState } from 'react'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import { RouteProp, useRoute } from '@react-navigation/native'; import { Alert, I18nManager, Linking, ScrollView, StyleSheet, TextInput, View } from 'react-native'; import { Button as ButtonRNElements } from '@rneui/themed'; - +import DefaultPreference from 'react-native-default-preference'; import { BlueButtonLink, BlueCard, BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents'; -import { BlueApp } from '../../class'; import DeeplinkSchemaMatch from '../../class/deeplink-schema-match'; import { LightningCustodianWallet } from '../../class/wallets/lightning-custodian-wallet'; -import presentAlert from '../../components/Alert'; +import presentAlert, { AlertType } from '../../components/Alert'; import { Button } from '../../components/Button'; import { useTheme } from '../../components/themes'; import { scanQrHelper } from '../../helpers/scan-qr'; import loc from '../../loc'; +import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; +import { GROUP_IO_BLUEWALLET } from '../../blue_modules/currency'; +import { clearLNDHub, getLNDHub, setLNDHub } from '../../helpers/lndHub'; const styles = StyleSheet.create({ uri: { @@ -61,28 +62,42 @@ const LightningSettings: React.FC = () => { }); useEffect(() => { - AsyncStorage.getItem(BlueApp.LNDHUB) - .then(value => setURI(value ?? undefined)) - .then(() => setIsLoading(false)) - .catch(() => setIsLoading(false)); + const fetchURI = async () => { + try { + // Try fetching from DefaultPreference first as DefaultPreference uses truly native storage + const value = await getLNDHub(); + setURI(value ?? undefined); + } catch (error) { + console.log(error); + } + }; - if (params?.url) { - Alert.alert( - loc.formatString(loc.settings.set_lndhub_as_default, { url: params.url }) as string, - '', - [ - { - text: loc._.ok, - onPress: () => { - params?.url && setLndhubURI(params.url); - }, - style: 'default', - }, - { text: loc._.cancel, onPress: () => {}, style: 'cancel' }, - ], - { cancelable: false }, - ); - } + const initialize = async () => { + setIsLoading(true); + await fetchURI().finally(() => { + setIsLoading(false); + if (params?.url) { + Alert.alert( + loc.formatString(loc.settings.set_lndhub_as_default, { url: params.url }) as string, + '', + [ + { + text: loc._.ok, + onPress: () => { + params?.url && setLndhubURI(params.url); + }, + style: 'default', + }, + { text: loc._.cancel, onPress: () => {}, style: 'cancel' }, + ], + { cancelable: false }, + ); + } + }); + }; + + // Call the initialize function + initialize(); }, [params?.url]); const setLndhubURI = (value: string) => { @@ -91,21 +106,24 @@ const LightningSettings: React.FC = () => { setURI(typeof setLndHubUrl === 'string' ? setLndHubUrl.trim() : value.trim()); }; - const save = useCallback(async () => { setIsLoading(true); try { + await DefaultPreference.setName(GROUP_IO_BLUEWALLET); if (URI) { - await LightningCustodianWallet.isValidNodeAddress(URI); - // validating only if its not empty. empty means use default - } - if (URI) { - await AsyncStorage.setItem(BlueApp.LNDHUB, URI); + const normalizedURI = new URL(URI.replace(/([^:]\/)\/+/g, '$1')).toString(); + + await LightningCustodianWallet.isValidNodeAddress(normalizedURI); + + await setLNDHub(normalizedURI); } else { - await AsyncStorage.removeItem(BlueApp.LNDHUB); + await clearLNDHub(); } - presentAlert({ message: loc.settings.lightning_saved }); + + presentAlert({ message: loc.settings.lightning_saved, type: AlertType.Toast }); + triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); } catch (error) { + triggerHapticFeedback(HapticFeedbackTypes.NotificationError); presentAlert({ message: loc.settings.lightning_error_lndhub_uri }); console.log(error); } diff --git a/screen/wallets/Add.tsx b/screen/wallets/Add.tsx index 2b761b727..10566a168 100644 --- a/screen/wallets/Add.tsx +++ b/screen/wallets/Add.tsx @@ -1,4 +1,3 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; import { useNavigation } from '@react-navigation/native'; import React, { useCallback, useEffect, useMemo, useReducer } from 'react'; import { @@ -18,7 +17,7 @@ import { import A from '../../blue_modules/analytics'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { BlueButtonLink, BlueFormLabel, BlueSpacing20, BlueSpacing40, BlueText } from '../../BlueComponents'; -import { BlueApp, HDSegwitBech32Wallet, HDSegwitP2SHWallet, LightningCustodianWallet, SegwitP2SHWallet } from '../../class'; +import { HDSegwitBech32Wallet, HDSegwitP2SHWallet, LightningCustodianWallet, SegwitP2SHWallet } from '../../class'; import presentAlert from '../../components/Alert'; import Button from '../../components/Button'; import { useTheme } from '../../components/themes'; @@ -30,6 +29,7 @@ import ToolTipMenu from '../../components/TooltipMenu'; import { Icon } from '@rneui/themed'; import { CommonToolTipActions } from '../../typings/CommonToolTipActions'; import { Action } from '../../components/types'; +import { getLNDHub } from '../../helpers/lndHub'; enum ButtonSelected { // @ts-ignore: Return later to update @@ -261,7 +261,7 @@ const WalletsAdd: React.FC = () => { }, [HeaderRight, colorScheme, colors.foregroundColor, navigateToEntropy, setOptions, toolTipActions]); useEffect(() => { - AsyncStorage.getItem(BlueApp.LNDHUB) + getLNDHub() .then(url => (url ? setWalletBaseURI(url) : setWalletBaseURI(''))) .catch(() => setWalletBaseURI('')) .finally(() => setIsLoading(false));