ADD: Set preferred server from menu

This commit is contained in:
Marcos Rodriguez Velez 2024-12-29 01:26:10 -04:00
parent cc8bedadb3
commit a76c847a10
5 changed files with 335 additions and 154 deletions

View file

@ -1,4 +1,3 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import BigNumber from 'bignumber.js';
import * as bitcoin from 'bitcoinjs-lib';
import DefaultPreference from 'react-native-default-preference';
@ -9,6 +8,7 @@ import { LegacyWallet, SegwitBech32Wallet, SegwitP2SHWallet, TaprootWallet } fro
import presentAlert from '../components/Alert';
import loc from '../loc';
import { GROUP_IO_BLUEWALLET } from './currency';
import { ElectrumServerItem } from '../screen/settings/ElectrumSettings';
const ElectrumClient = require('electrum-client');
const net = require('net');
@ -70,12 +70,12 @@ type MempoolTransaction = {
type Peer =
| {
host: string;
ssl: string;
ssl: number;
tcp?: undefined;
}
| {
host: string;
tcp: string;
tcp: number;
ssl?: undefined;
};
@ -85,16 +85,20 @@ export const ELECTRUM_SSL_PORT = 'electrum_ssl_port';
export const ELECTRUM_SERVER_HISTORY = 'electrum_server_history';
const ELECTRUM_CONNECTION_DISABLED = 'electrum_disabled';
const storageKey = 'ELECTRUM_PEERS';
const defaultPeer = { host: 'electrum1.bluewallet.io', ssl: '443' };
const defaultPeer = { host: 'electrum1.bluewallet.io', ssl: 443 };
export const hardcodedPeers: Peer[] = [
{ host: 'mainnet.foundationdevices.com', ssl: '50002' },
// { host: 'bitcoin.lukechilds.co', ssl: '50002' },
{ host: 'mainnet.foundationdevices.com', ssl: 50002 },
// { host: 'bitcoin.lukechilds.co', ssl: 50002 },
// { host: 'electrum.jochen-hoenicke.de', ssl: '50006' },
{ host: 'electrum1.bluewallet.io', ssl: '443' },
{ host: 'electrum.acinq.co', ssl: '50002' },
{ host: 'electrum.bitaroo.net', ssl: '50002' },
{ host: 'electrum1.bluewallet.io', ssl: 443 },
{ host: 'electrum.acinq.co', ssl: 50002 },
{ host: 'electrum.bitaroo.net', ssl: 50002 },
];
export const suggestedServers: Peer[] = hardcodedPeers.map(peer => ({
...peer,
}));
let mainClient: typeof ElectrumClient | undefined;
let mainConnected: boolean = false;
let wasConnectedAtLeastOnce: boolean = false;
@ -137,23 +141,56 @@ async function _getRealm() {
return _realm;
}
export const getPreferredServer = async (): Promise<ElectrumServerItem | undefined> => {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
const host = (await DefaultPreference.get(ELECTRUM_HOST)) as string;
const tcpPort = await DefaultPreference.get(ELECTRUM_TCP_PORT);
const sslPort = await DefaultPreference.get(ELECTRUM_SSL_PORT);
console.log('Getting preferred server:', { host, tcpPort, sslPort });
if (!host) {
console.warn('Preferred server host is undefined');
return;
}
return {
host,
tcp: tcpPort ? Number(tcpPort) : undefined,
ssl: sslPort ? Number(sslPort) : undefined,
};
};
export const removePreferredServer = async () => {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
console.log('Removing preferred server');
await DefaultPreference.clear(ELECTRUM_HOST);
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
};
export async function isDisabled(): Promise<boolean> {
let result;
try {
const savedValue = await AsyncStorage.getItem(ELECTRUM_CONNECTION_DISABLED);
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
const savedValue = await DefaultPreference.get(ELECTRUM_CONNECTION_DISABLED);
console.log('Getting Electrum connection disabled state:', savedValue);
if (savedValue === null) {
result = false;
} else {
result = savedValue;
}
} catch {
} catch (error) {
console.error('Error getting Electrum connection disabled state:', error);
result = false;
}
return !!result;
}
export async function setDisabled(disabled = true) {
return AsyncStorage.setItem(ELECTRUM_CONNECTION_DISABLED, disabled ? '1' : '');
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
console.log('Setting Electrum connection disabled state to:', disabled);
return DefaultPreference.set(ELECTRUM_CONNECTION_DISABLED, disabled ? '1' : '');
}
function getCurrentPeer() {
@ -171,20 +208,23 @@ function getNextPeer() {
}
async function getSavedPeer(): Promise<Peer | null> {
const host = await AsyncStorage.getItem(ELECTRUM_HOST);
const tcpPort = await AsyncStorage.getItem(ELECTRUM_TCP_PORT);
const sslPort = await AsyncStorage.getItem(ELECTRUM_SSL_PORT);
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
const host = (await DefaultPreference.get(ELECTRUM_HOST)) as string;
const tcpPort = await DefaultPreference.get(ELECTRUM_TCP_PORT);
const sslPort = await DefaultPreference.get(ELECTRUM_SSL_PORT);
console.log('Getting saved peer:', { host, tcpPort, sslPort });
if (!host) {
return null;
}
if (sslPort) {
return { host, ssl: sslPort };
return { host, ssl: Number(sslPort) };
}
if (tcpPort) {
return { host, tcp: tcpPort };
return { host, tcp: Number(tcpPort) };
}
return null;
@ -201,6 +241,8 @@ export async function connectMain(): Promise<void> {
usingPeer = savedPeer;
}
console.log('Using peer:', JSON.stringify(usingPeer));
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
try {
if (usingPeer.host.endsWith('onion')) {
@ -208,10 +250,6 @@ export async function connectMain(): Promise<void> {
await DefaultPreference.set(ELECTRUM_HOST, randomPeer.host);
await DefaultPreference.set(ELECTRUM_TCP_PORT, randomPeer.tcp ?? '');
await DefaultPreference.set(ELECTRUM_SSL_PORT, randomPeer.ssl ?? '');
} else {
await DefaultPreference.set(ELECTRUM_HOST, usingPeer.host);
await DefaultPreference.set(ELECTRUM_TCP_PORT, usingPeer.tcp ?? '');
await DefaultPreference.set(ELECTRUM_SSL_PORT, usingPeer.ssl ?? '');
}
} catch (e) {
// Must be running on Android
@ -292,6 +330,38 @@ export async function connectMain(): Promise<void> {
}
}
export async function presentResetToDefaultsAlert(): Promise<boolean> {
return new Promise(resolve => {
presentAlert({
title: loc.settings.electrum_reset,
message: loc.settings.electrum_reset_to_default,
buttons: [
{
text: loc._.cancel,
style: 'cancel',
onPress: () => resolve(false),
},
{
text: loc._.ok,
style: 'destructive',
onPress: async () => {
try {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
await DefaultPreference.clear(ELECTRUM_HOST);
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
} catch (e) {
console.log(e); // Must be running on Android
}
resolve(true);
},
},
],
options: { cancelable: true },
});
});
}
const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
if (await isDisabled()) {
console.log(
@ -299,6 +369,7 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
);
return;
}
presentAlert({
allowRepeat: false,
title: loc.errors.network,
@ -319,39 +390,13 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
{
text: loc.settings.electrum_reset,
onPress: () => {
presentAlert({
title: loc.settings.electrum_reset,
message: loc.settings.electrum_reset_to_default,
buttons: [
{
text: loc._.cancel,
style: 'cancel',
onPress: () => {},
},
{
text: loc._.ok,
style: 'destructive',
onPress: async () => {
await AsyncStorage.setItem(ELECTRUM_HOST, '');
await AsyncStorage.setItem(ELECTRUM_TCP_PORT, '');
await AsyncStorage.setItem(ELECTRUM_SSL_PORT, '');
try {
await DefaultPreference.setName('group.io.bluewallet.bluewallet');
await DefaultPreference.clear(ELECTRUM_HOST);
await DefaultPreference.clear(ELECTRUM_SSL_PORT);
await DefaultPreference.clear(ELECTRUM_TCP_PORT);
} catch (e) {
console.log(e); // Must be running on Android
}
presentAlert({ message: loc.settings.electrum_saved });
setTimeout(connectMain, 500);
},
},
],
options: { cancelable: true },
presentResetToDefaultsAlert().then(result => {
if (result) {
connectionAttempt = 0;
mainClient.close() && mainClient.close();
setTimeout(connectMain, 500);
}
});
connectionAttempt = 0;
mainClient.close() && mainClient.close();
},
style: 'destructive',
},
@ -377,13 +422,18 @@ const presentNetworkErrorAlert = async (usingPeer?: Peer) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function getRandomDynamicPeer(): Promise<Peer> {
try {
let peers = JSON.parse((await AsyncStorage.getItem(storageKey)) as string);
let peers = JSON.parse((await DefaultPreference.get(storageKey)) as string);
peers = peers.sort(() => Math.random() - 0.5); // shuffle
for (const peer of peers) {
const ret = {
host: peer[1] as string,
tcp: '',
};
const ret: Peer = { host: peer[0], ssl: peer[1] };
ret.host = peer[1];
if (peer[1] === 's') {
ret.ssl = peer[2];
} else {
ret.tcp = peer[2];
}
for (const item of peer[2]) {
if (item.startsWith('t')) {
ret.tcp = item.replace('t', '');

View file

@ -55,6 +55,14 @@ const ToolTipMenu = React.memo((props: ToolTipMenuProps, ref?: Ref<any>) => {
image: subaction.icon?.iconValue ? subaction.icon.iconValue : undefined,
state: subaction.menuState === undefined ? undefined : ((subaction.menuState ? 'on' : 'off') as MenuState),
attributes: { disabled: subaction.disabled, destructive: subaction.destructive, hidden: subaction.hidden },
subactions: subaction.subactions?.map(subsubaction => ({
id: subsubaction.id.toString(),
title: subsubaction.text,
subtitle: subsubaction.subtitle,
image: subsubaction.icon?.iconValue ? subsubaction.icon.iconValue : undefined,
state: subsubaction.menuState === undefined ? undefined : ((subsubaction.menuState ? 'on' : 'off') as MenuState),
attributes: { disabled: subsubaction.disabled, destructive: subsubaction.destructive, hidden: subsubaction.hidden },
})),
})) || [];
return {

View file

@ -28,6 +28,7 @@
"enter_amount": "Enter amount",
"qr_custom_input_button": "Tap 10 times to enter custom input",
"unlock": "Unlock",
"port": "Port",
"suggested": "Suggested"
},
"azteco": {
@ -253,11 +254,11 @@
"electrum_preferred_server_description": "Enter the server you want your wallet to use for all Bitcoin activities. Once set, your wallet will exclusively use this server to check balances, send transactions, and fetch network data. Ensure you trust this server before setting it.", "electrum_clear_alert_title": "Clear history?",
"electrum_clear_alert_message": "Do you want to clear electrum servers history?",
"electrum_clear_alert_cancel": "Cancel",
"electrum_clear_alert_ok": "Ok",
"electrum_reset": "Reset to default",
"only_use_preferred": "Only connect to preferred server",
"electrum_unable_to_connect": "Unable to connect to {server}.",
"electrum_history": "History",
"electrum_reset_to_default": "Are you sure to want to reset your Electrum settings to default?",
"electrum_reset_to_default": "This will let BlueWallet randomly choose a server from the suggested list and history. Your server history will remain unchanged.",
"electrum_reset": "Reset to default",
"electrum_clear": "Clear History",
"encrypt_decrypt": "Decrypt Storage",
"encrypt_decrypt_q": "Are you sure you want to decrypt your storage? This will allow your wallets to be accessed without a password.",
@ -272,6 +273,7 @@
"encrypt_title": "Security",
"encrypt_tstorage": "Storage",
"encrypt_use": "Use {type}",
"set_as_preferred": "Set as preferred",
"encrypted_feature_disabled": "This feature cannot be used with encrypted storage enabled.",
"encrypt_use_expl": "{type} will be used to confirm your identity before making a transaction, unlocking, exporting, or deleting a wallet. {type} will not be used to unlock encrypted storage.",
"biometrics_fail": "If {type} is not enabled, or fails to unlock, you can use your device passcode as an alternative.",
@ -291,6 +293,7 @@
"network": "Network",
"network_broadcast": "Broadcast Transaction",
"network_electrum": "Electrum Server",
"electrum_suggested_description": "When a preferred server is not set, a suggested server will be selected for use at random.",
"not_a_valid_uri": "Invalid URI",
"notifications": "Notifications",
"open_link_in_explorer": "Open link in explorer",

View file

@ -22,21 +22,25 @@ import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
import { Divider } from '@rneui/themed';
import { Header } from '../../components/Header';
import AddressInput from '../../components/AddressInput';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { GROUP_IO_BLUEWALLET } from '../../blue_modules/currency';
import { Action } from '../../components/types';
import ListItem, { PressableWrapper } from '../../components/ListItem';
import HeaderMenuButton from '../../components/HeaderMenuButton';
import { useSettings } from '../../hooks/context/useSettings';
import { suggestedServers, hardcodedPeers, presentResetToDefaultsAlert } from '../../blue_modules/BlueElectrum';
type RouteProps = RouteProp<DetailViewStackParamList, 'ElectrumSettings'>;
export interface ElectrumServerItem {
host: string;
port?: number;
sslPort?: number;
tcp?: number;
ssl?: number;
}
const SET_PREFERRED_PREFIX = 'set_preferred_';
const DELETE_PREFIX = 'delete_';
const PREFERRED_SERVER_ROW = 'preferredserverrow';
const ElectrumSettings: React.FC = () => {
const { colors } = useTheme();
const { server } = useRoute<RouteProps>().params;
@ -79,36 +83,54 @@ const ElectrumSettings: React.FC = () => {
},
});
useEffect(() => {
let configInterval: NodeJS.Timeout | null = null;
const fetchData = async () => {
const savedHost = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_HOST);
const savedPort = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_TCP_PORT);
const savedSslPort = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_SSL_PORT);
const serverHistoryStr = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_SERVER_HISTORY);
const configIntervalRef = React.useRef<NodeJS.Timeout | null>(null);
const parsedServerHistory: ElectrumServerItem[] = serverHistoryStr ? JSON.parse(serverHistoryStr) : [];
const fetchData = React.useCallback(async () => {
console.log('Fetching data...');
const preferredServer = await BlueElectrum.getPreferredServer();
const savedHost = preferredServer?.host;
const savedPort = preferredServer?.tcp;
const savedSslPort = preferredServer?.ssl;
const serverHistoryStr = (await DefaultPreference.get(BlueElectrum.ELECTRUM_SERVER_HISTORY)) as string;
setHost(savedHost || '');
setPort(savedPort ? Number(savedPort) : undefined);
setSslPort(savedSslPort ? Number(savedSslPort) : undefined);
setServerHistory(parsedServerHistory);
console.log('Preferred server:', preferredServer);
console.log('Server history string:', serverHistoryStr);
const parsedServerHistory: ElectrumServerItem[] = serverHistoryStr ? JSON.parse(serverHistoryStr) : [];
const filteredServerHistory = parsedServerHistory.filter(
v =>
v.host &&
(v.tcp || v.ssl) &&
!suggestedServers.some(suggested => suggested.host === v.host && suggested.tcp === v.tcp && suggested.ssl === v.ssl) &&
!hardcodedPeers.some(peer => peer.host === v.host),
);
console.log('Filtered server history:', filteredServerHistory);
setHost(savedHost || '');
setPort(savedPort ? Number(savedPort) : undefined);
setSslPort(savedSslPort ? Number(savedSslPort) : undefined);
setServerHistory(filteredServerHistory);
setConfig(await BlueElectrum.getConfig());
configIntervalRef.current = setInterval(async () => {
setConfig(await BlueElectrum.getConfig());
configInterval = setInterval(async () => {
setConfig(await BlueElectrum.getConfig());
}, 500);
}, 500);
setIsLoading(false);
};
fetchData();
setIsLoading(false);
return () => {
if (configInterval) clearInterval(configInterval);
if (configIntervalRef.current) clearInterval(configIntervalRef.current);
};
}, []);
useEffect(() => {
fetchData();
return () => {
if (configIntervalRef.current) clearInterval(configIntervalRef.current);
};
}, [fetchData]);
useEffect(() => {
if (server) {
triggerHapticFeedback(HapticFeedbackTypes.ImpactHeavy);
@ -132,56 +154,60 @@ const ElectrumSettings: React.FC = () => {
const clearHistory = useCallback(async () => {
setIsLoading(true);
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify([]));
await DefaultPreference.clear(BlueElectrum.ELECTRUM_SERVER_HISTORY);
setServerHistory([]);
setIsLoading(false);
}, []);
const serverExists = useCallback(
(value: ElectrumServerItem) => {
return serverHistory.some(s => `${s.host}:${s.port}:${s.sslPort}` === `${value.host}:${value.port}:${value.sslPort}`);
return serverHistory.some(s => `${s.host}:${s.tcp}:${s.ssl}` === `${value.host}:${value.tcp}:${value.ssl}`);
},
[serverHistory],
);
const save = useCallback(async () => {
Keyboard.dismiss();
setIsLoading(true);
const save = useCallback(
async (v?: ElectrumServerItem) => {
Keyboard.dismiss();
setIsLoading(true);
try {
if (!host && !port && !sslPort) {
await AsyncStorage.removeItem(BlueElectrum.ELECTRUM_HOST);
await AsyncStorage.removeItem(BlueElectrum.ELECTRUM_TCP_PORT);
await AsyncStorage.removeItem(BlueElectrum.ELECTRUM_SSL_PORT);
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
await DefaultPreference.clear(BlueElectrum.ELECTRUM_HOST);
await DefaultPreference.clear(BlueElectrum.ELECTRUM_TCP_PORT);
await DefaultPreference.clear(BlueElectrum.ELECTRUM_SSL_PORT);
} else {
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_HOST, host);
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_TCP_PORT, port?.toString() || '');
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SSL_PORT, sslPort?.toString() || '');
if (!serverExists({ host, port, sslPort })) {
const newServerHistory = [...serverHistory, { host, port, sslPort }];
await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify(newServerHistory));
setServerHistory(newServerHistory);
try {
const serverHost = v?.host || host;
const serverPort = v?.tcp || port?.toString() || '';
const serverSslPort = v?.ssl || sslPort?.toString() || '';
if (serverHost && (serverPort || serverSslPort)) {
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
await DefaultPreference.set(BlueElectrum.ELECTRUM_HOST, serverHost);
await DefaultPreference.set(BlueElectrum.ELECTRUM_TCP_PORT, serverPort);
await DefaultPreference.set(BlueElectrum.ELECTRUM_SSL_PORT, serverSslPort);
if (
!serverExists({ host: serverHost, tcp: Number(serverPort), ssl: Number(serverSslPort) }) &&
serverHost &&
(serverPort || serverSslPort) &&
!hardcodedPeers.some(peer => peer.host === serverHost)
) {
const newServerHistory = [...serverHistory, { host: serverHost, tcp: Number(serverPort), ssl: Number(serverSslPort) }];
await DefaultPreference.set(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify(newServerHistory));
setServerHistory(newServerHistory);
}
}
await DefaultPreference.setName(GROUP_IO_BLUEWALLET);
await DefaultPreference.set(BlueElectrum.ELECTRUM_HOST, host);
await DefaultPreference.set(BlueElectrum.ELECTRUM_TCP_PORT, port?.toString() || '');
await DefaultPreference.set(BlueElectrum.ELECTRUM_SSL_PORT, sslPort?.toString() || '');
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
presentAlert({ message: loc.settings.electrum_saved });
} catch (error) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: (error as Error).message });
} finally {
setIsLoading(false);
}
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
presentAlert({ message: loc.settings.electrum_saved });
} catch (error) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationError);
presentAlert({ message: (error as Error).message });
}
setIsLoading(false);
}, [host, port, sslPort, serverExists, serverHistory]);
},
[host, port, sslPort, serverExists, serverHistory],
);
const resetToDefault = useCallback(() => {
Alert.alert(loc.settings.electrum_reset, loc.settings.electrum_reset_to_default, [
Alert.alert(loc.settings.electrum_preferred_server, loc.settings.electrum_preferred_server_description, [
{
text: loc._.cancel,
onPress: () => console.log('Cancel Pressed'),
@ -191,22 +217,22 @@ const ElectrumSettings: React.FC = () => {
text: loc._.ok,
style: 'destructive',
onPress: async () => {
await BlueElectrum.removePreferredServer();
setHost('');
setPort(undefined);
setSslPort(undefined);
await save();
},
},
]);
}, [save]);
}, []);
const selectServer = useCallback(
(value: string) => {
const parsedServer = JSON.parse(value) as ElectrumServerItem;
setHost(parsedServer.host);
setPort(parsedServer.port);
setSslPort(parsedServer.sslPort);
save();
setPort(parsedServer.tcp);
setSslPort(parsedServer.ssl);
save(parsedServer);
},
[save],
);
@ -215,55 +241,139 @@ const ElectrumSettings: React.FC = () => {
triggerHapticFeedback(HapticFeedbackTypes.ImpactHeavy);
Alert.alert(loc.settings.electrum_clear_alert_title, loc.settings.electrum_clear_alert_message, [
{ text: loc.settings.electrum_clear_alert_cancel, onPress: () => console.log('Cancel Pressed'), style: 'cancel' },
{ text: loc.settings.electrum_clear_alert_ok, onPress: () => clearHistory() },
{ text: loc._.ok, onPress: () => clearHistory() },
]);
}, [clearHistory]);
const onPressMenuItem = useCallback(
(id: string) => {
switch (id) {
case CommonToolTipActions.ResetToDefault.id:
resetToDefault();
break;
case CommonToolTipActions.ClearHistory.id:
clearHistoryAlert();
break;
default:
try {
selectServer(id);
} catch (error) {
console.warn('Unknown menu item selected:', id);
if (id.startsWith(SET_PREFERRED_PREFIX)) {
const rawServer = JSON.parse(id.replace(SET_PREFERRED_PREFIX, ''));
selectServer(JSON.stringify(rawServer));
} else if (id.startsWith(DELETE_PREFIX)) {
const rawServer = JSON.parse(id.replace(DELETE_PREFIX, ''));
const newServerHistory = serverHistory
.filter(s => !(s.host === rawServer.host && s.tcp === rawServer.tcp && s.ssl === rawServer.ssl))
.filter(
v => !suggestedServers.some(suggested => suggested.host === v.host && suggested.tcp === v.tcp && suggested.ssl === v.ssl),
);
setServerHistory(newServerHistory);
DefaultPreference.set(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify(newServerHistory));
} else if (id === PREFERRED_SERVER_ROW) {
presentResetToDefaultsAlert().then(async result => {
if (result) {
await BlueElectrum.removePreferredServer();
fetchData();
}
break;
});
} else {
switch (id) {
case CommonToolTipActions.ResetToDefault.id:
resetToDefault();
break;
case CommonToolTipActions.ClearHistory.id:
clearHistoryAlert();
break;
default:
try {
selectServer(id);
} catch (error) {
console.warn('Unknown menu item selected:', id);
}
break;
}
}
},
[clearHistoryAlert, resetToDefault, selectServer],
[selectServer, serverHistory, fetchData, resetToDefault, clearHistoryAlert],
);
const toolTipActions = useMemo(() => {
const actions: Action[] = [CommonToolTipActions.ResetToDefault];
const createServerAction = useCallback(
(value: ElectrumServerItem, seenHosts: Set<string>) => {
const hostKey = `${value.host}:${value.ssl ?? value.tcp}`;
if (seenHosts.has(hostKey)) return null;
if (serverHistory.length > 0) {
const serverSubactions: Action[] = serverHistory.map(value => ({
seenHosts.add(hostKey);
return {
id: JSON.stringify(value),
text: `${value.host}`,
subtitle: `${value.port || value.sslPort}`,
menuState: `${host}:${port}:${sslPort}` === `${value.host}:${value.port}:${value.sslPort}`,
disabled: isLoading || (host === value.host && (port === value.port || sslPort === value.sslPort)),
}));
text: Platform.OS === 'android' ? `${value.host}:${value.ssl ?? value.tcp}` : value.host,
subactions: [
...(host === value.host && (port === value.tcp || sslPort === value.ssl)
? []
: [
{
id: `${SET_PREFERRED_PREFIX}${JSON.stringify(value)}`,
text: loc.settings.set_as_preferred,
subtitle: `${loc._.port}: ${value.ssl ?? value.tcp}`,
},
]),
...(hardcodedPeers.some(peer => peer.host === value.host)
? []
: [
{
id: `${DELETE_PREFIX}${JSON.stringify(value)}`,
text: loc.wallets.details_delete,
},
]),
],
} as Action;
},
[host, port, sslPort],
);
const generateToolTipActions = useCallback(() => {
const actions: Action[] = [];
const seenHosts = new Set<string>();
if (host) {
const preferred = {
id: 'preferred',
hidden: false,
displayInline: true,
text: loc.settings.electrum_preferred_server,
subactions: [
{
id: PREFERRED_SERVER_ROW,
text: Platform.OS === 'android' ? `${host} (${sslPort ?? port})` : host,
subtitle: `${loc._.port}: ${sslPort ?? port}`,
menuState: true,
},
],
};
actions.push(preferred);
seenHosts.add(`${host}:${sslPort ?? port}`);
}
const suggestedServersAction: Action = {
id: 'suggested_servers',
text: loc._.suggested,
displayInline: true,
subtitle: loc.settings.electrum_suggested_description,
subactions: suggestedServers.map(value => createServerAction(value, seenHosts)).filter((action): action is Action => action !== null),
};
actions.push(suggestedServersAction);
console.warn('serverHistory', serverHistory);
if (serverHistory.length > 0) {
const serverSubactions: Action[] = serverHistory
.map(value => createServerAction(value, seenHosts))
.filter((action): action is Action => action !== null);
actions.push({
id: 'server_history',
text: loc.settings.electrum_history,
displayInline: serverHistory.length <= 5 && serverHistory.length > 0,
subactions: [CommonToolTipActions.ClearHistory, ...serverSubactions],
hidden: serverHistory.length === 0,
});
}
return actions;
}, [host, isLoading, port, serverHistory, sslPort]);
}, [createServerAction, host, port, serverHistory, sslPort]);
const HeaderRight = useMemo(
() => <HeaderMenuButton actions={toolTipActions} onPressMenuItem={onPressMenuItem} />,
[onPressMenuItem, toolTipActions],
() => <HeaderMenuButton actions={generateToolTipActions()} onPressMenuItem={onPressMenuItem} />,
[onPressMenuItem, generateToolTipActions],
);
useEffect(() => {
@ -331,6 +441,8 @@ const ElectrumSettings: React.FC = () => {
}
};
const preferredServerIsEmpty = !host || (!port && !sslPort);
const renderElectrumSettings = () => {
return (
<>
@ -411,13 +523,19 @@ const ElectrumSettings: React.FC = () => {
testID="SSLPortInput"
value={sslPort !== undefined}
onValueChange={onSSLPortChange}
disabled={host?.endsWith('.onion') ?? false}
disabled={host?.endsWith('.onion') || isLoading || host === '' || (port === undefined && sslPort === undefined)}
/>
</View>
</BlueCard>
<BlueCard>
<BlueSpacing20 />
<Button showActivityIndicator={isLoading} disabled={isLoading} testID="Save" onPress={save} title={loc.settings.save} />
<Button
showActivityIndicator={isLoading}
disabled={isLoading || preferredServerIsEmpty}
testID="Save"
onPress={save}
title={loc.settings.save}
/>
</BlueCard>
{Platform.select({

View file

@ -313,7 +313,9 @@ export const CommonToolTipActions = {
},
ResetToDefault: {
id: keys.ResetToDefault,
text: loc.settings.electrum_reset,
text: loc.settings.only_use_preferred,
hidden: true,
menuState: true,
},
ClearHistory: {
id: keys.ClearHistory,