Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions nym-vpn-app/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Fix a race condition at app startup that could lead to
selected nodes to be reset to default
- Fix incorrect JS kv store API types

## [1.17.1] - 2025-10-20

Expand Down
1,511 changes: 738 additions & 773 deletions nym-vpn-app/package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion nym-vpn-app/src-tauri/Cargo.lock

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

2 changes: 1 addition & 1 deletion nym-vpn-app/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nym-vpn-app"
version = "1.18.0-fig-rc2"
version = "1.18.0-fig-rc3"
description = "NymVPN desktop client"
authors = [
"Nym Technologies SA",
Expand Down
1 change: 1 addition & 0 deletions nym-vpn-app/src/contexts/node-list-state/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as NodeListStateProvider } from './provider';
export { useNodeListState } from './context';
export * from './types';
10 changes: 5 additions & 5 deletions nym-vpn-app/src/i18n/en/home.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
"select-node-title": "Connect to",
"privacy-mode": {
"title": "Anonymous (mixnet)",
"desc": "Best for payments, emails, messages"
"desc": "Maximum privacy for sensitive tasks"
},
"fast-mode": {
"title": "Fast (WireGuard*)",
"desc": "Best for browsing, streaming, sharing"
"title": "Fast (WireGuard)",
"desc": "Browsing, streaming, downloads"
},
"last-node-select": {
"label": "Last hop",
Expand All @@ -38,8 +38,8 @@
},
"modes-dialog": {
"title": "Mode selection",
"privacy-description": "Enjoy maximum-level privacy by routing your connection through 5 servers via mixnet. Perfect for securely handling payments, emails, and messages.",
"fast-description": "Experience maximum speed with 2-server connectivity. This mode leverages AmneziaWG, a censorship-resistant version of WireGuard, ideal for fast browsing, streaming, and file sharing.",
"privacy-description": "Maximum privacy through 5-hop routing (up to 1-5 Mbps). Dummy traffic and timing obfuscation for enhanced anonymity. Perfect for highly sensitive activities like payments, emails, and messages.",
"fast-description": "High-speed browsing (250+ Mbps) with 2-hop routing. Uses AmneziaWG, a censorship-resistant version of WireGuard, ideal for streaming, fast browsing, and downloads.",
"link": "Continue reading"
},
"update-dialog": {
Expand Down
2 changes: 1 addition & 1 deletion nym-vpn-app/src/i18n/fr/node-location.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"offline": "Hors ligne"
},
"notes": {
"anti-censorship": "Activez le « protocole QUIC» dans les paramètres de lutte contre la censure pour utiliser cette fonctionnalité",
"anti-censorship": "<0>Activez le « protocole QUIC»</0> dans les paramètres de lutte contre la censure pour utiliser cette fonctionnalité",
"performance_with_time": "Score de performance calculé à partir de la charge du serveur et du temps de disponibilité. Temps de disponibilité des dernières 24 heures. Dernière mise à jour {{relativeTime}}.",
"performance": "Score de performance calculé à partir de la charge du serveur et de la durée de disponibilité comme moyenne des dernières 24 heures."
},
Expand Down
24 changes: 16 additions & 8 deletions nym-vpn-app/src/kvStore/kv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import { DbKey } from '../types';
* @param k - Key
* @returns The value for that key if any
*/
export async function kvGet<V>(k: DbKey): Promise<V | undefined> {
export async function kvGet<V>(k: DbKey): Promise<V | null> {
try {
return await invoke<V>('db_get', { key: k });
} catch {}
} catch {
return null;
}
}

/**
Expand All @@ -20,10 +22,12 @@ export async function kvGet<V>(k: DbKey): Promise<V | undefined> {
* @param v - Value
* @returns The last value if it was set
*/
export async function kvSet<V>(k: DbKey, v: V): Promise<V | undefined> {
export async function kvSet<V>(k: DbKey, v: V): Promise<V | null> {
try {
return await invoke<V>('db_set', { key: k, value: v });
} catch {}
} catch {
return null;
}
}

