diff --git a/nym-wallet/.storybook/mocks/tauri/shell.js b/nym-wallet/.storybook/mocks/tauri/shell.js new file mode 100644 index 00000000000..7bb835d708a --- /dev/null +++ b/nym-wallet/.storybook/mocks/tauri/shell.js @@ -0,0 +1,8 @@ +/** + * This is a mock for Tauri's API package (@tauri-apps/api/shell), to prevent stories from being excluded, because they either use + * or import dependencies that use Tauri. + */ + +module.exports = { + open: () => undefined, +}; diff --git a/nym-wallet/src/components/Bonding/NodeStats.tsx b/nym-wallet/src/components/Bonding/NodeStats.tsx index eb22b507278..088b9f0c847 100644 --- a/nym-wallet/src/components/Bonding/NodeStats.tsx +++ b/nym-wallet/src/components/Bonding/NodeStats.tsx @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { Stack, Typography, Box, useTheme, Grid, LinearProgress, LinearProgressProps, Button } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; -import { TBondedMixnode } from 'src/context'; -import { Cell, Pie, PieChart, Legend, ResponsiveContainer } from 'recharts'; +import { open as openLink } from '@tauri-apps/api/shell'; import { SelectionChance } from '@nymproject/types'; +import { AppContext, TBondedMixnode } from 'src/context'; +import { validatorApiFromNetwork } from 'src/constants'; +import { Cell, Pie, PieChart, Legend, ResponsiveContainer } from 'recharts'; import { NymCard } from '../NymCard'; import { InfoTooltip } from '../InfoToolTip'; @@ -50,7 +51,7 @@ const StatRow = ({ export const NodeStats = ({ mixnode }: { mixnode: TBondedMixnode }) => { const { activeSetProbability, routingScore } = mixnode; const theme = useTheme(); - const navigate = useNavigate(); + const { network } = useContext(AppContext); // clamp routing score to [0-100] const score = Math.min(Math.max(routingScore, 0), 100); @@ -74,8 +75,22 @@ export const NodeStats = ({ mixnode }: { mixnode: TBondedMixnode }) => { } }; - const handleGoToTestNode = () => { - navigate('/bonding/node-settings', { state: 'test-node' }); + const handleGoToTestNode = async () => { + // TODO Change URL to the deployed node-tester (once deployed) + const url = new window.URL('http://localhost:1234'); + + if (network) { + const validatorUrl = validatorApiFromNetwork(network); + + const urlParams = { + 'validator-address': validatorUrl, + 'mixnode-identity': mixnode.identityKey, + }; + + Object.entries(urlParams).forEach(([key, value]) => url.searchParams.append(key, value)); + + openLink(url.toString()); + } }; const renderLegend = () => ( diff --git a/nym-wallet/src/constants.ts b/nym-wallet/src/constants.ts index 55770cd39bd..78fe92caa20 100644 --- a/nym-wallet/src/constants.ts +++ b/nym-wallet/src/constants.ts @@ -1,5 +1,27 @@ +import { Network } from './types'; + const QA_VALIDATOR_URL = 'https://qa-nym-api.qa.nymte.ch/api'; const QWERTY_VALIDATOR_URL = 'https://qwerty-validator-api.qa.nymte.ch/api'; -const MAINNET_VALIDATOR_URL = 'https://validator.nymtech.net/api/'; +const SANDBOX_VALIDATOR_URL = 'https://sandbox-nym-api1.nymtech.net/api'; +const MAINNET_VALIDATOR_URL = 'https://validator.nymtech.net/api'; + +const validatorApiFromNetwork = (network: Network) => { + switch (network) { + case 'QA': + return QA_VALIDATOR_URL; + case 'SANDBOX': + return SANDBOX_VALIDATOR_URL; + case 'MAINNET': + return MAINNET_VALIDATOR_URL; + default: + throw new Error(`Unknown network: ${network}`); + } +}; -export { QA_VALIDATOR_URL, QWERTY_VALIDATOR_URL, MAINNET_VALIDATOR_URL }; +export { + QA_VALIDATOR_URL, + QWERTY_VALIDATOR_URL, + MAINNET_VALIDATOR_URL, + SANDBOX_VALIDATOR_URL, + validatorApiFromNetwork, +}; diff --git a/sdk/typescript/examples/node-tester/react/src/App.tsx b/sdk/typescript/examples/node-tester/react/src/App.tsx index eb0bd1927a6..c95dd7a5f1f 100644 --- a/sdk/typescript/examples/node-tester/react/src/App.tsx +++ b/sdk/typescript/examples/node-tester/react/src/App.tsx @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { + Alert, Button, Card, CardActions, @@ -21,8 +22,8 @@ import { TestStatusLabel } from 'src/components/TestStatusLabel'; import Icon from '../../../../../../assets/appicon/appicon.png'; export const App = () => { - const { testState, error, testNode, disconnectFromGateway, reconnectToGateway } = useNodeTesterClient(); - const [mixnodeIdentity, setMixnodeIdentity] = useState(''); + const { createClient, testState, error, testNode, disconnectFromGateway, reconnectToGateway } = useNodeTesterClient(); + const [mixnodeIdentity, setMixnodeIdentity] = useState(''); const [results, setResults] = React.useState(); console.log({ testState, error, testNode }); @@ -37,6 +38,26 @@ export const App = () => { } }; + const getParams = () => { + const urlParams = new URLSearchParams(window.location.search); + return { + mixnodeIdentity: urlParams.get('mixnode-identity'), + validatorAddress: urlParams.get('validator-address'), + }; + }; + + const initApp = async () => { + const { mixnodeIdentity, validatorAddress } = getParams(); + if (mixnodeIdentity) { + setMixnodeIdentity(mixnodeIdentity); + } + await createClient(validatorAddress || 'https://validator.nymtech.net/api'); + }; + + useEffect(() => { + initApp(); + }, []); + return ( @@ -66,6 +87,11 @@ export const App = () => { + {error && ( + + {error} + + )} diff --git a/sdk/typescript/examples/node-tester/react/src/hooks/useNodeTesterClient.ts b/sdk/typescript/examples/node-tester/react/src/hooks/useNodeTesterClient.ts index fcd6f929379..6ebab1c379e 100644 --- a/sdk/typescript/examples/node-tester/react/src/hooks/useNodeTesterClient.ts +++ b/sdk/typescript/examples/node-tester/react/src/hooks/useNodeTesterClient.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { createNodeTesterClient, NodeTester } from '@nymproject/sdk'; export type TestState = 'Ready' | 'Connecting' | 'Disconnected' | 'Disconnecting' | 'Error' | 'Testing' | 'Stopped'; @@ -8,26 +8,21 @@ export const useNodeTesterClient = () => { const [error, setError] = useState(); const [testState, setTestState] = useState('Disconnected'); - const createClient = async () => { + const createClient = async (validator: string) => { setTestState('Connecting'); try { - const validator = 'https://validator.nymtech.net/api'; const nodeTesterClient = await createNodeTesterClient(); - await nodeTesterClient.tester.init(validator); + await nodeTesterClient.tester.init(validator, validator); setClient(nodeTesterClient); + setTestState('Ready'); } catch (e) { console.log(e); - setError('Failed to load node tester client, please try again'); - } finally { - setTestState('Ready'); + setError('Failed to load node tester client, please try again. Error: ' + e.message); + setTestState('Error'); } }; - useEffect(() => { - createClient(); - }, []); - const testNode = !client ? undefined : async (mixnodeIdentity: string) => { @@ -38,7 +33,7 @@ export const useNodeTesterClient = () => { return result; } catch (e) { console.log(e); - setError('Failed to test node, please try again'); + setError('Failed to test node, please try again. Error: ' + e.message); setTestState('Error'); } }; @@ -67,5 +62,5 @@ export const useNodeTesterClient = () => { setTestState('Disconnected'); }; - return { testNode, disconnectFromGateway, reconnectToGateway, terminateWorker, testState, error }; + return { createClient, testNode, disconnectFromGateway, reconnectToGateway, terminateWorker, testState, error }; };