diff --git a/.github/workflows/build-and-upload-binaries-ci.yml b/.github/workflows/build-and-upload-binaries-ci.yml index c8e5bc924e..448b947179 100644 --- a/.github/workflows/build-and-upload-binaries-ci.yml +++ b/.github/workflows/build-and-upload-binaries-ci.yml @@ -34,7 +34,7 @@ on: - 'tools/ts-rs-cli/**' env: - NETWORK: mainnet + NETWORK: mainnet jobs: publish-nym: @@ -46,7 +46,7 @@ jobs: runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v3 - + - name: Prepare build output directory shell: bash env: @@ -110,4 +110,3 @@ jobs: REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/builds/ EXCLUDE: "/dist/, /node_modules/" - diff --git a/CHANGELOG.md b/CHANGELOG.md index b108c9437e..b49c2a3e01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,28 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ### Added -- remove coconut feature and unify builds ([#2890]) -- native-client: is now capable of listening for requests on sockets different than `127.0.0.1` ([#2939]). This can be specified via `--host` flag during `init` or `run`. Alternatively a custom `host` can be set in `config.toml` file under `socket` section. - dkg resharing mode ([#2936]) -[#2890]: https://github.com/nymtech/nym/pull/2890 -[#2939]: https://github.com/nymtech/nym/pull/2939 [#2936]: https://github.com/nymtech/nym/pull/2936 + +# [v1.1.9] (2023-02-07) + +### Added + +- Separate `nym-api` endpoints with values of "total-supply" and "circulating-supply" in `nym` ([#2964]) +- Add `host` option to client init ([#2912]) +- Remove Coconut feature flag ([#2793]) +- Don't drop in mixnet connection handler ([#2963]) + +### Changed +- native-client: is now capable of listening for requests on sockets different than `127.0.0.1` ([#2939]). This can be specified via `--host` flag during `init` or `run`. Alternatively a custom `host` can be set in `config.toml` file under `socket` section. +- mixnode, gateway: fix unexpected shutdown on corrupted connection ([#2963]) + +[#2939]: https://github.com/nymtech/nym/pull/2939 +[#2963]: https://github.com/nymtech/nym/pull/2963 + + # [v1.1.8] (2023-01-31) ### Added diff --git a/explorer/CHANGELOG.md b/explorer/CHANGELOG.md index 2bdbaeab0e..0098db042d 100644 --- a/explorer/CHANGELOG.md +++ b/explorer/CHANGELOG.md @@ -1,5 +1,15 @@ ## UNRELEASED +- nothing yet + +## [nym-explorer-v1.0.5](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.5) (2023-02-07) + +- NE - link `Owner` field on the node detail page to the account details on NG explorer ([#2923]) +- NE - Upgrade Sandbox and make below changes: ([#2332]) + +[#2923]: https://github.com/nymtech/nym/issues/2923 +[#2332]: https://github.com/nymtech/nym/issues/2332 + ## [nym-explorer-v1.0.4](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.4) (2023-01-31) - Add routing score on gateway list ([#2913]) diff --git a/explorer/src/components/DetailTable.tsx b/explorer/src/components/DetailTable.tsx index 17418de4f3..80e7ce969b 100644 --- a/explorer/src/components/DetailTable.tsx +++ b/explorer/src/components/DetailTable.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material'; +import { Link, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import { Tooltip } from '@nymproject/react/tooltip/Tooltip'; import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard'; @@ -37,9 +37,19 @@ function formatCellValues(val: string | number, field: string) { ); } + if (field === 'bond') { return unymToNym(val, 6); } + + if (field === 'owner') { + return ( + + {val} + + ); + } + return val; } diff --git a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs index 3581d287d3..34d9808040 100644 --- a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs +++ b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs @@ -181,6 +181,7 @@ impl ConnectionHandler { mut shutdown: TaskClient, ) { debug!("Starting connection handler for {:?}", remote); + shutdown.mark_as_success(); let mut framed_conn = Framed::new(conn, SphinxCodec); while !shutdown.is_shutdown() { tokio::select! { diff --git a/mixnode/src/node/listener/connection_handler/mod.rs b/mixnode/src/node/listener/connection_handler/mod.rs index 2b6e9f562b..d09a78db2b 100644 --- a/mixnode/src/node/listener/connection_handler/mod.rs +++ b/mixnode/src/node/listener/connection_handler/mod.rs @@ -77,6 +77,7 @@ impl ConnectionHandler { mut shutdown: TaskClient, ) { debug!("Starting connection handler for {:?}", remote); + shutdown.mark_as_success(); let mut framed_conn = Framed::new(conn, SphinxCodec); while !shutdown.is_shutdown() { tokio::select! { diff --git a/nym-api/src/circulating_supply_api/mod.rs b/nym-api/src/circulating_supply_api/mod.rs index 9d0e984ab6..900d38380b 100644 --- a/nym-api/src/circulating_supply_api/mod.rs +++ b/nym-api/src/circulating_supply_api/mod.rs @@ -15,7 +15,11 @@ pub(crate) mod routes; /// Merges the routes with http information and returns it to Rocket for serving pub(crate) fn circulating_supply_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { - openapi_get_routes_spec![settings: routes::get_circulating_supply] + openapi_get_routes_spec![ + settings: routes::get_full_circulating_supply, + routes::get_total_supply, + routes::get_circulating_supply + ] } /// Spawn the circulating supply cache refresher. diff --git a/nym-api/src/circulating_supply_api/routes.rs b/nym-api/src/circulating_supply_api/routes.rs index 898248445a..da6aea1d22 100644 --- a/nym-api/src/circulating_supply_api/routes.rs +++ b/nym-api/src/circulating_supply_api/routes.rs @@ -1,15 +1,30 @@ -use rocket::http::Status; -use rocket::serde::json::Json; -use rocket::State; +// Copyright 2022-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 use crate::circulating_supply_api::cache::CirculatingSupplyCache; use crate::node_status_api::models::ErrorResponse; use nym_api_requests::models::CirculatingSupplyResponse; +use rocket::http::Status; +use rocket::serde::json::Json; +use rocket::State; use rocket_okapi::openapi; +use validator_client::nyxd::Coin; + +// TODO: this is not the best place to put it, it should be more centralised, +// but for a quick fix, that's good enough for now... +// (for proper solution we should be managing `NymNetworkDetails` via rocket and grabbing display exponent +// value from the mix denom here. +const UNYM_RATIO: f64 = 1000000.; + +fn unym_coin_to_float_unym(coin: Coin) -> f64 { + // our total supply can't exceed 1B so an overflow here is impossible + // (if it happened, then we SHOULD crash) + coin.amount as f64 / UNYM_RATIO +} #[openapi(tag = "circulating-supply")] #[get("/circulating-supply")] -pub(crate) async fn get_circulating_supply( +pub(crate) async fn get_full_circulating_supply( cache: &State, ) -> Result, ErrorResponse> { match cache.get_circulating_supply().await { @@ -20,3 +35,43 @@ pub(crate) async fn get_circulating_supply( )), } } + +#[openapi(tag = "circulating-supply")] +#[get("/circulating-supply/total-supply-value")] +pub(crate) async fn get_total_supply( + cache: &State, +) -> Result, ErrorResponse> { + let full_circulating_supply = match cache.get_circulating_supply().await { + Some(res) => res, + None => { + return Err(ErrorResponse::new( + "unavailable", + Status::InternalServerError, + )) + } + }; + + Ok(Json(unym_coin_to_float_unym( + full_circulating_supply.total_supply.into(), + ))) +} + +#[openapi(tag = "circulating-supply")] +#[get("/circulating-supply/circulating-supply-value")] +pub(crate) async fn get_circulating_supply( + cache: &State, +) -> Result, ErrorResponse> { + let full_circulating_supply = match cache.get_circulating_supply().await { + Some(res) => res, + None => { + return Err(ErrorResponse::new( + "unavailable", + Status::InternalServerError, + )) + } + }; + + Ok(Json(unym_coin_to_float_unym( + full_circulating_supply.circulating_supply.into(), + ))) +} diff --git a/nym-connect/CHANGELOG.md b/nym-connect/CHANGELOG.md index c1bf4a7b85..5c0a81945a 100644 --- a/nym-connect/CHANGELOG.md +++ b/nym-connect/CHANGELOG.md @@ -2,6 +2,18 @@ ## UNRELEASED +## [nym-connect-v1.1.9](https://github.com/nymtech/nym/tree/nym-connect-v1.1.9) (2023-02-07) + +- NC - Button animations ([#2949]) +- NC - add effect when the button is clicked ([#2947]) +- NC - UI to select gateways based on some performance criteria by checking gateways' routing score from nym-api ([#2942]) +- NC - client health check when connecting ([#2859]) + +[#2949]: https://github.com/nymtech/nym/issues/2949 +[#2947]: https://github.com/nymtech/nym/issues/2947 +[#2942]: https://github.com/nymtech/nym/issues/2942 +[#2859]: https://github.com/nymtech/nym/issues/2859 + ## [nym-connect-v1.1.8](https://github.com/nymtech/nym/tree/nym-connect-v1.1.8) (2023-01-31) - Add supported apps in the menu + update guide ([#2868]) diff --git a/nym-connect/src-tauri/Cargo.toml b/nym-connect/src-tauri/Cargo.toml index 54c39aac73..93e21710bb 100644 --- a/nym-connect/src-tauri/Cargo.toml +++ b/nym-connect/src-tauri/Cargo.toml @@ -45,6 +45,8 @@ url = "2.2" yaml-rust = "0.4" client-core = { path = "../../clients/client-core" } +nym-api-requests = { path = "../../nym-api/nym-api-requests" } +contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common"} config-common = { path = "../../common/config", package = "config" } crypto = { path = "../../common/crypto" } logging = { path = "../../common/logging"} diff --git a/nym-connect/src-tauri/src/main.rs b/nym-connect/src-tauri/src/main.rs index 6540fa94f4..ec36c252d4 100644 --- a/nym-connect/src-tauri/src/main.rs +++ b/nym-connect/src-tauri/src/main.rs @@ -52,6 +52,7 @@ fn main() { crate::operations::connection::status::get_gateway_connection_status, crate::operations::connection::status::start_connection_health_check_task, crate::operations::directory::get_services, + crate::operations::directory::get_gateways_detailed, crate::operations::export::export_keys, crate::operations::window::hide_window, crate::operations::growth::test_and_earn::growth_tne_get_client_id, diff --git a/nym-connect/src-tauri/src/operations/directory/mod.rs b/nym-connect/src-tauri/src/operations/directory/mod.rs index efc2ba175b..66070a3b00 100644 --- a/nym-connect/src-tauri/src/operations/directory/mod.rs +++ b/nym-connect/src-tauri/src/operations/directory/mod.rs @@ -1,21 +1,35 @@ use itertools::Itertools; use crate::error::Result; -use crate::models::{DirectoryService, HarbourMasterService, PagedResult}; +use crate::models::{ + DirectoryService, DirectoryServiceProvider, HarbourMasterService, PagedResult, +}; +use contracts_common::types::Percent; +use nym_api_requests::models::GatewayBondAnnotated; static SERVICE_PROVIDER_WELLKNOWN_URL: &str = "https://nymtech.net/.wellknown/connect/service-providers.json"; static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100"; +static GATEWAYS_DETAILED_URL: &str = + "https://validator.nymtech.net/api/v1/status/gateways/detailed"; + #[tauri::command] -pub async fn get_services() -> Result> { +pub async fn get_services() -> Result> { log::trace!("Fetching services"); - let res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL) + let services_res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL) .await? .json::>() .await?; - log::trace!("Received: {:#?}", res); + log::trace!("Received: {:#?}", services_res); + + log::trace!("Fetching gateways"); + let gateway_res = reqwest::get(GATEWAYS_DETAILED_URL) + .await? + .json::>() + .await?; + log::trace!("Received: {:#?}", gateway_res); // TODO: get paged log::trace!("Fetching active services"); @@ -27,7 +41,7 @@ pub async fn get_services() -> Result> { let mut filtered: Vec = vec![]; - for service in &res { + for service in &services_res { let items: _ = service .items .clone() @@ -47,5 +61,32 @@ pub async fn get_services() -> Result> { }) } - Ok(filtered) + let perf_threshold = Percent::from_percentage_value(90).unwrap(); + + // Use only services that are active AND have a performance of >= 90% + let services_with_good_performance: Vec = filtered + .iter_mut() + .fold(vec![], |mut acc, sp| { + acc.append(&mut sp.items); + acc + }) + .into_iter() + .filter(|sp| { + gateway_res.iter().any(|gateway| { + gateway.gateway_bond.gateway.identity_key == sp.gateway + && gateway.performance >= perf_threshold + }) + }) + .collect(); + + Ok(services_with_good_performance) +} + +#[tauri::command] +pub async fn get_gateways_detailed() -> Result> { + let res = reqwest::get(GATEWAYS_DETAILED_URL) + .await? + .json::>() + .await?; + Ok(res) } diff --git a/nym-connect/src/App.tsx b/nym-connect/src/App.tsx deleted file mode 100644 index 9d57dcf4d8..0000000000 --- a/nym-connect/src/App.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useEffect } from 'react'; -import { DateTime } from 'luxon'; -import { forage } from '@tauri-apps/tauri-forage'; -import { useClientContext } from './context/main'; -import { useTauriEvents } from './utils'; -import { AppRoutes } from './routes'; -import { Connected } from './pages/connection/Connected'; - -export const App: FCWithChildren = () => { - const context = useClientContext(); - const [busy, setBusy] = React.useState(); - - useTauriEvents('help://clear-storage', (_event) => { - console.log('About to clear local storage...'); - // clear local storage - try { - forage.clear()(); - console.log('Local storage cleared'); - } catch (e) { - console.error('Failed to clear local storage', e); - } - }); - - const handleConnectClick = React.useCallback(async () => { - const currentStatus = context.connectionStatus; - if (currentStatus === 'connected' || currentStatus === 'disconnected') { - setBusy(true); - - // eslint-disable-next-line default-case - switch (currentStatus) { - case 'disconnected': - await context.startConnecting(); - context.setConnectedSince(DateTime.now()); - break; - case 'connected': - await context.startDisconnecting(); - context.setConnectedSince(undefined); - break; - } - setBusy(false); - } - }, [context.connectionStatus]); - - if (context.connectionStatus === 'disconnected' || context.connectionStatus === 'connecting') { - return ; - } - - return ( - context.setShowInfoModal(false)} - busy={busy} - onConnectClick={handleConnectClick} - ipAddress="127.0.0.1" - port={1080} - gatewayPerformance={context.gatewayPerformance} - connectedSince={context.connectedSince} - serviceProvider={context.selectedProvider} - stats={[ - { - label: 'in:', - totalBytes: 1024, - rateBytesPerSecond: 1024 * 1024 * 1024 + 10, - }, - { - label: 'out:', - totalBytes: 1024 * 1024 * 1024 * 1024 * 20, - rateBytesPerSecond: 1024 * 1024 + 10, - }, - ]} - /> - ); -}; diff --git a/nym-connect/src/components/ConnectionStatus.tsx b/nym-connect/src/components/ConnectionStatus.tsx index e917644efe..1be1f4487b 100644 --- a/nym-connect/src/components/ConnectionStatus.tsx +++ b/nym-connect/src/components/ConnectionStatus.tsx @@ -83,7 +83,6 @@ export const ConnectionStatus: FCWithChildren<{ serviceProvider?: ServiceProvider; }> = ({ status, serviceProvider, gatewayPerformance }) => { const color = status === 'connected' || status === 'disconnecting' ? '#21D072' : 'white'; - console.log(gatewayPerformance); return ( <> diff --git a/nym-connect/src/components/PowerButton.tsx b/nym-connect/src/components/PowerButton.tsx deleted file mode 100644 index 426028a767..0000000000 --- a/nym-connect/src/components/PowerButton.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React from 'react'; -import { ConnectionStatusKind } from 'src/types'; - -const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => { - if (isError && hover) { - return '#21D072'; - } - if (isError) { - return '#40475C'; - } - - switch (status) { - case 'disconnected': - if (hover) { - return '#FFF'; - } - return '#BBB'; - case 'connecting': - return '#FFF'; - case 'disconnecting': - return '#FFF'; - default: - // connected - if (hover) { - return '#E43E3E'; - } - return '#21D072'; - } -}; - -export const PowerButton: FCWithChildren<{ - onClick?: (status: ConnectionStatusKind) => void; - isError?: boolean; - disabled?: boolean; - status: ConnectionStatusKind; - busy?: boolean; -}> = ({ onClick, disabled, status, isError }) => { - const [hover, setHover] = React.useState(false); - - const handleClick = () => { - if (disabled === true) { - return; - } - if (onClick) { - onClick(status); - } - }; - - const statusFillColor = getStatusFillColor(status, hover, Boolean(isError)); - - return ( - !disabled && setHover(true)} - onMouseLeave={() => !disabled && setHover(false)} - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/nym-connect/src/components/PowerButton/PowerButton.tsx b/nym-connect/src/components/PowerButton/PowerButton.tsx new file mode 100644 index 0000000000..4e0c690967 --- /dev/null +++ b/nym-connect/src/components/PowerButton/PowerButton.tsx @@ -0,0 +1,123 @@ +import React, { useCallback } from 'react'; +import { ConnectionStatusKind } from 'src/types'; +import './power-button.css'; + +const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => { + if (isError && hover) { + return '#21D072'; + } + if (isError) { + return '#40475C'; + } + + switch (status) { + case 'disconnected': + if (hover) { + return '#FFF'; + } + return '#BBB'; + case 'connecting': + return '#FFF'; + case 'disconnecting': + return '#FFF'; + default: + // connected + if (hover) { + return '#E43E3E'; + } + return '#21D072'; + } +}; + +export const PowerButton: FCWithChildren<{ + onClick?: (status: ConnectionStatusKind) => void; + isError?: boolean; + disabled?: boolean; + status: ConnectionStatusKind; + busy?: boolean; +}> = ({ onClick, disabled, status, isError }) => { + const [hover, setHover] = React.useState(false); + + const handleClick = () => { + if (disabled === true) { + return; + } + if (onClick) { + onClick(status); + } + }; + + const statusFillColor = getStatusFillColor(status, hover, Boolean(isError)); + + const getClassName = useCallback(() => { + if (hover) { + switch (status) { + case 'disconnected': + return 'expand'; + default: + return 'contract'; + } + } + if (!hover) { + switch (status) { + case 'connected': + return 'expand'; + default: + return 'contract'; + } + } + }, [status, hover]); + + const buttonPulse = () => { + if (status === 'connecting' || status === 'disconnecting') return 'pulse'; + return undefined; + }; + + return ( + !disabled && setHover(true)} + onMouseLeave={() => !disabled && setHover(false)} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/nym-connect/src/components/PowerButton/power-button.css b/nym-connect/src/components/PowerButton/power-button.css new file mode 100644 index 0000000000..a493d8df25 --- /dev/null +++ b/nym-connect/src/components/PowerButton/power-button.css @@ -0,0 +1,72 @@ +#ring-expand { + animation-name: rings-expand; + animation-duration: 0.4s; + animation-timing-function: ease-in-out; + animation-play-state: paused; +} + +#ring-one.expand { + opacity: 0.3; + transition: opacity 0.1s ease-in-out; +} + +#ring-two.expand { + opacity: 0.2; + transition: opacity 0.2s ease-in-out; +} + +#ring-three.expand { + opacity: 0.1; + transition: opacity 0.3s ease-in-out; +} + +#ring-four.expand { + opacity: 0.05; + transition: opacity 0.4s ease-in-out; +} + +#ring-one.contract { + opacity: 0; + transition: opacity 0.4s; + transition-delay: 0.1s; +} + +#ring-two.contract { + opacity: 0; + transition: opacity 0.3s; + transition-delay: 0.1s; +} + +#ring-three.contract { + opacity: 0; + transition: opacity 0.1s; + transition-delay: 0.1s; +} + +#ring-four.contract { + opacity: 0; + transition: opacity 0.1s; + transition-delay: 0.1s; +} + +circle, +path { + transition: stroke 0.5s, fill 0.5s; +} + +.pulse { + animation-name: pulse; + animation-duration: 0.5s; + animation-iteration-count: infinite; + animation-direction: alternate; +} + +@keyframes pulse { + from { + opacity: 1; + } + + to { + opacity: 0.3; + } +} diff --git a/nym-connect/src/context/main.tsx b/nym-connect/src/context/main.tsx index 3ea8a3cad2..625cf7b5fd 100644 --- a/nym-connect/src/context/main.tsx +++ b/nym-connect/src/context/main.tsx @@ -1,15 +1,13 @@ import React, { createContext, useCallback, useContext, useEffect, useMemo, useState, useRef } from 'react'; import { DateTime } from 'luxon'; import { invoke } from '@tauri-apps/api'; -import type { UnlistenFn } from '@tauri-apps/api/event'; -import { listen } from '@tauri-apps/api/event'; import { forage } from '@tauri-apps/tauri-forage'; import { Error } from 'src/types/error'; -import { TauriEvent } from 'src/types/event'; import { getVersion } from '@tauri-apps/api/app'; import { ConnectionStatusKind, GatewayPerformance } from '../types'; import { ConnectionStatsItem } from '../components/ConnectionStats'; import { ServiceProvider, Services } from '../types/directory'; +import { useEvents } from 'src/hooks/events'; const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed'; @@ -57,21 +55,21 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { const timerId = useRef(); - const flattenProviders = (services: Services) => { - return services.reduce((a: ServiceProvider[], b) => { - return [...a, ...b.items]; - }, []); - }; - const initialiseApp = async () => { const services = await invoke('get_services'); - const allServiceProviders = flattenProviders(services as Services); const AppVersion = await getAppVersion(); + console.log(services); setAppVersion(AppVersion); - setServiceProviders(allServiceProviders); + setServiceProviders(services as ServiceProvider[]); }; + useEvents({ + onError: (error) => setError(error), + onGatewayPerformanceChange: (performance) => setGatewayPerformance(performance), + onStatusChange: (status) => setConnectionStatus(status), + }); + useEffect(() => { initialiseApp(); }, []); @@ -84,49 +82,6 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { })(); }, []); - useEffect(() => { - const unlisten: UnlistenFn[] = []; - - // TODO: fix typings - listen(TAURI_EVENT_STATUS_CHANGED, (event) => { - const { status } = event.payload as any; - console.log(TAURI_EVENT_STATUS_CHANGED, { status, event }); - setConnectionStatus(status); - }) - .then((result) => { - unlisten.push(result); - }) - .catch((e) => console.log(e)); - - listen('socks5-event', (e: TauriEvent) => { - console.log(e); - - setError(e.payload); - }).then((result) => { - unlisten.push(result); - }); - - listen('socks5-status-event', (e: TauriEvent) => { - if (e.payload.message.includes('slow')) { - setGatewayPerformance('Poor'); - - if (timerId.current) { - clearTimeout(timerId.current); - } - - timerId.current = setTimeout(() => { - setGatewayPerformance('Good'); - }, 10000); - } - }).then((result) => { - unlisten.push(result); - }); - - return () => { - unlisten.forEach((unsubscribe) => unsubscribe()); - }; - }, []); - const startConnecting = useCallback(async () => { try { await invoke('start_connecting'); diff --git a/nym-connect/src/hooks/events.ts b/nym-connect/src/hooks/events.ts new file mode 100644 index 0000000000..4b2c4fcc55 --- /dev/null +++ b/nym-connect/src/hooks/events.ts @@ -0,0 +1,68 @@ +import { useEffect, useRef } from 'react'; +import { listen, UnlistenFn } from '@tauri-apps/api/event'; +import { ConnectionStatusKind, GatewayPerformance } from 'src/types'; +import { Error } from 'src/types/error'; +import { TauriEvent } from 'src/types/event'; + +const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed'; + +export const useEvents = ({ + onError, + onStatusChange, + onGatewayPerformanceChange, +}: { + onError: (error: Error) => void; + onStatusChange: (status: ConnectionStatusKind) => void; + onGatewayPerformanceChange: (status: GatewayPerformance) => void; +}) => { + const timerId = useRef(); + + useEffect(() => { + const unlisten: UnlistenFn[] = []; + + // TODO: fix typings + listen(TAURI_EVENT_STATUS_CHANGED, (event) => { + const { status } = event.payload as any; + console.log(TAURI_EVENT_STATUS_CHANGED, { status, event }); + onStatusChange(status); + }) + .then((result) => { + unlisten.push(result); + }) + .catch((e) => console.log(e)); + + listen('socks5-event', (e: TauriEvent) => { + console.log(e); + onError(e.payload); + }).then((result) => { + unlisten.push(result); + }); + + listen('socks5-status-event', (e: TauriEvent) => { + if (e.payload.message.includes('slow')) { + onGatewayPerformanceChange('Poor'); + + if (timerId?.current) { + clearTimeout(timerId.current); + } + + timerId.current = setTimeout(() => { + onGatewayPerformanceChange('Good'); + }, 10000); + } + }).then((result) => { + unlisten.push(result); + }); + + listen('socks5-connection-fail-event', (e: TauriEvent) => { + onError({ title: 'Connection failed', message: `${e.payload.message} - Please disconnect and reconnect.` }); + onGatewayPerformanceChange('Poor'); + }).then((result) => { + unlisten.push(result); + }); + + return () => { + unlisten.forEach((unsubscribe) => unsubscribe()); + }; + }, []); +}; diff --git a/nym-connect/src/index.tsx b/nym-connect/src/index.tsx index 7041b86787..2498330910 100644 --- a/nym-connect/src/index.tsx +++ b/nym-connect/src/index.tsx @@ -4,7 +4,6 @@ import { ErrorBoundary } from 'react-error-boundary'; import { ClientContextProvider } from './context/main'; import { ErrorFallback } from './components/Error'; import { NymMixnetTheme } from './theme'; -import { App } from './App'; import { AppWindowFrame } from './components/AppWindowFrame'; import { TestAndEarnContextProvider } from './components/Growth/context/TestAndEarnContext'; import { BrowserRouter as Router } from 'react-router-dom'; diff --git a/nym-connect/src/pages/connection/Connected.tsx b/nym-connect/src/pages/connection/Connected.tsx index 63da0d6fb1..5071349ec3 100644 --- a/nym-connect/src/pages/connection/Connected.tsx +++ b/nym-connect/src/pages/connection/Connected.tsx @@ -10,9 +10,12 @@ import { IpAddressAndPort } from 'src/components/IpAddressAndPort'; import { ServiceProvider } from 'src/types/directory'; import { ExperimentalWarning } from 'src/components/ExperimentalWarning'; import { ConnectionLayout } from 'src/layouts/ConnectionLayout'; -import { PowerButton } from 'src/components/PowerButton'; +import { PowerButton } from 'src/components/PowerButton/PowerButton'; +import { Error } from 'src/types/error'; +import { InfoModal } from 'src/components/InfoModal'; export const Connected: FCWithChildren<{ + error?: Error; status: ConnectionStatusKind; showInfoModal: boolean; gatewayPerformance: GatewayPerformance; @@ -23,9 +26,11 @@ export const Connected: FCWithChildren<{ busy?: boolean; isError?: boolean; serviceProvider?: ServiceProvider; + clearError: () => void; onConnectClick: (status: ConnectionStatusKind) => void; closeInfoModal: () => void; }> = ({ + error, status, showInfoModal, gatewayPerformance, @@ -35,11 +40,13 @@ export const Connected: FCWithChildren<{ busy, isError, serviceProvider, + clearError, onConnectClick, closeInfoModal, }) => { return ( <> + {error && } } BottomContent={ diff --git a/nym-connect/src/pages/connection/Disconnected.tsx b/nym-connect/src/pages/connection/Disconnected.tsx index f92d70ad55..781f49eb85 100644 --- a/nym-connect/src/pages/connection/Disconnected.tsx +++ b/nym-connect/src/pages/connection/Disconnected.tsx @@ -2,14 +2,12 @@ import React from 'react'; import { Stack, Typography } from '@mui/material'; import { ConnectionStatus } from 'src/components/ConnectionStatus'; import { ConnectionTimer } from 'src/components/ConntectionTimer'; -import { useClientContext } from 'src/context/main'; import { InfoModal } from 'src/components/InfoModal'; import { Error } from 'src/types/error'; import { ExperimentalWarning } from 'src/components/ExperimentalWarning'; import { ServiceProvider, Services } from 'src/types/directory'; import { ConnectionStatusKind } from 'src/types'; -import { ConnectionButton } from 'src/components/ConnectionButton'; -import { PowerButton } from 'src/components/PowerButton'; +import { PowerButton } from 'src/components/PowerButton/PowerButton'; import { Box } from '@mui/system'; import { ConnectionLayout } from 'src/layouts/ConnectionLayout'; @@ -22,7 +20,7 @@ export const Disconnected: FCWithChildren<{ serviceProvider?: ServiceProvider; clearError: () => void; onConnectClick: (status: ConnectionStatusKind) => void; -}> = ({ status, error, onConnectClick, clearError, serviceProvider }) => { +}> = ({ status, error, onConnectClick, clearError }) => { return ( <> {error && } @@ -33,7 +31,7 @@ export const Disconnected: FCWithChildren<{ } - ConnectButton={} + ConnectButton={} BottomContent={ { if (context.connectionStatus === 'connected') return ( = () => { return ( {}} gatewayPerformance="Good" showInfoModal={false} closeInfoModal={() => undefined} diff --git a/nym-connect/src/stories/ConnectedLayout.stories.tsx b/nym-connect/src/stories/ConnectedLayout.stories.tsx index b572d58490..f6d744f68c 100644 --- a/nym-connect/src/stories/ConnectedLayout.stories.tsx +++ b/nym-connect/src/stories/ConnectedLayout.stories.tsx @@ -15,6 +15,7 @@ export default { export const Default: ComponentStory = () => ( {}} gatewayPerformance="Good" showInfoModal={false} closeInfoModal={() => { diff --git a/nym-connect/src/stories/PowerButton.stories.tsx b/nym-connect/src/stories/PowerButton.stories.tsx index 97681cc974..6cb2896849 100644 --- a/nym-connect/src/stories/PowerButton.stories.tsx +++ b/nym-connect/src/stories/PowerButton.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { PowerButton } from 'src/components/PowerButton'; +import { PowerButton } from 'src/components/PowerButton/PowerButton'; import { ConnectionStatusKind } from 'src/types'; export default { @@ -13,7 +13,7 @@ export const Disconnected: ComponentStory = () => ( ); export const Connecting: ComponentStory = () => ( - + ); export const Connected: ComponentStory = () => ( @@ -21,9 +21,9 @@ export const Connected: ComponentStory = () => ( ); export const Disconnecting: ComponentStory = () => ( - + ); export const Disabled: ComponentStory = () => ( - + );