/**
Expand All @@ -32,10 +36,12 @@ export async function kvSet<V>(k: DbKey, v: V): Promise<V | undefined> {
* @param k - Key
* @returns The previous value if any
*/
export async function kvDel<V>(k: DbKey): Promise<V | undefined> {
export async function kvDel<V>(k: DbKey): Promise<V | null> {
try {
return await invoke<V>('db_del', { key: k });
} catch {}
} catch {
return null;
}
}

/**
Expand All @@ -45,8 +51,10 @@ export async function kvDel<V>(k: DbKey): Promise<V | undefined> {
*
* @returns The number of bytes flushed during this call
*/
export async function kvFlush(): Promise<number | undefined> {
export async function kvFlush(): Promise<number | null> {
try {
return await invoke<number>('db_flush');
} catch {}
} catch {
return null;
}
}
2 changes: 1 addition & 1 deletion nym-vpn-app/src/screens/home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function Home() {
dispatch({ type: 'reset-error' });
dispatch({ type: 'connect' });
let savedQuic = await kvGet<boolean>('quic-enabled');
if (savedQuic === undefined) {
if (savedQuic === null) {
savedQuic = defaultQuic;
}
invoke('connect', {
Expand Down
19 changes: 16 additions & 3 deletions nym-vpn-app/src/screens/node/Node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,18 @@ function Node({ node }: { node: NodeHop }) {

const { isOpen, close } = useDialog();
const { loading, error } = useNodeList();
const { setFocused, reset: resetSaved, addToExpanded } = useNodeListState();
const {
setFocused,
exit: exitNodeList,
entry: entryNodeList,
reset: resetSaved,
addToExpanded,
} = useNodeListState();
const expanded =
node === 'entry' ? entryNodeList.expanded : exitNodeList.expanded;
const focused =
node === 'entry' ? entryNodeList.focused : exitNodeList.focused;

const { tE } = useI18nError();

const quicFilter =
Expand All @@ -37,7 +48,7 @@ function Node({ node }: { node: NodeHop }) {

const { filter, nodes, gateways } = useFilterList();
const deferredNodes = useDeferredValue(nodes);
const deferredGws = useDeferredValue(gateways);
const deferredGateways = useDeferredValue(gateways);

const handleSelect = async (selected: SelectedUiNode) => {
const selectedNode = uiNodeToSelectedNode(selected);
Expand Down Expand Up @@ -151,11 +162,13 @@ function Node({ node }: { node: NodeHop }) {
{!loading && (
<NodeList
nodes={deferredNodes}
gateways={deferredGws}
gateways={deferredGateways}
onSelect={handleSelect}
onNodeDetails={handleNodeDetails}
hop={node}
vpnMode={vpnMode}
expanded={expanded}
focused={focused}
/>
)}
</PageAnim>
Expand Down
17 changes: 9 additions & 8 deletions nym-vpn-app/src/screens/node/list/NodeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Accordion } from '@base-ui-components/react';
import { useTranslation } from 'react-i18next';
import { motion } from 'motion/react';
import {
Focused,
SelectedKind,
SelectedUiNode,
UiCountry,
Expand All @@ -24,6 +25,8 @@ export type NodeListProps = {
onNodeDetails: (node: UiGateway) => void;
hop: NodeHop;
vpnMode: VpnMode;
expanded: string[];
focused: Focused | null;
};

const NodeList = memo(function NodeList({
Expand All @@ -33,17 +36,13 @@ const NodeList = memo(function NodeList({
hop,
vpnMode,
onNodeDetails,
expanded,
focused,
}: NodeListProps) {
const { backendFlags, quic } = useMainState();
const {
exit: exitState,
entry: entryState,
setExpanded,
} = useNodeListState();
const { setExpanded } = useNodeListState();
const { t } = useTranslation('nodeLocation');

const expanded = hop === 'entry' ? entryState.expanded : exitState.expanded;
const focused = hop === 'entry' ? entryState.focused : exitState.focused;
const countriesRef = useRef<Map<string, HTMLDivElement>>(null);
const regionsRef = useRef<Map<string, HTMLDivElement>>(null);
const gatewaysRef = useRef<Map<string, HTMLDivElement>>(null);
Expand Down Expand Up @@ -154,7 +153,7 @@ const NodeList = memo(function NodeList({
data-testid="node-list-accordion"
value={expanded}
onValueChange={onValueChange}
openMultiple
multiple
>
{nodes.map(({ i18n, isSelected, gateways, country, regions }) => (
<Accordion.Item
Expand Down Expand Up @@ -279,6 +278,8 @@ function arePropsEqual(
if (oldProps.vpnMode !== newProps.vpnMode) return false;
if (oldProps.gateways.length !== newProps.gateways.length) return false;
if (oldProps.nodes.length !== newProps.nodes.length) return false;
if (!dequal(oldProps.expanded, newProps.expanded)) return false;
if (!dequal(oldProps.focused, newProps.focused)) return false;
if (!dequal(oldProps.gateways, newProps.gateways)) return false;
if (!dequal(oldProps.nodes, newProps.nodes)) return false;
return true;
Expand Down
29 changes: 16 additions & 13 deletions nym-vpn-app/src/state/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { updateAccountState, updateTunnel } from './update';
import { TauriReq, fireRequests } from './helper';

const defaultNetStats = window._APP.defaultNetstats;
const defaultQuic = window._APP.defaultQuic;
const defaultDomFront = window._APP.defaultDomainFronting;

Expand Down Expand Up @@ -107,9 +108,7 @@ export async function initFirstBatch(
},
};

const getDesktopNotificationsRq: TauriReq<
() => Promise<boolean | undefined>
> = {
const getDesktopNotificationsRq: TauriReq<() => Promise<boolean | null>> = {
name: 'getDesktopNotificationsRq',
request: () => kvGet<boolean>('desktop-notifications'),
onFulfilled: (enabled) => {
Expand All @@ -120,7 +119,7 @@ export async function initFirstBatch(
},
};

const getRootFontSizeRq: TauriReq<() => Promise<number | undefined>> = {
const getRootFontSizeRq: TauriReq<() => Promise<number | null>> = {
name: 'getRootFontSize',
request: () => kvGet<number>('ui-root-font-size'),
onFulfilled: (size) => {
Expand Down Expand Up @@ -165,7 +164,7 @@ export async function initFirstBatch(
},
};

const getIpv6SupportRq: TauriReq<() => Promise<boolean | undefined>> = {
const getIpv6SupportRq: TauriReq<() => Promise<boolean | null>> = {
name: 'getIpv6Support',
request: () => kvGet<boolean>('disable-ipv6'),
onFulfilled: (disabled) => {
Expand All @@ -175,32 +174,36 @@ export async function initFirstBatch(
},
};

const getQuicRq: TauriReq<() => Promise<boolean | undefined>> = {
const getQuicRq: TauriReq<() => Promise<boolean | null>> = {
name: 'getQuicRq',
request: () => kvGet<boolean>('quic-enabled'),
onFulfilled: (enabled) => {
dispatch({ type: 'set-quic', enabled: enabled || defaultQuic });
dispatch({
type: 'set-quic',
enabled: enabled !== null ? enabled : defaultQuic,
});
},
};

const getDomainFrontingRq: TauriReq<() => Promise<boolean | undefined>> = {
const getDomainFrontingRq: TauriReq<() => Promise<boolean | null>> = {
name: 'getDomainFrontingRq',
request: () => kvGet<boolean>('domain-fronting-enabled'),
onFulfilled: (enabled) => {
dispatch({
type: 'set-domain-fronting',
enabled: enabled || defaultDomFront,
enabled: enabled !== null ? enabled : defaultDomFront,
});
},
};

const getNetworkStatsRq: TauriReq<() => Promise<boolean | undefined>> = {
const getNetworkStatsRq: TauriReq<() => Promise<boolean | null>> = {
name: 'getNetworkStats',
request: () => kvGet<boolean>('network-stats-enabled'),
onFulfilled: (enabled) => {
if (enabled !== undefined) {
dispatch({ type: 'set-network-stats', enabled });
}
dispatch({
type: 'set-network-stats',
enabled: enabled !== null ? enabled : defaultNetStats,
});
},
};

Expand Down