Compare commits

...

52 Commits

Author SHA1 Message Date
Mark Sinclair 3e517b461c nym-wallet: release v1.0.7 with dark mode and new gas fee simulations 2022-07-11 14:45:52 +01:00
fmtabbara e42e4ddb9b remove hidden backdrops 2022-07-08 23:20:30 +01:00
fmtabbara a31f51b7ca only set stored mode in wallet if not undefined 2022-07-08 23:01:17 +01:00
Jon Häggblad c41a600bcb all: add clap test asserts (#1452) 2022-07-08 21:04:09 +02:00
Fouad 7aa34391fb use tauri forage (#1451)
* use tauri forage

* commit lock file

* update to set in storage directly in set state
2022-07-08 18:41:22 +01:00
fmtabbara f72b56d07e remove old fees ui 2022-07-08 17:06:08 +01:00
Jon Häggblad 44ddbf9ac3 nym-connect: update package versions to resolve conflict (#1450)
* nym-connect: update package versions to resolve conflict

* nym-connect: add pre-target to build shared packages when building storybook

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
2022-07-08 16:34:25 +01:00
fmtabbara 27c377860a remove old property useBootstrapper. See https://newreleases.io/project/github/tauri-apps/tauri/release/tauri-utils-v1.0.0-rc.5 2022-07-08 15:33:06 +01:00
Mark Sinclair 9a3f60f224 nym-connect: at pre-build target that builds the shared packages 2022-07-08 14:16:10 +01:00
Gala 5776600db3 Wallet: Dark mode for Balance page, Side nav. bar & theming in Storybook. (#1396)
* dark mode for balance page

* nav bar dark mode styles

* test wallet stories modes view

* Action Modals and Simple Modal stories

* Redeem Modals stories

* Delegation Modals

* mode handling

* remove not used code

* updating modal historial after update with develop

* wallet: more modals refactor for storybook

* more refactor

* non use redundant boolean value

* dark mode for balance page

* nav bar dark mode styles

* test wallet stories modes view

* adding missing import

* fixing modals in stories

* Action Modals and Simple Modal stories

* Redeem Modals stories

* Delegation Modals

* wrapper dialog content with a paper
2022-07-08 14:08:54 +01:00
Mark Sinclair e851ad8b27 Nym Connect: move into the workspace (#1449)
* nym-connect: use monorepo workspace and dependent packages

* nym-wallet: fix up dependencies

* nym-connect: ip and port copy values to clipboard with UI hint in tooltip

* nym-connect: update icons and word mark

* nym-connect: add automatic updater

* nym-connect: update CHANGELOG
2022-07-08 12:31:30 +01:00
Jon Häggblad 2e0e319511 validator-api: fix typo in apy estimate endpoint (#1447)
* validator-api: fix bug in apy calculations

* validator-api: missing variable
2022-07-07 13:41:51 +02:00
Jon Häggblad 6dd3e36acb mixnode & gateway: move detailed build info back to --version (#1445)
* mixnode and gatway: move build info back to version instead of help

* changelog: add note
2022-07-07 13:16:46 +02:00
Jon Häggblad 40cbfccb73 client: upgrade clap to latest and use declarative form (#1444)
* socks5: upgrade clap to latest and use declarateive derive form

* socks5: rustfmt and clippy

* socks5: missing doc strings

* socks5: default values for eth arguments

* socks5: tidy

* client: upgrade native client to latest clap and declerative form

* changelog: add note
2022-07-07 11:44:08 +02:00
Fouad b49681ef1d Feature/wallet bonding with fees rebase (#1428)
* use confirmation modal for successful/failed unbond

* show loading modal when getting fees

* use new back button

* use new confirmation modal component
2022-07-06 22:34:46 +01:00
Fouad 7e9409bbef Feature/send with simulated fee (#1402)
* add simulate send function

* extract loading modal into its own component

* move appbar component into folder

* add additional prop for making modal list item text bold

* create new send UI and stories

* remove old send page

* use simulated fee in send request

* use new confirmation modal component
2022-07-06 21:08:40 +01:00
Fouad a11e674967 Wallet: Transfer token component with fee included (#1385)
* set up transfer token component with fee included

* open transfer modal after balance refresh

* create fee warning component

* use confirmation modal
2022-07-06 16:19:15 +01:00
Jon Häggblad 2f4e505aac connect: add export_keys tauri function (#1443)
* connect: tidy

* connect: add export_keys tauri function

* changelog: add note

* connect: remove unused
2022-07-06 14:02:40 +02:00
Jon Häggblad 0a4bbf2573 Merge pull request #1440 from nymtech/jon/chore/nym-connect/upgrade-tauri
nym-connect: upgrade tauri to 1.0.2
2022-07-05 21:33:12 +02:00
Jon Häggblad 2ae5ebb8cb nym-connect: upgrade rust tauri to 1.0.2 2022-07-05 21:03:03 +02:00
Jon Häggblad 11be1f8e3e nym-connect: upgrade frontend tauri to 1.0.2
yarn upgrade @tauri-apps/cli @tauri-apps/api --latest
2022-07-05 21:03:03 +02:00
Jon Häggblad 513baba3fd nym-connect: sort out error handling 2022-07-05 21:02:39 +02:00
Jon Häggblad 21b87f6af5 nym-connect: fix catching panic in init 2022-07-05 20:36:55 +02:00
Fouad fe59697aa9 Merge pull request #1435 from nymtech/feature/wallet-modal-back-button
Feature/wallet modal back button
2022-07-05 14:58:32 +01:00
Pierre Dommerc 566ceae325 feat(wallet): add confirmation modal component (#1434)
* feat(wallet): add confirmation modal component
2022-07-05 15:48:47 +02:00
fmtabbara 6bc4d32573 remove unused import 2022-07-05 14:25:53 +01:00
fmtabbara 1fc3c9f31c rename prop 2022-07-05 14:10:54 +01:00
fmtabbara 3cb08a410e fix conflicts 2022-07-05 13:59:46 +01:00
fmtabbara 1648aef1d3 add component story 2022-07-05 11:16:58 +01:00
fmtabbara d98b6b107c add back button option to simple modal component 2022-07-05 11:03:11 +01:00
Mark Sinclair d3db0fc2cd Update CODEOWNERS 2022-07-05 10:00:29 +01:00
Jon Häggblad 06b823f4c1 connect: catch panics during init 2022-07-04 17:19:41 +02:00
Jon Häggblad 1c0ce9a420 Merge pull request #1429 from nymtech/jon/feat/nym-connect-disconnect-signalling
nym-connect: disconnect signalling
2022-07-04 16:46:37 +02:00
Jon Häggblad dede8899cf cargo: exclude nym-connect from top-level workspace 2022-07-04 15:51:09 +02:00
Jon Häggblad 70f6059ceb connect: handle disconnect signalling 2022-07-04 15:51:09 +02:00
Jon Häggblad fefd2dd267 Merge pull request #1427 from nymtech/feature/nym-connect-directory
nym-connect: add ability to select service provider and gateway
2022-07-04 15:50:17 +02:00
Jon Häggblad 0fbe77d934 connect: clippy 2022-07-04 15:23:14 +02:00
Tommy Verrall 8a267cfe3d Merge pull request #1426 from nymtech/tommy/add-placeholder-qa
Add placeholders to service
2022-07-04 14:10:06 +01:00
Jon Häggblad 5ebb2a3efe changelog: add note 2022-07-04 15:06:23 +02:00
Jon Häggblad de605dc3b9 connect: append gateway to id 2022-07-04 15:00:31 +02:00
Mark Sinclair 6a01edf5fe Nym Connect: fetch list of services from wellknown location and let the user choose 2022-07-04 15:00:31 +02:00
tommy 806b37bd83 Add placeholders to service
Update the all.rs to allow all envs
Placing placeholder addresses only
2022-07-04 13:40:22 +02:00
Jędrzej Stuczyński ffff596d45 Removed redundant lock file on explorer api (#1424) 2022-07-04 09:47:38 +01:00
dependabot[bot] ad826da782 build(deps): bump got from 11.8.2 to 11.8.5 in /nym-wallet/webdriver (#1394)
Bumps [got](https://github.com/sindresorhus/got) from 11.8.2 to 11.8.5.
- [Release notes](https://github.com/sindresorhus/got/releases)
- [Commits](https://github.com/sindresorhus/got/compare/v11.8.2...v11.8.5)

---
updated-dependencies:
- dependency-name: got
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 09:45:31 +01:00
dependabot[bot] 3414eeea7a build(deps): bump protobufjs from 6.10.2 to 6.10.3 in /clients/validator (#1407)
Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 6.10.2 to 6.10.3.
- [Release notes](https://github.com/protobufjs/protobuf.js/releases)
- [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/protobufjs/protobuf.js/commits)

---
updated-dependencies:
- dependency-name: protobufjs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 09:45:10 +01:00
dependabot[bot] 3a32d34fb4 build(deps): bump protobufjs (#1408)
Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 6.10.2 to 6.10.3.
- [Release notes](https://github.com/protobufjs/protobuf.js/releases)
- [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/protobufjs/protobuf.js/commits)

---
updated-dependencies:
- dependency-name: protobufjs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 09:45:04 +01:00
dependabot[bot] 248052aec7 build(deps): bump protobufjs from 6.10.2 to 6.10.3 in /testnet-faucet (#1409)
Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 6.10.2 to 6.10.3.
- [Release notes](https://github.com/protobufjs/protobuf.js/releases)
- [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/protobufjs/protobuf.js/commits)

---
updated-dependencies:
- dependency-name: protobufjs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 09:44:58 +01:00
dependabot[bot] 90c00fc343 build(deps): bump protobufjs from 6.10.2 to 6.10.3 (#1410)
Bumps [protobufjs](https://github.com/protobufjs/protobuf.js) from 6.10.2 to 6.10.3.
- [Release notes](https://github.com/protobufjs/protobuf.js/releases)
- [Changelog](https://github.com/protobufjs/protobuf.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/protobufjs/protobuf.js/commits)

---
updated-dependencies:
- dependency-name: protobufjs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-04 09:44:51 +01:00
Tommy Verrall 7bdfbfdcc5 Merge pull request #1384 from Tonycrypto44/Tonycrypto44-patch-1
Update README.md
2022-07-04 09:15:06 +01:00
Jon Häggblad 44e22b74a5 all: fix more clippy::derive-partial-eq-without-eq (#1423) 2022-07-03 23:37:25 +02:00
Jon Häggblad 7920c27648 nym-connect: Cargo.lock (#1422) 2022-07-03 19:34:13 +02:00
Tonycrypto44 f02f914ae2 Update README.md 2022-06-19 19:10:27 -04:00
212 changed files with 6025 additions and 21064 deletions
+4 -4
View File
@@ -19,10 +19,10 @@
Cargo.* @durch @futurechimp @jstuczyn @neacsu @octol
# JS rules:
*.js @mmsinclair @fmtabbara @Aid19801
*.ts @mmsinclair @fmtabbara @Aid19801
*.tsx @mmsinclair @fmtabbara @Aid19801
*.jsx @mmsinclair @fmtabbara @Aid19801
*.js @mmsinclair @fmtabbara
*.ts @mmsinclair @fmtabbara
*.tsx @mmsinclair @fmtabbara
*.jsx @mmsinclair @fmtabbara
# Something looking like possible documentation rules:
*.md @mfahampshire
+14 -3
View File
@@ -9,6 +9,9 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- socks5 client/websocket client: add `--force-register-gateway` flag, useful when rerunning init ([#1353])
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
- nym-connect: add ability to select network requester and gateway ([#1427])
- nym-connect: add ability to export gateway keys as JSON
- nym-connect: add auto updater
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
- explorer-api: learned how to sum the delegations by owner in a new endpoint.
- explorer-api: add apy values to `mix_nodes` endpoint
@@ -21,7 +24,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- validator-api: Added new endpoints for coconut spending flow and communications with coconut & multisig contracts ([#1261])
- validator-api: add `uptime`, `estimated_operator_apy`, `estimated_delegators_apy` to `/mixnodes/detailed` endpoint ([#1393])
- network-statistics: a new mixnet service that aggregates and exposes anonymized data about mixnet services ([#1328])
- wallet: when simulating gas costs, an automatic adjustment is being used ([#1388]).
- mixnode: Added basic mixnode hardware reporting to the HTTP API ([#1308]).
- validator-api: endpoint, in coconut mode, for returning the validator-api cosmos address ([#1404]).
@@ -37,12 +39,14 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
### Changed
- nym-connect: reuse config id instead of creating a new id on each connection.
- nym-connect: reuse config id instead of creating a new id on each connection
- validator-client: created internal `Coin` type that replaces coins from `cosmrs` and `cosmwasm` for API entrypoints [[#1295]]
- all: updated all `cosmwasm`-related dependencies to `1.0.0` and `cw-storage-plus` to `0.13.4` [[#1318]]
- all: updated `rocket` to `0.5.0-rc.2`.
- network-requester: allow to voluntarily store and send statistical data about the number of bytes the proxied server serves ([#1328])
- gateway: allow to voluntarily send statistical data about the number of active inboxes served by a gateway ([#1376])
- gateway & mixnode: move detailed build info back to `--version` from `--help`.
- socks5 client/websocket client: upgrade to latest clap and switched to declarative commandline parsing.
[#1249]: https://github.com/nymtech/nym/pull/1249
[#1256]: https://github.com/nymtech/nym/pull/1256
@@ -60,9 +64,16 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1329]: https://github.com/nymtech/nym/pull/1329
[#1353]: https://github.com/nymtech/nym/pull/1353
[#1376]: https://github.com/nymtech/nym/pull/1376
[#1388]: https://github.com/nymtech/nym/pull/1388
[#1393]: https://github.com/nymtech/nym/pull/1393
[#1404]: https://github.com/nymtech/nym/pull/1404
[#1427]: https://github.com/nymtech/nym/pull/1427
## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-06-21)
- wallet: dark mode
- wallet: when simulating gas costs, an automatic adjustment is being used ([#1388]).
[#1388]: https://github.com/nymtech/nym/pull/1388
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
Generated
+22 -16
View File
@@ -568,16 +568,16 @@ dependencies = [
[[package]]
name = "clap"
version = "3.1.8"
version = "3.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"lazy_static",
"os_str_bytes",
"once_cell",
"strsim 0.10.0",
"termcolor",
"textwrap 0.15.0",
@@ -585,9 +585,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "3.1.7"
version = "3.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902"
dependencies = [
"heck 0.4.0",
"proc-macro-error",
@@ -596,6 +596,15 @@ dependencies = [
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "client-core"
version = "1.0.1"
@@ -884,7 +893,7 @@ dependencies = [
"async-trait",
"bip39",
"cfg-if 0.1.10",
"clap 3.1.8",
"clap 3.2.8",
"coconut-interface",
"credential-storage",
"credentials",
@@ -3049,7 +3058,7 @@ dependencies = [
name = "nym-client"
version = "1.0.1"
dependencies = [
"clap 2.34.0",
"clap 3.2.8",
"client-core",
"coconut-interface",
"config",
@@ -3089,7 +3098,7 @@ dependencies = [
"bandwidth-claim-contract",
"bip39",
"bs58",
"clap 3.1.8",
"clap 3.2.8",
"coconut-interface",
"colored",
"config",
@@ -3134,7 +3143,7 @@ version = "1.0.1"
dependencies = [
"anyhow",
"bs58",
"clap 3.1.8",
"clap 3.2.8",
"colored",
"config",
"crypto",
@@ -3216,7 +3225,7 @@ dependencies = [
name = "nym-socks5-client"
version = "1.0.1"
dependencies = [
"clap 2.34.0",
"clap 3.2.8",
"client-core",
"coconut-interface",
"config",
@@ -3500,9 +3509,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.10.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]]
name = "oorandom"
@@ -3567,9 +3576,6 @@ name = "os_str_bytes"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
dependencies = [
"memchr",
]
[[package]]
name = "pairing"
+1 -1
View File
@@ -81,4 +81,4 @@ default-members = [
"explorer-api",
]
exclude = ["explorer", "contracts", "tokenomics-py", "clients/webassembly", "nym-wallet"]
exclude = ["explorer", "contracts", "tokenomics-py", "clients/webassembly", "nym-wallet", "nym-connect"]
+2 -2
View File
@@ -9,8 +9,8 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* nym-mixnode - shuffles [Sphinx](https://github.com/nymtech/sphinx) packets together to provide privacy against network-level attackers.
* nym-client - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
* nym-socks5-client - a Socks5 proxy you can run on your machine, and use with existing applications
* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices.
* nym-socks5-client - a Socks5 proxy you can run on your machine and use with existing applications.
* nym-gateway - acts sort of like a mailbox for mixnet messages, which removes the need for direct delivery to potentially offline or firewalled devices.
* nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
* nym-explorer - a (projected) block explorer and (existing) mixnet viewer.
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
+2 -1
View File
@@ -2,6 +2,7 @@
name = "nym-client"
version = "1.0.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
rust-version = "1.56"
@@ -19,7 +20,7 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it
# and the single instance of abortable we have should really be refactored anyway
url = "2.2"
clap = "2.33.0" # for the command line arguments
clap = { version = "3.2.8", features = ["cargo", "derive"] }
dirs = "4.0"
dotenv = "0.15.0" # for obtaining environmental variables (only used for RUST_LOG for time being)
log = "0.4" # self explanatory
+88 -79
View File
@@ -1,88 +1,100 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{App, Arg, ArgMatches};
use clap::Args;
use client_core::config::GatewayEndpoint;
use config::NymConfig;
use crate::client::config::Config;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ENABLED_CREDENTIALS_MODE_ARG_NAME,
ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
use crate::{
client::config::Config,
commands::{override_config, OverrideConfig},
};
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init")
.about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id")
.long("id")
.help("Id of the nym-mixnet-client we want to create config for.")
.takes_value(true)
.required(true)
)
.arg(Arg::with_name("gateway")
.long("gateway")
.help("Id of the gateway we are going to connect to.")
.takes_value(true)
)
.arg(Arg::with_name("force-register-gateway")
.long("force-register-gateway")
.help("Force register gateway. WARNING: this will overwrite any existing keys for the given id, potentially causing loss of access.")
.takes_value(false)
)
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
)
.arg(Arg::with_name("disable-socket")
.long("disable-socket")
.help("Whether to not start the websocket")
)
.arg(Arg::with_name("port")
.short("p")
.long("port")
.help("Port for the socket (if applicable) to listen on in all subsequent runs")
.takes_value(true)
)
.arg(Arg::with_name("fastmode")
.long("fastmode")
.hidden(true) // this will prevent this flag from being displayed in `--help`
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.help("Set this client to work in a disabled credentials mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
.required(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
.required(true)
);
#[cfg(all(feature = "eth", not(feature = "coconut")))]
use crate::commands::{DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY};
app
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
#[clap(long)]
id: String,
/// Id of the gateway we are going to connect to.
#[clap(long)]
gateway: Option<String>,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
force_register_gateway: bool,
/// Comma separated list of rest endpoints of the validators
#[clap(long)]
validators: Option<String>,
/// Whether to not start the websocket
#[clap(long)]
disable_socket: bool,
/// Port for the socket (if applicable) to listen on in all subsequent runs
#[clap(short, long)]
port: Option<u16>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hidden = true)]
fastmode: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
/// --eth-private_key don't need to be set.
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_ENDPOINT))
)]
eth_endpoint: String,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_PRIVATE_KEY))
)]
eth_private_key: String,
}
pub async fn execute(matches: ArgMatches<'static>) {
impl From<Init> for OverrideConfig {
fn from(init_config: Init) -> Self {
OverrideConfig {
validators: init_config.validators,
disable_socket: init_config.disable_socket,
port: init_config.port,
fastmode: init_config.fastmode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
enabled_credentials_mode: init_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Some(init_config.eth_private_key),
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Some(init_config.eth_endpoint),
}
}
}
pub(crate) async fn execute(args: &Init) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
let id = &args.id;
let already_init = Config::default_config_file_path(Some(id)).exists();
if already_init {
@@ -95,7 +107,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
// Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted.
let user_wants_force_register = matches.is_present("force-register-gateway");
let user_wants_force_register = args.force_register_gateway;
// If the client was already initialized, don't generate new keys and don't re-register with
// the gateway (because this would create a new shared key).
@@ -103,14 +115,11 @@ pub async fn execute(matches: ArgMatches<'static>) {
let register_gateway = !already_init || user_wants_force_register;
// Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = matches.value_of("gateway");
let user_chosen_gateway_id = args.gateway.as_deref();
let mut config = Config::new(id);
config = override_config(config, &matches);
if matches.is_present("fastmode") {
config.get_base_mut().set_high_default_traffic_volume();
}
let override_config_fields = OverrideConfig::from(args.clone());
config = override_config(config, override_config_fields);
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await;
config.get_base_mut().with_gateway_endpoint(gateway);
+123 -34
View File
@@ -2,14 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use clap::ArgMatches;
use clap::{Parser, Subcommand};
use network_defaults::DEFAULT_NETWORK;
use url::Url;
pub(crate) const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
@@ -21,6 +17,86 @@ pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
fn long_version() -> String {
format!(
r#"
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
"Build Version:",
env!("VERGEN_BUILD_SEMVER"),
"Commit SHA:",
env!("VERGEN_GIT_SHA"),
"Commit Date:",
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
"Commit Branch:",
env!("VERGEN_GIT_BRANCH"),
"rustc Version:",
env!("VERGEN_RUSTC_SEMVER"),
"rustc Channel:",
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
fn long_version_static() -> &'static str {
Box::leak(long_version().into_boxed_str())
}
#[derive(Parser)]
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
pub(crate) enum Commands {
/// Initialise a Nym client. Do this first!
Init(init::Init),
/// Run the Nym client with provided configuration client optionally overriding set parameters
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
}
// Configuration that can be overridden.
pub(crate) struct OverrideConfig {
validators: Option<String>,
disable_socket: bool,
port: Option<u16>,
fastmode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
enabled_credentials_mode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Option<String>,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Option<String>,
}
pub(crate) async fn execute(args: &Cli) {
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
}
}
fn parse_validators(raw: &str) -> Vec<Url> {
raw.split(',')
.map(|raw_validator| {
@@ -32,45 +108,58 @@ fn parse_validators(raw: &str) -> Vec<Url> {
.collect()
}
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config {
if let Some(raw_validators) = matches.value_of("validators") {
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
if let Some(raw_validators) = args.validators {
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(raw_validators));
.set_custom_validator_apis(parse_validators(&raw_validators));
}
if matches.is_present("disable-socket") {
if args.disable_socket {
config = config.with_socket(SocketType::None);
}
if let Some(port) = matches.value_of("port").map(str::parse) {
if let Err(err) = port {
// if port was overridden, it must be parsable
panic!("Invalid port value provided - {:?}", err);
if let Some(port) = args.port {
config = config.with_port(port);
}
#[cfg(all(not(feature = "eth"), not(feature = "coconut")))]
{
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT.to_string());
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
}
#[cfg(all(feature = "eth", not(feature = "coconut")))]
{
if args.enabled_credentials_mode {
config.get_base_mut().with_disabled_credentials(false)
}
if let Some(eth_endpoint) = args.eth_endpoint {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
if let Some(eth_private_key) = args.eth_private_key {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
config = config.with_port(port.unwrap());
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
} else if !cfg!(feature = "eth") {
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
config.get_base_mut().with_eth_private_key(eth_private_key);
} else if !cfg!(feature = "eth") {
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
}
if matches.is_present(ENABLED_CREDENTIALS_MODE_ARG_NAME) {
config.get_base_mut().with_disabled_credentials(false)
if args.fastmode {
config.get_base_mut().set_high_default_traffic_volume();
}
config
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
}
}
+69 -59
View File
@@ -1,68 +1,77 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::client::NymClient;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
ENABLED_CREDENTIALS_MODE_ARG_NAME, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
use crate::{
client::{config::Config, NymClient},
commands::{override_config, OverrideConfig},
};
use clap::{App, Arg, ArgMatches};
use clap::Args;
use config::NymConfig;
use log::*;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id")
.long("id")
.help("Id of the nym-mixnet-client we want to run.")
.takes_value(true)
.required(true)
)
// the rest of arguments are optional, they are used to override settings in config file
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list rest rest endpoints of the validators")
.takes_value(true),
)
.arg(Arg::with_name("gateway")
.long("gateway")
.help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened")
.takes_value(true)
)
.arg(Arg::with_name("disable-socket")
.long("disable-socket")
.help("Whether to not start the websocket")
)
.arg(Arg::with_name("port")
.short("p")
.long("port")
.help("Port for the socket (if applicable) to listen on")
.takes_value(true)
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.help("Set this client to work in a enabled credentials mode that would attempt to use gateway with bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true));
#[derive(Args, Clone)]
pub(crate) struct Run {
/// Id of the nym-mixnet-client we want to run.
#[clap(long)]
id: String,
app
/// Comma separated list of rest endpoints of the validators
#[clap(long)]
validators: Option<String>,
/// Id of the gateway we want to connect to. If overridden, it is user's responsibility to
/// ensure prior registration happened
#[clap(long)]
gateway: Option<String>,
/// Whether to not start the websocket
#[clap(long)]
disable_socket: bool,
/// Port for the socket to listen on
#[clap(short, long)]
port: Option<u16>,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
/// --eth-private-key don't need to be set.
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_endpoint: Option<String>,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_private_key: Option<String>,
}
impl From<Run> for OverrideConfig {
fn from(run_config: Run) -> Self {
OverrideConfig {
validators: run_config.validators,
disable_socket: run_config.disable_socket,
port: run_config.port,
fastmode: false,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
enabled_credentials_mode: run_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: run_config.eth_private_key,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: run_config.eth_endpoint,
}
}
}
// this only checks compatibility between config the binary. It does not take into consideration
@@ -84,8 +93,8 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub async fn execute(matches: ArgMatches<'static>) {
let id = matches.value_of("id").unwrap();
pub(crate) async fn execute(args: &Run) {
let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg,
@@ -95,7 +104,8 @@ pub async fn execute(matches: ArgMatches<'static>) {
}
};
config = override_config(config, &matches);
let override_config_fields = OverrideConfig::from(args.clone());
config = override_config(config, override_config_fields);
if !version_check(&config) {
error!("failed the local version check");
+16 -18
View File
@@ -2,12 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, MISSING_VALUE};
use clap::{App, Arg, ArgMatches};
use config::defaults::default_api_endpoints;
use config::NymConfig;
use config::{defaults::default_api_endpoints, NymConfig};
use version_checker::Version;
use clap::Args;
use std::fmt::Display;
use std::process;
use version_checker::Version;
#[allow(dead_code)]
fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! {
@@ -49,14 +50,11 @@ fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> !
process::exit(1)
}
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
App::new("upgrade").about("Try to upgrade the client").arg(
Arg::with_name("id")
.long("id")
.help("Id of the nym-client we want to upgrade")
.takes_value(true)
.required(true),
)
#[derive(Args, Clone)]
pub(crate) struct Upgrade {
/// Id of the nym-client we want to upgrade
#[clap(long)]
id: String,
}
fn parse_config_version(config: &Config) -> Version {
@@ -95,7 +93,7 @@ fn parse_package_version() -> Version {
fn minor_0_12_upgrade(
mut config: Config,
_matches: &ArgMatches<'_>,
_matches: &Upgrade,
config_version: &Version,
package_version: &Version,
) -> Config {
@@ -131,7 +129,7 @@ fn minor_0_12_upgrade(
config
}
fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: &Version) {
fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) {
loop {
let config_version = parse_config_version(&config);
@@ -143,7 +141,7 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: &Ve
config = match config_version.major {
0 => match config_version.minor {
9 | 10 => outdated_upgrade(&config_version, package_version),
11 => minor_0_12_upgrade(config, matches, &config_version, package_version),
11 => minor_0_12_upgrade(config, args, &config_version, package_version),
_ => unsupported_upgrade(&config_version, package_version),
},
_ => unsupported_upgrade(&config_version, package_version),
@@ -151,10 +149,10 @@ fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: &Ve
}
}
pub fn execute(matches: &ArgMatches<'_>) {
pub(crate) fn execute(args: &Upgrade) {
let package_version = parse_package_version();
let id = matches.value_of("id").unwrap();
let id = &args.id;
let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {:?}", err);
@@ -167,5 +165,5 @@ pub fn execute(matches: &ArgMatches<'_>) {
}
// here be upgrade path to 0.9.X and beyond based on version number from config
do_upgrade(existing_config, matches, &package_version)
do_upgrade(existing_config, args, &package_version)
}
+3 -60
View File
@@ -1,8 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, App, ArgMatches};
use network_defaults::DEFAULT_NETWORK;
use clap::{crate_version, Parser};
pub mod client;
pub mod commands;
@@ -14,30 +13,8 @@ async fn main() {
setup_logging();
println!("{}", banner());
let arg_matches = App::new("Nym Client")
.version(crate_version!())
.long_version(&*long_version())
.author("Nymtech")
.about("Implementation of the Nym Client")
.subcommand(commands::init::command_args())
.subcommand(commands::run::command_args())
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches).await;
}
async fn execute(matches: ArgMatches<'static>) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m.clone()).await,
("run", Some(m)) => commands::run::execute(m.clone()).await,
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
}
fn usage() -> &'static str {
"usage: --help to see available options.\n\n"
let args = commands::Cli::parse();
commands::execute(&args).await;
}
fn banner() -> String {
@@ -57,40 +34,6 @@ fn banner() -> String {
)
}
fn long_version() -> String {
format!(
r#"
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
"Build Version:",
env!("VERGEN_BUILD_SEMVER"),
"Commit SHA:",
env!("VERGEN_GIT_SHA"),
"Commit Date:",
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
"Commit Branch:",
env!("VERGEN_GIT_BRANCH"),
"rustc Version:",
env!("VERGEN_RUSTC_SEMVER"),
"rustc Channel:",
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
if let Ok(s) = ::std::env::var("RUST_LOG") {
+2 -1
View File
@@ -2,6 +2,7 @@
name = "nym-socks5-client"
version = "1.0.1"
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"
rust-version = "1.56"
@@ -10,7 +11,7 @@ name = "nym_socks5"
path = "src/lib.rs"
[dependencies]
clap = "2.33.0"
clap = { version = "3.2.8", features = ["cargo", "derive"] }
dirs = "4.0"
dotenv = "0.15.0"
futures = "0.3"
+8 -5
View File
@@ -287,12 +287,15 @@ impl NymClient {
pub async fn run_and_listen(&mut self, mut receiver: Socks5ControlMessageReceiver) {
self.start().await;
tokio::select! {
message = receiver.next() => match message {
Some(Socks5ControlMessage::Stop) => {
log::info!("Received: {:?}", message);
log::info!("Shutting down");
message = receiver.next() => {
log::debug!("Received message: {:?}", message);
match message {
Some(Socks5ControlMessage::Stop) => {
log::info!("Shutting down");
log::info!("Graceful shutdown of tasks not yet implemented, you might see (harmless) panics until then");
}
None => log::debug!("None"),
}
None => log::info!("none"),
}
}
}
+88 -82
View File
@@ -1,91 +1,100 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{App, Arg, ArgMatches};
use clap::Args;
use client_core::config::GatewayEndpoint;
use config::NymConfig;
use crate::client::config::Config;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ENABLED_CREDENTIALS_MODE_ARG_NAME,
ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
use crate::{
client::config::Config,
commands::{override_config, OverrideConfig},
};
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init")
.about("Initialise a Nym client. Do this first!")
.arg(Arg::with_name("id")
.long("id")
.help("Id of the nym-mixnet-client we want to create config for.")
.takes_value(true)
.required(true)
)
.arg(Arg::with_name("provider")
.long("provider")
.help("Address of the socks5 provider to send messages to.")
.takes_value(true)
.required(true)
)
.arg(Arg::with_name("gateway")
.long("gateway")
.help("Id of the gateway we are going to connect to.")
.takes_value(true)
)
.arg(Arg::with_name("force-register-gateway")
.long("force-register-gateway")
.help("Force register gateway. WARNING: this will overwrite any existing keys for the given id, potentially causing loss of access.")
.takes_value(false)
)
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
)
.arg(Arg::with_name("port")
.short("p")
.long("port")
.help("Port for the socket to listen on in all subsequent runs")
.takes_value(true)
)
.arg(Arg::with_name("fastmode")
.long("fastmode")
.hidden(true) // this will prevent this flag from being displayed in `--help`
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.help("Set this client to work in a enabled credentials mode that would attempt to use gateway with bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
.required(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true)
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
.required(true)
);
#[cfg(all(feature = "eth", not(feature = "coconut")))]
use crate::commands::{DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY};
app
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
#[clap(long)]
id: String,
/// Address of the socks5 provider to send messages to.
#[clap(long)]
provider: String,
/// Id of the gateway we are going to connect to.
#[clap(long)]
gateway: Option<String>,
/// Force register gateway. WARNING: this will overwrite any existing keys for the given id,
/// potentially causing loss of access.
#[clap(long)]
force_register_gateway: bool,
/// Comma separated list of rest endpoints of the validators
#[clap(long)]
validators: Option<String>,
/// Port for the socket to listen on in all subsequent runs
#[clap(short, long)]
port: Option<u16>,
/// Mostly debug-related option to increase default traffic rate so that you would not need to
/// modify config post init
#[clap(long, hidden = true)]
fastmode: bool,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
/// --eth-private_key don't need to be set.
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_ENDPOINT))
)]
eth_endpoint: String,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_PRIVATE_KEY))
)]
eth_private_key: String,
}
pub async fn execute(matches: ArgMatches<'static>) {
impl From<Init> for OverrideConfig {
fn from(init_config: Init) -> Self {
OverrideConfig {
validators: init_config.validators,
port: init_config.port,
fastmode: init_config.fastmode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
enabled_credentials_mode: init_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Some(init_config.eth_private_key),
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Some(init_config.eth_endpoint),
}
}
}
pub(crate) async fn execute(args: &Init) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
let provider_address = matches.value_of("provider").unwrap();
let id = &args.id;
let provider_address = &args.provider;
let already_init = Config::default_config_file_path(Some(id)).exists();
if already_init {
@@ -98,7 +107,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
// Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted.
let user_wants_force_register = matches.is_present("force-register-gateway");
let user_wants_force_register = args.force_register_gateway;
// If the client was already initialized, don't generate new keys and don't re-register with
// the gateway (because this would create a new shared key).
@@ -106,14 +115,11 @@ pub async fn execute(matches: ArgMatches<'static>) {
let register_gateway = !already_init || user_wants_force_register;
// Attempt to use a user-provided gateway, if possible
let user_chosen_gateway_id = matches.value_of("gateway");
let user_chosen_gateway_id = args.gateway.as_deref();
let mut config = Config::new(id, provider_address);
config = override_config(config, &matches);
if matches.is_present("fastmode") {
config.get_base_mut().set_high_default_traffic_volume();
}
let override_config_fields = OverrideConfig::from(args.clone());
config = override_config(config, override_config_fields);
let gateway = setup_gateway(id, register_gateway, user_chosen_gateway_id, &config).await;
config.get_base_mut().with_gateway_endpoint(gateway);
+121 -33
View File
@@ -2,18 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use clap::ArgMatches;
use clap::{Parser, Subcommand};
use network_defaults::DEFAULT_NETWORK;
use url::Url;
pub mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
pub(crate) const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
@@ -21,6 +17,85 @@ pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
fn long_version() -> String {
format!(
r#"
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
"Build Version:",
env!("VERGEN_BUILD_SEMVER"),
"Commit SHA:",
env!("VERGEN_GIT_SHA"),
"Commit Date:",
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
"Commit Branch:",
env!("VERGEN_GIT_BRANCH"),
"rustc Version:",
env!("VERGEN_RUSTC_SEMVER"),
"rustc Channel:",
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
fn long_version_static() -> &'static str {
Box::leak(long_version().into_boxed_str())
}
#[derive(Parser)]
#[clap(author = "Nymtech", version, long_version = long_version_static(), about)]
pub(crate) struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
pub(crate) enum Commands {
/// Initialise a Nym client. Do this first!
Init(init::Init),
/// Run the Nym client with provided configuration client optionally overriding set parameters
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
}
// Configuration that can be overridden.
pub(crate) struct OverrideConfig {
validators: Option<String>,
port: Option<u16>,
fastmode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
enabled_credentials_mode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Option<String>,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Option<String>,
}
pub(crate) async fn execute(args: &Cli) {
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
}
}
fn parse_validators(raw: &str) -> Vec<Url> {
raw.split(',')
.map(|raw_validator| {
@@ -32,41 +107,54 @@ fn parse_validators(raw: &str) -> Vec<Url> {
.collect()
}
pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config {
if let Some(raw_validators) = matches.value_of("validators") {
pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Config {
if let Some(raw_validators) = args.validators {
config
.get_base_mut()
.set_custom_validator_apis(parse_validators(raw_validators));
.set_custom_validator_apis(parse_validators(&raw_validators));
}
if let Some(port) = matches.value_of("port").map(|port| port.parse::<u16>()) {
if let Err(err) = port {
// if port was overridden, it must be parsable
panic!("Invalid port value provided - {:?}", err);
if let Some(port) = args.port {
config = config.with_port(port);
}
#[cfg(all(not(feature = "eth"), not(feature = "coconut")))]
{
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT.to_string());
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
}
#[cfg(all(feature = "eth", not(feature = "coconut")))]
{
if args.enabled_credentials_mode {
config.get_base_mut().with_disabled_credentials(false)
}
if let Some(eth_endpoint) = args.eth_endpoint {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
if let Some(eth_private_key) = args.eth_private_key {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
config = config.with_port(port.unwrap());
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
} else if !cfg!(feature = "eth") {
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
config.get_base_mut().with_eth_private_key(eth_private_key);
} else if !cfg!(feature = "eth") {
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
}
if matches.is_present(ENABLED_CREDENTIALS_MODE_ARG_NAME) {
config.get_base_mut().with_disabled_credentials(false)
if args.fastmode {
config.get_base_mut().set_high_default_traffic_volume();
}
config
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
}
}
+79 -69
View File
@@ -1,74 +1,80 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use crate::client::NymClient;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
ENABLED_CREDENTIALS_MODE_ARG_NAME, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
use crate::{
client::{config::Config, NymClient},
commands::{override_config, OverrideConfig},
};
use clap::{App, Arg, ArgMatches};
use clap::Args;
use config::NymConfig;
use log::*;
use version_checker::is_minor_version_compatible;
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("run")
.about("Run the Nym client with provided configuration client optionally overriding set parameters")
.arg(Arg::with_name("id")
.long("id")
.help("Id of the nym-mixnet-client we want to run.")
.takes_value(true)
.required(true)
)
// the rest of arguments are optional, they are used to override settings in config file
.arg(Arg::with_name("config")
.long("config")
.help("Custom path to the nym-mixnet-client configuration file")
.takes_value(true)
)
.arg(Arg::with_name("provider")
.long("provider")
.help("Address of the socks5 provider to send messages to.")
.takes_value(true)
)
.arg(Arg::with_name("gateway")
.long("gateway")
.help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened")
.takes_value(true)
)
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
)
.arg(Arg::with_name("port")
.short("p")
.long("port")
.help("Port for the socket to listen on")
.takes_value(true)
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.help("Set this client to work in a disabled credentials mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.takes_value(true));
#[derive(Args, Clone)]
pub(crate) struct Run {
/// Id of the nym-mixnet-client we want to run.
#[clap(long)]
id: String,
app
/// Custom path to the nym-mixnet-client configuration file
#[clap(long)]
config: Option<String>,
/// Address of the socks5 provider to send messages to.
#[clap(long)]
provider: Option<String>,
/// Id of the gateway we want to connect to. If overridden, it is user's responsibility to
/// ensure prior registration happened
#[clap(long)]
gateway: Option<String>,
/// Comma separated list of rest endpoints of the validators
#[clap(long)]
validators: Option<String>,
/// Port for the socket to listen on
#[clap(short, long)]
port: Option<u16>,
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement. If this value is set, --eth-endpoint and
/// --eth-private-key don't need to be set.
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long, conflicts_with_all = &["eth-endpoint", "eth-private-key"])]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_endpoint: Option<String>,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_private_key: Option<String>,
}
impl From<Run> for OverrideConfig {
fn from(run_config: Run) -> Self {
OverrideConfig {
validators: run_config.validators,
port: run_config.port,
fastmode: false,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
enabled_credentials_mode: run_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: run_config.eth_private_key,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: run_config.eth_endpoint,
}
}
}
// this only checks compatibility between config the binary. It does not take into consideration
@@ -76,8 +82,13 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
fn version_check(cfg: &Config) -> bool {
let binary_version = env!("CARGO_PKG_VERSION");
let config_version = cfg.get_base().get_version();
if binary_version != config_version {
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
if binary_version == config_version {
true
} else {
warn!(
"The mixnode binary has different version than what is specified in config file! {} and {}",
binary_version, config_version
);
if is_minor_version_compatible(binary_version, config_version) {
info!("but they are still semver compatible. However, consider running the `upgrade` command");
true
@@ -85,13 +96,11 @@ fn version_check(cfg: &Config) -> bool {
error!("and they are semver incompatible! - please run the `upgrade` command before attempting `run` again");
false
}
} else {
true
}
}
pub async fn execute(matches: ArgMatches<'static>) {
let id = matches.value_of("id").unwrap();
pub(crate) async fn execute(args: &Run) {
let id = &args.id;
let mut config = match Config::load_from_file(Some(id)) {
Ok(cfg) => cfg,
@@ -101,7 +110,8 @@ pub async fn execute(matches: ArgMatches<'static>) {
}
};
config = override_config(config, &matches);
let override_config_fields = OverrideConfig::from(args.clone());
config = override_config(config, override_config_fields);
if !version_check(&config) {
error!("failed the local version check");
+20 -23
View File
@@ -2,13 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, MISSING_VALUE};
use clap::{App, Arg, ArgMatches};
use config::defaults::default_api_endpoints;
use config::NymConfig;
use std::fmt::Display;
use std::process;
use config::{defaults::default_api_endpoints, NymConfig};
use version_checker::Version;
use clap::Args;
use std::{fmt::Display, process};
#[allow(dead_code)]
fn fail_upgrade<D1: Display, D2: Display>(from_version: D1, to_version: D2) -> ! {
print_failed_upgrade(from_version, to_version);
@@ -49,14 +49,11 @@ fn unsupported_upgrade(current_version: &Version, config_version: &Version) -> !
process::exit(1)
}
pub fn command_args<'a, 'b>() -> App<'a, 'b> {
App::new("upgrade").about("Try to upgrade the client").arg(
Arg::with_name("id")
.long("id")
.help("Id of the nym-client we want to upgrade")
.takes_value(true)
.required(true),
)
#[derive(Args, Clone)]
pub(crate) struct Upgrade {
/// Id of the nym-client we want to upgrade
#[clap(long)]
id: String,
}
fn parse_config_version(config: &Config) -> Version {
@@ -95,7 +92,7 @@ fn parse_package_version() -> Version {
fn minor_0_12_upgrade(
mut config: Config,
_matches: &ArgMatches<'_>,
_args: &Upgrade,
config_version: &Version,
package_version: &Version,
) -> Config {
@@ -131,30 +128,30 @@ fn minor_0_12_upgrade(
config
}
fn do_upgrade(mut config: Config, matches: &ArgMatches<'_>, package_version: Version) {
fn do_upgrade(mut config: Config, args: &Upgrade, package_version: &Version) {
loop {
let config_version = parse_config_version(&config);
if config_version == package_version {
if &config_version == package_version {
println!("You're using the most recent version!");
return;
}
config = match config_version.major {
0 => match config_version.minor {
9 | 10 => outdated_upgrade(&config_version, &package_version),
11 => minor_0_12_upgrade(config, matches, &config_version, &package_version),
_ => unsupported_upgrade(&config_version, &package_version),
9 | 10 => outdated_upgrade(&config_version, package_version),
11 => minor_0_12_upgrade(config, args, &config_version, package_version),
_ => unsupported_upgrade(&config_version, package_version),
},
_ => unsupported_upgrade(&config_version, &package_version),
_ => unsupported_upgrade(&config_version, package_version),
}
}
}
pub fn execute(matches: &ArgMatches<'_>) {
pub(crate) fn execute(args: &Upgrade) {
let package_version = parse_package_version();
let id = matches.value_of("id").unwrap();
let id = &args.id;
let existing_config = Config::load_from_file(Some(id)).unwrap_or_else(|err| {
eprintln!("failed to load existing config file! - {:?}", err);
@@ -167,5 +164,5 @@ pub fn execute(matches: &ArgMatches<'_>) {
}
// here be upgrade path to 0.9.X and beyond based on version number from config
do_upgrade(existing_config, matches, package_version)
do_upgrade(existing_config, args, &package_version)
}
+3 -60
View File
@@ -1,8 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, App, ArgMatches};
use network_defaults::DEFAULT_NETWORK;
use clap::{crate_version, Parser};
pub mod client;
mod commands;
@@ -14,30 +13,8 @@ async fn main() {
setup_logging();
println!("{}", banner());
let arg_matches = App::new("Nym Socks5 Proxy")
.version(env!("CARGO_PKG_VERSION"))
.author("Nymtech")
.long_version(&*long_version())
.about("A Socks5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address")
.subcommand(commands::init::command_args())
.subcommand(commands::run::command_args())
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches).await;
}
async fn execute(matches: ArgMatches<'static>) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m.clone()).await,
("run", Some(m)) => commands::run::execute(m.clone()).await,
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
}
fn usage() -> &'static str {
"usage: --help to see available options.\n\n"
let args = commands::Cli::parse();
commands::execute(&args).await;
}
fn banner() -> String {
@@ -57,40 +34,6 @@ fn banner() -> String {
)
}
fn long_version() -> String {
format!(
r#"
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
"Build Version:",
env!("VERGEN_BUILD_SEMVER"),
"Commit SHA:",
env!("VERGEN_GIT_SHA"),
"Commit Date:",
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
"Commit Branch:",
env!("VERGEN_GIT_BRANCH"),
"rustc Version:",
env!("VERGEN_RUSTC_SEMVER"),
"rustc Channel:",
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
fn setup_logging() {
let mut log_builder = pretty_env_logger::formatted_timed_builder();
if let Ok(s) = ::std::env::var("RUST_LOG") {
+14 -14
View File
@@ -221,7 +221,7 @@
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
"@protobufjs/base64@^1.1.2":
version "1.1.2"
@@ -236,12 +236,12 @@
"@protobufjs/eventemitter@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
"@protobufjs/fetch@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
dependencies:
"@protobufjs/aspromise" "^1.1.1"
"@protobufjs/inquire" "^1.1.0"
@@ -249,27 +249,27 @@
"@protobufjs/float@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
"@protobufjs/inquire@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
"@protobufjs/path@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
"@protobufjs/pool@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
"@protobufjs/utf8@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
"@types/json-schema@^7.0.9":
version "7.0.11"
@@ -282,9 +282,9 @@
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/long@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
"@types/node@>=13.7.0":
version "17.0.23"
@@ -1642,9 +1642,9 @@ protobufjs@^6.8.8, protobufjs@~6.11.2:
long "^4.0.0"
protobufjs@~6.10.2:
version "6.10.2"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b"
integrity sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==
version "6.10.3"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.3.tgz#11ed1dd02acbfcb330becf1611461d4b407f9eef"
integrity sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==
dependencies:
"@protobufjs/aspromise" "^1.1.2"
"@protobufjs/base64" "^1.1.2"
+1 -1
View File
@@ -15,7 +15,7 @@ use std::ops::Neg;
use zeroize::Zeroize;
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
pub struct Ciphertexts {
pub rr: [G1Projective; NUM_CHUNKS],
pub ss: [G1Projective; NUM_CHUNKS],
+1 -1
View File
@@ -345,7 +345,7 @@ impl PublicKey {
}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct PublicKeyWithProof {
pub(crate) key: PublicKey,
pub(crate) proof: ProofOfDiscreteLog,
+1 -1
View File
@@ -68,7 +68,7 @@ impl<'a> Instance<'a> {
}
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
pub struct ProofOfChunking {
y0: G1Projective,
bb: Vec<G1Projective>,
@@ -14,7 +14,7 @@ const DISCRETE_LOG_DOMAIN: &[u8] =
b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_DISCRETE_LOG";
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct ProofOfDiscreteLog {
pub(crate) rand_commitment: G1Projective,
pub(crate) response: Scalar,
+1 -1
View File
@@ -77,7 +77,7 @@ impl<'a> Instance<'a> {
}
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
pub struct ProofOfSecretSharing {
ff: G1Projective,
aa: G2Projective,
+1 -1
View File
@@ -18,7 +18,7 @@ use std::collections::BTreeMap;
use zeroize::Zeroize;
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct Dealing {
pub public_coefficients: PublicCoefficients,
pub ciphertexts: Ciphertexts,
+4 -2
View File
@@ -100,8 +100,10 @@ impl Network {
pub fn statistics_service_url(&self) -> &str {
match self {
Network::MAINNET => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
_ => {
panic!("statistics service url is only available for mainnet!")
Network::SANDBOX => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
Network::QA => crate::mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
Network::CUSTOM { .. } => {
panic!("statistics service url is unavailable for a custom network")
}
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://qa-validator.nymtech.net",
+1 -1
View File
@@ -21,7 +21,7 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("E8883BAeF3869e14E4823F46662e81D4F7d2A81F");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "nymt1jh0s6qu6tuw9ut438836mmn7f3f2wencrnmdj4";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://0.0.0.0";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://sandbox-validator.nymtech.net",
+3 -3
View File
@@ -20,7 +20,7 @@ pub type EphemeralKey = Scalar;
/// Two G1 points representing ElGamal ciphertext
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct Ciphertext(pub(crate) G1Projective, pub(crate) G1Projective);
impl TryFrom<&[u8]> for Ciphertext {
@@ -73,7 +73,7 @@ impl Ciphertext {
/// PrivateKey used in the ElGamal encryption scheme to recover the plaintext
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct PrivateKey(pub(crate) Scalar);
impl PrivateKey {
@@ -121,7 +121,7 @@ impl Base58 for PrivateKey {}
// TODO: perhaps be more explicit and apart from gamma also store generator and group order?
/// PublicKey used in the ElGamal encryption scheme to produce the ciphertext
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct PublicKey(G1Projective);
impl PublicKey {
+1 -1
View File
@@ -23,7 +23,7 @@ use crate::Attribute;
type ChallengeDigest = Sha256;
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct ProofCmCs {
challenge: Scalar,
response_opening: Scalar,
+1 -1
View File
@@ -25,7 +25,7 @@ use crate::utils::{hash_g1, try_deserialize_g1_projective};
// TODO NAMING: double check this one
// Lambda
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct BlindSignRequest {
// cm
commitment: G1Projective,
+2 -2
View File
@@ -23,7 +23,7 @@ use crate::utils::{
use crate::Base58;
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct SecretKey {
pub(crate) x: Scalar,
pub(crate) ys: Vec<Scalar>,
@@ -351,7 +351,7 @@ impl Bytable for VerificationKey {
impl Base58 for VerificationKey {}
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct KeyPair {
secret_key: SecretKey,
verification_key: VerificationKey,
+1 -1
View File
@@ -103,7 +103,7 @@ impl Bytable for Signature {
impl Base58 for Signature {}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct BlindedSignature(G1Projective, G1Projective);
impl Bytable for BlindedSignature {
+6 -6
View File
@@ -1480,9 +1480,9 @@
}
},
"node_modules/protobufjs": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz",
"integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==",
"version": "6.10.3",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz",
"integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==",
"hasInstallScript": true,
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
@@ -3162,9 +3162,9 @@
"dev": true
},
"protobufjs": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz",
"integrity": "sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ==",
"version": "6.10.3",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.3.tgz",
"integrity": "sha512-yvAslS0hNdBhlSKckI4R1l7wunVilX66uvrjzE4MimiAt7/qw1nLpMhZrn/ObuUTM/c3Xnfl01LYMdcSJe6dwg==",
"requires": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
-2438
View File
File diff suppressed because it is too large Load Diff
+19 -5
View File
@@ -9,15 +9,15 @@ mod commands;
mod config;
mod node;
static LONG_ABOUT: OnceCell<String> = OnceCell::new();
static LONG_VERSION: OnceCell<String> = OnceCell::new();
// Helper for passing LONG_ABOUT to clap
fn long_about() -> &'static str {
LONG_ABOUT.get().expect("Failed to get long about text")
fn long_version_static() -> &'static str {
LONG_VERSION.get().expect("Failed to get long about text")
}
#[derive(Parser)]
#[clap(author = "Nymtech", version, about, long_about = Some(long_about()))]
#[clap(author = "Nymtech", version, about, long_version = long_version_static())]
struct Cli {
#[clap(subcommand)]
command: commands::Commands,
@@ -28,7 +28,7 @@ async fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
LONG_ABOUT
LONG_VERSION
.set(long_version())
.expect("Failed to set long about text");
@@ -107,3 +107,17 @@ fn setup_logging() {
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.init();
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
LONG_VERSION
.set(long_version())
.expect("Failed to set long about text");
Cli::command().debug_assert();
}
}
+16 -5
View File
@@ -13,16 +13,16 @@ mod config;
mod node;
lazy_static! {
pub static ref LONG_ABOUT: String = long_version();
pub static ref LONG_VERSION: String = long_version();
}
// Helper for passing LONG_ABOUT to clap
fn long_about() -> &'static str {
&LONG_ABOUT
// Helper for passing LONG_VERSION to clap
fn long_version_static() -> &'static str {
&LONG_VERSION
}
#[derive(Parser)]
#[clap(author = "Nymtech", version, about, long_about = Some(long_about()))]
#[clap(author = "Nymtech", version, about, long_version = long_version_static())]
struct Cli {
#[clap(subcommand)]
command: commands::Commands,
@@ -106,3 +106,14 @@ fn setup_logging() {
.filter_module("want", log::LevelFilter::Warn)
.init();
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
}
}
+3 -107
View File
@@ -1,112 +1,8 @@
{
"env": {
"browser": true,
"es6": true,
"node": true,
"jest": true
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2019,
"sourceType": "module"
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["react", "react-hooks", "jsx-a11y", "prettier", "jest"],
"extends": [
"plugin:react/recommended",
"airbnb",
"prettier",
"plugin:jest/recommended",
"plugin:jest/style"
"@nymproject/eslint-config-react-typescript"
],
"rules": {
"jest/prefer-strict-equal": "error",
"jest/prefer-to-have-length": "warn",
"prettier/prettier": "error",
"import/prefer-default-export": "off",
"react/prop-types": "off",
"react/require-default-props": "off",
"react/jsx-filename-extension": "off",
"react/jsx-props-no-spreading": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.[jt]s",
"**/*.spec.[jt]s",
"**/*.test.[jt]sx",
"**/*.spec.[jt]sx"
]
}
],
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"tsx": "never",
"js": "never",
"jsx": "never"
}
]
},
"overrides": [
{
"files": "**/*.+(ts|tsx)",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx",
"**/*.stories.*",
"**/.storybook/**/*.*"
]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
],
"settings": {
"import/resolver": {
"root-import": {
"rootPathPrefix": "@",
"rootPathSuffix": "src",
"extensions": [".js", ".ts", ".tsx", ".jsx", ".mdx"]
}
}
"parserOptions": {
"project": "./tsconfig.eslint.json"
}
}
+56 -10
View File
@@ -1,14 +1,60 @@
/* eslint-disable no-param-reassign */
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
stories: [
"../src/**/*.stories.mdx",
"../src/**/*.stories.@(js|jsx|ts|tsx)"
],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials"
],
framework: "@storybook/react",
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
framework: '@storybook/react',
core: {
builder: 'webpack5',
},
typescript: { reactDocgen: false },
// webpackFinal: async (config, { configType }) => {
// // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
// // You can change the configuration based on that.
// // 'PRODUCTION' is used when building the static version of storybook.
webpackFinal: async (config) => {
config.module.rules.forEach((rule) => {
// look for SVG import rule and replace
// NOTE: the rule before modification is /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/
if (rule.test?.toString().includes('svg')) {
rule.test = /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/;
}
});
// handle asset loading with this
config.module.rules.unshift({
test: /\.svg(\?.*)?$/i,
issuer: /\.[jt]sx?$/,
use: ['@svgr/webpack'],
});
config.resolve.extensions = ['.tsx', '.ts', '.js'];
config.resolve.plugins = [new TsconfigPathsPlugin()];
config.plugins.push(
new ForkTsCheckerWebpackPlugin({
typescript: {
mode: 'write-references',
diagnosticOptions: {
semantic: true,
syntactic: true,
},
},
}),
);
if (!config.resolve.alias) {
config.resolve.alias = {};
}
config.resolve.alias['@tauri-apps/api'] = `${__dirname}/mocks/tauri`;
// Return the altered config
return config;
},
features: {
emotionAlias: false,
},
}
};
@@ -0,0 +1,8 @@
/**
* This is a mock for Tauri's API package (@tauri-apps/api/app), to prevent stories from being excluded, because they either use
* or import dependencies that use Tauri.
*/
module.exports = {
getVersion: () => undefined,
};
@@ -0,0 +1,8 @@
/**
* This is a mock for Tauri's API package (@tauri-apps/api/clipboard), to prevent stories from being excluded, because they either use
* or import dependencies that use Tauri.
*/
module.exports = {
writeText: async () => undefined,
}
@@ -0,0 +1,8 @@
/**
* This is a mock for Tauri's API package (@tauri-apps/api/clipboard), to prevent stories from being excluded, because they either use
* or import dependencies that use Tauri.
*/
module.exports = {
listen: async () => undefined,
}
@@ -0,0 +1,14 @@
/**
* This is a mock for Tauri's API package (@tauri-apps/api), to prevent stories from being excluded, because they either use
* or import dependencies that use Tauri.
*/
module.exports = {
invoke: (operation, args) => {
console.error(
`Tauri cannot be used in Storybook. The operation requested was "${operation}". You can add mock responses to "nym_connect/.storybook/mocks/tauri.js" if you need. The default response is "void".`,
);
return new Promise((resolve, reject) => {
reject(new Error(`Tauri operation ${operation} not available in storybook.`));
});
},
};
@@ -0,0 +1,10 @@
/**
* This is a mock for Tauri's API package (@tauri-apps/api/window), to prevent stories from being excluded, because they either use
* or import dependencies that use Tauri.
*/
module.exports = {
appWindow: {
maximize: () => undefined,
}
}
+954 -896
View File
File diff suppressed because it is too large Load Diff
+45 -27
View File
@@ -4,33 +4,36 @@
"main": "index.js",
"license": "MIT",
"scripts": {
"prewebpack:dev": "yarn --cwd .. build",
"webpack:dev": "yarn webpack serve --config webpack.dev.js",
"webpack:prod": "yarn webpack --progress --config webpack.prod.js",
"tauri:dev": "RUST_DEBUG=1 yarn tauri dev",
"tauri:build": "yarn tauri build",
"dev": "run-p webpack:dev tauri:dev",
"prebuild": "yarn --cwd .. build",
"build": "run-s webpack:prod tauri:build",
"storybook": "start-storybook -p 6006",
"prestorybook:build": "yarn --cwd .. build",
"storybook:build": "build-storybook",
"tsc": "tsc --noEmit true",
"tsc:watch": "tsc --noEmit true --watch",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"test": "jest",
"test:watch": "jest --watch"
},
"dependencies": {
"@babel/preset-typescript": "^7.15.0",
"@emotion/react": "^11.7.0",
"@emotion/styled": "^11.6.0",
"@hookform/resolvers": "^2.8.0",
"@mui/icons-material": "^5.2.0",
"@mui/material": "^5.2.2",
"@mui/styles": "^5.2.2",
"@types/react-dom": "^17.0.9",
"bs58": "^4.0.1",
"@nymproject/react": "^1.0.0",
"@tauri-apps/api": "^1.0.2",
"clsx": "^1.1.1",
"luxon": "^2.3.0",
"pretty-bytes": "^6.0.0",
"qrcode.react": "^1.0.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-error-boundary": "^3.1.3",
@@ -44,48 +47,63 @@
"@babel/plugin-transform-async-to-generator": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@storybook/addon-actions": "^6.4.18",
"@storybook/addon-essentials": "^6.4.18",
"@storybook/addon-links": "^6.4.18",
"@storybook/react": "^6.4.18",
"@babel/preset-typescript": "^7.15.0",
"@nymproject/eslint-config-react-typescript": "^1.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"@storybook/react": "^6.5.8",
"@svgr/webpack": "^6.1.1",
"@tauri-apps/api": "^1.0.0-rc.4",
"@tauri-apps/cli": "^1.0.0-rc.9",
"@types/bs58": "^4.0.1",
"@types/luxon": "^2.0.9",
"@types/qrcode.react": "^1.0.2",
"@types/react-router-dom": "^5.1.8",
"@tauri-apps/cli": "^1.0.2",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@types/jest": "^27.0.1",
"@types/luxon": "^2.3.2",
"@types/node": "^16.7.13",
"@types/react": "^17.0.34",
"@types/react-dom": "^17.0.9",
"@types/semver": "^7.3.8",
"@typescript-eslint/eslint-plugin": "4.31.0",
"@typescript-eslint/parser": "4.31.0",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"babel-loader": "^8.2.2",
"babel-plugin-root-import": "^6.6.0",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"dotenv-webpack": "^7.0.3",
"eslint": "7.32.0",
"eslint-config-airbnb": "18.2.1",
"eslint-config-prettier": "8.3.0",
"eslint-import-resolver-root-import": "1.0.4",
"eslint-plugin-import": "2.24.2",
"eslint-plugin-jest": "^24.4.0",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-prettier": "4.0.0",
"eslint-plugin-react": "7.25.1",
"eslint-plugin-react-hooks": "4.2.0",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-storybook": "^0.5.12",
"favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.1",
"html-webpack-plugin": "^5.3.2",
"jest": "^27.1.0",
"mini-css-extract-plugin": "^2.2.2",
"npm-run-all": "^4.1.5",
"prettier": "2.3.2",
"react-refresh": "^0.10.0",
"react-refresh-typescript": "^2.0.2",
"style-loader": "^3.2.1",
"thread-loader": "^3.0.4",
"ts-jest": "^27.0.5",
"typescript": "^4.4.2",
"ts-loader": "^9.2.5",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.6.2",
"url-loader": "^4.1.1",
"webpack": "^5.64.3",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.5.0",
"webpack-favicons": "^1.3.8",
"webpack-merge": "^5.8.0"
}
}
+14 -12
View File
@@ -13,28 +13,30 @@ rust-version = "1.58"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "=1.0.0-rc.7", features = [] }
tauri-build = { version = "^1.0.2", features = [] }
tauri-codegen = "=1.0.0-rc.5"
tauri-macros = "=1.0.0-rc.5"
tauri-codegen = "^1.0.2"
tauri-macros = "^1.0.2"
[dependencies]
bip39 = "1.0"
dirs = "4.0"
eyre = "0.6.5"
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
futures = "0.3"
rand = "0.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "=1.0.0-rc.8", features = ["ayatana-tray", "shell-open", "system-tray"] }
tendermint-rpc = "0.23.0"
thiserror = "1.0"
tokio = { version = "1.19.1", features = ["sync"] }
url = "2.2"
log = "0.4"
pretty_env_logger = "0.4.0"
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
rand = "0.8"
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tap = "1.0.1"
tauri = { version = "^1.0.2", features = ["clipboard-write-text", "shell-open", "system-tray", "updater"] }
tendermint-rpc = "0.23.0"
thiserror = "1.0"
tokio = { version = "1.19.1", features = ["sync", "time"] }
url = "2.2"
client-core = { path = "../../clients/client-core" }
config = { path = "../../common/config" }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 60 KiB

+1 -1
View File
@@ -1,3 +1,3 @@
fn main() {
tauri_build::build()
tauri_build::build();
}
+69 -31
View File
@@ -1,32 +1,48 @@
use std::path::PathBuf;
use client_core::config::GatewayEndpoint;
use log::info;
use std::sync::Arc;
use tap::TapFallible;
use tokio::sync::RwLock;
use client_core::config::Config as BaseConfig;
use config::NymConfig;
use nym_socks5::client::config::Config as Socks5Config;
pub static SOCKS5_CONFIG_ID: &str = "nym-connect";
use crate::{
error::{BackendError, Result},
state::State,
};
// This is an open-proxy network-requester for testing
// TODO: make this configurable from the UI
// TODO: once we can set this is the UI, consider just removing it, and put in guards to halt if
// user hasn't chosen the provider
pub static PROVIDER_ADDRESS: &str = "8CrdmK4mYgZ5caMxGU4AvNeT1dXL8VSbgMYAjSFvnfut.2GLdZ1Jn9vkTBMf858evGNGDsPoeivUPw7zFNceLiLX3@BNjYZPxzcJwczXHHgBxCAyVJKxN6LPteDRrKapxWmexv";
static SOCKS5_CONFIG_ID: &str = "nym-connect";
const DEFAULT_ETH_ENDPOINT: &str = "https://rinkeby.infura.io/v3/00000000000000000000000000000000";
const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
#[tauri::command]
pub fn get_config_file_location() -> String {
let id: &str = SOCKS5_CONFIG_ID;
Config::config_file_location(id)
.to_string_lossy()
.to_string()
pub fn socks5_config_id_appended_with(gateway_id: &str) -> Result<String> {
use std::fmt::Write as _;
let mut id = SOCKS5_CONFIG_ID.to_string();
write!(id, "-{}", gateway_id)?;
Ok(id)
}
#[tauri::command]
pub async fn get_config_id(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<String> {
state.read().await.get_config_id()
}
#[tauri::command]
pub async fn get_config_file_location(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<String> {
let id = get_config_id(state).await?;
Ok(Config::config_file_location(&id)
.to_string_lossy()
.to_string())
}
#[derive(Debug)]
pub struct Config {
socks5: Socks5Config,
}
@@ -55,12 +71,27 @@ impl Config {
self.socks5.get_base_mut()
}
pub async fn init(service_provider: Option<&String>, chosen_gateway_id: Option<&String>) {
let service_provider = service_provider.map_or(PROVIDER_ADDRESS, String::as_str);
let chosen_gateway_id = chosen_gateway_id.map(String::as_str);
info!("Initialising...");
init_socks5(service_provider, chosen_gateway_id).await;
info!("Configuration saved 🚀");
pub async fn init(service_provider: &str, chosen_gateway_id: &str) -> Result<()> {
log::info!("Initialising...");
let service_provider = service_provider.to_owned();
let chosen_gateway_id = chosen_gateway_id.to_owned();
// The client initialization was originally not written for this use case, so there are
// lots of ways it can panic. Until we have proper error handling in the init code for the
// clients we'll catch any panics here by spawning a new runtime in a separate thread.
std::thread::spawn(move || {
tokio::runtime::Runtime::new()
.expect("Failed to create tokio runtime")
.block_on(
async move { init_socks5_config(service_provider, chosen_gateway_id).await },
)
})
.join()
.map_err(|_| BackendError::InitializationPanic)??;
log::info!("Configuration saved 🚀");
Ok(())
}
pub fn config_file_location(id: &str) -> PathBuf {
@@ -68,16 +99,17 @@ impl Config {
}
}
pub async fn init_socks5(provider_address: &str, chosen_gateway_id: Option<&str>) {
log::info!("Initialising client...");
pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: String) -> Result<()> {
log::trace!("Initialising client...");
let id: &str = SOCKS5_CONFIG_ID;
// Append the gateway id to the name id that we store the config under
let id = socks5_config_id_appended_with(&chosen_gateway_id)?;
log::debug!(
"Attempting to use config file location: {}",
Config::config_file_location(id).to_string_lossy(),
Config::config_file_location(&id).to_string_lossy(),
);
let already_init = Config::config_file_location(id).exists();
let already_init = Config::config_file_location(&id).exists();
if already_init {
log::info!(
"SOCKS5 client \"{}\" was already initialised before! \
@@ -92,7 +124,7 @@ pub async fn init_socks5(provider_address: &str, chosen_gateway_id: Option<&str>
let register_gateway = !already_init || user_wants_force_register;
log::trace!("Creating config for id: {}", id);
let mut config = Config::new(id, provider_address);
let mut config = Config::new(id.as_str(), &provider_address);
// As far as I'm aware, these two are not used, they are only set because the socks5 init code
// requires them for initialising the bandwidth controller.
@@ -103,14 +135,19 @@ pub async fn init_socks5(provider_address: &str, chosen_gateway_id: Option<&str>
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
let gateway = setup_gateway(id, register_gateway, chosen_gateway_id, config.get_socks5()).await;
let gateway = setup_gateway(
&id,
register_gateway,
Some(&chosen_gateway_id),
config.get_socks5(),
)
.await;
config.get_base_mut().with_gateway_endpoint(gateway);
let config_save_location = config.get_socks5().get_config_file_save_location();
config
.get_socks5()
.save_to_file(None)
.expect("Failed to save the config file");
config.get_socks5().save_to_file(None).tap_err(|_| {
log::warn!("Failed to save the config file");
})?;
log::info!("Saved configuration file to {:?}", config_save_location);
log::info!("Gateway id: {}", config.get_base().get_gateway_id());
@@ -128,9 +165,10 @@ pub async fn init_socks5(provider_address: &str, chosen_gateway_id: Option<&str>
"Service provider port: {}",
config.get_socks5().get_listening_port()
);
info!("Client configuration completed.");
log::info!("Client configuration completed.");
client_core::init::show_address(config.get_base());
Ok(())
}
// TODO: deduplicate with same functions in other client
+42 -1
View File
@@ -4,23 +4,64 @@ use thiserror::Error;
#[allow(unused)]
#[derive(Error, Debug)]
pub enum BackendError {
#[error("{source}")]
ReqwestError {
#[from]
source: reqwest::Error,
},
#[error("I/O error: {source}")]
IoError {
#[from]
source: std::io::Error,
},
#[error("String formatting error: {source}")]
FmtError {
#[from]
source: std::fmt::Error,
},
#[error("Tauri error: {source}")]
TauriError {
#[from]
source: tauri::Error,
},
#[error("{source}")]
SerdeJsonError {
#[from]
source: serde_json::Error,
},
#[error("State error")]
StateError,
#[error("Could not connect")]
CouldNotConnect,
#[error("Could not disconnect")]
CouldNotDisconnect,
#[error("Could not send disconnect signal to the SOCKS5 client")]
CoundNotSendDisconnectSignal,
#[error("No service provider set")]
NoServiceProviderSet,
#[error("No gateway provider set")]
NoGatewaySet,
#[error("Initialization failed with a panic")]
InitializationPanic,
#[error("Could not get config id before gateway is set")]
CouldNotGetIdWithoutGateway,
#[error("Could initialize without gateway set")]
CouldNotInitWithoutGateway,
#[error("Could initialize without service provider set")]
CouldNotInitWithoutServiceProvider,
#[error("Could not get file name")]
CouldNotGetFilename,
}
impl Serialize for BackendError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(self)
}
}
// Local crate level Result alias
pub(crate) type Result<T, E = BackendError> = std::result::Result<T, E>;
+9
View File
@@ -19,6 +19,7 @@ mod menu;
mod models;
mod operations;
mod state;
mod tasks;
mod window;
fn main() {
@@ -35,12 +36,15 @@ fn main() {
.manage(Arc::new(RwLock::new(State::new())))
.invoke_handler(tauri::generate_handler![
crate::config::get_config_file_location,
crate::config::get_config_id,
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::directory::get_services,
crate::operations::export::export_keys,
crate::operations::window::hide_window,
])
.menu(Menu::new().add_default_app_submenu_if_macos())
@@ -61,5 +65,10 @@ fn setup_logging() {
log_builder
.filter_module("handlebars", log::LevelFilter::Warn)
.filter_module("mio", log::LevelFilter::Warn)
.filter_module("sled", log::LevelFilter::Warn)
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.filter_module("tungstenite", log::LevelFilter::Warn)
.filter_module("want", log::LevelFilter::Warn)
.init();
}
+33
View File
@@ -1,3 +1,5 @@
use core::fmt;
use serde::{Deserialize, Serialize};
#[cfg_attr(test, derive(ts_rs::TS))]
@@ -23,6 +25,17 @@ pub enum ConnectionStatusKind {
Connecting,
}
impl fmt::Display for ConnectionStatusKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ConnectionStatusKind::Disconnected => write!(f, "Disconnected"),
ConnectionStatusKind::Disconnecting => write!(f, "Disconnecting"),
ConnectionStatusKind::Connected => write!(f, "Connected"),
ConnectionStatusKind::Connecting => write!(f, "Connecting"),
}
}
}
pub const APP_EVENT_CONNECTION_STATUS_CHANGED: &str = "app:connection-status-changed";
#[cfg_attr(test, derive(ts_rs::TS))]
@@ -30,3 +43,23 @@ pub const APP_EVENT_CONNECTION_STATUS_CHANGED: &str = "app:connection-status-cha
pub struct AppEventConnectionStatusChangedPayload {
pub status: ConnectionStatusKind,
}
#[cfg_attr(test, derive(ts_rs::TS))]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct DirectoryService {
pub id: String,
pub description: String,
pub items: Vec<DirectoryServiceProvider>,
}
#[cfg_attr(test, derive(ts_rs::TS))]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct DirectoryServiceProvider {
pub id: String,
pub description: String,
/// Address of the network requester in the form "<gateway_id>.<service_provider_id>"
/// e.g. DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
pub address: String,
/// Address of the gateway, e.g. 2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
pub gateway: String,
}
@@ -1,6 +1,8 @@
use crate::error::BackendError;
use crate::models::ConnectResult;
use crate::State;
use crate::{
error::{BackendError, Result},
models::ConnectResult,
tasks, State,
};
use std::sync::Arc;
use tokio::sync::RwLock;
@@ -8,21 +10,23 @@ use tokio::sync::RwLock;
pub async fn start_connecting(
state: tauri::State<'_, Arc<RwLock<State>>>,
window: tauri::Window<tauri::Wry>,
) -> Result<ConnectResult, BackendError> {
let mut guard = state.write().await;
) -> Result<ConnectResult> {
let status_receiver = {
let mut state_w = state.write().await;
state_w.start_connecting(&window).await?
};
guard.start_connecting(&window).await;
// Setup task for checking status
let state = state.inner().clone();
tasks::start_disconnect_listener(state, window, status_receiver);
Ok(ConnectResult {
// WIP(JON): fixme
address: "Test".to_string(),
address: "PLACEHOLDER".to_string(),
})
}
#[tauri::command]
pub async fn get_service_provider(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<String, BackendError> {
pub async fn get_service_provider(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<String> {
let guard = state.read().await;
guard
.get_service_provider()
@@ -34,16 +38,15 @@ pub async fn get_service_provider(
pub async fn set_service_provider(
service_provider: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
) -> Result<()> {
log::trace!("Setting service_provider: {service_provider}");
let mut guard = state.write().await;
guard.set_service_provider(service_provider);
Ok(())
}
#[tauri::command]
pub async fn get_gateway(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<String, BackendError> {
pub async fn get_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<String> {
let guard = state.read().await;
guard
.get_gateway()
@@ -55,7 +58,8 @@ pub async fn get_gateway(
pub async fn set_gateway(
gateway: String,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
) -> Result<()> {
log::trace!("Setting gateway: {gateway}");
let mut guard = state.write().await;
guard.set_gateway(gateway);
Ok(())
@@ -1,4 +1,4 @@
use crate::error::BackendError;
use crate::error::Result;
use crate::models::ConnectResult;
use crate::State;
use std::sync::Arc;
@@ -8,13 +8,12 @@ use tokio::sync::RwLock;
pub async fn start_disconnecting(
state: tauri::State<'_, Arc<RwLock<State>>>,
window: tauri::Window<tauri::Wry>,
) -> Result<ConnectResult, BackendError> {
) -> Result<ConnectResult> {
let mut guard = state.write().await;
guard.start_disconnecting(&window).await;
guard.start_disconnecting(&window).await?;
Ok(ConnectResult {
// WIP(JON): fixme
address: "Test".to_string(),
address: "PLACEHOLDER".to_string(),
})
}
@@ -0,0 +1,14 @@
use crate::error::Result;
use crate::models::DirectoryService;
static SERVICE_PROVIDER_WELLKNOWN_URL: &str =
"https://nymtech.net/.wellknown/connect/service-providers.json";
#[tauri::command]
pub async fn get_services() -> Result<Vec<DirectoryService>> {
let res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
.await?
.json::<Vec<DirectoryService>>()
.await?;
Ok(res)
}
@@ -0,0 +1,73 @@
use std::{ffi::OsStr, fs, sync::Arc};
use tokio::sync::RwLock;
use crate::{
error::{BackendError, Result},
state::State,
};
/// Export the gateway keys as a JSON string blob
#[tauri::command]
pub async fn export_keys(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<String> {
let config = {
let state = state.read().await;
state.load_socks5_config()?
};
// Get key paths
let ack_key_file = config.get_base().get_ack_key_file();
let gateway_shared_key_file = config.get_base().get_gateway_shared_key_file();
let pub_id_key_file = config.get_base().get_public_identity_key_file();
let priv_id_key_file = config.get_base().get_private_identity_key_file();
let pub_enc_key_file = config.get_base().get_public_encryption_key_file();
let priv_enc_key_file = config.get_base().get_private_encryption_key_file();
// Read file contents
let ack_key = fs::read_to_string(ack_key_file.clone())?;
let gateway_shared_key = fs::read_to_string(gateway_shared_key_file.clone())?;
let pub_id_key = fs::read_to_string(pub_id_key_file.clone())?;
let priv_id_key = fs::read_to_string(priv_id_key_file.clone())?;
let pub_enc_key = fs::read_to_string(pub_enc_key_file.clone())?;
let priv_enc_key = fs::read_to_string(priv_enc_key_file.clone())?;
let ack_key_file = ack_key_file
.file_name()
.map(OsStr::to_string_lossy)
.ok_or(BackendError::CouldNotGetFilename)?;
let gateway_shared_key_file = gateway_shared_key_file
.file_name()
.map(OsStr::to_string_lossy)
.ok_or(BackendError::CouldNotGetFilename)?;
let pub_id_key_file = pub_id_key_file
.file_name()
.map(OsStr::to_string_lossy)
.ok_or(BackendError::CouldNotGetFilename)?;
let priv_id_key_file = priv_id_key_file
.file_name()
.map(OsStr::to_string_lossy)
.ok_or(BackendError::CouldNotGetFilename)?;
let pub_enc_key_file = pub_enc_key_file
.file_name()
.map(OsStr::to_string_lossy)
.ok_or(BackendError::CouldNotGetFilename)?;
let priv_enc_key_file = priv_enc_key_file
.file_name()
.map(OsStr::to_string_lossy)
.ok_or(BackendError::CouldNotGetFilename)?;
// Format and return as json
let json = serde_json::json!({
ack_key_file: ack_key,
gateway_shared_key_file: gateway_shared_key,
pub_id_key_file: pub_id_key,
priv_id_key_file: priv_id_key,
pub_enc_key_file: pub_enc_key,
priv_enc_key_file: priv_enc_key,
});
Ok(serde_json::to_string_pretty(&json)?)
}
@@ -1,2 +1,4 @@
pub mod connection;
pub mod directory;
pub mod export;
pub mod window;
+112 -63
View File
@@ -1,24 +1,35 @@
use client_core::config::GatewayEndpoint;
use futures::channel::mpsc;
use std::time::Duration;
use ::config::NymConfig;
use futures::SinkExt;
use log::info;
use config::NymConfig;
#[cfg(not(feature = "coconut"))]
use nym_socks5::client::NymClient as Socks5NymClient;
use nym_socks5::client::{Socks5ControlMessage, Socks5ControlMessageSender};
use crate::config::SOCKS5_CONFIG_ID;
use crate::models::{
AppEventConnectionStatusChangedPayload, ConnectionStatusKind,
APP_EVENT_CONNECTION_STATUS_CHANGED,
};
use tap::TapFallible;
use tauri::Manager;
use nym_socks5::client::{
config::Config as Socks5Config, Socks5ControlMessage, Socks5ControlMessageSender,
};
use crate::{
config::{self, socks5_config_id_appended_with},
error::{BackendError, Result},
models::{
AppEventConnectionStatusChangedPayload, ConnectionStatusKind,
APP_EVENT_CONNECTION_STATUS_CHANGED,
},
tasks::{self, StatusReceiver},
};
pub struct State {
/// The current connection status
status: ConnectionStatusKind,
/// The service provider
service_provider: Option<String>,
/// The gateway used. Note that this is also used to create the configuration id
gateway: Option<String>,
/// Channel that is used to send command messages to the SOCKS5 client, such as to disconnect
socks5_client_sender: Option<Socks5ControlMessageSender>,
}
@@ -38,13 +49,15 @@ impl State {
}
fn set_state(&mut self, status: ConnectionStatusKind, window: &tauri::Window<tauri::Wry>) {
log::info!("{status}");
self.status = status.clone();
window
.emit_all(
APP_EVENT_CONNECTION_STATUS_CHANGED,
AppEventConnectionStatusChangedPayload { status },
)
.unwrap();
.tap_err(|err| log::warn!("{err}"))
.ok();
}
pub fn get_service_provider(&self) -> &Option<String> {
@@ -63,62 +76,98 @@ impl State {
self.gateway = Some(gateway);
}
pub async fn init_config(&self) {
crate::config::Config::init(self.service_provider.as_ref(), self.gateway.as_ref()).await;
/// The effective config id is the static config id appended with the id of the gateway
pub fn get_config_id(&self) -> Result<String> {
self.get_gateway()
.as_ref()
.ok_or(BackendError::CouldNotGetIdWithoutGateway)
.and_then(|gateway_id| socks5_config_id_appended_with(gateway_id))
}
pub async fn start_connecting(&mut self, window: &tauri::Window<tauri::Wry>) {
info!("Connecting");
pub fn load_socks5_config(&self) -> Result<Socks5Config> {
let id = self.get_config_id()?;
let config = Socks5Config::load_from_file(Some(&id))
.tap_err(|_| log::warn!("Failed to load configuration file"))?;
Ok(config)
}
/// Start connecting by first creating a config file, followed by starting a thread running the
/// SOCKS5 client.
pub async fn start_connecting(
&mut self,
window: &tauri::Window<tauri::Wry>,
) -> Result<StatusReceiver> {
self.set_state(ConnectionStatusKind::Connecting, window);
self.status = ConnectionStatusKind::Connecting;
// Setup configuration by writing to file
self.init_config().await;
if let Err(err) = self.init_config().await {
log::warn!("Failed to initialize: {}", err);
// Kick of the main task and get the channel for controlling it
let (sender, used_gateway) = start_nym_socks5_client();
self.gateway = Some(used_gateway.gateway_id);
self.socks5_client_sender = Some(sender);
self.status = ConnectionStatusKind::Connected;
self.set_state(ConnectionStatusKind::Connected, window);
}
pub async fn start_disconnecting(&mut self, window: &tauri::Window<tauri::Wry>) {
info!("Disconnecting");
self.set_state(ConnectionStatusKind::Disconnecting, window);
self.status = ConnectionStatusKind::Disconnecting;
// Send shutdown message
if let Some(ref mut sender) = self.socks5_client_sender {
sender.send(Socks5ControlMessage::Stop).await.unwrap();
// Wait a little to give the user some rudimentary feedback that the click actually
// registered.
tokio::time::sleep(Duration::from_secs(1)).await;
self.set_state(ConnectionStatusKind::Disconnected, window);
return Err(err);
}
self.status = ConnectionStatusKind::Disconnected;
// Kick off the main task and get the channel for controlling it
let status_receiver = self.start_nym_socks5_client().await?;
self.set_state(ConnectionStatusKind::Connected, window);
Ok(status_receiver)
}
/// Create a configuration file
async fn init_config(&self) -> Result<()> {
let service_provider = self
.get_service_provider()
.as_ref()
.ok_or(BackendError::CouldNotInitWithoutServiceProvider)?;
let gateway = self
.get_gateway()
.as_ref()
.ok_or(BackendError::CouldNotInitWithoutGateway)?;
log::trace!(" service_provider: {:?}", service_provider);
log::trace!(" gateway: {:?}", gateway);
config::Config::init(service_provider, gateway).await
}
/// Spawn a new thread running the SOCKS5 client
async fn start_nym_socks5_client(&mut self) -> Result<StatusReceiver> {
let id = self.get_config_id()?;
let (control_tx, status_rx, used_gateway) = tasks::start_nym_socks5_client(&id)?;
self.socks5_client_sender = Some(control_tx);
self.gateway = Some(used_gateway.gateway_id);
Ok(status_rx)
}
/// Disconnect by sending a message to the SOCKS5 client thread. Once it has finished and is
/// disconnected, the disconnect handler will mark it as disconnected.
pub async fn start_disconnecting(&mut self, window: &tauri::Window<tauri::Wry>) -> Result<()> {
self.set_state(ConnectionStatusKind::Disconnecting, window);
// Send shutdown message
match self.socks5_client_sender {
Some(ref mut sender) => sender
.send(Socks5ControlMessage::Stop)
.await
.map_err(|err| {
log::warn!("Failed trying to send disconnect signal: {err}");
BackendError::CoundNotSendDisconnectSignal
}),
None => {
log::warn!(
"Trying to disconnect without being able to talk to the SOCKS5 client, \
is it running?"
);
Err(BackendError::CoundNotSendDisconnectSignal)
}
}
}
/// Once the SOCKS5 client has stopped, this should be called by the disconnect handler to mark
/// the state as disconnected.
pub fn mark_disconnected(&mut self, window: &tauri::Window<tauri::Wry>) {
self.set_state(ConnectionStatusKind::Disconnected, window);
}
}
fn start_nym_socks5_client() -> (Socks5ControlMessageSender, GatewayEndpoint) {
let id: &str = SOCKS5_CONFIG_ID;
info!("Loading config from file");
let config = nym_socks5::client::config::Config::load_from_file(Some(id)).unwrap();
let used_gateway = config.get_base().get_gateway_endpoint().clone();
let mut socks5_client = Socks5NymClient::new(config);
info!("Starting socks5 client");
let (sender, receiver) = mpsc::unbounded();
// Spawn a separate runtime for the socks5 client so we can forcefully terminate.
// Once we can gracefully shutdown the socks5 client we can get rid of this.
std::thread::spawn(|| {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async move {
socks5_client.run_and_listen(receiver).await;
});
});
(sender, used_gateway)
}
+82
View File
@@ -0,0 +1,82 @@
use client_core::config::GatewayEndpoint;
use futures::channel::mpsc;
use std::sync::Arc;
use tap::TapFallible;
use tokio::sync::RwLock;
use config::NymConfig;
#[cfg(not(feature = "coconut"))]
use nym_socks5::client::NymClient as Socks5NymClient;
use nym_socks5::client::{config::Config as Socks5Config, Socks5ControlMessageSender};
use crate::{error::Result, state::State};
pub type StatusReceiver = futures::channel::oneshot::Receiver<Socks5StatusMessage>;
/// Status messages sent by the SOCKS5 client task to the main tauri task.
#[derive(Debug)]
pub enum Socks5StatusMessage {
/// The SOCKS5 task successfully stopped
Stopped,
}
/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`.
pub fn start_nym_socks5_client(
id: &str,
) -> Result<(Socks5ControlMessageSender, StatusReceiver, GatewayEndpoint)> {
log::info!("Loading config from file: {id}");
let config = Socks5Config::load_from_file(Some(id))
.tap_err(|_| log::warn!("Failed to load configuration file"))?;
let used_gateway = config.get_base().get_gateway_endpoint().clone();
let mut socks5_client = Socks5NymClient::new(config);
log::info!("Starting socks5 client");
// Channel to send control messages to the socks5 client
let (socks5_ctrl_tx, socks5_ctrl_rx) = mpsc::unbounded();
// Channel to signal back to the main task when the socks5 client finishes, and why
let (socks5_status_tx, socks5_status_rx) = futures::channel::oneshot::channel();
// Spawn a separate runtime for the socks5 client so we can forcefully terminate.
// Once we can gracefully shutdown the socks5 client we can get rid of this.
// The status channel is used to both get the state of the task, and if it's closed, to check
// for panic.
std::thread::spawn(|| {
tokio::runtime::Runtime::new()
.expect("Failed to create runtime for SOCKS5 client")
.block_on(async move { socks5_client.run_and_listen(socks5_ctrl_rx).await });
log::info!("SOCKS5 task finished");
socks5_status_tx
.send(Socks5StatusMessage::Stopped)
.expect("Failed to send status message back to main task");
});
Ok((socks5_ctrl_tx, socks5_status_rx, used_gateway))
}
/// The disconnect listener listens to the channel setup between the socks5 proxy task and the main
/// tauri task. Primarily it listens for shutdown messages, and updates the state accordingly.
pub fn start_disconnect_listener(
state: Arc<RwLock<State>>,
window: tauri::Window<tauri::Wry>,
status_receiver: StatusReceiver,
) {
log::trace!("Starting disconnect listener");
tokio::spawn(async move {
match status_receiver.await {
Ok(Socks5StatusMessage::Stopped) => {
log::info!("SOCKS5 task reported it has finished");
}
Err(_) => {
log::info!("SOCKS5 task appears to have stopped abruptly");
// TODO: we should probably generate some events here, or otherwise signal to the
// frontend.
}
}
let mut state_w = state.write().await;
state_w.mark_disconnected(&window);
});
}
+9 -1
View File
@@ -48,11 +48,19 @@
}
},
"updater": {
"active": false
"active": true,
"endpoints": [
"https://nymtech.net/.wellknown/connect/updater.json"
],
"dialog": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENCNzQ2M0E5N0VFODE2NApSV1JrZ2U2WE9rYTNETTg1OTBKdE5uWUEra0hML2syOVUvQ2lxZmFZRzZ1T3NWbGM0eVRzUTVhVwo="
},
"allowlist": {
"shell": {
"open": true
},
"clipboard": {
"writeText": true
}
},
"windows": [
+11 -1
View File
@@ -7,6 +7,7 @@ import { ConnectedLayout } from './layouts/ConnectedLayout';
export const App: React.FC = () => {
const context = useClientContext();
const [busy, setBusy] = React.useState<boolean>();
const handleConnectClick = React.useCallback(async () => {
const oldStatus = context.connectionStatus;
if (oldStatus === ConnectionStatusKind.connected || oldStatus === ConnectionStatusKind.disconnected) {
@@ -29,7 +30,15 @@ export const App: React.FC = () => {
context.connectionStatus === ConnectionStatusKind.disconnected ||
context.connectionStatus === ConnectionStatusKind.connecting
) {
return <DefaultLayout status={context.connectionStatus} busy={busy} onConnectClick={handleConnectClick} />;
return (
<DefaultLayout
status={context.connectionStatus}
busy={busy}
onConnectClick={handleConnectClick}
services={context.services}
onServiceProviderChange={context.setServiceProvider}
/>
);
}
return (
@@ -40,6 +49,7 @@ export const App: React.FC = () => {
ipAddress="127.0.0.1"
port={1080}
connectedSince={context.connectedSince}
serviceProvider={context.serviceProvider}
stats={[
{
label: 'in:',
@@ -1,6 +1,6 @@
import React from 'react';
import { Box } from '@mui/material';
import { invoke } from '@tauri-apps/api/tauri';
import { NymWordmark } from '@nymproject/react/logo/NymWordmark';
export const AppWindowFrame: React.FC = ({ children }) => (
<Box
@@ -14,12 +14,7 @@ export const AppWindowFrame: React.FC = ({ children }) => (
}}
>
<Box display="flex" justifyContent="space-between" alignItems="center">
<svg width="22" height="6" viewBox="0 0 22 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.86777 6H5.35495L1.22609 1.32517V6H0V0H1.54986L5.67872 4.67354V0H6.86777V6ZM20.4496 0L18.5658 2.13277L16.6821 0H15.1322V6H16.3578V1.32517L18.2959 3.52046C18.4457 3.68998 18.6865 3.68998 18.8363 3.52046L20.7745 1.32517V6H22V0H20.4496ZM10.4063 3.13181V6H11.6318V3.13181L14.4527 0H12.9028L11.018 2.13277L9.13421 0H7.58435L10.4063 3.13181Z"
fill="#F2F2F2"
/>
</svg>
<NymWordmark width={22} />
</Box>
{children}
</Box>
@@ -3,17 +3,21 @@ import { ConnectionStatusKind } from '../types';
export const ConnectionButton: React.FC<{
status: ConnectionStatusKind;
disabled?: boolean;
busy?: boolean;
isError?: boolean;
onClick?: (status: ConnectionStatusKind) => void;
}> = ({ status, isError, onClick, busy }) => {
}> = ({ status, disabled, isError, onClick, busy }) => {
const [hover, setHover] = React.useState<boolean>(false);
const handleClick = React.useCallback(() => {
if (disabled === true) {
return;
}
if (onClick) {
onClick(status);
}
}, [status]);
}, [status, disabled]);
const statusText = getStatusText(status, hover);
const statusTextColor = isError ? '#40475C' : '#FFF';
@@ -21,16 +25,17 @@ export const ConnectionButton: React.FC<{
return (
<svg
opacity={disabled ? 0.75 : 1}
width="208"
height="208"
viewBox="0 0 208 208"
fill="none"
xmlns="http://www.w3.org/2000/svg"
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
onMouseEnter={() => !disabled && setHover(true)}
onMouseLeave={() => !disabled && setHover(false)}
>
<g transform="translate(-46 -46)">
<g onClick={handleClick} style={{ cursor: 'pointer' }}>
<g onClick={handleClick} style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}>
<g filter="url(#filter0_f_2_303)">
<circle cx="150" cy="150" r="70" fill="#3B445F" />
</g>
@@ -4,6 +4,7 @@ import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
import CircleOutlinedIcon from '@mui/icons-material/CircleOutlined';
import { DateTime } from 'luxon';
import { ConnectionStatusKind } from '../types';
import { ServiceProvider } from '../types/directory';
const FONT_SIZE = '16px';
const FONT_WEIGHT = '600';
@@ -57,7 +58,8 @@ const ConnectionStatusContent: React.FC<{
export const ConnectionStatus: React.FC<{
status: ConnectionStatusKind;
connectedSince?: DateTime;
}> = ({ status, connectedSince }) => {
serviceProvider?: ServiceProvider;
}> = ({ status, connectedSince, serviceProvider }) => {
const color =
status === ConnectionStatusKind.connected || status === ConnectionStatusKind.disconnecting ? '#21D072' : '#888';
const [duration, setDuration] = React.useState<string>();
@@ -72,13 +74,16 @@ export const ConnectionStatus: React.FC<{
};
}, [status, connectedSince]);
return (
<Box display="flex" justifyContent="space-between">
<Box color={color} fontSize={FONT_SIZE} display="flex" alignItems="center">
<ConnectionStatusContent status={status} />
<>
<Box display="flex" justifyContent="space-between">
<Box color={color} fontSize={FONT_SIZE} display="flex" alignItems="center">
<ConnectionStatusContent status={status} />
</Box>
<Typography color={color} fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE}>
{status === ConnectionStatusKind.connected && duration}
</Typography>
</Box>
<Typography color={color} fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE}>
{status === ConnectionStatusKind.connected && duration}
</Typography>
</Box>
<Box>{serviceProvider && <Typography fontSize={12}>{serviceProvider.description}</Typography>}</Box>
</>
);
};
+76 -21
View File
@@ -1,6 +1,8 @@
import { Box, Tooltip, Typography } from '@mui/material';
import { Box, Stack, Tooltip, Typography } from '@mui/material';
import React from 'react';
import { styled } from '@mui/system';
import { writeText } from '@tauri-apps/api/clipboard';
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
const IpAddressAndPortContainer = styled('div')({
'.hoverAddressCopy:hover': {
@@ -16,23 +18,76 @@ export const IpAddressAndPort: React.FC<{
label: string;
ipAddress: string;
port: number;
}> = ({ label, ipAddress, port }) => (
<IpAddressAndPortContainer>
<Box display="flex" justifyContent="space-between" color="rgba(255,255,255,0.6)">
<Typography fontSize="14px">{label}</Typography>
<Typography fontSize="14px">Port</Typography>
</Box>
<Box display="flex" justifyContent="space-between">
<Tooltip title="Click to copy SOCKS5 proxy hostname">
<Typography fontWeight="600" className="hoverAddressCopy">
{ipAddress}
</Typography>
</Tooltip>
<Tooltip title="Click to copy SOCKS5 proxy port">
<Typography fontWeight="600" className="hoverAddressCopy">
{port}
</Typography>
</Tooltip>
</Box>
</IpAddressAndPortContainer>
);
}> = ({ label, ipAddress, port }) => {
const [ipAddressCopied, setIpAddressCopied] = React.useState<boolean>(false);
const [portCopied, setPortCopied] = React.useState<boolean>(false);
React.useEffect(() => {
if (ipAddressCopied) {
setTimeout(() => setIpAddressCopied(false), 2000);
}
}, [ipAddressCopied]);
React.useEffect(() => {
if (portCopied) {
setTimeout(() => setPortCopied(false), 2000);
}
}, [portCopied]);
return (
<IpAddressAndPortContainer>
<Box display="flex" justifyContent="space-between" color="rgba(255,255,255,0.6)">
<Typography fontSize="14px">{label}</Typography>
<Typography fontSize="14px">Port</Typography>
</Box>
<Box display="flex" justifyContent="space-between">
<Tooltip
title={
ipAddressCopied ? (
<Stack direction="row" spacing={1} fontSize="inherit" alignItems="center">
<CheckCircleOutlineIcon color="success" fontSize="small" />
<Typography fontSize="inherit">SOCKS5 proxy hostname copied to the clipboard</Typography>
</Stack>
) : (
<>Click to copy SOCKS5 proxy hostname</>
)
}
>
<Typography
fontWeight="600"
className="hoverAddressCopy"
onClick={async () => {
await writeText(`${ipAddress}`);
setIpAddressCopied(true);
}}
>
{ipAddress}
</Typography>
</Tooltip>
<Tooltip
title={
portCopied ? (
<Stack direction="row" spacing={1} fontSize="inherit" alignItems="center">
<CheckCircleOutlineIcon color="success" fontSize="small" />
<Typography fontSize="inherit">SOCKS5 proxy port copied to the clipboard</Typography>
</Stack>
) : (
<>Click to copy SOCKS5 proxy port</>
)
}
>
<Typography
fontWeight="600"
className="hoverAddressCopy"
onClick={async () => {
await writeText(`${port}`);
setPortCopied(true);
}}
>
{port}
</Typography>
</Tooltip>
</Box>
</IpAddressAndPortContainer>
);
};
-10
View File
@@ -1,10 +0,0 @@
import React from 'react';
import Logo from '../images/logo-background.svg';
const imgSize = {
small: 40,
medium: 80,
large: 120,
};
export const NymLogo = ({ size = 'medium' }: { size?: 'small' | 'medium' | 'large' }) => <Logo width={imgSize[size]} />;
@@ -0,0 +1,110 @@
import React from 'react';
import IconButton from '@mui/material/IconButton';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import ArrowDropDownCircleIcon from '@mui/icons-material/ArrowDropDownCircle';
import { Box, CircularProgress, Stack, Tooltip, Typography } from '@mui/material';
import { ServiceProvider, Services } from '../types/directory';
export const ServiceProviderSelector: React.FC<{
onChange?: (serviceProvider: ServiceProvider) => void;
services?: Services;
}> = ({ services, onChange }) => {
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>();
const textEl = React.useRef<null | HTMLElement>(null);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = () => {
setAnchorEl(textEl.current);
};
const handleClose = (newServiceProvider?: ServiceProvider) => {
if (newServiceProvider) {
setServiceProvider(newServiceProvider);
onChange?.(newServiceProvider);
}
setAnchorEl(null);
};
if (!services) {
return (
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ mt: 3 }}>
<Typography fontSize={14} fontWeight={700} color={(theme) => theme.palette.common.white}>
<CircularProgress size={14} sx={{ mr: 1 }} color="inherit" />
Loading services...
</Typography>
<IconButton id="service-provider-button" disabled>
<ArrowDropDownCircleIcon />
</IconButton>
</Box>
);
}
return (
<>
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ mt: 3 }}>
<Typography
ref={textEl}
fontSize={14}
fontWeight={700}
color={(theme) => (serviceProvider ? undefined : theme.palette.primary.main)}
>
{serviceProvider ? serviceProvider.description : 'Select a service'}
</Typography>
<IconButton
id="service-provider-button"
aria-controls={open ? 'basic-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
>
<ArrowDropDownCircleIcon />
</IconButton>
</Box>
<Menu
id="service-provider-menu"
anchorEl={anchorEl}
open={open}
onClose={() => handleClose()}
MenuListProps={{
'aria-labelledby': 'service-provider-button',
}}
>
{services.map((service) => (
<>
<MenuItem disabled dense sx={{ fontSize: 'small', fontWeight: 'bold', mb: -1 }}>
{service.description}
</MenuItem>
{service.items.map((sp) => (
<MenuItem dense sx={{ fontSize: 'small', ml: 2, height: 'auto' }} onClick={() => handleClose(sp)}>
<Tooltip
title={
<Stack direction="column">
<Typography fontSize="inherit">
<code>{sp.id}</code>
</Typography>
<Typography fontSize="inherit" fontWeight={700}>
{sp.description}
</Typography>
<Typography fontSize="inherit">
Gateway <code>{sp.gateway.slice(0, 10)}...</code>
</Typography>
<Typography fontSize="inherit">
Provider <code>{sp.address.slice(0, 10)}...</code>
</Typography>
</Stack>
}
arrow
placement="top"
>
<Typography fontSize="inherit" noWrap>
{sp.description}
</Typography>
</Tooltip>
</MenuItem>
))}
</>
))}
</Menu>
</>
);
};
+24 -2
View File
@@ -1,9 +1,11 @@
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
import { DateTime } from 'luxon';
import { invoke } from '@tauri-apps/api/tauri';
import { listen, UnlistenFn } from '@tauri-apps/api/event';
import { invoke } from '@tauri-apps/api';
import type { UnlistenFn } from '@tauri-apps/api/event';
import { listen } from '@tauri-apps/api/event';
import { ConnectionStatusKind } from '../types';
import { ConnectionStatsItem } from '../components/ConnectionStats';
import { ServiceProvider, Services } from '../types/directory';
const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed';
@@ -14,11 +16,14 @@ type TClientContext = {
connectionStatus: ConnectionStatusKind;
connectionStats?: ConnectionStatsItem[];
connectedSince?: DateTime;
services?: Services;
serviceProvider?: ServiceProvider;
setMode: (mode: ModeType) => void;
setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void;
setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void;
setConnectedSince: (connectedSince: DateTime | undefined) => void;
setServiceProvider: (serviceProvider: ServiceProvider) => void;
startConnecting: () => Promise<void>;
startDisconnecting: () => Promise<void>;
@@ -31,6 +36,14 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatusKind>(ConnectionStatusKind.disconnected);
const [connectionStats, setConnectionStats] = useState<ConnectionStatsItem[]>();
const [connectedSince, setConnectedSince] = useState<DateTime>();
const [services, setServices] = React.useState<Services>();
const [serviceProvider, setRawServiceProvider] = React.useState<ServiceProvider>();
useEffect(() => {
invoke('get_services').then((result) => {
setServices(result as Services);
});
}, []);
useEffect(() => {
let unlisten: UnlistenFn | undefined;
@@ -59,6 +72,12 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
await invoke('start_disconnecting');
}, []);
const setServiceProvider = useCallback(async (newServiceProvider: ServiceProvider) => {
await invoke('set_gateway', { gateway: newServiceProvider.gateway });
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
setRawServiceProvider(newServiceProvider);
}, []);
return (
<ClientContext.Provider
value={{
@@ -72,6 +91,9 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
setConnectedSince,
startConnecting,
startDisconnecting,
services,
serviceProvider,
setServiceProvider,
}}
>
{children}
@@ -1,41 +0,0 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
viewBox="0 0 5389.9 5389.9" style="enable-background:new 0 0 5389.9 5389.9;" xml:space="preserve">
<style type="text/css">
.st0{fill:#121726;}
.st1{fill:url(#SVGID_1_);}
.st2{fill:#FFFFFF;}
</style>
<g>
<g>
<circle class="st0" cx="2695" cy="2695" r="2585"/>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="0" y1="8058.165" x2="5390" y2="8058.165" gradientTransform="matrix(1 0 0 -1 0 10753.165)">
<stop offset="0" style="stop-color:#F77846"/>
<stop offset="1" style="stop-color:#ED3572"/>
</linearGradient>
<path class="st1" d="M2695,5390c-182.8,0-365.5-18.4-543-54.8c-173.1-35.4-343.3-88.3-506-157.1
c-159.7-67.6-313.7-151.2-457.8-248.5c-142.7-96.4-276.8-207.1-398.8-329c-121.9-121.9-232.6-256.1-329-398.8
C363,4057.8,279.4,3903.8,211.8,3744C143,3581.4,90.2,3411.1,54.8,3238C18.4,3060.5,0,2877.8,0,2695c0-182.8,18.4-365.5,54.8-543
c35.4-173.1,88.3-343.3,157.1-506c67.6-159.7,151.2-313.7,248.5-457.8c96.4-142.7,207.1-276.8,329-398.8s256.1-232.6,398.8-329
c144.1-97.3,298.1-180.9,457.8-248.5c162.7-68.8,332.9-121.7,506-157.1C2329.5,18.4,2512.2,0,2695,0c182.8,0,365.5,18.4,543,54.8
c173.1,35.4,343.3,88.3,506,157.1c159.7,67.6,313.7,151.2,457.8,248.5c142.7,96.4,276.8,207.1,398.8,329
c121.9,121.9,232.6,256.1,329,398.8c97.3,144.1,180.9,298.1,248.5,457.8c68.8,162.7,121.7,332.9,157.1,506
c36.3,177.5,54.8,360.2,54.8,543c0,182.8-18.4,365.5-54.8,543c-35.4,173.1-88.3,343.3-157.1,506
c-67.6,159.7-151.2,313.7-248.5,457.8c-96.4,142.7-207.1,276.8-329,398.8c-121.9,121.9-256.1,232.6-398.8,329
c-144.1,97.3-298.1,180.9-457.8,248.5c-162.7,68.8-332.9,121.7-506,157.1C3060.5,5371.6,2877.8,5390,2695,5390z M2695,220
c-168,0-335.9,16.9-498.9,50.3c-158.9,32.5-315.1,81-464.4,144.2c-146.6,62-288.1,138.8-420.4,228.2
c-131.1,88.6-254.3,190.3-366.4,302.3c-112,112-213.7,235.3-302.3,366.4c-89.4,132.3-166.2,273.7-228.2,420.4
c-63.2,149.3-111.7,305.6-144.2,464.4C236.9,2359.1,220,2527,220,2695s16.9,335.9,50.3,498.9c32.5,158.9,81,315.1,144.2,464.4
c62,146.6,138.8,288.1,228.2,420.4c88.6,131.1,190.3,254.3,302.3,366.4c112,112,235.3,213.7,366.4,302.3
c132.3,89.4,273.7,166.2,420.4,228.2c149.3,63.2,305.6,111.7,464.4,144.2c163.1,33.4,330.9,50.3,498.9,50.3s335.9-16.9,498.9-50.3
c158.9-32.5,315.1-81,464.4-144.2c146.6-62,288.1-138.8,420.4-228.2c131.1-88.6,254.3-190.3,366.4-302.3
c112-112,213.7-235.3,302.3-366.4c89.4-132.3,166.2-273.7,228.2-420.4c63.2-149.3,111.7-305.6,144.2-464.4
c33.4-163.1,50.3-330.9,50.3-498.9s-16.9-335.9-50.3-498.9c-32.5-158.9-81-315.1-144.2-464.4c-62-146.6-138.8-288.1-228.2-420.4
c-88.6-131.1-190.3-254.3-302.3-366.4c-112-112-235.3-213.7-366.4-302.3c-132.3-89.4-273.7-166.2-420.4-228.2
c-149.3-63.2-305.6-111.7-464.4-144.2C3030.9,236.9,2863,220,2695,220z"/>
</g>
</g>
<path class="st2" d="M1958.5,3160.4h-269.6l-735.8-725.3v725.3H734.6v-930.9h276.2l735.8,725.1v-725.1h211.9V3160.4z M4378.9,2229.5
l-335.7,330.9l-335.7-330.9h-276.2v930.9h218.4v-725.3l345.4,340.6c26.7,26.3,69.6,26.3,96.3,0l345.4-340.6v725.3h218.4v-930.9
H4378.9z M2589.1,2715.4v445h218.4v-445l502.7-485.9H3034l-335.9,330.9l-335.7-330.9h-276.2L2589.1,2715.4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="22" height="6" viewBox="0 0 22 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.86777 6H5.35495L1.22609 1.32517V6H0V0H1.54986L5.67872 4.67354V0H6.86777V6ZM20.4496 0L18.5658 2.13277L16.6821 0H15.1322V6H16.3578V1.32517L18.2959 3.52046C18.4457 3.68998 18.6865 3.68998 18.8363 3.52046L20.7745 1.32517V6H22V0H20.4496ZM10.4063 3.13181V6H11.6318V3.13181L14.4527 0H12.9028L11.018 2.13277L9.13421 0H7.58435L10.4063 3.13181Z" fill="#F2F2F2"/>
</svg>

Before

Width:  |  Height:  |  Size: 470 B

+5 -3
View File
@@ -8,6 +8,7 @@ import { ConnectionStats, ConnectionStatsItem } from '../components/ConnectionSt
import { NeedHelp } from '../components/NeedHelp';
import { ConnectionButton } from '../components/ConnectionButton';
import { IpAddressAndPort } from '../components/IpAddressAndPort';
import { ServiceProvider } from '../types/directory';
export const ConnectedLayout: React.FC<{
status: ConnectionStatusKind;
@@ -18,15 +19,16 @@ export const ConnectedLayout: React.FC<{
busy?: boolean;
isError?: boolean;
onConnectClick?: (status: ConnectionStatusKind) => void;
}> = ({ status, stats, ipAddress, port, connectedSince, busy, isError, onConnectClick }) => (
serviceProvider?: ServiceProvider;
}> = ({ status, stats, ipAddress, port, connectedSince, busy, isError, serviceProvider, onConnectClick }) => (
<AppWindowFrame>
<Box pb={4}>
<ConnectionStatus status={status} connectedSince={connectedSince} />
<ConnectionStatus status={status} connectedSince={connectedSince} serviceProvider={serviceProvider} />
</Box>
<Box pb={4}>
<IpAddressAndPort label="SOCKS5 Proxy" ipAddress={ipAddress} port={port} />
</Box>
<ConnectionStats stats={stats} />
{/* <ConnectionStats stats={stats} /> */}
<ConnectionButton status={status} busy={busy} onClick={onConnectClick} isError={isError} />
<NeedHelp />
</AppWindowFrame>
+30 -12
View File
@@ -4,21 +4,39 @@ import { AppWindowFrame } from '../components/AppWindowFrame';
import { ConnectionButton } from '../components/ConnectionButton';
import { ConnectionStatusKind } from '../types';
import { NeedHelp } from '../components/NeedHelp';
import { ServiceProviderSelector } from '../components/ServiceProviderSelector';
import { ServiceProvider, Services } from '../types/directory';
export const DefaultLayout: React.FC<{
status: ConnectionStatusKind;
services?: Services;
busy?: boolean;
isError?: boolean;
onConnectClick?: (status: ConnectionStatusKind) => void;
}> = ({ status, busy, isError, onConnectClick }) => (
<AppWindowFrame>
<Typography fontWeight="700" fontSize="14px" textAlign="center">
Connect, your privacy will be 100% protected thanks to the Nym Mixnet
</Typography>
<Typography fontWeight="700" fontSize="14px" textAlign="center" color="#60D6EF" pt={2}>
You are not protected now
</Typography>
<ConnectionButton status={status} busy={busy} isError={isError} onClick={onConnectClick} />
<NeedHelp />
</AppWindowFrame>
);
onServiceProviderChange?: (serviceProvider: ServiceProvider) => void;
}> = ({ status, services, busy, isError, onConnectClick, onServiceProviderChange }) => {
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>();
const handleServiceProviderChange = (newServiceProvider: ServiceProvider) => {
setServiceProvider(newServiceProvider);
onServiceProviderChange?.(newServiceProvider);
};
return (
<AppWindowFrame>
<Typography fontWeight="700" fontSize="14px" textAlign="center">
Connect, your privacy will be 100% protected thanks to the Nym Mixnet
</Typography>
<Typography fontWeight="700" fontSize="14px" textAlign="center" color="#60D6EF" pt={2}>
You are not protected now
</Typography>
<ServiceProviderSelector services={services} onChange={handleServiceProviderChange} />
<ConnectionButton
status={status}
disabled={serviceProvider === undefined}
busy={busy}
isError={isError}
onClick={onConnectClick}
/>
<NeedHelp />
</AppWindowFrame>
);
};
-1
View File
@@ -1 +0,0 @@
// TODO
-10
View File
@@ -1,10 +0,0 @@
import React from 'react';
import { Route, Switch } from 'react-router-dom';
export const Routes: React.FC = () => (
<Switch>
<Route path="/">
<div>Root</div>
</Route>
</Switch>
);
+22 -1
View File
@@ -7,6 +7,7 @@ import { useClientContext } from '../context/main';
import { ConnectionStatusKind } from '../types';
import { DefaultLayout } from '../layouts/DefaultLayout';
import { ConnectedLayout } from '../layouts/ConnectedLayout';
import { Services } from '../types/directory';
export default {
title: 'App/Flow',
@@ -16,6 +17,20 @@ export default {
export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
const context = useClientContext();
const [busy, setBusy] = React.useState<boolean>();
const services: Services = [
{
id: 'keybase',
description: 'Keybase',
items: [
{
id: 'nym-keybase',
description: 'Nym Keybase Service Provider',
address: '1234.5678',
gateway: 'abcedf',
},
],
},
];
const handleConnectClick = React.useCallback(() => {
const oldStatus = context.connectionStatus;
if (oldStatus === ConnectionStatusKind.connected || oldStatus === ConnectionStatusKind.disconnected) {
@@ -53,7 +68,12 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
) {
return (
<Box p={4} sx={{ background: 'white' }}>
<DefaultLayout status={context.connectionStatus} busy={busy} onConnectClick={handleConnectClick} />
<DefaultLayout
status={context.connectionStatus}
busy={busy}
onConnectClick={handleConnectClick}
services={services}
/>
</Box>
);
}
@@ -67,6 +87,7 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
ipAddress="127.0.0.1"
port={1080}
connectedSince={context.connectedSince}
serviceProvider={services[0].items[0]}
stats={[
{
label: 'in:',
+14
View File
@@ -0,0 +1,14 @@
export interface ServiceProvider {
id: string;
description: string;
address: string;
gateway: string;
}
export interface Service {
id: string;
description: string;
items: ServiceProvider[];
}
export type Services = Service[];
+20
View File
@@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"noEmit": true
},
"include": [
".storybook/*.js",
"webpack.*.js",
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.stories.*",
],
"exclude": [
"node_modules",
"dist"
]
}
+5 -3
View File
@@ -1,5 +1,4 @@
{
"compileOnSave": false,
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
@@ -15,7 +14,10 @@
"isolatedModules": false,
"jsx": "react-jsx",
"sourceMap": true,
"baseUrl": "."
"baseUrl": ".",
"paths": {
"@assets/*": ["../assets/*"]
}
},
"exclude": ["node_modules", "dist", "jest.config.js", "webpack.config.js", "webpack.prod.js", "webpack.common.js"]
"exclude": ["node_modules", "dist", "jest.config.js", "webpack.config.js", "webpack.prod.js", "webpack.common.js", "target"]
}
+14 -46
View File
@@ -1,50 +1,18 @@
const HtmlWebpackPlugin = require('html-webpack-plugin')
const FaviconsWebpackPlugin = require('favicons-webpack-plugin')
const Dotenv = require('dotenv-webpack')
var path = require('path')
const path = require('path');
const { mergeWithRules } = require('webpack-merge');
const { webpackCommon } = require('@nymproject/webpack');
module.exports = {
entry: path.resolve(__dirname, '/src/index'),
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/index.html'),
filename: 'index.html',
}),
new FaviconsWebpackPlugin(path.resolve(__dirname, 'public/favicon.png')),
new Dotenv(),
],
module.exports = mergeWithRules({
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
},
],
},
{
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ['@svgr/webpack'],
},
{
// See https://webpack.js.org/guides/asset-management/#loading-fonts
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
],
rules: {
test: 'match',
use: 'replace',
},
},
resolve: {
extensions: ['*', '.js', '.jsx', '.ts', '.tsx'],
})(webpackCommon(__dirname, 'public/index.html'), {
entry: path.resolve(__dirname, 'src/index.tsx'),
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
}
});
+55 -6
View File
@@ -1,10 +1,59 @@
const common = require('./webpack.common')
const { merge } = require('webpack-merge')
var path = require('path')
const { mergeWithRules } = require('webpack-merge');
const webpack = require('webpack');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const ReactRefreshTypeScript = require('react-refresh-typescript');
const commonConfig = require('./webpack.common');
module.exports = merge(common, {
module.exports = mergeWithRules({
module: {
rules: {
test: 'match',
use: 'replace',
},
},
})(commonConfig, {
mode: 'development',
entry: path.resolve(__dirname, '/src/index'),
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
options: {
getCustomTransformers: () => ({
before: [ReactRefreshTypeScript()],
}),
// `ts-loader` does not work with HMR unless `transpileOnly` is used.
// If you need type checking, `ForkTsCheckerWebpackPlugin` is an alternative.
transpileOnly: true,
},
},
],
},
plugins: [
new ReactRefreshWebpackPlugin(),
// this can be included automatically by the dev server, however build mode fails if missing
new webpack.HotModuleReplacementPlugin(),
],
// recommended for faster rebuild
optimization: {
runtimeChunk: true,
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
},
cache: {
type: 'filesystem',
buildDependencies: {
// restart on config change
config: ['./webpack.dev.js'],
},
},
devServer: {
port: 9000,
compress: true,
@@ -14,4 +63,4 @@ module.exports = merge(common, {
overlay: false,
},
},
})
});
+4 -4
View File
@@ -1,6 +1,6 @@
var path = require('path')
const common = require('./webpack.common')
const { default: merge } = require('webpack-merge')
const path = require('path');
const { default: merge } = require('webpack-merge');
const common = require('./webpack.common');
module.exports = merge(common, {
mode: 'production',
@@ -8,4 +8,4 @@ module.exports = merge(common, {
__dirname: false,
},
entry: path.resolve(__dirname, './src/index'),
})
});
-15405
View File
File diff suppressed because it is too large Load Diff

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