ADD: Electrum connection toggle (#3470)

* ADD: Electrum connection toggle
This commit is contained in:
Marcos Rodriguez Vélez 2021-08-10 07:35:47 -07:00 committed by GitHub
parent c4b3fb3ae5
commit 5a61a8a41d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 189 additions and 114 deletions

View file

@ -17,6 +17,7 @@ const ELECTRUM_HOST = 'electrum_host';
const ELECTRUM_TCP_PORT = 'electrum_tcp_port';
const ELECTRUM_SSL_PORT = 'electrum_ssl_port';
const ELECTRUM_SERVER_HISTORY = 'electrum_server_history';
const ELECTRUM_CONNECTION_DISABLED = 'electrum_disabled';
let _realm;
async function _getRealm() {
@ -74,7 +75,30 @@ let latestBlockheightTimestamp = false;
const txhashHeightCache = {};
async function isDisabled() {
let isDisabled;
try {
const savedValue = await AsyncStorage.getItem(ELECTRUM_CONNECTION_DISABLED);
if (savedValue === null) {
isDisabled = false;
} else {
isDisabled = savedValue;
}
} catch {
isDisabled = false;
}
return !!isDisabled;
}
async function setDisabled(disabled = true) {
return AsyncStorage.setItem(ELECTRUM_CONNECTION_DISABLED, disabled ? '1' : '');
}
async function connectMain() {
if (await isDisabled()) {
console.log('Electrum connection disabled by user. Skipping connectMain call');
return;
}
let usingPeer = await getRandomHardcodedPeer();
const savedPeer = await getSavedPeer();
if (savedPeer && savedPeer.host && (savedPeer.tcp || savedPeer.ssl)) {
@ -162,6 +186,12 @@ async function connectMain() {
connectMain();
async function presentNetworkErrorAlert(usingPeer) {
if (await isDisabled()) {
console.log(
'Electrum connection disabled by user. Perhaps we are attempting to show this network error alert after the user disabled connections.',
);
return;
}
Alert.alert(
loc.errors.network,
loc.formatString(
@ -636,6 +666,10 @@ module.exports.multiGetTransactionByTxid = async function (txids, batchsize, ver
module.exports.waitTillConnected = async function () {
let waitTillConnectedInterval = false;
let retriesCounter = 0;
if (await isDisabled()) {
console.warn('Electrum connections disabled by user. waitTillConnected skipping...');
return;
}
return new Promise(function (resolve, reject) {
waitTillConnectedInterval = setInterval(() => {
if (mainConnected) {
@ -851,7 +885,9 @@ module.exports.setBatchingDisabled = () => {
module.exports.setBatchingEnabled = () => {
disableBatching = false;
};
module.exports.connectMain = connectMain;
module.exports.isDisabled = isDisabled;
module.exports.setDisabled = setDisabled;
module.exports.hardcodedPeers = hardcodedPeers;
module.exports.getRandomHardcodedPeer = getRandomHardcodedPeer;
module.exports.ELECTRUM_HOST = ELECTRUM_HOST;

View file

@ -263,6 +263,8 @@
"electrum_connected_not": "Not Connected",
"electrum_error_connect": "Cant connect to the provided Electrum server",
"electrum_host": "E.g. {example}",
"electrum_offline_mode": "Offline Mode",
"electrum_offline_description": "When enabled, your Bitcoin wallets will not attempt to fetch balances or transactions.",
"electrum_port": "Port, usually {example}",
"use_ssl": "Use SSL",
"electrum_saved": "Your changes have been saved successfully. Restarting BlueWallet may be required for the changes to take effect.",

View file

@ -13,6 +13,7 @@ import {
KeyboardAvoidingView,
Platform,
Switch,
Pressable,
} from 'react-native';
import DefaultPreference from 'react-native-default-preference';
import AsyncStorage from '@react-native-async-storage/async-storage';
@ -30,6 +31,7 @@ import {
SafeBlueArea,
BlueDoneAndDismissKeyboardInputAccessory,
BlueDismissKeyboardInputAccessory,
BlueListItem,
} from '../../BlueComponents';
import { BlueCurrentTheme } from '../../components/themes';
import { isTorCapable } from '../../blue_modules/environment';
@ -44,6 +46,7 @@ export default class ElectrumSettings extends Component {
const server = props?.route?.params?.server;
this.state = {
isLoading: true,
isOfflineMode: false,
serverHistory: [],
config: {},
server,
@ -61,6 +64,7 @@ export default class ElectrumSettings extends Component {
const port = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_TCP_PORT);
const sslPort = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_SSL_PORT);
const serverHistoryStr = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_SERVER_HISTORY);
const isOfflineMode = await BlueElectrum.isDisabled();
const serverHistory = JSON.parse(serverHistoryStr) || [];
this.setState({
isLoading: false,
@ -68,6 +72,7 @@ export default class ElectrumSettings extends Component {
port,
sslPort,
serverHistory,
isOfflineMode,
isAndroidNumericKeyboardFocused: false,
isAndroidAddressKeyboardVisible: false,
});
@ -248,7 +253,18 @@ export default class ElectrumSettings extends Component {
}
};
render() {
onElectrumConnectionEnabledSwitchValueChangd = async value => {
if (value === true) {
await BlueElectrum.setDisabled(true);
BlueElectrum.forceDisconnect();
} else {
await BlueElectrum.setDisabled(false);
BlueElectrum.connectMain();
}
this.setState({ isOfflineMode: value });
};
renderElectrumSettings = () => {
const serverHistoryItems = this.state.serverHistory.map((server, i) => {
return (
<View key={i} style={styles.serverHistoryItem}>
@ -262,133 +278,151 @@ export default class ElectrumSettings extends Component {
});
return (
<SafeBlueArea>
<ScrollView keyboardShouldPersistTaps="always">
<>
<BlueCard>
<BlueText style={styles.status}>{loc.settings.electrum_status}</BlueText>
<View style={styles.connectWrap}>
<View style={[styles.container, this.state.config.connected === 1 ? styles.containerConnected : styles.containerDisconnected]}>
<BlueText style={this.state.config.connected === 1 ? styles.textConnected : styles.textDisconnected}>
{this.state.config.connected === 1 ? loc.settings.electrum_connected : loc.settings.electrum_connected_not}
</BlueText>
</View>
</View>
<BlueSpacing20 />
<BlueText style={styles.hostname} onPress={this.checkServer}>
{this.state.config.host}:{this.state.config.port}
</BlueText>
</BlueCard>
<KeyboardAvoidingView>
<BlueCard>
<BlueText style={styles.status}>{loc.settings.electrum_status}</BlueText>
<View style={styles.connectWrap}>
<View
style={[styles.container, this.state.config.connected === 1 ? styles.containerConnected : styles.containerDisconnected]}
>
<BlueText style={this.state.config.connected === 1 ? styles.textConnected : styles.textDisconnected}>
{this.state.config.connected === 1 ? loc.settings.electrum_connected : loc.settings.electrum_connected_not}
</BlueText>
</View>
<View style={styles.inputWrap}>
<TextInput
placeholder={
loc.formatString(loc.settings.electrum_host, { example: '111.222.333.111' }) +
(isTorCapable ? ' (' + loc.settings.tor_supported + ')' : '')
}
value={this.state.host}
onChangeText={text => this.setState({ host: text.trim() })}
numberOfLines={1}
style={styles.inputText}
editable={!this.state.isLoading}
placeholderTextColor="#81868e"
autoCorrect={false}
autoCapitalize="none"
underlineColorAndroid="transparent"
inputAccessoryViewID={BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID}
testID="HostInput"
onFocus={() => this.setState({ isAndroidAddressKeyboardVisible: true })}
onBlur={() => this.setState({ isAndroidAddressKeyboardVisible: false })}
/>
</View>
<BlueSpacing20 />
<BlueText style={styles.hostname} onPress={this.checkServer}>
{this.state.config.host}:{this.state.config.port}
</BlueText>
</BlueCard>
<KeyboardAvoidingView>
<BlueCard>
<View style={styles.portWrap}>
<View style={styles.inputWrap}>
<TextInput
placeholder={
loc.formatString(loc.settings.electrum_host, { example: '111.222.333.111' }) +
(isTorCapable ? ' (' + loc.settings.tor_supported + ')' : '')
placeholder={loc.formatString(loc.settings.electrum_port, { example: '50001' })}
value={this.state.sslPort?.trim() === '' || this.state.sslPort === null ? this.state.port : this.state.sslPort}
onChangeText={text =>
this.setState(prevState => {
if (prevState.sslPort?.trim() === '') {
return { port: text.trim(), sslPort: '' };
} else {
return { port: '', sslPort: text.trim() };
}
})
}
value={this.state.host}
onChangeText={text => this.setState({ host: text.trim() })}
numberOfLines={1}
style={styles.inputText}
editable={!this.state.isLoading}
placeholderTextColor="#81868e"
underlineColorAndroid="transparent"
autoCorrect={false}
autoCapitalize="none"
underlineColorAndroid="transparent"
inputAccessoryViewID={BlueDoneAndDismissKeyboardInputAccessory.InputAccessoryViewID}
testID="HostInput"
onFocus={() => this.setState({ isAndroidAddressKeyboardVisible: true })}
onBlur={() => this.setState({ isAndroidAddressKeyboardVisible: false })}
keyboardType="number-pad"
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
testID="PortInput"
onFocus={() => this.setState({ isAndroidNumericKeyboardFocused: true })}
onBlur={() => this.setState({ isAndroidNumericKeyboardFocused: false })}
/>
</View>
<BlueSpacing20 />
<View style={styles.portWrap}>
<View style={styles.inputWrap}>
<TextInput
placeholder={loc.formatString(loc.settings.electrum_port, { example: '50001' })}
value={this.state.sslPort?.trim() === '' || this.state.sslPort === null ? this.state.port : this.state.sslPort}
onChangeText={text =>
this.setState(prevState => {
if (prevState.sslPort?.trim() === '') {
return { port: text.trim(), sslPort: '' };
} else {
return { port: '', sslPort: text.trim() };
}
})
}
numberOfLines={1}
style={styles.inputText}
editable={!this.state.isLoading}
placeholderTextColor="#81868e"
underlineColorAndroid="transparent"
autoCorrect={false}
autoCapitalize="none"
keyboardType="number-pad"
inputAccessoryViewID={BlueDismissKeyboardInputAccessory.InputAccessoryViewID}
testID="PortInput"
onFocus={() => this.setState({ isAndroidNumericKeyboardFocused: true })}
onBlur={() => this.setState({ isAndroidNumericKeyboardFocused: false })}
/>
</View>
<BlueText style={styles.usePort}>{loc.settings.use_ssl}</BlueText>
<Switch testID="SSLPortInput" value={this.state.sslPort?.trim() > 0} onValueChange={this.useSSLPortToggled} />
</View>
<BlueSpacing20 />
<BlueText style={styles.usePort}>{loc.settings.use_ssl}</BlueText>
<Switch testID="SSLPortInput" value={this.state.sslPort?.trim() > 0} onValueChange={this.useSSLPortToggled} />
</View>
<BlueSpacing20 />
<View style={styles.serverAddTitle}>
<BlueText style={styles.explain}>{loc.settings.electrum_settings_explain}</BlueText>
<TouchableOpacity accessibilityRole="button" testID="ResetToDefault" onPress={() => this.resetToDefault()}>
<BlueText>{loc.settings.electrum_reset}</BlueText>
</TouchableOpacity>
</View>
<BlueSpacing20 />
{this.state.isLoading ? <BlueLoading /> : <BlueButton testID="Save" onPress={this.save} title={loc.settings.save} />}
<BlueSpacing20 />
<BlueButtonLink title={loc.wallets.import_scan_qr} onPress={this.importScan} />
<BlueSpacing20 />
</BlueCard>
{Platform.select({
ios: <BlueDismissKeyboardInputAccessory />,
android: this.state.isAndroidNumericKeyboardFocused && <BlueDismissKeyboardInputAccessory />,
})}
<View style={styles.serverAddTitle}>
<BlueText style={styles.explain}>{loc.settings.electrum_settings_explain}</BlueText>
<TouchableOpacity accessibilityRole="button" testID="ResetToDefault" onPress={() => this.resetToDefault()}>
<BlueText>{loc.settings.electrum_reset}</BlueText>
</TouchableOpacity>
</View>
<BlueSpacing20 />
{this.state.isLoading ? <BlueLoading /> : <BlueButton testID="Save" onPress={this.save} title={loc.settings.save} />}
<BlueSpacing20 />
<BlueButtonLink title={loc.wallets.import_scan_qr} onPress={this.importScan} />
<BlueSpacing20 />
</BlueCard>
{Platform.select({
ios: <BlueDismissKeyboardInputAccessory />,
android: this.state.isAndroidNumericKeyboardFocused && <BlueDismissKeyboardInputAccessory />,
})}
{Platform.select({
ios: (
<BlueDoneAndDismissKeyboardInputAccessory
onClearTapped={() => this.setState({ host: '' })}
onPasteTapped={text => {
this.setState({ host: text });
Keyboard.dismiss();
}}
/>
),
android: this.state.isAndroidAddressKeyboardVisible && (
<BlueDoneAndDismissKeyboardInputAccessory
onClearTapped={() => {
this.setState({ host: '' });
Keyboard.dismiss();
}}
onPasteTapped={text => {
this.setState({ host: text });
Keyboard.dismiss();
}}
/>
),
})}
</KeyboardAvoidingView>
{serverHistoryItems.length > 0 && !this.state.isLoading && (
<BlueCard>
<View style={styles.serverHistoryTitle}>
<BlueText style={styles.explain}>{loc.settings.electrum_history}</BlueText>
<TouchableOpacity accessibilityRole="button" onPress={() => this.clearHistoryAlert()}>
<BlueText>{loc.settings.electrum_clear}</BlueText>
</TouchableOpacity>
</View>
{serverHistoryItems}
</BlueCard>
)}
{Platform.select({
ios: (
<BlueDoneAndDismissKeyboardInputAccessory
onClearTapped={() => this.setState({ host: '' })}
onPasteTapped={text => {
this.setState({ host: text });
Keyboard.dismiss();
}}
/>
),
android: this.state.isAndroidAddressKeyboardVisible && (
<BlueDoneAndDismissKeyboardInputAccessory
onClearTapped={() => {
this.setState({ host: '' });
Keyboard.dismiss();
}}
onPasteTapped={text => {
this.setState({ host: text });
Keyboard.dismiss();
}}
/>
),
})}
</KeyboardAvoidingView>
{serverHistoryItems.length > 0 && !this.state.isLoading && (
<BlueCard>
<View style={styles.serverHistoryTitle}>
<BlueText style={styles.explain}>{loc.settings.electrum_history}</BlueText>
<TouchableOpacity accessibilityRole="button" onPress={() => this.clearHistoryAlert()}>
<BlueText>{loc.settings.electrum_clear}</BlueText>
</TouchableOpacity>
</View>
{serverHistoryItems}
</BlueCard>
)}
</>
);
};
render() {
return (
<SafeBlueArea>
<ScrollView keyboardShouldPersistTaps="always">
<BlueListItem
Component={Pressable}
title={loc.settings.electrum_offline_mode}
switch={{
onValueChange: this.onElectrumConnectionEnabledSwitchValueChangd,
value: this.state.isOfflineMode,
testID: 'ElectrumConnectionEnabledSwitch',
}}
/>
<BlueCard>
<BlueText>{loc.settings.electrum_offline_description}</BlueText>
</BlueCard>
{!this.state.isOfflineMode && this.renderElectrumSettings()}
</ScrollView>
</SafeBlueArea>
);

View file

@ -29,6 +29,7 @@ import { isDesktop, isMacCatalina, isTablet } from '../../blue_modules/environme
import BlueClipboard from '../../blue_modules/clipboard';
import navigationStyle from '../../components/navigationStyle';
const BlueElectrum = require('../../blue_modules/BlueElectrum');
const scanqrHelper = require('../../helpers/scan-qr');
const A = require('../../blue_modules/analytics');
const fs = require('../../blue_modules/fs');
@ -141,7 +142,8 @@ const WalletsList = () => {
* Forcefully fetches TXs and balance for ALL wallets.
* Triggered manually by user on pull-to-refresh.
*/
const refreshTransactions = (showLoadingIndicator = true, showUpdateStatusIndicator = false) => {
const refreshTransactions = async (showLoadingIndicator = true, showUpdateStatusIndicator = false) => {
if (await BlueElectrum.isDisabled()) return setIsLoading(false);
setIsLoading(showLoadingIndicator);
refreshAllWalletTransactions(showLoadingIndicator, showUpdateStatusIndicator).finally(() => setIsLoading(false));
};

View file

@ -175,6 +175,7 @@ const WalletTransactions = () => {
* Forcefully fetches TXs and balance for wallet
*/
const refreshTransactions = async () => {
if (await BlueElectrum.isDisabled()) return setIsLoading(false);
if (isLoading) return;
setIsLoading(true);
let noErr = true;