Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34b8a0706d | |||
| 275417c186 | |||
| 40a92e07d6 | |||
| e83a12bd91 | |||
| 381162554e | |||
| 7a740c06fd | |||
| e65285ac7b | |||
| 715a3bd687 | |||
| 858f1ac13c | |||
| 3ee1328626 | |||
| 36ac825b43 | |||
| 165f189115 | |||
| b5fcfbe2fe | |||
| 7a38f1f469 | |||
| 5faca46235 | |||
| d780ac55b1 | |||
| c5f7d066b0 | |||
| 8cc90be8c6 | |||
| aae96e7537 | |||
| 39b521bc1f | |||
| 7339695ce8 | |||
| 0e1c9853aa | |||
| 76b9c669d7 | |||
| 553cfd098b | |||
| 40e1243f3c | |||
| 50d2ca0a12 | |||
| 32d9baaf02 | |||
| 0179f7648c | |||
| 45c04d63e2 | |||
| 0f8ac1506b | |||
| efdf27d1e9 |
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
outputs:
|
||||
release_id: ${{ steps.create-release.outputs.id }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets[0].published_at) }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }}
|
||||
version: ${{ steps.release-info.outputs.version }}
|
||||
filename: ${{ steps.release-info.outputs.filename }}
|
||||
file_hash: ${{ steps.release-info.outputs.file_hash }}
|
||||
@@ -31,10 +31,20 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: |
|
||||
export WASM_PACK_VERSION="v0.12.1"
|
||||
curl -LO https://github.com/rustwasm/wasm-pack/releases/download/${WASM_PACK_VERSION}/wasm-pack-${WASM_PACK_VERSION}-x86_64-apple-darwin.tar.gz
|
||||
tar xvzf wasm-pack-${WASM_PACK_VERSION}-x86_64-apple-darwin.tar.gz -C $HOME/.cargo/bin --strip-components=1
|
||||
rm wasm-pack-${WASM_PACK_VERSION}-x86_64-apple-darwin.tar.gz
|
||||
|
||||
- name: Install the Apple developer certificate for code signing
|
||||
env:
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
@@ -105,9 +115,10 @@ jobs:
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
semver="${${{ github.ref_name }}##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=$semver" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_$version_x64.dmg" >> "$GITHUB_OUTPUT"
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_${version}_x64_en-US.msi" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/dmg/nym-connect_*_x64.dmg') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
outputs:
|
||||
release_id: ${{ steps.create-release.outputs.id }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets[0].published_at) }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }}
|
||||
version: ${{ steps.release-info.outputs.version }}
|
||||
filename: ${{ steps.release-info.outputs.filename }}
|
||||
file_hash: ${{ steps.release-info.outputs.file_hash }}
|
||||
@@ -82,11 +82,11 @@ jobs:
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
semver="${${{ github.ref_name }}##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=$semver" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_$version_amd64.AppImage" >> "$GITHUB_OUTPUT"
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_${version}_amd64.AppImage" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/appimage/nym-connect_*_amd64.AppImage') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
outputs:
|
||||
release_id: ${{ steps.create-release.outputs.id }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets[0].published_at) }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }}
|
||||
version: ${{ steps.release-info.outputs.version }}
|
||||
filename: ${{ steps.release-info.outputs.filename }}
|
||||
file_hash: ${{ steps.release-info.outputs.file_hash }}
|
||||
@@ -101,9 +101,10 @@ jobs:
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
semver="${${{ github.ref_name }}##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=$semver" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_$version_x64_en-US.msi" >> "$GITHUB_OUTPUT"
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_${version}_x64_en-US.msi" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/msi/nym-connect_*_x64_en-US.msi') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
outputs:
|
||||
release_id: ${{ steps.create-release.outputs.id }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }}
|
||||
version: ${{ steps.release-info.outputs.version }}
|
||||
filename: ${{ steps.release-info.outputs.filename }}
|
||||
file_hash: ${{ steps.release-info.outputs.file_hash }}
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
outputs:
|
||||
release_id: ${{ steps.create-release.outputs.id }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }}
|
||||
version: ${{ steps.release-info.outputs.version }}
|
||||
filename: ${{ steps.release-info.outputs.filename }}
|
||||
file_hash: ${{ steps.release-info.outputs.file_hash }}
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
outputs:
|
||||
release_id: ${{ steps.create-release.outputs.id }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }}
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].created_at }}
|
||||
version: ${{ steps.release-info.outputs.version }}
|
||||
filename: ${{ steps.release-info.outputs.filename }}
|
||||
file_hash: ${{ steps.release-info.outputs.file_hash }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[book]
|
||||
title = "Nym Developer Portal"
|
||||
authors = ["Max Hampshire"]
|
||||
authors = ["Max Hampshire, Serinko, Alexia Lorenza Martinel"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
@@ -47,10 +47,10 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
# variables preprocessor: import variables into files
|
||||
# https://gitlab.com/tglman/mdbook-variables/
|
||||
[preprocessor.variables.variables]
|
||||
# code prerequisites versions
|
||||
minimum_rust_version = "1.66"
|
||||
# TODO remove this in place of develop in next release
|
||||
platform_release_version = "v1.1.21"
|
||||
# vars for links: TODO think on how to streamline updating
|
||||
platform_release_version = "v1.1.25"
|
||||
wallet_release_version = "v1.2.7"
|
||||
|
||||
[preprocessor.last-changed]
|
||||
command = "mdbook-last-changed"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[book]
|
||||
title = "Nym Docs"
|
||||
authors = ["Max Hampshire"]
|
||||
authors = ["Max Hampshire, Serinko, Alexia Lorenza Martinel"]
|
||||
description = "Nym technical documentation"
|
||||
language = "en"
|
||||
multilingual = false # for the moment - ideally work on chinese, brazillian, spanish next
|
||||
@@ -49,8 +49,8 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
[preprocessor.variables.variables]
|
||||
minimum_rust_version = "1.66"
|
||||
# vars for links: TODO think on how to streamline updating
|
||||
platform_release_version = "v1.1.22"
|
||||
wallet_release_version = "v1.2.5"
|
||||
platform_release_version = "v1.1.25"
|
||||
wallet_release_version = "v1.2.7"
|
||||
|
||||
[preprocessor.last-changed]
|
||||
command = "mdbook-last-changed"
|
||||
|
||||
@@ -153,8 +153,6 @@ Follow these steps to upgrade your binary and update its config file:
|
||||
* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**.
|
||||
* restart your gateway process with the new binary.
|
||||
|
||||
> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release.
|
||||
|
||||
#### Step 2: updating your node information in the smart contract
|
||||
Follow these steps to update the information about your node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net).
|
||||
|
||||
|
||||
@@ -191,8 +191,6 @@ Follow these steps to upgrade your mix node binary and update its config file:
|
||||
* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**.
|
||||
* restart your mix node process with the new binary.
|
||||
|
||||
> Do **not** use the `upgrade` command: there is a known error with the command that will be fixed in a subsequent release.
|
||||
|
||||
#### Step 2: updating your node information in the smart contract
|
||||
Follow these steps to update the information about your mix node which is publically avaliable from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net).
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
<!-- cmdrun ../../../../target/release/nym-network-requester --version | grep "Build Version" | cut -b 21-26 -->
|
||||
```
|
||||
|
||||
|
||||
## Network Requester Whitelist
|
||||
If you have access to a server, you can run the network requester, which allows Nym users to send outbound requests from their local machine through the mixnet to a server, which then makes the request on their behalf, shielding them (and their metadata) from clearnet, untrusted and unknown infrastructure, such as email or message client servers.
|
||||
|
||||
@@ -65,6 +64,34 @@ p2pify.com
|
||||
2001:67c:4e8::/48
|
||||
2001:b28:f23c::/48
|
||||
2a0a:f280::/32
|
||||
|
||||
# nym matrix server
|
||||
nymtech.chat
|
||||
|
||||
# generic matrix server backends
|
||||
vector.im
|
||||
matrix.org
|
||||
|
||||
# monero desktop - mainnet
|
||||
212.83.175.67
|
||||
212.83.172.165
|
||||
176.9.0.187
|
||||
88.198.163.90
|
||||
95.217.25.101
|
||||
136.244.105.131
|
||||
104.238.221.81
|
||||
66.85.74.134
|
||||
88.99.173.38
|
||||
51.79.173.165
|
||||
|
||||
# monero desktop - stagenet
|
||||
162.210.173.150
|
||||
176.9.0.187
|
||||
88.99.173.38
|
||||
51.79.173.165
|
||||
|
||||
# alephium
|
||||
alephium.org
|
||||
```
|
||||
|
||||
## Network Requester Directory
|
||||
@@ -193,25 +220,32 @@ sudo ufw enable
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
Finally open your requester's p2p port, as well as ports for ssh and incoming traffic connections:
|
||||
Finally open your requester's ssh port to incoming administration connections:
|
||||
|
||||
```
|
||||
sudo ufw allow 22,9000/tcp
|
||||
sudo ufw allow 22/tcp
|
||||
# check the status of the firewall
|
||||
sudo ufw status
|
||||
```
|
||||
|
||||
For more information about your requester's port configuration, check the [requester port reference table](./network-requester-setup.md#requester-port-reference) below.
|
||||
|
||||
## Using your network requester
|
||||
|
||||
The next thing to do is use your requester, share its address with friends (or whoever you want to help privacy-enhance their app traffic). Is this safe to do? If it was an open proxy, this would be unsafe, because any Nym user could make network requests to any system on the internet.
|
||||
|
||||
To make things a bit less stressful for administrators, the Network Requester drops all incoming requests by default. In order for it to make requests, you need to add specific domains to the `allowed.list` file at `$HOME/.nym/service-providers/network-requester/allowed.list`.
|
||||
|
||||
### Global vs local allow lists
|
||||
Your Network Requester will check for a domain against 2 lists before allowing traffic through for a particular domain or IP.
|
||||
|
||||
* The first list is the default list on the [nymtech.net server](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt). Your Requester will not check against this list every time, but instead will keep a record of accepted domains in memory.
|
||||
|
||||
* The second is the local `allowed.list` file.
|
||||
|
||||
### Supporting custom domains with your network requester
|
||||
It is easy to add new domains and services to your network requester - simply find out which endpoints (both URLs and raw IP addresses are supported) you need to whitelist, and then add these endpoints to your `allowed.list`.
|
||||
|
||||
> In order to keep things more organised, you can now use comments in the `allow.list` like the example at the top of this page.
|
||||
|
||||
How to go about this? Have a look in your nym-network-requester config directory:
|
||||
|
||||
```
|
||||
@@ -249,7 +283,3 @@ This command should return the following:
|
||||
### Requester port reference
|
||||
|
||||
All network-requester-specific port configuration can be found in `$HOME/.nym/service-providers/network-requester/<YOUR_ID>/config/config.toml`. If you do edit any port configs, remember to restart your client and requester processes.
|
||||
|
||||
| Default port | Use |
|
||||
|--------------|---------------------------|
|
||||
| 9000 | Listen for Client traffic |
|
||||
|
||||
@@ -56,14 +56,19 @@ If you're integrating mixnet functionality into an existing app and want to inte
|
||||
### Anonymous replies with SURBs
|
||||
Both functions used to send messages through the mixnet (`send_str` and `send_bytes`) send a pre-determined number of SURBs along with their messages by default.
|
||||
|
||||
The number of SURBs is set [here](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/sdk/rust/nym-sdk/src/mixnet/client.rs#L35):
|
||||
The number of SURBs is set [here](https://github.com/nymtech/nym/blob/release/{{platform_release_version}}/sdk/rust/nym-sdk/src/mixnet/client.rs#L34):
|
||||
|
||||
```rust,noplayground
|
||||
{{#include ../../../../sdk/rust/nym-sdk/src/mixnet/client.rs:30}}
|
||||
{{#include ../../../../sdk/rust/nym-sdk/src/mixnet/client.rs:34}}
|
||||
```
|
||||
|
||||
You can read more about how SURBs function under the hood [here](../architecture/traffic-flow.md#private-replies-using-surbs).
|
||||
|
||||
In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to:
|
||||
|
||||
```rust,noplayground
|
||||
{{#include ../../../../sdk/rust/nym-sdk/examples/surb-reply.rs}}
|
||||
```
|
||||
|
||||
### Importing and using a custom network topology
|
||||
If you want to send traffic through a sub-set of nodes (for instance, ones you control, or a small test setup) when developing, debugging, or peforming research, you will need to import these nodes as a custom network topology, instead of grabbing it from the [`Mainnet Nym-API`](https://validator.nymtech.net/api/swagger/index.html) (`examples/custom_topology_provider.rs`).
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.15] (2023-07-25)
|
||||
|
||||
- NC Desktop - remove sentry DSN from code ([#3694])
|
||||
- NC - Add Alephium wallet in the supported app list ([#3681])
|
||||
|
||||
[#3694]: https://github.com/nymtech/nym/issues/3694
|
||||
[#3681]: https://github.com/nymtech/nym/issues/3681
|
||||
|
||||
## [v1.1.14] (2023-07-04)
|
||||
|
||||
- Nym connect fails to start when encountering an old config version ([#3588])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nym/nym-connect",
|
||||
"version": "1.1.13",
|
||||
"version": "1.1.15",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -115,4 +115,4 @@
|
||||
"webpack-merge": "^5.8.0",
|
||||
"yaml-loader": "^0.8.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-connect"
|
||||
version = "1.1.13"
|
||||
version = "1.1.15"
|
||||
description = "nym-connect"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-connect",
|
||||
"version": "1.1.14"
|
||||
"version": "1.1.15"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
@@ -86,4 +86,4 @@
|
||||
"csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -30,8 +30,7 @@
|
||||
"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",
|
||||
"preinstall": "yarn install:copy-placeholders && yarn install:copy-storage-placeholders",
|
||||
"install:copy-placeholders": "cp scripts/build/yarn/wasm-placeholder/package.json sdk/typescript/packages/nym-client-wasm",
|
||||
"install:copy-storage-placeholders": "mkdir -p nym-browser-extension/storage/pkg && cp scripts/build/yarn/storage-placeholder/package.json nym-browser-extension/storage/pkg"
|
||||
"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,66 @@
|
||||
use nym_sdk::mixnet::{
|
||||
AnonymousSenderTag, MixnetClientBuilder, ReconstructedMessage, StoragePaths,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
nym_bin_common::logging::setup_logging();
|
||||
|
||||
// Specify some config options
|
||||
let config_dir = PathBuf::from("/tmp/surb-example");
|
||||
let storage_paths = StoragePaths::new_from_dir(&config_dir).unwrap();
|
||||
|
||||
// Create the client with a storage backend, and enable it by giving it some paths. If keys
|
||||
// exists at these paths, they will be loaded, otherwise they will be generated.
|
||||
let client = MixnetClientBuilder::new_with_default_storage(storage_paths)
|
||||
.await
|
||||
.unwrap()
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Now we connect to the mixnet, using keys now stored in the paths provided.
|
||||
let mut client = client.connect_to_mixnet().await.unwrap();
|
||||
|
||||
// Be able to get our client address
|
||||
let our_address = client.nym_address();
|
||||
println!("\nOur client nym address is: {our_address}");
|
||||
|
||||
// Send a message through the mixnet to ourselves using our nym address
|
||||
client.send_str(*our_address, "hello there").await;
|
||||
|
||||
// we're going to parse the sender_tag (AnonymousSenderTag) from the incoming message and use it to 'reply' to ourselves instead of our Nym address.
|
||||
// we know there will be a sender_tag since the sdk sends SURBs along with messages by default.
|
||||
println!("Waiting for message\n");
|
||||
|
||||
// get the actual message - discard the empty vec sent along with a potential SURB topup request
|
||||
let mut message: Vec<ReconstructedMessage> = Vec::new();
|
||||
while let Some(new_message) = client.wait_for_messages().await {
|
||||
if new_message.is_empty() {
|
||||
continue;
|
||||
}
|
||||
message = new_message;
|
||||
break;
|
||||
}
|
||||
|
||||
let mut parsed = String::new();
|
||||
if let Some(r) = message.first() {
|
||||
parsed = String::from_utf8(r.message.clone()).unwrap();
|
||||
}
|
||||
// parse sender_tag: we will use this to reply to sender without needing their Nym address
|
||||
let return_recipient: AnonymousSenderTag = message[0].sender_tag.unwrap();
|
||||
println!(
|
||||
"\nReceived the following message: {} \nfrom sender with surb bucket {}",
|
||||
parsed, return_recipient
|
||||
);
|
||||
|
||||
// reply to self with it: note we use `send_str_reply` instead of `send_str`
|
||||
println!("Replying with using SURBs");
|
||||
client.send_str_reply(return_recipient, "hi an0n!").await;
|
||||
|
||||
println!("Waiting for message (once you see it, ctrl-c to exit)\n");
|
||||
client
|
||||
.on_messages(|msg| println!("\nReceived: {}", String::from_utf8_lossy(&msg.message)))
|
||||
.await;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
# Nym Chrome Extension Example
|
||||
|
||||
This is an example of how Nym can be used within the context of a Chrome extension.
|
||||
|
||||
## Running the example
|
||||
|
||||
1. Copy a build of the Nym TypeScript SDK (ESM version) into `./sdk`.
|
||||
2. Navigate to `chrome://extensions` in Google Chrome.
|
||||
3. Enable "Developer mode" (top right of the page).
|
||||
4. Click on "Load unpacked" (top left of the page).
|
||||
5. Load this extension folder.
|
||||
|
||||
## How does it work?
|
||||
|
||||
The Nym Mixnet Client runs a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) that wraps
|
||||
a WASM library that builds and encrypts Sphinx packets in the browser to send over the Nym mixnet:
|
||||
|
||||

|
||||
|
||||
The WASM code encrypts each layer of the Sphinx packet in the browser, before sending the Sphinx packet over a websocket to the ingress gateway:
|
||||
|
||||

|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "Nym Chrome Extension Example",
|
||||
"description": "An example demonstrating how to integrate the Nym TypeScript SDK in the context of a Google Chrome browser extension.",
|
||||
"version": "1.0",
|
||||
"manifest_version": 3,
|
||||
"icons": {
|
||||
"48": "icon.png"
|
||||
},
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
|
||||
},
|
||||
"action": {
|
||||
"default_title": "Nym Chrome Extension Example",
|
||||
"default_icon": "icon.png",
|
||||
"default_popup": "popup.html"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "chrome-extension",
|
||||
"version": "1.0.0",
|
||||
"description": "This is an example of how Nym can be used within the context of a Chrome extension.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "webpack"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"webpack": "^5.88.1",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nymproject/sdk": "^1.1.8"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
body {
|
||||
width: 800px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
#editdialog input {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="popup.css" />
|
||||
<script type="module" src="main.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p><label>Sender:</label><input disabled="true" size="85" id="sender" value="" /></p>
|
||||
<p><label>Recipient:</label><input size="85" id="recipient" value="" /></p>
|
||||
<p><label>Message:</label><input id="message" value="Hello mixnet!" /></p>
|
||||
<p><button id="send-button">Send</button></p>
|
||||
<p>Send messages from your browser, through the mixnet, and to the recipient using the "send" button.</p>
|
||||
<p>
|
||||
<span style="color: blue">Sent</span> messages show in blue, <span style="color: green">received</span> messages
|
||||
show in green.
|
||||
</p>
|
||||
<hr />
|
||||
<p></p>
|
||||
<div id="output"></div>
|
||||
<p></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,66 +0,0 @@
|
||||
// dom-utils.js
|
||||
// Contains utility functionality to help manipulate the DOM elements necessary to demonstrate the Nym example.
|
||||
|
||||
/**
|
||||
* Create a Sphinx packet and send it to the mixnet through the gateway node.
|
||||
*
|
||||
* Message and recipient are taken from the values in the user interface.
|
||||
*
|
||||
* @param {Client} nymClient the nym client to use for message sending
|
||||
*/
|
||||
async function sendMessageTo(nym) {
|
||||
const message = document.getElementById('message').value;
|
||||
const recipient = document.getElementById('recipient').value;
|
||||
await nym.client.send({
|
||||
payload: {
|
||||
message,
|
||||
mimeType: 'text/plain'
|
||||
},
|
||||
recipient
|
||||
});
|
||||
displaySend(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display messages that have been sent up the websocket. Colours them blue.
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
function displaySend(message) {
|
||||
const timestamp = new Date().toISOString().substr(11, 12);
|
||||
const sendDiv = document.createElement('div');
|
||||
const paragraph = document.createElement('p');
|
||||
paragraph.setAttribute('style', 'color: blue');
|
||||
const paragraphContent = document.createTextNode(`${timestamp} sent >>> ${message}`);
|
||||
paragraph.appendChild(paragraphContent);
|
||||
sendDiv.appendChild(paragraph);
|
||||
document.getElementById('output')?.appendChild(sendDiv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display received text messages in the browser. Colour them green.
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
function displayReceived(message) {
|
||||
const content = message;
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const receivedDiv = document.createElement('div');
|
||||
const paragraph = document.createElement('p');
|
||||
paragraph.setAttribute('style', 'color: green');
|
||||
const paragraphContent = document.createTextNode(`${timestamp} received >>> ${content}`);
|
||||
paragraph.appendChild(paragraphContent);
|
||||
receivedDiv.appendChild(paragraph);
|
||||
document.getElementById('output')?.appendChild(receivedDiv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the nymClient's sender address in the user interface
|
||||
*
|
||||
* @param {Client} nymClient
|
||||
*/
|
||||
function displaySenderAddress(address) {
|
||||
document.getElementById('sender').value = address;
|
||||
}
|
||||
|
||||
export { sendMessageTo, displaySend, displayReceived , displaySenderAddress }
|
||||
@@ -1,53 +0,0 @@
|
||||
// main.js
|
||||
// Simple example of how to load Nym's TypeScript SDK and bind it to a DOM.
|
||||
// Look at dom-utils.js for the DOM utility functionality referenced here.
|
||||
|
||||
// Import the Nym mixnet ESM module.
|
||||
import { createNymMixnetClient } from '@nymproject/sdk';
|
||||
|
||||
// Import the DOM utility functionality.
|
||||
import { displaySenderAddress, displayReceived, sendMessageTo } from './dom-utils.js';
|
||||
|
||||
async function main() {
|
||||
// Initialize the Nym mixnet client.
|
||||
let nymClient = await createNymMixnetClient();
|
||||
if (!nymClient) {
|
||||
console.error('Oh no! Could not create client');
|
||||
return;
|
||||
}
|
||||
|
||||
const nymApiUrl = 'https://validator.nymtech.net/api';
|
||||
const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
|
||||
|
||||
// subscribe to connect event, so that we can show the client's address
|
||||
nymClient.events.subscribeToConnected((e) => {
|
||||
if (e.args.address) {
|
||||
displaySenderAddress(e.args.address);
|
||||
}
|
||||
});
|
||||
|
||||
// subscribe to message received events and show any string messages received
|
||||
nymClient.events.subscribeToTextMessageReceivedEvent((e) => {
|
||||
displayReceived(e.args.payload);
|
||||
});
|
||||
|
||||
const sendButton = document.querySelector('#send-button');
|
||||
if (sendButton) {
|
||||
sendButton.onclick = function () {
|
||||
if (nymClient) {
|
||||
sendMessageTo(nymClient);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
nymClient.events.subscribeToRawMessageReceivedEvent((e) => console.log('Received: ', e.args.payload));
|
||||
await nymClient.client.start({
|
||||
clientId: 'My awesome client',
|
||||
nymApiUrl,
|
||||
preferredGatewayIdentityKey,
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
main();
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
// Webpack configuration for the Chrome extension example
|
||||
|
||||
const path = require('path');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: {
|
||||
main: './src/main.js',
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
'manifest.json',
|
||||
'popup.html',
|
||||
{ from: path.resolve(__dirname, '../../../../assets/favicon/favicon.png'), to: 'icon.png' },
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
sdk/index.js
|
||||
@@ -1,35 +0,0 @@
|
||||
# Nym Firefox Extension Example
|
||||
|
||||
This is an example of how Nym can be used within the context of a Mozilla Firefox extension.
|
||||
|
||||
## Running the example
|
||||
|
||||
First, build the Nym SDK:
|
||||
|
||||
From the SDK directory `sdk/typescript/packages/sdk` run:
|
||||
|
||||
```js
|
||||
npm run build:local
|
||||
```
|
||||
|
||||
Then, from the example directory `sdk/typescript/examples/firefox-extension` run:
|
||||
|
||||
```js
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Workers
|
||||
|
||||
Firefox browser extensions cannot run inline web workers. In order to overcome this limitation, the Nym Firefox Extension Example imports workers from the SDK and uses Webpack's `worker-loader` to allow the worker's to be bundled inline into the extension. In order for webpack to include the workers in the build, they are imported as modules in the `src/index.js` file:
|
||||
|
||||
## How does it work?
|
||||
|
||||
The Nym Mixnet Client runs a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) that wraps
|
||||
a WASM library that builds and encrypts Sphinx packets in the browser to send over the Nym mixnet:
|
||||
|
||||

|
||||
|
||||
The WASM code encrypts each layer of the Sphinx packet in the browser, before sending the Sphinx packet over a websocket to the ingress gateway:
|
||||
|
||||

|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Nym Firefox Extension Example",
|
||||
"version": "1.0",
|
||||
"description": "An example demonstrating how to integrate the Nym TypeScript SDK in the context of a Mozilla Firefox browser extension.",
|
||||
"icons": {
|
||||
"48": "icon.png"
|
||||
},
|
||||
"permissions": [],
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval';"
|
||||
},
|
||||
"background": {
|
||||
"scripts": ["background.js"]
|
||||
},
|
||||
"action": {
|
||||
"default_icon": {
|
||||
"32": "icon.png"
|
||||
},
|
||||
"default_title": "Nym Firefox Extension Example",
|
||||
"default_popup": "popup.html"
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "firefox-extension",
|
||||
"version": "1.0.0",
|
||||
"description": "This is an example of how Nym can be used within the context of a Firefox extension.",
|
||||
"main": "index.js",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"webpack": "^5.88.1",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn webpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"worker-loader": "^3.0.8"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
body {
|
||||
width: 800px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
#editdialog input {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="popup.css" />
|
||||
<script type="module" src="popup.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p><label>Sender:</label><input disabled="true" size="85" id="sender" value="" /></p>
|
||||
<p><label>Recipient:</label><input size="85" id="recipient" value="" /></p>
|
||||
<p><label>Message:</label><input id="message" value="Hello mixnet!" /></p>
|
||||
<p><button id="send-button">Send</button></p>
|
||||
<p>Send messages from your browser, through the mixnet, and to the recipient using the "send" button.</p>
|
||||
<p>
|
||||
<span style="color: blue">Sent</span> messages show in blue, <span style="color: green">received</span> messages
|
||||
show in green.
|
||||
</p>
|
||||
<hr />
|
||||
<p></p>
|
||||
<div id="output"></div>
|
||||
<p></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,107 +0,0 @@
|
||||
// main.js
|
||||
// Simple example of how to load Nym's TypeScript SDK and bind it to a DOM.
|
||||
// Look at dom-utils.js for the DOM utility functionality referenced here.
|
||||
|
||||
// Import the Nym mixnet ESM module.
|
||||
// Import The web workers for the Nym mixnet ESM module.These are required for to run the Nym mixnet client.
|
||||
|
||||
import { createNymMixnetClient } from '../../../packages/sdk/dist/full-fat/index.js';
|
||||
import '../../../packages/sdk/dist/full-fat/web-worker-0.js';
|
||||
import '../../../packages/sdk/dist/full-fat/web-worker-1.js';
|
||||
|
||||
const backgroundState = {
|
||||
isReady: false,
|
||||
address: '',
|
||||
recipient: '',
|
||||
messageLog: [],
|
||||
};
|
||||
|
||||
async function initBackground() {
|
||||
// Initialize the Nym mixnet client.
|
||||
let nymClient = await createNymMixnetClient().catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
if (!nymClient) {
|
||||
console.error('Oh no! Could not create client');
|
||||
return;
|
||||
}
|
||||
const nymApiUrl = 'https://validator.nymtech.net/api';
|
||||
const preferredGatewayIdentityKey = 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM';
|
||||
|
||||
// subscribe to connect event, so that we can show the client's address
|
||||
nymClient.events.subscribeToConnected((e) => {
|
||||
if (e.args.address) {
|
||||
backgroundState.address = e.args.address;
|
||||
browser.runtime.sendMessage({
|
||||
type: 'displaySenderAddress',
|
||||
message: backgroundState.address,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// subscribe to message received events and show any string messages received
|
||||
nymClient.events.subscribeToTextMessageReceivedEvent((e) => {
|
||||
backgroundState.messageLog.push({
|
||||
type: 'received',
|
||||
message: e.args.payload,
|
||||
});
|
||||
browser.runtime.sendMessage({
|
||||
type: 'displayReceived',
|
||||
message: e.args.payload,
|
||||
});
|
||||
});
|
||||
|
||||
nymClient.events.subscribeToRawMessageReceivedEvent((e) => console.log('Received: ', e.args.payload));
|
||||
await nymClient.client.start({
|
||||
clientId: 'My awesome client',
|
||||
nymApiUrl,
|
||||
preferredGatewayIdentityKey,
|
||||
});
|
||||
browser.runtime.onMessage.addListener(async (data) => {
|
||||
switch (data.type) {
|
||||
case 'nymClientSendMessage':
|
||||
if (nymClient) {
|
||||
await nymClient.client.send({
|
||||
payload: {
|
||||
message: data.message,
|
||||
mimeType: 'text/plain',
|
||||
},
|
||||
recipient: data.recipient,
|
||||
});
|
||||
backgroundState.messageLog.push({
|
||||
type: 'sent',
|
||||
message: data.message,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
backgroundState.isReady = true;
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
browser.runtime.onMessage.addListener((data) => {
|
||||
switch (data.type) {
|
||||
case 'popupReady':
|
||||
if (backgroundState.isReady) {
|
||||
browser.runtime.sendMessage({
|
||||
type: 'displaySenderAddress',
|
||||
message: backgroundState.address,
|
||||
});
|
||||
browser.runtime.sendMessage({
|
||||
type: 'displayMessageLog',
|
||||
message: backgroundState.messageLog,
|
||||
});
|
||||
browser.runtime.sendMessage({
|
||||
type: 'updateRecipient',
|
||||
message: backgroundState.recipient,
|
||||
});
|
||||
} else {
|
||||
initBackground();
|
||||
}
|
||||
break;
|
||||
case 'updateRecipient':
|
||||
backgroundState.recipient = data.message;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
// dom-utils.js
|
||||
// Contains utility functionality to help manipulate the DOM elements necessary to demonstrate the Nym example.
|
||||
|
||||
/**
|
||||
* Create a Sphinx packet and send it to the mixnet through the gateway node.
|
||||
*
|
||||
* Message and recipient are taken from the values in the user interface.
|
||||
*
|
||||
* @param {Client} nymClient the nym client to use for message sending
|
||||
*/
|
||||
async function sendMessageTo() {
|
||||
const message = document.getElementById('message').value;
|
||||
const recipient = document.getElementById('recipient').value;
|
||||
browser.runtime.sendMessage({
|
||||
type: 'nymClientSendMessage',
|
||||
message,
|
||||
recipient,
|
||||
});
|
||||
displaySend(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display messages that have been sent up the websocket. Colours them blue.
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
function displaySend(message) {
|
||||
const timestamp = new Date().toISOString().substr(11, 12);
|
||||
const sendDiv = document.createElement('div');
|
||||
const paragraph = document.createElement('p');
|
||||
paragraph.setAttribute('style', 'color: blue');
|
||||
const paragraphContent = document.createTextNode(`${timestamp} sent >>> ${message}`);
|
||||
paragraph.appendChild(paragraphContent);
|
||||
sendDiv.appendChild(paragraph);
|
||||
document.getElementById('output')?.appendChild(sendDiv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display received text messages in the browser. Colour them green.
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
function displayReceived(message) {
|
||||
const content = message;
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const receivedDiv = document.createElement('div');
|
||||
const paragraph = document.createElement('p');
|
||||
paragraph.setAttribute('style', 'color: green');
|
||||
const paragraphContent = document.createTextNode(`${timestamp} received >>> ${content}`);
|
||||
paragraph.appendChild(paragraphContent);
|
||||
receivedDiv.appendChild(paragraph);
|
||||
document.getElementById('output')?.appendChild(receivedDiv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the nymClient's sender address in the user interface
|
||||
*
|
||||
* @param {Client} nymClient
|
||||
*/
|
||||
function displaySenderAddress(address) {
|
||||
document.getElementById('sender').value = address;
|
||||
}
|
||||
|
||||
function displayMessageLog(messageLog) {
|
||||
for (let i = 0; i < messageLog.length; i++) {
|
||||
if (messageLog[i].type === 'sent') {
|
||||
displaySend(messageLog[i].message);
|
||||
} else if (messageLog[i].type === 'received') {
|
||||
displayReceived(messageLog[i].message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { sendMessageTo, displaySend, displayReceived, displaySenderAddress, displayMessageLog };
|
||||
@@ -1,40 +0,0 @@
|
||||
// Import the DOM utility functionality.
|
||||
import { displaySenderAddress, displayReceived, sendMessageTo, displayMessageLog } from './dom-utils.js';
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const sendButton = document.querySelector('#send-button');
|
||||
if (sendButton) {
|
||||
sendButton.onclick = function () {
|
||||
sendMessageTo();
|
||||
};
|
||||
}
|
||||
const recipient = document.getElementById('recipient');
|
||||
recipient.onchange = () => {
|
||||
browser.runtime.sendMessage({
|
||||
type: 'updateRecipient',
|
||||
message: recipient.value,
|
||||
});
|
||||
};
|
||||
browser.runtime.onMessage.addListener((data) => {
|
||||
switch (data.type) {
|
||||
case 'displaySenderAddress':
|
||||
displaySenderAddress(data.message);
|
||||
break;
|
||||
case 'displayReceived':
|
||||
displayReceived(data.message);
|
||||
break;
|
||||
case 'sendMessageTo':
|
||||
sendMessageTo(data.message);
|
||||
break;
|
||||
case 'displayMessageLog':
|
||||
displayMessageLog(data.message);
|
||||
break;
|
||||
case 'updateRecipient':
|
||||
recipient.value = data.message;
|
||||
}
|
||||
});
|
||||
browser.runtime.sendMessage({
|
||||
type: 'popupReady',
|
||||
message: '',
|
||||
});
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
// Webpack configuration for the Firefox extension example
|
||||
|
||||
const path = require('path');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: {
|
||||
background: './src/background.js',
|
||||
popup: './src/popup.js',
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
'manifest.json',
|
||||
'popup.html',
|
||||
{ from: path.resolve(__dirname, '../../../../assets/favicon/favicon.png'), to: 'icon.png' },
|
||||
],
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /web-worker.*\.js$/,
|
||||
loader: 'worker-loader',
|
||||
options: {
|
||||
filename: '[name].js',
|
||||
inline: 'fallback',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nym Node Tester Demo</title>
|
||||
<title>Nym WebAssembly Demo</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["@babel/env", "@babel/react"]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nym Node Tester Demo</title>
|
||||
<title>Nym WebAssembly Demo</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# Nym Node Tester - React
|
||||
|
||||
This is an example of using the Nym Mixnet node tester.
|
||||
|
||||
You can use this example as a seed for a new project.
|
||||
|
||||
## Running the example
|
||||
|
||||
Try out the node tester app by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="shortcut icon" href="../../../../../assets/favicon/favicon.png" />
|
||||
<title>Nym Node Tester Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="src/index.tsx" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "@nymproject/sdk-example-node-tester-react",
|
||||
"description": "An example project that uses WASM node tester and React",
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/icons-material": "^5.14.0",
|
||||
"@mui/material": "^5.14.0",
|
||||
"@nymproject/sdk": "1",
|
||||
"parcel": "^2.9.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "parcel index.html"
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardActions,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CircularProgress,
|
||||
Grid,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { NodeTestResultResponse } from '@nymproject/sdk';
|
||||
import { ScoreIndicator } from 'src/components/ScoreIndicator';
|
||||
import { useNodeTesterClient } from 'src/hooks/useNodeTesterClient';
|
||||
import { BasicPageLayout } from 'src/layouts';
|
||||
import { TestStatusLabel } from 'src/components/TestStatusLabel';
|
||||
import Icon from '../../../../../../assets/appicon/appicon.png';
|
||||
|
||||
export const App = () => {
|
||||
const { testState, error, testNode, disconnectFromGateway, reconnectToGateway } = useNodeTesterClient();
|
||||
const [mixnodeIdentity, setMixnodeIdentity] = useState<string>('');
|
||||
const [results, setResults] = React.useState<NodeTestResultResponse>();
|
||||
|
||||
console.log({ testState, error, testNode });
|
||||
|
||||
const handleTestNode = async () => {
|
||||
setResults(undefined);
|
||||
try {
|
||||
const result = await testNode(mixnodeIdentity);
|
||||
setResults(result);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BasicPageLayout>
|
||||
<Card variant="outlined" sx={{ mt: 15, p: 4 }}>
|
||||
<CardHeader
|
||||
title={<Typography variant="h6">Nym Mixnode Testnet Node Tester</Typography>}
|
||||
action={<TestStatusLabel state={testState} />}
|
||||
avatar={<img src={Icon} width={40} />}
|
||||
/>
|
||||
<CardContent sx={{ mb: 2 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<ScoreIndicator score={results?.score || 0} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<List>
|
||||
<ListItem>
|
||||
<ListItemText primary="Packets sent" secondary={results?.sentPackets.toString() || '-'} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText primary="Packets received" secondary={results?.receivedPackets.toString() || '-'} />
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Duplicate packets received"
|
||||
secondary={results?.duplicatePackets.toString() || '-'}
|
||||
/>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
label="Enter a Mixnode Identity to test"
|
||||
value={mixnodeIdentity}
|
||||
onChange={(e) => {
|
||||
setMixnodeIdentity(e.target.value);
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Button
|
||||
disabled={!disconnectFromGateway || testState === 'Disconnected' || testState === 'Testing'}
|
||||
onClick={disconnectFromGateway}
|
||||
variant="outlined"
|
||||
disableElevation
|
||||
size="large"
|
||||
fullWidth
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Button
|
||||
disabled={!reconnectToGateway || testState === 'Ready' || testState === 'Testing'}
|
||||
onClick={reconnectToGateway}
|
||||
variant="outlined"
|
||||
disableElevation
|
||||
size="large"
|
||||
fullWidth
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
Reconnect
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<Button
|
||||
disabled={!testNode || !mixnodeIdentity || testState === 'Testing' || testState === 'Disconnected'}
|
||||
onClick={handleTestNode}
|
||||
variant="contained"
|
||||
disableElevation
|
||||
fullWidth
|
||||
size="large"
|
||||
endIcon={testState === 'Testing' && <CircularProgress size={25} />}
|
||||
>
|
||||
Start test
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</BasicPageLayout>
|
||||
);
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, CircularProgressProps, Stack, Typography } from '@mui/material';
|
||||
|
||||
const getPerformanceDescriptionAndColor = (score: number) => {
|
||||
const res: { description: string; color: CircularProgressProps['color'] } = { description: '', color: 'warning' };
|
||||
|
||||
if (score >= 90) {
|
||||
res.description = 'Reliable node';
|
||||
res.color = 'success';
|
||||
}
|
||||
|
||||
if (score >= 75 && score < 90) {
|
||||
res.description = 'Average node';
|
||||
res.color = 'warning';
|
||||
}
|
||||
|
||||
if (score > 0 && score < 75) {
|
||||
res.description = 'Unreliable node';
|
||||
res.color = 'error';
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
export const ScoreIndicator = ({ score }) => {
|
||||
const { color } = getPerformanceDescriptionAndColor(score);
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
width: 250,
|
||||
height: 250,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
mx: 'auto',
|
||||
mt: 4,
|
||||
}}
|
||||
>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={100}
|
||||
size={250}
|
||||
sx={{ position: 'absolute', top: 0, left: 0, color: 'grey.200' }}
|
||||
/>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={score}
|
||||
size={250}
|
||||
sx={{ position: 'absolute', top: 0, left: 0 }}
|
||||
color={color}
|
||||
/>
|
||||
<Stack alignItems="center" gap={1}>
|
||||
<Typography fontWeight="bold" variant="h4">
|
||||
{Math.round(score)}%
|
||||
</Typography>
|
||||
<Typography>Performance Score</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Chip } from '@mui/material';
|
||||
import { HourglassTop, ErrorOutline, CheckCircleOutline, WarningAmber } from '@mui/icons-material';
|
||||
import { TestState } from 'src/hooks/useNodeTesterClient';
|
||||
|
||||
const getColor = (state: TestState) => {
|
||||
switch (state) {
|
||||
case 'Connecting':
|
||||
return 'warning';
|
||||
case 'Error':
|
||||
return 'error';
|
||||
case 'Ready':
|
||||
return 'success';
|
||||
default:
|
||||
return 'warning';
|
||||
}
|
||||
};
|
||||
|
||||
const getIcon = (state: TestState) => {
|
||||
switch (state) {
|
||||
case 'Connecting':
|
||||
return <HourglassTop />;
|
||||
case 'Error':
|
||||
return <ErrorOutline />;
|
||||
case 'Ready':
|
||||
return <CheckCircleOutline />;
|
||||
default:
|
||||
return <WarningAmber />;
|
||||
}
|
||||
};
|
||||
|
||||
export const TestStatusLabel = ({ state }: { state: TestState }) => (
|
||||
<Chip label={state} color={getColor(state)} icon={getIcon(state)} sx={{ color: 'white' }} />
|
||||
);
|
||||
@@ -1,71 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { createNodeTesterClient, NodeTester } from '@nymproject/sdk';
|
||||
|
||||
export type TestState = 'Ready' | 'Connecting' | 'Disconnected' | 'Disconnecting' | 'Error' | 'Testing' | 'Stopped';
|
||||
|
||||
export const useNodeTesterClient = () => {
|
||||
const [client, setClient] = useState<NodeTester>();
|
||||
const [error, setError] = useState<string>();
|
||||
const [testState, setTestState] = useState<TestState>('Disconnected');
|
||||
|
||||
const createClient = async () => {
|
||||
setTestState('Connecting');
|
||||
try {
|
||||
const validator = 'https://validator.nymtech.net/api';
|
||||
const nodeTesterClient = await createNodeTesterClient();
|
||||
|
||||
await nodeTesterClient.tester.init(validator);
|
||||
setClient(nodeTesterClient);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setError('Failed to load node tester client, please try again');
|
||||
} finally {
|
||||
setTestState('Ready');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
createClient();
|
||||
}, []);
|
||||
|
||||
const testNode = !client
|
||||
? undefined
|
||||
: async (mixnodeIdentity: string) => {
|
||||
try {
|
||||
setTestState('Testing');
|
||||
const result = await client.tester.startTest(mixnodeIdentity);
|
||||
setTestState('Ready');
|
||||
return result;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setError('Failed to test node, please try again');
|
||||
setTestState('Error');
|
||||
}
|
||||
};
|
||||
|
||||
const disconnectFromGateway = !client
|
||||
? undefined
|
||||
: async () => {
|
||||
setTestState('Disconnecting');
|
||||
await client.tester.disconnectFromGateway();
|
||||
setTestState('Disconnected');
|
||||
};
|
||||
|
||||
const reconnectToGateway = !client
|
||||
? undefined
|
||||
: async () => {
|
||||
setTestState('Connecting');
|
||||
await client.tester.reconnectToGateway();
|
||||
setTestState('Ready');
|
||||
};
|
||||
|
||||
const terminateWorker = !client
|
||||
? undefined
|
||||
: async () => {
|
||||
setTestState('Disconnecting');
|
||||
await client.terminate();
|
||||
setTestState('Disconnected');
|
||||
};
|
||||
|
||||
return { testNode, disconnectFromGateway, reconnectToGateway, terminateWorker, testState, error };
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { App } from './App';
|
||||
import { NymThemeProvider } from './theme/theme';
|
||||
|
||||
const rootDOMElem = document.getElementById('root');
|
||||
if (!rootDOMElem) throw new Error('Root element not found');
|
||||
|
||||
const root = createRoot(rootDOMElem);
|
||||
root.render(
|
||||
<NymThemeProvider>
|
||||
<App />
|
||||
</NymThemeProvider>,
|
||||
);
|
||||
@@ -1,6 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Container } from '@mui/material';
|
||||
|
||||
export const BasicPageLayout = ({ children }: { children: React.ReactNode }) => (
|
||||
<Container maxWidth="md">{children}</Container>
|
||||
);
|
||||
@@ -1,28 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
import { CssBaseline } from '@mui/material';
|
||||
|
||||
export const NymThemeProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: {
|
||||
main: '#FB6E4E',
|
||||
},
|
||||
success: {
|
||||
main: '#21D073',
|
||||
},
|
||||
background: {
|
||||
default: '#1D2125',
|
||||
paper: '#292E34',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
declare module '*.png' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"react": ["node_modules/react"],
|
||||
"react-dom": ["node_modules/react-dom"]
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"paths": {
|
||||
"@assets/*": ["../../../../../../assets"]
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"presets": ["@babel/env"]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import preset from 'ts-jest/presets/index.js'
|
||||
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
export default {
|
||||
...preset.defaults,
|
||||
transform: {
|
||||
'^.+\\.(ts|tsx)$': [
|
||||
'ts-jest',
|
||||
{
|
||||
tsconfig: 'tsconfig.jest.json',
|
||||
useESM: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -27,22 +27,16 @@
|
||||
"prebuild:dev": "yarn build:dependencies",
|
||||
"build:dev": "yarn build:dev:only-this",
|
||||
"build:dev:only-this": "scripts/build.sh",
|
||||
"build:local": "run-s build:dependencies:nym-client-wasm build:dev:only-this",
|
||||
"test": "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js -c=jest.config.mjs --no-cache"
|
||||
"build:local": "run-s build:dependencies:nym-client-wasm build:dev:only-this"
|
||||
},
|
||||
"dependencies": {
|
||||
"@npmcli/node-gyp": "^3.0.0",
|
||||
"@nymproject/nym-client-wasm": "1.0.0",
|
||||
"@nymproject/nym-client-wasm": "1",
|
||||
"comlink": "^4.3.1",
|
||||
"lerna": "^6.6.2",
|
||||
"node-gyp": "^9.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.14.5",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@rollup/plugin-commonjs": "^24.0.1",
|
||||
"@rollup/plugin-inject": "^5.0.3",
|
||||
@@ -54,8 +48,6 @@
|
||||
"@rollup/plugin-wasm": "^6.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.13.0",
|
||||
"@typescript-eslint/parser": "^5.13.0",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/node": "^16.7.13",
|
||||
"eslint": "^8.10.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^16.1.0",
|
||||
@@ -71,9 +63,6 @@
|
||||
"rollup": "^3.9.1",
|
||||
"rollup-plugin-base64": "^1.0.1",
|
||||
"rollup-plugin-web-worker-loader": "^1.6.1",
|
||||
"typescript": "^4.8.4",
|
||||
"jest": "^29.5.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-loader": "^9.4.2"
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// This is the rollup config for the full-fat SDK package.
|
||||
// The config is similar to the esm config, but exports web workers as separate files.
|
||||
// This can be necessary for implentations that do not support inline web workers.
|
||||
|
||||
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'];
|
||||
|
||||
export default {
|
||||
input: 'src/index.ts',
|
||||
output: {
|
||||
dir: 'dist/full-fat',
|
||||
format: 'es',
|
||||
},
|
||||
plugins: [
|
||||
webWorkerLoader({
|
||||
targetPlatform: 'browser',
|
||||
inline: false,
|
||||
}),
|
||||
resolve({ extensions }),
|
||||
typescript({
|
||||
exclude: ['mixnet/wasm/worker.ts', 'mixnet/node-tester/worker.ts'],
|
||||
compilerOptions: { outDir: 'dist/full-fat' },
|
||||
}),
|
||||
],
|
||||
};
|
||||
@@ -48,13 +48,6 @@ rollup -c rollup-esm.config.mjs
|
||||
# build the SDK as a CommonJS bundle
|
||||
rollup -c rollup-cjs.config.mjs
|
||||
|
||||
#-------------------------------------------------------
|
||||
# FULL FAT
|
||||
#-------------------------------------------------------
|
||||
|
||||
# build the SDK as a ESM bundle
|
||||
rollup -c rollup-full-fat.config.mjs
|
||||
|
||||
#-------------------------------------------------------
|
||||
# CLEAN UP
|
||||
#-------------------------------------------------------
|
||||
|
||||
@@ -3,16 +3,16 @@ import InlineWasmWebWorker from 'web-worker:./worker';
|
||||
import {
|
||||
BinaryMessageReceivedEvent,
|
||||
ConnectedEvent,
|
||||
EventHandlerFn,
|
||||
EventKinds,
|
||||
IWebWorker,
|
||||
IWebWorkerAsync,
|
||||
IWebWorkerEvents,
|
||||
LoadedEvent,
|
||||
MimeTypes,
|
||||
RawMessageReceivedEvent,
|
||||
StringMessageReceivedEvent,
|
||||
RawMessageReceivedEvent,
|
||||
} from './types';
|
||||
import { createSubscriptions } from './subscriptions';
|
||||
|
||||
/**
|
||||
* Client for the Nym mixnet.
|
||||
@@ -33,13 +33,25 @@ export const createNymMixnetClient = async (options?: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
const worker = await createWorker();
|
||||
|
||||
const subscriptions = createSubscriptions();
|
||||
const { getSubscriptions, addSubscription } = subscriptions;
|
||||
// stores the subscriptions for events
|
||||
const subscriptions: {
|
||||
[key: string]: Array<EventHandlerFn<unknown>>;
|
||||
} = {};
|
||||
|
||||
/**
|
||||
* Helper method to get typed subscriptions
|
||||
*/
|
||||
const getSubscriptions = <E>(key: EventKinds): Array<EventHandlerFn<E>> => {
|
||||
if (!subscriptions[key]) {
|
||||
subscriptions[key] = [];
|
||||
}
|
||||
return subscriptions[key] as Array<EventHandlerFn<E>>;
|
||||
};
|
||||
|
||||
// listen to messages from the worker, parse them and let the subscribers handle them, catching any unhandled exceptions
|
||||
worker.addEventListener('message', (msg) => {
|
||||
if (msg.data && msg.data.kind) {
|
||||
const subscribers = getSubscriptions(msg.data.kind);
|
||||
const subscribers = subscriptions[msg.data.kind];
|
||||
(subscribers || []).forEach((s) => {
|
||||
try {
|
||||
// let the subscriber handle the message
|
||||
@@ -54,14 +66,36 @@ export const createNymMixnetClient = async (options?: {
|
||||
|
||||
// manage the subscribers, returning self-unsubscribe methods
|
||||
const events: IWebWorkerEvents = {
|
||||
subscribeToConnected: (handler) => addSubscription<ConnectedEvent>(EventKinds.Connected, handler),
|
||||
subscribeToLoaded: (handler) => addSubscription<LoadedEvent>(EventKinds.Loaded, handler),
|
||||
subscribeToTextMessageReceivedEvent: (handler) =>
|
||||
addSubscription<StringMessageReceivedEvent>(EventKinds.StringMessageReceived, handler),
|
||||
subscribeToBinaryMessageReceivedEvent: (handler) =>
|
||||
addSubscription<BinaryMessageReceivedEvent>(EventKinds.BinaryMessageReceived, handler),
|
||||
subscribeToRawMessageReceivedEvent: (handler) =>
|
||||
addSubscription<RawMessageReceivedEvent>(EventKinds.RawMessageReceived, handler),
|
||||
subscribeToConnected: (handler) => {
|
||||
getSubscriptions<ConnectedEvent>(EventKinds.Connected).push(handler);
|
||||
return () => {
|
||||
getSubscriptions<ConnectedEvent>(EventKinds.Connected).unshift(handler);
|
||||
};
|
||||
},
|
||||
subscribeToLoaded: (handler) => {
|
||||
getSubscriptions<LoadedEvent>(EventKinds.Loaded).push(handler);
|
||||
return () => {
|
||||
getSubscriptions<LoadedEvent>(EventKinds.Loaded).unshift(handler);
|
||||
};
|
||||
},
|
||||
subscribeToTextMessageReceivedEvent: (handler) => {
|
||||
getSubscriptions<StringMessageReceivedEvent>(EventKinds.StringMessageReceived).push(handler);
|
||||
return () => {
|
||||
getSubscriptions<StringMessageReceivedEvent>(EventKinds.StringMessageReceived).unshift(handler);
|
||||
};
|
||||
},
|
||||
subscribeToBinaryMessageReceivedEvent: (handler) => {
|
||||
getSubscriptions<BinaryMessageReceivedEvent>(EventKinds.BinaryMessageReceived).push(handler);
|
||||
return () => {
|
||||
getSubscriptions<BinaryMessageReceivedEvent>(EventKinds.BinaryMessageReceived).unshift(handler);
|
||||
};
|
||||
},
|
||||
subscribeToRawMessageReceivedEvent: (handler) => {
|
||||
getSubscriptions<RawMessageReceivedEvent>(EventKinds.RawMessageReceived).push(handler);
|
||||
return () => {
|
||||
getSubscriptions<RawMessageReceivedEvent>(EventKinds.RawMessageReceived).unshift(handler);
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// let comlink handle interop with the web worker
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { createSubscriptions } from './subscriptions';
|
||||
import { EventKinds, MimeTypes, StringMessageReceivedEvent } from './types';
|
||||
|
||||
describe('wasm subscription manager', () => {
|
||||
test('works with default values', () => {
|
||||
const { getSubscriptions, fireEvent, addSubscription } = createSubscriptions();
|
||||
|
||||
expect(getSubscriptions(EventKinds.StringMessageReceived)).toHaveLength(0);
|
||||
|
||||
// the event should fire and not fail
|
||||
fireEvent(EventKinds.StringMessageReceived, {});
|
||||
|
||||
// mock a handler, fire events and check that it was called
|
||||
const mockHandler = jest.fn();
|
||||
addSubscription(EventKinds.StringMessageReceived, mockHandler);
|
||||
fireEvent(EventKinds.StringMessageReceived, {});
|
||||
expect(mockHandler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('adding and removing subscriptions works as expected', () => {
|
||||
const { addSubscription, getSubscriptions, fireEvent } = createSubscriptions();
|
||||
|
||||
expect(getSubscriptions(EventKinds.StringMessageReceived)).toHaveLength(0);
|
||||
|
||||
const callStats: number[] = [0, 0, 0];
|
||||
|
||||
const showDebug = false;
|
||||
|
||||
const handler1 = (e: StringMessageReceivedEvent) => {
|
||||
if (showDebug) {
|
||||
console.log('handler1', e);
|
||||
}
|
||||
callStats[0] += 1;
|
||||
};
|
||||
const handler2 = (e: StringMessageReceivedEvent) => {
|
||||
if (showDebug) {
|
||||
console.log('handler2', e);
|
||||
}
|
||||
callStats[1] += 1;
|
||||
};
|
||||
const handler3 = (e: StringMessageReceivedEvent) => {
|
||||
if (showDebug) {
|
||||
console.log('handler3', e);
|
||||
}
|
||||
callStats[2] += 1;
|
||||
};
|
||||
|
||||
const unsubcribeFn1 = addSubscription(EventKinds.StringMessageReceived, handler1);
|
||||
const unsubcribeFn2 = addSubscription(EventKinds.StringMessageReceived, handler2);
|
||||
const unsubcribeFn3 = addSubscription(EventKinds.StringMessageReceived, handler3);
|
||||
|
||||
const event: StringMessageReceivedEvent = {
|
||||
kind: EventKinds.StringMessageReceived,
|
||||
args: {
|
||||
payload: 'Testing',
|
||||
mimeType: MimeTypes.TextPlain,
|
||||
payloadRaw: new Uint8Array(),
|
||||
},
|
||||
};
|
||||
|
||||
// fire and expect all handlers to get message
|
||||
fireEvent(EventKinds.StringMessageReceived, event);
|
||||
expect(callStats[0]).toBe(1);
|
||||
expect(callStats[1]).toBe(1);
|
||||
expect(callStats[2]).toBe(1);
|
||||
expect(getSubscriptions(EventKinds.StringMessageReceived)).toHaveLength(3);
|
||||
|
||||
// unscribe and fire again
|
||||
unsubcribeFn2();
|
||||
fireEvent(EventKinds.StringMessageReceived, event);
|
||||
expect(callStats[0]).toBe(2);
|
||||
expect(callStats[1]).toBe(1);
|
||||
expect(callStats[2]).toBe(2);
|
||||
expect(getSubscriptions(EventKinds.StringMessageReceived)).toHaveLength(2);
|
||||
|
||||
// unscribe and fire again
|
||||
unsubcribeFn3();
|
||||
fireEvent(EventKinds.StringMessageReceived, event);
|
||||
expect(callStats[0]).toBe(3);
|
||||
expect(callStats[1]).toBe(1);
|
||||
expect(callStats[2]).toBe(2);
|
||||
expect(getSubscriptions(EventKinds.StringMessageReceived)).toHaveLength(1);
|
||||
|
||||
// unscribe and fire again
|
||||
unsubcribeFn1();
|
||||
fireEvent(EventKinds.StringMessageReceived, event);
|
||||
expect(callStats[0]).toBe(3);
|
||||
expect(callStats[1]).toBe(1);
|
||||
expect(callStats[2]).toBe(2);
|
||||
expect(getSubscriptions(EventKinds.StringMessageReceived)).toHaveLength(0);
|
||||
|
||||
// nothing is subscribed, so fire again and check
|
||||
fireEvent(EventKinds.StringMessageReceived, event);
|
||||
expect(callStats[0]).toBe(3);
|
||||
expect(callStats[1]).toBe(1);
|
||||
expect(callStats[2]).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { EventHandlerFn } from './types';
|
||||
import { EventKinds } from './types';
|
||||
|
||||
type ISubscriptions = {
|
||||
[key: string]: Array<EventHandlerFn<unknown>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a subscription manager.
|
||||
*/
|
||||
export const createSubscriptions = () => {
|
||||
// stores the subscriptions for events
|
||||
const subscriptions: ISubscriptions = {};
|
||||
|
||||
/**
|
||||
* Helper method to get typed subscriptions.
|
||||
*/
|
||||
const getSubscriptions = <E>(key: EventKinds): Array<EventHandlerFn<E>> => {
|
||||
if (!subscriptions[key]) {
|
||||
subscriptions[key] = [];
|
||||
}
|
||||
return subscriptions[key] as Array<EventHandlerFn<E>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a subscription.
|
||||
*/
|
||||
const removeSubscription = <E>(key: EventKinds, handler: EventHandlerFn<E>) => {
|
||||
if (!subscriptions[key]) {
|
||||
subscriptions[key] = [];
|
||||
}
|
||||
const items: Array<EventHandlerFn<unknown>> = (subscriptions[key] as Array<EventHandlerFn<unknown>>).filter(
|
||||
(h) => h !== handler,
|
||||
);
|
||||
subscriptions[key] = items;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add typed subscription.
|
||||
*/
|
||||
const addSubscription = <E>(key: EventKinds, handler: EventHandlerFn<E>) => {
|
||||
getSubscriptions(key).push(handler as EventHandlerFn<unknown>);
|
||||
|
||||
return () => {
|
||||
removeSubscription(key, handler);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Fires an event.
|
||||
*/
|
||||
const fireEvent = <E>(key: EventKinds, event: E) => {
|
||||
getSubscriptions(key).forEach((handler) => {
|
||||
try {
|
||||
handler(event);
|
||||
} catch (e: any) {
|
||||
console.error(`Unhandled exception in handler for ${key}: `, e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
getSubscriptions,
|
||||
addSubscription,
|
||||
removeSubscription,
|
||||
fireEvent,
|
||||
subscriptions,
|
||||
};
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"es2021",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext",
|
||||
"webworker"
|
||||
],
|
||||
"module": "CommonJS",
|
||||
"target": "es5",
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"declaration": true,
|
||||
"baseUrl": ".",
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"jest.*",
|
||||
"webpack.config.js",
|
||||
"webpack.prod.js",
|
||||
"webpack.common.js",
|
||||
"node_modules",
|
||||
"**/node_modules",
|
||||
"dist",
|
||||
"**/dist",
|
||||
"scripts",
|
||||
"jest",
|
||||
"__tests__",
|
||||
"**/__tests__",
|
||||
"__jest__",
|
||||
"**/__jest__",
|
||||
"config/*"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user