Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions nym-vpn-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions nym-vpn-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
},
"dependencies": {
"@base-ui-components/react": "^1.0.0-beta.3",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@headlessui/react": "^2.1.2",
"@lottiefiles/dotlottie-react": "^0.17.0",
"@radix-ui/react-accordion": "^1.2.3",
Expand Down
28 changes: 28 additions & 0 deletions nym-vpn-app/src-tauri/src/commands/tunnel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
tunnel::{ConnectingState, TunnelState},
},
};
use std::net::IpAddr;
use tauri::{Manager, State};
use tracing::{debug, info, instrument, warn};

Expand Down Expand Up @@ -153,3 +154,30 @@ pub async fn set_allow_lan(vpnd: State<'_, VpndClient>, enabled: bool) -> Result
vpnd.set_allow_lan(enabled).await?;
Ok(())
}

#[instrument(skip(vpnd))]
#[tauri::command]
pub async fn get_default_dns(vpnd: State<'_, VpndClient>) -> Result<Vec<IpAddr>, BackendError> {
let dns = vpnd.get_default_dns().await?;
Ok(dns)
}

#[instrument(skip(vpnd))]
#[tauri::command]
pub async fn set_custom_dns_enabled(
vpnd: State<'_, VpndClient>,
enabled: bool,
) -> Result<(), BackendError> {
vpnd.set_custom_dns_enabled(enabled).await?;
Ok(())
}

