Switch from yarn to pnpm (#6779)

* switch from yarn to pnpm

* Remove full-nym-wasm (#6796)

* Remove nym-browser-extension (#6798)

* Remove nym-browser-extension

* remove unused from makefile

* Remove Node tester (#6800)

* Remove dom-utils (#6801)

* gh-actions: remove pnpm version

* nuke dist and pkg

* add missing dependency

* set node version to 24 and pnpm version to 11

* upgrade lock file from pnpm version 9 to 11

* pnpm add approved builds

* yarn -> pnpm

* upgrade jest version

* yarn -> pnpm

* Remove unused cfg; clippy!

* pnpm: when dev mode is on, unfreeze the lock file

* pnpm approve more scripts

* pnpm syntax error

* add `pnpm i`

* disable eslint temporarily while switching to biome in later PR

---------

Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
Co-authored-by: mfahampshire <maxhampshire@pm.me>
This commit is contained in:
Mark Sinclair
2026-05-22 20:29:51 +01:00
committed by GitHub
parent 28b22f6b22
commit 626d013547
195 changed files with 34745 additions and 34438 deletions
+2 -2
View File
@@ -23,10 +23,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v5.0.0
with:
version: 9
version: 11.1.2
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 24
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
+7 -4
View File
@@ -17,13 +17,16 @@ jobs:
run: sudo apt-get install rsync
continue-on-error: true
- uses: rlespinasse/github-slug-action@v3.x
- name: Setup pnpm
uses: pnpm/action-setup@v5.0.0
with:
version: 11.1.2
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup yarn
run: npm install -g yarn
node-version: 24
cache: pnpm
- name: Build
run: yarn && yarn build && yarn build:ci:storybook
run: pnpm install && pnpm build && pnpm build:ci:storybook
- name: Deploy branch to CI www (storybook)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
-1
View File
@@ -23,7 +23,6 @@ on:
- 'sdk/ffi/**'
- 'sdk/rust/**'
- 'service-providers/**'
- 'nym-browser-extension/storage/**'
- 'tools/**'
- 'wasm/**'
- 'Cargo.toml'
@@ -40,7 +40,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: 24
- name: Validate version format
run: |
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
node-version: 24
- name: Validate version format
run: |
+2 -2
View File
@@ -30,10 +30,10 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v5.0.0
with:
version: 9
version: 11.1.2
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 24
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
+11 -9
View File
@@ -20,12 +20,14 @@ jobs:
- uses: actions/checkout@v6
- uses: rlespinasse/github-slug-action@v3.x
- name: Setup pnpm
uses: pnpm/action-setup@v5.0.0
with:
version: 11.1.2
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup yarn
run: npm install -g yarn
node-version: 24
cache: pnpm
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
@@ -44,16 +46,16 @@ jobs:
go-version: "1.24.6"
- name: Install
run: yarn
run: pnpm i
- name: Build packages
run: yarn build:ci
run: pnpm build:ci
- name: Install again
run: yarn
run: pnpm i
- name: Lint
run: yarn lint
run: pnpm lint
- name: Typecheck with tsc
run: yarn tsc
run: pnpm tsc
+14 -10
View File
@@ -12,30 +12,34 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v5.0.0
with:
version: 11.1.2
- uses: actions/setup-node@v4
with:
node-version-file: nym-wallet/.nvmrc
cache: yarn
cache-dependency-path: yarn.lock
cache: pnpm
- name: Install dependencies
run: yarn install --network-timeout 100000
run: pnpm install
- name: Build TypeScript packages (wallet depends on @nymproject/types, etc.)
run: yarn build:types
run: pnpm build:types
- name: Build @nymproject/mui-theme and @nymproject/react (wallet imports subpaths)
run: yarn build:packages
run: pnpm build:packages
- name: Typecheck nym-wallet
run: yarn --cwd nym-wallet tsc
run: pnpm --filter @nymproject/nym-wallet-app tsc
- name: Lint nym-wallet
run: yarn --cwd nym-wallet lint
run: pnpm --filter @nymproject/nym-wallet-app lint
- name: Yarn audit (workspace lockfile; informational)
run: yarn audit --level critical
- name: pnpm audit (workspace lockfile; informational)
run: pnpm audit --audit-level critical
continue-on-error: true
- name: Unit tests (nym-wallet)
run: yarn --cwd nym-wallet test
run: pnpm --filter @nymproject/nym-wallet-app test
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 24
- uses: actions-rs/toolchain@v1
with:
+12 -9
View File
@@ -23,10 +23,13 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Node
uses: actions/setup-node@v4
- name: Setup pnpm
uses: pnpm/action-setup@v5.0.0
with:
node-version: 22.13.0
version: 11.1.2
- uses: actions/setup-node@v4
with:
node-version: 24
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
@@ -68,17 +71,17 @@ jobs:
fileName: '.env'
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
- name: Yarn cache clean
- name: pnpm cache clean
shell: bash
run: cd .. && yarn cache clean
run: cd .. && pnpm cache delete
- name: Install project dependencies
shell: bash
run: cd .. && yarn --network-timeout 100000
run: cd .. && pnpm i
- name: Yarn build
- name: Build
shell: bash
run: cd .. && yarn build
run: cd .. && pnpm build
- name: Install dependencies and build it
env:
@@ -97,7 +100,7 @@ jobs:
TAURI_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
TAURI_NOTARIZATION_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
yarn build-macx86
pnpm build-macx86
- name: Create app tarball
run: |
@@ -27,11 +27,16 @@ jobs:
libgtk-3-dev squashfs-tools libayatana-appindicator3-dev make libfuse2 unzip librsvg2-dev file \
libsoup-3.0-dev libjavascriptcoregtk-4.1-dev
- name: Setup pnpm
uses: pnpm/action-setup@v5.0.0
with:
version: 11.1.2
- name: Node
uses: actions/setup-node@v4
with:
node-version: 22.13.0
cache: 'yarn'
node-version: 24
cache: 'pnpm'
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
@@ -40,10 +45,10 @@ jobs:
- name: Install project dependencies
shell: bash
run: cd .. && yarn --network-timeout 100000
run: cd .. && pnpm i
- name: Install app dependencies
run: yarn
run: pnpm
- name: Create env file
uses: timheuer/base64-to-file@v1.2
@@ -52,7 +57,7 @@ jobs:
encodedString: ${{ secrets.WALLET_ADMIN_ADDRESS }}
- name: Build app
run: yarn build
run: pnpm build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
+9 -12
View File
@@ -40,16 +40,13 @@ jobs:
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v3
# No cache:yarn here: setup-node needs yarn on PATH to populate the cache, but this runner
# only gets yarn from the step below.
- name: Node
uses: actions/setup-node@v4
- name: Setup pnpm
uses: pnpm/action-setup@v5.0.0
with:
node-version: 22.13.0
- name: Install Yarn (classic)
shell: bash
run: npm install -g yarn@1.22.22
version: 11.1.2
- uses: actions/setup-node@v4
with:
node-version: 24
- name: Strip Authenticode thumbprint (avoid signtool on runner)
working-directory: nym-wallet/src-tauri
@@ -118,11 +115,11 @@ jobs:
' tauri.conf.json
- name: Install project dependencies
shell: bash
run: cd .. && yarn --network-timeout 100000
run: cd .. && pnpm i
- name: Install app dependencies
shell: bash
run: yarn --network-timeout 100000
run: pnpm i
- name: Build and sign it
shell: bash
@@ -136,7 +133,7 @@ jobs:
SSL_COM_TOTP_SECRET: ${{ env.SIGN_WINDOWS == 'true' && secrets.SSL_COM_TOTP_SECRET }}
run: |
echo "Starting build process..."
yarn build
pnpm build
- name: Check bundle directory
shell: bash
+8 -6
View File
@@ -8,15 +8,17 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v5.0.0
with:
version: 11.1.2
- name: Install Node
uses: actions/setup-node@v4
with:
node-version: 20
node-version: 24
registry-url: "https://registry.npmjs.org"
- name: Setup yarn
run: npm install -g yarn
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
@@ -40,10 +42,10 @@ jobs:
run: ./wasm/mix-fetch/go-mix-conn/scripts/update-root-certs.sh
- name: Install dependencies
run: yarn
run: pnpm i
- name: Build WASM and Typescript SDK
run: yarn sdk:build
run: pnpm sdk:build
- name: Publish to NPM
env:
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 24
- uses: nymtech/nym/.github/actions/nym-hash-releases@develop
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+4
View File
@@ -79,3 +79,7 @@ CLAUDE.md
/notes
/target-otel
test-tutorials/
# pnpm
.pnpm-store/
+9
View File
@@ -0,0 +1,9 @@
shamefully-hoist=false
prefer-workspace-packages=true
hoist-pattern[]=*eslint*
hoist-pattern[]=*prettier*
hoist-pattern[]=*typescript*
hoist-pattern[]=*@types*
auto-install-peers=true
strict-peer-dependencies=false
Generated
-38
View File
@@ -2965,22 +2965,6 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "extension-storage"
version = "1.4.1"
dependencies = [
"bip39",
"console_error_panic_hook",
"js-sys",
"nym-wasm-storage",
"nym-wasm-utils",
"serde-wasm-bindgen 0.6.5",
"thiserror 2.0.18",
"wasm-bindgen",
"wasm-bindgen-futures",
"zeroize",
]
[[package]]
name = "eyre"
version = "0.6.12"
@@ -6312,8 +6296,6 @@ dependencies = [
"js-sys",
"nym-bin-common",
"nym-gateway-requests",
"nym-node-tester-utils",
"nym-node-tester-wasm",
"nym-wasm-client-core",
"nym-wasm-utils",
"once_cell",
@@ -7897,26 +7879,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "nym-node-tester-wasm"
version = "1.3.1"
dependencies = [
"futures",
"js-sys",
"nym-node-tester-utils",
"nym-wasm-client-core",
"nym-wasm-utils",
"rand 0.8.6",
"serde",
"serde-wasm-bindgen 0.6.5",
"thiserror 2.0.18",
"tokio",
"tsify",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasmtimer",
]
[[package]]
name = "nym-noise"
version = "1.21.0"
-12
View File
@@ -130,7 +130,6 @@ members = [
"nym-api",
"nym-api/nym-api-requests",
"nym-authenticator-client",
"nym-browser-extension/storage",
"nym-credential-proxy/nym-credential-proxy",
"nym-credential-proxy/nym-credential-proxy-requests",
"nym-data-observatory",
@@ -174,9 +173,7 @@ members = [
"tools/nymvisor",
"tools/ts-rs-cli",
"wasm/client",
# "wasm/full-nym-wasm", # If we uncomment this again, remember to also uncomment the profile settings below
"wasm/mix-fetch",
"wasm/node-tester",
"wasm/zknym-lib",
]
@@ -589,16 +586,7 @@ opt-level = 3
# lto = true
opt-level = 'z'
[profile.release.package.nym-node-tester-wasm]
# lto = true
opt-level = 'z'
# Commented out since the crate is also commented out from the inclusion in the
# workspace above. We should uncomment this if we re-include it in the
# workspace
#[profile.release.package.nym-wasm-sdk]
## lto = true
#opt-level = 'z'
[profile.release.package.mix-fetch-wasm]
# lto = true
+4 -8
View File
@@ -104,23 +104,19 @@ $(eval $(call add_cargo_workspace,wallet,nym-wallet))
sdk-wasm: sdk-wasm-build sdk-wasm-test sdk-wasm-lint
sdk-wasm-build:
# $(MAKE) -C nym-browser-extension/storage wasm-pack
$(MAKE) -C wasm/client
$(MAKE) -C wasm/node-tester
$(MAKE) -C wasm/mix-fetch
# $(MAKE) -C wasm/zknym-lib
# $(MAKE) -C wasm/full-nym-wasm
# run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo
sdk-typescript-build:
npx lerna run --scope @nymproject/sdk build --stream
npx lerna run --scope @nymproject/mix-fetch build --stream
npx lerna run --scope @nymproject/node-tester build --stream
yarn --cwd sdk/typescript/codegen/contract-clients build
pnpm --pwd sdk/typescript/codegen/contract-clients build
# NOTE: These targets are part of the main workspace (but not as wasm32-unknown-unknown)
# WASM_CRATES = extension-storage nym-client-wasm nym-node-tester-wasm zknym-lib
WASM_CRATES = nym-client-wasm nym-node-tester-wasm
WASM_CRATES = nym-client-wasm
sdk-wasm-test:
#cargo test $(addprefix -p , $(WASM_CRATES)) --target wasm32-unknown-unknown -- -Dwarnings
@@ -223,7 +219,7 @@ build-nym-cli:
generate-typescript:
cd tools/ts-rs-cli && cargo run && cd ../..
yarn types:lint:fix
pnpm types:lint:fix
# Run the integration tests for public nym-api endpoints
run-api-tests:
+2 -2
View File
@@ -74,9 +74,9 @@ Nym Node Operators and Validators Terms and Conditions can be found [here](https
## Getting Started
```bash
yarn install
pnpm install
```
```bash
yarn build
pnpm build
```
+20 -12
View File
@@ -4,7 +4,7 @@
"scripts": {
"dev": "next dev",
"build": "next build",
"build:prod": "yarn --cwd .. build && next build",
"build:prod": "pnpm --dir .. build && next build",
"start": "next start",
"lint": "biome check --fix"
},
@@ -13,21 +13,28 @@
},
"dependencies": {
"@chain-registry/types": "^0.50.36",
"@cosmjs/cosmwasm-stargate": "catalog:",
"@cosmjs/stargate": "catalog:",
"axios": "catalog:",
"big.js": "catalog:",
"long": "catalog:",
"@cosmos-kit/keplr-extension": "^2.14.0",
"@cosmos-kit/react": "^2.20.1",
"@emotion/cache": "^11.13.5",
"@emotion/react": "^11.13.5",
"@emotion/styled": "^11.13.5",
"@emotion/cache": "catalog:",
"@emotion/react": "catalog:",
"@emotion/styled": "catalog:",
"@interchain-ui/react": "^1.26.1",
"@keplr-wallet/types": "^0.12.211",
"@mui/icons-material": "^5.16.11",
"@mui/icons-material": "catalog:",
"@mui/material": "^6.1.10",
"@mui/system": "^6.1.10",
"@mui/material-nextjs": "^6.1.9",
"@mui/x-date-pickers": "^7.23.2",
"@nivo/line": "^0.88.0",
"@nymproject/contract-clients": "^1.4.1",
"@nymproject/react": "1.0.0",
"@tanstack/react-query": "^5.64.2",
"@nymproject/react": "workspace:*",
"@nymproject/types": "workspace:*",
"@tanstack/react-query": "catalog:",
"@tanstack/react-query-devtools": "^5.64.2",
"@tanstack/react-query-next-experimental": "^5.66.0",
"@tanstack/react-table": "^8.20.6",
@@ -44,8 +51,8 @@
"openapi-fetch": "^0.13.4",
"qrcode.react": "^4.1.0",
"qs": "^6.14.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react": "catalog:",
"react-dom": "catalog:",
"react-i18next": "^15.4.0",
"react-markdown": "^9.0.3",
"react-random-avatars": "^1.3.1",
@@ -54,10 +61,11 @@
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/big.js": "catalog:",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^9",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"eslint": "catalog:",
"eslint-config-next": "15.0.3",
"lefthook": "^1.8.5",
"typescript": "^5"
@@ -1,9 +1,10 @@
import { Stack, Typography, useTheme } from "@mui/material";
import Flag from "react-world-flags";
import type React from "react";
interface ICountryFlag {
countryCode: string;
countryName?: string | JSX.Element;
countryName?: string | React.JSX.Element;
}
const CountryFlag = ({ countryCode, countryName }: ICountryFlag) => {
@@ -1,9 +1,10 @@
"use client";
import type React from "react";
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
// import { useMainContext } from "@/context";
export const SocialIcon = ({ channel }: { channel: string }): JSX.Element => {
export const SocialIcon = ({ channel }: { channel: string }): React.JSX.Element => {
// const { mode } = useMainContext();
const modeType = "light";
+1 -1
View File
@@ -52,7 +52,7 @@ export const DarkLightSwitch = styled(Switch)(({ theme }) => ({
},
}));
export const DarkLightSwitchDesktop = (): JSX.Element => {
export const DarkLightSwitchDesktop = (): React.JSX.Element => {
const [mode, setMode] = useLocalStorage<PaletteMode>("mode", "dark");
const toggleMode = () => setMode((m) => (m !== "light" ? "light" : "dark"));
+2 -2
View File
@@ -43,9 +43,9 @@
pre-commit:
commands:
check:
root: "explorer-nextjs/"
root: "explorer-v2/"
glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc,css}"
run: yarn biome check --write --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files}
run: pnpm biome check --write --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files}
stage_fixed: true
rust-lint:
glob: "*.rs"
+2 -1
View File
@@ -1,3 +1,4 @@
{
"version": "independent"
"version": "independent",
"npmClient": "pnpm"
}
-14
View File
@@ -1,14 +0,0 @@
dist/
build/
**/*.wasm
**/*.js.map
.env*
!.env.example
.vscode/
.idea/
*.swp
*.swo
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-125
View File
@@ -1,125 +0,0 @@
# Nym Browser Extension Storage
A WebAssembly-based storage component for securely managing mnemonics in extensions. This component provides encrypted storage functionality using IndexedDB with password-based encryption.
## Overview
This storage component is built in Rust and compiled to WebAssembly, providing:
- **Secure mnemonic storage**: Password-encrypted storage of BIP39 mnemonics
- **IndexedDB integration**: Browser-native persistent storage
- **Multiple account support**: Store and manage multiple mnemonic phrases with custom names
- **Type-safe API**: Promise-based JavaScript API with proper error handling
## Getting Started
### Prerequisites
- Rust (latest stable)
- `wasm-pack` tool for building WebAssembly
- Node.js (for the demo server)
### Building
```bash
cd storage
make wasm-pack
```
This will compile the Rust code to WebAssembly and generate the necessary JavaScript bindings.
### Example Usage
See the [internal-dev example](./storage/internal-dev/index.js) for complete usage examples.
Basic usage:
```javascript
import init, { ExtensionStorage, set_panic_hook } from "@nymproject/extension-storage"
// Initialize the WASM module first
await init();
// Set up better error handling
set_panic_hook();
// Create storage instance with password
const storage = await new ExtensionStorage("your-secure-password");
// Store a mnemonic
const mnemonic = "your twenty four word mnemonic phrase goes here...";
await storage.store_mnemonic("my-wallet", mnemonic);
// Read a mnemonic
const retrievedMnemonic = await storage.read_mnemonic("my-wallet");
// Check if a mnemonic exists
const exists = await storage.has_mnemonic("my-wallet");
// Get all stored mnemonic keys
const allKeys = await storage.get_all_mnemonic_keys();
// Remove a mnemonic
await storage.remove_mnemonic("my-wallet");
```
## Development
To run the internal development example:
```bash
cd storage
make demo
# Option 2: Manual server setup
cd internal-dev && node serve.js
# Then open http://localhost:8000 in your browser
```
**Note**: The demo requires a server that properly serves WASM files with `application/wasm` MIME type. The Node.js server is recommended as it handles MIME types more reliably.
## API Reference
### Initialization
- `init()` - **Required**: Initialize the WASM module before using other functions
### Constructor
- `new ExtensionStorage(password: string)` - Creates a new storage instance with the given password
### Static Methods
- `ExtensionStorage.exists()` - Check if storage database exists
### Instance Methods
- `store_mnemonic(name: string, mnemonic: string)` - Store a mnemonic with the given name
- `read_mnemonic(name: string)` - Retrieve a mnemonic by name (returns null if not found)
- `has_mnemonic(name: string)` - Check if a mnemonic with the given name exists
- `get_all_mnemonic_keys()` - Get all stored mnemonic names
- `remove_mnemonic(name: string)` - Remove a mnemonic by name
### Error Handling
- `set_panic_hook()` - Set up better stack traces for Rust panics in development
## Security Features
- **Password-based encryption**: All data is encrypted using the provided password
- **BIP39 validation**: Mnemonics are validated before storage
- **Secure memory handling**: Sensitive data is zeroed from memory when no longer needed
- **Browser sandbox**: Runs within the browser's security model
## Architecture
The storage component consists of:
- **Rust core** (`src/storage.rs`): Main storage implementation with encryption
- **WASM bindings** (`src/lib.rs`): WebAssembly interface layer
- **Error handling** (`src/error.rs`): Comprehensive error types
- **Build configuration** (`Cargo.toml`, `Makefile`): Build and dependency management
## Important Notes
1. **WASM Initialization**: Always call `await init()` before using any other functions
2. **MIME Types**: The demo requires a server that properly serves WASM files
3. **Browser Compatibility**: Requires modern browsers with WebAssembly support
4. **Module Loading**: Uses ES modules - ensure your build system supports them
@@ -1,4 +0,0 @@
[build]
target = "wasm32-unknown-unknown"
target_arch = "wasm32"
rustflags = ["--cfg=getrandom_backend=\"wasm_js\""]
-36
View File
@@ -1,36 +0,0 @@
pkg
target
Cargo.lock
*.wasm
*.js
*.ts
*.d.ts
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
internal-dev/wasm/
-37
View File
@@ -1,37 +0,0 @@
[package]
name = "extension-storage"
description = "WebAssembly-based secure storage for browser extension mnemonics"
version = "1.4.1"
authors = ["Nym Technologies SA <contact@nymtech.net>"]
edition = "2024"
license = "Apache-2.0"
repository = "https://github.com/nymtech/nym"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
bip39 = { workspace = true }
zeroize = { workspace = true }
js-sys = { workspace = true }
wasm-bindgen = { workspace = true }
wasm-bindgen-futures = { workspace = true }
serde-wasm-bindgen = { workspace = true }
thiserror = { workspace = true }
console_error_panic_hook = { workspace = true, optional = true }
nym-wasm-utils = { workspace = true }
nym-wasm-storage = { workspace = true }
#[package.metadata.wasm-pack.profile.release]
#wasm-opt = false
[features]
default = ["console_error_panic_hook"]
-65
View File
@@ -1,65 +0,0 @@
# Nym Extension Storage - Build Configuration
#
# This Makefile helps build the WebAssembly storage component
# and run development examples.
wasm-pack:
@echo "🔨 Building WebAssembly package..."
unset RUSTC_WRAPPER && wasm-pack build --target web --scope nymproject --out-dir ../../dist/wasm/extension-storage
@echo "✅ Build complete! Output in ../../dist/wasm/extension-storage"
wasm-pack-dev:
@echo "🔨 Building WebAssembly package (development mode)..."
unset RUSTC_WRAPPER && wasm-pack build --dev --target web --scope nymproject --out-dir ../../dist/wasm/extension-storage
@echo "✅ Development build complete!"
copy-wasm-to-demo:
@echo "📁 Copying WASM files to demo directory..."
@mkdir -p internal-dev/wasm
@cp ../../dist/wasm/extension-storage/extension_storage.js internal-dev/wasm/
@cp ../../dist/wasm/extension-storage/extension_storage_bg.wasm internal-dev/wasm/
@cp ../../dist/wasm/extension-storage/extension_storage.d.ts internal-dev/wasm/
@echo "✅ WASM files copied to internal-dev/wasm/"
clean:
@echo "🧹 Cleaning build artifacts..."
cargo clean
rm -rf ../../dist/wasm/extension-storage
rm -rf pkg
rm -rf internal-dev/wasm
@echo "✅ Clean complete!"
demo: wasm-pack-dev copy-wasm-to-demo
cd internal-dev && node serve.js
check-deps:
@echo "🔍 Checking dependencies..."
@command -v cargo >/dev/null 2>&1 || { echo "❌ cargo is required but not installed. Please install Rust."; exit 1; }
@command -v wasm-pack >/dev/null 2>&1 || { echo "❌ wasm-pack is required but not installed. Run: cargo install wasm-pack"; exit 1; }
@echo "✅ All dependencies are installed!"
help:
@echo "Nym Extension Storage Build Commands:"
@echo ""
@echo "📦 Building:"
@echo " make wasm-pack - Build optimized WASM package for production"
@echo " make wasm-pack-dev - Build WASM package with debug symbols"
@echo " make copy-wasm-to-demo - Copy WASM files to demo directory"
@echo ""
@echo "🧪 Development:"
@echo " make demo - Build and serve the interactive demo (Node.js)"
@echo " make check-deps - Verify all required tools are installed"
@echo ""
@echo "🧹 Maintenance:"
@echo " make clean - Remove all build artifacts"
@echo " make help - Show this help message"
@echo ""
@echo "💡 Quick start: make demo"
.DEFAULT_GOAL := help
.PHONY: wasm-pack wasm-pack-dev copy-wasm-to-demo clean demo check-deps help
@@ -1,161 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nym Extension Storage - Demo</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #6750A4;
text-align: center;
margin-bottom: 30px;
}
.warning {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 5px;
padding: 15px;
margin-bottom: 20px;
}
.info {
background: #d1ecf1;
border: 1px solid #bee5eb;
border-radius: 5px;
padding: 15px;
margin-bottom: 20px;
}
.success {
background: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 5px;
padding: 15px;
margin-bottom: 20px;
}
code {
background: #f8f9fa;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
.console-output {
background: #000;
color: #00ff00;
padding: 15px;
border-radius: 5px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 12px;
margin-top: 20px;
overflow-x: auto;
}
.button-group {
text-align: center;
margin: 20px 0;
}
button {
background: #6750A4;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin: 5px;
font-size: 14px;
}
button:hover {
background: #5a45a0;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 Nym Extension Storage Demo</h1>
<div class="warning">
<strong>⚠️ Demo Environment</strong><br>
This is a development demo. The mnemonics used here are for testing only and should never be used in production.
</div>
<div class="info">
<strong>📋 How to use this demo:</strong><br>
1. Open your browser's developer console (F12)<br>
2. The demo will run automatically when the page loads<br>
3. You can also use the interactive functions shown below<br>
4. Check the IndexedDB in your browser's dev tools to see stored data
</div>
<div class="success">
<strong>✅ What this demo shows:</strong><br>
• Creating encrypted storage instances<br>
• Storing and retrieving BIP39 mnemonics<br>
• Managing multiple wallets<br>
• Error handling for invalid data<br>
• Cleaning up stored data
</div>
<h2>Interactive Functions</h2>
<p>Open the browser console and try these commands:</p>
<div class="console-output">
// Create a storage instance<br/>
const storage = await window.nymStorageDemo.createStorage();<br/>
<br/>
// Run a quick test<br/>
await window.nymStorageDemo.quickTest(storage);<br/>
<br/>
// Access test mnemonics<br/>
console.log(window.nymStorageDemo.mnemonics);<br/>
<br/>
// Manual operations<br/>
await storage.store_mnemonic("my-wallet", "your mnemonic here...");<br/>
<br/>
const mnemonic = await storage.read_mnemonic("my-wallet");<br/>
const exists = await storage.has_mnemonic("my-wallet");<br/>
const allKeys = await storage.get_all_mnemonic_keys();<br/>
await storage.remove_mnemonic("my-wallet");
</div>
<div class="button-group">
<button onclick="location.reload()">🔄 Restart Demo</button>
<button onclick="window.open('https://github.com/nymtech/nym', '_blank')">📖 Nym Documentation</button>
</div>
<h2>Architecture Overview</h2>
<p>This storage component uses:</p>
<ul>
<li><strong>Rust/WASM</strong> - Core storage logic compiled to WebAssembly</li>
<li><strong>IndexedDB</strong> - Browser-native persistent storage</li>
<li><strong>AES Encryption</strong> - Password-based encryption for sensitive data</li>
<li><strong>BIP39 Validation</strong> - Ensures mnemonic phrases are valid</li>
</ul>
<div class="info">
<strong>🔒 Security Notes:</strong><br>
• All data is encrypted with your password before storage<br>
• Mnemonics are validated against BIP39 standards<br>
• Sensitive data is zeroed from memory when no longer needed<br>
• Storage is isolated per browser origin
</div>
</div>
<!-- Load the WASM module and demo -->
<script type="module" src="./index.js"></script>
</body>
</html>
@@ -1,303 +0,0 @@
/**
* Nym Extension Storage - Internal Development Example
*
* This file demonstrates how to use the ExtensionStorage WASM module
* for securely storing and managing BIP39 mnemonics in browser extensions.
*
* To run this example:
* 1. Build the WASM module: `cd .. && make wasm-pack`
* 2. Serve this directory: `python3 -m http.server 8000`
* 3. Open http://localhost:8000 in your browser
* 4. Open the browser's developer console to see the output
*/
import init, {
ExtensionStorage,
set_panic_hook
} from "./wasm/extension_storage.js"
/**
* Test mnemonics for demonstration
* Note: These are for testing only - never use these in production!
*/
const TEST_MNEMONICS = {
valid: {
wallet1: "figure aspect pill salute review sponsor army city muffin engine army kid rival chunk unit insect blouse paddle velvet shallow box crawl grace never",
wallet2: "salmon picture danger pill tomato hour hand chaos tray bargain frequent fuel scheme coil divert season lucky ginger mom stem mistake blanket lake suffer",
wallet3: "cat quiz circle letter trade unhappy quarter garlic sting gravity zone stock scatter merge account barrel forward fame club chest camp under crop connect",
wallet4: "mammal fashion rice two marble high brain achieve first harsh infant timber flush cloud hunt address brand immune tip identify aspect call beyond once"
},
invalid: "this is not a valid mnemonic phrase"
};
const STORAGE_PASSWORD = "my-super-secure-password-123";
/**
* Helper function to log results with better formatting
*/
function logResult(operation, result, error = null) {
const timestamp = new Date().toISOString();
console.group(`🔍 [${timestamp}] ${operation}`);
if (error) {
console.error("❌ Error:", error);
} else {
console.log("✅ Result:", result);
}
console.groupEnd();
}
/**
* Test basic storage operations
*/
async function testBasicOperations(storage) {
console.log("\n📦 Testing Basic Storage Operations");
console.log("=" .repeat(50));
// Test storing a valid mnemonic
try {
await storage.store_mnemonic("test-wallet", TEST_MNEMONICS.valid.wallet1);
logResult("Store valid mnemonic", "Successfully stored");
} catch (error) {
logResult("Store valid mnemonic", null, error);
}
// Test reading the stored mnemonic
try {
const result = await storage.read_mnemonic("test-wallet");
logResult("Read stored mnemonic", result);
} catch (error) {
logResult("Read stored mnemonic", null, error);
}
// Test reading a non-existent mnemonic
try {
const result = await storage.read_mnemonic("non-existent");
logResult("Read non-existent mnemonic", result);
} catch (error) {
logResult("Read non-existent mnemonic", null, error);
}
// Test storing an invalid mnemonic
try {
await storage.store_mnemonic("invalid-wallet", TEST_MNEMONICS.invalid);
logResult("Store invalid mnemonic", "Should not reach here");
} catch (error) {
logResult("Store invalid mnemonic", null, error.toString());
}
// Clean up
try {
await storage.remove_mnemonic("test-wallet");
logResult("Remove test wallet", "Successfully removed");
} catch (error) {
logResult("Remove test wallet", null, error);
}
}
/**
* Test multiple wallet management
*/
async function testMultipleWallets(storage) {
console.log("\n👥 Testing Multiple Wallet Management");
console.log("=" .repeat(50));
// Store multiple wallets
const walletNames = Object.keys(TEST_MNEMONICS.valid);
const walletMnemonics = Object.values(TEST_MNEMONICS.valid);
for (let i = 0; i < walletNames.length; i++) {
try {
await storage.store_mnemonic(walletNames[i], walletMnemonics[i]);
logResult(`Store wallet: ${walletNames[i]}`, "Success");
} catch (error) {
logResult(`Store wallet: ${walletNames[i]}`, null, error);
}
}
// Get all wallet keys
try {
const allKeys = await storage.get_all_mnemonic_keys();
logResult("Get all wallet keys", allKeys);
} catch (error) {
logResult("Get all wallet keys", null, error);
}
// Check if specific wallets exist
for (const walletName of walletNames) {
try {
const exists = await storage.has_mnemonic(walletName);
logResult(`Check wallet exists: ${walletName}`, exists);
} catch (error) {
logResult(`Check wallet exists: ${walletName}`, null, error);
}
}
// Read each wallet
for (const walletName of walletNames) {
try {
const mnemonic = await storage.read_mnemonic(walletName);
logResult(`Read wallet: ${walletName}`, `${mnemonic.substring(0, 20)}...`);
} catch (error) {
logResult(`Read wallet: ${walletName}`, null, error);
}
}
}
/**
* Test wallet removal and cleanup
*/
async function testWalletRemoval(storage) {
console.log("\n🗑️ Testing Wallet Removal");
console.log("=" .repeat(50));
const walletNames = Object.keys(TEST_MNEMONICS.valid);
// Remove wallets one by one
for (const walletName of walletNames) {
try {
await storage.remove_mnemonic(walletName);
logResult(`Remove wallet: ${walletName}`, "Success");
} catch (error) {
logResult(`Remove wallet: ${walletName}`, null, error);
}
// Verify removal
try {
const exists = await storage.has_mnemonic(walletName);
logResult(`Verify removal: ${walletName}`, `Exists: ${exists}`);
} catch (error) {
logResult(`Verify removal: ${walletName}`, null, error);
}
}
// Check final state
try {
const allKeys = await storage.get_all_mnemonic_keys();
logResult("Final wallet count", `${allKeys.length} wallets remaining`);
} catch (error) {
logResult("Final wallet count", null, error);
}
}
/**
* Test storage existence check
*/
async function testStorageExistence() {
console.log("\n💾 Testing Storage Existence");
console.log("=" .repeat(50));
try {
const exists = await ExtensionStorage.exists();
logResult("Storage database exists", exists);
} catch (error) {
logResult("Storage database exists", null, error);
}
}
/**
* Main demonstration function
*/
async function runDemo() {
console.log("🚀 Nym Extension Storage Demo");
console.log("=" .repeat(60));
console.log("This demo shows how to use the ExtensionStorage WASM module");
console.log("Check the browser console for detailed output\n");
try {
// Initialize the WASM module
console.log("🔧 Initializing WASM module...");
await init();
console.log("✅ WASM module initialized successfully\n");
// Set up better stack traces for Rust panics
set_panic_hook();
} catch (error) {
console.error("💥 Failed to initialize WASM module:", error);
return;
}
// Test storage existence before creating instance
await testStorageExistence();
try {
// Create storage instance with password
console.log("🔐 Creating storage instance with password...");
const storage = await new ExtensionStorage(STORAGE_PASSWORD);
console.log("✅ Storage instance created successfully\n");
// Run all tests
await testBasicOperations(storage);
await testMultipleWallets(storage);
await testWalletRemoval(storage);
// Test storage existence after operations
await testStorageExistence();
console.log("\n🎉 Demo completed successfully!");
console.log("Check the IndexedDB in your browser's developer tools to see the stored data.");
} catch (error) {
console.error("💥 Fatal error during demo:", error);
}
}
/**
* Additional utility functions for interactive testing
*/
window.nymStorageDemo = {
/**
* Create a new storage instance for manual testing
*/
async createStorage(password = STORAGE_PASSWORD) {
try {
await init();
set_panic_hook();
return await new ExtensionStorage(password);
} catch (error) {
console.error("Failed to initialize WASM or create storage:", error);
throw error;
}
},
/**
* Quick test with a storage instance
*/
async quickTest(storage, name = "test", mnemonic = TEST_MNEMONICS.valid.wallet1) {
console.log(`Testing with wallet: ${name}`);
await storage.store_mnemonic(name, mnemonic);
console.log("✅ Stored");
const retrieved = await storage.read_mnemonic(name);
console.log("✅ Retrieved:", retrieved);
const exists = await storage.has_mnemonic(name);
console.log("✅ Exists:", exists);
await storage.remove_mnemonic(name);
console.log("✅ Removed");
return "Test completed";
},
/**
* Available test mnemonics
*/
mnemonics: TEST_MNEMONICS
};
// Start the demo when the page loads
document.addEventListener('DOMContentLoaded', () => {
console.log("📚 Interactive Demo Functions Available:");
console.log("- window.nymStorageDemo.createStorage() - Create storage instance");
console.log("- window.nymStorageDemo.quickTest(storage) - Run quick test");
console.log("- window.nymStorageDemo.mnemonics - Test mnemonic phrases");
console.log("\nStarting automated demo...\n");
runDemo();
});
// Export for module usage
export { runDemo, TEST_MNEMONICS, STORAGE_PASSWORD };
@@ -1,32 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_wasm_storage::error::StorageError;
use nym_wasm_utils::wasm_error;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ExtensionStorageError {
#[error("serialization failure: {source}")]
JsonError {
#[from]
source: serde_wasm_bindgen::Error,
},
#[error("failed to use the storage: {source}")]
StorageError {
#[from]
source: StorageError,
},
#[error("there's already a stored mnemonic with name {name}")]
DuplicateMnemonic { name: String },
#[error("the provided mnemonic is malformed: {source}")]
InvalidMnemonic {
#[from]
source: bip39::Error,
},
}
wasm_error!(ExtensionStorageError);
-17
View File
@@ -1,17 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(target_arch = "wasm32")]
pub mod error;
#[cfg(target_arch = "wasm32")]
pub mod storage;
#[cfg(target_arch = "wasm32")]
pub use error::ExtensionStorageError;
#[cfg(target_arch = "wasm32")]
pub use storage::ExtensionStorage;
#[cfg(target_arch = "wasm32")]
pub use nym_wasm_utils::set_panic_hook;
@@ -1,189 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// I'm not convinced by the lint (on Arc<WasmStorage>).
// Sure. wasm is currently single threaded and does not require `Send` or `Sync`
// but this data is moved across futures, so imo we should leave the Arc as it is,
// because it might cause us headache in the future
#![allow(clippy::arc_with_non_send_sync)]
use crate::ExtensionStorageError;
use js_sys::Promise;
use nym_wasm_storage::RawDbResult;
use nym_wasm_storage::{Build, Database, VersionChangeEvent, WasmStorage};
use nym_wasm_utils::check_promise_result;
use nym_wasm_utils::error::{PromisableResult, PromisableResultError};
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use zeroize::Zeroizing;
const STORAGE_NAME: &str = "nym-extension-storage";
const STORAGE_VERSION: u32 = 1;
// v1 tables
mod v1 {
// stores
pub const MNEMONICS_STORE: &str = "mnemonics";
}
#[wasm_bindgen]
#[derive(Clone)]
pub struct ExtensionStorage {
inner: Arc<WasmStorage>,
}
#[allow(clippy::type_complexity)]
fn db_migration() -> Box<dyn Fn(VersionChangeEvent, Database) -> RawDbResult<()>> {
Box::new(|evt: VersionChangeEvent, db: Database| -> RawDbResult<()> {
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
// works with an unsigned integer.
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
let old_version = evt.old_version() as u32;
if old_version < 1 {
// migrating to version 1
db.create_object_store(v1::MNEMONICS_STORE).build()?;
}
Ok(())
})
}
#[wasm_bindgen]
impl ExtensionStorage {
pub(crate) async fn new_async(passphrase: String) -> Result<Self, ExtensionStorageError> {
// make sure the password is zeroized when no longer used, especially if we error out.
// special care must be taken on JS side to ensure it's correctly used there.
let passphrase = Zeroizing::new(passphrase);
let pass_ref: &str = passphrase.as_ref();
let inner = WasmStorage::new(
STORAGE_NAME,
STORAGE_VERSION,
Some(db_migration()),
Some(pass_ref.as_bytes()),
)
.await?;
Ok(ExtensionStorage {
inner: Arc::new(inner),
})
}
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(passphrase: String) -> Promise {
future_to_promise(async move { Self::new_async(passphrase).await.into_promise_result() })
}
pub(crate) async fn exists_async() -> Result<bool, ExtensionStorageError> {
Ok(WasmStorage::exists(STORAGE_NAME).await?)
}
pub fn exists() -> Promise {
future_to_promise(async move { Self::exists_async().await.into_promise_result() })
}
async fn store_mnemonic_async(
&self,
name: String,
value: &bip39::Mnemonic,
) -> Result<(), ExtensionStorageError> {
self.inner
.store_value(v1::MNEMONICS_STORE, JsValue::from_str(&name), value)
.await
.map_err(Into::into)
}
async fn read_mnemonic_async(
&self,
name: String,
) -> Result<Option<bip39::Mnemonic>, ExtensionStorageError> {
self.inner
.read_value(v1::MNEMONICS_STORE, JsValue::from_str(&name))
.await
.map_err(Into::into)
}
async fn remove_mnemonic_async(&self, name: String) -> Result<(), ExtensionStorageError> {
self.inner
.remove_value(v1::MNEMONICS_STORE, JsValue::from_str(&name))
.await
.map_err(Into::into)
}
async fn has_mnemonic_async(&self, name: String) -> Result<bool, ExtensionStorageError> {
self.inner
.has_value(v1::MNEMONICS_STORE, JsValue::from_str(&name))
.await
.map_err(Into::into)
}
async fn get_all_mnemonic_keys_async(&self) -> Result<Vec<JsValue>, ExtensionStorageError> {
self.inner
.get_all_keys(v1::MNEMONICS_STORE)
.await
.map_err(Into::into)
}
#[wasm_bindgen]
pub fn store_mnemonic(&self, name: String, value: String) -> Promise {
let wrapped = Zeroizing::new(value);
let inner: &str = wrapped.as_ref();
let mnemonic = check_promise_result!(
bip39::Mnemonic::parse(inner).map_err(ExtensionStorageError::from)
);
// this clones the Arc pointer
let this = self.clone();
future_to_promise(async move {
this.store_mnemonic_async(name, &mnemonic)
.await
.map(|_| JsValue::null())
.map_promise_err()
})
}
#[wasm_bindgen]
pub fn read_mnemonic(&self, name: String) -> Promise {
// this clones the Arc pointer
let this = self.clone();
future_to_promise(async move {
let maybe_mnemonic = this.read_mnemonic_async(name).await?;
Ok(serde_wasm_bindgen::to_value(&maybe_mnemonic)?)
})
}
#[wasm_bindgen]
pub fn remove_mnemonic(&self, name: String) -> Promise {
// this clones the Arc pointer
let this = self.clone();
future_to_promise(async move {
this.remove_mnemonic_async(name)
.await
.map(|_| JsValue::null())
.map_promise_err()
})
}
#[wasm_bindgen]
pub fn has_mnemonic(&self, name: String) -> Promise {
// this clones the Arc pointer
let this = self.clone();
future_to_promise(async move { this.has_mnemonic_async(name).await.into_promise_result() })
}
#[wasm_bindgen]
pub fn get_all_mnemonic_keys(&self) -> Promise {
// this clones the Arc pointer
let this = self.clone();
future_to_promise(async move {
this.get_all_mnemonic_keys_async()
.await
.into_promise_result()
})
}
}
+1 -1
View File
@@ -11,7 +11,7 @@
"test:prod": "TEST_ENV=prod jest --forceExit --detectOpenHandles --passWithNoTests",
"build": "tsc",
"lint": "eslint --fix --ext .js,.ts,.tsx .",
"cleanup": "rm -rf node_modules; rm -rf dist; yarn install"
"cleanup": "rm -rf node_modules; rm -rf dist; pnpm install"
},
"author": "Nymtech",
"license": "MIT",
+1 -1
View File
@@ -1 +1 @@
22.13.0
24
+173 -111
View File
@@ -10,133 +10,195 @@
"dev": "run-p tauri:dev webpack:dev",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"prebuild": "yarn --cwd .. build",
"prewebpack:dev": "yarn --cwd .. build",
"prewebpack:prod": "yarn check:singletons",
"prebuild": "pnpm --dir .. run build",
"prewebpack:dev": "pnpm --dir .. run build",
"prewebpack:prod": "pnpm run check:singletons",
"check:singletons": "node scripts/check-mui-singletons.js",
"tauri:build": "yarn tauri build",
"tauri:build:no-sign": "yarn tauri build --no-sign -b app",
"tauri:build:adhoc": "APPLE_SIGNING_IDENTITY=- yarn tauri build -b app",
"tauri:dev": "yarn tauri dev",
"tauri:buildx86": "yarn tauri build --target x86_64-apple-darwin",
"tauri:build": "tauri build",
"tauri:build:no-sign": "tauri build --no-sign -b app",
"tauri:build:adhoc": "APPLE_SIGNING_IDENTITY=- tauri build -b app",
"tauri:dev": "tauri dev",
"tauri:buildx86": "tauri build --target x86_64-apple-darwin",
"test": "jest --config jest.config.cjs",
"tsc": "tsc --noEmit true",
"tsc:watch": "tsc --noEmit true --watch",
"webpack:dev": "yarn webpack serve --config webpack.dev.js",
"webpack:prod": "yarn webpack --progress --config webpack.prod.js"
"webpack:dev": "webpack serve --config webpack.dev.js",
"webpack:prod": "webpack --progress --config webpack.prod.js"
},
"dependencies": {
"@babel/helper-simple-access": "^7.25.9",
"@emotion/cache": "^11.14.0",
"@emotion/react": "^11.7.0",
"@emotion/styled": "^11.6.0",
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
"@hookform/resolvers": "^2.8.0",
"@mui/icons-material": "^5.2.0",
"@mui/material": "^5.2.2",
"@mui/styles": "^5.18.0",
"@mui/utils": "^5.7.0",
"@nymproject/node-tester": "^1.3.1",
"@nymproject/react": "^1.0.0",
"@nymproject/types": "^1.0.0",
"@tanstack/react-query": "^5.62.0",
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-opener": "^2.5.3",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-updater": "^2.10.1",
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
"big.js": "^6.2.1",
"bs58": "^4.0.1",
"clsx": "^1.1.1",
"date-fns": "^2.28.0",
"joi": "^17.11.0",
"lodash": "^4.17.21",
"notistack": "^2.0.3",
"npm-run-all": "^4.1.5",
"@babel/helper-simple-access": "catalog:",
"@babel/runtime": "catalog:",
"@cosmjs/math": "catalog:",
"@emotion/cache": "catalog:",
"@emotion/hash": "catalog:",
"@emotion/is-prop-valid": "catalog:",
"@emotion/memoize": "catalog:",
"@emotion/react": "catalog:",
"@emotion/serialize": "catalog:",
"@emotion/sheet": "catalog:",
"@emotion/styled": "catalog:",
"@emotion/unitless": "catalog:",
"@emotion/use-insertion-effect-with-fallbacks": "catalog:",
"@emotion/utils": "catalog:",
"@emotion/weak-memoize": "catalog:",
"@hookform/resolvers": "catalog:",
"@mui/base": "catalog:",
"@mui/lab": "catalog:",
"@mui/material": "catalog:",
"@mui/private-theming": "catalog:",
"@mui/styles": "catalog:",
"@mui/system": "catalog:",
"@mui/icons-material": "catalog:",
"@mui/utils": "catalog:",
"@nymproject/react": "workspace:*",
"@nymproject/types": "workspace:*",
"@popperjs/core": "catalog:",
"@remix-run/router": "catalog:",
"@tanstack/react-query": "catalog:",
"@tanstack/query-core": "catalog:",
"@tauri-apps/api": "catalog:",
"@tauri-apps/plugin-clipboard-manager": "catalog:",
"@tauri-apps/plugin-opener": "catalog:",
"@tauri-apps/plugin-process": "catalog:",
"@tauri-apps/plugin-updater": "catalog:",
"@tauri-apps/tauri-forage": "catalog:",
"base-x": "catalog:",
"base64-js": "catalog:",
"bech32": "catalog:",
"big.js": "catalog:",
"bn.js": "catalog:",
"bs58": "catalog:",
"buffer": "catalog:",
"clsx": "catalog:",
"clipboard-copy": "catalog:",
"colornames": "catalog:",
"d3-array": "catalog:",
"d3-color": "catalog:",
"d3-format": "catalog:",
"d3-interpolate": "catalog:",
"d3-path": "catalog:",
"d3-scale": "catalog:",
"d3-shape": "catalog:",
"d3-time": "catalog:",
"d3-time-format": "catalog:",
"date-fns": "catalog:",
"decimal.js-light": "catalog:",
"dom-helpers": "catalog:",
"eventemitter3": "catalog:",
"fast-equals": "catalog:",
"hex-rgb": "catalog:",
"hoist-non-react-statics": "catalog:",
"ieee754": "catalog:",
"internmap": "catalog:",
"joi": "catalog:",
"localforage": "catalog:",
"lodash": "catalog:",
"lodash.padend": "catalog:",
"lodash.trimstart": "catalog:",
"lodash.words": "catalog:",
"nanoclone": "catalog:",
"notistack": "catalog:",
"npm-run-all": "catalog:",
"prop-types": "catalog:",
"property-expr": "catalog:",
"qr.js": "catalog:",
"qrcode.react": "^1.0.1",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-error-boundary": "^3.1.3",
"react-hook-form": "^7.14.2",
"react-router-dom": "6",
"recharts": "^2.1.13",
"semver": "^6.3.0",
"string-to-color": "^2.2.2",
"use-clipboard-copy": "^0.2.0",
"uuid": "^8.3.2",
"yup": "^0.32.9",
"zxcvbn": "^4.4.2"
"ramda": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"react-error-boundary": "catalog:",
"react-hook-form": "catalog:",
"react-is": "catalog:",
"react-router": "catalog:",
"react-router-dom": "catalog:",
"react-smooth": "catalog:",
"react-transition-group": "catalog:",
"recharts": "catalog:",
"recharts-scale": "catalog:",
"rgb-hex": "catalog:",
"safe-buffer": "catalog:",
"scheduler": "catalog:",
"semver": "catalog:",
"string-to-color": "catalog:",
"stylis": "catalog:",
"tiny-invariant": "catalog:",
"toposort": "catalog:",
"tweetnacl": "catalog:",
"tweetnacl-util": "catalog:",
"use-clipboard-copy": "catalog:",
"uuid": "catalog:",
"victory-vendor": "catalog:",
"yup": "catalog:",
"zxcvbn": "catalog:"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-transform-async-to-generator": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.15.0",
"@nymproject/eslint-config-react-typescript": "^1.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"@svgr/webpack": "^6.1.1",
"@tauri-apps/cli": "^2.10.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/big.js": "^6.1.6",
"@types/bs58": "^4.0.1",
"@types/jest": "^29.5.14",
"@types/minimatch": "5.1.2",
"@babel/core": "catalog:",
"@babel/plugin-transform-async-to-generator": "catalog:",
"@babel/preset-env": "catalog:",
"@babel/preset-react": "catalog:",
"@babel/preset-typescript": "catalog:",
"@nymproject/webpack": "workspace:*",
"@nymproject/eslint-config-react-typescript": "workspace:*",
"@pmmmwh/react-refresh-webpack-plugin": "catalog:",
"@svgr/webpack": "catalog:",
"@tauri-apps/cli": "catalog:",
"@testing-library/dom": "catalog:",
"@testing-library/jest-dom": "catalog:",
"@testing-library/react": "catalog:",
"@types/big.js": "catalog:",
"@types/bs58": "catalog:",
"@types/jest": "catalog:",
"@types/lodash": "catalog:",
"@types/minimatch": "catalog:",
"@types/node": "^22.15.29",
"@types/qrcode.react": "^1.0.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/semver": "^7.3.8",
"@types/uuid": "^8.3.4",
"@types/zxcvbn": "^4.4.1",
"@types/qrcode.react": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/semver": "catalog:",
"@types/uuid": "catalog:",
"@types/zxcvbn": "catalog:",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
"babel-loader": "^8.3.0",
"babel-loader": "catalog:",
"babel-plugin-root-import": "^6.6.0",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^3.0.2",
"dotenv-webpack": "^7.0.3",
"clean-webpack-plugin": "catalog:",
"css-loader": "catalog:",
"css-minimizer-webpack-plugin": "catalog:",
"dotenv-webpack": "catalog:",
"eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react-hooks": "^4.3.0",
"favicons": "^7.0.2",
"favicons-webpack-plugin": "^5.0.2",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.1",
"html-webpack-plugin": "^5.3.2",
"jest": "^30.3.0",
"mini-css-extract-plugin": "^2.2.2",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.7",
"react-refresh": "^0.10.0",
"react-refresh-typescript": "^2.0.2",
"style-loader": "^3.3.1",
"thread-loader": "^3.0.4",
"eslint-config-airbnb": "catalog:",
"eslint-config-airbnb-typescript": "catalog:",
"eslint-config-prettier": "catalog:",
"eslint-import-resolver-root-import": "catalog:",
"eslint-plugin-import": "catalog:",
"eslint-plugin-jest": "catalog:",
"eslint-plugin-jsx-a11y": "catalog:",
"eslint-plugin-prettier": "catalog:",
"eslint-plugin-react": "catalog:",
"eslint-plugin-react-hooks": "catalog:",
"favicons": "catalog:",
"favicons-webpack-plugin": "catalog:",
"file-loader": "catalog:",
"fork-ts-checker-webpack-plugin": "catalog:",
"html-webpack-plugin": "catalog:",
"jest": "^29.7.0",
"mini-css-extract-plugin": "catalog:",
"npm-run-all": "catalog:",
"prettier": "catalog:",
"react-refresh": "catalog:",
"react-refresh-typescript": "catalog:",
"style-loader": "catalog:",
"thread-loader": "catalog:",
"ts-jest": "^29.4.9",
"ts-loader": "^9.4.2",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"ts-loader": "catalog:",
"tsconfig-paths-webpack-plugin": "catalog:",
"typescript": "^5.9.3",
"url-loader": "^4.1.1",
"webpack": "^5.75.0",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.5.0",
"webpack-favicons": "^1.3.8",
"webpack-merge": "^5.8.0"
},
"resolutions": {
"@types/minimatch": "5.1.2"
"url-loader": "catalog:",
"webpack": "catalog:",
"webpack-cli": "catalog:",
"webpack-dev-server": "catalog:",
"webpack-favicons": "catalog:",
"webpack-merge": "catalog:"
},
"private": false
}
+1 -1
View File
@@ -6,7 +6,7 @@
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
SRC="$ROOT/src-tauri/icons/app-icon-source.png"
yarn --cwd "$ROOT" tauri icon "$SRC" -o "$ROOT/src-tauri/icons"
pnpm --dir "$ROOT" exec tauri icon "$SRC" -o "$ROOT/src-tauri/icons"
rm -rf "$ROOT/src-tauri/icons/android" "$ROOT/src-tauri/icons/ios"
rm -f "$ROOT/src-tauri/icons"/Square*.png "$ROOT/src-tauri/icons/StoreLogo.png"
python3 - <<PY
File diff suppressed because one or more lines are too long
@@ -429,10 +429,10 @@
"markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`"
},
{
"description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`",
"description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`",
"type": "string",
"const": "core:app:default",
"markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`"
"markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`"
},
{
"description": "Enables the app_hide command without any pre-configured scope.",
@@ -506,6 +506,12 @@
"const": "core:app:allow-set-dock-visibility",
"markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope."
},
{
"description": "Enables the supports_multiple_windows command without any pre-configured scope.",
"type": "string",
"const": "core:app:allow-supports-multiple-windows",
"markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope."
},
{
"description": "Enables the tauri_version command without any pre-configured scope.",
"type": "string",
@@ -590,6 +596,12 @@
"const": "core:app:deny-set-dock-visibility",
"markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope."
},
{
"description": "Denies the supports_multiple_windows command without any pre-configured scope.",
"type": "string",
"const": "core:app:deny-supports-multiple-windows",
"markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope."
},
{
"description": "Denies the tauri_version command without any pre-configured scope.",
"type": "string",
@@ -1113,10 +1125,10 @@
"markdownDescription": "Denies the close command without any pre-configured scope."
},
{
"description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`",
"description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`",
"type": "string",
"const": "core:tray:default",
"markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`"
"markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`"
},
{
"description": "Enables the get_by_id command without any pre-configured scope.",
@@ -1148,6 +1160,12 @@
"const": "core:tray:allow-set-icon-as-template",
"markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope."
},
{
"description": "Enables the set_icon_with_as_template command without any pre-configured scope.",
"type": "string",
"const": "core:tray:allow-set-icon-with-as-template",
"markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope."
},
{
"description": "Enables the set_menu command without any pre-configured scope.",
"type": "string",
@@ -1214,6 +1232,12 @@
"const": "core:tray:deny-set-icon-as-template",
"markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope."
},
{
"description": "Denies the set_icon_with_as_template command without any pre-configured scope.",
"type": "string",
"const": "core:tray:deny-set-icon-with-as-template",
"markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope."
},
{
"description": "Denies the set_menu command without any pre-configured scope.",
"type": "string",
@@ -1473,10 +1497,16 @@
"markdownDescription": "Denies the webview_size command without any pre-configured scope."
},
{
"description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`",
"description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`",
"type": "string",
"const": "core:window:default",
"markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`"
"markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`"
},
{
"description": "Enables the activity_name command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-activity-name",
"markdownDescription": "Enables the activity_name command without any pre-configured scope."
},
{
"description": "Enables the available_monitors command without any pre-configured scope.",
@@ -1670,6 +1700,12 @@
"const": "core:window:allow-scale-factor",
"markdownDescription": "Enables the scale_factor command without any pre-configured scope."
},
{
"description": "Enables the scene_identifier command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-scene-identifier",
"markdownDescription": "Enables the scene_identifier command without any pre-configured scope."
},
{
"description": "Enables the set_always_on_bottom command without any pre-configured scope.",
"type": "string",
@@ -1934,6 +1970,12 @@
"const": "core:window:allow-unminimize",
"markdownDescription": "Enables the unminimize command without any pre-configured scope."
},
{
"description": "Denies the activity_name command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-activity-name",
"markdownDescription": "Denies the activity_name command without any pre-configured scope."
},
{
"description": "Denies the available_monitors command without any pre-configured scope.",
"type": "string",
@@ -2126,6 +2168,12 @@
"const": "core:window:deny-scale-factor",
"markdownDescription": "Denies the scale_factor command without any pre-configured scope."
},
{
"description": "Denies the scene_identifier command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-scene-identifier",
"markdownDescription": "Denies the scene_identifier command without any pre-configured scope."
},
{
"description": "Denies the set_always_on_bottom command without any pre-configured scope.",
"type": "string",
@@ -429,10 +429,10 @@
"markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`"
},
{
"description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`",
"description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`",
"type": "string",
"const": "core:app:default",
"markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`"
"markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`"
},
{
"description": "Enables the app_hide command without any pre-configured scope.",
@@ -506,6 +506,12 @@
"const": "core:app:allow-set-dock-visibility",
"markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope."
},
{
"description": "Enables the supports_multiple_windows command without any pre-configured scope.",
"type": "string",
"const": "core:app:allow-supports-multiple-windows",
"markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope."
},
{
"description": "Enables the tauri_version command without any pre-configured scope.",
"type": "string",
@@ -590,6 +596,12 @@
"const": "core:app:deny-set-dock-visibility",
"markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope."
},
{
"description": "Denies the supports_multiple_windows command without any pre-configured scope.",
"type": "string",
"const": "core:app:deny-supports-multiple-windows",
"markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope."
},
{
"description": "Denies the tauri_version command without any pre-configured scope.",
"type": "string",
@@ -1113,10 +1125,10 @@
"markdownDescription": "Denies the close command without any pre-configured scope."
},
{
"description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`",
"description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`",
"type": "string",
"const": "core:tray:default",
"markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`"
"markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`"
},
{
"description": "Enables the get_by_id command without any pre-configured scope.",
@@ -1148,6 +1160,12 @@
"const": "core:tray:allow-set-icon-as-template",
"markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope."
},
{
"description": "Enables the set_icon_with_as_template command without any pre-configured scope.",
"type": "string",
"const": "core:tray:allow-set-icon-with-as-template",
"markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope."
},
{
"description": "Enables the set_menu command without any pre-configured scope.",
"type": "string",
@@ -1214,6 +1232,12 @@
"const": "core:tray:deny-set-icon-as-template",
"markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope."
},
{
"description": "Denies the set_icon_with_as_template command without any pre-configured scope.",
"type": "string",
"const": "core:tray:deny-set-icon-with-as-template",
"markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope."
},
{
"description": "Denies the set_menu command without any pre-configured scope.",
"type": "string",
@@ -1473,10 +1497,16 @@
"markdownDescription": "Denies the webview_size command without any pre-configured scope."
},
{
"description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`",
"description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`",
"type": "string",
"const": "core:window:default",
"markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`"
"markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`"
},
{
"description": "Enables the activity_name command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-activity-name",
"markdownDescription": "Enables the activity_name command without any pre-configured scope."
},
{
"description": "Enables the available_monitors command without any pre-configured scope.",
@@ -1670,6 +1700,12 @@
"const": "core:window:allow-scale-factor",
"markdownDescription": "Enables the scale_factor command without any pre-configured scope."
},
{
"description": "Enables the scene_identifier command without any pre-configured scope.",
"type": "string",
"const": "core:window:allow-scene-identifier",
"markdownDescription": "Enables the scene_identifier command without any pre-configured scope."
},
{
"description": "Enables the set_always_on_bottom command without any pre-configured scope.",
"type": "string",
@@ -1934,6 +1970,12 @@
"const": "core:window:allow-unminimize",
"markdownDescription": "Enables the unminimize command without any pre-configured scope."
},
{
"description": "Denies the activity_name command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-activity-name",
"markdownDescription": "Denies the activity_name command without any pre-configured scope."
},
{
"description": "Denies the available_monitors command without any pre-configured scope.",
"type": "string",
@@ -2126,6 +2168,12 @@
"const": "core:window:deny-scale-factor",
"markdownDescription": "Denies the scale_factor command without any pre-configured scope."
},
{
"description": "Denies the scene_identifier command without any pre-configured scope.",
"type": "string",
"const": "core:window:deny-scene-identifier",
"markdownDescription": "Denies the scene_identifier command without any pre-configured scope."
},
{
"description": "Denies the set_always_on_bottom command without any pre-configured scope.",
"type": "string",
+1 -1
View File
@@ -87,7 +87,7 @@ export const BondingContext = createContext<TBondingContext>({
isVestingAccount: false,
});
export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Element => {
export const BondingContextProvider: FCWithChildren = ({ children }): React.JSX.Element => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string>();
+1 -1
View File
@@ -74,7 +74,7 @@ export const MockBondingContextProvider = ({
}: {
network?: Network;
children?: React.ReactNode;
}): JSX.Element => {
}): React.JSX.Element => {
const [isLoading, setIsLoading] = useState(true);
const [feeLoading, setFeeLoading] = useState(false);
const [fee, setFee] = useState<FeeDetails | undefined>();
@@ -1,123 +0,0 @@
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { Box, Button } from '@mui/material';
import { Download } from '@mui/icons-material';
import { NodeTestResultResponse, NodeTester, createNodeTesterClient } from '@nymproject/node-tester';
import { format } from 'date-fns';
import { AppContext, useBondingContext } from 'src/context';
import { LoadingModal } from 'src/components/Modals/LoadingModal';
import { Results } from 'src/components/TestNode/Results';
import { ErrorModal } from 'src/components/Modals/ErrorModal';
import { PrintResults } from 'src/components/TestNode/PrintResults';
import { MAINNET_VALIDATOR_URL } from 'src/constants';
import { TestStatus } from 'src/components/TestNode/types';
import { isMixnode } from 'src/types';
export const NodeTestPage = () => {
const [nodeTestClient, setNodeTestClient] = useState<NodeTester>();
const [error, setError] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
const [results, setResults] = useState<NodeTestResultResponse>();
const [printResults, setPrintResults] = useState(false);
const [testDate, setTestDate] = useState<string>();
const testStateRef = useRef<TestStatus>('Stopped');
const timerRef = useRef<NodeJS.Timeout | null>(null);
const { network } = useContext(AppContext);
const { bondedNode } = useBondingContext();
const handleTestTimeout = () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
timerRef.current = setTimeout(() => {
if (testStateRef.current === 'Running') {
setIsLoading(false);
setError('Test has timed out, please try again');
testStateRef.current = 'Stopped';
}
}, 15000);
};
const handleTestNode = async () => {
if (nodeTestClient && bondedNode && isMixnode(bondedNode)) {
setResults(undefined);
setTestDate(format(new Date(), 'dd/MM/yyyy HH:mm'));
setIsLoading(true);
setError(undefined);
testStateRef.current = 'Running';
handleTestTimeout();
try {
const result = await nodeTestClient.tester.startTest(bondedNode.identityKey);
setResults(result);
testStateRef.current = 'Complete';
} catch (e) {
setError('Node test failed, please try again');
testStateRef.current = 'Stopped';
// eslint-disable-next-line no-console
console.log(e);
} finally {
setIsLoading(false);
}
}
};
const loadNodeTestClient = useCallback(async () => {
try {
const nodeTesterId = new Date().toISOString(); // make a new tester id for each session
const validator = network === 'MAINNET' ? MAINNET_VALIDATOR_URL : 'https://rpc.nymtech.net/api/';
const client = await createNodeTesterClient();
await client.tester.init(validator, nodeTesterId);
setNodeTestClient(client);
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
setError('Failed to load node tester client, please try again');
}
}, []);
useEffect(() => {
loadNodeTestClient();
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
if (nodeTestClient) {
nodeTestClient.tester.disconnectFromGateway();
nodeTestClient.terminate();
}
};
}, [loadNodeTestClient]);
return (
<Box p={4}>
{isLoading && <LoadingModal text="Testing mixnode, please wait.." />}
{error && <ErrorModal open title="Node test failed" message={error} onClose={() => setError(undefined)} />}
{printResults && results && bondedNode && isMixnode(bondedNode) && (
<PrintResults
mixnodeId={bondedNode?.identityKey || '-'}
mixnodeName={bondedNode?.name || '-'}
packetsSent={results.sentPackets}
packetsReceived={results.receivedPackets}
score={results.score}
date={testDate}
OnPrintRequestComplete={() => setPrintResults(false)}
/>
)}
<Results
packetsSent={results?.sentPackets}
packetsReceived={results?.receivedPackets}
score={results?.score}
status={testStateRef.current}
date={testDate}
onStartTest={handleTestNode}
/>
<Box display="flex" justifyContent="flex-end">
<Button onClick={() => setPrintResults(true)} startIcon={<Download />} disabled={!results}>
Save test results as PDF
</Button>
</Box>
</Box>
);
};
@@ -41,7 +41,7 @@ const operatorCostHint = `This is your (operator) rewards including the PM and c
const profitMarginHint =
'PM is the percentage of the node rewards that you as the node operator take before rewards are distributed to the delegators.';
export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => {
export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): React.JSX.Element => {
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
const [intervalTime, setIntervalTime] = useState<string>();
const [pendingUpdates, setPendingUpdates] = useState<NodeCostParams>();
+28 -19
View File
@@ -1,13 +1,14 @@
{
"name": "@nymproject/nymsphere",
"version": "1.0.1",
"packageManager": "pnpm@11.1.2",
"license": "Apache 2.0",
"scripts": {
"audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run",
"audit:fix": "pnpm audit --fix",
"build": "run-s build:types build:packages",
"build:ci": "run-s build:types build:packages build:wasm build:ci:sdk",
"build:ci:sdk": "lerna run --scope '{@nymproject/sdk,@nymproject/node-tester,@nymproject/sdk-react,@nymproject/mix-fetch,@nymproject/nodejs-client,@nymproject/mix-fetch-node}' build --stream",
"build:ci:storybook": "yarn build && yarn dev:on && run-p build:playground && yarn build:ci:storybook:collect-artifacts ",
"build:ci:sdk": "lerna run --scope '{@nymproject/sdk,@nymproject/sdk-react,@nymproject/mix-fetch,@nymproject/nodejs-client,@nymproject/mix-fetch-node}' build --stream",
"build:ci:storybook": "pnpm build && pnpm dev:on && run-p build:playground && pnpm build:ci:storybook:collect-artifacts ",
"build:ci:storybook:collect-artifacts": "mkdir -p ts-packages/dist && mv sdk/typescript/packages/react-components/storybook-static ts-packages/dist/storybook ",
"build:packages": "run-s build:packages:theme build:packages:react",
"build:packages:react": "lerna run --scope @nymproject/react build",
@@ -20,11 +21,12 @@
"dev:on": "node sdk/typescript/scripts/dev-mode-add.mjs",
"docs:prod:build": "run-s docs:prod:build:ws",
"docs:prod:build:ws": "lerna run docs:prod:build --stream",
"lint": "lerna run lint --stream",
"lint": "echo 'linting disabled in preparation of switch to biome'",
"eslint:lint": "lerna run lint --stream",
"lint:fix": "lerna run lint:fix --stream",
"nuke": "npx rimraf **/node_modules node_modules",
"postbuild:ci": "yarn dev:off",
"prebuild:ci": "yarn dev:on && yarn",
"nuke": "npx rimraf **/node_modules node_modules dist pkg",
"postbuild:ci": "pnpm dev:off",
"prebuild:ci": "pnpm dev:on && pnpm install --frozen-lockfile false",
"scrub": "npx rimraf **/dist dist",
"sdk:build": "./sdk/typescript/scripts/build-prod-sdk.sh",
"sdk:publish": "./sdk/typescript/scripts/publish.sh",
@@ -46,7 +48,24 @@
"tslog": "3.3.3"
},
"private": true,
"resolutions": {
"packageExtensions": {
"@cosmos-kit/core@*": {
"dependencies": {
"axios": "*"
}
},
"@cosmos-kit/keplr-extension@*": {
"dependencies": {
"long": "*"
}
},
"@nivo/line@*": {
"dependencies": {
"lodash": "*"
}
}
},
"overrides": {
"eslint": "8.57.1",
"@cosmjs/amino": "^0.32.4",
"@cosmjs/cosmwasm-stargate": "^0.32.4",
@@ -62,15 +81,5 @@
"@typescript-eslint/typescript-estree": "5.62.0",
"@typescript-eslint/utils": "5.62.0",
"cosmjs-types": "^0.9.0"
},
"workspaces": [
"ts-packages/*",
"nym-wallet",
"explorer-nextjs",
"explorer-v2",
"types",
"sdk/typescript/packages/mix-fetch/internal-dev",
"sdk/typescript/packages/react-components",
"sdk/typescript/packages/mui-theme"
]
}
}
+31489
View File
File diff suppressed because it is too large Load Diff
+244
View File
@@ -0,0 +1,244 @@
packages:
- 'ts-packages/*'
- 'nym-wallet'
- 'explorer-v2'
- 'types'
- 'sdk/typescript/packages/mix-fetch/internal-dev'
- 'sdk/typescript/packages/react-components'
- 'sdk/typescript/packages/mui-theme'
allowBuilds:
'@biomejs/biome': true
'@parcel/watcher': true
'@swc/core': true
core-js: true
core-js-pure: true
es5-ext: true
esbuild: true
fsevents: true
lefthook: true
lmdb: true
msgpackr-extract: true
nx: true
protobufjs: true
sharp: true
tiny-secp256k1: true
unrs-resolver: true
catalog:
# babel
"@babel/core": "^7.22.10"
"@babel/helper-simple-access": "^7.25.9"
"@babel/runtime": "^7.29.2"
"@babel/plugin-transform-async-to-generator": "^7.22.5"
"@babel/preset-env": "^7.22.10"
"@babel/preset-react": "^7.14.5"
"@babel/preset-typescript": "^7.22.5"
# emotion
"@emotion/cache": "^11.14.0"
"@emotion/hash": "^0.9.2"
"@emotion/is-prop-valid": "^1.4.0"
"@emotion/memoize": "^0.9.0"
"@emotion/react": "^11.13.5"
"@emotion/serialize": "^1.3.3"
"@emotion/sheet": "^1.4.0"
"@emotion/styled": "^11.13.5"
"@emotion/unitless": "^0.10.0"
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0"
"@emotion/utils": "^1.4.2"
"@emotion/weak-memoize": "^0.4.0"
# mui
"@mui/base": "5.0.0-beta.40"
"@mui/icons-material": "^5.2.0"
"@mui/lab": "5.0.0-alpha.170"
"@mui/material": "^5.2.2"
"@mui/private-theming": "^5.17.1"
"@mui/styles": "^5.18.0"
"@mui/system": "^5.18.0"
"@mui/utils": "^5.7.0"
"@mui/x-tree-view": "^7.11.1"
# tanstack
"@tanstack/react-query": "^5.64.2"
"@tanstack/query-core": "^5.64.2"
# tauri
"@tauri-apps/api": "^2.10.1"
"@tauri-apps/cli": "^2.10.1"
"@tauri-apps/plugin-clipboard-manager": "^2.3.2"
"@tauri-apps/plugin-opener": "^2.5.3"
"@tauri-apps/plugin-process": "^2.3.1"
"@tauri-apps/plugin-updater": "^2.10.1"
"@tauri-apps/tauri-forage": "^1.0.0-beta.2"
# types
"@types/big.js": "^6.1.6"
"@types/bs58": "^4.0.1"
"@types/flat": "^5.0.2"
"@types/jest": "^29.5.14"
"@types/lodash": "^4.17.21"
"@types/minimatch": "5.1.2"
"@types/qrcode.react": "^1.0.2"
"@types/react": "^19.2.14"
"@types/react-dom": "^19.2.3"
"@types/semver": "^7.3.8"
"@types/uuid": "^8.3.4"
"@types/ws": "^8.18.1"
"@types/zxcvbn": "^4.4.1"
# react ecosystem
"@popperjs/core": "^2.11.8"
"hoist-non-react-statics": "^3.3.2"
"prop-types": "^15.8.1"
"react": "^19.2.6"
"react-dom": "^19.2.6"
"react-error-boundary": "^3.1.3"
"react-hook-form": "^7.14.2"
"react-is": "^19.2.6"
"react-refresh": "^0.10.0"
"react-refresh-typescript": "^2.0.2"
"react-smooth": "^4.0.4"
"react-transition-group": "^4.4.5"
"scheduler": "^0.27.0"
"stylis": "^4.2.0"
# react router
"@remix-run/router": "^1.23.2"
"react-router": "^6.30.3"
"react-router-dom": "6"
"tiny-invariant": "^1.3.3"
# storybook
"@storybook/addon-docs": "^6.5.8"
"@storybook/addon-actions": "^6.5.8"
"@storybook/addon-essentials": "^6.5.8"
"@storybook/addon-interactions": "^6.5.8"
"@storybook/addon-links": "^6.5.8"
"@storybook/builder-webpack5": "^6.5.8"
"@storybook/manager-webpack5": "^6.5.8"
"@storybook/react": "^6.5.15"
"@storybook/testing-library": "^0.0.9"
# testing
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4"
"@testing-library/dom": "^10.4.1"
"@testing-library/jest-dom": "^6.9.1"
"@testing-library/react": "^16.3.2"
# crypto / cosmjs
"@cosmjs/cosmwasm-stargate": "^0.32.4"
"@cosmjs/math": "^0.32.4"
"@cosmjs/stargate": "^0.32.4"
"axios": "^1.16.0"
"long": "^4.0.0"
"base-x": "^3.0.11"
"base64-js": "^1.5.1"
"bech32": "^1.1.4"
"bn.js": "^5.2.3"
"bs58": "^4.0.1"
"buffer": "^6.0.3"
"ieee754": "^1.2.1"
"safe-buffer": "^5.2.1"
"tweetnacl": "^1.0.3"
"tweetnacl-util": "^0.15.1"
# d3 / recharts
"d3-array": "^3.2.4"
"d3-color": "^3.1.0"
"d3-format": "^1.4.5"
"d3-interpolate": "^3.0.1"
"d3-path": "^3.1.0"
"d3-scale": "^4.0.2"
"d3-shape": "^3.2.0"
"d3-time": "^3.1.0"
"d3-time-format": "^3.0.0"
"decimal.js-light": "^2.5.1"
"eventemitter3": "^4.0.7"
"fast-equals": "^5.4.0"
"internmap": "^2.0.3"
"ramda": "^0.28.0"
"recharts": "^2.1.13"
"recharts-scale": "^0.4.5"
"victory-vendor": "^36.9.2"
# typescript-eslint (v5 line — nym-wallet pins v8 separately)
"@typescript-eslint/eslint-plugin": "^5.13.0"
"@typescript-eslint/parser": "^5.13.0"
# webpack helpers
"@svgr/webpack": "^6.1.1"
"babel-loader": "^8.3.0"
"clean-webpack-plugin": "^4.0.0"
"css-loader": "^6.8.1"
"css-minimizer-webpack-plugin": "^3.0.2"
"dotenv-webpack": "^7.0.3"
"favicons": "^7.0.2"
"favicons-webpack-plugin": "^5.0.2"
"file-loader": "^6.2.0"
"fork-ts-checker-webpack-plugin": "^7.2.1"
"html-webpack-plugin": "^5.5.3"
"mini-css-extract-plugin": "^2.7.6"
"style-loader": "^3.3.3"
"thread-loader": "^3.0.4"
"ts-loader": "^9.4.4"
"tsconfig-paths-webpack-plugin": "^3.5.2"
"url-loader": "^4.1.1"
"webpack": "^5.88.2"
"webpack-cli": "^4.8.0"
"webpack-dev-server": "^4.15.1"
"webpack-favicons": "^1.3.8"
"webpack-merge": "^5.9.0"
# eslint ecosystem
"eslint": "^9.26.0"
"eslint-config-airbnb": "^19.0.4"
"eslint-config-airbnb-typescript": "^16.1.0"
"eslint-config-prettier": "^8.5.0"
"eslint-import-resolver-root-import": "^1.0.4"
"eslint-plugin-import": "^2.25.4"
"eslint-plugin-jest": "^26.1.1"
"eslint-plugin-jsx-a11y": "^6.5.1"
"eslint-plugin-prettier": "^4.0.0"
"eslint-plugin-react": "^7.29.2"
"eslint-plugin-react-hooks": "^4.3.0"
"eslint-plugin-storybook": "^0.5.12"
# test
"jest": "^27.1.0"
"ts-jest": "^27.0.5"
# misc shared
"@hookform/resolvers": "^2.8.0"
"big.js": "^6.2.1"
"clipboard-copy": "^3.2.0"
"clsx": "^1.1.1"
"colornames": "^1.1.1"
"date-fns": "^2.28.0"
"dom-helpers": "^5.2.1"
"flat": "^5.0.2"
"glob": "^10.5.0"
"hex-rgb": "^4.3.0"
"joi": "^17.11.0"
"localforage": "^1.10.0"
"lodash": "^4.17.21"
"lodash.padend": "^4.6.1"
"lodash.trimstart": "^4.5.1"
"lodash.words": "^4.2.0"
"nanoclone": "^0.2.1"
"notistack": "^2.0.3"
"npm-run-all": "^4.1.5"
"prettier": "^2.8.7"
"property-expr": "^2.0.6"
"qr.js": "0.0.0"
"rgb-hex": "^3.0.0"
"rimraf": "^3.0.2"
"semver": "^6.3.0"
"string-to-color": "^2.2.2"
"toposort": "^2.0.2"
"tslib": "^2.8.1"
"use-clipboard-copy": "^0.2.0"
"uuid": "^8.3.2"
"yup": "^0.32.9"
"zxcvbn": "^4.4.2"
@@ -7,7 +7,7 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@nymproject/contract-clients": "file:.."
"@nymproject/contract-clients": "workspace:*"
},
"author": "",
"license": "ISC"
@@ -15,12 +15,12 @@
"docs:generate:prod": "typedoc --basePath ./docs/tsdoc/nymproject/contract-clients/",
"docs:prod:build": "scripts/build-prod-docs-collect.sh",
"docs:serve": "reload -b -d ./docs -p 3000",
"docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"yarn docs:generate\""
"docs:watch": "nodemon --ext ts --watch './src/**/*' --watch './typedoc.json' --exec \"pnpm docs:generate\""
},
"devDependencies": {
"@cosmwasm/ts-codegen": "^1.13.3",
"nodemon": "3.0.1",
"npm-run-all": "^4.1.5",
"npm-run-all": "catalog:",
"reload": "^3.2.1",
"typedoc": "^0.24.8",
"typescript": "^4.6.2"
@@ -7,7 +7,7 @@ set -o pipefail
rm -rf ../../../../dist/ts/docs/tsdoc/nymproject/contract-clients || true
# run the build
yarn docs:generate:prod
pnpm docs:generate:prod
# move the output outside of the yarn/npm workspaces
mkdir -p ../../../../dist/ts/docs/tsdoc/nymproject
@@ -20,22 +20,22 @@
"devDependencies": {
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "catalog:",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.1.0",
"mini-css-extract-plugin": "^2.2.2",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.7",
"ts-jest": "^27.0.5",
"eslint-config-airbnb": "catalog:",
"eslint-config-airbnb-typescript": "catalog:",
"eslint-config-prettier": "catalog:",
"eslint-import-resolver-root-import": "catalog:",
"eslint-plugin-import": "catalog:",
"eslint-plugin-jest": "catalog:",
"eslint-plugin-jsx-a11y": "catalog:",
"eslint-plugin-prettier": "catalog:",
"jest": "catalog:",
"mini-css-extract-plugin": "catalog:",
"npm-run-all": "catalog:",
"prettier": "catalog:",
"ts-jest": "catalog:",
"typescript": "^4.6.2"
},
"private": false,
@@ -19,47 +19,47 @@
"@nymproject/sdk": ">=1.4.1-rc1 || ^1"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-transform-async-to-generator": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-typescript": "^7.15.0",
"@babel/core": "catalog:",
"@babel/plugin-transform-async-to-generator": "catalog:",
"@babel/preset-env": "catalog:",
"@babel/preset-typescript": "catalog:",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"babel-loader": "^8.3.0",
"@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "catalog:",
"babel-loader": "catalog:",
"babel-plugin-root-import": "^5.1.0",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.7.3",
"clean-webpack-plugin": "catalog:",
"css-loader": "catalog:",
"css-minimizer-webpack-plugin": "^3.0.2",
"dotenv-webpack": "^7.0.3",
"dotenv-webpack": "catalog:",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.1",
"html-webpack-plugin": "^5.3.2",
"jest": "^27.1.0",
"mini-css-extract-plugin": "^2.2.2",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.7",
"style-loader": "^3.3.1",
"eslint-config-airbnb": "catalog:",
"eslint-config-airbnb-typescript": "catalog:",
"eslint-config-prettier": "catalog:",
"eslint-import-resolver-root-import": "catalog:",
"eslint-plugin-import": "catalog:",
"eslint-plugin-jest": "catalog:",
"eslint-plugin-jsx-a11y": "catalog:",
"eslint-plugin-prettier": "catalog:",
"file-loader": "catalog:",
"fork-ts-checker-webpack-plugin": "catalog:",
"html-webpack-plugin": "catalog:",
"jest": "catalog:",
"mini-css-extract-plugin": "catalog:",
"npm-run-all": "catalog:",
"prettier": "catalog:",
"style-loader": "catalog:",
"thread-loader": "^3.0.4",
"ts-jest": "^27.0.5",
"ts-loader": "^9.4.2",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"ts-jest": "catalog:",
"ts-loader": "catalog:",
"tsconfig-paths-webpack-plugin": "catalog:",
"typescript": "^4.6.2",
"url-loader": "^4.1.1",
"webpack": "^5.75.0",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.5.0",
"webpack-merge": "^5.8.0"
"url-loader": "catalog:",
"webpack": "catalog:",
"webpack-cli": "catalog:",
"webpack-dev-server": "catalog:",
"webpack-merge": "catalog:"
},
"private": false
}
@@ -16,78 +16,78 @@
"tsc:watch": "tsc --watch"
},
"dependencies": {
"@mui/icons-material": "^5.5.0",
"@mui/icons-material": "catalog:",
"@mui/lab": "^5.0.0-alpha.72",
"@mui/material": "^5.0.1",
"@mui/styles": "^5.0.1",
"@nymproject/sdk": ">=1.4.1-rc1 || ^1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "catalog:",
"react-dom": "catalog:",
"react-dropzone": "^14.2.3",
"react-mui-dropzone": "^4.0.6",
"use-clipboard-copy": "^0.2.0"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-transform-async-to-generator": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.15.0",
"@babel/core": "catalog:",
"@babel/plugin-transform-async-to-generator": "catalog:",
"@babel/preset-env": "catalog:",
"@babel/preset-react": "catalog:",
"@babel/preset-typescript": "catalog:",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"@svgr/webpack": "^6.1.1",
"@svgr/webpack": "catalog:",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.0.0",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"babel-loader": "^8.3.0",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "catalog:",
"babel-loader": "catalog:",
"babel-plugin-root-import": "^5.1.0",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.7.3",
"clean-webpack-plugin": "catalog:",
"css-loader": "catalog:",
"css-minimizer-webpack-plugin": "^3.0.2",
"dotenv-webpack": "^7.0.3",
"dotenv-webpack": "catalog:",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.2",
"eslint-plugin-react-hooks": "^4.3.0",
"favicons": "^7.0.2",
"favicons-webpack-plugin": "^5.0.2",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.1",
"html-webpack-plugin": "^5.3.2",
"jest": "^27.1.0",
"mini-css-extract-plugin": "^2.2.2",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.7",
"eslint-config-airbnb": "catalog:",
"eslint-config-airbnb-typescript": "catalog:",
"eslint-config-prettier": "catalog:",
"eslint-import-resolver-root-import": "catalog:",
"eslint-plugin-import": "catalog:",
"eslint-plugin-jest": "catalog:",
"eslint-plugin-jsx-a11y": "catalog:",
"eslint-plugin-prettier": "catalog:",
"eslint-plugin-react": "catalog:",
"eslint-plugin-react-hooks": "catalog:",
"favicons": "catalog:",
"favicons-webpack-plugin": "catalog:",
"file-loader": "catalog:",
"fork-ts-checker-webpack-plugin": "catalog:",
"html-webpack-plugin": "catalog:",
"jest": "catalog:",
"mini-css-extract-plugin": "catalog:",
"npm-run-all": "catalog:",
"prettier": "catalog:",
"react-refresh-typescript": "^2.0.3",
"style-loader": "^3.3.1",
"style-loader": "catalog:",
"thread-loader": "^3.0.4",
"ts-jest": "^27.0.5",
"ts-loader": "^9.4.2",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"ts-jest": "catalog:",
"ts-loader": "catalog:",
"tsconfig-paths-webpack-plugin": "catalog:",
"typescript": "^4.6.2",
"url-loader": "^4.1.1",
"webpack": "^5.75.0",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.5.0",
"webpack-favicons": "^1.3.8",
"webpack-merge": "^5.8.0"
"url-loader": "catalog:",
"webpack": "catalog:",
"webpack-cli": "catalog:",
"webpack-dev-server": "catalog:",
"webpack-favicons": "catalog:",
"webpack-merge": "catalog:"
},
"private": false,
"overrides": {
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"react": "^19.2.6",
"react-dom": "^19.2.6"
}
}
@@ -1,22 +0,0 @@
# Nym Chrome Extension Example
This is an example of how Nym can be used within the context of a Chrome extension.
## Running the example
1. Copy a build of the Nym TypeScript SDK (ESM version) into `./sdk`.
2. Navigate to `chrome://extensions` in Google Chrome.
3. Enable "Developer mode" (top right of the page).
4. Click on "Load unpacked" (top left of the page).
5. Load this extension folder.
## How does it work?
The Nym Mixnet Client runs a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) that wraps
a WASM library that builds and encrypts Sphinx packets in the browser to send over the Nym mixnet:
![Sphinx packet](../docs/worker.svg)
The WASM code encrypts each layer of the Sphinx packet in the browser, before sending the Sphinx packet over a websocket to the ingress gateway:
![Sphinx packet](../docs/sphinx.svg)
@@ -1,17 +0,0 @@
{
"name": "Nym Chrome Extension Example",
"description": "An example demonstrating how to integrate the Nym TypeScript SDK in the context of a Google Chrome browser extension.",
"version": "1.0",
"manifest_version": 3,
"icons": {
"48": "icon.png"
},
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
},
"action": {
"default_title": "Nym Chrome Extension Example",
"default_icon": "icon.png",
"default_popup": "popup.html"
}
}
@@ -1,21 +0,0 @@
{
"name": "@nymproject/sdk-example-chrome-extension",
"version": "1.0.5",
"description": "This is an example of how Nym can be used within the context of a Chrome extension.",
"license": "ISC",
"author": "",
"main": "index.js",
"scripts": {
"build": "webpack"
},
"dependencies": {
"@nymproject/sdk": ">=1.4.1-rc1 || ^1"
},
"devDependencies": {
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4"
},
"private": false
}
@@ -1,8 +0,0 @@
body {
width: 800px;
min-height: 400px;
}
#editdialog input {
width: 100%;
}
@@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="popup.css" />
<script type="module" src="main.js"></script>
</head>
<body>
<p><label>Sender:</label><input disabled="true" size="85" id="sender" value="" /></p>
<p><label>Recipient:</label><input size="85" id="recipient" value="" /></p>
<p><label>Message:</label><input id="message" value="Hello mixnet!" /></p>
<p><button id="send-button">Send</button></p>
<p>Send messages from your browser, through the mixnet, and to the recipient using the "send" button.</p>
<p>
<span style="color: blue">Sent</span> messages show in blue, <span style="color: green">received</span> messages
show in green.
</p>
<hr />
<p></p>
<div id="output"></div>
<p></p>
</body>
</html>
@@ -1,66 +0,0 @@
// dom-utils.js
// Contains utility functionality to help manipulate the DOM elements necessary to demonstrate the Nym example.
/**
* Create a Sphinx packet and send it to the mixnet through the gateway node.
*
* Message and recipient are taken from the values in the user interface.
*
* @param {Client} nymClient the nym client to use for message sending
*/
async function sendMessageTo(nym) {
const message = document.getElementById('message').value;
const recipient = document.getElementById('recipient').value;
await nym.client.send({
payload: {
message,
mimeType: 'text/plain'
},
recipient
});
displaySend(message);
}
/**
* Display messages that have been sent up the websocket. Colours them blue.
*
* @param {string} message
*/
function displaySend(message) {
const timestamp = new Date().toISOString().substr(11, 12);
const sendDiv = document.createElement('div');
const paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: blue');
const paragraphContent = document.createTextNode(`${timestamp} sent >>> ${message}`);
paragraph.appendChild(paragraphContent);
sendDiv.appendChild(paragraph);
document.getElementById('output')?.appendChild(sendDiv);
}
/**
* Display received text messages in the browser. Colour them green.
*
* @param {string} message
*/
function displayReceived(message) {
const content = message;
const timestamp = new Date().toLocaleTimeString();
const receivedDiv = document.createElement('div');
const paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: green');
const paragraphContent = document.createTextNode(`${timestamp} received >>> ${content}`);
paragraph.appendChild(paragraphContent);
receivedDiv.appendChild(paragraph);
document.getElementById('output')?.appendChild(receivedDiv);
}
/**
* Display the nymClient's sender address in the user interface
*
* @param {Client} nymClient
*/
function displaySenderAddress(address) {
document.getElementById('sender').value = address;
}
export { sendMessageTo, displaySend, displayReceived , displaySenderAddress }
@@ -1,53 +0,0 @@
// main.js
// Simple example of how to load Nym's TypeScript SDK and bind it to a DOM.
// Look at dom-utils.js for the DOM utility functionality referenced here.
// Import the Nym mixnet ESM module.
import { createNymMixnetClient } from '@nymproject/sdk';
// Import the DOM utility functionality.
import { displaySenderAddress, displayReceived, sendMessageTo } from './dom-utils.js';
async function main() {
// Initialize the Nym mixnet client.
let nymClient = await createNymMixnetClient();
if (!nymClient) {
console.error('Oh no! Could not create client');
return;
}
const nymApiUrl = 'https://validator.nymtech.net/api';
const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
// subscribe to connect event, so that we can show the client's address
nymClient.events.subscribeToConnected((e) => {
if (e.args.address) {
displaySenderAddress(e.args.address);
}
});
// subscribe to message received events and show any string messages received
nymClient.events.subscribeToTextMessageReceivedEvent((e) => {
displayReceived(e.args.payload);
});
const sendButton = document.querySelector('#send-button');
if (sendButton) {
sendButton.onclick = function () {
if (nymClient) {
sendMessageTo(nymClient);
}
};
}
nymClient.events.subscribeToRawMessageReceivedEvent((e) => console.log('Received: ', e.args.payload));
await nymClient.client.start({
clientId: 'My awesome client',
nymApiUrl,
preferredGatewayIdentityKey,
});
}
window.addEventListener('DOMContentLoaded', () => {
main();
});
@@ -1,9 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"declaration": false,
"declarationMap": false
},
"exclude": ["node_modules", "dist"]
}
@@ -1,25 +0,0 @@
// Webpack configuration for the Chrome extension example
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
main: './src/main.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
'manifest.json',
'popup.html',
{ from: path.resolve(__dirname, '../../../../assets/favicon/favicon.png'), to: 'icon.png' },
],
}),
],
};
@@ -1 +0,0 @@
sdk/index.js
@@ -1,35 +0,0 @@
# Nym Firefox Extension Example
This is an example of how Nym can be used within the context of a Mozilla Firefox extension.
## Running the example
First, build the Nym SDK:
From the SDK directory `sdk/typescript/packages/sdk` run:
```js
npm run build:local
```
Then, from the example directory `sdk/typescript/examples/firefox-extension` run:
```js
npm install
npm run build
```
## Workers
Firefox browser extensions cannot run inline web workers. In order to overcome this limitation, the Nym Firefox Extension Example imports workers from the SDK and uses Webpack's `worker-loader` to allow the worker's to be bundled inline into the extension. In order for webpack to include the workers in the build, they are imported as modules in the `src/index.js` file:
## How does it work?
The Nym Mixnet Client runs a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) that wraps
a WASM library that builds and encrypts Sphinx packets in the browser to send over the Nym mixnet:
![Sphinx packet](../docs/worker.svg)
The WASM code encrypts each layer of the Sphinx packet in the browser, before sending the Sphinx packet over a websocket to the ingress gateway:
![Sphinx packet](../docs/sphinx.svg)
@@ -1,23 +0,0 @@
{
"manifest_version": 3,
"name": "Nym Firefox Extension Example",
"version": "1.0",
"description": "An example demonstrating how to integrate the Nym TypeScript SDK in the context of a Mozilla Firefox browser extension.",
"icons": {
"48": "icon.png"
},
"permissions": [],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval';"
},
"background": {
"scripts": ["background.js"]
},
"action": {
"default_icon": {
"32": "icon.png"
},
"default_title": "Nym Firefox Extension Example",
"default_popup": "popup.html"
}
}
@@ -1,21 +0,0 @@
{
"name": "@nymproject/sdk-example-firefox-extension",
"version": "1.0.5",
"description": "This is an example of how Nym can be used within the context of a Firefox extension.",
"license": "ISC",
"author": "",
"main": "index.js",
"scripts": {
"build": "yarn webpack"
},
"dependencies": {
"@nymproject/sdk": ">=1.4.1-rc1 || ^1"
},
"devDependencies": {
"copy-webpack-plugin": "^11.0.0",
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4",
"worker-loader": "^3.0.8"
},
"private": false
}
@@ -1,8 +0,0 @@
body {
width: 800px;
min-height: 400px;
}
#editdialog input {
width: 100%;
}
@@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="popup.css" />
<script type="module" src="popup.js"></script>
</head>
<body>
<p><label>Sender:</label><input disabled="true" size="85" id="sender" value="" /></p>
<p><label>Recipient:</label><input size="85" id="recipient" value="" /></p>
<p><label>Message:</label><input id="message" value="Hello mixnet!" /></p>
<p><button id="send-button">Send</button></p>
<p>Send messages from your browser, through the mixnet, and to the recipient using the "send" button.</p>
<p>
<span style="color: blue">Sent</span> messages show in blue, <span style="color: green">received</span> messages
show in green.
</p>
<hr />
<p></p>
<div id="output"></div>
<p></p>
</body>
</html>
@@ -1,107 +0,0 @@
// main.js
// Simple example of how to load Nym's TypeScript SDK and bind it to a DOM.
// Look at dom-utils.js for the DOM utility functionality referenced here.
// Import the Nym mixnet ESM module.
// Import The web workers for the Nym mixnet ESM module.These are required for to run the Nym mixnet client.
import { createNymMixnetClient } from '../../../packages/sdk/dist/full-fat/index.js';
import '../../../packages/sdk/dist/full-fat/web-worker-0.js';
import '../../../packages/sdk/dist/full-fat/web-worker-1.js';
const backgroundState = {
isReady: false,
address: '',
recipient: '',
messageLog: [],
};
async function initBackground() {
// Initialize the Nym mixnet client.
let nymClient = await createNymMixnetClient().catch((err) => {
console.log(err);
});
if (!nymClient) {
console.error('Oh no! Could not create client');
return;
}
const nymApiUrl = 'https://validator.nymtech.net/api';
const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
// subscribe to connect event, so that we can show the client's address
nymClient.events.subscribeToConnected((e) => {
if (e.args.address) {
backgroundState.address = e.args.address;
browser.runtime.sendMessage({
type: 'displaySenderAddress',
message: backgroundState.address,
});
}
});
// subscribe to message received events and show any string messages received
nymClient.events.subscribeToTextMessageReceivedEvent((e) => {
backgroundState.messageLog.push({
type: 'received',
message: e.args.payload,
});
browser.runtime.sendMessage({
type: 'displayReceived',
message: e.args.payload,
});
});
nymClient.events.subscribeToRawMessageReceivedEvent((e) => console.log('Received: ', e.args.payload));
await nymClient.client.start({
clientId: 'My awesome client',
nymApiUrl,
preferredGatewayIdentityKey,
});
browser.runtime.onMessage.addListener(async (data) => {
switch (data.type) {
case 'nymClientSendMessage':
if (nymClient) {
await nymClient.client.send({
payload: {
message: data.message,
mimeType: 'text/plain',
},
recipient: data.recipient,
});
backgroundState.messageLog.push({
type: 'sent',
message: data.message,
});
break;
}
}
});
backgroundState.isReady = true;
}
window.addEventListener('DOMContentLoaded', () => {
browser.runtime.onMessage.addListener((data) => {
switch (data.type) {
case 'popupReady':
if (backgroundState.isReady) {
browser.runtime.sendMessage({
type: 'displaySenderAddress',
message: backgroundState.address,
});
browser.runtime.sendMessage({
type: 'displayMessageLog',
message: backgroundState.messageLog,
});
browser.runtime.sendMessage({
type: 'updateRecipient',
message: backgroundState.recipient,
});
} else {
initBackground();
}
break;
case 'updateRecipient':
backgroundState.recipient = data.message;
}
});
});
@@ -1,114 +0,0 @@
// main.js
// Simple example of how to load Nym's TypeScript SDK and bind it to a DOM.
// Look at dom-utils.js for the DOM utility functionality referenced here.
// Import the Nym mixnet ESM module.
// Import The web workers for the Nym mixnet ESM module.These are required for to run the Nym mixnet client.
import { createNymMixnetClient } from '../../../packages/sdk/dist/full-fat/index.js';
import '../../../packages/sdk/dist/full-fat/web-worker-0.js';
import '../../../packages/sdk/dist/full-fat/web-worker-1.js';
export type BackgroundState = {
isReady: boolean;
address: string;
recipient: string;
messageLog: { type: string; message: string }[];
};
const backgroundState: BackgroundState = {
isReady: false,
address: '',
recipient: '',
messageLog: [],
};
async function initBackground() {
// Initialize the Nym mixnet client.
let nymClient = await createNymMixnetClient().catch((err) => {
console.log(err);
});
if (!nymClient) {
console.error('Oh no! Could not create client');
return;
}
const nymApiUrl = 'https://validator.nymtech.net/api';
const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
// subscribe to connect event, so that we can show the client's address
nymClient.events.subscribeToConnected((e) => {
if (e.args.address) {
backgroundState.address = e.args.address;
browser.runtime.sendMessage({
type: 'displaySenderAddress',
message: backgroundState.address,
});
}
});
// subscribe to message received events and show any string messages received
nymClient.events.subscribeToTextMessageReceivedEvent((e) => {
backgroundState.messageLog.push({
type: 'received',
message: e.args.payload,
});
browser.runtime.sendMessage({
type: 'displayReceived',
message: e.args.payload,
});
});
nymClient.events.subscribeToRawMessageReceivedEvent((e) => console.log('Received: ', e.args.payload));
await nymClient.client.start({
clientId: 'My awesome client',
nymApiUrl,
preferredGatewayIdentityKey,
});
browser.runtime.onMessage.addListener(async (data) => {
switch (data.type) {
case 'nymClientSendMessage':
if (nymClient) {
await nymClient.client.send({
payload: {
message: data.message,
mimeType: 'text/plain',
},
recipient: data.recipient,
});
backgroundState.messageLog.push({
type: 'sent',
message: data.message,
});
break;
}
}
});
backgroundState.isReady = true;
}
window.addEventListener('DOMContentLoaded', () => {
browser.runtime.onMessage.addListener((data) => {
switch (data.type) {
case 'popupReady':
if (backgroundState.isReady) {
browser.runtime.sendMessage({
type: 'displaySenderAddress',
message: backgroundState.address,
});
browser.runtime.sendMessage({
type: 'displayMessageLog',
message: backgroundState.messageLog,
});
browser.runtime.sendMessage({
type: 'updateRecipient',
message: backgroundState.recipient,
});
} else {
initBackground();
}
break;
case 'updateRecipient':
backgroundState.recipient = data.message;
}
});
});
@@ -1,74 +0,0 @@
// dom-utils.js
// Contains utility functionality to help manipulate the DOM elements necessary to demonstrate the Nym example.
/**
* Create a Sphinx packet and send it to the mixnet through the gateway node.
*
* Message and recipient are taken from the values in the user interface.
*
* @param {Client} nymClient the nym client to use for message sending
*/
async function sendMessageTo() {
const message = document.getElementById('message').value;
const recipient = document.getElementById('recipient').value;
browser.runtime.sendMessage({
type: 'nymClientSendMessage',
message,
recipient,
});
displaySend(message);
}
/**
* Display messages that have been sent up the websocket. Colours them blue.
*
* @param {string} message
*/
function displaySend(message) {
const timestamp = new Date().toISOString().substr(11, 12);
const sendDiv = document.createElement('div');
const paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: blue');
const paragraphContent = document.createTextNode(`${timestamp} sent >>> ${message}`);
paragraph.appendChild(paragraphContent);
sendDiv.appendChild(paragraph);
document.getElementById('output')?.appendChild(sendDiv);
}
/**
* Display received text messages in the browser. Colour them green.
*
* @param {string} message
*/
function displayReceived(message) {
const content = message;
const timestamp = new Date().toLocaleTimeString();
const receivedDiv = document.createElement('div');
const paragraph = document.createElement('p');
paragraph.setAttribute('style', 'color: green');
const paragraphContent = document.createTextNode(`${timestamp} received >>> ${content}`);
paragraph.appendChild(paragraphContent);
receivedDiv.appendChild(paragraph);
document.getElementById('output')?.appendChild(receivedDiv);
}
/**
* Display the nymClient's sender address in the user interface
*
* @param {Client} nymClient
*/
function displaySenderAddress(address) {
document.getElementById('sender').value = address;
}
function displayMessageLog(messageLog) {
for (let i = 0; i < messageLog.length; i++) {
if (messageLog[i].type === 'sent') {
displaySend(messageLog[i].message);
} else if (messageLog[i].type === 'received') {
displayReceived(messageLog[i].message);
}
}
}
export { sendMessageTo, displaySend, displayReceived, displaySenderAddress, displayMessageLog };
@@ -1,35 +0,0 @@
// dom-utils.js
// Contains utility functionality to help manipulate the DOM elements necessary to demonstrate the Nym example.
import { BackgroundState } from './background';
import { displayReceived, displaySend, displaySenderAddress } from '../../shared/dom-utils';
/**
* Create a Sphinx packet and send it to the mixnet through the gateway node.
*
* Message and recipient are taken from the values in the user interface.
*
* @param {Client} nymClient the nym client to use for message sending
*/
async function sendMessageTo() {
const message = (document.getElementById('message') as HTMLFormElement).value;
const recipient = (document.getElementById('recipient') as HTMLFormElement).value;
browser.runtime.sendMessage({
type: 'nymClientSendMessage',
message,
recipient,
});
displaySend(message);
}
function displayMessageLog(messageLog: BackgroundState['messageLog']) {
for (let i = 0; i < messageLog.length; i++) {
if (messageLog[i].type === 'sent') {
displaySend(messageLog[i].message);
} else if (messageLog[i].type === 'received') {
displayReceived(messageLog[i].message);
}
}
}
export { sendMessageTo, displaySend, displayReceived, displaySenderAddress, displayMessageLog };
@@ -1,40 +0,0 @@
// Import the DOM utility functionality.
import { displaySenderAddress, displayReceived, sendMessageTo, displayMessageLog } from './dom-utils.js';
window.addEventListener('DOMContentLoaded', () => {
const sendButton = document.querySelector('#send-button');
if (sendButton) {
sendButton.onclick = function () {
sendMessageTo();
};
}
const recipient = document.getElementById('recipient');
recipient.onchange = () => {
browser.runtime.sendMessage({
type: 'updateRecipient',
message: recipient.value,
});
};
browser.runtime.onMessage.addListener((data) => {
switch (data.type) {
case 'displaySenderAddress':
displaySenderAddress(data.message);
break;
case 'displayReceived':
displayReceived(data.message);
break;
case 'sendMessageTo':
sendMessageTo(data.message);
break;
case 'displayMessageLog':
displayMessageLog(data.message);
break;
case 'updateRecipient':
recipient.value = data.message;
}
});
browser.runtime.sendMessage({
type: 'popupReady',
message: '',
});
});
@@ -1,40 +0,0 @@
// Import the DOM utility functionality.
import { displaySenderAddress, displayReceived, sendMessageTo, displayMessageLog } from './dom-utils';
window.addEventListener('DOMContentLoaded', () => {
const sendButton = document.querySelector('#send-button') as HTMLButtonElement;
if (sendButton) {
sendButton.onclick = function () {
sendMessageTo();
};
}
const recipient = document.getElementById('recipient') as HTMLFormElement;
recipient.onchange = () => {
browser.runtime.sendMessage({
type: 'updateRecipient',
message: recipient.value,
});
};
browser.runtime.onMessage.addListener((data) => {
switch (data.type) {
case 'displaySenderAddress':
displaySenderAddress(data.message);
break;
case 'displayReceived':
displayReceived(data.message);
break;
// case 'sendMessageTo':
// sendMessageTo(data.message);
// break;
case 'displayMessageLog':
displayMessageLog(data.message);
break;
case 'updateRecipient':
recipient.value = data.message;
}
});
browser.runtime.sendMessage({
type: 'popupReady',
message: '',
});
});
@@ -1,9 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"declaration": false,
"declarationMap": false
},
"exclude": ["node_modules", "dist"]
}
@@ -1,38 +0,0 @@
// Webpack configuration for the Firefox extension example
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'production',
entry: {
background: './src/background.js',
popup: './src/popup.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
'manifest.json',
'popup.html',
{ from: path.resolve(__dirname, '../../../../assets/favicon/favicon.png'), to: 'icon.png' },
],
}),
],
module: {
rules: [
{
test: /web-worker.*\.js$/,
loader: 'worker-loader',
options: {
filename: '[name].js',
inline: 'fallback',
},
},
],
},
};
@@ -1,14 +0,0 @@
# Nym Node Tester - Parcel bundler
This is an example of using the Nym Mixnet node tester.
You can use this example as a seed for a new project.
## Running the example
Try out the node tester app by running:
```
npm install
npm start
```
@@ -1,44 +0,0 @@
{
"name": "@nymproject/sdk-example-node-tester-plain-html-parcel",
"version": "1.0.5",
"description": "An example project that uses WASM and plain HTML bundled with Parcel v2",
"license": "Apache-2.0",
"scripts": {
"build": "npx parcel build",
"build:serve": "npx serve dist",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"start": "npx parcel",
"test": "jest",
"test:watch": "jest --watch",
"tsc": "tsc",
"tsc:watch": "tsc --watch"
},
"dependencies": {
"@nymproject/sdk": ">=1.4.1-rc1 || ^1"
},
"devDependencies": {
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.1.0",
"mini-css-extract-plugin": "^2.2.2",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.7",
"ts-jest": "^27.0.5",
"typescript": "^4.6.2"
},
"private": false,
"browserslist": "> 0.5%, last 2 versions, not dead",
"source": "src/index.html"
}
@@ -1,29 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nym Node Tester Demo</title>
</head>
<body>
<p>
<label>Mixnode Id: </label><input size="85" type="text" id="mixnodeId" value="">
</p>
<p>
<button id="test-button">Test</button>
<button id="disconnect-button">Disconnect from Gateway</button>
<button id="reconnect-button">Reconnect to Gateway</button>
<button id="terminate-button">Terminate worker</button>
</p>
<p>Test a mixnode by entering the mixnode id above and click the Test button</p>
<hr>
<p>
<div id="output"></div>
</p>
<script src="./index.ts" type="module"></script>
</body>
</html>
@@ -1,118 +0,0 @@
import { createNodeTesterClient, NodeTester } from '@nymproject/sdk';
let nodeTester: NodeTester | null = null;
/**
* Display messages that have been sent up the websocket. Colours them blue.
*
* @param {string} message
*/
function displayOutput(message: string, color?: string) {
const timestamp = new Date().toISOString().substr(11, 12);
const sendDiv = document.createElement('div');
const paragraph = document.createElement('p');
paragraph.setAttribute('style', `color: ${color || 'blue'}`);
const paragraphContent = document.createTextNode(`${timestamp} >>> ${message}`);
paragraph.appendChild(paragraphContent);
sendDiv.appendChild(paragraph);
document.getElementById('output')?.appendChild(sendDiv);
}
/**
* The main entry point
*/
async function main() {
nodeTester = await createNodeTesterClient();
// add node tester to the Window globally, so that it can be used from the dev tools console
(window as any).nodeTester = nodeTester;
if (!nodeTester) {
console.error('Oh no! Could not the node test');
return;
}
const nymApiUrl = 'https://validator.nymtech.net/api';
const nodeTesterId = new Date().toISOString(); // make a new tester id for each session
await nodeTester.tester.init(nymApiUrl, nodeTesterId);
const mixnodes = await (await fetch(`${nymApiUrl}/v1/mixnodes/active`)).json();
const exampleMixnodeIdentityKey = mixnodes[0].bond_information.mix_node.identity_key;
const testButton: HTMLButtonElement = document.querySelector('#test-button') as HTMLButtonElement;
const reconnectButton: HTMLButtonElement = document.querySelector('#reconnect-button') as HTMLButtonElement;
const disconnectButton: HTMLButtonElement = document.querySelector('#disconnect-button') as HTMLButtonElement;
const terminateButton: HTMLButtonElement = document.querySelector('#terminate-button') as HTMLButtonElement;
const mixnodeIdInput = document.getElementById('mixnodeId') as HTMLFormElement;
mixnodeIdInput.value = exampleMixnodeIdentityKey;
reconnectButton.onclick = async function () {
try {
await nodeTester?.tester.reconnectToGateway();
} catch (e: any) {
console.error('Error', e);
displayOutput(`ERROR: ${e.message}`, 'red');
}
};
disconnectButton.onclick = async function () {
try {
await nodeTester?.tester.disconnectFromGateway();
} catch (e: any) {
console.error('Error', e);
displayOutput(`ERROR: ${e.message}`, 'red');
}
};
terminateButton.onclick = async function () {
try {
await nodeTester?.terminate();
} catch (e: any) {
console.error('Error', e);
displayOutput(`ERROR: ${e.message}`, 'red');
}
};
if (testButton) {
testButton.onclick = async function () {
console.log('clicked');
const mixnodeId = mixnodeIdInput.value;
if (!nodeTester) {
displayOutput('ERROR: The node tester is not defined');
console.error('The node tester is not defined');
return;
}
if (!mixnodeId) {
displayOutput('ERROR: No mix id specified');
console.error('No mix id specified');
return;
}
if (nodeTester && mixnodeId) {
displayOutput('Starting test...');
try {
const response = await nodeTester.tester.startTest(mixnodeId);
displayOutput('Done!');
if (response) {
displayOutput(JSON.stringify(response, null, 2), 'green');
}
} catch (e: any) {
console.error('Error', e);
displayOutput(`ERROR: ${e.message}`, 'red');
}
}
};
}
}
// wait for the html to load
window.addEventListener('DOMContentLoaded', () => {
// let's do this!
main();
});
@@ -1,8 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "build", "dist"]
}
@@ -1,14 +0,0 @@
# Nym Node Tester - HTML
This is an example of using the Nym Mixnet node tester.
You can use this example as a seed for a new project.
## Running the example
Try out the node tester app by running:
```
npm install
npm start
```
@@ -1,65 +0,0 @@
{
"name": "@nymproject/sdk-example-node-tester-plain-html",
"version": "1.0.5",
"description": "An example project that uses WASM node tester and plain HTML",
"license": "Apache-2.0",
"scripts": {
"build": "webpack build --progress --config webpack.prod.js",
"build:dev": "webpack build --progress",
"build:serve": "npx serve dist",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"start": "webpack serve --progress --port 3000",
"test": "jest",
"test:watch": "jest --watch",
"tsc": "tsc",
"tsc:watch": "tsc --watch"
},
"dependencies": {
"@nymproject/sdk": ">=1.4.1-rc1 || ^1"
},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/plugin-transform-async-to-generator": "^7.14.5",
"@babel/preset-env": "^7.15.0",
"@babel/preset-typescript": "^7.15.0",
"@types/jest": "^27.0.1",
"@types/node": "^16.7.13",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"babel-loader": "^8.3.0",
"babel-plugin-root-import": "^5.1.0",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^3.0.2",
"dotenv-webpack": "^7.0.3",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-jest": "^26.1.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^7.2.1",
"html-webpack-plugin": "^5.3.2",
"jest": "^27.1.0",
"mini-css-extract-plugin": "^2.2.2",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.7",
"style-loader": "^3.3.1",
"thread-loader": "^3.0.4",
"ts-jest": "^27.0.5",
"ts-loader": "^9.4.2",
"tsconfig-paths-webpack-plugin": "^3.5.2",
"typescript": "^4.6.2",
"url-loader": "^4.1.1",
"webpack": "^5.75.0",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.5.0",
"webpack-merge": "^5.8.0"
},
"private": false
}
@@ -1,29 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nym Node Tester Demo</title>
</head>
<body>
<p>
<label>Mixnode Id: </label><input size="85" type="text" id="mixnodeId" value="">
</p>
<p>
<button id="send-button">Test</button>
<button id="disconnect-button">Disconnect from Gateway</button>
<button id="reconnect-button">Reconnect to Gateway</button>
<button id="terminate-button">Terminate worker</button>
</p>
<p>Test a mixnode by entering the mixnode id above and click the Test button</p>
<hr>
<p>
<div id="output"></div>
</p>
</body>
</html>
@@ -1,116 +0,0 @@
import { createNodeTesterClient, NodeTester } from '@nymproject/sdk';
let nodeTester: NodeTester | null = null;
/**
* Display messages that have been sent up the websocket. Colours them blue.
*
* @param {string} message
*/
function displayOutput(message: string, color?: string) {
const timestamp = new Date().toISOString().substr(11, 12);
const sendDiv = document.createElement('div');
const paragraph = document.createElement('p');
paragraph.setAttribute('style', `color: ${color || 'blue'}`);
const paragraphContent = document.createTextNode(`${timestamp} >>> ${message}`);
paragraph.appendChild(paragraphContent);
sendDiv.appendChild(paragraph);
document.getElementById('output')?.appendChild(sendDiv);
}
/**
* The main entry point
*/
async function main() {
nodeTester = await createNodeTesterClient();
// add node tester to the Window globally, so that it can be used from the dev tools console
(window as any).nodeTester = nodeTester;
if (!nodeTester) {
console.error('Oh no! Could not the node test');
return;
}
const nymApiUrl = 'https://validator.nymtech.net/api';
const nodeTesterId = new Date().toISOString(); // make a new tester id for each session
await nodeTester.tester.init(nymApiUrl, nodeTesterId);
const mixnodes = await (await fetch(`${nymApiUrl}/v1/mixnodes/active`)).json();
const exampleMixnodeIdentityKey = mixnodes[0].bond_information.mix_node.identity_key;
const sendButton: HTMLButtonElement = document.querySelector('#send-button') as HTMLButtonElement;
const reconnectButton: HTMLButtonElement = document.querySelector('#reconnect-button') as HTMLButtonElement;
const disconnectButton: HTMLButtonElement = document.querySelector('#disconnect-button') as HTMLButtonElement;
const terminateButton: HTMLButtonElement = document.querySelector('#terminate-button') as HTMLButtonElement;
const mixnodeIdInput = document.getElementById('mixnodeId') as HTMLFormElement;
mixnodeIdInput.value = exampleMixnodeIdentityKey;
reconnectButton.onclick = async function () {
try {
await nodeTester?.tester.reconnectToGateway();
} catch (e: any) {
console.error('Error', e);
displayOutput(`ERROR: ${e.message}`, 'red');
}
};
disconnectButton.onclick = async function () {
try {
await nodeTester?.tester.disconnectFromGateway();
} catch (e: any) {
console.error('Error', e);
displayOutput(`ERROR: ${e.message}`, 'red');
}
};
terminateButton.onclick = async function () {
try {
await nodeTester?.terminate();
} catch (e: any) {
console.error('Error', e);
displayOutput(`ERROR: ${e.message}`, 'red');
}
};
if (sendButton) {
sendButton.onclick = async function () {
const mixnodeId = mixnodeIdInput.value;
if (!nodeTester) {
displayOutput('ERROR: The node tester is not defined');
console.error('The node tester is not defined');
return;
}
if (!mixnodeId) {
displayOutput('ERROR: No mix id specified');
console.error('No mix id specified');
return;
}
if (nodeTester && mixnodeId) {
displayOutput('Starting test...');
try {
const response = await nodeTester.tester.startTest(mixnodeId);
displayOutput('Done!');
if (response) {
displayOutput(JSON.stringify(response, null, 2), 'green');
}
} catch (e: any) {
console.error('Error', e);
displayOutput(`ERROR: ${e.message}`, 'red');
}
}
};
}
}
// wait for the html to load
window.addEventListener('DOMContentLoaded', () => {
// let's do this!
main();
});
@@ -1,8 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "build", "dist"]
}
@@ -1,10 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"outDir": "./dist",
"declaration": false
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "build", "dist", "**/*.stories.*", "**/*.test.*", "**/*.spec.*"]
}
@@ -1,30 +0,0 @@
const path = require('path');
const { mergeWithRules } = require('webpack-merge');
const { webpackCommon } = require('../../.webpack/webpack.base');
module.exports = mergeWithRules({
module: {
rules: {
test: 'match',
use: 'replace',
},
},
})(
webpackCommon(__dirname, [
{
inject: true,
filename: 'index.html',
template: path.resolve(__dirname, 'src/index.html'),
chunks: ['index'],
},
]),
{
entry: {
index: path.resolve(__dirname, 'src/index.ts'),
},
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
},
},
);
@@ -1,67 +0,0 @@
const { mergeWithRules } = require('webpack-merge');
const webpack = require('webpack');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const ReactRefreshTypeScript = require('react-refresh-typescript');
const commonConfig = require('./webpack.common');
module.exports = mergeWithRules({
module: {
rules: {
test: 'match',
use: 'replace',
},
},
})(commonConfig, {
mode: 'development',
devtool: 'inline-source-map',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
options: {
getCustomTransformers: () => ({
before: [ReactRefreshTypeScript()],
}),
// `ts-loader` does not work with HMR unless `transpileOnly` is used.
// If you need type checking, `ForkTsCheckerWebpackPlugin` is an alternative.
transpileOnly: true,
},
},
],
},
plugins: [
new ReactRefreshWebpackPlugin(),
// this can be included automatically by the dev server, however build mode fails if missing
new webpack.HotModuleReplacementPlugin(),
],
target: 'web',
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
historyApiFallback: true,
},
// recommended for faster rebuild
optimization: {
runtimeChunk: true,
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
},
cache: {
type: 'filesystem',
buildDependencies: {
// restart on config change
config: ['./webpack.config.js'],
},
},
});
@@ -1,42 +0,0 @@
const { mergeWithRules } = require('webpack-merge');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const commonConfig = require('./webpack.common');
module.exports = mergeWithRules({
module: {
rules: {
test: 'match',
use: 'replace',
},
},
})(commonConfig, {
mode: 'production',
// TODO: no source maps, add back
devtool: false,
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
optimization: {
minimizer: ['...', new CssMinimizerPlugin()],
splitChunks: {
chunks: 'all',
},
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
output: {
pathinfo: false,
filename: '[name].[contenthash].js',
},
});
@@ -1,14 +0,0 @@
# Nym Node Tester - React
This is an example of using the Nym Mixnet node tester.
You can use this example as a seed for a new project.
## Running the example
Try out the node tester app by running:
```
npm install
npm start
```
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="../../../../../assets/favicon/favicon.png" />
<title>Nym Node Tester Example</title>
</head>
<body>
<div id="root"></div>
<script src="src/index.tsx" type="module"></script>
</body>
</html>
@@ -1,20 +0,0 @@
{
"name": "@nymproject/sdk-example-node-tester-react",
"version": "1.0.5",
"description": "An example project that uses WASM node tester and React",
"license": "Apache-2.0",
"scripts": {
"start": "parcel index.html"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.14.0",
"@mui/material": "^5.14.0",
"@nymproject/sdk": ">=1.4.1-rc1 || ^1",
"parcel": "^2.9.3",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"private": false
}
@@ -1,127 +0,0 @@
import React, { useState } from 'react';
import {
Button,
Card,
CardActions,
CardContent,
CardHeader,
CircularProgress,
Grid,
List,
ListItem,
ListItemText,
TextField,
Typography,
} from '@mui/material';
import { NodeTestResultResponse } from '@nymproject/sdk';
import { ScoreIndicator } from 'src/components/ScoreIndicator';
import { useNodeTesterClient } from 'src/hooks/useNodeTesterClient';
import { BasicPageLayout } from 'src/layouts';
import { TestStatusLabel } from 'src/components/TestStatusLabel';
import Icon from '../../../../../../assets/appicon/appicon.png';
export const App = () => {
const { testState, error, testNode, disconnectFromGateway, reconnectToGateway } = useNodeTesterClient();
const [mixnodeIdentity, setMixnodeIdentity] = useState<string>('');
const [results, setResults] = React.useState<NodeTestResultResponse>();
console.log({ testState, error, testNode });
const handleTestNode = async () => {
setResults(undefined);
try {
const result = await testNode(mixnodeIdentity);
setResults(result);
} catch (e) {
console.error(e);
}
};
return (
<BasicPageLayout>
<Card variant="outlined" sx={{ mt: 15, p: 4 }}>
<CardHeader
title={<Typography variant="h6">Nym Mixnode Testnet Node Tester</Typography>}
action={<TestStatusLabel state={testState} />}
avatar={<img src={Icon} width={40} />}
/>
<CardContent sx={{ mb: 2 }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<ScoreIndicator score={results?.score || 0} />
</Grid>
<Grid item xs={12} sm={6}>
<List>
<ListItem>
<ListItemText primary="Packets sent" secondary={results?.sentPackets.toString() || '-'} />
</ListItem>
<ListItem>
<ListItemText primary="Packets received" secondary={results?.receivedPackets.toString() || '-'} />
</ListItem>
<ListItem>
<ListItemText
primary="Duplicate packets received"
secondary={results?.duplicatePackets.toString() || '-'}
/>
</ListItem>
</List>
</Grid>
</Grid>
</CardContent>
<CardActions>
<Grid container spacing={2}>
<Grid item xs={12}>
<TextField
label="Enter a Mixnode Identity to test"
value={mixnodeIdentity}
onChange={(e) => {
setMixnodeIdentity(e.target.value);
}}
fullWidth
/>
</Grid>
<Grid item xs={12} sm={4}>
<Button
disabled={!disconnectFromGateway || testState === 'Disconnected' || testState === 'Testing'}
onClick={disconnectFromGateway}
variant="outlined"
disableElevation
size="large"
fullWidth
sx={{ mr: 2 }}
>
Disconnect
</Button>
</Grid>
<Grid item xs={12} sm={4}>
<Button
disabled={!reconnectToGateway || testState === 'Ready' || testState === 'Testing'}
onClick={reconnectToGateway}
variant="outlined"
disableElevation
size="large"
fullWidth
sx={{ mr: 2 }}
>
Reconnect
</Button>
</Grid>
<Grid item xs={12} sm={4}>
<Button
disabled={!testNode || !mixnodeIdentity || testState === 'Testing' || testState === 'Disconnected'}
onClick={handleTestNode}
variant="contained"
disableElevation
fullWidth
size="large"
endIcon={testState === 'Testing' && <CircularProgress size={25} />}
>
Start test
</Button>
</Grid>
</Grid>
</CardActions>
</Card>
</BasicPageLayout>
);
};
@@ -1,61 +0,0 @@
import React from 'react';
import { Box, CircularProgress, CircularProgressProps, Stack, Typography } from '@mui/material';
const getPerformanceDescriptionAndColor = (score: number) => {
const res: { description: string; color: CircularProgressProps['color'] } = { description: '', color: 'warning' };
if (score >= 90) {
res.description = 'Reliable node';
res.color = 'success';
}
if (score >= 75 && score < 90) {
res.description = 'Average node';
res.color = 'warning';
}
if (score > 0 && score < 75) {
res.description = 'Unreliable node';
res.color = 'error';
}
return res;
};
export const ScoreIndicator = ({ score }) => {
const { color } = getPerformanceDescriptionAndColor(score);
return (
<Box
sx={{
display: 'flex',
position: 'relative',
width: 250,
height: 250,
justifyContent: 'center',
alignItems: 'center',
mx: 'auto',
mt: 4,
}}
>
<CircularProgress
variant="determinate"
value={100}
size={250}
sx={{ position: 'absolute', top: 0, left: 0, color: 'grey.200' }}
/>
<CircularProgress
variant="determinate"
value={score}
size={250}
sx={{ position: 'absolute', top: 0, left: 0 }}
color={color}
/>
<Stack alignItems="center" gap={1}>
<Typography fontWeight="bold" variant="h4">
{Math.round(score)}%
</Typography>
<Typography>Performance Score</Typography>
</Stack>
</Box>
);
};
@@ -1,34 +0,0 @@
import React from 'react';
import { Chip } from '@mui/material';
import { HourglassTop, ErrorOutline, CheckCircleOutline, WarningAmber } from '@mui/icons-material';
import { TestState } from 'src/hooks/useNodeTesterClient';
const getColor = (state: TestState) => {
switch (state) {
case 'Connecting':
return 'warning';
case 'Error':
return 'error';
case 'Ready':
return 'success';
default:
return 'warning';
}
};
const getIcon = (state: TestState) => {
switch (state) {
case 'Connecting':
return <HourglassTop />;
case 'Error':
return <ErrorOutline />;
case 'Ready':
return <CheckCircleOutline />;
default:
return <WarningAmber />;
}
};
export const TestStatusLabel = ({ state }: { state: TestState }) => (
<Chip label={state} color={getColor(state)} icon={getIcon(state)} sx={{ color: 'white' }} />
);

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