diff --git a/Cargo.lock b/Cargo.lock index 7674b1b773..4fd1750af7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index e37ea29c84..886b9a70f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/common/client-libs/gateway-client/src/error.rs b/common/client-libs/gateway-client/src/error.rs index 65a6e6ca98..081ea3f999 100644 --- a/common/client-libs/gateway-client/src/error.rs +++ b/common/client-libs/gateway-client/src/error.rs @@ -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")] diff --git a/common/task/Cargo.toml b/common/task/Cargo.toml index 500cdb2a53..6f0f6b7579 100644 --- a/common/task/Cargo.toml +++ b/common/task/Cargo.toml @@ -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"] } diff --git a/common/task/src/manager.rs b/common/task/src/manager.rs index 41dd3e7558..a59ae545d9 100644 --- a/common/task/src/manager.rs +++ b/common/task/src/manager.rs @@ -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; @@ -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(), ) diff --git a/nym-browser-extension/storage/Cargo.toml b/nym-browser-extension/storage/Cargo.toml index 0a878e27d0..629acd8548 100644 --- a/nym-browser-extension/storage/Cargo.toml +++ b/nym-browser-extension/storage/Cargo.toml @@ -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" diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index 06e7e47d01..409a1ee9e2 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -4595,6 +4595,7 @@ dependencies = [ "tokio", "wasm-bindgen", "wasm-bindgen-futures", + "wasmtimer", ] [[package]] diff --git a/package.json b/package.json index 7dec6236ae..de7428d0d5 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "license": "Apache 2.0", "workspaces": [ "dist/wasm/**", + "dist/node/**", "sdk/typescript/packages/**", "ts-packages/*", "nym-wallet", diff --git a/sdk/typescript/codegen/contract-clients/package.json b/sdk/typescript/codegen/contract-clients/package.json index a85c6b17eb..7b612f2c2c 100644 --- a/sdk/typescript/codegen/contract-clients/package.json +++ b/sdk/typescript/codegen/contract-clients/package.json @@ -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", diff --git a/sdk/typescript/docs/components/mixfetch.tsx b/sdk/typescript/docs/components/mixfetch.tsx index 18b7470a3a..7d72eb7e6a 100644 --- a/sdk/typescript/docs/components/mixfetch.tsx +++ b/sdk/typescript/docs/components/mixfetch.tsx @@ -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 = () => { diff --git a/sdk/typescript/docs/package.json b/sdk/typescript/docs/package.json index 867c3ae47d..91904542dc 100644 --- a/sdk/typescript/docs/package.json +++ b/sdk/typescript/docs/package.json @@ -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", diff --git a/sdk/typescript/docs/pages/guides/mix-fetch.mdx b/sdk/typescript/docs/pages/guides/mix-fetch.mdx index 50cd2abbcd..edaa364eaf 100644 --- a/sdk/typescript/docs/pages/guides/mix-fetch.mdx +++ b/sdk/typescript/docs/pages/guides/mix-fetch.mdx @@ -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 +}; +``` diff --git a/sdk/typescript/examples/mix-fetch/browser/package.json b/sdk/typescript/examples/mix-fetch/browser/package.json index b6be4fc525..7d6f737298 100644 --- a/sdk/typescript/examples/mix-fetch/browser/package.json +++ b/sdk/typescript/examples/mix-fetch/browser/package.json @@ -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", diff --git a/sdk/typescript/packages/mix-fetch/internal-dev/package.json b/sdk/typescript/packages/mix-fetch/internal-dev/package.json index 549143b0d1..6ea459f549 100644 --- a/sdk/typescript/packages/mix-fetch/internal-dev/package.json +++ b/sdk/typescript/packages/mix-fetch/internal-dev/package.json @@ -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", diff --git a/sdk/typescript/packages/mix-fetch/internal-dev/parcel/package.json b/sdk/typescript/packages/mix-fetch/internal-dev/parcel/package.json index d0ae96d225..886012198e 100644 --- a/sdk/typescript/packages/mix-fetch/internal-dev/parcel/package.json +++ b/sdk/typescript/packages/mix-fetch/internal-dev/parcel/package.json @@ -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", diff --git a/sdk/typescript/packages/mix-fetch/internal-dev/src/index.ts b/sdk/typescript/packages/mix-fetch/internal-dev/src/index.ts index 8a678d3cdb..d6c3795331 100644 --- a/sdk/typescript/packages/mix-fetch/internal-dev/src/index.ts +++ b/sdk/typescript/packages/mix-fetch/internal-dev/src/index.ts @@ -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); diff --git a/sdk/typescript/packages/mix-fetch/package.json b/sdk/typescript/packages/mix-fetch/package.json index f4773281ed..f60542e52a 100644 --- a/sdk/typescript/packages/mix-fetch/package.json +++ b/sdk/typescript/packages/mix-fetch/package.json @@ -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" -} \ No newline at end of file +} diff --git a/sdk/typescript/packages/mix-fetch/src/create-mix-fetch.ts b/sdk/typescript/packages/mix-fetch/src/create-mix-fetch.ts index d45b74704e..9739042932 100644 --- a/sdk/typescript/packages/mix-fetch/src/create-mix-fetch.ts +++ b/sdk/typescript/packages/mix-fetch/src/create-mix-fetch.ts @@ -70,6 +70,7 @@ export const createMixFetch = async (): Promise => { 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; diff --git a/sdk/typescript/packages/mix-fetch/src/index.ts b/sdk/typescript/packages/mix-fetch/src/index.ts index dca21cb2e3..9b60a5977e 100644 --- a/sdk/typescript/packages/mix-fetch/src/index.ts +++ b/sdk/typescript/packages/mix-fetch/src/index.ts @@ -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 => { + 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(); + } +}; diff --git a/sdk/typescript/packages/mix-fetch/src/types.ts b/sdk/typescript/packages/mix-fetch/src/types.ts index 64dbab8366..553c02cc09 100644 --- a/sdk/typescript/packages/mix-fetch/src/types.ts +++ b/sdk/typescript/packages/mix-fetch/src/types.ts @@ -12,11 +12,13 @@ export type SetupMixFetchOps = MixFetchOpts & { export interface IMixFetchWebWorker { mixFetch: IMixFetchWorkerFn; setupMixFetch: (opts?: SetupMixFetchOps) => Promise; + disconnectMixFetch: () => Promise; } export interface IMixFetch { mixFetch: IMixFetchFn; setupMixFetch: (opts?: SetupMixFetchOps) => Promise; + disconnectMixFetch: () => Promise; } export enum EventKinds { diff --git a/sdk/typescript/packages/mix-fetch/src/worker/main.ts b/sdk/typescript/packages/mix-fetch/src/worker/main.ts index fee270b055..f126f68bb8 100644 --- a/sdk/typescript/packages/mix-fetch/src/worker/main.ts +++ b/sdk/typescript/packages/mix-fetch/src/worker/main.ts @@ -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 diff --git a/sdk/typescript/packages/node-tester/package.json b/sdk/typescript/packages/node-tester/package.json index 4c0911ac64..f6ba856373 100644 --- a/sdk/typescript/packages/node-tester/package.json +++ b/sdk/typescript/packages/node-tester/package.json @@ -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" -} \ No newline at end of file +} diff --git a/sdk/typescript/packages/nodejs-client/package.json b/sdk/typescript/packages/nodejs-client/package.json index dcd5ecd3cc..906a603198 100644 --- a/sdk/typescript/packages/nodejs-client/package.json +++ b/sdk/typescript/packages/nodejs-client/package.json @@ -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": { diff --git a/sdk/typescript/packages/sdk-react/package.json b/sdk/typescript/packages/sdk-react/package.json index 9d6d1cf814..f86056b55e 100644 --- a/sdk/typescript/packages/sdk-react/package.json +++ b/sdk/typescript/packages/sdk-react/package.json @@ -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" -} \ No newline at end of file +} diff --git a/sdk/typescript/packages/sdk/package.json b/sdk/typescript/packages/sdk/package.json index 7d634b30cb..387483b19a 100644 --- a/sdk/typescript/packages/sdk/package.json +++ b/sdk/typescript/packages/sdk/package.json @@ -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" -} \ No newline at end of file +} diff --git a/sdk/typescript/packages/validator-client/package.json b/sdk/typescript/packages/validator-client/package.json index c6c2e3e778..e766d43338 100644 --- a/sdk/typescript/packages/validator-client/package.json +++ b/sdk/typescript/packages/validator-client/package.json @@ -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)", diff --git a/wasm/client/Cargo.toml b/wasm/client/Cargo.toml index 1a3056ec60..57592de674 100644 --- a/wasm/client/Cargo.toml +++ b/wasm/client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nym-client-wasm" authors = ["Dave Hrycyszyn ", "Jedrzej Stuczynski "] -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" diff --git a/wasm/full-nym-wasm/Cargo.toml b/wasm/full-nym-wasm/Cargo.toml index 291c8f053f..486f5eefa2 100644 --- a/wasm/full-nym-wasm/Cargo.toml +++ b/wasm/full-nym-wasm/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nym-wasm-sdk" authors = ["Jedrzej Stuczynski "] -version = "1.2.0-rc.5" +version = "1.2.0-rc.7" edition = "2021" keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy"] license = "Apache-2.0" diff --git a/wasm/mix-fetch/Cargo.toml b/wasm/mix-fetch/Cargo.toml index 2fb6e180e8..e43eab3bee 100644 --- a/wasm/mix-fetch/Cargo.toml +++ b/wasm/mix-fetch/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mix-fetch-wasm" authors = ["Jedrzej Stuczynski "] -version = "1.2.0-rc.5" +version = "1.2.0-rc.7" edition = "2021" keywords = ["nym", "fetch", "wasm", "webassembly", "privacy"] license = "Apache-2.0" diff --git a/wasm/mix-fetch/go-mix-conn/internal/bridge/go_bridge/go_bridge.go b/wasm/mix-fetch/go-mix-conn/internal/bridge/go_bridge/go_bridge.go index e44c8cb9d7..db84d033f3 100644 --- a/wasm/mix-fetch/go-mix-conn/internal/bridge/go_bridge/go_bridge.go +++ b/wasm/mix-fetch/go-mix-conn/internal/bridge/go_bridge/go_bridge.go @@ -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 { diff --git a/wasm/mix-fetch/internal-dev/worker.js b/wasm/mix-fetch/internal-dev/worker.js index c4d7d1e318..5c371af2f9 100644 --- a/wasm/mix-fetch/internal-dev/worker.js +++ b/wasm/mix-fetch/internal-dev/worker.js @@ -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) diff --git a/wasm/mix-fetch/src/active_requests.rs b/wasm/mix-fetch/src/active_requests.rs index a7407aa317..3d4e8a5f31 100644 --- a/wasm/mix-fetch/src/active_requests.rs +++ b/wasm/mix-fetch/src/active_requests.rs @@ -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 { let mut guard = self.inner.lock().await; if let Some(req) = guard.get_mut(&id) { diff --git a/wasm/mix-fetch/src/client.rs b/wasm/mix-fetch/src/client.rs index e222056ab0..15c402bb23 100644 --- a/wasm/mix-fetch/src/client.rs +++ b/wasm/mix-fetch/src/client.rs @@ -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, } #[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() } diff --git a/wasm/mix-fetch/src/error.rs b/wasm/mix-fetch/src/error.rs index 5333f1849c..8bdb7ae9bb 100644 --- a/wasm/mix-fetch/src/error.rs +++ b/wasm/mix-fetch/src/error.rs @@ -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, diff --git a/wasm/mix-fetch/src/fetch.rs b/wasm/mix-fetch/src/fetch.rs index d7ee3ba26b..2fd8da80dc 100644 --- a/wasm/mix-fetch/src/fetch.rs +++ b/wasm/mix-fetch/src/fetch.rs @@ -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( diff --git a/wasm/mix-fetch/src/go_bridge.rs b/wasm/mix-fetch/src/go_bridge.rs index d9acd8d37c..2069572b8e 100644 --- a/wasm/mix-fetch/src/go_bridge.rs +++ b/wasm/mix-fetch/src/go_bridge.rs @@ -1,7 +1,8 @@ // Copyright 2023 - Nym Technologies SA // 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) -> 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 { + 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()) }) diff --git a/wasm/mix-fetch/src/lib.rs b/wasm/mix-fetch/src/lib.rs index f2e9b695c5..1f3f62111b 100644 --- a/wasm/mix-fetch/src/lib.rs +++ b/wasm/mix-fetch/src/lib.rs @@ -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::*; diff --git a/wasm/node-tester/Cargo.toml b/wasm/node-tester/Cargo.toml index 58a0fd1da1..12b68ff4cc 100644 --- a/wasm/node-tester/Cargo.toml +++ b/wasm/node-tester/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nym-node-tester-wasm" authors = ["Jedrzej Stuczynski "] -version = "1.2.0-rc.5" +version = "1.2.0-rc.7" edition = "2021" keywords = ["nym", "sphinx", "webassembly", "privacy", "tester"] license = "Apache-2.0" diff --git a/yarn.lock b/yarn.lock index 6a8d825d6f..024fb6dd07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"