Feature/mixfetch disconnect (#3890)

* js error message

* Ability to explicitly disconnect mixfetch

* removed unused import

* added disconnect method directly to sdk package

* simplifying error throw

Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com>

* added onunload event listener handler

* Using global instance of mixfetch to disconnect

Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com>

* Bump RC versions

* MixFetch, move unload handler to creation and check for undefined `window`

* Bump RC version

* Force TLS on mixFetch demo

* Add info about working around mixed content errors for mixFetch

---------

Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com>
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
This commit is contained in:
Jędrzej Stuczyński
2023-09-22 10:25:46 +01:00
committed by GitHub
parent 3ccec3857e
commit eccd6e16e2
39 changed files with 224 additions and 88 deletions
Generated
+6 -5
View File
@@ -3020,7 +3020,7 @@ dependencies = [
[[package]]
name = "extension-storage"
version = "1.2.0-rc.5"
version = "1.2.0-rc.7"
dependencies = [
"bip39",
"console_error_panic_hook",
@@ -5613,7 +5613,7 @@ dependencies = [
[[package]]
name = "mix-fetch-wasm"
version = "1.2.0-rc.5"
version = "1.2.0-rc.7"
dependencies = [
"futures",
"js-sys",
@@ -6298,7 +6298,7 @@ dependencies = [
[[package]]
name = "nym-client-wasm"
version = "1.2.0-rc.5"
version = "1.2.0-rc.7"
dependencies = [
"anyhow",
"futures",
@@ -6917,7 +6917,7 @@ dependencies = [
[[package]]
name = "nym-node-tester-wasm"
version = "1.2.0-rc.5"
version = "1.2.0-rc.7"
dependencies = [
"futures",
"js-sys",
@@ -7375,6 +7375,7 @@ dependencies = [
"tokio",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasmtimer",
]
[[package]]
@@ -7510,7 +7511,7 @@ dependencies = [
[[package]]
name = "nym-wasm-sdk"
version = "1.2.0-rc.5"
version = "1.2.0-rc.7"
dependencies = [
"mix-fetch-wasm",
"nym-client-wasm",
+1
View File
@@ -155,6 +155,7 @@ k256 = "0.13"
lazy_static = "1.4.0"
log = "0.4"
once_cell = "1.7.2"
parking_lot = "0.12.1"
rand = "0.8.5"
reqwest = "0.11.18"
serde = "1.0.152"
@@ -13,17 +13,17 @@ pub enum GatewayClientError {
#[error("Connection to the gateway is not established")]
ConnectionNotEstablished,
#[error("Gateway returned an error response - {0}")]
#[error("Gateway returned an error response: {0}")]
GatewayError(String),
#[error("There was a network error - {0}")]
#[error("There was a network error: {0}")]
NetworkError(#[from] WsError),
#[cfg(target_arch = "wasm32")]
#[error("There was a network error")]
#[error("There was a network error: {0}")]
NetworkErrorWasm(#[from] JsError),
#[error("Invalid URL - {0}")]
#[error("Invalid URL: {0}")]
InvalidURL(String),
#[error("No shared key was provided or obtained")]
@@ -32,7 +32,7 @@ pub enum GatewayClientError {
#[error("No bandwidth controller provided")]
NoBandwidthControllerAvailable,
#[error("Bandwidth controller error - {0}")]
#[error("Bandwidth controller error: {0}")]
BandwidthControllerError(#[from] nym_bandwidth_controller::error::BandwidthControllerError),
#[error("Connection was abruptly closed")]
@@ -62,7 +62,7 @@ pub enum GatewayClientError {
#[error("Connection is in an invalid state - please send a bug report")]
ConnectionInInvalidState,
#[error("Failed to finish registration handshake - {0}")]
#[error("Failed to finish registration handshake: {0}")]
RegistrationFailure(HandshakeError),
#[error("Authentication failure")]
+8 -4
View File
@@ -14,14 +14,18 @@ thiserror = { workspace = true }
tokio = { workspace = true, features = ["macros", "sync"] }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
version = "1.24.1"
workspace = true
features = ["signal", "time", "rt"]
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
version = "0.4"
workspace = true
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2.83"
workspace = true
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
workspace = true
features = ["tokio"]
[dev-dependencies]
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal", "test-util", "macros"] }
tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal", "test-util", "macros"] }
+11 -17
View File
@@ -6,14 +6,17 @@ use log::{log, Level};
use std::future::Future;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{error::Error, time::Duration};
use tokio::{
sync::{
mpsc,
watch::{self, error::SendError},
},
time::sleep,
use tokio::sync::{
mpsc,
watch::{self, error::SendError},
};
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::{sleep, timeout};
#[cfg(target_arch = "wasm32")]
use wasmtimer::tokio::{sleep, timeout};
const DEFAULT_SHUTDOWN_TIMER_SECS: u64 = 5;
pub(crate) type SentError = Box<dyn Error + Send + Sync>;
@@ -216,16 +219,10 @@ impl TaskManager {
#[cfg(not(target_arch = "wasm32"))]
let interrupt_future = tokio::signal::ctrl_c();
// in wasm we'll never get our shutdown anyway...
#[cfg(target_arch = "wasm32")]
let interrupt_future = futures::future::pending::<()>();
#[cfg(not(target_arch = "wasm32"))]
let wait_future = tokio::time::sleep(Duration::from_secs(self.shutdown_timer_secs));
// TODO: we should be using a `Delay` here for wasm
#[cfg(target_arch = "wasm32")]
let wait_future = futures::future::pending::<()>();
let wait_future = sleep(Duration::from_secs(self.shutdown_timer_secs));
tokio::select! {
_ = self.notify_tx.closed() => {
@@ -300,7 +297,6 @@ impl TaskClient {
const MAX_NAME_LENGTH: usize = 128;
const OVERFLOW_NAME: &'static str = "reached maximum TaskClient children name depth";
#[cfg(not(target_arch = "wasm32"))]
const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(5);
fn new(
@@ -428,12 +424,10 @@ impl TaskClient {
pub async fn recv_timeout(&mut self) {
if self.mode.is_dummy() {
#[cfg_attr(target_arch = "wasm32", allow(clippy::needless_return))]
return pending().await;
}
#[cfg(not(target_arch = "wasm32"))]
if let Err(timeout) = tokio::time::timeout(
if let Err(timeout) = timeout(
Self::SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT,
self.recv(),
)
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "extension-storage"
version = "1.2.0-rc.5"
version = "1.2.0-rc.7"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/nymtech/nym"
+1
View File
@@ -4595,6 +4595,7 @@ dependencies = [
"tokio",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasmtimer",
]
[[package]]
+1
View File
@@ -5,6 +5,7 @@
"license": "Apache 2.0",
"workspaces": [
"dist/wasm/**",
"dist/node/**",
"sdk/typescript/packages/**",
"ts-packages/*",
"nym-wallet",
@@ -1,6 +1,6 @@
{
"name": "@nymproject/contract-clients",
"version": "1.2.0-rc.5",
"version": "1.2.0-rc.7",
"description": "A client for all Nym smart contracts",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
+3 -1
View File
@@ -7,16 +7,18 @@ import Box from '@mui/material/Box';
import { mixFetch } from '@nymproject/mix-fetch-full-fat';
import Stack from '@mui/material/Stack';
import Paper from '@mui/material/Paper';
import type { SetupMixFetchOps } from '@nymproject/mix-fetch';
const defaultUrl = 'https://nymtech.net/favicon.svg';
const args = { mode: 'unsafe-ignore-cors' };
const mixFetchOptions = {
const mixFetchOptions: SetupMixFetchOps = {
preferredGateway: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', // with WSS
preferredNetworkRequester:
'GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW',
mixFetchOverride: {
requestTimeoutMs: 60_000,
},
forceTls: true, // force WSS
};
export const MixFetch = () => {
+4 -4
View File
@@ -36,10 +36,10 @@
"@mui/icons-material": "^5.14.9",
"@mui/lab": "^5.0.0-alpha.145",
"@mui/material": "^5.14.8",
"@nymproject/contract-clients": "^1.2.0-rc.5",
"@nymproject/mix-fetch": "^1.2.0-rc.5",
"@nymproject/mix-fetch-full-fat": "^1.2.0-rc.5",
"@nymproject/sdk-full-fat": "^1.2.0-rc.5",
"@nymproject/contract-clients": "^1.2.0-rc.7",
"@nymproject/mix-fetch": "^1.2.0-rc.7",
"@nymproject/mix-fetch-full-fat": "^1.2.0-rc.7",
"@nymproject/sdk-full-fat": "^1.2.0-rc.7",
"chain-registry": "^1.19.0",
"cosmjs-types": "^0.8.0",
"next": "^13.4.19",
@@ -42,3 +42,24 @@ root CA certificate you need in our list. [Send us a PR](https://github.com/nymt
3. If you are using `mixFetch` in a web app with HTTPS you will need to use a gateway that has Secure Websockets to
avoid getting a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error.
4. Workaround for Mixed Content Errors because you might be using `mixFetch` from web app served from HTTPS while
connecting a gateway that only listens on a plain websocket, without HTTPS/TLS.
We are currently working on a feature that adds a Secure Websocket (WSS) listener with HTTPS (automatically generated with LetsEncrypt) to Nym's
gateways.
While we are adding this feature, you can use a gateway that has Caddy providing HTTPS/WSS by adding this to the options when settings up `mixFetch`:
```ts
import type { SetupMixFetchOps } from '@nymproject/mix-fetch';
const mixFetchOptions: SetupMixFetchOps = {
preferredGateway: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', // with WSS
preferredNetworkRequester:
'GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW',
mixFetchOverride: {
requestTimeoutMs: 60_000,
},
forceTls: true, // force WSS
};
```
@@ -5,7 +5,7 @@
"source": "src/index.html",
"dependencies": {
"parcel": "^2.9.3",
"@nymproject/mix-fetch": ">=1.2.0-rc.5 || ^1"
"@nymproject/mix-fetch": ">=1.2.0-rc.7 || ^1"
},
"scripts": {
"start": "parcel --no-cache",
@@ -3,7 +3,7 @@
"version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {
"@nymproject/mix-fetch": ">=1.2.0-rc.2 || ^1"
"@nymproject/mix-fetch": ">=1.2.0-rc.7 || ^1"
},
"devDependencies": {
"@babel/core": "^7.22.10",
@@ -4,7 +4,7 @@
"license": "Apache-2.0",
"source": "../src/index.html",
"dependencies": {
"@nymproject/mix-fetch": ">=1.2.0-rc.2 || ^1"
"@nymproject/mix-fetch": ">=1.2.0-rc.7 || ^1"
},
"scripts": {
"start": "npx parcel --no-cache",
@@ -1,4 +1,4 @@
import { createMixFetch } from '@nymproject/mix-fetch';
import { createMixFetch, disconnectMixFetch } from '@nymproject/mix-fetch';
function appendOutput(value: string) {
const el = document.getElementById('output') as HTMLPreElement;
@@ -54,6 +54,10 @@ async function main() {
appendOutput(JSON.stringify(resp, null, 2));
appendOutput(JSON.stringify({ text }, null, 2));
console.log('disconnecting');
await disconnectMixFetch();
console.log('disconnected! all further usages should fail');
// get an image
url = 'https://nymtech.net/favicon.svg';
resp = await mixFetch(url, args);
@@ -1,6 +1,6 @@
{
"name": "@nymproject/mix-fetch",
"version": "1.2.0-rc.5",
"version": "1.2.0-rc.7",
"description": "This package is a drop-in replacement for `fetch` to send HTTP requests over the Nym Mixnet.",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
@@ -33,7 +33,7 @@
"tsc": "tsc --noEmit true"
},
"dependencies": {
"@nymproject/mix-fetch-wasm": ">=1.2.0-rc.0 || 1",
"@nymproject/mix-fetch-wasm": ">=1.2.0-rc.7 || 1",
"comlink": "^4.3.1"
},
"devDependencies": {
@@ -81,4 +81,4 @@
"private": false,
"type": "module",
"types": "./dist/esm/index.d.ts"
}
}
@@ -70,6 +70,7 @@ export const createMixFetch = async (): Promise<IMixFetch> => {
const body = Object.values(workerResponse.body)[0]; // we are expecting only one value to be set in `.body`
return new Response(body, { headers, status, statusText });
},
disconnectMixFetch: wrappedWorker.disconnectMixFetch,
};
return mixFetchWebWorker;
@@ -22,10 +22,20 @@ declare global {
* @param opts Optional settings
*/
export const createMixFetch = async (opts?: SetupMixFetchOps) => {
if (!window) {
throw new Error('`window` is not defined');
}
if (!window.__mixFetchGlobal) {
// load the worker and set up mixFetch with defaults
window.__mixFetchGlobal = await createMixFetchInternal();
await window.__mixFetchGlobal.setupMixFetch(opts);
window.onunload = async () => {
if (window.__mixFetchGlobal) {
await window.__mixFetchGlobal.disconnectMixFetch();
}
};
}
return window.__mixFetchGlobal;
};
@@ -49,3 +59,21 @@ export const mixFetch: IMixFetchFn = async (url, args, opts?: SetupMixFetchOps)
// execute user request
return instance.mixFetch(url, args);
};
/**
* Stops the usage of mixFetch and disconnect the client from the mixnet.
*/
export const disconnectMixFetch = async (): Promise<void> => {
if (!window) {
throw new Error('`window` is not defined');
}
// JS: I'm ignoring this lint (no-else-return) because I want to explicitly state
// that `__mixFetchGlobal` is definitely not null in the else branch.
if (!window.__mixFetchGlobal) {
throw new Error("mixFetch hasn't been setup");
// eslint-disable-next-line no-else-return
} else {
return window.__mixFetchGlobal.disconnectMixFetch();
}
};
@@ -12,11 +12,13 @@ export type SetupMixFetchOps = MixFetchOpts & {
export interface IMixFetchWebWorker {
mixFetch: IMixFetchWorkerFn;
setupMixFetch: (opts?: SetupMixFetchOps) => Promise<void>;
disconnectMixFetch: () => Promise<void>;
}
export interface IMixFetch {
mixFetch: IMixFetchFn;
setupMixFetch: (opts?: SetupMixFetchOps) => Promise<void>;
disconnectMixFetch: () => Promise<void>;
}
export enum EventKinds {
@@ -1,5 +1,5 @@
/* eslint-disable no-restricted-globals */
import { setupMixFetch } from '@nymproject/mix-fetch-wasm';
import { setupMixFetch, disconnectMixFetch } from '@nymproject/mix-fetch-wasm';
import * as Comlink from 'comlink';
import type { IMixFetchWebWorker, LoadedEvent } from '../types';
import { EventKinds, ResponseBodyConfigMap, ResponseBodyConfigMapDefaults } from '../types';
@@ -53,6 +53,11 @@ export async function run() {
}
await setupMixFetch(opts || {});
},
disconnectMixFetch: async () => {
console.log('[Worker] --- disconnectMixFetch ---');
await disconnectMixFetch();
},
};
// start comlink listening for messages and handle them above
@@ -1,6 +1,6 @@
{
"name": "@nymproject/node-tester",
"version": "1.2.0-rc.5",
"version": "1.2.0-rc.7",
"description": "This package provides a tester that can send test packets to mixnode that is part of the Nym Mixnet.",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
@@ -25,7 +25,7 @@
"tsc": "tsc --noEmit true"
},
"dependencies": {
"@nymproject/nym-node-tester-wasm": ">=1.2.0-rc.0 || ^1",
"@nymproject/nym-node-tester-wasm": ">=1.2.0-rc.7 || ^1",
"comlink": "^4.3.1"
},
"devDependencies": {
@@ -71,4 +71,4 @@
"private": false,
"type": "module",
"types": "./dist/esm/index.d.ts"
}
}
@@ -27,7 +27,7 @@
"tsc": "tsc --noEmit true"
},
"dependencies": {
"@nymproject/nym-client-wasm-node": ">=1.2.0-rc.5 || ^1",
"@nymproject/nym-client-wasm-node": ">=1.2.0-rc.7 || ^1",
"comlink": "^4.3.1"
},
"devDependencies": {
@@ -1,6 +1,6 @@
{
"name": "@nymproject/sdk-react",
"version": "1.2.0-rc.5",
"version": "1.2.0-rc.7",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
"files": [
@@ -20,7 +20,7 @@
"tsc": "tsc --noEmit true"
},
"dependencies": {
"@nymproject/sdk": ">=1.2.0-rc.2 || ^1"
"@nymproject/sdk": ">=1.2.0-rc.7 || ^1"
},
"devDependencies": {
"@babel/core": "^7.17.5",
@@ -67,4 +67,4 @@
"private": false,
"type": "module",
"types": "./dist/index.d.ts"
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@nymproject/sdk",
"version": "1.2.0-rc.5",
"version": "1.2.0-rc.7",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
"files": [
@@ -30,7 +30,7 @@
"tsc": "tsc --noEmit true"
},
"dependencies": {
"@nymproject/nym-client-wasm": ">=1.2.0-rc.0 || ^1",
"@nymproject/nym-client-wasm": ">=1.2.0-rc.7 || ^1",
"comlink": "^4.3.1"
},
"devDependencies": {
@@ -80,4 +80,4 @@
"private": false,
"type": "module",
"types": "./dist/esm/index.d.ts"
}
}
@@ -1,6 +1,6 @@
{
"name": "@nymproject/nym-validator-client",
"version": "1.2.0-rc.5",
"version": "1.2.0-rc.7",
"description": "A TypeScript client for interacting with smart contracts in Nym validators",
"license": "Apache-2.0",
"author": "Nym Technologies SA (https://nymtech.net)",
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.2.0-rc.5"
version = "1.2.0-rc.7"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-wasm-sdk"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.2.0-rc.5"
version = "1.2.0-rc.7"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy"]
license = "Apache-2.0"
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "mix-fetch-wasm"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.2.0-rc.5"
version = "1.2.0-rc.7"
edition = "2021"
keywords = ["nym", "fetch", "wasm", "webassembly", "privacy"]
license = "Apache-2.0"
@@ -55,7 +55,7 @@ func setLoggingLevel(_ js.Value, args []js.Value) any {
func mixFetch(_ js.Value, args []js.Value) (any, error) {
if !rust_bridge.RsIsInitialised() {
return nil, errors.New("mix fetch hasn't been initialised")
return nil, errors.New("mix fetch is not available")
}
if len(args) == 0 {
+28 -8
View File
@@ -33,6 +33,7 @@ const {
send_client_data,
start_new_mixnet_connection,
setupMixFetch,
disconnectMixFetch,
setupMixFetchWithConfig,
mix_fetch_initialised,
finish_mixnet_connection} = wasm_bindgen;
@@ -120,12 +121,12 @@ async function wasm_bindgenSetup() {
}
async function nativeSetup() {
const preferredGateway = "6qQYb4ArXANU6HJDxzH4PFCUqYb39Dae2Gem2KpxescM";
const validator = 'https://qa-nym-api.qa.nymte.ch/api';
// const preferredGateway = "6qQYb4ArXANU6HJDxzH4PFCUqYb39Dae2Gem2KpxescM";
// const validator = 'https://qa-nym-api.qa.nymte.ch/api';
// local
const mixFetchNetworkRequesterAddress= "2o47bhnXWna6VEyt4mXMGQQAbXfpKmX7BkjkxUz8uQVi.6uQGnCqSczpXwh86NdbsCoDDXuqZQM9Uwko8GE7uC9g8@6qQYb4ArXANU6HJDxzH4PFCUqYb39Dae2Gem2KpxescM";
// const mixFetchNetworkRequesterAddress= "GqiGWmKRCbGQFSqH88BzLKijvZgipnqhmbNFsmkZw84t.4L8sXFuAUyUYyHZYgMdM3AtiusKnYUft6Pd8e41rrCHA@6qQYb4ArXANU6HJDxzH4PFCUqYb39Dae2Gem2KpxescM";
// const preferredNetworkRequester= "2o47bhnXWna6VEyt4mXMGQQAbXfpKmX7BkjkxUz8uQVi.6uQGnCqSczpXwh86NdbsCoDDXuqZQM9Uwko8GE7uC9g8@6qQYb4ArXANU6HJDxzH4PFCUqYb39Dae2Gem2KpxescM";
// const preferredNetworkRequester= "GqiGWmKRCbGQFSqH88BzLKijvZgipnqhmbNFsmkZw84t.4L8sXFuAUyUYyHZYgMdM3AtiusKnYUft6Pd8e41rrCHA@6qQYb4ArXANU6HJDxzH4PFCUqYb39Dae2Gem2KpxescM";
// those are just some examples, there are obviously more permutations;
// note, the extra optional argument is of the following type:
@@ -147,9 +148,9 @@ async function nativeSetup() {
*/
// #1
// await setupMixFetch(mixFetchNetworkRequesterAddress)
// await setupMixFetch(preferredNetworkRequester)
// #2
// await setupMixFetch(mixFetchNetworkRequesterAddress, { nymApiUrl: validator })
// await setupMixFetch(preferredNetworkRequester, { nymApiUrl: validator })
// // #3
const noCoverTrafficOverride = {
traffic: { disableMainPoissonPacketDistribution: true },
@@ -187,18 +188,37 @@ async function testMixFetch() {
const {target} = event.data.args;
const url = target;
const args = { mode: "cors", redirect: "manual" }
// const args = { mode: "unsafe-ignore-cors" }
// const args = { mode: "ors", redirect: "manual", signal }
const args = { mode: "unsafe-ignore-cors" }
try {
console.log('using mixFetch...');
const mixFetchRes = await mixFetch(url, args)
console.log(">>> MIX FETCH")
await logFetchResult(mixFetchRes)
console.log('done')
} catch(e) {
console.error("mix fetch request failure: ", e)
}
console.log("will disconnect");
await disconnectMixFetch()
try {
console.log('using mixFetch...');
const mixFetchRes = await mixFetch(url, args)
console.log(">>> MIX FETCH")
await logFetchResult(mixFetchRes)
console.log('done')
} catch(e) {
console.error("mix fetch request failure: ", e)
}
// try {
// console.log('using normal Fetch...');
// const fetchRes = await fetch(url, args)
+8
View File
@@ -39,6 +39,14 @@ impl ActiveRequests {
request_id
}
pub async fn invalidate_all(&self) {
let mut guard = self.inner.lock().await;
for (id, _req) in guard.drain() {
let err = MixFetchError::AbortedRequest { request_id: id };
goWasmInjectConnError(id.to_string(), err.to_string())
}
}
pub async fn get_sending_sequence(&self, id: RequestId) -> Option<u64> {
let mut guard = self.inner.lock().await;
if let Some(req) = guard.get_mut(&id) {
+28 -4
View File
@@ -10,6 +10,8 @@ use crate::socks_helpers::{socks5_connect_request, socks5_data_request};
use crate::{config, RequestId};
use js_sys::Promise;
use nym_socks5_requests::RemoteAddress;
use std::sync::atomic::{AtomicBool, Ordering};
use tokio::sync::Mutex;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use wasm_client_core::client::base_client::{BaseClientBuilder, ClientInput, ClientOutput};
@@ -26,6 +28,8 @@ use wasm_utils::error::PromisableResult;
#[wasm_bindgen]
pub struct MixFetchClient {
invalidated: AtomicBool,
mix_fetch_config: config::MixFetch,
self_address: Recipient,
@@ -34,9 +38,8 @@ pub struct MixFetchClient {
requests: ActiveRequests,
// even though we don't use graceful shutdowns, other components rely on existence of this struct
// and if it's dropped, everything will start going offline
_task_manager: TaskManager,
// this has to be guarded by a mutex to be able to disconnect with an immutable reference
_task_manager: Mutex<TaskManager>,
}
#[wasm_bindgen]
@@ -126,12 +129,13 @@ impl MixFetchClientBuilder {
Self::start_reconstructor(client_output, active_requests.clone());
Ok(MixFetchClient {
invalidated: AtomicBool::new(false),
mix_fetch_config: self.config.mix_fetch,
self_address,
client_input,
requests: active_requests,
// this cannot failed as we haven't passed an external task manager
_task_manager: started_client.task_handle.try_into_task_manager().unwrap(),
_task_manager: Mutex::new(started_client.task_handle.try_into_task_manager().unwrap()),
})
}
}
@@ -164,6 +168,26 @@ impl MixFetchClient {
})
}
pub fn active(&self) -> bool {
!self.invalidated.load(Ordering::Relaxed)
}
pub async fn disconnect(&self) -> Result<(), MixFetchError> {
self.invalidated.store(true, Ordering::Relaxed);
console_log!("sending shutdown signal");
let mut shutdown_guard = self._task_manager.lock().await;
shutdown_guard.signal_shutdown().ok();
console_log!("waiting for shutdown to complete");
shutdown_guard.wait_for_shutdown().await;
self.requests.invalidate_all().await;
console_log!("done");
Ok(())
}
pub fn self_address(&self) -> String {
self.self_address.to_string()
}
+3
View File
@@ -40,6 +40,9 @@ pub enum MixFetchError {
#[error("mix fetch has already been initialised before")]
AlreadyInitialised,
#[error("mix fetch client has already been disconnected")]
Disconnected,
#[error("provided mix fetch url wasn't a string")]
NotStringMixFetchUrl,
+20 -1
View File
@@ -126,6 +126,20 @@ pub fn setup_mix_fetch_with_config(config: MixFetchConfig, opts: MixFetchOptsSim
})
}
#[wasm_bindgen(js_name = disconnectMixFetch)]
pub async fn disconnect_mix_fetch() -> Promise {
future_to_promise(async move {
disconnect_mix_fetch_async()
.await
.map(|_| JsValue::undefined())
.map_promise_err()
})
}
async fn disconnect_mix_fetch_async() -> Result<(), MixFetchError> {
mix_fetch_client()?.disconnect().await
}
pub(super) fn set_mix_fetch_client(mix_fetch_client: MixFetchClient) -> Result<(), MixFetchError> {
MIX_FETCH
.set(mix_fetch_client)
@@ -133,7 +147,12 @@ pub(super) fn set_mix_fetch_client(mix_fetch_client: MixFetchClient) -> Result<(
}
pub(super) fn mix_fetch_client() -> Result<&'static MixFetchClient, MixFetchError> {
MIX_FETCH.get().ok_or(MixFetchError::Uninitialised)
let mix_fetch = MIX_FETCH.get().ok_or(MixFetchError::Uninitialised)?;
if !mix_fetch.active() {
return Err(MixFetchError::Disconnected);
}
Ok(mix_fetch)
}
async fn setup_mix_fetch_async(
+8 -6
View File
@@ -1,7 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{mix_fetch_client, MIX_FETCH};
use crate::error::MixFetchError;
use crate::mix_fetch_client;
use js_sys::Promise;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
@@ -26,7 +27,7 @@ pub fn send_client_data(stringified_request_id: String, data: Vec<u8>) -> Promis
future_to_promise(async move {
// this error should be impossible in normal use
// (unless, of course, user is messing around, but then it's their fault for this panic)
let mix_fetch = mix_fetch_client().expect("mix fetch hasn't been setup");
let mix_fetch = mix_fetch_client().map_err(JsValue::from)?;
mix_fetch.forward_request_content(request_id, data).await?;
Ok(JsValue::undefined())
})
@@ -44,7 +45,7 @@ pub fn start_new_mixnet_connection(target: String) -> Promise {
future_to_promise(async move {
// this error should be impossible in normal use
// (unless, of course, user is messing around, but then it's their fault for this panic)
let mix_fetch = mix_fetch_client().expect("mix fetch hasn't been setup");
let mix_fetch = mix_fetch_client().map_err(JsValue::from)?;
mix_fetch
.connect_to_mixnet(target)
.await
@@ -54,8 +55,9 @@ pub fn start_new_mixnet_connection(target: String) -> Promise {
}
#[wasm_bindgen]
pub fn mix_fetch_initialised() -> bool {
MIX_FETCH.get().is_some()
pub fn mix_fetch_initialised() -> Result<bool, MixFetchError> {
mix_fetch_client()?;
Ok(true)
}
/// Called by go runtime whenever it's done with a connection
@@ -76,7 +78,7 @@ pub fn finish_mixnet_connection(stringified_request_id: String) -> Promise {
future_to_promise(async move {
// this error should be impossible in normal use
// (unless, of course, user is messing around, but then it's their fault for this panic)
let mix_fetch = mix_fetch_client().expect("mix fetch hasn't been setup");
let mix_fetch = mix_fetch_client().map_err(JsValue::from)?;
mix_fetch.disconnect_from_mixnet(request_id).await?;
Ok(JsValue::undefined())
})
+1 -1
View File
@@ -23,7 +23,7 @@ mod request_writer;
mod socks_helpers;
#[cfg(target_arch = "wasm32")]
pub(crate) use fetch::{mix_fetch_client, RequestId, MIX_FETCH};
pub(crate) use fetch::{mix_fetch_client, RequestId};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-node-tester-wasm"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.2.0-rc.5"
version = "1.2.0-rc.7"
edition = "2021"
keywords = ["nym", "sphinx", "webassembly", "privacy", "tester"]
license = "Apache-2.0"
-5
View File
@@ -2844,11 +2844,6 @@
resolved "https://registry.yarnpkg.com/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-16.8.1.tgz#6ec1930aaf4d9dea19149d6b3d100b2c7e69d582"
integrity sha512-yHZ5FAcx54rVc31R0yIpniepkHMPwaxG23l8E/ZYbL1iPwE/Wc1HeUzUvxUuSXtguRp7ihcRhaUEPkcSl2EAVw==
"@nymproject/nym-client-wasm-node@>=1.2.0-rc.5 || ^1":
version "1.2.0-rc.5"
resolved "https://registry.yarnpkg.com/@nymproject/nym-client-wasm-node/-/nym-client-wasm-node-1.2.0-rc.5.tgz#464cc905a029cedb3acbe5513f60d47c22a28137"
integrity sha512-NF15b+DVfLgC3zx3wszZkxdyaDvQcxxxNF4sr6y5osQDMXbixRMU/rNDF8kEa/cSDNipxf8G5Z0ApJSGKjcehQ==
"@nymproject/nym-validator-client@^0.18.0":
version "0.18.0"
resolved "https://registry.yarnpkg.com/@nymproject/nym-validator-client/-/nym-validator-client-0.18.0.tgz#4dd72bafdf6c72b603242f32c0bb9a1f9e475b98"