Feature/node tester package (#3634)
* create node tester package dir * start building node tester package * refactor code + build updates * fix up types * add more methods and fix up types * use node tester sdk inside wallet * fix frontend state * Use Node 18 instead of 16 * Fix up dependencies and yarn workspace * Fix lint error * Try to fix up linting error * Remove explorer linting and move it to the existing action * Add wasm-pack build to linting GH Action * change lerna to use workspaces and fix linting errors * Fix up node versions in GitHub Actions and add wasm-pack * fix build:lint target in sdk * exclude all worker.js from eslint for sdk --------- Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
This commit is contained in:
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: install npm
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
|
||||
@@ -16,9 +16,15 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- name: Build
|
||||
run: yarn && yarn build && yarn build:ci
|
||||
- name: Deploy branch to CI www (storybook)
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: 18
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: 18
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: 18
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
node-version: 18
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: Install Yarn
|
||||
run: npm install -g yarn
|
||||
- run: yarn
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
name: Linting for Network Explorer (eslint/prettier)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'explorer/**'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: explorer
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
- name: Run ESLint
|
||||
# GitHub should automatically annotate the PR
|
||||
run: yarn && yarn lint
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
continue-on-error: true
|
||||
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: Matrix - Node Install
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
run: npm install
|
||||
|
||||
@@ -167,7 +167,7 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: Matrix - Node Install
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
run: npm install
|
||||
|
||||
@@ -167,7 +167,7 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: Matrix - Node Install
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
run: npm install
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
||||
@@ -16,19 +16,9 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
working-directory: clients/webassembly
|
||||
- name: Build WASM
|
||||
run: wasm-pack build
|
||||
working-directory: clients/webassembly
|
||||
- name: Build dependencies
|
||||
run: yarn && yarn build
|
||||
- name: Build storybook
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
- name: Node v16
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- name: Install yarn for building application
|
||||
run: yarn install
|
||||
|
||||
@@ -1 +1 @@
|
||||
16
|
||||
18
|
||||
|
||||
@@ -11,6 +11,7 @@ on:
|
||||
- 'nym-connect/mobile/package.json'
|
||||
- 'nym-wallet/src/**'
|
||||
- 'nym-wallet/package.json'
|
||||
- 'explorer/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'ts-packages/**'
|
||||
@@ -21,6 +22,7 @@ on:
|
||||
- 'nym-connect/mobile/package.json'
|
||||
- 'nym-wallet/src/**'
|
||||
- 'nym-wallet/package.json'
|
||||
- 'explorer/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -33,7 +35,7 @@ jobs:
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
- name: Install Rust stable
|
||||
@@ -42,10 +44,6 @@ jobs:
|
||||
toolchain: stable
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
working-directory: clients/webassembly
|
||||
- name: Build WASM
|
||||
run: wasm-pack build
|
||||
working-directory: clients/webassembly
|
||||
- name: Install
|
||||
run: yarn
|
||||
- name: Build packages
|
||||
|
||||
@@ -1 +1 @@
|
||||
16
|
||||
18
|
||||
+1
-1
@@ -1 +1 @@
|
||||
16
|
||||
18
|
||||
|
||||
+2
-8
@@ -1,10 +1,4 @@
|
||||
{
|
||||
"packages": [
|
||||
"ts-packages/*",
|
||||
"nym-wallet",
|
||||
"nym-connect/**",
|
||||
"sdk/typescript/examples/docs",
|
||||
"sdk/typescript/packages/**"
|
||||
],
|
||||
"version": "0.0.0"
|
||||
"version": "0.0.0",
|
||||
"useWorkspaces": true
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
14
|
||||
18
|
||||
@@ -1 +1 @@
|
||||
14
|
||||
18
|
||||
+1
-1
@@ -1 +1 @@
|
||||
16
|
||||
18
|
||||
@@ -31,6 +31,7 @@
|
||||
"@nymproject/mui-theme": "^1.0.0",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@nymproject/types": "^1.0.0",
|
||||
"@nymproject/sdk": "1",
|
||||
"@storybook/react": "^6.5.15",
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
|
||||
@@ -41,7 +42,6 @@
|
||||
"lodash": "^4.17.21",
|
||||
"notistack": "^2.0.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nym-client-wasm": "1.1.1",
|
||||
"qrcode.react": "^1.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { TestStatus } from '@nymproject/sdk';
|
||||
import { Divider, Typography } from '@mui/material';
|
||||
import { TestStatus } from 'src/pages/bonding/node-settings/node-test/types';
|
||||
import { ResultsCard, ResultsCardDetail } from './ResultsCard';
|
||||
|
||||
export const Packets = ({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Card, CardContent, CardHeader, Dialog, Divider } from '@mui/material';
|
||||
import { NymLogo } from '@nymproject/react/logo/NymLogo';
|
||||
import { ResultsCardDetail } from './ResultsCard';
|
||||
import { sleep } from 'src/utils/sleep';
|
||||
import { ResultsCardDetail } from './ResultsCard';
|
||||
|
||||
export const PrintResults = ({
|
||||
packetsSent,
|
||||
@@ -30,7 +30,7 @@ export const PrintResults = ({
|
||||
asyncPrint();
|
||||
window.addEventListener('afterprint', OnPrintRequestComplete);
|
||||
|
||||
() => window.removeEventListener('afterprint', OnPrintRequestComplete);
|
||||
return () => window.removeEventListener('afterprint', OnPrintRequestComplete);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { TestStatus } from '@nymproject/sdk';
|
||||
import { Grid } from '@mui/material';
|
||||
|
||||
import { TestStatus } from 'src/pages/bonding/node-settings/node-test/types';
|
||||
import { Packets } from './Packets';
|
||||
import { NodeScore } from './NodeScore';
|
||||
import { Overview } from './Overview';
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { WasmGateway, WasmMixNode, WasmNymTopology } from 'nym-client-wasm';
|
||||
|
||||
export const createDummyTopology = () => {
|
||||
const l1Mixnode = new WasmMixNode(
|
||||
1,
|
||||
'n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47',
|
||||
'178.79.143.65',
|
||||
1789,
|
||||
'4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz',
|
||||
'8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG',
|
||||
1,
|
||||
'1.10.0',
|
||||
);
|
||||
|
||||
const l2Mixnode = new WasmMixNode(
|
||||
2,
|
||||
'n1z93z44vf8ssvdhujjvxcj4rd5e3lz0l60wdk70',
|
||||
'109.74.197.180',
|
||||
1789,
|
||||
'7sVjiMrPYZrDWRujku9QLxgE8noT7NTgBAqizCsu7AoK',
|
||||
'GepXwRnKZDd8x2nBWAajGGBVvF3mrpVMQBkgfrGuqRCN',
|
||||
2,
|
||||
'1.10.0',
|
||||
);
|
||||
|
||||
const l3Mixnode = new WasmMixNode(
|
||||
3,
|
||||
'n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77',
|
||||
'176.58.101.80',
|
||||
1789,
|
||||
'FoM5Mx9Pxk1g3zEqkS3APgtBeTtTo3M8k7Yu4bV6kK1R',
|
||||
'DeYjrDC2AcQRVFshiKnbUo6bRvPyZ33QGYR2DLeFJ9qD',
|
||||
3,
|
||||
'1.10.0',
|
||||
);
|
||||
|
||||
const gateway = new WasmGateway(
|
||||
'n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts',
|
||||
'85.159.212.96',
|
||||
1789,
|
||||
9000,
|
||||
'336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9',
|
||||
'BtYjoWihiuFihGKQypmpSspbhmWDPxzqeTVSd8ciCpWL',
|
||||
'1.10.1',
|
||||
);
|
||||
|
||||
const mixnodes = new Map();
|
||||
mixnodes.set(1, [l1Mixnode]);
|
||||
mixnodes.set(2, [l2Mixnode]);
|
||||
mixnodes.set(3, [l3Mixnode]);
|
||||
|
||||
const gateways = [gateway];
|
||||
|
||||
return new WasmNymTopology(mixnodes, gateways);
|
||||
};
|
||||
@@ -1,19 +1,20 @@
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { Download } from '@mui/icons-material';
|
||||
import { NodeTestResultResponse, NodeTester, TestStatus, createNodeTesterClient } from '@nymproject/sdk';
|
||||
import { format } from 'date-fns';
|
||||
import { AppContext, useBondingContext } from 'src/context';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { Results } from 'src/components/TestNode/Results';
|
||||
import { ErrorModal } from 'src/components/Modals/ErrorModal';
|
||||
import { PrintResults } from 'src/components/TestNode/PrintResults';
|
||||
import { Download } from '@mui/icons-material';
|
||||
import { format } from 'date-fns';
|
||||
import { NodeTestEvent, NodeTestResult, TestStatus } from './types';
|
||||
import { MAINNET_VALIDATOR_URL, QA_VALIDATOR_URL } from 'src/constants';
|
||||
|
||||
export const NodeTestPage = () => {
|
||||
const [nodeTestWorker, setNodeTestWorker] = useState<Worker>();
|
||||
const [nodeTestClient, setNodeTestClient] = useState<NodeTester>();
|
||||
const [error, setError] = useState<string>();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [results, setResults] = useState<NodeTestResult>();
|
||||
const [results, setResults] = useState<NodeTestResultResponse>();
|
||||
const [printResults, setPrintResults] = useState(false);
|
||||
const [testDate, setTestDate] = useState<string>();
|
||||
|
||||
@@ -23,15 +24,6 @@ export const NodeTestPage = () => {
|
||||
const { network } = useContext(AppContext);
|
||||
const { bondedNode } = useBondingContext();
|
||||
|
||||
const loadWorker = () => {
|
||||
try {
|
||||
const worker: Worker = new Worker(new URL('./worker.ts', import.meta.url));
|
||||
setNodeTestWorker(worker);
|
||||
} catch (e) {
|
||||
setError('Error loading worker');
|
||||
}
|
||||
};
|
||||
|
||||
const handleTestTimeout = () => {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = setTimeout(() => {
|
||||
@@ -43,56 +35,52 @@ export const NodeTestPage = () => {
|
||||
}, 15000);
|
||||
};
|
||||
|
||||
const handleWorkerMessages = (ev: MessageEvent<NodeTestEvent>) => {
|
||||
const eventKind = ev.data.kind;
|
||||
|
||||
if (eventKind === 'Error') {
|
||||
setError(ev.data.args.message);
|
||||
testStateRef.current = 'Stopped';
|
||||
}
|
||||
if (eventKind === 'DisplayTesterResults') {
|
||||
setResults(ev.data.args.result);
|
||||
testStateRef.current = 'Complete';
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const handleTestNode = async () => {
|
||||
setError(undefined);
|
||||
setResults(undefined);
|
||||
setIsLoading(true);
|
||||
setTestDate(format(new Date(), 'dd/MM/yyyy HH:mm'));
|
||||
|
||||
if (nodeTestWorker) {
|
||||
if (nodeTestClient && bondedNode) {
|
||||
setResults(undefined);
|
||||
setTestDate(format(new Date(), 'dd/MM/yyyy HH:mm'));
|
||||
setIsLoading(true);
|
||||
setError(undefined);
|
||||
testStateRef.current = 'Running';
|
||||
nodeTestWorker.postMessage({
|
||||
kind: 'TestPacket',
|
||||
args: {
|
||||
mixnodeIdentity: bondedNode?.identityKey,
|
||||
network,
|
||||
},
|
||||
} as NodeTestEvent);
|
||||
handleTestTimeout();
|
||||
try {
|
||||
const result = await nodeTestClient.tester.startTest(bondedNode.identityKey);
|
||||
setResults(result);
|
||||
testStateRef.current = 'Complete';
|
||||
} catch (e) {
|
||||
setError('Node test failed, please try again');
|
||||
testStateRef.current = 'Stopped';
|
||||
console.log(e);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadWorker();
|
||||
|
||||
return () => {
|
||||
if (nodeTestWorker) {
|
||||
nodeTestWorker.terminate();
|
||||
}
|
||||
};
|
||||
const loadNodeTestClient = useCallback(async () => {
|
||||
try {
|
||||
const nodeTesterId = new Date().toISOString(); // make a new tester id for each session
|
||||
const validator = network === 'MAINNET' ? MAINNET_VALIDATOR_URL : QA_VALIDATOR_URL;
|
||||
const client = await createNodeTesterClient();
|
||||
await client.tester.init(validator, nodeTesterId);
|
||||
setNodeTestClient(client);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setError('Failed to load node tester client, please try again');
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeTestWorker) {
|
||||
nodeTestWorker.addEventListener('message', (e) => handleWorkerMessages(e));
|
||||
}
|
||||
loadNodeTestClient();
|
||||
|
||||
return () => nodeTestWorker?.removeEventListener('message', handleWorkerMessages);
|
||||
}, [nodeTestWorker]);
|
||||
return () => {
|
||||
clearTimeout(timerRef.current);
|
||||
if (nodeTestClient) {
|
||||
nodeTestClient.tester.disconnectFromGateway();
|
||||
nodeTestClient.terminate();
|
||||
}
|
||||
};
|
||||
}, [loadNodeTestClient]);
|
||||
|
||||
return (
|
||||
<Box p={4}>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { Network } from 'src/types';
|
||||
|
||||
export type NodeTestResult = {
|
||||
score: number;
|
||||
sentPackets: number;
|
||||
receivedPackets: number;
|
||||
receivedAcks: number;
|
||||
duplicatePackets: number;
|
||||
duplicateAcks: number;
|
||||
};
|
||||
|
||||
type Error = {
|
||||
kind: 'Error';
|
||||
args: { message: string };
|
||||
};
|
||||
|
||||
type WorkerLoaded = {
|
||||
kind: 'WorkerLoaded';
|
||||
};
|
||||
|
||||
type DisplayTesterResults = {
|
||||
kind: 'DisplayTesterResults';
|
||||
args: {
|
||||
result: NodeTestResult;
|
||||
};
|
||||
};
|
||||
|
||||
type TestPacket = {
|
||||
kind: 'TestPacket';
|
||||
args: {
|
||||
mixnodeIdentity: string;
|
||||
network: Network;
|
||||
};
|
||||
};
|
||||
|
||||
export type TestStatus = 'Stopped' | 'Running' | 'Complete';
|
||||
|
||||
export type NodeTestEvent = Error | DisplayTesterResults | TestPacket | WorkerLoaded;
|
||||
@@ -1,102 +0,0 @@
|
||||
// Copyright 2020-2023 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { NymNodeTester, set_panic_hook, current_network_topology, NodeTestResult } from 'nym-client-wasm';
|
||||
import { Network } from 'src/types';
|
||||
import { NodeTestEvent } from './types';
|
||||
import { MAINNET_VALIDATOR_URL, QA_VALIDATOR_URL } from 'src/constants';
|
||||
|
||||
console.log('Initializing worker');
|
||||
|
||||
const postMessage = (data: NodeTestEvent) => self.postMessage(data);
|
||||
|
||||
postMessage({
|
||||
kind: 'WorkerLoaded',
|
||||
});
|
||||
|
||||
const printAndDisplayTestResult = (result: NodeTestResult) => {
|
||||
result.log_details();
|
||||
|
||||
postMessage({
|
||||
kind: 'DisplayTesterResults',
|
||||
args: {
|
||||
result: {
|
||||
score: result.score(),
|
||||
sentPackets: result.sent_packets,
|
||||
receivedPackets: result.received_packets,
|
||||
receivedAcks: result.received_acks,
|
||||
duplicatePackets: result.duplicate_packets,
|
||||
duplicateAcks: result.duplicate_acks,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const buildTester = async (network: Network) => {
|
||||
const validator = network === 'QA' ? QA_VALIDATOR_URL : MAINNET_VALIDATOR_URL;
|
||||
const topology = await current_network_topology(validator);
|
||||
const nodeTester = await new NymNodeTester(topology, network);
|
||||
|
||||
return nodeTester;
|
||||
};
|
||||
|
||||
async function testNode() {
|
||||
self.onmessage = async (event: MessageEvent<NodeTestEvent>) => {
|
||||
const eventKind = event.data.kind;
|
||||
|
||||
switch (eventKind) {
|
||||
case 'TestPacket': {
|
||||
const { mixnodeIdentity, network } = event.data.args;
|
||||
const nodeTester = await buildTester(network);
|
||||
|
||||
try {
|
||||
console.log(`Testing mixnode identity: ${mixnodeIdentity}, on network: ${network}.`);
|
||||
const result = await nodeTester.test_node(mixnodeIdentity);
|
||||
|
||||
printAndDisplayTestResult(result);
|
||||
|
||||
await nodeTester.disconnect_from_gateway();
|
||||
console.log('Disconnected from gateway');
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : 'Node test error';
|
||||
console.log(errorMessage);
|
||||
|
||||
nodeTester.disconnect_from_gateway();
|
||||
|
||||
postMessage({
|
||||
kind: 'Error',
|
||||
args: { message: errorMessage },
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// sets up better stack traces in case of in-rust panics
|
||||
set_panic_hook();
|
||||
|
||||
// run test on simplified and dedicated tester:
|
||||
await testNode();
|
||||
}
|
||||
|
||||
// Let's get started!
|
||||
main();
|
||||
+6
-4
@@ -10,14 +10,14 @@
|
||||
"nym-connect/**",
|
||||
"explorer",
|
||||
"types",
|
||||
"clients/validator",
|
||||
"clients/webassembly/**"
|
||||
"clients/validator"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "lerna run clean",
|
||||
"build": "run-s build:types build:packages",
|
||||
"build:types": "lerna run --scope @nymproject/types build --stream",
|
||||
"build:packages": "run-s build:packages:theme build:packages:react",
|
||||
"build:packages": "run-s build:packages:theme build:packages:react build:packages:sdk",
|
||||
"build:packages:sdk": "lerna run --scope @nymproject/sdk build:lint",
|
||||
"build:packages:theme": "lerna run --scope @nymproject/mui-theme build",
|
||||
"build:packages:react": "lerna run --scope @nymproject/react build",
|
||||
"build:react-example": "lerna run --scope @nymproject/react-webpack-with-theme-example build --stream",
|
||||
@@ -28,7 +28,9 @@
|
||||
"lint:fix": "lerna run lint:fix --stream",
|
||||
"tsc": "lerna run tsc --stream",
|
||||
"types:lint:fix": "lerna run lint:fix --scope @nymproject/types --scope @nymproject/nym-wallet-app",
|
||||
"audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run"
|
||||
"audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run",
|
||||
"preinstall": "yarn install:copy-placeholders",
|
||||
"install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm"
|
||||
},
|
||||
"devDependencies": {
|
||||
"lerna": "^6.6.2",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@nymproject/nym-client-wasm",
|
||||
"version": "1.0.0",
|
||||
"sideEffects": false
|
||||
}
|
||||
@@ -27,7 +27,7 @@ module.exports = {
|
||||
'plugin:jest/recommended',
|
||||
'plugin:jest/style',
|
||||
],
|
||||
ignorePatterns: ['dist/**/*', 'dist', 'node_modules', '**/wasm/worker.js'],
|
||||
ignorePatterns: ['dist/**/*', 'dist', 'node_modules', '**/worker.js'],
|
||||
rules: {
|
||||
'jest/prefer-strict-equal': 'error',
|
||||
'jest/prefer-to-have-length': 'warn',
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["@babel/env", "@babel/react"]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
# HTML Nyn Node Test
|
||||
|
||||
This is an example of using the Nym Mixnet node tester.
|
||||
|
||||
You can use this example as a seed for a new project.
|
||||
|
||||
## Running the example
|
||||
|
||||
Try out the node tester app by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "@nymproject/sdk-example-node-tester-plain-html",
|
||||
"description": "An example project that uses WASM node tester and plain HTML",
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.14.5",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^16.7.13",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"babel-loader": "^8.3.0",
|
||||
"babel-plugin-root-import": "^5.1.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"css-minimizer-webpack-plugin": "^3.0.2",
|
||||
"dotenv-webpack": "^7.0.3",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-root-import": "^1.0.4",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jest": "^26.1.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^7.2.1",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
"jest": "^27.1.0",
|
||||
"mini-css-extract-plugin": "^2.2.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.8.7",
|
||||
"style-loader": "^3.3.1",
|
||||
"thread-loader": "^3.0.4",
|
||||
"ts-jest": "^27.0.5",
|
||||
"ts-loader": "^9.4.2",
|
||||
"tsconfig-paths-webpack-plugin": "^3.5.2",
|
||||
"typescript": "^4.6.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^4.8.0",
|
||||
"webpack-dev-server": "^4.5.0",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack serve --progress --port 3000",
|
||||
"build": "webpack build --progress --config webpack.prod.js",
|
||||
"build:dev": "webpack build --progress",
|
||||
"build:serve": "npx serve dist",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"tsc": "tsc",
|
||||
"tsc:watch": "tsc --watch",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nym WebAssembly Demo</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>
|
||||
<label>Mixnode Id: </label><input size="85" type="text" id="mixnodeId" value="">
|
||||
</p>
|
||||
<p>
|
||||
<button id="send-button">Test</button>
|
||||
<button id="disconnect-button">Disconnect from Gateway</button>
|
||||
<button id="reconnect-button">Reconnect to Gateway</button>
|
||||
<button id="terminate-button">Terminate worker</button>
|
||||
</p>
|
||||
|
||||
<p>Test a mixnode by entering the mixnode id above and click the Test button</p>
|
||||
|
||||
<hr>
|
||||
<p>
|
||||
<div id="output"></div>
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,116 @@
|
||||
import { createNodeTesterClient, NodeTester } from '@nymproject/sdk';
|
||||
|
||||
let nodeTester: NodeTester | null = null;
|
||||
|
||||
/**
|
||||
* Display messages that have been sent up the websocket. Colours them blue.
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
function displayOutput(message: string, color?: string) {
|
||||
const timestamp = new Date().toISOString().substr(11, 12);
|
||||
|
||||
const sendDiv = document.createElement('div');
|
||||
const paragraph = document.createElement('p');
|
||||
paragraph.setAttribute('style', `color: ${color || 'blue'}`);
|
||||
const paragraphContent = document.createTextNode(`${timestamp} >>> ${message}`);
|
||||
paragraph.appendChild(paragraphContent);
|
||||
|
||||
sendDiv.appendChild(paragraph);
|
||||
document.getElementById('output')?.appendChild(sendDiv);
|
||||
}
|
||||
|
||||
/**
|
||||
* The main entry point
|
||||
*/
|
||||
async function main() {
|
||||
nodeTester = await createNodeTesterClient();
|
||||
|
||||
// add node tester to the Window globally, so that it can be used from the dev tools console
|
||||
(window as any).nodeTester = nodeTester;
|
||||
|
||||
if (!nodeTester) {
|
||||
console.error('Oh no! Could not the node test');
|
||||
return;
|
||||
}
|
||||
|
||||
const nymApiUrl = 'https://validator.nymtech.net/api';
|
||||
const nodeTesterId = new Date().toISOString(); // make a new tester id for each session
|
||||
await nodeTester.tester.init(nymApiUrl, nodeTesterId);
|
||||
|
||||
const mixnodes = await (await fetch(`${nymApiUrl}/v1/mixnodes/active`)).json();
|
||||
|
||||
const exampleMixnodeIdentityKey = mixnodes[0].bond_information.mix_node.identity_key;
|
||||
|
||||
const sendButton: HTMLButtonElement = document.querySelector('#send-button') as HTMLButtonElement;
|
||||
const reconnectButton: HTMLButtonElement = document.querySelector('#reconnect-button') as HTMLButtonElement;
|
||||
const disconnectButton: HTMLButtonElement = document.querySelector('#disconnect-button') as HTMLButtonElement;
|
||||
const terminateButton: HTMLButtonElement = document.querySelector('#terminate-button') as HTMLButtonElement;
|
||||
|
||||
const mixnodeIdInput = document.getElementById('mixnodeId') as HTMLFormElement;
|
||||
|
||||
mixnodeIdInput.value = exampleMixnodeIdentityKey;
|
||||
|
||||
reconnectButton.onclick = async function () {
|
||||
try {
|
||||
await nodeTester?.tester.reconnectToGateway();
|
||||
} catch (e: any) {
|
||||
console.error('Error', e);
|
||||
displayOutput(`ERROR: ${e.message}`, 'red');
|
||||
}
|
||||
};
|
||||
|
||||
disconnectButton.onclick = async function () {
|
||||
try {
|
||||
await nodeTester?.tester.disconnectFromGateway();
|
||||
} catch (e: any) {
|
||||
console.error('Error', e);
|
||||
displayOutput(`ERROR: ${e.message}`, 'red');
|
||||
}
|
||||
};
|
||||
|
||||
terminateButton.onclick = async function () {
|
||||
try {
|
||||
await nodeTester?.terminate();
|
||||
} catch (e: any) {
|
||||
console.error('Error', e);
|
||||
displayOutput(`ERROR: ${e.message}`, 'red');
|
||||
}
|
||||
};
|
||||
|
||||
if (sendButton) {
|
||||
sendButton.onclick = async function () {
|
||||
const mixnodeId = mixnodeIdInput.value;
|
||||
if (!nodeTester) {
|
||||
displayOutput('ERROR: The node tester is not defined');
|
||||
console.error('The node tester is not defined');
|
||||
return;
|
||||
}
|
||||
if (!mixnodeId) {
|
||||
displayOutput('ERROR: No mix id specified');
|
||||
console.error('No mix id specified');
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodeTester && mixnodeId) {
|
||||
displayOutput('Starting test...');
|
||||
try {
|
||||
const response = await nodeTester.tester.startTest(mixnodeId);
|
||||
displayOutput('Done!');
|
||||
if (response) {
|
||||
displayOutput(JSON.stringify(response, null, 2), 'green');
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error('Error', e);
|
||||
displayOutput(`ERROR: ${e.message}`, 'red');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// wait for the html to load
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// let's do this!
|
||||
main();
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "build", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"outDir": "./dist",
|
||||
"declaration": false
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "build", "dist", "**/*.stories.*", "**/*.test.*", "**/*.spec.*"]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
const path = require('path');
|
||||
const { mergeWithRules } = require('webpack-merge');
|
||||
const { webpackCommon } = require('../../.webpack/webpack.base');
|
||||
|
||||
module.exports = mergeWithRules({
|
||||
module: {
|
||||
rules: {
|
||||
test: 'match',
|
||||
use: 'replace',
|
||||
},
|
||||
},
|
||||
})(
|
||||
webpackCommon(__dirname, [
|
||||
{
|
||||
inject: true,
|
||||
filename: 'index.html',
|
||||
template: path.resolve(__dirname, 'src/index.html'),
|
||||
chunks: ['index'],
|
||||
},
|
||||
]),
|
||||
{
|
||||
entry: {
|
||||
index: path.resolve(__dirname, 'src/index.ts'),
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
publicPath: '/',
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,67 @@
|
||||
const { mergeWithRules } = require('webpack-merge');
|
||||
const webpack = require('webpack');
|
||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
const ReactRefreshTypeScript = require('react-refresh-typescript');
|
||||
const commonConfig = require('./webpack.common');
|
||||
|
||||
module.exports = mergeWithRules({
|
||||
module: {
|
||||
rules: {
|
||||
test: 'match',
|
||||
use: 'replace',
|
||||
},
|
||||
},
|
||||
})(commonConfig, {
|
||||
mode: 'development',
|
||||
devtool: 'inline-source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
options: {
|
||||
getCustomTransformers: () => ({
|
||||
before: [ReactRefreshTypeScript()],
|
||||
}),
|
||||
// `ts-loader` does not work with HMR unless `transpileOnly` is used.
|
||||
// If you need type checking, `ForkTsCheckerWebpackPlugin` is an alternative.
|
||||
transpileOnly: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new ReactRefreshWebpackPlugin(),
|
||||
|
||||
// this can be included automatically by the dev server, however build mode fails if missing
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
],
|
||||
|
||||
target: 'web',
|
||||
|
||||
devServer: {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
},
|
||||
historyApiFallback: true,
|
||||
},
|
||||
|
||||
// recommended for faster rebuild
|
||||
optimization: {
|
||||
runtimeChunk: true,
|
||||
removeAvailableModules: false,
|
||||
removeEmptyChunks: false,
|
||||
splitChunks: false,
|
||||
},
|
||||
|
||||
cache: {
|
||||
type: 'filesystem',
|
||||
buildDependencies: {
|
||||
// restart on config change
|
||||
config: ['./webpack.config.js'],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
const { mergeWithRules } = require('webpack-merge');
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const commonConfig = require('./webpack.common');
|
||||
|
||||
module.exports = mergeWithRules({
|
||||
module: {
|
||||
rules: {
|
||||
test: 'match',
|
||||
use: 'replace',
|
||||
},
|
||||
},
|
||||
})(commonConfig, {
|
||||
mode: 'production',
|
||||
|
||||
// TODO: no source maps, add back
|
||||
devtool: false,
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
optimization: {
|
||||
minimizer: ['...', new CssMinimizerPlugin()],
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].[contenthash].css',
|
||||
}),
|
||||
],
|
||||
output: {
|
||||
pathinfo: false,
|
||||
filename: '[name].[contenthash].js',
|
||||
},
|
||||
});
|
||||
@@ -1,20 +1,10 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"outDir": "./dist",
|
||||
"declaration": false
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"build",
|
||||
"dist",
|
||||
"**/*.stories.*",
|
||||
"**/*.test.*",
|
||||
"**/*.spec.*"
|
||||
]
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "build", "dist", "**/*.stories.*", "**/*.test.*", "**/*.spec.*"]
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
src/mixnet/wasm/worker.js
|
||||
src/mixnet/node-tester/worker.js
|
||||
@@ -26,10 +26,12 @@
|
||||
"build:only-this": "scripts/build-prod.sh",
|
||||
"prebuild:dev": "yarn build:dependencies",
|
||||
"build:dev": "yarn build:dev:only-this",
|
||||
"build:dev:only-this": "scripts/build.sh"
|
||||
"build:dev:only-this": "scripts/build.sh",
|
||||
"build:lint": "run-s build:dependencies:nym-client-wasm build:dev:only-this"
|
||||
},
|
||||
"dependencies": {
|
||||
"@npmcli/node-gyp": "^3.0.0",
|
||||
"@nymproject/nym-client-wasm": "1",
|
||||
"comlink": "^4.3.1",
|
||||
"lerna": "^6.6.2",
|
||||
"node-gyp": "^9.3.1"
|
||||
|
||||
@@ -3,9 +3,7 @@ import resolve from '@rollup/plugin-node-resolve';
|
||||
import { wasm } from '@rollup/plugin-wasm';
|
||||
import webWorkerLoader from 'rollup-plugin-web-worker-loader';
|
||||
|
||||
const extensions = [
|
||||
'.js', '.jsx', '.ts', '.tsx',
|
||||
];
|
||||
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
|
||||
|
||||
export default {
|
||||
input: 'src/index.ts',
|
||||
@@ -17,6 +15,9 @@ export default {
|
||||
webWorkerLoader({ targetPlatform: 'browser', inline: true }),
|
||||
resolve({ extensions }),
|
||||
wasm({ maxFileSize: 10000000, targetEnv: 'browser' }),
|
||||
typescript({ compilerOptions: { outDir: 'dist/cjs', target: 'es5' }, exclude: 'mixnet/wasm/worker.ts', }),
|
||||
typescript({
|
||||
compilerOptions: { outDir: 'dist/cjs', target: 'es5' },
|
||||
exclude: ['mixnet/wasm/worker.ts', 'mixnet/node-tester/worker.ts'],
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -2,9 +2,7 @@ import typescript from '@rollup/plugin-typescript';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import webWorkerLoader from 'rollup-plugin-web-worker-loader';
|
||||
|
||||
const extensions = [
|
||||
'.js', '.jsx', '.ts', '.tsx',
|
||||
];
|
||||
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
|
||||
|
||||
export default {
|
||||
input: 'src/index.ts',
|
||||
@@ -15,6 +13,9 @@ export default {
|
||||
plugins: [
|
||||
webWorkerLoader({ targetPlatform: 'browser', inline: true }),
|
||||
resolve({ extensions }),
|
||||
typescript({ exclude: 'mixnet/wasm/worker.ts', compilerOptions: { outDir: 'dist/esm' } }),
|
||||
typescript({
|
||||
exclude: ['mixnet/wasm/worker.ts', 'mixnet/node-tester/worker.ts'],
|
||||
compilerOptions: { outDir: 'dist/esm' },
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import { wasm } from '@rollup/plugin-wasm';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
|
||||
const extensions = ['.js', '.jsx', '.ts', '.tsx'];
|
||||
|
||||
export default {
|
||||
input: 'src/mixnet/node-tester/worker.ts',
|
||||
output: {
|
||||
dir: 'dist',
|
||||
format: 'cjs',
|
||||
},
|
||||
plugins: [
|
||||
resolve({ extensions }),
|
||||
// this is some nasty monkey patching that removes the WASM URL (because it is handled by the `wasm` plugin)
|
||||
replace({
|
||||
values: { "input = new URL('nym_client_wasm_bg.wasm', import.meta.url);": 'input = undefined;' },
|
||||
delimiters: ['', ''],
|
||||
preventAssignment: true,
|
||||
}),
|
||||
// force the wasm plugin to embed the wasm bundle - this means no downstream bundlers have to worry about handling it
|
||||
wasm({ maxFileSize: 10000000, targetEnv: 'browser' }),
|
||||
typescript({ compilerOptions: { declaration: false, target: 'es5' } }),
|
||||
],
|
||||
};
|
||||
@@ -7,7 +7,7 @@ set -o pipefail
|
||||
rm -rf dist || true
|
||||
|
||||
#-------------------------------------------------------
|
||||
# WEB WORKER
|
||||
# WEB WORKER (WASM client)
|
||||
#-------------------------------------------------------
|
||||
# The web worker needs to be bundled because the WASM bundle needs to be loaded synchronously and all dependencies
|
||||
# must be included in the worker script (because it is not loaded as an ES Module)
|
||||
@@ -19,6 +19,19 @@ rollup -c rollup-worker.config.mjs
|
||||
rm -f src/mixnet/wasm/worker.js
|
||||
mv dist/worker.js src/mixnet/wasm/worker.js
|
||||
|
||||
#-------------------------------------------------------
|
||||
# WEB WORKER (Node tester)
|
||||
#-------------------------------------------------------
|
||||
# The web worker needs to be bundled because the WASM bundle needs to be loaded synchronously and all dependencies
|
||||
# must be included in the worker script (because it is not loaded as an ES Module)
|
||||
|
||||
# build the worker
|
||||
rollup -c rollup-node-tester-worker.config.mjs
|
||||
|
||||
# move it next to the Typescript `mixnet/node-tester/index.ts` so it can be inlined by rollup
|
||||
rm -f src/mixnet/node-tester/worker.js
|
||||
mv dist/worker.js src/mixnet/node-tester/worker.js
|
||||
|
||||
#-------------------------------------------------------
|
||||
# ESM
|
||||
#-------------------------------------------------------
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from './wasm';
|
||||
export * from './wasm/types';
|
||||
export * from './node-tester';
|
||||
export * from './node-tester/types';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
const QA_VALIDATOR_URL = 'https://qa-nym-api.qa.nymte.ch/api';
|
||||
const QWERTY_VALIDATOR_URL = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
const MAINNET_VALIDATOR_URL = 'https://validator.nymtech.net/api/';
|
||||
|
||||
export { QA_VALIDATOR_URL, QWERTY_VALIDATOR_URL, MAINNET_VALIDATOR_URL };
|
||||
@@ -0,0 +1,50 @@
|
||||
import InlineWasmWebWorker from 'web-worker:./worker';
|
||||
import * as Comlink from 'comlink';
|
||||
import { INodeTesterWorkerAsync, NodeTester, NodeTesterEventKinds } from './types';
|
||||
|
||||
/**
|
||||
* Client for the Nym node tester.
|
||||
*/
|
||||
export const createNodeTesterClient = async (): Promise<NodeTester> => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
const worker = await createWorker();
|
||||
|
||||
// let comlink handle interop with the web worker
|
||||
const tester = Comlink.wrap<INodeTesterWorkerAsync>(worker);
|
||||
|
||||
// expose the method to terminate the worker
|
||||
const terminate = async () => {
|
||||
worker.terminate();
|
||||
};
|
||||
|
||||
return { tester, terminate };
|
||||
};
|
||||
|
||||
/**
|
||||
* Async method to create a web worker that runs the Nym node tester client on another thread. It will only return once the worker
|
||||
* has passed back a `Loaded` event to the calling thread.
|
||||
*
|
||||
* @return The instance of the web worker.
|
||||
*/
|
||||
|
||||
const createWorker = async () =>
|
||||
new Promise<Worker>((resolve, reject) => {
|
||||
// rollup will inline the built worker script, so that when the SDK is used in
|
||||
// other projects, they will not need to mess around trying to bundle it
|
||||
// however, it will make this SDK bundle bigger because of Base64 inline data
|
||||
const worker = new InlineWasmWebWorker();
|
||||
|
||||
worker.addEventListener('error', reject);
|
||||
worker.addEventListener(
|
||||
'message',
|
||||
(msg) => {
|
||||
worker.removeEventListener('error', reject);
|
||||
if (msg.data?.kind === NodeTesterEventKinds.Loaded) {
|
||||
resolve(worker);
|
||||
} else {
|
||||
reject(msg);
|
||||
}
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
export interface INodeTesterWorkerAsync {
|
||||
init: (validatorUrl: string, nodeTesterId?: string) => Promise<void>;
|
||||
reconnectToGateway: () => Promise<void>;
|
||||
disconnectFromGateway: () => Promise<void>;
|
||||
startTest: (mixnodeIdentityKey: string) => Promise<NodeTestResultResponse | undefined>;
|
||||
}
|
||||
|
||||
export interface INodeTesterWorkerDisposableAsync {
|
||||
terminate: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface NodeTester extends INodeTesterWorkerDisposableAsync {
|
||||
tester: INodeTesterWorkerAsync;
|
||||
}
|
||||
|
||||
export enum NodeTesterEventKinds {
|
||||
Loaded = 'Loaded',
|
||||
Connected = 'Connected',
|
||||
}
|
||||
|
||||
export interface NodeTesterLoadedEvent {
|
||||
kind: NodeTesterEventKinds.Loaded;
|
||||
args: {
|
||||
loaded: true;
|
||||
};
|
||||
}
|
||||
|
||||
export type Network = 'QA' | 'SANDBOX' | 'MAINNET';
|
||||
|
||||
export type NodeTestResultResponse = {
|
||||
score: number;
|
||||
sentPackets: number;
|
||||
receivedPackets: number;
|
||||
receivedAcks: number;
|
||||
duplicatePackets: number;
|
||||
duplicateAcks: number;
|
||||
};
|
||||
|
||||
export type Error = {
|
||||
kind: 'Error';
|
||||
args: { message: string };
|
||||
};
|
||||
|
||||
export type WorkerLoaded = {
|
||||
kind: 'WorkerLoaded';
|
||||
};
|
||||
|
||||
export type DisplayTesterResults = {
|
||||
kind: 'DisplayTesterResults';
|
||||
args: {
|
||||
result: NodeTestResultResponse;
|
||||
};
|
||||
};
|
||||
|
||||
export type TestPacket = {
|
||||
kind: 'TestPacket';
|
||||
args: {
|
||||
mixnodeIdentity: string;
|
||||
network: Network;
|
||||
};
|
||||
};
|
||||
|
||||
export type TestStatus = 'Stopped' | 'Running' | 'Complete';
|
||||
|
||||
export type NodeTestEvent = Error | DisplayTesterResults | TestPacket | WorkerLoaded;
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2020-2023 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import * as Comlink from 'comlink';
|
||||
|
||||
//
|
||||
// Rollup will replace wasmBytes with a function that loads the WASM bundle from a base64 string embedded in the output.
|
||||
//
|
||||
// Doing it this way, saves having to support a large variety of bundlers and their quirks.
|
||||
//
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import wasmBytes from '@nymproject/nym-client-wasm/nym_client_wasm_bg.wasm';
|
||||
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import init, { NymNodeTester, current_network_topology, NodeTestResult } from '@nymproject/nym-client-wasm';
|
||||
import type { INodeTesterWorkerAsync, NodeTesterLoadedEvent } from './types';
|
||||
import { NodeTesterEventKinds } from './types';
|
||||
|
||||
/**
|
||||
* Helper method to send typed messages.
|
||||
* @param event The strongly typed message to send back to the calling thread.
|
||||
*/
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const postMessageWithType = <E>(event: E) => self.postMessage(event);
|
||||
|
||||
console.log('[Nym WASM client] Starting Nym WASM web worker...');
|
||||
|
||||
const buildTester = async (validatorUrl: string, nodeTesterId?: string): Promise<NymNodeTester> => {
|
||||
const topology = await current_network_topology(validatorUrl);
|
||||
return new NymNodeTester(topology, nodeTesterId);
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const importResult = await init(wasmBytes());
|
||||
importResult.set_panic_hook();
|
||||
|
||||
let nodeTester: NymNodeTester | null = null;
|
||||
|
||||
const webWorker: INodeTesterWorkerAsync = {
|
||||
async init(validatorUrl: string, nodeTesterId?: string) {
|
||||
nodeTester = await buildTester(validatorUrl, nodeTesterId);
|
||||
},
|
||||
async reconnectToGateway() {
|
||||
if (!nodeTester) {
|
||||
throw Error('Please run init first');
|
||||
}
|
||||
await nodeTester.reconnect_to_gateway();
|
||||
},
|
||||
async disconnectFromGateway() {
|
||||
if (!nodeTester) {
|
||||
throw Error('Please run init first');
|
||||
}
|
||||
await nodeTester.disconnect_from_gateway();
|
||||
},
|
||||
async startTest(mixnodeIdentityKey: string) {
|
||||
if (!nodeTester) {
|
||||
throw Error('Please run init first');
|
||||
}
|
||||
console.log(`Testing mixnode with identity key = ${mixnodeIdentityKey}`);
|
||||
// TODO: fix typing in Rust code
|
||||
const result = (await nodeTester.test_node(mixnodeIdentityKey)) as NodeTestResult | undefined;
|
||||
|
||||
// return early if there was an error
|
||||
if (!result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// log the result in the worker so that the packet stats are visible somewhere and extract the score
|
||||
result.log_details();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { duplicate_acks, duplicate_packets, received_acks, received_packets, sent_packets } = result;
|
||||
|
||||
// construct the response to avoid any weird proxy effects
|
||||
return {
|
||||
score: result.score(),
|
||||
sentPackets: sent_packets,
|
||||
receivedPackets: received_packets,
|
||||
receivedAcks: received_acks,
|
||||
duplicatePackets: duplicate_packets,
|
||||
duplicateAcks: duplicate_acks,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// start comlink listening for messages and handle them above
|
||||
Comlink.expose(webWorker);
|
||||
|
||||
// notify any listeners that the web worker has loaded and is ready for testing
|
||||
postMessageWithType<NodeTesterLoadedEvent>({ kind: NodeTesterEventKinds.Loaded, args: { loaded: true } });
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
*/
|
||||
export interface TopologyWasm {
|
||||
free(): void;
|
||||
/**
|
||||
* Specifies whether the client should not refresh the network topology after obtaining
|
||||
* the first valid instance.
|
||||
* Supersedes `topology_refresh_rate_ms`.
|
||||
*/
|
||||
disable_refreshing: boolean;
|
||||
/**
|
||||
* The uniform delay every which clients are querying the directory server
|
||||
* to try to obtain a compatible network topology to send sphinx packets through.
|
||||
*/
|
||||
topology_refresh_rate_ms: bigint;
|
||||
/**
|
||||
* During topology refresh, test packets are sent through every single possible network
|
||||
* path. This timeout determines waiting period until it is decided that the packet
|
||||
* did not reach its destination.
|
||||
*/
|
||||
topology_resolution_timeout_ms: bigint;
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export interface TrafficWasm {
|
||||
free(): void;
|
||||
/**
|
||||
* The parameter of Poisson distribution determining how long, on average,
|
||||
* sent packet is going to be delayed at any given mix node.
|
||||
* So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
* until the packet reaches its destination.
|
||||
*/
|
||||
average_packet_delay_ms: bigint;
|
||||
/**
|
||||
* Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
* poisson distribution.
|
||||
*/
|
||||
disable_main_poisson_packet_distribution: boolean;
|
||||
/**
|
||||
* The parameter of Poisson distribution determining how long, on average,
|
||||
* it is going to take another 'real traffic stream' message to be sent.
|
||||
* If no real packets are available and cover traffic is enabled,
|
||||
* a loop cover message is sent instead in order to preserve the rate.
|
||||
*/
|
||||
message_sending_average_delay_ms: bigint;
|
||||
/**
|
||||
* Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
*/
|
||||
use_extended_packet_size: boolean;
|
||||
/**
|
||||
* Controls whether the sent packets should use outfox as opposed to the default sphinx.
|
||||
*/
|
||||
use_outfox: boolean;
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export interface WasmGateway {
|
||||
free(): void;
|
||||
/**
|
||||
*/
|
||||
clients_port: number;
|
||||
/**
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
*/
|
||||
identity_key: string;
|
||||
/**
|
||||
*/
|
||||
mix_port: number;
|
||||
/**
|
||||
*/
|
||||
owner: string;
|
||||
/**
|
||||
*/
|
||||
sphinx_key: string;
|
||||
/**
|
||||
*/
|
||||
version: string;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export interface ReplySurbsWasm {
|
||||
free(): void;
|
||||
/**
|
||||
* Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
|
||||
*/
|
||||
maximum_allowed_reply_surb_request_size: number;
|
||||
/**
|
||||
* Defines maximum amount of time given reply key is going to be valid for.
|
||||
* This is going to be superseded by key rotation once implemented.
|
||||
*/
|
||||
maximum_reply_key_age_ms: bigint;
|
||||
/**
|
||||
* Defines maximum amount of time given reply surb is going to be valid for.
|
||||
* This is going to be superseded by key rotation once implemented.
|
||||
*/
|
||||
maximum_reply_surb_age_ms: bigint;
|
||||
/**
|
||||
* Defines maximum amount of time the client is going to wait for reply surbs before
|
||||
* deciding it's never going to get them and would drop all pending messages
|
||||
*/
|
||||
maximum_reply_surb_drop_waiting_period_ms: bigint;
|
||||
/**
|
||||
* Defines the maximum number of reply surbs the client would request.
|
||||
*/
|
||||
maximum_reply_surb_request_size: number;
|
||||
/**
|
||||
* Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
|
||||
* for more even though in theory they wouldn't need to.
|
||||
*/
|
||||
maximum_reply_surb_rerequest_waiting_period_ms: bigint;
|
||||
/**
|
||||
* Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
|
||||
*/
|
||||
maximum_reply_surb_storage_threshold: number;
|
||||
/**
|
||||
* Defines the minimum number of reply surbs the client would request.
|
||||
*/
|
||||
minimum_reply_surb_request_size: number;
|
||||
/**
|
||||
* Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
|
||||
* It can only allow to go below that value if its to request additional reply surbs.
|
||||
*/
|
||||
minimum_reply_surb_storage_threshold: number;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export interface GatewayConnectionWasm {
|
||||
free(): void;
|
||||
/**
|
||||
* How long we're willing to wait for a response to a message sent to the gateway,
|
||||
* before giving up on it.
|
||||
*/
|
||||
gateway_response_timeout_ms: bigint;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export interface AcknowledgementsWasm {
|
||||
free(): void;
|
||||
/**
|
||||
* Value added to the expected round trip time of an acknowledgement packet before
|
||||
* it is assumed it was lost and retransmission of the data packet happens.
|
||||
* In an ideal network with 0 latency, this value would have been 0.
|
||||
*/
|
||||
ack_wait_addition_ms: bigint;
|
||||
/**
|
||||
* Value multiplied with the expected round trip time of an acknowledgement packet before
|
||||
* it is assumed it was lost and retransmission of the data packet happens.
|
||||
* In an ideal network with 0 latency, this value would have been 1.
|
||||
*/
|
||||
ack_wait_multiplier: number;
|
||||
/**
|
||||
* The parameter of Poisson distribution determining how long, on average,
|
||||
* sent acknowledgement is going to be delayed at any given mix node.
|
||||
* So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
* until the packet reaches its destination.
|
||||
*/
|
||||
average_ack_delay_ms: bigint;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export interface CoverTrafficWasm {
|
||||
free(): void;
|
||||
/**
|
||||
* Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
|
||||
* Only applicable if `secondary_packet_size` is enabled.
|
||||
*/
|
||||
cover_traffic_primary_size_ratio: number;
|
||||
/**
|
||||
* Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
* (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
|
||||
*/
|
||||
disable_loop_cover_traffic_stream: boolean;
|
||||
/**
|
||||
* The parameter of Poisson distribution determining how long, on average,
|
||||
* it is going to take for another loop cover traffic message to be sent.
|
||||
*/
|
||||
loop_cover_traffic_average_delay_ms: bigint;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
free(): void;
|
||||
}
|
||||
|
||||
export interface DebugWasm {
|
||||
free(): void;
|
||||
/**
|
||||
* Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
|
||||
*/
|
||||
acknowledgements: AcknowledgementsWasm;
|
||||
/**
|
||||
* Defines all configuration options related to cover traffic stream(s).
|
||||
*/
|
||||
cover_traffic: CoverTrafficWasm;
|
||||
/**
|
||||
* Defines all configuration options related to the gateway connection.
|
||||
*/
|
||||
gateway_connection: GatewayConnectionWasm;
|
||||
/**
|
||||
* Defines all configuration options related to reply SURBs.
|
||||
*/
|
||||
reply_surbs: ReplySurbsWasm;
|
||||
/**
|
||||
* Defines all configuration options related topology, such as refresh rates or timeouts.
|
||||
*/
|
||||
topology: TopologyWasm;
|
||||
/**
|
||||
* Defines all configuration options related to traffic streams.
|
||||
*/
|
||||
traffic: TrafficWasm;
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export interface GatewayEndpointConfig {
|
||||
free(): void;
|
||||
/**
|
||||
* gateway_id specifies ID of the gateway to which the client should send messages.
|
||||
* If initially omitted, a random gateway will be chosen from the available topology.
|
||||
*/
|
||||
gateway_id: string;
|
||||
/**
|
||||
* Address of the gateway listener to which all client requests should be sent.
|
||||
*/
|
||||
gateway_listener: string;
|
||||
/**
|
||||
* Address of the gateway owner to which the client should send messages.
|
||||
*/
|
||||
gateway_owner: string;
|
||||
}
|
||||
/**
|
||||
*/
|
||||
export interface NymClient {
|
||||
free(): void;
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
self_address(): string;
|
||||
/**
|
||||
* The simplest message variant where no additional information is attached.
|
||||
* You're simply sending your `data` to specified `recipient` without any tagging.
|
||||
*
|
||||
* Ends up with `NymMessage::Plain` variant
|
||||
* @param {Uint8Array} message
|
||||
* @param {string} recipient
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
send_regular_message(message: Uint8Array, recipient: string): Promise<any>;
|
||||
/**
|
||||
* Creates a message used for a duplex anonymous communication where the recipient
|
||||
* will never learn of our true identity. This is achieved by carefully sending `reply_surbs`.
|
||||
*
|
||||
* Note that if reply_surbs is set to zero then
|
||||
* this variant requires the client having sent some reply_surbs in the past
|
||||
* (and thus the recipient also knowing our sender tag).
|
||||
*
|
||||
* Ends up with `NymMessage::Repliable` variant
|
||||
* @param {Uint8Array} message
|
||||
* @param {string} recipient
|
||||
* @param {number} reply_surbs
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
send_anonymous_message(message: Uint8Array, recipient: string, reply_surbs: number): Promise<any>;
|
||||
/**
|
||||
* Attempt to use our internally received and stored `ReplySurb` to send the message back
|
||||
* to specified recipient whilst not knowing its full identity (or even gateway).
|
||||
*
|
||||
* Ends up with `NymMessage::Reply` variant
|
||||
* @param {Uint8Array} message
|
||||
* @param {string} recipient_tag
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
send_reply(message: Uint8Array, recipient_tag: string): Promise<any>;
|
||||
}
|
||||
|
||||
export interface Topology {
|
||||
free(): void;
|
||||
/**
|
||||
* Specifies whether the client should not refresh the network topology after obtaining
|
||||
* the first valid instance.
|
||||
* Supersedes `topology_refresh_rate_ms`.
|
||||
*/
|
||||
disable_refreshing: boolean;
|
||||
/**
|
||||
* The uniform delay every which clients are querying the directory server
|
||||
* to try to obtain a compatible network topology to send sphinx packets through.
|
||||
*/
|
||||
topology_refresh_rate_ms: bigint;
|
||||
/**
|
||||
* During topology refresh, test packets are sent through every single possible network
|
||||
* path. This timeout determines waiting period until it is decided that the packet
|
||||
* did not reach its destination.
|
||||
*/
|
||||
topology_resolution_timeout_ms: bigint;
|
||||
}
|
||||
|
||||
export interface Traffic {
|
||||
free(): void;
|
||||
/**
|
||||
* The parameter of Poisson distribution determining how long, on average,
|
||||
* sent packet is going to be delayed at any given mix node.
|
||||
* So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
* until the packet reaches its destination.
|
||||
*/
|
||||
average_packet_delay_ms: bigint;
|
||||
/**
|
||||
* Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
* poisson distribution.
|
||||
*/
|
||||
disable_main_poisson_packet_distribution: boolean;
|
||||
/**
|
||||
* The parameter of Poisson distribution determining how long, on average,
|
||||
* it is going to take another 'real traffic stream' message to be sent.
|
||||
* If no real packets are available and cover traffic is enabled,
|
||||
* a loop cover message is sent instead in order to preserve the rate.
|
||||
*/
|
||||
message_sending_average_delay_ms: bigint;
|
||||
/**
|
||||
* Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
*/
|
||||
use_extended_packet_size: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export interface NymClientBuilder {
|
||||
free(): void;
|
||||
/**
|
||||
* @returns {Promise<Promise<any>>}
|
||||
*/
|
||||
start_client(): Promise<Promise<any>>;
|
||||
}
|
||||
@@ -1,174 +1,6 @@
|
||||
export interface Acknowledgements {
|
||||
free(): void;
|
||||
/**
|
||||
* Value added to the expected round trip time of an acknowledgement packet before
|
||||
* it is assumed it was lost and retransmission of the data packet happens.
|
||||
* In an ideal network with 0 latency, this value would have been 0.
|
||||
*/
|
||||
ack_wait_addition_ms: bigint;
|
||||
/**
|
||||
* Value multiplied with the expected round trip time of an acknowledgement packet before
|
||||
* it is assumed it was lost and retransmission of the data packet happens.
|
||||
* In an ideal network with 0 latency, this value would have been 1.
|
||||
*/
|
||||
ack_wait_multiplier: number;
|
||||
/**
|
||||
* The parameter of Poisson distribution determining how long, on average,
|
||||
* sent acknowledgement is going to be delayed at any given mix node.
|
||||
* So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
* until the packet reaches its destination.
|
||||
*/
|
||||
average_ack_delay_ms: bigint;
|
||||
}
|
||||
import type { DebugWasm } from './types-from-wasm-pack';
|
||||
|
||||
export interface CoverTraffic {
|
||||
free(): void;
|
||||
/**
|
||||
* Specifies the ratio of `primary_packet_size` to `secondary_packet_size` used in cover traffic.
|
||||
* Only applicable if `secondary_packet_size` is enabled.
|
||||
*/
|
||||
cover_traffic_primary_size_ratio: number;
|
||||
/**
|
||||
* Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
* (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
|
||||
*/
|
||||
disable_loop_cover_traffic_stream: boolean;
|
||||
/**
|
||||
* The parameter of Poisson distribution determining how long, on average,
|
||||
* it is going to take for another loop cover traffic message to be sent.
|
||||
*/
|
||||
loop_cover_traffic_average_delay_ms: bigint;
|
||||
}
|
||||
|
||||
export interface GatewayConnection {
|
||||
free(): void;
|
||||
/**
|
||||
* How long we're willing to wait for a response to a message sent to the gateway,
|
||||
* before giving up on it.
|
||||
*/
|
||||
gateway_response_timeout_ms: bigint;
|
||||
}
|
||||
|
||||
export interface ReplySurbs {
|
||||
free(): void;
|
||||
/**
|
||||
* Defines the maximum number of reply surbs a remote party is allowed to request from this client at once.
|
||||
*/
|
||||
maximum_allowed_reply_surb_request_size: number;
|
||||
/**
|
||||
* Defines maximum amount of time given reply key is going to be valid for.
|
||||
* This is going to be superseded by key rotation once implemented.
|
||||
*/
|
||||
maximum_reply_key_age_ms: bigint;
|
||||
/**
|
||||
* Defines maximum amount of time given reply surb is going to be valid for.
|
||||
* This is going to be superseded by key rotation once implemented.
|
||||
*/
|
||||
maximum_reply_surb_age_ms: bigint;
|
||||
/**
|
||||
* Defines maximum amount of time the client is going to wait for reply surbs before
|
||||
* deciding it's never going to get them and would drop all pending messages
|
||||
*/
|
||||
maximum_reply_surb_drop_waiting_period_ms: bigint;
|
||||
/**
|
||||
* Defines the maximum number of reply surbs the client would request.
|
||||
*/
|
||||
maximum_reply_surb_request_size: number;
|
||||
/**
|
||||
* Defines maximum amount of time the client is going to wait for reply surbs before explicitly asking
|
||||
* for more even though in theory they wouldn't need to.
|
||||
*/
|
||||
maximum_reply_surb_rerequest_waiting_period_ms: bigint;
|
||||
/**
|
||||
* Defines the maximum number of reply surbs the client wants to keep in its storage at any times.
|
||||
*/
|
||||
maximum_reply_surb_storage_threshold: number;
|
||||
/**
|
||||
* Defines the minimum number of reply surbs the client would request.
|
||||
*/
|
||||
minimum_reply_surb_request_size: number;
|
||||
/**
|
||||
* Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
|
||||
* It can only allow to go below that value if its to request additional reply surbs.
|
||||
*/
|
||||
minimum_reply_surb_storage_threshold: number;
|
||||
}
|
||||
|
||||
export interface Debug {
|
||||
free(): void;
|
||||
/**
|
||||
* Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
|
||||
*/
|
||||
acknowledgements: Acknowledgements;
|
||||
/**
|
||||
* Defines all configuration options related to cover traffic stream(s).
|
||||
*/
|
||||
cover_traffic: CoverTraffic;
|
||||
/**
|
||||
* Defines all configuration options related to the gateway connection.
|
||||
*/
|
||||
gateway_connection: GatewayConnection;
|
||||
/**
|
||||
* Defines all configuration options related to reply SURBs.
|
||||
*/
|
||||
reply_surbs: ReplySurbs;
|
||||
/**
|
||||
* Defines all configuration options related topology, such as refresh rates or timeouts.
|
||||
*/
|
||||
topology: Topology;
|
||||
/**
|
||||
* Defines all configuration options related to traffic streams.
|
||||
*/
|
||||
traffic: Traffic;
|
||||
}
|
||||
|
||||
export interface Topology {
|
||||
free(): void;
|
||||
/**
|
||||
* Specifies whether the client should not refresh the network topology after obtaining
|
||||
* the first valid instance.
|
||||
* Supersedes `topology_refresh_rate_ms`.
|
||||
*/
|
||||
disable_refreshing: boolean;
|
||||
/**
|
||||
* The uniform delay every which clients are querying the directory server
|
||||
* to try to obtain a compatible network topology to send sphinx packets through.
|
||||
*/
|
||||
topology_refresh_rate_ms: bigint;
|
||||
/**
|
||||
* During topology refresh, test packets are sent through every single possible network
|
||||
* path. This timeout determines waiting period until it is decided that the packet
|
||||
* did not reach its destination.
|
||||
*/
|
||||
topology_resolution_timeout_ms: bigint;
|
||||
}
|
||||
|
||||
export interface Traffic {
|
||||
free(): void;
|
||||
/**
|
||||
* The parameter of Poisson distribution determining how long, on average,
|
||||
* sent packet is going to be delayed at any given mix node.
|
||||
* So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
* until the packet reaches its destination.
|
||||
*/
|
||||
average_packet_delay_ms: bigint;
|
||||
/**
|
||||
* Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
* poisson distribution.
|
||||
*/
|
||||
disable_main_poisson_packet_distribution: boolean;
|
||||
/**
|
||||
* The parameter of Poisson distribution determining how long, on average,
|
||||
* it is going to take another 'real traffic stream' message to be sent.
|
||||
* If no real packets are available and cover traffic is enabled,
|
||||
* a loop cover message is sent instead in order to preserve the rate.
|
||||
*/
|
||||
message_sending_average_delay_ms: bigint;
|
||||
/**
|
||||
* Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
*/
|
||||
use_extended_packet_size: boolean;
|
||||
}
|
||||
export * from './types-from-wasm-pack';
|
||||
|
||||
/**
|
||||
* Some common mime types, however, you can always just specify the mime-type as a string
|
||||
@@ -221,7 +53,7 @@ export interface NymClientConfig {
|
||||
/**
|
||||
* Optional. Settings for the WASM client.
|
||||
*/
|
||||
debug?: Debug;
|
||||
debug?: DebugWasm;
|
||||
}
|
||||
|
||||
export interface IWebWorker {
|
||||
|
||||
@@ -168,43 +168,40 @@ init(wasmBytes())
|
||||
if (config.gatewayListener) gatewayEndpoint.gateway_listener = config.gatewayListener;
|
||||
|
||||
// create the client, passing handlers for events
|
||||
wrapper.init(
|
||||
new Config(config.clientId, config.nymApiUrl, gatewayEndpoint, config.debug || default_debug()),
|
||||
async (message) => {
|
||||
// fire an event with the raw message
|
||||
postMessageWithType<RawMessageReceivedEvent>({
|
||||
kind: EventKinds.RawMessageReceived,
|
||||
args: { payload: message },
|
||||
});
|
||||
wrapper.init(new Config(config.clientId, config.nymApiUrl, config.debug || default_debug()), async (message) => {
|
||||
// fire an event with the raw message
|
||||
postMessageWithType<RawMessageReceivedEvent>({
|
||||
kind: EventKinds.RawMessageReceived,
|
||||
args: { payload: message },
|
||||
});
|
||||
|
||||
try {
|
||||
// try to decode the payload to extract the mime-type, headers and payload body
|
||||
const decodedPayload = decode_payload(message);
|
||||
const { payload, headers } = decodedPayload;
|
||||
const mimeType = decodedPayload.mimeType as MimeTypes;
|
||||
try {
|
||||
// try to decode the payload to extract the mime-type, headers and payload body
|
||||
const decodedPayload = decode_payload(message);
|
||||
const { payload, headers } = decodedPayload;
|
||||
const mimeType = decodedPayload.mimeType as MimeTypes;
|
||||
|
||||
if (wrapper.getTextMimeTypes().includes(mimeType)) {
|
||||
const stringMessage = parse_utf8_string(payload);
|
||||
if (wrapper.getTextMimeTypes().includes(mimeType)) {
|
||||
const stringMessage = parse_utf8_string(payload);
|
||||
|
||||
// the payload is a string type (in the options at creation time, string mime-types are set, or fall back
|
||||
// to defaults, such as `text/plain`, `application/json`, etc)
|
||||
postMessageWithType<StringMessageReceivedEvent>({
|
||||
kind: EventKinds.StringMessageReceived,
|
||||
args: { mimeType, payload: stringMessage, payloadRaw: payload, headers },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// the payload is a binary type
|
||||
postMessageWithType<BinaryMessageReceivedEvent>({
|
||||
kind: EventKinds.BinaryMessageReceived,
|
||||
args: { mimeType, payload, headers },
|
||||
// the payload is a string type (in the options at creation time, string mime-types are set, or fall back
|
||||
// to defaults, such as `text/plain`, `application/json`, etc)
|
||||
postMessageWithType<StringMessageReceivedEvent>({
|
||||
kind: EventKinds.StringMessageReceived,
|
||||
args: { mimeType, payload: stringMessage, payloadRaw: payload, headers },
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to parse binary message', e);
|
||||
return;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// the payload is a binary type
|
||||
postMessageWithType<BinaryMessageReceivedEvent>({
|
||||
kind: EventKinds.BinaryMessageReceived,
|
||||
args: { mimeType, payload, headers },
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to parse binary message', e);
|
||||
}
|
||||
});
|
||||
|
||||
// start the client sending traffic
|
||||
await wrapper.start();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
import express from 'express';
|
||||
import dotenv from 'dotenv';
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||
|
||||
@@ -20,6 +20,7 @@ export const Content: FCWithChildren = () => {
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
if (isMounted()) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Content is mounted');
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user