feat(gui): Tor toggle (#300)

* re-add tor info box, show switch for toggling tor

* add use_tor to TauriSettings, only initialize tor client when it's true

* add warning log message when not using tor client

* change the label text of the switch, fail to align switch with SettingsBox icons

* move Tor settings to SettingsBox
This commit is contained in:
Raphael 2025-04-22 16:36:09 +02:00 committed by GitHub
parent ffe103cb49
commit 3fa31ba139
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 44 additions and 77 deletions

View file

@ -18,7 +18,7 @@ const useStyles = makeStyles((theme) => ({
export default function HelpPage() {
const classes = useStyles();
const location = useLocation();
const location = useLocation();
useEffect(() => {
if (location.hash) {

View file

@ -44,6 +44,8 @@ import { Theme } from "renderer/components/theme";
import { Add, ArrowUpward, Delete, Edit, HourglassEmpty } from "@material-ui/icons";
import { getNetwork } from "store/config";
import { currencySymbol } from "utils/formatUtils";
import { setTorEnabled } from "store/features/settingsSlice";
const PLACEHOLDER_ELECTRUM_RPC_URL = "ssl://blockstream.info:700";
const PLACEHOLDER_MONERO_NODE_URL = "http://xmr-node.cakewallet.com:18081";
@ -82,6 +84,7 @@ export default function SettingsBox() {
<TableContainer>
<Table>
<TableBody>
<TorSettings />
<ElectrumRpcUrlSetting />
<MoneroNodeUrlSetting />
<FetchFiatPricesSetting />
@ -489,4 +492,23 @@ function NodeTable({
</Table>
</TableContainer>
)
}
}
export function TorSettings() {
const dispatch = useAppDispatch();
const torEnabled = useSettings((settings) => settings.enableTor)
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => dispatch(setTorEnabled(event.target.checked));
const status = (state: boolean) => state === true ? "enabled" : "disabled";
return (
<TableRow>
<TableCell>
<SettingLabel label="Use Tor" tooltip="Tor (The Onion Router) is a decentralized network allowing for anonymous browsing. If enabled, the app will use its internal Tor client to hide your IP address from the maker. Requires a restart to take effect." />
</TableCell>
<TableCell>
<Switch checked={torEnabled} onChange={handleChange} color="primary" />
</TableCell>
</TableRow>
)
}

View file

@ -1,71 +0,0 @@
import { Box, makeStyles, Typography } from "@material-ui/core";
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
import StopIcon from "@material-ui/icons/Stop";
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
import { useAppSelector } from "store/hooks";
import InfoBox from "../../modal/swap/InfoBox";
import CliLogsBox from "../../other/RenderedCliLog";
const useStyles = makeStyles((theme) => ({
actionsOuter: {
display: "flex",
gap: theme.spacing(1),
},
}));
export default function TorInfoBox() {
const isTorRunning = useAppSelector((state) => state.tor.processRunning);
const torStdOut = useAppSelector((s) => s.tor.stdOut);
const classes = useStyles();
return (
<InfoBox
title="Tor (The Onion Router)"
mainContent={
<Box
style={{
width: "100%",
display: "flex",
flexDirection: "column",
gap: "8px",
}}
>
<Typography variant="subtitle2">
Tor is a network that allows you to anonymously connect to the
internet. It is a free and open network that is operated by
volunteers. You can start and stop Tor by clicking the buttons
below. If Tor is running, all traffic will be routed through it and
the maker will not be able to see your IP address.
</Typography>
<CliLogsBox label="Tor Daemon Logs" logs={torStdOut.split("\n")} />
</Box>
}
additionalContent={
<Box className={classes.actionsOuter}>
<PromiseInvokeButton
variant="contained"
disabled={isTorRunning}
endIcon={<PlayArrowIcon />}
onInvoke={() => {
throw new Error("Not implemented");
}}
>
Start Tor
</PromiseInvokeButton>
<PromiseInvokeButton
variant="contained"
disabled={!isTorRunning}
endIcon={<StopIcon />}
onInvoke={() => {
throw new Error("Not implemented");
}}
>
Stop Tor
</PromiseInvokeButton>
</Box>
}
icon={null}
loading={false}
/>
);
}

View file

@ -175,6 +175,7 @@ export async function listSellersAtRendezvousPoint(
export async function initializeContext() {
const network = getNetwork();
const testnet = isTestnet();
const useTor = store.getState().settings.enableTor;
// This looks convoluted but it does the following:
// - Fetch the status of all nodes for each blockchain in parallel
@ -208,6 +209,7 @@ export async function initializeContext() {
const tauriSettings: TauriSettings = {
electrum_rpc_url: bitcoinNode,
monero_node_url: moneroNode,
use_tor: useTor
};
logger.info("Initializing context with settings", tauriSettings);

View file

@ -9,6 +9,8 @@ export interface SettingsState {
/// Whether to fetch fiat prices from the internet
fetchFiatPrices: boolean;
fiatCurrency: FiatCurrency;
/// Whether to enable Tor for p2p connections
enableTor: boolean
}
export enum FiatCurrency {
@ -98,6 +100,7 @@ const initialState: SettingsState = {
theme: Theme.Darker,
fetchFiatPrices: true,
fiatCurrency: FiatCurrency.Usd,
enableTor: true
};
const alertsSlice = createSlice({
@ -134,6 +137,9 @@ const alertsSlice = createSlice({
},
resetSettings(_) {
return initialState;
},
setTorEnabled(slice, action: PayloadAction<boolean>) {
slice.enableTor = action.payload;
}
},
});
@ -146,6 +152,7 @@ export const {
resetSettings,
setFetchFiatPrices,
setFiatCurrency,
setTorEnabled,
} = alertsSlice.actions;
export default alertsSlice.reducer;

View file

@ -18,7 +18,6 @@ use swap::cli::{
command::{Bitcoin, Monero},
};
use tauri::{async_runtime::RwLock, Manager, RunEvent};
use uuid::Uuid;
/// Trait to convert Result<T, E> to Result<T, String>
/// Tauri commands require the error type to be a string
@ -327,7 +326,7 @@ async fn initialize_context(
})
.with_json(false)
.with_debug(true)
.with_tor(true)
.with_tor(settings.use_tor)
.with_tauri(tauri_handle.clone())
.build()
.await;

View file

@ -394,6 +394,12 @@ impl ContextBuilder {
};
let initialize_tor_client = async {
// Don't init a tor client unless we should use it.
if !self.tor {
tracing::warn!("Internal Tor client not enabled, skipping initialization");
return Ok(None);
}
self.tauri_handle.emit_context_init_progress_event(
TauriContextStatusEvent::Initializing(vec![
TauriPartialInitProgress::EstablishingTorCircuits(

View file

@ -133,7 +133,7 @@ impl TauriHandle {
let request_id = Uuid::new_v4();
let now_secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.expect("it is later than the begin of the unix epoch")
.as_secs();
let expiration_ts = now_secs + timeout_secs;
@ -501,4 +501,6 @@ pub struct TauriSettings {
/// The URL of the Electrum RPC server e.g `ssl://bitcoin.com:50001`
#[typeshare(serialized_as = "string")]
pub electrum_rpc_url: Option<Url>,
/// Whether to initialize and use a tor client.
pub use_tor: bool,
}

View file

@ -164,7 +164,7 @@ async fn next_state(
let btc_lock_amount = bitcoin::Amount::from_sat(
signed_tx
.output
.get(0)
.first()
.context("Failed to get lock amount")?
.value,
);