Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00bd028d8b | |||
| 273dc41559 | |||
| 61bc74148f | |||
| e181a1cfb1 | |||
| fdbe3a1f6a | |||
| 5b15ed6f15 | |||
| 57703af642 | |||
| 9dd4c5d871 | |||
| 4813cf6c18 | |||
| 83b76c6b37 | |||
| 4120234155 | |||
| 42b444ddf8 | |||
| acd832d8e5 | |||
| 75a726ebbe | |||
| b9fbab6024 | |||
| 06ed8716a1 | |||
| beddd3dcee | |||
| 4d02dfb899 | |||
| 80d6cb5c12 | |||
| 7422ab69ba | |||
| 60c8185bea | |||
| 9d3c7c0be8 | |||
| d6048fae52 | |||
| ddb7b0e872 | |||
| 4995dde705 | |||
| 63bfe4246f | |||
| 4654b360e0 | |||
| 7c5c19986a | |||
| 46edca0bd4 | |||
| b03a1f922d | |||
| 9616c90433 | |||
| ea49f0a265 | |||
| 2470c8b9b5 | |||
| 7d001965ec | |||
| 11b1089d83 | |||
| 52699d7598 | |||
| 87443dd624 | |||
| e0f2fa6705 | |||
| 7949b07213 | |||
| bd20fd0b1f | |||
| 57d3d6fd0f | |||
| 06eff652dd | |||
| 246decac4a | |||
| a14ae298ae | |||
| 43188051d3 | |||
| 8aa15fa467 | |||
| f77b037ef7 | |||
| 75dbc5d790 | |||
| e6bcd706ff | |||
| 9a077a0928 | |||
| 11fd42e187 | |||
| 6e499e5996 | |||
| f98cc73a1f | |||
| db9bf4d3fa | |||
| b30628529d | |||
| 3be615f74f | |||
| a8ccd2ec17 | |||
| 2d71caf50a | |||
| 4d08d62fc2 | |||
| 40e5595d65 | |||
| 6780d58a98 | |||
| ccbf06f179 | |||
| e6ecf71cc3 | |||
| c66312ee96 | |||
| 808802aeb4 | |||
| fb38f24e5c | |||
| 65f148a5ad | |||
| 2bcdc5d11e | |||
| 5ecb03ffe9 | |||
| 8505989dad | |||
| ed5e865db7 | |||
| ea3194e0c3 | |||
| 190bff4ffe | |||
| c509555d15 | |||
| f0d66fdd88 | |||
| 14847d01b7 | |||
| f4711902bd | |||
| 32ad93c57e | |||
| c4a68dbbe6 | |||
| 1140503eba | |||
| d92d6877a4 | |||
| b9fed9f455 | |||
| 5e45f7d3a5 | |||
| 1133acd8bd | |||
| 08e6f3c4b7 | |||
| 0ed546b739 | |||
| b4f2233d2b | |||
| d8369eb4c9 | |||
| db3d379219 | |||
| a746738d48 | |||
| c67f0fb7f8 | |||
| 7c12a3422c | |||
| f047209baf | |||
| cc5a69d4f1 | |||
| 776d27a899 | |||
| 829dbb1695 | |||
| 52156e0c38 | |||
| 901275fd63 | |||
| 6c4f0bc2f4 | |||
| a0a73421d0 | |||
| 80c81fa3d7 | |||
| 27e6539e98 | |||
| a2d10d9956 | |||
| 142a2bb26b | |||
| 677d8c7fce | |||
| 3563ad67b2 | |||
| c11a4c23fa | |||
| ce676c2bb5 | |||
| a096f9d54e | |||
| d9b6823106 | |||
| 89bcb5649b | |||
| a74a99d81d | |||
| c09d3af92f | |||
| 76773c58d7 | |||
| fd90175e87 | |||
| 0eb859467e |
+1
-1
@@ -14,7 +14,7 @@ GEOIPUPDATE_LICENSE_KEY=xxx
|
||||
# List of space-separated database edition IDs. Edition IDs may
|
||||
# consist of letters, digits, and dashes. For example, GeoIP2-City
|
||||
# would download the GeoIP2 City database (GeoIP2-City).
|
||||
GEOIPUPDATE_EDITION_IDS=GeoLite2-Country
|
||||
GEOIPUPDATE_EDITION_IDS=GeoLite2-City
|
||||
# The number of hours between geoipupdate runs. If this is not set
|
||||
# or is set to 0, geoipupdate will run once and exit.
|
||||
GEOIPUPDATE_FREQUENCY=72
|
||||
|
||||
+46
-34
@@ -6,17 +6,35 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
### Added
|
||||
|
||||
- socks5: send status message for service ready, and network-requester error response
|
||||
- socks5: send status message for service ready, and network-requester error response in https://github.com/nymtech/nym/pull/2715
|
||||
|
||||
### Changed
|
||||
|
||||
- all-binaries: improved error logging ([#2686])
|
||||
- native client: bring shutdown logic up to the same level as socks5-client
|
||||
- nym-api, coconut-dkg contract: automatic, time-based dkg epoch state advancement ([#2670])
|
||||
- all-binaries: improved error logging in https://github.com/nymtech/nym/pull/2686
|
||||
- native client: bring shutdown logic up to the same level as socks5-client in https://github.com/nymtech/nym/pull/2695
|
||||
- nym-api, coconut-dkg contract: automatic, time-based dkg epoch state advancement in https://github.com/nymtech/nym/pull/2670
|
||||
- DKG resharing unit test by @neacsu in https://github.com/nymtech/nym/pull/2668
|
||||
- Renaming validator-api to nym-api by @futurechimp in https://github.com/nymtech/nym/pull/1863
|
||||
- Modify wasm specific make targets by @neacsu in https://github.com/nymtech/nym/pull/2693
|
||||
- client: create websocket handler builder by @octol in https://github.com/nymtech/nym/pull/2700
|
||||
- Outfox and Lion by @durch in https://github.com/nymtech/nym/pull/2730
|
||||
- Feature/multi surb transmission lanes by @jstuczyn in https://github.com/nymtech/nym/pull/2723
|
||||
|
||||
[#2686]: https://github.com/nymtech/nym/pull/2686
|
||||
[#2670]: https://github.com/nymtech/nym/pull/2670
|
||||
## [v1.1.4] (2022-12-20)
|
||||
|
||||
This release adds multiple Single Use Reply Blocks (SURBs) to allow arbitrarily-sized anonymized replies.
|
||||
At the moment this is turned off by default, but available for use by application developers.
|
||||
We will need to wait for network-requesters to upgrade to this new release, after which multi-SURB anonymization will become the default setting for the SOCKS proxy clients.
|
||||
|
||||
The release also include some additional work for distributed key generation in the Coconut signing authority nodes.
|
||||
|
||||
### Changed
|
||||
|
||||
- Feature/dkg contract threshold by @neacsu in https://github.com/nymtech/nym/pull/1885
|
||||
- Multi-surbs by @jstuczyn in https://github.com/nymtech/nym/pull/2667
|
||||
- Fix multi-surb backwards compatibility in pre 1.1.4 client config files by @jstuczyn in https://github.com/nymtech/nym/pull/2703
|
||||
- fix: ignore corrupted surb storage and instead create fresh one by @jstuczyn in https://github.com/nymtech/nym/pull/2711
|
||||
- socks5: rework waiting in inbound.rs by @octol in https://github.com/nymtech/nym/pull/1880
|
||||
|
||||
## [v1.1.3] (2022-12-13)
|
||||
|
||||
@@ -31,7 +49,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
- network-requester: fix bug where websocket connection disconnect resulted in success error code
|
||||
- clients: fix a few panics handling the gateway-client
|
||||
- mixnode, gateway, validator-api: Use mainnet values as defaults for URLs and mixnet contract ([#1884])
|
||||
- mixnode, gateway, validator-api: Use mainnet values as defaults for URLs and mixnet contract ([#1884])
|
||||
- socks5: fixed bug where connections sometimes where closed too early
|
||||
- clients: improve message logging when received message fails to get reconstructed ([#1803])
|
||||
|
||||
@@ -51,7 +69,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
- gateway: Renamed flag from `enabled/disabled_credentials_mode` to `only-coconut-credentials`
|
||||
- "Family" feature for node families + layers
|
||||
- Initial coconut functionality including credentials and distributed key generation
|
||||
- Initial coconut functionality including credentials and distributed key generation
|
||||
|
||||
## [v1.1.1](https://github.com/nymtech/nym/tree/v1.1.1) (2022-11-29)
|
||||
|
||||
@@ -87,7 +105,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#1786]: https://github.com/nymtech/nym/pull/1786
|
||||
[#1805]: https://github.com/nymtech/nym/pull/1805
|
||||
|
||||
|
||||
## [v1.1.0](https://github.com/nymtech/nym/tree/v1.1.0) (2022-11-09)
|
||||
|
||||
### Added
|
||||
@@ -114,7 +131,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- gateway-client: will attempt to read now as many as 8 websocket messages at once, assuming they're already available on the socket ([#1669])
|
||||
- moved `Percent` struct to `contracts-common`, change affects explorer-api
|
||||
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
|
||||
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
|
||||
- validator-api: changed error serialization on `inclusion_probability`, `stake-saturation` and `reward-estimation` endpoints to provide more accurate information ([#1681])
|
||||
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
|
||||
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
|
||||
- validator-api: changes to internal SQL schema due to the mixnet contract revamp ([#1472])
|
||||
@@ -142,7 +159,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#1724]: https://github.com/nymtech/nym/pull/1724
|
||||
[#1725]: https://github.com/nymtech/nym/pull/1725
|
||||
|
||||
|
||||
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
|
||||
|
||||
### Added
|
||||
@@ -196,8 +212,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- gateway, network-statistics: include gateway id in the sent statistical data ([#1478])
|
||||
- network explorer: tweak how active set probability is shown ([#1503])
|
||||
- validator-api: rewarder set update fails without panicking on possible nymd queries ([#1520])
|
||||
- network-requester, socks5 client (nym-connect): send and receive respectively a message error to be displayed about filter check failure ([#1576])
|
||||
|
||||
- network-requester, socks5 client (nym-connect): send and receive respectively a message error to be displayed about filter check failure ([#1576])
|
||||
|
||||
[#1249]: https://github.com/nymtech/nym/pull/1249
|
||||
[#1256]: https://github.com/nymtech/nym/pull/1256
|
||||
@@ -291,9 +306,9 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /clients/tauri-client [\#1163](https://github.com/nymtech/nym/pull/1163) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /clients/webassembly/js-example [\#1162](https://github.com/nymtech/nym/pull/1162) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /clients/native/examples/js-examples/websocket [\#1160](https://github.com/nymtech/nym/pull/1160) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /docker/typescript\_client/upload\_contract [\#1159](https://github.com/nymtech/nym/pull/1159) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /docker/typescript_client/upload_contract [\#1159](https://github.com/nymtech/nym/pull/1159) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Feature/vesting full [\#1158](https://github.com/nymtech/nym/pull/1158) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- get\_current\_epoch tauri [\#1156](https://github.com/nymtech/nym/pull/1156) ([durch](https://github.com/durch))
|
||||
- get_current_epoch tauri [\#1156](https://github.com/nymtech/nym/pull/1156) ([durch](https://github.com/durch))
|
||||
- Cleanup [\#1155](https://github.com/nymtech/nym/pull/1155) ([durch](https://github.com/durch))
|
||||
- Feature flag reward payments [\#1154](https://github.com/nymtech/nym/pull/1154) ([durch](https://github.com/durch))
|
||||
- Add Query endpoints for calculating rewards [\#1152](https://github.com/nymtech/nym/pull/1152) ([durch](https://github.com/durch))
|
||||
@@ -302,7 +317,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- wallet: use Urls rather than Strings for validator urls [\#1148](https://github.com/nymtech/nym/pull/1148) ([octol](https://github.com/octol))
|
||||
- Change accumulated reward to Option, migrate delegations [\#1147](https://github.com/nymtech/nym/pull/1147) ([durch](https://github.com/durch))
|
||||
- wallet: fetch validators url remotely if available [\#1146](https://github.com/nymtech/nym/pull/1146) ([octol](https://github.com/octol))
|
||||
- Fix delegated\_free calculation [\#1145](https://github.com/nymtech/nym/pull/1145) ([durch](https://github.com/durch))
|
||||
- Fix delegated_free calculation [\#1145](https://github.com/nymtech/nym/pull/1145) ([durch](https://github.com/durch))
|
||||
- Update Nym wallet dependencies to use `ts-packages` [\#1144](https://github.com/nymtech/nym/pull/1144) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- wallet: try validators one by one if available [\#1143](https://github.com/nymtech/nym/pull/1143) ([octol](https://github.com/octol))
|
||||
- Update Network Explorer Packages and add mix node identity key copy [\#1142](https://github.com/nymtech/nym/pull/1142) ([mmsinclair](https://github.com/mmsinclair))
|
||||
@@ -342,14 +357,13 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- feature/pedersen-commitments [\#1048](https://github.com/nymtech/nym/pull/1048) ([danielementary](https://github.com/danielementary))
|
||||
- Feature/reuse init owner [\#970](https://github.com/nymtech/nym/pull/970) ([neacsu](https://github.com/neacsu))
|
||||
|
||||
|
||||
## [v0.12.1](https://github.com/nymtech/nym/tree/v0.12.1) (2021-12-23)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.12.0...v0.12.1)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Add version check to binaries [\#967](https://github.com/nymtech/nym/issues/967)
|
||||
- Add version check to binaries [\#967](https://github.com/nymtech/nym/issues/967)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
@@ -379,7 +393,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Bugfix/remove mixnode bonding overwrite [\#917](https://github.com/nymtech/nym/pull/917) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Fixes crash condition in validator API when calculating last day uptime [\#909](https://github.com/nymtech/nym/pull/909) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bugfix/monitor initial values wait [\#907](https://github.com/nymtech/nym/pull/907) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bug fix: Network Explorer: Add freegeoip API key and split out tasks for country distributions [\#806](https://github.com/nymtech/nym/pull/806) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Bug fix: Network Explorer: Add freegeoip API key and split out tasks for country distributions [\#806](https://github.com/nymtech/nym/pull/806) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Explorer API: port test now split out address resolution and add units tests [\#755](https://github.com/nymtech/nym/pull/755) ([mmsinclair](https://github.com/mmsinclair))
|
||||
|
||||
**Closed issues:**
|
||||
@@ -394,7 +408,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- help!!! [\#712](https://github.com/nymtech/nym/issues/712)
|
||||
- UX feature request: show all delegated nodes in wallet [\#711](https://github.com/nymtech/nym/issues/711)
|
||||
- UX feature request: add current balance on wallet pages [\#710](https://github.com/nymtech/nym/issues/710)
|
||||
- got sign issue from bot [\#709](https://github.com/nymtech/nym/issues/709)
|
||||
- got sign issue from bot [\#709](https://github.com/nymtech/nym/issues/709)
|
||||
- As a wallet user, I would like to be able to log out of the wallet [\#706](https://github.com/nymtech/nym/issues/706)
|
||||
- As a wallet user, I would like to have a "receive" page where I can see my own wallet address [\#705](https://github.com/nymtech/nym/issues/705)
|
||||
- Update native client/socks client/mixnode/gateway `upgrade` command [\#689](https://github.com/nymtech/nym/issues/689)
|
||||
@@ -404,7 +418,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- nym-socks5-client crash after opening Keybase team "Browse all channels" [\#494](https://github.com/nymtech/nym/issues/494)
|
||||
- Mixed Content problem [\#400](https://github.com/nymtech/nym/issues/400)
|
||||
- Gateway disk quota [\#137](https://github.com/nymtech/nym/issues/137)
|
||||
- Simplify message encapsulation with regards to topology [\#127](https://github.com/nymtech/nym/issues/127)
|
||||
- Simplify message encapsulation with regards to topology [\#127](https://github.com/nymtech/nym/issues/127)
|
||||
- Create constants for cli argument names [\#115](https://github.com/nymtech/nym/issues/115)
|
||||
- Using Blake3 as a hash function [\#103](https://github.com/nymtech/nym/issues/103)
|
||||
- Validator should decide which layer a node is in [\#86](https://github.com/nymtech/nym/issues/86)
|
||||
@@ -460,10 +474,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Feature/pre cosmrs updates [\#935](https://github.com/nymtech/nym/pull/935) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/client on behalf [\#934](https://github.com/nymtech/nym/pull/934) ([neacsu](https://github.com/neacsu))
|
||||
- Webpack wallet prod configuration [\#933](https://github.com/nymtech/nym/pull/933) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Adding tx\_hash to wallet response [\#932](https://github.com/nymtech/nym/pull/932) ([futurechimp](https://github.com/futurechimp))
|
||||
- Adding tx_hash to wallet response [\#932](https://github.com/nymtech/nym/pull/932) ([futurechimp](https://github.com/futurechimp))
|
||||
- Release/1.0.0 pre1 [\#931](https://github.com/nymtech/nym/pull/931) ([durch](https://github.com/durch))
|
||||
- Feature/identity verification [\#930](https://github.com/nymtech/nym/pull/930) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Move cleaned up smart contracts to main code repo [\#929](https://github.com/nymtech/nym/pull/929) ([mfahampshire](https://github.com/mfahampshire))
|
||||
- Move cleaned up smart contracts to main code repo [\#929](https://github.com/nymtech/nym/pull/929) ([mfahampshire](https://github.com/mfahampshire))
|
||||
- Feature/mixnet contract further adjustments [\#928](https://github.com/nymtech/nym/pull/928) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- typo copy change for nodemap [\#926](https://github.com/nymtech/nym/pull/926) ([Aid19801](https://github.com/Aid19801))
|
||||
- Feature/UI enhancements for Desktop Wallet [\#925](https://github.com/nymtech/nym/pull/925) ([fmtabbara](https://github.com/fmtabbara))
|
||||
@@ -476,7 +490,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Feature/faucet page react [\#911](https://github.com/nymtech/nym/pull/911) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Feature/mixnet contract refactor [\#910](https://github.com/nymtech/nym/pull/910) ([futurechimp](https://github.com/futurechimp))
|
||||
- Update README.md [\#905](https://github.com/nymtech/nym/pull/905) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- BUG: Bond cell denom [\#904](https://github.com/nymtech/nym/pull/904) ([Aid19801](https://github.com/Aid19801))
|
||||
- BUG: Bond cell denom [\#904](https://github.com/nymtech/nym/pull/904) ([Aid19801](https://github.com/Aid19801))
|
||||
- Explorer UI tests missing data-testid [\#903](https://github.com/nymtech/nym/pull/903) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Fix up Nym-Wallet README.md [\#899](https://github.com/nymtech/nym/pull/899) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Feature/batch delegator rewarding [\#898](https://github.com/nymtech/nym/pull/898) ([jstuczyn](https://github.com/jstuczyn))
|
||||
@@ -494,7 +508,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Reverted gateway registration handshake to its 0.11.0 version [\#882](https://github.com/nymtech/nym/pull/882) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Network Explorer [\#881](https://github.com/nymtech/nym/pull/881) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Feature/rewarding interval updates [\#880](https://github.com/nymtech/nym/pull/880) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Put client\_address and id in the correct order [\#875](https://github.com/nymtech/nym/pull/875) ([neacsu](https://github.com/neacsu))
|
||||
- Put client_address and id in the correct order [\#875](https://github.com/nymtech/nym/pull/875) ([neacsu](https://github.com/neacsu))
|
||||
- remove gateway selection on delegation and undelegation pages [\#873](https://github.com/nymtech/nym/pull/873) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Set MSRV on all binaries to 1.56 [\#872](https://github.com/nymtech/nym/pull/872) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- add native window items \(copy/paste\) via tauri [\#871](https://github.com/nymtech/nym/pull/871) ([fmtabbara](https://github.com/fmtabbara))
|
||||
@@ -510,7 +524,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Overflow checks in release [\#846](https://github.com/nymtech/nym/pull/846) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- fix delegate success overflow [\#842](https://github.com/nymtech/nym/pull/842) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Feature NYM wallet webdriverio test [\#841](https://github.com/nymtech/nym/pull/841) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Update nym\_wallet.yml [\#840](https://github.com/nymtech/nym/pull/840) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Update nym_wallet.yml [\#840](https://github.com/nymtech/nym/pull/840) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Feature/vouchers [\#837](https://github.com/nymtech/nym/pull/837) ([aniampio](https://github.com/aniampio))
|
||||
- Apply readable ids to elements on Nym Wallet [\#836](https://github.com/nymtech/nym/pull/836) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Feature/removal of monitor good nodes [\#833](https://github.com/nymtech/nym/pull/833) ([jstuczyn](https://github.com/jstuczyn))
|
||||
@@ -534,8 +548,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Created getters for AccountData [\#787](https://github.com/nymtech/nym/pull/787) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/migrate hidden delegations [\#786](https://github.com/nymtech/nym/pull/786) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/persistent gateway storage [\#784](https://github.com/nymtech/nym/pull/784) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Replaced unwrap\_or\_else with unwrap\_or\_default [\#780](https://github.com/nymtech/nym/pull/780) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Add block\_height method to Delegation [\#778](https://github.com/nymtech/nym/pull/778) ([durch](https://github.com/durch))
|
||||
- Replaced unwrap_or_else with unwrap_or_default [\#780](https://github.com/nymtech/nym/pull/780) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Add block_height method to Delegation [\#778](https://github.com/nymtech/nym/pull/778) ([durch](https://github.com/durch))
|
||||
- Make fee helpers public [\#777](https://github.com/nymtech/nym/pull/777) ([durch](https://github.com/durch))
|
||||
- re-enable bonding [\#776](https://github.com/nymtech/nym/pull/776) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Explorer-api: add API resource to show the delegations for each mix node [\#774](https://github.com/nymtech/nym/pull/774) ([mmsinclair](https://github.com/mmsinclair))
|
||||
@@ -544,14 +558,14 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Adding deps for building the Tauri wallet under Ubuntu [\#770](https://github.com/nymtech/nym/pull/770) ([futurechimp](https://github.com/futurechimp))
|
||||
- remove alert [\#767](https://github.com/nymtech/nym/pull/767) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Feature/consumable bandwidth [\#766](https://github.com/nymtech/nym/pull/766) ([neacsu](https://github.com/neacsu))
|
||||
- Update coconut-rs and use hash\_to\_scalar from there [\#765](https://github.com/nymtech/nym/pull/765) ([neacsu](https://github.com/neacsu))
|
||||
- Update coconut-rs and use hash_to_scalar from there [\#765](https://github.com/nymtech/nym/pull/765) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/active sets [\#764](https://github.com/nymtech/nym/pull/764) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- add app alert banner [\#762](https://github.com/nymtech/nym/pull/762) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Updated cosmos-sdk [\#761](https://github.com/nymtech/nym/pull/761) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/bond blockstamp [\#760](https://github.com/nymtech/nym/pull/760) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/revert migration code [\#759](https://github.com/nymtech/nym/pull/759) ([neacsu](https://github.com/neacsu))
|
||||
- Bump next from 11.1.0 to 11.1.1 in /wallet-web [\#758](https://github.com/nymtech/nym/pull/758) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Add block\_height in the Delegation structure as well [\#757](https://github.com/nymtech/nym/pull/757) ([neacsu](https://github.com/neacsu))
|
||||
- Add block_height in the Delegation structure as well [\#757](https://github.com/nymtech/nym/pull/757) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/add blockstamp [\#756](https://github.com/nymtech/nym/pull/756) ([neacsu](https://github.com/neacsu))
|
||||
- NetworkMonitorBuilder - starting the monitor after rocket has launched [\#754](https://github.com/nymtech/nym/pull/754) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Enabled validators api argument [\#753](https://github.com/nymtech/nym/pull/753) ([jstuczyn](https://github.com/jstuczyn))
|
||||
@@ -563,7 +577,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Feature/more reliable uptime calculation [\#747](https://github.com/nymtech/nym/pull/747) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Update template toml key [\#746](https://github.com/nymtech/nym/pull/746) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/cred after handshake [\#745](https://github.com/nymtech/nym/pull/745) ([neacsu](https://github.com/neacsu))
|
||||
- Reinstate the POST method blind\_sign [\#744](https://github.com/nymtech/nym/pull/744) ([neacsu](https://github.com/neacsu))
|
||||
- Reinstate the POST method blind_sign [\#744](https://github.com/nymtech/nym/pull/744) ([neacsu](https://github.com/neacsu))
|
||||
- explorer-api: add pending field to port check response [\#742](https://github.com/nymtech/nym/pull/742) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Feature/use delegation rates [\#741](https://github.com/nymtech/nym/pull/741) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/copy to clipboard [\#740](https://github.com/nymtech/nym/pull/740) ([fmtabbara](https://github.com/fmtabbara))
|
||||
@@ -600,6 +614,4 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
- Hang coconut issuance off the validator-api [\#679](https://github.com/nymtech/nym/pull/679) ([durch](https://github.com/durch))
|
||||
- Update hmac and blake3 [\#673](https://github.com/nymtech/nym/pull/673) ([durch](https://github.com/durch))
|
||||
|
||||
|
||||
|
||||
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
||||
\* _This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)_
|
||||
|
||||
Generated
+170
-22
@@ -23,6 +23,16 @@ dependencies = [
|
||||
"generic-array 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
@@ -53,7 +63,7 @@ version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aead 0.4.3",
|
||||
"aes 0.7.5",
|
||||
"cipher 0.3.0",
|
||||
"ctr 0.8.0",
|
||||
@@ -81,6 +91,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
@@ -443,6 +459,12 @@ dependencies = [
|
||||
"rustc_version 0.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
@@ -474,6 +496,30 @@ dependencies = [
|
||||
"keystream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cipher 0.4.3",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20poly1305"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||
dependencies = [
|
||||
"aead 0.5.1",
|
||||
"chacha20",
|
||||
"cipher 0.4.3",
|
||||
"poly1305",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
@@ -488,6 +534,33 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
@@ -505,6 +578,7 @@ checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -586,7 +660,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"client-connections",
|
||||
@@ -980,9 +1054,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"cast",
|
||||
"cast 0.2.7",
|
||||
"clap 2.34.0",
|
||||
"criterion-plot",
|
||||
"criterion-plot 0.4.4",
|
||||
"csv",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
@@ -999,13 +1073,49 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"atty",
|
||||
"cast 0.3.0",
|
||||
"ciborium",
|
||||
"clap 3.2.8",
|
||||
"criterion-plot 0.5.0",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"cast 0.2.7",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
dependencies = [
|
||||
"cast 0.3.0",
|
||||
"itertools",
|
||||
]
|
||||
|
||||
@@ -1134,11 +1244,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.3"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array 0.14.5",
|
||||
"rand_core 0.6.3",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
@@ -1466,7 +1577,7 @@ dependencies = [
|
||||
"bitvec",
|
||||
"bls12_381 0.6.0",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"criterion 0.3.5",
|
||||
"ff 0.11.0",
|
||||
"group 0.11.0",
|
||||
"lazy_static",
|
||||
@@ -1698,9 +1809,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
|
||||
checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
@@ -3123,7 +3234,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@@ -3177,7 +3288,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"clap 3.2.8",
|
||||
"client-connections",
|
||||
@@ -3216,7 +3327,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3263,7 +3374,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
@@ -3305,7 +3416,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"clap 3.2.8",
|
||||
@@ -3337,7 +3448,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"log",
|
||||
@@ -3351,9 +3462,25 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-outfox"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"chacha20",
|
||||
"chacha20poly1305",
|
||||
"criterion 0.4.0",
|
||||
"curve25519-dalek",
|
||||
"fastrand",
|
||||
"getrandom 0.2.6",
|
||||
"rayon",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"clap 3.2.8",
|
||||
"client-connections",
|
||||
@@ -3420,7 +3547,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator-api"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3509,7 +3636,7 @@ dependencies = [
|
||||
"bincode",
|
||||
"bls12_381 0.6.0",
|
||||
"bs58",
|
||||
"criterion",
|
||||
"criterion 0.3.5",
|
||||
"digest 0.9.0",
|
||||
"dkg",
|
||||
"doc-comment",
|
||||
@@ -4021,6 +4148,17 @@ dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
"opaque-debug 0.3.0",
|
||||
"universal-hash 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.5.3"
|
||||
@@ -4030,7 +4168,7 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cpufeatures",
|
||||
"opaque-debug 0.3.0",
|
||||
"universal-hash",
|
||||
"universal-hash 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6388,6 +6526,16 @@ dependencies = [
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
@@ -6911,9 +7059,9 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.4.3"
|
||||
version = "1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
|
||||
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
@@ -76,6 +76,7 @@ members = [
|
||||
"service-providers/network-statistics",
|
||||
"nym-api",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-outfox",
|
||||
"tools/nym-cli",
|
||||
"tools/ts-rs-cli"
|
||||
]
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.1.3"
|
||||
version = "1.1.5"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.66"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ where
|
||||
|
||||
// channels responsible for dealing with reply-related fun
|
||||
let (reply_controller_sender, reply_controller_receiver) =
|
||||
reply_controller::new_control_channels();
|
||||
reply_controller::requests::new_control_channels();
|
||||
|
||||
let self_address = self.as_mix_recipient();
|
||||
|
||||
@@ -437,7 +437,7 @@ where
|
||||
input_receiver,
|
||||
sphinx_message_sender.clone(),
|
||||
reply_storage,
|
||||
reply_controller_sender,
|
||||
reply_controller_sender.clone(),
|
||||
reply_controller_receiver,
|
||||
shared_lane_queue_lengths.clone(),
|
||||
client_connection_rx,
|
||||
@@ -471,6 +471,7 @@ where
|
||||
received_buffer_request_sender,
|
||||
},
|
||||
},
|
||||
reply_controller_sender,
|
||||
task_manager,
|
||||
})
|
||||
}
|
||||
@@ -480,5 +481,8 @@ pub struct BaseClient {
|
||||
pub client_input: ClientInputStatus,
|
||||
pub client_output: ClientOutputStatus,
|
||||
|
||||
// it feels very wrong to put this channel here, but I can't think of any other way of passing it to the native client
|
||||
pub reply_controller_sender: ReplyControllerSender,
|
||||
|
||||
pub task_manager: TaskManager,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod non_wasm;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use non_wasm::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm::*;
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub use tokio::time::*;
|
||||
pub type IntervalStream = tokio_stream::wrappers::IntervalStream;
|
||||
|
||||
pub(crate) fn get_time_now() -> Instant {
|
||||
Instant::now()
|
||||
}
|
||||
|
||||
pub(crate) fn new_interval_stream(polling_rate: Duration) -> IntervalStream {
|
||||
tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(polling_rate))
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::time::Duration;
|
||||
use wasm_timer;
|
||||
|
||||
pub use wasm_timer::*;
|
||||
pub type IntervalStream = gloo_timers::future::IntervalStream;
|
||||
|
||||
pub(crate) fn get_time_now() -> Instant {
|
||||
wasm_timer::Instant::now()
|
||||
}
|
||||
|
||||
pub(crate) fn new_interval_stream(polling_rate: Duration) -> IntervalStream {
|
||||
gloo_timers::future::IntervalStream::new(polling_rate.as_millis() as u32)
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
pub mod base_client;
|
||||
pub mod cover_traffic_stream;
|
||||
pub(crate) mod helpers;
|
||||
pub mod inbound_messages;
|
||||
pub mod key_manager;
|
||||
pub mod mix_traffic;
|
||||
@@ -10,3 +11,4 @@ pub mod real_messages_control;
|
||||
pub mod received_buffer;
|
||||
pub mod replies;
|
||||
pub mod topology_control;
|
||||
pub(crate) mod transmission_buffer;
|
||||
|
||||
@@ -20,6 +20,7 @@ use nymsphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
|
||||
use nymsphinx::preparer::{MessagePreparer, PreparedFragment};
|
||||
use nymsphinx::Delay;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
@@ -278,7 +279,7 @@ where
|
||||
reply_surb: ReplySurb,
|
||||
amount: u32,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
debug!("requesting {amount} reply SURBs from {from:?}");
|
||||
debug!("requesting {amount} reply SURBs from {from}");
|
||||
|
||||
let surbs_request =
|
||||
ReplyMessage::new_surb_request_message(self.config.sender_address, amount);
|
||||
@@ -294,19 +295,11 @@ where
|
||||
)))
|
||||
}
|
||||
|
||||
// the only difference between this method and `try_send_reply_chunks` is that
|
||||
// here we are not creating acks as acks are already in memory waiting to get cleared.
|
||||
// we are only updating their existing delays
|
||||
pub(crate) async fn try_send_retransmission_reply_chunks(
|
||||
pub(crate) async fn send_retransmission_reply_chunks(
|
||||
&mut self,
|
||||
fragments: Vec<Fragment>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
prepared_fragments: Vec<PreparedFragment>,
|
||||
lane: TransmissionLane,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
let prepared_fragments = self
|
||||
.prepare_reply_chunks_for_sending(fragments.clone(), reply_surbs)
|
||||
.await?;
|
||||
|
||||
) {
|
||||
let mut real_messages = Vec::with_capacity(prepared_fragments.len());
|
||||
|
||||
for prepared in prepared_fragments {
|
||||
@@ -315,33 +308,58 @@ where
|
||||
}
|
||||
|
||||
self.forward_messages(real_messages, lane).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn try_send_reply_chunks(
|
||||
pub(crate) async fn try_send_reply_chunks_on_lane(
|
||||
&mut self,
|
||||
target: AnonymousSenderTag,
|
||||
fragments: Vec<Fragment>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
lane: TransmissionLane,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
// TODO: technically this is performing an unnecessary cloning, but in the grand scheme of things
|
||||
// is it really that bad?
|
||||
self.try_send_reply_chunks(
|
||||
target,
|
||||
fragments.into_iter().map(|f| (lane, f)).collect(),
|
||||
reply_surbs,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn try_send_reply_chunks(
|
||||
&mut self,
|
||||
target: AnonymousSenderTag,
|
||||
fragments: Vec<(TransmissionLane, Fragment)>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
let prepared_fragments = self
|
||||
.prepare_reply_chunks_for_sending(fragments.clone(), reply_surbs)
|
||||
.prepare_reply_chunks_for_sending(
|
||||
fragments.iter().map(|(_, f)| f.clone()).collect(),
|
||||
reply_surbs,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut pending_acks = Vec::with_capacity(fragments.len());
|
||||
let mut real_messages = Vec::with_capacity(fragments.len());
|
||||
let mut to_forward: HashMap<_, Vec<_>> = HashMap::new();
|
||||
|
||||
for (raw, prepared) in fragments.into_iter().zip(prepared_fragments.into_iter()) {
|
||||
let lane = raw.0;
|
||||
let fragment = raw.1;
|
||||
|
||||
let real_message = RealMessage::new(prepared.mix_packet, prepared.fragment_identifier);
|
||||
let delay = prepared.total_delay;
|
||||
let pending_ack = PendingAcknowledgement::new_anonymous(raw, delay, target, false);
|
||||
let pending_ack = PendingAcknowledgement::new_anonymous(fragment, delay, target, false);
|
||||
|
||||
real_messages.push(real_message);
|
||||
let entry = to_forward.entry(lane).or_default();
|
||||
entry.push(real_message);
|
||||
pending_acks.push(pending_ack);
|
||||
}
|
||||
|
||||
self.forward_messages(real_messages, lane).await;
|
||||
for (lane, real_messages) in to_forward {
|
||||
self.forward_messages(real_messages, lane).await;
|
||||
}
|
||||
|
||||
self.insert_pending_acks(pending_acks);
|
||||
Ok(())
|
||||
}
|
||||
@@ -467,7 +485,7 @@ where
|
||||
Ok(prepared_fragment)
|
||||
}
|
||||
|
||||
async fn prepare_reply_chunks_for_sending(
|
||||
pub(crate) async fn prepare_reply_chunks_for_sending(
|
||||
&mut self,
|
||||
fragments: Vec<Fragment>,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use self::sending_delay_controller::SendingDelayController;
|
||||
use crate::client::mix_traffic::BatchMixMessageSender;
|
||||
use crate::client::real_messages_control::acknowledgement_control::SentPacketNotificationSender;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use crate::client::transmission_buffer::TransmissionBuffer;
|
||||
use client_connections::{
|
||||
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
@@ -29,22 +31,7 @@ use tokio::time;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_timer;
|
||||
|
||||
use self::{
|
||||
sending_delay_controller::SendingDelayController, transmission_buffer::TransmissionBuffer,
|
||||
};
|
||||
|
||||
mod sending_delay_controller;
|
||||
mod transmission_buffer;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn get_time_now() -> time::Instant {
|
||||
time::Instant::now()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn get_time_now() -> wasm_timer::Instant {
|
||||
wasm_timer::Instant::now()
|
||||
}
|
||||
|
||||
/// Configurable parameters of the `OutQueueControl`
|
||||
pub(crate) struct Config {
|
||||
@@ -135,7 +122,7 @@ where
|
||||
|
||||
/// Buffer containing all incoming real messages keyed by transmission lane, that we will send
|
||||
/// out to the mixnet.
|
||||
transmission_buffer: TransmissionBuffer,
|
||||
transmission_buffer: TransmissionBuffer<RealMessage>,
|
||||
|
||||
/// Incoming channel for being notified of closed connections, so that we can close lanes
|
||||
/// corresponding to connections. To avoid sending traffic unnecessary
|
||||
@@ -162,6 +149,10 @@ impl From<PreparedFragment> for RealMessage {
|
||||
}
|
||||
|
||||
impl RealMessage {
|
||||
pub(crate) fn packet_size(&self) -> usize {
|
||||
self.mix_packet.sphinx_packet().len()
|
||||
}
|
||||
|
||||
pub(crate) fn new(mix_packet: MixPacket, fragment_id: FragmentIdentifier) -> Self {
|
||||
RealMessage {
|
||||
mix_packet,
|
||||
@@ -207,7 +198,7 @@ where
|
||||
real_receiver,
|
||||
rng,
|
||||
topology_access,
|
||||
transmission_buffer: Default::default(),
|
||||
transmission_buffer: TransmissionBuffer::new(),
|
||||
client_connection_rx,
|
||||
lane_queue_lengths,
|
||||
}
|
||||
@@ -328,7 +319,9 @@ where
|
||||
|
||||
fn pop_next_message(&mut self) -> Option<RealMessage> {
|
||||
// Pop the next message from the transmission buffer
|
||||
let (lane, real_next) = self.transmission_buffer.pop_next_message_at_random()?;
|
||||
let (lane, real_next) = self
|
||||
.transmission_buffer
|
||||
.pop_next_message_at_random(&mut self.rng)?;
|
||||
|
||||
// Update the published queue length
|
||||
let lane_length = self.transmission_buffer.lane_length(&lane);
|
||||
|
||||
+3
-16
@@ -1,14 +1,9 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::get_time_now;
|
||||
use crate::client::helpers::{get_time_now, Instant};
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_timer;
|
||||
|
||||
// The minimum time between increasing the average delay between packets. If we hit the ceiling in
|
||||
// the available buffer space we want to take somewhat swift action, but we still need to give a
|
||||
// short time to give the channel a chance reduce pressure.
|
||||
@@ -39,19 +34,11 @@ pub(crate) struct SendingDelayController {
|
||||
lower_bound: u32,
|
||||
|
||||
/// To make sure we don't change the multiplier to fast, we limit a change to some duration
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
time_when_changed: time::Instant,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
time_when_changed: wasm_timer::Instant,
|
||||
time_when_changed: Instant,
|
||||
|
||||
/// If we have a long enough time without any backpressure detected we try reducing the sending
|
||||
/// delay multiplier
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
time_when_backpressure_detected: time::Instant,
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
time_when_backpressure_detected: wasm_timer::Instant,
|
||||
time_when_backpressure_detected: Instant,
|
||||
}
|
||||
|
||||
impl Default for SendingDelayController {
|
||||
|
||||
+155
-218
@@ -4,8 +4,8 @@
|
||||
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
|
||||
use crate::client::real_messages_control::message_handler::{MessageHandler, PreparationError};
|
||||
use crate::client::replies::reply_storage::CombinedReplyStorage;
|
||||
use client_connections::TransmissionLane;
|
||||
use futures::channel::mpsc;
|
||||
use client_connections::{ConnectionId, TransmissionLane};
|
||||
use futures::channel::oneshot;
|
||||
use futures::StreamExt;
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
@@ -15,116 +15,16 @@ use nymsphinx::chunking::fragment::{Fragment, FragmentIdentifier};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::{BTreeMap, HashMap, VecDeque};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
type IntervalStream = tokio_stream::wrappers::IntervalStream;
|
||||
use crate::client::helpers::new_interval_stream;
|
||||
use crate::client::transmission_buffer::TransmissionBuffer;
|
||||
pub(crate) use requests::{ReplyControllerMessage, ReplyControllerReceiver, ReplyControllerSender};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
type IntervalStream = gloo_timers::future::IntervalStream;
|
||||
|
||||
pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
(tx.into(), rx)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct ReplyControllerSender(mpsc::UnboundedSender<ReplyControllerMessage>);
|
||||
|
||||
impl From<mpsc::UnboundedSender<ReplyControllerMessage>> for ReplyControllerSender {
|
||||
fn from(inner: mpsc::UnboundedSender<ReplyControllerMessage>) -> Self {
|
||||
ReplyControllerSender(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplyControllerSender {
|
||||
pub(crate) fn send_retransmission_data(
|
||||
&self,
|
||||
recipient: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
extra_surb_request: bool,
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::RetransmitReply {
|
||||
recipient,
|
||||
timed_out_ack,
|
||||
extra_surb_request,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_reply(
|
||||
&self,
|
||||
recipient: AnonymousSenderTag,
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::SendReply {
|
||||
recipient,
|
||||
message,
|
||||
lane,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_additional_surbs(
|
||||
&self,
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
from_surb_request: bool,
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbs {
|
||||
sender_tag,
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_additional_surbs_request(&self, recipient: Recipient, amount: u32) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest {
|
||||
recipient: Box::new(recipient),
|
||||
amount,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver<ReplyControllerMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ReplyControllerMessage {
|
||||
RetransmitReply {
|
||||
recipient: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
extra_surb_request: bool,
|
||||
},
|
||||
|
||||
SendReply {
|
||||
recipient: AnonymousSenderTag,
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
},
|
||||
|
||||
AdditionalSurbs {
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
from_surb_request: bool,
|
||||
},
|
||||
|
||||
// Should this also be handled in here? it's technically a completely different side of the pipe
|
||||
// let's see how it works when combined, might split it before creating PR
|
||||
AdditionalSurbsRequest {
|
||||
recipient: Box<Recipient>,
|
||||
amount: u32,
|
||||
},
|
||||
}
|
||||
pub mod requests;
|
||||
|
||||
pub struct Config {
|
||||
min_surb_request_size: u32,
|
||||
@@ -172,7 +72,7 @@ pub struct ReplyController<R> {
|
||||
// of surbs required to send the message through
|
||||
// expected_reliability: f32,
|
||||
request_receiver: ReplyControllerReceiver,
|
||||
pending_replies: HashMap<AnonymousSenderTag, VecDeque<Fragment>>,
|
||||
pending_replies: HashMap<AnonymousSenderTag, TransmissionBuffer<Fragment>>,
|
||||
|
||||
/// Retransmission packets that have already timed out and are waiting for additional reply SURBs
|
||||
/// so that they could be sent back to the network. Once we receive more SURBs, we should send them ASAP.
|
||||
@@ -204,17 +104,28 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts the pending replies into the BACK of the queue fn insert_pending_replies<V: Into<VecDeque<Fragment>>>(
|
||||
fn insert_pending_replies<V: Into<VecDeque<Fragment>>>(
|
||||
fn insert_pending_replies<I: IntoIterator<Item = Fragment>>(
|
||||
&mut self,
|
||||
recipient: &AnonymousSenderTag,
|
||||
fragments: V,
|
||||
fragments: I,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
if let Some(existing) = self.pending_replies.get_mut(recipient) {
|
||||
existing.append(&mut fragments.into())
|
||||
} else {
|
||||
self.pending_replies.insert(*recipient, fragments.into());
|
||||
}
|
||||
self.pending_replies
|
||||
.entry(*recipient)
|
||||
.or_insert_with(TransmissionBuffer::new)
|
||||
.store(&lane, fragments)
|
||||
}
|
||||
|
||||
fn re_insert_pending_replies(
|
||||
&mut self,
|
||||
recipient: &AnonymousSenderTag,
|
||||
fragments: Vec<(TransmissionLane, Fragment)>,
|
||||
) {
|
||||
// the buffer should ALWAYS exist at this point, if it doesn't, it's a bug...
|
||||
self.pending_replies
|
||||
.entry(*recipient)
|
||||
.or_insert_with(TransmissionBuffer::new)
|
||||
.store_multiple(fragments)
|
||||
}
|
||||
|
||||
fn re_insert_pending_retransmission(
|
||||
@@ -244,7 +155,7 @@ where
|
||||
let pending_queue_size = self
|
||||
.pending_replies
|
||||
.get(target)
|
||||
.map(|pending_queue| pending_queue.len())
|
||||
.map(|pending_queue| pending_queue.total_size())
|
||||
.unwrap_or_default();
|
||||
|
||||
let retransmission_queue = self
|
||||
@@ -299,43 +210,62 @@ where
|
||||
}
|
||||
|
||||
trace!("handling reply to {:?}", recipient_tag);
|
||||
let fragments = self.message_handler.split_reply_message(data);
|
||||
let mut fragments = self.message_handler.split_reply_message(data);
|
||||
let total_size = fragments.len();
|
||||
trace!("This reply requires {:?} SURBs", total_size);
|
||||
|
||||
let required_surbs = fragments.len();
|
||||
trace!("This reply requires {:?} SURBs", required_surbs);
|
||||
|
||||
// TODO: edge case:
|
||||
// we're making a lot of requests and have to request a lot of surbs
|
||||
// (but at some point we run out of surbs for surb requests)
|
||||
|
||||
let (surbs, _surbs_left) = self
|
||||
let available_surbs = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.get_reply_surbs(&recipient_tag, required_surbs);
|
||||
.available_surbs(&recipient_tag);
|
||||
let min_surbs_threshold = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.min_surb_threshold();
|
||||
|
||||
if let Some(reply_surbs) = surbs {
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_reply_chunks(recipient_tag, fragments, reply_surbs, lane)
|
||||
.await
|
||||
{
|
||||
let err = err.return_unused_surbs(
|
||||
self.full_reply_storage.surbs_storage_ref(),
|
||||
&recipient_tag,
|
||||
);
|
||||
warn!("failed to send reply to {:?} - {err}", recipient_tag);
|
||||
|
||||
// TODO: should we buffer that data to try again?
|
||||
}
|
||||
let max_to_send = if available_surbs > min_surbs_threshold {
|
||||
min(fragments.len(), available_surbs - min_surbs_threshold)
|
||||
} else {
|
||||
// we don't have enough surbs for this reply
|
||||
self.insert_pending_replies(&recipient_tag, fragments);
|
||||
0
|
||||
};
|
||||
|
||||
if self.should_request_more_surbs(&recipient_tag) {
|
||||
self.request_reply_surbs_for_queue_clearing(recipient_tag)
|
||||
.await;
|
||||
if max_to_send > 0 {
|
||||
let (surbs, _surbs_left) = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.get_reply_surbs(&recipient_tag, max_to_send);
|
||||
|
||||
if let Some(reply_surbs) = surbs {
|
||||
let to_send = fragments.drain(..max_to_send).collect::<Vec<_>>();
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_reply_chunks_on_lane(
|
||||
recipient_tag,
|
||||
to_send.clone(),
|
||||
reply_surbs,
|
||||
lane,
|
||||
)
|
||||
.await
|
||||
{
|
||||
let err = err.return_unused_surbs(
|
||||
self.full_reply_storage.surbs_storage_ref(),
|
||||
&recipient_tag,
|
||||
);
|
||||
warn!("failed to send reply to {recipient_tag}: {err}");
|
||||
self.insert_pending_replies(&recipient_tag, to_send, lane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there's leftover data we didn't send because we didn't have enough (or any) surbs - buffer it
|
||||
if !fragments.is_empty() {
|
||||
self.insert_pending_replies(&recipient_tag, fragments, lane);
|
||||
}
|
||||
|
||||
if self.should_request_more_surbs(&recipient_tag) {
|
||||
self.request_reply_surbs_for_queue_clearing(recipient_tag)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn request_additional_reply_surbs(
|
||||
@@ -398,35 +328,18 @@ where
|
||||
};
|
||||
|
||||
let mut to_take = Vec::new();
|
||||
let mut to_remove = Vec::new();
|
||||
|
||||
// TODO: once rust 1.66.0 is stabilised on 15.12.22, just change it to
|
||||
// `.pop_front()` to directly take ownership
|
||||
for (k, data) in pending.iter() {
|
||||
let upgraded = match data.upgrade() {
|
||||
Some(upgraded) => upgraded,
|
||||
None => {
|
||||
// we got the ack while the data was waiting in the queue
|
||||
to_remove.push(*k);
|
||||
continue;
|
||||
while to_take.len() < max_to_clear {
|
||||
if let Some((_, data)) = pending.pop_first() {
|
||||
// no need to do anything if we failed to upgrade the reference,
|
||||
// it means we got the ack while the data was waiting in the queue
|
||||
if let Some(upgraded) = data.upgrade() {
|
||||
to_take.push(upgraded)
|
||||
}
|
||||
};
|
||||
|
||||
to_take.push(upgraded);
|
||||
|
||||
// we have taken as many entries as we could have
|
||||
if to_take.len() >= max_to_clear {
|
||||
} else {
|
||||
// our map is empty!
|
||||
break;
|
||||
}
|
||||
// TODO: use if upgraded.is_extra_surb_request() to bypass the limit
|
||||
}
|
||||
|
||||
for ack in &to_take {
|
||||
pending.remove(&ack.inner_fragment_identifier());
|
||||
}
|
||||
|
||||
for id in to_remove {
|
||||
pending.remove(&id);
|
||||
}
|
||||
|
||||
if to_take.is_empty() {
|
||||
@@ -447,46 +360,47 @@ where
|
||||
|
||||
let to_send_vec = to_take.iter().map(|ack| ack.fragment_data()).collect();
|
||||
|
||||
if let Err(err) = self
|
||||
let prepared_fragments = match self
|
||||
.message_handler
|
||||
.try_send_retransmission_reply_chunks(
|
||||
to_send_vec,
|
||||
surbs_for_reply,
|
||||
TransmissionLane::Retransmission,
|
||||
)
|
||||
.prepare_reply_chunks_for_sending(to_send_vec, surbs_for_reply)
|
||||
.await
|
||||
{
|
||||
let err = err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target);
|
||||
self.re_insert_pending_retransmission(&target, to_take);
|
||||
Ok(prepared) => prepared,
|
||||
Err(err) => {
|
||||
let err =
|
||||
err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target);
|
||||
self.re_insert_pending_retransmission(&target, to_take);
|
||||
|
||||
warn!(
|
||||
"failed to clear pending retransmission queue for {:?} - {err}",
|
||||
target
|
||||
);
|
||||
}
|
||||
warn!(
|
||||
"failed to clear pending retransmission queue for {:?} - {err}",
|
||||
target
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// we can't fail at this point, so drop all references to acks so that timer updates wouldn't blow up
|
||||
drop(to_take);
|
||||
|
||||
self.message_handler
|
||||
.send_retransmission_reply_chunks(prepared_fragments, TransmissionLane::Retransmission)
|
||||
.await;
|
||||
}
|
||||
|
||||
fn pop_at_most_pending_replies(
|
||||
&mut self,
|
||||
from: &AnonymousSenderTag,
|
||||
amount: usize,
|
||||
) -> Option<VecDeque<Fragment>> {
|
||||
) -> Option<Vec<(TransmissionLane, Fragment)>> {
|
||||
// if possible, pop all pending replies, if not, pop only entries for which we'd have a reply surb
|
||||
let total = self.pending_replies.get(from)?.len();
|
||||
let total = self.pending_replies.get(from)?.total_size();
|
||||
trace!("pending queue has {total} elements");
|
||||
if total == 0 {
|
||||
return None;
|
||||
}
|
||||
if total < amount {
|
||||
self.pending_replies.remove(from)
|
||||
} else {
|
||||
Some(
|
||||
self.pending_replies
|
||||
.get_mut(from)?
|
||||
.drain(..amount)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
self.pending_replies
|
||||
.get_mut(from)?
|
||||
.pop_at_most_n_next_messages_at_random(amount)
|
||||
}
|
||||
|
||||
async fn try_clear_pending_queue(&mut self, target: AnonymousSenderTag) {
|
||||
@@ -510,9 +424,9 @@ where
|
||||
|
||||
// we're guaranteed to not get more entries than we have reply surbs for
|
||||
if let Some(to_send) = self.pop_at_most_pending_replies(&target, max_to_clear) {
|
||||
let to_send_vec = to_send.iter().cloned().collect::<Vec<_>>();
|
||||
let to_send_clone = to_send.clone();
|
||||
|
||||
if to_send_vec.is_empty() {
|
||||
if to_send_clone.is_empty() {
|
||||
panic!(
|
||||
"please let the devs know if you ever see this message (reply_controller.rs)"
|
||||
);
|
||||
@@ -521,27 +435,22 @@ where
|
||||
let (surbs_for_reply, _) = self
|
||||
.full_reply_storage
|
||||
.surbs_storage_ref()
|
||||
.get_reply_surbs(&target, to_send_vec.len());
|
||||
.get_reply_surbs(&target, to_send_clone.len());
|
||||
|
||||
let Some(surbs_for_reply) = surbs_for_reply else {
|
||||
error!("somehow different task has stolen our reply surbs! - this should have been impossible");
|
||||
self.insert_pending_replies(&target, to_send);
|
||||
self.re_insert_pending_replies(&target, to_send);
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_reply_chunks(
|
||||
target,
|
||||
to_send_vec,
|
||||
surbs_for_reply,
|
||||
TransmissionLane::General,
|
||||
)
|
||||
.try_send_reply_chunks(target, to_send_clone, surbs_for_reply)
|
||||
.await
|
||||
{
|
||||
let err =
|
||||
err.return_unused_surbs(self.full_reply_storage.surbs_storage_ref(), &target);
|
||||
self.insert_pending_replies(&target, to_send);
|
||||
self.re_insert_pending_replies(&target, to_send);
|
||||
warn!("failed to clear pending queue for {:?} - {err}", target);
|
||||
}
|
||||
} else {
|
||||
@@ -712,6 +621,30 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// to be honest this doesn't make a lot of sense in the context of `connection_id`,
|
||||
// it should really be asked per tag
|
||||
fn handle_lane_queue_length(
|
||||
&self,
|
||||
connection_id: ConnectionId,
|
||||
response_channel: oneshot::Sender<usize>,
|
||||
) {
|
||||
// TODO: if we ever have duplicate ids for different senders, it means our rng is super weak
|
||||
// thus I don't think we have to worry about it?
|
||||
let lane = TransmissionLane::ConnectionId(connection_id);
|
||||
for buf in self.pending_replies.values() {
|
||||
if let Some(length) = buf.lane_length(&lane) {
|
||||
if response_channel.send(length).is_err() {
|
||||
error!("the requester for lane queue length has dropped the response channel!")
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// make sure that if we didn't find that lane, we reply with 0
|
||||
if response_channel.send(0).is_err() {
|
||||
error!("the requester for lane queue length has dropped the response channel!")
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_request(&mut self, request: ReplyControllerMessage) {
|
||||
match request {
|
||||
ReplyControllerMessage::RetransmitReply {
|
||||
@@ -735,19 +668,26 @@ where
|
||||
self.handle_received_surbs(sender_tag, reply_surbs, from_surb_request)
|
||||
.await
|
||||
}
|
||||
ReplyControllerMessage::LaneQueueLength {
|
||||
connection_id,
|
||||
response_channel,
|
||||
} => self.handle_lane_queue_length(connection_id, response_channel),
|
||||
ReplyControllerMessage::AdditionalSurbsRequest { recipient, amount } => {
|
||||
self.handle_surb_request(*recipient, amount).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: modify this method to more accurately determine the amount of surbs it needs to request
|
||||
// it should take into consideration the average latency, sending rate and queue size.
|
||||
// it should request as many surbs as it takes to saturate its sending rate before next batch arrives
|
||||
async fn request_reply_surbs_for_queue_clearing(&mut self, target: AnonymousSenderTag) {
|
||||
trace!("requesting surbs for queues clearing");
|
||||
|
||||
let pending_queue_size = self
|
||||
.pending_replies
|
||||
.get(&target)
|
||||
.map(|pending_queue| pending_queue.len())
|
||||
.map(|pending_queue| pending_queue.total_size())
|
||||
.unwrap_or_default();
|
||||
|
||||
let retransmission_queue = self
|
||||
@@ -787,7 +727,7 @@ where
|
||||
}
|
||||
|
||||
let Some(last_received) = self.full_reply_storage.surbs_storage_ref().surbs_last_received_at(pending_reply_target) else {
|
||||
error!("we have {} pending replies for {pending_reply_target}, but we somehow never received any reply surbs from them!", vals.len());
|
||||
error!("we have {} pending replies for {pending_reply_target}, but we somehow never received any reply surbs from them!", vals.total_size());
|
||||
to_remove.push(*pending_reply_target);
|
||||
continue;
|
||||
};
|
||||
@@ -883,23 +823,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn create_interval_stream(polling_rate: Duration) -> IntervalStream {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
return tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(polling_rate));
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
return gloo_timers::future::IntervalStream::new(polling_rate.as_millis() as u32);
|
||||
}
|
||||
// #[cfg(not(target_arch = "wasm32"))]
|
||||
// async fn log_status(&self) {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
pub(crate) async fn run_with_shutdown(&mut self, mut shutdown: task::TaskClient) {
|
||||
debug!("Started ReplyController with graceful shutdown support");
|
||||
|
||||
let polling_rate = Duration::from_secs(5);
|
||||
let mut stale_inspection = Self::create_interval_stream(polling_rate);
|
||||
let mut stale_inspection = new_interval_stream(polling_rate);
|
||||
|
||||
// this is in the order of hours/days so we don't have to poll it that often
|
||||
let polling_rate = Duration::from_secs(self.config.max_reply_surb_age.as_secs() / 10);
|
||||
let mut invalidation_inspection = Self::create_interval_stream(polling_rate);
|
||||
let mut invalidation_inspection = new_interval_stream(polling_rate);
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::real_messages_control::acknowledgement_control::PendingAcknowledgement;
|
||||
use client_connections::{ConnectionId, TransmissionLane};
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use log::error;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nymsphinx::anonymous_replies::ReplySurb;
|
||||
use std::sync::Weak;
|
||||
|
||||
pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
(tx.into(), rx)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReplyControllerSender(mpsc::UnboundedSender<ReplyControllerMessage>);
|
||||
|
||||
impl From<mpsc::UnboundedSender<ReplyControllerMessage>> for ReplyControllerSender {
|
||||
fn from(inner: mpsc::UnboundedSender<ReplyControllerMessage>) -> Self {
|
||||
ReplyControllerSender(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReplyControllerSender {
|
||||
pub(crate) fn send_retransmission_data(
|
||||
&self,
|
||||
recipient: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
extra_surb_request: bool,
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::RetransmitReply {
|
||||
recipient,
|
||||
timed_out_ack,
|
||||
extra_surb_request,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_reply(
|
||||
&self,
|
||||
recipient: AnonymousSenderTag,
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::SendReply {
|
||||
recipient,
|
||||
message,
|
||||
lane,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_additional_surbs(
|
||||
&self,
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
from_surb_request: bool,
|
||||
) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbs {
|
||||
sender_tag,
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub(crate) fn send_additional_surbs_request(&self, recipient: Recipient, amount: u32) {
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::AdditionalSurbsRequest {
|
||||
recipient: Box::new(recipient),
|
||||
amount,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!")
|
||||
}
|
||||
|
||||
pub async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> usize {
|
||||
let (response_tx, response_rx) = oneshot::channel();
|
||||
self.0
|
||||
.unbounded_send(ReplyControllerMessage::LaneQueueLength {
|
||||
connection_id,
|
||||
response_channel: response_tx,
|
||||
})
|
||||
.expect("ReplyControllerReceiver has died!");
|
||||
|
||||
match response_rx.await {
|
||||
Ok(length) => length,
|
||||
Err(_) => {
|
||||
error!("The reply controller has dropped our response channel!");
|
||||
// TODO: should we panic here instead? this message implies something weird and unrecoverable has happened
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type ReplyControllerReceiver = mpsc::UnboundedReceiver<ReplyControllerMessage>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ReplyControllerMessage {
|
||||
RetransmitReply {
|
||||
recipient: AnonymousSenderTag,
|
||||
timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
extra_surb_request: bool,
|
||||
},
|
||||
|
||||
SendReply {
|
||||
recipient: AnonymousSenderTag,
|
||||
message: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
},
|
||||
|
||||
AdditionalSurbs {
|
||||
sender_tag: AnonymousSenderTag,
|
||||
reply_surbs: Vec<ReplySurb>,
|
||||
from_surb_request: bool,
|
||||
},
|
||||
|
||||
// this one doesn't belong here either...
|
||||
LaneQueueLength {
|
||||
connection_id: ConnectionId,
|
||||
response_channel: oneshot::Sender<usize>,
|
||||
},
|
||||
|
||||
// Should this also be handled in here? it's technically a completely different side of the pipe
|
||||
// let's see how it works when combined, might split it before creating PR
|
||||
AdditionalSurbsRequest {
|
||||
recipient: Box<Recipient>,
|
||||
amount: u32,
|
||||
},
|
||||
}
|
||||
+118
-48
@@ -1,38 +1,57 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::helpers::{get_time_now, Instant};
|
||||
use crate::client::real_messages_control::real_traffic_stream::RealMessage;
|
||||
use client_connections::TransmissionLane;
|
||||
use nymsphinx::chunking::fragment::Fragment;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_timer;
|
||||
|
||||
use super::{get_time_now, RealMessage};
|
||||
|
||||
// The number of lanes included in the oldest set. Used when we need to prioritize traffic.
|
||||
const OLDEST_LANE_SET_SIZE: usize = 4;
|
||||
// As a way of prune connections we also check for timeouts.
|
||||
const MSG_CONSIDERED_STALE_AFTER_SECS: u64 = 10 * 60;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TransmissionBuffer {
|
||||
buffer: HashMap<TransmissionLane, LaneBufferEntry>,
|
||||
pub(crate) trait SizedData {
|
||||
fn data_size(&self) -> usize;
|
||||
}
|
||||
|
||||
impl TransmissionBuffer {
|
||||
impl SizedData for RealMessage {
|
||||
fn data_size(&self) -> usize {
|
||||
self.packet_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl SizedData for Fragment {
|
||||
fn data_size(&self) -> usize {
|
||||
// note that raw `Fragment` is smaller than sphinx packet payload
|
||||
// as it doesn't include surb-ack or the [shared] key materials
|
||||
self.payload_size()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TransmissionBuffer<T> {
|
||||
buffer: HashMap<TransmissionLane, LaneBufferEntry<T>>,
|
||||
}
|
||||
|
||||
impl<T> TransmissionBuffer<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
TransmissionBuffer {
|
||||
buffer: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.buffer.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option<LaneBufferEntry> {
|
||||
pub(crate) fn remove(&mut self, lane: &TransmissionLane) -> Option<LaneBufferEntry<T>> {
|
||||
self.buffer.remove(lane)
|
||||
}
|
||||
|
||||
@@ -57,20 +76,22 @@ impl TransmissionBuffer {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn total_size(&self) -> usize {
|
||||
self.buffer.values().map(LaneBufferEntry::len).sum()
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn total_size_in_bytes(&self) -> usize {
|
||||
pub(crate) fn total_size_in_bytes(&self) -> usize
|
||||
where
|
||||
T: SizedData,
|
||||
{
|
||||
self.buffer
|
||||
.values()
|
||||
.map(|lane_buffer_entry| {
|
||||
lane_buffer_entry
|
||||
.real_messages
|
||||
.items
|
||||
.iter()
|
||||
.map(|real_message| real_message.mix_packet.sphinx_packet().len())
|
||||
.map(|item| item.data_size())
|
||||
.sum::<usize>()
|
||||
})
|
||||
.sum()
|
||||
@@ -92,42 +113,51 @@ impl TransmissionBuffer {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn store(&mut self, lane: &TransmissionLane, real_messages: Vec<RealMessage>) {
|
||||
pub(crate) fn store<I: IntoIterator<Item = T>>(&mut self, lane: &TransmissionLane, items: I) {
|
||||
if let Some(lane_buffer_entry) = self.buffer.get_mut(lane) {
|
||||
lane_buffer_entry.append(real_messages);
|
||||
lane_buffer_entry.extend(items);
|
||||
} else {
|
||||
self.buffer
|
||||
.insert(*lane, LaneBufferEntry::new(real_messages));
|
||||
.insert(*lane, LaneBufferEntry::new(items.into_iter().collect()));
|
||||
}
|
||||
}
|
||||
|
||||
fn pick_random_lane(&self) -> Option<&TransmissionLane> {
|
||||
let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect();
|
||||
lanes.choose(&mut rand::thread_rng()).copied()
|
||||
pub(crate) fn store_multiple(&mut self, items: Vec<(TransmissionLane, T)>) {
|
||||
for (lane, item) in items {
|
||||
self.buffer
|
||||
.entry(lane)
|
||||
.or_insert_with(LaneBufferEntry::new_empty)
|
||||
.push_item(item)
|
||||
}
|
||||
}
|
||||
|
||||
fn pick_random_small_lane(&self) -> Option<&TransmissionLane> {
|
||||
fn pick_random_lane<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<&TransmissionLane> {
|
||||
let lanes: Vec<&TransmissionLane> = self.buffer.keys().collect();
|
||||
lanes.choose(rng).copied()
|
||||
}
|
||||
|
||||
fn pick_random_small_lane<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<&TransmissionLane> {
|
||||
let lanes: Vec<&TransmissionLane> = self
|
||||
.buffer
|
||||
.iter()
|
||||
.filter(|(_, v)| v.is_small())
|
||||
.map(|(k, _)| k)
|
||||
.collect();
|
||||
lanes.choose(&mut rand::thread_rng()).copied()
|
||||
lanes.choose(rng).copied()
|
||||
}
|
||||
|
||||
// 2/3 chance to pick from the old lanes
|
||||
fn pick_random_old_lane(&self) -> Option<TransmissionLane> {
|
||||
fn pick_random_old_lane<R: Rng + ?Sized>(&self, rng: &mut R) -> Option<TransmissionLane> {
|
||||
let rand = &mut rand::thread_rng();
|
||||
if rand.gen_ratio(2, 3) {
|
||||
let lanes = self.get_oldest_set();
|
||||
lanes.choose(rand).copied()
|
||||
} else {
|
||||
self.pick_random_lane().copied()
|
||||
self.pick_random_lane(rng).copied()
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option<RealMessage> {
|
||||
fn pop_front_from_lane(&mut self, lane: &TransmissionLane) -> Option<T> {
|
||||
let real_msgs_queued = self.buffer.get_mut(lane)?;
|
||||
let real_next = real_msgs_queued.pop_front()?;
|
||||
real_msgs_queued.messages_transmitted += 1;
|
||||
@@ -137,19 +167,48 @@ impl TransmissionBuffer {
|
||||
Some(real_next)
|
||||
}
|
||||
|
||||
pub(crate) fn pop_next_message_at_random(&mut self) -> Option<(TransmissionLane, RealMessage)> {
|
||||
pub(crate) fn pop_at_most_n_next_messages_at_random(
|
||||
&mut self,
|
||||
n: usize,
|
||||
) -> Option<Vec<(TransmissionLane, T)>> {
|
||||
// let start = Instant::now();
|
||||
|
||||
if self.buffer.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let rng = &mut rand::thread_rng();
|
||||
let mut items = Vec::with_capacity(n);
|
||||
|
||||
while items.len() < n {
|
||||
let Some(next) = self.pop_next_message_at_random(rng) else {
|
||||
break
|
||||
};
|
||||
items.push(next)
|
||||
}
|
||||
|
||||
// todo!("time time taken");
|
||||
|
||||
Some(items)
|
||||
}
|
||||
|
||||
pub(crate) fn pop_next_message_at_random<R: Rng + ?Sized>(
|
||||
&mut self,
|
||||
// turns out the caller always have access to some rng, so no point in instantiating new one
|
||||
rng: &mut R,
|
||||
) -> Option<(TransmissionLane, T)> {
|
||||
if self.buffer.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Very basic heuristic where we prioritize according to small lanes first, the older lanes
|
||||
// to try to finish lanes when possible, then the rest.
|
||||
let lane = if let Some(small_lane) = self.pick_random_small_lane() {
|
||||
let lane = if let Some(small_lane) = self.pick_random_small_lane(rng) {
|
||||
*small_lane
|
||||
} else if let Some(old_lane) = self.pick_random_old_lane() {
|
||||
} else if let Some(old_lane) = self.pick_random_old_lane(rng) {
|
||||
old_lane
|
||||
} else {
|
||||
*self.pick_random_lane()?
|
||||
*self.pick_random_lane(rng)?
|
||||
};
|
||||
|
||||
let msg = self.pop_front_from_lane(&lane)?;
|
||||
@@ -171,35 +230,46 @@ impl TransmissionBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct LaneBufferEntry {
|
||||
pub real_messages: VecDeque<RealMessage>,
|
||||
pub(crate) struct LaneBufferEntry<T> {
|
||||
pub items: VecDeque<T>,
|
||||
pub messages_transmitted: usize,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub time_for_last_activity: time::Instant,
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub time_for_last_activity: wasm_timer::Instant,
|
||||
pub time_for_last_activity: Instant,
|
||||
}
|
||||
|
||||
impl LaneBufferEntry {
|
||||
fn new(real_messages: Vec<RealMessage>) -> Self {
|
||||
impl<T> LaneBufferEntry<T> {
|
||||
fn new_empty() -> Self {
|
||||
LaneBufferEntry {
|
||||
real_messages: real_messages.into(),
|
||||
items: VecDeque::new(),
|
||||
messages_transmitted: 0,
|
||||
time_for_last_activity: get_time_now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn append(&mut self, real_messages: Vec<RealMessage>) {
|
||||
self.real_messages.append(&mut real_messages.into());
|
||||
fn new(items: VecDeque<T>) -> Self {
|
||||
LaneBufferEntry {
|
||||
items,
|
||||
messages_transmitted: 0,
|
||||
time_for_last_activity: get_time_now(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_item(&mut self, item: T) {
|
||||
self.items.push_back(item);
|
||||
// I'm not updating time here on purpose. This method is called just after `new_empty`,
|
||||
// where the time is already set. Furthermore, this method is called there multiple times at once
|
||||
}
|
||||
|
||||
fn extend<I: IntoIterator<Item = T>>(&mut self, items: I) {
|
||||
self.items.extend(items);
|
||||
self.time_for_last_activity = get_time_now();
|
||||
}
|
||||
|
||||
fn pop_front(&mut self) -> Option<RealMessage> {
|
||||
self.real_messages.pop_front()
|
||||
fn pop_front(&mut self) -> Option<T> {
|
||||
self.items.pop_front()
|
||||
}
|
||||
|
||||
fn is_small(&self) -> bool {
|
||||
self.real_messages.len() < 100
|
||||
self.items.len() < 100
|
||||
}
|
||||
|
||||
fn is_stale(&self) -> bool {
|
||||
@@ -208,10 +278,10 @@ impl LaneBufferEntry {
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.real_messages.len()
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.real_messages.is_empty()
|
||||
self.items.is_empty()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.3"
|
||||
version = "1.1.5"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -13,6 +13,7 @@ use client_core::client::base_client::{
|
||||
use client_core::client::inbound_messages::InputMessage;
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::client::received_buffer::{ReceivedBufferMessage, ReconstructedMessagesReceiver};
|
||||
use client_core::client::replies::reply_controller::requests::ReplyControllerSender;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use futures::channel::mpsc;
|
||||
use gateway_client::bandwidth::BandwidthController;
|
||||
@@ -87,6 +88,7 @@ impl SocketClient {
|
||||
client_input: ClientInput,
|
||||
client_output: ClientOutput,
|
||||
self_address: &Recipient,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
shutdown: task::TaskClient,
|
||||
) {
|
||||
info!("Starting websocket listener...");
|
||||
@@ -107,6 +109,7 @@ impl SocketClient {
|
||||
received_buffer_request_sender,
|
||||
self_address,
|
||||
shared_lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
);
|
||||
|
||||
websocket::Listener::new(config.get_listening_port()).start(websocket_handler, shutdown);
|
||||
@@ -154,6 +157,7 @@ impl SocketClient {
|
||||
client_input,
|
||||
client_output,
|
||||
&self_address,
|
||||
started_client.reply_controller_sender,
|
||||
started_client.task_manager.subscribe(),
|
||||
);
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use client_connections::{
|
||||
ConnectionCommand, ConnectionCommandSender, LaneQueueLengths, TransmissionLane,
|
||||
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
use client_core::client::replies::reply_controller::requests::ReplyControllerSender;
|
||||
use client_core::client::{
|
||||
inbound_messages::{InputMessage, InputMessageSender},
|
||||
received_buffer::{
|
||||
@@ -16,7 +17,9 @@ use log::*;
|
||||
use nymsphinx::addressing::clients::Recipient;
|
||||
use nymsphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nymsphinx::receiver::ReconstructedMessage;
|
||||
use std::time::Duration;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::Instant;
|
||||
use tokio_tungstenite::{
|
||||
accept_async,
|
||||
tungstenite::{protocol::Message as WsMessage, Error as WsError},
|
||||
@@ -41,6 +44,7 @@ pub(crate) struct HandlerBuilder {
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
self_full_address: Recipient,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
}
|
||||
|
||||
impl HandlerBuilder {
|
||||
@@ -50,6 +54,7 @@ impl HandlerBuilder {
|
||||
buffer_requester: ReceivedBufferRequestSender,
|
||||
self_full_address: &Recipient,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
) -> Self {
|
||||
Self {
|
||||
msg_input,
|
||||
@@ -57,6 +62,7 @@ impl HandlerBuilder {
|
||||
buffer_requester,
|
||||
self_full_address: *self_full_address,
|
||||
lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +76,7 @@ impl HandlerBuilder {
|
||||
socket: None,
|
||||
received_response_type: Default::default(),
|
||||
lane_queue_lengths: self.lane_queue_lengths.clone(),
|
||||
reply_controller_sender: self.reply_controller_sender.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +89,7 @@ pub(crate) struct Handler {
|
||||
socket: Option<WebSocketStream<TcpStream>>,
|
||||
received_response_type: ReceivedResponseType,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
}
|
||||
|
||||
impl Drop for Handler {
|
||||
@@ -97,6 +105,48 @@ impl Drop for Handler {
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
async fn get_lane_queue_length(&self, connection_id: ConnectionId) -> Option<ServerResponse> {
|
||||
let req_start = Instant::now();
|
||||
|
||||
// get the base queue length
|
||||
// Note that this does _NOT_ take into account the packets that have been received but not
|
||||
// yet reach `OutQueueControl`, so it might be a tad low.
|
||||
let conn_lane = TransmissionLane::ConnectionId(connection_id);
|
||||
let Ok(base_length) = self
|
||||
.lane_queue_lengths
|
||||
.lock()
|
||||
.map(|guard| guard.get(&conn_lane).unwrap_or_default()) else {
|
||||
// I'd argue we should panic here as this error it not recoverable
|
||||
error!("The lane queue length lock is poisoned!!");
|
||||
return None
|
||||
};
|
||||
|
||||
// get the number of pending replies waiting for reply surbs
|
||||
let reply_queue_length = self
|
||||
.reply_controller_sender
|
||||
.get_lane_queue_length(connection_id)
|
||||
.await;
|
||||
|
||||
let queue_length = base_length + reply_queue_length;
|
||||
|
||||
let time_taken = req_start.elapsed();
|
||||
let msg =
|
||||
format!("it took {time_taken:?} to get lane length for connection {connection_id}. The length is: {queue_length} = {base_length} (already queued up) + {reply_queue_length} (waiting for reply SURBs)");
|
||||
|
||||
if time_taken > Duration::from_millis(1) {
|
||||
info!("{msg}");
|
||||
} else if time_taken > Duration::from_millis(10) {
|
||||
warn!("{msg}");
|
||||
} else if time_taken > Duration::from_millis(50) {
|
||||
error!("{msg}");
|
||||
}
|
||||
|
||||
Some(ServerResponse::LaneQueueLength {
|
||||
lane: connection_id,
|
||||
queue_length,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_send(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
@@ -121,27 +171,11 @@ impl Handler {
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let connection_id = match lane {
|
||||
TransmissionLane::General
|
||||
| TransmissionLane::ReplySurbRequest
|
||||
| TransmissionLane::Retransmission
|
||||
| TransmissionLane::AdditionalReplySurbs => return None,
|
||||
TransmissionLane::ConnectionId(id) => id,
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
return None
|
||||
};
|
||||
|
||||
// on receiving a send, we reply back the current lane queue length for that connection id.
|
||||
// Note that this does _NOT_ take into account the packets that have been received but not
|
||||
// yet reach `OutQueueControl`, so it might be a tad low.
|
||||
if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() {
|
||||
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
|
||||
return Some(ServerResponse::LaneQueueLength {
|
||||
lane: connection_id,
|
||||
queue_length,
|
||||
});
|
||||
}
|
||||
|
||||
log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length");
|
||||
None
|
||||
self.get_lane_queue_length(connection_id).await
|
||||
}
|
||||
|
||||
async fn handle_send_anonymous(
|
||||
@@ -168,27 +202,11 @@ impl Handler {
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let connection_id = match lane {
|
||||
TransmissionLane::General
|
||||
| TransmissionLane::ReplySurbRequest
|
||||
| TransmissionLane::Retransmission
|
||||
| TransmissionLane::AdditionalReplySurbs => return None,
|
||||
TransmissionLane::ConnectionId(id) => id,
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
return None
|
||||
};
|
||||
|
||||
// on receiving a send, we reply back the current lane queue length for that connection id.
|
||||
// Note that this does _NOT_ take into account the packets that have been received but not
|
||||
// yet reach `OutQueueControl`, so it might be a tad low.
|
||||
if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() {
|
||||
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
|
||||
return Some(ServerResponse::LaneQueueLength {
|
||||
lane: connection_id,
|
||||
queue_length,
|
||||
});
|
||||
}
|
||||
|
||||
log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length");
|
||||
None
|
||||
self.get_lane_queue_length(connection_id).await
|
||||
}
|
||||
|
||||
async fn handle_reply(
|
||||
@@ -211,27 +229,11 @@ impl Handler {
|
||||
.expect("InputMessageReceiver has stopped receiving!");
|
||||
|
||||
// Only reply back with a `LaneQueueLength` if the sender providided a connection id
|
||||
let connection_id = match lane {
|
||||
TransmissionLane::General
|
||||
| TransmissionLane::ReplySurbRequest
|
||||
| TransmissionLane::Retransmission
|
||||
| TransmissionLane::AdditionalReplySurbs => return None,
|
||||
TransmissionLane::ConnectionId(id) => id,
|
||||
let TransmissionLane::ConnectionId(connection_id) = lane else {
|
||||
return None
|
||||
};
|
||||
|
||||
// on receiving a send, we reply back the current lane queue length for that connection id.
|
||||
// Note that this does _NOT_ take into account the packets that have been received but not
|
||||
// yet reach `OutQueueControl`, so it might be a tad low.
|
||||
if let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() {
|
||||
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
|
||||
return Some(ServerResponse::LaneQueueLength {
|
||||
lane: connection_id,
|
||||
queue_length,
|
||||
});
|
||||
}
|
||||
|
||||
log::warn!("Failed to get the lane queue length lock, not responding back with the current queue length");
|
||||
None
|
||||
self.get_lane_queue_length(connection_id).await
|
||||
}
|
||||
|
||||
fn handle_self_address(&self) -> ServerResponse {
|
||||
@@ -245,20 +247,8 @@ impl Handler {
|
||||
None
|
||||
}
|
||||
|
||||
fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option<ServerResponse> {
|
||||
let Ok(lane_queue_lengths) = self.lane_queue_lengths.lock() else {
|
||||
log::warn!(
|
||||
"Failed to get the lane queue length lock, not responding back with the current queue length"
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
let lane = TransmissionLane::ConnectionId(connection_id);
|
||||
let queue_length = lane_queue_lengths.get(&lane).unwrap_or(0);
|
||||
Some(ServerResponse::LaneQueueLength {
|
||||
lane: connection_id,
|
||||
queue_length,
|
||||
})
|
||||
async fn handle_get_lane_queue_length(&self, connection_id: u64) -> Option<ServerResponse> {
|
||||
self.get_lane_queue_length(connection_id).await
|
||||
}
|
||||
|
||||
async fn handle_request(&mut self, request: ClientRequest) -> Option<ServerResponse> {
|
||||
@@ -287,7 +277,7 @@ impl Handler {
|
||||
|
||||
ClientRequest::SelfAddress => Some(self.handle_self_address()),
|
||||
ClientRequest::ClosedConnection(id) => self.handle_closed_connection(id),
|
||||
ClientRequest::GetLaneQueueLength(id) => self.handle_get_lane_queue_length(id),
|
||||
ClientRequest::GetLaneQueueLength(id) => self.handle_get_lane_queue_length(id).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.3"
|
||||
version = "1.1.5"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub type ConnectionId = u64;
|
||||
|
||||
|
||||
@@ -9,10 +9,11 @@ use nym_api_requests::coconut::{
|
||||
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
|
||||
};
|
||||
use nym_api_requests::models::{
|
||||
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
|
||||
InclusionProbabilityResponse, MixNodeBondAnnotated, MixnodeCoreStatusResponse,
|
||||
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RequestError,
|
||||
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
|
||||
ComputeRewardEstParam, GatewayCoreStatusResponse, GatewayStatusReportResponse,
|
||||
GatewayUptimeHistoryResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
|
||||
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
|
||||
MixnodeUptimeHistoryResponse, RequestError, RewardEstimationResponse, StakeSaturationResponse,
|
||||
UptimeResponse,
|
||||
};
|
||||
use reqwest::Response;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -361,6 +362,25 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn compute_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
request_body: &ComputeRewardEstParam,
|
||||
) -> Result<RewardEstimationResponse, NymAPIError> {
|
||||
self.post_nym_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
routes::COMPUTE_REWARD_ESTIMATION,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_stake_saturation(
|
||||
&self,
|
||||
mix_id: MixId,
|
||||
|
||||
@@ -28,6 +28,7 @@ pub const STATUS: &str = "status";
|
||||
pub const REPORT: &str = "report";
|
||||
pub const HISTORY: &str = "history";
|
||||
pub const REWARD_ESTIMATION: &str = "reward-estimation";
|
||||
pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation";
|
||||
pub const AVG_UPTIME: &str = "avg_uptime";
|
||||
pub const STAKE_SATURATION: &str = "stake-saturation";
|
||||
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
|
||||
|
||||
@@ -166,7 +166,7 @@ fn try_parse_abci_log(log: &abci::Log) -> Option<String> {
|
||||
.value()
|
||||
.contains("Maximum amount of locked coins has already been pledged")
|
||||
{
|
||||
Some("Maximum amount of locked tokens has alredy been used. You can only use up tp 10% of your locked tokens for bonding and delegating.".to_string())
|
||||
Some("Maximum amount of locked tokens has alredy been used. You can only use up to 10% of your locked tokens for bonding and delegating.".to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -208,6 +208,11 @@ impl Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the size of payload contained in this `Fragment`.
|
||||
pub fn payload_size(&self) -> usize {
|
||||
self.payload.len()
|
||||
}
|
||||
|
||||
/// Extracts id of this `Fragment`.
|
||||
pub fn id(&self) -> i32 {
|
||||
self.header.id
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
# The path to the geoip database file
|
||||
GEOIP_DB_PATH=./geo_ip/GeoLite2-Country.mmdb
|
||||
GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -40,7 +40,7 @@ It should be previously installed thanks to `geoipupdate` service.
|
||||
For example:
|
||||
|
||||
```shell
|
||||
GEOIP_DB_PATH=./geo_ip/GeoLite2-Country.mmdb cargo run
|
||||
GEOIP_DB_PATH=./geo_ip/GeoLite2-City.mmdb cargo run
|
||||
```
|
||||
|
||||
Note: explorer-api binary reads the provided `.env` file.
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::geo_ip::location::Location;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use rocket::response::status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
|
||||
pub fn nym_terms_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![settings: terms]
|
||||
}
|
||||
|
||||
#[openapi(tag = "terms")]
|
||||
#[get("/")]
|
||||
pub(crate) async fn terms(
|
||||
_state: &State<ExplorerApiStateContext>,
|
||||
location: Location,
|
||||
) -> Result<Json<String>, status::Forbidden<String>> {
|
||||
if location.iso_alpha2 == "US" {
|
||||
return Err(status::Forbidden(Some("US government sucks".to_string())));
|
||||
}
|
||||
Ok(Json("Nym Terms & Conditions: Welcome".to_string()))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
pub(crate) mod http;
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
use isocountry::CountryCode;
|
||||
use log::warn;
|
||||
use maxminddb::{geoip2::Country, MaxMindDBError, Reader};
|
||||
use maxminddb::{geoip2::City, MaxMindDBError, Reader};
|
||||
use std::{
|
||||
net::{IpAddr, ToSocketAddrs},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
const DEFAULT_DATABASE_PATH: &str = "./geo_ip/GeoLite2-Country.mmdb";
|
||||
const DEFAULT_DATABASE_PATH: &str = "./geo_ip/GeoLite2-City.mmdb";
|
||||
const FAKE_PORT: u16 = 1234;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -38,6 +38,8 @@ pub(crate) struct Location {
|
||||
pub(crate) iso_alpha3: String,
|
||||
/// English country short name (ISO 3166-1)
|
||||
pub(crate) name: String,
|
||||
pub(crate) latitude: Option<f64>,
|
||||
pub(crate) longitude: Option<f64>,
|
||||
}
|
||||
|
||||
impl GeoIp {
|
||||
@@ -86,7 +88,7 @@ impl GeoIp {
|
||||
error!("No registered GeoIP database");
|
||||
GeoIpError::InternalError
|
||||
})?
|
||||
.lookup::<Country>(ip);
|
||||
.lookup::<City>(ip);
|
||||
match &result {
|
||||
Ok(v) => Ok(Some(
|
||||
Location::try_from(v).map_err(|_| GeoIpError::InternalError)?,
|
||||
@@ -99,11 +101,11 @@ impl GeoIp {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&Country<'a>> for Location {
|
||||
impl<'a> TryFrom<&City<'a>> for Location {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(country: &Country) -> Result<Self, Self::Error> {
|
||||
let data = country.country.as_ref().ok_or_else(|| {
|
||||
fn try_from(city: &City) -> Result<Self, Self::Error> {
|
||||
let data = city.country.as_ref().ok_or_else(|| {
|
||||
warn!("No Country data found");
|
||||
"No Country data found"
|
||||
})?;
|
||||
@@ -119,10 +121,13 @@ impl<'a> TryFrom<&Country<'a>> for Location {
|
||||
warn!("{}", &message);
|
||||
message
|
||||
})?;
|
||||
|
||||
Ok(Location {
|
||||
iso_alpha2,
|
||||
iso_alpha3: String::from(iso_codes.alpha3()),
|
||||
name: String::from(iso_codes.name()),
|
||||
latitude: city.location.as_ref().and_then(|l| l.latitude),
|
||||
longitude: city.location.as_ref().and_then(|l| l.longitude),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use rocket::{Build, Request, Rocket};
|
||||
use rocket_cors::{AllowedHeaders, AllowedOrigins};
|
||||
use rocket_okapi::swagger_ui::make_swagger_ui;
|
||||
|
||||
use crate::buy_terms::http::nym_terms_make_default_routes;
|
||||
use crate::country_statistics::http::country_statistics_make_default_routes;
|
||||
use crate::gateways::http::gateways_make_default_routes;
|
||||
use crate::http::swagger::get_docs;
|
||||
@@ -57,7 +56,6 @@ fn configure_rocket(state: ExplorerApiStateContext) -> Rocket<Build> {
|
||||
"/overview" => overview_make_default_routes(&openapi_settings),
|
||||
"/ping" => ping_make_default_routes(&openapi_settings),
|
||||
"/validators" => validators_make_default_routes(&openapi_settings),
|
||||
"/terms" => nym_terms_make_default_routes(&openapi_settings),
|
||||
};
|
||||
|
||||
building_rocket
|
||||
|
||||
@@ -10,7 +10,6 @@ use logging::setup_logging;
|
||||
use network_defaults::setup_env;
|
||||
use task::TaskManager;
|
||||
|
||||
mod buy_terms;
|
||||
pub(crate) mod cache;
|
||||
mod client;
|
||||
pub(crate) mod commands;
|
||||
|
||||
@@ -36,6 +36,8 @@ pub(crate) struct Location {
|
||||
pub(crate) two_letter_iso_country_code: String,
|
||||
pub(crate) three_letter_iso_country_code: String,
|
||||
pub(crate) country_name: String,
|
||||
pub(crate) latitude: Option<f64>,
|
||||
pub(crate) longitude: Option<f64>,
|
||||
}
|
||||
|
||||
impl Location {
|
||||
@@ -44,6 +46,8 @@ impl Location {
|
||||
country_name: location.name,
|
||||
two_letter_iso_country_code: location.iso_alpha2,
|
||||
three_letter_iso_country_code: location.iso_alpha3,
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
## UNRELEASED
|
||||
|
||||
## [nym-explorer-v1.0.1](https://github.com/nymtech/nym/tree/nym-explorer-v1.0.1) (2023-01-10)
|
||||
|
||||
- Feat/2161 ne gate version by @gala1234 in https://github.com/nymtech/nym/pull/2743
|
||||
- fix(explorer): set gateway bond 6 decimals by @doums in https://github.com/nymtech/nym/pull/2741
|
||||
- Feat/2130 tables update rebase by @gala1234 in https://github.com/nymtech/nym/pull/2742
|
||||
- fix(explorer,explorer-api): mixnode location by @doums in https://github.com/nymtech/nym-api
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/network-explorer",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Tooltip } from '@nymproject/react/tooltip/Tooltip';
|
||||
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
|
||||
import { Box } from '@mui/system';
|
||||
import { cellStyles } from './Universal-DataGrid';
|
||||
import { currencyToString } from '../utils/currency';
|
||||
import { unymToNym } from '../utils/currency';
|
||||
import { GatewayEnrichedRowType } from './Gateways';
|
||||
import { MixnodeRowType } from './MixNodes';
|
||||
|
||||
@@ -38,7 +38,7 @@ function formatCellValues(val: string | number, field: string) {
|
||||
);
|
||||
}
|
||||
if (field === 'bond') {
|
||||
return currencyToString(val.toString());
|
||||
return unymToNym(val, 6);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export type GatewayRowType = {
|
||||
bond: number;
|
||||
host: string;
|
||||
location: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type GatewayEnrichedRowType = GatewayRowType & {
|
||||
@@ -26,6 +27,7 @@ export function gatewayToGridRow(arrayOfGateways: GatewayResponse): GatewayRowTy
|
||||
location: gw?.gateway?.location || '',
|
||||
bond: gw.pledge_amount.amount || 0,
|
||||
host: gw.gateway.host || '',
|
||||
version: gw.gateway.version || '',
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -40,6 +42,7 @@ export function gatewayEnrichedToGridRow(
|
||||
location: gateway?.gateway?.location || '',
|
||||
bond: gateway.pledge_amount.amount || 0,
|
||||
host: gateway.gateway.host || '',
|
||||
version: gateway.gateway.version || '',
|
||||
clientsPort: gateway.gateway.clients_port || 0,
|
||||
mixPort: gateway.gateway.mix_port || 0,
|
||||
routingScore: `${report.most_recent}%`,
|
||||
|
||||
@@ -4,6 +4,8 @@ import Select, { SelectChangeEvent } from '@mui/material/Select';
|
||||
import { Filters } from './Filters/Filters';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
|
||||
const fieldsHeight = '42.25px';
|
||||
|
||||
type TableToolBarProps = {
|
||||
onChangeSearch: (arg: string) => void;
|
||||
onChangePageSize: (event: SelectChangeEvent<string>) => void;
|
||||
@@ -35,7 +37,7 @@ export const TableToolbar: React.FC<TableToolBarProps> = ({
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flexDirection: isMobile ? 'column-reverse' : 'row', alignItems: 'middle' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', height: fieldsHeight }}>
|
||||
{childrenBefore}
|
||||
<Select
|
||||
labelId="simple-select-label"
|
||||
@@ -65,6 +67,9 @@ export const TableToolbar: React.FC<TableToolBarProps> = ({
|
||||
sx={{
|
||||
width: isMobile ? '100%' : 200,
|
||||
marginBottom: isMobile ? 2 : 0,
|
||||
'& > :not(style)': {
|
||||
height: fieldsHeight,
|
||||
},
|
||||
}}
|
||||
value={searchTerm}
|
||||
data-testid="search-box"
|
||||
|
||||
@@ -58,6 +58,12 @@ const columns: ColumnsType[] = [
|
||||
headerAlign: 'left',
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: 'version',
|
||||
title: 'Version',
|
||||
headerAlign: 'left',
|
||||
flex: 1,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,7 @@ import { TableToolbar } from '../../components/TableToolbar';
|
||||
import { CustomColumnHeading } from '../../components/CustomColumnHeading';
|
||||
import { Title } from '../../components/Title';
|
||||
import { cellStyles, UniversalDataGrid } from '../../components/Universal-DataGrid';
|
||||
import { currencyToString } from '../../utils/currency';
|
||||
import { unymToNym } from '../../utils/currency';
|
||||
import { Tooltip } from '../../components/Tooltip';
|
||||
import { BIG_DIPPER } from '../../api/constants';
|
||||
import { splice } from '../../utils';
|
||||
@@ -77,7 +77,7 @@ export const PageGateways: React.FC = () => {
|
||||
to={`/network-components/gateway/${params.row.identityKey}`}
|
||||
data-testid="pledge-amount"
|
||||
>
|
||||
{currencyToString(params.value)}
|
||||
{unymToNym(params.value, 6)}
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
@@ -128,7 +128,7 @@ export const PageGateways: React.FC = () => {
|
||||
field: 'owner',
|
||||
headerName: 'Owner',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Owner" />,
|
||||
width: 380,
|
||||
width: 180,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
@@ -142,6 +142,24 @@ export const PageGateways: React.FC = () => {
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'version',
|
||||
headerName: 'Version',
|
||||
renderHeader: () => <CustomColumnHeading headingTitle="Version" />,
|
||||
width: 150,
|
||||
headerAlign: 'left',
|
||||
headerClassName: 'MuiDataGrid-header-override',
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<MuiLink
|
||||
sx={{ ...cellStyles }}
|
||||
href={`${BIG_DIPPER}/account/${params.value}`}
|
||||
target="_blank"
|
||||
data-testid="owner"
|
||||
>
|
||||
{params.value}
|
||||
</MuiLink>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const handlePageSize = (event: SelectChangeEvent<string>) => {
|
||||
|
||||
@@ -164,10 +164,10 @@ const PageMixnodeDetailWithState: React.FC = () => {
|
||||
{mixNode && (
|
||||
<ContentCard title="Location">
|
||||
{mixNode?.error && <ComponentError text="There was a problem retrieving this mixnode location" />}
|
||||
{mixNode.data && mixNode?.data?.location && (
|
||||
{mixNode?.data?.location?.latitude && mixNode?.data?.location?.longitude && (
|
||||
<WorldMap
|
||||
loading={mixNode.isLoading}
|
||||
userLocation={[mixNode?.data?.location?.lng, mixNode?.data?.location?.lat]}
|
||||
userLocation={[mixNode.data.location.longitude, mixNode.data.location.latitude]}
|
||||
/>
|
||||
)}
|
||||
</ContentCard>
|
||||
|
||||
@@ -77,8 +77,8 @@ export interface MixNodeResponseItem {
|
||||
status: MixnodeStatus;
|
||||
location: {
|
||||
country_name: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
three_letter_iso_country_code: string;
|
||||
two_letter_iso_country_code: string;
|
||||
};
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.3"
|
||||
version = "1.1.5"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.3"
|
||||
version = "1.1.5"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-validator-api"
|
||||
version = "1.1.3"
|
||||
version = "1.1.5"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -114,7 +114,7 @@ impl MixNodeBondAnnotated {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ComputeRewardEstParam {
|
||||
pub performance: Option<Performance>,
|
||||
pub active_in_rewarded_set: Option<bool>,
|
||||
|
||||
@@ -30,6 +30,19 @@ module.exports = {
|
||||
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()];
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 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 +1,25 @@
|
||||
## UNRELEASED
|
||||
|
||||
## [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
|
||||
- Feat/2130 tables update rebase by @gala1234 in https://github.com/nymtech/nym/pull/2742
|
||||
|
||||
## [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-11-29)
|
||||
## [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.
|
||||
|
||||
|
||||
Generated
+230
-92
@@ -14,12 +14,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
@@ -469,12 +463,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cargo_toml"
|
||||
version = "0.11.6"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4419e9adae9fd7e231b60d50467481bf8181ddeef6ed54683b23ae925c74c9c"
|
||||
checksum = "aa0e3586af56b3bfa51fca452bd56e8dbbbd5d8d81cbf0b7e4e35b695b537eb8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"toml",
|
||||
]
|
||||
|
||||
@@ -639,7 +632,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"client-connections",
|
||||
@@ -754,6 +747,17 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"lazy_static",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
@@ -1316,13 +1320,14 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate"
|
||||
version = "0.7.20"
|
||||
name = "dbus"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4"
|
||||
checksum = "6f8bcdd56d2e5c4ed26a529c5a9029f5db8290d433497506f958eae3be148eb6"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"byteorder",
|
||||
"libc",
|
||||
"libdbus-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1471,6 +1476,12 @@ dependencies = [
|
||||
"dtoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.9"
|
||||
@@ -1632,6 +1643,16 @@ dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fern"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a"
|
||||
dependencies = [
|
||||
"colored 1.9.3",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.10.1"
|
||||
@@ -2620,12 +2641,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ico"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a4b3331534254a9b64095ae60d3dc2a8225a7a70229cd5888be127cdc1f6804"
|
||||
checksum = "031530fe562d8c8d71c0635013d6d155bbfe8ba0aa4b4d2d24ce8af6b71047bd"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"png 0.11.0",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2702,15 +2723,6 @@ dependencies = [
|
||||
"cfb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inflate"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5f9f47468e9a76a6452271efadc88fe865a82be91fe75e6c0c57b87ccea59d4"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
@@ -2749,9 +2761,9 @@ checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.4"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8bf247779e67a9082a4790b45e71ac7cfd1321331a5c856a74a9faebdab78d0"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
@@ -2793,9 +2805,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
|
||||
checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c"
|
||||
dependencies = [
|
||||
"cesu8",
|
||||
"combine",
|
||||
@@ -2914,6 +2926,15 @@ version = "0.2.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c185b5b7ad900923ef3a8ff594083d4d9b5aea80bb4f32b8342363138c0d456b"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.14.0+1.5.0"
|
||||
@@ -2974,6 +2995,12 @@ dependencies = [
|
||||
"safemem",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "lioness"
|
||||
version = "0.1.2"
|
||||
@@ -3003,6 +3030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3034,6 +3062,19 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "mac-notification-sys"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e72d50edb17756489e79d52eb146927bec8eba9dd48faadf9ef08bca3791ad5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"dirs-next",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"time 0.3.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
@@ -3255,6 +3296,17 @@ dependencies = [
|
||||
"wasm-timer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-rust"
|
||||
version = "4.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368e89ea58df747ce88be669ae44e79783c1d30bfd540ad0fc520b3f41f0b3b0"
|
||||
dependencies = [
|
||||
"dbus",
|
||||
"mac-notification-sys",
|
||||
"tauri-winrt-notification",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.3.3"
|
||||
@@ -3276,17 +3328,6 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.1"
|
||||
@@ -3357,21 +3398,28 @@ dependencies = [
|
||||
name = "nym-connect"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
"chrono",
|
||||
"client-core",
|
||||
"config",
|
||||
"crypto",
|
||||
"dirs",
|
||||
"eyre",
|
||||
"fern",
|
||||
"fix-path-env",
|
||||
"futures",
|
||||
"itertools",
|
||||
"log",
|
||||
"logging",
|
||||
"nym-socks5-client",
|
||||
"pretty_env_logger",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tap",
|
||||
"task",
|
||||
"tauri",
|
||||
@@ -3385,11 +3433,12 @@ dependencies = [
|
||||
"topology",
|
||||
"ts-rs",
|
||||
"url",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"client-connections",
|
||||
@@ -3612,9 +3661,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.14.0"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
@@ -4065,18 +4114,6 @@ dependencies = [
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0b0cabbbd20c2d7f06dbf015e06aad59b6ca3d9ed14848783e98af9aaf19925"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"deflate",
|
||||
"inflate",
|
||||
"num-iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.6"
|
||||
@@ -4221,6 +4258,15 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
@@ -4473,9 +4519,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -4535,6 +4581,7 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-socks",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
@@ -4604,6 +4651,41 @@ dependencies = [
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "6.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "6.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "7.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"sha2 0.10.6",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.3.3"
|
||||
@@ -5459,6 +5541,27 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7ac893c7d471c8a21f31cfe213ec4f6d9afeed25537c772e08ef3f005f8729e"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "339f799d8b549e3744c7ac7feb216383e4005d94bdb22561b3ab8f3b808ae9fb"
|
||||
dependencies = [
|
||||
"heck 0.3.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "1.0.0"
|
||||
@@ -5531,9 +5634,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.14.0"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43336f5d1793543ba96e2a1e75f3a5c7dcd592743be06a0ab3a190f4fcb4b934"
|
||||
checksum = "a1fa15735311b4816d030ff54da58560b047daca0970e1031aed5502e84231a8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
@@ -5566,12 +5669,12 @@ dependencies = [
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"paste",
|
||||
"png 0.17.6",
|
||||
"png",
|
||||
"raw-window-handle",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
"unicode-segmentation",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"windows 0.39.0",
|
||||
"windows-implement",
|
||||
"x11-dl",
|
||||
@@ -5608,9 +5711,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "1.1.1"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efbf22abd61d95ca9b2becd77f9db4c093892f73e8a07d21d8b0b2bf71a7bcea"
|
||||
checksum = "d8ea1d785ab2164373703817bff144c4610a69ad3f659becaca0e1ea004b98d8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"attohttpc",
|
||||
@@ -5628,6 +5731,7 @@ dependencies = [
|
||||
"http",
|
||||
"ignore",
|
||||
"minisign-verify",
|
||||
"notify-rust",
|
||||
"objc",
|
||||
"once_cell",
|
||||
"open",
|
||||
@@ -5652,7 +5756,7 @@ dependencies = [
|
||||
"time 0.3.17",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
@@ -5661,9 +5765,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "1.1.1"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0991fb306849897439dbd4a72e4cbed2413e2eb26cb4b3ba220b94edba8b4b88"
|
||||
checksum = "8807c85d656b2b93927c19fe5a5f1f1f348f96c2de8b90763b3c2d561511f9b4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@@ -5677,16 +5781,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "1.1.1"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "356fa253e40ae4d6ff02075011f2f2bb4066f5c9d8c1e16ca6912d7b75903ba6"
|
||||
checksum = "14388d484b6b1b5dc0f6a7d6cc6433b3b230bec85eaa576adcdf3f9fafa49251"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"brotli",
|
||||
"ico",
|
||||
"json-patch",
|
||||
"plist",
|
||||
"png 0.17.6",
|
||||
"png",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
@@ -5697,15 +5801,15 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"time 0.3.17",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "1.1.1"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6051fd6940ddb22af452340d03c66a3e2f5d72e0788d4081d91e31528ccdc4d"
|
||||
checksum = "069319e5ecbe653a799b94b0690d9f9bf5d00f7b1d3989aa331c524d4e354075"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"proc-macro2",
|
||||
@@ -5717,30 +5821,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "0.11.1"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d49439a5ea47f474572b854972f42eda2e02a470be5ca9609cc83bb66945abe2"
|
||||
checksum = "c507d954d08ac8705d235bc70ec6975b9054fb95ff7823af72dbb04186596f3b"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
"http-range",
|
||||
"infer",
|
||||
"rand 0.8.5",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "0.11.1"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dce920995fd49907aa9bea7249ed1771454f11f7611924c920a1f75fb614d4"
|
||||
checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"gtk",
|
||||
@@ -5749,7 +5852,7 @@ dependencies = [
|
||||
"raw-window-handle",
|
||||
"tauri-runtime",
|
||||
"tauri-utils",
|
||||
"uuid 1.1.2",
|
||||
"uuid 1.2.2",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows 0.39.0",
|
||||
@@ -5758,15 +5861,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.1.1"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e8fdae6f29cef959809a3c3afef510c5b715a446a597ab8b791497585363f39"
|
||||
checksum = "5abbc109a6eb45127956ffcc26ef0e875d160150ac16cfa45d26a6b2871686f1"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"ctor",
|
||||
"glob",
|
||||
"heck 0.4.0",
|
||||
"html5ever",
|
||||
"infer",
|
||||
"json-patch",
|
||||
"kuchiki",
|
||||
"memchr",
|
||||
@@ -5783,6 +5887,17 @@ dependencies = [
|
||||
"windows 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-winrt-notification"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c58de036c4d2e20717024de2a3c4bf56c301f07b21bc8ef9b57189fce06f1f3b"
|
||||
dependencies = [
|
||||
"quick-xml",
|
||||
"strum",
|
||||
"windows 0.39.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
@@ -6055,6 +6170,18 @@ dependencies = [
|
||||
"webpki 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-socks"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures-util",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.10"
|
||||
@@ -6346,9 +6473,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.1.2"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
|
||||
checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.7",
|
||||
]
|
||||
@@ -6363,7 +6490,7 @@ dependencies = [
|
||||
"coconut-bandwidth-contract-common",
|
||||
"coconut-dkg-common",
|
||||
"coconut-interface",
|
||||
"colored",
|
||||
"colored 2.0.0",
|
||||
"config",
|
||||
"contracts-common",
|
||||
"cosmrs",
|
||||
@@ -6634,9 +6761,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk"
|
||||
version = "0.18.0"
|
||||
version = "0.18.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29952969fb5e10fe834a52eb29ad0814ccdfd8387159b0933edf1344a1c9cdcc"
|
||||
checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
@@ -7011,15 +7138,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.21.1"
|
||||
version = "0.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff5c1352b4266fdf92c63479d2f58ab4cd29dc4e78fbc1b62011ed1227926945"
|
||||
checksum = "4c1ad8e2424f554cc5bdebe8aa374ef5b433feff817aebabca0389961fc7ef98"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"block",
|
||||
"cocoa",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dunce",
|
||||
"gdk",
|
||||
"gio",
|
||||
"glib",
|
||||
@@ -7035,6 +7163,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.6",
|
||||
"soup2",
|
||||
"tao",
|
||||
"thiserror",
|
||||
"url",
|
||||
@@ -7101,6 +7230,15 @@ version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.5.7"
|
||||
@@ -7124,9 +7262,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "0.6.2"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf225bcf73bb52cbb496e70475c7bd7a3f769df699c0020f6c7bd9a96dcf0b8d"
|
||||
checksum = "537ce7411d25e54e8ae21a7ce0b15840e7bfcff15b51d697ec3266cc76bdf080"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc32fast",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "@nym/nym-connect",
|
||||
"version": "1.1.3",
|
||||
"version": "1.1.5",
|
||||
"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",
|
||||
@@ -30,7 +31,7 @@
|
||||
"@mui/material": "^5.2.2",
|
||||
"@mui/styles": "^5.2.2",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@tauri-apps/api": "^1.1.0",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
|
||||
"clsx": "^1.1.1",
|
||||
"luxon": "^2.3.0",
|
||||
@@ -39,6 +40,7 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-boundary": "^3.1.3",
|
||||
"react-hook-form": "^7.14.2",
|
||||
"react-markdown": "^8.0.4",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"semver": "^6.3.0",
|
||||
"yup": "^0.32.9"
|
||||
@@ -49,11 +51,12 @@
|
||||
"@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.8",
|
||||
"@svgr/webpack": "^6.1.1",
|
||||
"@tauri-apps/cli": "^1.1.0",
|
||||
"@tauri-apps/cli": "^1.2.2",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@types/jest": "^27.0.1",
|
||||
@@ -105,6 +108,7 @@
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-dev-server": "^4.5.0",
|
||||
"webpack-favicons": "^1.3.8",
|
||||
"webpack-merge": "^5.8.0"
|
||||
"webpack-merge": "^5.8.0",
|
||||
"yaml-loader": "^0.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<!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>
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>nym-connect</title>
|
||||
<title>Nym Connect</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<!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,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-connect"
|
||||
version = "1.1.4"
|
||||
version = "1.1.5"
|
||||
description = "nym-connect"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
@@ -13,33 +13,40 @@ 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.1.1", features = [] }
|
||||
tauri-build = { version = "^1.2.1", features = [] }
|
||||
|
||||
tauri-codegen = "^1.1.1"
|
||||
tauri-macros = "^1.1.1"
|
||||
tauri-codegen = "^1.2.1"
|
||||
tauri-macros = "^1.2.1"
|
||||
|
||||
[dependencies]
|
||||
|
||||
anyhow = "1.0"
|
||||
bip39 = "1.0"
|
||||
chrono = "0.4"
|
||||
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"
|
||||
log = "0.4"
|
||||
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", features = ["json"] }
|
||||
reqwest = { version = "0.11", 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"
|
||||
tauri = { version = "^1.1.1", features = ["clipboard-write-text", "macos-private-api", "shell-open", "system-tray", "updater", "window-close", "window-minimize", "window-start-dragging"] }
|
||||
tauri = { version = "^1.2.2", 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"
|
||||
tokio = { version = "1.21.2", features = ["sync", "time"] }
|
||||
url = "2.2"
|
||||
yaml-rust = "0.4"
|
||||
|
||||
client-core = { path = "../../clients/client-core" }
|
||||
config-common = { path = "../../common/config", package = "config" }
|
||||
crypto = { path = "../../common/crypto" }
|
||||
logging = { path = "../../common/logging"}
|
||||
nym-socks5-client = { path = "../../clients/socks5" }
|
||||
task = { path = "../../common/task" }
|
||||
|
||||
@@ -44,6 +44,13 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn new_with_port<S: Into<String>>(id: S, provider_mix_address: S, port: u16) -> Self {
|
||||
Config {
|
||||
socks5: Socks5Config::new(id, provider_mix_address).with_port(port),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_socks5(&self) -> &Socks5Config {
|
||||
&self.socks5
|
||||
}
|
||||
|
||||
@@ -27,6 +27,11 @@ pub enum BackendError {
|
||||
source: tauri::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
TauriApiError {
|
||||
#[from]
|
||||
source: tauri::api::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
SerdeJsonError {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
@@ -36,6 +41,11 @@ pub enum BackendError {
|
||||
#[from]
|
||||
source: ClientCoreError<fs_backend::Backend>,
|
||||
},
|
||||
#[error("{source}")]
|
||||
ApiClientError {
|
||||
#[from]
|
||||
source: crate::operations::growth::api_client::ApiClientError,
|
||||
},
|
||||
|
||||
#[error("Could not send disconnect signal to the SOCKS5 client")]
|
||||
CoundNotSendDisconnectSignal,
|
||||
@@ -57,6 +67,8 @@ pub enum BackendError {
|
||||
CouldNotGetConfigFilename,
|
||||
#[error("Could not load existing gateway configuration")]
|
||||
CouldNotLoadExistingGatewayConfiguration(std::io::Error),
|
||||
#[error("Unable to open a new window")]
|
||||
NewWindowError,
|
||||
}
|
||||
|
||||
impl Serialize for BackendError {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use fern::colors::ColoredLevelConfig;
|
||||
use serde::Serialize;
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use tauri::Manager;
|
||||
|
||||
pub fn setup_logging(app_handle: tauri::AppHandle) -> Result<(), log::SetLoggerError> {
|
||||
let colors = ColoredLevelConfig::new();
|
||||
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!(
|
||||
"{}[{}][{}] {}",
|
||||
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
|
||||
record.target(),
|
||||
colors.color(record.level()),
|
||||
message,
|
||||
))
|
||||
})
|
||||
.chain(std::io::stdout());
|
||||
|
||||
let tauri_event_config = fern::Dispatch::new()
|
||||
.format(move |out, message, record| {
|
||||
out.finish(format_args!(
|
||||
"{}[{}] {}",
|
||||
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
|
||||
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();
|
||||
}));
|
||||
|
||||
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("hyper", log::LevelFilter::Warn)
|
||||
.level_for("tokio_reactor", log::LevelFilter::Warn)
|
||||
.level_for("reqwest", log::LevelFilter::Warn)
|
||||
.level_for("mio", log::LevelFilter::Warn)
|
||||
.level_for("want", log::LevelFilter::Warn)
|
||||
.level_for("sled", log::LevelFilter::Warn)
|
||||
.level_for("tungstenite", log::LevelFilter::Warn)
|
||||
.level_for("tokio_tungstenite", log::LevelFilter::Warn)
|
||||
.level_for("rustls", log::LevelFilter::Warn)
|
||||
.level_for("tokio_util", 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use config_common::defaults::setup_env;
|
||||
use logging::setup_logging;
|
||||
use tauri::Menu;
|
||||
use tauri::{Manager, Menu};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::menu::AddDefaultSubmenus;
|
||||
@@ -17,6 +16,7 @@ use crate::window::window_toggle;
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
mod logging;
|
||||
mod menu;
|
||||
mod models;
|
||||
mod operations;
|
||||
@@ -25,7 +25,6 @@ mod tasks;
|
||||
mod window;
|
||||
|
||||
fn main() {
|
||||
setup_logging();
|
||||
setup_env(None);
|
||||
println!("Starting up...");
|
||||
|
||||
@@ -35,11 +34,13 @@ fn main() {
|
||||
log::warn!("Failed to fix PATH: {error}");
|
||||
}
|
||||
|
||||
let context = tauri::generate_context!();
|
||||
tauri::Builder::default()
|
||||
.manage(Arc::new(RwLock::new(State::new())))
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
crate::config::get_config_file_location,
|
||||
crate::config::get_config_id,
|
||||
crate::operations::connection::status::get_connection_status,
|
||||
crate::operations::connection::connect::get_gateway,
|
||||
crate::operations::connection::connect::get_service_provider,
|
||||
crate::operations::connection::connect::set_gateway,
|
||||
@@ -49,10 +50,31 @@ fn main() {
|
||||
crate::operations::directory::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,
|
||||
])
|
||||
.menu(Menu::new().add_default_app_submenu_if_macos())
|
||||
.menu(Menu::os_default(&context.package_info().name).add_default_app_submenus())
|
||||
.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(|app| Ok(crate::logging::setup_logging(app.app_handle())?))
|
||||
.system_tray(create_tray_menu())
|
||||
.on_system_tray_event(tray_menu_event_handler)
|
||||
.run(tauri::generate_context!())
|
||||
.run(context)
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -1,40 +1,24 @@
|
||||
use crate::window_toggle;
|
||||
use tauri::{
|
||||
AppHandle, CustomMenuItem, Menu, SystemTray, SystemTrayEvent, SystemTrayMenu,
|
||||
AppHandle, CustomMenuItem, Menu, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu,
|
||||
SystemTrayMenuItem, Wry,
|
||||
};
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::{MenuItem, Submenu};
|
||||
|
||||
use crate::window_toggle;
|
||||
|
||||
pub const SHOW_LOG_WINDOW: &str = "show_log_window";
|
||||
pub const CLEAR_STORAGE: &str = "clear_storage";
|
||||
|
||||
pub trait AddDefaultSubmenus {
|
||||
fn add_default_app_submenu_if_macos(self) -> Self;
|
||||
fn add_default_app_submenus(self) -> Self;
|
||||
}
|
||||
|
||||
impl AddDefaultSubmenus for Menu {
|
||||
fn add_default_app_submenu_if_macos(self) -> Menu {
|
||||
#[cfg(target_os = "macos")]
|
||||
return self
|
||||
.add_submenu(Submenu::new(
|
||||
"File",
|
||||
Menu::new().add_native_item(MenuItem::Quit),
|
||||
))
|
||||
.add_submenu(Submenu::new(
|
||||
"Edit",
|
||||
Menu::new()
|
||||
.add_native_item(MenuItem::Copy)
|
||||
.add_native_item(MenuItem::Cut)
|
||||
.add_native_item(MenuItem::Paste)
|
||||
.add_native_item(MenuItem::SelectAll),
|
||||
))
|
||||
.add_submenu(Submenu::new(
|
||||
"Window",
|
||||
Menu::new()
|
||||
.add_native_item(MenuItem::Hide)
|
||||
.add_native_item(MenuItem::HideOthers)
|
||||
.add_native_item(MenuItem::ShowAll),
|
||||
));
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
return self;
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,21 +39,31 @@ impl fmt::Display for ConnectionStatusKind {
|
||||
pub const APP_EVENT_CONNECTION_STATUS_CHANGED: &str = "app:connection-status-changed";
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
#[derive(Clone, Serialize)]
|
||||
pub struct AppEventConnectionStatusChangedPayload {
|
||||
pub status: ConnectionStatusKind,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[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 gateway_identity_key: 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, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DirectoryServiceProvider {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
@@ -63,3 +73,11 @@ pub struct DirectoryServiceProvider {
|
||||
/// Address of the gateway, e.g. 2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
|
||||
pub gateway: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct PagedResult<T> {
|
||||
pub page: u32,
|
||||
pub size: u32,
|
||||
pub total: i32,
|
||||
pub items: Vec<T>,
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ pub async fn get_service_provider(state: tauri::State<'_, Arc<RwLock<State>>>) -
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_service_provider(
|
||||
service_provider: String,
|
||||
service_provider: Option<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<()> {
|
||||
log::trace!("Setting service_provider: {service_provider}");
|
||||
log::trace!("Setting service_provider: {:?}", &service_provider);
|
||||
let mut guard = state.write().await;
|
||||
guard.set_service_provider(service_provider);
|
||||
Ok(())
|
||||
@@ -57,10 +57,10 @@ pub async fn get_gateway(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_gateway(
|
||||
gateway: String,
|
||||
gateway: Option<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<()> {
|
||||
log::trace!("Setting gateway: {gateway}");
|
||||
log::trace!("Setting gateway: {:?}", &gateway);
|
||||
let mut guard = state.write().await;
|
||||
guard.set_gateway(gateway);
|
||||
Ok(())
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod connect;
|
||||
pub mod disconnect;
|
||||
pub mod status;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
use crate::error::Result;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::models::ConnectionStatusKind;
|
||||
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())
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::models::DirectoryService;
|
||||
use crate::models::{DirectoryService, HarbourMasterService, PagedResult};
|
||||
|
||||
static SERVICE_PROVIDER_WELLKNOWN_URL: &str =
|
||||
"https://nymtech.net/.wellknown/connect/service-providers.json";
|
||||
|
||||
static HARBOUR_MASTER_URL: &str = "https://harbourmaster.nymtech.net/v1/services/?size=100";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
log::trace!("Fetching services");
|
||||
@@ -12,5 +16,36 @@ pub async fn get_services() -> Result<Vec<DirectoryService>> {
|
||||
.json::<Vec<DirectoryService>>()
|
||||
.await?;
|
||||
log::trace!("Received: {:#?}", res);
|
||||
Ok(res)
|
||||
|
||||
// TODO: get paged
|
||||
log::trace!("Fetching active services");
|
||||
let active_services = reqwest::get(HARBOUR_MASTER_URL)
|
||||
.await?
|
||||
.json::<PagedResult<HarbourMasterService>>()
|
||||
.await?;
|
||||
log::trace!("Active: {:#?}", active_services);
|
||||
|
||||
let mut filtered: Vec<DirectoryService> = vec![];
|
||||
|
||||
for service in &res {
|
||||
let items: _ = service
|
||||
.items
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|sp| {
|
||||
active_services
|
||||
.items
|
||||
.iter()
|
||||
.any(|active| active.service_provider_client_id == sp.address)
|
||||
})
|
||||
.collect_vec();
|
||||
log::trace!("service = {} has {} items", service.id, items.len());
|
||||
filtered.push(DirectoryService {
|
||||
id: service.id.clone(),
|
||||
description: service.description.clone(),
|
||||
items,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(filtered)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,24 @@ use crate::{
|
||||
error::{BackendError, Result},
|
||||
state::State,
|
||||
};
|
||||
use client_core::client::key_manager::KeyManager;
|
||||
use client_core::config::persistence::key_pathfinder::ClientKeyPathfinder;
|
||||
use 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_socks5_config()?
|
||||
};
|
||||
|
||||
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
|
||||
let key_manager = KeyManager::load_keys(&pathfinder)?;
|
||||
let identity_keypair = key_manager.identity_keypair();
|
||||
|
||||
Ok(identity_keypair)
|
||||
}
|
||||
|
||||
/// Export the gateway keys as a JSON string blob
|
||||
#[tauri::command]
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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"],
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod api_client;
|
||||
pub mod assets;
|
||||
pub mod test_and_earn;
|
||||
@@ -0,0 +1,156 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod log;
|
||||
pub mod storage;
|
||||
@@ -0,0 +1,20 @@
|
||||
use crate::error::BackendError;
|
||||
use serde::Serialize;
|
||||
use tauri::Manager;
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
struct ClearStorageEvent {
|
||||
kind: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn help_clear_storage(app_handle: tauri::AppHandle) -> Result<(), BackendError> {
|
||||
log::info!("Sending event to clear local storage...");
|
||||
|
||||
let event = ClearStorageEvent {
|
||||
kind: "local_storage".to_string(),
|
||||
};
|
||||
app_handle.emit_all("help://clear-storage", event)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
pub mod connection;
|
||||
pub mod directory;
|
||||
pub mod export;
|
||||
pub mod growth;
|
||||
pub mod help;
|
||||
pub mod window;
|
||||
|
||||
@@ -64,16 +64,16 @@ impl State {
|
||||
&self.service_provider
|
||||
}
|
||||
|
||||
pub fn set_service_provider(&mut self, provider: String) {
|
||||
self.service_provider = Some(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: String) {
|
||||
self.gateway = Some(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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-connect",
|
||||
"version": "1.1.4"
|
||||
"version": "1.1.5"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
@@ -59,13 +59,16 @@
|
||||
"startDragging": true,
|
||||
"close": true,
|
||||
"minimize": true
|
||||
},
|
||||
"notification": {
|
||||
"all": true
|
||||
}
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "NymConnect",
|
||||
"width": 240,
|
||||
"height": 575,
|
||||
"height": 675,
|
||||
"resizable": false,
|
||||
"decorations": false,
|
||||
"transparent": true
|
||||
|
||||
+12
-1
@@ -1,15 +1,27 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { ConnectionStatusKind } from './types';
|
||||
import { useClientContext } from './context/main';
|
||||
import { DefaultLayout } from './layouts/DefaultLayout';
|
||||
import { ConnectedLayout } from './layouts/ConnectedLayout';
|
||||
import { HelpGuideLayout } from './layouts/HelpGuideLayout';
|
||||
import { useTauriEvents } from './utils';
|
||||
|
||||
export const App: React.FC = () => {
|
||||
const context = useClientContext();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
const [showInfoModal, setShowInfoModal] = React.useState(false);
|
||||
useTauriEvents('help://clear-storage', (_event) => {
|
||||
console.log('About to clear local storage...');
|
||||
// clear local storage
|
||||
try {
|
||||
forage.clear()();
|
||||
console.log('Local storage cleared');
|
||||
} catch (e) {
|
||||
console.error('Failed to clear local storage', e);
|
||||
}
|
||||
});
|
||||
|
||||
const handleConnectClick = React.useCallback(async () => {
|
||||
const currentStatus = context.connectionStatus;
|
||||
@@ -49,7 +61,6 @@ export const App: React.FC = () => {
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
services={context.services}
|
||||
onServiceProviderChange={context.setServiceProvider}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { Typography } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { Typography, Box } from '@mui/material';
|
||||
import { useClientContext } from 'src/context/main';
|
||||
|
||||
export const AppVersion = () => {
|
||||
const { appVersion } = useClientContext();
|
||||
|
||||
return (
|
||||
<Typography
|
||||
fontSize="small"
|
||||
textAlign="center"
|
||||
sx={{ color: 'grey.600', position: 'absolute', bottom: 10, width: '100%' }}
|
||||
>
|
||||
Version {appVersion}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'grid', width: '100%', justifyContent: 'center' }}>
|
||||
<Box fontSize="small" sx={{ mb: 4, color: 'grey.600' }}>
|
||||
Version {appVersion}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,10 +8,9 @@ export const AppWindowFrame: React.FC = ({ children }) => (
|
||||
sx={{
|
||||
display: 'grid',
|
||||
borderRadius: '12px',
|
||||
// screen height is 540px - These should add up to that
|
||||
gridTemplateRows: '40px 1fr 30px',
|
||||
bgcolor: 'nym.background.dark',
|
||||
height: '100vh',
|
||||
overflowY: 'hidden',
|
||||
}}
|
||||
>
|
||||
<CustomTitleBar />
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, Typography } from '@mui/material';
|
||||
import { Box, CircularProgress, Divider, Stack, Tooltip, Typography } from '@mui/material';
|
||||
import { DateTime } from 'luxon';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
import { ServiceProviderInfo } from './ServiceProviderInfo';
|
||||
|
||||
const FONT_SIZE = '10px';
|
||||
const FONT_WEIGHT = '600';
|
||||
@@ -14,8 +15,8 @@ const ConnectionStatusContent: React.FC<{
|
||||
switch (status) {
|
||||
case ConnectionStatusKind.connected:
|
||||
return (
|
||||
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} textAlign="center">
|
||||
Connected
|
||||
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} fontSize="14px">
|
||||
Connected to
|
||||
</Typography>
|
||||
);
|
||||
case ConnectionStatusKind.disconnecting:
|
||||
@@ -44,7 +45,7 @@ const ConnectionStatusContent: React.FC<{
|
||||
ml={1}
|
||||
textTransform="uppercase"
|
||||
textAlign="center"
|
||||
fontSize="10px"
|
||||
fontSize={FONT_SIZE}
|
||||
sx={{ wordSpacing: 3, letterSpacing: 2 }}
|
||||
>
|
||||
You are not protected
|
||||
@@ -59,7 +60,7 @@ export const ConnectionStatus: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
connectedSince?: DateTime;
|
||||
serviceProvider?: ServiceProvider;
|
||||
}> = ({ status, connectedSince, serviceProvider }) => {
|
||||
}> = ({ status, serviceProvider }) => {
|
||||
const color =
|
||||
status === ConnectionStatusKind.connected || status === ConnectionStatusKind.disconnecting
|
||||
? '#21D072'
|
||||
@@ -70,13 +71,13 @@ export const ConnectionStatus: React.FC<{
|
||||
<Box color={color} fontSize={FONT_SIZE} sx={{ mb: 1 }}>
|
||||
<ConnectionStatusContent status={status} />
|
||||
</Box>
|
||||
<Box>
|
||||
{serviceProvider && (
|
||||
<Typography fontSize={12} textAlign="center">
|
||||
To {serviceProvider.description}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
{serviceProvider ? (
|
||||
<Tooltip title={<ServiceProviderInfo serviceProvider={serviceProvider} />}>
|
||||
<Box sx={{ cursor: 'pointer' }}>
|
||||
{serviceProvider && <Typography>{serviceProvider.description}</Typography>}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,9 +14,13 @@ export const CopyToClipboard = ({
|
||||
}) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async (text: string) => {
|
||||
const handleCopy = async (textToCopy: string) => {
|
||||
try {
|
||||
await clipboard.writeText(text);
|
||||
if (clipboard) {
|
||||
await clipboard.writeText(textToCopy);
|
||||
} else {
|
||||
await navigator.clipboard.writeText(textToCopy);
|
||||
}
|
||||
setCopied(true);
|
||||
} catch (e) {
|
||||
console.log(`failed to copy: ${e}`);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { FallbackProps } from 'react-error-boundary';
|
||||
import { Alert, AlertTitle, Button } from '@mui/material';
|
||||
|
||||
export const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => (
|
||||
<div>
|
||||
<Alert severity="error" data-testid="error-message">
|
||||
<AlertTitle>{error.name}</AlertTitle>
|
||||
{error.message}
|
||||
</Alert>
|
||||
<Alert severity="error" data-testid="stack-trace">
|
||||
<AlertTitle>Stack trace</AlertTitle>
|
||||
{error.stack}
|
||||
</Alert>
|
||||
<Button onClick={resetErrorBoundary}>Back to safety</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import { Badge, Box, Button, Tooltip } from '@mui/material';
|
||||
import MonetizationOnIcon from '@mui/icons-material/MonetizationOn';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import Content from './content/en.yaml';
|
||||
import { useClientContext } from '../../context/main';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { NymShipyardTheme } from '../../theme';
|
||||
import { ConnectionStatusKind } from '../../types';
|
||||
|
||||
export const Wrapper: React.FC<{ disabled: boolean }> = ({ disabled, children }) => {
|
||||
if (disabled) {
|
||||
return (
|
||||
<Badge badgeContent="!" color="warning">
|
||||
<Tooltip arrow title={disabled ? Content.testAndEarn.mainWindow.button.popup.disconnected : undefined}>
|
||||
<div>{children}</div>
|
||||
</Tooltip>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export const TestAndEarnButtonArea: React.FC = () => {
|
||||
const clientContext = useClientContext();
|
||||
const context = useTestAndEarnContext();
|
||||
const disabled = clientContext.connectionStatus !== ConnectionStatusKind.connected;
|
||||
const pinger = React.useRef<NodeJS.Timer | null>();
|
||||
|
||||
const doPing = async () => {
|
||||
if (context.clientDetails) {
|
||||
try {
|
||||
await invoke('growth_tne_ping', { clientDetails: context.clientDetails });
|
||||
} catch (_e) {
|
||||
// console.error('Failed to ping: ', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
if (!disabled) {
|
||||
// sleep a little until the SOCKS5 proxy connects
|
||||
setTimeout(() => {
|
||||
doPing();
|
||||
}, 1000 * 10);
|
||||
|
||||
// update every 15 mins
|
||||
pinger.current = setInterval(doPing, 1000 * 60 * 15);
|
||||
} else if (pinger.current) {
|
||||
clearInterval(pinger.current);
|
||||
pinger.current = null;
|
||||
}
|
||||
})();
|
||||
}, [disabled, context.clientDetails]);
|
||||
|
||||
const handleClick = async () => {
|
||||
if (!disabled) {
|
||||
await context.toggleGrowthWindow(Content.testAndEarn.popupWindow.title);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NymShipyardTheme>
|
||||
<Box justifyContent="center" display="grid">
|
||||
<Wrapper disabled={disabled}>
|
||||
<Button
|
||||
color={disabled ? 'secondary' : undefined}
|
||||
variant="contained"
|
||||
size="small"
|
||||
endIcon={<MonetizationOnIcon />}
|
||||
sx={{ width: '150px', mb: 4, opacity: disabled ? 0.4 : undefined }}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{context.registration
|
||||
? Content.testAndEarn.mainWindow.button.text.entered
|
||||
: Content.testAndEarn.mainWindow.button.text.default}
|
||||
</Button>
|
||||
</Wrapper>
|
||||
</Box>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
import {
|
||||
TestAndEarnCurrentDraw,
|
||||
TestAndEarnCurrentDrawEntered,
|
||||
TestAndEarnCurrentDrawFuture,
|
||||
} from './TestAndEarnCurrentDraw';
|
||||
import { NymShipyardTheme } from '../../theme';
|
||||
import { DrawEntryStatus } from './context/types';
|
||||
import { testMarkdown } from './context/mocks/TestAndEarnContext';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Components/Cards/Current Draw',
|
||||
component: TestAndEarnCurrentDraw,
|
||||
} as ComponentMeta<typeof TestAndEarnCurrentDraw>;
|
||||
|
||||
export const Valid = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDraw
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now().toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: 'words words words',
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const EnteredMalformedDraw = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDrawEntered
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now().toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: undefined,
|
||||
entry: {
|
||||
draw_id: '1',
|
||||
status: DrawEntryStatus.pending,
|
||||
id: 'aaaa',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const EnteredDraw = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDrawEntered
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now().toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: testMarkdown,
|
||||
entry: {
|
||||
draw_id: '1',
|
||||
status: DrawEntryStatus.pending,
|
||||
id: 'aaaa',
|
||||
timestamp: DateTime.now().toISO(),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const Future = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnCurrentDrawFuture
|
||||
draw={{
|
||||
id: '1',
|
||||
start_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600))
|
||||
.toISO(),
|
||||
end_utc: DateTime.now()
|
||||
.plus(Duration.fromMillis(1000 * 3600 * 2))
|
||||
.toISO(),
|
||||
last_modified: DateTime.now().toISO(),
|
||||
word_of_the_day: 'words words words',
|
||||
}}
|
||||
/>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
@@ -0,0 +1,192 @@
|
||||
import React from 'react';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import { Alert, AlertTitle, Box, Card, CardContent, CardMedia, Link, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { DateTime } from 'luxon';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import assetAnimation from './content/assets/matrix.webp';
|
||||
import { CopyToClipboard } from '../CopyToClipboard';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { DrawEntryStatus, DrawWithWordOfTheDay } from './context/types';
|
||||
import Content from './content/en.yaml';
|
||||
|
||||
export const TestAndEarnCurrentDrawFuture: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
|
||||
const startsUtc = React.useMemo(() => draw && DateTime.fromISO(draw.start_utc), [draw?.start_utc]);
|
||||
const startsIn = React.useMemo(() => {
|
||||
if (draw && startsUtc) {
|
||||
return startsUtc.toRelative();
|
||||
}
|
||||
return undefined;
|
||||
}, [draw?.start_utc]);
|
||||
|
||||
if (!draw || !startsUtc) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Card sx={{ mb: 2 }} elevation={10}>
|
||||
<CardContent>
|
||||
<h3>
|
||||
{Content.testAndEarn.draw.next.header} {startsIn} ⏰
|
||||
</h3>
|
||||
<p>on {startsUtc.toLocaleString(DateTime.DATETIME_FULL)}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDrawEnter: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const handleClick = async () => {
|
||||
if (!draw) {
|
||||
setError('No draw selected');
|
||||
return;
|
||||
}
|
||||
|
||||
setBusy(true);
|
||||
try {
|
||||
await context.enterDraw(draw.id);
|
||||
} catch (e) {
|
||||
const message = `${e}`;
|
||||
console.error('Could not enter draw', message);
|
||||
setError(message);
|
||||
}
|
||||
setBusy(false);
|
||||
};
|
||||
return (
|
||||
<Box display="flex" flexDirection="column" alignItems="center" py={3} px={2} mx={6} my={2}>
|
||||
<Typography mb={4}>Complete today’s task for the chance to earn 1000 NYMs.</Typography>
|
||||
<LoadingButton variant="contained" size="large" loading={busy} onClick={handleClick}>
|
||||
Start task ✨
|
||||
</LoadingButton>
|
||||
{error && (
|
||||
<Box mt={2}>
|
||||
<Alert variant="filled" severity="error">
|
||||
<AlertTitle>Oh no! Something went wrong.</AlertTitle>
|
||||
{error}
|
||||
</Alert>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDrawEntered: React.FC<{ draw?: DrawWithWordOfTheDay }> = ({ draw }) => {
|
||||
if (!draw || !draw.entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!draw.word_of_the_day) {
|
||||
return (
|
||||
<Alert severity="error" variant="filled">
|
||||
<AlertTitle>Oh no! Something is wrong</AlertTitle>
|
||||
Someone configured the wrong instructions for the task, you will not be able to see it until this is fixed
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems="center"
|
||||
sx={{ background: 'rgba(255,255,255,0.1)' }}
|
||||
py={4}
|
||||
mx={6}
|
||||
my={2}
|
||||
borderRadius={2}
|
||||
>
|
||||
<Box py={2} px={4} color="warning.light">
|
||||
<ReactMarkdown>{draw.word_of_the_day}</ReactMarkdown>
|
||||
</Box>
|
||||
|
||||
<Typography>{Content.testAndEarn.task.afterText}</Typography>
|
||||
<Typography mt={2} fontFamily="monospace" fontWeight="bold" color="warning.main">
|
||||
{draw.entry.id} <CopyToClipboard iconButton light text={draw.entry.id} />
|
||||
</Typography>
|
||||
|
||||
<Typography mt={2}>{Content.testAndEarn.task.beforeSocials}</Typography>
|
||||
<Typography mt={2} mx={1} textAlign="center">
|
||||
<Typography component="span" color="info.light" fontWeight="bold">
|
||||
Twitter
|
||||
</Typography>{' '}
|
||||
- remember to
|
||||
<Typography component="span" color="info.light">
|
||||
@nymproject
|
||||
</Typography>{' '}
|
||||
and use the hashtag{' '}
|
||||
<Typography component="span" color="info.light">
|
||||
#PrivacyLovesCompany
|
||||
</Typography>
|
||||
</Typography>
|
||||
<Typography mt={2}>or</Typography>
|
||||
<Typography textAlign="center" fontWeight="bold">
|
||||
Nym{' '}
|
||||
<Link target="_blank" href="https://t.me/nymchan" color="info.light">
|
||||
Telegram channel
|
||||
</Link>
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDraw: React.FC<{
|
||||
draw?: DrawWithWordOfTheDay;
|
||||
sx?: SxProps;
|
||||
}> = ({ draw, sx }) => {
|
||||
const [trigger, setTrigger] = React.useState(DateTime.now().toISO());
|
||||
const endsUtc = React.useMemo(() => draw && DateTime.fromISO(draw.end_utc), [draw?.end_utc]);
|
||||
const closesIn = React.useMemo(() => {
|
||||
if (draw && endsUtc) {
|
||||
return endsUtc.toRelative();
|
||||
}
|
||||
return undefined;
|
||||
}, [trigger, endsUtc]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const timer = setInterval(() => setTrigger(DateTime.now().toISO()), 1000 * 3600 * 15);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
if (draw && closesIn && endsUtc) {
|
||||
return (
|
||||
<Card sx={{ mb: 2, ...sx }} elevation={10}>
|
||||
<CardContent>
|
||||
<h3>
|
||||
{"Today's task ends "}
|
||||
{closesIn}
|
||||
<Typography sx={{ opacity: 0.5 }}>
|
||||
{endsUtc.weekdayLong} {endsUtc.toLocaleString(DateTime.DATETIME_FULL)}
|
||||
</Typography>
|
||||
</h3>
|
||||
{!draw.entry && <TestAndEarnCurrentDrawEnter draw={draw} />}
|
||||
{draw.entry && <TestAndEarnCurrentDrawEntered draw={draw} />}
|
||||
</CardContent>
|
||||
<CardMedia component="img" height="150" image={assetAnimation} alt="lottery" />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const TestAndEarnCurrentDrawWithState: React.FC<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
if (
|
||||
context.draws?.current?.entry?.status === DrawEntryStatus.winner ||
|
||||
context.draws?.current?.entry?.status === DrawEntryStatus.claimed ||
|
||||
context.draws?.current?.entry?.status === DrawEntryStatus.noWin
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!context.draws?.current) {
|
||||
return <TestAndEarnCurrentDrawFuture draw={context.draws?.next} />;
|
||||
}
|
||||
|
||||
return <TestAndEarnCurrentDraw sx={sx} draw={context.draws.current} />;
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { NymShipyardTheme } from 'src/theme';
|
||||
import { TestAndEarnDraws } from './TestAndEarnDraws';
|
||||
import { MockTestAndEarnProvider_RegisteredWithAllDraws } from './context/mocks/TestAndEarnContext';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Components/Cards/Draws',
|
||||
component: TestAndEarnDraws,
|
||||
} as ComponentMeta<typeof TestAndEarnDraws>;
|
||||
|
||||
export const Draws = () => (
|
||||
<NymShipyardTheme>
|
||||
<MockTestAndEarnProvider_RegisteredWithAllDraws>
|
||||
<TestAndEarnDraws />
|
||||
</MockTestAndEarnProvider_RegisteredWithAllDraws>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
@@ -0,0 +1,195 @@
|
||||
import React from 'react';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import {
|
||||
Alert,
|
||||
AlertTitle,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
Chip,
|
||||
Dialog,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { DrawEntry, DrawEntryStatus } from './context/types';
|
||||
import { CopyToClipboard } from '../CopyToClipboard';
|
||||
import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress';
|
||||
import Content from './content/en.yaml';
|
||||
|
||||
const statusToText = (status: string): string => Content.testAndEarn.status.chip[status] || '-';
|
||||
|
||||
const statusToColor = (status: string): 'info' | 'success' | 'warning' | undefined => {
|
||||
switch (status) {
|
||||
case DrawEntryStatus.pending:
|
||||
return 'info';
|
||||
case DrawEntryStatus.winner:
|
||||
return 'warning';
|
||||
case DrawEntryStatus.claimed:
|
||||
return 'success';
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const StatusText: React.FC<{ entry: DrawEntry }> = ({ entry }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const [showWalletCapture, setShowWalletCapture] = React.useState(false);
|
||||
|
||||
const clear = () => {
|
||||
setShowWalletCapture(false);
|
||||
setError(undefined);
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
const handleStartWalletCapture = async () => {
|
||||
setBusy(true);
|
||||
setShowWalletCapture(true);
|
||||
};
|
||||
|
||||
const cancelEndWalletCapture = async () => {
|
||||
setBusy(false);
|
||||
setShowWalletCapture(false);
|
||||
};
|
||||
|
||||
const handleEndWalletCapture = async () => {
|
||||
setBusy(true);
|
||||
setShowWalletCapture(false);
|
||||
|
||||
if (!context.walletAddress) {
|
||||
setError('Wallet address is not set');
|
||||
return;
|
||||
}
|
||||
if (!entry.draw_id) {
|
||||
setError('Task id is not set');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await context.claim(entry.draw_id, context.walletAddress);
|
||||
} catch (e) {
|
||||
const message = `${e}`;
|
||||
console.error('Failed to submit claim');
|
||||
setError(message);
|
||||
}
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Alert severity="error" variant="filled">
|
||||
<AlertTitle>Oh no! Failed to submit claim</AlertTitle>
|
||||
{error}
|
||||
<Button variant="contained" color="secondary" size="small" onClick={() => clear()} sx={{ mx: 2 }}>
|
||||
Try again!
|
||||
</Button>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (showWalletCapture) {
|
||||
return (
|
||||
<Dialog open fullWidth onBackdropClick={cancelEndWalletCapture}>
|
||||
<TestAndEarnEnterWalletAddress onSubmit={handleEndWalletCapture} />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
switch (entry.status) {
|
||||
case DrawEntryStatus.pending:
|
||||
return <>{Content.testAndEarn.status.text.Pending}</>;
|
||||
case DrawEntryStatus.winner:
|
||||
return (
|
||||
<>
|
||||
{Content.testAndEarn.status.text.Winner}
|
||||
<LoadingButton
|
||||
loading={busy}
|
||||
disabled={busy}
|
||||
variant="contained"
|
||||
sx={{ ml: 2 }}
|
||||
size="small"
|
||||
onClick={handleStartWalletCapture}
|
||||
>
|
||||
{Content.testAndEarn.winner.claimButton.text}
|
||||
</LoadingButton>
|
||||
</>
|
||||
);
|
||||
case DrawEntryStatus.claimed:
|
||||
return <>{Content.testAndEarn.status.text.Claimed}</>;
|
||||
case DrawEntryStatus.noWin:
|
||||
return <>{Content.testAndEarn.status.text.NoWin}</>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const TestAndEarnDraws: React.FC<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
const draws = React.useMemo<DrawEntry[]>(
|
||||
() =>
|
||||
(context.draws?.draws || []).map((item) => ({
|
||||
...item,
|
||||
timestamp: DateTime.fromISO(item.timestamp).toLocaleString(DateTime.DATETIME_FULL),
|
||||
})),
|
||||
[context.draws?.draws],
|
||||
);
|
||||
|
||||
if (!context.draws) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card sx={{ mb: 2 }}>
|
||||
<CardContent>
|
||||
<Typography mb={2}>Here is a history of the tasks you have completed:</Typography>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableBody>
|
||||
{draws.map((entry) => (
|
||||
<TableRow key={entry.draw_id}>
|
||||
<TableCell width="150px">{entry.timestamp}</TableCell>
|
||||
<TableCell width="150px">
|
||||
<Tooltip arrow title={`Task Id: ${entry.draw_id}`}>
|
||||
<Chip label={statusToText(entry.status)} color={statusToColor(entry.status)} />
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StatusText entry={entry} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{entry.id} <CopyToClipboard iconButton light text={entry.id} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnDrawsWithState: React.FC<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
const drawCount = context.draws?.draws?.length || 0;
|
||||
if (drawCount < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <TestAndEarnDraws sx={sx} />;
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Box } from '@mui/material';
|
||||
import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress';
|
||||
import { TestAndEarnContextProvider } from './context/TestAndEarnContext';
|
||||
import { NymShipyardTheme } from '../../theme';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Components/Enter wallet address',
|
||||
component: TestAndEarnEnterWalletAddress,
|
||||
} as ComponentMeta<typeof TestAndEarnEnterWalletAddress>;
|
||||
|
||||
export const Empty = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnContextProvider>
|
||||
<Box minWidth="25vw" maxWidth={500}>
|
||||
<TestAndEarnEnterWalletAddress sx={{ width: '100%' }} />
|
||||
</Box>
|
||||
</TestAndEarnContextProvider>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const ErrorValue = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnContextProvider>
|
||||
<Box minWidth="25vw" maxWidth={500}>
|
||||
<TestAndEarnEnterWalletAddress initialValue="this is a bad value" sx={{ width: '100%' }} />
|
||||
</Box>
|
||||
</TestAndEarnContextProvider>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const ValidValue = () => (
|
||||
<NymShipyardTheme>
|
||||
<TestAndEarnContextProvider>
|
||||
<Box minWidth="25vw" maxWidth={500}>
|
||||
<TestAndEarnEnterWalletAddress initialValue="n1xr4w0kddak8d8zlfmu8sl6dk2r4p9uhhzzlaec" sx={{ width: '100%' }} />
|
||||
</Box>
|
||||
</TestAndEarnContextProvider>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { WalletAddressFormField } from '@nymproject/react/account/WalletAddressFormField';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { Paper, Stack, Button, Box } from '@mui/material';
|
||||
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
|
||||
export const TestAndEarnEnterWalletAddress: React.FC<{
|
||||
initialValue?: string;
|
||||
placeholder?: string;
|
||||
onSubmit?: () => Promise<void> | void;
|
||||
sx?: SxProps;
|
||||
}> = ({ initialValue, placeholder, onSubmit, sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
const [isAddressValid, setAddressIsValid] = React.useState(false);
|
||||
return (
|
||||
<Paper sx={{ py: 4, px: 2 }}>
|
||||
<Stack spacing={4}>
|
||||
<Box>
|
||||
<WalletAddressFormField
|
||||
label="Wallet address"
|
||||
initialValue={initialValue}
|
||||
placeholder={placeholder || 'Please enter your wallet address'}
|
||||
onChanged={context.setWalletAddress}
|
||||
onValidate={setAddressIsValid}
|
||||
sx={{ width: '80%' }}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Button variant="contained" endIcon={<ArrowCircleRightIcon />} disabled={!isAddressValid} onClick={onSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
|
||||
export const TestAndEarnError: React.FC<{ error?: string }> = ({ error = 'An error has occurred' }) => (
|
||||
<Box>
|
||||
<Box mb={4} fontWeight="bold">
|
||||
{error}
|
||||
</Box>
|
||||
<Button variant="outlined" color="secondary">
|
||||
Send us an error report
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,167 @@
|
||||
/* eslint-disable react/jsx-pascal-case */
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { Alert, Box } from '@mui/material';
|
||||
import { NymShipyardTheme } from 'src/theme';
|
||||
import { TestAndEarnPopup, TestAndEarnPopupContent } from './TestAndEarnPopup';
|
||||
import { TestAndEarnContextProvider } from './context/TestAndEarnContext';
|
||||
import { MockProvider } from '../../context/mocks/main';
|
||||
import { ConnectionStatusKind } from '../../types';
|
||||
import { TestAndEarnCurrentDraw } from './TestAndEarnCurrentDraw';
|
||||
import { TestAndEarnWinner } from './TestAndEarnWinner';
|
||||
import { TestAndEarnDraws } from './TestAndEarnDraws';
|
||||
import { TestAndEarnWinnerWalletAddress } from './TestAndEarnWinnerWalletAddress';
|
||||
import {
|
||||
MockTestAndEarnProvider_NotRegistered,
|
||||
MockTestAndEarnProvider_Registered,
|
||||
MockTestAndEarnProvider_RegisteredAndError,
|
||||
MockTestAndEarnProvider_RegisteredWithDraws,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntry,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet,
|
||||
MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent,
|
||||
} from './context/mocks/TestAndEarnContext';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Content/Popup',
|
||||
component: TestAndEarnPopupContent,
|
||||
} as ComponentMeta<typeof TestAndEarnPopupContent>;
|
||||
|
||||
const MacOSWindow: React.FC<{ width?: string | number; height?: string | number; title?: string }> = ({
|
||||
title,
|
||||
width,
|
||||
height,
|
||||
children,
|
||||
}) => (
|
||||
<Box sx={{ border: '1px solid #EEEEEE', width, height }}>
|
||||
<Box sx={{ background: '#EEEEEE', display: 'grid', gridTemplateColumns: 'auto auto', gridTemplateRows: 'auto' }}>
|
||||
<Box ml={1}>
|
||||
<svg width="52px" height="12px" viewBox="0 0 52 12" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Components" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
||||
<g id="macOS" transform="translate(-600.000000, -220.000000)">
|
||||
<g id="Group" transform="translate(600.000000, 220.000000)" strokeWidth="0.5">
|
||||
<g id="Traffic-Lights">
|
||||
<circle id="Traffic-Light---Zoom" stroke="#1BAC2C" fill="#2ACB42" cx="46" cy="6" r="5.75" />
|
||||
<circle id="Traffic-Light---Minimise" stroke="#DFA023" fill="#FFC12F" cx="26" cy="6" r="5.75" />
|
||||
<circle id="Traffic-Light---Close" stroke="#E24640" fill="#FF6157" cx="6" cy="6" r="5.75" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
alignSelf: 'center',
|
||||
color: '#000000',
|
||||
opacity: 0.848675272,
|
||||
fontSize: 13,
|
||||
}}
|
||||
>
|
||||
{title || 'Window title'}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ overflowY: 'scroll', height: 'calc(100% - 25px)' }}>{children}</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const Wrapper: React.FC<{ text: React.ReactNode }> = ({ text }) => (
|
||||
<NymShipyardTheme>
|
||||
<Alert severity="info" sx={{ mb: 4 }}>
|
||||
{text}
|
||||
</Alert>
|
||||
<MacOSWindow width={700} height={600} title="Test&Earn">
|
||||
<TestAndEarnPopup />
|
||||
</MacOSWindow>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
|
||||
export const Stage0 = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_NotRegistered>
|
||||
<Wrapper text="The user sees this content when they have not joined Test&Earn." />
|
||||
</MockTestAndEarnProvider_NotRegistered>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage1EnterDraw = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDraws>
|
||||
<Wrapper text="The user has signed up and can see the next draw and choose the enter." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDraws>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage2GetTask = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntry>
|
||||
<Wrapper text="The user has entered a draw and can view the word of the day if they missed the popup notification." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntry>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage3Winner = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
|
||||
<Wrapper text="The user has won and can claim their prize." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage3NoPrize = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner>
|
||||
<Wrapper text="The user has not won. A winner has been announced." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndNoWinner>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage4EnterWalletAddress = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet>
|
||||
<Wrapper text="The user is a winner, claims their prize and enters their wallet address." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerCollectWallet>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage5ClaimedPrize = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed>
|
||||
<Wrapper text="The user is a winner and has claimed their prize." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinnerClaimed>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Stage6DrawsFinished = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connected}>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent>
|
||||
<Wrapper text="There are no more draws. The user can see their entries and prizes they have claimed." />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsNoCurrent>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Connecting = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.connecting}>
|
||||
<TestAndEarnContextProvider>
|
||||
<Wrapper text="Test&Earn requires the user to be connected to talk the API. This is shown while connecting." />
|
||||
</TestAndEarnContextProvider>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Disconnected = () => (
|
||||
<MockProvider connectionStatus={ConnectionStatusKind.disconnected}>
|
||||
<TestAndEarnContextProvider>
|
||||
<Wrapper text="Test&Earn requires the user to be connected to talk the API. This is shown when not connected." />
|
||||
</TestAndEarnContextProvider>
|
||||
</MockProvider>
|
||||
);
|
||||
|
||||
export const Error = () => (
|
||||
<MockProvider>
|
||||
<MockTestAndEarnProvider_RegisteredAndError>
|
||||
<Wrapper text="The user see this with details about errors. They can submit an error report." />
|
||||
</MockTestAndEarnProvider_RegisteredAndError>
|
||||
</MockProvider>
|
||||
);
|
||||
@@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, LinearProgress, Stack, Typography } from '@mui/material';
|
||||
import { useClientContext } from '../../context/main';
|
||||
import ErrorContent from './content/TestAndEarn/Error.mdx';
|
||||
import ContentStep0 from './content/TestAndEarn/Stage0_intro.mdx';
|
||||
import ContentNotAvailable from './content/TestAndEarnNotAvaialble.mdx';
|
||||
import { ConnectionStatusKind } from '../../types';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { TestAndEarnWinnerWithState } from './TestAndEarnWinner';
|
||||
import { TestAndEarnCurrentDrawWithState } from './TestAndEarnCurrentDraw';
|
||||
import { TestAndEarnDrawsWithState } from './TestAndEarnDraws';
|
||||
|
||||
enum Stages {
|
||||
mustRegister = 'mustRegister',
|
||||
registered = 'registered',
|
||||
}
|
||||
|
||||
export const TestAndEarnPopupContent: React.FC<{
|
||||
stage?: string;
|
||||
connectionStatus?: ConnectionStatusKind;
|
||||
error?: string;
|
||||
}> = ({ connectionStatus, error, stage = Stages.mustRegister }) => {
|
||||
if (error) {
|
||||
return (
|
||||
<Box p={4}>
|
||||
<ErrorContent error={error} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (!connectionStatus || connectionStatus === ConnectionStatusKind.disconnected) {
|
||||
return (
|
||||
<Box p={4}>
|
||||
<ContentNotAvailable />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (connectionStatus === ConnectionStatusKind.connecting || connectionStatus === ConnectionStatusKind.disconnecting) {
|
||||
return (
|
||||
<Box p={4} justifyContent="center" alignItems="center" display="flex">
|
||||
<CircularProgress />
|
||||
<Typography ml={3}>Please wait...</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
switch (stage) {
|
||||
case Stages.mustRegister:
|
||||
return (
|
||||
<Box p={4}>
|
||||
<ContentStep0 />
|
||||
</Box>
|
||||
);
|
||||
case Stages.registered:
|
||||
return (
|
||||
<Box p={4}>
|
||||
<TestAndEarnWinnerWithState />
|
||||
<TestAndEarnCurrentDrawWithState />
|
||||
<TestAndEarnDrawsWithState />
|
||||
</Box>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Box p={4}>
|
||||
<Stack direction="row" spacing={2} display="flex" alignItems="center">
|
||||
<CircularProgress />
|
||||
<Box>Waiting for task information...</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const TestAndEarnPopup: React.FC = () => {
|
||||
const clientContext = useClientContext();
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (clientContext.connectionStatus === ConnectionStatusKind.connected) {
|
||||
context.refresh();
|
||||
}
|
||||
}, [clientContext.connectionStatus]);
|
||||
|
||||
const stage = React.useMemo<Stages>(() => {
|
||||
if (context.registration) {
|
||||
return Stages.registered;
|
||||
}
|
||||
return Stages.mustRegister;
|
||||
}, [context.registration?.id]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(context.refresh, 1000 * 60 * 5);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
if (!context.loadedOnce && clientContext.connectionStatus === ConnectionStatusKind.connected) {
|
||||
const message = 'Waiting for data to be transferred over the mixnet...';
|
||||
return (
|
||||
<Box p={4}>
|
||||
<Stack direction="row" spacing={2} display="flex" alignItems="center">
|
||||
<CircularProgress />
|
||||
<Box>{message}</Box>
|
||||
{/* {process.env.NODE_ENV === 'development' && <pre>{JSON.stringify(context, null, 2)}</pre>} */}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{context.loading && <LinearProgress />}
|
||||
{/* <Button onClick={context.refresh}>Refresh</Button> */}
|
||||
<TestAndEarnPopupContent connectionStatus={clientContext.connectionStatus} stage={stage} error={context.error} />
|
||||
{/* {process.env.NODE_ENV === 'development' && <pre>{JSON.stringify(context, null, 2)}</pre>} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import { Alert, AlertTitle, Box, Checkbox, Link, Stack } from '@mui/material';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import { SxProps } from '@mui/system';
|
||||
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { ClientId, Registration } from './context/types';
|
||||
|
||||
export const TestAndEarnTakePart: React.FC<{
|
||||
websiteLinkUrl: string;
|
||||
websiteLinkText: string;
|
||||
content: string;
|
||||
sx?: SxProps;
|
||||
}> = ({ content, websiteLinkText, websiteLinkUrl, sx }) => {
|
||||
const [agree, setAgree] = React.useState(false);
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const context = useTestAndEarnContext();
|
||||
const handleNext = async () => {
|
||||
try {
|
||||
setBusy(true);
|
||||
if (context.clientDetails) {
|
||||
const registration: Registration = await invoke('growth_tne_take_part');
|
||||
console.log('Registration: ', { registration });
|
||||
await context.setAndStoreRegistration(registration);
|
||||
if (registration) {
|
||||
console.log('Registered...');
|
||||
} else {
|
||||
setError('Failed to get registration details');
|
||||
}
|
||||
} else {
|
||||
setError('Failed to get client details');
|
||||
}
|
||||
} catch (e) {
|
||||
const message = `${e}`;
|
||||
console.error('An error occurred', message);
|
||||
setError(message);
|
||||
setBusy(false); // the busy state only resets on errors, for success stats, the context will navigate the window away
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Stack direction="row" spacing={6} alignItems="center" sx={sx}>
|
||||
<Stack alignItems="center" direction="row">
|
||||
<Checkbox onChange={(_event, checked) => setAgree(checked)} />
|
||||
<Box color="primary.light" fontWeight="bold">
|
||||
{content}
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box>
|
||||
<Link href={websiteLinkUrl} target="_blank" color="secondary" sx={{ opacity: 0.5 }}>
|
||||
{websiteLinkText}
|
||||
</Link>
|
||||
</Box>
|
||||
<LoadingButton
|
||||
loading={busy}
|
||||
disabled={!agree || busy}
|
||||
variant="contained"
|
||||
sx={{ justifySelf: 'end' }}
|
||||
endIcon={<ArrowCircleRightIcon />}
|
||||
onClick={handleNext}
|
||||
>
|
||||
Next
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
{error && (
|
||||
<Alert severity="error" variant="filled">
|
||||
<AlertTitle>Oh no! Something went wrong</AlertTitle>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
import { NymShipyardTheme } from 'src/theme';
|
||||
import { TestAndEarnWinner, TestAndEarnWinnerWithState } from './TestAndEarnWinner';
|
||||
import { MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner } from './context/mocks/TestAndEarnContext';
|
||||
|
||||
export default {
|
||||
title: 'Growth/TestAndEarn/Components/Cards/Winner',
|
||||
component: TestAndEarnWinner,
|
||||
} as ComponentMeta<typeof TestAndEarnWinner>;
|
||||
|
||||
export const Winner = () => (
|
||||
<NymShipyardTheme>
|
||||
<MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
|
||||
<TestAndEarnWinnerWithState />
|
||||
</MockTestAndEarnProvider_RegisteredWithDrawsAndEntryAndWinner>
|
||||
</NymShipyardTheme>
|
||||
);
|
||||
@@ -0,0 +1,114 @@
|
||||
import React from 'react';
|
||||
import { Alert, AlertTitle, Button, Card, CardContent, CardMedia, Dialog, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import winner from './content/assets/winner.webp';
|
||||
import { useTestAndEarnContext } from './context/TestAndEarnContext';
|
||||
import { DrawEntry, DrawEntryStatus } from './context/types';
|
||||
import { TestAndEarnEnterWalletAddress } from './TestAndEarnEnterWalletAddress';
|
||||
import Content from './content/en.yaml';
|
||||
|
||||
export const TestAndEarnWinner: React.FC<{
|
||||
sx?: SxProps;
|
||||
entry?: DrawEntry;
|
||||
}> = ({ sx, entry }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
const [error, setError] = React.useState<string>();
|
||||
const [showWalletCapture, setShowWalletCapture] = React.useState(false);
|
||||
|
||||
const clear = () => {
|
||||
setShowWalletCapture(false);
|
||||
setError(undefined);
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
const handleStartWalletCapture = async () => {
|
||||
setBusy(true);
|
||||
setShowWalletCapture(true);
|
||||
};
|
||||
|
||||
const cancelEndWalletCapture = async () => {
|
||||
setBusy(false);
|
||||
setShowWalletCapture(false);
|
||||
};
|
||||
|
||||
const handleEndWalletCapture = async () => {
|
||||
setBusy(true);
|
||||
setShowWalletCapture(false);
|
||||
|
||||
if (!context.walletAddress) {
|
||||
setError('Wallet address is not set');
|
||||
return;
|
||||
}
|
||||
if (!entry?.draw_id) {
|
||||
setError('Draw id is not set');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await context.claim(entry.draw_id, context.walletAddress);
|
||||
} catch (e) {
|
||||
const message = `${e}`;
|
||||
console.error('Failed to submit claim', entry.draw_id, context.walletAddress);
|
||||
setError(message);
|
||||
}
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{showWalletCapture && (
|
||||
<Dialog open fullWidth onBackdropClick={cancelEndWalletCapture}>
|
||||
<TestAndEarnEnterWalletAddress onSubmit={handleEndWalletCapture} />
|
||||
</Dialog>
|
||||
)}
|
||||
<Card sx={{ mb: 2 }}>
|
||||
<CardMedia component="img" height="165" image={winner} alt="winner" />
|
||||
<CardContent>
|
||||
<Typography color="warning.main" fontSize={20} fontWeight="bold">
|
||||
{Content.testAndEarn.winner.card.header}
|
||||
</Typography>
|
||||
<Typography mt={2}>
|
||||
{entry && (
|
||||
<>
|
||||
{Content.testAndEarn.winner.card.text} {entry.draw_id}.
|
||||
</>
|
||||
)}
|
||||
<LoadingButton
|
||||
loading={busy}
|
||||
variant="contained"
|
||||
sx={{ ml: 2, my: 2 }}
|
||||
size="small"
|
||||
onClick={handleStartWalletCapture}
|
||||
>
|
||||
{Content.testAndEarn.winner.claimButton.text}
|
||||
</LoadingButton>
|
||||
</Typography>
|
||||
{error && (
|
||||
<Alert severity="error" variant="filled">
|
||||
<AlertTitle>Oh no! Failed to submit claim</AlertTitle>
|
||||
{error}
|
||||
<Button variant="contained" color="secondary" size="small" onClick={() => clear()} sx={{ mx: 2 }}>
|
||||
Try again!
|
||||
</Button>
|
||||
</Alert>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestAndEarnWinnerWithState: React.FC<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => {
|
||||
const context = useTestAndEarnContext();
|
||||
|
||||
if (context.draws?.current?.entry?.status === DrawEntryStatus.winner) {
|
||||
return <TestAndEarnWinner sx={sx} entry={context.draws.current.entry} />;
|
||||
}
|
||||
|
||||
// when the user does not have any unclaimed prizes, don't render anything
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Card, CardContent, CardMedia, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import Content from './content/TestAndEarn/WinnerEntersWalletAddress.mdx';
|
||||
|
||||
export const TestAndEarnWinnerWalletAddress: React.FC<{
|
||||
sx?: SxProps;
|
||||
}> = ({ sx }) => (
|
||||
<Box>
|
||||
<Content />
|
||||
</Box>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Alert, AlertTitle, Link } from '@mui/material';
|
||||
import { TestAndEarnError } from '../../TestAndEarnError';
|
||||
|
||||
<Alert severity="error" sx={{ mb: 4 }}>
|
||||
<AlertTitle>Oh no! Something went wrong</AlertTitle>
|
||||
|
||||
Sorry about that. Here is some more information about the error that occurred:
|
||||
|
||||
<TestAndEarnError/>
|
||||
|
||||
Any error reports that you send us will contain information about your client, the gateway you're using and your IP address.
|
||||
|
||||
We need this information to make Nym better for everyone.
|
||||
|
||||
</Alert>
|
||||
|
||||
If you'd like more information about Test&Earn please look on the <Link href="http://shipyard.nymtech.net/test-and-win">Shipyard website</Link>.
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Card, CardContent, Link, Typography } from '@mui/material';
|
||||
import { TestAndEarnTakePart } from '../../TestAndEarnTakePart';
|
||||
|
||||
### Test privacy & Earn tokens!
|
||||
|
||||
<Typography color="primary.light" component="span">
|
||||
Help us stress test the Nym privacy system and have the chance to earn 1000 NYMs per day!
|
||||
</Typography>
|
||||
|
||||
All you need to do is:
|
||||
|
||||
1. Make sure you're running NymConnect and it is connected.
|
||||
2. Note your reference number.
|
||||
3. NymConnect will ping you a task every day.
|
||||
|
||||
Complete the task, post it on Twitter <Typography color="primary.light" component="span">#PrivacyLovesCompany</Typography> or <Link target="_blank" href="https://t.me/nymchan">Telegram</Link> along with your reference number!
|
||||
|
||||
Thank you for being part of the Nym community and helping to build a flourishing and free digital society. #PrivacyLovesCompany and we love you!
|
||||
|
||||
<Card>
|
||||
<CardContent sx={{ py: 2 }}>
|
||||
<TestAndEarnTakePart content={"I want to take part"} websiteLinkText={"Terms and conditions"}
|
||||
websiteLinkUrl={"https://shipyard.nymtech.net/test-and-win"} sx={{ py: 2 }} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { TestAndEarnEnterWalletAddress } from '../../TestAndEarnEnterWalletAddress';
|
||||
|
||||
### 🎉 Congratulations! One more thing...
|
||||
|
||||
We need one more thing from you, and that is your wallet address:
|
||||
|
||||
<TestAndEarnEnterWalletAddress/>
|
||||
|
||||
Once you've submitting your wallet address over the mixnet, we will be in touch to arrange sending your tokens to you.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user