#[instrument(skip(vpnd))]
#[tauri::command]
pub async fn set_custom_dns(
vpnd: State<'_, VpndClient>,
dns: Vec<IpAddr>,
) -> Result<(), BackendError> {
vpnd.set_custom_dns(dns).await?;
Ok(())
}
3 changes: 3 additions & 0 deletions nym-vpn-app/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@ async fn main() -> Result<()> {
tunnel::disconnect,
tunnel::set_node,
tunnel::set_quic,
tunnel::get_default_dns,
tunnel::set_custom_dns,
tunnel::set_custom_dns_enabled,
tunnel::set_no_ipv6,
tunnel::set_allow_lan,
cmd_db::db_set,
Expand Down
41 changes: 41 additions & 0 deletions nym-vpn-app/src-tauri/src/vpnd/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use nym_vpn_proto::rpc_client::RpcClient;
use once_cell::sync::Lazy;
use std::{
env::consts::{ARCH, OS},
net::IpAddr,
path::PathBuf,
sync::Mutex,
};
Expand Down Expand Up @@ -772,6 +773,46 @@ impl VpndClient {
Ok(())
}

#[instrument(skip_all)]
pub async fn get_default_dns(&self) -> Result<Vec<IpAddr>, VpndError> {
let mut vpnd = self.vpnd().await?;

let dns = vpnd.get_default_dns().await.map_err(|e| {
error!("failed to get default DNS: {}", e);
VpndError::RpcClient(e)
})?;

Ok(dns)
}

#[instrument(skip_all)]
pub async fn set_custom_dns_enabled(&self, enabled: bool) -> Result<(), VpndError> {
let mut vpnd = self.vpnd().await?;

vpnd.set_enable_custom_dns(enabled)
.await
.map_err(VpndError::RpcClient)
.inspect_err(|e| {
error!("failed to set custom DNS enabled: {}", e);
})?;

Ok(())
}

#[instrument(skip_all)]
pub async fn set_custom_dns(&self, dns: Vec<IpAddr>) -> Result<(), VpndError> {
let mut vpnd = self.vpnd().await?;

vpnd.set_custom_dns(dns)
.await
.map_err(VpndError::RpcClient)
.inspect_err(|e| {
error!("failed to set custom DNS: {}", e);
})?;

Ok(())
}

pub fn reset_log_flag() {
let mut logged = VPND_DOWN_LOGGED.lock().unwrap();
*logged = false;
Expand Down
2 changes: 2 additions & 0 deletions nym-vpn-app/src-tauri/src/vpnd/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub struct VpndConfig {
pub entry_node: Node,
pub exit_node: Node,
pub custom_dns: Option<Vec<IpAddr>>,
pub enable_custom_dns: bool,
pub allow_lan: bool,
pub disable_ipv6: bool,
pub vpn_mode: VpnMode,
Expand All @@ -38,6 +39,7 @@ impl VpndConfig {
entry_node: config.entry_point.into(),
exit_node: config.exit_point.try_into()?,
custom_dns: Some(config.custom_dns),
enable_custom_dns: config.enable_custom_dns,
allow_lan: config.allow_lan,
disable_ipv6: config.disable_ipv6,
vpn_mode,
Expand Down
9 changes: 6 additions & 3 deletions nym-vpn-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
MainStateProvider,
NodeListStateProvider,
Socks5Provider,
TopBarProvider,
} from './contexts';
import { useLang } from './hooks';
import { LngTag } from './i18n';
Expand Down Expand Up @@ -74,9 +75,11 @@ function App({ init }: { init: InitState }) {
<Socks5Provider>
<ThemeSetter>
<DialogProvider>
<Suspense fallback={<RouteLoading />}>
<RouterProvider router={router} />
</Suspense>
<TopBarProvider>
<Suspense fallback={<RouteLoading />}>
<RouterProvider router={router} />
</Suspense>
</TopBarProvider>
</DialogProvider>
</ThemeSetter>
</Socks5Provider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useCallback, useEffect, useState } from 'react';
import { useNavigate } from 'react-router';
import { DialogTitle } from '@headlessui/react';
import { useTranslation } from 'react-i18next';
import { Button, Dialog, MsIcon } from '../../ui';
import { useTopBar } from '../../contexts';

type ConfirmationDialogProps = {
hasUnsavedChanges: boolean;
onConfirm: () => Promise<void>;
onCancel: () => void;
};

export function ConfirmationDialog({
hasUnsavedChanges,
onConfirm,
onCancel,
}: ConfirmationDialogProps) {
const { t } = useTranslation('settings');
const { setCustomLeftNavHandler } = useTopBar();
const navigate = useNavigate();

const [isConfirmationDialogOpen, setIsConfirmationDialogOpen] =
useState(false);
const [isApplying, setIsApplying] = useState(false);

const handleBackNavigation = useCallback(() => {
if (hasUnsavedChanges) {
setIsConfirmationDialogOpen(true);
} else {
navigate(-1);
}
}, [hasUnsavedChanges, navigate, setIsConfirmationDialogOpen]);

useEffect(() => {
setCustomLeftNavHandler(handleBackNavigation);
return () => {
setCustomLeftNavHandler(null);
};
}, [handleBackNavigation, setCustomLeftNavHandler]);

const handleConfirm = async () => {
setIsApplying(true);
try {
await onConfirm();
} finally {
setIsApplying(false);
}
};

const handleCancel = () => {
setIsConfirmationDialogOpen(false);
onCancel();
};

return (
<Dialog
open={isConfirmationDialogOpen}
onClose={() => setIsConfirmationDialogOpen(false)}
>
<div className="flex flex-col items-center gap-4 w-11/12">
<MsIcon icon="settings" className="text-baltic-sea dark:text-white" />

<DialogTitle
as="h3"
className="text-xl text-baltic-sea dark:text-white text-center w-full truncate"
>
{t('confirmation-dialog.title')}
</DialogTitle>
</div>
<p className="mt-4 text-center text-iron dark:text-bombay max-w-80 whitespace-pre-line">
{t('confirmation-dialog.description')}
</p>
<div className="mt-6 flex flex-col flex-nowrap justify-center w-full gap-2">
<Button
onClick={handleConfirm}
className="min-w-32"
color="malachite"
spinner={isApplying}
disabled={isApplying}
>
{t('confirmation-dialog.save')}
</Button>
<Button
onClick={handleCancel}
className="min-w-32 text-aphrodisiac!"
color="gray"
outline
>
{t('confirmation-dialog.cancel')}
</Button>
</div>
</Dialog>
);
}
1 change: 1 addition & 0 deletions nym-vpn-app/src/components/confirmation-dialog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ConfirmationDialog } from './ConfirmationDialog';
1 change: 1 addition & 0 deletions nym-vpn-app/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './confirmation-dialog';
3 changes: 3 additions & 0 deletions nym-vpn-app/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,6 @@ export const ResidentialIpServersUrl =
'https://support.nym.com/hc/en-us/articles/35279486714641-Why-can-t-I-access-streaming-services-while-using-NymVPN';
export const QuicSupportArticleUrl =
'https://support.nym.com/hc/en-us/articles/39648047741457-QUIC-transport-mode';
export const LocationAccuracyLink =
'https://support.nym.com/hc/en-us/articles/26448676449297-How-is-server-location-determined-by-NymVPN';
export const CustomDnsHelpUrl = 'https://nym.com/features/custom-dns';
1 change: 1 addition & 0 deletions nym-vpn-app/src/contexts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './node-list';
export * from './node-list-state';
export * from './gateways';
export * from './socks5';
export * from './topbar';
2 changes: 2 additions & 0 deletions nym-vpn-app/src/contexts/main/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ function MainStateProvider({ children, init }: Props) {
quic: init.quic,
ipv6Support: !init.noIpv6,
allowLan: init.allowLan,
customDnsEnabled: init.customDnsEnabled,
customDns: init.customDns,
});

const { push } = useInAppNotify();
Expand Down
24 changes: 22 additions & 2 deletions nym-vpn-app/src/contexts/main/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ export type StateAction =
| { type: 'set-backend-flags'; flags: FeatureFlags }
| { type: 'set-quic'; enabled: boolean }
| { type: 'set-domain-fronting'; enabled: boolean }
| { type: 'set-streaming-optimized-label-seen'; seen: boolean };
| { type: 'set-streaming-optimized-label-seen'; seen: boolean }
| { type: 'set-custom-dns-enabled'; enabled: boolean }
| { type: 'set-custom-dns'; dns: string[] }
| { type: 'set-default-dns'; dns: string[] };

export const initialState: AppState = {
initialized: false,
Expand Down Expand Up @@ -115,6 +118,9 @@ export const initialState: AppState = {
zknymCredential: false,
},
streamingOptimizedLabelSeen: false,
customDnsEnabled: false,
customDns: [],
defaultDns: [],
};

export function reducer(state: AppState, action: StateAction): AppState {
Expand Down Expand Up @@ -380,7 +386,21 @@ export function reducer(state: AppState, action: StateAction): AppState {
...state,
domainFronting: action.enabled,
};

case 'set-custom-dns-enabled':
return {
...state,
customDnsEnabled: action.enabled,
};
case 'set-custom-dns':
return {
...state,
customDns: action.dns,
};
case 'set-default-dns':
return {
...state,
defaultDns: action.dns,
};
case 'reset':
return initialState;
}
Expand Down
Loading