diff --git a/CHANGELOG.md b/CHANGELOG.md index af9cf92aac..ec9414c775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,18 +4,31 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// # [Unreleased] +### Added + +- remove coconut feature and unify builds ([#2890]) + +[#2890]: https://github.com/nymtech/nym/pull/2890 + +# [v1.1.8] (2023-01-31) ### Added -- dkg rerun from scratch and dkg-specific epochs ([#2839]) -- nym-sdk: add support for surb storage ([#2870]) -- nym-sdk: enable reply-SURBs by default ([#2874]) -- remove coconut feature and unify builds ([#2890]) +- Rust SDK - Support SURBS (anonymous send + storage) ([#2754]) +- dkg rerun from scratch and dkg-specific epochs ([#2810]) +- Rename `'initial_supply'` field to `'total_supply'` in the circulating supply endpoint ([#2931]) +- Circulating supply api endpoint (read the note inside before testing/deploying) ([#1902]) + +### Changed + +- nym-api: an `--id` flag is now always explicitly required ([#2873]) + +[#2754]: https://github.com/nymtech/nym/issues/2754 +[#2839]: https://github.com/nymtech/nym/issues/2810 +[#2931]: https://github.com/nymtech/nym/issues/2931 +[#1902]: https://github.com/nymtech/nym/issues/1902 +[#2873]: https://github.com/nymtech/nym/issues/2873 -[#2839]: https://github.com/nymtech/nym/pull/2839 -[#2870]: https://github.com/nymtech/nym/pull/2870 -[#2874]: https://github.com/nymtech/nym/pull/2874 -[#2890]: https://github.com/nymtech/nym/pull/2890 # [v1.1.7] (2023-01-24) diff --git a/Cargo.lock b/Cargo.lock index 30841aab70..0f404263c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -701,7 +701,7 @@ dependencies = [ [[package]] name = "client-core" -version = "1.1.7" +version = "1.1.8" dependencies = [ "async-trait", "client-connections", @@ -1891,7 +1891,7 @@ dependencies = [ [[package]] name = "explorer-api" -version = "1.1.7" +version = "1.1.8" dependencies = [ "chrono", "clap 4.1.3", @@ -3314,6 +3314,7 @@ dependencies = [ "cw4", "schemars", "serde", + "thiserror", ] [[package]] @@ -3437,7 +3438,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.7" +version = "1.1.8" dependencies = [ "anyhow", "async-trait", @@ -3535,7 +3536,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.7" +version = "1.1.8" dependencies = [ "anyhow", "base64 0.13.1", @@ -3593,7 +3594,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.7" +version = "1.1.8" dependencies = [ "build-information", "clap 4.1.3", @@ -3633,7 +3634,7 @@ dependencies = [ [[package]] name = "nym-gateway" -version = "1.1.7" +version = "1.1.8" dependencies = [ "anyhow", "async-trait", @@ -3685,7 +3686,7 @@ dependencies = [ [[package]] name = "nym-mixnode" -version = "1.1.8" +version = "1.1.9" dependencies = [ "anyhow", "atty", @@ -3730,7 +3731,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.7" +version = "1.1.8" dependencies = [ "async-trait", "clap 4.1.3", @@ -3762,7 +3763,7 @@ dependencies = [ [[package]] name = "nym-network-statistics" -version = "1.1.7" +version = "1.1.8" dependencies = [ "dirs", "log", @@ -3819,7 +3820,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.7" +version = "1.1.8" dependencies = [ "build-information", "clap 4.1.3", diff --git a/clients/client-core/Cargo.toml b/clients/client-core/Cargo.toml index 34ddb0de78..f9802bc367 100644 --- a/clients/client-core/Cargo.toml +++ b/clients/client-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "client-core" -version = "1.1.7" +version = "1.1.8" authors = ["Dave Hrycyszyn "] edition = "2021" rust-version = "1.66" diff --git a/clients/client-core/src/config/mod.rs b/clients/client-core/src/config/mod.rs index d7eada542c..e6085720a2 100644 --- a/clients/client-core/src/config/mod.rs +++ b/clients/client-core/src/config/mod.rs @@ -546,35 +546,35 @@ impl Default for Client { impl Client { fn default_private_identity_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("private_identity.pem") + T::default_data_directory(id).join("private_identity.pem") } fn default_public_identity_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("public_identity.pem") + T::default_data_directory(id).join("public_identity.pem") } fn default_private_encryption_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("private_encryption.pem") + T::default_data_directory(id).join("private_encryption.pem") } fn default_public_encryption_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("public_encryption.pem") + T::default_data_directory(id).join("public_encryption.pem") } fn default_gateway_shared_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("gateway_shared.pem") + T::default_data_directory(id).join("gateway_shared.pem") } fn default_ack_key_file(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("ack_key.pem") + T::default_data_directory(id).join("ack_key.pem") } fn default_reply_surb_database_path(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join("persistent_reply_store.sqlite") + T::default_data_directory(id).join("persistent_reply_store.sqlite") } fn default_database_path(id: &str) -> PathBuf { - T::default_data_directory(Some(id)).join(DB_FILE_NAME) + T::default_data_directory(id).join(DB_FILE_NAME) } } diff --git a/clients/client-core/src/init/mod.rs b/clients/client-core/src/init/mod.rs index 9af8a7c1bc..e236bd588b 100644 --- a/clients/client-core/src/init/mod.rs +++ b/clients/client-core/src/init/mod.rs @@ -149,7 +149,7 @@ pub fn load_existing_gateway_config(id: &str) -> Result", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index e3a0845223..c2273c020f 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -108,7 +108,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> { let id = &args.id; - let already_init = Config::default_config_file_path(Some(id)).exists(); + let already_init = Config::default_config_file_path(id).exists(); if already_init { println!("Client \"{id}\" was already initialised before"); } diff --git a/clients/native/src/commands/run.rs b/clients/native/src/commands/run.rs index f7e73ba6fc..f0537be3c9 100644 --- a/clients/native/src/commands/run.rs +++ b/clients/native/src/commands/run.rs @@ -94,7 +94,7 @@ fn version_check(cfg: &Config) -> bool { pub(crate) async fn execute(args: &Run) -> Result<(), Box> { let id = &args.id; - let mut config = match Config::load_from_file(Some(id)) { + let mut config = match Config::load_from_file(id) { Ok(cfg) => cfg, Err(err) => { error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id); diff --git a/clients/native/src/commands/upgrade.rs b/clients/native/src/commands/upgrade.rs index 2d4a258011..a440054aea 100644 --- a/clients/native/src/commands/upgrade.rs +++ b/clients/native/src/commands/upgrade.rs @@ -131,7 +131,7 @@ pub(crate) fn execute(args: &Upgrade) { let id = &args.id; - let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { + let existing_config = Config::load_from_file(id).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {err}"); process::exit(1) }); diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index c559c6351a..da121b4ad0 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-socks5-client" -version = "1.1.7" +version = "1.1.8" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 0f451c67ae..38df141afe 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -117,7 +117,7 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> { let id = &args.id; let provider_address = &args.provider; - let already_init = Config::default_config_file_path(Some(id)).exists(); + let already_init = Config::default_config_file_path(id).exists(); if already_init { println!("SOCKS5 client \"{id}\" was already initialised before"); } diff --git a/clients/socks5/src/commands/run.rs b/clients/socks5/src/commands/run.rs index 9157f62513..cfb71032ba 100644 --- a/clients/socks5/src/commands/run.rs +++ b/clients/socks5/src/commands/run.rs @@ -108,7 +108,7 @@ fn version_check(cfg: &Config) -> bool { pub(crate) async fn execute(args: &Run) -> Result<(), Box> { let id = &args.id; - let mut config = match Config::load_from_file(Some(id)) { + let mut config = match Config::load_from_file(id) { Ok(cfg) => cfg, Err(err) => { error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", id); diff --git a/clients/socks5/src/commands/upgrade.rs b/clients/socks5/src/commands/upgrade.rs index db77167f34..121f045905 100644 --- a/clients/socks5/src/commands/upgrade.rs +++ b/clients/socks5/src/commands/upgrade.rs @@ -144,7 +144,7 @@ pub(crate) fn execute(args: &Upgrade) { let id = &args.id; - let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| { + let existing_config = Config::load_from_file(id).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {err}"); process::exit(1) }); diff --git a/common/config/src/lib.rs b/common/config/src/lib.rs index d0ebecdca3..f14b94faf2 100644 --- a/common/config/src/lib.rs +++ b/common/config/src/lib.rs @@ -30,23 +30,15 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned { fn default_root_directory() -> PathBuf; // default, most probable, implementations; can be easily overridden where required - fn default_config_directory(id: Option<&str>) -> PathBuf { - if let Some(id) = id { - Self::default_root_directory().join(id).join(CONFIG_DIR) - } else { - Self::default_root_directory().join(CONFIG_DIR) - } + fn default_config_directory(id: &str) -> PathBuf { + Self::default_root_directory().join(id).join(CONFIG_DIR) } - fn default_data_directory(id: Option<&str>) -> PathBuf { - if let Some(id) = id { - Self::default_root_directory().join(id).join(DATA_DIR) - } else { - Self::default_root_directory().join(DATA_DIR) - } + fn default_data_directory(id: &str) -> PathBuf { + Self::default_root_directory().join(id).join(DATA_DIR) } - fn default_config_file_path(id: Option<&str>) -> PathBuf { + fn default_config_file_path(id: &str) -> PathBuf { Self::default_config_directory(id).join(Self::config_file_name()) } @@ -54,23 +46,15 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned { fn try_default_root_directory() -> Option; - fn try_default_config_directory(id: Option<&str>) -> Option { - if let Some(id) = id { - Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR)) - } else { - Self::try_default_root_directory().map(|d| d.join(CONFIG_DIR)) - } + fn try_default_config_directory(id: &str) -> Option { + Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR)) } - fn try_default_data_directory(id: Option<&str>) -> Option { - if let Some(id) = id { - Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR)) - } else { - Self::try_default_root_directory().map(|d| d.join(DATA_DIR)) - } + fn try_default_data_directory(id: &str) -> Option { + Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR)) } - fn try_default_config_file_path(id: Option<&str>) -> Option { + fn try_default_config_file_path(id: &str) -> Option { Self::try_default_config_directory(id).map(|d| d.join(Self::config_file_name())) } @@ -113,7 +97,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned { Ok(()) } - fn load_from_file(id: Option<&str>) -> io::Result { + fn load_from_file(id: &str) -> io::Result { let file = Self::default_config_file_path(id); log::trace!("Loading from file: {:#?}", file); let config_contents = fs::read_to_string(file)?; diff --git a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml index f493cb6635..096e4affe0 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml @@ -13,3 +13,4 @@ cw4 = { version = "0.13.4" } cosmwasm-std = "1.0.0" schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.23" } diff --git a/contracts/multisig/cw3-flex-multisig/src/error.rs b/common/cosmwasm-smart-contracts/multisig-contract/src/error.rs similarity index 90% rename from contracts/multisig/cw3-flex-multisig/src/error.rs rename to common/cosmwasm-smart-contracts/multisig-contract/src/error.rs index 75a546ca0e..b10fa6b506 100644 --- a/contracts/multisig/cw3-flex-multisig/src/error.rs +++ b/common/cosmwasm-smart-contracts/multisig-contract/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use cosmwasm_std::StdError; use cw_utils::ThresholdError; diff --git a/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs b/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs index d0e87a0d30..a968ed14e5 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs @@ -1 +1,2 @@ +pub mod error; pub mod msg; diff --git a/contracts/coconut-test/src/helpers.rs b/contracts/coconut-test/src/helpers.rs index 319e523c91..f5bd455d62 100644 --- a/contracts/coconut-test/src/helpers.rs +++ b/contracts/coconut-test/src/helpers.rs @@ -2,8 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response}; -use cw3_flex_multisig::{state::CONFIG, ContractError}; +use cw3_flex_multisig::state::CONFIG; use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper}; +use multisig_contract_common::error::ContractError; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/contracts/multisig/cw3-flex-multisig/Cargo.toml b/contracts/multisig/cw3-flex-multisig/Cargo.toml index e014c728b7..dd127b54ba 100644 --- a/contracts/multisig/cw3-flex-multisig/Cargo.toml +++ b/contracts/multisig/cw3-flex-multisig/Cargo.toml @@ -26,7 +26,6 @@ cw-storage-plus = { version = "0.13.4" } cosmwasm-std = { version = "1.0.0" } schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.23" } group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" } multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" } diff --git a/contracts/multisig/cw3-flex-multisig/src/contract.rs b/contracts/multisig/cw3-flex-multisig/src/contract.rs index 677006daf4..edd1a648b2 100644 --- a/contracts/multisig/cw3-flex-multisig/src/contract.rs +++ b/contracts/multisig/cw3-flex-multisig/src/contract.rs @@ -17,8 +17,8 @@ use cw4::{Cw4Contract, MemberChangedHookMsg, MemberDiff}; use cw_storage_plus::Bound; use cw_utils::{maybe_addr, Expiration, ThresholdResponse}; -use crate::error::ContractError; use crate::state::{Config, CONFIG}; +use multisig_contract_common::error::ContractError; use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; // version info for migration info diff --git a/contracts/multisig/cw3-flex-multisig/src/lib.rs b/contracts/multisig/cw3-flex-multisig/src/lib.rs index e544aa55c8..3407c199dd 100644 --- a/contracts/multisig/cw3-flex-multisig/src/lib.rs +++ b/contracts/multisig/cw3-flex-multisig/src/lib.rs @@ -1,5 +1,2 @@ pub mod contract; -pub mod error; pub mod state; - -pub use crate::error::ContractError; diff --git a/envs/qa-qwerty.env b/envs/qa-qwerty.env index 30130fd6e9..474bf8d073 100644 --- a/envs/qa-qwerty.env +++ b/envs/qa-qwerty.env @@ -12,10 +12,10 @@ DENOMS_EXPONENT=6 MIXNET_CONTRACT_ADDRESS=n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g VESTING_CONTRACT_ADDRESS=n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0 -COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n12ckdkm3q7eytefs7rwu4ue3t9hxgvl9v08jddmtwgct2ve0pv50q0t8dlt -GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m -MULTISIG_CONTRACT_ADDRESS=n14krxe8ukzagwhvec0rmteexu62w8k9kp9sra9ww6em2hnmzcukqsa0utc8 -COCONUT_DKG_CONTRACT_ADDRESS=n1rl5n6cxuz2hdy3f7d9hsnw8zn0zwwwr0r4dxfz7tktgpgkcnz9zshmvksc +COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19d2nwj7fdhxqmyvgy8lf3ad49a6vmww4shryhrkj2mqk36att66s6xzszw +GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm28l5 +MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy +COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090" NYXD="https://qwerty-validator.qa.nymte.ch/" diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 3d4e48f1ab..5d09ccb5c3 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "explorer-api" -version = "1.1.7" +version = "1.1.8" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/explorer/CHANGELOG.md b/explorer/CHANGELOG.md index b021d3babc..2bdbaeab0e 100644 --- a/explorer/CHANGELOG.md +++ b/explorer/CHANGELOG.md @@ -1,5 +1,15 @@ ## UNRELEASED +## [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]) +- Add gateway's last Routing Score to the gateways list page ([#2186]) +- Upgrade Sandbox and make below changes: ([#2332]) + +[#2913]: https://github.com/nymtech/nym/pull/2913 +[#2186]: https://github.com/nymtech/nym/issues/2186 +[#2332]: https://github.com/nymtech/nym/issues/2332 + ## [nym-explorer-v1.0.3](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.3) (2023-01-24) - Stake Saturation tooltip on node list and node pages updated ([#2877]) diff --git a/explorer/package.json b/explorer/package.json index 3ca03f5b56..c4b82c7a78 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -1,6 +1,6 @@ { "name": "@nym/network-explorer", - "version": "1.0.3", + "version": "1.0.4", "private": true, "license": "Apache-2.0", "dependencies": { @@ -120,4 +120,4 @@ "last 1 safari version" ] } -} +} \ No newline at end of file diff --git a/explorer/src/api/constants.ts b/explorer/src/api/constants.ts index e0488df565..81f6592c48 100644 --- a/explorer/src/api/constants.ts +++ b/explorer/src/api/constants.ts @@ -9,7 +9,7 @@ export const OVERVIEW_API = `${API_BASE_URL}/overview`; export const MIXNODE_PING = `${API_BASE_URL}/ping`; export const MIXNODES_API = `${API_BASE_URL}/mix-nodes`; export const MIXNODE_API = `${API_BASE_URL}/mix-node`; -export const GATEWAYS_API = `${NYM_API_BASE_URL}/api/v1/gateways`; +export const GATEWAYS_API = `${NYM_API_BASE_URL}/api/v1/status/gateways/detailed`; export const VALIDATORS_API = `${VALIDATOR_BASE_URL}/validators`; export const BLOCK_API = `${NYM_API_BASE_URL}/block`; export const COUNTRY_DATA_API = `${API_BASE_URL}/countries`; diff --git a/explorer/src/api/index.ts b/explorer/src/api/index.ts index 37e68b8096..49dcb04e85 100644 --- a/explorer/src/api/index.ts +++ b/explorer/src/api/index.ts @@ -15,7 +15,6 @@ import { CountryDataResponse, DelegationsResponse, UniqDelegationsResponse, - GatewayResponse, GatewayReportResponse, UptimeStoryResponse, MixNodeDescriptionResponse, @@ -27,7 +26,10 @@ import { StatusResponse, SummaryOverviewResponse, ValidatorsResponse, + GatewayBondAnnotated, + GatewayBond, } from '../typeDefs/explorer-api'; +import { toPercentIntegerString } from '../utils'; function getFromCache(key: string) { const ts = Number(localStorage.getItem('ts')); @@ -89,9 +91,13 @@ export class Api { return response.json(); }; - static fetchGateways = async (): Promise => { + static fetchGateways = async (): Promise => { const res = await fetch(GATEWAYS_API); - return res.json(); + const gatewaysAnnotated: GatewayBondAnnotated[] = await res.json(); + return gatewaysAnnotated.map(({ gateway_bond, performance }) => ({ + ...gateway_bond, + performance: toPercentIntegerString(performance), + })); }; static fetchGatewayUptimeStoryById = async (id: string): Promise => diff --git a/explorer/src/components/Gateways.ts b/explorer/src/components/Gateways.ts index aa055a71d0..1f81900ddc 100644 --- a/explorer/src/components/Gateways.ts +++ b/explorer/src/components/Gateways.ts @@ -1,4 +1,4 @@ -import { GatewayResponse, GatewayResponseItem, GatewayReportResponse } from '../typeDefs/explorer-api'; +import { GatewayResponse, GatewayBond, GatewayReportResponse } from '../typeDefs/explorer-api'; export type GatewayRowType = { id: string; @@ -8,6 +8,7 @@ export type GatewayRowType = { host: string; location: string; version: string; + performance: string; }; export type GatewayEnrichedRowType = GatewayRowType & { @@ -28,13 +29,11 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy bond: gw.pledge_amount.amount || 0, host: gw.gateway.host || '', version: gw.gateway.version || '', + performance: gw.performance, })); } -export function gatewayEnrichedToGridRow( - gateway: GatewayResponseItem, - report: GatewayReportResponse, -): GatewayEnrichedRowType { +export function gatewayEnrichedToGridRow(gateway: GatewayBond, report: GatewayReportResponse): GatewayEnrichedRowType { return { id: gateway.owner, owner: gateway.owner, @@ -47,5 +46,6 @@ export function gatewayEnrichedToGridRow( mixPort: gateway.gateway.mix_port || 0, routingScore: `${report.most_recent}%`, avgUptime: `${report.last_day || report.last_hour}%`, + performance: gateway.performance, }; } diff --git a/explorer/src/pages/GatewayDetail/index.tsx b/explorer/src/pages/GatewayDetail/index.tsx index 752b1be58a..6989fc2216 100644 --- a/explorer/src/pages/GatewayDetail/index.tsx +++ b/explorer/src/pages/GatewayDetail/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Alert, AlertTitle, Box, CircularProgress, Grid } from '@mui/material'; import { useParams } from 'react-router-dom'; -import { GatewayResponseItem } from '../../typeDefs/explorer-api'; +import { GatewayBond } from '../../typeDefs/explorer-api'; import { ColumnsType, DetailTable } from '../../components/DetailTable'; import { gatewayEnrichedToGridRow, GatewayEnrichedRowType } from '../../components/Gateways'; import { ComponentError } from '../../components/ComponentError'; @@ -69,7 +69,7 @@ const columns: ColumnsType[] = [ /** * Shows gateway details */ -const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: GatewayResponseItem | undefined }) => { +const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: GatewayBond | undefined }) => { const [enrichGateway, setEnrichGateway] = React.useState(); const [status, setStatus] = React.useState(); const { uptimeReport, uptimeStory } = useGatewayContext(); @@ -130,7 +130,7 @@ const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: Gat * Guard component to handle loading and not found states */ const PageGatewayDetailGuard: FCWithChildren = () => { - const [selectedGateway, setSelectedGateway] = React.useState(); + const [selectedGateway, setSelectedGateway] = React.useState(); const { gateways } = useMainContext(); const { id } = useParams<{ id: string | undefined }>(); diff --git a/explorer/src/pages/Gateways/index.tsx b/explorer/src/pages/Gateways/index.tsx index aa2b7077bd..ab93e4c4eb 100644 --- a/explorer/src/pages/Gateways/index.tsx +++ b/explorer/src/pages/Gateways/index.tsx @@ -81,6 +81,24 @@ export const PageGateways: FCWithChildren = () => { ), }, + { + field: 'performance', + headerName: 'Routing Score', + renderHeader: () => , + width: 150, + headerAlign: 'left', + headerClassName: 'MuiDataGrid-header-override', + renderCell: (params: GridRenderCellParams) => ( + + {`${params.value}%`} + + ), + }, { field: 'host', renderHeader: () => , diff --git a/explorer/src/typeDefs/explorer-api.ts b/explorer/src/typeDefs/explorer-api.ts index 188a0c3ac0..48cf2091d9 100644 --- a/explorer/src/typeDefs/explorer-api.ts +++ b/explorer/src/typeDefs/explorer-api.ts @@ -116,15 +116,21 @@ export interface StatsResponse { export type MixNodeHistoryResponse = StatsResponse; -export interface GatewayResponseItem { +export interface GatewayBond { block_height: number; pledge_amount: Amount; total_delegation: Amount; owner: string; gateway: Gateway; + performance: string; } -export type GatewayResponse = GatewayResponseItem[]; +export interface GatewayBondAnnotated { + gateway_bond: GatewayBond; + performance: string; +} + +export type GatewayResponse = GatewayBond[]; export interface GatewayReportResponse { identity: string; diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 3a6f126b69..5b1fd14ccf 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-gateway" -version = "1.1.7" +version = "1.1.8" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index 70f979b844..45548d7cd8 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -102,7 +102,7 @@ impl From for OverrideConfig { pub async fn execute(args: Init, output: OutputFormat) -> Result<(), Box> { println!("Initialising gateway {}...", args.id); - let already_init = if Config::default_config_file_path(Some(&args.id)).exists() { + let already_init = if Config::default_config_file_path(&args.id).exists() { eprintln!( "Gateway \"{}\" was already initialised before! Config information will be \ overwritten (but keys will be kept)!", diff --git a/gateway/src/commands/upgrade.rs b/gateway/src/commands/upgrade.rs index b4ea39c493..b3eefd9ed2 100644 --- a/gateway/src/commands/upgrade.rs +++ b/gateway/src/commands/upgrade.rs @@ -139,7 +139,7 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) { pub async fn execute(args: &Upgrade) { let package_version = parse_package_version(); - let existing_config = Config::load_from_file(Some(&args.id)).unwrap_or_else(|err| { + let existing_config = Config::load_from_file(&args.id).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {err}"); process::exit(1) }); diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 6f7f937594..924f361779 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -374,23 +374,23 @@ pub struct Gateway { impl Gateway { fn default_private_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_sphinx.pem") + Config::default_data_directory(id).join("private_sphinx.pem") } fn default_public_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_sphinx.pem") + Config::default_data_directory(id).join("public_sphinx.pem") } fn default_private_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_identity.pem") + Config::default_data_directory(id).join("private_identity.pem") } fn default_public_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_identity.pem") + Config::default_data_directory(id).join("public_identity.pem") } fn default_database_path(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("db.sqlite") + Config::default_data_directory(id).join("db.sqlite") } } diff --git a/gateway/src/support/config.rs b/gateway/src/support/config.rs index ff61d95da8..90576a4d89 100644 --- a/gateway/src/support/config.rs +++ b/gateway/src/support/config.rs @@ -11,14 +11,14 @@ pub(crate) fn build_config>( id: String, override_args: O, ) -> Result { - let config = match Config::load_from_file(Some(&id)) { + let config = match Config::load_from_file(&id) { Ok(cfg) => cfg, Err(err) => { error!( "Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})", ); return Err(GatewayError::ConfigLoadFailure { - path: Config::default_config_file_path(Some(&id)), + path: Config::default_config_file_path(&id), id, source: err, }); diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index ea5577f64c..69b4deb6e2 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-mixnode" -version = "1.1.8" +version = "1.1.9" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/mixnode/src/commands/describe.rs b/mixnode/src/commands/describe.rs index 5b66eb8d87..d9cecbe410 100644 --- a/mixnode/src/commands/describe.rs +++ b/mixnode/src/commands/describe.rs @@ -41,7 +41,7 @@ fn read_user_input() -> String { pub(crate) fn execute(args: Describe) { // ensure that the mixnode has in fact been initialized - match Config::load_from_file(Some(&args.id)) { + match Config::load_from_file(&args.id) { Ok(cfg) => cfg, Err(err) => { error!("Failed to load config for {}. Are you sure you have run `init` before? (Error was: {err})", &args.id); @@ -83,7 +83,7 @@ pub(crate) fn execute(args: Describe) { // save the struct NodeDescription::save_to_file( &node_description, - Config::default_config_directory(Some(&args.id)), + Config::default_config_directory(&args.id), ) .unwrap() } diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index d25b234ab8..fe6b18bc4f 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -68,7 +68,7 @@ pub(crate) fn execute(args: &Init, output: OutputFormat) { let id = &override_config_fields.id; eprintln!("Initialising mixnode {id}..."); - let already_init = if Config::default_config_file_path(Some(id)).exists() { + let already_init = if Config::default_config_file_path(id).exists() { eprintln!("Mixnode \"{id}\" was already initialised before! Config information will be overwritten (but keys will be kept)!"); true } else { diff --git a/mixnode/src/commands/node_details.rs b/mixnode/src/commands/node_details.rs index cdc0d5162a..98298cea74 100644 --- a/mixnode/src/commands/node_details.rs +++ b/mixnode/src/commands/node_details.rs @@ -15,7 +15,7 @@ pub(crate) struct NodeDetails { } pub(crate) fn execute(args: &NodeDetails, output: OutputFormat) { - let config = match Config::load_from_file(Some(&args.id)) { + let config = match Config::load_from_file(&args.id) { Ok(cfg) => cfg, Err(err) => { error!( diff --git a/mixnode/src/commands/run.rs b/mixnode/src/commands/run.rs index 910b9fc869..e578c422c1 100644 --- a/mixnode/src/commands/run.rs +++ b/mixnode/src/commands/run.rs @@ -79,7 +79,7 @@ fn special_addresses() -> Vec<&'static str> { pub(crate) async fn execute(args: &Run, output: OutputFormat) { eprintln!("Starting mixnode {}...", args.id); - let mut config = match Config::load_from_file(Some(&args.id)) { + let mut config = match Config::load_from_file(&args.id) { Ok(cfg) => cfg, Err(err) => { error!( diff --git a/mixnode/src/commands/sign.rs b/mixnode/src/commands/sign.rs index ed84cc6d3b..52360d7a03 100644 --- a/mixnode/src/commands/sign.rs +++ b/mixnode/src/commands/sign.rs @@ -71,7 +71,7 @@ fn print_signed_text(private_key: &identity::PrivateKey, text: &str) { } pub(crate) fn execute(args: &Sign) { - let config = match Config::load_from_file(Some(&args.id)) { + let config = match Config::load_from_file(&args.id) { Ok(cfg) => cfg, Err(err) => { error!( diff --git a/mixnode/src/commands/upgrade.rs b/mixnode/src/commands/upgrade.rs index 3ba4192289..46c8192312 100644 --- a/mixnode/src/commands/upgrade.rs +++ b/mixnode/src/commands/upgrade.rs @@ -125,7 +125,7 @@ fn do_upgrade(mut config: Config, args: &Upgrade, package_version: Version) { pub(crate) fn execute(args: &Upgrade) { let package_version = parse_package_version(); - let existing_config = Config::load_from_file(Some(&args.id)).unwrap_or_else(|err| { + let existing_config = Config::load_from_file(&args.id).unwrap_or_else(|err| { eprintln!("failed to load existing config file! - {err}"); process::exit(1) }); diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 2b69e45725..6183ba073e 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -376,19 +376,19 @@ struct MixNode { impl MixNode { fn default_private_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_identity.pem") + Config::default_data_directory(id).join("private_identity.pem") } fn default_public_identity_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_identity.pem") + Config::default_data_directory(id).join("public_identity.pem") } fn default_private_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("private_sphinx.pem") + Config::default_data_directory(id).join("private_sphinx.pem") } fn default_public_sphinx_key_file(id: &str) -> PathBuf { - Config::default_data_directory(Some(id)).join("public_sphinx.pem") + Config::default_data_directory(id).join("public_sphinx.pem") } } diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index ccf4a9a3db..e581860fbd 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -58,7 +58,7 @@ impl MixNode { } fn load_node_description(config: &Config) -> NodeDescription { - NodeDescription::load_from_file(Config::default_config_directory(Some(&config.get_id()))) + NodeDescription::load_from_file(Config::default_config_directory(&config.get_id())) .unwrap_or_default() } diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index ea1b70f0e5..a32ea8c807 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-api" -version = "1.1.7" +version = "1.1.8" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", diff --git a/nym-api/nym-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs index 237f4ce90d..e241fad0e1 100644 --- a/nym-api/nym-api-requests/src/models.rs +++ b/nym-api/nym-api-requests/src/models.rs @@ -291,7 +291,7 @@ pub struct GatewayUptimeHistoryResponse { #[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)] pub struct CirculatingSupplyResponse { - pub initial_supply: Coin, + pub total_supply: Coin, pub mixmining_reserve: Coin, pub vesting_tokens: Coin, pub circulating_supply: Coin, diff --git a/nym-api/src/circulating_supply_api/cache/data.rs b/nym-api/src/circulating_supply_api/cache/data.rs index 9fb68a8cac..17e906dff3 100644 --- a/nym-api/src/circulating_supply_api/cache/data.rs +++ b/nym-api/src/circulating_supply_api/cache/data.rs @@ -7,7 +7,7 @@ use validator_client::nyxd::Coin; pub(crate) struct CirculatingSupplyCacheData { // no need to cache that one as it's constant, but let's put it here for consistency sake - pub(crate) initial_supply: Coin, + pub(crate) total_supply: Coin, pub(crate) mixmining_reserve: Cache, pub(crate) vesting_tokens: Cache, pub(crate) circulating_supply: Cache, @@ -18,7 +18,7 @@ impl CirculatingSupplyCacheData { let zero_coin = Coin::new(0, &mix_denom); CirculatingSupplyCacheData { - initial_supply: Coin::new(1_000_000_000_000_000, mix_denom), + total_supply: Coin::new(1_000_000_000_000_000, mix_denom), mixmining_reserve: Cache::new(zero_coin.clone()), vesting_tokens: Cache::new(zero_coin.clone()), circulating_supply: Cache::new(zero_coin), @@ -29,7 +29,7 @@ impl CirculatingSupplyCacheData { impl<'a> From<&'a CirculatingSupplyCacheData> for CirculatingSupplyResponse { fn from(value: &'a CirculatingSupplyCacheData) -> Self { CirculatingSupplyResponse { - initial_supply: value.initial_supply.clone().into(), + total_supply: value.total_supply.clone().into(), mixmining_reserve: value.mixmining_reserve.clone().into_inner().into(), vesting_tokens: value.vesting_tokens.clone().into_inner().into(), circulating_supply: value.circulating_supply.clone().into_inner().into(), diff --git a/nym-api/src/circulating_supply_api/cache/mod.rs b/nym-api/src/circulating_supply_api/cache/mod.rs index 5e12477476..2f432c8d4d 100644 --- a/nym-api/src/circulating_supply_api/cache/mod.rs +++ b/nym-api/src/circulating_supply_api/cache/mod.rs @@ -76,7 +76,7 @@ impl CirculatingSupplyCache { pub(crate) async fn update(&self, mixmining_reserve: Coin, vesting_tokens: Coin) { let mut cache = self.data.write().await; - let mut circulating_supply = cache.initial_supply.clone(); + let mut circulating_supply = cache.total_supply.clone(); circulating_supply.amount -= mixmining_reserve.amount; circulating_supply.amount -= vesting_tokens.amount; diff --git a/nym-api/src/coconut/dkg/verification_key.rs b/nym-api/src/coconut/dkg/verification_key.rs index 15f0138a90..9aed655cfa 100644 --- a/nym-api/src/coconut/dkg/verification_key.rs +++ b/nym-api/src/coconut/dkg/verification_key.rs @@ -5,6 +5,7 @@ use crate::coconut::dkg::client::DkgClient; use crate::coconut::dkg::complaints::ComplaintReason; use crate::coconut::dkg::state::{ConsistentState, State}; use crate::coconut::error::CoconutError; +use crate::coconut::helpers::accepted_vote_err; use coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID; use coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS}; use coconut_dkg_common::verification_key::owner_from_cosmos_msgs; @@ -203,21 +204,23 @@ pub(crate) async fn verification_key_validation( .iter() .position(|node_index| contract_share.node_index == *node_index) { - if !check_vk_pairing(¶ms, &recovered_partials[idx], &vk) { + let ret = if !check_vk_pairing(¶ms, &recovered_partials[idx], &vk) { dkg_client .vote_verification_key_share(proposal_id, false) - .await?; + .await } else { dkg_client .vote_verification_key_share(proposal_id, true) - .await?; - } + .await + }; + accepted_vote_err(ret)?; } } Err(_) => { - dkg_client + let ret = dkg_client .vote_verification_key_share(proposal_id, false) - .await? + .await; + accepted_vote_err(ret)?; } } } diff --git a/nym-api/src/coconut/helpers.rs b/nym-api/src/coconut/helpers.rs new file mode 100644 index 0000000000..986bb6b305 --- /dev/null +++ b/nym-api/src/coconut/helpers.rs @@ -0,0 +1,18 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::error::CoconutError; +use validator_client::nyxd::error::NyxdError::AbciError; + +// If the result is already established, the vote might be redundant and +// thus the transaction might fail +pub(crate) fn accepted_vote_err(ret: Result<(), CoconutError>) -> Result<(), CoconutError> { + if let Err(CoconutError::NyxdError(AbciError { ref log, .. })) = ret { + let accepted_err = multisig_contract_common::error::ContractError::NotOpen {}.to_string(); + // If redundant voting is not the case, error out on all other error variants + if !log.value().contains(&accepted_err) { + ret?; + } + } + Ok(()) +} diff --git a/nym-api/src/coconut/mod.rs b/nym-api/src/coconut/mod.rs index a0aa042440..dd059923cb 100644 --- a/nym-api/src/coconut/mod.rs +++ b/nym-api/src/coconut/mod.rs @@ -5,6 +5,7 @@ use self::comm::APICommunicationChannel; use crate::coconut::client::Client as LocalClient; use crate::coconut::deposit::extract_encryption_key; use crate::coconut::error::{CoconutError, Result}; +use crate::coconut::helpers::accepted_vote_err; use crate::support::storage::NymApiStorage; use coconut_bandwidth_contract_common::spend_credential::{ funds_from_cosmos_msgs, SpendCredentialStatus, @@ -40,6 +41,7 @@ pub(crate) mod comm; mod deposit; pub(crate) mod dkg; pub(crate) mod error; +pub(crate) mod helpers; pub(crate) mod keypair; #[cfg(test)] pub(crate) mod tests; @@ -301,7 +303,7 @@ pub async fn verify_bandwidth_credential( ); // Vote yes or no on the proposal based on the verification result - state + let ret = state .client .vote_proposal( proposal_id, @@ -312,7 +314,8 @@ pub async fn verify_bandwidth_credential( Some(verify_credential_body.gateway_cosmos_addr().to_owned()), )), ) - .await?; + .await; + accepted_vote_err(ret)?; Ok(Json(VerifyCredentialResponse::new(vote_yes))) } diff --git a/nym-api/src/support/cli/mod.rs b/nym-api/src/support/cli/mod.rs index 674e29fc1c..3000177dbf 100644 --- a/nym-api/src/support/cli/mod.rs +++ b/nym-api/src/support/cli/mod.rs @@ -44,7 +44,7 @@ pub(crate) struct CliArgs { /// Id of the nym-api we want to run #[clap(long)] - pub(crate) id: Option, + pub(crate) id: String, /// Specifies whether network monitoring is enabled on this API #[clap(short = 'm', long)] @@ -102,12 +102,13 @@ pub(crate) struct CliArgs { } pub(crate) fn build_config(args: CliArgs) -> Result { + let id = args.id.clone(); + // try to load config from the file, if it doesn't exist, use default values - let id = args.id.as_deref(); - let (config_from_file, _already_initialized) = match Config::load_from_file(id) { + let (config_from_file, already_initialized) = match Config::load_from_file(&id) { Ok(cfg) => (cfg, true), Err(_) => { - let config_path = Config::default_config_file_path(id) + let config_path = Config::default_config_file_path(&id) .into_os_string() .into_string() .unwrap(); @@ -121,7 +122,17 @@ pub(crate) fn build_config(args: CliArgs) -> Result { let config = override_config(config_from_file, args); +<<<<<<< HEAD if !_already_initialized { +======= + if already_initialized { + fs::create_dir_all(Config::default_config_directory(&id)) + .expect("Could not create config directory"); + fs::create_dir_all(Config::default_data_directory(&id)) + .expect("Could not create data directory"); + + #[cfg(feature = "coconut")] +>>>>>>> release/v1.1.8 crate::coconut::dkg::controller::init_keypair(&config)?; } @@ -129,6 +140,7 @@ pub(crate) fn build_config(args: CliArgs) -> Result { } pub(crate) fn override_config(mut config: Config, args: CliArgs) -> Config { +<<<<<<< HEAD if let Some(id) = args.id { fs::create_dir_all(Config::default_config_directory(Some(&id))) .expect("Could not create config directory"); @@ -138,6 +150,10 @@ pub(crate) fn override_config(mut config: Config, args: CliArgs) -> Config { } config +======= + config = config + .with_id(&args.id) +>>>>>>> release/v1.1.8 .with_optional(Config::with_custom_nyxd_validator, args.nyxd_validator) .with_optional_env( Config::with_custom_mixnet_contract, diff --git a/nym-api/src/support/config/mod.rs b/nym-api/src/support/config/mod.rs index 50bdb34826..1b2645c393 100644 --- a/nym-api/src/support/config/mod.rs +++ b/nym-api/src/support/config/mod.rs @@ -201,8 +201,8 @@ pub struct NetworkMonitor { impl NetworkMonitor { pub const DB_FILE: &'static str = "credentials_database.db"; - fn default_credentials_database_path() -> PathBuf { - Config::default_data_directory(None).join(Self::DB_FILE) + fn default_credentials_database_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::DB_FILE) } } @@ -220,7 +220,7 @@ impl Default for NetworkMonitor { gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, gateway_connection_timeout: DEFAULT_GATEWAY_CONNECTION_TIMEOUT, packet_delivery_timeout: DEFAULT_PACKET_DELIVERY_TIMEOUT, - credentials_database_path: Self::default_credentials_database_path(), + credentials_database_path: Default::default(), test_routes: DEFAULT_TEST_ROUTES, minimum_test_routes: DEFAULT_MINIMUM_TEST_ROUTES, route_test_packets: DEFAULT_ROUTE_TEST_PACKETS, @@ -242,15 +242,15 @@ pub struct NodeStatusAPI { impl NodeStatusAPI { pub const DB_FILE: &'static str = "db.sqlite"; - fn default_database_path() -> PathBuf { - Config::default_data_directory(None).join(Self::DB_FILE) + fn default_database_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::DB_FILE) } } impl Default for NodeStatusAPI { fn default() -> Self { NodeStatusAPI { - database_path: Self::default_database_path(), + database_path: Default::default(), caching_interval: DEFAULT_NODE_STATUS_CACHE_INTERVAL, } } @@ -342,24 +342,24 @@ impl CoconutSigner { pub const COCONUT_VERIFICATION_KEY_FILE: &'static str = "coconut_verification_key.pem"; pub const COCONUT_SECRET_KEY_FILE: &'static str = "coconut_secret_key.pem"; - fn default_coconut_verification_key_path() -> PathBuf { - Config::default_data_directory(None).join(Self::COCONUT_VERIFICATION_KEY_FILE) + fn default_coconut_verification_key_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::COCONUT_VERIFICATION_KEY_FILE) } - fn default_coconut_secret_key_path() -> PathBuf { - Config::default_data_directory(None).join(Self::COCONUT_SECRET_KEY_FILE) + fn default_coconut_secret_key_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::COCONUT_SECRET_KEY_FILE) } - fn default_dkg_persistent_state_path() -> PathBuf { - Config::default_data_directory(None).join(Self::DKG_PERSISTENT_STATE_FILE) + fn default_dkg_persistent_state_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::DKG_PERSISTENT_STATE_FILE) } - fn default_dkg_decryption_key_path() -> PathBuf { - Config::default_data_directory(None).join(Self::DKG_DECRYPTION_KEY_FILE) + fn default_dkg_decryption_key_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::DKG_DECRYPTION_KEY_FILE) } - fn default_dkg_public_key_with_proof_path() -> PathBuf { - Config::default_data_directory(None).join(Self::DKG_PUBLIC_KEY_WITH_PROOF_FILE) + fn default_dkg_public_key_with_proof_path(id: &str) -> PathBuf { + Config::default_data_directory(id).join(Self::DKG_PUBLIC_KEY_WITH_PROOF_FILE) } } @@ -367,11 +367,11 @@ impl Default for CoconutSigner { fn default() -> Self { Self { enabled: Default::default(), - dkg_persistent_state_path: CoconutSigner::default_dkg_persistent_state_path(), - verification_key_path: CoconutSigner::default_coconut_verification_key_path(), - secret_key_path: CoconutSigner::default_coconut_secret_key_path(), - decryption_key_path: CoconutSigner::default_dkg_decryption_key_path(), - public_key_with_proof_path: CoconutSigner::default_dkg_public_key_with_proof_path(), + dkg_persistent_state_path: Default::default(), + verification_key_path: Default::default(), + secret_key_path: Default::default(), + decryption_key_path: Default::default(), + public_key_with_proof_path: Default::default(), dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE, } } @@ -384,20 +384,18 @@ impl Config { pub fn with_id(mut self, id: &str) -> Self { self.base.id = id.to_string(); - self.node_status_api.database_path = - Config::default_data_directory(Some(id)).join(NodeStatusAPI::DB_FILE); + self.node_status_api.database_path = NodeStatusAPI::default_database_path(id); self.network_monitor.credentials_database_path = - Config::default_data_directory(Some(id)).join(NetworkMonitor::DB_FILE); + NetworkMonitor::default_credentials_database_path(id); self.coconut_signer.dkg_persistent_state_path = - Config::default_data_directory(Some(id)).join(CoconutSigner::DKG_PERSISTENT_STATE_FILE); - self.coconut_signer.verification_key_path = Config::default_data_directory(Some(id)) - .join(CoconutSigner::COCONUT_VERIFICATION_KEY_FILE); - self.coconut_signer.secret_key_path = - Config::default_data_directory(Some(id)).join(CoconutSigner::COCONUT_SECRET_KEY_FILE); + CoconutSigner::default_dkg_persistent_state_path(id); + self.coconut_signer.verification_key_path = + CoconutSigner::default_coconut_verification_key_path(id); + self.coconut_signer.secret_key_path = CoconutSigner::default_coconut_secret_key_path(id); self.coconut_signer.decryption_key_path = - Config::default_data_directory(Some(id)).join(CoconutSigner::DKG_DECRYPTION_KEY_FILE); - self.coconut_signer.public_key_with_proof_path = Config::default_data_directory(Some(id)) - .join(CoconutSigner::DKG_PUBLIC_KEY_WITH_PROOF_FILE); + CoconutSigner::default_dkg_decryption_key_path(id); + self.coconut_signer.public_key_with_proof_path = + CoconutSigner::default_dkg_public_key_with_proof_path(id); self } diff --git a/nym-connect/CHANGELOG.md b/nym-connect/CHANGELOG.md index cddba036e0..c1bf4a7b85 100644 --- a/nym-connect/CHANGELOG.md +++ b/nym-connect/CHANGELOG.md @@ -2,6 +2,14 @@ ## UNRELEASED +## [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]) +- Copy changes to remove the dropdown: ([#2777]) + +[#2868]: https://github.com/nymtech/nym/issues/2868 +[#2777]: https://github.com/nymtech/nym/issues/2777 + ## [nym-connect-v1.1.7](https://github.com/nymtech/nym/tree/nym-connect-v1.1.7) (2023-01-24) - Remove test and earn ([#2865]) diff --git a/nym-connect/package.json b/nym-connect/package.json index 4fb0869862..127ac38de1 100644 --- a/nym-connect/package.json +++ b/nym-connect/package.json @@ -1,6 +1,6 @@ { "name": "@nym/nym-connect", - "version": "1.1.7", + "version": "1.1.8", "main": "index.js", "license": "MIT", "scripts": { @@ -43,7 +43,7 @@ "react-error-boundary": "^3.1.3", "react-hook-form": "^7.14.2", "react-markdown": "^8.0.4", - "react-router-dom": "^5.2.0", + "react-router-dom": "^6.7.0", "semver": "^6.3.0", "yup": "^0.32.9" }, @@ -113,4 +113,4 @@ "webpack-merge": "^5.8.0", "yaml-loader": "^0.8.0" } -} +} \ No newline at end of file diff --git a/nym-connect/src-tauri/Cargo.toml b/nym-connect/src-tauri/Cargo.toml index dd88c73c49..54c39aac73 100644 --- a/nym-connect/src-tauri/Cargo.toml +++ b/nym-connect/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-connect" -version = "1.1.7" +version = "1.1.8" description = "nym-connect" authors = ["Nym Technologies SA"] license = "" diff --git a/nym-connect/src-tauri/src/config/mod.rs b/nym-connect/src-tauri/src/config/mod.rs index b7fa03636b..9c51348b42 100644 --- a/nym-connect/src-tauri/src/config/mod.rs +++ b/nym-connect/src-tauri/src/config/mod.rs @@ -93,7 +93,7 @@ impl Config { } pub fn config_file_location(id: &str) -> Result { - Socks5Config::try_default_config_file_path(Some(id)) + Socks5Config::try_default_config_file_path(id) .ok_or(BackendError::CouldNotGetConfigFilename) } } diff --git a/nym-connect/src-tauri/src/error.rs b/nym-connect/src-tauri/src/error.rs index 50a672f8ac..11ae239f77 100644 --- a/nym-connect/src-tauri/src/error.rs +++ b/nym-connect/src-tauri/src/error.rs @@ -56,9 +56,9 @@ pub enum BackendError { InitializationPanic, #[error("could not get config id before gateway is set")] CouldNotGetIdWithoutGateway, - #[error("could initialize without gateway set")] + #[error("could not initialize without gateway set")] CouldNotInitWithoutGateway, - #[error("could initialize without service provider set")] + #[error("could not initialize without service provider set")] CouldNotInitWithoutServiceProvider, #[error("could not get file name")] CouldNotGetFilename, diff --git a/nym-connect/src-tauri/src/state.rs b/nym-connect/src-tauri/src/state.rs index 84526f1d11..949adfe8dc 100644 --- a/nym-connect/src-tauri/src/state.rs +++ b/nym-connect/src-tauri/src/state.rs @@ -154,7 +154,7 @@ impl State { pub fn load_socks5_config(&self) -> Result { let id = self.get_config_id()?; - let config = Socks5Config::load_from_file(Some(&id)) + let config = Socks5Config::load_from_file(&id) .tap_err(|_| log::warn!("Failed to load configuration file"))?; Ok(config) } diff --git a/nym-connect/src-tauri/src/tasks.rs b/nym-connect/src-tauri/src/tasks.rs index 423097d0ab..1cf53b55d3 100644 --- a/nym-connect/src-tauri/src/tasks.rs +++ b/nym-connect/src-tauri/src/tasks.rs @@ -41,7 +41,7 @@ pub fn start_nym_socks5_client( GatewayEndpointConfig, )> { log::info!("Loading config from file: {id}"); - let config = Socks5Config::load_from_file(Some(id)) + let config = Socks5Config::load_from_file(id) .tap_err(|_| log::warn!("Failed to load configuration file"))?; let used_gateway = config.get_base().get_gateway_endpoint().clone(); diff --git a/nym-connect/src-tauri/tauri.conf.json b/nym-connect/src-tauri/tauri.conf.json index 96cc9f0fa6..deff29513f 100644 --- a/nym-connect/src-tauri/tauri.conf.json +++ b/nym-connect/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-connect", - "version": "1.1.7" + "version": "1.1.8" }, "build": { "distDir": "../dist", @@ -19,7 +19,13 @@ "active": true, "targets": "all", "identifier": "net.nymtech.connect", - "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], "resources": [], "externalBin": [], "copyright": "Copyright © 2021-2022 Nym Technologies SA", @@ -44,7 +50,9 @@ }, "updater": { "active": true, - "endpoints": ["https://nymtech.net/.wellknown/connect/updater.json"], + "endpoints": [ + "https://nymtech.net/.wellknown/connect/updater.json" + ], "dialog": true, "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENCNzQ2M0E5N0VFODE2NApSV1JrZ2U2WE9rYTNETTg1OTBKdE5uWUEra0hML2syOVUvQ2lxZmFZRzZ1T3NWbGM0eVRzUTVhVwo=" }, @@ -68,7 +76,7 @@ { "title": "NymConnect", "width": 240, - "height": 635, + "height": 480, "resizable": false, "decorations": false, "transparent": true @@ -78,4 +86,4 @@ "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" } } -} +} \ No newline at end of file diff --git a/nym-connect/src/App.tsx b/nym-connect/src/App.tsx index 3b3295ccd8..9d57dcf4d8 100644 --- a/nym-connect/src/App.tsx +++ b/nym-connect/src/App.tsx @@ -1,17 +1,15 @@ import React, { useEffect } from 'react'; import { DateTime } from 'luxon'; import { forage } from '@tauri-apps/tauri-forage'; -import { ConnectionStatusKind } from './types'; import { useClientContext } from './context/main'; -import { DefaultLayout } from './layouts/DefaultLayout'; -import { ConnectedLayout } from './layouts/ConnectedLayout'; -import { HelpGuideLayout } from './layouts/HelpGuideLayout'; 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(); - const [showInfoModal, setShowInfoModal] = React.useState(false); + useTauriEvents('help://clear-storage', (_event) => { console.log('About to clear local storage...'); // clear local storage @@ -25,16 +23,16 @@ export const App: FCWithChildren = () => { const handleConnectClick = React.useCallback(async () => { const currentStatus = context.connectionStatus; - if (currentStatus === ConnectionStatusKind.connected || currentStatus === ConnectionStatusKind.disconnected) { + if (currentStatus === 'connected' || currentStatus === 'disconnected') { setBusy(true); // eslint-disable-next-line default-case switch (currentStatus) { - case ConnectionStatusKind.disconnected: + case 'disconnected': await context.startConnecting(); context.setConnectedSince(DateTime.now()); break; - case ConnectionStatusKind.connected: + case 'connected': await context.startDisconnecting(); context.setConnectedSince(undefined); break; @@ -43,40 +41,22 @@ export const App: FCWithChildren = () => { } }, [context.connectionStatus]); - useEffect(() => { - if (context.connectionStatus === ConnectionStatusKind.connected) setShowInfoModal(true); - }, [context.connectionStatus]); - - if (context.showHelp) return ; - - if ( - context.connectionStatus === ConnectionStatusKind.disconnected || - context.connectionStatus === ConnectionStatusKind.connecting - ) { - return ( - - ); + if (context.connectionStatus === 'disconnected' || context.connectionStatus === 'connecting') { + return ; } return ( - setShowInfoModal(false)} + context.setShowInfoModal(false)} busy={busy} onConnectClick={handleConnectClick} ipAddress="127.0.0.1" port={1080} gatewayPerformance={context.gatewayPerformance} connectedSince={context.connectedSince} - serviceProvider={context.serviceProvider} + serviceProvider={context.selectedProvider} stats={[ { label: 'in:', diff --git a/nym-connect/src/assets/help-step-one-legacy.png b/nym-connect/src/assets/help-step-one-legacy.png new file mode 100644 index 0000000000..5e91a29cff Binary files /dev/null and b/nym-connect/src/assets/help-step-one-legacy.png differ diff --git a/nym-connect/src/assets/help-step-one.png b/nym-connect/src/assets/help-step-one.png index 5e91a29cff..482e752286 100644 Binary files a/nym-connect/src/assets/help-step-one.png and b/nym-connect/src/assets/help-step-one.png differ diff --git a/nym-connect/src/assets/help-step-two-legacy.png b/nym-connect/src/assets/help-step-two-legacy.png new file mode 100644 index 0000000000..f2910c98cf Binary files /dev/null and b/nym-connect/src/assets/help-step-two-legacy.png differ diff --git a/nym-connect/src/assets/help-step-two.png b/nym-connect/src/assets/help-step-two.png index f2910c98cf..c6f4429d74 100644 Binary files a/nym-connect/src/assets/help-step-two.png and b/nym-connect/src/assets/help-step-two.png differ diff --git a/nym-connect/src/components/AppVersion.tsx b/nym-connect/src/components/AppVersion.tsx index 2a0250e86b..4ced230efa 100644 --- a/nym-connect/src/components/AppVersion.tsx +++ b/nym-connect/src/components/AppVersion.tsx @@ -6,8 +6,8 @@ export const AppVersion = () => { const { appVersion } = useClientContext(); return ( - - + + Version {appVersion} diff --git a/nym-connect/src/components/AppWindowFrame.tsx b/nym-connect/src/components/AppWindowFrame.tsx index 89a68f7f66..164ac64baf 100644 --- a/nym-connect/src/components/AppWindowFrame.tsx +++ b/nym-connect/src/components/AppWindowFrame.tsx @@ -1,20 +1,21 @@ import React from 'react'; import { Box } from '@mui/material'; import { CustomTitleBar } from './CustomTitleBar'; -import { AppVersion } from './AppVersion'; +import { useLocation, useParams } from 'react-router-dom'; -export const AppWindowFrame: FCWithChildren = ({ children }) => ( - - - {children} - - -); +export const AppWindowFrame: FCWithChildren = ({ children }) => { + const location = useLocation(); + + return ( + + + {children} + + ); +}; diff --git a/nym-connect/src/components/ConnectionButton.tsx b/nym-connect/src/components/ConnectionButton.tsx index 29fb90c1db..1dd72f78df 100644 --- a/nym-connect/src/components/ConnectionButton.tsx +++ b/nym-connect/src/components/ConnectionButton.tsx @@ -10,13 +10,13 @@ const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isErro } switch (status) { - case ConnectionStatusKind.disconnected: + case 'disconnected': if (hover) { return '#FFFF33'; } return '#FFE600'; - case ConnectionStatusKind.connecting: - case ConnectionStatusKind.disconnecting: + case 'connecting': + case 'disconnecting': return '#FFE600'; default: // connected @@ -29,11 +29,11 @@ const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isErro const getStatusText = (status: ConnectionStatusKind, hover: boolean): string => { switch (status) { - case ConnectionStatusKind.disconnected: + case 'disconnected': return 'Connect'; - case ConnectionStatusKind.connecting: + case 'connecting': return 'Connecting'; - case ConnectionStatusKind.disconnecting: + case 'disconnecting': return 'Connected'; default: // connected @@ -89,7 +89,7 @@ export const ConnectionButton: FCWithChildren<{ - {status === ConnectionStatusKind.connected && hover ? ( + {status === 'connected' && hover ? ( = ({ status }) => { + serviceProvider?: ServiceProvider; + gatewayError: boolean; +}> = ({ status, serviceProvider, gatewayError }) => { + if (gatewayError) { + return ( + : undefined}> + + + + Gateway has issues + + + + ); + } switch (status) { - case ConnectionStatusKind.connected: + case 'connected': return ( - - Connected to + : undefined}> + + + + Connected to Nym Mixnet + + + + ); + case 'disconnected': + return ( + + Connect to the mixnet ); - case ConnectionStatusKind.disconnecting: + case 'disconnecting': return ( - + Disconnecting... ); - case ConnectionStatusKind.connecting: + case 'connecting': return ( @@ -37,20 +70,7 @@ const ConnectionStatusContent: FCWithChildren<{ ); - case ConnectionStatusKind.disconnected: - return ( - - You are not protected - - ); + default: return null; } @@ -62,29 +82,18 @@ export const ConnectionStatus: FCWithChildren<{ connectedSince?: DateTime; serviceProvider?: ServiceProvider; }> = ({ status, serviceProvider, gatewayPerformance }) => { - const color = - status === ConnectionStatusKind.connected || status === ConnectionStatusKind.disconnecting - ? '#21D072' - : 'warning.main'; + const color = status === 'connected' || status === 'disconnecting' ? '#21D072' : 'white'; + console.log(gatewayPerformance); return ( <> - - {status === ConnectionStatusKind.connected && gatewayPerformance !== 'Good' ? ( - - Gateway has issues - - ) : ( - - )} + + - {serviceProvider ? ( - }> - - {serviceProvider && {serviceProvider.description}} - - - ) : null} ); }; diff --git a/nym-connect/src/components/CustomTitleBar.tsx b/nym-connect/src/components/CustomTitleBar.tsx index 41c71137b9..85a89fd540 100644 --- a/nym-connect/src/components/CustomTitleBar.tsx +++ b/nym-connect/src/components/CustomTitleBar.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { ArrowBack, Close, HelpOutline, Minimize } from '@mui/icons-material'; -import { Box, IconButton } from '@mui/material'; +import { ArrowBack, Close, Menu, Minimize } from '@mui/icons-material'; +import { Box, IconButton, Typography } from '@mui/material'; import { NymWordmark } from '@nymproject/react/logo/NymWordmark'; import { appWindow } from '@tauri-apps/api/window'; -import { useClientContext } from 'src/context/main'; +import { useNavigate } from 'react-router-dom'; const customTitleBarStyles = { titlebar: { @@ -24,22 +24,34 @@ const CustomButton = ({ Icon, onClick }: { Icon: React.JSXElementConstructor ); -export const CustomTitleBar = () => { - const { showHelp, handleShowHelp } = useClientContext(); +const MenuIcon = () => { + const navigate = useNavigate(); + return navigate('/menu')} />; +}; + +const ArrowBackIcon = () => { + const navigate = useNavigate(); + return navigate(-1)} />; +}; + +const getTitleIcon = (path: string) => { + if (path !== '/') { + const title = path.split('/').slice(-1); + return ( + + {title} + + ); + } + return ; +}; + +export const CustomTitleBar = ({ path = '/' }: { path?: string }) => { return ( {/* set width to keep logo centered */} - - { - handleShowHelp(); - }} - /> - - - - + {path === '/' ? : } + {getTitleIcon(path)} appWindow.minimize()} /> appWindow.close()} /> diff --git a/nym-connect/src/components/ExperimentalWarning.tsx b/nym-connect/src/components/ExperimentalWarning.tsx new file mode 100644 index 0000000000..122f741d0b --- /dev/null +++ b/nym-connect/src/components/ExperimentalWarning.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { Box, Typography } from '@mui/material'; + +export const ExperimentalWarning = () => ( + + + This is experimental software. + + + Do not rely on it for strong anonymity (yet). + + +); diff --git a/nym-connect/src/components/Growth/TestAndEarnButtonArea.tsx b/nym-connect/src/components/Growth/TestAndEarnButtonArea.tsx index 92c88ec321..ffca05043b 100644 --- a/nym-connect/src/components/Growth/TestAndEarnButtonArea.tsx +++ b/nym-connect/src/components/Growth/TestAndEarnButtonArea.tsx @@ -25,7 +25,7 @@ export const Wrapper: FCWithChildren<{ disabled: boolean }> = ({ disabled, child export const TestAndEarnButtonArea: FCWithChildren = () => { const clientContext = useClientContext(); const context = useTestAndEarnContext(); - const disabled = clientContext.connectionStatus !== ConnectionStatusKind.connected; + const disabled = clientContext.connectionStatus !== 'connected'; const pinger = React.useRef(); const doPing = async () => { diff --git a/nym-connect/src/components/Growth/TestAndEarnPopup.tsx b/nym-connect/src/components/Growth/TestAndEarnPopup.tsx index dc1148c1e5..2535f2ef6f 100644 --- a/nym-connect/src/components/Growth/TestAndEarnPopup.tsx +++ b/nym-connect/src/components/Growth/TestAndEarnPopup.tsx @@ -28,7 +28,7 @@ export const TestAndEarnPopupContent: FCWithChildren<{ ); } - if (!connectionStatus || connectionStatus === ConnectionStatusKind.disconnected) { + if (!connectionStatus || connectionStatus === 'disconnected') { return ( @@ -36,7 +36,7 @@ export const TestAndEarnPopupContent: FCWithChildren<{ ); } - if (connectionStatus === ConnectionStatusKind.connecting || connectionStatus === ConnectionStatusKind.disconnecting) { + if (connectionStatus === 'connecting' || connectionStatus === 'disconnecting') { return ( @@ -77,7 +77,7 @@ export const TestAndEarnPopup: FCWithChildren = () => { const context = useTestAndEarnContext(); React.useEffect(() => { - if (clientContext.connectionStatus === ConnectionStatusKind.connected) { + if (clientContext.connectionStatus === 'connected') { context.refresh(); } }, [clientContext.connectionStatus]); @@ -94,7 +94,7 @@ export const TestAndEarnPopup: FCWithChildren = () => { return () => clearInterval(interval); }, []); - if (!context.loadedOnce && clientContext.connectionStatus === ConnectionStatusKind.connected) { + if (!context.loadedOnce && clientContext.connectionStatus === 'connected') { const message = 'Waiting for data to be transferred over the mixnet...'; return ( diff --git a/nym-connect/src/components/Growth/context/TestAndEarnContext.tsx b/nym-connect/src/components/Growth/context/TestAndEarnContext.tsx index a28fd221e2..5c4776a4a7 100644 --- a/nym-connect/src/components/Growth/context/TestAndEarnContext.tsx +++ b/nym-connect/src/components/Growth/context/TestAndEarnContext.tsx @@ -151,7 +151,7 @@ export const TestAndEarnContextProvider: FCWithChildren = ({ children }) => { }, []); React.useEffect(() => { - if (registration && clientContext.connectionStatus === ConnectionStatusKind.connected) { + if (registration && clientContext.connectionStatus === 'connected') { setTimeout(() => { loadDraws().catch(console.error); }, 1000 * 3); diff --git a/nym-connect/src/components/HelpPage.tsx b/nym-connect/src/components/HelpPage.tsx index 788f7264d5..5cc7815117 100644 --- a/nym-connect/src/components/HelpPage.tsx +++ b/nym-connect/src/components/HelpPage.tsx @@ -6,24 +6,26 @@ import { StepIndicator } from './HelpPageStepIndicator'; export const HelpPage = ({ step, + totalSteps, description, img, onNext, onPrev, }: { step: number; + totalSteps: number; description: string; img: any; onNext?: () => void; onPrev?: () => void; }) => ( - + - How to connect guide {step}/4 + How to connect guide {step}/{totalSteps} - + {description} diff --git a/nym-connect/src/components/HelpPageImage.tsx b/nym-connect/src/components/HelpPageImage.tsx index 27a8dcdca3..8e9920ca4d 100644 --- a/nym-connect/src/components/HelpPageImage.tsx +++ b/nym-connect/src/components/HelpPageImage.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import { Box } from '@mui/material'; -export const HelpImage = ({ img, imageDescription }: { img: any; imageDescription: string }) => ( - {imageDescription} +export const HelpImage = ({ img, imageDescription }: { img: string; imageDescription: string }) => ( + {imageDescription} ); diff --git a/nym-connect/src/components/HelpPageStepIndicator.tsx b/nym-connect/src/components/HelpPageStepIndicator.tsx index b5dff42b34..02d0b3338e 100644 --- a/nym-connect/src/components/HelpPageStepIndicator.tsx +++ b/nym-connect/src/components/HelpPageStepIndicator.tsx @@ -1,15 +1,14 @@ -import { Box } from '@mui/material'; import React from 'react'; +import { Box } from '@mui/material'; const Step = ({ highlight }: { highlight: boolean }) => ( - + ); export const StepIndicator = ({ step }: { step: number }) => ( - + = 2} /> = 3} /> - = 4} /> ); diff --git a/nym-connect/src/components/InfoModal.tsx b/nym-connect/src/components/InfoModal.tsx index ed628de7eb..8b1157689e 100644 --- a/nym-connect/src/components/InfoModal.tsx +++ b/nym-connect/src/components/InfoModal.tsx @@ -18,7 +18,7 @@ const styles = { const ModalTitle = ({ title, withCloseIcon }: { title: string; withCloseIcon: boolean }) => ( - + {title} @@ -27,7 +27,7 @@ const ModalTitle = ({ title, withCloseIcon }: { title: string; withCloseIcon: bo const ModalBody = ({ description, children }: { description: string; children?: React.ReactElement }) => ( {children} - + {description} diff --git a/nym-connect/src/components/IpAddressAndPortModal.tsx b/nym-connect/src/components/IpAddressAndPortModal.tsx index 8ed6d51e20..8afe0acd1b 100644 --- a/nym-connect/src/components/IpAddressAndPortModal.tsx +++ b/nym-connect/src/components/IpAddressAndPortModal.tsx @@ -3,6 +3,9 @@ import { Box, Button, Typography } from '@mui/material'; import { InfoModal } from './InfoModal'; import { CopyToClipboard } from './CopyToClipboard'; +const FONT_SIZE = '12px'; +const FONT_COLOR = 'grey.400'; + export const IpAddressAndPortModal = ({ show, ipAddress, @@ -16,12 +19,15 @@ export const IpAddressAndPortModal = ({ }) => ( Done} + title="You are half way there" + description="Check NymConnect menu for supported apps" + onClose={onClose} > - + + Paste below values in the proxy settings of your app + + Socks5 address @@ -29,7 +35,7 @@ export const IpAddressAndPortModal = ({ - + Port diff --git a/nym-connect/src/components/PowerButton.tsx b/nym-connect/src/components/PowerButton.tsx new file mode 100644 index 0000000000..426028a767 --- /dev/null +++ b/nym-connect/src/components/PowerButton.tsx @@ -0,0 +1,132 @@ +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/ServiceProviderInfo.tsx b/nym-connect/src/components/TooltipInfo.tsx similarity index 60% rename from nym-connect/src/components/ServiceProviderInfo.tsx rename to nym-connect/src/components/TooltipInfo.tsx index a6289b5c41..2d76c6859e 100644 --- a/nym-connect/src/components/ServiceProviderInfo.tsx +++ b/nym-connect/src/components/TooltipInfo.tsx @@ -7,14 +7,23 @@ export const ServiceProviderInfo = ({ serviceProvider }: { serviceProvider: Serv Connection info - {serviceProvider.description} Gateway {serviceProvider.gateway} - Provider {serviceProvider.address.slice(0, 35)}... + Service provider {serviceProvider.address.slice(0, 35)}... ); + +export const GatwayWarningInfo = () => ( + + + Connection issue + + + Try disconnecting and connecting again + +); diff --git a/nym-connect/src/context/main.tsx b/nym-connect/src/context/main.tsx index bba01764e7..3ea8a3cad2 100644 --- a/nym-connect/src/context/main.tsx +++ b/nym-connect/src/context/main.tsx @@ -21,19 +21,17 @@ export type TClientContext = { connectionStatus: ConnectionStatusKind; connectionStats?: ConnectionStatsItem[]; connectedSince?: DateTime; - services?: Services; - serviceProvider?: ServiceProvider; - showHelp: boolean; error?: Error; gatewayPerformance: GatewayPerformance; + selectedProvider?: ServiceProvider; + showInfoModal: boolean; setMode: (mode: ModeType) => void; clearError: () => void; - handleShowHelp: () => void; setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void; setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void; setConnectedSince: (connectedSince: DateTime | undefined) => void; - setServiceProvider: (serviceProvider?: ServiceProvider) => void; - + setShowInfoModal: (show: boolean) => void; + setRandomSerivceProvider: () => void; startConnecting: () => Promise; startDisconnecting: () => Promise; }; @@ -42,28 +40,40 @@ export const ClientContext = createContext({} as TClientContext); export const ClientContextProvider: FCWithChildren = ({ children }) => { const [mode, setMode] = useState('dark'); - const [connectionStatus, setConnectionStatus] = useState(ConnectionStatusKind.disconnected); + const [connectionStatus, setConnectionStatus] = useState(ConnectionStatusKind.connected); const [connectionStats, setConnectionStats] = useState(); const [connectedSince, setConnectedSince] = useState(); - const [services, setServices] = React.useState([]); - const [serviceProvider, setRawServiceProvider] = React.useState(); - const [showHelp, setShowHelp] = useState(false); + const [selectedProvider, setSelectedProvider] = React.useState(); + const [serviceProviders, setServiceProviders] = React.useState(); const [error, setError] = useState(); const [appVersion, setAppVersion] = useState(); const [gatewayPerformance, setGatewayPerformance] = useState('Good'); + const [showInfoModal, setShowInfoModal] = useState(false); const getAppVersion = async () => { const version = await getVersion(); - setAppVersion(version); + return version; }; 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(); + + setAppVersion(AppVersion); + setServiceProviders(allServiceProviders); + }; + useEffect(() => { - invoke('get_services').then((result) => { - setServices(result as Services); - }); - getAppVersion(); + initialiseApp(); }, []); useEffect(() => { @@ -129,12 +139,18 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { const startDisconnecting = useCallback(async () => { try { await invoke('start_disconnecting'); - setGatewayPerformance('Good'); } catch (e) { console.log(e); } }, []); + const setServiceProvider = async (newServiceProvider?: ServiceProvider) => { + if (newServiceProvider) { + await invoke('set_gateway', { gateway: newServiceProvider.gateway }); + await invoke('set_service_provider', { serviceProvider: newServiceProvider.address }); + } + }; + const setSpInStorage = async (sp: ServiceProvider) => { await forage.setItem({ key: 'nym-connect-sp', @@ -142,51 +158,36 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { } as any)(); }; - const setServiceProvider = useCallback(async (newServiceProvider?: ServiceProvider) => { - await invoke('set_gateway', { gateway: newServiceProvider?.gateway }); - await invoke('set_service_provider', { serviceProvider: newServiceProvider?.address }); - if (newServiceProvider) { - await setSpInStorage(newServiceProvider); - } - setRawServiceProvider(newServiceProvider); - }, []); + const removeSpFromStorage = async () => { + await forage.removeItem({ + key: 'nym-connect-sp', + })(); + }; - const getSpFromStorage = async () => { + const getSpFromStorage = async (): Promise => { try { const spFromStorage = await forage.getItem({ key: 'nym-connect-sp' })(); - if (spFromStorage) { - setRawServiceProvider(spFromStorage); - setServiceProvider(spFromStorage); - } + return spFromStorage; } catch (e) { console.warn(e); } }; - const handleShowHelp = () => setShowHelp((show) => !show); + const getRandomSPFromList = (serviceProviders: ServiceProvider[]) => { + const randomSelection = serviceProviders[Math.floor(Math.random() * serviceProviders.length)]; + return randomSelection; + }; + + const setRandomSerivceProvider = async () => { + if (serviceProviders) { + const randomServiceProvider = getRandomSPFromList(serviceProviders); + await setServiceProvider(randomServiceProvider); + setSelectedProvider(randomServiceProvider); + } + }; const clearError = () => setError(undefined); - useEffect(() => { - const validityCheck = async () => { - if (services.length > 0 && serviceProvider) { - const isValid = services.some(({ items }) => items.some(({ id }) => id === serviceProvider.id)); - if (!isValid) { - console.warn('invalid SP, cleaning local storage'); - await forage.removeItem({ - key: 'nym-connect-sp', - })(); - setRawServiceProvider(undefined); - } - } - }; - validityCheck(); - }, [services, serviceProvider]); - - useEffect(() => { - getSpFromStorage(); - }, []); - const contextValue = useMemo( () => ({ mode, @@ -197,31 +198,28 @@ export const ClientContextProvider: FCWithChildren = ({ children }) => { connectionStatus, setConnectionStatus, connectionStats, + showInfoModal, setConnectionStats, + selectedProvider, connectedSince, setConnectedSince, + setRandomSerivceProvider, startConnecting, startDisconnecting, - services, - serviceProvider, - setServiceProvider, - showHelp, - handleShowHelp, gatewayPerformance, + setShowInfoModal, }), [ - appVersion, mode, appVersion, error, + showInfoModal, connectedSince, - showHelp, connectionStatus, connectionStats, connectedSince, - services, - serviceProvider, gatewayPerformance, + selectedProvider, ], ); diff --git a/nym-connect/src/context/mocks/main.tsx b/nym-connect/src/context/mocks/main.tsx index 9b4341cc23..ea86bb365d 100644 --- a/nym-connect/src/context/mocks/main.tsx +++ b/nym-connect/src/context/mocks/main.tsx @@ -6,19 +6,18 @@ const mockValues: TClientContext = { appVersion: 'v1.x.x', mode: 'dark', connectionStatus: ConnectionStatusKind.disconnected, - services: [], - showHelp: false, - serviceProvider: { id: '1', description: 'Keybase service provider', gateway: 'abc123', address: '123abc' }, + selectedProvider: { id: '1', description: 'Keybase service provider', gateway: 'abc123', address: '123abc' }, gatewayPerformance: 'Good', + showInfoModal: false, + setShowInfoModal: () => {}, setMode: () => {}, clearError: () => {}, - handleShowHelp: () => {}, setConnectedSince: () => {}, setConnectionStats: () => {}, setConnectionStatus: () => {}, - setServiceProvider: () => {}, startConnecting: async () => {}, startDisconnecting: async () => {}, + setRandomSerivceProvider: () => {}, }; export const MockProvider: FCWithChildren<{ diff --git a/nym-connect/src/index.tsx b/nym-connect/src/index.tsx index 8f682fd98b..7041b86787 100644 --- a/nym-connect/src/index.tsx +++ b/nym-connect/src/index.tsx @@ -7,6 +7,9 @@ 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'; +import { AppRoutes } from './routes'; +import { GlobalStyles } from '@mui/material'; const elem = document.getElementById('root'); @@ -14,15 +17,18 @@ if (elem) { const root = createRoot(elem); root.render( - - - - - - - - - + + + + + + + + + + + + , ); } diff --git a/nym-connect/src/layouts/ConnectedLayout.tsx b/nym-connect/src/layouts/ConnectedLayout.tsx deleted file mode 100644 index da4c757208..0000000000 --- a/nym-connect/src/layouts/ConnectedLayout.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import { Box, Divider } from '@mui/material'; -import { DateTime } from 'luxon'; -import { IpAddressAndPortModal } from 'src/components/IpAddressAndPortModal'; -import { ConnectionTimer } from 'src/components/ConntectionTimer'; -import { ConnectionStatus } from '../components/ConnectionStatus'; -import { ConnectionStatusKind, GatewayPerformance } from '../types'; -import { ConnectionStatsItem } from '../components/ConnectionStats'; -import { ConnectionButton } from '../components/ConnectionButton'; -import { IpAddressAndPort } from '../components/IpAddressAndPort'; -import { ServiceProvider } from '../types/directory'; -import { TestAndEarnButtonArea } from '../components/Growth/TestAndEarnButtonArea'; - -export const ConnectedLayout: FCWithChildren<{ - status: ConnectionStatusKind; - gatewayPerformance: GatewayPerformance; - stats: ConnectionStatsItem[]; - ipAddress: string; - port: number; - connectedSince?: DateTime; - busy?: boolean; - showInfoModal: boolean; - isError?: boolean; - handleCloseInfoModal: () => void; - onConnectClick?: (status: ConnectionStatusKind) => void; - serviceProvider?: ServiceProvider; -}> = ({ - status, - gatewayPerformance, - showInfoModal, - handleCloseInfoModal, - ipAddress, - port, - connectedSince, - busy, - isError, - serviceProvider, - onConnectClick, -}) => ( - <> - - - - - - - - - {/* */} - - - -); diff --git a/nym-connect/src/layouts/ConnectionLayout.tsx b/nym-connect/src/layouts/ConnectionLayout.tsx new file mode 100644 index 0000000000..d37c7ec888 --- /dev/null +++ b/nym-connect/src/layouts/ConnectionLayout.tsx @@ -0,0 +1,30 @@ +import { Box } from '@mui/material'; +import React from 'react'; + +const layout = { + display: 'grid', + gridTemplateColumns: '1fr', + gridTemplateRows: '80px 180px 1fr', + gridColumnGap: '0px', + gridRowGap: '4px', + overflow: 'hidden', + height: '100%', +}; + +export const ConnectionLayout = ({ + TopContent, + ConnectButton, + BottomContent, +}: { + TopContent: React.ReactNode; + ConnectButton: React.ReactNode; + BottomContent: React.ReactNode; +}) => ( + + {TopContent} + + {ConnectButton} + + {BottomContent} + +); diff --git a/nym-connect/src/layouts/DefaultLayout.tsx b/nym-connect/src/layouts/DefaultLayout.tsx deleted file mode 100644 index 29858afe7f..0000000000 --- a/nym-connect/src/layouts/DefaultLayout.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { Box, Typography } from '@mui/material'; -import { ConnectionStatus } from 'src/components/ConnectionStatus'; -import { ConnectionTimer } from 'src/components/ConntectionTimer'; -import { InfoModal } from 'src/components/InfoModal'; -import { Error } from 'src/types/error'; -import { ConnectionButton } from '../components/ConnectionButton'; -import { ServiceSelector } from '../components/ServiceSelector'; -import { useClientContext } from '../context/main'; -import { ConnectionStatusKind } from '../types'; -import { Services } from '../types/directory'; -import { TestAndEarnButtonArea } from '../components/Growth/TestAndEarnButtonArea'; -import { AppVersion } from '../components/AppVersion'; - -export const DefaultLayout: FCWithChildren<{ - error?: Error; - status: ConnectionStatusKind; - services?: Services; - busy?: boolean; - isError?: boolean; - clearError: () => void; - onConnectClick?: (status: ConnectionStatusKind) => void; -}> = ({ status, error, services, busy, isError, onConnectClick, clearError }) => { - const context = useClientContext(); - - return ( - - {error && } - - - - Connect to the Nym
mixnet for privacy. -
- - This is experimental software. Do not rely on it for strong anonymity (yet). - -
- - - -
- ); -}; diff --git a/nym-connect/src/layouts/HelpGuideLayout.tsx b/nym-connect/src/layouts/HelpGuideLayout.tsx deleted file mode 100644 index d56fd6b0ae..0000000000 --- a/nym-connect/src/layouts/HelpGuideLayout.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useState } from 'react'; -import { HelpPage } from 'src/components/HelpPage'; -import { Button, Link, Stack } from '@mui/material'; -import Image1 from '../assets/help-step-one.png'; -import Image2 from '../assets/help-step-two.png'; -import Image3 from '../assets/help-step-three.png'; -import Image4 from '../assets/help-step-four.png'; - -export const HelpGuideLayout = () => { - const [step, setStep] = useState(0); - - if (step === 1) - return ( - setStep(2)} - /> - ); - - if (step === 2) - return ( - setStep(1)} - onNext={() => setStep(3)} - /> - ); - - if (step === 3) - return ( - setStep(2)} - onNext={() => setStep(4)} - /> - ); - - if (step === 4) - return ( - setStep(3)} - /> - ); - - return ( - - - - - ); -}; diff --git a/nym-connect/src/pages/connection/Connected.tsx b/nym-connect/src/pages/connection/Connected.tsx new file mode 100644 index 0000000000..63da0d6fb1 --- /dev/null +++ b/nym-connect/src/pages/connection/Connected.tsx @@ -0,0 +1,75 @@ +import React, { useState } from 'react'; +import { Box, Stack } from '@mui/material'; +import { DateTime } from 'luxon'; +import { IpAddressAndPortModal } from 'src/components/IpAddressAndPortModal'; +import { ConnectionTimer } from 'src/components/ConntectionTimer'; +import { ConnectionStatus } from 'src/components/ConnectionStatus'; +import { ConnectionStatusKind, GatewayPerformance } from 'src/types'; +import { ConnectionStatsItem } from 'src/components/ConnectionStats'; +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'; + +export const Connected: FCWithChildren<{ + status: ConnectionStatusKind; + showInfoModal: boolean; + gatewayPerformance: GatewayPerformance; + stats: ConnectionStatsItem[]; + ipAddress: string; + port: number; + connectedSince?: DateTime; + busy?: boolean; + isError?: boolean; + serviceProvider?: ServiceProvider; + onConnectClick: (status: ConnectionStatusKind) => void; + closeInfoModal: () => void; +}> = ({ + status, + showInfoModal, + gatewayPerformance, + ipAddress, + port, + connectedSince, + busy, + isError, + serviceProvider, + onConnectClick, + closeInfoModal, +}) => { + return ( + <> + + + + +
+ } + ConnectButton={ + + } + BottomContent={ + + + + + + + } + /> + + ); +}; diff --git a/nym-connect/src/pages/connection/Disconnected.tsx b/nym-connect/src/pages/connection/Disconnected.tsx new file mode 100644 index 0000000000..f92d70ad55 --- /dev/null +++ b/nym-connect/src/pages/connection/Disconnected.tsx @@ -0,0 +1,55 @@ +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 { Box } from '@mui/system'; +import { ConnectionLayout } from 'src/layouts/ConnectionLayout'; + +export const Disconnected: FCWithChildren<{ + error?: Error; + status: ConnectionStatusKind; + services?: Services; + busy?: boolean; + isError?: boolean; + serviceProvider?: ServiceProvider; + clearError: () => void; + onConnectClick: (status: ConnectionStatusKind) => void; +}> = ({ status, error, onConnectClick, clearError, serviceProvider }) => { + return ( + <> + {error && } + + + +
+ } + ConnectButton={} + BottomContent={ + + + You are not protected + + + + } + /> + + ); +}; diff --git a/nym-connect/src/pages/connection/index.tsx b/nym-connect/src/pages/connection/index.tsx new file mode 100644 index 0000000000..9489137367 --- /dev/null +++ b/nym-connect/src/pages/connection/index.tsx @@ -0,0 +1,85 @@ +import React from 'react'; +import { forage } from '@tauri-apps/tauri-forage'; +import { DateTime } from 'luxon'; +import { useClientContext } from 'src/context/main'; +import { useTauriEvents } from 'src/utils'; +import { Connected } from './Connected'; +import { Disconnected } from './Disconnected'; + +export const ConnectionPage = () => { + 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 = async () => { + const currentStatus = context.connectionStatus; + if (currentStatus === 'connected' || currentStatus === 'disconnected') { + setBusy(true); + // eslint-disable-next-line default-case + switch (currentStatus) { + case 'disconnected': + await context.setRandomSerivceProvider(); + await context.startConnecting(); + context.setConnectedSince(DateTime.now()); + context.setShowInfoModal(true); + break; + case 'connected': + await context.startDisconnecting(); + context.setConnectedSince(undefined); + break; + } + setBusy(false); + } + }; + + const closeInfoModal = () => context.setShowInfoModal(false); + + if (context.connectionStatus === 'connected') + return ( + + ); + + return ( + + ); +}; diff --git a/nym-connect/src/pages/menu/Apps.tsx b/nym-connect/src/pages/menu/Apps.tsx new file mode 100644 index 0000000000..59d2823b69 --- /dev/null +++ b/nym-connect/src/pages/menu/Apps.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Divider, Typography } from '@mui/material'; +import { Box } from '@mui/system'; + +const appsSchema = { + messagingApps: ['Telegram', 'Keybase'], + wallets: ['Blockstream', 'Electum'], +}; + +export const CompatibleApps = () => ( + + + Supported apps + + + Messaging apps + + + + + + {appsSchema.messagingApps.map((app, i) => ( + + {app} + + ))} + + + Wallets + + + + + + {appsSchema.wallets.map((wallet, i) => ( + + {wallet} + + ))} + + +); diff --git a/nym-connect/src/pages/menu/Guide.tsx b/nym-connect/src/pages/menu/Guide.tsx new file mode 100644 index 0000000000..2323d68a82 --- /dev/null +++ b/nym-connect/src/pages/menu/Guide.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import { HelpPage } from 'src/components/HelpPage'; +import Image1 from '../../assets/help-step-one.png'; +import Image2 from '../../assets/help-step-two.png'; +import Image4 from '../../assets/help-step-four.png'; + +export const HelpGuide = () => { + const [step, setStep] = useState(1); + const TOTAL_STEPS = 3; + + if (step === 1) + return ( + setStep(2)} + /> + ); + + if (step === 2) + return ( + setStep(1)} + onNext={() => setStep(3)} + /> + ); + + if (step === 3) + return ( + setStep(2)} + /> + ); + + return null; +}; diff --git a/nym-connect/src/pages/menu/index.tsx b/nym-connect/src/pages/menu/index.tsx new file mode 100644 index 0000000000..156ab061e4 --- /dev/null +++ b/nym-connect/src/pages/menu/index.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Apps, HelpOutline } from '@mui/icons-material'; +import { Stack, Link, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; +import { Link as RouterLink } from 'react-router-dom'; +import { AppVersion } from 'src/components/AppVersion'; + +const menuSchema = [ + { title: 'Supported apps', icon: Apps, path: 'apps' }, + { title: 'How to connect guide', icon: HelpOutline, path: 'guide' }, +]; + +export const Menu = () => { + return ( + + + {menuSchema.map((item) => ( + + + + {}{' '} + {item.title} + + + + ))} + + + + ); +}; diff --git a/nym-connect/src/routes/index.tsx b/nym-connect/src/routes/index.tsx new file mode 100644 index 0000000000..37bb876d91 --- /dev/null +++ b/nym-connect/src/routes/index.tsx @@ -0,0 +1,18 @@ +import { Routes, Route } from 'react-router-dom'; +import { ConnectionPage } from 'src/pages/connection'; +import { Menu } from 'src/pages/menu'; +import { CompatibleApps } from 'src/pages/menu/Apps'; +import { HelpGuide } from 'src/pages/menu/Guide'; + +export const AppRoutes = () => { + return ( + + } /> + + } /> + } /> + } /> + + + ); +}; diff --git a/nym-connect/src/stories/AppFlow.stories.tsx b/nym-connect/src/stories/AppFlow.stories.tsx index f0c8428058..011a269610 100644 --- a/nym-connect/src/stories/AppFlow.stories.tsx +++ b/nym-connect/src/stories/AppFlow.stories.tsx @@ -4,10 +4,10 @@ import { Box } from '@mui/material'; import { DateTime } from 'luxon'; import { AppWindowFrame } from '../components/AppWindowFrame'; import { useClientContext } from '../context/main'; -import { ConnectionStatusKind } from '../types'; -import { DefaultLayout } from '../layouts/DefaultLayout'; -import { ConnectedLayout } from '../layouts/ConnectedLayout'; import { Services } from '../types/directory'; +import { Disconnected } from 'src/pages/connection/Disconnected'; +import { Connected } from 'src/pages/connection/Connected'; +import { ConnectionStatusKind } from 'src/types'; export default { title: 'App/Flow', @@ -36,15 +36,15 @@ export const Mock: ComponentStory = () => { ]; const handleConnectClick = React.useCallback(() => { const oldStatus = context.connectionStatus; - if (oldStatus === ConnectionStatusKind.connected || oldStatus === ConnectionStatusKind.disconnected) { + if (oldStatus === 'connected' || oldStatus === 'disconnected') { setBusy(true); // eslint-disable-next-line default-case switch (oldStatus) { - case ConnectionStatusKind.disconnected: + case 'disconnected': context.setConnectionStatus(ConnectionStatusKind.connecting); break; - case ConnectionStatusKind.connected: + case 'connected': context.setConnectionStatus(ConnectionStatusKind.disconnecting); break; } @@ -52,11 +52,11 @@ export const Mock: ComponentStory = () => { setTimeout(() => { // eslint-disable-next-line default-case switch (oldStatus) { - case ConnectionStatusKind.disconnected: + case 'disconnected': context.setConnectedSince(DateTime.now()); context.setConnectionStatus(ConnectionStatusKind.connected); break; - case ConnectionStatusKind.connected: + case 'connected': context.setConnectionStatus(ConnectionStatusKind.disconnected); break; } @@ -72,7 +72,7 @@ export const Mock: ComponentStory = () => { return ( - = () => { return ( - undefined} + closeInfoModal={() => undefined} status={context.connectionStatus} busy={busy} onConnectClick={handleConnectClick} diff --git a/nym-connect/src/stories/ConnectedLayout.stories.tsx b/nym-connect/src/stories/ConnectedLayout.stories.tsx index 216e9c38db..b572d58490 100644 --- a/nym-connect/src/stories/ConnectedLayout.stories.tsx +++ b/nym-connect/src/stories/ConnectedLayout.stories.tsx @@ -2,23 +2,28 @@ import React from 'react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { Box } from '@mui/material'; import { DateTime } from 'luxon'; -import { ConnectedLayout } from '../layouts/ConnectedLayout'; -import { ConnectionStatusKind } from '../types'; +import { Connected } from 'src/pages/connection/Connected'; +import { ConnectionStatusKind } from 'src/types'; + +const onClick = () => undefined; export default { title: 'Layouts/ConnectedLayout', - component: ConnectedLayout, -} as ComponentMeta; + component: Connected, +} as ComponentMeta; -export const Default: ComponentStory = () => ( +export const Default: ComponentStory = () => ( - undefined} + showInfoModal={false} + closeInfoModal={() => { + return undefined; + }} status={ConnectionStatusKind.connected} connectedSince={DateTime.now()} ipAddress="127.0.0.1" + serviceProvider={{ id: 'service 1', description: 'good services', address: 'abc123', gateway: '8910xyz' }} port={1080} stats={[ { @@ -32,6 +37,7 @@ export const Default: ComponentStory = () => ( rateBytesPerSecond: 1024 * 1024 + 10, }, ]} + onConnectClick={onClick} /> ); diff --git a/nym-connect/src/stories/ConnectionButton.stories.tsx b/nym-connect/src/stories/ConnectionButton.stories.tsx index efce95ace4..d1aa35b0b5 100644 --- a/nym-connect/src/stories/ConnectionButton.stories.tsx +++ b/nym-connect/src/stories/ConnectionButton.stories.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { ConnectionButton } from '../components/ConnectionButton'; -import { ConnectionStatusKind } from '../types'; +import { ConnectionStatusKind } from 'src/types'; export default { title: 'Components/ConnectionButton', diff --git a/nym-connect/src/stories/DefaultLayout.stories.tsx b/nym-connect/src/stories/DefaultLayout.stories.tsx index ba311a410b..e7362ae685 100644 --- a/nym-connect/src/stories/DefaultLayout.stories.tsx +++ b/nym-connect/src/stories/DefaultLayout.stories.tsx @@ -1,23 +1,30 @@ import React from 'react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; import { Box } from '@mui/material'; -import { DefaultLayout } from '../layouts/DefaultLayout'; import { ConnectionStatusKind } from '../types'; +import { Disconnected } from 'src/pages/connection/Disconnected'; export default { title: 'Layouts/DefaultLayout', - component: DefaultLayout, -} as ComponentMeta; + component: Disconnected, +} as ComponentMeta; -export const Default: ComponentStory = () => ( +const onClick = () => undefined; + +export const Default: ComponentStory = () => ( - {}} error={undefined} /> + {}} + error={undefined} + onConnectClick={onClick} + /> ); -export const WithServices: ComponentStory = () => ( +export const WithServices: ComponentStory = () => ( - = () => ( ]} clearError={() => {}} error={undefined} + onConnectClick={onClick} /> ); diff --git a/nym-connect/src/stories/PowerButton.stories.tsx b/nym-connect/src/stories/PowerButton.stories.tsx new file mode 100644 index 0000000000..97681cc974 --- /dev/null +++ b/nym-connect/src/stories/PowerButton.stories.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { PowerButton } from 'src/components/PowerButton'; +import { ConnectionStatusKind } from 'src/types'; + +export default { + title: 'Components/PowerButton', + component: PowerButton, +} as ComponentMeta; + +export const Disconnected: ComponentStory = () => ( + +); + +export const Connecting: ComponentStory = () => ( + +); + +export const Connected: ComponentStory = () => ( + +); + +export const Disconnecting: ComponentStory = () => ( + +); + +export const Disabled: ComponentStory = () => ( + +); diff --git a/nym-connect/src/theme/theme.tsx b/nym-connect/src/theme/theme.tsx index abda87ef72..18678fce98 100644 --- a/nym-connect/src/theme/theme.tsx +++ b/nym-connect/src/theme/theme.tsx @@ -20,7 +20,7 @@ import { */ const nymPalette: NymPalette = { /** emphasises important elements */ - highlight: '#FB6E4E', + highlight: '#21D072', success: '#21D073', info: '#60D7EF', warning: '#FFE600', diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 02bf5970f0..9a67556428 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -2710,6 +2710,7 @@ dependencies = [ "cw4", "schemars", "serde", + "thiserror", ] [[package]] diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 9814c3e338..bc48c72adb 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "nym-network-requester" -version = "1.1.7" +version = "1.1.8" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] edition = "2021" rust-version = "1.65" diff --git a/service-providers/network-statistics/Cargo.toml b/service-providers/network-statistics/Cargo.toml index 2e6d067ab4..accdccc977 100644 --- a/service-providers/network-statistics/Cargo.toml +++ b/service-providers/network-statistics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-network-statistics" -version = "1.1.7" +version = "1.1.8" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index f866e7fa82..7680ea44fe 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym-cli" -version = "1.1.7" +version = "1.1.8" authors.workspace = true edition = "2021" diff --git a/yarn.lock b/yarn.lock index dc24ba32fd..906ab624b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10715,18 +10715,6 @@ hex-rgb@^4.1.0: resolved "https://registry.yarnpkg.com/hex-rgb/-/hex-rgb-4.3.0.tgz#af5e974e83bb2fefe44d55182b004ec818c07776" integrity sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw== -history@^4.9.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" - integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== - dependencies: - "@babel/runtime" "^7.1.2" - loose-envify "^1.2.0" - resolve-pathname "^3.0.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - value-equal "^1.0.1" - hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -10736,7 +10724,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -11697,11 +11685,6 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -12969,7 +12952,7 @@ longest-streak@^3.0.0: resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== -loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -15078,13 +15061,6 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -16060,7 +16036,7 @@ react-is@17.0.2, react-is@^17.0.1, react-is@^17.0.2: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^16.10.2, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: +react-is@^16.10.2, react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -16128,7 +16104,7 @@ react-resize-detector@^7.1.2: dependencies: lodash "^4.17.21" -react-router-dom@6: +react-router-dom@6, react-router-dom@^6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.7.0.tgz#0249f4ca4eb704562b8b0ff29caeb928c3a6ed38" integrity sha512-jQtXUJyhso3kFw430+0SPCbmCmY1/kJv8iRffGHwHy3CkoomGxeYzMkmeSPYo6Egzh3FKJZRAL22yg5p2tXtfg== @@ -16136,34 +16112,6 @@ react-router-dom@6: "@remix-run/router" "1.3.0" react-router "6.7.0" -react-router-dom@^5.2.0: - version "5.3.4" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.3.4.tgz#2ed62ffd88cae6db134445f4a0c0ae8b91d2e5e6" - integrity sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ== - dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.3.4" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - -react-router@5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.3.4.tgz#8ca252d70fcc37841e31473c7a151cf777887bb5" - integrity sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA== - dependencies: - "@babel/runtime" "^7.12.13" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - react-router@6.7.0: version "6.7.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.7.0.tgz#db262684c13b5c2970694084ae9e8531718a0681" @@ -16742,11 +16690,6 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-pathname@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" - integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== - resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -18180,12 +18123,7 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" -tiny-invariant@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" - integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== - -tiny-warning@^1.0.0, tiny-warning@^1.0.2: +tiny-warning@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== @@ -18985,11 +18923,6 @@ validate-npm-package-name@^3.0.0: dependencies: builtins "^1.0.3" -value-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" - integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== - vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"