Compare commits

...

19 Commits

Author SHA1 Message Date
farbanas 9881a94757 bumped nym-api version 2023-02-03 15:12:18 +01:00
Jędrzej Stuczyński 76b07d487b introduced '/circulating-supply/total-supply-value' and '/circulating-supply/circulating-supply-value' endpoints (#2965) 2023-02-03 13:37:55 +00:00
farbanas f04fc452dc Merge branch 'release/v1.1.8' of github.com:nymtech/nym into release/v1.1.8 2023-01-31 14:05:53 +01:00
farbanas be90d03129 changelog cleanup 2023-01-31 14:01:25 +01:00
Bogdan-Ștefan Neacşu 0a3e42700c Fix vote soft error everywhere (#2941) 2023-01-31 14:58:47 +02:00
joeiacono2021 55d554701c Merge branch 'release/v1.1.8' of https://github.com/nymtech/nym into release/v1.1.8 2023-01-31 12:55:09 +00:00
joeiacono2021 19c4769260 Changelog Updates for RELEASE 1.1.8 on 31/01 2023-01-31 12:54:55 +00:00
farbanas 71aadc8e1b update versions for the release v1.1.8 2023-01-31 13:44:41 +01:00
Fouad 95340b5817 Feature/nym connect new UI (#2916)
* reduce window size

* use new highlight color

* use react router

* render new routes

* remove old help page

* render app routes

* update connection status UI

* remove service provider info

* remove unneeded additional step

* render title from route

* experimental warning as component

* render connection page

* nym-connect: connectivity status improvements (#2915)

* connect: keep track of connectivity state

* nym-connect: query connection state

* nym-connect: function for kicking of the health check task

* rustfmt

* nym-connect: extract out into function

* nym-connect: extract out events.rs

* add app version to menu page

* help page content and style updates

* update guide content

* use layout component on disconnect page

* handle gateway issues

* only show info modal once after connecting

* power button colors

* update stories and button colors

---------

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2023-01-31 11:39:38 +00:00
Tommy Verrall 12751665bb white space 2023-01-31 09:14:56 -01:00
Tommy Verrall 01b86bcc0d updating qwerty contract addresses 2023-01-31 07:39:18 -01:00
Bogdan-Ștefan Neacşu c6ce8caaf7 Feature/fix soft multisig error (#2938) 2023-01-30 18:44:45 +02:00
Jędrzej Stuczyński 265713b9d2 Renamed 'initial_supply' to 'total_supply' in the 'circulating-supply' endpoint (#2932)
* Renamed 'initial_supply' to 'total_supply' in the 'circulating-supply' endpoint

* clippy issue messing with CI
2023-01-30 15:39:55 +00:00
Jon Häggblad c9af4721f3 wasm-utils: fix clippy 2023-01-30 10:46:11 +01:00
Pierre Dommerc 8c0ab7c697 feat(explorer): add routing score on gateway list (#2913)
* feat: adding routing score on gateway list

* feat(explorer): adding routing score on gateway list

* feat(explorer): add routing score on gateway list
2023-01-26 18:39:26 +01:00
Bogdan-Ștefan Neacșu 92b220ca4b Fix typo 2023-01-26 12:27:11 +02:00
Jędrzej Stuczyński c218cba96c Feature/default nym api (#2898)
* Setting default 'id' if not provided

* Modified 'NymConfig' to always require 'id'

* moved creation of nym-api directories away from 'override_config'

* missing optional id usage in nym-connect

* changelog

* Removed default value for '--id' argument
2023-01-25 15:49:28 +01:00
Jędrzej Stuczyński c958975fff Merge branch 'master' into release/v1.1.8 2023-01-25 13:40:20 +00:00
joeiacono2021 027b0dbc39 Merge pull request #2901 from nymtech/release/v1.1.7
Release/v1.1.7
2023-01-24 13:04:22 +00:00
118 changed files with 1416 additions and 761 deletions
+16 -6
View File
@@ -4,15 +4,25 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
# [Unreleased]
# [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])
- 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
# [v1.1.7] (2023-01-24)
Generated
+2 -1
View File
@@ -3312,6 +3312,7 @@ dependencies = [
"cw4",
"schemars",
"serde",
"thiserror",
]
[[package]]
@@ -3683,7 +3684,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.1.7"
version = "1.1.8"
dependencies = [
"anyhow",
"atty",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "1.1.7"
version = "1.1.8"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.66"
+8 -8
View File
@@ -541,35 +541,35 @@ impl<T: NymConfig> Default for Client<T> {
impl<T: NymConfig> Client<T> {
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)
}
}
+1 -1
View File
@@ -149,7 +149,7 @@ pub fn load_existing_gateway_config<T>(id: &str) -> Result<GatewayEndpointConfig
where
T: NymConfig + ClientCoreConfigTrait,
{
T::load_from_file(Some(id))
T::load_from_file(id)
.map(|existing_config| existing_config.get_gateway_endpoint().clone())
.map_err(|err| {
log::error!(
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.7"
version = "1.1.8"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -112,7 +112,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");
}
+1 -1
View File
@@ -99,7 +99,7 @@ fn version_check(cfg: &Config) -> bool {
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
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);
+1 -1
View File
@@ -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)
});
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.7"
version = "1.1.8"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+1 -1
View File
@@ -122,7 +122,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");
}
+1 -1
View File
@@ -113,7 +113,7 @@ fn version_check(cfg: &Config) -> bool {
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
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);
+1 -1
View File
@@ -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)
});
+11 -27
View File
@@ -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<PathBuf>;
fn try_default_config_directory(id: Option<&str>) -> Option<PathBuf> {
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<PathBuf> {
Self::try_default_root_directory().map(|d| d.join(id).join(CONFIG_DIR))
}
fn try_default_data_directory(id: Option<&str>) -> Option<PathBuf> {
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<PathBuf> {
Self::try_default_root_directory().map(|d| d.join(id).join(DATA_DIR))
}
fn try_default_config_file_path(id: Option<&str>) -> Option<PathBuf> {
fn try_default_config_file_path(id: &str) -> Option<PathBuf> {
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<Self> {
fn load_from_file(id: &str) -> io::Result<Self> {
let file = Self::default_config_file_path(id);
log::trace!("Loading from file: {:#?}", file);
let config_contents = fs::read_to_string(file)?;
@@ -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" }
@@ -1,3 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::StdError;
use cw_utils::ThresholdError;
@@ -1 +1,2 @@
pub mod error;
pub mod msg;
+1 -1
View File
@@ -200,7 +200,7 @@ impl NymNetworkDetails {
#[must_use]
pub fn with_group_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
self.contracts.multisig_contract_address = contract.map(Into::into);
self.contracts.group_contract_address = contract.map(Into::into);
self
}
+1 -1
View File
@@ -21,7 +21,7 @@ impl From<u16> for State {
WebSocket::OPEN => State::Open,
WebSocket::CLOSING => State::Closing,
WebSocket::CLOSED => State::Closed,
n => panic!("{} is not a valid WebSocket state!", n), // should we panic here or change it into `TryFrom` instead?
n => panic!("{n} is not a valid WebSocket state!"), // should we panic here or change it into `TryFrom` instead?
}
}
}
+2 -1
View File
@@ -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};
@@ -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" }
@@ -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
@@ -1,5 +1,2 @@
pub mod contract;
pub mod error;
pub mod state;
pub use crate::error::ContractError;
+4 -4
View File
@@ -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/"
+1 -1
View File
@@ -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
+10
View File
@@ -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])
+2 -2
View File
@@ -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"
]
}
}
}
+1 -1
View File
@@ -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`;
+9 -3
View File
@@ -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<GatewayResponse> => {
static fetchGateways = async (): Promise<GatewayBond[]> => {
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<UptimeStoryResponse> =>
+5 -5
View File
@@ -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,
};
}
+3 -3
View File
@@ -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<GatewayEnrichedRowType>();
const [status, setStatus] = React.useState<number[] | undefined>();
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<GatewayResponseItem | undefined>();
const [selectedGateway, setSelectedGateway] = React.useState<GatewayBond | undefined>();
const { gateways } = useMainContext();
const { id } = useParams<{ id: string | undefined }>();
+18
View File
@@ -81,6 +81,24 @@ export const PageGateways: FCWithChildren = () => {
</MuiLink>
),
},
{
field: 'performance',
headerName: 'Routing Score',
renderHeader: () => <CustomColumnHeading headingTitle="Routing Score" />,
width: 150,
headerAlign: 'left',
headerClassName: 'MuiDataGrid-header-override',
renderCell: (params: GridRenderCellParams) => (
<MuiLink
sx={{ ...cellStyles }}
component={RRDLink}
to={`/network-components/gateway/${params.row.identityKey}`}
data-testid="pledge-amount"
>
{`${params.value}%`}
</MuiLink>
),
},
{
field: 'host',
renderHeader: () => <CustomColumnHeading headingTitle="IP:Port" />,
+8 -2
View File
@@ -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;
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "1.1.7"
version = "1.1.8"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -1
View File
@@ -106,7 +106,7 @@ impl From<Init> for OverrideConfig {
pub async fn execute(args: Init, output: OutputFormat) -> Result<(), Box<dyn Error + Send + Sync>> {
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)!",
+1 -1
View File
@@ -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)
});
+5 -5
View File
@@ -378,23 +378,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")
}
}
+2 -2
View File
@@ -11,14 +11,14 @@ pub(crate) fn build_config<O: Into<OverrideConfig>>(
id: String,
override_args: O,
) -> Result<Config, GatewayError> {
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,
});
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "1.1.8"
version = "1.1.9"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+2 -2
View File
@@ -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()
}
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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!(
+1 -1
View File
@@ -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!(
+1 -1
View File
@@ -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!(
+1 -1
View File
@@ -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)
});
+4 -4
View File
@@ -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")
}
}
+1 -1
View File
@@ -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()
}
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-api"
version = "1.1.7"
version = "1.1.9"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -1
View File
@@ -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,
+3 -3
View File
@@ -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<Coin>,
pub(crate) vesting_tokens: Cache<Coin>,
pub(crate) circulating_supply: Cache<Coin>,
@@ -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(),
+1 -1
View File
@@ -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;
+5 -1
View File
@@ -15,7 +15,11 @@ pub(crate) mod routes;
/// Merges the routes with http information and returns it to Rocket for serving
pub(crate) fn circulating_supply_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![settings: routes::get_circulating_supply]
openapi_get_routes_spec![
settings: routes::get_full_circulating_supply,
routes::get_total_supply,
routes::get_circulating_supply
]
}
/// Spawn the circulating supply cache refresher.
+59 -4
View File
@@ -1,15 +1,30 @@
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::State;
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
use crate::node_status_api::models::ErrorResponse;
use nym_api_requests::models::CirculatingSupplyResponse;
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::State;
use rocket_okapi::openapi;
use validator_client::nyxd::Coin;
// TODO: this is not the best place to put it, it should be more centralised,
// but for a quick fix, that's good enough for now...
// (for proper solution we should be managing `NymNetworkDetails` via rocket and grabbing display exponent
// value from the mix denom here.
const UNYM_RATIO: f64 = 1000000.;
fn unym_coin_to_float_unym(coin: Coin) -> f64 {
// our total supply can't exceed 1B so an overflow here is impossible
// (if it happened, then we SHOULD crash)
coin.amount as f64 / UNYM_RATIO
}
#[openapi(tag = "circulating-supply")]
#[get("/circulating-supply")]
pub(crate) async fn get_circulating_supply(
pub(crate) async fn get_full_circulating_supply(
cache: &State<CirculatingSupplyCache>,
) -> Result<Json<CirculatingSupplyResponse>, ErrorResponse> {
match cache.get_circulating_supply().await {
@@ -20,3 +35,43 @@ pub(crate) async fn get_circulating_supply(
)),
}
}
#[openapi(tag = "circulating-supply")]
#[get("/circulating-supply/total-supply-value")]
pub(crate) async fn get_total_supply(
cache: &State<CirculatingSupplyCache>,
) -> Result<Json<f64>, ErrorResponse> {
let full_circulating_supply = match cache.get_circulating_supply().await {
Some(res) => res,
None => {
return Err(ErrorResponse::new(
"unavailable",
Status::InternalServerError,
))
}
};
Ok(Json(unym_coin_to_float_unym(
full_circulating_supply.total_supply.into(),
)))
}
#[openapi(tag = "circulating-supply")]
#[get("/circulating-supply/circulating-supply-value")]
pub(crate) async fn get_circulating_supply(
cache: &State<CirculatingSupplyCache>,
) -> Result<Json<f64>, ErrorResponse> {
let full_circulating_supply = match cache.get_circulating_supply().await {
Some(res) => res,
None => {
return Err(ErrorResponse::new(
"unavailable",
Status::InternalServerError,
))
}
};
Ok(Json(unym_coin_to_float_unym(
full_circulating_supply.circulating_supply.into(),
)))
}
+9 -6
View File
@@ -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(&params, &recovered_partials[idx], &vk) {
let ret = if !check_vk_pairing(&params, &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)?;
}
}
}
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// 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(())
}
+5 -2
View File
@@ -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)))
}
+13 -14
View File
@@ -44,7 +44,7 @@ pub(crate) struct CliArgs {
/// Id of the nym-api we want to run
#[clap(long)]
pub(crate) id: Option<String>,
pub(crate) id: String,
/// Specifies whether network monitoring is enabled on this API
#[clap(short = 'm', long)]
@@ -104,12 +104,13 @@ pub(crate) struct CliArgs {
}
pub(crate) fn build_config(args: CliArgs) -> Result<Config> {
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();
@@ -123,8 +124,13 @@ pub(crate) fn build_config(args: CliArgs) -> Result<Config> {
let config = override_config(config_from_file, args);
#[cfg(feature = "coconut")]
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")]
crate::coconut::dkg::controller::init_keypair(&config)?;
}
@@ -132,15 +138,8 @@ pub(crate) fn build_config(args: CliArgs) -> Result<Config> {
}
pub(crate) fn override_config(mut config: Config, args: CliArgs) -> Config {
if let Some(id) = args.id {
fs::create_dir_all(Config::default_config_directory(Some(&id)))
.expect("Could not create config directory");
fs::create_dir_all(Config::default_data_directory(Some(&id)))
.expect("Could not create data directory");
config = config.with_id(&id);
}
config = config
.with_id(&args.id)
.with_optional(Config::with_custom_nyxd_validator, args.nyxd_validator)
.with_optional_env(
Config::with_custom_mixnet_contract,
+30 -32
View File
@@ -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
}
+8
View File
@@ -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])
+3 -3
View File
@@ -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"
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-connect"
version = "1.1.7"
version = "1.1.8"
description = "nym-connect"
authors = ["Nym Technologies SA"]
license = ""
+1 -1
View File
@@ -93,7 +93,7 @@ impl Config {
}
pub fn config_file_location(id: &str) -> Result<PathBuf> {
Socks5Config::try_default_config_file_path(Some(id))
Socks5Config::try_default_config_file_path(id)
.ok_or(BackendError::CouldNotGetConfigFilename)
}
}
+2 -2
View File
@@ -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,
+71
View File
@@ -0,0 +1,71 @@
use std::sync::Arc;
use client_core::error::ClientCoreStatusMessage;
use task::manager::TaskStatus;
use tauri::async_runtime::RwLock;
use crate::{
state::{GatewayConnectivity, State},
tasks,
};
#[derive(Clone, serde::Serialize)]
struct Payload {
title: String,
message: String,
}
impl Payload {
fn new(title: String, message: String) -> Self {
Self { title, message }
}
}
pub fn emit_event(event: &str, title: &str, msg: &str, window: &tauri::Window<tauri::Wry>) {
if let Err(err) = window.emit(event, Payload::new(title.into(), msg.into())) {
log::error!("Failed to emit tauri event: {err}");
}
}
pub fn emit_status_event<T: ToString>(event: &str, msg: &T, window: &tauri::Window<tauri::Wry>) {
if let Err(err) = window.emit(event, Payload::new("SOCKS5 update".into(), msg.to_string())) {
log::error!("Failed to emit tauri event: {err}");
}
}
pub async fn handle_task_status(
task_status: &TaskStatus,
state: &Arc<RwLock<State>>,
window: &tauri::Window,
) {
match task_status {
TaskStatus::Ready => {
{
let mut state_w = state.write().await;
state_w.mark_connected(window);
}
emit_status_event("socks5-connected-event", task_status, window);
tasks::start_connection_check(state.clone(), window.clone());
}
}
}
pub async fn handle_client_status_message(
client_status_message: &ClientCoreStatusMessage,
state: &Arc<RwLock<State>>,
window: &tauri::Window,
) {
// TODO: use this instead once we change on the frontend too
let _event_name = match client_status_message {
ClientCoreStatusMessage::GatewayIsSlow | ClientCoreStatusMessage::GatewayIsVerySlow => {
"socks5-gateway-status"
}
};
if let Ok(connectivity) = GatewayConnectivity::try_from(client_status_message) {
state.write().await.set_gateway_connectivity(connectivity);
}
emit_status_event("socks5-status-event", client_status_message, window);
}
+5 -2
View File
@@ -16,6 +16,7 @@ use crate::window::window_toggle;
mod config;
mod error;
mod events;
mod logging;
mod menu;
mod models;
@@ -40,14 +41,16 @@ fn main() {
.invoke_handler(tauri::generate_handler![
crate::config::get_config_file_location,
crate::config::get_config_id,
crate::operations::connection::status::get_connection_status,
crate::operations::connection::status::run_health_check,
crate::operations::connection::connect::get_gateway,
crate::operations::connection::connect::get_service_provider,
crate::operations::connection::connect::set_gateway,
crate::operations::connection::connect::set_service_provider,
crate::operations::connection::connect::start_connecting,
crate::operations::connection::disconnect::start_disconnecting,
crate::operations::connection::status::get_connection_health_check_status,
crate::operations::connection::status::get_connection_status,
crate::operations::connection::status::get_gateway_connection_status,
crate::operations::connection::status::start_connection_health_check_task,
crate::operations::directory::get_services,
crate::operations::export::export_keys,
crate::operations::window::hide_window,
+31
View File
@@ -2,6 +2,8 @@ use core::fmt;
use serde::{Deserialize, Serialize};
use crate::state::GatewayConnectivity;
#[cfg_attr(test, derive(ts_rs::TS))]
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct ConnectResult {
@@ -25,6 +27,35 @@ pub enum ConnectionStatusKind {
Connecting,
}
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(test, ts(rename_all = "lowercase"))]
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(rename_all = "camelCase")]
pub enum GatewayConnectionStatusKind {
Good,
Bad,
VeryBad,
}
impl From<GatewayConnectivity> for GatewayConnectionStatusKind {
fn from(conn: GatewayConnectivity) -> Self {
match conn {
GatewayConnectivity::Good => GatewayConnectionStatusKind::Good,
GatewayConnectivity::Bad { .. } => GatewayConnectionStatusKind::Bad,
GatewayConnectivity::VeryBad { .. } => GatewayConnectionStatusKind::VeryBad,
}
}
}
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(test, ts(rename_all = "lowercase"))]
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
pub enum ConnectivityTestResult {
NotAvailable,
Success,
Fail,
}
impl fmt::Display for ConnectionStatusKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
@@ -0,0 +1,26 @@
use serde::{Deserialize, Serialize};
static HEALTH_CHECK_URL: &str = "https://nymtech.net/.wellknown/connect/healthcheck.json";
#[derive(Serialize, Deserialize, Debug)]
struct ConnectionSuccess {
status: String,
}
pub async fn run_health_check() -> bool {
log::info!("Running network health check");
match crate::operations::http::socks5_get::<_, ConnectionSuccess>(HEALTH_CHECK_URL).await {
Ok(res) if res.status == "ok" => {
log::info!("Healthcheck success!");
true
}
Ok(res) => {
log::error!("Healthcheck failed with status: {}", res.status);
false
}
Err(err) => {
log::error!("Healthcheck failed: {err}");
false
}
}
}
@@ -1,3 +1,4 @@
pub mod connect;
pub mod disconnect;
pub mod health_check;
pub mod status;
@@ -1,14 +1,12 @@
use crate::error::Result;
use crate::tasks;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use crate::models::ConnectionStatusKind;
use crate::models::{ConnectionStatusKind, ConnectivityTestResult, GatewayConnectionStatusKind};
use crate::state::State;
static HEALTH_CHECK_URL: &str = "https://nymtech.net/.wellknown/connect/healthcheck.json";
#[tauri::command]
pub async fn get_connection_status(
state: tauri::State<'_, Arc<RwLock<State>>>,
@@ -17,26 +15,30 @@ pub async fn get_connection_status(
Ok(state.get_status())
}
#[derive(Serialize, Deserialize, Debug)]
struct ConnectionSuccess {
status: String,
#[tauri::command]
pub async fn get_gateway_connection_status(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<GatewayConnectionStatusKind> {
let mut state_w = state.write().await;
let gateway_connectivity = state_w.get_gateway_connectivity();
Ok(gateway_connectivity.into())
}
#[tauri::command]
pub async fn run_health_check() -> bool {
log::info!("Running network health check");
match crate::operations::http::socks5_get::<_, ConnectionSuccess>(HEALTH_CHECK_URL).await {
Ok(res) if res.status == "ok" => {
log::info!("Healthcheck success!");
true
}
Ok(res) => {
log::error!("Healthcheck failed with status: {}", res.status);
false
}
Err(err) => {
log::error!("Healthcheck failed: {err}");
false
}
}
pub async fn get_connection_health_check_status(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<ConnectivityTestResult> {
let state = state.read().await;
Ok(state.get_connectivity_test_result())
}
// Start a connection check task. This should return with an event within one minute, and update
// the state.
// Trying to run multiple concurrent connection checks probably works but is not supported.
#[tauri::command]
pub fn start_connection_health_check_task(
state: tauri::State<'_, Arc<RwLock<State>>>,
window: tauri::Window<tauri::Wry>,
) {
tasks::start_connection_check(state.inner().clone(), window);
}
+71 -2
View File
@@ -1,6 +1,7 @@
use std::time::Duration;
use ::config_common::NymConfig;
use client_core::error::ClientCoreStatusMessage;
use futures::SinkExt;
use tap::TapFallible;
use tauri::Manager;
@@ -8,17 +9,46 @@ use tauri::Manager;
use nym_socks5::client::{
config::Config as Socks5Config, Socks5ControlMessage, Socks5ControlMessageSender,
};
use tokio::time::Instant;
use crate::{
config::{self, socks5_config_id_appended_with},
error::{BackendError, Result},
models::{
AppEventConnectionStatusChangedPayload, ConnectionStatusKind,
AppEventConnectionStatusChangedPayload, ConnectionStatusKind, ConnectivityTestResult,
APP_EVENT_CONNECTION_STATUS_CHANGED,
},
tasks::{self, ExitStatusReceiver},
};
// The client will emit messages if the connection to the gateway is poor (or the gateway can't
// keep up with the messages we are sendind). If no messages about this has been received for a
// certain duration then we assume it's all good.
const GATEWAY_CONNECTIVITY_TIMEOUT_SECS: u64 = 20;
#[derive(Clone, Copy)]
pub enum GatewayConnectivity {
Good,
Bad { when: Instant },
VeryBad { when: Instant },
}
impl TryFrom<&ClientCoreStatusMessage> for GatewayConnectivity {
type Error = BackendError;
fn try_from(value: &ClientCoreStatusMessage) -> Result<Self, Self::Error> {
let conn = match value {
ClientCoreStatusMessage::GatewayIsSlow => GatewayConnectivity::Bad {
when: Instant::now(),
},
ClientCoreStatusMessage::GatewayIsVerySlow => GatewayConnectivity::VeryBad {
when: Instant::now(),
},
};
Ok(conn)
}
}
pub struct State {
/// The current connection status
status: ConnectionStatusKind,
@@ -31,6 +61,14 @@ pub struct State {
/// Channel that is used to send command messages to the SOCKS5 client, such as to disconnect
socks5_client_sender: Option<Socks5ControlMessageSender>,
/// The client will periodically report connectivity to the gateway it's connected to. Unless
/// we get a status message from the client we assume it's good.
gateway_connectivity: GatewayConnectivity,
/// The latest end-to-end connectivity test result. The first test is initiated on connection
/// established. Additional tests can be triggered.
connectivity_test_result: ConnectivityTestResult,
}
impl State {
@@ -40,9 +78,40 @@ impl State {
service_provider: None,
gateway: None,
socks5_client_sender: None,
gateway_connectivity: GatewayConnectivity::Good,
connectivity_test_result: ConnectivityTestResult::NotAvailable,
}
}
pub fn get_gateway_connectivity(&mut self) -> GatewayConnectivity {
self.gateway_connectivity = match self.gateway_connectivity {
c @ (GatewayConnectivity::Bad { when } | GatewayConnectivity::VeryBad { when }) => {
if Instant::now() > when + Duration::from_secs(GATEWAY_CONNECTIVITY_TIMEOUT_SECS) {
GatewayConnectivity::Good
} else {
c
}
}
current => current,
};
self.gateway_connectivity
}
pub fn set_gateway_connectivity(&mut self, gateway_connectivity: GatewayConnectivity) {
self.gateway_connectivity = gateway_connectivity
}
pub fn get_connectivity_test_result(&self) -> ConnectivityTestResult {
self.connectivity_test_result
}
pub fn set_connectivity_test_result(
&mut self,
connectivity_test_result: ConnectivityTestResult,
) {
self.connectivity_test_result = connectivity_test_result;
}
pub fn get_status(&self) -> ConnectionStatusKind {
self.status.clone()
}
@@ -85,7 +154,7 @@ impl State {
pub fn load_socks5_config(&self) -> Result<Socks5Config> {
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)
}
+28 -59
View File
@@ -13,7 +13,13 @@ use config_common::NymConfig;
use nym_socks5::client::NymClient as Socks5NymClient;
use nym_socks5::client::{config::Config as Socks5Config, Socks5ControlMessageSender};
use crate::{error::Result, models::ConnectionStatusKind, operations::connection, state::State};
use crate::{
error::Result,
events::{self, emit_event, emit_status_event},
models::{ConnectionStatusKind, ConnectivityTestResult},
operations,
state::State,
};
pub type ExitStatusReceiver = futures::channel::oneshot::Receiver<Socks5ExitStatusMessage>;
@@ -36,7 +42,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();
@@ -87,34 +93,6 @@ pub fn start_nym_socks5_client(
))
}
#[derive(Clone, serde::Serialize)]
struct Payload {
title: String,
message: String,
}
impl Payload {
fn new(title: String, message: String) -> Self {
Self { title, message }
}
}
fn emit_event(event: &str, title: &str, msg: &str, window: &tauri::Window<tauri::Wry>) {
if let Err(err) = window.emit(event, Payload::new(title.into(), msg.into())) {
log::error!("Failed to emit tauri event: {err}");
}
}
fn emit_status_event(
event: &str,
msg: Box<dyn std::error::Error + Send + Sync>,
window: &tauri::Window<tauri::Wry>,
) {
if let Err(err) = window.emit(event, Payload::new("SOCKS5 update".into(), msg.to_string())) {
log::error!("Failed to emit tauri event: {err}");
}
}
pub fn start_connection_check(state: Arc<RwLock<State>>, window: tauri::Window<tauri::Wry>) {
log::debug!("Starting connection check handler");
tokio::spawn(async move {
@@ -124,17 +102,22 @@ pub fn start_connection_check(state: Arc<RwLock<State>>, window: tauri::Window<t
}
log::info!("Running connection health check");
if connection::status::run_health_check().await {
if operations::connection::health_check::run_health_check().await {
state
.write()
.await
.set_connectivity_test_result(ConnectivityTestResult::Success);
emit_event(
"socks5-connection-success-event",
"SOCKS5 success",
"SOCKS5 connection health check successful",
&window,
);
} else {
if state.read().await.get_status() != ConnectionStatusKind::Connected {
log::debug!("SOCKS5 connection status check cancelled: not connected");
}
} else if state.read().await.get_status() == ConnectionStatusKind::Connected {
state
.write()
.await
.set_connectivity_test_result(ConnectivityTestResult::Fail);
log::error!("SOCKS5 connection health check failed");
emit_event(
"socks5-connection-fail-event",
@@ -142,26 +125,14 @@ pub fn start_connection_check(state: Arc<RwLock<State>>, window: tauri::Window<t
"SOCKS5 connection health check failed",
&window,
);
} else {
log::debug!("SOCKS5 connection status check cancelled: not connected");
}
log::debug!("Connection check handler exiting");
});
}
async fn handle_connection_ready(
state: &Arc<RwLock<State>>,
window: &tauri::Window,
msg: Box<dyn std::error::Error + Send + Sync>,
) {
{
let mut state_w = state.write().await;
state_w.mark_connected(window);
}
emit_status_event("socks5-connected-event", msg, window);
start_connection_check(state.clone(), window.clone());
}
/// The status listener listens for non-exit status messages from the background socks5 proxy task.
pub fn start_status_listener(
state: Arc<RwLock<State>>,
@@ -169,21 +140,19 @@ pub fn start_status_listener(
mut msg_receiver: task::StatusReceiver,
) {
log::info!("Starting status listener");
tokio::spawn(async move {
while let Some(msg) = msg_receiver.next().await {
log::info!("SOCKS5 proxy sent status message: {}", msg);
if let Some(TaskStatus::Ready) = msg.downcast_ref::<TaskStatus>() {
handle_connection_ready(&state, &window, msg).await;
} else if let Some(_gateway_status) = msg.downcast_ref::<ClientCoreStatusMessage>() {
// TODO: use this instead once we change on the frontend too
//let event_name = match gateway_status {
// ClientCoreStatusMessage::GatewayIsSlow => "socks5-gateway-status",
// ClientCoreStatusMessage::GatewayIsVerySlow => "socks5-gateway-status",
//};
emit_status_event("socks5-status-event", msg, &window);
if let Some(task_status) = msg.downcast_ref::<TaskStatus>() {
events::handle_task_status(task_status, &state, &window).await;
} else if let Some(client_status_message) =
msg.downcast_ref::<ClientCoreStatusMessage>()
{
events::handle_client_status_message(client_status_message, &state, &window).await;
} else {
emit_status_event("socks5-status-event", msg, &window);
emit_status_event("socks5-status-event", &msg, &window);
}
}
log::info!("Status listener exiting");
+13 -5
View File
@@ -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'"
}
}
}
}
+12 -32
View File
@@ -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<boolean>();
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 <HelpGuideLayout />;
if (
context.connectionStatus === ConnectionStatusKind.disconnected ||
context.connectionStatus === ConnectionStatusKind.connecting
) {
return (
<DefaultLayout
error={context.error}
clearError={context.clearError}
status={context.connectionStatus}
busy={busy}
onConnectClick={handleConnectClick}
services={context.services}
/>
);
if (context.connectionStatus === 'disconnected' || context.connectionStatus === 'connecting') {
return <AppRoutes />;
}
return (
<ConnectedLayout
showInfoModal={showInfoModal}
handleCloseInfoModal={() => setShowInfoModal(false)}
<Connected
status={context.connectionStatus}
showInfoModal={context.showInfoModal}
closeInfoModal={() => 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:',
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 18 KiB

+2 -2
View File
@@ -6,8 +6,8 @@ export const AppVersion = () => {
const { appVersion } = useClientContext();
return (
<Box sx={{ display: 'grid', width: '100%', justifyContent: 'center' }}>
<Box fontSize="small" sx={{ mb: 4, color: 'grey.600' }}>
<Box sx={{ display: 'flex', width: '100%', justifyContent: 'center' }}>
<Box fontSize="small" sx={{ color: 'grey.600' }}>
Version {appVersion}
</Box>
</Box>
+17 -16
View File
@@ -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 }) => (
<Box
sx={{
display: 'grid',
borderRadius: '12px',
gridTemplateRows: '40px 1fr 30px',
height: '100vh',
overflowY: 'hidden',
}}
>
<CustomTitleBar />
<Box style={{ padding: '16px' }}>{children}</Box>
<AppVersion />
</Box>
);
export const AppWindowFrame: FCWithChildren = ({ children }) => {
const location = useLocation();
return (
<Box
sx={{
display: 'grid',
gridTemplateRows: '40px 1fr',
height: '100vh',
}}
>
<CustomTitleBar path={location.pathname} />
<Box style={{ padding: '16px' }}>{children}</Box>
</Box>
);
};
@@ -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<{
<circle cx="131" cy="131" r="64" stroke={statusFillColor} strokeWidth="2" />
</g>
<circle cx="131" cy="131" r="73.5" stroke={statusFillColor} strokeOpacity="0.5" />
{status === ConnectionStatusKind.connected && hover ? (
{status === 'connected' && hover ? (
<path
d="M120.217 119.833C120.217 117.838 121.838 116.217 123.833 116.217H128.5V114H123.833C120.613 114 118 116.613 118 119.833C118 123.053 120.613 125.667 123.833 125.667H128.5V123.45H123.833C121.838 123.45 120.217 121.828 120.217 119.833ZM127 121H136.333V118.667H127V121ZM139.5 114H134.833V116.217H139.505C141.5 116.217 143.117 117.838 143.117 119.833C143.117 121.828 141.495 123.45 139.5 123.45H134.833V125.667H139.5C142.72 125.667 145.333 123.053 145.333 119.833C145.333 116.613 142.72 114 139.5 114Z"
fill="white"
+51 -42
View File
@@ -3,32 +3,65 @@ import { Box, CircularProgress, Tooltip, Typography } from '@mui/material';
import { DateTime } from 'luxon';
import { ConnectionStatusKind, GatewayPerformance } from '../types';
import { ServiceProvider } from '../types/directory';
import { ServiceProviderInfo } from './ServiceProviderInfo';
import { GatwayWarningInfo, ServiceProviderInfo } from './TooltipInfo';
import { ErrorOutline, InfoOutlined } from '@mui/icons-material';
const FONT_SIZE = '10px';
const FONT_SIZE = '14px';
const FONT_WEIGHT = '600';
const FONT_STYLE = 'normal';
const ConnectionStatusContent: FCWithChildren<{
status: ConnectionStatusKind;
}> = ({ status }) => {
serviceProvider?: ServiceProvider;
gatewayError: boolean;
}> = ({ status, serviceProvider, gatewayError }) => {
if (gatewayError) {
return (
<Tooltip title={serviceProvider ? <GatwayWarningInfo /> : undefined}>
<Box
display="flex"
alignItems="center"
gap={0.5}
justifyContent="center"
sx={{ cursor: 'pointer' }}
color="warning.main"
>
<ErrorOutline sx={{ fontSize: 14 }} />
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} fontSize={FONT_SIZE} textAlign="center">
Gateway has issues
</Typography>
</Box>
</Tooltip>
);
}
switch (status) {
case ConnectionStatusKind.connected:
case 'connected':
return (
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} fontSize="14px">
Connected to
<Tooltip title={serviceProvider ? <ServiceProviderInfo serviceProvider={serviceProvider} /> : undefined}>
<Box display="flex" alignItems="center" gap={0.5} justifyContent="center" sx={{ cursor: 'pointer' }}>
<InfoOutlined sx={{ fontSize: 14 }} />
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} fontSize={FONT_SIZE} textAlign="center">
Connected to Nym Mixnet
</Typography>
</Box>
</Tooltip>
);
case 'disconnected':
return (
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} textAlign="center" fontSize={FONT_SIZE}>
Connect to the mixnet
</Typography>
);
case ConnectionStatusKind.disconnecting:
case 'disconnecting':
return (
<Box display="flex" alignItems="center" justifyContent="center">
<CircularProgress size={FONT_SIZE} color="inherit" />
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} ml={1}>
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE}>
Disconnecting...
</Typography>
</Box>
);
case ConnectionStatusKind.connecting:
case 'connecting':
return (
<Box display="flex" alignItems="center" justifyContent="center">
<CircularProgress size={FONT_SIZE} color="inherit" />
@@ -37,20 +70,7 @@ const ConnectionStatusContent: FCWithChildren<{
</Typography>
</Box>
);
case ConnectionStatusKind.disconnected:
return (
<Typography
fontWeight={FONT_WEIGHT}
fontStyle={FONT_STYLE}
ml={1}
textTransform="uppercase"
textAlign="center"
fontSize={FONT_SIZE}
sx={{ wordSpacing: 3, letterSpacing: 2 }}
>
You are not protected
</Typography>
);
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 (
<>
<Box color={color} fontSize={FONT_SIZE} sx={{ mb: 1 }}>
{status === ConnectionStatusKind.connected && gatewayPerformance !== 'Good' ? (
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} textAlign="left" color="primary">
Gateway has issues
</Typography>
) : (
<ConnectionStatusContent status={status} />
)}
<Box color={color} sx={{ mb: 2 }}>
<ConnectionStatusContent
status={status}
serviceProvider={serviceProvider}
gatewayError={gatewayPerformance !== 'Good'}
/>
</Box>
{serviceProvider ? (
<Tooltip title={<ServiceProviderInfo serviceProvider={serviceProvider} />}>
<Box sx={{ cursor: 'pointer' }}>
{serviceProvider && <Typography>{serviceProvider.description}</Typography>}
</Box>
</Tooltip>
) : null}
</>
);
};
+28 -16
View File
@@ -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<any
</IconButton>
);
export const CustomTitleBar = () => {
const { showHelp, handleShowHelp } = useClientContext();
const MenuIcon = () => {
const navigate = useNavigate();
return <CustomButton Icon={Menu} onClick={() => navigate('/menu')} />;
};
const ArrowBackIcon = () => {
const navigate = useNavigate();
return <CustomButton Icon={ArrowBack} onClick={() => navigate(-1)} />;
};
const getTitleIcon = (path: string) => {
if (path !== '/') {
const title = path.split('/').slice(-1);
return (
<Typography textTransform="capitalize" fontWeight={700}>
{title}
</Typography>
);
}
return <NymWordmark width={36} />;
};
export const CustomTitleBar = ({ path = '/' }: { path?: string }) => {
return (
<Box data-tauri-drag-region style={customTitleBarStyles.titlebar}>
{/* set width to keep logo centered */}
<Box sx={{ width: '40px' }}>
<CustomButton
Icon={!showHelp ? HelpOutline : ArrowBack}
onClick={() => {
handleShowHelp();
}}
/>
</Box>
<NymWordmark width={36} />
<Box sx={{ width: '40px' }}>{path === '/' ? <MenuIcon /> : <ArrowBackIcon />}</Box>
{getTitleIcon(path)}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CustomButton Icon={Minimize} onClick={() => appWindow.minimize()} />
<CustomButton Icon={Close} onClick={() => appWindow.close()} />
@@ -0,0 +1,13 @@
import React from 'react';
import { Box, Typography } from '@mui/material';
export const ExperimentalWarning = () => (
<Box sx={{ color: 'grey.600' }}>
<Typography fontSize="10px" textAlign="center">
This is experimental software.
</Typography>
<Typography fontSize="10px" textAlign="center">
Do not rely on it for strong anonymity (yet).
</Typography>
</Box>
);
@@ -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<NodeJS.Timer | null>();
const doPing = async () => {
@@ -28,7 +28,7 @@ export const TestAndEarnPopupContent: FCWithChildren<{
);
}
if (!connectionStatus || connectionStatus === ConnectionStatusKind.disconnected) {
if (!connectionStatus || connectionStatus === 'disconnected') {
return (
<Box p={4}>
<ContentNotAvailable />
@@ -36,7 +36,7 @@ export const TestAndEarnPopupContent: FCWithChildren<{
);
}
if (connectionStatus === ConnectionStatusKind.connecting || connectionStatus === ConnectionStatusKind.disconnecting) {
if (connectionStatus === 'connecting' || connectionStatus === 'disconnecting') {
return (
<Box p={4} justifyContent="center" alignItems="center" display="flex">
<CircularProgress />
@@ -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 (
<Box p={4}>
@@ -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);
+5 -3
View File
@@ -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;
}) => (
<Stack justifyContent="space-between" sx={{ height: '100%' }}>
<Stack gap={3}>
<Stack gap={2}>
<StepIndicator step={step} />
<Typography variant="body2" color="white" fontWeight="bold">
How to connect guide {step}/4
How to connect guide {step}/{totalSteps}
</Typography>
<Typography variant="body2" sx={{ color: 'grey.400' }}>
<Typography variant="body2" sx={{ color: 'grey.400' }} textAlign="left">
{description}
</Typography>
<HelpImage img={img} imageDescription="select a provider" />
+3 -2
View File
@@ -1,5 +1,6 @@
import React from 'react';
import { Box } from '@mui/material';
export const HelpImage = ({ img, imageDescription }: { img: any; imageDescription: string }) => (
<img src={img} alt={imageDescription} />
export const HelpImage = ({ img, imageDescription }: { img: string; imageDescription: string }) => (
<img src={img} alt={imageDescription} width="100%" />
);
@@ -1,15 +1,14 @@
import { Box } from '@mui/material';
import React from 'react';
import { Box } from '@mui/material';
const Step = ({ highlight }: { highlight: boolean }) => (
<Box sx={{ width: '48px', height: '1px', bgcolor: highlight ? 'nym.highlight' : 'grey.600' }} />
<Box sx={{ width: '65px', height: '1px', bgcolor: highlight ? 'nym.highlight' : 'grey.600' }} />
);
export const StepIndicator = ({ step }: { step: number }) => (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-evenly' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Step highlight />
<Step highlight={step >= 2} />
<Step highlight={step >= 3} />
<Step highlight={step >= 4} />
</Box>
);
+2 -2
View File
@@ -18,7 +18,7 @@ const styles = {
const ModalTitle = ({ title, withCloseIcon }: { title: string; withCloseIcon: boolean }) => (
<Box textAlign="center" mt={withCloseIcon ? -2 : 0}>
<ErrorOutline sx={{ color: 'warning.main' }} />
<Typography variant="body2" textAlign="center" sx={{ color: 'warning.main' }}>
<Typography variant="body2" textAlign="center" sx={{ color: 'warning.main', my: 1 }}>
{title}
</Typography>
</Box>
@@ -27,7 +27,7 @@ const ModalTitle = ({ title, withCloseIcon }: { title: string; withCloseIcon: bo
const ModalBody = ({ description, children }: { description: string; children?: React.ReactElement }) => (
<Box textAlign="center" mt={1}>
{children}
<Typography fontSize="small" sx={{ mt: 1, overflowWrap: 'anywhere', color: 'grey.300' }}>
<Typography fontSize="small" sx={{ mt: 1, overflowWrap: 'anywhere', color: 'grey.400' }}>
{description}
</Typography>
</Box>
@@ -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 = ({
}) => (
<InfoModal
show={show}
title="Almost there"
description="Copy these values to the proxy settings in your application"
Action={<Button onClick={onClose}>Done</Button>}
title="You are half way there"
description="Check NymConnect menu for supported apps"
onClose={onClose}
>
<Box sx={{ mt: 1 }}>
<Typography fontSize="14px" sx={{ color: 'grey.600' }}>
<Typography fontSize={FONT_SIZE} color={FONT_COLOR} sx={{ my: 2 }}>
Paste below values in the proxy settings of your app
</Typography>
<Typography fontSize={FONT_SIZE} sx={{ color: 'grey.600' }}>
Socks5 address
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
@@ -29,7 +35,7 @@ export const IpAddressAndPortModal = ({
<CopyToClipboard text={ipAddress} iconButton light />
</Box>
<Typography fontSize="14px" sx={{ color: 'grey.600', mt: 2 }}>
<Typography fontSize={FONT_SIZE} sx={{ color: 'grey.600', mt: 2 }}>
Port
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
+132
View File
@@ -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<boolean>(false);
const handleClick = () => {
if (disabled === true) {
return;
}
if (onClick) {
onClick(status);
}
};
const statusFillColor = getStatusFillColor(status, hover, Boolean(isError));
return (
<svg
width="190"
height="190"
viewBox="0 0 200 200"
fill="none"
xmlns="http://www.w3.org/2000/svg"
onClick={handleClick}
style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}
onMouseEnter={() => !disabled && setHover(true)}
onMouseLeave={() => !disabled && setHover(false)}
>
<g transform="translate(-30, -25) ">
<circle cx={131} cy={131} r={70} strokeWidth={2} stroke={statusFillColor} filter="url(#blur)" opacity="0.6" />
<circle cx={131} cy={131} r={22} strokeWidth={1} stroke={statusFillColor} filter="url(#blur)" opacity="0.3" />
<circle opacity={0.6} cx={131} cy={131} r={68.5} stroke={statusFillColor} />
<g filter="url(#filter1_d_944_9033)">
<circle cx={131} cy={131} r={64} fill="url(#paint1_radial_944_9033)" />
<circle cx={131} cy={131} r={63} stroke={statusFillColor} strokeWidth={2} />
</g>
<g opacity={0.5} filter="url(#filter2_f_944_9033)">
<g clipPath="url(#clip0_944_9033)">
<path
d="M131 113C129.9 113 129 113.9 129 115V131C129 132.1 129.9 133 131 133C132.1 133 133 132.1 133 131V115C133 113.9 132.1 113 131 113ZM141.28 118.72C140.5 119.5 140.52 120.72 141.26 121.5C143.52 123.9 144.92 127.1 145 130.64C145.18 138.3 138.84 144.9 131.18 144.98C123.36 145.1 117 138.8 117 131C117 127.32 118.42 123.98 120.74 121.48C121.48 120.7 121.48 119.48 120.72 118.72C119.92 117.92 118.62 117.94 117.86 118.76C114.96 121.84 113.14 125.94 113 130.48C112.72 140.24 120.66 148.68 130.42 148.98C140.62 149.3 149 141.12 149 130.98C149 126.24 147.16 121.96 144.16 118.76C143.4 117.94 142.08 117.92 141.28 118.72Z"
stroke={statusFillColor}
/>
</g>
</g>
<g clipPath="url(#clip1_944_9033)">
<path
d="M131 113C129.9 113 129 113.9 129 115V131C129 132.1 129.9 133 131 133C132.1 133 133 132.1 133 131V115C133 113.9 132.1 113 131 113ZM141.28 118.72C140.5 119.5 140.52 120.72 141.26 121.5C143.52 123.9 144.92 127.1 145 130.64C145.18 138.3 138.84 144.9 131.18 144.98C123.36 145.1 117 138.8 117 131C117 127.32 118.42 123.98 120.74 121.48C121.48 120.7 121.48 119.48 120.72 118.72C119.92 117.92 118.62 117.94 117.86 118.76C114.96 121.84 113.14 125.94 113 130.48C112.72 140.24 120.66 148.68 130.42 148.98C140.62 149.3 149 141.12 149 130.98C149 126.24 147.16 121.96 144.16 118.76C143.4 117.94 142.08 117.92 141.28 118.72Z"
fill={statusFillColor}
/>
</g>
<defs>
<filter
id="filter0_f_944_9033"
x={0}
y={0}
width={240}
height={240}
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity={0} result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation={40} result="effect1_foregroundBlur_944_9033" />
</filter>
<filter
id="filter1_d_944_9033"
x={52}
y={58}
width={158}
height={158}
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity={0} result="BackgroundImageFix" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_944_9033" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_944_9033" result="shape" />
</filter>
<filter
id="filter2_f_944_9033"
x={97}
y={97}
width={68}
height={68}
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity={0} result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation={5} result="effect1_foregroundBlur_944_9033" />
</filter>
<filter id="blur">
<feGaussianBlur stdDeviation="5" />
</filter>
</defs>
</g>
</svg>
);
};
@@ -7,14 +7,23 @@ export const ServiceProviderInfo = ({ serviceProvider }: { serviceProvider: Serv
<Typography variant="body2" fontWeight="bold">
Connection info
</Typography>
<Typography variant="caption">{serviceProvider.description}</Typography>
<Divider />
<Typography variant="caption" fontWeight="bold">
Gateway <Typography variant="caption">{serviceProvider.gateway}</Typography>
</Typography>
<Divider />
<Typography variant="caption" fontWeight="bold">
Provider <Typography variant="caption">{serviceProvider.address.slice(0, 35)}...</Typography>
Service provider <Typography variant="caption">{serviceProvider.address.slice(0, 35)}...</Typography>
</Typography>
</Stack>
);
export const GatwayWarningInfo = () => (
<Stack gap={1} sx={{ wordWrap: 'break-word', maxWidth: 150, p: 1 }}>
<Typography variant="body2" fontWeight="bold" color="warning.main">
Connection issue
</Typography>
<Divider />
<Typography variant="caption">Try disconnecting and connecting again</Typography>
</Stack>
);
+57 -59
View File
@@ -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<void>;
startDisconnecting: () => Promise<void>;
};
@@ -42,28 +40,40 @@ export const ClientContext = createContext({} as TClientContext);
export const ClientContextProvider: FCWithChildren = ({ children }) => {
const [mode, setMode] = useState<ModeType>('dark');
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatusKind>(ConnectionStatusKind.disconnected);
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatusKind>(ConnectionStatusKind.connected);
const [connectionStats, setConnectionStats] = useState<ConnectionStatsItem[]>();
const [connectedSince, setConnectedSince] = useState<DateTime>();
const [services, setServices] = React.useState<Services>([]);
const [serviceProvider, setRawServiceProvider] = React.useState<ServiceProvider>();
const [showHelp, setShowHelp] = useState(false);
const [selectedProvider, setSelectedProvider] = React.useState<ServiceProvider>();
const [serviceProviders, setServiceProviders] = React.useState<ServiceProvider[]>();
const [error, setError] = useState<Error>();
const [appVersion, setAppVersion] = useState<string>();
const [gatewayPerformance, setGatewayPerformance] = useState<GatewayPerformance>('Good');
const [showInfoModal, setShowInfoModal] = useState(false);
const getAppVersion = async () => {
const version = await getVersion();
setAppVersion(version);
return version;
};
const timerId = useRef<NodeJS.Timeout>();
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<ServiceProvider | undefined> => {
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,
],
);
+4 -5
View File
@@ -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<{
+15 -9
View File
@@ -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(
<ErrorBoundary FallbackComponent={ErrorFallback}>
<ClientContextProvider>
<TestAndEarnContextProvider>
<NymMixnetTheme mode="dark">
<AppWindowFrame>
<App />
</AppWindowFrame>
</NymMixnetTheme>
</TestAndEarnContextProvider>
</ClientContextProvider>
<Router>
<ClientContextProvider>
<TestAndEarnContextProvider>
<GlobalStyles styles={{ html: { borderRadius: 10 } }} />
<NymMixnetTheme mode="dark">
<AppWindowFrame>
<AppRoutes />
</AppWindowFrame>
</NymMixnetTheme>
</TestAndEarnContextProvider>
</ClientContextProvider>
</Router>
</ErrorBoundary>,
);
}
@@ -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,
}) => (
<>
<IpAddressAndPortModal show={showInfoModal} onClose={handleCloseInfoModal} ipAddress={ipAddress} port={port} />
<Box pb={1}>
<ConnectionStatus
status={ConnectionStatusKind.connected}
serviceProvider={serviceProvider}
gatewayPerformance={gatewayPerformance}
/>
</Box>
<Divider sx={{ my: 2 }} />
<Box sx={{ mb: 3 }}>
<IpAddressAndPort label="Socks5 address" ipAddress={ipAddress} port={port} />
</Box>
{/* <ConnectionStats stats={stats} /> */}
<ConnectionTimer connectedSince={connectedSince} />
<ConnectionButton status={status} busy={busy} onClick={onConnectClick} isError={isError} />
</>
);
@@ -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;
}) => (
<Box sx={layout}>
{TopContent}
<Box display="flex" justifyContent="center" alignItems="center">
{ConnectButton}
</Box>
{BottomContent}
</Box>
);
-49
View File
@@ -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 (
<Box pt={1}>
{error && <InfoModal show title={error.title} description={error.message} onClose={clearError} />}
<ConnectionStatus status={ConnectionStatusKind.disconnected} />
<Box px={2}>
<Typography fontWeight="400" fontSize="16px" textAlign="center" pt={2}>
Connect to the Nym <br /> mixnet for privacy.
</Typography>
<Typography textAlign="center" fontSize="small" sx={{ color: 'grey.500' }}>
This is experimental software. Do not rely on it for strong anonymity (yet).
</Typography>
</Box>
<ServiceSelector services={services} onChange={context.setServiceProvider} currentSp={context.serviceProvider} />
<ConnectionTimer />
<ConnectionButton
status={status}
disabled={context.serviceProvider === undefined}
busy={busy}
isError={isError}
onClick={onConnectClick}
/>
</Box>
);
};
@@ -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 (
<HelpPage
step={step}
description="Select your service provider from the dropdown menu."
img={Image1}
onNext={() => setStep(2)}
/>
);
if (step === 2)
return (
<HelpPage
step={step}
description="Click yellow button and connect to a service provider."
img={Image2}
onPrev={() => setStep(1)}
onNext={() => setStep(3)}
/>
);
if (step === 3)
return (
<HelpPage
step={step}
description="Click on IP and Port to copy their values to the clipboard."
img={Image3}
onPrev={() => setStep(2)}
onNext={() => setStep(4)}
/>
);
if (step === 4)
return (
<HelpPage
step={step}
description="Go to the settings of your selected app, under Proxy select run via SOCKS5 proxy then paste the IP and Port values given by NymConnect."
img={Image4}
onPrev={() => setStep(3)}
/>
);
return (
<Stack gap={1}>
<Button variant="text" color="inherit" onClick={() => setStep(1)}>
How to connect guide
</Button>
<Button
LinkComponent={Link}
variant="text"
color="inherit"
href="https://nymtech.net/docs/stable/quickstart/nym-connect/"
target="_blank"
>
Docs
</Button>
</Stack>
);
};

Some files were not shown because too many files have changed in this diff Show More