Delete nym-connect directory
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"esmodules": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-typescript"
|
||||
],
|
||||
"plugins": ["@babel/plugin-transform-async-to-generator"]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
ADMIN_ADDRESS=
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"@nymproject/eslint-config-react-typescript"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.eslint.json"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
18
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"semi": true
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/* 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', '@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.module.rules.unshift({
|
||||
test: /\.ya?ml$/,
|
||||
type: 'json',
|
||||
use: [
|
||||
{
|
||||
loader: 'yaml-loader',
|
||||
options: {
|
||||
asJSON: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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.`));
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* This is a mock for Tauri's API package (@tauri-apps/api/notification), to prevent stories from being excluded, because they either use
|
||||
* or import dependencies that use Tauri.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
isPermissionGranted: () => undefined,
|
||||
requestPermission: () => undefined,
|
||||
sendNotification: () => undefined,
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import '../src/fonts/fonts.css';
|
||||
|
||||
export const Fonts = ({ children }) => <>{children}</>;
|
||||
@@ -1,26 +0,0 @@
|
||||
import { NymMixnetTheme } from '../src/theme';
|
||||
import { Fonts } from './preview-fonts';
|
||||
import { MockProvider } from '../src/context/mocks/main';
|
||||
const withThemeProvider = (Story, context) => {
|
||||
return (
|
||||
<Fonts>
|
||||
<MockProvider>
|
||||
<NymMixnetTheme mode="dark">
|
||||
<Story {...context} />
|
||||
</NymMixnetTheme>
|
||||
</MockProvider>
|
||||
</Fonts>
|
||||
);
|
||||
};
|
||||
|
||||
export const decorators = [withThemeProvider];
|
||||
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,208 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.21-kitkat] (2023-09-12)
|
||||
|
||||
- NC - Handle failure when config is too old ([#3847])
|
||||
|
||||
[#3847]: https://github.com/nymtech/nym/issues/3847
|
||||
|
||||
## [v1.1.20-twix] (2023-09-05)
|
||||
|
||||
- nym-connect directory error handling ([#3830])
|
||||
- NC - it should not be possible to toggle speedy mode while the connection is active ([#3816])
|
||||
|
||||
[#3830]: https://github.com/nymtech/nym/pull/3830
|
||||
[#3816]: https://github.com/nymtech/nym/issues/3816
|
||||
|
||||
## [v1.1.19-snickers] (2023-08-29)
|
||||
|
||||
- NymConnect sometimes fails to connect because the gateway it fetches from the validator-api to use is running an old version (of the gateway binary) ([#3788])
|
||||
|
||||
[#3788]: https://github.com/nymtech/nym/issues/3788
|
||||
|
||||
## [1.1.18] (2023-08-22)
|
||||
|
||||
- refactor(nc-desktop): use userdata storage to save user gateway&sp ([#3723])
|
||||
|
||||
[#3723]: https://github.com/nymtech/nym/pull/3723
|
||||
|
||||
## [1.1.17] (2023-08-16)
|
||||
|
||||
- Add a "Send us your feedback" section in NC (on the main screen) to collect user feedback using Sentry ([#3619])
|
||||
- NC native android - deploy on FDroid ([#3483])
|
||||
|
||||
[#3619]: https://github.com/nymtech/nym/issues/3619
|
||||
[#3483]: https://github.com/nymtech/nym/issues/3483
|
||||
|
||||
## [v1.1.16] (2023-08-08)
|
||||
|
||||
- Uncouple network-requester <-> gateway in nym-connect and harbourmaster ([#3472])
|
||||
|
||||
[#3472]: https://github.com/nymtech/nym/issues/3472
|
||||
|
||||
## [v1.1.15] (2023-07-25)
|
||||
|
||||
- NC Desktop - remove sentry DSN from code ([#3694])
|
||||
- NC - Add Alephium wallet in the supported app list ([#3681])
|
||||
|
||||
[#3694]: https://github.com/nymtech/nym/issues/3694
|
||||
[#3681]: https://github.com/nymtech/nym/issues/3681
|
||||
|
||||
## [v1.1.14] (2023-07-04)
|
||||
|
||||
- Nym connect fails to start when encountering an old config version ([#3588])
|
||||
- NC desktop - apps section adjustments + add monero integration ([#2977])
|
||||
- nym-connect: use different service provider directory when medium toggle enabled ([#3617])
|
||||
- Fix medium toggle in nym-connect ([#3590])
|
||||
- [bugfix] NC: load old gateway configuration if we're not registering ([#3586])
|
||||
- nym-connect: medium speed setting ([#3585])
|
||||
|
||||
[#3588]: https://github.com/nymtech/nym/issues/3588
|
||||
[#2977]: https://github.com/nymtech/nym/issues/2977
|
||||
[#3617]: https://github.com/nymtech/nym/pull/3617
|
||||
[#3590]: https://github.com/nymtech/nym/pull/3590
|
||||
[#3586]: https://github.com/nymtech/nym/pull/3586
|
||||
[#3585]: https://github.com/nymtech/nym/pull/3585
|
||||
|
||||
## [v1.1.13] (2023-06-20)
|
||||
|
||||
- NymConnect - add sentry.io reporting ([#3421])
|
||||
|
||||
[#3421]: https://github.com/nymtech/nym/issues/3421
|
||||
|
||||
## [v1.1.12] (2023-03-07)
|
||||
|
||||
- NymConnect - Update display for selected Service Provider ([#3116])
|
||||
|
||||
[#3116]: https://github.com/nymtech/nym/issues/3116
|
||||
|
||||
## [v1.1.11] (2023-02-28)
|
||||
|
||||
- NC - add the option to manually select and use a specific Service Provider ([#2953])
|
||||
|
||||
[#2953]: https://github.com/nymtech/nym/issues/2953
|
||||
|
||||
## [v1.1.10] (2023-02-21)
|
||||
|
||||
- NC - add logs window for troubleshooting ([#2951])
|
||||
|
||||
[#2951]: https://github.com/nymtech/nym/issues/2951
|
||||
|
||||
## [nym-connect-v1.1.9](https://github.com/nymtech/nym/tree/nym-connect-v1.1.9) (2023-02-14)
|
||||
|
||||
- Button animations ([#2949])
|
||||
- add effect when the button is clicked ([#2947])
|
||||
- UI to select gateways based on some performance criteria by checking gateways' routing score from nym-api ([#2942])
|
||||
- client health check when connecting ([#2859])
|
||||
- allow user to select own gateway ([#2952])
|
||||
|
||||
[#2952]: https://github.com/nymtech/nym/issues/2952
|
||||
[#2949]: https://github.com/nymtech/nym/issues/2949
|
||||
[#2947]: https://github.com/nymtech/nym/issues/2947
|
||||
[#2942]: https://github.com/nymtech/nym/issues/2942
|
||||
[#2859]: https://github.com/nymtech/nym/issues/2859
|
||||
|
||||
## [nym-connect-v1.1.8](https://github.com/nymtech/nym/tree/nym-connect-v1.1.8) (2023-01-31)
|
||||
|
||||
- Add supported apps in the menu + update guide ([#2868])
|
||||
- Copy changes to remove the dropdown: ([#2777])
|
||||
|
||||
[#2868]: https://github.com/nymtech/nym/issues/2868
|
||||
[#2777]: https://github.com/nymtech/nym/issues/2777
|
||||
|
||||
## [nym-connect-v1.1.7](https://github.com/nymtech/nym/tree/nym-connect-v1.1.7) (2023-01-24)
|
||||
|
||||
- Remove test and earn ([#2865])
|
||||
|
||||
[#2865]: https://github.com/nymtech/nym/issue/2865
|
||||
|
||||
## [nym-connect-v1.1.6](https://github.com/nymtech/nym/tree/nym-connect-v1.1.6) (2023-01-17)
|
||||
|
||||
- part (1) show gateway status on the UI if the gateway is not live, is overloaded or is slow ([#2824])
|
||||
|
||||
[#2824]: https://github.com/nymtech/nym/pull/2824
|
||||
|
||||
## [nym-connect-v1.1.5](https://github.com/nymtech/nym/tree/nym-connect-v1.1.5) (2023-01-10)
|
||||
|
||||
- get version number from tauri and display by @fmtabbara in https://github.com/nymtech/nym/pull/2684
|
||||
- Feature/nym connect experimental software text by @fmtabbara in https://github.com/nymtech/nym/pull/2692
|
||||
- NymConnect - Display service info in tooltip **1.1.5 Release** by @fmtabbara in https://github.com/nymtech/nym/pull/2704
|
||||
|
||||
## [nym-connect-v1.1.4](https://github.com/nymtech/nym/tree/nym-connect-v1.1.4) (2022-12-20)
|
||||
|
||||
This release contains the new opt-in Test & Earn program, and it uses a stress-tested directory of network requesters to improve reliability. It also has some bugfixes, performance improvements, and better error handling.
|
||||
|
||||
- nym-connect: send status messages from socks5 task to tauri backend by @octol in https://github.com/nymtech/nym/pull/1882
|
||||
- socks5: rework waiting in inbound.rs by @octol in https://github.com/nymtech/nym/pull/1880
|
||||
- Test&Earn by @mmsinclair in https://github.com/nymtech/nym/pull/2729
|
||||
|
||||
## [nym-connect-v1.1.3](https://github.com/nymtech/nym/tree/nym-connect-v1.1.3) (2022-12-13)
|
||||
|
||||
- socks5-client: added support for socks4a.
|
||||
|
||||
## [nym-connect-v1.1.2](https://github.com/nymtech/nym/tree/nym-connect-v1.1.2) (2022-12-06)
|
||||
|
||||
- socks5-client: fix error with client failing and disconnecting unnecessarily.
|
||||
|
||||
## [nym-connect-v1.1.1](https://github.com/nymtech/nym/tree/nym-connect-v1.1.1) (2022-11-29)
|
||||
|
||||
- socks5-client: fix multiplex concurrent connections ([#1720], [#1777])
|
||||
- socks5-client: fix wait closing inbound connection until data is sent, and throttle incoming data in general ([#1772], [#1783],[#1789])
|
||||
- socks5-client: fix shutting down all background workers if anyone of them panics or errors out. This fixes an issue where the nym-connect UI was showing connected even though the socks5 tunnel was non-functional. ([#1805])
|
||||
- gateway-libs: fix decryping messages stored on the gateway between reconnects ([#1786])
|
||||
|
||||
- nymconnect: updated UI
|
||||
- nymconnect: new help area
|
||||
- nymconnect: listen for service errors and display on frontend
|
||||
|
||||
[#1720]: https://github.com/nymtech/nym/pull/1720
|
||||
[#1772]: https://github.com/nymtech/nym/pull/1772
|
||||
[#1777]: https://github.com/nymtech/nym/pull/1777
|
||||
[#1783]: https://github.com/nymtech/nym/pull/1783
|
||||
[#1786]: https://github.com/nymtech/nym/pull/1786
|
||||
[#1789]: https://github.com/nymtech/nym/pull/1789
|
||||
[#1805]: https://github.com/nymtech/nym/pull/1805
|
||||
|
||||
## [nym-connect-v1.1.0](https://github.com/nymtech/nym/tree/nym-connect-v1.1.0) (2022-11-09)
|
||||
|
||||
- nym-connect: rework of rewarding changes the directory data structures that describe the mixnet topology ([#1472])
|
||||
- clients: add testing-only support for two more extended packet sizes (8kb and 16kb).
|
||||
- native-client/socks5-client: `disable_loop_cover_traffic_stream` Debug config option to disable the separate loop cover traffic stream ([#1666])
|
||||
- native-client/socks5-client: `disable_main_poisson_packet_distribution` Debug config option to make the client ignore poisson distribution in the main packet stream and ONLY send real message (and as fast as they come) ([#1664])
|
||||
- native-client/socks5-client: `use_extended_packet_size` Debug config option to make the client use 'ExtendedPacketSize' for its traffic (32kB as opposed to 2kB in 1.0.2) ([#1671])
|
||||
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611])
|
||||
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NyxdClient` ([#1558])
|
||||
|
||||
[#1472]: https://github.com/nymtech/nym/pull/1472
|
||||
[#1558]: https://github.com/nymtech/nym/pull/1558
|
||||
[#1611]: https://github.com/nymtech/nym/pull/1611
|
||||
[#1664]: https://github.com/nymtech/nym/pull/1664
|
||||
[#1666]: https://github.com/nymtech/nym/pull/1666
|
||||
[#1671]: https://github.com/nymtech/nym/pull/1671
|
||||
|
||||
## [nym-connect-v1.0.2](https://github.com/nymtech/nym/tree/nym-connect-v1.0.2) (2022-08-18)
|
||||
|
||||
### Changed
|
||||
|
||||
- nym-connect: "load balance" the service providers by picking a random Service Provider for each Service and storing in local storage so it remains sticky for the user ([#1540])
|
||||
- nym-connect: the ServiceProviderSelector only displays the available Services, and picks a random Service Provider for Services the user has never used before ([#1540])
|
||||
- nym-connect: add `local-forage` for storing user settings ([#1540])
|
||||
|
||||
[#1540]: https://github.com/nymtech/nym/pull/1540
|
||||
|
||||
## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22)
|
||||
|
||||
### Added
|
||||
|
||||
- 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
|
||||
|
||||
### Changed
|
||||
|
||||
- nym-connect: reuse config id instead of creating a new id on each connection
|
||||
|
||||
[#1427]: https://github.com/nymtech/nym/pull/1427
|
||||
@@ -1,3 +0,0 @@
|
||||
[workspace]
|
||||
members = ["src-tauri"]
|
||||
resolver = "2"
|
||||
@@ -1,95 +0,0 @@
|
||||
<!--
|
||||
Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
|
||||
# Nym Connect - Desktop
|
||||
|
||||
Nym is an open-source, decentralized and permissionless privacy system. It provides full-stack privacy, allowing other applications, services or blockchains to provide their users with strong metadata protection, at both the network level (mixnet), and the application level (anonymous credentials) without the need to build privacy from scratch.
|
||||
|
||||
Nym Connects sets up a SOCKS5 proxy for local applications to use.
|
||||
|
||||
## Installation prerequisites - Linux / Mac
|
||||
|
||||
- `Yarn`
|
||||
- `NodeJS >= v16`
|
||||
- `Rust & cargo`
|
||||
|
||||
## Installation prerequisites - Windows
|
||||
|
||||
- When running on Windows you will need to install c++ build tools
|
||||
- An easy guide to get rust up and running [Installation]("http://kennykerr.ca/2019/11/18/rust-getting-started/")
|
||||
- When installing NodeJS please use the `current features` version
|
||||
- Using a package manager like [Chocolatey]("chocolatey.org") is recommended
|
||||
- Nym connect requires you to have `Webview2` installed, please head to the [Installer](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section), this will ensure a smooth app launch
|
||||
|
||||
## Installation
|
||||
|
||||
Inside the `nym-connect` directory, run the following command:
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Development mode
|
||||
|
||||
You can compile nym-connect in development mode by running the following command inside the `nym-connect` directory:
|
||||
|
||||
```
|
||||
yarn dev
|
||||
```
|
||||
This will produce a binary in - `nym-connect/target/debug/` named `nym-connect`
|
||||
|
||||
To launch, navigate to the directory and run the following command: `./nym-connect`
|
||||
|
||||
## Production mode
|
||||
|
||||
Run the following command from the `nym-connect` folder
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
The output will compile different types of binaries dependent on your hardware / OS system. Once the binaries are built, they can be located as follows:
|
||||
|
||||
### Binary output directory structure
|
||||
```
|
||||
**macos**
|
||||
|
|
||||
└─── target/release
|
||||
| |─ nym-connect
|
||||
└───target/release/bundle/dmg
|
||||
│ │─ bundle_dmg.sh
|
||||
│ │─ nym-connect.*.dmg
|
||||
└───target/release/bundle/macos/MacOs
|
||||
│ │─ nym-connect
|
||||
|
|
||||
**Linux**
|
||||
└─── target/release
|
||||
| │─ nym-connect
|
||||
└───target/release/bundle/appimage
|
||||
│ │─ nym-connect_*_.AppImage
|
||||
│ │─ build_appimage.sh
|
||||
└───target/release/bundle/deb
|
||||
│ │─ nym-connect_*_.deb
|
||||
|
|
||||
**Windows**
|
||||
└─── target/release
|
||||
| │─ nym-connect.exe
|
||||
└───target/release/bundle/msi
|
||||
│ │─ nym-connect_*_.msi
|
||||
```
|
||||
|
||||
For instructions on how to release nym-connect, please see [RELEASE.md](./docs/release/RELEASE.md).
|
||||
|
||||
# Storybook
|
||||
|
||||
Run storybook with:
|
||||
|
||||
```
|
||||
yarn storybook
|
||||
```
|
||||
|
||||
And build storybook static site with:
|
||||
|
||||
```
|
||||
yarn storybook:build
|
||||
```
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
{
|
||||
"name": "@nym/nym-connect",
|
||||
"version": "1.1.21",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prewebpack:dev": "yarn --cwd ../.. build",
|
||||
"webpack:dev": "yarn webpack serve --config webpack.dev.js",
|
||||
"webpack:dev:onlyThis": "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": {
|
||||
"@emotion/react": "^11.7.0",
|
||||
"@emotion/styled": "^11.6.0",
|
||||
"@hookform/resolvers": "^2.8.0",
|
||||
"@mui/icons-material": "^5.2.0",
|
||||
"@mui/lab": "^5.0.0-alpha.72",
|
||||
"@mui/material": "^5.2.2",
|
||||
"@mui/styles": "^5.2.2",
|
||||
"@mui/system": ">= 5",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@sentry/integrations": "^7.54.0",
|
||||
"@sentry/react": "^7.54.0",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
|
||||
"clsx": "^1.1.1",
|
||||
"luxon": "^2.3.0",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^3.1.3",
|
||||
"react-hook-form": "^7.14.2",
|
||||
"react-markdown": "^8.0.4",
|
||||
"react-router-dom": "^6.7.0",
|
||||
"semver": "^6.3.0",
|
||||
"yup": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.14.5",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@mdx-js/loader": "^2.1.5",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@storybook/react": "^6.5.15",
|
||||
"@svgr/webpack": "^6.1.1",
|
||||
"@tauri-apps/cli": "^1.2.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": "^18.0.26",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/semver": "^7.3.8",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"babel-loader": "^8.3.0",
|
||||
"babel-plugin-root-import": "^6.6.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"css-minimizer-webpack-plugin": "^3.0.2",
|
||||
"dotenv-webpack": "^7.0.3",
|
||||
"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": "^7.0.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.8.7",
|
||||
"react-refresh": "^0.10.0",
|
||||
"react-refresh-typescript": "^2.0.2",
|
||||
"style-loader": "^3.3.1",
|
||||
"thread-loader": "^3.0.4",
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-loader": "^9.4.2",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||
"typescript": "^4.6.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-dev-server": "^4.5.0",
|
||||
"webpack-favicons": "^1.3.8",
|
||||
"webpack-merge": "^5.8.0",
|
||||
"yaml-loader": "^0.8.0"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Nym Connect</title>
|
||||
</head>
|
||||
<body style="background: rgb(29, 33, 37);">
|
||||
<div id="root-growth"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Nym Connect</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Nym Wallet Logs</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root-log"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,17 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if ! [[ "$RELEASE_TAG" =~ ^nym-connect-s-v.*? ]]; then
|
||||
echo -e " ✗ Invalid release tag $RELEASE_TAG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="${RELEASE_TAG#nym-connect-s-v}"
|
||||
|
||||
sed -i "s/^name = \".*\"/name = \"nym-connect-s\"/" src-tauri/Cargo.toml
|
||||
sed -i "s/^version = \".*\"/version = \"$version\"/" src-tauri/Cargo.toml
|
||||
sed -i 's/"productName": "nym-connect"/"productName": "NymConnect S"/' src-tauri/tauri.conf.json
|
||||
sed -i "s/\"version\": \".*\"/\"version\": \"$version\"/" src-tauri/tauri.conf.json
|
||||
|
||||
echo -e " ✓ bump version $version"
|
||||
@@ -1,4 +0,0 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
WixTools
|
||||
@@ -1,81 +0,0 @@
|
||||
[package]
|
||||
name = "nym-connect"
|
||||
version = "1.1.21"
|
||||
description = "nym-connect"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
repository = ""
|
||||
default-run = "nym-connect"
|
||||
edition = "2021"
|
||||
build = "src/build.rs"
|
||||
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.2.1", features = [] }
|
||||
|
||||
tauri-codegen = "^1.2.1"
|
||||
tauri-macros = "^1.2.1"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
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"
|
||||
fern = { version = "0.6.1", features = ["colored"] }
|
||||
itertools = "0.10.5"
|
||||
log = { version = "0.4", features = ["serde"] }
|
||||
pretty_env_logger = "0.4.0"
|
||||
rand = "0.8"
|
||||
reqwest = { version = "0.11.22", features = ["json", "socks"] }
|
||||
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_repr = "0.1"
|
||||
tap = "1.0.1"
|
||||
# 07.07.23: JS: I changed the version from ^1.2.2 to fix up indirect import of web-sys
|
||||
tauri = { version = "1.4.1", features = [
|
||||
"clipboard-write-text",
|
||||
"macos-private-api",
|
||||
"notification-all",
|
||||
"shell-open",
|
||||
"system-tray",
|
||||
"updater",
|
||||
"window-close",
|
||||
"window-minimize",
|
||||
"window-start-dragging",
|
||||
] }
|
||||
#tendermint-rpc = "0.23.0"
|
||||
thiserror = "1.0"
|
||||
time = { version = "0.3.30", features = ["local-offset"] }
|
||||
tokio = { version = "1.24.1", features = ["sync", "time"] }
|
||||
url = "2.4"
|
||||
yaml-rust = "0.4"
|
||||
toml = "0.7"
|
||||
sentry = { version = "0.31.5", features = ["anyhow"] }
|
||||
sentry-log = "0.31.5"
|
||||
dotenvy = "0.15.7"
|
||||
|
||||
nym-client-core = { path = "../../../common/client-core" }
|
||||
nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
|
||||
nym-contracts-common = { path = "../../../common/cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-config = { path = "../../../common/config" }
|
||||
nym-crypto = { path = "../../../common/crypto" }
|
||||
nym-credential-storage = { path = "../../../common/credential-storage" }
|
||||
nym-bin-common = { path = "../../../common/bin-common" }
|
||||
nym-socks5-client-core = { path = "../../../common/socks5-client-core" }
|
||||
nym-sphinx = { path = "../../../common/nymsphinx" }
|
||||
nym-task = { path = "../../../common/task" }
|
||||
nym-topology = { path = "../../../common/topology" }
|
||||
nym-validator-client = { path = "../../../common/client-libs/validator-client" }
|
||||
|
||||
[dev-dependencies]
|
||||
ts-rs = "7.0.0"
|
||||
tempfile = "3.3.0"
|
||||
|
||||
[features]
|
||||
default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 56 KiB |
@@ -1,16 +0,0 @@
|
||||
use std::env;
|
||||
|
||||
mod constants;
|
||||
|
||||
use constants::{SENTRY_DSN_JS, SENTRY_DSN_RUST};
|
||||
|
||||
fn main() {
|
||||
// set these env vars at compile time
|
||||
if let Ok(dsn) = env::var(SENTRY_DSN_RUST) {
|
||||
println!("cargo:rustc-env={}={}", SENTRY_DSN_RUST, dsn);
|
||||
}
|
||||
if let Ok(dsn) = env::var(SENTRY_DSN_JS) {
|
||||
println!("cargo:rustc-env={}={}", SENTRY_DSN_JS, dsn);
|
||||
}
|
||||
tauri_build::build();
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::persistence::NymConnectPaths;
|
||||
use crate::config::template::CONFIG_TEMPLATE;
|
||||
use crate::config::upgrade::try_upgrade_config;
|
||||
use crate::error::{BackendError, Result};
|
||||
use nym_client_core::client::base_client::non_wasm_helpers::setup_fs_gateways_storage;
|
||||
use nym_client_core::client::base_client::storage::gateways_storage::{
|
||||
GatewayDetails, RemoteGatewayDetails,
|
||||
};
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_client_core::init::generate_new_client_keys;
|
||||
use nym_client_core::init::helpers::current_gateways;
|
||||
use nym_client_core::init::types::{GatewaySelectionSpecification, GatewaySetup};
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
|
||||
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR,
|
||||
};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_socks5_client_core::config::Config as Socks5CoreConfig;
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
use tap::TapFallible;
|
||||
|
||||
mod old_config_v1_1_13;
|
||||
mod old_config_v1_1_20;
|
||||
mod old_config_v1_1_20_2;
|
||||
mod old_config_v1_1_30;
|
||||
mod old_config_v1_1_33;
|
||||
mod persistence;
|
||||
mod template;
|
||||
mod upgrade;
|
||||
mod user_data;
|
||||
|
||||
pub use user_data::*;
|
||||
|
||||
static SOCKS5_CONFIG_ID: &str = "nym-connect";
|
||||
|
||||
// backwards compatibility : )
|
||||
const DEFAULT_NYM_CONNECT_CLIENTS_DIR: &str = "socks5-clients";
|
||||
|
||||
/// Derive default path to clients's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_NYM_CONNECT_CLIENTS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_CONFIG_DIR)
|
||||
}
|
||||
|
||||
/// Derive default path to client's config file.
|
||||
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/config/config.toml`
|
||||
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
|
||||
}
|
||||
|
||||
/// Derive default path to nym connects's data directory where files, such as keys, are stored.
|
||||
/// It should get resolved to `$HOME/.nym/socks5-clients/<id>/data`
|
||||
pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
must_get_home()
|
||||
.join(NYM_DIR)
|
||||
.join(DEFAULT_NYM_CONNECT_CLIENTS_DIR)
|
||||
.join(id)
|
||||
.join(DEFAULT_DATA_DIR)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct Config {
|
||||
pub core: Socks5CoreConfig,
|
||||
|
||||
pub storage_paths: NymConnectPaths,
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
}
|
||||
}
|
||||
|
||||
pub fn socks5_config_id_appended_with(gateway_id: &str) -> String {
|
||||
format!("{SOCKS5_CONFIG_ID}-{gateway_id}")
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: AsRef<str>>(id: S, provider_mix_address: S) -> Self {
|
||||
Config {
|
||||
core: Socks5CoreConfig::new(
|
||||
id.as_ref(),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
provider_mix_address.as_ref(),
|
||||
),
|
||||
storage_paths: NymConnectPaths::new_default(default_data_directory(id.as_ref())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
}
|
||||
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
|
||||
pub fn default_location(&self) -> PathBuf {
|
||||
default_config_filepath(&self.core.base.client.id)
|
||||
}
|
||||
|
||||
pub fn save_to_default_location(&self) -> io::Result<()> {
|
||||
let config_save_location: PathBuf = self.default_location();
|
||||
save_formatted_config_to_file(self, config_save_location)
|
||||
}
|
||||
|
||||
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.
|
||||
// JS: why are we spawning a new thread here?
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
fn init_paths(id: &str) -> io::Result<()> {
|
||||
fs::create_dir_all(default_data_directory(id))?;
|
||||
fs::create_dir_all(default_config_directory(id))
|
||||
}
|
||||
|
||||
fn try_extract_version_for_upgrade_failure(err: BackendError) -> Option<String> {
|
||||
if let BackendError::ClientCoreError {
|
||||
source: ClientCoreError::UnableToUpgradeConfigFile { new_version },
|
||||
} = err
|
||||
{
|
||||
Some(new_version)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init_socks5_config(provider_address: String, chosen_gateway_id: String) -> Result<()> {
|
||||
log::trace!("Initialising client...");
|
||||
|
||||
// Append the gateway id to the name id that we store the config under
|
||||
let id = socks5_config_id_appended_with(&chosen_gateway_id);
|
||||
let _validated = identity::PublicKey::from_base58_string(&chosen_gateway_id)
|
||||
.map_err(|_| BackendError::UnableToParseGateway)?;
|
||||
|
||||
let config_path = default_config_filepath(&id);
|
||||
let already_init = if config_path.exists() {
|
||||
// in case we're using old config, try to upgrade it
|
||||
// (if we're using the current version, it's a no-op)
|
||||
if let Err(err) = try_upgrade_config(&id).await {
|
||||
log::error!(
|
||||
"Failed to upgrade config file {}: {err}",
|
||||
config_path.display(),
|
||||
);
|
||||
return if let Some(failed_at_version) = try_extract_version_for_upgrade_failure(err) {
|
||||
Err(
|
||||
BackendError::CouldNotUpgradeExistingConfigurationFileAtVersion {
|
||||
file: config_path,
|
||||
failed_at_version,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Err(BackendError::CouldNotUpgradeExistingConfigurationFile { file: config_path })
|
||||
};
|
||||
};
|
||||
eprintln!("SOCKS5 client \"{id}\" was already initialised before");
|
||||
true
|
||||
} else {
|
||||
init_paths(&id)?;
|
||||
false
|
||||
};
|
||||
|
||||
// // Future proofing. This flag exists for the other clients
|
||||
// let user_wants_force_register = false;
|
||||
|
||||
log::trace!("Creating config for id: {id}");
|
||||
let mut config = Config::new(&id, &provider_address);
|
||||
|
||||
if let Ok(raw_validators) = std::env::var(nym_config::defaults::var_names::NYM_API) {
|
||||
config.core.base.client.nym_api_urls = nym_config::parse_urls(&raw_validators);
|
||||
}
|
||||
|
||||
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
|
||||
let details_store =
|
||||
setup_fs_gateways_storage(&config.storage_paths.common_paths.gateway_registrations).await?;
|
||||
|
||||
// if this is a first time client with this particular id is initialised, generated long-term keys
|
||||
if !already_init {
|
||||
let mut rng = OsRng;
|
||||
generate_new_client_keys(&mut rng, &key_store).await?;
|
||||
}
|
||||
|
||||
let gateway_setup = if !already_init {
|
||||
let selection_spec =
|
||||
GatewaySelectionSpecification::new(Some(chosen_gateway_id), None, false);
|
||||
let mut rng = rand::thread_rng();
|
||||
let available_gateways =
|
||||
current_gateways(&mut rng, &config.core.base.client.nym_api_urls).await?;
|
||||
GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
available_gateways,
|
||||
wg_tun_address: None,
|
||||
}
|
||||
} else {
|
||||
GatewaySetup::MustLoad {
|
||||
gateway_id: Some(chosen_gateway_id),
|
||||
}
|
||||
};
|
||||
|
||||
let init_details =
|
||||
nym_client_core::init::setup_gateway(gateway_setup, &key_store, &details_store).await?;
|
||||
|
||||
let GatewayDetails::Remote(gateway_details) = &init_details.gateway_registration.details else {
|
||||
return Err(ClientCoreError::UnexpectedPersistedCustomGatewayDetails)?;
|
||||
};
|
||||
|
||||
config.save_to_default_location().tap_err(|_| {
|
||||
log::error!("Failed to save the config file");
|
||||
})?;
|
||||
|
||||
print_saved_config(&config, gateway_details);
|
||||
|
||||
let address = init_details.client_address();
|
||||
log::info!("The address of this client is: {address}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_saved_config(config: &Config, gateway_details: &RemoteGatewayDetails) {
|
||||
log::info!(
|
||||
"Saved configuration file to {}",
|
||||
config.default_location().display()
|
||||
);
|
||||
log::info!("Gateway id: {}", gateway_details.gateway_id);
|
||||
if let Some(owner) = gateway_details.gateway_owner_address.as_ref() {
|
||||
log::info!("Gateway owner: {owner}");
|
||||
}
|
||||
log::info!("Gateway listener: {}", gateway_details.gateway_listener);
|
||||
log::info!(
|
||||
"Service provider address: {}",
|
||||
config.core.socks5.provider_mix_address
|
||||
);
|
||||
log::info!(
|
||||
"Service provider port: {}",
|
||||
config.core.socks5.bind_address.port()
|
||||
);
|
||||
log::info!("Client configuration completed.");
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::old_config_v1_1_20::{ConfigV1_1_20, Socks5V1_1_20};
|
||||
use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use nym_config::must_get_home;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OldConfigV1_1_13 {
|
||||
#[serde(flatten)]
|
||||
pub base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
|
||||
|
||||
pub socks5: Socks5V1_1_20,
|
||||
}
|
||||
|
||||
impl MigrationNymConfig for OldConfigV1_1_13 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let base_dir = must_get_home();
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let base_dir = PathBuf::from("/tmp");
|
||||
|
||||
base_dir.join(".nym").join("socks5-clients")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OldConfigV1_1_13> for ConfigV1_1_20 {
|
||||
fn from(value: OldConfigV1_1_13) -> Self {
|
||||
ConfigV1_1_20 {
|
||||
base: value.base.into(),
|
||||
socks5: value.socks5,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::old_config_v1_1_20_2::{
|
||||
ConfigV1_1_20_2, CoreConfigV1_1_20_2, SocksClientPathsV1_1_20_2,
|
||||
};
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
|
||||
use nym_client_core::config::disk_persistence::old_v1_1_33::ClientKeysPathsV1_1_33;
|
||||
use nym_client_core::config::old_config_v1_1_20::ConfigV1_1_20 as BaseConfigV1_1_20;
|
||||
use nym_client_core::config::old_config_v1_1_20_2::ClientV1_1_20_2;
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use nym_config::must_get_home;
|
||||
use nym_socks5_client_core::config::old_config_v1_1_20_2::{
|
||||
BaseClientConfigV1_1_20_2, Socks5DebugV1_1_20_2, Socks5V1_1_20_2,
|
||||
};
|
||||
use nym_socks5_client_core::config::{ProviderInterfaceVersion, Socks5ProtocolVersion};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const DEFAULT_CONNECTION_START_SURBS: u32 = 20;
|
||||
const DEFAULT_PER_REQUEST_SURBS: u32 = 3;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_20 {
|
||||
#[serde(flatten)]
|
||||
pub base: BaseConfigV1_1_20<ConfigV1_1_20>,
|
||||
|
||||
pub socks5: Socks5V1_1_20,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_20> for ConfigV1_1_20_2 {
|
||||
fn from(value: ConfigV1_1_20) -> Self {
|
||||
ConfigV1_1_20_2 {
|
||||
core: CoreConfigV1_1_20_2 {
|
||||
base: BaseClientConfigV1_1_20_2 {
|
||||
client: ClientV1_1_20_2 {
|
||||
version: value.base.client.version,
|
||||
id: value.base.client.id,
|
||||
disabled_credentials_mode: value.base.client.disabled_credentials_mode,
|
||||
nyxd_urls: value.base.client.nyxd_urls,
|
||||
nym_api_urls: value.base.client.nym_api_urls,
|
||||
gateway_endpoint: value.base.client.gateway_endpoint.into(),
|
||||
},
|
||||
debug: value.base.debug.into(),
|
||||
},
|
||||
socks5: value.socks5.into(),
|
||||
},
|
||||
storage_paths: SocksClientPathsV1_1_20_2 {
|
||||
common_paths: CommonClientPathsV1_1_20_2 {
|
||||
keys: ClientKeysPathsV1_1_33 {
|
||||
private_identity_key_file: value.base.client.private_identity_key_file,
|
||||
public_identity_key_file: value.base.client.public_identity_key_file,
|
||||
private_encryption_key_file: value.base.client.private_encryption_key_file,
|
||||
public_encryption_key_file: value.base.client.public_encryption_key_file,
|
||||
gateway_shared_key_file: value.base.client.gateway_shared_key_file,
|
||||
ack_key_file: value.base.client.ack_key_file,
|
||||
},
|
||||
credentials_database: value.base.client.database_path,
|
||||
reply_surb_database: value.base.client.reply_surb_database_path,
|
||||
},
|
||||
},
|
||||
logging: LoggingSettings::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MigrationNymConfig for ConfigV1_1_20 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
let base_dir = must_get_home();
|
||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||
let base_dir = PathBuf::from("/tmp");
|
||||
|
||||
base_dir.join(".nym").join("socks5-clients")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Socks5V1_1_20 {
|
||||
pub listening_port: u16,
|
||||
|
||||
pub provider_mix_address: String,
|
||||
|
||||
#[serde(default = "ProviderInterfaceVersion::new_legacy")]
|
||||
pub provider_interface_version: ProviderInterfaceVersion,
|
||||
|
||||
#[serde(default = "Socks5ProtocolVersion::new_legacy")]
|
||||
pub socks5_protocol_version: Socks5ProtocolVersion,
|
||||
|
||||
#[serde(default)]
|
||||
pub send_anonymously: bool,
|
||||
|
||||
#[serde(default)]
|
||||
pub socks5_debug: Socks5DebugV1_1_20,
|
||||
}
|
||||
|
||||
impl From<Socks5V1_1_20> for Socks5V1_1_20_2 {
|
||||
fn from(value: Socks5V1_1_20) -> Self {
|
||||
Socks5V1_1_20_2 {
|
||||
listening_port: value.listening_port,
|
||||
provider_mix_address: value.provider_mix_address,
|
||||
provider_interface_version: value.provider_interface_version,
|
||||
socks5_protocol_version: value.socks5_protocol_version,
|
||||
send_anonymously: value.send_anonymously,
|
||||
socks5_debug: value.socks5_debug.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Socks5DebugV1_1_20 {
|
||||
connection_start_surbs: u32,
|
||||
per_request_surbs: u32,
|
||||
}
|
||||
|
||||
impl From<Socks5DebugV1_1_20> for Socks5DebugV1_1_20_2 {
|
||||
fn from(value: Socks5DebugV1_1_20) -> Self {
|
||||
Socks5DebugV1_1_20_2 {
|
||||
connection_start_surbs: value.connection_start_surbs,
|
||||
per_request_surbs: value.per_request_surbs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Socks5DebugV1_1_20 {
|
||||
fn default() -> Self {
|
||||
Socks5DebugV1_1_20 {
|
||||
connection_start_surbs: DEFAULT_CONNECTION_START_SURBS,
|
||||
per_request_surbs: DEFAULT_PER_REQUEST_SURBS,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::default_config_filepath;
|
||||
use crate::config::old_config_v1_1_30::ConfigV1_1_30;
|
||||
use crate::config::old_config_v1_1_33::NymConnectPathsV1_1_33;
|
||||
use crate::error::Result;
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
|
||||
use nym_client_core::config::old_config_v1_1_33::OldGatewayEndpointConfigV1_1_33;
|
||||
use nym_config::read_config_from_toml_file;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
pub use nym_socks5_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as CoreConfigV1_1_20_2;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
|
||||
pub struct SocksClientPathsV1_1_20_2 {
|
||||
#[serde(flatten)]
|
||||
pub common_paths: CommonClientPathsV1_1_20_2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_20_2 {
|
||||
pub core: CoreConfigV1_1_20_2,
|
||||
|
||||
pub storage_paths: SocksClientPathsV1_1_20_2,
|
||||
|
||||
pub logging: LoggingSettings,
|
||||
}
|
||||
|
||||
impl ConfigV1_1_20_2 {
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
}
|
||||
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
|
||||
// in this upgrade, gateway endpoint configuration was moved out of the config file,
|
||||
// so its returned to be stored elsewhere.
|
||||
pub fn upgrade(self) -> Result<(ConfigV1_1_30, OldGatewayEndpointConfigV1_1_33)> {
|
||||
let gateway_details = self.core.base.client.gateway_endpoint.clone().into();
|
||||
let config = ConfigV1_1_30 {
|
||||
core: self.core.into(),
|
||||
storage_paths: NymConnectPathsV1_1_33 {
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default()?,
|
||||
},
|
||||
// logging: self.logging,
|
||||
};
|
||||
|
||||
Ok((config, gateway_details))
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::default_config_filepath;
|
||||
use crate::config::old_config_v1_1_33::{ConfigV1_1_33, NymConnectPathsV1_1_33};
|
||||
use nym_config::read_config_from_toml_file;
|
||||
use nym_socks5_client_core::config::old_config_v1_1_30::ConfigV1_1_30 as CoreConfigV1_1_30;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_30 {
|
||||
pub core: CoreConfigV1_1_30,
|
||||
|
||||
pub storage_paths: NymConnectPathsV1_1_33,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_30> for ConfigV1_1_33 {
|
||||
fn from(value: ConfigV1_1_30) -> Self {
|
||||
ConfigV1_1_33 {
|
||||
core: value.core.into(),
|
||||
storage_paths: value.storage_paths,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigV1_1_30 {
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
}
|
||||
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::persistence::NymConnectPaths;
|
||||
use crate::config::{default_config_filepath, Config};
|
||||
use crate::error::BackendError;
|
||||
use nym_client_core::config::disk_persistence::old_v1_1_33::CommonClientPathsV1_1_33;
|
||||
use nym_config::read_config_from_toml_file;
|
||||
use nym_socks5_client_core::config::old_config_v1_1_33::ConfigV1_1_33 as CoreConfigV1_1_33;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct NymConnectPathsV1_1_33 {
|
||||
#[serde(flatten)]
|
||||
pub common_paths: CommonClientPathsV1_1_33,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_33 {
|
||||
pub core: CoreConfigV1_1_33,
|
||||
|
||||
// \/ CHANGED
|
||||
pub storage_paths: NymConnectPathsV1_1_33,
|
||||
// /\ CHANGED
|
||||
}
|
||||
|
||||
impl ConfigV1_1_33 {
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
}
|
||||
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
}
|
||||
|
||||
pub fn try_upgrade(self) -> Result<Config, BackendError> {
|
||||
Ok(Config {
|
||||
core: self.core.into(),
|
||||
storage_paths: NymConnectPaths {
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default()?,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_client_core::config::disk_persistence::CommonClientPaths;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
|
||||
pub struct NymConnectPaths {
|
||||
#[serde(flatten)]
|
||||
pub common_paths: CommonClientPaths,
|
||||
}
|
||||
|
||||
impl NymConnectPaths {
|
||||
pub fn new_default<P: AsRef<Path>>(base_data_directory: P) -> Self {
|
||||
NymConnectPaths {
|
||||
common_paths: CommonClientPaths::new_base(base_data_directory),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// While using normal toml marshalling would have been way simpler with less overhead,
|
||||
// I think it's useful to have comments attached to the saved config file to explain behaviour of
|
||||
// particular fields.
|
||||
// Note: any changes to the template must be reflected in the appropriate structs.
|
||||
pub(crate) const CONFIG_TEMPLATE: &str = r#"
|
||||
# This is a TOML config file.
|
||||
# For more information, see https://github.com/toml-lang/toml
|
||||
|
||||
##### main base client config options #####
|
||||
|
||||
[core.client]
|
||||
# Version of the client for which this configuration was created.
|
||||
version = '{{ core.client.version }}'
|
||||
|
||||
# Human readable ID of this particular client.
|
||||
id = '{{ core.client.id }}'
|
||||
|
||||
# Indicates whether this client is running in a disabled credentials mode, thus attempting
|
||||
# to claim bandwidth without presenting bandwidth credentials.
|
||||
disabled_credentials_mode = {{ core.client.disabled_credentials_mode }}
|
||||
|
||||
# Addresses to nyxd validators via which the client can communicate with the chain.
|
||||
nyxd_urls = [
|
||||
{{#each core.client.nyxd_urls }}
|
||||
'{{this}}',
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
# Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
nym_api_urls = [
|
||||
{{#each core.client.nym_api_urls }}
|
||||
'{{this}}',
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
[storage_paths]
|
||||
|
||||
# Path to file containing private identity key.
|
||||
keys.private_identity_key_file = '{{ storage_paths.keys.private_identity_key_file }}'
|
||||
|
||||
# Path to file containing public identity key.
|
||||
keys.public_identity_key_file = '{{ storage_paths.keys.public_identity_key_file }}'
|
||||
|
||||
# Path to file containing private encryption key.
|
||||
keys.private_encryption_key_file = '{{ storage_paths.keys.private_encryption_key_file }}'
|
||||
|
||||
# Path to file containing public encryption key.
|
||||
keys.public_encryption_key_file = '{{ storage_paths.keys.public_encryption_key_file }}'
|
||||
|
||||
# A gateway specific, optional, base58 stringified shared key used for
|
||||
# communication with particular gateway.
|
||||
keys.gateway_shared_key_file = '{{ storage_paths.keys.gateway_shared_key_file }}'
|
||||
|
||||
# Path to file containing key used for encrypting and decrypting the content of an
|
||||
# acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
keys.ack_key_file = '{{ storage_paths.keys.ack_key_file }}'
|
||||
|
||||
# Path to the database containing bandwidth credentials
|
||||
credentials_database = '{{ storage_paths.credentials_database }}'
|
||||
|
||||
# Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
reply_surb_database = '{{ storage_paths.reply_surb_database }}'
|
||||
|
||||
# Path to the file containing information about gateway used by this client,
|
||||
# i.e. details such as its public key, owner address or the network information.
|
||||
gateway_details = '{{ storage_paths.gateway_details }}'
|
||||
|
||||
|
||||
##### socket config options #####
|
||||
|
||||
[core.socks5]
|
||||
|
||||
# The mix address of the provider to which all requests are going to be sent.
|
||||
provider_mix_address = '{{ core.socks5.provider_mix_address }}'
|
||||
|
||||
# The address on which the client will be listening for incoming requests
|
||||
# (default: 127.0.0.1:1080)
|
||||
bind_address = '{{ core.socks5.bind_address }}'
|
||||
|
||||
# Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
|
||||
# While this is going to hide its actual address information, it will make the actual communication
|
||||
# slower and consume nearly double the bandwidth as it will require sending reply SURBs.
|
||||
#
|
||||
# Note that some service providers might not support this.
|
||||
send_anonymously = {{ core.socks5.send_anonymously }}
|
||||
|
||||
##### logging configuration options #####
|
||||
|
||||
[logging]
|
||||
|
||||
# TODO
|
||||
|
||||
|
||||
##### debug configuration options #####
|
||||
# The following options should not be modified unless you know EXACTLY what you are doing
|
||||
# as if set incorrectly, they may impact your anonymity.
|
||||
|
||||
# [core.socks5.socks5_debug]
|
||||
|
||||
|
||||
[core.debug]
|
||||
|
||||
[core.debug.traffic]
|
||||
average_packet_delay = '{{ core.debug.traffic.average_packet_delay }}'
|
||||
message_sending_average_delay = '{{ core.debug.traffic.message_sending_average_delay }}'
|
||||
|
||||
[core.debug.acknowledgements]
|
||||
average_ack_delay = '{{ core.debug.acknowledgements.average_ack_delay }}'
|
||||
|
||||
[core.debug.cover_traffic]
|
||||
loop_cover_traffic_average_delay = '{{ core.debug.cover_traffic.loop_cover_traffic_average_delay }}'
|
||||
|
||||
"#;
|
||||
@@ -1,174 +0,0 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::old_config_v1_1_30::ConfigV1_1_30;
|
||||
use crate::config::old_config_v1_1_33::ConfigV1_1_33;
|
||||
use crate::{
|
||||
config::{
|
||||
old_config_v1_1_13::OldConfigV1_1_13, old_config_v1_1_20::ConfigV1_1_20,
|
||||
old_config_v1_1_20_2::ConfigV1_1_20_2,
|
||||
},
|
||||
error::Result,
|
||||
};
|
||||
use log::{debug, info};
|
||||
use nym_client_core::client::base_client::storage::migration_helpers::v1_1_33;
|
||||
|
||||
async fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.13 (which is incompatible with the next step, i.e. 1.1.19)
|
||||
let Ok(old_config) = OldConfigV1_1_13::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.13 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_20 = old_config.into();
|
||||
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
|
||||
let (updated_step3, gateway_config) = updated_step2.upgrade()?;
|
||||
let old_paths = updated_step3.storage_paths.clone();
|
||||
|
||||
let updated_step4: ConfigV1_1_33 = updated_step3.into();
|
||||
let updated = updated_step4.try_upgrade()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
&updated.storage_paths.common_paths,
|
||||
Some(gateway_config),
|
||||
)
|
||||
.await?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.20 (which is incompatible with the current one, i.e. +1.1.21)
|
||||
let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.20 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_20_2 = old_config.into();
|
||||
let (updated_step2, gateway_config) = updated_step1.upgrade()?;
|
||||
let old_paths = updated_step2.storage_paths.clone();
|
||||
|
||||
let updated_step3: ConfigV1_1_33 = updated_step2.into();
|
||||
let updated = updated_step3.try_upgrade()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
&updated.storage_paths.common_paths,
|
||||
Some(gateway_config),
|
||||
)
|
||||
.await?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool> {
|
||||
// explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21)
|
||||
let Ok(old_config) = ConfigV1_1_20_2::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.20_2 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let (updated_step1, gateway_config) = old_config.upgrade()?;
|
||||
let old_paths = updated_step1.storage_paths.clone();
|
||||
|
||||
let updated_step2: ConfigV1_1_33 = updated_step1.into();
|
||||
let updated = updated_step2.try_upgrade()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
&updated.storage_paths.common_paths,
|
||||
Some(gateway_config),
|
||||
)
|
||||
.await?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn try_upgrade_v1_1_30_config(id: &str) -> Result<bool> {
|
||||
// explicitly load it as v1.1.20_2 (which is incompatible with the current one, i.e. +1.1.21)
|
||||
let Ok(old_config) = ConfigV1_1_30::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.30 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let old_paths = old_config.storage_paths.clone();
|
||||
|
||||
let updated_step1: ConfigV1_1_33 = old_config.into();
|
||||
let updated = updated_step1.try_upgrade()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
&updated.storage_paths.common_paths,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn try_upgrade_v1_1_33_config(id: &str) -> Result<bool> {
|
||||
// explicitly load it as v1.1.33 (which is incompatible with the current one, i.e. +1.1.34)
|
||||
let Ok(old_config) = ConfigV1_1_33::read_from_default_path(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(false);
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.33 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let old_paths = old_config.storage_paths.clone();
|
||||
|
||||
let updated = old_config.try_upgrade()?;
|
||||
|
||||
v1_1_33::migrate_gateway_details(
|
||||
&old_paths.common_paths,
|
||||
&updated.storage_paths.common_paths,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub async fn try_upgrade_config(id: &str) -> Result<()> {
|
||||
debug!("Attempting to upgrade config file for \"{id}\"");
|
||||
if try_upgrade_v1_1_13_config(id).await? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_20_config(id).await? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_20_2_config(id).await? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_30_config(id).await? {
|
||||
return Ok(());
|
||||
}
|
||||
if try_upgrade_v1_1_33_config(id).await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
use eyre::{eyre, Context, Result};
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs, str};
|
||||
use tauri::api::path::data_dir;
|
||||
|
||||
const DATA_DIR: &str = "nym-connect";
|
||||
const DATA_FILE: &str = "user-data.toml";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Default)]
|
||||
pub enum PrivacyLevel {
|
||||
#[default]
|
||||
High,
|
||||
Medium,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct SelectedGateway {
|
||||
address: Option<String>,
|
||||
is_active: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct SelectedSp {
|
||||
address: Option<String>,
|
||||
is_active: Option<bool>,
|
||||
}
|
||||
|
||||
// User data is read from and write on disk
|
||||
// Linux: $XDG_DATA_HOME or $HOME/.local/share/
|
||||
// macOS: $HOME/Library/Application Support
|
||||
// Windows: {FOLDERID_RoamingAppData}
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct UserData {
|
||||
pub monitoring: Option<bool>,
|
||||
pub privacy_level: Option<PrivacyLevel>,
|
||||
pub selected_gateway: Option<SelectedGateway>,
|
||||
pub selected_sp: Option<SelectedSp>,
|
||||
}
|
||||
|
||||
fn create_directory_path() -> Result<()> {
|
||||
let mut data_dir = data_dir().ok_or(eyre!("Failed to retrieve data directory"))?;
|
||||
data_dir.push(DATA_DIR);
|
||||
|
||||
fs::create_dir_all(&data_dir).context(format!(
|
||||
"Failed to create user data directory path {}",
|
||||
data_dir.display()
|
||||
))
|
||||
}
|
||||
|
||||
impl UserData {
|
||||
pub fn read() -> Result<Self> {
|
||||
// create the full directory path if it is missing
|
||||
create_directory_path()?;
|
||||
|
||||
let mut data_path = data_dir().ok_or(eyre!("Failed to retrieve data directory"))?;
|
||||
|
||||
data_path.push(DATA_DIR);
|
||||
data_path.push(DATA_FILE);
|
||||
let content = fs::read(&data_path)
|
||||
.context(format!("Failed to read user data {}", data_path.display()))?;
|
||||
|
||||
toml::from_str::<UserData>(str::from_utf8(&content)?).map_err(|e| {
|
||||
error!("{}", e);
|
||||
eyre!("{e}")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write(&self) -> Result<()> {
|
||||
// create the full directory path if it is missing
|
||||
create_directory_path()?;
|
||||
|
||||
let mut data_path = data_dir().ok_or(eyre!("Failed to retrieve data directory"))?;
|
||||
|
||||
data_path.push(DATA_DIR);
|
||||
data_path.push(DATA_FILE);
|
||||
let toml = toml::to_string(&self)?;
|
||||
fs::write(data_path, toml)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clear(&self) -> Result<()> {
|
||||
// create the full directory path if it is missing
|
||||
create_directory_path()?;
|
||||
|
||||
let mut data_path = data_dir().ok_or(eyre!("Failed to retrieve data directory"))?;
|
||||
|
||||
data_path.push(DATA_DIR);
|
||||
data_path.push(DATA_FILE);
|
||||
fs::write(data_path, vec![])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// env var keys
|
||||
pub const SENTRY_DSN_RUST: &str = "SENTRY_DSN_RUST";
|
||||
pub const SENTRY_DSN_JS: &str = "SENTRY_DSN_JS";
|
||||
@@ -1,136 +0,0 @@
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use serde::{Serialize, Serializer};
|
||||
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}")]
|
||||
TauriApiError {
|
||||
#[from]
|
||||
source: tauri::api::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
SerdeJsonError {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ClientCoreError {
|
||||
#[from]
|
||||
source: ClientCoreError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ApiClientError {
|
||||
#[from]
|
||||
source: crate::operations::growth::api_client::ApiClientError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
EnvError {
|
||||
#[from]
|
||||
source: std::env::VarError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
UrlError {
|
||||
#[from]
|
||||
source: url::ParseError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
APIError {
|
||||
#[from]
|
||||
source: nym_validator_client::nym_api::error::NymAPIError,
|
||||
},
|
||||
|
||||
#[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 not initialize without gateway set")]
|
||||
CouldNotInitWithoutGateway,
|
||||
#[error("could not initialize without service provider set")]
|
||||
CouldNotInitWithoutServiceProvider,
|
||||
#[error("could not get file name")]
|
||||
CouldNotGetFilename,
|
||||
#[error("could not get config file location")]
|
||||
CouldNotGetConfigFilename,
|
||||
#[error("could not load existing gateway configuration")]
|
||||
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
|
||||
#[error("could not upgrade `{file}` to latest version")]
|
||||
CouldNotUpgradeExistingConfigurationFile { file: std::path::PathBuf },
|
||||
#[error("could not upgrade `{file}` to latest version (failed at {failed_at_version})")]
|
||||
CouldNotUpgradeExistingConfigurationFileAtVersion {
|
||||
file: std::path::PathBuf,
|
||||
failed_at_version: String,
|
||||
},
|
||||
|
||||
#[error("no gateways found in directory")]
|
||||
NoGatewaysFoundInDirectory,
|
||||
#[error("no gateways found with compatible version: {0}")]
|
||||
NoVersionCompatibleGatewaysFound(String),
|
||||
#[error("no gateways found with acceptable performance")]
|
||||
NoGatewaysWithAcceptablePerformanceFound,
|
||||
|
||||
#[error("no network-requesters found in directory")]
|
||||
NoServicesFoundInDirectory,
|
||||
#[error("no active network-requesters found in directory")]
|
||||
NoActiveServicesFound,
|
||||
|
||||
#[error("unable to open a new window")]
|
||||
NewWindowError,
|
||||
#[error("unable to parse the specified gateway")]
|
||||
UnableToParseGateway,
|
||||
#[error("unable to write user data to disk")]
|
||||
UserDataWriteError,
|
||||
|
||||
#[error("unable to load keys: {source}")]
|
||||
UnableToLoadKeys {
|
||||
source: Box<dyn std::error::Error + Send + Sync>,
|
||||
},
|
||||
|
||||
#[error(transparent)]
|
||||
ConfigUpgradeFailure(#[from] nym_client_core::config::ConfigUpgradeFailure),
|
||||
|
||||
#[error("HTTP get request failed: {status_code}")]
|
||||
RequestFail {
|
||||
url: reqwest::Url,
|
||||
status_code: reqwest::StatusCode,
|
||||
},
|
||||
}
|
||||
|
||||
impl Serialize for BackendError {
|
||||
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>;
|
||||
@@ -1,71 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use nym_client_core::error::ClientCoreStatusMessage;
|
||||
use nym_task::manager::TaskStatus;
|
||||
use tauri::async_runtime::RwLock;
|
||||
|
||||
use crate::{
|
||||
state::{GatewayConnectivity, State},
|
||||
tasks,
|
||||
};
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
struct Payload {
|
||||
title: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl Payload {
|
||||
fn new(title: String, message: String) -> Self {
|
||||
Self { title, message }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_event(event: &str, title: &str, msg: &str, window: &tauri::Window<tauri::Wry>) {
|
||||
if let Err(err) = window.emit(event, Payload::new(title.into(), msg.into())) {
|
||||
log::error!("Failed to emit tauri event: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_status_event<T: ToString>(event: &str, msg: &T, window: &tauri::Window<tauri::Wry>) {
|
||||
if let Err(err) = window.emit(event, Payload::new("SOCKS5 update".into(), msg.to_string())) {
|
||||
log::error!("Failed to emit tauri event: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_task_status(
|
||||
task_status: &TaskStatus,
|
||||
state: &Arc<RwLock<State>>,
|
||||
window: &tauri::Window,
|
||||
) {
|
||||
match task_status {
|
||||
TaskStatus::Ready | TaskStatus::ReadyWithGateway(_) => {
|
||||
{
|
||||
let mut state_w = state.write().await;
|
||||
state_w.mark_connected(window);
|
||||
}
|
||||
|
||||
emit_status_event("socks5-connected-event", task_status, window);
|
||||
tasks::start_connection_check(state.clone(), window.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_client_status_message(
|
||||
client_status_message: &ClientCoreStatusMessage,
|
||||
state: &Arc<RwLock<State>>,
|
||||
window: &tauri::Window,
|
||||
) {
|
||||
// TODO: use this instead once we change on the frontend too
|
||||
let _event_name = match client_status_message {
|
||||
ClientCoreStatusMessage::GatewayIsSlow | ClientCoreStatusMessage::GatewayIsVerySlow => {
|
||||
"socks5-gateway-status"
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(connectivity) = GatewayConnectivity::try_from(client_status_message) {
|
||||
state.write().await.set_gateway_connectivity(connectivity);
|
||||
}
|
||||
|
||||
emit_status_event("socks5-status-event", client_status_message, window);
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
use fern::colors::{Color, ColoredLevelConfig};
|
||||
use log::Level;
|
||||
use sentry::Level as SentryLevel;
|
||||
use serde::Serialize;
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::str::FromStr;
|
||||
use tauri::Manager;
|
||||
use time::{format_description, OffsetDateTime};
|
||||
|
||||
fn formatted_time() -> String {
|
||||
// if we fail to obtain local time according to local offset, fallback to utc
|
||||
let _now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
|
||||
|
||||
// if we're running it in the unit test, use the hardcoded value
|
||||
#[cfg(test)]
|
||||
let _now = OffsetDateTime::from_unix_timestamp(1666666666).unwrap();
|
||||
|
||||
// the unwraps are fine as we know this description is correct
|
||||
// note: the reason for this very particular format is a very simple one
|
||||
// it's what we've always been using since we copied it from the example,
|
||||
// so feel free to update it to whatever
|
||||
let format =
|
||||
format_description::parse("[[[year]-[month]-[day]][[[hour]:[minute]:[second]]").unwrap();
|
||||
_now.format(&format).unwrap()
|
||||
}
|
||||
|
||||
pub fn setup_logging(
|
||||
app_handle: tauri::AppHandle,
|
||||
monitoring: bool,
|
||||
) -> Result<(), log::SetLoggerError> {
|
||||
let colors = ColoredLevelConfig::new()
|
||||
.trace(Color::Magenta)
|
||||
.debug(Color::Blue)
|
||||
.info(Color::Green)
|
||||
.warn(Color::Yellow)
|
||||
.error(Color::Red);
|
||||
let base_config = fern::Dispatch::new()
|
||||
.level(global_level())
|
||||
.filter_lowlevel_external_components()
|
||||
.show_operations();
|
||||
|
||||
let stdout_config = fern::Dispatch::new()
|
||||
.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{} {:5} {} > {}",
|
||||
formatted_time(),
|
||||
colors.color(record.level()),
|
||||
record.target(),
|
||||
message,
|
||||
))
|
||||
})
|
||||
.chain(std::io::stdout());
|
||||
|
||||
let tauri_event_config = fern::Dispatch::new()
|
||||
.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{}[{}] {}",
|
||||
formatted_time(),
|
||||
record.target(),
|
||||
message,
|
||||
))
|
||||
})
|
||||
.chain(fern::Output::call(move |record| {
|
||||
let msg = LogMessage {
|
||||
message: record.args().to_string(),
|
||||
level: record.level().into(),
|
||||
};
|
||||
app_handle.emit_all("log://log", msg).unwrap();
|
||||
}))
|
||||
.chain(fern::Output::call(move |record| {
|
||||
if !monitoring {
|
||||
return;
|
||||
}
|
||||
let level = match record.level() {
|
||||
Level::Error => SentryLevel::Error,
|
||||
Level::Warn => SentryLevel::Warning,
|
||||
Level::Info => SentryLevel::Info,
|
||||
_ => SentryLevel::Debug,
|
||||
};
|
||||
// only send error and warn logs to sentry
|
||||
if let Level::Error | Level::Warn = record.level() {
|
||||
sentry::capture_message(&record.args().to_string(), level);
|
||||
};
|
||||
}));
|
||||
|
||||
base_config
|
||||
.chain(stdout_config)
|
||||
.chain(tauri_event_config)
|
||||
.apply()
|
||||
}
|
||||
|
||||
trait FernExt {
|
||||
fn show_operations(self) -> Self;
|
||||
fn filter_lowlevel_external_components(self) -> Self;
|
||||
}
|
||||
|
||||
impl FernExt for fern::Dispatch {
|
||||
fn show_operations(self) -> Self {
|
||||
if ::std::env::var("RUST_TRACE_OPERATIONS").is_ok() {
|
||||
self.level_for("nym_connect::operations", log::LevelFilter::Trace)
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_lowlevel_external_components(self) -> Self {
|
||||
self.level_for("handlebars", log::LevelFilter::Warn)
|
||||
.level_for("hyper", log::LevelFilter::Warn)
|
||||
.level_for("mio", log::LevelFilter::Warn)
|
||||
.level_for("reqwest", log::LevelFilter::Warn)
|
||||
.level_for("rustls", log::LevelFilter::Warn)
|
||||
.level_for("sled", log::LevelFilter::Warn)
|
||||
.level_for("tokio_reactor", log::LevelFilter::Warn)
|
||||
.level_for("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.level_for("tokio_util", log::LevelFilter::Warn)
|
||||
.level_for("tungstenite", log::LevelFilter::Warn)
|
||||
.level_for("want", log::LevelFilter::Warn)
|
||||
}
|
||||
}
|
||||
|
||||
fn global_level() -> log::LevelFilter {
|
||||
if let Ok(s) = ::std::env::var("RUST_LOG") {
|
||||
log::LevelFilter::from_str(&s).unwrap_or(log::LevelFilter::Info)
|
||||
} else {
|
||||
log::LevelFilter::Info
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
struct LogMessage {
|
||||
message: String,
|
||||
level: LogLevel,
|
||||
}
|
||||
|
||||
// Serialize to u16 instead of strings.
|
||||
#[derive(Debug, Clone, Deserialize_repr, Serialize_repr)]
|
||||
#[repr(u16)]
|
||||
enum LogLevel {
|
||||
Trace = 1,
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl From<log::Level> for LogLevel {
|
||||
fn from(level: log::Level) -> Self {
|
||||
match level {
|
||||
log::Level::Trace => LogLevel::Trace,
|
||||
log::Level::Debug => LogLevel::Debug,
|
||||
log::Level::Info => LogLevel::Info,
|
||||
log::Level::Warn => LogLevel::Warn,
|
||||
log::Level::Error => LogLevel::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn log_formatting() {
|
||||
let expected_chrono_formated = "[2022-10-25][02:57:46]".to_string();
|
||||
let new_time_based = formatted_time();
|
||||
assert_eq!(new_time_based, expected_chrono_formated)
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use nym_config::defaults::setup_env;
|
||||
use tauri::Manager;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::config::UserData;
|
||||
use crate::menu::{create_tray_menu, tray_menu_event_handler};
|
||||
use crate::state::State;
|
||||
use crate::window::window_toggle;
|
||||
|
||||
mod config;
|
||||
mod constants;
|
||||
mod error;
|
||||
mod events;
|
||||
mod logging;
|
||||
mod menu;
|
||||
mod models;
|
||||
mod monitoring;
|
||||
mod operations;
|
||||
mod state;
|
||||
mod tasks;
|
||||
mod window;
|
||||
|
||||
fn main() {
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
setup_env(env::args().nth(1).map(PathBuf::from).as_ref());
|
||||
println!("Starting up...");
|
||||
|
||||
// As per breaking change description here
|
||||
// https://github.com/tauri-apps/tauri/blob/feac1d193c6d618e49916ad0707201f43d5cdd36/tooling/bundler/CHANGELOG.md
|
||||
if let Err(error) = fix_path_env::fix() {
|
||||
println!("Failed to fix PATH: {error}");
|
||||
}
|
||||
|
||||
let user_data = UserData::read().unwrap_or_else(|e| {
|
||||
println!("{}", e);
|
||||
println!("Fallback to default");
|
||||
UserData::default()
|
||||
});
|
||||
|
||||
let monitoring = user_data.monitoring.unwrap_or(false);
|
||||
let mut _sentry_guard;
|
||||
|
||||
if monitoring {
|
||||
match monitoring::init() {
|
||||
Ok(guard) => {
|
||||
println!("Monitoring and error reporting enabled");
|
||||
|
||||
// we must keep the sentry guard in scope during app lifetime
|
||||
_sentry_guard = guard;
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Unable to init monitoring: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let context = tauri::generate_context!();
|
||||
tauri::Builder::default()
|
||||
.manage(Arc::new(RwLock::new(State::new(user_data))))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
crate::operations::config::get_config_file_location,
|
||||
crate::operations::config::get_config_id,
|
||||
crate::operations::common::get_env,
|
||||
crate::operations::common::get_user_data,
|
||||
crate::operations::common::set_monitoring,
|
||||
crate::operations::common::set_privacy_level,
|
||||
crate::operations::common::set_selected_gateway,
|
||||
crate::operations::common::set_selected_sp,
|
||||
crate::operations::connection::connect::get_gateway,
|
||||
crate::operations::connection::connect::get_service_provider,
|
||||
crate::operations::connection::connect::set_gateway,
|
||||
crate::operations::connection::connect::set_service_provider,
|
||||
crate::operations::connection::connect::start_connecting,
|
||||
crate::operations::connection::disconnect::start_disconnecting,
|
||||
crate::operations::connection::status::get_connection_health_check_status,
|
||||
crate::operations::connection::status::get_connection_status,
|
||||
crate::operations::connection::status::get_gateway_connection_status,
|
||||
crate::operations::connection::status::start_connection_health_check_task,
|
||||
crate::operations::directory::gateways::get_gateways,
|
||||
crate::operations::directory::gateways::select_gateway_with_low_latency_from_list,
|
||||
crate::operations::directory::services::get_services,
|
||||
crate::operations::export::export_keys,
|
||||
crate::operations::window::hide_window,
|
||||
crate::operations::growth::test_and_earn::growth_tne_get_client_id,
|
||||
crate::operations::growth::test_and_earn::growth_tne_take_part,
|
||||
crate::operations::growth::test_and_earn::growth_tne_get_draws,
|
||||
crate::operations::growth::test_and_earn::growth_tne_ping,
|
||||
crate::operations::growth::test_and_earn::growth_tne_submit_wallet_address,
|
||||
crate::operations::growth::test_and_earn::growth_tne_enter_draw,
|
||||
crate::operations::growth::test_and_earn::growth_tne_toggle_window,
|
||||
crate::operations::help::log::help_log_toggle_window,
|
||||
])
|
||||
.on_menu_event(|event| {
|
||||
if event.menu_item_id() == menu::SHOW_LOG_WINDOW {
|
||||
let _r = crate::operations::help::log::help_log_toggle_window(
|
||||
event.window().app_handle(),
|
||||
);
|
||||
}
|
||||
if event.menu_item_id() == menu::CLEAR_STORAGE {
|
||||
let _r = crate::operations::help::storage::help_clear_storage(
|
||||
event.window().app_handle(),
|
||||
);
|
||||
}
|
||||
})
|
||||
.setup(move |app| Ok(crate::logging::setup_logging(app.app_handle(), monitoring)?))
|
||||
.system_tray(create_tray_menu())
|
||||
.on_system_tray_event(tray_menu_event_handler)
|
||||
.run(context)
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
use tauri::{
|
||||
AppHandle, CustomMenuItem, Menu, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu,
|
||||
SystemTrayMenuItem, Wry,
|
||||
};
|
||||
|
||||
use crate::window_toggle;
|
||||
|
||||
pub const SHOW_LOG_WINDOW: &str = "show_log_window";
|
||||
pub const CLEAR_STORAGE: &str = "clear_storage";
|
||||
|
||||
pub trait AddDefaultSubmenus {
|
||||
#[allow(dead_code)]
|
||||
fn add_default_app_submenus(self) -> Self;
|
||||
}
|
||||
|
||||
impl AddDefaultSubmenus for Menu {
|
||||
fn add_default_app_submenus(self) -> Self {
|
||||
let submenu = Submenu::new(
|
||||
"Help",
|
||||
Menu::new().add_item(CustomMenuItem::new(SHOW_LOG_WINDOW, "Show logs")),
|
||||
);
|
||||
self.add_submenu(submenu)
|
||||
}
|
||||
}
|
||||
|
||||
pub const TRAY_MENU_QUIT: &str = "quit";
|
||||
pub const TRAY_MENU_SHOW_HIDE: &str = "show-hide";
|
||||
pub const TRAY_MENU_CONNECTION: &str = "connection";
|
||||
|
||||
pub(crate) fn create_tray_menu() -> SystemTray {
|
||||
let quit = CustomMenuItem::new(TRAY_MENU_QUIT, "Quit");
|
||||
let hide = CustomMenuItem::new(TRAY_MENU_SHOW_HIDE, "Hide");
|
||||
let connection = CustomMenuItem::new(TRAY_MENU_CONNECTION, "Connect");
|
||||
let tray_menu = SystemTrayMenu::new()
|
||||
.add_item(hide)
|
||||
.add_item(connection)
|
||||
.add_native_item(SystemTrayMenuItem::Separator)
|
||||
.add_item(quit);
|
||||
|
||||
SystemTray::new().with_menu(tray_menu)
|
||||
}
|
||||
|
||||
pub(crate) fn tray_menu_event_handler(app: &AppHandle<Wry>, event: SystemTrayEvent) {
|
||||
match event {
|
||||
SystemTrayEvent::LeftClick { position, size, .. } => {
|
||||
println!("Event {position:?} {size:?}");
|
||||
}
|
||||
SystemTrayEvent::MenuItemClick { id, .. } => {
|
||||
println!("Event {id}");
|
||||
match id.as_str() {
|
||||
TRAY_MENU_SHOW_HIDE => {
|
||||
window_toggle(app);
|
||||
}
|
||||
TRAY_MENU_QUIT => {
|
||||
// TODO: add disconnecting first
|
||||
app.exit(0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::state::GatewayConnectivity;
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct ConnectResult {
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct DisconnectResult {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(rename_all = "lowercase"))]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ConnectionStatusKind {
|
||||
Disconnected,
|
||||
Disconnecting,
|
||||
Connected,
|
||||
Connecting,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(rename_all = "lowercase"))]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum GatewayConnectionStatusKind {
|
||||
Good,
|
||||
Bad,
|
||||
VeryBad,
|
||||
}
|
||||
|
||||
impl From<GatewayConnectivity> for GatewayConnectionStatusKind {
|
||||
fn from(conn: GatewayConnectivity) -> Self {
|
||||
match conn {
|
||||
GatewayConnectivity::Good => GatewayConnectionStatusKind::Good,
|
||||
GatewayConnectivity::Bad { .. } => GatewayConnectionStatusKind::Bad,
|
||||
GatewayConnectivity::VeryBad { .. } => GatewayConnectionStatusKind::VeryBad,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[cfg_attr(test, ts(rename_all = "lowercase"))]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum ConnectivityTestResult {
|
||||
NotAvailable,
|
||||
Success,
|
||||
Fail,
|
||||
}
|
||||
|
||||
impl fmt::Display for ConnectionStatusKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
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))]
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct AppEventConnectionStatusChangedPayload {
|
||||
pub status: ConnectionStatusKind,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DirectoryService {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
pub items: Vec<DirectoryServiceProvider>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct HarbourMasterService {
|
||||
pub service_provider_client_id: String,
|
||||
pub ip_address: String,
|
||||
pub last_successful_ping_utc: String,
|
||||
pub last_updated_utc: String,
|
||||
pub routing_score: f32,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DirectoryServiceProvider {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
/// Address of the network requester in the form "<client_id>.<client_enc>@<gateway_id>"
|
||||
/// e.g. DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct PagedResult<T> {
|
||||
pub page: u32,
|
||||
pub size: u32,
|
||||
pub total: i32,
|
||||
pub items: Vec<T>,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Gateway {
|
||||
pub identity: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for Gateway {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Gateway({})", self.identity)
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
use std::env;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use sentry::ClientInitGuard;
|
||||
|
||||
use crate::constants::{SENTRY_DSN_JS, SENTRY_DSN_RUST};
|
||||
|
||||
pub fn init() -> Result<ClientInitGuard> {
|
||||
// if these env vars were set at compile time, use their value
|
||||
if let Some(v) = option_env!("SENTRY_DSN_RUST") {
|
||||
env::set_var(SENTRY_DSN_RUST, v);
|
||||
}
|
||||
if let Some(v) = option_env!("SENTRY_DSN_JS") {
|
||||
env::set_var(SENTRY_DSN_JS, v);
|
||||
}
|
||||
|
||||
let dsn = env::var(SENTRY_DSN_RUST).context(format!("{} env var not set", SENTRY_DSN_RUST))?;
|
||||
println!("using DSN {dsn}");
|
||||
let guard = sentry::init((
|
||||
dsn,
|
||||
sentry::ClientOptions {
|
||||
release: sentry::release_name!(),
|
||||
sample_rate: 1.0, // TODO lower this in prod
|
||||
traces_sample_rate: 1.0,
|
||||
..Default::default() // TODO add data scrubbing
|
||||
// see https://docs.sentry.io/platforms/rust/data-management/sensitive-data/
|
||||
},
|
||||
));
|
||||
|
||||
sentry::configure_scope(|scope| {
|
||||
scope.set_user(Some(sentry::User {
|
||||
id: Some("nym".into()),
|
||||
..Default::default()
|
||||
}));
|
||||
});
|
||||
|
||||
Ok(guard)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
use crate::config::{PrivacyLevel, SelectedGateway, SelectedSp};
|
||||
use crate::error::Result;
|
||||
use crate::{config::UserData, state::State};
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_env(variable: String) -> Option<String> {
|
||||
let var = env::var(&variable).ok();
|
||||
log::trace!("get_env {variable} {:?}", var);
|
||||
|
||||
var
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_user_data(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<UserData> {
|
||||
let guard = state.read().await;
|
||||
Ok(guard.get_user_data().clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_monitoring(
|
||||
enabled: bool,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<()> {
|
||||
let mut guard = state.write().await;
|
||||
guard.set_monitoring(enabled)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_privacy_level(
|
||||
privacy_level: PrivacyLevel,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<()> {
|
||||
let mut guard = state.write().await;
|
||||
guard.set_privacy_level(privacy_level)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_selected_gateway(
|
||||
gateway: Option<SelectedGateway>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<()> {
|
||||
let mut guard = state.write().await;
|
||||
guard.set_user_selected_gateway(gateway)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_selected_sp(
|
||||
service_provider: Option<SelectedSp>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<()> {
|
||||
let mut guard = state.write().await;
|
||||
guard.set_user_selected_sp(service_provider)
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::default_config_filepath;
|
||||
use crate::{error::Result, state::State};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[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(default_config_filepath(id).display().to_string())
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
use crate::{
|
||||
error::{BackendError, Result},
|
||||
models::ConnectResult,
|
||||
tasks, State,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_connecting(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
window: tauri::Window<tauri::Wry>,
|
||||
) -> Result<ConnectResult> {
|
||||
log::trace!("Start connecting");
|
||||
|
||||
let (msg_receiver, exit_status_receiver) = {
|
||||
let mut state_w = state.write().await;
|
||||
state_w.start_connecting(&window).await?
|
||||
};
|
||||
|
||||
// Setup task for checking status
|
||||
let state = state.inner().clone();
|
||||
tasks::start_disconnect_listener(state.clone(), window.clone(), exit_status_receiver);
|
||||
tasks::start_status_listener(state, window.clone(), msg_receiver);
|
||||
|
||||
Ok(ConnectResult {
|
||||
address: "PLACEHOLDER".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_service_provider(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<String> {
|
||||
let guard = state.read().await;
|
||||
guard
|
||||
.get_service_provider()
|
||||
.clone()
|
||||
.ok_or(BackendError::NoServiceProviderSet)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_service_provider(
|
||||
service_provider: Option<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> 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> {
|
||||
let guard = state.read().await;
|
||||
guard
|
||||
.get_gateway()
|
||||
.clone()
|
||||
.ok_or(BackendError::NoGatewaySet)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_gateway(
|
||||
gateway: Option<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<()> {
|
||||
log::trace!("Setting gateway: {:?}", &gateway);
|
||||
let mut guard = state.write().await;
|
||||
guard.set_gateway(gateway);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
use crate::error::Result;
|
||||
use crate::models::ConnectResult;
|
||||
use crate::State;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn start_disconnecting(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
window: tauri::Window<tauri::Wry>,
|
||||
) -> Result<ConnectResult> {
|
||||
log::trace!("Start disconnecting");
|
||||
let mut guard = state.write().await;
|
||||
|
||||
guard.start_disconnecting(&window).await?;
|
||||
|
||||
Ok(ConnectResult {
|
||||
address: "PLACEHOLDER".to_string(),
|
||||
})
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
use crate::operations::directory::WELLKNOWN_DIR;
|
||||
use nym_config::defaults::var_names::NETWORK_NAME;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
static HEALTH_CHECK_URL: &str = "connect/healthcheck.json";
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct ConnectionSuccess {
|
||||
status: String,
|
||||
}
|
||||
|
||||
pub async fn run_health_check() -> bool {
|
||||
log::info!("Running network health check");
|
||||
let network_name = std::env::var(NETWORK_NAME).expect("network name not set");
|
||||
let url = format!("{}/{}/{}", WELLKNOWN_DIR, network_name, HEALTH_CHECK_URL);
|
||||
match crate::operations::http::socks5_get::<_, ConnectionSuccess>(url).await {
|
||||
Ok(res) if res.status == "ok" => {
|
||||
log::info!("✅✅✅ Healthcheck success!");
|
||||
true
|
||||
}
|
||||
Ok(res) => {
|
||||
log::error!("⛔⛔⛔ Healthcheck failed with status: {}", res.status);
|
||||
false
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("⛔⛔⛔ Healthcheck failed: {err}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod connect;
|
||||
pub mod disconnect;
|
||||
pub mod health_check;
|
||||
pub mod status;
|
||||
@@ -1,44 +0,0 @@
|
||||
use crate::error::Result;
|
||||
use crate::tasks;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::models::{ConnectionStatusKind, ConnectivityTestResult, GatewayConnectionStatusKind};
|
||||
use crate::state::State;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_connection_status(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<ConnectionStatusKind> {
|
||||
let state = state.read().await;
|
||||
Ok(state.get_status())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_gateway_connection_status(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<GatewayConnectionStatusKind> {
|
||||
let mut state_w = state.write().await;
|
||||
let gateway_connectivity = state_w.get_gateway_connectivity();
|
||||
Ok(gateway_connectivity.into())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_connection_health_check_status(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<ConnectivityTestResult> {
|
||||
let state = state.read().await;
|
||||
Ok(state.get_connectivity_test_result())
|
||||
}
|
||||
|
||||
// Start a connection check task. This should return with an event within one minute, and update
|
||||
// the state.
|
||||
// Trying to run multiple concurrent connection checks probably works but is not supported.
|
||||
#[tauri::command]
|
||||
pub fn start_connection_health_check_task(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
window: tauri::Window<tauri::Wry>,
|
||||
) {
|
||||
tasks::start_connection_check(state.inner().clone(), window);
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
use crate::{
|
||||
error::{BackendError, Result},
|
||||
models::Gateway,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use nym_api_requests::models::GatewayBondAnnotated;
|
||||
use nym_bin_common::version_checker::is_minor_version_compatible;
|
||||
use nym_config::defaults::var_names::NYM_API;
|
||||
use nym_contracts_common::types::Percent;
|
||||
use nym_topology::gateway;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::nym_api::Client as ApiClient;
|
||||
use std::str::FromStr;
|
||||
use url::Url;
|
||||
|
||||
// Only use gateways with a performnnce score above this
|
||||
const GATEWAY_PERFORMANCE_SCORE_THRESHOLD: u64 = 90;
|
||||
|
||||
async fn fetch_all_gateways() -> Result<Vec<GatewayBondAnnotated>> {
|
||||
let api_client = ApiClient::new(Url::from_str(&std::env::var(NYM_API)?)?, None);
|
||||
let gateways = api_client.get_gateways_detailed().await?;
|
||||
if gateways.is_empty() {
|
||||
Err(BackendError::NoGatewaysFoundInDirectory)
|
||||
} else {
|
||||
Ok(gateways)
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_only_compatible_gateways() -> Result<Vec<GatewayBondAnnotated>> {
|
||||
let gateways = fetch_all_gateways().await?;
|
||||
let our_version = env!("CARGO_PKG_VERSION");
|
||||
log::debug!(
|
||||
"Our version that we use to filter compatible gateways: {}",
|
||||
our_version
|
||||
);
|
||||
let gateways: Vec<_> = gateways
|
||||
.into_iter()
|
||||
.filter(|g| is_minor_version_compatible(&g.gateway_bond.gateway.version, our_version))
|
||||
.collect();
|
||||
if gateways.is_empty() {
|
||||
Err(BackendError::NoVersionCompatibleGatewaysFound(
|
||||
our_version.to_string(),
|
||||
))
|
||||
} else {
|
||||
Ok(gateways)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_out_low_performance_gateways(
|
||||
gateways: Vec<GatewayBondAnnotated>,
|
||||
) -> Result<Vec<GatewayBondAnnotated>> {
|
||||
let mut filtered_gateways: Vec<_> = gateways
|
||||
.iter()
|
||||
.filter(|g| {
|
||||
g.node_performance.most_recent
|
||||
> Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap()
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// Sometimes the most_recent is zero for all gateways (bug in nym-api?)
|
||||
if filtered_gateways.is_empty() {
|
||||
log::warn!(
|
||||
"No gateways with recent performance score above threshold found! Using \
|
||||
last hour performance scores instead as fallback"
|
||||
);
|
||||
filtered_gateways = gateways
|
||||
.into_iter()
|
||||
.filter(|g| {
|
||||
g.node_performance.last_hour
|
||||
> Percent::from_percentage_value(GATEWAY_PERFORMANCE_SCORE_THRESHOLD).unwrap()
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
if filtered_gateways.is_empty() {
|
||||
log::error!("No gateways found! (with high enough performance score)");
|
||||
Err(BackendError::NoGatewaysWithAcceptablePerformanceFound)
|
||||
} else {
|
||||
Ok(filtered_gateways)
|
||||
}
|
||||
}
|
||||
|
||||
async fn select_gateway_by_latency(gateways: Vec<GatewayBondAnnotated>) -> Result<gateway::Node> {
|
||||
let gateways_as_nodes: Vec<gateway::Node> = gateways
|
||||
.into_iter()
|
||||
.filter_map(|g| g.gateway_bond.try_into().ok())
|
||||
.collect();
|
||||
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
let selected_gateway = nym_client_core::init::helpers::choose_gateway_by_latency(
|
||||
&mut rng,
|
||||
&gateways_as_nodes,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
Ok(selected_gateway)
|
||||
}
|
||||
|
||||
// Get all gateways satisfying the performance threshold.
|
||||
#[tauri::command]
|
||||
pub async fn get_gateways() -> Result<Vec<Gateway>> {
|
||||
log::trace!("Fetching gateways");
|
||||
let all_gateways = fetch_only_compatible_gateways().await?;
|
||||
log::debug!("Received {} gateways", all_gateways.len());
|
||||
log::trace!("Received: {:#?}", all_gateways);
|
||||
|
||||
let gateways_filtered = filter_out_low_performance_gateways(all_gateways)?
|
||||
.into_iter()
|
||||
.map(|g| Gateway {
|
||||
identity: g.identity().clone(),
|
||||
})
|
||||
.collect_vec();
|
||||
log::debug!(
|
||||
"After filtering out low-performance gateways: {}",
|
||||
gateways_filtered.len()
|
||||
);
|
||||
log::trace!(
|
||||
"Filtered: [\n\t{}\n]",
|
||||
gateways_filtered.iter().join(",\n\t")
|
||||
);
|
||||
|
||||
Ok(gateways_filtered)
|
||||
}
|
||||
|
||||
// From a given list of gateways, select the one with low latency.
|
||||
#[tauri::command]
|
||||
pub async fn select_gateway_with_low_latency_from_list(gateways: Vec<Gateway>) -> Result<Gateway> {
|
||||
log::debug!("Selecting a gateway with low latency");
|
||||
let gateways = gateways.into_iter().map(|g| g.identity).collect_vec();
|
||||
let all_gateways = fetch_only_compatible_gateways().await?;
|
||||
let gateways_union_set: Vec<GatewayBondAnnotated> = all_gateways
|
||||
.into_iter()
|
||||
.filter(|g| gateways.contains(g.identity()))
|
||||
.collect();
|
||||
let gateways_filtered = filter_out_low_performance_gateways(gateways_union_set)?;
|
||||
let selected_gateway = select_gateway_by_latency(gateways_filtered).await?;
|
||||
log::debug!("Selected gateway: {}", selected_gateway);
|
||||
Ok(Gateway {
|
||||
identity: selected_gateway.identity().to_base58_string(),
|
||||
})
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod gateways;
|
||||
pub mod services;
|
||||
|
||||
pub(crate) static WELLKNOWN_DIR: &str = "https://nymtech.net/.wellknown";
|
||||
@@ -1,140 +0,0 @@
|
||||
use crate::{
|
||||
config::PrivacyLevel,
|
||||
error::{BackendError, Result},
|
||||
models::{DirectoryService, DirectoryServiceProvider, HarbourMasterService, PagedResult},
|
||||
state::State,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use nym_config::defaults::var_names::NETWORK_NAME;
|
||||
use std::sync::Arc;
|
||||
use tap::TapFallible;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use super::WELLKNOWN_DIR;
|
||||
|
||||
static SERVICE_PROVIDER_URL_PATH: &str = "connect/service-providers.json";
|
||||
|
||||
// List of network-requesters running with medium toggle enabled, for testing
|
||||
static SERVICE_PROVIDER_MEDIUM_URL_PATH: &str = "connect/service-providers-medium.json";
|
||||
|
||||
// Harbour master is used to periodically keep track of which network-requesters are online
|
||||
static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100";
|
||||
|
||||
// We only consider network requesters with a routing score above this threshold
|
||||
const SERVICE_ROUTING_SCORE_THRESHOLD: f32 = 0.9;
|
||||
|
||||
// Fetch all the services from the directory (currently hardcoded, but in the future it could be a
|
||||
// contract).
|
||||
async fn fetch_services(privacy_level: &PrivacyLevel) -> Result<Vec<DirectoryService>> {
|
||||
let services_url = match privacy_level {
|
||||
PrivacyLevel::Medium => SERVICE_PROVIDER_MEDIUM_URL_PATH,
|
||||
_ => SERVICE_PROVIDER_URL_PATH,
|
||||
};
|
||||
|
||||
let network_name = std::env::var(NETWORK_NAME)?;
|
||||
let url = format!("{}/{}/{}", WELLKNOWN_DIR, network_name, services_url);
|
||||
let services_res = reqwest::get(url)
|
||||
.await?
|
||||
.json::<Vec<DirectoryService>>()
|
||||
.await?;
|
||||
if services_res.is_empty() {
|
||||
log::error!("No services found in directory!");
|
||||
Err(BackendError::NoServicesFoundInDirectory)
|
||||
} else {
|
||||
Ok(services_res)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all the active services from harbour master
|
||||
async fn query_active_services() -> Result<PagedResult<HarbourMasterService>> {
|
||||
let active_services = reqwest::get(HARBOUR_MASTER_URL)
|
||||
.await?
|
||||
.json::<PagedResult<HarbourMasterService>>()
|
||||
.await?;
|
||||
if active_services.items.is_empty() {
|
||||
log::error!("No active services found!");
|
||||
Err(BackendError::NoActiveServicesFound)
|
||||
} else {
|
||||
Ok(active_services)
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_out_inactive_services(
|
||||
all_services: &[DirectoryServiceProvider],
|
||||
active_services: PagedResult<HarbourMasterService>,
|
||||
) -> Result<Vec<DirectoryServiceProvider>> {
|
||||
let services: Vec<_> = all_services
|
||||
.iter()
|
||||
.filter(|sp| {
|
||||
active_services.items.iter().any(|active| {
|
||||
active.service_provider_client_id == sp.address
|
||||
&& active.routing_score > SERVICE_ROUTING_SCORE_THRESHOLD
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
if services.is_empty() {
|
||||
Err(BackendError::NoServicesFoundInDirectory)
|
||||
} else {
|
||||
Ok(services)
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_services(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Vec<DirectoryServiceProvider>> {
|
||||
let guard = state.read().await;
|
||||
let privacy_level = guard.get_user_data().privacy_level.unwrap_or_default();
|
||||
|
||||
log::trace!("Fetching services");
|
||||
let all_services_with_category = fetch_services(&privacy_level).await?;
|
||||
|
||||
// Flatten all services into a single vector (get rid of categories)
|
||||
// We currently don't care about categories, but we might in the future...
|
||||
let all_services = all_services_with_category
|
||||
.into_iter()
|
||||
.flat_map(|sp| sp.items)
|
||||
.collect_vec();
|
||||
log::debug!("Received {} services", all_services.len());
|
||||
log::trace!("Received: {:#?}", all_services);
|
||||
|
||||
// Early return if we're running with medium toggle enabled
|
||||
if let PrivacyLevel::Medium = privacy_level {
|
||||
return Ok(all_services);
|
||||
}
|
||||
|
||||
// If there is a failure getting the active services, just return all of them
|
||||
// TODO: get paged
|
||||
log::trace!("Fetching active services");
|
||||
let Ok(active_services) = query_active_services().await else {
|
||||
log::warn!("Using all services instead as fallback");
|
||||
return Ok(all_services);
|
||||
};
|
||||
log::debug!(
|
||||
"Received {} active services from harbourmaster",
|
||||
active_services.items.len()
|
||||
);
|
||||
log::trace!("Active: {:#?}", active_services);
|
||||
|
||||
// From the list of all services, filter out the ones that are inactive
|
||||
log::trace!("Filter out inactive and low performance");
|
||||
let filtered_services = filter_out_inactive_services(&all_services, active_services);
|
||||
|
||||
// If there is a failure filtering out inactive services, just return all of them
|
||||
filtered_services
|
||||
.tap_ok(|services| {
|
||||
log::debug!(
|
||||
"After filtering out inactive and low performance: {}",
|
||||
services.len()
|
||||
);
|
||||
log::trace!("After filtering: {:#?}", services);
|
||||
})
|
||||
.or_else(|_| {
|
||||
// If for some reason harbourmaster is done, we want things to still sort of work.
|
||||
log::warn!(
|
||||
"After filtering, no active services found! Using all services instead as fallback"
|
||||
);
|
||||
Ok(all_services)
|
||||
})
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
use std::path::Path;
|
||||
use std::{fs, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
error::{BackendError, Result},
|
||||
state::State,
|
||||
};
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_client_core::client::key_manager::ClientKeys;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
|
||||
pub async fn get_identity_key(
|
||||
state: &tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Arc<identity::KeyPair>> {
|
||||
let config = {
|
||||
let state = state.read().await;
|
||||
state.load_config()?
|
||||
};
|
||||
|
||||
let paths = config.storage_paths.common_paths.keys;
|
||||
|
||||
// wtf, why are we loading EVERYTHING to just get identity key??
|
||||
let key_store = OnDiskKeys::from(paths);
|
||||
let key_manager =
|
||||
ClientKeys::load_keys(&key_store)
|
||||
.await
|
||||
.map_err(|err| BackendError::UnableToLoadKeys {
|
||||
source: Box::new(err),
|
||||
})?;
|
||||
let identity_keypair = key_manager.identity_keypair();
|
||||
|
||||
Ok(identity_keypair)
|
||||
}
|
||||
|
||||
fn key_filename<P: AsRef<Path>>(path: P) -> Result<String> {
|
||||
path.as_ref()
|
||||
.file_name()
|
||||
.ok_or(BackendError::CouldNotGetFilename)?
|
||||
.to_os_string()
|
||||
.into_string()
|
||||
.map_err(|_| BackendError::CouldNotGetFilename)
|
||||
}
|
||||
|
||||
/// 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_config()?
|
||||
};
|
||||
|
||||
let key_paths = config.storage_paths.common_paths.keys;
|
||||
|
||||
// Get key paths
|
||||
let ack_key_file = key_paths.ack_key();
|
||||
|
||||
let pub_id_key_file = key_paths.public_identity_key();
|
||||
let priv_id_key_file = key_paths.private_identity_key();
|
||||
|
||||
let pub_enc_key_file = key_paths.public_encryption_key();
|
||||
let priv_enc_key_file = key_paths.private_encryption_key();
|
||||
|
||||
// Read file contents
|
||||
let ack_key = fs::read_to_string(ack_key_file)?;
|
||||
|
||||
let pub_id_key = fs::read_to_string(pub_id_key_file)?;
|
||||
let priv_id_key = fs::read_to_string(priv_id_key_file)?;
|
||||
|
||||
let pub_enc_key = fs::read_to_string(pub_enc_key_file)?;
|
||||
let priv_enc_key = fs::read_to_string(priv_enc_key_file)?;
|
||||
|
||||
let ack_key_file = key_filename(&key_paths.ack_key_file)?;
|
||||
let pub_id_key_file = key_filename(&key_paths.public_identity_key_file)?;
|
||||
let priv_id_key_file = key_filename(&key_paths.private_identity_key_file)?;
|
||||
let pub_enc_key_file = key_filename(&key_paths.public_encryption_key_file)?;
|
||||
let priv_enc_key_file = key_filename(&key_paths.private_encryption_key_file)?;
|
||||
|
||||
// Format and return as json
|
||||
let json = serde_json::json!({
|
||||
ack_key_file: ack_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,270 +0,0 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ApiClientError {
|
||||
#[error("{source}")]
|
||||
Reqwest {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
SerdeJson {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
},
|
||||
#[error("{0}")]
|
||||
Status(String),
|
||||
}
|
||||
|
||||
const API_BASE_URL: &str = "https://growth-api.nymtech.net";
|
||||
|
||||
// For development mode, switch to this
|
||||
// const API_BASE_URL: &str = "http://localhost:8000";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GrowthApiClient {
|
||||
base_url: String,
|
||||
}
|
||||
|
||||
impl GrowthApiClient {
|
||||
pub fn new(resource_base: &str) -> Self {
|
||||
let base_url = std::env::var("API_BASE_URL").unwrap_or_else(|_| API_BASE_URL.to_string());
|
||||
GrowthApiClient {
|
||||
base_url: format!("{base_url}{resource_base}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn registrations() -> Registrations {
|
||||
Registrations::new(GrowthApiClient::new("/v1/tne"))
|
||||
}
|
||||
|
||||
pub fn daily_draws() -> DailyDraws {
|
||||
DailyDraws::new(GrowthApiClient::new("/v1/tne/daily_draw"))
|
||||
}
|
||||
|
||||
pub(crate) async fn get<T: DeserializeOwned>(&self, url: &str) -> Result<T, ApiClientError> {
|
||||
log::info!(">>> GET {}", url);
|
||||
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?;
|
||||
let client = reqwest::Client::builder()
|
||||
.proxy(proxy)
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()?;
|
||||
|
||||
match client.get(format!("{}{}", self.base_url, url)).send().await {
|
||||
Ok(res) => {
|
||||
if res.status().is_client_error() || res.status().is_server_error() {
|
||||
log::error!("<<< {}", res.status());
|
||||
return Err(ApiClientError::Status(res.status().to_string()));
|
||||
}
|
||||
match res.text().await {
|
||||
Ok(response_body) => {
|
||||
log::info!("<<< {}", response_body);
|
||||
match serde_json::from_str(&response_body) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => {
|
||||
log::error!("<<< JSON parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Request error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Response parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: use the method in `operations::http` instead
|
||||
pub(crate) async fn post<REQ: Serialize + ?Sized, RESP: DeserializeOwned>(
|
||||
&self,
|
||||
url: &str,
|
||||
body: &REQ,
|
||||
) -> Result<RESP, ApiClientError> {
|
||||
log::info!(">>> POST {}", url);
|
||||
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?;
|
||||
let client = reqwest::Client::builder()
|
||||
.proxy(proxy)
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()?;
|
||||
|
||||
match client
|
||||
.post(format!("{}{}", self.base_url, url))
|
||||
.json(body)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(res) => {
|
||||
if res.status().is_client_error() || res.status().is_server_error() {
|
||||
log::error!("<<< {}", res.status());
|
||||
return Err(ApiClientError::Status(res.status().to_string()));
|
||||
}
|
||||
match res.text().await {
|
||||
Ok(response_body) => {
|
||||
log::info!("<<< {}", response_body);
|
||||
match serde_json::from_str(&response_body) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => {
|
||||
log::error!("<<< JSON parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Request error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("<<< Response parsing error: {}", e);
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ClientIdPartial {
|
||||
pub client_id: String,
|
||||
pub client_id_signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Registration {
|
||||
pub id: String,
|
||||
pub client_id: String,
|
||||
pub timestamp: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Ping {
|
||||
pub client_id: String,
|
||||
pub timestamp: String,
|
||||
}
|
||||
|
||||
pub struct Registrations {
|
||||
client: GrowthApiClient,
|
||||
}
|
||||
|
||||
impl Registrations {
|
||||
pub fn new(client: GrowthApiClient) -> Self {
|
||||
Registrations { client }
|
||||
}
|
||||
|
||||
pub async fn register(
|
||||
&self,
|
||||
registration: &ClientIdPartial,
|
||||
) -> Result<Registration, ApiClientError> {
|
||||
self.client.post("/register", ®istration).await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn unregister(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
|
||||
self.client.post("/unregister", ®istration).await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn status(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
|
||||
self.client.post("/status", ®istration).await
|
||||
}
|
||||
|
||||
pub async fn ping(&self, registration: &ClientIdPartial) -> Result<(), ApiClientError> {
|
||||
self.client.post("/ping", ®istration).await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn health(&self) -> Result<(), ApiClientError> {
|
||||
self.client.get("/health").await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct DrawEntryPartial {
|
||||
pub draw_id: String,
|
||||
pub client_id: String,
|
||||
pub client_id_signature: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct DrawEntry {
|
||||
pub id: String,
|
||||
pub draw_id: String,
|
||||
pub timestamp: String,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct DrawWithWordOfTheDay {
|
||||
pub id: String,
|
||||
pub start_utc: String,
|
||||
pub end_utc: String,
|
||||
pub word_of_the_day: Option<String>,
|
||||
pub last_modified: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ClaimPartial {
|
||||
pub draw_id: String,
|
||||
pub registration_id: String,
|
||||
pub client_id: String,
|
||||
pub client_id_signature: String,
|
||||
pub wallet_address: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Winner {
|
||||
pub id: String,
|
||||
pub client_id: String,
|
||||
pub draw_id: String,
|
||||
pub timestamp: String,
|
||||
pub winner_reg_id: String,
|
||||
pub winner_wallet_address: Option<String>,
|
||||
pub winner_claim_timestamp: Option<String>,
|
||||
}
|
||||
|
||||
pub struct DailyDraws {
|
||||
client: GrowthApiClient,
|
||||
}
|
||||
|
||||
impl DailyDraws {
|
||||
pub fn new(client: GrowthApiClient) -> Self {
|
||||
DailyDraws { client }
|
||||
}
|
||||
|
||||
pub async fn current(&self) -> Result<DrawWithWordOfTheDay, ApiClientError> {
|
||||
self.client.get("/current").await
|
||||
}
|
||||
|
||||
pub async fn next(&self) -> Result<DrawWithWordOfTheDay, ApiClientError> {
|
||||
self.client.get("/next").await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn status(&self, draw_id: &str) -> Result<DrawWithWordOfTheDay, ApiClientError> {
|
||||
self.client.get(format!("/status/{draw_id}").as_str()).await
|
||||
}
|
||||
|
||||
pub async fn enter(&self, entry: &DrawEntryPartial) -> Result<DrawEntry, ApiClientError> {
|
||||
self.client.post("/enter", entry).await
|
||||
}
|
||||
|
||||
pub async fn entries(
|
||||
&self,
|
||||
client_id: &ClientIdPartial,
|
||||
) -> Result<Vec<DrawEntry>, ApiClientError> {
|
||||
self.client.post("/entries", client_id).await
|
||||
}
|
||||
|
||||
pub async fn claim(&self, claim: &ClaimPartial) -> Result<Winner, ApiClientError> {
|
||||
self.client.post("/claim", claim).await
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use rust_embed::RustEmbed;
|
||||
extern crate yaml_rust;
|
||||
use yaml_rust::YamlLoader;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../src/components/Growth/content/"]
|
||||
#[include = "*.yaml"]
|
||||
#[exclude = "*.mdx"]
|
||||
struct Asset;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NotificationContent {
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Notifications {
|
||||
pub you_are_in_draw: NotificationContent,
|
||||
pub take_part: NotificationContent,
|
||||
}
|
||||
|
||||
pub struct Content {}
|
||||
|
||||
const RESOURCE_ERROR: &str = "❌ RESOURCE ERROR";
|
||||
|
||||
fn get_as_string_or_error_message(value: &yaml_rust::Yaml) -> String {
|
||||
value.as_str().unwrap_or(RESOURCE_ERROR).to_string()
|
||||
}
|
||||
|
||||
impl Content {
|
||||
pub fn get_notifications() -> Notifications {
|
||||
let content = Asset::get("en.yaml").unwrap();
|
||||
let s = std::str::from_utf8(content.data.as_ref()).unwrap();
|
||||
let content = YamlLoader::load_from_str(s).unwrap();
|
||||
let content = &content[0];
|
||||
|
||||
Notifications {
|
||||
you_are_in_draw: NotificationContent {
|
||||
title: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["youAreInDraw"]["title"],
|
||||
),
|
||||
body: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["youAreInDraw"]["body"],
|
||||
),
|
||||
},
|
||||
take_part: NotificationContent {
|
||||
title: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["takePart"]["title"],
|
||||
),
|
||||
body: get_as_string_or_error_message(
|
||||
&content["testAndEarn"]["notifications"]["takePart"]["body"],
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod api_client;
|
||||
pub mod assets;
|
||||
pub mod test_and_earn;
|
||||
@@ -1,156 +0,0 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::operations::export::get_identity_key;
|
||||
use crate::operations::growth::api_client::{
|
||||
ClaimPartial, ClientIdPartial, DrawEntry, DrawEntryPartial, DrawWithWordOfTheDay,
|
||||
GrowthApiClient, Registration, Winner,
|
||||
};
|
||||
use crate::State;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tauri::api::notification::Notification;
|
||||
use tauri::Manager;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
async fn get_client_id(
|
||||
state: &tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<ClientIdPartial, BackendError> {
|
||||
let keypair = get_identity_key(state).await?;
|
||||
let client_id = keypair.public_key().to_base58_string();
|
||||
let client_id_signature = keypair
|
||||
.private_key()
|
||||
.sign(client_id.as_bytes())
|
||||
.to_base58_string();
|
||||
Ok(ClientIdPartial {
|
||||
client_id,
|
||||
client_id_signature,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_get_client_id(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<ClientIdPartial, BackendError> {
|
||||
get_client_id(&state).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_take_part(
|
||||
app_handle: tauri::AppHandle,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Registration, BackendError> {
|
||||
let notifications = super::assets::Content::get_notifications();
|
||||
|
||||
let client_id = get_client_id(&state).await?;
|
||||
let registration = GrowthApiClient::registrations()
|
||||
.register(&client_id)
|
||||
.await?;
|
||||
|
||||
log::info!("<<< Test&Earn: registration details: {:?}", registration);
|
||||
|
||||
if let Err(e) = Notification::new(&app_handle.config().tauri.bundle.identifier)
|
||||
.title(notifications.take_part.title)
|
||||
.body(notifications.take_part.body)
|
||||
.show()
|
||||
{
|
||||
log::error!("Could not show notification. Error = {:?}", e);
|
||||
}
|
||||
|
||||
Ok(registration)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Draws {
|
||||
pub current: Option<DrawWithWordOfTheDay>,
|
||||
pub next: Option<DrawWithWordOfTheDay>,
|
||||
pub draws: Vec<DrawEntry>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_get_draws(client_details: ClientIdPartial) -> Result<Draws, BackendError> {
|
||||
let draws_api = GrowthApiClient::daily_draws();
|
||||
|
||||
let current = draws_api.current().await.ok();
|
||||
let next = draws_api.next().await.ok();
|
||||
let draws = draws_api.entries(&client_details).await?;
|
||||
|
||||
Ok(Draws {
|
||||
current,
|
||||
next,
|
||||
draws,
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_enter_draw(
|
||||
client_details: ClientIdPartial,
|
||||
draw_id: String,
|
||||
) -> Result<DrawEntry, BackendError> {
|
||||
Ok(GrowthApiClient::daily_draws()
|
||||
.enter(&DrawEntryPartial {
|
||||
draw_id,
|
||||
client_id: client_details.client_id,
|
||||
client_id_signature: client_details.client_id_signature,
|
||||
})
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_submit_wallet_address(
|
||||
client_details: ClientIdPartial,
|
||||
draw_id: String,
|
||||
wallet_address: String,
|
||||
registration_id: String,
|
||||
) -> Result<Winner, BackendError> {
|
||||
Ok(GrowthApiClient::daily_draws()
|
||||
.claim(&ClaimPartial {
|
||||
draw_id,
|
||||
client_id: client_details.client_id,
|
||||
client_id_signature: client_details.client_id_signature,
|
||||
wallet_address,
|
||||
registration_id,
|
||||
})
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_ping(client_details: ClientIdPartial) -> Result<(), BackendError> {
|
||||
log::info!("Test&Earn is sending a ping...");
|
||||
Ok(GrowthApiClient::registrations()
|
||||
.ping(&client_details)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn growth_tne_toggle_window(
|
||||
app_handle: tauri::AppHandle,
|
||||
window_title: Option<String>,
|
||||
) -> Result<(), BackendError> {
|
||||
if let Some(window) = app_handle.windows().get("growth") {
|
||||
log::info!("Closing growth window...");
|
||||
if let Err(e) = window.close() {
|
||||
log::error!("Unable to close growth window: {:?}", e);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log::info!("Creating growth window...");
|
||||
match tauri::WindowBuilder::new(
|
||||
&app_handle,
|
||||
"growth",
|
||||
tauri::WindowUrl::App("growth.html".into()),
|
||||
)
|
||||
.title(window_title.unwrap_or_else(|| "NymConnect Test&Earn".to_string()))
|
||||
.build()
|
||||
{
|
||||
Ok(window) => {
|
||||
if let Err(e) = window.set_focus() {
|
||||
log::error!("Unable to focus growth window: {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Unable to create growth window: {:?}", e);
|
||||
Err(BackendError::NewWindowError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
use crate::error::BackendError;
|
||||
use tauri::Manager;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn help_log_toggle_window(app_handle: tauri::AppHandle) -> Result<(), BackendError> {
|
||||
if let Some(current_log_window) = app_handle.windows().get("log") {
|
||||
log::info!("Closing log window...");
|
||||
if let Err(e) = current_log_window.close() {
|
||||
log::error!("Unable to close log window: {:?}", e);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log::info!("Creating log window...");
|
||||
match tauri::WindowBuilder::new(&app_handle, "log", tauri::WindowUrl::App("log.html".into()))
|
||||
.title("Nym Connect Logs")
|
||||
.build()
|
||||
{
|
||||
Ok(window) => {
|
||||
if let Err(e) = window.set_focus() {
|
||||
log::error!("Unable to focus log window: {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Unable to create log window: {:?}", e);
|
||||
Err(BackendError::NewWindowError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod log;
|
||||
pub mod storage;
|
||||
@@ -1,20 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{error::BackendError, state::State};
|
||||
use tauri::Manager;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn help_clear_storage(app_handle: tauri::AppHandle) -> Result<(), BackendError> {
|
||||
log::info!("Clearing user data");
|
||||
|
||||
let state = app_handle.try_state::<Arc<RwLock<State>>>();
|
||||
if let Some(s) = state {
|
||||
let mut guard = s.blocking_write();
|
||||
guard.clear_user_data().ok();
|
||||
} else {
|
||||
log::warn!("fail to retrieve the state, user data has not been cleared");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
use crate::error::{BackendError, Result};
|
||||
use serde::de::DeserializeOwned;
|
||||
use tap::TapFallible;
|
||||
|
||||
pub async fn socks5_get<U, T>(url: U) -> Result<T>
|
||||
where
|
||||
U: reqwest::IntoUrl + std::fmt::Display,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
log::info!(">>> GET {url}");
|
||||
let proxy = reqwest::Proxy::all("socks5h://127.0.0.1:1080")?;
|
||||
let client = reqwest::Client::builder()
|
||||
.proxy(proxy)
|
||||
.timeout(std::time::Duration::from_secs(20))
|
||||
.build()?;
|
||||
|
||||
let resp = client.get(url).send().await.tap_err(|err| {
|
||||
log::error!("<<< Request send error: {err}");
|
||||
})?;
|
||||
|
||||
if resp.status().is_client_error() || resp.status().is_server_error() {
|
||||
log::error!("<<< {}", resp.status());
|
||||
return Err(BackendError::RequestFail {
|
||||
url: resp.url().clone(),
|
||||
status_code: resp.status(),
|
||||
});
|
||||
}
|
||||
|
||||
let response_body = resp.text().await.tap_err(|err| {
|
||||
log::error!("<<< Request error: {err}");
|
||||
})?;
|
||||
log::info!("<<< {response_body}");
|
||||
|
||||
Ok(serde_json::from_str(&response_body).tap_err(|err| {
|
||||
log::error!("<<< JSON parsing error: {err}");
|
||||
})?)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
pub mod common;
|
||||
pub mod config;
|
||||
pub mod connection;
|
||||
pub mod directory;
|
||||
pub mod export;
|
||||
pub mod growth;
|
||||
pub mod help;
|
||||
pub mod http;
|
||||
pub mod window;
|
||||
@@ -1,7 +0,0 @@
|
||||
use crate::window::window_hide;
|
||||
use tauri::{AppHandle, Wry};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn hide_window(app: AppHandle<Wry>) {
|
||||
window_hide(&app);
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
use futures::SinkExt;
|
||||
use log::error;
|
||||
use nym_client_core::error::ClientCoreStatusMessage;
|
||||
use nym_socks5_client_core::{Socks5ControlMessage, Socks5ControlMessageSender};
|
||||
use std::time::Duration;
|
||||
use tap::TapFallible;
|
||||
use tauri::Manager;
|
||||
use tokio::time::Instant;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::config::PrivacyLevel;
|
||||
use crate::config::SelectedGateway;
|
||||
use crate::config::SelectedSp;
|
||||
use crate::config::UserData;
|
||||
use crate::{
|
||||
config::{self, socks5_config_id_appended_with},
|
||||
error::{BackendError, Result},
|
||||
models::{
|
||||
AppEventConnectionStatusChangedPayload, ConnectionStatusKind, ConnectivityTestResult,
|
||||
APP_EVENT_CONNECTION_STATUS_CHANGED,
|
||||
},
|
||||
tasks::{self, ExitStatusReceiver},
|
||||
};
|
||||
|
||||
// The client will emit messages if the connection to the gateway is poor (or the gateway can't
|
||||
// keep up with the messages we are sending). If no messages about this has been received for a
|
||||
// certain duration then we assume it's all good.
|
||||
const GATEWAY_CONNECTIVITY_TIMEOUT_SECS: u64 = 20;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum GatewayConnectivity {
|
||||
Good,
|
||||
Bad { when: Instant },
|
||||
VeryBad { when: Instant },
|
||||
}
|
||||
|
||||
impl TryFrom<&ClientCoreStatusMessage> for GatewayConnectivity {
|
||||
type Error = BackendError;
|
||||
|
||||
fn try_from(value: &ClientCoreStatusMessage) -> Result<Self, Self::Error> {
|
||||
let conn = match value {
|
||||
ClientCoreStatusMessage::GatewayIsSlow => GatewayConnectivity::Bad {
|
||||
when: Instant::now(),
|
||||
},
|
||||
ClientCoreStatusMessage::GatewayIsVerySlow => GatewayConnectivity::VeryBad {
|
||||
when: Instant::now(),
|
||||
},
|
||||
};
|
||||
Ok(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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>,
|
||||
|
||||
/// The client will periodically report connectivity to the gateway it's connected to. Unless
|
||||
/// we get a status message from the client we assume it's good.
|
||||
gateway_connectivity: GatewayConnectivity,
|
||||
|
||||
/// The latest end-to-end connectivity test result. The first test is initiated on connection
|
||||
/// established. Additional tests can be triggered.
|
||||
connectivity_test_result: ConnectivityTestResult,
|
||||
|
||||
/// User data saved on disk, like user settings
|
||||
user_data: UserData,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(user_data: UserData) -> Self {
|
||||
State {
|
||||
status: ConnectionStatusKind::Disconnected,
|
||||
service_provider: None,
|
||||
gateway: None,
|
||||
socks5_client_sender: None,
|
||||
gateway_connectivity: GatewayConnectivity::Good,
|
||||
connectivity_test_result: ConnectivityTestResult::NotAvailable,
|
||||
user_data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_gateway_connectivity(&mut self) -> GatewayConnectivity {
|
||||
self.gateway_connectivity = match self.gateway_connectivity {
|
||||
c @ (GatewayConnectivity::Bad { when } | GatewayConnectivity::VeryBad { when }) => {
|
||||
if Instant::now() > when + Duration::from_secs(GATEWAY_CONNECTIVITY_TIMEOUT_SECS) {
|
||||
GatewayConnectivity::Good
|
||||
} else {
|
||||
c
|
||||
}
|
||||
}
|
||||
current => current,
|
||||
};
|
||||
self.gateway_connectivity
|
||||
}
|
||||
|
||||
pub fn get_user_data(&self) -> &UserData {
|
||||
&self.user_data
|
||||
}
|
||||
|
||||
pub fn clear_user_data(&mut self) -> Result<()> {
|
||||
self.user_data.clear().map_err(|e| {
|
||||
error!("Failed to clear user data {e}");
|
||||
BackendError::UserDataWriteError
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_monitoring(&mut self, enabled: bool) -> Result<()> {
|
||||
self.user_data.monitoring = Some(enabled);
|
||||
self.user_data.write().map_err(|e| {
|
||||
error!("Failed to write user data to disk {e}");
|
||||
BackendError::UserDataWriteError
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_privacy_level(&mut self, privacy_level: PrivacyLevel) -> Result<()> {
|
||||
self.user_data.privacy_level = Some(privacy_level);
|
||||
self.user_data.write().map_err(|e| {
|
||||
error!("Failed to write user data to disk {e}");
|
||||
BackendError::UserDataWriteError
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_user_selected_gateway(&mut self, gateway: Option<SelectedGateway>) -> Result<()> {
|
||||
self.user_data.selected_gateway = gateway;
|
||||
self.user_data.write().map_err(|e| {
|
||||
error!("Failed to write user data to disk {e}");
|
||||
BackendError::UserDataWriteError
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_user_selected_sp(&mut self, service_provider: Option<SelectedSp>) -> Result<()> {
|
||||
self.user_data.selected_sp = service_provider;
|
||||
self.user_data.write().map_err(|e| {
|
||||
error!("Failed to write user data to disk {e}");
|
||||
BackendError::UserDataWriteError
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_gateway_connectivity(&mut self, gateway_connectivity: GatewayConnectivity) {
|
||||
self.gateway_connectivity = gateway_connectivity
|
||||
}
|
||||
|
||||
pub fn get_connectivity_test_result(&self) -> ConnectivityTestResult {
|
||||
self.connectivity_test_result
|
||||
}
|
||||
|
||||
pub fn set_connectivity_test_result(
|
||||
&mut self,
|
||||
connectivity_test_result: ConnectivityTestResult,
|
||||
) {
|
||||
self.connectivity_test_result = connectivity_test_result;
|
||||
}
|
||||
|
||||
pub fn get_status(&self) -> ConnectionStatusKind {
|
||||
self.status.clone()
|
||||
}
|
||||
|
||||
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 },
|
||||
)
|
||||
.tap_err(|err| log::warn!("{err}"))
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn get_service_provider(&self) -> &Option<String> {
|
||||
&self.service_provider
|
||||
}
|
||||
|
||||
pub fn set_service_provider(&mut self, provider: Option<String>) {
|
||||
self.service_provider = provider;
|
||||
}
|
||||
|
||||
pub fn get_gateway(&self) -> &Option<String> {
|
||||
&self.gateway
|
||||
}
|
||||
|
||||
pub fn set_gateway(&mut self, gateway: Option<String>) {
|
||||
self.gateway = gateway;
|
||||
}
|
||||
|
||||
/// The effective config id is the static config id appended with the id of the gateway
|
||||
pub fn get_config_id(&self) -> Result<String> {
|
||||
let gateway_id = self
|
||||
.get_gateway()
|
||||
.as_ref()
|
||||
.ok_or(BackendError::CouldNotGetIdWithoutGateway)?;
|
||||
Ok(socks5_config_id_appended_with(gateway_id))
|
||||
}
|
||||
|
||||
pub fn load_config(&self) -> Result<Config> {
|
||||
let id = self.get_config_id()?;
|
||||
let config = Config::read_from_default_path(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<(nym_task::StatusReceiver, ExitStatusReceiver)> {
|
||||
self.set_state(ConnectionStatusKind::Connecting, window);
|
||||
|
||||
// Setup configuration by writing to file
|
||||
if let Err(err) = self.init_config().await {
|
||||
log::error!("Failed to initialize: {err}");
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Kick off the main task and get the channel for controlling it
|
||||
self.start_nym_socks5_client().await
|
||||
}
|
||||
|
||||
/// 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<(nym_task::StatusReceiver, ExitStatusReceiver)> {
|
||||
let id = self.get_config_id()?;
|
||||
let privacy_level = self.user_data.privacy_level.unwrap_or_default();
|
||||
let (control_tx, msg_rx, exit_status_rx, used_gateway) =
|
||||
tasks::start_nym_socks5_client(&id, &privacy_level).await?;
|
||||
self.socks5_client_sender = Some(control_tx);
|
||||
self.gateway = Some(used_gateway.gateway_id().to_base58_string());
|
||||
Ok((msg_rx, exit_status_rx))
|
||||
}
|
||||
|
||||
/// Once the SOCKS5 client is operational, the status listener would call this
|
||||
pub fn mark_connected(&mut self, window: &tauri::Window<tauri::Wry>) {
|
||||
log::trace!("state::mark_connected");
|
||||
self.set_state(ConnectionStatusKind::Connected, window);
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
log::trace!("state::start_disconnecting");
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
use nym_client_core::client::base_client::storage::gateways_storage::{
|
||||
GatewayDetails, GatewaysDetailsStore,
|
||||
};
|
||||
use nym_client_core::{
|
||||
client::base_client::storage::{MixnetClientStorage, OnDiskPersistent},
|
||||
config::{GroupBy, TopologyStructure},
|
||||
error::ClientCoreStatusMessage,
|
||||
};
|
||||
use nym_socks5_client_core::{NymClient as Socks5NymClient, Socks5ControlMessageSender};
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_task::manager::TaskStatus;
|
||||
use std::sync::Arc;
|
||||
use tap::TapFallible;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
config::{Config, PrivacyLevel},
|
||||
error::Result,
|
||||
events::{self, emit_event, emit_status_event},
|
||||
models::{ConnectionStatusKind, ConnectivityTestResult},
|
||||
operations,
|
||||
state::State,
|
||||
};
|
||||
|
||||
pub type ExitStatusReceiver = futures::channel::oneshot::Receiver<Socks5ExitStatusMessage>;
|
||||
|
||||
/// Status messages sent by the SOCKS5 client task to the main tauri task.
|
||||
#[derive(Debug)]
|
||||
pub enum Socks5ExitStatusMessage {
|
||||
/// The SOCKS5 task successfully stopped
|
||||
Stopped,
|
||||
/// The SOCKS5 task failed to start
|
||||
Failed(Box<dyn std::error::Error + Send>),
|
||||
}
|
||||
|
||||
fn override_config_from_env(config: &mut Config, privacy_level: &PrivacyLevel) {
|
||||
// Disable both the loop cover traffic that runs in the background as well as the Poisson
|
||||
// process that injects cover traffic into the traffic stream.
|
||||
if let PrivacyLevel::Medium = privacy_level {
|
||||
log::info!("Running in Medium privacy level");
|
||||
log::warn!("Disabling cover traffic");
|
||||
config.core.base.set_no_cover_traffic_with_keepalive();
|
||||
|
||||
log::warn!("Enabling mixed size packets");
|
||||
config
|
||||
.core
|
||||
.base
|
||||
.set_secondary_packet_size(Some(PacketSize::ExtendedPacket16));
|
||||
|
||||
log::warn!("Disabling per-hop delay");
|
||||
config.core.base.set_no_per_hop_delays();
|
||||
|
||||
// TODO: selectable in the UI
|
||||
let address = config
|
||||
.core
|
||||
.socks5
|
||||
.provider_mix_address
|
||||
.parse()
|
||||
.expect("failed to parse provider mix address");
|
||||
log::warn!("Using geo-aware mixnode selection based on the location of: {address}");
|
||||
config
|
||||
.core
|
||||
.base
|
||||
.set_topology_structure(TopologyStructure::GeoAware(GroupBy::NymAddress(address)));
|
||||
}
|
||||
}
|
||||
|
||||
/// The main SOCKS5 client task. It loads the configuration from file determined by the `id`.
|
||||
pub async fn start_nym_socks5_client(
|
||||
id: &str,
|
||||
privacy_level: &PrivacyLevel,
|
||||
) -> Result<(
|
||||
Socks5ControlMessageSender,
|
||||
nym_task::StatusReceiver,
|
||||
ExitStatusReceiver,
|
||||
GatewayDetails,
|
||||
)> {
|
||||
log::info!("Loading config from file: {id}");
|
||||
let mut config = Config::read_from_default_path(id)
|
||||
.tap_err(|_| log::warn!("Failed to load configuration file"))?;
|
||||
|
||||
override_config_from_env(&mut config, privacy_level);
|
||||
|
||||
log::trace!("Configuration used: {:#?}", config);
|
||||
|
||||
let storage =
|
||||
OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug)
|
||||
.await?;
|
||||
|
||||
let used_gateway = storage
|
||||
.gateway_details_store()
|
||||
.active_gateway()
|
||||
.await
|
||||
.expect("failed to load active gateway details")
|
||||
.registration
|
||||
.expect("no active gateway set")
|
||||
.details;
|
||||
|
||||
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 send status update messages from the background socks5 task to the frontend.
|
||||
let (socks5_status_tx, socks5_status_rx) = mpsc::channel(128);
|
||||
|
||||
// Channel to signal back to the main task when the socks5 client finishes, and why
|
||||
let (socks5_exit_tx, socks5_exit_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(|| {
|
||||
let result = tokio::runtime::Runtime::new()
|
||||
.expect("Failed to create runtime for SOCKS5 client")
|
||||
.block_on(async move {
|
||||
let socks5_client = Socks5NymClient::new(config.core, storage, None);
|
||||
|
||||
socks5_client
|
||||
.run_and_listen(socks5_ctrl_rx, socks5_status_tx)
|
||||
.await
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
log::error!("SOCKS5 proxy failed: {err}");
|
||||
socks5_exit_tx
|
||||
.send(Socks5ExitStatusMessage::Failed(err))
|
||||
.expect("Failed to send status message back to main task");
|
||||
return;
|
||||
}
|
||||
|
||||
log::info!("SOCKS5 task finished");
|
||||
socks5_exit_tx
|
||||
.send(Socks5ExitStatusMessage::Stopped)
|
||||
.expect("Failed to send status message back to main task");
|
||||
});
|
||||
|
||||
Ok((
|
||||
socks5_ctrl_tx,
|
||||
socks5_status_rx,
|
||||
socks5_exit_rx,
|
||||
used_gateway,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn start_connection_check(state: Arc<RwLock<State>>, window: tauri::Window<tauri::Wry>) {
|
||||
log::debug!("Starting connection check handler");
|
||||
tokio::spawn(async move {
|
||||
if state.read().await.get_status() != ConnectionStatusKind::Connected {
|
||||
log::error!("SOCKS5 connection status check failed: not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
log::info!("Running connection health check");
|
||||
if operations::connection::health_check::run_health_check().await {
|
||||
state
|
||||
.write()
|
||||
.await
|
||||
.set_connectivity_test_result(ConnectivityTestResult::Success);
|
||||
emit_event(
|
||||
"socks5-connection-success-event",
|
||||
"SOCKS5 success",
|
||||
"SOCKS5 connection health check successful",
|
||||
&window,
|
||||
);
|
||||
} else if state.read().await.get_status() == ConnectionStatusKind::Connected {
|
||||
state
|
||||
.write()
|
||||
.await
|
||||
.set_connectivity_test_result(ConnectivityTestResult::Fail);
|
||||
log::error!("SOCKS5 connection health check failed");
|
||||
emit_event(
|
||||
"socks5-connection-fail-event",
|
||||
"SOCKS5 error",
|
||||
"SOCKS5 connection health check failed",
|
||||
&window,
|
||||
);
|
||||
} else {
|
||||
log::debug!("SOCKS5 connection status check cancelled: not connected");
|
||||
}
|
||||
|
||||
log::debug!("Connection check handler exiting");
|
||||
});
|
||||
}
|
||||
|
||||
/// The status listener listens for non-exit status messages from the background socks5 proxy task.
|
||||
pub fn start_status_listener(
|
||||
state: Arc<RwLock<State>>,
|
||||
window: tauri::Window<tauri::Wry>,
|
||||
mut msg_receiver: nym_task::StatusReceiver,
|
||||
) {
|
||||
log::info!("Starting status listener");
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(msg) = msg_receiver.next().await {
|
||||
log::info!("SOCKS5 proxy sent status message: {}", msg);
|
||||
|
||||
if let Some(task_status) = msg.downcast_ref::<TaskStatus>() {
|
||||
events::handle_task_status(task_status, &state, &window).await;
|
||||
} else if let Some(client_status_message) =
|
||||
msg.downcast_ref::<ClientCoreStatusMessage>()
|
||||
{
|
||||
events::handle_client_status_message(client_status_message, &state, &window).await;
|
||||
} else {
|
||||
emit_status_event("socks5-status-event", &msg, &window);
|
||||
}
|
||||
}
|
||||
log::info!("Status listener exiting");
|
||||
});
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
exit_status_receiver: ExitStatusReceiver,
|
||||
) {
|
||||
log::trace!("Starting disconnect listener");
|
||||
tokio::spawn(async move {
|
||||
match exit_status_receiver.await {
|
||||
Ok(Socks5ExitStatusMessage::Stopped) => {
|
||||
log::info!("SOCKS5 task reported it has finished");
|
||||
emit_event(
|
||||
"socks5-event",
|
||||
"SOCKS5 finished",
|
||||
"SOCKS5 task reported it has finished",
|
||||
&window,
|
||||
);
|
||||
}
|
||||
Ok(Socks5ExitStatusMessage::Failed(err)) => {
|
||||
log::info!("SOCKS5 task reported error: {err}");
|
||||
emit_event(
|
||||
"socks5-event",
|
||||
"SOCKS5 error",
|
||||
&format!("SOCKS5 failed: {err}"),
|
||||
&window,
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
log::info!("SOCKS5 task appears to have stopped abruptly");
|
||||
emit_event(
|
||||
"socks5-event",
|
||||
"SOCKS5 error",
|
||||
"SOCKS5 stopped abruptly. Please try reconnecting.",
|
||||
&window,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut state_w = state.write().await;
|
||||
state_w.mark_disconnected(&window);
|
||||
});
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
use crate::menu::TRAY_MENU_SHOW_HIDE;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
pub(crate) fn window_hide(app: &AppHandle<tauri::Wry>) {
|
||||
let window = app.get_window("main").unwrap();
|
||||
let item_handle = app.tray_handle().get_item(TRAY_MENU_SHOW_HIDE);
|
||||
if window.is_visible().unwrap() {
|
||||
window.hide().unwrap();
|
||||
item_handle.set_title("Show").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn window_show(app: &AppHandle<tauri::Wry>) {
|
||||
let window = app.get_window("main").unwrap();
|
||||
let item_handle = app.tray_handle().get_item(TRAY_MENU_SHOW_HIDE);
|
||||
if !window.is_visible().unwrap() {
|
||||
window.show().unwrap();
|
||||
item_handle.set_title("Hide").unwrap();
|
||||
window.set_focus().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn window_toggle(app: &AppHandle<tauri::Wry>) {
|
||||
let window = app.get_window("main").unwrap();
|
||||
if window.is_visible().unwrap() {
|
||||
window_hide(app);
|
||||
} else {
|
||||
window_show(app);
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-connect",
|
||||
"version": "1.1.21"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
"devPath": "http://localhost:9000",
|
||||
"beforeDevCommand": "",
|
||||
"beforeBuildCommand": ""
|
||||
},
|
||||
"tauri": {
|
||||
"macOSPrivateApi": true,
|
||||
"systemTray": {
|
||||
"iconPath": "icons/tray_icon.png",
|
||||
"iconAsTemplate": true
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "net.nymtech.connect",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
"copyright": "Copyright © 2021-2023 Nym Technologies SA",
|
||||
"category": "Business",
|
||||
"shortDescription": "Browse the internet privately using the Nym Mixnet",
|
||||
"longDescription": "",
|
||||
"deb": {
|
||||
"depends": []
|
||||
},
|
||||
"macOS": {
|
||||
"frameworks": [],
|
||||
"minimumSystemVersion": "",
|
||||
"exceptionDomain": "",
|
||||
"signingIdentity": "Developer ID Application: Nym Technologies SA (VW5DZLFHM5)",
|
||||
"entitlements": null
|
||||
},
|
||||
"windows": {
|
||||
"certificateThumbprint": "6DB77B1F529A0804FE0E6843A3EB8A8CECFFD408",
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "http://timestamp.comodoca.com"
|
||||
}
|
||||
},
|
||||
"updater": {
|
||||
"active": true,
|
||||
"endpoints": [
|
||||
"https://nymtech.net/.wellknown/connect/updater.json"
|
||||
],
|
||||
"dialog": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENCNzQ2M0E5N0VFODE2NApSV1JrZ2U2WE9rYTNETTg1OTBKdE5uWUEra0hML2syOVUvQ2lxZmFZRzZ1T3NWbGM0eVRzUTVhVwo="
|
||||
},
|
||||
"allowlist": {
|
||||
"shell": {
|
||||
"open": true
|
||||
},
|
||||
"clipboard": {
|
||||
"writeText": true
|
||||
},
|
||||
"window": {
|
||||
"startDragging": true,
|
||||
"close": true,
|
||||
"minimize": true
|
||||
},
|
||||
"notification": {
|
||||
"all": true
|
||||
}
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "NymConnect",
|
||||
"width": 240,
|
||||
"height": 480,
|
||||
"resizable": false,
|
||||
"decorations": false,
|
||||
"transparent": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
|
||||
export const AppVersion = () => {
|
||||
const { appVersion } = useClientContext();
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', width: '100%', justifyContent: 'center' }}>
|
||||
<Box fontSize="small" sx={{ color: 'grey.600' }}>
|
||||
Version {appVersion}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,48 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
import { CustomTitleBar } from './CustomTitleBar';
|
||||
|
||||
export const AppWindowFrame: FCWithChildren = ({ children }) => {
|
||||
const location = useLocation();
|
||||
const { userDefinedGateway, setUserDefinedGateway, userDefinedSPAddress, setUserDefinedSPAddress } =
|
||||
useClientContext();
|
||||
|
||||
// defined functions to be used when moving away from pages
|
||||
const onBack = () => {
|
||||
switch (location.pathname) {
|
||||
case '/menu/settings/gateway':
|
||||
return () => {
|
||||
// when the user moves away from the settings page and the gateway is not valid
|
||||
// set isActive to false
|
||||
if (!userDefinedGateway?.address) {
|
||||
setUserDefinedGateway((current) => ({ ...current, isActive: false }));
|
||||
}
|
||||
};
|
||||
case '/menu/settings/service-provider':
|
||||
return () => {
|
||||
// when the user moves away from the settings page and the sp is not valid
|
||||
// set isActive to false
|
||||
if (!userDefinedSPAddress?.address) {
|
||||
setUserDefinedSPAddress((current) => ({ ...current, isActive: false }));
|
||||
}
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateRows: '40px 1fr',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<CustomTitleBar path={location.pathname} onBack={onBack()} />
|
||||
<Box style={{ padding: '16px' }}>{children}</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,168 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
|
||||
const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => {
|
||||
if (isError && hover) {
|
||||
return '#21D072';
|
||||
}
|
||||
if (isError) {
|
||||
return '#40475C';
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case 'disconnected':
|
||||
if (hover) {
|
||||
return '#FFFF33';
|
||||
}
|
||||
return '#FFE600';
|
||||
case 'connecting':
|
||||
case 'disconnecting':
|
||||
return '#FFE600';
|
||||
default:
|
||||
// connected
|
||||
if (hover) {
|
||||
return '#E43E3E';
|
||||
}
|
||||
return '#21D072';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = (status: ConnectionStatusKind, hover: boolean): string => {
|
||||
switch (status) {
|
||||
case 'disconnected':
|
||||
return 'Connect';
|
||||
case 'connecting':
|
||||
return 'Connecting';
|
||||
case 'disconnecting':
|
||||
return 'Connected';
|
||||
default:
|
||||
// connected
|
||||
if (hover) {
|
||||
return 'Disconnect';
|
||||
}
|
||||
return 'Connected';
|
||||
}
|
||||
};
|
||||
|
||||
export const ConnectionButton: FCWithChildren<{
|
||||
status: ConnectionStatusKind;
|
||||
disabled?: boolean;
|
||||
busy?: boolean;
|
||||
isError?: boolean;
|
||||
onClick?: (status: ConnectionStatusKind) => void;
|
||||
}> = ({ status, disabled, isError, onClick }) => {
|
||||
const [hover, setHover] = React.useState<boolean>(false);
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
if (disabled === true) {
|
||||
return;
|
||||
}
|
||||
if (onClick) {
|
||||
onClick(status);
|
||||
}
|
||||
}, [status, disabled]);
|
||||
|
||||
const statusText = getStatusText(status, hover);
|
||||
const statusTextColor = isError ? '#40475C' : '#FFF';
|
||||
const statusFillColor = getStatusFillColor(status, hover, Boolean(isError));
|
||||
|
||||
return (
|
||||
<svg
|
||||
opacity={disabled ? 0.75 : 1}
|
||||
width="200"
|
||||
height="200"
|
||||
viewBox="0 0 200 200"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g
|
||||
transform="translate(-27, -27)"
|
||||
onMouseEnter={() => !disabled && setHover(true)}
|
||||
onMouseLeave={() => !disabled && setHover(false)}
|
||||
>
|
||||
<g onClick={handleClick} style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}>
|
||||
<g filter="url(#filter0_f_639_9730)">
|
||||
<circle cx="131" cy="131" r="51" fill="url(#paint0_radial_639_9730)" />
|
||||
</g>
|
||||
<circle cx="131" cy="131" r="65" fill="#1C1B1F" />
|
||||
<g filter="url(#filter1_d_639_9730)">
|
||||
<circle cx="131" cy="131" r="64" stroke={statusFillColor} strokeWidth="2" />
|
||||
</g>
|
||||
<circle cx="131" cy="131" r="73.5" stroke={statusFillColor} strokeOpacity="0.5" />
|
||||
{status === 'connected' && hover ? (
|
||||
<path
|
||||
d="M120.217 119.833C120.217 117.838 121.838 116.217 123.833 116.217H128.5V114H123.833C120.613 114 118 116.613 118 119.833C118 123.053 120.613 125.667 123.833 125.667H128.5V123.45H123.833C121.838 123.45 120.217 121.828 120.217 119.833ZM127 121H136.333V118.667H127V121ZM139.5 114H134.833V116.217H139.505C141.5 116.217 143.117 117.838 143.117 119.833C143.117 121.828 141.495 123.45 139.5 123.45H134.833V125.667H139.5C142.72 125.667 145.333 123.053 145.333 119.833C145.333 116.613 142.72 114 139.5 114Z"
|
||||
fill="white"
|
||||
/>
|
||||
) : (
|
||||
<path
|
||||
d="M122.217 119.833C122.217 117.838 123.838 116.217 125.833 116.217H130.5V114H125.833C122.613 114 120 116.613 120 119.833C120 123.053 122.613 125.667 125.833 125.667H130.5V123.45H125.833C123.838 123.45 122.217 121.828 122.217 119.833ZM127 121H136.333V118.667H127V121ZM137.5 114H132.833V116.217H137.505C139.5 116.217 141.117 117.838 141.117 119.833C141.117 121.828 139.495 123.45 137.5 123.45H132.833V125.667H137.5C140.72 125.667 143.333 123.053 143.333 119.833C143.333 116.613 140.72 114 137.5 114Z"
|
||||
fill="white"
|
||||
/>
|
||||
)}
|
||||
<text
|
||||
className="button_text"
|
||||
x={131}
|
||||
y={146}
|
||||
fill={statusTextColor}
|
||||
dominantBaseline="middle"
|
||||
textAnchor="middle"
|
||||
fontWeight="700"
|
||||
fontSize="16px"
|
||||
>
|
||||
{statusText}
|
||||
</text>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_f_639_9730"
|
||||
x="0"
|
||||
y="0"
|
||||
width="262"
|
||||
height="262"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feGaussianBlur stdDeviation="25" result="effect1_foregroundBlur_639_9730" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d_639_9730"
|
||||
x="51"
|
||||
y="57"
|
||||
width="160"
|
||||
height="160"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="6" />
|
||||
<feGaussianBlur stdDeviation="7.5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_639_9730" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_639_9730" result="shape" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
id="paint0_radial_639_9730"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(131 131) rotate(90) scale(51)"
|
||||
>
|
||||
<stop stopColor="#1C1C1F" />
|
||||
<stop offset="1" stopColor={statusFillColor} />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
export interface ConnectionStatsItem {
|
||||
label: string;
|
||||
rateBytesPerSecond: number;
|
||||
totalBytes: number;
|
||||
}
|
||||
|
||||
const FONT_SIZE = '14px';
|
||||
|
||||
export const ConnectionStats: FCWithChildren<{
|
||||
stats: ConnectionStatsItem[];
|
||||
}> = ({ stats }) => (
|
||||
<Box color="rgba(255,255,255,0.6)" width="100%" display="flex" justifyContent="space-between">
|
||||
<div>
|
||||
{stats.map((stat) => (
|
||||
<Typography key={`stat-${stat.label}-label`} fontSize={FONT_SIZE}>
|
||||
{stat.label}
|
||||
</Typography>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
{stats.map((stat) => (
|
||||
<Typography key={`stat-${stat.label}-rate`} textAlign="center" fontSize={FONT_SIZE}>
|
||||
{formatRate(stat.rateBytesPerSecond)}
|
||||
</Typography>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
{stats.map((stat) => (
|
||||
<Typography key={`stat-${stat.label}-total`} textAlign="right" fontSize={FONT_SIZE}>
|
||||
{formatTotal(stat.totalBytes)}
|
||||
</Typography>
|
||||
))}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
|
||||
export function formatRate(bytesPerSecond: number): string {
|
||||
return `${prettyBytes(bytesPerSecond)}/s`;
|
||||
}
|
||||
|
||||
export function formatTotal(totalBytes: number): string {
|
||||
return prettyBytes(totalBytes);
|
||||
}
|
||||