import AsyncStorage from '@react-native-async-storage/async-storage'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { Alert, Keyboard, Platform, Pressable, ScrollView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, View, } from 'react-native'; import DefaultPreference from 'react-native-default-preference'; import * as BlueElectrum from '../../blue_modules/BlueElectrum'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { BlueButtonLink, BlueCard, BlueLoading, BlueSpacing20, BlueText } from '../../BlueComponents'; import DeeplinkSchemaMatch from '../../class/deeplink-schema-match'; import presentAlert, { AlertType } from '../../components/Alert'; import Button from '../../components/Button'; import ListItem from '../../components/ListItem'; import { BlueCurrentTheme } from '../../components/themes'; import { scanQrHelper } from '../../helpers/scan-qr'; import loc from '../../loc'; import { StorageContext } from '../../components/Context/StorageProvider'; import { DoneAndDismissKeyboardInputAccessory, DoneAndDismissKeyboardInputAccessoryViewID, } from '../../components/DoneAndDismissKeyboardInputAccessory'; import { DismissKeyboardInputAccessory, DismissKeyboardInputAccessoryViewID } from '../../components/DismissKeyboardInputAccessory'; export default class ElectrumSettings extends Component { static contextType = StorageContext; constructor(props) { super(props); const server = props?.route?.params?.server; this.state = { isLoading: true, isOfflineMode: false, serverHistory: [], config: {}, server, sslPort: '', port: '', }; } componentWillUnmount() { clearInterval(this.state.inverval); } async componentDidMount() { const host = await AsyncStorage.getItem(BlueElectrum.ELECTRUM_HOST); 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, host, port, sslPort, serverHistory, isOfflineMode, isAndroidNumericKeyboardFocused: false, isAndroidAddressKeyboardVisible: false, }); const inverval = setInterval(async () => { this.setState({ config: await BlueElectrum.getConfig(), }); }, 500); this.setState({ config: await BlueElectrum.getConfig(), inverval, }); if (this.state.server) { triggerHapticFeedback(HapticFeedbackTypes.ImpactHeavy); Alert.alert( loc.formatString(loc.settings.set_electrum_server_as_default, { server: this.state.server }), '', [ { text: loc._.ok, onPress: () => { this.onBarScanned(this.state.server); }, style: 'default', }, { text: loc._.cancel, onPress: () => {}, style: 'cancel' }, ], { cancelable: false }, ); } } checkServer = async () => { this.setState({ isLoading: true }, async () => { const features = await BlueElectrum.serverFeatures(); triggerHapticFeedback(HapticFeedbackTypes.NotificationWarning); presentAlert({ message: JSON.stringify(features, null, 2) }); this.setState({ isLoading: false }); }); }; selectServer = async server => { this.setState({ host: server.host, port: server.port, sslPort: server.sslPort }, () => { this.save(); }); }; clearHistoryAlert() { 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: () => this.clearHistory() }, ]); } clearHistory = async () => { this.setState({ isLoading: true }, async () => { await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify([])); this.setState({ serverHistory: [], isLoading: false, }); }); }; resetToDefault = async () => { this.setState({ port: '', host: '', sslPort: '' }, () => { this.save(); }); }; serverExists = server => { const { serverHistory } = this.state; return serverHistory.some(s => { return `${s.host}${s.port}${s.sslPort}` === `${server.host}${server.port}${server.sslPort}`; }); }; save = () => { const host = this.state.host ? this.state.host : ''; const port = this.state.port ? this.state.port : ''; const sslPort = this.state.sslPort ? this.state.sslPort : ''; const serverHistory = this.state.serverHistory || []; this.setState({ isLoading: true }, async () => { try { if (!host && !port && !sslPort) { await AsyncStorage.setItem(BlueElectrum.ELECTRUM_HOST, ''); await AsyncStorage.setItem(BlueElectrum.ELECTRUM_TCP_PORT, ''); await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SSL_PORT, ''); try { await DefaultPreference.setName('group.io.bluewallet.bluewallet'); await DefaultPreference.clear(BlueElectrum.ELECTRUM_HOST); await DefaultPreference.clear(BlueElectrum.ELECTRUM_SSL_PORT); await DefaultPreference.clear(BlueElectrum.ELECTRUM_TCP_PORT); } catch (e) { // Must be running on Android console.log(e); } triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); presentAlert({ message: loc.settings.electrum_saved }); } else if (!(await BlueElectrum.testConnection(host, port, sslPort))) { triggerHapticFeedback(HapticFeedbackTypes.NotificationError); presentAlert({ message: loc.settings.electrum_error_connect }); } else { await AsyncStorage.setItem(BlueElectrum.ELECTRUM_HOST, host); await AsyncStorage.setItem(BlueElectrum.ELECTRUM_TCP_PORT, port); await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SSL_PORT, sslPort); if (!this.serverExists({ host, port, sslPort })) { serverHistory.push({ host, port, sslPort, }); await AsyncStorage.setItem(BlueElectrum.ELECTRUM_SERVER_HISTORY, JSON.stringify(serverHistory)); } try { await DefaultPreference.setName('group.io.bluewallet.bluewallet'); await DefaultPreference.set(BlueElectrum.ELECTRUM_HOST, host); await DefaultPreference.set(BlueElectrum.ELECTRUM_TCP_PORT, port); await DefaultPreference.set(BlueElectrum.ELECTRUM_SSL_PORT, sslPort); } catch (e) { // Must be running on Android console.log(e); } triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess); presentAlert({ message: loc.settings.electrum_saved }); } } catch (error) { triggerHapticFeedback(HapticFeedbackTypes.NotificationError); presentAlert({ message: error, type: AlertType.Toast }); } this.setState({ isLoading: false }); }); }; onBarScanned = value => { if (DeeplinkSchemaMatch.getServerFromSetElectrumServerAction(value)) { // in case user scans a QR with a deeplink like `bluewallet:setelectrumserver?server=electrum1.bluewallet.io%3A443%3As` value = DeeplinkSchemaMatch.getServerFromSetElectrumServerAction(value); } const [host, port, type] = value.split(':'); this.setState({ host, sslPort: '', port: '' }, () => { type === 's' ? this.setState({ sslPort: port }) : this.setState({ port }); }); }; importScan = async () => { const scanned = await scanQrHelper('ElectrumSettings', true); this.onBarScanned(scanned); }; useSSLPortToggled = value => { switch (value) { case true: this.setState(prevState => { return { port: '', sslPort: prevState.port }; }); break; case false: this.setState(prevState => { return { port: prevState.sslPort, sslPort: '' }; }); break; } }; onElectrumConnectionEnabledSwitchValueChanged = async value => { if (value === true) { await BlueElectrum.setDisabled(true); this.context.setIsElectrumDisabled(true); BlueElectrum.forceDisconnect(); } else { await BlueElectrum.setDisabled(false); this.context.setIsElectrumDisabled(false); BlueElectrum.connectMain(); } this.setState({ isOfflineMode: value }); }; renderElectrumSettings = () => { const serverHistoryItems = this.state.serverHistory.map((server, i) => { return ( {`${server.host}:${server.port || server.sslPort}`} this.selectServer(server)}> {loc.settings.electrum_select} ); }); return ( <> {loc.settings.electrum_status} {this.state.config.connected === 1 ? loc.settings.electrum_connected : loc.settings.electrum_connected_not} {this.state.config.host}:{this.state.config.port} { const host = text.trim(); this.setState({ host }, () => { if (host.endsWith('.onion')) { this.useSSLPortToggled(false); } }); }} numberOfLines={1} style={styles.inputText} editable={!this.state.isLoading} placeholderTextColor="#81868e" autoCorrect={false} autoCapitalize="none" underlineColorAndroid="transparent" inputAccessoryViewID={DoneAndDismissKeyboardInputAccessoryViewID} testID="HostInput" onFocus={() => this.setState({ isAndroidAddressKeyboardVisible: true })} onBlur={() => this.setState({ isAndroidAddressKeyboardVisible: false })} /> 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={DismissKeyboardInputAccessoryViewID} testID="PortInput" onFocus={() => this.setState({ isAndroidNumericKeyboardFocused: true })} onBlur={() => this.setState({ isAndroidNumericKeyboardFocused: false })} /> {loc.settings.use_ssl} 0} onValueChange={this.useSSLPortToggled} disabled={this.state.host?.endsWith('.onion') ?? false} /> {loc.settings.electrum_settings_explain} this.resetToDefault()}> {loc.settings.electrum_reset} {this.state.isLoading ? :