Compare commits

...

48 Commits

Author SHA1 Message Date
benedetta davico e5c3f39a57 Merge pull request #6498 from nymtech/master
Merge pull request #6481 from nymtech/release/2026.4-quark
2026-02-27 11:13:58 +01:00
Merve 76f999fc88 {DOCs/operators]: Platform release docs and changelog + docs cleanup (#6482)
* changelog-updates

* Update changelog.mdx

* Update changelog.mdx

* Edits per reviewer request

* fixes

* fixes

* typo fixed

* removed outdated info

* Update docs based on reviewer feedback

* Update changelog.mdx

---------

Co-authored-by: merve <e@E-MacBook-Air.local>
2026-02-27 10:10:16 +00:00
dependabot[bot] 2fce8c7ca3 build(deps): bump qs and express in /wasm/client/internal-dev (#6461)
Bumps [qs](https://github.com/ljharb/qs) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `qs` from 6.13.0 to 6.14.2
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.13.0...v6.14.2)

Updates `express` from 4.21.2 to 4.22.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.21.2...v4.22.1)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.2
  dependency-type: indirect
- dependency-name: express
  dependency-version: 4.22.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 14:20:59 +00:00
Jędrzej Stuczyński 468bd8b5d1 chore: removed all matrix notifications from github actions (#6495) 2026-02-26 13:48:10 +00:00
dependabot[bot] 45022b1671 build(deps): bump ajv from 6.12.6 to 6.14.0 in /documentation/docs (#6477)
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.6 to 6.14.0.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.6...v6.14.0)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 6.14.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:44:29 +00:00
dependabot[bot] 3b3c5beae4 build(deps-dev): bump webpack in /wasm/node-tester/internal-dev (#6451)
Bumps [webpack](https://github.com/webpack/webpack) from 5.77.0 to 5.104.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack/compare/v5.77.0...v5.104.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-version: 5.104.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:35:57 +00:00
dependabot[bot] 650917e216 build(deps): bump mikefarah/yq from 4.52.2 to 4.52.4 (#6465)
Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from 4.52.2 to 4.52.4.
- [Release notes](https://github.com/mikefarah/yq/releases)
- [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt)
- [Commits](https://github.com/mikefarah/yq/compare/v4.52.2...v4.52.4)

---
updated-dependencies:
- dependency-name: mikefarah/yq
  dependency-version: 4.52.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:34:24 +00:00
dependabot[bot] c02adaa019 build(deps-dev): bump qs (#6466)
Bumps [qs](https://github.com/ljharb/qs) from 6.14.1 to 6.14.2.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.1...v6.14.2)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:34:16 +00:00
dependabot[bot] d01c34263a build(deps): bump keccak from 0.1.5 to 0.1.6 (#6472)
Bumps [keccak](https://github.com/RustCrypto/sponges) from 0.1.5 to 0.1.6.
- [Commits](https://github.com/RustCrypto/sponges/compare/keccak-v0.1.5...keccak-v0.1.6)

---
updated-dependencies:
- dependency-name: keccak
  dependency-version: 0.1.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:33:23 +00:00
dependabot[bot] f247e028f2 build(deps): bump hono from 4.11.9 to 4.12.0 (#6475)
Bumps [hono](https://github.com/honojs/hono) from 4.11.9 to 4.12.0.
- [Release notes](https://github.com/honojs/hono/releases)
- [Commits](https://github.com/honojs/hono/compare/v4.11.9...v4.12.0)

---
updated-dependencies:
- dependency-name: hono
  dependency-version: 4.12.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:32:55 +00:00
dependabot[bot] 20fe8dd028 build(deps): bump minimatch and glob (#6476)
Bumps [minimatch](https://github.com/isaacs/minimatch) to 10.2.2 and updates ancestor dependency [glob](https://github.com/isaacs/node-glob). These dependencies need to be updated together.


Updates `minimatch` from 9.0.5 to 10.2.2
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v9.0.5...v10.2.2)

Updates `glob` from 10.5.0 to 13.0.6
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.5.0...v13.0.6)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 10.2.2
  dependency-type: indirect
- dependency-name: glob
  dependency-version: 13.0.6
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:32:28 +00:00
dependabot[bot] 89edabf796 build(deps): bump ajv in /clients/native/examples/js-examples/websocket (#6478)
Bumps [ajv](https://github.com/ajv-validator/ajv) from 8.17.1 to 8.18.0.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v8.17.1...v8.18.0)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 8.18.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:32:01 +00:00
dependabot[bot] bf5352906f build(deps): bump bn.js from 4.12.2 to 4.12.3 (#6483)
Bumps [bn.js](https://github.com/indutny/bn.js) from 4.12.2 to 4.12.3.
- [Release notes](https://github.com/indutny/bn.js/releases)
- [Changelog](https://github.com/indutny/bn.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/indutny/bn.js/compare/v4.12.2...v4.12.3)

---
updated-dependencies:
- dependency-name: bn.js
  dependency-version: 4.12.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:31:44 +00:00
dependabot[bot] 8eb9999876 build(deps): bump bn.js from 4.12.2 to 4.12.3 in /documentation/docs (#6484)
Bumps [bn.js](https://github.com/indutny/bn.js) from 4.12.2 to 4.12.3.
- [Release notes](https://github.com/indutny/bn.js/releases)
- [Changelog](https://github.com/indutny/bn.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/indutny/bn.js/compare/v4.12.2...v4.12.3)

---
updated-dependencies:
- dependency-name: bn.js
  dependency-version: 4.12.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:31:26 +00:00
dependabot[bot] c0f582b336 build(deps): bump minimatch from 3.1.2 to 3.1.4 in /documentation/docs (#6486)
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.4.
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.4)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 3.1.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-26 13:31:09 +00:00
mfahampshire 133a855e01 Max/ci seo tweaks (#6488)
* Tweak README ordering

* Linting

* Add sitemap generation + NEXT env var to CI

* Fix lockfile

* Regenerate with newer pnpm
2026-02-25 11:07:35 +00:00
mfahampshire 98149dde87 Max/docs theme tweaks (#6480)
* Simplified landing page card layout, centered text, switched to raw
layout on index page for theming flexibility.

* Tweak theme
2026-02-25 10:05:20 +00:00
bnemeroff 5e733a5ebf SEO: Add frontmatter, structured data, and sitemap config (#6453)
* SEO: Add frontmatter, structured data, and sitemap config

* Fix: restore deleted prebuild output file

---------

Co-authored-by: Benjamin Nemeroff <ben@Benjamins-MacBook-Air.local>
Co-authored-by: mfahampshire <maxhampshire@pm.me>
2026-02-25 09:48:15 +00:00
benedetta davico 5647ae6a41 Merge pull request #6469 from nymtech/release/2026.4-quark
quark to develop
2026-02-25 08:53:48 +01:00
benedetta davico 4ed9d8fb7a Merge pull request #6481 from nymtech/release/2026.4-quark
Quark to master
2026-02-25 08:53:45 +01:00
benedettadavico a2081af603 . 2026-02-24 12:02:35 +01:00
benedettadavico 5b62fd76ba update changelog 2026-02-24 11:29:04 +01:00
mfahampshire 77a34fe3bf Update MixFetch docs playground + components (#6479) 2026-02-24 09:29:15 +00:00
mfahampshire 630c4922ac Max/mixfetch concurrent test (#6417)
* * Experiment with changing address mapping from canonical -> full URL as
  string.
* Up MaxConns config.

* Bump webpack-cli version

* Modify internal-dev tester for concurrent testing

* Add logging + POST request to internal-dev/ 

* push lockfiles

* Remove RequestURL from RequestOptions struct for interface

* Bump versions + update lockfiles
2026-02-23 15:30:49 +00:00
Jędrzej Stuczyński 6edbece3ad bugfix: restore 'latest_measurement' field for nym-node /verloc endpoint (#6452) 2026-02-21 19:10:15 +00:00
import this 8529a3c351 [DOCs/operators]: Cleanup (#6474) 2026-02-20 14:43:05 +00:00
import this 453e1cbe70 [DOCs/operators]: Documentation for SOCKS5 probe score (#6473)
* bump up stats and run prebuild

* fix typos

* add socks5 probe calculation

* fix conflicts

* fix wording
2026-02-20 14:19:25 +00:00
import this 94a3599b4d [DOCs]: Fix missing diagnostic tool in developers menu (#6470)
* bump up stats and run prebuild

* fix typos
2026-02-19 15:08:04 +00:00
import this a6bc54461a [DOCs]: Diagnostic tool (#6467)
* create diagnostic-tool page

* add to menu

* add to list of tools

* syntax fix

* syntax fix

* syntax fix

* syntax fix

* rm old
2026-02-18 16:57:55 +00:00
Tommy Verrall 4f0c40dab7 Merge pull request #6464 from nymtech/otel-minimal-v2
Otel minimal v2
2026-02-18 14:23:35 +01:00
Tommy Verrall 3eff6e5e3b fix testthroughput 2026-02-18 11:06:42 +01:00
Tommy Verrall a519f4ccb8 pr feedback
- Moved OTel CLI options into a separate OtelArgs
- Otel is built behind the feature flag otel
- Store timing is in microseconds
- Restore comments to existing files
2026-02-18 10:48:54 +01:00
Tommy Verrall a3ba3bfc5a remove non OTEL work here 2026-02-17 10:17:22 +01:00
Tommy Verrall 988df7cff7 sampling to avoid costs
- add otel timeouts
2026-02-17 09:10:52 +01:00
Tommy Verrall 260f8e9714 revert docker/localnet to develop; localnet work to follow in separate PR 2026-02-17 08:37:49 +01:00
Tommy Verrall d28d0ac39e fix replay batch drop, harden error handling and scripts 2026-02-16 19:42:24 +01:00
Tommy Verrall dce4d6b34b otel: refactor key selection, add environment label, fix clippy 2026-02-16 19:13:11 +01:00
Tommy Verrall bc47e9a1b2 otel: explicit TLS config for https endpoints 2026-02-16 18:11:28 +01:00
Tommy Verrall 3b693741b2 Merge branch 'develop' of https://github.com/nymtech/nym into otel-minimal-v2 2026-02-16 16:41:16 +01:00
Tommy Verrall cb277fe487 otel: support signoz cloud ingestion key and TLS 2026-02-16 16:11:31 +01:00
Tommy Verrall 8bb29f4d07 localnet: add loadtest script and signoz docs 2026-02-16 15:44:55 +01:00
Tommy Verrall e753f24ed1 localnet: fix runtime and gateway flags 2026-02-16 15:21:45 +01:00
Tommy Verrall c7cd962627 localnet: multi-stage dockerfile 2026-02-16 14:45:05 +01:00
Tommy Verrall 00467e4440 fix upstream build: update lockfile and stabilise nym-lp 2026-02-16 14:11:40 +01:00
Tommy Verrall f3d1000472 Add gitignore 2026-02-16 13:57:04 +01:00
Tommy Verrall 597aae1a20 localnet: wire otel 2026-02-16 13:54:15 +01:00
Tommy Verrall 40a3cd28b7 otel: add tracing 2026-02-16 13:46:17 +01:00
benedettadavico a4950485d1 bump versions 2026-02-13 09:04:15 +01:00
111 changed files with 6645 additions and 4464 deletions
+1
View File
@@ -3,4 +3,5 @@
.gitignore
**/node_modules
**/target
target-otel
dist
+4
View File
@@ -6,6 +6,8 @@ on:
jobs:
build:
runs-on: arc-ubuntu-22.04
env:
NEXT_PUBLIC_SITE_URL: https://nymtech.net/docs
defaults:
run:
working-directory: documentation/docs
@@ -41,6 +43,8 @@ jobs:
run: pnpm i
- name: Build project
run: pnpm run build
- name: Generate sitemap
run: npx next-sitemap
- name: Move files to /dist/
run: ../scripts/move-to-dist.sh
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v6
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.52.2
uses: mikefarah/yq@v4.52.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v6
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.52.2
uses: mikefarah/yq@v4.52.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -51,25 +51,3 @@ jobs:
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/wallet-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Matrix - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
env:
NYM_NOTIFICATION_KIND: nym-wallet
NYM_PROJECT_NAME: "nym-wallet"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "wallet-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
IS_SUCCESS: "${{ job.status == 'success' }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+2 -37
View File
@@ -10,8 +10,8 @@ jobs:
strategy:
fail-fast: false
matrix:
rust: [stable, beta]
os: [ubuntu-22.04, windows-latest, macos-latest]
rust: [ stable, beta ]
os: [ ubuntu-22.04, windows-latest, macos-latest ]
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
@@ -93,38 +93,3 @@ jobs:
with:
command: clippy
args: --workspace --all-targets -- -D warnings
notification:
needs: build
runs-on: custom-linux
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v3
- name: Check out repository code
uses: actions/checkout@v6
- name: install npm
uses: actions/setup-node@v4
if: env.WORKFLOW_CONCLUSION == 'failure'
with:
node-version: 20
- name: Matrix - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Nym nightly build"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+1 -36
View File
@@ -10,7 +10,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, macos-latest, windows-latest]
os: [ ubuntu-22.04, macos-latest, windows-latest ]
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
@@ -55,38 +55,3 @@ jobs:
with:
command: clippy
args: ${{ env.MANIFEST_PATH }} --workspace --all-targets -- -D warnings
notification:
needs: build
runs-on: custom-linux
steps:
- name: Collect jobs status
uses: technote-space/workflow-conclusion-action@v3
- name: Check out repository code
uses: actions/checkout@v6
- name: install npm
uses: actions/setup-node@v4
if: env.WORKFLOW_CONCLUSION == 'failure'
with:
node-version: 20
- name: Matrix - Node Install
if: env.WORKFLOW_CONCLUSION == 'failure'
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
if: env.WORKFLOW_CONCLUSION == 'failure'
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "nym-wallet-nightly-build"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
@@ -24,34 +24,3 @@ jobs:
with:
name: report
path: .github/workflows/support-files/notifications/deny.message
notification:
needs: cargo-deny
runs-on: custom-linux
steps:
- name: Check out repository code
uses: actions/checkout@v6
- name: Download report from previous job
uses: actions/download-artifact@v7
with:
name: report
path: .github/workflows/support-files/notifications
- name: install npm
uses: actions/setup-node@v4
with:
node-version: 20
- name: Matrix - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Matrix - Send Notification
env:
NYM_NOTIFICATION_KIND: security
NYM_PROJECT_NAME: "Daily security report"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_AUDIT }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.52.2
uses: mikefarah/yq@v4.52.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-credential-proxy/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.52.2
uses: mikefarah/yq@v4.52.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.52.2
uses: mikefarah/yq@v4.52.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-network-monitor/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.52.2
uses: mikefarah/yq@v4.52.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/nym-api/Cargo.toml
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.52.2
uses: mikefarah/yq@v4.52.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.52.2
uses: mikefarah/yq@v4.52.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.52.2
uses: mikefarah/yq@v4.52.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
@@ -26,7 +26,7 @@ jobs:
git config --global user.name "Lawrence Stalder"
- name: Get version from cargo.toml
uses: mikefarah/yq@v4.52.2
uses: mikefarah/yq@v4.52.4
id: get_version
with:
cmd: yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml
+7 -35
View File
@@ -4,51 +4,23 @@ This is a collection of scripts and files to support GitHub Actions.
## Sending Notifications
These scripts send CI notifications to Matrix by creating messages from templates and env vars passed from GitHub Actions.
### Adding notifications to a GitHub Action
```
jobs:
build:
...
- name: Notifications - Node Install
run: npm install
working-directory: .github/workflows/support-files/notifications
- name: Notifications - Send
env:
NYM_NOTIFICATION_KIND: "my-component"
GIT_BRANCH: "${GITHUB_REF##*/}"
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM }}"
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
IS_SUCCESS: "${{ job.status == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
```
Notifications are run by adding the snippet above to a GitHub Action, and:
1. Installing node packages needed at run time
2. Set the env vars as required:
- `NYM_NOTIFICATION_KIND` matches the directory in `.github/workflows/support-files/${NYM_NOTIFICATION_KIND}` to provide the templates and extra scripting in `index.js`
- Matrix credentials, room and other env vars for the status of the build and repo
3. Replacing the default entry point shell script on the `keybaseio/client:stable-node` docker image to run `.github/workflows/support-files/notifications/entry_point.sh`
These scripts send CI notifications to Matrix by creating messages from templates and env vars passed from GitHub
Actions.
### Running locally
You will need:
- Node 16 LTS
- npm
Copy `.github/workflows/support-files/.env.example` to `.github/workflows/support-files/.env` and valid Matrix credentials.
Copy `.github/workflows/support-files/.env.example` to `.github/workflows/support-files/.env` and valid Matrix
credentials.
Then run `npm install` to get dependencies.
Start development mode for the notification type you want either by passing the value as an env var called `NYM_NOTIFICATION_KIND` or set the `.env` file values correctly.
Start development mode for the notification type you want either by passing the value as an env var called
`NYM_NOTIFICATION_KIND` or set the `.env` file values correctly.
```bash
cd .github/workflows/support-files
@@ -1,10 +0,0 @@
#!/usr/bin/env bash
# pass exit codes out to GitHub Actions
set -euxo pipefail
# change to the directory that contains this script
cd "${0%/*}"
# run the node script
node send_message.js
@@ -1,126 +0,0 @@
require('dotenv').config();
const { sendMatrixMessage } = require('./send_message_to_matrix');
let context = {
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly', 'nym-connect','security','ci-docs','cd-docs','ci-dev','cd-dev'],
};
/**
* Validate that all required env and context vars are available
*/
function validateContext() {
if (!context.env.NYM_NOTIFICATION_KIND) {
throw new Error(
'Please set env var NYM_NOTIFICATION_KIND with the project kind that matches a directory in ".github/workflows/support-files"',
);
}
if (!context.kinds.includes(context.env.NYM_NOTIFICATION_KIND)) {
throw new Error(`Env var NYM_NOTIFICATION_KIND is not in ${context.kinds}`);
}
if (!context.env.NYM_PROJECT_NAME) {
throw new Error(
'Please set env var NYM_PROJECT_NAME with the project name for displaying in notification messages',
);
}
if (context.env.MATRIX_ROOM) {
if (!context.env.MATRIX_SERVER) {
throw new Error(
'Matrix server is not defined. Please set env var MATRIX_SERVER',
);
}
if (!context.env.MATRIX_USER_ID) {
throw new Error(
'Matrix user id is not defined. Please set env var MATRIX_USER_ID',
);
}
if (!context.env.MATRIX_TOKEN) {
throw new Error(
'Matrix token is not defined. Please set env var MATRIX_TOKEN',
);
}
if (!context.env.MATRIX_DEVICE_ID) {
throw new Error(
'Matrix device id is not defined. Please set env var MATRIX_DEVICE_ID',
);
}
}
}
/**
* Creates a context that will be available in the templates for rendering notifications
*/
function createTemplateContext() {
const options = { dateStyle: 'full', timeStyle: 'long' };
context.timestamp = new Date().toLocaleString(undefined, options);
// add environment to template context and validate
context.env = process.env;
try {
validateContext();
} catch (e) {
if(process.env.SHOW_DEBUG) {
// recursively print the context for easy debugging and rethrow the error
console.dir({ context }, { depth: null });
}
throw e;
}
context.kind = context.env.NYM_NOTIFICATION_KIND;
if (!context.env.GIT_BRANCH_NAME) {
context.env.GIT_BRANCH_NAME = context.env.GITHUB_REF.split('/')
.slice(2)
.join('/');
}
context.status = process.env.IS_SUCCESS === 'true' ? 'success' : 'failure';
}
/**
* Uses the `kind` set in the context to process the context and generate a notification message
* @returns {Promise<string>} A string notification message body
*/
async function processKindScript() {
const script = require(`../${context.kind}`);
if (!script.addToContextAndValidate) {
throw new Error(
`"./${context.kind}/index.js" does not export a method called "async addToContextAndValidate(context)"`,
);
}
if (!script.getMessageBody) {
throw new Error(
`"./${context.kind}/index.js" does not export a method called "async getMessageBody(context)"`,
);
}
// call the script to modify and validate the context
await script.addToContextAndValidate(context);
// let the script create a message body and return the result as a string for sending
return await script.getMessageBody(context);
}
/**
* The main function, as async so that await syntax is available
*/
async function main() {
createTemplateContext();
console.log(`Sending notification for kind "${context.kind}"...`);
const messageBody = await processKindScript();
if(process.env.SHOW_DEBUG) {
console.log('-----------------------------------------');
console.log(messageBody);
console.log('-----------------------------------------');
}
if(context.env.MATRIX_ROOM) {
await sendMatrixMessage(context, messageBody, context.env.MATRIX_ROOM)
}
if(context.env.MATRIX_ROOM_OF_SHAME && context.env.IS_SUCCESS !== 'true') {
// when a job fails
await sendMatrixMessage(context, messageBody, context.env.MATRIX_ROOM_OF_SHAME)
}
}
// call main function and let NodeJS handle the promise
main();
@@ -1,67 +0,0 @@
const sdk = require('matrix-js-sdk');
global.Olm = require('olm');
const { LocalStorage } = require('node-localstorage');
const localStorage = new LocalStorage('./scratch');
const {
LocalStorageCryptoStore,
} = require('matrix-js-sdk/lib/crypto/store/localStorage-crypto-store');
var showdown = require('showdown');
// hide all matrix client output
console.error = (error) => console.log('❌ error: ', error);
process.stderr.write = () => {};
process.stdout.write = () => {};
function createClient(context, room, message) {
const server = context.env.MATRIX_SERVER;
const token = context.env.MATRIX_TOKEN;
const deviceId = context.env.MATRIX_DEVICE_ID;
const userId = context.env.MATRIX_USER_ID;
const client = sdk.createClient({
baseUrl: server,
accessToken: token,
userId,
deviceId,
sessionStore: new sdk.WebStorageSessionStore(localStorage),
cryptoStore: new LocalStorageCryptoStore(localStorage),
});
client.on('sync', async function(state, prevState, res) {
if (state !== 'PREPARED') return;
client.setGlobalErrorOnUnknownDevices(false);
try {
await client.joinRoom(room);
await client.sendEvent(
room,
'm.room.message',
{
msgtype: 'm.text',
format: 'org.matrix.custom.html',
body: message,
formatted_body: message,
},
'',
);
} catch (error) {
console.error('Job failed: ' + error.message);
}
client.stopClient();
process.exit(0);
});
return client;
}
async function sendMatrixMessage(contextArg, messageAsMarkdown, roomId) {
const converter = new showdown.Converter();
const messageAsHtml = converter.makeHtml(messageAsMarkdown);
const client = createClient(contextArg, roomId, messageAsHtml);
await client.initCrypto();
await client.startClient({ initialSyncLimit: 1 });
}
module.exports = {
sendMatrixMessage,
};
+1
View File
@@ -76,3 +76,4 @@ CLAUDE.md
.claude/settings.json
/notes
/target-otel
+76
View File
@@ -4,6 +4,82 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2026.4-quark] (2026-02-24)
- Enhance CI workflow with feature inputs ([#6462])
- Chore/revert 6433 ([#6445])
- Lp/stateless handshake ([#6437])
- build(deps-dev): bump webpack from 5.98.0 to 5.105.0 in /wasm/client/internal-dev ([#6435])
- build(deps-dev): bump webpack from 5.102.1 to 5.104.1 ([#6432])
- build(deps-dev): bump webpack from 5.98.0 to 5.105.0 in /wasm/mix-fetch/internal-dev ([#6431])
- build(deps-dev): bump webpack from 5.94.0 to 5.104.1 in /nym-credential-proxy/vpn-api-lib-wasm/internal-dev ([#6430])
- build(deps-dev): bump webpack from 5.77.0 to 5.104.1 in /wasm/zknym-lib/internal-dev ([#6429])
- build(deps-dev): bump webpack from 5.76.0 to 5.105.0 in /clients/native/examples/js-examples/websocket ([#6428])
- HTTP & DNS Improvements ([#6423])
- Endpoint for exit GW IPs ([#6418])
- build(deps): bump bytes from 1.6.0 to 1.11.1 in /contracts ([#6416])
- build(deps): bump @isaacs/brace-expansion from 5.0.0 to 5.0.1 ([#6415])
- build(deps): bump bytes from 1.11.0 to 1.11.1 ([#6414])
- build(deps): bump mikefarah/yq from 4.50.1 to 4.52.2 ([#6407])
- build(deps-dev): bump eslint from 8.57.1 to 9.26.0 ([#6405])
- Update reqwest to v0.13.1 ([#6401])
- build(deps): bump next from 15.5.9 to 16.1.5 in /documentation/docs ([#6387])
- build(deps): bump next from 15.4.10 to 16.1.5 in /nym-node-status-api/nym-node-status-ui ([#6385])
- build(deps): bump lodash from 4.17.21 to 4.17.23 ([#6369])
- build(deps): bump lodash-es from 4.17.21 to 4.17.23 ([#6360])
- build(deps-dev): bump lodash from 4.17.21 to 4.17.23 in /sdk/typescript/codegen/contract-clients ([#6359])
- build(deps): bump lodash from 4.17.21 to 4.17.23 in /sdk/typescript/packages/nodejs-client ([#6354])
- build(deps): bump lodash from 4.17.21 to 4.17.23 in /documentation/docs ([#6353])
- build(deps): bump lodash from 4.17.21 to 4.17.23 in /clients/native/examples/js-examples/websocket ([#6351])
- build(deps): bump lodash-es from 4.17.21 to 4.17.23 in /documentation/docs ([#6350])
- build(deps): bump diff from 5.2.0 to 5.2.2 in /documentation/docs ([#6345])
- Max/crates publishing tweaks ([#6343])
- build(deps): bump h3 from 1.15.4 to 1.15.5 ([#6339])
- build(deps): bump h3 from 1.15.4 to 1.15.5 in /documentation/docs ([#6332])
- build(deps): bump undici from 6.21.3 to 6.23.0 in /documentation/docs ([#6325])
- build(deps): bump rsa from 0.9.8 to 0.9.10 ([#6311])
- build(deps): bump qs and express in /wasm/mix-fetch/internal-dev ([#6308])
- build(deps): bump qs and express in /clients/native/examples/js-examples/websocket ([#6307])
- feat: introduce on-disk cache persistance for major nym-api caches ([#6302])
- Fix migrations in the Data Observatory ([#6271])
[#6462]: https://github.com/nymtech/nym/pull/6462
[#6445]: https://github.com/nymtech/nym/pull/6445
[#6437]: https://github.com/nymtech/nym/pull/6437
[#6435]: https://github.com/nymtech/nym/pull/6435
[#6432]: https://github.com/nymtech/nym/pull/6432
[#6431]: https://github.com/nymtech/nym/pull/6431
[#6430]: https://github.com/nymtech/nym/pull/6430
[#6429]: https://github.com/nymtech/nym/pull/6429
[#6428]: https://github.com/nymtech/nym/pull/6428
[#6423]: https://github.com/nymtech/nym/pull/6423
[#6418]: https://github.com/nymtech/nym/pull/6418
[#6416]: https://github.com/nymtech/nym/pull/6416
[#6415]: https://github.com/nymtech/nym/pull/6415
[#6414]: https://github.com/nymtech/nym/pull/6414
[#6407]: https://github.com/nymtech/nym/pull/6407
[#6405]: https://github.com/nymtech/nym/pull/6405
[#6401]: https://github.com/nymtech/nym/pull/6401
[#6387]: https://github.com/nymtech/nym/pull/6387
[#6385]: https://github.com/nymtech/nym/pull/6385
[#6369]: https://github.com/nymtech/nym/pull/6369
[#6360]: https://github.com/nymtech/nym/pull/6360
[#6359]: https://github.com/nymtech/nym/pull/6359
[#6354]: https://github.com/nymtech/nym/pull/6354
[#6353]: https://github.com/nymtech/nym/pull/6353
[#6351]: https://github.com/nymtech/nym/pull/6351
[#6350]: https://github.com/nymtech/nym/pull/6350
[#6345]: https://github.com/nymtech/nym/pull/6345
[#6343]: https://github.com/nymtech/nym/pull/6343
[#6339]: https://github.com/nymtech/nym/pull/6339
[#6332]: https://github.com/nymtech/nym/pull/6332
[#6325]: https://github.com/nymtech/nym/pull/6325
[#6311]: https://github.com/nymtech/nym/pull/6311
[#6308]: https://github.com/nymtech/nym/pull/6308
[#6307]: https://github.com/nymtech/nym/pull/6307
[#6302]: https://github.com/nymtech/nym/pull/6302
[#6271]: https://github.com/nymtech/nym/pull/6271
## [2026.3-parmigiano] (2026-02-10)
- chore: disable LP on parmigiano branch ([#6422])
Generated
+1636 -1635
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -309,8 +309,10 @@ nix = "0.30.1"
notify = "5.1.0"
num_enum = "0.7.5"
once_cell = "1.21.3"
opentelemetry = "0.19.0"
opentelemetry-jaeger = "0.18.0"
opentelemetry = "0.31.0"
opentelemetry_sdk = "0.31.0"
opentelemetry-otlp = "0.31.0"
tonic = "0.14.4"
parking_lot = "0.12.3"
pem = "0.8"
petgraph = "0.6.5"
@@ -368,9 +370,8 @@ tower = "0.5.2"
tower-http = "0.6.6"
tracing = "0.1.41"
tracing-log = "0.2"
tracing-opentelemetry = "0.19.0"
tracing-opentelemetry = "0.32.1"
tracing-subscriber = "0.3.20"
tracing-tree = "0.2.2"
tracing-indicatif = "0.3.9"
tracing-test = "0.2.5"
ts-rs = "10.1.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.70"
version = "1.1.71"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
@@ -513,9 +513,9 @@
}
},
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -3067,9 +3067,9 @@
}
},
"node_modules/qs": {
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"dev": true,
"dependencies": {
"side-channel": "^1.1.0"
@@ -4989,9 +4989,9 @@
}
},
"ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"requires": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -6870,9 +6870,9 @@
}
},
"qs": {
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"dev": true,
"requires": {
"side-channel": "^1.1.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.70"
version = "1.1.71"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+13 -9
View File
@@ -19,12 +19,15 @@ serde_json = { workspace = true, optional = true }
## tracing
tracing-subscriber = { workspace = true, features = ["env-filter"], optional = true }
tracing-tree = { workspace = true, optional = true }
tracing = { workspace = true, optional = true }
opentelemetry-jaeger = { workspace = true, features = ["rt-tokio", "collector_client", "isahc_collector_client"], optional = true }
tracing-opentelemetry = { workspace = true, optional = true }
utoipa = { workspace = true, optional = true }
opentelemetry = { workspace = true, features = ["rt-tokio"], optional = true }
opentelemetry = { workspace = true, features = ["trace"], optional = true }
## otel-otlp (modern OTLP export to SigNoz/any OTLP collector)
opentelemetry_sdk = { workspace = true, features = ["trace"], optional = true }
opentelemetry-otlp = { workspace = true, features = ["grpc-tonic", "trace", "tls-roots"], optional = true }
tonic = { workspace = true, optional = true }
[build-dependencies]
@@ -35,13 +38,14 @@ default = []
openapi = ["utoipa"]
output_format = ["serde_json", "dep:clap"]
bin_info_schema = ["schemars"]
basic_tracing = ["dep:tracing", "tracing-subscriber"]
tracing = [
basic_tracing = ["dep:tracing", "dep:tracing-subscriber"]
otel-otlp = [
"basic_tracing",
"tracing-tree",
"opentelemetry-jaeger",
"tracing-opentelemetry",
"opentelemetry",
"dep:opentelemetry",
"dep:opentelemetry_sdk",
"dep:opentelemetry-otlp",
"dep:tracing-opentelemetry",
"dep:tonic",
]
clap = ["dep:clap", "dep:clap_complete", "dep:clap_complete_fig"]
models = []
+97 -38
View File
@@ -4,16 +4,9 @@
use serde::{Deserialize, Serialize};
use std::io::IsTerminal;
#[cfg(feature = "tracing")]
pub use opentelemetry;
#[cfg(feature = "tracing")]
pub use opentelemetry_jaeger;
#[cfg(feature = "tracing")]
pub use tracing_opentelemetry;
// Re-export tracing_subscriber for consumers that need to compose layers
#[cfg(feature = "basic_tracing")]
pub use tracing_subscriber;
#[cfg(feature = "tracing")]
pub use tracing_tree;
#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
@@ -69,40 +62,106 @@ pub fn setup_tracing_logger() {
build_tracing_logger().init()
}
// TODO: This has to be a macro, running it as a function does not work for the file_appender for some reason
#[cfg(feature = "tracing")]
#[macro_export]
macro_rules! setup_tracing {
($service_name: expr) => {
use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt;
use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt;
/// Initialize an OpenTelemetry tracing layer that exports spans via OTLP/gRPC.
///
/// This produces a layer compatible with `tracing_subscriber::registry()` that
/// sends traces to any OTLP-compatible collector (SigNoz, Grafana Tempo, etc).
///
/// Returns both the tracing layer and the [`SdkTracerProvider`] so the caller
/// can invoke [`SdkTracerProvider::shutdown`] for graceful flush on exit.
///
/// # Arguments
/// * `service_name` - The service name reported to the collector (e.g. "nym-node")
/// * `endpoint` - The OTLP/gRPC collector endpoint (e.g. "http://localhost:4317"
/// or "https://ingest.eu.signoz.cloud:443" for SigNoz Cloud)
/// * `ingestion_key` - Optional SigNoz Cloud ingestion key. When provided, it is
/// sent as the `signoz-ingestion-key` gRPC metadata header on every export.
/// * `environment` - Deployment environment label (e.g. "sandbox", "mainnet", "canary").
/// Attached as the `deployment.environment` OTel resource attribute.
/// * `sample_ratio` - Trace sampling ratio in 0.0..=1.0 (e.g. 0.1 = 10% of traces).
/// Used to limit cost when exporting from many nodes; clamped to [0.0, 1.0].
/// * `export_timeout_secs` - Timeout in seconds for each OTLP export batch. Prevents
/// unbounded blocking if the collector is slow or unreachable.
#[cfg(feature = "otel-otlp")]
pub fn init_otel_layer<S>(
service_name: &str,
endpoint: &str,
ingestion_key: Option<&str>,
environment: &str,
sample_ratio: f64,
export_timeout_secs: u64,
) -> Result<
(
tracing_opentelemetry::OpenTelemetryLayer<S, opentelemetry_sdk::trace::SdkTracer>,
opentelemetry_sdk::trace::SdkTracerProvider,
),
Box<dyn std::error::Error + Send + Sync>,
>
where
S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
{
use opentelemetry::trace::TracerProvider as _;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_otlp::WithTonicConfig;
use opentelemetry_sdk::trace::Sampler;
use std::time::Duration;
let registry = nym_bin_common::logging::tracing_subscriber::Registry::default()
.with(nym_bin_common::logging::tracing_subscriber::EnvFilter::from_default_env())
.with(
nym_bin_common::logging::tracing_tree::HierarchicalLayer::new(4)
.with_targets(true)
.with_bracketed_fields(true),
);
// Validate endpoint URI early to fail with a clear message
if !endpoint.starts_with("http://") && !endpoint.starts_with("https://") {
return Err(format!(
"invalid OTLP endpoint URI: {endpoint} (must start with http:// or https://)"
)
.into());
}
let tracer = nym_bin_common::logging::opentelemetry_jaeger::new_collector_pipeline()
.with_endpoint("http://44.199.230.10:14268/api/traces")
.with_service_name($service_name)
.with_isahc()
.with_trace_config(
nym_bin_common::logging::opentelemetry::sdk::trace::config().with_sampler(
nym_bin_common::logging::opentelemetry::sdk::trace::Sampler::TraceIdRatioBased(
0.1,
),
),
)
.install_batch(nym_bin_common::logging::opentelemetry::runtime::Tokio)
.expect("Could not init tracer");
let sample_ratio_clamped = sample_ratio.clamp(0.0, 1.0);
let telemetry = nym_bin_common::logging::tracing_opentelemetry::layer().with_tracer(tracer);
let mut builder = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint)
.with_timeout(Duration::from_secs(export_timeout_secs));
registry.with(telemetry).init();
};
// Explicitly configure TLS when the endpoint uses HTTPS
if endpoint.starts_with("https://") {
builder =
builder.with_tls_config(tonic::transport::ClientTlsConfig::new().with_native_roots());
}
if let Some(key) = ingestion_key {
let mut metadata = tonic::metadata::MetadataMap::new();
metadata.insert(
"signoz-ingestion-key",
key.parse()
.map_err(|_| "invalid ingestion key format (value redacted)")?,
);
builder = builder.with_metadata(metadata);
}
let exporter = builder
.build()
.map_err(|e| format!("failed to build OTLP exporter for endpoint {endpoint}: {e}"))?;
let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
.with_sampler(Sampler::TraceIdRatioBased(sample_ratio_clamped))
.with_batch_exporter(exporter)
.with_resource(
opentelemetry_sdk::Resource::builder()
.with_service_name(service_name.to_owned())
.with_attribute(opentelemetry::KeyValue::new(
"deployment.environment",
environment.to_owned(),
))
.build(),
)
.build();
opentelemetry::global::set_tracer_provider(tracer_provider.clone());
let tracer = tracer_provider.tracer(service_name.to_owned());
Ok((
tracing_opentelemetry::layer().with_tracer(tracer),
tracer_provider,
))
}
pub fn banner(crate_name: &str, crate_version: &str) -> String {
+80 -26
View File
@@ -128,54 +128,95 @@ impl ManagedConnection {
async fn run(self) {
let address = self.address;
let reconnection_attempt = self.current_reconnection.load(Ordering::Acquire);
let connect_start = tokio::time::Instant::now();
let connection_fut = TcpStream::connect(address);
let conn = match tokio::time::timeout(self.connection_timeout, connection_fut).await {
Ok(stream_res) => match stream_res {
Ok(stream) => {
debug!("Managed to establish connection to {}", self.address);
let connect_ms = connect_start.elapsed().as_millis() as u64;
debug!(
peer = %address,
connect_ms,
"Managed to establish connection to {}", self.address
);
let noise_start = tokio::time::Instant::now();
let noise_stream =
match upgrade_noise_initiator(stream, &self.noise_config).await {
Ok(noise_stream) => noise_stream,
Err(err) => {
error!("Failed to perform Noise handshake with {address} - {err}");
// we failed to finish the noise handshake - increase reconnection attempt
let noise_handshake_ms = noise_start.elapsed().as_millis() as u64;
warn!(
event = "connection.failed.noise",
peer = %address,
error = %err,
connect_ms,
noise_handshake_ms,
reconnection_attempt,
exit_reason = "noise_error",
"Failed to perform Noise initiator handshake with {address}"
);
self.current_reconnection.fetch_add(1, Ordering::SeqCst);
return;
}
};
// if we managed to connect AND do the noise handshake, reset the reconnection count (whatever it might have been)
let noise_handshake_ms = noise_start.elapsed().as_millis() as u64;
self.current_reconnection.store(0, Ordering::Release);
debug!("Noise initiator handshake completed for {:?}", address);
debug!(
peer = %address,
connect_ms,
noise_handshake_ms,
"Noise initiator handshake completed for {:?}", address
);
Framed::new(noise_stream, NymCodec)
}
Err(err) => {
debug!("failed to establish connection to {address} (err: {err})",);
let connect_ms = connect_start.elapsed().as_millis() as u64;
warn!(
event = "connection.failed.connect",
peer = %address,
error = %err,
connect_ms,
reconnection_attempt,
exit_reason = "connect_error",
"failed to establish connection to {address}"
);
return;
}
},
Err(_) => {
debug!(
let connect_ms = connect_start.elapsed().as_millis() as u64;
warn!(
event = "connection.failed.timeout",
peer = %address,
timeout_ms = self.connection_timeout.as_millis() as u64,
connect_ms,
reconnection_attempt,
exit_reason = "timeout",
"failed to connect to {address} within {:?}",
self.connection_timeout
);
// we failed to connect - increase reconnection attempt
self.current_reconnection.fetch_add(1, Ordering::SeqCst);
return;
}
};
// Take whatever the receiver channel produces and put it on the connection.
// We could have as well used conn.send_all(receiver.map(Ok)), but considering we don't care
// about neither receiver nor the connection, it doesn't matter which one gets consumed
if let Err(err) = self.message_receiver.map(Ok).forward(conn).await {
warn!("Failed to forward packets to {address}: {err}");
warn!(
event = "connection.forward_error",
peer = %address,
error = %err,
exit_reason = "forward_error",
"Failed to forward packets to {address}: {err}"
);
}
debug!(
"connection manager to {address} is finished. Either the connection failed or mixnet client got dropped",
peer = %address,
exit_reason = "sender_dropped",
"connection manager to {address} finished"
);
}
}
@@ -272,16 +313,18 @@ impl SendWithoutResponse for Client {
trace!("Sending packet to {address}");
// TODO: optimisation for the future: rather than constantly using legacy encoding,
// once we're addressing by node_id (and thus have full node info here),
// we could simply infer supported encoding based on their version
// use the mix packet type / flags to pick encoding per packet
let framed_packet =
FramedNymPacket::from_mix_packet(packet, self.config.use_legacy_packet_encoding);
let Some(sender) = self.active_connections.get_mut(&address) else {
// there was never a connection to begin with
debug!("establishing initial connection to {address}");
// it's not a 'big' error, but we did not manage to send the packet, but queue the packet
// for sending for as soon as the connection is created
debug!(
event = "mixclient.try_send",
peer = %address,
result = "not_connected",
"establishing initial connection to {address}"
);
self.make_connection(address, framed_packet);
return Err(io::Error::new(
io::ErrorKind::NotConnected,
@@ -289,15 +332,24 @@ impl SendWithoutResponse for Client {
));
};
let channel_capacity = sender.channel.max_capacity();
let channel_available = sender.channel.capacity();
let channel_used = channel_capacity - channel_available;
let sending_res = sender.channel.try_send(framed_packet);
drop(sender);
sending_res.map_err(|err| {
match err {
TrySendError::Full(_) => {
debug!("Connection to {address} seems to not be able to handle all the traffic - dropping the current packet");
// it's not a 'big' error, but we did not manage to send the packet
// if the queue is full, we can't really do anything but to drop the packet
warn!(
event = "mixclient.try_send",
peer = %address,
result = "full_dropped",
channel_capacity,
channel_used,
"dropping packet: connection buffer to {address} is full ({channel_used}/{channel_capacity})"
);
io::Error::new(
io::ErrorKind::WouldBlock,
"connection queue is full",
@@ -305,11 +357,13 @@ impl SendWithoutResponse for Client {
}
TrySendError::Closed(dropped) => {
debug!(
"Connection to {address} seems to be dead. attempting to re-establish it...",
event = "mixclient.try_send",
peer = %address,
result = "closed_reconnecting",
channel_capacity,
channel_used,
"connection to {address} dead, attempting re-establishment"
);
// it's not a 'big' error, but we did not manage to send the packet, but queue
// it up to send it as soon as the connection is re-established
self.make_connection(address, dropped);
io::Error::new(
io::ErrorKind::ConnectionAborted,
+33
View File
@@ -56,6 +56,39 @@ pnpm run build
## CI/CD
- **Link checking**: Runs on every push to `documentation/docs/` via `.github/workflows/ci-docs-linkcheck.yml`
## SEO & Structured Data
### Frontmatter
Every `.mdx` page supports frontmatter fields that control meta tags, Open Graph, and JSON-LD schema:
```yaml
---
title: "Page Title for Search Engines"
description: "Unique meta description for this page."
schemaType: "TechArticle" # TechArticle (default), HowTo, or FAQPage
section: "Operators" # Operators, Developers, Network, APIs
lastUpdated: "2026-02-11" # Feeds dateModified schema
breadcrumbLabel: "Custom Label" # Optional, overrides URL slug in breadcrumbs
---
```
### Sitemap
```bash
npx next-sitemap
```
Outputs `sitemap.xml` and `robots.txt` to `/public`.
### Environment Variable
Set in production:
```
NEXT_PUBLIC_SITE_URL=https://nymtech.net/docs
```
### Schema Types
| Type | Use When |
|------|----------|
| TechArticle | Reference docs, config guides, overviews (default) |
| HowTo | Step-by-step install/setup guides |
| FAQPage | Question-answer pages |
## Licensing and copyright information
This is a monorepo and components that make up Nym as a system are licensed individually, so for accurate information, please check individual files.
@@ -1,86 +1,273 @@
```tsx copy filename="mixFetchExample.tsx"
import React, { useState } from "react";
```tsx
import React, { useState, useRef, useEffect } from "react";
import CircularProgress from "@mui/material/CircularProgress";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import { mixFetch } from "@nymproject/mix-fetch-full-fat";
import { mixFetch, createMixFetch } from "@nymproject/mix-fetch-full-fat";
import Stack from "@mui/material/Stack";
import Paper from "@mui/material/Paper";
import type { SetupMixFetchOps } from "@nymproject/mix-fetch-full-fat";
const defaultUrl = "https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
const defaultUrl =
"https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
const args = { mode: "unsafe-ignore-cors" };
const mixFetchOptions: SetupMixFetchOps = {
preferredGateway: "2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW", // with WSS
// preferredNetworkRequester:
// "CTDxrcXgrZHWyCWnuCgjpJPghQUcEVz1HkhUr5mGdFnT.3UAww1YWNyVNYNWFQL1LaHYouQtDiXBGK5GiDZgpXkTK@2RFtU5BwxvJJXagAWAEuaPgb5ZVPRoy2542TT93Edw6v",
clientId: "docs-mixfetch-demo", // explicit ID
preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1",
mixFetchOverride: {
requestTimeoutMs: 60_000,
},
forceTls: true, // force WSS
};
// Log entry type for the visible log panel
type LogLevel = "info" | "error" | "send" | "receive";
type LogEntry = { timestamp: string; message: string; level: LogLevel };
const logColors: Record<LogLevel, string> = {
info: "gray",
error: "red",
send: "blue",
receive: "green",
};
const logLabels: Record<LogLevel, string> = {
info: "INFO",
error: "ERROR",
send: "SEND",
receive: "RECV",
};
export const MixFetch = () => {
// MixFetch initialization state
const [status, setStatus] = useState<"idle" | "starting" | "ready" | "error">("idle");
const [errorMsg, setErrorMsg] = useState<string | null>(null);
// Log panel state
const [logs, setLogs] = useState<LogEntry[]>([]);
const logEndRef = useRef<HTMLDivElement>(null);
// Single fetch state
const [url, setUrl] = useState<string>(defaultUrl);
const [html, setHtml] = useState<string>();
const [busy, setBusy] = useState<boolean>(false);
// Concurrent fetch state
const [concurrentResults, setConcurrentResults] = useState<string[]>([]);
const [concurrentBusy, setConcurrentBusy] = useState<boolean>(false);
// Auto-scroll log panel to bottom
useEffect(() => {
logEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [logs]);
// Helper to add a timestamped log entry
const addLog = (message: string, level: LogLevel) => {
const timestamp = new Date().toISOString().substring(11, 23);
setLogs((prev) => [...prev, { timestamp, message, level }]);
};
// Initialize MixFetch explicitly via createMixFetch
const handleStart = async () => {
try {
setStatus("starting");
setErrorMsg(null);
addLog("Starting MixFetch...", "info");
await createMixFetch(mixFetchOptions);
setStatus("ready");
addLog("MixFetch is ready!", "info");
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
setStatus("error");
setErrorMsg(msg);
addLog(`Error: ${msg}`, "error");
}
};
// Single URL fetch — reuses the existing MixFetch singleton
const handleFetch = async () => {
try {
setBusy(true);
setHtml(undefined);
addLog(`Sending request to ${url}...`, "send");
const response = await mixFetch(url, args, mixFetchOptions);
console.log(response);
const resHtml = await response.text();
setHtml(resHtml);
addLog(`Response received (${resHtml.length} bytes)`, "receive");
} catch (err) {
console.log(err);
const msg = err instanceof Error ? err.message : String(err);
addLog(`Fetch error: ${msg}`, "error");
} finally {
setBusy(false);
}
};
// Send 5 concurrent requests to different URLs on the same domain
const handleConcurrentFetch = async () => {
const baseUrl = "https://jsonplaceholder.typicode.com/posts/";
const count = 5;
try {
setConcurrentBusy(true);
setConcurrentResults([]);
addLog(
`Starting ${count} concurrent requests to ${baseUrl}1-${count}...`,
"send",
);
// Fire off all requests concurrently using Promise.all
const requests = Array.from({ length: count }, (_, i) => {
const targetUrl = `${baseUrl}${i + 1}`;
return mixFetch(targetUrl, args, mixFetchOptions)
.then((res) => res.json())
.then((json: { id: number; title: string }) => {
const entry = `[${json.id}] ${json.title}`;
addLog(entry, "receive");
return entry;
});
});
const results = await Promise.all(requests);
setConcurrentResults(results);
addLog(`All ${count} concurrent requests completed!`, "info");
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
addLog(`Concurrent fetch error: ${msg}`, "error");
} finally {
setConcurrentBusy(false);
}
};
const isReady = status === "ready";
const statusText = {
idle: "Not started",
starting: "Starting...",
ready: "Ready",
error: `Error: ${errorMsg}`,
};
const statusColor = {
idle: "gray",
starting: "orange",
ready: "green",
error: "red",
};
return (
<div style={{ marginTop: "1rem" }}>
<Stack direction="row">
<TextField
disabled={busy}
fullWidth
label="URL"
type="text"
variant="outlined"
defaultValue={defaultUrl}
onChange={(e) => setUrl(e.target.value)}
/>
<Button
variant="outlined"
disabled={busy}
sx={{ marginLeft: "1rem" }}
onClick={handleFetch}
>
Fetch
</Button>
</Stack>
{/* Start MixFetch */}
<Paper sx={{ p: 2, mb: 2 }} variant="outlined">
<Stack direction="row" alignItems="center" spacing={2}>
<Button
variant="contained"
disabled={status === "starting" || status === "ready"}
onClick={handleStart}
>
Start MixFetch
</Button>
{status === "starting" && <CircularProgress size={20} />}
<Typography
fontFamily="monospace"
fontSize="small"
sx={{ color: statusColor[status] }}
>
{statusText[status]}
</Typography>
</Stack>
</Paper>
{busy && (
<Box mt={4}>
<CircularProgress />
</Box>
)}
{html && (
<>
<Box mt={4}>
<strong>Response</strong>
{/* Fetch controls — disabled until MixFetch is ready */}
<Box
sx={{
opacity: isReady ? 1 : 0.5,
pointerEvents: isReady ? "auto" : "none",
}}
>
{/* Single fetch */}
<Stack direction="row">
<TextField
disabled={busy}
fullWidth
label="URL"
type="text"
variant="outlined"
defaultValue={defaultUrl}
onChange={(e) => setUrl(e.target.value)}
/>
<Button
variant="outlined"
disabled={busy}
sx={{ marginLeft: "1rem" }}
onClick={handleFetch}
>
Fetch
</Button>
</Stack>
{busy && (
<Box mt={2}>
<CircularProgress />
</Box>
<Paper sx={{ p: 2, mt: 1 }} elevation={4}>
<Typography fontFamily="monospace" fontSize="small">
{html}
</Typography>
)}
{html && (
<>
<Box mt={2}>
<strong>Response</strong>
</Box>
<Paper sx={{ p: 2, mt: 1 }} elevation={4}>
<Typography fontFamily="monospace" fontSize="small">
{html}
</Typography>
</Paper>
</>
)}
{/* Concurrent fetch */}
<Box mt={3}>
<strong>Concurrent Requests</strong>
<Box mt={1}>
<Button
variant="outlined"
disabled={concurrentBusy}
onClick={handleConcurrentFetch}
>
Send 5 Concurrent Requests (posts/1-5)
</Button>
</Box>
</Box>
{concurrentBusy && (
<Box mt={2}>
<CircularProgress />
</Box>
)}
{concurrentResults.length > 0 && (
<Paper sx={{ p: 2, mt: 2 }} elevation={4}>
{concurrentResults.map((result, i) => (
<Typography key={i} fontFamily="monospace" fontSize="small">
{result}
</Typography>
))}
</Paper>
</>
)}
</Box>
{/* Log Panel */}
{logs.length > 0 && (
<Paper
sx={{ p: 2, mt: 3, maxHeight: 200, overflow: "auto" }}
variant="outlined"
>
<strong>Log</strong>
{logs.map((entry, i) => (
<Typography
key={i}
fontFamily="monospace"
fontSize="small"
sx={{ color: logColors[entry.level] }}
>
{entry.timestamp} [{logLabels[entry.level]}] {entry.message}
</Typography>
))}
<div ref={logEndRef} />
</Paper>
)}
</div>
);
+26 -42
View File
@@ -51,8 +51,8 @@ export const LandingPage = () => {
};
return (
<Box margin={"0 auto"}>
<Typography variant="h2" mb={6}>
<Box margin={"0 auto"} textAlign="center">
{/*<Typography variant="h2" mb={6}>
Nym Docs
</Typography>
@@ -62,70 +62,54 @@ export const LandingPage = () => {
using blinded, re-randomizable, decentralized credentials. Our goal is
to allow developers to build new applications, or upgrade existing apps,
with privacy features unavailable in other systems.
</Typography>
<Grid container border={"1px solid #262626"}>
</Typography>*/}
<Grid container border={"1px solid #2E3538"}>
{squares.map((square, index) => (
<Grid
item
key={index}
xs={12}
lg={6}
sm={6}
padding={{ xs: 3, xl: 4 }}
width={"100%"}
sx={{
borderBottom: {
xs: index < 3 ? "1px solid #262626" : "none",
lg: index === 0 || index === 1 ? "1px solid #262626" : "none",
xs: index < 3 ? "1px solid #2E3538" : "none",
sm: index === 0 || index === 1 ? "1px solid #2E3538" : "none",
},
borderRight: {
md: index === 0 || index === 2 ? "1px solid #262626" : "none",
xs: "none",
sm: index === 0 || index === 2 ? "1px solid #2E3538" : "none",
},
}}
>
<Link href={square.href} target="_blank" rel="noopener noreferrer">
<Link href={square.href}>
<Box
display={"flex"}
gap={{ xs: 3, xl: 4 }}
height={"100%"}
flexDirection={{ xs: "column", sm: "row" }}
alignItems={{ xs: "center" }}
flexDirection="column"
alignItems="center"
>
<Typography variant="h5" sx={{ fontWeight: 600 }}>
{square.text}
</Typography>
<Typography
variant="body1"
textAlign="center"
sx={{
color: "#909195",
}}
>
{square.description}
</Typography>
<Image
src={square.icon}
alt={square.text}
width={isDesktop ? 180 : isTablet ? 140 : 180}
height={isDesktop ? 134 : isTablet ? 90 : 134}
/>
<Box
display={"flex"}
flexDirection={"column"}
justifyContent={"space-between"}
alignItems={{ xs: "center", sm: "flex-start" }}
flexGrow={1}
height={"100%"}
>
<Typography variant="h5" sx={{ fontWeight: 600 }}>
{square.text}
</Typography>
<Typography
variant="body1"
textAlign={{ xs: "center", sm: "left" }}
sx={{
color: "#909195",
display: {
lg: "none",
xl: "block",
},
}}
>
{square.description}
</Typography>
<Typography sx={{ color: "#14E76F", fontWeight: 600 }}>
Open
</Typography>
</Box>
</Box>
</Link>
</Grid>
+234 -38
View File
@@ -1,10 +1,10 @@
import React, { useState } from "react";
import React, { useState, useRef, useEffect } from "react";
import CircularProgress from "@mui/material/CircularProgress";
import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField";
import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
import { mixFetch } from "@nymproject/mix-fetch-full-fat";
import { mixFetch, createMixFetch } from "@nymproject/mix-fetch-full-fat";
import Stack from "@mui/material/Stack";
import Paper from "@mui/material/Paper";
import type { SetupMixFetchOps } from "@nymproject/mix-fetch-full-fat";
@@ -12,8 +12,8 @@ import type { SetupMixFetchOps } from "@nymproject/mix-fetch-full-fat";
const defaultUrl =
"https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
const args = { mode: "unsafe-ignore-cors" };
const mixFetchOptions: SetupMixFetchOps = {
clientId: "docs-mixfetch-demo", // explicit ID
preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1",
mixFetchOverride: {
requestTimeoutMs: 60_000,
@@ -21,64 +21,260 @@ const mixFetchOptions: SetupMixFetchOps = {
forceTls: true, // force WSS
};
// Log entry type for the visible log panel
type LogLevel = "info" | "error" | "send" | "receive";
type LogEntry = { timestamp: string; message: string; level: LogLevel };
// Color map for log levels
const logColors: Record<LogLevel, string> = {
info: "gray",
error: "red",
send: "blue",
receive: "green",
};
// Label map for log levels
const logLabels: Record<LogLevel, string> = {
info: "INFO",
error: "ERROR",
send: "SEND",
receive: "RECV",
};
export const MixFetch = () => {
// MixFetch initialization state
const [status, setStatus] = useState<"idle" | "starting" | "ready" | "error">(
"idle"
);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
// Log panel state
const [logs, setLogs] = useState<LogEntry[]>([]);
// Single fetch state
const [url, setUrl] = useState<string>(defaultUrl);
const [html, setHtml] = useState<string>();
const [busy, setBusy] = useState<boolean>(false);
// Concurrent fetch state
const [concurrentResults, setConcurrentResults] = useState<string[]>([]);
const [concurrentBusy, setConcurrentBusy] = useState<boolean>(false);
// Auto-scroll within the log panel when new entries are added (without scrolling the page)
const logContainerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (logContainerRef.current) {
logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight;
}
}, [logs]);
// Helper to add a timestamped log entry
const addLog = (message: string, level: LogLevel) => {
const timestamp = new Date().toISOString().substring(11, 23); // HH:MM:SS.mmm
setLogs((prev) => [...prev, { timestamp, message, level }]);
};
// Initialize MixFetch explicitly via createMixFetch
const handleStart = async () => {
try {
setStatus("starting");
setErrorMsg(null);
addLog("Starting MixFetch...", "info");
await createMixFetch(mixFetchOptions);
setStatus("ready");
addLog("MixFetch is ready!", "info");
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
setStatus("error");
setErrorMsg(msg);
addLog(`Error: ${msg}`, "error");
}
};
// Single URL fetch — mixFetch reuses the existing singleton
const handleFetch = async () => {
try {
setBusy(true);
setHtml(undefined);
addLog(`Sending request to ${url}...`, "send");
const response = await mixFetch(url, args, mixFetchOptions);
console.log(response);
const resHtml = await response.text();
setHtml(resHtml);
addLog(`Response received (${resHtml.length} bytes)`, "receive");
} catch (err) {
console.log(err);
const msg = err instanceof Error ? err.message : String(err);
addLog(`Fetch error: ${msg}`, "error");
} finally {
setBusy(false);
}
};
// Send 5 concurrent requests to different URLs on the same domain
const handleConcurrentFetch = async () => {
const baseUrl = "https://jsonplaceholder.typicode.com/posts/";
const count = 5;
try {
setConcurrentBusy(true);
setConcurrentResults([]);
addLog(
`Starting ${count} concurrent requests to ${baseUrl}1-${count}...`,
"send"
);
// Fire off all requests concurrently using Promise.all
const requests = Array.from({ length: count }, (_, i) => {
const targetUrl = `${baseUrl}${i + 1}`;
return mixFetch(targetUrl, args, mixFetchOptions)
.then((res) => res.json())
.then((json: { id: number; title: string }) => {
const entry = `[${json.id}] ${json.title}`;
addLog(entry, "receive");
return entry;
});
});
const results = await Promise.all(requests);
setConcurrentResults(results);
addLog(`All ${count} concurrent requests completed!`, "info");
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
addLog(`Concurrent fetch error: ${msg}`, "error");
} finally {
setConcurrentBusy(false);
}
};
// Are fetch controls enabled?
const isReady = status === "ready";
// Status text + color for the startup indicator
const statusText: Record<typeof status, string> = {
idle: "Not started",
starting: "Starting...",
ready: "Ready",
error: `Error: ${errorMsg}`,
};
const statusColor: Record<typeof status, string> = {
idle: "gray",
starting: "orange",
ready: "green",
error: "red",
};
return (
<div style={{ marginTop: "1rem" }}>
<Stack direction="row">
<TextField
disabled={busy}
fullWidth
label="URL"
type="text"
variant="outlined"
defaultValue={defaultUrl}
onChange={(e) => setUrl(e.target.value)}
/>
<Button
variant="outlined"
disabled={busy}
sx={{ marginLeft: "1rem" }}
onClick={handleFetch}
>
Fetch
</Button>
</Stack>
{/* --- Start MixFetch Section --- */}
<Paper sx={{ p: 2, mb: 2 }} variant="outlined">
<Stack direction="row" alignItems="center" spacing={2}>
<Button
variant="contained"
disabled={status === "starting" || status === "ready"}
onClick={handleStart}
>
Start MixFetch
</Button>
{status === "starting" && <CircularProgress size={20} />}
<Typography
fontFamily="monospace"
fontSize="small"
sx={{ color: statusColor[status] }}
>
{statusText[status]}
</Typography>
</Stack>
</Paper>
{busy && (
<Box mt={4}>
<CircularProgress />
</Box>
)}
{html && (
<>
<Box mt={4}>
<strong>Response</strong>
{/* --- Fetch Controls (disabled until ready) --- */}
<Box
sx={{
opacity: isReady ? 1 : 0.5,
pointerEvents: isReady ? "auto" : "none",
}}
>
{/* Single fetch */}
<Stack direction="row">
<TextField
disabled={busy}
fullWidth
label="URL"
type="text"
variant="outlined"
defaultValue={defaultUrl}
onChange={(e) => setUrl(e.target.value)}
/>
<Button
variant="outlined"
disabled={busy}
sx={{ marginLeft: "1rem" }}
onClick={handleFetch}
>
Fetch
</Button>
</Stack>
{busy && (
<Box mt={2}>
<CircularProgress />
</Box>
<Paper sx={{ p: 2, mt: 1 }} elevation={4}>
<Typography fontFamily="monospace" fontSize="small">
{html}
</Typography>
)}
{html && (
<>
<Box mt={2}>
<strong>Response</strong>
</Box>
<Paper sx={{ p: 2, mt: 1 }} elevation={4}>
<Typography fontFamily="monospace" fontSize="small">
{html}
</Typography>
</Paper>
</>
)}
{/* Concurrent fetch demo */}
<Box mt={3}>
<strong>Concurrent Requests</strong>
<Box mt={1}>
<Button
variant="outlined"
disabled={concurrentBusy}
onClick={handleConcurrentFetch}
>
Send 5 Concurrent Requests (posts/1-5)
</Button>
</Box>
</Box>
{concurrentBusy && (
<Box mt={2}>
<CircularProgress />
</Box>
)}
{concurrentResults.length > 0 && (
<Paper sx={{ p: 2, mt: 2 }} elevation={4}>
{concurrentResults.map((result, i) => (
<Typography key={i} fontFamily="monospace" fontSize="small">
{result}
</Typography>
))}
</Paper>
</>
)}
</Box>
{/* --- Log Panel --- */}
{logs.length > 0 && (
<Paper
ref={logContainerRef}
sx={{ p: 2, mt: 3, maxHeight: 200, overflow: "auto" }}
variant="outlined"
>
<strong>Log</strong>
{logs.map((entry, i) => (
<Typography
key={i}
fontFamily="monospace"
fontSize="small"
sx={{ color: logColors[entry.level] }}
>
{entry.timestamp} [{logLabels[entry.level]}] {entry.message}
</Typography>
))}
</Paper>
)}
</div>
);
@@ -1 +1 @@
Wednesday, February 11th 2026, 11:35:05 UTC
Tuesday, February 24th 2026, 10:08:37 UTC
@@ -11,7 +11,7 @@ positional arguments:
version_count (v, version)
Sum of nodes in given version(s)
options:
optional arguments:
-h, --help show this help message and exit
-V, --version show program's version number and exit
```
@@ -6,7 +6,7 @@ usage: Nym-node API check query_stats [-h] [--no_routing_history]
positional arguments:
id supply nym-node identity key
options:
optional arguments:
-h, --help show this help message and exit
--no_routing_history Display node stats without routing history
--no_verloc_metrics Display node stats without verloc metrics
+28
View File
@@ -0,0 +1,28 @@
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: 'https://nymtech.net/docs',
generateRobotsTxt: true,
outDir: './public',
exclude: ['/api/*', '/docs/_*', '/404'],
robotsTxtOptions: {
policies: [
{ userAgent: '*', allow: '/' },
{ userAgent: '*', disallow: ['/api/', '/_next/'] },
],
additionalSitemaps: [
'https://nymtech.net/docs/sitemap-docs.xml',
],
},
transform: async (config, path) => ({
loc: path,
changefreq: path.includes('/changelog')
? 'weekly'
: path.includes('/docs/operators') || path.includes('/docs/developers')
? 'monthly'
: 'yearly',
priority: path === '/docs' ? 1.0
: path.includes('/operators/nodes') || path.includes('/developers') ? 0.8
: 0.6,
lastmod: new Date().toISOString(),
}),
}
+2 -1
View File
@@ -38,7 +38,7 @@
"@nextui-org/accordion": "^2.0.40",
"@nextui-org/react": "^2.4.8",
"@nymproject/contract-clients": ">=1.2.4-rc.2 || ^1",
"@nymproject/mix-fetch-full-fat": ">=1.5.1-rc.0 || ^1.4.1",
"@nymproject/mix-fetch-full-fat": "^1.4.2",
"@nymproject/sdk-full-fat": ">=1.5.1-rc.0 || ^1.4.1",
"@redocly/cli": "^1.25.15",
"@types/mdx": "^2.0.13",
@@ -61,6 +61,7 @@
"copy-webpack-plugin": "^11.0.0",
"eslint": "8.46.0",
"eslint-config-next": "13.4.13",
"next-sitemap": "4.2.3",
"raw-loader": "^4.0.2",
"typescript": "^5.9.3"
},
+5 -1
View File
@@ -10,7 +10,11 @@ const MyApp: React.FC<AppProps> = ({ Component, pageProps }) => {
palette: {
mode: 'dark',
primary: {
main: '#e67300',
main: '#85E89D',
},
background: {
default: '#242B2D',
paper: '#2A3235',
},
},
}),
@@ -1,3 +1,11 @@
---
title: "Nym API Reference: Network Infrastructure"
description: "Interactive API documentation for Nym network infrastructure. Query node status, network topology, blockchain state & mixnet performance programmatically."
schemaType: "TechArticle"
section: "APIs"
lastUpdated: "2026-02-01"
---
# Introduction
This site contains interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet.
@@ -1,3 +1,11 @@
---
title: "Nyx Blockchain & Nym Smart Contracts"
description: "Developer guide for interacting with the Nyx blockchain via Cosmos SDK. Covers CLI wallet setup, Cosmos Registry, Ledger Live, and RPC node deployment."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-02-11"
---
# Interacting with Nyx Chain and Smart Contracts
There are two options for interacting with the blockchain to send tokens or interact with deployed smart contracts:
@@ -1,3 +1,11 @@
---
title: "Run a Nyx RPC Node for the Nym Network"
description: "Set up and run a dedicated RPC node for the Nyx blockchain. Query network state, serve chain data, and interact with Nym smart contracts programmatically."
schemaType: "HowTo"
section: "Developers"
lastUpdated: "2026-02-01"
---
# RPC Nodes
RPC Nodes (which might otherwise be referred to as 'Lite Nodes' or just 'Full Nodes') differ from Validators in that they hold a copy of the Nyx blockchain, but do **not** participate in consensus / block-production.
@@ -1,2 +1,10 @@
---
title: "Nym Developer Portal: SDKs & Tools"
description: "Developer documentation for building privacy-enhanced applications on the Nym mixnet. Covers Rust SDK, TypeScript SDK, blockchain interaction & CLI tools."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-02-01"
---
# Introduction
Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.
@@ -1,3 +1,11 @@
---
title: "NymVPN CLI: Run NymVPN from the Command Line"
description: "Install and run NymVPN from the terminal on Linux, macOS, and Windows. Requires Rust and Go. Includes mnemonic generation and testnet configuration."
schemaType: "HowTo"
section: "Developers"
lastUpdated: "2026-02-11"
---
import { Callout } from 'nextra/components'
# Nym VPN CLI
@@ -1,3 +1,11 @@
---
title: "Nym Rust SDK: Privacy Apps for the Mixnet"
description: "Rust SDK reference for building privacy applications on the Nym mixnet. Covers TcpProxy, Mixnet module, Client Pool, FFI bindings, and code examples."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-02-01"
---
# Introduction
The Rust SDK allows exposes a few different modules, some more plug and play than others. Each of which handles exposes a Nym Client, which handles finding and using a route for packets through the Mixnet, encryption, and cover traffic, all under the hood.
@@ -1,3 +1,11 @@
---
title: "Nym Rust SDK: Mixnet Messaging Module"
description: "Use the Nym Rust SDK Mixnet module to send messages through the mixnet. Covers builder patterns, custom topologies, SOCKS proxy, and anonymous replies."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-02-01"
---
# Mixnet Module
import { Callout } from 'nextra/components';
@@ -1,3 +1,11 @@
---
title: "Nym TcpProxy: Route TCP via the Mixnet"
description: "Route TCP traffic through the Nym mixnet using the TcpProxy Rust module. Covers architecture, single and multi-connection patterns, and troubleshooting."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-02-11"
---
# TcpProxy Module
import { Callout } from 'nextra/components';
@@ -1,3 +1,11 @@
---
title: "Nym Developer Tools: CLI, Echo & TcpProxy"
description: "Overview of Nym developer tools including nym-cli for blockchain interaction, echo server for traffic testing, and standalone TcpProxy binary downloads."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-02-01"
---
# Tools
There are a few tools available to developers for chain interaction: the `nym-cli` tool, which operates as an easier-to-use wrapper around `nyxd`, to allow operators to script interactions with their infrastructure (and those who prefer CLI tools).
@@ -1,5 +1,6 @@
{
"nym-cli": "Nym-cli",
"diagnostic-tool": "Diagnostic Tool",
"echo-server": "Echo Server",
"standalone-tcpproxy": "TcpProxy Binaries (Standalone)"
}
@@ -0,0 +1,134 @@
import { Steps } from 'nextra/components';
# Diagnostic Tool
The Diagnostic Tool is a standalone binary designed to perform various network tests, including DNS, HTTP, and gateway connectivity tests. This tool helps diagnose connectivity issues and provides insights into network performance.
Its also possible to run it within the daemon with the same CLI interface.
## Download Binary
To get `nym-diagnostic` follow these steps:
<Steps>
###### 1. Download `nym-vpn-core`
- Navigate to [github.com/nymtech/nym-vpn-client/releases](https://github.com/nymtech/nym-vpn-client/releases)
- Find latest `nym-vpn-core-<VERSION>`
- Download version for your system
###### 2. Install or extract and make executable
- If you downloaded `.deb` installer, install it with this command:
```sh
sudo dpkg -i <FILE_NAME>
```
- If you downloaded `.tar.gz`, in terminal you can extract the file with
```sh
tar -xvf <FILE_NAME>
```
- Navigate inside the directory and make executable:
```sh
cd nym-vpn-core-<VERSION>
chmod +x ./*
```
</ Steps>
## CLI Usage
The Diagnostic Tool can be executed from the command line interface (CLI). Below are the usage instructions and options available. Read in the chapter [*Tests Performed*](#tests-performed) about the purpose and outcome of these commands.
### Command Syntax
```sh
./nym-diagnostic [command] [options]
./nym-vpnc diagnostic [command] [options]
```
#### `run` command arguments
The most useful command is `run`, here are the options for that command:
```sh
-h, --help Display help information and exit.
--skip-dns Skip the DNS tests
--skip-http Skip the HTTP tests
--gateway <ID_KEY> Run the gateway connectivity test on the given gateway. Skip those tests if not provided
-v, --verbose Enable verbose output for detailed logging.
```
#### `register` command arguments
Command `register` requires valid credential. Here are the options for that command:
```sh
--gateway <ID_KEY> Register to the given gateway
--storage-path Path to the directory containing the credentials database. If it is not valid registration will be skipped.
--skip-wireguard Skip Wireguard tests
```
### Command Examples
- Run all tests on a gateway:
```sh
./nym-diagnostic run --gateway <ID_KEY>
```
- Run the DNS tests only:
```sh
./nym-diagnostic run --skip-http
```
- Register to a gateway:
```sh
sudo ./nym-diagnostic register --gateway <ID_KEY> --storage-path /var/lib/nym-vpnd/mainnet
# sudo is required to read the database
```
- You can also run DNS and HTTP tests from `nym-vpnc` (installation [here](/developers/nymvpncli)):
```sh
./nym-vpnc diagnostic run
```
## Tests Performed
The Diagnostic Tool runs the following tests:
### 1. DNS Test
- **Purpose**: To check the resolution DNS availability.
- **Process**: We try to resolve all the domain names present in a given nym network environment with different DNS configurations
- **Output**: Displays the resolved IP address and the time taken for the resolution.
### 2. HTTP Test
- **Purpose**: To verify the accessibility of the NymVPN API.
- **Process**: The tool query the `health` endpoint as well as the `nodes/described` endpoint.
- **Output**: Displays the response of the `health` endpoint, the time skew and the number of nodes in the network (sanity check)
### 3. Gateway Test
- **Purpose**: To check the connectivity to a given gateway.
- **Process**: The tool fetches information about the gateway, then establishes a TCP connection, upgrades it to WS and sends a request
- **Output**: Display the gateway reported information, the status of the connections and the WS response.
### 4. Registration Test
- **Purpose:** To check the correctness of the registration process.
- **Process:** The tool tries to build a mixnet client to the provided gateway and then tries to register to the entry authenticator
- **Output:** Display the status of the different steps
- **Caveat:** This test requires a credential to be spent, which is why it is available as a separate command only
### 5. Wireguard Test
- **Purpose:** To check the soundness of a wireguard connection
- **Process:** The tool uses the registration data from the previous step to establish a wireguard connection and ping an IP.
- **Output:** Display the ping RTTs and any error that might have happened
## Reports
Reports are logged in a JSON format and also returned by the commands for a future use
@@ -1,3 +1,11 @@
---
title: "Nym CLI: Mixnet & Blockchain Commands"
description: "Use nym-cli to interact with the Nym mixnet and Nyx blockchain. Manage nodes, delegate stake, and query network state from the command line."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-02-11"
---
# Nym-CLI
This is a CLI tool for interacting with:
@@ -1,3 +1,10 @@
---
title: "Nym TypeScript SDK: Privacy for Web Apps"
description: "TypeScript SDK for integrating web apps with the Nym mixnet. Covers mixFetch, Mixnet Client, Smart Contracts, and Cosmos Kit with live playground examples."
schemaType: "TechArticle"
section: "Developers"
lastUpdated: "2026-02-11"
---
# Introduction
@@ -13,7 +13,7 @@ Sounds great, are there any catches? Well, there are a few (for now):
- For now, `mixfetch` doesn't work with SURBS, although this will change in the future.
- For now, `mixFetch` cannot deal with concurrent requests with the same base URL.
- `mixFetch` supports concurrent requests (up to 10) to either different URLs on the same domain or different domains. Duplicate requests to the exact same URL will be deduplicated.
<Callout type="info" emoji="️">
Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so.
@@ -33,6 +33,7 @@ curl -X 'GET' \
import type { SetupMixFetchOps } from '@nymproject/mix-fetch';
const mixFetchOptions: SetupMixFetchOps = {
clientId: "my-mixfetch-client", // explicit ID to avoid stale default IndexedDB storage
preferredGateway: "q2A2cbooyC16YJzvdYaSMH9X3cSiieZNtfBr8cE8Fi1", // with WSS
mixFetchOverride: {
requestTimeoutMs: 60_000,
@@ -78,7 +79,7 @@ import { mixFetch } from '@nymproject/mix-fetch-full-fat';
##### Example: using the `mixFetch` client:
`Get` and `Post` outputs will be observable from your console.
`Get`, `Post`, and `Concurrent` outputs will be observable from your console. MixFetch auto-initializes on the first request. Individual concurrent results are logged as they arrive.
```ts
import './App.css';
@@ -86,6 +87,7 @@ import { mixFetch, SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat';
import React from 'react';
const mixFetchOptions: SetupMixFetchOps = {
clientId: "my-mixfetch-client", // explicit ID to avoid stale default IndexedDB storage
preferredGateway: '23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb', // with WSS
preferredNetworkRequester:
'HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb',
@@ -103,7 +105,7 @@ export function HttpGET() {
const response = await mixFetch('https://nym.com/favicon.svg', { mode: 'unsafe-ignore-cors' }, mixFetchOptions);
const text = await response.text();
console.log('response was', text);
setHtml(html);
setHtml(text);
}
return (
@@ -146,11 +148,63 @@ export function HttpPOST() {
);
}
// Send 5 concurrent requests to different URLs on the same domain using Promise.all
export function HttpConcurrent() {
const [results, setResults] = React.useState<string[]>([]);
const [busy, setBusy] = React.useState(false);
async function fetchConcurrent() {
const baseUrl = 'https://jsonplaceholder.typicode.com/posts/';
const count = 5;
setBusy(true);
setResults([]);
console.log(`Starting ${count} concurrent requests to ${baseUrl}1-${count}...`);
try {
// Fire off all requests at once with Promise.all
const requests = Array.from({ length: count }, (_, i) => {
const url = `${baseUrl}${i + 1}`;
return mixFetch(url, { mode: 'unsafe-ignore-cors' }, mixFetchOptions)
.then((res) => res.json())
.then((json: { id: number; title: string }) => {
const entry = `[${json.id}] ${json.title}`;
console.log(entry);
return entry;
});
});
const allResults = await Promise.all(requests);
setResults(allResults);
console.log('All concurrent requests completed!', allResults);
} catch (err) {
console.error('Concurrent fetch error:', err);
} finally {
setBusy(false);
}
}
return (
<>
<button onClick={fetchConcurrent} disabled={busy}>
{busy ? 'Fetching...' : 'Send 5 Concurrent Requests'}
</button>
{results.length > 0 && (
<ul>
{results.map((r, i) => (
<li key={i}>{r}</li>
))}
</ul>
)}
</>
);
}
export default function App() {
return (
<>
<HttpGET />
<HttpPOST />
<HttpConcurrent />
</>
);
}
+9
View File
@@ -1,3 +1,12 @@
---
title: "Nym Docs: Guides, SDKs & Architecture"
description: "Official Nym documentation hub. Build privacy-enhanced applications, run mixnet nodes, and explore the network architecture and protocols powering NymVPN."
schemaType: "TechArticle"
section: "Documentation"
lastUpdated: "2026-02-11"
layout: raw
---
import { LandingPage } from '../components/landing-page.tsx'
<LandingPage />
@@ -1,3 +1,11 @@
---
title: "Nym Network Architecture: How the Mixnet Works"
description: "Deep dive into Nym network architecture, cryptographic systems, and how the mixnet provides network-level privacy against end-to-end attackers."
schemaType: "TechArticle"
section: "Network"
lastUpdated: "2026-02-11"
---
# Introduction
Nym's network documentation covering network architecture, node types, tokenomics, and crypto systems.
@@ -1,3 +1,12 @@
---
title: "Building Nym from Source: Linux, macOS & Windows"
description: "How to build Nym platform binaries from source code. Covers dependencies for Debian, Arch, macOS, and Windows. Requires Rust toolchain and Git."
schemaType: "HowTo"
section: "Operators"
lastUpdated: "2026-02-01"
breadcrumbLabel: "Building from Source"
---
import { Callout } from 'nextra/components';
import { VarInfo } from 'components/variable-info.tsx';
@@ -1,3 +1,11 @@
---
title: "Nym Node Changelog & Release History"
description: "Complete changelog for Nym node releases, binary updates, SDK changes, and network upgrades. Sorted newest first with links to relevant documentation."
schemaType: "TechArticle"
section: "Operators"
lastUpdated: "2026-02-01"
---
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { MyTab } from 'components/generic-tabs.tsx';
@@ -49,6 +57,37 @@ This page displays a full list of all the changes during our release cycle from
<VarInfo />
## `v2026.4-quark`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2026.4-quark)
- [`nym-node`](nodes/nym-node.mdx) version `1.26.0`
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2026-02-24T13:43:24.098285047Z
Build Version: 1.26.0
Commit SHA: a2081af6038ef3ef40b3d9368299d2676a2fbb6a
Commit Date: 2026-02-24T12:02:35.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.91.1
rustc Channel: stable
cargo Profile: release
```
### Operator & Developer Updates
### Features
- [Stateless handshake improvements for LP Gateway](https://github.com/nymtech/nym/pull/6437)
- [HTTP & DNS improvements](https://github.com/nymtech/nym/pull/6423)
- [Endpoint support for exit gateway IPs](https://github.com/nymtech/nym/pull/6418)
### Tools
- **Diagnostic Tool** - a standalone binary for network diagnostics. It performs DNS, HTTP, and gateway connectivity tests, helping developers identify connectivity issues and monitor network performance. It can also be run via the daemon CLI. Read the full guide [here](https://nym.com/docs/developers/tools/diagnostic-tool).
- **Socks5 Score Calculation** - performed by the Gateway probe, which tests `nym-node --mode exit-gateway` instances over Socks5. The probe assigns a latency-based rating: high, medium, low, or offline. Full guide [here](https://nym.com/docs/operators/performance-and-testing#socks5-score-calculation-process).
## `v2026.3-parmigiano`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2026.3-parmigiano)
- [`nym-node`](nodes/nym-node.mdx) version `1.25.0`
@@ -66,9 +105,9 @@ rustc Channel: stable
cargo Profile: release
```
### Key updates for operators include:
### Operator & Developer Updates
LP Gateway and Client fixes:
### Features
- [Registration client now properly supports fallback](https://github.com/nymtech/nym/pull/6419)
- [Exposed WireGuard PSK for vpn-client](https://github.com/nymtech/nym/pull/6411)
@@ -77,28 +116,14 @@ LP Gateway and Client fixes:
Note: This code is currently deactivated and doesnt involve any changes for operators right now, but it will in the future.
Mixnet & Networking Enhancements:
- [NS API Socks5 support](https://github.com/nymtech/nym/pull/6361)
- [Two-step dvpn registration flow](https://github.com/nymtech/nym/pull/6386)
- [DVPN PSK injection](https://github.com/nymtech/nym/pull/6378)
Security & Encoding Improvements:
- [Hex-encoding for LP key digests](https://github.com/nymtech/nym/pull/6394)
- [Encrypted KKT](https://github.com/nymtech/nym/pull/6331)
- [Reject packets with incompatible versions](https://github.com/nymtech/nym/pull/6326)
Bugfixes & Quality-of-Life:
### Bugfix
- [Share IP allocation fixes](https://github.com/nymtech/nym/pull/6395)
- [Mixnet registration fixes](https://github.com/nymtech/nym/pull/6356)
- [Small QoL changes](https://github.com/nymtech/nym/pull/6340)
Chores & Maintenance:
### Refactors & Maintenance
- [Cleanup x25519/ed22519 usage](https://github.com/nymtech/nym/pull/6335)
- [Upgrade to def_guard_wireguard v0.8.0](https://github.com/nymtech/nym/pull/6315)
## `v2026.2-oscypek`
@@ -130,7 +155,7 @@ Secondly, the outcome of [NIP-7: Nym Exit Policy Update - Opening Ports for Stea
This release brings changes which would lead into a *foreign constraint bug* if operators just switched binaries and restarted the node. To prevent it we need to do a little `sqlite` tweak on the node database.
To simplify this, we made **a build in command, which operators must run after getting the new binary, but beofre restarting the node.**
To simplify this, we made **a build in command, which operators must run after getting the new binary, but before restarting the node.**
These are the steps to follow:
@@ -154,7 +179,7 @@ chmod +x nym-node
```sh
systemctl restart nym-node
```
- Additionaly look for starus or serivice journal
- Additionally look for status or service journal
```sh
service nym-node status
# or
@@ -1294,7 +1319,7 @@ cargo Profile: release
- [Listen for shutdown signals during nym-node startup](https://github.com/nymtech/nym/pull/5879): This is to avoid situation where the process can't be killed without 'kill -9' because the logic to listen to shutdown signals hasn't been hit yet
### Bugfixes
### Bugfix
- [Don't allow mixnode running in exit mode](https://github.com/nymtech/nym/pull/5898)
@@ -1,3 +1,11 @@
---
title: "Legal Guide for Nym Exit Gateway Operators"
description: "Legal considerations for running a Nym exit gateway. Covers ISP communication templates, jurisdiction guidance, and abuse report response strategies."
schemaType: "TechArticle"
section: "Operators"
lastUpdated: "2026-02-01"
---
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
@@ -1,3 +1,11 @@
---
title: "Email & Legal Templates for Nym Node Operators"
description: "Ready-to-use templates for communicating with VPS providers about running Nym nodes. Includes introduction emails and abuse report response templates."
schemaType: "TechArticle"
section: "Operators"
lastUpdated: "2026-02-01"
---
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
@@ -1,3 +1,11 @@
---
title: "Nym Node Operator FAQ: Questions Answered"
description: "Frequently asked questions about running Nym nodes. Covers VPS selection, staking requirements, node configuration, rewards, and community resources."
schemaType: "FAQPage"
section: "Operators"
lastUpdated: "2026-02-01"
---
# General Operators FAQ
## Nym Network
@@ -16,8 +24,8 @@ Yes, there are..
**Built by community**
* [ExploreNYM](https://explorenym.net/)
* [Mixplorer](https://mixplorer.xyz/)
* [SpectreDAO Explorer](https://explorer.nym.spectredao.net/dashboard)
* [Nymesis](https://nymesis.vercel.app)
### Which VPS providers would you recommend?
@@ -1,3 +1,11 @@
---
title: "Nym Node Operator Guide & Prerequisites"
description: "Introduction to running Nym nodes. Learn requirements, skill expectations, time commitment, and how to get started operating a node on the Nym mixnet."
schemaType: "TechArticle"
section: "Operators"
lastUpdated: "2026-02-11"
---
import QuickStart from 'components/operators/snippets/quick-start.mdx'
# Introduction
@@ -1,3 +1,11 @@
---
title: "How to Set Up & Run a Nym Node on the Mixnet"
description: "Step-by-step guide to installing, configuring, and running a nym-node on the Nym mixnet. Covers prerequisites, staking requirements, and CLI setup."
schemaType: "HowTo"
section: "Operators"
lastUpdated: "2026-02-01"
---
import { Callout } from 'nextra/components';
import { Steps } from 'nextra/components';
import { Tabs } from 'nextra/components';
@@ -1,3 +1,11 @@
---
title: "Nym Node Configuration & Systemd Setup"
description: "Configure your nym-node with systemd automation, reverse proxy, IPv6, and custom settings. Includes service file templates and maintenance tips."
schemaType: "TechArticle"
section: "Operators"
lastUpdated: "2026-02-01"
---
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { VarInfo } from 'components/variable-info.tsx';
@@ -21,17 +21,16 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2026-01-27T14:54:15.579821601Z
Build Version: 1.24.0
Commit SHA: 83bf9dc7cc2b01f65cab671733f2bf6c3abd471d
Commit Date: 2026-01-27T15:46:52.000000000+01:00
Build Timestamp: 2026-02-24T13:43:24.098285047Z
Build Version: 1.26.0
Commit SHA: a2081af6038ef3ef40b3d9368299d2676a2fbb6a
Commit Date: 2026-02-24T12:02:35.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.91.1
rustc Channel: stable
cargo Profile: release
```
Detailed version archive and release notes is documented [here](../../changelog.mdx).
{/* COMMENTING THIS OUT ASS WE HAVE TO FIGURE OUT HOW TO SHOW THE LATEST VERSION FROM MASTER BRANCH
@@ -1,4 +1,13 @@
---
title: "Nym Node Performance Monitoring & Testing Guide"
description: "Monitor your Nym node performance with Prometheus, Grafana, and community tools. Covers key metrics, routing score analysis, and testing best practices."
schemaType: "TechArticle"
section: "Operators"
lastUpdated: "2026-02-01"
---
import { Callout } from 'nextra/components';
import { Steps } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { MyTab } from 'components/generic-tabs.tsx';
import { AccordionTemplate } from 'components/accordion-template.tsx';
@@ -7,11 +16,11 @@ import NodePerfMixnet from 'components/operators/snippets/node-perf-mixnet.mdx';
# Performance Monitoring & Testing
As Nym developers constantly improve the software, the role of Node Operators is to keep their nodes up to date, monitor their performance and share feedback with the rest of the community and Nym team. Node performance measurements and [server monitoring](#monitoring) are an essential pillar of our common work.
As Nym developers constantly improve the software, the role of Node Operators is to keep their nodes up to date, monitor their performance and share feedback with the rest of the community and Nym team. Node performance measurements and [server monitoring](#monitoring) are an essential pillar of our common work.
Nym Network is routed either through the Mixnet (5-hop) or through Wireguard (2-hop). In all cases Nym node operators always employ only one binary called [`nym-node`](/operators/nodes/nym-node). Through provided arguments (or changes in the config file), `nym-node` can be utilised for different [functionalities](/operators/nodes/nym-node/setup#functionality-mode). However, once it's [registered to Nym Network](/operators/nodes/nym-node/bonding) it's by default available for Nym Mixnet not for Wireguard routing. Only nodes with Wireguard enabled, are also available for Wireguard routing. This creates a situation where every Wireguard enabled `nym-node` is required to have a solid performance score in Mixnet to begin with, but not every Mixnet routing `nym-node` must have Wireguard enabled.
Nym Network is routed either through the Mixnet (5-hop) or through Wireguard (2-hop). In all cases Nym node operators always employ only one binary called [`nym-node`](/operators/nodes/nym-node). Through provided arguments (or changes in the config file), `nym-node` can be utilised for different [functionalities](/operators/nodes/nym-node/setup#functionality-mode). However, once it's [registered to Nym Network](/operators/nodes/nym-node/bonding) it's by default available for Nym Mixnet not for Wireguard routing. Only nodes with Wireguard enabled, are also available for Wireguard routing. This creates a situation where every Wireguard enabled `nym-node` is required to have a solid performance score in Mixnet to begin with, but not every Mixnet routing `nym-node` must have Wireguard enabled.
Given this complexity, we divided the part below about perfromance calculation logic and node selection into two parallel tabs: Mixnet and Wireguard.
Given this complexity, we divided the part below about performance calculation logic and node selection into two parallel tabs: Mixnet and Wireguard.
<div>
<Tabs items={[
@@ -56,18 +65,53 @@ https://<HOSTNAME>/api/v1/roles
```
</AccordionTemplate>
## Socks5 Score Calculation
Gateway probe also runs tests through a Network requester - a module build as a part of `nym-node`, active only in mode Exit Gateway, used for [Socks5](/developers/clients/socks5) proxy TCP connection.
Socks5 score is displayed in [Nym Node Status Observatory](https://harbourmaster.nymtech.net) (if you open a page with a particular gateway) and in detail it can be previewed at [mainnet-node-status-api.nymtech.cc/dvpn/v1/directory/gateways](https://mainnet-node-status-api.nymtech.cc/dvpn/v1/directory/gateways) or when running own instance of [Gateway probe](/operators/performance-and-testing/gateway-probe).
### Socks5 Score Calculation Process
Socks5 score is defined in the json output of Gateway probe as `"socks5"` key. Here is an example of the dictionary:
```json
"socks5": {
"can_proxy_https": true,
"score": "medium",
"errors": null
}
```
> Note: When we write *gateway* we refer to a `nym-node --mode exit-gateway` in this sub-chapter.
<Steps>
1. Gateway gets probed as part of a Gateway probe test where other components get tested as well
2. Probe tries to connect to the Gateway through Socks5 10 times per testing instance
3. Latency is calculated as an average of the successful attempts
4. Gateway is scored as `"low"`, `"medium"`, `"high"` or `"offline"`, in numbers it means:
- `"offline"`: Gateway failed the test 3 or more times (out of 10 attempts)
- `"high"`: Top 50% of nodes with lowest average latency
- `"medium"`: Following 25% of nodes with lowest average latency below top 50% nodes
- `"low"`: Remaining 25% of nodes with the highest average latency time
</Steps>
## Monitoring
There are multiple ways to monitor performance of nodes and the machines on which they run. For the purpose of maximal privacy and decentralisation of the data - preventing Nym Mixnet from any global adversary takeover - we created these pages as a source of mutual empowerment, a place where operators can share and learn new skills to **setup metrics monitors on their own infrastructure**.
### Guides to Setup Own Metrics
A list of different scripts, templates and guides for easier navigation:
A list of different tools, templates and guides for easier navigation:
* [`nym-gateway-probe`](performance-and-testing/gateway-probe.mdx): a useful tool used under the hood of [Node Status Observatory](https://harbourmaster.nymtech.net)
* [Diagnostic Tool](/developers/tools/diagnostic-tool): diagnose connectivity issues and provides insights into network performance
* [`nym-gateway-probe`](performance-and-testing/gateway-probe.mdx) - a useful tool used under the hood of [Node Status Observatory](https://harbourmaster.nymtech.net)
* [Prometheus and Grafana](performance-and-testing/prometheus-grafana.mdx) self-hosted setup
* [Nym-node CPU cron service](https://gist.github.com/tommyv1987/97e939a7adf491333d686a8eaa68d4bd) - an easy bash script by Nym core developer [@tommy1987](https://gist.github.com/tommyv1987), designed to monitor a CPU usage of your node, running locally
* Nym's script [`prom_targets.py`](https://github.com/nymtech/nym/blob/develop/scripts/prom_targets.py) - a useful python program to request data from API and can be run on its own or plugged to more sophisticated flows
### Collecting Testing Metrics
@@ -93,4 +137,4 @@ We do testing in order to **understand and increase the overall quality of the N
7. Adjust rewarding based on the machine specs and server pricing
Visit [Nym Harbour Master](https://harbourmaster.nymtech.net/) monitoring page to monitor network components (nodes) performance.
*/}
*/}
@@ -1,3 +1,11 @@
---
title: "Nym Sandbox Testnet for Node Operators"
description: "Run your Nym node in the Sandbox testnet environment. Test configurations, try new features, and experiment safely before deploying to mainnet."
schemaType: "HowTo"
section: "Operators"
lastUpdated: "2026-02-01"
---
import { Callout } from 'nextra/components';
import { VarInfo } from 'components/variable-info.tsx';
import { Steps } from 'nextra/components';import { Tabs } from 'nextra/components'
@@ -202,3 +202,13 @@ PING_RETRIES=10 PING_TIMEOUT=5 CONCURRENCY=16 ./test-nodes-pings.sh
You can look up the IPs from `ping_not_working.csv`, using some online database, like [ipinfo.io](https://ipinfo.io).
Feel invited to share the outcome with Nym team, mentors and the rest of the operators in our [Matrix Node Operators channel](https://matrix.to/#/#operators:nymtech.chat).
## Guides to Setup Own Metrics
A list of different tools, templates and guides for easier navigation:
* [`nym-gateway-probe`](performance-and-testing/gateway-probe.mdx): a useful tool used under the hood of [Node Status Observatory](https://harbourmaster.nymtech.net)
* [Diagnostic Tool](/developers/tools/diagnostic-tool): diagnose connectivity issues and provides insights into network performance
* [Prometheus and Grafana](performance-and-testing/prometheus-grafana.mdx) self-hosted setup
@@ -1,3 +1,11 @@
---
title: "Nym Node Troubleshooting: Common Errors & Fixes"
description: "Solutions for common nym-node issues including build failures, connectivity problems, and configuration errors. Includes error messages and fix steps."
schemaType: "TechArticle"
section: "Operators"
lastUpdated: "2026-02-01"
---
import { Tabs } from 'nextra/components';
import { Callout } from 'nextra/components';
import { VarInfo } from 'components/variable-info.tsx';
+38 -5
View File
@@ -1,3 +1,29 @@
/* nym.com-aligned colour tokens */
:root {
--colorPrimary: #85E89D;
--textPrimary: #FFFFFF;
--bg-dark: #1E2426;
--border-dark: #2E3538;
}
/* dark mode background override */
html.dark {
background-color: var(--bg-dark);
}
html.dark body {
background-color: var(--bg-dark);
}
/* nextra main content area bg */
html.dark .nextra-nav-container,
html.dark .nextra-sidebar-container,
html.dark .nextra-content,
html.dark .nx-bg-white,
html.dark .dark\:nx-bg-dark {
background-color: var(--bg-dark) !important;
}
footer {
text-align: center;
}
@@ -19,7 +45,7 @@ footer {
padding-right: 0px;
padding-left: 0px;
/* text-align: right; */
border-left: 1px solid #262626;
border-left: 1px solid var(--border-dark);
}
.nextra-content {
@@ -34,14 +60,21 @@ footer {
background-color: hsl(var(black) 100% 77%/0.1) !important;
}
/* Sidebar buttons */
/* Sidebar active item */
:is(html .dark\:nx-bg-primary-400\/10) {
background: var(--colorPrimary) !important;
color: var(--textPrimary) !important;
background: transparent !important;
border-left: 2px solid #85E89D;
color: #FFFFFF !important;
}
:is(html:not(.dark) .dark\:nx-bg-primary-400\/10) {
background: transparent !important;
border-left: 2px solid #4A9E5C;
color: #242B2D !important;
}
.nextra-sidebar-container {
border-right: 1px solid #262626;
border-right: 1px solid var(--border-dark);
width: 300px !important;
}
+68 -27
View File
@@ -78,8 +78,8 @@ importers:
specifier: '>=1.2.4-rc.2 || ^1'
version: 1.4.1
'@nymproject/mix-fetch-full-fat':
specifier: '>=1.5.1-rc.0 || ^1.4.1'
version: 1.4.1
specifier: ^1.4.2
version: 1.4.2
'@nymproject/sdk-full-fat':
specifier: '>=1.5.1-rc.0 || ^1.4.1'
version: 1.4.1
@@ -141,6 +141,9 @@ importers:
eslint-config-next:
specifier: 13.4.13
version: 13.4.13(eslint@8.46.0)(typescript@5.9.3)
next-sitemap:
specifier: 4.2.3
version: 4.2.3(next@15.5.10(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
raw-loader:
specifier: ^4.0.2
version: 4.0.2(webpack@5.101.3)
@@ -247,6 +250,9 @@ packages:
react: '>=17'
react-dom: '>=17'
'@corex/deepmerge@4.0.43':
resolution: {integrity: sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==}
'@cosmjs/amino@0.25.6':
resolution: {integrity: sha512-9dXN2W7LHjDtJUGNsQ9ok0DfxeN3ca/TXnxCR3Ikh/5YqBqxI8Gel1J9PQO9L6EheYyh045Wff4bsMaLjyEeqQ==}
@@ -1029,6 +1035,9 @@ packages:
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
'@next/env@13.5.11':
resolution: {integrity: sha512-fbb2C7HChgM7CemdCY+y3N1n8pcTKdqtQLbC7/EQtPdLvlMUT9JX/dBYl8MMZAtYG4uVMyPFHXckb68q/NRwqg==}
'@next/env@15.5.10':
resolution: {integrity: sha512-plg+9A/KoZcTS26fe15LHg+QxReTazrIOoKKUC3Uz4leGGeNPgLHdevVraAAOX0snnUs3WkRx3eUQpj9mreG6A==}
@@ -1705,8 +1714,8 @@ packages:
'@nymproject/contract-clients@1.4.1':
resolution: {integrity: sha512-HuJZ4Hv+Rl6ZZEtCHKgurNLJapM+QQRJlGkevFH2a4UdqUqF9omUkUi3AVes4679dPoSFgvA7plyVSDBdbgV6w==}
'@nymproject/mix-fetch-full-fat@1.4.1':
resolution: {integrity: sha512-AMa21sEd9FELqAJe1lCyHPqxxbc13ApiJ1P/exAslQjiFPb/de/3Ow0FHqKGNPrwyVRS/T2pSzjQ3l8TddiEBA==}
'@nymproject/mix-fetch-full-fat@1.4.2':
resolution: {integrity: sha512-QHPwa7A+c/2VUm4Imq2I21toFiZhbZxcjHud1sFsE9hN5BWxZ+QJKV2bg9oBUzulzoQabsk48RA13/hqU7c4KA==}
'@nymproject/sdk-full-fat@1.4.1':
resolution: {integrity: sha512-dh5bvMUj3m8nEssvO8Nl66WpcJAjwRZrGNwqfczJWLG4nX3Vt95tPLv4v0/Z1W3DQWQFW6WmEPPYHNjl18V/fA==}
@@ -3215,6 +3224,11 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
acorn@8.16.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
engines: {node: '>=0.4.0'}
hasBin: true
agent-base@7.1.4:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
@@ -3390,8 +3404,9 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
baseline-browser-mapping@2.9.19:
resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
baseline-browser-mapping@2.10.0:
resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==}
engines: {node: '>=6.0.0'}
hasBin: true
bech32@1.1.4:
@@ -3510,6 +3525,9 @@ packages:
caniuse-lite@1.0.30001769:
resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
caniuse-lite@1.0.30001774:
resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==}
cardinal@2.1.1:
resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==}
hasBin: true
@@ -4022,8 +4040,8 @@ packages:
duplexify@4.1.3:
resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==}
electron-to-chromium@1.5.286:
resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==}
electron-to-chromium@1.5.302:
resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==}
elkjs@0.9.3:
resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==}
@@ -5355,8 +5373,8 @@ packages:
modern-ahocorasick@1.1.0:
resolution: {integrity: sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==}
motion-dom@12.34.0:
resolution: {integrity: sha512-Lql3NuEcScRDxTAO6GgUsRHBZOWI/3fnMlkMcH5NftzcN37zJta+bpbMAV9px4Nj057TuvRooMK7QrzMCgtz6Q==}
motion-dom@12.34.3:
resolution: {integrity: sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ==}
motion-utils@12.29.2:
resolution: {integrity: sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==}
@@ -5404,6 +5422,13 @@ packages:
react: '>=16.0.0'
react-dom: '>=16.0.0'
next-sitemap@4.2.3:
resolution: {integrity: sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==}
engines: {node: '>=14.18'}
hasBin: true
peerDependencies:
next: '*'
next-themes@0.2.1:
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
peerDependencies:
@@ -6709,8 +6734,8 @@ packages:
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
webpack-sources@3.3.3:
resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==}
webpack-sources@3.3.4:
resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==}
engines: {node: '>=10.13.0'}
webpack@5.101.3:
@@ -6982,6 +7007,8 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@corex/deepmerge@4.0.43': {}
'@cosmjs/amino@0.25.6':
dependencies:
'@cosmjs/crypto': 0.25.6
@@ -8213,6 +8240,8 @@ snapshots:
'@tybys/wasm-util': 0.10.0
optional: true
'@next/env@13.5.11': {}
'@next/env@15.5.10': {}
'@next/eslint-plugin-next@13.4.13':
@@ -9307,7 +9336,7 @@ snapshots:
'@nymproject/contract-clients@1.4.1': {}
'@nymproject/mix-fetch-full-fat@1.4.1': {}
'@nymproject/mix-fetch-full-fat@1.4.2': {}
'@nymproject/sdk-full-fat@1.4.1': {}
@@ -11810,9 +11839,9 @@ snapshots:
dependencies:
event-target-shim: 5.0.1
acorn-import-phases@1.0.4(acorn@8.15.0):
acorn-import-phases@1.0.4(acorn@8.16.0):
dependencies:
acorn: 8.15.0
acorn: 8.16.0
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
@@ -11820,6 +11849,8 @@ snapshots:
acorn@8.15.0: {}
acorn@8.16.0: {}
agent-base@7.1.4: {}
ajv-draft-04@1.0.0(ajv@8.17.1):
@@ -12009,7 +12040,7 @@ snapshots:
base64-js@1.5.1: {}
baseline-browser-mapping@2.9.19: {}
baseline-browser-mapping@2.10.0: {}
bech32@1.1.4: {}
@@ -12079,9 +12110,9 @@ snapshots:
browserslist@4.28.1:
dependencies:
baseline-browser-mapping: 2.9.19
caniuse-lite: 1.0.30001769
electron-to-chromium: 1.5.286
baseline-browser-mapping: 2.10.0
caniuse-lite: 1.0.30001774
electron-to-chromium: 1.5.302
node-releases: 2.0.27
update-browserslist-db: 1.2.3(browserslist@4.28.1)
@@ -12135,6 +12166,8 @@ snapshots:
caniuse-lite@1.0.30001769: {}
caniuse-lite@1.0.30001774: {}
cardinal@2.1.1:
dependencies:
ansicolors: 0.3.2
@@ -12672,7 +12705,7 @@ snapshots:
readable-stream: 3.6.2
stream-shift: 1.0.3
electron-to-chromium@1.5.286: {}
electron-to-chromium@1.5.302: {}
elkjs@0.9.3: {}
@@ -13171,7 +13204,7 @@ snapshots:
framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
motion-dom: 12.34.0
motion-dom: 12.34.3
motion-utils: 12.29.2
tslib: 2.8.1
optionalDependencies:
@@ -14491,7 +14524,7 @@ snapshots:
modern-ahocorasick@1.1.0: {}
motion-dom@12.34.0:
motion-dom@12.34.3:
dependencies:
motion-utils: 12.29.2
@@ -14530,6 +14563,14 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
next-sitemap@4.2.3(next@15.5.10(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
dependencies:
'@corex/deepmerge': 4.0.43
'@next/env': 13.5.11
fast-glob: 3.3.3
minimist: 1.2.8
next: 15.5.10(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes@0.2.1(next@15.5.10(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
next: 15.5.10(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -15781,7 +15822,7 @@ snapshots:
terser@5.46.0:
dependencies:
'@jridgewell/source-map': 0.3.11
acorn: 8.15.0
acorn: 8.16.0
commander: 2.20.3
source-map-support: 0.5.21
@@ -16171,7 +16212,7 @@ snapshots:
webidl-conversions@3.0.1: {}
webpack-sources@3.3.3: {}
webpack-sources@3.3.4: {}
webpack@5.101.3:
dependencies:
@@ -16181,8 +16222,8 @@ snapshots:
'@webassemblyjs/ast': 1.14.1
'@webassemblyjs/wasm-edit': 1.14.1
'@webassemblyjs/wasm-parser': 1.14.1
acorn: 8.15.0
acorn-import-phases: 1.0.4(acorn@8.15.0)
acorn: 8.16.0
acorn-import-phases: 1.0.4(acorn@8.16.0)
browserslist: 4.28.1
chrome-trace-event: 1.0.4
enhanced-resolve: 5.19.0
@@ -16199,7 +16240,7 @@ snapshots:
tapable: 2.3.0
terser-webpack-plugin: 5.3.16(webpack@5.101.3)
watchpack: 2.5.1
webpack-sources: 3.3.3
webpack-sources: 3.3.4
transitivePeerDependencies:
- '@swc/core'
- esbuild
+15
View File
@@ -0,0 +1,15 @@
# *
User-agent: *
Allow: /
# *
User-agent: *
Disallow: /api/
Disallow: /_next/
# Host
Host: https://nymtech.net/docs
# Sitemaps
Sitemap: https://nymtech.net/docs/sitemap.xml
Sitemap: https://nymtech.net/docs/sitemap-docs.xml
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
<url><loc>https://nymtech.net/docs</loc><lastmod>2026-02-25T10:35:41.122Z</lastmod><changefreq>yearly</changefreq><priority>0.6</priority></url>
<url><loc>https://nymtech.net/docs/network</loc><lastmod>2026-02-25T10:35:41.122Z</lastmod><changefreq>yearly</changefreq><priority>0.6</priority></url>
</urlset>
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>https://nymtech.net/docs/sitemap-0.xml</loc></sitemap>
<sitemap><loc>https://nymtech.net/docs/sitemap-docs.xml</loc></sitemap>
</sitemapindex>
+108 -29
View File
@@ -13,52 +13,129 @@ const config: DocsThemeConfig = {
const image = url + "/images/Nym_meta_Image.png";
const favicon = url + "/favicon.svg";
// Define descriptions for different "books"
const bookDescriptions: Record<string, string> = {
"/developers":
"Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.",
"/network":
"Nym's network documentation covering network architecture, node types, tokenomics, and cryptography.",
"/operators":
"Nym's Operators guide containing information and setup guides for the various components of Nym network and Nyx blockchain validators.",
"/apis":
"Interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet.",
};
const defaultDescription =
"Nym is a privacy platform. It provides strong network-level privacy against sophisticated end-to-end attackers, and anonymous access control using blinded, re-randomizable, decentralized credentials.";
const topLevel = "/" + route.split("/")[1];
const description =
config.frontMatter.description ||
bookDescriptions[topLevel] ||
defaultDescription;
// Frontmatter-first description
const description = config.frontMatter.description || defaultDescription;
const title = (route === "/" ? "Nym docs" : config.title + " - Nym docs");
const baseTitle = config.frontMatter.title || config.title || "";
const title =
route === "/"
? "Nym Docs: Privacy Network Documentation"
: baseTitle.includes("| Nym Docs")
? baseTitle
: `${baseTitle} | Nym Docs`;
const pageUrl = `${url}${route}`;
// Frontmatter fields
const section = config.frontMatter.section || "";
const lastUpdated = config.frontMatter.lastUpdated || "";
const schemaType = config.frontMatter.schemaType || "TechArticle";
// JSON-LD structured data
const org = {
"@id": "https://nym.com/#org",
"@type": "Organization",
name: "Nym Technologies SA",
url: "https://nym.com",
logo: {
"@id": "https://nym.com/#logo",
"@type": "ImageObject",
url: "https://nym.com/apple-touch-icon.png",
},
sameAs: ["https://x.com/nymproject", "https://github.com/nymtech"],
};
const website = {
"@id": "https://nym.com/docs#website",
"@type": "WebSite",
name: "Nym Docs",
url: "https://nym.com/docs",
publisher: { "@id": "https://nym.com/#org" },
};
const webpage = {
"@id": `${pageUrl}#webpage`,
"@type": "WebPage",
url: pageUrl,
name: title,
description: description,
inLanguage: "en",
isPartOf: { "@id": "https://nym.com/docs#website" },
breadcrumb: { "@id": `${pageUrl}#breadcrumb` },
potentialAction: { "@type": "ReadAction", target: pageUrl },
};
const articleSchema: Record<string, any> = {
"@id": `${pageUrl}#article`,
"@type": schemaType,
...(schemaType === "HowTo"
? { name: baseTitle }
: { headline: baseTitle }),
description: description,
url: pageUrl,
author: { "@id": "https://nym.com/#org" },
publisher: { "@id": "https://nym.com/#org" },
mainEntityOfPage: { "@id": `${pageUrl}#webpage` },
...(lastUpdated && {
datePublished: lastUpdated,
dateModified: lastUpdated,
}),
};
const pathParts = route.split("/").filter(Boolean);
const breadcrumb = {
"@id": `${pageUrl}#breadcrumb`,
"@type": "BreadcrumbList",
itemListElement: pathParts.map((part: string, i: number) => ({
"@type": "ListItem",
position: i + 1,
name:
config.frontMatter.breadcrumbLabel && i === pathParts.length - 1
? config.frontMatter.breadcrumbLabel
: part.charAt(0).toUpperCase() + part.slice(1).replace(/-/g, " "),
item: `${url}/${pathParts.slice(0, i + 1).join("/")}`,
})),
};
const schema = {
"@context": "https://schema.org",
"@graph": [org, website, webpage, articleSchema, breadcrumb],
};
return (
<>
<title>{title}</title>
<meta name="author" content="Nym" />
<link rel="canonical" href={url + route} />
<link rel="canonical" href={pageUrl} />
<link rel="icon" href={favicon} type="image/svg+xml" />
<meta property="og:title" content={title} />
<meta property="og:site_name" content="Nym docs"></meta>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:site_name" content="Nym docs" />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:type" content="website" />
<meta property="og:url" content={url + route}></meta>
<meta property="twitter:title" content={title}></meta>
<meta property="twitter:description" content={description}></meta>
<meta property="og:type" content="article" />
<meta property="og:url" content={pageUrl} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
{section && <meta property="article:section" content={section} />}
{lastUpdated && (
<meta property="article:modified_time" content={lastUpdated} />
)}
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:image" content={image}></meta>
<meta property="twitter:image" content={image} />
<meta name="twitter:site" content="@nymproject" />
<meta name="twitter:site:domain" content={url} />
<meta name="twitter:url" content={url + route} />
<meta name="twitter:url" content={pageUrl} />
<meta name="apple-mobile-web-app-title" content="Nym docs" />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
</>
);
},
@@ -72,6 +149,8 @@ const config: DocsThemeConfig = {
// text: Footer,
// },
darkMode: true,
primaryHue: 135,
primarySaturation: 64,
nextThemes: {
defaultTheme: "dark",
},
@@ -5,29 +5,29 @@ set -o nounset
set -o pipefail
cd ../scripts &&
python csv2md.py -s 1 ../docs/data/csv/variables.csv > ../docs/components/outputs/csv2md-outputs/variables.md &&
python csv2md.py -s 0 ../docs/data/csv/isp-sheet.csv > ../docs/components/outputs/csv2md-outputs/isp-sheet.md &&
python3 csv2md.py -s 1 ../docs/data/csv/variables.csv > ../docs/components/outputs/csv2md-outputs/variables.md &&
python3 csv2md.py -s 0 ../docs/data/csv/isp-sheet.csv > ../docs/components/outputs/csv2md-outputs/isp-sheet.md &&
cd cmdrun &&
./nyx-percent-stake.sh > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-percent-stake.md &&
./nyx-total-stake.sh > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/nyx-total-stake.md &&
cd ../api-scraping &&
python api_targets.py validator --api mainnet --endpoint circulating-supply --value circulating_supply amount --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md &&
python3 api_targets.py validator --api mainnet --endpoint circulating-supply --value circulating_supply amount --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/circulating-supply.md &&
python api_targets.py validator --api mainnet --endpoint circulating-supply --format markdown --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md &&
python3 api_targets.py validator --api mainnet --endpoint circulating-supply --format markdown --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/token-table.md &&
python api_targets.py validator --api mainnet --endpoint epoch/reward_params --value interval staking_supply_scale_factor --format percent > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-scale-factor.md &&
python3 api_targets.py validator --api mainnet --endpoint epoch/reward_params --value interval staking_supply_scale_factor --format percent > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-scale-factor.md &&
python api_targets.py validator --api mainnet --endpoint epoch/reward_params --value interval stake_saturation_point --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md &&
python3 api_targets.py validator --api mainnet --endpoint epoch/reward_params --value interval stake_saturation_point --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/stake-saturation.md &&
python api_targets.py validator --api mainnet --endpoint epoch/reward_params --value interval staking_supply --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/staking_supply.md &&
python3 api_targets.py validator --api mainnet --endpoint epoch/reward_params --value interval staking_supply --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/staking_supply.md &&
python api_targets.py validator --api mainnet --endpoint epoch/reward_params --value interval epoch_reward_budget --format markdown --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/epoch-reward-budget.md &&
python3 api_targets.py validator --api mainnet --endpoint epoch/reward_params --value interval epoch_reward_budget --format markdown --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/epoch-reward-budget.md &&
python api_targets.py time_now > ../../docs/components/outputs/api-scraping-outputs/time-now.md &&
python3 api_targets.py time_now > ../../docs/components/outputs/api-scraping-outputs/time-now.md &&
python api_targets.py calculate --staking_target --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md &&
python3 api_targets.py calculate --staking_target --separator _ > ../../docs/components/outputs/api-scraping-outputs/nyx-outputs/staking-target.md &&
curl -L https://validator.nymtech.net/api/v1/circulating-supply | jq > ../../docs/components/outputs/api-scraping-outputs/circulating-supply.json &&
@@ -35,11 +35,11 @@ curl -L https://validator.nymtech.net/api/v1/epoch/reward_params | jq > ../../do
cd ../../../scripts &&
echo '```python' > ../documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md &&
python node_api_check.py query_stats --help >> ../documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md &&
python3 node_api_check.py query_stats --help >> ../documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md &&
echo '```' >> ../documentation/docs/components/outputs/command-outputs/node-api-check-query-help.md &&
echo '```python' > ../documentation/docs/components/outputs/command-outputs/node-api-check-help.md &&
python node_api_check.py --help >> ../documentation/docs/components/outputs/command-outputs/node-api-check-help.md &&
python3 node_api_check.py --help >> ../documentation/docs/components/outputs/command-outputs/node-api-check-help.md &&
echo '```' >> ../documentation/docs/components/outputs/command-outputs/node-api-check-help.md &&
cd ../target/release/ &&
@@ -62,11 +62,11 @@ echo '```' >> ../../documentation/docs/components/outputs/command-outputs/nym-ap
cd ../../scripts/nym-node-setup
echo '```sh' > ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
python ./nym-node-cli.py install --help >> ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
python3 ./nym-node-cli.py install --help >> ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
echo '```' >> ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
echo '```sh' > ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
python ./nym-node-cli.py install --help >> ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
python3 ./nym-node-cli.py install --help >> ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
echo '```' >> ../../documentation/docs/components/outputs/command-outputs/nym-node-cli-install-help.md &&
echo "prebuild finished"
+47 -424
View File
@@ -9,30 +9,13 @@
"version": "1.0.0",
"dependencies": {
"@jsdevtools/rehype-url-inspector": "^2.0.2",
"glob": "^10.5.0",
"glob": "^13.0.6",
"rehype-parse": "^9.0.0",
"rehype-stringify": "^10.0.0",
"to-vfile": "^8.0.0",
"unified": "^11.0.2"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@jsdevtools/rehype-url-inspector": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@jsdevtools/rehype-url-inspector/-/rehype-url-inspector-2.0.2.tgz",
@@ -45,16 +28,6 @@
"node": ">=10"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@types/hast": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.0.tgz",
@@ -81,30 +54,6 @@
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
},
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/bail": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
@@ -115,18 +64,24 @@
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz",
"integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==",
"license": "MIT",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz",
"integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
"balanced-match": "^4.0.2"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/ccount": {
@@ -156,24 +111,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/comma-separated-tokens": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
@@ -183,19 +120,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -216,18 +140,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -239,36 +151,18 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
"node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1"
"minimatch": "^10.2.2",
"minipass": "^7.1.3",
"path-scurry": "^2.0.2"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -532,15 +426,6 @@
"node": ">=4"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-plain-obj": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
@@ -552,31 +437,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
"version": "11.2.6",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
"integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/mdast-util-to-hast": {
"version": "13.0.2",
@@ -682,35 +550,29 @@
]
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz",
"integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^2.0.1"
"brace-expansion": "^5.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/parse5": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
@@ -722,25 +584,17 @@
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"engines": {
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": ">=16 || 14 >=14.18"
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
@@ -783,36 +637,6 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"engines": {
"node": ">=8"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/space-separated-tokens": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
@@ -822,65 +646,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/stringify-entities": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz",
@@ -894,43 +659,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/strip-ansi": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/tlds": {
"version": "1.242.0",
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.242.0.tgz",
@@ -1225,111 +953,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
@@ -7,7 +7,7 @@
},
"dependencies": {
"@jsdevtools/rehype-url-inspector": "^2.0.2",
"glob": "^10.5.0",
"glob": "^13.0.6",
"rehype-parse": "^9.0.0",
"rehype-stringify": "^10.0.0",
"to-vfile": "^8.0.0",
+1 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.73"
version = "1.1.74"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
+4 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node"
version = "1.25.0"
version = "1.26.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
@@ -40,6 +40,8 @@ thiserror.workspace = true
tracing.workspace = true
tracing-indicatif = { workspace = true }
tracing-subscriber.workspace = true
opentelemetry = { workspace = true, features = ["trace"], optional = true }
opentelemetry_sdk = { workspace = true, features = ["trace"], optional = true }
tokio = { workspace = true, features = ["macros", "sync", "rt-multi-thread"] }
tokio-util = { workspace = true, features = ["codec"] }
tokio-stream = { workspace = true }
@@ -135,6 +137,7 @@ rand_chacha = { workspace = true }
[features]
tokio-console = ["console-subscriber", "nym-task/tokio-tracing"]
otel = ["nym-bin-common/otel-otlp", "dep:opentelemetry", "dep:opentelemetry_sdk"]
[lints]
workspace = true
@@ -106,6 +106,8 @@ pub mod verloc {
#[serde(with = "bs58_ed25519_pubkey")]
#[cfg_attr(feature = "openapi", schema(value_type = String))]
pub node_identity: ed25519::PublicKey,
pub latest_measurement: Option<VerlocMeasurement>,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
+102 -28
View File
@@ -8,7 +8,6 @@ use crate::cli::commands::{
use crate::env::vars::{NYMNODE_CONFIG_ENV_FILE_ARG, NYMNODE_NO_BANNER_ARG};
use clap::{Args, Parser, Subcommand};
use nym_bin_common::bin_info;
use std::future::Future;
use std::sync::OnceLock;
pub(crate) mod commands;
@@ -22,6 +21,43 @@ fn pretty_build_info_static() -> &'static str {
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
}
/// OpenTelemetry-related CLI arguments. Only present when built with the `otel` feature.
#[cfg(feature = "otel")]
#[derive(Args, Debug, Clone)]
pub(crate) struct OtelArgs {
/// Enable OpenTelemetry tracing export via OTLP/gRPC.
#[clap(long, env = "NYMNODE_OTEL_ENABLE")]
pub(crate) otel: bool,
/// OpenTelemetry OTLP collector endpoint (gRPC).
/// Only used when --otel is enabled.
/// For SigNoz Cloud use https://ingest.<region>.signoz.cloud:443
#[clap(
long,
env = "NYMNODE_OTEL_ENDPOINT",
default_value = "http://localhost:4317"
)]
pub(crate) otel_endpoint: String,
/// SigNoz Cloud ingestion key for authenticated OTLP export.
/// Only needed for SigNoz Cloud (not self-hosted).
#[clap(long, env = "NYMNODE_OTEL_KEY")]
pub(crate) otel_key: Option<String>,
/// Deployment environment label attached to all exported traces.
/// Used to distinguish sandbox / mainnet / canary in the OTel backend.
#[clap(long, env = "NYMNODE_OTEL_ENV", default_value = "mainnet")]
pub(crate) otel_env: String,
/// Trace sampling ratio (0.0 to 1.0). e.g. 0.1 = 10%% of traces exported. Reduces cost.
#[clap(long, env = "NYMNODE_OTEL_SAMPLE_RATIO", default_value = "0.1")]
pub(crate) otel_sample_ratio: f64,
/// Timeout in seconds for each OTLP export batch. Prevents unbounded blocking.
#[clap(long, env = "NYMNODE_OTEL_EXPORT_TIMEOUT", default_value = "10")]
pub(crate) otel_export_timeout: u64,
}
#[derive(Parser, Debug)]
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
pub(crate) struct Cli {
@@ -40,44 +76,82 @@ pub(crate) struct Cli {
)]
pub(crate) no_banner: bool,
#[cfg(feature = "otel")]
#[clap(flatten)]
pub(crate) otel: OtelArgs,
#[clap(subcommand)]
command: Commands,
}
impl Cli {
fn execute_async<F: Future>(fut: F) -> anyhow::Result<F::Output> {
Ok(tokio::runtime::Builder::new_multi_thread()
pub(crate) fn execute(self) -> anyhow::Result<()> {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?
.block_on(fut))
.build()?;
// Set up tracing inside the runtime so the OTel batch exporter (when enabled)
// can spawn its background tasks on the tokio reactor.
let use_otel = matches!(self.command, Commands::Run(..));
let _otel_guard = runtime.block_on(async { self.setup_logging(use_otel) })?;
// `_otel_guard` is dropped at function exit, flushing pending spans via its Drop impl
runtime.block_on(async {
match self.command {
Commands::BuildInfo(args) => build_info::execute(args)?,
Commands::BondingInformation(args) => bonding_information::execute(args).await?,
Commands::NodeDetails(args) => node_details::execute(args).await?,
Commands::Run(args) => run::execute(*args).await?,
Commands::Migrate(args) => migrate::execute(*args)?,
Commands::Sign(args) => sign::execute(args).await?,
Commands::TestThroughput(args) => test_throughput::execute(args)?,
Commands::UnsafeResetSphinxKeys(args) => reset_sphinx_keys::execute(args).await?,
Commands::Debug(debug) => match debug.command {
DebugCommands::ResetProvidersGatewayDbs(args) => {
debug::reset_providers_dbs::execute(args).await?
}
},
}
Ok::<(), anyhow::Error>(())
})
}
pub(crate) fn execute(self) -> anyhow::Result<()> {
// NOTE: `test_throughput` sets up its own logger as it has to include additional layers
if !matches!(self.command, Commands::TestThroughput(..)) {
crate::logging::setup_tracing_logger()?;
#[cfg(feature = "otel")]
fn build_otel_config(&self) -> Option<crate::logging::OtelConfig> {
if self.otel.otel {
Some(crate::logging::OtelConfig {
endpoint: self.otel.otel_endpoint.clone(),
service_name: "nym-node".to_string(),
ingestion_key: self.otel.otel_key.clone(),
environment: self.otel.otel_env.clone(),
sample_ratio: self.otel.otel_sample_ratio,
export_timeout_secs: self.otel.otel_export_timeout,
})
} else {
None
}
}
match self.command {
Commands::BuildInfo(args) => build_info::execute(args)?,
Commands::BondingInformation(args) => {
{ Self::execute_async(bonding_information::execute(args))? }?
}
Commands::NodeDetails(args) => { Self::execute_async(node_details::execute(args))? }?,
Commands::Run(args) => { Self::execute_async(run::execute(*args))? }?,
Commands::Migrate(args) => migrate::execute(*args)?,
Commands::Sign(args) => { Self::execute_async(sign::execute(args))? }?,
Commands::TestThroughput(args) => test_throughput::execute(args)?,
Commands::UnsafeResetSphinxKeys(args) => {
{ Self::execute_async(reset_sphinx_keys::execute(args))? }?
}
Commands::Debug(debug) => match debug.command {
DebugCommands::ResetProvidersGatewayDbs(args) => {
{ Self::execute_async(debug::reset_providers_dbs::execute(args))? }?
}
},
#[cfg(feature = "otel")]
fn setup_logging(&self, use_otel: bool) -> anyhow::Result<Option<crate::logging::OtelGuard>> {
if matches!(self.command, Commands::TestThroughput(..)) {
return Ok(None);
}
Ok(())
let otel_config = if use_otel {
self.build_otel_config()
} else {
None
};
crate::logging::setup_tracing_logger(otel_config)
}
#[cfg(not(feature = "otel"))]
fn setup_logging(&self, _use_otel: bool) -> anyhow::Result<Option<()>> {
if matches!(self.command, Commands::TestThroughput(..)) {
return Ok(None);
}
crate::logging::setup_tracing_logger()?;
Ok(None)
}
}
+111 -1
View File
@@ -7,6 +7,42 @@ use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, Layer};
/// Configuration for OpenTelemetry OTLP export.
#[cfg(feature = "otel")]
pub(crate) struct OtelConfig {
/// OTLP/gRPC collector endpoint, e.g. `http://localhost:4317`
/// or `https://ingest.eu.signoz.cloud:443` for SigNoz Cloud.
pub endpoint: String,
/// Service name reported to the collector (appears in SigNoz "Services" view).
pub service_name: String,
/// Optional SigNoz Cloud ingestion key for authenticated export.
/// Sent as the `signoz-ingestion-key` gRPC metadata header.
pub ingestion_key: Option<String>,
/// Deployment environment label, e.g. `mainnet`, `sandbox`, `canary`.
/// Attached as the `deployment.environment` OTel resource attribute.
pub environment: String,
/// Trace sampling ratio in 0.0..=1.0 (e.g. 0.1 = 10% of traces). Used to limit cost.
pub sample_ratio: f64,
/// Timeout in seconds for each OTLP export batch. Prevents unbounded blocking.
pub export_timeout_secs: u64,
}
/// Handle returned when OTel is active. Flushes pending spans on drop
/// to prevent telemetry loss during panics or early exits.
#[cfg(feature = "otel")]
pub(crate) struct OtelGuard {
pub provider: opentelemetry_sdk::trace::SdkTracerProvider,
}
#[cfg(feature = "otel")]
impl Drop for OtelGuard {
fn drop(&mut self) {
if let Err(e) = self.provider.shutdown() {
eprintln!("OpenTelemetry shutdown error in Drop: {e}");
}
}
}
pub(crate) fn granual_filtered_env() -> anyhow::Result<EnvFilter> {
fn directive_checked(directive: impl Into<String>) -> anyhow::Result<Directive> {
directive.into().parse().map_err(From::from)
@@ -22,12 +58,86 @@ pub(crate) fn granual_filtered_env() -> anyhow::Result<EnvFilter> {
Ok(filter)
}
/// Initialise the tracing subscriber stack.
///
/// When the `otel` feature is enabled **and** an `OtelConfig` is supplied, an
/// OTLP exporter layer is added and the returned `OtelGuard` must be used to
/// flush pending spans on shutdown.
#[cfg(feature = "otel")]
pub(crate) fn setup_tracing_logger(otel: Option<OtelConfig>) -> anyhow::Result<Option<OtelGuard>> {
let stderr_layer =
default_tracing_fmt_layer(std::io::stderr).with_filter(granual_filtered_env()?);
cfg_if::cfg_if! {if #[cfg(feature = "tokio-console")] {
let console_layer = console_subscriber::spawn();
if let Some(otel_config) = otel {
let (otel_layer, provider) = nym_bin_common::logging::init_otel_layer(
&otel_config.service_name,
&otel_config.endpoint,
otel_config.ingestion_key.as_deref(),
&otel_config.environment,
otel_config.sample_ratio,
otel_config.export_timeout_secs,
).map_err(|e| anyhow::anyhow!(
"failed to initialise OpenTelemetry exporter (endpoint: {}, service: {}): {e}",
otel_config.endpoint,
otel_config.service_name,
))?;
tracing_subscriber::registry()
.with(console_layer)
.with(stderr_layer)
.with(otel_layer)
.init();
Ok(Some(OtelGuard { provider }))
} else {
tracing_subscriber::registry()
.with(console_layer)
.with(stderr_layer)
.init();
Ok(None)
}
} else {
if let Some(otel_config) = otel {
let (otel_layer, provider) = nym_bin_common::logging::init_otel_layer(
&otel_config.service_name,
&otel_config.endpoint,
otel_config.ingestion_key.as_deref(),
&otel_config.environment,
otel_config.sample_ratio,
otel_config.export_timeout_secs,
).map_err(|e| anyhow::anyhow!(
"failed to initialise OpenTelemetry exporter (endpoint: {}, service: {}): {e}",
otel_config.endpoint,
otel_config.service_name,
))?;
tracing_subscriber::registry()
.with(stderr_layer)
.with(otel_layer)
.init();
Ok(Some(OtelGuard { provider }))
} else {
tracing_subscriber::registry()
.with(stderr_layer)
.init();
Ok(None)
}
}}
}
/// Non-OTel variant -- identical subscriber stack without the OTLP layer.
#[cfg(not(feature = "otel"))]
pub(crate) fn setup_tracing_logger() -> anyhow::Result<()> {
let stderr_layer =
default_tracing_fmt_layer(std::io::stderr).with_filter(granual_filtered_env()?);
cfg_if::cfg_if! {if #[cfg(feature = "tokio-console")] {
// instrument tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time
let console_layer = console_subscriber::spawn();
tracing_subscriber::registry()
@@ -4,7 +4,7 @@
use axum::extract::{Query, State};
use nym_http_api_common::{FormattedResponse, OutputParams};
use nym_node_requests::api::v1::metrics::models::{
VerlocNodeResult, VerlocResult, VerlocResultData, VerlocStats,
VerlocMeasurement, VerlocNodeResult, VerlocResult, VerlocResultData, VerlocStats,
};
use nym_verloc::measurements::SharedVerlocStats;
@@ -43,6 +43,12 @@ async fn build_response(verloc_stats: &SharedVerlocStats) -> VerlocStats {
.iter()
.map(|r| VerlocNodeResult {
node_identity: r.node_identity,
latest_measurement: r.latest_measurement.map(|m| VerlocMeasurement {
minimum: m.minimum,
mean: m.mean,
maximum: m.maximum,
standard_deviation: m.standard_deviation,
}),
})
.collect(),
}
+227 -65
View File
@@ -1,6 +1,7 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::node::key_rotation::active_keys::SphinxKeyGuard;
use crate::node::mixnet::shared::SharedData;
use futures::StreamExt;
use nym_noise::connection::Connection;
@@ -20,7 +21,10 @@ use std::net::SocketAddr;
use tokio::net::TcpStream;
use tokio::time::Instant;
use tokio_util::codec::Framed;
use tracing::{debug, error, instrument, trace, warn};
use tracing::{Span, debug, error, instrument, trace, warn};
/// How often (in packets) the stream-level span updates its packet count.
const SPAN_UPDATE_INTERVAL: u64 = 10_000;
struct PendingReplayCheckPackets {
// map of rotation id used for packet creation to the packets
@@ -51,6 +55,10 @@ impl PendingReplayCheckPackets {
.push(packet.packet)
}
fn total_count(&self) -> usize {
self.packets.values().map(|v| v.len()).sum()
}
fn replay_tags(&self) -> HashMap<u32, Vec<&[u8; REPLAY_TAG_SIZE]>> {
let mut replay_tags = HashMap::with_capacity(self.packets.len());
'outer: for (rotation_id, packets) in &self.packets {
@@ -130,20 +138,54 @@ impl ConnectionHandler {
Some(now + delay)
}
#[instrument(
name = "mixnode.forward_packet",
skip(self, mix_packet, delay),
level = "debug",
fields(
remote_addr = %self.remote_address,
delay_ms = tracing::field::Empty,
)
)]
fn handle_forward_packet(&self, now: Instant, mix_packet: MixPacket, delay: Option<Delay>) {
if !self.shared.processing_config.forward_hop_processing_enabled {
trace!("this nym-node does not support forward hop packets");
warn!(
event = "packet.dropped.forward_disabled",
remote_addr = %self.remote_address,
"dropping packet: forward hop processing disabled"
);
self.shared.dropped_forward_packet(self.remote_address.ip());
return;
}
let forward_instant = self.create_delay_target(now, delay);
if let Some(target) = forward_instant {
Span::current().record(
"delay_ms",
target.saturating_duration_since(now).as_millis() as u64,
);
}
self.shared.forward_mix_packet(mix_packet, forward_instant);
}
#[instrument(
name = "mixnode.final_hop",
skip(self, final_hop_data),
level = "debug",
fields(
remote_addr = %self.remote_address,
client_online,
disk_fallback = false,
ack_forwarded = false,
)
)]
async fn handle_final_hop(&self, final_hop_data: ProcessedFinalHop) {
if !self.shared.processing_config.final_hop_processing_enabled {
trace!("this nym-node does not support final hop packets");
warn!(
event = "packet.dropped.final_hop_disabled",
remote_addr = %self.remote_address,
"dropping packet: final hop processing disabled"
);
self.shared
.dropped_final_hop_packet(self.remote_address.ip());
return;
@@ -151,11 +193,13 @@ impl ConnectionHandler {
let client = final_hop_data.destination;
let message = final_hop_data.message;
let has_ack = final_hop_data.forward_ack.is_some();
// if possible attempt to push message directly to the client
match self.shared.try_push_message_to_client(client, message) {
Err(unsent_plaintext) => {
// if that failed, store it on disk (to be 🔥 soon...)
// if that failed, store it on disk
Span::current().record("client_online", false);
match self
.shared
.store_processed_packet_payload(client, unsent_plaintext)
@@ -163,6 +207,7 @@ impl ConnectionHandler {
{
Err(err) => error!("Failed to store client data - {err}"),
Ok(_) => {
Span::current().record("disk_fallback", true);
self.shared
.metrics
.mixnet
@@ -172,13 +217,18 @@ impl ConnectionHandler {
}
}
}
Ok(_) => trace!("Pushed received packet to {client}"),
Ok(_) => {
Span::current().record("client_online", true);
trace!("Pushed received packet to {client}");
}
}
// if we managed to either push message directly to the [online] client or store it at
// its inbox, it means that it must exist at this gateway, hence we can send the
// received ack back into the network
// disk, forward the ack
self.shared.forward_ack_packet(final_hop_data.forward_ack);
if has_ack {
Span::current().record("ack_forwarded", true);
}
}
fn within_deferral_threshold(&self, now: Instant) -> bool {
@@ -206,32 +256,86 @@ impl ConnectionHandler {
if !time_threshold {
warn!(
"{}: time failure - {}",
event = "replay_detection.deferral_exceeded",
threshold_type = "time",
deferred_count = self.pending_packets.total_count(),
deferral_ms = now.saturating_duration_since(self.pending_packets.last_acquired_mutex).as_millis() as u64,
remote_addr = %self.remote_address,
"{}: time deferral threshold exceeded with {} pending packets",
self.remote_address,
self.pending_packets.packets.len()
self.pending_packets.total_count()
)
}
if !count_threshold {
warn!("{}, count failure", self.remote_address)
warn!(
event = "replay_detection.deferral_exceeded",
threshold_type = "count",
deferred_count = self.pending_packets.total_count(),
remote_addr = %self.remote_address,
"{}: count deferral threshold exceeded",
self.remote_address
)
}
time_threshold && count_threshold
}
/// Resolve the sphinx key for the given rotation, recording the rotation
/// label on the current tracing span. Returns `ExpiredKey` if the requested
/// odd/even key has already been rotated out.
fn resolve_rotation_key(
&self,
rotation: SphinxKeyRotation,
) -> Result<SphinxKeyGuard, PacketProcessingError> {
let rotation_label = match rotation {
SphinxKeyRotation::Unknown => "unknown",
SphinxKeyRotation::OddRotation => "odd",
SphinxKeyRotation::EvenRotation => "even",
};
Span::current().record("key_rotation", rotation_label);
match rotation {
SphinxKeyRotation::Unknown => Ok(self.shared.sphinx_keys.primary()),
SphinxKeyRotation::OddRotation => self.shared.sphinx_keys.odd().ok_or_else(|| {
warn!(
event = "packet.dropped.expired_key",
key_rotation = "odd",
remote_addr = %self.remote_address,
"dropping packet: odd key rotation expired"
);
PacketProcessingError::ExpiredKey
}),
SphinxKeyRotation::EvenRotation => self.shared.sphinx_keys.even().ok_or_else(|| {
warn!(
event = "packet.dropped.expired_key",
key_rotation = "even",
remote_addr = %self.remote_address,
"dropping packet: even key rotation expired"
);
PacketProcessingError::ExpiredKey
}),
}
}
#[instrument(
name = "mixnode.sphinx_partial_unwrap",
skip(self, packet),
level = "debug",
fields(key_rotation, unwrap_result,)
)]
fn try_partially_unwrap_packet(
&self,
packet: FramedNymPacket,
) -> Result<PartialyUnwrappedPacketWithKeyRotation, PacketProcessingError> {
// based on the received sphinx key rotation information,
// attempt to choose appropriate key for processing the packet
match packet.header().key_rotation {
let rotation = packet.header().key_rotation;
let result = match rotation {
SphinxKeyRotation::Unknown => {
let primary = self.shared.sphinx_keys.primary();
// Unknown rotation: try primary, fallback to secondary
let primary = self.resolve_rotation_key(rotation)?;
let primary_rotation = primary.rotation_id();
// we have to try both keys, start with the primary as it has higher likelihood of being correct
// if let Ok(partially_unwrapped) = PartiallyUnwrappedPacket::new()
match PartiallyUnwrappedPacket::new(packet, primary.inner().as_ref()) {
Ok(unwrapped_packet) => {
Ok(unwrapped_packet.with_key_rotation(primary_rotation))
@@ -248,25 +352,17 @@ impl ConnectionHandler {
}
}
}
SphinxKeyRotation::OddRotation => {
let Some(odd_key) = self.shared.sphinx_keys.odd() else {
return Err(PacketProcessingError::ExpiredKey);
};
let odd_rotation = odd_key.rotation_id();
PartiallyUnwrappedPacket::new(packet, odd_key.inner().as_ref())
_ => {
let key = self.resolve_rotation_key(rotation)?;
let rotation_id = key.rotation_id();
PartiallyUnwrappedPacket::new(packet, key.inner().as_ref())
.map_err(|(_, err)| err)
.map(|p| p.with_key_rotation(odd_rotation))
.map(|p| p.with_key_rotation(rotation_id))
}
SphinxKeyRotation::EvenRotation => {
let Some(even_key) = self.shared.sphinx_keys.even() else {
return Err(PacketProcessingError::ExpiredKey);
};
let even_rotation = even_key.rotation_id();
PartiallyUnwrappedPacket::new(packet, even_key.inner().as_ref())
.map_err(|(_, err)| err)
.map(|p| p.with_key_rotation(even_rotation))
}
}
};
Span::current().record("unwrap_result", if result.is_ok() { "ok" } else { "err" });
result
}
async fn handle_received_packet_with_replay_detection(
@@ -280,6 +376,12 @@ impl ConnectionHandler {
Ok(unwrapped) => unwrapped,
Err(err) => {
trace!("failed to process received mix packet: {err}");
warn!(
event = "packet.dropped.malformed",
error = %err,
remote_addr = %self.remote_address,
"dropping malformed packet"
);
self.shared
.metrics
.mixnet
@@ -316,7 +418,9 @@ impl ConnectionHandler {
// 3. forward the packet to the relevant sink (if enabled)
match unwrapped_packet {
Err(err) => trace!("failed to process received mix packet: {err}"),
Err(err) => {
trace!("failed to process received mix packet: {err}");
}
Ok(processed_packet) => match processed_packet.processing_data {
MixProcessingResultData::ForwardHop { packet, delay } => {
self.handle_forward_packet(now, packet, delay);
@@ -334,6 +438,7 @@ impl ConnectionHandler {
packets: HashMap<u32, Vec<PartiallyUnwrappedPacket>>,
replay_check_results: HashMap<u32, Vec<bool>>,
) {
let mut replays_detected: u64 = 0;
for (rotation_id, packets) in packets {
let Some(replay_checks) = replay_check_results.get(&rotation_id) else {
// this should never happen, but if we messed up, and it does, don't panic, just drop the packets
@@ -342,6 +447,13 @@ impl ConnectionHandler {
};
for (packet, &replayed) in packets.into_iter().zip(replay_checks) {
let unwrapped_packet = if replayed {
replays_detected += 1;
warn!(
event = "packet.dropped.replay",
remote_addr = %self.remote_address,
rotation_id,
"dropping replayed packet"
);
Err(PacketProcessingError::PacketReplay)
} else {
packet.finalise_unwrapping()
@@ -350,6 +462,13 @@ impl ConnectionHandler {
self.handle_unwrapped_packet(now, unwrapped_packet).await;
}
}
if replays_detected > 0 {
debug!(
replays_detected,
remote_addr = %self.remote_address,
"replay detection batch completed with replays"
);
}
}
async fn handle_pending_packets_batch_no_locking(&mut self, now: Instant) -> bool {
@@ -379,13 +498,22 @@ impl ConnectionHandler {
true
}
#[instrument(
name = "mixnode.replay_check_batch",
skip(self),
level = "debug",
fields(batch_size, mutex_wait_ms,)
)]
async fn handle_pending_packets_batch(&mut self, now: Instant) {
let batch = self.pending_packets.reset(now);
let replay_tags = self.pending_packets.replay_tags();
if replay_tags.is_empty() {
return;
}
let batch_size = self.pending_packets.total_count();
Span::current().record("batch_size", batch_size as u64);
let mutex_start = Instant::now();
let Ok(replay_check_results) = self
.shared
.replay_protection_filter
@@ -396,37 +524,25 @@ impl ConnectionHandler {
self.shared.shutdown_token.cancel();
return;
};
Span::current().record("mutex_wait_ms", mutex_start.elapsed().as_millis() as u64);
let batch = self.pending_packets.reset(now);
self.handle_post_replay_detection_packets(now, batch, replay_check_results)
.await;
}
#[instrument(
name = "mixnode.sphinx_full_unwrap",
skip(self, packet),
level = "debug",
fields(key_rotation)
)]
fn try_full_unwrap_packet(
&self,
packet: FramedNymPacket,
) -> Result<MixProcessingResult, PacketProcessingError> {
// based on the received sphinx key rotation information,
// attempt to choose appropriate key for processing the packet
// NOTE: due to the function signatures, outfox packets will **only** attempt primary key
// if no rotation information is available (but that's fine given outfox is not really in use,
// and by the time we need it, the rotation info should be present)
match packet.header().key_rotation {
SphinxKeyRotation::Unknown => {
process_framed_packet(packet, self.shared.sphinx_keys.primary().inner().as_ref())
}
SphinxKeyRotation::OddRotation => {
let Some(odd_key) = self.shared.sphinx_keys.odd() else {
return Err(PacketProcessingError::ExpiredKey);
};
process_framed_packet(packet, odd_key.inner().as_ref())
}
SphinxKeyRotation::EvenRotation => {
let Some(even_key) = self.shared.sphinx_keys.even() else {
return Err(PacketProcessingError::ExpiredKey);
};
process_framed_packet(packet, even_key.inner().as_ref())
}
}
let key = self.resolve_rotation_key(packet.header().key_rotation)?;
process_framed_packet(packet, key.inner().as_ref())
}
async fn handle_received_packet_with_no_replay_detection(
@@ -456,23 +572,36 @@ impl ConnectionHandler {
}
#[instrument(
skip(self),
name = "mixnode.connection",
skip(self, socket),
level = "debug",
fields(
remote = %self.remote_address
remote = %self.remote_address,
noise_handshake_ms = tracing::field::Empty,
)
)]
pub(crate) async fn handle_connection(&mut self, socket: TcpStream) {
let handshake_start = Instant::now();
let noise_stream = match upgrade_noise_responder(socket, &self.shared.noise_config).await {
Ok(noise_stream) => noise_stream,
Err(err) => {
error!(
"Failed to perform Noise handshake with {:?} - {err}",
self.remote_address
Span::current().record(
"noise_handshake_ms",
handshake_start.elapsed().as_millis() as u64,
);
warn!(
event = "connection.failed.noise",
remote_addr = %self.remote_address,
error = %err,
"Noise responder handshake failed"
);
return;
}
};
Span::current().record(
"noise_handshake_ms",
handshake_start.elapsed().as_millis() as u64,
);
debug!(
"Noise responder handshake completed for {:?}",
self.remote_address
@@ -481,26 +610,58 @@ impl ConnectionHandler {
.await
}
#[instrument(
name = "mixnode.stream",
skip(self, mixnet_connection),
level = "debug",
fields(
remote = %self.remote_address,
packets_processed = 0u64,
exit_reason,
)
)]
pub(crate) async fn handle_stream(
&mut self,
mut mixnet_connection: Framed<Connection<TcpStream>, NymCodec>,
) {
let mut packets_processed: u64 = 0;
loop {
tokio::select! {
biased;
_ = self.shared.shutdown_token.cancelled() => {
trace!("connection handler: received shutdown");
Span::current().record("exit_reason", "shutdown");
break
}
maybe_framed_nym_packet = mixnet_connection.next() => {
match maybe_framed_nym_packet {
Some(Ok(packet)) => self.handle_received_nym_packet(packet).await,
Some(Ok(packet)) => {
self.handle_received_nym_packet(packet).await;
packets_processed += 1;
if packets_processed.is_multiple_of(SPAN_UPDATE_INTERVAL) {
Span::current().record("packets_processed", packets_processed);
}
}
Some(Err(err)) => {
debug!("connection got corrupted with: {err}");
warn!(
event = "connection.corrupted",
remote_addr = %self.remote_address,
error = %err,
packets_processed,
"connection stream corrupted"
);
Span::current().record("exit_reason", "corrupted");
Span::current().record("packets_processed", packets_processed);
return
}
None => {
debug!("connection got closed by the remote");
debug!(
remote_addr = %self.remote_address,
packets_processed,
"connection closed by remote"
);
Span::current().record("exit_reason", "closed_by_remote");
Span::current().record("packets_processed", packets_processed);
return
}
}
@@ -508,6 +669,7 @@ impl ConnectionHandler {
}
}
Span::current().record("packets_processed", packets_processed);
debug!("exiting and closing connection");
}
}
@@ -56,11 +56,17 @@ impl<C, F> PacketForwarder<C, F> {
if let Err(err) = self.mixnet_client.send_without_response(packet) {
if err.kind() == io::ErrorKind::WouldBlock {
// we only know for sure if we dropped a packet if our sending queue was full
// in any other case the connection might still be re-established (or created for the first time)
// and the packet might get sent, but we won't know about it
warn!(
event = "packet.dropped.buffer_full",
next_hop = %next_hop,
"dropping packet: egress connection buffer full (WouldBlock)"
);
self.metrics.mixnet.egress_dropped_forward_packet(next_hop)
} else if err.kind() == io::ErrorKind::NotConnected {
// let's give the benefit of the doubt and assume we manage to establish connection
debug!(
next_hop = %next_hop,
"packet queued for not-yet-connected peer"
);
self.metrics.mixnet.egress_sent_forward_packet(next_hop)
}
} else {
@@ -86,7 +92,11 @@ impl<C, F> PacketForwarder<C, F> {
let next_hop = new_packet.packet.next_hop();
if !self.routing_filter.should_route(next_hop.as_ref().ip()) {
debug!("dropping packet as the egress address does not belong to any known node");
warn!(
event = "packet.dropped.routing_filter",
next_hop = %next_hop,
"dropping packet: egress address does not belong to any known node"
);
self.metrics
.mixnet
.egress_dropped_forward_packet(next_hop.into());
@@ -125,7 +135,7 @@ impl<C, F> PacketForwarder<C, F> {
C: SendWithoutResponse,
F: RoutingFilter,
{
let mut processed = 0;
let mut processed: u64 = 0;
trace!("starting PacketForwarder");
loop {
tokio::select! {
@@ -145,11 +155,29 @@ impl<C, F> PacketForwarder<C, F> {
#[allow(clippy::unwrap_used)]
self.handle_new_packet(new_packet.unwrap());
let channel_len = self.packet_sender.len();
if processed % 1000 == 0 {
let delay_queue_len = self.delay_queue.len();
if processed.is_multiple_of(1000) {
match channel_len {
n if n > 1000 => error!("there are currently {n} mix packets waiting to get forwarded - the node seems to be significantly overloaded!"),
n if n > 500 => warn!("there are currently {n} mix packets waiting to get forwarded - is the node overloaded?"),
n => trace!("there are currently {n} mix packets waiting to get forwarded"),
n if n > 1000 => error!(
event = "forwarder.queue_overload",
channel_depth = n,
delay_queue_depth = delay_queue_len,
packets_processed = processed,
"there are currently {n} mix packets waiting to get forwarded - the node seems to be significantly overloaded!"
),
n if n > 500 => warn!(
event = "forwarder.queue_high",
channel_depth = n,
delay_queue_depth = delay_queue_len,
packets_processed = processed,
"there are currently {n} mix packets waiting to get forwarded - is the node overloaded?"
),
n => trace!(
channel_depth = n,
delay_queue_depth = delay_queue_len,
packets_processed = processed,
"forwarder queue status"
),
}
}
self.update_channel_size_metric(channel_len);
+41 -4
View File
@@ -5,7 +5,8 @@ use nym_gateway::node::{
ActiveClientsStore, GatewayStorage, GatewayStorageError, InboxGatewayStorage,
};
use nym_sphinx_types::DestinationAddressBytes;
use tracing::debug;
use tokio::time::Instant;
use tracing::{debug, warn};
#[derive(Clone)]
pub(crate) struct SharedFinalHopData {
@@ -27,14 +28,37 @@ impl SharedFinalHopData {
message: Vec<u8>,
) -> Result<(), Vec<u8>> {
match self.active_clients.get_sender(client_address) {
None => Err(message),
None => {
debug!(
event = "gateway.push_to_client",
client_found = false,
send_result = "client_not_found",
"client {client_address} not found in active clients"
);
Err(message)
}
Some(sender_channel) => {
let send_start = Instant::now();
if let Err(unsent) = sender_channel.unbounded_send(vec![message]) {
warn!(
event = "gateway.push_to_client",
client_found = true,
send_result = "channel_closed",
send_us = send_start.elapsed().as_micros() as u64,
"client {client_address} channel closed, message not delivered"
);
// the unwrap here is fine as the original message got returned;
// plus we're only ever sending 1 message at the time (for now)
#[allow(clippy::unwrap_used)]
Err(unsent.into_inner().pop().unwrap())
} else {
debug!(
event = "gateway.push_to_client",
client_found = true,
send_result = "ok",
send_us = send_start.elapsed().as_micros() as u64,
"pushed message to client {client_address}"
);
Ok(())
}
}
@@ -46,8 +70,21 @@ impl SharedFinalHopData {
client_address: DestinationAddressBytes,
message: Vec<u8>,
) -> Result<(), GatewayStorageError> {
let start = Instant::now();
debug!("Storing received message for {client_address} on the disk...",);
self.storage.store_message(client_address, message).await
let result = self.storage.store_message(client_address, message).await;
let store_us = start.elapsed().as_micros() as u64;
if result.is_ok() {
debug!(
event = "gateway.disk_store",
store_us, "stored message for {client_address} on disk in {store_us}us"
);
} else {
warn!(
event = "gateway.disk_store_failed",
store_us, "failed to store message for {client_address} on disk after {store_us}us"
);
}
result
}
}
+3
View File
@@ -185,6 +185,7 @@ impl SharedData {
}
pub(super) fn forward_mix_packet(&self, packet: MixPacket, delay_until: Option<Instant>) {
let has_delay = delay_until.is_some();
if self
.mixnet_forwarder
.forward_packet(PacketToForward::new(packet, delay_until))
@@ -192,6 +193,8 @@ impl SharedData {
&& !self.shutdown_token.is_cancelled()
{
error!(
event = "forwarder.channel_send_failed",
has_delay,
"failed to forward sphinx packet on the channel while the process is not going through the shutdown!"
);
self.shutdown_token.cancel();
@@ -1,6 +1,6 @@
{
"name": "@nymproject/mix-fetch-node",
"version": "1.4.1",
"version": "1.4.2",
"description": "This package is a drop-in replacement for `fetch` in NodeJS to send HTTP requests over the Nym Mixnet.",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
@@ -1,6 +1,6 @@
{
"name": "@nymproject/mix-fetch",
"version": "1.4.1",
"version": "1.4.2",
"description": "This package is a drop-in replacement for `fetch` to send HTTP requests over the Nym Mixnet.",
"license": "Apache-2.0",
"author": "Nym Technologies SA",
@@ -4,7 +4,7 @@
[package]
name = "nym-network-requester"
license = "GPL-3.0"
version = "1.1.71"
version = "1.1.72"
authors.workspace = true
edition.workspace = true
rust-version = "1.85"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-cli"
version = "1.1.70"
version = "1.1.71"
authors.workspace = true
edition = "2021"
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nymvisor"
version = "0.1.35"
version = "0.1.36"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+32 -16
View File
@@ -1408,40 +1408,40 @@
}
},
"node_modules/express": {
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"body-parser": "~1.20.3",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"cookie": "~0.7.1",
"cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"finalhandler": "~1.3.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.12",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.19.0",
"serve-static": "1.16.2",
"send": "~0.19.0",
"serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
@@ -1461,6 +1461,22 @@
"dev": true,
"license": "MIT"
},
"node_modules/express/node_modules/qs": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+106 -111
View File
@@ -445,23 +445,23 @@ binary-extensions@^2.0.0:
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
body-parser@1.20.3:
version "1.20.3"
resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz"
integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
body-parser@~1.20.3:
version "1.20.4"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f"
integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==
dependencies:
bytes "3.1.2"
bytes "~3.1.2"
content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.13.0"
raw-body "2.5.2"
destroy "~1.2.0"
http-errors "~2.0.1"
iconv-lite "~0.4.24"
on-finished "~2.4.1"
qs "~6.14.0"
raw-body "~2.5.3"
type-is "~1.6.18"
unpipe "1.0.0"
unpipe "~1.0.0"
bonjour-service@^1.0.11:
version "1.1.1"
@@ -509,9 +509,9 @@ bytes@3.0.0:
resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz"
integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==
bytes@3.1.2:
bytes@~3.1.2:
version "3.1.2"
resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
@@ -609,9 +609,9 @@ connect-history-api-fallback@^2.0.0:
resolved "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz"
integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==
content-disposition@0.5.4:
content-disposition@~0.5.4:
version "0.5.4"
resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
dependencies:
safe-buffer "5.2.1"
@@ -621,15 +621,15 @@ content-type@~1.0.4, content-type@~1.0.5:
resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
cookie-signature@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454"
integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==
cookie@0.7.1:
version "0.7.1"
resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz"
integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
cookie@~0.7.1:
version "0.7.2"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
copy-webpack-plugin@^11.0.0:
version "11.0.0"
@@ -683,7 +683,7 @@ define-lazy-prop@^2.0.0:
resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz"
integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
depd@2.0.0:
depd@2.0.0, depd@~2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
@@ -693,7 +693,7 @@ depd@~1.1.2:
resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz"
integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
destroy@1.2.0:
destroy@1.2.0, destroy@~1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
@@ -741,11 +741,6 @@ electron-to-chromium@^1.5.263:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e"
integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
encodeurl@~2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz"
@@ -852,38 +847,38 @@ execa@^5.0.0:
strip-final-newline "^2.0.0"
express@^4.17.3:
version "4.21.2"
resolved "https://registry.npmjs.org/express/-/express-4.21.2.tgz"
integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==
version "4.22.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.22.1.tgz#1de23a09745a4fffdb39247b344bb5eaff382069"
integrity sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.3"
content-disposition "0.5.4"
body-parser "~1.20.3"
content-disposition "~0.5.4"
content-type "~1.0.4"
cookie "0.7.1"
cookie-signature "1.0.6"
cookie "~0.7.1"
cookie-signature "~1.0.6"
debug "2.6.9"
depd "2.0.0"
encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "1.3.1"
fresh "0.5.2"
http-errors "2.0.0"
finalhandler "~1.3.1"
fresh "~0.5.2"
http-errors "~2.0.0"
merge-descriptors "1.0.3"
methods "~1.1.2"
on-finished "2.4.1"
on-finished "~2.4.1"
parseurl "~1.3.3"
path-to-regexp "0.1.12"
path-to-regexp "~0.1.12"
proxy-addr "~2.0.7"
qs "6.13.0"
qs "~6.14.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
send "0.19.0"
serve-static "1.16.2"
send "~0.19.0"
serve-static "~1.16.2"
setprototypeof "1.2.0"
statuses "2.0.1"
statuses "~2.0.1"
type-is "~1.6.18"
utils-merge "1.0.1"
vary "~1.1.2"
@@ -930,17 +925,17 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
finalhandler@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz"
integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==
finalhandler@~1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88"
integrity sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==
dependencies:
debug "2.6.9"
encodeurl "~2.0.0"
escape-html "~1.0.3"
on-finished "2.4.1"
on-finished "~2.4.1"
parseurl "~1.3.3"
statuses "2.0.1"
statuses "~2.0.2"
unpipe "~1.0.0"
find-up@^4.0.0:
@@ -961,9 +956,9 @@ forwarded@0.2.0:
resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
fresh@0.5.2:
fresh@~0.5.2:
version "0.5.2"
resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
fs-monkey@^1.0.3:
@@ -1121,17 +1116,6 @@ http-deceiver@^1.2.7:
resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz"
integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==
http-errors@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz"
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
dependencies:
depd "2.0.0"
inherits "2.0.4"
setprototypeof "1.2.0"
statuses "2.0.1"
toidentifier "1.0.1"
http-errors@~1.6.2:
version "1.6.3"
resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz"
@@ -1142,6 +1126,17 @@ http-errors@~1.6.2:
setprototypeof "1.1.0"
statuses ">= 1.4.0 < 2"
http-errors@~2.0.0, http-errors@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b"
integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==
dependencies:
depd "~2.0.0"
inherits "~2.0.4"
setprototypeof "~1.2.0"
statuses "~2.0.2"
toidentifier "~1.0.1"
http-parser-js@>=0.5.1:
version "0.5.8"
resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz"
@@ -1172,9 +1167,9 @@ human-signals@^2.1.0:
resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
iconv-lite@0.4.24:
iconv-lite@~0.4.24:
version "0.4.24"
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
dependencies:
safer-buffer ">= 2.1.2 < 3"
@@ -1200,7 +1195,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@~2.0.4:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -1488,9 +1483,9 @@ obuf@^1.0.0, obuf@^1.1.2:
resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz"
integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
on-finished@2.4.1:
on-finished@~2.4.1:
version "2.4.1"
resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
dependencies:
ee-first "1.1.1"
@@ -1575,9 +1570,9 @@ path-parse@^1.0.7:
resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-to-regexp@0.1.12:
path-to-regexp@~0.1.12:
version "0.1.12"
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7"
integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==
path-type@^4.0.0:
@@ -1620,12 +1615,12 @@ punycode@^2.1.0:
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz"
integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
qs@6.13.0:
version "6.13.0"
resolved "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz"
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
qs@~6.14.0:
version "6.14.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.2.tgz#b5634cf9d9ad9898e31fba3504e866e8efb6798c"
integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==
dependencies:
side-channel "^1.0.6"
side-channel "^1.1.0"
queue-microtask@^1.2.2:
version "1.2.3"
@@ -1644,15 +1639,15 @@ range-parser@^1.2.1, range-parser@~1.2.1:
resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.5.2:
version "2.5.2"
resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz"
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
raw-body@~2.5.3:
version "2.5.3"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2"
integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==
dependencies:
bytes "3.1.2"
http-errors "2.0.0"
iconv-lite "0.4.24"
unpipe "1.0.0"
bytes "~3.1.2"
http-errors "~2.0.1"
iconv-lite "~0.4.24"
unpipe "~1.0.0"
readable-stream@^2.0.1:
version "2.3.8"
@@ -1782,24 +1777,24 @@ selfsigned@^2.1.1:
dependencies:
node-forge "^1"
send@0.19.0:
version "0.19.0"
resolved "https://registry.npmjs.org/send/-/send-0.19.0.tgz"
integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
send@~0.19.0, send@~0.19.1:
version "0.19.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29"
integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==
dependencies:
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
encodeurl "~1.0.2"
encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "2.0.0"
fresh "~0.5.2"
http-errors "~2.0.1"
mime "1.6.0"
ms "2.1.3"
on-finished "2.4.1"
on-finished "~2.4.1"
range-parser "~1.2.1"
statuses "2.0.1"
statuses "~2.0.2"
serialize-javascript@^6.0.0, serialize-javascript@^6.0.2:
version "6.0.2"
@@ -1821,22 +1816,22 @@ serve-index@^1.9.1:
mime-types "~2.1.17"
parseurl "~1.3.2"
serve-static@1.16.2:
version "1.16.2"
resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz"
integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
serve-static@~1.16.2:
version "1.16.3"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9"
integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==
dependencies:
encodeurl "~2.0.0"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.19.0"
send "~0.19.1"
setprototypeof@1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz"
integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
setprototypeof@1.2.0:
setprototypeof@1.2.0, setprototypeof@~1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz"
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
@@ -1894,9 +1889,9 @@ side-channel-weakmap@^1.0.2:
object-inspect "^1.13.3"
side-channel-map "^1.0.1"
side-channel@^1.0.6:
side-channel@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9"
integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
dependencies:
es-errors "^1.3.0"
@@ -1960,16 +1955,16 @@ spdy@^4.0.2:
select-hose "^2.0.0"
spdy-transport "^3.0.0"
statuses@2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
"statuses@>= 1.4.0 < 2":
version "1.5.0"
resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz"
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
statuses@~2.0.1, statuses@~2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382"
integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
@@ -2039,9 +2034,9 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
toidentifier@1.0.1:
toidentifier@~1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
type-is@~1.6.18:
@@ -2052,7 +2047,7 @@ type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
unpipe@1.0.0, unpipe@~1.0.0:
unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "mix-fetch-wasm"
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.4.1"
version = "1.4.2"
edition = "2021"
keywords = ["nym", "fetch", "wasm", "webassembly", "privacy"]
license = "Apache-2.0"
+1
View File
@@ -10,6 +10,7 @@ build-go-opt:
build-rust:
taskset -c 0-11 wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/mix-fetch
# taskset -c 0-11 wasm-pack build --scope nymproject --target no-modules --out-dir ../../dist/wasm/mix-fetch
taskset -c 0-11 wasm-opt -Oz -o ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm
build-rust-debug:
@@ -142,7 +142,7 @@ func schemeFetch(req *conv.ParsedRequest) error {
}
}
func dialContext(_ctx context.Context, opts *types.RequestOptions, _network, addr string) (net.Conn, error) {
func dialContext(_ctx context.Context, requestURL string, _network, addr string) (net.Conn, error) {
log.Debug("dialing plain connection to %s", addr)
requestId, err := rust_bridge.RsStartNewMixnetRequest(addr)
@@ -154,12 +154,14 @@ func dialContext(_ctx context.Context, opts *types.RequestOptions, _network, add
}
conn, inj := state.NewFakeConnection(requestId, addr)
state.ActiveRequests.Insert(requestId, addr, inj)
// Use requestURL (full URL) as the mapping key, meaning we can now
// have concurrent requests to different paths on the same domain.
state.ActiveRequests.Insert(requestId, requestURL, inj)
return conn, nil
}
func dialTLSContext(_ctx context.Context, opts *types.RequestOptions, _network, addr string) (net.Conn, error) {
func dialTLSContext(_ctx context.Context, requestURL string, _network, addr string) (net.Conn, error) {
log.Debug("dialing TLS connection to %s", addr)
requestId, err := rust_bridge.RsStartNewMixnetRequest(addr)
@@ -171,7 +173,9 @@ func dialTLSContext(_ctx context.Context, opts *types.RequestOptions, _network,
}
conn, inj := state.NewFakeTlsConn(requestId, addr)
state.ActiveRequests.Insert(requestId, addr, inj)
// Use requestURL (full URL) as the mapping key, meaning we can now
// have concurrent requests to different paths on the same domain.
state.ActiveRequests.Insert(requestId, requestURL, inj)
if err := conn.Handshake(); err != nil {
return nil, err
@@ -180,7 +184,7 @@ func dialTLSContext(_ctx context.Context, opts *types.RequestOptions, _network,
return conn, nil
}
func buildHttpClient(reqCtx *types.RequestContext, opts *types.RequestOptions) *http.Client {
func buildHttpClient(reqCtx *types.RequestContext, opts *types.RequestOptions, requestURL string) *http.Client {
return &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return checkRedirect(reqCtx, opts, req, via)
@@ -188,17 +192,19 @@ func buildHttpClient(reqCtx *types.RequestContext, opts *types.RequestOptions) *
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialContext(ctx, opts, network, addr)
return dialContext(ctx, requestURL, network, addr)
},
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialTLSContext(ctx, opts, network, addr)
return dialTLSContext(ctx, requestURL, network, addr)
},
//TLSClientConfig: &tlsConfig,
DisableKeepAlives: true,
MaxIdleConns: 1,
MaxIdleConnsPerHost: 1,
MaxConnsPerHost: 1,
DisableKeepAlives: true,
// Allow multiple concurrent connections to the same host.
// Previously set to 1.
MaxIdleConns: 10,
MaxIdleConnsPerHost: 10,
MaxConnsPerHost: 10,
},
Timeout: state.RequestTimeout,
}
@@ -270,7 +276,7 @@ func doCorsCheck(reqOpts *types.RequestOptions, resp *http.Response) error {
return errors.New("failed cors check")
}
func performRequest(req *conv.ParsedRequest) (*conv.ResponseWrapper, error) {
func performRequest(req *conv.ParsedRequest, requestURL string) (*conv.ResponseWrapper, error) {
err := mainFetchChecks(req)
if err != nil {
return nil, err
@@ -278,7 +284,7 @@ func performRequest(req *conv.ParsedRequest) (*conv.ResponseWrapper, error) {
reqCtx := &types.RequestContext{}
reqClient := buildHttpClient(reqCtx, req.Options)
reqClient := buildHttpClient(reqCtx, req.Options, requestURL)
if req.Options.ReferrerPolicy == "" {
// 4.1.8
@@ -322,12 +328,15 @@ func performRequest(req *conv.ParsedRequest) (*conv.ResponseWrapper, error) {
func onErrCleanup(url *url.URL) {
// TODO: cancel stuff here.... somehow...
canonicalAddr := canonicalAddr(url)
id := state.ActiveRequests.GetId(canonicalAddr)
// Use full URL string to match the key used in MixFetch for request deduplication.
// Makes sure we clean up the correct request when multiple requests to
// different paths on the same domain are in process.
requestURL := url.String()
id := state.ActiveRequests.GetId(requestURL)
// TODO: can we guarantee that rust is not holding any references to that id (that we don't know on this side)?
if id == 0 {
// if id doesn't exist it [probably] means the error was thrown before the request was properly created
log.Debug("there doesn't seem to exist a request associated with addr %s", canonicalAddr)
log.Debug("there doesn't seem to exist a request associated with URL %s", requestURL)
return
}
state.ActiveRequests.Remove(id)
@@ -341,16 +350,20 @@ func onErrCleanup(url *url.URL) {
func MixFetch(request *conv.ParsedRequest) (any, error) {
log.Info("_mixFetch: start")
canonical := canonicalAddr(request.Request.URL)
if state.ActiveRequests.ExistsCanonical(canonical) {
// TODO: how to deal with it to allow for concurrent requests to say `https://foo.com/index.html` and `https://foo.com/index.js`?
return nil, errors.New(fmt.Sprintf("there is already an active request for %s", canonical))
// Use the full URL (inc path and query params) as the deduplication key.
// Allows concurrent requests to different paths on the same domain
// (e.g., foo.com/index.html and foo.com/index.js) while still preventing
// duplicate requests to the exact same URL.
requestURL := request.Request.URL.String()
if state.ActiveRequests.ExistsCanonical(requestURL) {
return nil, errors.New(fmt.Sprintf("there is already an active request for %s", requestURL))
}
resCh := make(chan *conv.ResponseWrapper)
errCh := make(chan error)
go func(resCh chan *conv.ResponseWrapper, errCh chan error) {
resp, err := performRequest(request)
resp, err := performRequest(request, requestURL)
if err != nil {
errCh <- err
} else {

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